/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007-2008 William Jon McCann * * 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. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif /* HAVE_PATHS_H */ #include /* Note on sync with gdm; need to use -lib here */ #include #include #include #include #include #include #include #include "gdm-user-manager.h" #include "gdm-user-private.h" #define GDM_USER_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_USER_MANAGER, GdmUserManagerPrivate)) #define CK_NAME "org.freedesktop.ConsoleKit" #define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" #define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager" #define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat" #define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session" #define GDM_DBUS_TYPE_G_OBJECT_PATH_ARRAY (dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH)) /* Prefs Defaults */ #ifdef __sun #define FALLBACK_MINIMAL_UID 100 #else #define FALLBACK_MINIMAL_UID 500 #endif #ifndef _PATH_SHELLS #define _PATH_SHELLS "/etc/shells" #endif #define PATH_PASSWD "/etc/passwd" #ifndef GDM_USERNAME #define GDM_USERNAME "gdm" #endif #define RELOAD_PASSWD_THROTTLE_SECS 5 /* approximately two months */ #define LOGIN_FREQUENCY_TIME_WINDOW_SECS (60 * 24 * 60 * 60) #define ACCOUNTS_NAME "org.freedesktop.Accounts" #define ACCOUNTS_PATH "/org/freedesktop/Accounts" #define ACCOUNTS_INTERFACE "org.freedesktop.Accounts" typedef enum { GDM_USER_MANAGER_SEAT_STATE_UNLOADED = 0, GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID, GDM_USER_MANAGER_SEAT_STATE_GET_ID, GDM_USER_MANAGER_SEAT_STATE_GET_PROXY, GDM_USER_MANAGER_SEAT_STATE_LOADED, } GdmUserManagerSeatState; typedef struct { GdmUserManagerSeatState state; char *id; char *session_id; union { DBusGProxyCall *get_current_session_call; DBusGProxyCall *get_seat_id_call; }; DBusGProxy *proxy; } GdmUserManagerSeat; typedef enum { GDM_USER_MANAGER_NEW_SESSION_STATE_UNLOADED = 0, GDM_USER_MANAGER_NEW_SESSION_STATE_GET_PROXY, GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID, GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY, GDM_USER_MANAGER_NEW_SESSION_STATE_MAYBE_ADD, GDM_USER_MANAGER_NEW_SESSION_STATE_LOADED, } GdmUserManagerNewSessionState; typedef struct { GdmUserManager *manager; GdmUserManagerNewSessionState state; char *id; union { DBusGProxyCall *get_unix_user_call; DBusGProxyCall *get_x11_display_call; }; DBusGProxy *proxy; uid_t uid; char *x11_display; } GdmUserManagerNewSession; typedef enum { GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED = 0, GDM_USER_MANAGER_GET_USER_STATE_WAIT_FOR_LOADED, GDM_USER_MANAGER_GET_USER_STATE_ASK_ACCOUNTS_SERVICE, GDM_USER_MANAGER_GET_USER_STATE_FETCHED } GdmUserManagerGetUserState; typedef struct { GdmUserManager *manager; GdmUserManagerGetUserState state; GdmUser *user; char *username; char *object_path; DBusGProxyCall *call; } GdmUserManagerFetchUserRequest; struct GdmUserManagerPrivate { GHashTable *users_by_name; GHashTable *users_by_object_path; GHashTable *sessions; GHashTable *shells; DBusGConnection *connection; DBusGProxyCall *get_sessions_call; DBusGProxy *accounts_proxy; GdmUserManagerSeat seat; GSList *new_sessions; GSList *new_users; GSList *fetch_user_requests; GFileMonitor *passwd_monitor; GFileMonitor *shells_monitor; GSList *exclude_usernames; GSList *include_usernames; gboolean include_all; gboolean load_passwd_pending; guint load_id; guint reload_passwd_id; guint ck_history_id; guint ck_history_watchdog_id; GPid ck_history_pid; gboolean is_loaded; gboolean has_multiple_users; gboolean listing_cached_users; }; enum { PROP_0, PROP_INCLUDE_ALL, PROP_INCLUDE_USERNAMES_LIST, PROP_EXCLUDE_USERNAMES_LIST, PROP_IS_LOADED, PROP_HAS_MULTIPLE_USERS }; enum { USER_ADDED, USER_REMOVED, USER_IS_LOGGED_IN_CHANGED, USER_CHANGED, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0, }; static void gdm_user_manager_class_init (GdmUserManagerClass *klass); static void gdm_user_manager_init (GdmUserManager *user_manager); static void gdm_user_manager_finalize (GObject *object); static void load_users_manually (GdmUserManager *manager); static void monitor_local_users (GdmUserManager *manager); static void load_seat_incrementally (GdmUserManager *manager); static void unload_seat (GdmUserManager *manager); static void load_users (GdmUserManager *manager); static void queue_load_seat_and_users (GdmUserManager *manager); static void monitor_local_users (GdmUserManager *manager); static void load_new_session_incrementally (GdmUserManagerNewSession *new_session); static void set_is_loaded (GdmUserManager *manager, gboolean is_loaded); static void on_new_user_loaded (GdmUser *user, GParamSpec *pspec, GdmUserManager *manager); static void give_up_and_fetch_user_locally (GdmUserManager *manager, GdmUserManagerFetchUserRequest *request); static void fetch_user_locally (GdmUserManager *manager, GdmUser *user, const char *username); static void fetch_user_incrementally (GdmUserManagerFetchUserRequest *request); static void maybe_set_is_loaded (GdmUserManager *manager); static gpointer user_manager_object = NULL; G_DEFINE_TYPE (GdmUserManager, gdm_user_manager, G_TYPE_OBJECT) GQuark gdm_user_manager_error_quark (void) { static GQuark ret = 0; if (ret == 0) { ret = g_quark_from_static_string ("gdm_user_manager_error"); } return ret; } static gboolean start_new_login_session (GdmUserManager *manager) { GError *error; gboolean res; res = g_spawn_command_line_async ("gdmflexiserver -s", &error); if (! res) { if (error != NULL) { g_warning ("Unable to start new login: %s", error->message); g_error_free (error); } else { g_warning ("Unable to start new login"); } } return res; } static gboolean activate_session_id (GdmUserManager *manager, const char *seat_id, const char *session_id) { DBusError local_error; DBusMessage *message; DBusMessage *reply; gboolean ret; ret = FALSE; reply = NULL; dbus_error_init (&local_error); message = dbus_message_new_method_call ("org.freedesktop.ConsoleKit", seat_id, "org.freedesktop.ConsoleKit.Seat", "ActivateSession"); if (message == NULL) { goto out; } if (! dbus_message_append_args (message, DBUS_TYPE_OBJECT_PATH, &session_id, DBUS_TYPE_INVALID)) { goto out; } dbus_error_init (&local_error); reply = dbus_connection_send_with_reply_and_block (dbus_g_connection_get_connection (manager->priv->connection), message, -1, &local_error); if (reply == NULL) { if (dbus_error_is_set (&local_error)) { g_warning ("Unable to activate session: %s", local_error.message); dbus_error_free (&local_error); goto out; } } ret = TRUE; out: if (message != NULL) { dbus_message_unref (message); } if (reply != NULL) { dbus_message_unref (reply); } return ret; } static gboolean session_is_login_window (GdmUserManager *manager, const char *session_id) { DBusGProxy *proxy; GError *error; gboolean res; gboolean ret; char *session_type; ret = FALSE; proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, session_id, CK_SESSION_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit seat object"); goto out; } session_type = NULL; error = NULL; res = dbus_g_proxy_call (proxy, "GetSessionType", &error, G_TYPE_INVALID, G_TYPE_STRING, &session_type, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_debug ("GdmUserManager: Failed to identify the session type: %s", error->message); g_error_free (error); } else { g_debug ("GdmUserManager: Failed to identify the session type"); } goto out; } if (session_type == NULL || session_type[0] == '\0' || strcmp (session_type, "LoginWindow") != 0) { goto out; } ret = TRUE; out: if (proxy != NULL) { g_object_unref (proxy); } return ret; } static char * _get_login_window_session_id (GdmUserManager *manager) { gboolean res; gboolean can_activate_sessions; GError *error; GPtrArray *sessions; char *primary_ssid; int i; if (manager->priv->seat.id == NULL || manager->priv->seat.id[0] == '\0') { g_debug ("GdmUserManager: display seat ID is not set; can't switch sessions"); return NULL; } primary_ssid = NULL; sessions = NULL; can_activate_sessions = gdm_user_manager_can_switch (manager); if (! can_activate_sessions) { g_debug ("GdmUserManager: seat is unable to activate sessions"); goto out; } error = NULL; res = dbus_g_proxy_call (manager->priv->seat.proxy, "GetSessions", &error, G_TYPE_INVALID, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &sessions, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_warning ("unable to determine sessions for user: %s", error->message); g_error_free (error); } else { g_warning ("unable to determine sessions for user"); } goto out; } for (i = 0; i < sessions->len; i++) { char *ssid; ssid = g_ptr_array_index (sessions, i); if (session_is_login_window (manager, ssid)) { primary_ssid = g_strdup (ssid); break; } } g_ptr_array_foreach (sessions, (GFunc)g_free, NULL); g_ptr_array_free (sessions, TRUE); out: return primary_ssid; } gboolean gdm_user_manager_goto_login_session (GdmUserManager *manager) { gboolean ret; gboolean res; char *ssid; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE); g_return_val_if_fail (manager->priv->is_loaded, FALSE); ret = FALSE; /* First look for any existing LoginWindow sessions on the seat. If none are found, create a new one. */ ssid = _get_login_window_session_id (manager); if (ssid != NULL) { res = activate_session_id (manager, manager->priv->seat.id, ssid); if (res) { ret = TRUE; } } if (! ret) { res = start_new_login_session (manager); if (res) { ret = TRUE; } } return ret; } gboolean gdm_user_manager_can_switch (GdmUserManager *manager) { gboolean res; gboolean can_activate_sessions; GError *error; if (!manager->priv->is_loaded) { g_debug ("GdmUserManager: Unable to switch sessions until fully loaded"); return FALSE; } if (manager->priv->seat.id == NULL || manager->priv->seat.id[0] == '\0') { g_debug ("GdmUserManager: display seat ID is not set; can't switch sessions"); return FALSE; } g_debug ("GdmUserManager: checking if seat can activate sessions"); error = NULL; res = dbus_g_proxy_call (manager->priv->seat.proxy, "CanActivateSessions", &error, G_TYPE_INVALID, G_TYPE_BOOLEAN, &can_activate_sessions, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_warning ("unable to determine if seat can activate sessions: %s", error->message); g_error_free (error); } else { g_warning ("unable to determine if seat can activate sessions"); } return FALSE; } return can_activate_sessions; } gboolean gdm_user_manager_activate_user_session (GdmUserManager *manager, GdmUser *user) { gboolean ret; const char *ssid; gboolean res; gboolean can_activate_sessions; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE); g_return_val_if_fail (GDM_IS_USER (user), FALSE); g_return_val_if_fail (manager->priv->is_loaded, FALSE); ret = FALSE; can_activate_sessions = gdm_user_manager_can_switch (manager); if (! can_activate_sessions) { g_debug ("GdmUserManager: seat is unable to activate sessions"); goto out; } ssid = gdm_user_get_primary_session_id (user); if (ssid == NULL) { goto out; } res = activate_session_id (manager, manager->priv->seat.id, ssid); if (! res) { g_debug ("GdmUserManager: unable to activate session: %s", ssid); goto out; } ret = TRUE; out: return ret; } static void on_user_sessions_changed (GdmUser *user, GdmUserManager *manager) { guint nsessions; if (! manager->priv->is_loaded) { return; } nsessions = gdm_user_get_num_sessions (user); g_debug ("GdmUserManager: sessions changed user=%s num=%d", gdm_user_get_user_name (user), nsessions); /* only signal on zero and one */ if (nsessions > 1) { return; } g_signal_emit (manager, signals [USER_IS_LOGGED_IN_CHANGED], 0, user); } static void on_user_changed (GdmUser *user, GdmUserManager *manager) { if (manager->priv->is_loaded) { g_debug ("GdmUserManager: user changed"); g_signal_emit (manager, signals[USER_CHANGED], 0, user); } } static void on_get_seat_id_finished (DBusGProxy *proxy, DBusGProxyCall *call, GdmUserManager *manager) { GError *error; char *seat_id; gboolean res; g_assert (manager->priv->seat.get_seat_id_call == call); error = NULL; seat_id = NULL; res = dbus_g_proxy_end_call (proxy, call, &error, DBUS_TYPE_G_OBJECT_PATH, &seat_id, G_TYPE_INVALID); manager->priv->seat.get_seat_id_call = NULL; g_object_unref (proxy); if (! res) { if (error != NULL) { g_debug ("Failed to identify the seat of the " "current session: %s", error->message); g_error_free (error); } else { g_debug ("Failed to identify the seat of the " "current session"); } unload_seat (manager); maybe_set_is_loaded (manager); return; } g_debug ("GdmUserManager: Found current seat: %s", seat_id); manager->priv->seat.id = seat_id; manager->priv->seat.state++; load_seat_incrementally (manager); } static void get_seat_id_for_current_session (GdmUserManager *manager) { DBusGProxy *proxy; DBusGProxyCall *call; proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, manager->priv->seat.session_id, CK_SESSION_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit session object"); goto failed; } call = dbus_g_proxy_begin_call (proxy, "GetSeatId", (DBusGProxyCallNotify) on_get_seat_id_finished, manager, NULL, G_TYPE_INVALID); if (call == NULL) { g_warning ("GdmUserManager: failed to make GetSeatId call"); goto failed; } manager->priv->seat.get_seat_id_call = call; return; failed: if (proxy != NULL) { g_object_unref (proxy); } unload_seat (manager); } static gint match_name_cmpfunc (gconstpointer a, gconstpointer b) { return g_strcmp0 ((char *) a, (char *) b); } static gboolean username_in_exclude_list (GdmUserManager *manager, const char *username) { GSList *found; gboolean ret = FALSE; /* always exclude the "gdm" user. */ if (username == NULL || (strcmp (username, GDM_USERNAME) == 0)) { return TRUE; } if (manager->priv->exclude_usernames != NULL) { found = g_slist_find_custom (manager->priv->exclude_usernames, username, match_name_cmpfunc); if (found != NULL) { ret = TRUE; } } return ret; } static void add_session_for_user (GdmUserManager *manager, GdmUser *user, const char *ssid) { g_hash_table_insert (manager->priv->sessions, g_strdup (ssid), g_strdup (gdm_user_get_user_name (user))); _gdm_user_add_session (user, ssid); g_debug ("GdmUserManager: added session for user: %s", gdm_user_get_user_name (user)); } static void set_has_multiple_users (GdmUserManager *manager, gboolean has_multiple_users) { if (manager->priv->has_multiple_users != has_multiple_users) { manager->priv->has_multiple_users = has_multiple_users; g_object_notify (G_OBJECT (manager), "has-multiple-users"); } } static GdmUser * create_new_user (GdmUserManager *manager) { GdmUser *user; user = g_object_new (GDM_TYPE_USER, NULL); manager->priv->new_users = g_slist_prepend (manager->priv->new_users, user); g_signal_connect (user, "notify::is-loaded", G_CALLBACK (on_new_user_loaded), manager); return g_object_ref (user); } static void add_user (GdmUserManager *manager, GdmUser *user) { const char *object_path; g_hash_table_insert (manager->priv->users_by_name, g_strdup (gdm_user_get_user_name (user)), g_object_ref (user)); object_path = gdm_user_get_object_path (user); if (object_path != NULL) { g_hash_table_insert (manager->priv->users_by_object_path, (gpointer) object_path, g_object_ref (user)); } g_signal_connect (user, "sessions-changed", G_CALLBACK (on_user_sessions_changed), manager); g_signal_connect (user, "changed", G_CALLBACK (on_user_changed), manager); if (manager->priv->is_loaded) { g_signal_emit (manager, signals[USER_ADDED], 0, user); } if (g_hash_table_size (manager->priv->users_by_name) > 1) { set_has_multiple_users (manager, TRUE); } } static void remove_user (GdmUserManager *manager, GdmUser *user) { g_object_ref (user); g_signal_handlers_disconnect_by_func (user, on_user_changed, manager); g_signal_handlers_disconnect_by_func (user, on_user_sessions_changed, manager); if (gdm_user_get_object_path (user) != NULL) { g_hash_table_remove (manager->priv->users_by_object_path, gdm_user_get_object_path (user)); } g_hash_table_remove (manager->priv->users_by_name, gdm_user_get_user_name (user)); if (manager->priv->is_loaded) { g_signal_emit (manager, signals[USER_REMOVED], 0, user); } g_object_unref (user); if (g_hash_table_size (manager->priv->users_by_name) > 1) { set_has_multiple_users (manager, FALSE); } } static void on_new_user_loaded (GdmUser *user, GParamSpec *pspec, GdmUserManager *manager) { const char *username; GdmUser *old_user; if (!gdm_user_is_loaded (user)) { return; } g_signal_handlers_disconnect_by_func (user, on_new_user_loaded, manager); manager->priv->new_users = g_slist_remove (manager->priv->new_users, user); username = gdm_user_get_user_name (user); if (username == NULL) { const char *object_path; object_path = gdm_user_get_object_path (user); if (object_path != NULL) { g_warning ("GdmUserManager: user has no username " "(object path: %s, uid: %lu)", object_path, gdm_user_get_uid (user)); } else { g_warning ("GdmUserManager: user has no username (uid: %lu)", gdm_user_get_uid (user)); } g_object_unref (user); return; } if (username_in_exclude_list (manager, username)) { g_debug ("GdmUserManager: excluding user '%s'", username); g_object_unref (user); return; } old_user = g_hash_table_lookup (manager->priv->users_by_name, username); /* If username got added earlier by a different means, trump it now. */ if (old_user != NULL) { remove_user (manager, old_user); } add_user (manager, user); g_object_unref (user); if (manager->priv->new_users == NULL) { set_is_loaded (manager, TRUE); } } static GdmUser * add_new_user_for_object_path (const char *object_path, GdmUserManager *manager) { GdmUser *user; user = g_hash_table_lookup (manager->priv->users_by_object_path, object_path); if (user != NULL) { return user; } user = create_new_user (manager); _gdm_user_update_from_object_path (user, object_path); return user; } static void on_new_user_in_accounts_service (DBusGProxy *proxy, const char *object_path, gpointer user_data) { GdmUserManager *manager = GDM_USER_MANAGER (user_data); add_new_user_for_object_path (object_path, manager); } static void on_user_removed_in_accounts_service (DBusGProxy *proxy, const char *object_path, gpointer user_data) { GdmUserManager *manager = GDM_USER_MANAGER (user_data); GdmUser *user; user = g_hash_table_lookup (manager->priv->users_by_object_path, object_path); manager->priv->new_users = g_slist_remove (manager->priv->new_users, user); remove_user (manager, user); } static void on_get_current_session_finished (DBusGProxy *proxy, DBusGProxyCall *call, GdmUserManager *manager) { GError *error; char *session_id; gboolean res; g_assert (manager->priv->seat.get_current_session_call == call); g_assert (manager->priv->seat.state == GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID); error = NULL; session_id = NULL; res = dbus_g_proxy_end_call (proxy, call, &error, DBUS_TYPE_G_OBJECT_PATH, &session_id, G_TYPE_INVALID); manager->priv->seat.get_current_session_call = NULL; g_object_unref (proxy); if (! res) { if (error != NULL) { g_debug ("Failed to identify the current session: %s", error->message); g_error_free (error); } else { g_debug ("Failed to identify the current session"); } unload_seat (manager); maybe_set_is_loaded (manager); return; } manager->priv->seat.session_id = session_id; manager->priv->seat.state++; load_seat_incrementally (manager); } static void get_current_session_id (GdmUserManager *manager) { DBusGProxy *proxy; DBusGProxyCall *call; proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, CK_MANAGER_PATH, CK_MANAGER_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit manager object"); goto failed; } call = dbus_g_proxy_begin_call (proxy, "GetCurrentSession", (DBusGProxyCallNotify) on_get_current_session_finished, manager, NULL, G_TYPE_INVALID); if (call == NULL) { g_warning ("GdmUserManager: failed to make GetCurrentSession call"); goto failed; } manager->priv->seat.get_current_session_call = call; return; failed: if (proxy != NULL) { g_object_unref (proxy); } unload_seat (manager); } static void unload_new_session (GdmUserManagerNewSession *new_session) { GdmUserManager *manager; manager = new_session->manager; manager->priv->new_sessions = g_slist_remove (manager->priv->new_sessions, new_session); if (new_session->proxy != NULL) { g_object_unref (new_session->proxy); } g_free (new_session->x11_display); g_free (new_session->id); g_slice_free (GdmUserManagerNewSession, new_session); } static void get_proxy_for_new_session (GdmUserManagerNewSession *new_session) { GdmUserManager *manager; DBusGProxy *proxy; manager = new_session->manager; proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, new_session->id, CK_SESSION_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit '%s' object", new_session->id); unload_new_session (new_session); return; } new_session->proxy = proxy; new_session->state++; load_new_session_incrementally (new_session); } static void on_get_unix_user_finished (DBusGProxy *proxy, DBusGProxyCall *call, GdmUserManagerNewSession *new_session) { GError *error; guint uid; gboolean res; g_assert (new_session->get_unix_user_call == call); error = NULL; uid = (guint) -1; res = dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_UINT, &uid, G_TYPE_INVALID); new_session->get_unix_user_call = NULL; if (! res) { if (error != NULL) { g_debug ("Failed to get uid of session '%s': %s", new_session->id, error->message); g_error_free (error); } else { g_debug ("Failed to get uid of session '%s'", new_session->id); } unload_new_session (new_session); return; } g_debug ("GdmUserManager: Found uid of session '%s': %u", new_session->id, uid); new_session->uid = (uid_t) uid; new_session->state++; load_new_session_incrementally (new_session); } static void get_uid_for_new_session (GdmUserManagerNewSession *new_session) { DBusGProxyCall *call; g_assert (new_session->proxy != NULL); call = dbus_g_proxy_begin_call (new_session->proxy, "GetUnixUser", (DBusGProxyCallNotify) on_get_unix_user_finished, new_session, NULL, G_TYPE_INVALID); if (call == NULL) { g_warning ("GdmUserManager: failed to make GetUnixUser call"); goto failed; } new_session->get_unix_user_call = call; return; failed: unload_new_session (new_session); } static void on_find_user_by_name_finished (DBusGProxy *proxy, DBusGProxyCall *call, GdmUserManagerFetchUserRequest *request) { GdmUserManager *manager; GError *error; char *object_path; gboolean res; g_assert (request->call == call); error = NULL; object_path = NULL; manager = request->manager; res = dbus_g_proxy_end_call (manager->priv->accounts_proxy, call, &error, DBUS_TYPE_G_OBJECT_PATH, &object_path, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_debug ("GdmUserManager: Failed to find user %s: %s", request->username, error->message); g_error_free (error); } else { g_debug ("GdmUserManager: Failed to find user %s", request->username); } give_up_and_fetch_user_locally (manager, request); return; } g_debug ("GdmUserManager: Found object path of user '%s': %s", request->username, object_path); request->object_path = object_path; request->state++; fetch_user_incrementally (request); } static void find_user_in_accounts_service (GdmUserManager *manager, GdmUserManagerFetchUserRequest *request) { DBusGProxyCall *call; g_debug ("GdmUserManager: Looking for user %s in accounts service", request->username); g_assert (manager->priv->accounts_proxy != NULL); call = dbus_g_proxy_begin_call (manager->priv->accounts_proxy, "FindUserByName", (DBusGProxyCallNotify) on_find_user_by_name_finished, request, NULL, G_TYPE_STRING, request->username, G_TYPE_INVALID); if (call == NULL) { g_warning ("GdmUserManager: failed to make FindUserByName('%s') call", request->username); goto failed; } request->call = call; return; failed: give_up_and_fetch_user_locally (manager, request); } static void set_is_loaded (GdmUserManager *manager, gboolean is_loaded) { if (manager->priv->is_loaded != is_loaded) { manager->priv->is_loaded = is_loaded; g_object_notify (G_OBJECT (manager), "is-loaded"); } } static void on_list_cached_users_finished (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer data) { GdmUserManager *manager = data; GError *error = NULL; GPtrArray *paths; manager->priv->listing_cached_users = FALSE; 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)) { g_debug ("GdmUserManager: ListCachedUsers failed: %s", error->message); g_error_free (error); g_object_unref (manager->priv->accounts_proxy); manager->priv->accounts_proxy = NULL; load_users_manually (manager); return; } maybe_set_is_loaded (manager); g_ptr_array_foreach (paths, (GFunc)add_new_user_for_object_path, manager); g_ptr_array_foreach (paths, (GFunc)g_free, NULL); g_ptr_array_free (paths, TRUE); /* Add users who are specifically included */ if (manager->priv->include_usernames != NULL) { GSList *l; for (l = manager->priv->include_usernames; l != NULL; l = l->next) { GdmUser *user; g_debug ("GdmUserManager: Adding included user %s", (char *)l->data); /* * The call to gdm_user_manager_get_user will add the user if it is * valid and not already in the hash. */ user = gdm_user_manager_get_user (manager, l->data); if (user == NULL) { g_debug ("GdmUserManager: unable to lookup user '%s'", (char *)l->data); } } } } static void on_get_x11_display_finished (DBusGProxy *proxy, DBusGProxyCall *call, GdmUserManagerNewSession *new_session) { GError *error; char *x11_display; gboolean res; g_assert (new_session->get_x11_display_call == call); error = NULL; x11_display = NULL; res = dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_STRING, &x11_display, G_TYPE_INVALID); new_session->get_x11_display_call = NULL; if (! res) { if (error != NULL) { g_debug ("Failed to get the x11 display of session '%s': %s", new_session->id, error->message); g_error_free (error); } else { g_debug ("Failed to get the x11 display of session '%s'", new_session->id); } unload_new_session (new_session); return; } g_debug ("GdmUserManager: Found x11 display of session '%s': %s", new_session->id, x11_display); new_session->x11_display = x11_display; new_session->state++; load_new_session_incrementally (new_session); } static void get_x11_display_for_new_session (GdmUserManagerNewSession *new_session) { DBusGProxyCall *call; g_assert (new_session->proxy != NULL); call = dbus_g_proxy_begin_call (new_session->proxy, "GetX11Display", (DBusGProxyCallNotify) on_get_x11_display_finished, new_session, NULL, G_TYPE_INVALID); if (call == NULL) { g_warning ("GdmUserManager: failed to make GetX11Display call"); goto failed; } new_session->get_x11_display_call = call; return; failed: unload_new_session (new_session); } static gboolean get_pwent_for_name (const char *name, struct passwd **pwentp) { struct passwd *pwent; do { errno = 0; pwent = getpwnam (name); } while (pwent == NULL && errno == EINTR); if (pwentp != NULL) { *pwentp = pwent; } return (pwent != NULL); } static gboolean get_pwent_for_uid (uid_t uid, struct passwd **pwentp) { struct passwd *pwent; do { errno = 0; pwent = getpwuid (uid); } while (pwent == NULL && errno == EINTR); if (pwentp != NULL) { *pwentp = pwent; } return (pwent != NULL); } static void maybe_add_new_session (GdmUserManagerNewSession *new_session) { GdmUserManager *manager; struct passwd *pwent; GdmUser *user; manager = GDM_USER_MANAGER (new_session->manager); errno = 0; get_pwent_for_uid (new_session->uid, &pwent); if (pwent == NULL) { g_warning ("Unable to lookup user ID %d: %s", (int) new_session->uid, g_strerror (errno)); goto failed; } /* check exclusions up front */ if (username_in_exclude_list (manager, pwent->pw_name)) { g_debug ("GdmUserManager: excluding user '%s'", pwent->pw_name); goto failed; } user = gdm_user_manager_get_user (manager, pwent->pw_name); if (user == NULL) { return; } add_session_for_user (manager, user, new_session->id); /* if we haven't yet gotten the login frequency then at least add one because the session exists */ if (gdm_user_get_login_frequency (user) == 0) { _gdm_user_update_login_frequency (user, 1); } manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_LOADED; unload_new_session (new_session); return; failed: unload_new_session (new_session); } static void load_new_session (GdmUserManager *manager, const char *session_id) { GdmUserManagerNewSession *new_session; new_session = g_slice_new0 (GdmUserManagerNewSession); new_session->manager = manager; new_session->id = g_strdup (session_id); new_session->state = GDM_USER_MANAGER_NEW_SESSION_STATE_UNLOADED + 1; manager->priv->new_sessions = g_slist_prepend (manager->priv->new_sessions, new_session); load_new_session_incrementally (new_session); } static void seat_session_added (DBusGProxy *seat_proxy, const char *session_id, GdmUserManager *manager) { g_debug ("GdmUserManager: Session added: %s", session_id); load_new_session (manager, session_id); } static gint match_new_session_cmpfunc (gconstpointer a, gconstpointer b) { GdmUserManagerNewSession *new_session; const char *session_id; new_session = (GdmUserManagerNewSession *) a; session_id = (const char *) b; return strcmp (new_session->id, session_id); } static void seat_session_removed (DBusGProxy *seat_proxy, const char *session_id, GdmUserManager *manager) { GdmUser *user; GSList *found; char *username; g_debug ("GdmUserManager: Session removed: %s", session_id); found = g_slist_find_custom (manager->priv->new_sessions, session_id, match_new_session_cmpfunc); if (found != NULL) { GdmUserManagerNewSession *new_session; new_session = (GdmUserManagerNewSession *) found->data; if (new_session->state > GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY) { g_debug ("GdmUserManager: New session for uid %d on " "x11 display %s removed before fully loading", (int) new_session->uid, new_session->x11_display); } else if (new_session->state > GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID) { g_debug ("GdmUserManager: New session for uid %d " "removed before fully loading", (int) new_session->uid); } else { g_debug ("GdmUserManager: New session removed " "before fully loading"); } unload_new_session (new_session); return; } /* since the session object may already be gone * we can't query CK directly */ username = g_hash_table_lookup (manager->priv->sessions, session_id); if (username == NULL) { return; } user = g_hash_table_lookup (manager->priv->users_by_name, username); if (user == NULL) { /* nothing to do */ return; } g_debug ("GdmUserManager: Session removed for %s", username); _gdm_user_remove_session (user, session_id); } static void on_seat_proxy_destroy (DBusGProxy *proxy, GdmUserManager *manager) { g_debug ("GdmUserManager: seat proxy destroyed"); manager->priv->seat.proxy = NULL; } static void get_seat_proxy (GdmUserManager *manager) { DBusGProxy *proxy; GError *error; g_assert (manager->priv->seat.proxy == NULL); error = NULL; proxy = dbus_g_proxy_new_for_name_owner (manager->priv->connection, CK_NAME, manager->priv->seat.id, CK_SEAT_INTERFACE, &error); if (proxy == NULL) { if (error != NULL) { g_warning ("Failed to connect to the ConsoleKit seat object: %s", error->message); g_error_free (error); } else { g_warning ("Failed to connect to the ConsoleKit seat object"); } unload_seat (manager); return; } g_signal_connect (proxy, "destroy", G_CALLBACK (on_seat_proxy_destroy), manager); dbus_g_proxy_add_signal (proxy, "SessionAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); dbus_g_proxy_add_signal (proxy, "SessionRemoved", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); dbus_g_proxy_connect_signal (proxy, "SessionAdded", G_CALLBACK (seat_session_added), manager, NULL); dbus_g_proxy_connect_signal (proxy, "SessionRemoved", G_CALLBACK (seat_session_removed), manager, NULL); manager->priv->seat.proxy = proxy; manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_LOADED; } static void unload_seat (GdmUserManager *manager) { manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED; if (manager->priv->seat.proxy != NULL) { g_object_unref (manager->priv->seat.proxy); manager->priv->seat.proxy = NULL; } g_free (manager->priv->seat.id); manager->priv->seat.id = NULL; g_free (manager->priv->seat.session_id); manager->priv->seat.session_id = NULL; } static void get_accounts_proxy (GdmUserManager *manager) { DBusGProxy *proxy; g_assert (manager->priv->accounts_proxy == NULL); proxy = dbus_g_proxy_new_for_name (manager->priv->connection, ACCOUNTS_NAME, ACCOUNTS_PATH, ACCOUNTS_INTERFACE); manager->priv->accounts_proxy = proxy; dbus_g_proxy_add_signal (proxy, "UserAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); dbus_g_proxy_add_signal (proxy, "UserDeleted", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); dbus_g_proxy_connect_signal (proxy, "UserAdded", G_CALLBACK (on_new_user_in_accounts_service), manager, NULL); dbus_g_proxy_connect_signal (proxy, "UserDeleted", G_CALLBACK (on_user_removed_in_accounts_service), manager, NULL); } static void load_new_session_incrementally (GdmUserManagerNewSession *new_session) { switch (new_session->state) { case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_PROXY: get_proxy_for_new_session (new_session); break; case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID: get_uid_for_new_session (new_session); break; case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY: get_x11_display_for_new_session (new_session); break; case GDM_USER_MANAGER_NEW_SESSION_STATE_MAYBE_ADD: maybe_add_new_session (new_session); break; case GDM_USER_MANAGER_NEW_SESSION_STATE_LOADED: break; default: g_assert_not_reached (); } } static void free_fetch_user_request (GdmUserManagerFetchUserRequest *request) { GdmUserManager *manager; manager = request->manager; manager->priv->fetch_user_requests = g_slist_remove (manager->priv->fetch_user_requests, request); g_free (request->username); g_free (request->object_path); g_slice_free (GdmUserManagerFetchUserRequest, request); } static void give_up_and_fetch_user_locally (GdmUserManager *manager, GdmUserManagerFetchUserRequest *request) { g_debug ("GdmUserManager: account service unavailable, " "fetching user %s locally", request->username); fetch_user_locally (manager, request->user, request->username); request->state = GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED; } static void on_user_manager_maybe_ready_for_request (GdmUserManager *manager, GParamSpec *pspec, GdmUserManagerFetchUserRequest *request) { if (!manager->priv->is_loaded) { return; } g_signal_handlers_disconnect_by_func (manager, on_user_manager_maybe_ready_for_request, request); request->state++; fetch_user_incrementally (request); } static void fetch_user_incrementally (GdmUserManagerFetchUserRequest *request) { GdmUserManager *manager; g_debug ("GdmUserManager: finding user %s state %d", request->username, request->state); manager = request->manager; switch (request->state) { case GDM_USER_MANAGER_GET_USER_STATE_WAIT_FOR_LOADED: if (manager->priv->is_loaded) { request->state++; fetch_user_incrementally (request); } else { g_debug ("GdmUserManager: waiting for user manager to load before finding user %s", request->username); g_signal_connect (manager, "notify::is-loaded", G_CALLBACK (on_user_manager_maybe_ready_for_request), request); } break; case GDM_USER_MANAGER_GET_USER_STATE_ASK_ACCOUNTS_SERVICE: if (manager->priv->accounts_proxy == NULL) { give_up_and_fetch_user_locally (manager, request); } else { find_user_in_accounts_service (manager, request); } break; case GDM_USER_MANAGER_GET_USER_STATE_FETCHED: g_debug ("GdmUserManager: user %s fetched", request->username); _gdm_user_update_from_object_path (request->user, request->object_path); break; case GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED: g_debug ("GdmUserManager: user %s was not fetched", request->username); break; default: g_assert_not_reached (); } if (request->state == GDM_USER_MANAGER_GET_USER_STATE_FETCHED || request->state == GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED) { g_debug ("GdmUserManager: finished handling request for user %s", request->username); free_fetch_user_request (request); } } static void fetch_user_from_accounts_service (GdmUserManager *manager, GdmUser *user, const char *username) { GdmUserManagerFetchUserRequest *request; request = g_slice_new0 (GdmUserManagerFetchUserRequest); request->manager = manager; request->username = g_strdup (username); request->user = user; request->state = GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED + 1; manager->priv->fetch_user_requests = g_slist_prepend (manager->priv->fetch_user_requests, request); fetch_user_incrementally (request); } static void fetch_user_locally (GdmUserManager *manager, GdmUser *user, const char *username) { struct passwd *pwent; get_pwent_for_name (username, &pwent); if (pwent != NULL) { _gdm_user_update_from_pwent (user, pwent); } } /** * gdm_user_manager_get_user: * @manager: the manager to query. * @username: the login name of the user to get. * * Retrieves a pointer to the #GdmUser object for the login @username * from @manager. Trying to use this object before its * #GdmUser:is-loaded property is %TRUE will result in undefined * behavior. * * Returns: (transfer none): #GdmUser object **/ GdmUser * gdm_user_manager_get_user (GdmUserManager *manager, const char *username) { GdmUser *user; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); g_return_val_if_fail (username != NULL && username[0] != '\0', NULL); user = g_hash_table_lookup (manager->priv->users_by_name, username); /* if we don't have it loaded try to load it now */ if (user == NULL) { user = create_new_user (manager); if (manager->priv->accounts_proxy != NULL) { fetch_user_from_accounts_service (manager, user, username); } else { fetch_user_locally (manager, user, username); } } return user; } /** * gdm_user_manager_get_user_by_uid: * @manager: the manager to query. * @uid: the uid of the user to get. * * Retrieves a pointer to the #GdmUser object for the uid @uid * from @manager. Trying to use this object before its * #GdmUser:is-loaded property is %TRUE will result in undefined * behavior. * * Returns: (transfer none): #GdmUser object */ GdmUser * gdm_user_manager_get_user_by_uid (GdmUserManager *manager, gulong uid) { struct passwd *pwent; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); get_pwent_for_uid (uid, &pwent); if (pwent == NULL) { g_warning ("GdmUserManager: unable to lookup uid %d", (int)uid); return NULL; } return gdm_user_manager_get_user (manager, pwent->pw_name); } static void listify_hash_values_hfunc (gpointer key, gpointer value, gpointer user_data) { GSList **list = user_data; *list = g_slist_prepend (*list, value); } /** * gdm_user_manager_list_users: * @manager: a #GdmUserManager * * Get a list of system user accounts * * Returns: (element-type GdmUser) (transfer full): List of #GdmUser objects */ GSList * gdm_user_manager_list_users (GdmUserManager *manager) { GSList *retval; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); retval = NULL; g_hash_table_foreach (manager->priv->users_by_name, listify_hash_values_hfunc, &retval); return g_slist_sort (retval, (GCompareFunc) gdm_user_collate); } static gboolean parse_value_as_ulong (const char *value, gulong *ulongval) { char *end_of_valid_long; glong long_value; gulong ulong_value; errno = 0; long_value = strtol (value, &end_of_valid_long, 10); if (*value == '\0' || *end_of_valid_long != '\0') { return FALSE; } ulong_value = long_value; if (ulong_value != long_value || errno == ERANGE) { return FALSE; } *ulongval = ulong_value; return TRUE; } static gboolean parse_ck_history_line (const char *line, char **user_namep, gulong *frequencyp) { GRegex *re; GMatchInfo *match_info; gboolean res; gboolean ret; GError *error; ret = FALSE; re = NULL; match_info = NULL; error = NULL; re = g_regex_new ("(?P[0-9a-zA-Z]+)[ ]+(?P[0-9]+)", 0, 0, &error); if (re == NULL) { if (error != NULL) { g_critical ("%s", error->message); } else { g_critical ("Error in regex call"); } goto out; } g_regex_match (re, line, 0, &match_info); res = g_match_info_matches (match_info); if (! res) { g_warning ("Unable to parse history: %s", line); goto out; } if (user_namep != NULL) { *user_namep = g_match_info_fetch_named (match_info, "username"); } if (frequencyp != NULL) { char *freq; freq = g_match_info_fetch_named (match_info, "frequency"); res = parse_value_as_ulong (freq, frequencyp); g_free (freq); if (! res) { goto out; } } ret = TRUE; out: if (match_info != NULL) { g_match_info_free (match_info); } if (re != NULL) { g_regex_unref (re); } return ret; } static void process_ck_history_line (GdmUserManager *manager, const char *line) { gboolean res; char *username; gulong frequency; GdmUser *user; frequency = 0; username = NULL; res = parse_ck_history_line (line, &username, &frequency); if (! res) { return; } if (username_in_exclude_list (manager, username)) { g_debug ("GdmUserManager: excluding user '%s'", username); g_free (username); return; } user = gdm_user_manager_get_user (manager, username); if (user == NULL) { g_debug ("GdmUserManager: unable to lookup user '%s'", username); g_free (username); return; } _gdm_user_update_login_frequency (user, frequency); g_free (username); } static void maybe_set_is_loaded (GdmUserManager *manager) { if (manager->priv->is_loaded) { return; } if (manager->priv->ck_history_pid != 0) { return; } if (manager->priv->load_passwd_pending) { return; } if (manager->priv->get_sessions_call != NULL) { return; } if (manager->priv->listing_cached_users) { return; } /* Don't set is_loaded yet unless the seat is already loaded * or failed to load. */ if (manager->priv->seat.state != GDM_USER_MANAGER_SEAT_STATE_LOADED && manager->priv->seat.state != GDM_USER_MANAGER_SEAT_STATE_UNLOADED) { return; } set_is_loaded (manager, TRUE); } static gboolean ck_history_watch (GIOChannel *source, GIOCondition condition, GdmUserManager *manager) { GIOStatus status; gboolean done = FALSE; g_return_val_if_fail (manager != NULL, FALSE); if (condition & G_IO_IN) { char *str; GError *error; error = NULL; status = g_io_channel_read_line (source, &str, NULL, NULL, &error); if (error != NULL) { g_warning ("GdmUserManager: unable to read line: %s", error->message); g_error_free (error); } if (status == G_IO_STATUS_NORMAL) { g_debug ("GdmUserManager: history output: %s", str); process_ck_history_line (manager, str); } else if (status == G_IO_STATUS_EOF) { done = TRUE; } g_free (str); } else if (condition & G_IO_HUP) { done = TRUE; } if (done) { manager->priv->ck_history_id = 0; if (manager->priv->ck_history_watchdog_id != 0) { g_source_remove (manager->priv->ck_history_watchdog_id); manager->priv->ck_history_watchdog_id = 0; } manager->priv->ck_history_pid = 0; maybe_set_is_loaded (manager); return FALSE; } return TRUE; } static int signal_pid (int pid, int signal) { int status = -1; status = kill (pid, signal); if (status < 0) { if (errno == ESRCH) { g_debug ("Child process %lu was already dead.", (unsigned long) pid); } else { char buf [1024]; snprintf (buf, sizeof (buf), "Couldn't kill child process %lu", (unsigned long) pid); perror (buf); } } return status; } static gboolean ck_history_watchdog (GdmUserManager *manager) { if (manager->priv->ck_history_pid > 0) { g_debug ("Killing ck-history process"); signal_pid (manager->priv->ck_history_pid, SIGTERM); manager->priv->ck_history_pid = 0; } manager->priv->ck_history_watchdog_id = 0; return FALSE; } static gboolean load_ck_history (GdmUserManager *manager) { char *command; char *since; const char *seat_id; GError *error; gboolean res; char **argv; int standard_out; GIOChannel *channel; GTimeVal tv; g_assert (manager->priv->ck_history_id == 0); command = NULL; seat_id = NULL; if (manager->priv->seat.id != NULL && g_str_has_prefix (manager->priv->seat.id, "/org/freedesktop/ConsoleKit/")) { seat_id = manager->priv->seat.id + strlen ("/org/freedesktop/ConsoleKit/"); } if (seat_id == NULL) { g_warning ("Unable to load CK history: no seat-id found"); goto out; } g_get_current_time (&tv); tv.tv_sec -= LOGIN_FREQUENCY_TIME_WINDOW_SECS; since = g_time_val_to_iso8601 (&tv); command = g_strdup_printf ("ck-history --frequent --since='%s' --seat='%s' --session-type=''", since, seat_id); g_free (since); g_debug ("GdmUserManager: running '%s'", command); error = NULL; if (! g_shell_parse_argv (command, NULL, &argv, &error)) { if (error != NULL) { g_warning ("Could not parse command: %s", error->message); g_error_free (error); } else { g_warning ("Could not parse command"); } goto out; } error = NULL; res = g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &manager->priv->ck_history_pid, /* pid */ NULL, &standard_out, NULL, &error); g_strfreev (argv); if (! res) { if (error != NULL) { g_warning ("Unable to run ck-history: %s", error->message); g_error_free (error); } else { g_warning ("Unable to run ck-history"); } goto out; } channel = g_io_channel_unix_new (standard_out); g_io_channel_set_close_on_unref (channel, TRUE); g_io_channel_set_flags (channel, g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK, NULL); manager->priv->ck_history_watchdog_id = g_timeout_add_seconds (1, (GSourceFunc) ck_history_watchdog, manager); manager->priv->ck_history_id = g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, (GIOFunc)ck_history_watch, manager); g_io_channel_unref (channel); out: g_free (command); return manager->priv->ck_history_id != 0; } static void reload_passwd_file (GHashTable *valid_shells, GSList *exclude_users, GSList *include_users, gboolean include_all, GHashTable *current_users_by_name, GSList **added_users, GSList **removed_users) { FILE *fp; GHashTableIter iter; GHashTable *new_users_by_name; GdmUser *user; char *name; new_users_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); errno = 0; fp = fopen (PATH_PASSWD, "r"); if (fp == NULL) { g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno)); goto out; } /* Make sure we keep users who are logged in no matter what. */ g_hash_table_iter_init (&iter, current_users_by_name); while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { struct passwd *pwent; get_pwent_for_name (name, &pwent); if (pwent == NULL) { continue; } g_object_freeze_notify (G_OBJECT (user)); _gdm_user_update_from_pwent (user, pwent); g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), g_object_ref (user)); } if (include_users != NULL) { GSList *l; for (l = include_users; l != NULL; l = l->next) { struct passwd *pwent; get_pwent_for_name (l->data, &pwent); if (pwent == NULL) { continue; } user = g_hash_table_lookup (new_users_by_name, pwent->pw_name); if (user != NULL) { /* already there */ continue; } user = g_hash_table_lookup (current_users_by_name, pwent->pw_name); if (user == NULL) { user = g_object_new (GDM_TYPE_USER, NULL); } else { g_object_ref (user); } g_object_freeze_notify (G_OBJECT (user)); _gdm_user_update_from_pwent (user, pwent); g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), user); } } if (include_all != TRUE) { g_debug ("GdmUserManager: include_all is FALSE"); } else { struct passwd *pwent; g_debug ("GdmUserManager: include_all is TRUE"); for (pwent = fgetpwent (fp); pwent != NULL; pwent = fgetpwent (fp)) { /* Skip users below MinimalUID... */ if (pwent->pw_uid < FALLBACK_MINIMAL_UID) { continue; } /* ...And users w/ invalid shells... */ if (pwent->pw_shell == NULL || !g_hash_table_lookup (valid_shells, pwent->pw_shell)) { g_debug ("GdmUserManager: skipping user with bad shell: %s", pwent->pw_name); continue; } /* always exclude the "gdm" user. */ if (strcmp (pwent->pw_name, GDM_USERNAME) == 0) { continue; } /* ...And explicitly excluded users */ if (exclude_users != NULL) { GSList *found; found = g_slist_find_custom (exclude_users, pwent->pw_name, match_name_cmpfunc); if (found != NULL) { g_debug ("GdmUserManager: explicitly skipping user: %s", pwent->pw_name); continue; } } user = g_hash_table_lookup (new_users_by_name, pwent->pw_name); if (user != NULL) { /* already there */ continue; } user = g_hash_table_lookup (current_users_by_name, pwent->pw_name); if (user == NULL) { user = g_object_new (GDM_TYPE_USER, NULL); } else { g_object_ref (user); } /* Freeze & update users not already in the new list */ g_object_freeze_notify (G_OBJECT (user)); _gdm_user_update_from_pwent (user, pwent); g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), user); } } /* Go through and handle added users */ g_hash_table_iter_init (&iter, new_users_by_name); while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { GdmUser *user2; user2 = g_hash_table_lookup (current_users_by_name, name); if (user2 == NULL) { *added_users = g_slist_prepend (*added_users, g_object_ref (user)); } } /* Go through and handle removed users */ g_hash_table_iter_init (&iter, current_users_by_name); while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { GdmUser *user2; user2 = g_hash_table_lookup (new_users_by_name, name); if (user2 == NULL) { *removed_users = g_slist_prepend (*removed_users, g_object_ref (user)); } } out: /* Cleanup */ fclose (fp); g_hash_table_iter_init (&iter, new_users_by_name); while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { g_object_thaw_notify (G_OBJECT (user)); } g_hash_table_destroy (new_users_by_name); } typedef struct { GdmUserManager *manager; GSList *exclude_users; GSList *include_users; gboolean include_all; GHashTable *shells; GHashTable *current_users_by_name; GSList *added_users; GSList *removed_users; } PasswdData; static void passwd_data_free (PasswdData *data) { if (data->manager != NULL) { g_object_unref (data->manager); } g_slist_foreach (data->added_users, (GFunc) g_object_unref, NULL); g_slist_free (data->added_users); g_slist_foreach (data->removed_users, (GFunc) g_object_unref, NULL); g_slist_free (data->removed_users); g_slist_foreach (data->exclude_users, (GFunc) g_free, NULL); g_slist_free (data->exclude_users); g_slist_foreach (data->include_users, (GFunc) g_free, NULL); g_slist_free (data->include_users); g_slice_free (PasswdData, data); } static gboolean reload_passwd_job_done (PasswdData *data) { GSList *l; g_debug ("GdmUserManager: done reloading passwd file"); /* Go through and handle added users */ for (l = data->added_users; l != NULL; l = l->next) { add_user (data->manager, l->data); } /* Go through and handle removed users */ for (l = data->removed_users; l != NULL; l = l->next) { remove_user (data->manager, l->data); } data->manager->priv->load_passwd_pending = FALSE; if (! data->manager->priv->is_loaded) { maybe_set_is_loaded (data->manager); if (data->manager->priv->include_all == TRUE) { monitor_local_users (data->manager); } } passwd_data_free (data); return FALSE; } static gboolean do_reload_passwd_job (GIOSchedulerJob *job, GCancellable *cancellable, PasswdData *data) { g_debug ("GdmUserManager: reloading passwd file worker"); reload_passwd_file (data->shells, data->exclude_users, data->include_users, data->include_all, data->current_users_by_name, &data->added_users, &data->removed_users); g_io_scheduler_job_send_to_mainloop_async (job, (GSourceFunc) reload_passwd_job_done, data, NULL); return FALSE; } static GSList * slist_deep_copy (const GSList *list) { GSList *retval; GSList *l; if (list == NULL) return NULL; retval = g_slist_copy ((GSList *) list); for (l = retval; l != NULL; l = l->next) { l->data = g_strdup (l->data); } return retval; } static void schedule_reload_passwd (GdmUserManager *manager) { PasswdData *passwd_data; manager->priv->load_passwd_pending = TRUE; passwd_data = g_slice_new0 (PasswdData); passwd_data->manager = g_object_ref (manager); passwd_data->shells = manager->priv->shells; passwd_data->exclude_users = slist_deep_copy (manager->priv->exclude_usernames); passwd_data->include_users = slist_deep_copy (manager->priv->include_usernames); passwd_data->include_all = manager->priv->include_all; passwd_data->current_users_by_name = manager->priv->users_by_name; passwd_data->added_users = NULL; passwd_data->removed_users = NULL; g_debug ("GdmUserManager: scheduling a passwd file update"); g_io_scheduler_push_job ((GIOSchedulerJobFunc) do_reload_passwd_job, passwd_data, NULL, G_PRIORITY_DEFAULT, NULL); } static void load_sessions_from_array (GdmUserManager *manager, const char * const *session_ids, int number_of_sessions) { int i; for (i = 0; i < number_of_sessions; i++) { load_new_session (manager, session_ids[i]); } } static void on_get_sessions_finished (DBusGProxy *proxy, DBusGProxyCall *call, GdmUserManager *manager) { GError *error; gboolean res; GPtrArray *sessions; g_assert (manager->priv->get_sessions_call == call); error = NULL; sessions = NULL; res = dbus_g_proxy_end_call (proxy, call, &error, GDM_DBUS_TYPE_G_OBJECT_PATH_ARRAY, &sessions, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_warning ("unable to determine sessions for seat: %s", error->message); g_error_free (error); } else { g_warning ("unable to determine sessions for seat"); } return; } manager->priv->get_sessions_call = NULL; g_assert (sessions->len <= G_MAXINT); load_sessions_from_array (manager, (const char * const *) sessions->pdata, (int) sessions->len); g_ptr_array_foreach (sessions, (GFunc) g_free, NULL); g_ptr_array_free (sessions, TRUE); maybe_set_is_loaded (manager); } static void load_sessions (GdmUserManager *manager) { DBusGProxyCall *call; if (manager->priv->seat.proxy == NULL) { g_debug ("GdmUserManager: no seat proxy; can't load sessions"); return; } call = dbus_g_proxy_begin_call (manager->priv->seat.proxy, "GetSessions", (DBusGProxyCallNotify) on_get_sessions_finished, manager, NULL, G_TYPE_INVALID); if (call == NULL) { g_warning ("GdmUserManager: failed to make GetSessions call"); return; } manager->priv->get_sessions_call = call; } static void reload_shells (GdmUserManager *manager) { char *shell; setusershell (); g_hash_table_remove_all (manager->priv->shells); for (shell = getusershell (); shell != NULL; shell = getusershell ()) { /* skip well known not-real shells */ if (shell == NULL || strcmp (shell, "/sbin/nologin") == 0 || strcmp (shell, "/bin/false") == 0) { g_debug ("GdmUserManager: skipping shell %s", shell); continue; } g_hash_table_insert (manager->priv->shells, g_strdup (shell), GUINT_TO_POINTER (TRUE)); } endusershell (); } static void load_users_manually (GdmUserManager *manager) { manager->priv->shells = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); reload_shells (manager); load_sessions (manager); load_ck_history (manager); schedule_reload_passwd (manager); } static void load_users (GdmUserManager *manager) { g_assert (manager->priv->accounts_proxy != NULL); g_debug ("GdmUserManager: calling 'ListCachedUsers'"); dbus_g_proxy_begin_call (manager->priv->accounts_proxy, "ListCachedUsers", on_list_cached_users_finished, manager, NULL, G_TYPE_INVALID); manager->priv->listing_cached_users = TRUE; } static void load_seat_incrementally (GdmUserManager *manager) { g_assert (manager->priv->seat.proxy == NULL); switch (manager->priv->seat.state) { case GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID: get_current_session_id (manager); break; case GDM_USER_MANAGER_SEAT_STATE_GET_ID: get_seat_id_for_current_session (manager); break; case GDM_USER_MANAGER_SEAT_STATE_GET_PROXY: get_seat_proxy (manager); break; case GDM_USER_MANAGER_SEAT_STATE_LOADED: break; default: g_assert_not_reached (); } if (manager->priv->seat.state == GDM_USER_MANAGER_SEAT_STATE_LOADED) { load_sessions (manager); load_ck_history (manager); } maybe_set_is_loaded (manager); } static gboolean load_idle (GdmUserManager *manager) { manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED + 1; load_seat_incrementally (manager); load_users (manager); manager->priv->load_id = 0; return FALSE; } static void queue_load_seat_and_users (GdmUserManager *manager) { if (manager->priv->load_id > 0) { return; } manager->priv->load_id = g_idle_add ((GSourceFunc)load_idle, manager); } static gboolean reload_passwd_idle (GdmUserManager *manager) { schedule_reload_passwd (manager); manager->priv->reload_passwd_id = 0; return FALSE; } static void queue_reload_passwd (GdmUserManager *manager) { if (manager->priv->reload_passwd_id > 0) { g_source_remove (manager->priv->reload_passwd_id); } manager->priv->reload_passwd_id = g_timeout_add_seconds (RELOAD_PASSWD_THROTTLE_SECS, (GSourceFunc)reload_passwd_idle, manager); } static void on_shells_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, GdmUserManager *manager) { if (event_type != G_FILE_MONITOR_EVENT_CHANGED && event_type != G_FILE_MONITOR_EVENT_CREATED) { return; } reload_shells (manager); queue_reload_passwd (manager); } static void on_passwd_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, GdmUserManager *manager) { if (event_type != G_FILE_MONITOR_EVENT_CHANGED && event_type != G_FILE_MONITOR_EVENT_CREATED) { return; } queue_reload_passwd (manager); } static void gdm_user_manager_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GdmUserManager *manager; manager = GDM_USER_MANAGER (object); switch (prop_id) { case PROP_IS_LOADED: g_value_set_boolean (value, manager->priv->is_loaded); break; case PROP_HAS_MULTIPLE_USERS: g_value_set_boolean (value, manager->priv->has_multiple_users); break; case PROP_INCLUDE_ALL: g_value_set_boolean (value, manager->priv->include_all); break; case PROP_INCLUDE_USERNAMES_LIST: g_value_set_pointer (value, manager->priv->include_usernames); break; case PROP_EXCLUDE_USERNAMES_LIST: g_value_set_pointer (value, manager->priv->exclude_usernames); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void set_include_usernames (GdmUserManager *manager, GSList *list) { if (manager->priv->include_usernames != NULL) { g_slist_foreach (manager->priv->include_usernames, (GFunc) g_free, NULL); g_slist_free (manager->priv->include_usernames); } manager->priv->include_usernames = slist_deep_copy (list); } static void set_exclude_usernames (GdmUserManager *manager, GSList *list) { if (manager->priv->exclude_usernames != NULL) { g_slist_foreach (manager->priv->exclude_usernames, (GFunc) g_free, NULL); g_slist_free (manager->priv->exclude_usernames); } manager->priv->exclude_usernames = slist_deep_copy (list); } static void set_include_all (GdmUserManager *manager, gboolean all) { if (manager->priv->include_all != all) { manager->priv->include_all = all; } } static void gdm_user_manager_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GdmUserManager *self; self = GDM_USER_MANAGER (object); switch (prop_id) { case PROP_INCLUDE_ALL: set_include_all (self, g_value_get_boolean (value)); break; case PROP_INCLUDE_USERNAMES_LIST: set_include_usernames (self, g_value_get_pointer (value)); break; case PROP_EXCLUDE_USERNAMES_LIST: set_exclude_usernames (self, g_value_get_pointer (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void monitor_local_users (GdmUserManager *manager) { GFile *file; GError *error; g_debug ("GdmUserManager: Monitoring local users"); /* /etc/shells */ file = g_file_new_for_path (_PATH_SHELLS); error = NULL; manager->priv->shells_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); if (manager->priv->shells_monitor != NULL) { g_signal_connect (manager->priv->shells_monitor, "changed", G_CALLBACK (on_shells_monitor_changed), manager); } else { g_warning ("Unable to monitor %s: %s", _PATH_SHELLS, error->message); g_error_free (error); } g_object_unref (file); /* /etc/passwd */ file = g_file_new_for_path (PATH_PASSWD); manager->priv->passwd_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); if (manager->priv->passwd_monitor != NULL) { g_signal_connect (manager->priv->passwd_monitor, "changed", G_CALLBACK (on_passwd_monitor_changed), manager); } else { g_warning ("Unable to monitor %s: %s", PATH_PASSWD, error->message); g_error_free (error); } g_object_unref (file); } static void gdm_user_manager_class_init (GdmUserManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gdm_user_manager_finalize; object_class->get_property = gdm_user_manager_get_property; object_class->set_property = gdm_user_manager_set_property; g_object_class_install_property (object_class, PROP_IS_LOADED, g_param_spec_boolean ("is-loaded", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_HAS_MULTIPLE_USERS, g_param_spec_boolean ("has-multiple-users", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_INCLUDE_ALL, g_param_spec_boolean ("include-all", NULL, NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_INCLUDE_USERNAMES_LIST, g_param_spec_pointer ("include-usernames-list", NULL, NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_EXCLUDE_USERNAMES_LIST, g_param_spec_pointer ("exclude-usernames-list", NULL, NULL, G_PARAM_READWRITE)); signals [USER_ADDED] = g_signal_new ("user-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmUserManagerClass, user_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); signals [USER_REMOVED] = g_signal_new ("user-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmUserManagerClass, user_removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); signals [USER_IS_LOGGED_IN_CHANGED] = g_signal_new ("user-is-logged-in-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmUserManagerClass, user_is_logged_in_changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); signals [USER_CHANGED] = g_signal_new ("user-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmUserManagerClass, user_changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); g_type_class_add_private (klass, sizeof (GdmUserManagerPrivate)); } /** * gdm_user_manager_queue_load: * @manager: a #GdmUserManager * * Queue loading users into user manager. This must be called, and the * #GdmUserManager:is-loaded property must be %TRUE before calling * gdm_user_manager_list_users() */ void gdm_user_manager_queue_load (GdmUserManager *manager) { g_return_if_fail (GDM_IS_USER_MANAGER (manager)); if (! manager->priv->is_loaded) { queue_load_seat_and_users (manager); } } static void gdm_user_manager_init (GdmUserManager *manager) { GError *error; manager->priv = GDM_USER_MANAGER_GET_PRIVATE (manager); /* sessions */ manager->priv->sessions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); /* users */ manager->priv->users_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); manager->priv->users_by_object_path = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); g_assert (manager->priv->seat.proxy == NULL); error = NULL; manager->priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); if (manager->priv->connection == NULL) { if (error != NULL) { g_warning ("Failed to connect to the D-Bus daemon: %s", error->message); g_error_free (error); } else { g_warning ("Failed to connect to the D-Bus daemon"); } return; } get_accounts_proxy (manager); manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED; } static void gdm_user_manager_finalize (GObject *object) { GdmUserManager *manager; GSList *node; g_return_if_fail (object != NULL); g_return_if_fail (GDM_IS_USER_MANAGER (object)); manager = GDM_USER_MANAGER (object); g_return_if_fail (manager->priv != NULL); if (manager->priv->ck_history_pid > 0) { g_debug ("Killing ck-history process"); signal_pid (manager->priv->ck_history_pid, SIGTERM); } g_slist_foreach (manager->priv->new_sessions, (GFunc) unload_new_session, NULL); g_slist_free (manager->priv->new_sessions); g_slist_foreach (manager->priv->fetch_user_requests, (GFunc) free_fetch_user_request, NULL); g_slist_free (manager->priv->fetch_user_requests); node = manager->priv->new_users; while (node != NULL) { GdmUser *user; GSList *next_node; user = GDM_USER (node->data); next_node = node->next; g_signal_handlers_disconnect_by_func (user, on_new_user_loaded, manager); g_object_unref (user); manager->priv->new_users = g_slist_delete_link (manager->priv->new_users, node); node = next_node; } unload_seat (manager); if (manager->priv->exclude_usernames != NULL) { g_slist_foreach (manager->priv->exclude_usernames, (GFunc) g_free, NULL); g_slist_free (manager->priv->exclude_usernames); } if (manager->priv->include_usernames != NULL) { g_slist_foreach (manager->priv->include_usernames, (GFunc) g_free, NULL); g_slist_free (manager->priv->include_usernames); } if (manager->priv->seat.proxy != NULL) { g_object_unref (manager->priv->seat.proxy); } if (manager->priv->accounts_proxy != NULL) { g_object_unref (manager->priv->accounts_proxy); } if (manager->priv->ck_history_id != 0) { g_source_remove (manager->priv->ck_history_id); manager->priv->ck_history_id = 0; } if (manager->priv->ck_history_watchdog_id != 0) { g_source_remove (manager->priv->ck_history_watchdog_id); manager->priv->ck_history_watchdog_id = 0; } if (manager->priv->load_id > 0) { g_source_remove (manager->priv->load_id); manager->priv->load_id = 0; } if (manager->priv->reload_passwd_id > 0) { g_source_remove (manager->priv->reload_passwd_id); manager->priv->reload_passwd_id = 0; } g_hash_table_destroy (manager->priv->sessions); if (manager->priv->passwd_monitor != NULL) { g_file_monitor_cancel (manager->priv->passwd_monitor); } g_hash_table_destroy (manager->priv->users_by_name); g_hash_table_destroy (manager->priv->users_by_object_path); if (manager->priv->shells_monitor != NULL) { g_file_monitor_cancel (manager->priv->shells_monitor); } if (manager->priv->shells != NULL) { g_hash_table_destroy (manager->priv->shells); } G_OBJECT_CLASS (gdm_user_manager_parent_class)->finalize (object); } /** * gdm_user_manager_ref_default: * * Queue loading users into user manager. This must be called, and the * #GdmUserManager:is-loaded property must be %TRUE before calling * gdm_user_manager_list_users() * * Returns: (transfer full): user manager object */ GdmUserManager * gdm_user_manager_ref_default (void) { if (user_manager_object != NULL) { g_object_ref (user_manager_object); } else { user_manager_object = g_object_new (GDM_TYPE_USER_MANAGER, NULL); g_object_add_weak_pointer (user_manager_object, (gpointer *) &user_manager_object); } return GDM_USER_MANAGER (user_manager_object); }