/* -*- 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Written by: Matthias Clasen */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "daemon.h" #include "daemon-glue.h" #include "util.h" #define PATH_PASSWD "/etc/passwd" #define PATH_SHADOW "/etc/shadow" #define PATH_LOGIN_DEFS "/etc/login.defs" #ifndef FALLBACK_MINIMAL_UID #define FALLBACK_MINIMAL_UID 500 #endif #define USERDIR LOCALSTATEDIR "/lib/AccountsService/users" static const char *default_excludes[] = { "bin", "root", "daemon", "adm", "lp", "sync", "shutdown", "halt", "mail", "news", "uucp", "operator", "nobody", "nobody4", "noaccess", "postgres", "pvm", "rpm", "nfsnobody", "pcap", NULL }; enum { PROP_0, PROP_DAEMON_VERSION }; enum { USER_ADDED, USER_REMOVED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; struct DaemonPrivate { DBusGConnection *bus_connection; DBusGProxy *bus_proxy; GHashTable *users; GHashTable *exclusions; uid_t minimal_uid; User *autologin; GFileMonitor *passwd_monitor; GFileMonitor *shadow_monitor; guint reload_id; guint autologin_id; PolkitAuthority *authority; }; static void daemon_finalize (GObject *object); G_DEFINE_TYPE (Daemon, daemon, G_TYPE_OBJECT) #define DAEMON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_DAEMON, DaemonPrivate)) GQuark error_quark (void) { static GQuark ret = 0; if (ret == 0) { ret = g_quark_from_static_string ("accounts_error"); } return ret; } #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } GType error_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { ENUM_ENTRY (ERROR_FAILED, "Failed"), ENUM_ENTRY (ERROR_USER_EXISTS, "UserExists"), ENUM_ENTRY (ERROR_USER_DOES_NOT_EXIST, "UserDoesntExist"), ENUM_ENTRY (ERROR_PERMISSION_DENIED, "PermissionDenied"), ENUM_ENTRY (ERROR_NOT_SUPPORTED, "NotSupported"), { 0, 0, 0 } }; g_assert (NUM_ERRORS == G_N_ELEMENTS (values) - 1); etype = g_enum_register_static ("Error", values); } return etype; } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_DAEMON_VERSION: g_value_set_string (value, VERSION); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void daemon_class_init (DaemonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = daemon_finalize; object_class->get_property = get_property; g_type_class_add_private (klass, sizeof (DaemonPrivate)); signals[USER_ADDED] = g_signal_new ("user-added", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, DBUS_TYPE_G_OBJECT_PATH); signals[USER_REMOVED] = g_signal_new ("user-deleted", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, DBUS_TYPE_G_OBJECT_PATH); dbus_g_object_type_install_info (TYPE_DAEMON, &dbus_glib_daemon_object_info); dbus_g_error_domain_register (ERROR, "org.freedesktop.Accounts.Error", TYPE_ERROR); g_object_class_install_property (object_class, PROP_DAEMON_VERSION, g_param_spec_string ("daemon-version", "Daemon version", "Daemon version", NULL, G_PARAM_READABLE)); } gboolean daemon_local_user_is_excluded (Daemon *daemon, const gchar *username, uid_t uid) { if (uid < daemon->priv->minimal_uid) { return TRUE; } if (g_hash_table_lookup (daemon->priv->exclusions, username)) { return TRUE; } return FALSE; } static void reload_wtmp_history (Daemon *daemon) { struct utmpx *wtmp_entry; GHashTable *login_frequency_hash; GHashTableIter iter; gpointer key, value; utmpxname(_PATH_WTMPX); setutxent (); login_frequency_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); while ((wtmp_entry = getutxent ())) { if (wtmp_entry->ut_type != USER_PROCESS) continue; if (wtmp_entry->ut_user[0] == 0) continue; if (daemon_local_user_is_excluded (daemon, wtmp_entry->ut_user, daemon->priv->minimal_uid)) { g_debug ("excluding user '%s'", wtmp_entry->ut_user); continue; } if (!g_hash_table_lookup_extended (login_frequency_hash, wtmp_entry->ut_user, &key, &value)) { value = GUINT_TO_POINTER (0); g_hash_table_insert (login_frequency_hash, g_strdup (wtmp_entry->ut_user), GUINT_TO_POINTER (0)); } else { guint frequency; frequency = GPOINTER_TO_UINT (value) + 1; g_hash_table_insert (login_frequency_hash, key, GUINT_TO_POINTER (frequency)); } } endutxent (); g_hash_table_iter_init (&iter, login_frequency_hash); while (g_hash_table_iter_next (&iter, &key, &value)) { User *user; char *username = (char *) key; guint64 frequency = (guint64) GPOINTER_TO_UINT (value); user = daemon_local_find_user_by_name (daemon, username); if (user == NULL) { g_debug ("unable to lookup user '%s'", username); continue; } g_object_set (user, "login-frequency", frequency, NULL); } g_hash_table_foreach (login_frequency_hash, (GHFunc) g_free, NULL); g_hash_table_unref (login_frequency_hash); } static void listify_hash_values_hfunc (gpointer key, gpointer value, gpointer user_data) { GSList **list = user_data; *list = g_slist_prepend (*list, value); } static gint compare_user_name (gconstpointer a, gconstpointer b) { User *user = (User *)a; const gchar *name = b; return g_strcmp0 (user_local_get_user_name (user), name); } static void reload_passwd (Daemon *daemon) { struct passwd *pwent; GSList *old_users; GSList *new_users; GSList *list; FILE *fp; User *user = NULL; old_users = NULL; new_users = NULL; errno = 0; fp = fopen (PATH_PASSWD, "r"); if (fp == NULL) { g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno)); goto out; } g_hash_table_foreach (daemon->priv->users, listify_hash_values_hfunc, &old_users); g_slist_foreach (old_users, (GFunc) g_object_ref, NULL); for (pwent = fgetpwent (fp); pwent != NULL; pwent = fgetpwent (fp)) { /* Skip users below MINIMAL_UID... */ if (daemon_local_user_is_excluded (daemon, pwent->pw_name, pwent->pw_uid)) { g_debug ("skipping user: %s", pwent->pw_name); continue; } /* ignore duplicate entries */ if (g_slist_find_custom (new_users, pwent->pw_name, compare_user_name)) { continue; } user = g_hash_table_lookup (daemon->priv->users, pwent->pw_name); if (user == NULL) { user = user_local_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_local_update_from_pwent (user, pwent); new_users = g_slist_prepend (new_users, user); } /* Go through and handle removed users */ for (list = old_users; list; list = list->next) { user = list->data; if (! g_slist_find (new_users, user)) { g_signal_emit (daemon, signals[USER_REMOVED], 0, user_local_get_object_path (user)); user_local_unregister (user); g_hash_table_remove (daemon->priv->users, user_local_get_user_name (user)); } } /* Go through and handle added users or update display names */ for (list = new_users; list; list = list->next) { user = list->data; if (!g_slist_find (old_users, user)) { user_local_register (user); g_hash_table_insert (daemon->priv->users, g_strdup (user_local_get_user_name (user)), g_object_ref (user)); g_signal_emit (daemon, signals[USER_ADDED], 0, user_local_get_object_path (user)); } } out: /* Cleanup */ fclose (fp); 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); g_slist_foreach (old_users, (GFunc) g_object_unref, NULL); g_slist_free (old_users); } static void reload_data (Daemon *daemon) { GHashTableIter iter; const gchar *name; User *user; GKeyFile *key_file; gchar *filename; g_hash_table_iter_init (&iter, daemon->priv->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_local_update_from_keyfile (user, key_file); g_key_file_free (key_file); g_free (filename); } } static void reload_users (Daemon *daemon) { reload_wtmp_history (daemon); reload_passwd (daemon); reload_data (daemon); } 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; User *user; daemon->priv->autologin_id = 0; if (!load_autologin (daemon, &name, &enabled, &error)) { g_debug ("failed to load gdms custom.conf: %s", error->message); g_error_free (error); g_free (name); return FALSE; } if (enabled) { g_debug ("automatic login is enabled for '%s'\n", name); user = daemon_local_find_user_by_name (daemon, name); g_object_set (user, "automatic-login", TRUE, NULL); daemon->priv->autologin = g_object_ref (user); } else { g_debug ("automatic login is disabled\n"); } g_free (name); return FALSE; } static void queue_reload_users (Daemon *daemon) { if (daemon->priv->reload_id > 0) { return; } daemon->priv->reload_id = g_idle_add ((GSourceFunc)reload_users_timeout, daemon); } static void queue_reload_autologin (Daemon *daemon) { if (daemon->priv->autologin_id > 0) { return; } daemon->priv->autologin_id = g_idle_add ((GSourceFunc)reload_autologin_timeout, daemon); } static void on_passwd_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, Daemon *daemon) { if (event_type != G_FILE_MONITOR_EVENT_CHANGED && event_type != G_FILE_MONITOR_EVENT_CREATED) { return; } reload_users (daemon); } static uid_t get_minimal_uid (void) { GError *error; char *contents; gboolean contents_loaded; const char *uid_min_string, *start_of_uid_string; char *end; uid_t uid = FALLBACK_MINIMAL_UID; guint64 uid_as_number; error = NULL; contents = NULL; contents_loaded = g_file_get_contents (PATH_LOGIN_DEFS, &contents, NULL, &error); if (!contents_loaded) { g_debug ("unable to read " PATH_LOGIN_DEFS ": %s", error->message); g_error_free (error); goto out; } uid_min_string = strstr (contents, "UID_MIN"); if (uid_min_string == NULL || (uid_min_string != contents && uid_min_string[-1] != '\n')) { g_debug (PATH_LOGIN_DEFS " does not have a UID_MIN field"); goto out; } start_of_uid_string = uid_min_string + strlen ("UID_MIN"); if (start_of_uid_string == '\0') { g_debug (PATH_LOGIN_DEFS " contains UID_MIN key with no value"); goto out; } uid_as_number = g_ascii_strtoll (start_of_uid_string, &end, 10); if (!g_ascii_isspace (*end) && *end != '\0') { g_debug (PATH_LOGIN_DEFS " contains non-numerical value for UID_MIN"); goto out; } if (uid_as_number < 0 || ((uid_t) uid_as_number) != uid_as_number) { g_debug (PATH_LOGIN_DEFS " contains out-of-range value for UID_MIN"); goto out; } uid = (uid_t) uid_as_number; out: g_free (contents); return uid; } static void daemon_init (Daemon *daemon) { gint i; GFile *file; GError *error; daemon->priv = DAEMON_GET_PRIVATE (daemon); daemon->priv->minimal_uid = get_minimal_uid (); daemon->priv->exclusions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (i = 0; default_excludes[i] != NULL; i++) { g_hash_table_insert (daemon->priv->exclusions, g_strdup (default_excludes[i]), GUINT_TO_POINTER (TRUE)); } daemon->priv->users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); file = g_file_new_for_path (PATH_PASSWD); daemon->priv->passwd_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); g_object_unref (file); file = g_file_new_for_path (PATH_SHADOW); daemon->priv->shadow_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); g_object_unref (file); if (daemon->priv->passwd_monitor != NULL) { g_signal_connect (daemon->priv->passwd_monitor, "changed", G_CALLBACK (on_passwd_monitor_changed), daemon); } else { g_warning ("Unable to monitor %s: %s", PATH_PASSWD, error->message); g_error_free (error); } if (daemon->priv->shadow_monitor != NULL) { g_signal_connect (daemon->priv->shadow_monitor, "changed", G_CALLBACK (on_passwd_monitor_changed), daemon); } else { g_warning ("Unable to monitor %s: %s", PATH_SHADOW, error->message); g_error_free (error); } 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) dbus_g_connection_unref (daemon->priv->bus_connection); g_hash_table_destroy (daemon->priv->users); G_OBJECT_CLASS (daemon_parent_class)->finalize (object); } static gboolean register_accounts_daemon (Daemon *daemon) { DBusConnection *connection; DBusError dbus_error; GError *error = NULL; daemon->priv->authority = polkit_authority_get (); error = NULL; daemon->priv->bus_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &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; } connection = dbus_g_connection_get_connection (daemon->priv->bus_connection); dbus_g_connection_register_g_object (daemon->priv->bus_connection, "/org/freedesktop/Accounts", G_OBJECT (daemon)); daemon->priv->bus_proxy = dbus_g_proxy_new_for_name (daemon->priv->bus_connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); dbus_error_init (&dbus_error); /* need to listen to NameOwnerChanged */ dbus_bus_add_match (connection, "type='signal'" ",interface='"DBUS_INTERFACE_DBUS"'" ",sender='"DBUS_SERVICE_DBUS"'" ",member='NameOwnerChanged'", &dbus_error); if (dbus_error_is_set (&dbus_error)) { g_warning ("Cannot add match rule: %s: %s", dbus_error.name, dbus_error.message); dbus_error_free (&dbus_error); goto error; } return TRUE; error: return FALSE; } Daemon * daemon_new (void) { Daemon *daemon; daemon = DAEMON (g_object_new (TYPE_DAEMON, NULL)); if (!register_accounts_daemon (DAEMON (daemon))) { g_object_unref (daemon); goto error; } return daemon; error: return NULL; } static void throw_error (DBusGMethodInvocation *context, gint error_code, const gchar *format, ...) { GError *error; va_list args; gchar *message; va_start (args, format); message = g_strdup_vprintf (format, args); va_end (args); error = g_error_new (ERROR, error_code, "%s", message); dbus_g_method_return_error (context, error); g_error_free (error); g_free (message); } static User * add_new_user_for_pwent (Daemon *daemon, struct passwd *pwent) { User *user; user = user_local_new (daemon, pwent->pw_uid); user_local_update_from_pwent (user, pwent); user_local_register (user); g_hash_table_insert (daemon->priv->users, g_strdup (user_local_get_user_name (user)), user); g_signal_emit (daemon, signals[USER_ADDED], 0, user_local_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) user = add_new_user_for_pwent (daemon, pwent); 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", name); return NULL; } user = g_hash_table_lookup (daemon->priv->users, pwent->pw_name); if (user == NULL) user = add_new_user_for_pwent (daemon, pwent); return user; } gboolean daemon_find_user_by_id (Daemon *daemon, gint64 uid, DBusGMethodInvocation *context) { User *user; user = daemon_local_find_user_by_id (daemon, uid); if (user) { dbus_g_method_return (context, user_local_get_object_path (user)); } else { throw_error (context, ERROR_FAILED, "Failed to look up user with uid %d.", (int)uid); } return TRUE; } gboolean daemon_find_user_by_name (Daemon *daemon, const gchar *name, DBusGMethodInvocation *context) { User *user; user = daemon_local_find_user_by_name (daemon, name); if (user) { dbus_g_method_return (context, user_local_get_object_path (user)); } else { throw_error (context, ERROR_FAILED, "Failed to look up user with name %s.", name); } return TRUE; } typedef struct { Daemon *daemon; DBusGMethodInvocation *context; } ListUserData; static ListUserData * list_user_data_new (Daemon *daemon, DBusGMethodInvocation *context) { ListUserData *data; data = g_new0 (ListUserData, 1); data->daemon = g_object_ref (daemon); data->context = context; return data; } 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; 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_local_get_uid (user); if (!daemon_local_user_is_excluded (data->daemon, name, uid)) { g_debug ("user %s %ld not excluded\n", name, uid); g_ptr_array_add (object_paths, g_strdup (user_local_get_object_path (user))); } } dbus_g_method_return (data->context, object_paths); g_ptr_array_foreach (object_paths, (GFunc) g_free, NULL); g_ptr_array_free (object_paths, TRUE); list_user_data_free (data); return FALSE; } gboolean daemon_list_cached_users (Daemon *daemon, DBusGMethodInvocation *context) { 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); } return TRUE; } typedef struct { gchar *user_name; gchar *real_name; gint account_type; } CreateUserData; static void create_data_free (gpointer data) { CreateUserData *cd = data; g_free (cd->user_name); g_free (cd->real_name); g_free (cd); } static void daemon_create_user_authorized_cb (Daemon *daemon, User *dummy, DBusGMethodInvocation *context, gpointer data) { CreateUserData *cd = data; User *user; GError *error; gchar *argv[9]; if (getpwnam (cd->user_name) != NULL) { throw_error (context, ERROR_USER_EXISTS, "A user with name '%s' already exists", cd->user_name); return; } sys_log (context, "create user '%s'", cd->user_name); argv[0] = "/usr/sbin/useradd"; argv[1] = "-m"; argv[2] = "-c"; argv[3] = cd->real_name; if (cd->account_type == ACCOUNT_TYPE_ADMINISTRATOR) { argv[4] = "-G"; argv[5] = "wheel"; argv[6] = "--"; argv[7] = cd->user_name; argv[8] = NULL; } else if (cd->account_type == ACCOUNT_TYPE_STANDARD) { argv[4] = "--"; argv[5] = cd->user_name; argv[6] = NULL; } else { throw_error (context, ERROR_FAILED, "Don't know how to add user of type %d", cd->account_type); return; } 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; } user = daemon_local_find_user_by_name (daemon, cd->user_name); dbus_g_method_return (context, user_local_get_object_path (user)); } gboolean daemon_create_user (Daemon *daemon, const gchar *user_name, const gchar *real_name, gint account_type, DBusGMethodInvocation *context) { CreateUserData *data; data = g_new0 (CreateUserData, 1); data->user_name = g_strdup (user_name); data->real_name = g_strdup (real_name); data->account_type = account_type; daemon_local_check_auth (daemon, NULL, "org.freedesktop.accounts.user-administration", TRUE, daemon_create_user_authorized_cb, context, data, (GDestroyNotify)create_data_free); return TRUE; } typedef struct { gint64 uid; gboolean remove_files; } DeleteUserData; static void daemon_delete_user_authorized_cb (Daemon *daemon, User *dummy, DBusGMethodInvocation *context, gpointer data) { DeleteUserData *ud = data; GError *error; gchar *filename; struct passwd *pwent; gchar *argv[5]; 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); argv[0] = "/usr/sbin/userdel"; if (ud->remove_files) { argv[1] = "-r"; argv[2] = "--"; argv[3] = pwent->pw_name; argv[4] = NULL; } else { argv[1] = "--"; argv[2] = pwent->pw_name; argv[3] = 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; } filename = g_build_filename (USERDIR, pwent->pw_name, NULL); g_remove (filename); g_free (filename); dbus_g_method_return (context); } gboolean daemon_delete_user (Daemon *daemon, gint64 uid, gboolean remove_files, DBusGMethodInvocation *context) { DeleteUserData *data; data = g_new0 (DeleteUserData, 1); data->uid = uid; data->remove_files = remove_files; daemon_local_check_auth (daemon, NULL, "org.freedesktop.accounts.user-administration", TRUE, daemon_delete_user_authorized_cb, context, data, (GDestroyNotify)g_free); return TRUE; } typedef struct { Daemon *daemon; User *user; AuthorizedCallback authorized_cb; DBusGMethodInvocation *context; gpointer data; GDestroyNotify destroy_notify; } CheckAuthData; static void check_auth_data_free (CheckAuthData *data) { g_object_unref (data->daemon); if (data->user) g_object_unref (data->user); if (data->destroy_notify) (*data->destroy_notify) (data->data); g_free (data); } static void check_auth_cb (PolkitAuthority *authority, GAsyncResult *res, gpointer data) { CheckAuthData *cad = data; PolkitAuthorizationResult *result; GError *error; gboolean is_authorized; is_authorized = FALSE; error = NULL; result = polkit_authority_check_authorization_finish (authority, res, &error); if (error) { throw_error (cad->context, ERROR_PERMISSION_DENIED, "Not authorized: %s", error->message); g_error_free (error); } else { if (polkit_authorization_result_get_is_authorized (result)) { is_authorized = TRUE; } else if (polkit_authorization_result_get_is_challenge (result)) { throw_error (cad->context, ERROR_PERMISSION_DENIED, "Authentication is required"); } else { throw_error (cad->context, ERROR_PERMISSION_DENIED, "Not authorized"); } g_object_unref (result); } if (is_authorized) { (* cad->authorized_cb) (cad->daemon, cad->user, cad->context, cad->data); } check_auth_data_free (data); } void daemon_local_check_auth (Daemon *daemon, User *user, const gchar *action_id, gboolean allow_interaction, AuthorizedCallback authorized_cb, DBusGMethodInvocation *context, gpointer authorized_cb_data, GDestroyNotify destroy_notify) { CheckAuthData *data; PolkitSubject *subject; PolkitCheckAuthorizationFlags flags; data = g_new0 (CheckAuthData, 1); data->daemon = g_object_ref (daemon); if (user) data->user = g_object_ref (user); data->context = context; data->authorized_cb = authorized_cb; data->data = authorized_cb_data; data->destroy_notify = destroy_notify; subject = polkit_system_bus_name_new (dbus_g_method_get_sender (context)); flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; if (allow_interaction) flags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; polkit_authority_check_authorization (daemon->priv->authority, subject, action_id, NULL, flags, NULL, (GAsyncReadyCallback) check_auth_cb, data); g_object_unref (subject); } gboolean load_autologin (Daemon *daemon, gchar **name, gboolean *enabled, GError **error) { GKeyFile *keyfile; const gchar *filename; GError *local_error; gchar *string; filename = "/etc/gdm/custom.conf"; keyfile = g_key_file_new (); if (!g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_KEEP_COMMENTS, error)) { g_key_file_free (keyfile); return FALSE; } local_error = NULL; string = g_key_file_get_string (keyfile, "daemon", "AutomaticLoginEnable", &local_error); if (local_error) { g_propagate_error (error, local_error); g_key_file_free (keyfile); g_free (string); return FALSE; } if (string != NULL && (g_ascii_strcasecmp (string, "true") == 0 || strcmp (string, "1") == 0)) { *enabled = TRUE; } else { *enabled = FALSE; } g_free (string); *name = g_key_file_get_string (keyfile, "daemon", "AutomaticLogin", &local_error); if (local_error) { g_propagate_error (error, local_error); g_key_file_free (keyfile); return FALSE; } g_key_file_free (keyfile); return TRUE; } static gboolean save_autologin (Daemon *daemon, const gchar *name, gboolean enabled, GError **error) { GKeyFile *keyfile; const gchar *filename; gchar *data; gboolean result; filename = "/etc/gdm/custom.conf"; keyfile = g_key_file_new (); if (!g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_KEEP_COMMENTS, error)) { g_key_file_free (keyfile); return FALSE; } g_key_file_set_string (keyfile, "daemon", "AutomaticLoginEnable", enabled ? "True" : "False"); g_key_file_set_string (keyfile, "daemon", "AutomaticLogin", name); data = g_key_file_to_data (keyfile, NULL, NULL); result = g_file_set_contents (filename, data, -1, error); g_key_file_free (keyfile); g_free (data); return result; } gboolean daemon_local_set_automatic_login (Daemon *daemon, User *user, gboolean enabled, GError **error) { if (daemon->priv->autologin == user && enabled) { return TRUE; } if (!save_autologin (daemon, user_local_get_user_name (user), enabled, error)) { return FALSE; } if (daemon->priv->autologin != NULL) { g_object_set (daemon->priv->autologin, "automatic-login", FALSE, NULL); g_signal_emit_by_name (daemon->priv->autologin, "changed", 0); g_object_unref (daemon->priv->autologin); daemon->priv->autologin = NULL; } if (enabled) { g_object_set (user, "automatic-login", TRUE, NULL); g_signal_emit_by_name (user, "changed", 0); g_object_ref (user); daemon->priv->autologin = user; } return TRUE; }