From 2b23e50057fc92da30093fbebb78b320fc4107d0 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 29 Jun 2016 10:50:37 -0400 Subject: [PATCH 1/5] user: check if user is in wheel more efficiently We currently get all the groups a user belongs to in one pass, then check each one to see if it's wheel. It's much more efficient to just get the wheel group and check if any of its members are the user. https://bugs.freedesktop.org/show_bug.cgi?id=48177 --- src/user.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/user.c b/src/user.c index de30090..52f57d0 100644 --- a/src/user.c +++ b/src/user.c @@ -92,88 +92,79 @@ struct User { gchar *home_dir; gchar *shell; gchar *email; gchar *language; gchar *x_session; gchar *location; guint64 login_frequency; gint64 login_time; GVariant *login_history; gchar *icon_file; gchar *default_icon_file; gboolean locked; gboolean automatic_login; gboolean system_account; gboolean local_account; }; typedef struct UserClass { AccountsUserSkeletonClass parent_class; } UserClass; static void user_accounts_user_iface_init (AccountsUserIface *iface); G_DEFINE_TYPE_WITH_CODE (User, user, ACCOUNTS_TYPE_USER_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_USER, user_accounts_user_iface_init)); static gint account_type_from_pwent (struct passwd *pwent) { struct group *grp; - gid_t wheel; - gid_t *groups; - gint ngroups; gint i; if (pwent->pw_uid == 0) { g_debug ("user is root so account type is administrator"); return ACCOUNT_TYPE_ADMINISTRATOR; } grp = getgrnam (ADMIN_GROUP); if (grp == NULL) { g_debug (ADMIN_GROUP " group not found"); return ACCOUNT_TYPE_STANDARD; } - wheel = grp->gr_gid; - ngroups = get_user_groups (pwent->pw_name, pwent->pw_gid, &groups); - - for (i = 0; i < ngroups; i++) { - if (groups[i] == wheel) { - g_free (groups); + for (i = 0; grp->gr_mem[i] != NULL; i++) { + if (g_strcmp0 (grp->gr_mem[i], pwent->pw_name) == 0) { return ACCOUNT_TYPE_ADMINISTRATOR; } } - g_free (groups); - return ACCOUNT_TYPE_STANDARD; } void user_update_from_pwent (User *user, struct passwd *pwent) { #ifdef HAVE_SHADOW_H struct spwd *spent; #endif gchar *real_name; gboolean changed; const gchar *passwd; gboolean locked; PasswordMode mode; AccountType account_type; g_object_freeze_notify (G_OBJECT (user)); changed = FALSE; if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0') { gchar *first_comma = NULL; gchar *valid_utf8_name = NULL; if (g_utf8_validate (pwent->pw_gecos, -1, NULL)) { valid_utf8_name = pwent->pw_gecos; first_comma = g_utf8_strchr (valid_utf8_name, -1, ','); } else { -- 2.7.4 From c2b87f89a85ffa5465a523aa291ea0018a050cc5 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Tue, 28 Jun 2016 15:43:08 -0400 Subject: [PATCH 2/5] daemon: get local users from /etc/shadow not /etc/passwd For some sites, it's common practice to rsync around large /etc/passwd files containing the password entries for remote users. That means accountsservices' "assume /etc/passwd is local users" heuristic falls over. This commit changes it to only treat users in /etc/shadow as local. https://bugs.freedesktop.org/show_bug.cgi?id=48177 --- src/daemon.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/daemon.c b/src/daemon.c index 38f6a47..5c269af 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -2,60 +2,63 @@ * * Copyright (C) 2009-2010 Red Hat, Inc. * Copyright (c) 2013 Canonical Limited * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Written by: Matthias Clasen */ #include "config.h" #include #include #include #include #include #include #include +#ifdef HAVE_SHADOW_H +#include +#endif #include #include #include #ifdef HAVE_UTMPX_H #include #endif #include #include #include #include #include #include #include "user-classify.h" #include "daemon.h" #include "util.h" #define PATH_PASSWD "/etc/passwd" #define PATH_SHADOW "/etc/shadow" #define PATH_GROUP "/etc/group" #define PATH_GDM_CUSTOM "/etc/gdm/custom.conf" #ifdef HAVE_UTMPX_H #define PATH_WTMP _PATH_WTMPX #endif enum { PROP_0, PROP_DAEMON_VERSION }; @@ -279,81 +282,125 @@ entry_generator_wtmp (GHashTable *users, builder = g_variant_builder_new (G_VARIANT_TYPE ("a(xxa{sv})")); for (l = g_list_last (accounting->previous_logins); l != NULL; l = l->prev) { previous_login = l->data; builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (builder2, "{sv}", "type", g_variant_new_string (previous_login->id)); g_variant_builder_add (builder, "(xxa{sv})", previous_login->login_time, previous_login->logout_time, builder2); g_variant_builder_unref (builder2); } g_object_set (user, "login-history", g_variant_new ("a(xxa{sv})", builder), NULL); g_variant_builder_unref (builder); g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free); user_changed (user); } g_hash_table_unref (login_hash); g_hash_table_unref (logout_hash); g_free (state_data); *state = NULL; return NULL; } #endif /* HAVE_UTMPX_H */ static struct passwd * entry_generator_fgetpwent (GHashTable *users, gpointer *state) { struct passwd *pwent; - FILE *fp; + struct { + FILE *fp; + GHashTable *users; + } *generator_state; /* First iteration */ if (*state == NULL) { - *state = fp = fopen (PATH_PASSWD, "r"); + GHashTable *shadow_users = NULL; + FILE *fp; +#ifdef HAVE_SHADOW_H + struct spwd *shadow_entry; + + fp = fopen (PATH_SHADOW, "r"); + if (fp == NULL) { + g_warning ("Unable to open %s: %s", PATH_SHADOW, g_strerror (errno)); + return NULL; + } + + shadow_users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + do { + shadow_entry = fgetspent (fp); + if (shadow_entry != NULL) { + g_hash_table_add (shadow_users, g_strdup (shadow_entry->sp_namp)); + } else if (errno != EINTR) { + break; + } + } while (shadow_entry != NULL); + + fclose (fp); + + if (g_hash_table_size (shadow_users) == 0) { + g_clear_pointer (&shadow_users, g_hash_table_unref); + return NULL; + } +#endif + + fp = fopen (PATH_PASSWD, "r"); if (fp == NULL) { + g_clear_pointer (&shadow_users, g_hash_table_unref); g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno)); return NULL; } + + generator_state = g_malloc0 (sizeof (*generator_state)); + generator_state->fp = fp; + generator_state->users = shadow_users; + + *state = generator_state; } /* Every iteration */ - fp = *state; - pwent = fgetpwent (fp); + generator_state = *state; + pwent = fgetpwent (generator_state->fp); if (pwent != NULL) { - return pwent; + if (!generator_state->users || g_hash_table_lookup (generator_state->users, pwent->pw_name)) + return pwent; } /* Last iteration */ - fclose (fp); + fclose (generator_state->fp); + g_hash_table_unref (generator_state->users); + g_free (generator_state); *state = NULL; + return NULL; } static struct passwd * entry_generator_cachedir (GHashTable *users, gpointer *state) { struct passwd *pwent; const gchar *name; GError *error = NULL; gchar *filename; gboolean regular; GHashTableIter iter; GKeyFile *key_file; User *user; GDir *dir; /* First iteration */ if (*state == NULL) { *state = g_dir_open (USERDIR, 0, &error); if (error != NULL) { if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) g_warning ("couldn't list user cache directory: %s", USERDIR); g_error_free (error); return NULL; } } /* Every iteration */ -- 2.7.4 From 011ef555b0db601186a38c43f9359589ed61e230 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 29 Jun 2016 15:57:38 -0400 Subject: [PATCH 3/5] daemon: don't call getspnam for local users We're already iterating over the whole shadow file, so just cache the entries instead of calling getspname a few lines later. https://bugs.freedesktop.org/show_bug.cgi?id=48177 --- src/daemon.c | 86 +++++++++++++++++++++++++++++++++++++++++------------------- src/user.c | 11 ++------ src/user.h | 4 ++- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/daemon.c b/src/daemon.c index 5c269af..71a3ea4 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -58,61 +58,61 @@ #define PATH_WTMP _PATH_WTMPX #endif enum { PROP_0, PROP_DAEMON_VERSION }; struct DaemonPrivate { GDBusConnection *bus_connection; GDBusProxy *bus_proxy; GHashTable *users; User *autologin; GFileMonitor *passwd_monitor; GFileMonitor *shadow_monitor; GFileMonitor *group_monitor; GFileMonitor *gdm_monitor; #ifdef HAVE_UTMPX_H GFileMonitor *wtmp_monitor; #endif guint reload_id; guint autologin_id; PolkitAuthority *authority; }; -typedef struct passwd * (* EntryGeneratorFunc) (GHashTable *, gpointer *); +typedef struct passwd * (* EntryGeneratorFunc) (GHashTable *, gpointer *, struct spwd **shadow_entry); static void daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface); G_DEFINE_TYPE_WITH_CODE (Daemon, daemon, ACCOUNTS_TYPE_ACCOUNTS_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_ACCOUNTS, daemon_accounts_accounts_iface_init)); #define DAEMON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_DAEMON, DaemonPrivate)) static const GDBusErrorEntry accounts_error_entries[] = { { ERROR_FAILED, "org.freedesktop.Accounts.Error.Failed" }, { ERROR_USER_EXISTS, "org.freedesktop.Accounts.Error.UserExists" }, { ERROR_USER_DOES_NOT_EXIST, "org.freedesktop.Accounts.Error.UserDoesNotExist" }, { ERROR_PERMISSION_DENIED, "org.freedesktop.Accounts.Error.PermissionDenied" }, { ERROR_NOT_SUPPORTED, "org.freedesktop.Accounts.Error.NotSupported" } }; GQuark error_quark (void) { static volatile gsize quark_volatile = 0; g_dbus_error_register_error_domain ("accounts_error", &quark_volatile, accounts_error_entries, G_N_ELEMENTS (accounts_error_entries)); return (GQuark) quark_volatile; } #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } @@ -138,62 +138,63 @@ error_get_type (void) return etype; } #ifdef HAVE_UTMPX_H typedef struct { guint64 frequency; gint64 time; GList *previous_logins; } UserAccounting; typedef struct { gchar *id; gint64 login_time; gint64 logout_time; } UserPreviousLogin; typedef struct { GHashTable *login_hash; GHashTable *logout_hash; } WTmpGeneratorState; static void user_previous_login_free (UserPreviousLogin *previous_login) { g_free (previous_login->id); g_free (previous_login); } static struct passwd * -entry_generator_wtmp (GHashTable *users, - gpointer *state) +entry_generator_wtmp (GHashTable *users, + gpointer *state, + struct spwd **spent) { GHashTable *login_hash, *logout_hash; struct utmpx *wtmp_entry; GHashTableIter iter; gpointer key, value; struct passwd *pwent; User *user; WTmpGeneratorState *state_data; GVariantBuilder *builder, *builder2; GList *l; if (*state == NULL) { /* First iteration */ #ifdef UTXDB_LOG if (setutxdb (UTXDB_LOG, NULL) != 0) { return NULL; } #else utmpxname (PATH_WTMP); setutxent (); #endif *state = g_new (WTmpGeneratorState, 1); state_data = *state; state_data->login_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); state_data->logout_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); } /* Every iteration */ state_data = *state; login_hash = state_data->login_hash; @@ -232,284 +233,308 @@ entry_generator_wtmp (GHashTable *users, } pwent = getpwnam (wtmp_entry->ut_user); if (pwent == NULL) { continue; } if (!g_hash_table_lookup_extended (login_hash, wtmp_entry->ut_user, &key, &value)) { accounting = g_new (UserAccounting, 1); accounting->frequency = 0; accounting->previous_logins = NULL; g_hash_table_insert (login_hash, g_strdup (wtmp_entry->ut_user), accounting); } else { accounting = value; } accounting->frequency++; accounting->time = wtmp_entry->ut_tv.tv_sec; /* Add zero logout time to change it later on logout record */ previous_login = g_new (UserPreviousLogin, 1); previous_login->id = g_strdup (wtmp_entry->ut_line); previous_login->login_time = wtmp_entry->ut_tv.tv_sec; previous_login->logout_time = 0; accounting->previous_logins = g_list_prepend (accounting->previous_logins, previous_login); g_hash_table_insert (logout_hash, g_strdup (wtmp_entry->ut_line), previous_login); + *spent = getspnam (pwent->pw_name); return pwent; } /* Last iteration */ endutxent (); g_hash_table_iter_init (&iter, login_hash); while (g_hash_table_iter_next (&iter, &key, &value)) { UserAccounting *accounting = (UserAccounting *) value; UserPreviousLogin *previous_login; user = g_hash_table_lookup (users, key); if (user == NULL) { g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free); continue; } g_object_set (user, "login-frequency", accounting->frequency, NULL); g_object_set (user, "login-time", accounting->time, NULL); builder = g_variant_builder_new (G_VARIANT_TYPE ("a(xxa{sv})")); for (l = g_list_last (accounting->previous_logins); l != NULL; l = l->prev) { previous_login = l->data; builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (builder2, "{sv}", "type", g_variant_new_string (previous_login->id)); g_variant_builder_add (builder, "(xxa{sv})", previous_login->login_time, previous_login->logout_time, builder2); g_variant_builder_unref (builder2); } g_object_set (user, "login-history", g_variant_new ("a(xxa{sv})", builder), NULL); g_variant_builder_unref (builder); g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free); user_changed (user); } g_hash_table_unref (login_hash); g_hash_table_unref (logout_hash); g_free (state_data); *state = NULL; return NULL; } #endif /* HAVE_UTMPX_H */ static struct passwd * -entry_generator_fgetpwent (GHashTable *users, - gpointer *state) +entry_generator_fgetpwent (GHashTable *users, + gpointer *state, + struct spwd **spent) { struct passwd *pwent; + + struct { + struct spwd spbuf; + char buf[1024]; + } *shadow_entry_buffers; + struct { FILE *fp; GHashTable *users; } *generator_state; /* First iteration */ if (*state == NULL) { GHashTable *shadow_users = NULL; FILE *fp; -#ifdef HAVE_SHADOW_H struct spwd *shadow_entry; fp = fopen (PATH_SHADOW, "r"); if (fp == NULL) { g_warning ("Unable to open %s: %s", PATH_SHADOW, g_strerror (errno)); return NULL; } - shadow_users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + shadow_users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); do { - shadow_entry = fgetspent (fp); - if (shadow_entry != NULL) { - g_hash_table_add (shadow_users, g_strdup (shadow_entry->sp_namp)); - } else if (errno != EINTR) { - break; + int ret = 0; + + shadow_entry_buffers = g_malloc0 (sizeof (*shadow_entry_buffers)); + + ret = fgetspent_r (fp, &shadow_entry_buffers->spbuf, shadow_entry_buffers->buf, sizeof (shadow_entry_buffers->buf), &shadow_entry); + if (ret == 0) { + g_hash_table_insert (shadow_users, g_strdup (shadow_entry->sp_namp), shadow_entry_buffers); + } else { + g_free (shadow_entry_buffers); + + if (errno != EINTR) { + break; + } } } while (shadow_entry != NULL); fclose (fp); if (g_hash_table_size (shadow_users) == 0) { g_clear_pointer (&shadow_users, g_hash_table_unref); return NULL; } -#endif fp = fopen (PATH_PASSWD, "r"); if (fp == NULL) { g_clear_pointer (&shadow_users, g_hash_table_unref); g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno)); return NULL; } generator_state = g_malloc0 (sizeof (*generator_state)); generator_state->fp = fp; generator_state->users = shadow_users; *state = generator_state; } /* Every iteration */ generator_state = *state; pwent = fgetpwent (generator_state->fp); if (pwent != NULL) { - if (!generator_state->users || g_hash_table_lookup (generator_state->users, pwent->pw_name)) + shadow_entry_buffers = g_hash_table_lookup (generator_state->users, pwent->pw_name); + + if (shadow_entry_buffers != NULL) { + *spent = &shadow_entry_buffers->spbuf; return pwent; + } } /* Last iteration */ fclose (generator_state->fp); g_hash_table_unref (generator_state->users); g_free (generator_state); *state = NULL; return NULL; } static struct passwd * -entry_generator_cachedir (GHashTable *users, - gpointer *state) +entry_generator_cachedir (GHashTable *users, + gpointer *state, + struct spwd **shadow_entry) { struct passwd *pwent; const gchar *name; GError *error = NULL; gchar *filename; gboolean regular; GHashTableIter iter; GKeyFile *key_file; User *user; GDir *dir; /* First iteration */ if (*state == NULL) { *state = g_dir_open (USERDIR, 0, &error); if (error != NULL) { if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) g_warning ("couldn't list user cache directory: %s", USERDIR); g_error_free (error); return NULL; } } /* Every iteration */ /* * Use names of files of regular type to lookup information * about each user. Loop until we find something valid. */ dir = *state; while (TRUE) { name = g_dir_read_name (dir); if (name == NULL) break; /* Only load files in this directory */ filename = g_build_filename (USERDIR, name, NULL); regular = g_file_test (filename, G_FILE_TEST_IS_REGULAR); g_free (filename); if (regular) { pwent = getpwnam (name); - if (pwent == NULL) + if (pwent == NULL) { g_debug ("user '%s' in cache dir but not present on system", name); - else + } else { + *shadow_entry = getspnam (pwent->pw_name); + return pwent; + } } } /* Last iteration */ g_dir_close (dir); /* Update all the users from the files in the cache dir */ g_hash_table_iter_init (&iter, users); while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&user)) { filename = g_build_filename (USERDIR, name, NULL); key_file = g_key_file_new (); if (g_key_file_load_from_file (key_file, filename, 0, NULL)) user_update_from_keyfile (user, key_file); g_key_file_unref (key_file); g_free (filename); } *state = NULL; return NULL; } static void load_entries (Daemon *daemon, GHashTable *users, EntryGeneratorFunc entry_generator) { gpointer generator_state = NULL; struct passwd *pwent; + struct spwd *spent = NULL; User *user = NULL; g_assert (entry_generator != NULL); for (;;) { - pwent = entry_generator (users, &generator_state); + spent = NULL; + pwent = entry_generator (users, &generator_state, &spent); if (pwent == NULL) break; /* Skip system users... */ - if (!user_classify_is_human (pwent->pw_uid, pwent->pw_name, pwent->pw_shell, NULL)) { + if (!user_classify_is_human (pwent->pw_uid, pwent->pw_name, pwent->pw_shell, spent? spent->sp_pwdp : NULL)) { g_debug ("skipping user: %s", pwent->pw_name); continue; } /* ignore duplicate entries */ if (g_hash_table_lookup (users, pwent->pw_name)) { continue; } user = g_hash_table_lookup (daemon->priv->users, pwent->pw_name); if (user == NULL) { user = user_new (daemon, pwent->pw_uid); } else { g_object_ref (user); } /* freeze & update users not already in the new list */ g_object_freeze_notify (G_OBJECT (user)); - user_update_from_pwent (user, pwent); + user_update_from_pwent (user, pwent, spent); g_hash_table_insert (users, g_strdup (user_get_user_name (user)), user); g_debug ("loaded user: %s", user_get_user_name (user)); } /* Generator should have cleaned up */ g_assert (generator_state == NULL); } static GHashTable * create_users_hash_table (void) { return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); } static void reload_users (Daemon *daemon) { GHashTable *users; GHashTable *old_users; GHashTable *local; GHashTableIter iter; gpointer name; User *user; /* Track the users that we saw during our (re)load */ users = create_users_hash_table (); @@ -827,115 +852,122 @@ daemon_new (void) g_object_unref (daemon); goto error; } return daemon; error: return NULL; } static void throw_error (GDBusMethodInvocation *context, gint error_code, const gchar *format, ...) { va_list args; gchar *message; va_start (args, format); message = g_strdup_vprintf (format, args); va_end (args); g_dbus_method_invocation_return_error (context, ERROR, error_code, "%s", message); g_free (message); } static User * add_new_user_for_pwent (Daemon *daemon, - struct passwd *pwent) + struct passwd *pwent, + struct spwd *spent) { User *user; user = user_new (daemon, pwent->pw_uid); - user_update_from_pwent (user, pwent); + user_update_from_pwent (user, pwent, spent); user_register (user); g_hash_table_insert (daemon->priv->users, g_strdup (user_get_user_name (user)), user); accounts_accounts_emit_user_added (ACCOUNTS_ACCOUNTS (daemon), user_get_object_path (user)); return user; } User * daemon_local_find_user_by_id (Daemon *daemon, uid_t uid) { User *user; struct passwd *pwent; pwent = getpwuid (uid); if (pwent == NULL) { g_debug ("unable to lookup uid %d", (int)uid); return NULL; } user = g_hash_table_lookup (daemon->priv->users, pwent->pw_name); - if (user == NULL) - user = add_new_user_for_pwent (daemon, pwent); + if (user == NULL) { + struct spwd *spent; + spent = getspnam (pwent->pw_name); + user = add_new_user_for_pwent (daemon, pwent, spent); + } return user; } User * daemon_local_find_user_by_name (Daemon *daemon, const gchar *name) { User *user; struct passwd *pwent; pwent = getpwnam (name); if (pwent == NULL) { g_debug ("unable to lookup name %s: %s", name, g_strerror (errno)); return NULL; } user = g_hash_table_lookup (daemon->priv->users, pwent->pw_name); - if (user == NULL) - user = add_new_user_for_pwent (daemon, pwent); + if (user == NULL) { + struct spwd *spent; + spent = getspnam (pwent->pw_name); + user = add_new_user_for_pwent (daemon, pwent, spent); + } return user; } User * daemon_local_get_automatic_login_user (Daemon *daemon) { return daemon->priv->autologin; } static gboolean daemon_find_user_by_id (AccountsAccounts *accounts, GDBusMethodInvocation *context, gint64 uid) { Daemon *daemon = (Daemon*)accounts; User *user; user = daemon_local_find_user_by_id (daemon, uid); if (user) { accounts_accounts_complete_find_user_by_id (NULL, context, user_get_object_path (user)); } else { throw_error (context, ERROR_FAILED, "Failed to look up user with uid %d.", (int)uid); } return TRUE; } diff --git a/src/user.c b/src/user.c index 52f57d0..247ca2f 100644 --- a/src/user.c +++ b/src/user.c @@ -116,65 +116,63 @@ static void user_accounts_user_iface_init (AccountsUserIface *iface); G_DEFINE_TYPE_WITH_CODE (User, user, ACCOUNTS_TYPE_USER_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_USER, user_accounts_user_iface_init)); static gint account_type_from_pwent (struct passwd *pwent) { struct group *grp; gint i; if (pwent->pw_uid == 0) { g_debug ("user is root so account type is administrator"); return ACCOUNT_TYPE_ADMINISTRATOR; } grp = getgrnam (ADMIN_GROUP); if (grp == NULL) { g_debug (ADMIN_GROUP " group not found"); return ACCOUNT_TYPE_STANDARD; } for (i = 0; grp->gr_mem[i] != NULL; i++) { if (g_strcmp0 (grp->gr_mem[i], pwent->pw_name) == 0) { return ACCOUNT_TYPE_ADMINISTRATOR; } } return ACCOUNT_TYPE_STANDARD; } void user_update_from_pwent (User *user, - struct passwd *pwent) + struct passwd *pwent, + struct spwd *spent) { -#ifdef HAVE_SHADOW_H - struct spwd *spent; -#endif gchar *real_name; gboolean changed; const gchar *passwd; gboolean locked; PasswordMode mode; AccountType account_type; g_object_freeze_notify (G_OBJECT (user)); changed = FALSE; if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0') { gchar *first_comma = NULL; gchar *valid_utf8_name = NULL; if (g_utf8_validate (pwent->pw_gecos, -1, NULL)) { valid_utf8_name = pwent->pw_gecos; first_comma = g_utf8_strchr (valid_utf8_name, -1, ','); } else { g_warning ("User %s has invalid UTF-8 in GECOS field. " "It would be a good thing to check /etc/passwd.", pwent->pw_name ? pwent->pw_name : ""); } if (first_comma) { real_name = g_strndup (valid_utf8_name, (first_comma - valid_utf8_name)); } else if (valid_utf8_name) { @@ -219,93 +217,88 @@ user_update_from_pwent (User *user, g_object_notify (G_OBJECT (user), "account-type"); } /* Username */ if (g_strcmp0 (user->user_name, pwent->pw_name) != 0) { g_free (user->user_name); user->user_name = g_strdup (pwent->pw_name); changed = TRUE; g_object_notify (G_OBJECT (user), "user-name"); } /* Home Directory */ if (g_strcmp0 (user->home_dir, pwent->pw_dir) != 0) { g_free (user->home_dir); user->home_dir = g_strdup (pwent->pw_dir); g_free (user->default_icon_file); user->default_icon_file = g_build_filename (user->home_dir, ".face", NULL); changed = TRUE; g_object_notify (G_OBJECT (user), "home-directory"); } /* Shell */ if (g_strcmp0 (user->shell, pwent->pw_shell) != 0) { g_free (user->shell); user->shell = g_strdup (pwent->pw_shell); changed = TRUE; g_object_notify (G_OBJECT (user), "shell"); } passwd = NULL; -#ifdef HAVE_SHADOW_H - spent = getspnam (pwent->pw_name); if (spent) passwd = spent->sp_pwdp; -#endif if (passwd && passwd[0] == '!') { locked = TRUE; } else { locked = FALSE; } if (user->locked != locked) { user->locked = locked; changed = TRUE; g_object_notify (G_OBJECT (user), "locked"); } if (passwd == NULL || passwd[0] != 0) { mode = PASSWORD_MODE_REGULAR; } else { mode = PASSWORD_MODE_NONE; } -#ifdef HAVE_SHADOW_H if (spent) { if (spent->sp_lstchg == 0) { mode = PASSWORD_MODE_SET_AT_LOGIN; } } -#endif if (user->password_mode != mode) { user->password_mode = mode; changed = TRUE; g_object_notify (G_OBJECT (user), "password-mode"); } user->system_account = !user_classify_is_human (user->uid, user->user_name, pwent->pw_shell, passwd); g_object_thaw_notify (G_OBJECT (user)); if (changed) accounts_user_emit_changed (ACCOUNTS_USER (user)); } void user_update_from_keyfile (User *user, GKeyFile *keyfile) { gchar *s; g_object_freeze_notify (G_OBJECT (user)); s = g_key_file_get_string (keyfile, "User", "Language", NULL); if (s != NULL) { /* TODO: validate / normalize */ g_free (user->language); user->language = s; g_object_notify (G_OBJECT (user), "language"); } diff --git a/src/user.h b/src/user.h index 0848b50..22548f9 100644 --- a/src/user.h +++ b/src/user.h @@ -1,80 +1,82 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2009-2010 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __USER__ #define __USER__ #include #include +#include #include #include #include "types.h" G_BEGIN_DECLS #define TYPE_USER (user_get_type ()) #define USER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_USER, User)) #define IS_USER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TYPE_USER)) typedef enum { ACCOUNT_TYPE_STANDARD, ACCOUNT_TYPE_ADMINISTRATOR, #define ACCOUNT_TYPE_LAST ACCOUNT_TYPE_ADMINISTRATOR } AccountType; typedef enum { PASSWORD_MODE_REGULAR, PASSWORD_MODE_SET_AT_LOGIN, PASSWORD_MODE_NONE, #define PASSWORD_MODE_LAST PASSWORD_MODE_NONE } PasswordMode; /* local methods */ GType user_get_type (void) G_GNUC_CONST; User * user_new (Daemon *daemon, uid_t uid); void user_update_from_pwent (User *user, - struct passwd *pwent); + struct passwd *pwent, + struct spwd *spent); void user_update_from_keyfile (User *user, GKeyFile *keyfile); void user_update_local_account_property (User *user, gboolean local); void user_update_system_account_property (User *user, gboolean system); void user_register (User *user); void user_unregister (User *user); void user_changed (User *user); void user_save (User *user); const gchar * user_get_user_name (User *user); gboolean user_get_system_account (User *user); gboolean user_get_local_account (User *user); const gchar * user_get_object_path (User *user); uid_t user_get_uid (User *user); const gchar * user_get_shell (User *user); G_END_DECLS #endif -- 2.7.4 From 2accf123c55f3c6a9596e9fc2d614fcb07c88559 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 29 Jun 2016 16:22:42 -0400 Subject: [PATCH 4/5] daemon: constrain max local users to 50 Systems with tens of thousands of users don't want all those users showing up in the user list. Set a cap at an even 50, which should cover the lion's share of use cases well. Of course, if a user not in the list explicitly logs in (from Not Listed? or whatever) they get added to the list. https://bugs.freedesktop.org/show_bug.cgi?id=48177 --- src/daemon.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/daemon.c b/src/daemon.c index 71a3ea4..cb586bb 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -279,60 +279,64 @@ entry_generator_wtmp (GHashTable *users, continue; } g_object_set (user, "login-frequency", accounting->frequency, NULL); g_object_set (user, "login-time", accounting->time, NULL); builder = g_variant_builder_new (G_VARIANT_TYPE ("a(xxa{sv})")); for (l = g_list_last (accounting->previous_logins); l != NULL; l = l->prev) { previous_login = l->data; builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (builder2, "{sv}", "type", g_variant_new_string (previous_login->id)); g_variant_builder_add (builder, "(xxa{sv})", previous_login->login_time, previous_login->logout_time, builder2); g_variant_builder_unref (builder2); } g_object_set (user, "login-history", g_variant_new ("a(xxa{sv})", builder), NULL); g_variant_builder_unref (builder); g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free); user_changed (user); } g_hash_table_unref (login_hash); g_hash_table_unref (logout_hash); g_free (state_data); *state = NULL; return NULL; } #endif /* HAVE_UTMPX_H */ +#ifndef MAX_LOCAL_USERS +#define MAX_LOCAL_USERS 50 +#endif + static struct passwd * entry_generator_fgetpwent (GHashTable *users, gpointer *state, struct spwd **spent) { struct passwd *pwent; struct { struct spwd spbuf; char buf[1024]; } *shadow_entry_buffers; struct { FILE *fp; GHashTable *users; } *generator_state; /* First iteration */ if (*state == NULL) { GHashTable *shadow_users = NULL; FILE *fp; struct spwd *shadow_entry; fp = fopen (PATH_SHADOW, "r"); if (fp == NULL) { g_warning ("Unable to open %s: %s", PATH_SHADOW, g_strerror (errno)); return NULL; } shadow_users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); @@ -350,67 +354,70 @@ entry_generator_fgetpwent (GHashTable *users, if (errno != EINTR) { break; } } } while (shadow_entry != NULL); fclose (fp); if (g_hash_table_size (shadow_users) == 0) { g_clear_pointer (&shadow_users, g_hash_table_unref); return NULL; } fp = fopen (PATH_PASSWD, "r"); if (fp == NULL) { g_clear_pointer (&shadow_users, g_hash_table_unref); g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno)); return NULL; } generator_state = g_malloc0 (sizeof (*generator_state)); generator_state->fp = fp; generator_state->users = shadow_users; *state = generator_state; } /* Every iteration */ generator_state = *state; - pwent = fgetpwent (generator_state->fp); - if (pwent != NULL) { - shadow_entry_buffers = g_hash_table_lookup (generator_state->users, pwent->pw_name); - if (shadow_entry_buffers != NULL) { - *spent = &shadow_entry_buffers->spbuf; - return pwent; + if (g_hash_table_size (users) < MAX_LOCAL_USERS) { + pwent = fgetpwent (generator_state->fp); + if (pwent != NULL) { + shadow_entry_buffers = g_hash_table_lookup (generator_state->users, pwent->pw_name); + + if (shadow_entry_buffers != NULL) { + *spent = &shadow_entry_buffers->spbuf; + return pwent; + } } } /* Last iteration */ fclose (generator_state->fp); g_hash_table_unref (generator_state->users); g_free (generator_state); *state = NULL; return NULL; } static struct passwd * entry_generator_cachedir (GHashTable *users, gpointer *state, struct spwd **shadow_entry) { struct passwd *pwent; const gchar *name; GError *error = NULL; gchar *filename; gboolean regular; GHashTableIter iter; GKeyFile *key_file; User *user; GDir *dir; /* First iteration */ if (*state == NULL) { *state = g_dir_open (USERDIR, 0, &error); -- 2.7.4 From ed58ad3210010a09b6f114b4d392afb66ad0bbfa Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 29 Jun 2016 16:32:17 -0400 Subject: [PATCH 5/5] daemon: don't source user list from wtmp wtmp can get rather large on some systems from ssh logins. Furthermore it's pretty much completely redundant given the user cache in /var/lib/AccountService This commit changes the wtmp code to only get used for maintaining login frequency and accounting, not for generating new users. https://bugs.freedesktop.org/show_bug.cgi?id=48177 --- src/daemon.c | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/src/daemon.c b/src/daemon.c index cb586bb..815e2c9 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -137,95 +137,83 @@ error_get_type (void) } return etype; } #ifdef HAVE_UTMPX_H typedef struct { guint64 frequency; gint64 time; GList *previous_logins; } UserAccounting; typedef struct { gchar *id; gint64 login_time; gint64 logout_time; } UserPreviousLogin; typedef struct { GHashTable *login_hash; GHashTable *logout_hash; } WTmpGeneratorState; static void user_previous_login_free (UserPreviousLogin *previous_login) { g_free (previous_login->id); g_free (previous_login); } -static struct passwd * -entry_generator_wtmp (GHashTable *users, - gpointer *state, - struct spwd **spent) +static void +wtmp_update_login_frequencies (GHashTable *users) { GHashTable *login_hash, *logout_hash; struct utmpx *wtmp_entry; GHashTableIter iter; gpointer key, value; struct passwd *pwent; User *user; - WTmpGeneratorState *state_data; GVariantBuilder *builder, *builder2; GList *l; - if (*state == NULL) { - /* First iteration */ #ifdef UTXDB_LOG - if (setutxdb (UTXDB_LOG, NULL) != 0) { - return NULL; - } + if (setutxdb (UTXDB_LOG, NULL) != 0) { + return NULL; + } #else - utmpxname (PATH_WTMP); - setutxent (); + utmpxname (PATH_WTMP); + setutxent (); #endif - *state = g_new (WTmpGeneratorState, 1); - state_data = *state; - state_data->login_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - state_data->logout_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - } - /* Every iteration */ - state_data = *state; - login_hash = state_data->login_hash; - logout_hash = state_data->logout_hash; + login_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + logout_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); while ((wtmp_entry = getutxent ())) { UserAccounting *accounting; UserPreviousLogin *previous_login; if (wtmp_entry->ut_type == BOOT_TIME) { /* Set boot time for missing logout records */ g_hash_table_iter_init (&iter, logout_hash); while (g_hash_table_iter_next (&iter, &key, &value)) { previous_login = (UserPreviousLogin *) value; if (previous_login->logout_time == 0) { previous_login->logout_time = wtmp_entry->ut_tv.tv_sec; } } g_hash_table_remove_all (logout_hash); } else if (wtmp_entry->ut_type == DEAD_PROCESS) { /* Save corresponding logout time */ if (g_hash_table_lookup_extended (logout_hash, wtmp_entry->ut_line, &key, &value)) { previous_login = (UserPreviousLogin *) value; previous_login->logout_time = wtmp_entry->ut_tv.tv_sec; g_hash_table_remove (logout_hash, previous_login->id); } } if (wtmp_entry->ut_type != USER_PROCESS) { continue; } if (wtmp_entry->ut_user[0] == 0) { @@ -233,103 +221,96 @@ entry_generator_wtmp (GHashTable *users, } pwent = getpwnam (wtmp_entry->ut_user); if (pwent == NULL) { continue; } if (!g_hash_table_lookup_extended (login_hash, wtmp_entry->ut_user, &key, &value)) { accounting = g_new (UserAccounting, 1); accounting->frequency = 0; accounting->previous_logins = NULL; g_hash_table_insert (login_hash, g_strdup (wtmp_entry->ut_user), accounting); } else { accounting = value; } accounting->frequency++; accounting->time = wtmp_entry->ut_tv.tv_sec; /* Add zero logout time to change it later on logout record */ previous_login = g_new (UserPreviousLogin, 1); previous_login->id = g_strdup (wtmp_entry->ut_line); previous_login->login_time = wtmp_entry->ut_tv.tv_sec; previous_login->logout_time = 0; accounting->previous_logins = g_list_prepend (accounting->previous_logins, previous_login); g_hash_table_insert (logout_hash, g_strdup (wtmp_entry->ut_line), previous_login); - *spent = getspnam (pwent->pw_name); - - return pwent; } - /* Last iteration */ endutxent (); g_hash_table_iter_init (&iter, login_hash); while (g_hash_table_iter_next (&iter, &key, &value)) { UserAccounting *accounting = (UserAccounting *) value; UserPreviousLogin *previous_login; user = g_hash_table_lookup (users, key); if (user == NULL) { g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free); continue; } g_object_set (user, "login-frequency", accounting->frequency, NULL); g_object_set (user, "login-time", accounting->time, NULL); builder = g_variant_builder_new (G_VARIANT_TYPE ("a(xxa{sv})")); for (l = g_list_last (accounting->previous_logins); l != NULL; l = l->prev) { previous_login = l->data; builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (builder2, "{sv}", "type", g_variant_new_string (previous_login->id)); g_variant_builder_add (builder, "(xxa{sv})", previous_login->login_time, previous_login->logout_time, builder2); g_variant_builder_unref (builder2); } g_object_set (user, "login-history", g_variant_new ("a(xxa{sv})", builder), NULL); g_variant_builder_unref (builder); g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free); user_changed (user); } g_hash_table_unref (login_hash); g_hash_table_unref (logout_hash); - g_free (state_data); - *state = NULL; - return NULL; } #endif /* HAVE_UTMPX_H */ #ifndef MAX_LOCAL_USERS #define MAX_LOCAL_USERS 50 #endif static struct passwd * entry_generator_fgetpwent (GHashTable *users, gpointer *state, struct spwd **spent) { struct passwd *pwent; struct { struct spwd spbuf; char buf[1024]; } *shadow_entry_buffers; struct { FILE *fp; GHashTable *users; } *generator_state; /* First iteration */ if (*state == NULL) { GHashTable *shadow_users = NULL; FILE *fp; struct spwd *shadow_entry; @@ -533,64 +514,65 @@ create_users_hash_table (void) g_object_unref); } static void reload_users (Daemon *daemon) { GHashTable *users; GHashTable *old_users; GHashTable *local; GHashTableIter iter; gpointer name; User *user; /* Track the users that we saw during our (re)load */ users = create_users_hash_table (); /* * NOTE: As we load data from all the sources, notifies are * frozen in load_entries() and then thawed as we process * them below. */ /* Load the local users into our hash table */ load_entries (daemon, users, entry_generator_fgetpwent); local = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_iter_init (&iter, users); while (g_hash_table_iter_next (&iter, &name, NULL)) g_hash_table_add (local, name); /* Now add/update users from other sources, possibly non-local */ + load_entries (daemon, users, entry_generator_cachedir); + #ifdef HAVE_UTMPX_H - load_entries (daemon, users, entry_generator_wtmp); + wtmp_update_login_frequencies (users); #endif - load_entries (daemon, users, entry_generator_cachedir); /* Mark which users are local, which are not */ g_hash_table_iter_init (&iter, users); while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) user_update_local_account_property (user, g_hash_table_lookup (local, name) != NULL); g_hash_table_destroy (local); /* Swap out the users */ old_users = daemon->priv->users; daemon->priv->users = users; /* Remove all the old users */ g_hash_table_iter_init (&iter, old_users); while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) { if (!g_hash_table_lookup (users, name)) { user_unregister (user); accounts_accounts_emit_user_deleted (ACCOUNTS_ACCOUNTS (daemon), user_get_object_path (user)); } } /* Register all the new users */ g_hash_table_iter_init (&iter, users); while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) { if (!g_hash_table_lookup (old_users, name)) { user_register (user); accounts_accounts_emit_user_added (ACCOUNTS_ACCOUNTS (daemon), user_get_object_path (user)); } -- 2.7.4