When used with accountsservice >= 0.6.9, this fixes the problem where, when you click the unlock button on the user panel, the root user pops up in the user list, unexpectedly. When used with 0.6.8 it silently does nothing, since the system-account property is not available. https://bugzilla.gnome.org/show_bug.cgi?id=647487
631 lines
21 KiB
C
631 lines
21 KiB
C
/* -*- 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 <mclasen@redhat.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <pwd.h>
|
|
|
|
#ifdef HAVE_PATHS_H
|
|
#include <paths.h>
|
|
#endif /* HAVE_PATHS_H */
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <glib/gstdio.h>
|
|
#include <glib-object.h>
|
|
#include <gio/gio.h>
|
|
|
|
#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");
|
|
}
|