/* -*- 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. * * 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; um_user_show_short_display_name (user); dupes = g_object_get_data (G_OBJECT (user), "dupes"); if (dupes == NULL) { return; } if (dupes->next == dupes->prev) { dup = dupes->next->data; um_user_show_short_display_name (dup); g_signal_emit (manager, signals[USER_CHANGED], 0, dup); 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); } 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; } um_user_show_full_display_name (user); dupes = g_object_get_data (G_OBJECT (dup), "dupes"); if (!dupes) { um_user_show_full_display_name (dup); g_signal_emit (manager, signals[USER_CHANGED], 0, dup); dupes = g_list_append (NULL, dup); g_object_set_data (G_OBJECT (dup), "dupes", dupes); dupes->next = dupes->prev = dupes; } 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; } 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 (DBusGProxy *proxy, const char *object_path, gpointer user_data) { UmUserManager *manager = UM_USER_MANAGER (user_data); 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 (DBusGProxy *proxy, const char *object_path, gpointer user_data) { UmUserManager *manager = UM_USER_MANAGER (user_data); 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 add_user (const gchar *object_path, UmUserManager *manager) { user_added_handler (NULL, object_path, manager); } static void got_users (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer data) { UmUserManager *manager = data; GError *error = NULL; GPtrArray *paths; if (!dbus_g_proxy_end_call (proxy, call_id, &error, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &paths, G_TYPE_INVALID)) { manager->no_service = TRUE; g_error_free (error); goto done; } g_ptr_array_foreach (paths, (GFunc)add_user, manager); g_ptr_array_foreach (paths, (GFunc)g_free, NULL); g_ptr_array_free (paths, TRUE); done: g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0); } static void get_users (UmUserManager *manager) { g_debug ("calling 'ListCachedUsers'"); dbus_g_proxy_begin_call (manager->proxy, "ListCachedUsers", got_users, manager, NULL, G_TYPE_INVALID); } static void um_user_manager_init (UmUserManager *manager) { GError *error = NULL; 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); manager->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); if (manager->bus == NULL) { g_warning ("Couldn't connect to system bus: %s", error->message); g_error_free (error); goto error; } manager->proxy = dbus_g_proxy_new_for_name (manager->bus, "org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts"); dbus_g_proxy_add_signal (manager->proxy, "UserAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); dbus_g_proxy_add_signal (manager->proxy, "UserDeleted", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); dbus_g_proxy_connect_signal (manager->proxy, "UserAdded", G_CALLBACK (user_added_handler), manager, NULL); dbus_g_proxy_connect_signal (manager->proxy, "UserDeleted", G_CALLBACK (user_deleted_handler), manager, NULL); get_users (manager); error: ; } 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_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); } static void create_user_done (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data) { AsyncUserOpData *data = user_data; gchar *path; GError *error; GSimpleAsyncResult *res; res = g_simple_async_result_new (G_OBJECT (data->manager), data->callback, data->data, um_user_manager_create_user); error = NULL; if (!dbus_g_proxy_end_call (proxy, call_id, &error, DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID)) { /* 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. */ if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.PermissionDenied")) { g_simple_async_result_set_error (res, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized"); } else if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.UserExists")) { 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 { g_simple_async_result_set_from_error (res, error); } g_error_free (error); } else { g_simple_async_result_set_op_res_gpointer (res, path, g_free); } data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data); } 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, 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; dbus_g_proxy_begin_call (manager->proxy, "CreateUser", create_user_done, data, async_user_op_data_free, G_TYPE_STRING, user_name, G_TYPE_STRING, real_name, G_TYPE_INT, account_type, G_TYPE_INVALID); } static void delete_user_done (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data) { AsyncUserOpData *data = user_data; GError *error; GSimpleAsyncResult *res; res = g_simple_async_result_new (G_OBJECT (data->manager), data->callback, data->data, um_user_manager_delete_user); error = NULL; if (!dbus_g_proxy_end_call (proxy, call_id, &error, G_TYPE_INVALID)) { if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.PermissionDenied")) { g_simple_async_result_set_error (res, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized"); } else if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.UserDoesntExists")) { 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); } } data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data); } 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; dbus_g_proxy_begin_call (manager->proxy, "DeleteUser", delete_user_done, data, async_user_op_data_free, G_TYPE_INT64, um_user_get_uid (user), G_TYPE_BOOLEAN, remove_files, G_TYPE_INVALID); } 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"); }