/* -*- 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 2 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. * * Written by: Matthias Clasen */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif /* HAVE_PATHS_H */ #include #include #include #include #include #include "um-user-manager.h" enum { USERS_LOADED, USER_ADDED, USER_REMOVED, USER_CHANGED, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0, }; static void um_user_manager_class_init (UmUserManagerClass *klass); static void um_user_manager_init (UmUserManager *user_manager); static void um_user_manager_finalize (GObject *object); static gpointer user_manager_object = NULL; G_DEFINE_TYPE (UmUserManager, um_user_manager, G_TYPE_OBJECT) static void um_user_manager_class_init (UmUserManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = um_user_manager_finalize; signals [USERS_LOADED] = g_signal_new ("users-loaded", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (UmUserManagerClass, users_loaded), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals [USER_ADDED] = g_signal_new ("user-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (UmUserManagerClass, user_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, UM_TYPE_USER); signals [USER_REMOVED] = g_signal_new ("user-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (UmUserManagerClass, user_removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, UM_TYPE_USER); signals [USER_CHANGED] = g_signal_new ("user-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (UmUserManagerClass, user_changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, UM_TYPE_USER); } /* We maintain a ring for each group of users with the same real name. * We need this to pick the right display names. */ static void remove_user_from_dupe_ring (UmUserManager *manager, UmUser *user) { GList *dupes; UmUser *dup; dup = NULL; dupes = g_object_get_data (G_OBJECT (user), "dupes"); if (!dupes) { goto out; } if (dupes->next == dupes->prev) { dup = dupes->next->data; g_list_free_1 (dupes->next); g_object_set_data (G_OBJECT (dup), "dupes", NULL); } else { dupes->next->prev = dupes->prev; dupes->prev->next = dupes->next; } g_list_free_1 (dupes); g_object_set_data (G_OBJECT (user), "dupes", NULL); out: if (dup) { um_user_show_short_display_name (dup); g_signal_emit (manager, signals[USER_CHANGED], 0, dup); } um_user_show_short_display_name (user); g_signal_emit (manager, signals[USER_CHANGED], 0, user); } static gboolean match_real_name_hrfunc (gpointer key, gpointer value, gpointer user) { return (value != user && g_strcmp0 (um_user_get_real_name (user), um_user_get_real_name (value)) == 0); } static void add_user_to_dupe_ring (UmUserManager *manager, UmUser *user) { UmUser *dup; GList *dupes; GList *l; dup = g_hash_table_find (manager->user_by_object_path, match_real_name_hrfunc, user); if (!dup) { return; } dupes = g_object_get_data (G_OBJECT (dup), "dupes"); if (!dupes) { dupes = g_list_append (NULL, dup); g_object_set_data (G_OBJECT (dup), "dupes", dupes); dupes->next = dupes->prev = dupes; } else { dup = NULL; } l = g_list_append (NULL, user); g_object_set_data (G_OBJECT (user), "dupes", l); l->prev = dupes->prev; dupes->prev->next = l; l->next = dupes; dupes->prev = l; if (dup) { um_user_show_full_display_name (dup); g_signal_emit (manager, signals[USER_CHANGED], 0, dup); } um_user_show_full_display_name (user); g_signal_emit (manager, signals[USER_CHANGED], 0, user); } static void user_changed_handler (UmUser *user, UmUserManager *manager) { remove_user_from_dupe_ring (manager, user); add_user_to_dupe_ring (manager, user); g_signal_emit (manager, signals[USER_CHANGED], 0, user); } static void user_added_handler (UmUserManager *manager, const char *object_path) { UmUser *user; if (g_hash_table_lookup (manager->user_by_object_path, object_path)) return; user = um_user_new_from_object_path (object_path); if (!user) return; if (um_user_is_system_account (user)) { g_object_unref (user); return; } add_user_to_dupe_ring (manager, user); g_signal_connect (user, "changed", G_CALLBACK (user_changed_handler), manager); g_hash_table_insert (manager->user_by_object_path, g_strdup (um_user_get_object_path (user)), g_object_ref (user)); g_hash_table_insert (manager->user_by_name, g_strdup (um_user_get_user_name (user)), g_object_ref (user)); g_signal_emit (manager, signals[USER_ADDED], 0, user); g_object_unref (user); } static void user_deleted_handler (UmUserManager *manager, const char *object_path) { UmUser *user; user = g_hash_table_lookup (manager->user_by_object_path, object_path); if (!user) return; g_object_ref (user); g_signal_handlers_disconnect_by_func (user, user_changed_handler, manager); remove_user_from_dupe_ring (manager, user); g_hash_table_remove (manager->user_by_object_path, um_user_get_object_path (user)); g_hash_table_remove (manager->user_by_name, um_user_get_user_name (user)); g_signal_emit (manager, signals[USER_REMOVED], 0, user); g_object_unref (user); } static void manager_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, UmUserManager *manager) { if (strcmp (signal_name, "UserAdded") == 0) { if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) { gchar *object_path; g_variant_get (parameters, "(&o)", &object_path); user_added_handler (manager, object_path); } } else if (strcmp (signal_name, "UserDeleted") == 0) { if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) { gchar *object_path; g_variant_get (parameters, "(&o)", &object_path); user_deleted_handler (manager, object_path); } } } static void got_users (GObject *object, GAsyncResult *res, gpointer data) { UmUserManager *manager = data; GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error); if (!result) { manager->no_service = TRUE; g_error_free (error); goto done; } if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(ao)"))) { GVariantIter *iter; gchar *object_path; g_variant_get (result, "(ao)", &iter); while (g_variant_iter_loop (iter, "&o", &object_path)) user_added_handler (manager, object_path); g_variant_iter_free (iter); } g_variant_unref (result); done: g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0); } static void get_users (UmUserManager *manager) { g_debug ("calling 'ListCachedUsers'"); g_dbus_proxy_call (manager->proxy, "ListCachedUsers", g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, got_users, manager); } static void um_user_manager_init (UmUserManager *manager) { GError *error = NULL; GDBusConnection *bus; manager->user_by_object_path = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); manager->user_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (bus == NULL) { g_warning ("Couldn't connect to system bus: %s", error->message); g_error_free (error); return; } manager->proxy = g_dbus_proxy_new_sync (bus, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts", NULL, &error); if (manager->proxy == NULL) { g_warning ("Couldn't get accounts proxy: %s", error->message); g_error_free (error); return; } g_signal_connect (manager->proxy, "g-signal", G_CALLBACK (manager_signal_cb), manager); get_users (manager); } static void clear_dup (gpointer key, gpointer value, gpointer data) { GList *dupes; /* don't bother maintaining the ring, we're destroying the * entire hash table anyway */ dupes = g_object_get_data (G_OBJECT (value), "dupes"); if (dupes) { g_list_free_1 (dupes); g_object_set_data (G_OBJECT (value), "dupes", NULL); } } static void um_user_manager_finalize (GObject *object) { UmUserManager *manager; manager = UM_USER_MANAGER (object); g_hash_table_foreach (manager->user_by_object_path, clear_dup, NULL); g_hash_table_destroy (manager->user_by_object_path); g_hash_table_destroy (manager->user_by_name); g_object_unref (manager->proxy); G_OBJECT_CLASS (um_user_manager_parent_class)->finalize (object); } UmUserManager * um_user_manager_ref_default (void) { if (user_manager_object != NULL) { g_object_ref (user_manager_object); } else { user_manager_object = g_object_new (UM_TYPE_USER_MANAGER, NULL); g_object_add_weak_pointer (user_manager_object, (gpointer *) &user_manager_object); } return UM_USER_MANAGER (user_manager_object); } typedef struct { UmUserManager *manager; gchar *user_name; GAsyncReadyCallback callback; gpointer data; GDestroyNotify destroy; } AsyncUserOpData; static void async_user_op_data_free (gpointer d) { AsyncUserOpData *data = d; g_object_unref (data->manager); g_free (data->user_name); if (data->destroy) data->destroy (data->data); g_free (data); } /* Used for both create_user and cache_user */ static void user_call_done (GObject *proxy, GAsyncResult *r, gpointer user_data) { AsyncUserOpData *data = user_data; GSimpleAsyncResult *res; GVariant *result; GError *error = NULL; gchar *remote; res = g_simple_async_result_new (G_OBJECT (data->manager), data->callback, data->data, um_user_manager_create_user); result = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), r, &error); if (!result) { /* dbus-glib fail: * We have to translate the errors manually here, since * calling dbus_g_error_has_name on the error returned in * um_user_manager_create_user_finish doesn't work. */ remote = g_dbus_error_get_remote_error (error); if (g_dbus_error_is_remote_error (error) && strcmp (remote, "org.freedesktop.Accounts.Error.PermissionDenied") == 0) { g_simple_async_result_set_error (res, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized"); } if (g_dbus_error_is_remote_error (error) && strcmp (remote, "org.freedesktop.Accounts.Error.UserExists") == 0) { g_simple_async_result_set_error (res, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_USER_EXISTS, _("A user with name '%s' already exists."), data->user_name); } else if (g_dbus_error_is_remote_error (error) && strcmp (remote, "org.freedesktop.Accounts.Error.UserDoesNotExist") == 0) { g_simple_async_result_set_error (res, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST, _("No user with the name '%s' exists."), data->user_name); } else { g_simple_async_result_set_from_error (res, error); } g_error_free (error); g_free (remote); } else { if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(o)"))) { gchar *path; g_variant_get (result, "(o)", &path); g_simple_async_result_set_op_res_gpointer (res, path, g_free); } else g_simple_async_result_set_error (res, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_FAILED, "Got invalid response from AccountsService"); g_variant_unref (result); } data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data); async_user_op_data_free (data); g_object_unref (res); } gboolean um_user_manager_create_user_finish (UmUserManager *manager, GAsyncResult *result, UmUser **user, GError **error) { gchar *path; GSimpleAsyncResult *res; res = G_SIMPLE_ASYNC_RESULT (result); *user = NULL; if (g_simple_async_result_propagate_error (res, error)) { return FALSE; } path = g_simple_async_result_get_op_res_gpointer (res); *user = g_hash_table_lookup (manager->user_by_object_path, path); return TRUE; } void um_user_manager_create_user (UmUserManager *manager, const char *user_name, const char *real_name, gint account_type, GCancellable *cancellable, GAsyncReadyCallback done, gpointer done_data, GDestroyNotify destroy) { AsyncUserOpData *data; data = g_new0 (AsyncUserOpData, 1); data->manager = g_object_ref (manager); data->user_name = g_strdup (user_name); data->callback = done; data->data = done_data; data->destroy = destroy; g_dbus_proxy_call (manager->proxy, "CreateUser", g_variant_new ("(ssi)", user_name, real_name, account_type), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, user_call_done, data); } gboolean um_user_manager_cache_user_finish (UmUserManager *manager, GAsyncResult *result, UmUser **user, GError **error) { gchar *path; GSimpleAsyncResult *res; res = G_SIMPLE_ASYNC_RESULT (result); *user = NULL; if (g_simple_async_result_propagate_error (res, error)) { return FALSE; } path = g_simple_async_result_get_op_res_gpointer (res); *user = g_hash_table_lookup (manager->user_by_object_path, path); return TRUE; } void um_user_manager_cache_user (UmUserManager *manager, const char *user_name, GCancellable *cancellable, GAsyncReadyCallback done, gpointer done_data, GDestroyNotify destroy) { AsyncUserOpData *data; data = g_new0 (AsyncUserOpData, 1); data->manager = g_object_ref (manager); data->user_name = g_strdup (user_name); data->callback = done; data->data = done_data; data->destroy = destroy; g_dbus_proxy_call (manager->proxy, "CacheUser", g_variant_new ("(s)", user_name), G_DBUS_CALL_FLAGS_NONE, -1, cancellable, user_call_done, data); } static void delete_user_done (GObject *proxy, GAsyncResult *r, gpointer user_data) { AsyncUserOpData *data = user_data; GSimpleAsyncResult *res; GVariant *result; GError *error = NULL; res = g_simple_async_result_new (G_OBJECT (data->manager), data->callback, data->data, um_user_manager_delete_user); result = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), r, &error); if (!result) { if (g_dbus_error_is_remote_error (error) && strcmp (g_dbus_error_get_remote_error(error), "org.freedesktop.Accounts.Error.PermissionDenied") == 0) { g_simple_async_result_set_error (res, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized"); } else if (g_dbus_error_is_remote_error (error) && strcmp (g_dbus_error_get_remote_error(error), "org.freedesktop.Accounts.Error.UserExists") == 0) { g_simple_async_result_set_error (res, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST, _("This user does not exist.")); } else { g_simple_async_result_set_from_error (res, error); g_error_free (error); } } else g_variant_unref (result); data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data); async_user_op_data_free (data); g_object_unref (res); } gboolean um_user_manager_delete_user_finish (UmUserManager *manager, GAsyncResult *result, GError **error) { GSimpleAsyncResult *res; res = G_SIMPLE_ASYNC_RESULT (result); if (g_simple_async_result_propagate_error (res, error)) { return FALSE; } return TRUE; } void um_user_manager_delete_user (UmUserManager *manager, UmUser *user, gboolean remove_files, GAsyncReadyCallback done, gpointer done_data, GDestroyNotify destroy) { AsyncUserOpData *data; data = g_new0 (AsyncUserOpData, 1); data->manager = g_object_ref (manager); data->callback = done; data->data = done_data; data->destroy = destroy; g_dbus_proxy_call (manager->proxy, "DeleteUser", g_variant_new ("(xb)", (gint64) um_user_get_uid (user), remove_files), G_DBUS_CALL_FLAGS_NONE, -1, NULL, delete_user_done, data); } GSList * um_user_manager_list_users (UmUserManager *manager) { GSList *list = NULL; GHashTableIter iter; gpointer value; g_hash_table_iter_init (&iter, manager->user_by_name); while (g_hash_table_iter_next (&iter, NULL, &value)) { list = g_slist_prepend (list, value); } return g_slist_sort (list, (GCompareFunc) um_user_collate); } UmUser * um_user_manager_get_user (UmUserManager *manager, const gchar *name) { return g_hash_table_lookup (manager->user_by_name, name); } UmUser * um_user_manager_get_user_by_id (UmUserManager *manager, uid_t uid) { struct passwd *pwent; pwent = getpwuid (uid); if (!pwent) { return NULL; } return um_user_manager_get_user (manager, pwent->pw_name); } gboolean um_user_manager_no_service (UmUserManager *manager) { return manager->no_service; } GQuark um_user_manager_error_quark (void) { return g_quark_from_static_string ("um-user-manager-error-quark"); }