From 45ffb417bc5b585925cf4f2699c416ba11b98f43 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 6 Jun 2012 10:10:41 +0200 Subject: [PATCH] daemon: Add CacheUser() DBus method * Caches a user so that it becomes visible via ListCachedUsers() * The user account must be accessible via getpwnam() * Refactor how reloading of users works, if user is present in multiple sources track how many places loaded, and only unregister if gone from all. https://bugs.freedesktop.org/show_bug.cgi?id=50770 --- data/org.freedesktop.Accounts.xml | 27 ++++ src/daemon.c | 248 +++++++++++++++++++++++++---- src/libaccountsservice/act-user-manager.c | 43 +++++ src/libaccountsservice/act-user-manager.h | 4 + src/user.c | 28 +++- src/user.h | 6 +- 6 files changed, 317 insertions(+), 39 deletions(-) diff --git a/data/org.freedesktop.Accounts.xml b/data/org.freedesktop.Accounts.xml index ab3a15c..c5ac523 100644 --- a/data/org.freedesktop.Accounts.xml +++ b/data/org.freedesktop.Accounts.xml @@ -112,6 +112,33 @@ + + + + The username for the user + + + Object path of user + + + + + + Caches a user account, so that it shows up in ListCachedUsers() output. + The user name may be a remote user, but the system must be able to lookup + the user name and resolve the user information. + + + + The caller needs the org.freedesktop.accounts.user-administration PolicyKit authorization. + + + if the caller lacks the appropriate PolicyKit authorization + if the user name cannot be resolved + + + + diff --git a/src/daemon.c b/src/daemon.c index 12f189e..997b39d 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -293,39 +293,145 @@ compare_user_name (gconstpointer a, gconstpointer b) return g_strcmp0 (user_local_get_user_name (user), name); } +#ifdef HAVE_FGETPWENT + +static struct passwd * +next_entry_from_fgetpwent (gpointer *state) +{ + struct passwd *pwent; + FILE *fp; + + /* First iteration */ + if (*state == NULL) { + errno = 0; + fp = fopen (PATH_PASSWD, "r"); + if (fp == NULL) { + g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno)); + return NULL; + } + *state = fp; + + /* Later iterations */ + } else { + fp = *state; + } + + pwent = fgetpwent (fp); + + /* Complete? */ + if (pwent == NULL) { + fclose (fp); + *state = NULL; + } + + return pwent; +} + +#else /* !HAVE_FGETPWENT */ + +static struct passwd * +next_entry_from_getpwent (gpointer *state) +{ + struct passwd *pwent; + + /* First iteration */ + if (*state == NULL) { + setpwent(); + *state = GINT_TO_POINTER (1); + } + + pwent = getpwent (); + + /* Complete? */ + if (pwent == NULL) + *state = NULL; + + return pwent; +} + +#endif /* !HAVE_FGETPWENT */ + +static struct passwd * +next_entry_from_cachedir (gpointer *state) +{ + struct passwd *pwent; + const gchar *name; + GError *error = NULL; + gchar *filename; + gboolean regular; + GDir *dir; + + /* First iteration */ + if (*state == NULL) { + dir = 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; + } + *state = dir; + + /* Later iterations */ + } else { + dir = *state; + } + + /* + * Use names of files of regular type to lookup information + * about each user. Loop until we find something valid. + */ + pwent = NULL; + while (pwent == NULL) { + 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) + g_debug ("user '%s' in cache dir but not present on system", name); + } + } + + /* Complete? */ + if (pwent == NULL) { + g_dir_close (dir); + *state = NULL; + } + + return pwent; +} + static void -reload_passwd (Daemon *daemon) +reload_entries (Daemon *daemon, + struct passwd * (entry_generator) (gpointer *state)) { + gpointer generator_state = NULL; struct passwd *pwent; GSList *old_users; GSList *new_users; GSList *list; -#ifdef HAVE_FGETPWENT - FILE *fp; -#endif User *user = NULL; + g_assert (entry_generator != NULL); + old_users = NULL; new_users = NULL; -#ifdef HAVE_FGETPWENT - errno = 0; - fp = fopen (PATH_PASSWD, "r"); - if (fp == NULL) { - g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno)); - goto out; - } -#else - setpwent(); -#endif g_hash_table_foreach (daemon->priv->users, listify_hash_values_hfunc, &old_users); g_slist_foreach (old_users, (GFunc) g_object_ref, NULL); -#ifdef HAVE_FGETPWENT - while ((pwent = fgetpwent (fp)) != NULL) { -#else - while ((pwent = getpwent ()) != NULL) { -#endif + for (;;) { + pwent = (entry_generator) (&generator_state); + if (pwent == NULL) + break; + /* Skip system users... */ if (daemon_local_user_is_excluded (daemon, pwent->pw_name, pwent->pw_shell)) { g_debug ("skipping user: %s", pwent->pw_name); @@ -351,14 +457,18 @@ reload_passwd (Daemon *daemon) new_users = g_slist_prepend (new_users, user); } + /* Generator should have cleaned up */ + g_assert (generator_state == NULL); + /* Go through and handle removed users */ for (list = old_users; list; list = list->next) { user = list->data; if (! g_slist_find (new_users, user)) { - accounts_accounts_emit_user_deleted (ACCOUNTS_ACCOUNTS (daemon), user_local_get_object_path (user)); - user_local_unregister (user); - g_hash_table_remove (daemon->priv->users, - user_local_get_user_name (user)); + if (user_local_unregister_for_source (user, entry_generator)) { + accounts_accounts_emit_user_deleted (ACCOUNTS_ACCOUNTS (daemon), user_local_get_object_path (user)); + g_hash_table_remove (daemon->priv->users, + user_local_get_user_name (user)); + } } } @@ -366,7 +476,7 @@ reload_passwd (Daemon *daemon) for (list = new_users; list; list = list->next) { user = list->data; if (!g_slist_find (old_users, user)) { - user_local_register (user); + user_local_register_for_source (user, entry_generator); g_hash_table_insert (daemon->priv->users, g_strdup (user_local_get_user_name (user)), g_object_ref (user)); @@ -375,13 +485,6 @@ reload_passwd (Daemon *daemon) } } -#ifdef HAVE_FGETPWENT - out: - /* Cleanup */ - - fclose (fp); -#endif - g_slist_foreach (new_users, (GFunc) g_object_thaw_notify, NULL); g_slist_foreach (new_users, (GFunc) g_object_unref, NULL); g_slist_free (new_users); @@ -413,7 +516,12 @@ reload_data (Daemon *daemon) static void reload_users (Daemon *daemon) { - reload_passwd (daemon); +#ifdef HAVE_FGETPWENT + reload_entries (daemon, next_entry_from_fgetpwent); +#else + reload_entries (daemon, next_entry_from_getpwent); +#endif + reload_entries (daemon, next_entry_from_cachedir); reload_wtmp_history (daemon); reload_data (daemon); } @@ -720,7 +828,7 @@ add_new_user_for_pwent (Daemon *daemon, user = user_local_new (daemon, pwent->pw_uid); user_local_update_from_pwent (user, pwent); - user_local_register (user); + user_local_register_for_source (user, add_new_user_for_pwent); g_hash_table_insert (daemon->priv->users, g_strdup (user_local_get_user_name (user)), @@ -748,6 +856,8 @@ daemon_local_find_user_by_id (Daemon *daemon, if (user == NULL) user = add_new_user_for_pwent (daemon, pwent); + else + user_local_update_from_pwent (user, pwent); return user; } @@ -769,6 +879,8 @@ daemon_local_find_user_by_name (Daemon *daemon, if (user == NULL) user = add_new_user_for_pwent (daemon, pwent); + else + user_local_update_from_pwent (user, pwent); return user; } @@ -1004,6 +1116,75 @@ daemon_create_user (AccountsAccounts *accounts, return TRUE; } +static void +daemon_cache_user_authorized_cb (Daemon *daemon, + User *dummy, + GDBusMethodInvocation *context, + gpointer data) +{ + const gchar *user_name = data; + GError *error = NULL; + gchar *filename; + gchar *comment; + User *user; + + sys_log (context, "cache 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_local_get_user_name (user); + + filename = g_build_filename (USERDIR, user_name, NULL); + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { + comment = g_strdup_printf ("# Cached file for %s\n\n", user_name); + g_file_set_contents (filename, comment, -1, &error); + g_free (comment); + + if (error != NULL) { + g_warning ("Couldn't write user cache file: %s: %s", + filename, error->message); + g_error_free (error); + } + } + + g_free (filename); + + accounts_accounts_complete_cache_user (NULL, context, user_local_get_object_path (user)); +} + +static gboolean +daemon_cache_user (AccountsAccounts *accounts, + GDBusMethodInvocation *context, + const gchar *user_name) +{ + Daemon *daemon = (Daemon*)accounts; + + /* Can't have a slash in the user name */ + if (strchr (user_name, '/') != NULL) { + g_dbus_method_invocation_return_error (context, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid user name: %s", user_name); + return TRUE; + } + + daemon_local_check_auth (daemon, + NULL, + "org.freedesktop.accounts.user-administration", + TRUE, + daemon_cache_user_authorized_cb, + context, + g_strdup (user_name), + g_free); + + return TRUE; +} + typedef struct { gint64 uid; gboolean remove_files; @@ -1367,4 +1548,5 @@ daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface) iface->handle_find_user_by_name = daemon_find_user_by_name; iface->handle_list_cached_users = daemon_list_cached_users; iface->get_daemon_version = daemon_get_daemon_version; + iface->handle_cache_user = daemon_cache_user; } diff --git a/src/libaccountsservice/act-user-manager.c b/src/libaccountsservice/act-user-manager.c index 0d7e433..72cdb96 100644 --- a/src/libaccountsservice/act-user-manager.c +++ b/src/libaccountsservice/act-user-manager.c @@ -2612,6 +2612,49 @@ act_user_manager_create_user (ActUserManager *manager, return user; } +/** + * act_user_manager_cache_user: + * @manager: a #ActUserManager + * @username: a user name + * @error: a #GError + * + * Caches a user account so it shows up via act_user_manager_list_users(). + * + * Returns: (transfer full): user object + */ +ActUser * +act_user_manager_cache_user (ActUserManager *manager, + const char *username, + GError **error) +{ + GError *local_error = NULL; + gboolean res; + gchar *path; + ActUser *user; + + g_debug ("ActUserManager: Caching user '%s'", + username); + + g_assert (manager->priv->accounts_proxy != NULL); + + local_error = NULL; + res = accounts_accounts_call_cache_user_sync (manager->priv->accounts_proxy, + username, + &path, + NULL, + &local_error); + if (! res) { + g_propagate_error (error, local_error); + return NULL; + } + + user = add_new_user_for_object_path (path, manager); + + g_free (path); + + return user; +} + gboolean act_user_manager_delete_user (ActUserManager *manager, ActUser *user, diff --git a/src/libaccountsservice/act-user-manager.h b/src/libaccountsservice/act-user-manager.h index 4e79ce9..d2b083a 100644 --- a/src/libaccountsservice/act-user-manager.h +++ b/src/libaccountsservice/act-user-manager.h @@ -88,6 +88,10 @@ ActUser * act_user_manager_create_user (ActUserManager * ActUserAccountType accounttype, GError **error); +ActUser * act_user_manager_cache_user (ActUserManager *manager, + const char *username, + GError **error); + gboolean act_user_manager_delete_user (ActUserManager *manager, ActUser *user, gboolean remove_files, diff --git a/src/user.c b/src/user.c index 55c238d..165c65f 100644 --- a/src/user.c +++ b/src/user.c @@ -72,6 +72,7 @@ struct User { GDBusConnection *system_bus_connection; gchar *object_path; + GHashTable *registered; Daemon *daemon; @@ -418,10 +419,15 @@ compute_object_path (User *user) } void -user_local_register (User *user) +user_local_register_for_source (User *user, + gpointer source_tag) { GError *error = NULL; + /* Already registered? */ + if (g_hash_table_size (user->registered) > 0) + return; + user->system_bus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (user->system_bus_connection == NULL) { if (error != NULL) { @@ -443,12 +449,24 @@ user_local_register (User *user) } return; } + + /* Mark us as registered */ + g_hash_table_add (user->registered, source_tag); } -void -user_local_unregister (User *user) +gboolean +user_local_unregister_for_source (User *user, + gpointer source_tag) { - g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (user)); + if (!g_hash_table_remove (user->registered, source_tag)) + return FALSE; + + if (g_hash_table_size (user->registered) == 0) { + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (user)); + return TRUE; + } + + return FALSE; } User * @@ -1704,6 +1722,7 @@ user_finalize (GObject *object) user = USER (object); + g_hash_table_destroy (user->registered); g_free (user->object_path); g_free (user->user_name); g_free (user->real_name); @@ -1883,6 +1902,7 @@ user_accounts_user_iface_init (AccountsUserIface *iface) static void user_init (User *user) { + user->registered = g_hash_table_new (g_direct_hash, g_direct_equal); user->system_bus_connection = NULL; user->object_path = NULL; user->user_name = NULL; diff --git a/src/user.h b/src/user.h index ffae648..ccbbb7e 100644 --- a/src/user.h +++ b/src/user.h @@ -58,8 +58,10 @@ void user_local_update_from_pwent (User *user, void user_local_update_from_keyfile (User *user, GKeyFile *keyfile); -void user_local_register (User *user); -void user_local_unregister (User *user); +void user_local_register_for_source (User *user, + gpointer source_tag); +gboolean user_local_unregister_for_source (User *user, + gpointer source_tag); const gchar *user_local_get_user_name (User *user); gboolean user_local_get_system_account (User *user); -- 1.7.10.1