diff --git a/SOURCES/0001-daemon-don-t-treat-explicitly-requested-users-as-cac.patch b/SOURCES/0001-daemon-don-t-treat-explicitly-requested-users-as-cac.patch new file mode 100644 index 0000000..bbf9cdd --- /dev/null +++ b/SOURCES/0001-daemon-don-t-treat-explicitly-requested-users-as-cac.patch @@ -0,0 +1,741 @@ +From 83567748f5f5c4eabc233680a553f3edd803a24d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 4 May 2017 12:04:05 -0400 +Subject: [PATCH] daemon: don't treat explicitly requested users as "cached" + +The ListCachedUsers method currently returns users that have +been explicitly requested by a client. It's weird that merely +querying a user can make it show up in login screen user lists. +Furthermore, UncacheUser is broken since commit +177509e9460b149ecbf85e75c930be2ea00b7d05 because the user has +been explicitly requested in order to uncache it. So trying +to uncache a user inadvertently caches the user. + +This commit fixes that. + +https://bugs.freedesktop.org/show_bug.cgi?id=101052 +--- + src/daemon.c | 71 +++++++++++++++++++++++++++++++++++++++--------------------- + src/user.c | 17 +++++++++++++++ + src/user.h | 3 +++ + 3 files changed, 66 insertions(+), 25 deletions(-) + +diff --git a/src/daemon.c b/src/daemon.c +index 4586eff..fce5a60 100644 +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -481,100 +481,108 @@ entry_generator_requested_users (Daemon *daemon, + 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, ++ gboolean explicitly_requested, + 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 (daemon, users, &generator_state, &spent); + if (pwent == NULL) + break; + + /* Skip system users... */ +- if (!allow_system_users && !user_classify_is_human (pwent->pw_uid, pwent->pw_name, pwent->pw_shell, spent? spent->sp_pwdp : NULL)) { ++ if (!explicitly_requested && !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; +- } ++ /* Only process users that haven't been processed yet. ++ * We do always make sure entries get promoted ++ * to "cached" status if they are supposed to be ++ */ ++ ++ user = g_hash_table_lookup (users, pwent->pw_name); + +- 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); +- } ++ 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); + +- /* 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)); ++ } + +- g_hash_table_insert (users, g_strdup (user_get_user_name (user)), user); +- g_debug ("loaded user: %s", user_get_user_name (user)); ++ if (!explicitly_requested) { ++ user_set_cached (user, TRUE); ++ } + } + + /* 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 +@@ -586,71 +594,79 @@ reload_users (Daemon *daemon) + 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, 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 *refreshed_user; ++ ++ refreshed_user = g_hash_table_lookup (users, name); ++ ++ if (!refreshed_user || !user_get_cached (refreshed_user)) { + 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 *stale_user; ++ ++ stale_user = g_hash_table_lookup (old_users, name); ++ ++ if (!stale_user || !user_get_cached (stale_user) && user_get_cached (user)) { + user_register (user); + accounts_accounts_emit_user_added (ACCOUNTS_ACCOUNTS (daemon), + user_get_object_path (user)); + } + g_object_thaw_notify (G_OBJECT (user)); + } + + g_hash_table_destroy (old_users); + } + + static gboolean + reload_users_timeout (Daemon *daemon) + { + reload_users (daemon); + daemon->priv->reload_id = 0; + + return FALSE; + } + + static gboolean load_autologin (Daemon *daemon, + gchar **name, + gboolean *enabled, + GError **error); + + static gboolean + reload_autologin_timeout (Daemon *daemon) + { + gboolean enabled; + gchar *name = NULL; + GError *error = NULL; +@@ -1063,60 +1079,65 @@ list_user_data_new (Daemon *daemon, + static void + list_user_data_free (ListUserData *data) + { + g_object_unref (data->daemon); + g_free (data); + } + + static gboolean + finish_list_cached_users (gpointer user_data) + { + ListUserData *data = user_data; + GPtrArray *object_paths; + GHashTableIter iter; + const gchar *name; + User *user; + uid_t uid; + const gchar *shell; + + object_paths = g_ptr_array_new (); + + g_hash_table_iter_init (&iter, data->daemon->priv->users); + while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&user)) { + uid = user_get_uid (user); + shell = user_get_shell (user); + + if (!user_classify_is_human (uid, name, shell, NULL)) { + g_debug ("user %s %ld excluded", name, (long) uid); + continue; + } + ++ if (!user_get_cached (user)) { ++ g_debug ("user %s %ld not cached", name, (long) uid); ++ continue; ++ } ++ + g_debug ("user %s %ld not excluded", name, (long) uid); + g_ptr_array_add (object_paths, (gpointer) user_get_object_path (user)); + } + g_ptr_array_add (object_paths, NULL); + + accounts_accounts_complete_list_cached_users (NULL, data->context, (const gchar * const *) object_paths->pdata); + + g_ptr_array_free (object_paths, TRUE); + + list_user_data_free (data); + + return FALSE; + } + + static gboolean + daemon_list_cached_users (AccountsAccounts *accounts, + GDBusMethodInvocation *context) + { + Daemon *daemon = (Daemon*)accounts; + ListUserData *data; + + data = list_user_data_new (daemon, context); + + if (daemon->priv->reload_id > 0) { + /* reload in progress, wait a bit */ + g_idle_add (finish_list_cached_users, data); + } + else { + finish_list_cached_users (data); + } +@@ -1303,123 +1324,123 @@ daemon_cache_user (AccountsAccounts *accounts, + static void + daemon_uncache_user_authorized_cb (Daemon *daemon, + User *dummy, + GDBusMethodInvocation *context, + gpointer data) + { + const gchar *user_name = data; + gchar *filename; + User *user; + + sys_log (context, "uncache user '%s'", user_name); + + user = daemon_local_find_user_by_name (daemon, user_name); + if (user == NULL) { + throw_error (context, ERROR_USER_DOES_NOT_EXIST, + "No user with the name %s found", user_name); + return; + } + + /* Always use the canonical user name looked up */ + user_name = user_get_user_name (user); + + filename = g_build_filename (USERDIR, user_name, NULL); + g_remove (filename); + g_free (filename); + + filename = g_build_filename (ICONDIR, user_name, NULL); + g_remove (filename); + g_free (filename); + ++ user_set_cached (user, FALSE); ++ + accounts_accounts_complete_uncache_user (NULL, context); + + queue_reload_users (daemon); + } + + static gboolean + daemon_uncache_user (AccountsAccounts *accounts, + GDBusMethodInvocation *context, + const gchar *user_name) + { + Daemon *daemon = (Daemon*)accounts; + + daemon_local_check_auth (daemon, + NULL, + "org.freedesktop.accounts.user-administration", + TRUE, + daemon_uncache_user_authorized_cb, + context, + g_strdup (user_name), + g_free); + + return TRUE; + } + + typedef struct { + uid_t uid; + gboolean remove_files; + } DeleteUserData; + + static void + daemon_delete_user_authorized_cb (Daemon *daemon, + User *dummy, + GDBusMethodInvocation *context, + gpointer data) + + { + DeleteUserData *ud = data; + GError *error; + gchar *filename; + struct passwd *pwent; + const gchar *argv[6]; ++ User *user; + + pwent = getpwuid (ud->uid); + + if (pwent == NULL) { + throw_error (context, ERROR_USER_DOES_NOT_EXIST, "No user with uid %d found", ud->uid); + + return; + } + + sys_log (context, "delete user '%s' (%d)", pwent->pw_name, ud->uid); + +- if (daemon->priv->autologin != NULL) { +- User *user; ++ user = daemon_local_find_user_by_id (daemon, ud->uid); + +- user = daemon_local_find_user_by_id (daemon, ud->uid); ++ if (user != NULL) { ++ user_set_cached (user, FALSE); + +- g_assert (user != NULL); +- +- if (daemon->priv->autologin == user) { ++ if (daemon->priv->autologin == user) { + daemon_local_set_automatic_login (daemon, user, FALSE, NULL); + } +- + } + + filename = g_build_filename (USERDIR, pwent->pw_name, NULL); + g_remove (filename); + g_free (filename); + + filename = g_build_filename (ICONDIR, pwent->pw_name, NULL); + g_remove (filename); + g_free (filename); + + argv[0] = "/usr/sbin/userdel"; + if (ud->remove_files) { + argv[1] = "-f"; + argv[2] = "-r"; + argv[3] = "--"; + argv[4] = pwent->pw_name; + argv[5] = NULL; + } + else { + argv[1] = "-f"; + argv[2] = "--"; + argv[3] = pwent->pw_name; + argv[4] = NULL; + } + + error = NULL; + if (!spawn_with_login_uid (context, argv, &error)) { + throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message); + g_error_free (error); + return; +diff --git a/src/user.c b/src/user.c +index 247ca2f..056be2f 100644 +--- a/src/user.c ++++ b/src/user.c +@@ -77,60 +77,61 @@ struct User { + + GDBusConnection *system_bus_connection; + gchar *object_path; + + Daemon *daemon; + + GKeyFile *keyfile; + + uid_t uid; + gid_t gid; + gchar *user_name; + gchar *real_name; + AccountType account_type; + PasswordMode password_mode; + gchar *password_hint; + 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; ++ gboolean cached; + }; + + 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; + 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) { +@@ -323,109 +324,112 @@ user_update_from_keyfile (User *user, + user->location = s; + g_object_notify (G_OBJECT (user), "location"); + } + + s = g_key_file_get_string (keyfile, "User", "PasswordHint", NULL); + if (s != NULL) { + g_free (user->password_hint); + user->password_hint = s; + g_object_notify (G_OBJECT (user), "password-hint"); + } + + s = g_key_file_get_string (keyfile, "User", "Icon", NULL); + if (s != NULL) { + g_free (user->icon_file); + user->icon_file = s; + g_object_notify (G_OBJECT (user), "icon-file"); + } + + if (g_key_file_has_key (keyfile, "User", "SystemAccount", NULL)) { + gboolean system_account; + + system_account = g_key_file_get_boolean (keyfile, "User", "SystemAccount", NULL); + if (system_account != user->system_account) { + user->system_account = system_account; + g_object_notify (G_OBJECT (user), "system-account"); + } + } + + g_clear_pointer (&user->keyfile, g_key_file_unref); + user->keyfile = g_key_file_ref (keyfile); ++ user_set_cached (user, TRUE); + + g_object_thaw_notify (G_OBJECT (user)); + } + + void + user_update_local_account_property (User *user, + gboolean local) + { + if (local == user->local_account) + return; + user->local_account = local; + g_object_notify (G_OBJECT (user), "local-account"); + } + + void + user_update_system_account_property (User *user, + gboolean system) + { + if (system == user->system_account) + return; + user->system_account = system; + g_object_notify (G_OBJECT (user), "system-account"); + } + + static void + user_save_to_keyfile (User *user, + GKeyFile *keyfile) + { + g_key_file_remove_group (keyfile, "User", NULL); + + if (user->email) + g_key_file_set_string (keyfile, "User", "Email", user->email); + + if (user->language) + g_key_file_set_string (keyfile, "User", "Language", user->language); + + if (user->x_session) + g_key_file_set_string (keyfile, "User", "XSession", user->x_session); + + if (user->location) + g_key_file_set_string (keyfile, "User", "Location", user->location); + + if (user->password_hint) + g_key_file_set_string (keyfile, "User", "PasswordHint", user->password_hint); + + if (user->icon_file) + g_key_file_set_string (keyfile, "User", "Icon", user->icon_file); + + g_key_file_set_boolean (keyfile, "User", "SystemAccount", user->system_account); ++ ++ user_set_cached (user, TRUE); + } + + static void + save_extra_data (User *user) + { + gchar *filename; + gchar *data; + GError *error; + + user_save_to_keyfile (user, user->keyfile); + + error = NULL; + data = g_key_file_to_data (user->keyfile, NULL, &error); + if (error == NULL) { + filename = g_build_filename (USERDIR, + user->user_name, + NULL); + g_file_set_contents (filename, data, -1, &error); + g_free (filename); + g_free (data); + } + if (error) { + g_warning ("Saving data for user %s failed: %s", + user->user_name, error->message); + g_error_free (error); + } + } + + static void + move_extra_data (const gchar *old_name, +@@ -524,60 +528,73 @@ user_get_user_name (User *user) + gboolean + user_get_system_account (User *user) + { + return user->system_account; + } + + gboolean + user_get_local_account (User *user) + { + return user->local_account; + } + + const gchar * + user_get_object_path (User *user) + { + return user->object_path; + } + + uid_t + user_get_uid (User *user) + { + return user->uid; + } + + const gchar * + user_get_shell(User *user) + { + return user->shell; + } + ++gboolean ++user_get_cached (User *user) ++{ ++ return user->cached; ++} ++ ++void ++user_set_cached (User *user, ++ gboolean cached) ++{ ++ user->cached = cached; ++} ++ + 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 void + user_change_real_name_authorized_cb (Daemon *daemon, + User *user, + GDBusMethodInvocation *context, + gpointer data) + + { + gchar *name = data; + GError *error; + const gchar *argv[6]; + + if (g_strcmp0 (user->real_name, name) != 0) { +diff --git a/src/user.h b/src/user.h +index 22548f9..39c6f13 100644 +--- a/src/user.h ++++ b/src/user.h +@@ -36,47 +36,50 @@ G_BEGIN_DECLS + #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 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); ++gboolean user_get_cached (User *user); ++void user_set_cached (User *user, ++ gboolean cached); + + 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.12.2 + diff --git a/SOURCES/0001-daemon-make-sure-explicitly-requested-users-aren-t-l.patch b/SOURCES/0001-daemon-make-sure-explicitly-requested-users-aren-t-l.patch new file mode 100644 index 0000000..f35251c --- /dev/null +++ b/SOURCES/0001-daemon-make-sure-explicitly-requested-users-aren-t-l.patch @@ -0,0 +1,584 @@ +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 + diff --git a/SPECS/accountsservice.spec b/SPECS/accountsservice.spec index cdce0e5..e4ce1c7 100644 --- a/SPECS/accountsservice.spec +++ b/SPECS/accountsservice.spec @@ -2,7 +2,7 @@ Name: accountsservice Version: 0.6.35 -Release: 12%{?dist} +Release: 14%{?dist} Summary: D-Bus interfaces for querying and manipulating user account information Group: System Environment/Daemons @@ -39,6 +39,8 @@ Patch7: scale-better.patch Patch8: fix-log-leak.patch Patch9: 0001-user-classify-exclude-nologin-users.patch Patch10: 0001-configure-actually-define-HAVE_GETUSERSHELL.patch +Patch11: 0001-daemon-make-sure-explicitly-requested-users-aren-t-l.patch +Patch12: 0001-daemon-don-t-treat-explicitly-requested-users-as-cac.patch %package libs Summary: Client-side library to talk to accountsservice @@ -80,6 +82,8 @@ of these interfaces, based on the useradd, usermod and userdel commands. %patch8 -p1 -b .fix-log-leak %patch9 -p1 -b .hide-nologin-users %patch10 -p1 -b .define-HAVE_GETUSERSHELL +%patch11 -p1 -b .crashfix +%patch12 -p1 -b .uncache-fix %build autoreconf -f -i @@ -134,6 +138,14 @@ rm $RPM_BUILD_ROOT%{_libdir}/*.a %{_datadir}/gtk-doc/html/libaccountsservice/* %changelog +* Mon May 15 2017 Ray Strode - 0.6.35-14 +- address uncache regression introduced in last commit + Related: #1432602 + +* Mon Apr 24 2017 Ray Strode - 0.6.35-13 +- address libaccountsservice crash when /etc/passwd changes + Resolves: #1432602 + * Tue Sep 06 2016 Ray Strode - 0.6.35-12 - hide users with /sbin/nologin shell Resolves: #1341276