From 691d11c09d40cf6e9745e0c61e3fc59f77865e04 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 23 Mar 2017 16:59:11 -0400 Subject: [PATCH] daemon: make sure explicitly requested users aren't lost on reloads Right now, a user proxy can suddenly become defunct if the /etc/passwd file is updated or some other reason leads to a reload. This commit makes sure that the objects associated with proxies stick around across reloads. --- src/daemon.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/src/daemon.c b/src/daemon.c index 815e2c9..4586eff 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -41,78 +41,79 @@ #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 }; struct DaemonPrivate { GDBusConnection *bus_connection; GDBusProxy *bus_proxy; GHashTable *users; + GList *explicitly_requested_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 *, struct spwd **shadow_entry); +typedef struct passwd * (* EntryGeneratorFunc) (Daemon *, 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 } @@ -265,61 +266,62 @@ wtmp_update_login_frequencies (GHashTable *users) 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); } #endif /* HAVE_UTMPX_H */ #ifndef MAX_LOCAL_USERS #define MAX_LOCAL_USERS 50 #endif static struct passwd * -entry_generator_fgetpwent (GHashTable *users, +entry_generator_fgetpwent (Daemon *daemon, + 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); do { @@ -358,61 +360,62 @@ entry_generator_fgetpwent (GHashTable *users, generator_state->users = shadow_users; *state = generator_state; } /* Every iteration */ generator_state = *state; 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, +entry_generator_cachedir (Daemon *daemon, + 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. */ @@ -430,145 +433,194 @@ entry_generator_cachedir (GHashTable *users, if (regular) { pwent = getpwnam (name); if (pwent == NULL) { g_debug ("user '%s' in cache dir but not present on system", name); } 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 struct passwd * +entry_generator_requested_users (Daemon *daemon, + GHashTable *users, + gpointer *state, + struct spwd **shadow_entry) +{ + struct passwd *pwent; + GList *node; + + /* First iteration */ + if (*state == NULL) { + *state = daemon->priv->explicitly_requested_users; + } + + /* Every iteration */ + + if (g_hash_table_size (users) < MAX_LOCAL_USERS) { + node = *state; + while (node != NULL) { + const char *name; + + name = node->data; + node = node->next; + + *state = node; + + if (!g_hash_table_lookup (users, name)) { + pwent = getpwnam (name); + if (pwent == NULL) { + g_debug ("user '%s' requested previously but not present on system", name); + } else { + *shadow_entry = getspnam (pwent->pw_name); + + return pwent; + } + } + } + } + + /* Last iteration */ + + *state = NULL; + return NULL; +} + static void load_entries (Daemon *daemon, GHashTable *users, + gboolean allow_system_users, EntryGeneratorFunc entry_generator) { gpointer generator_state = NULL; struct passwd *pwent; struct spwd *spent = NULL; User *user = NULL; g_assert (entry_generator != NULL); for (;;) { spent = NULL; - pwent = entry_generator (users, &generator_state, &spent); + pwent = entry_generator (daemon, 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, spent? spent->sp_pwdp : NULL)) { + if (!allow_system_users && !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, 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 (); /* * 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); + load_entries (daemon, users, FALSE, 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); + /* and add users to hash table that were explicitly requested */ + load_entries (daemon, users, TRUE, entry_generator_requested_users); + /* Now add/update users from other sources, possibly non-local */ - load_entries (daemon, users, entry_generator_cachedir); + load_entries (daemon, users, FALSE, entry_generator_cachedir); #ifdef HAVE_UTMPX_H wtmp_update_login_frequencies (users); #endif /* 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)) { @@ -757,60 +809,62 @@ daemon_init (Daemon *daemon) #ifdef HAVE_UTMPX_H daemon->priv->wtmp_monitor = setup_monitor (daemon, PATH_WTMP, on_users_monitor_changed); #endif daemon->priv->gdm_monitor = setup_monitor (daemon, PATH_GDM_CUSTOM, on_gdm_monitor_changed); queue_reload_users (daemon); queue_reload_autologin (daemon); } static void daemon_finalize (GObject *object) { Daemon *daemon; g_return_if_fail (IS_DAEMON (object)); daemon = DAEMON (object); if (daemon->priv->bus_proxy != NULL) g_object_unref (daemon->priv->bus_proxy); if (daemon->priv->bus_connection != NULL) g_object_unref (daemon->priv->bus_connection); + g_list_free_full (daemon->priv->explicitly_requested_users, g_free); + g_hash_table_destroy (daemon->priv->users); G_OBJECT_CLASS (daemon_parent_class)->finalize (object); } static gboolean register_accounts_daemon (Daemon *daemon) { GError *error = NULL; daemon->priv->authority = polkit_authority_get_sync (NULL, &error); if (daemon->priv->authority == NULL) { if (error != NULL) { g_critical ("error getting polkit authority: %s", error->message); g_error_free (error); } goto error; } daemon->priv->bus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (daemon->priv->bus_connection == NULL) { if (error != NULL) { g_critical ("error getting system bus: %s", error->message); g_error_free (error); } goto error; } if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (daemon), @@ -878,84 +932,90 @@ add_new_user_for_pwent (Daemon *daemon, 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) { struct spwd *spent; spent = getspnam (pwent->pw_name); user = add_new_user_for_pwent (daemon, pwent, spent); + + daemon->priv->explicitly_requested_users = g_list_append (daemon->priv->explicitly_requested_users, + g_strdup (pwent->pw_name)); } 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) { struct spwd *spent; spent = getspnam (pwent->pw_name); user = add_new_user_for_pwent (daemon, pwent, spent); + + daemon->priv->explicitly_requested_users = g_list_append (daemon->priv->explicitly_requested_users, + g_strdup (pwent->pw_name)); } 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; } -- 2.12.0