/* -*- 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 #include #include #include #include #include #include #include #include "gdm-user-manager.h" #include "gdm-user-private.h" #include "gdm-settings-keys.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_PATH "/org/freedesktop/ConsoleKit" #define CK_INTERFACE "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" /* Prefs Defaults */ #define DEFAULT_ALLOW_ROOT TRUE #define DEFAULT_MAX_ICON_SIZE 128 #define DEFAULT_USER_MAX_FILE 65536 #ifdef __sun #define DEFAULT_MINIMAL_UID 100 #else #define DEFAULT_MINIMAL_UID 500 #endif #ifndef _PATH_SHELLS #define _PATH_SHELLS "/etc/shells" #endif #define PATH_PASSWD "/etc/passwd" #define DEFAULT_GLOBAL_FACE_DIR DATADIR "/faces" #define DEFAULT_USER_ICON "stock_person" struct GdmUserManagerPrivate { GHashTable *users; GHashTable *sessions; GHashTable *shells; DBusGConnection *connection; DBusGProxy *seat_proxy; char *seat_id; GFileMonitor *passwd_monitor; GFileMonitor *shells_monitor; GSList *exclude; GSList *include; gboolean include_all; guint reload_id; guint ck_history_id; guint8 users_dirty : 1; }; enum { LOADING_USERS, USERS_LOADED, USER_ADDED, USER_REMOVED, USER_IS_LOGGED_IN_CHANGED, USER_LOGIN_FREQUENCY_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 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; } /* needs to stay in sync with gdm-slave */ static char * _get_primary_user_session_id (GdmUserManager *manager, GdmUser *user) { gboolean can_activate_sessions; GList *sessions; GList *l; char *primary_ssid; 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; } sessions = gdm_user_get_sessions (user); if (sessions == NULL) { g_warning ("unable to determine sessions for user: %s", gdm_user_get_user_name (user)); goto out; } for (l = sessions; l != NULL; l = l->next) { const char *ssid; ssid = l->data; /* FIXME: better way to choose? */ if (ssid != NULL) { primary_ssid = g_strdup (ssid); break; } } out: return primary_ssid; } 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 ("Failed to identify the session type: %s", error->message); g_error_free (error); } else { g_debug ("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 ("GdmSlave: 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); 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->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; char *ssid; gboolean res; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE); g_return_val_if_fail (GDM_IS_USER (user), FALSE); ret = FALSE; ssid = _get_primary_user_session_id (manager, 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; 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_icon_changed (GdmUser *user, GdmUserManager *manager) { g_debug ("GdmUserManager: user icon changed"); } static char * get_seat_id_for_session (DBusGConnection *connection, const char *session_id) { DBusGProxy *proxy; GError *error; char *seat_id; gboolean res; proxy = NULL; seat_id = NULL; proxy = dbus_g_proxy_new_for_name (connection, CK_NAME, session_id, CK_SESSION_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit session object"); goto out; } error = NULL; res = dbus_g_proxy_call (proxy, "GetSeatId", &error, G_TYPE_INVALID, DBUS_TYPE_G_OBJECT_PATH, &seat_id, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_debug ("Failed to identify the current seat: %s", error->message); g_error_free (error); } else { g_debug ("Failed to identify the current seat"); } } out: if (proxy != NULL) { g_object_unref (proxy); } return seat_id; } static char * get_x11_display_for_session (DBusGConnection *connection, const char *session_id) { DBusGProxy *proxy; GError *error; char *x11_display; gboolean res; proxy = NULL; x11_display = NULL; proxy = dbus_g_proxy_new_for_name (connection, CK_NAME, session_id, CK_SESSION_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit session object"); goto out; } error = NULL; res = dbus_g_proxy_call (proxy, "GetX11Display", &error, G_TYPE_INVALID, G_TYPE_STRING, &x11_display, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_debug ("Failed to identify the x11 display: %s", error->message); g_error_free (error); } else { g_debug ("Failed to identify the x11 display"); } } out: if (proxy != NULL) { g_object_unref (proxy); } return x11_display; } static gint match_name_cmpfunc (gconstpointer a, gconstpointer b) { if (a == NULL || b == NULL) { return -1; } return g_strcmp0 ((char *) a, (char *) b); } static gboolean user_in_exclude_list (GdmUserManager *manager, const char *user) { GSList *found; gboolean ret = FALSE; g_debug ("checking exclude list"); /* always exclude the "gdm" user. */ if (user == NULL || (strcmp (user, GDM_USERNAME) == 0)) { return TRUE; } if (manager->priv->exclude != NULL) { found = g_slist_find_custom (manager->priv->exclude, user, match_name_cmpfunc); if (found != NULL) { ret = TRUE; } } return ret; } static gboolean maybe_add_session_for_user (GdmUserManager *manager, GdmUser *user, const char *ssid) { char *sid; char *x11_display; gboolean ret; ret = FALSE; sid = NULL; x11_display = NULL; /* skip if on another seat */ sid = get_seat_id_for_session (manager->priv->connection, ssid); if (sid == NULL || manager->priv->seat_id == NULL || strcmp (sid, manager->priv->seat_id) != 0) { g_debug ("GdmUserManager: not adding session on other seat: %s", ssid); goto out; } /* skip if doesn't have an x11 display */ x11_display = get_x11_display_for_session (manager->priv->connection, ssid); if (x11_display == NULL || x11_display[0] == '\0') { g_debug ("GdmUserManager: not adding session without a x11 display: %s", ssid); goto out; } if (user_in_exclude_list (manager, gdm_user_get_user_name (user))) { g_debug ("GdmUserManager: excluding user '%s'", gdm_user_get_user_name (user)); goto out; } 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)); ret = TRUE; out: g_free (sid); g_free (x11_display); return ret; } static void add_sessions_for_user (GdmUserManager *manager, GdmUser *user) { DBusGProxy *proxy; GError *error; gboolean res; guint32 uid; GPtrArray *sessions; int i; 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 out; } uid = gdm_user_get_uid (user); g_debug ("Getting list of sessions for user %u", uid); error = NULL; res = dbus_g_proxy_call (proxy, "GetSessionsForUnixUser", &error, G_TYPE_UINT, uid, G_TYPE_INVALID, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &sessions, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_debug ("Failed to find sessions for user: %s", error->message); g_error_free (error); } else { g_debug ("Failed to find sessions for user"); } goto out; } g_debug ("Found %d sessions for user %s", sessions->len, gdm_user_get_user_name (user)); for (i = 0; i < sessions->len; i++) { char *ssid; ssid = g_ptr_array_index (sessions, i); maybe_add_session_for_user (manager, user, ssid); } g_ptr_array_foreach (sessions, (GFunc)g_free, NULL); g_ptr_array_free (sessions, TRUE); out: if (proxy != NULL) { g_object_unref (proxy); } } static GdmUser * create_user (GdmUserManager *manager) { GdmUser *user; user = g_object_new (GDM_TYPE_USER, "manager", manager, NULL); g_signal_connect (user, "sessions-changed", G_CALLBACK (on_user_sessions_changed), manager); g_signal_connect (user, "icon-changed", G_CALLBACK (on_user_icon_changed), manager); return user; } static gint match_real_name_cmpfunc (gconstpointer a, gconstpointer b) { if (a == b) return -1; return g_strcmp0 (gdm_user_get_real_name ((GdmUser *) a), gdm_user_get_real_name ((GdmUser *) b)); } static gboolean match_real_name_hrfunc (gpointer key, gpointer value, gpointer user_data) { return (g_strcmp0 (user_data, gdm_user_get_real_name (value)) == 0); } static void add_user (GdmUserManager *manager, GdmUser *user) { GdmUser *dup; add_sessions_for_user (manager, user); dup = g_hash_table_find (manager->priv->users, match_real_name_hrfunc, (char *) gdm_user_get_real_name (user)); if (dup != NULL) { _gdm_user_show_full_display_name (user); _gdm_user_show_full_display_name (dup); } g_hash_table_insert (manager->priv->users, g_strdup (gdm_user_get_user_name (user)), g_object_ref (user)); g_signal_emit (manager, signals[USER_ADDED], 0, user); } static GdmUser * add_new_user_for_pwent (GdmUserManager *manager, struct passwd *pwent) { GdmUser *user; g_debug ("Creating new user"); user = create_user (manager); _gdm_user_update (user, pwent); add_user (manager, user); return user; } static char * get_current_seat_id (DBusGConnection *connection) { DBusGProxy *proxy; GError *error; char *session_id; char *seat_id; gboolean res; proxy = NULL; session_id = NULL; seat_id = NULL; proxy = dbus_g_proxy_new_for_name (connection, CK_NAME, CK_MANAGER_PATH, CK_MANAGER_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit manager object"); goto out; } error = NULL; res = dbus_g_proxy_call (proxy, "GetCurrentSession", &error, G_TYPE_INVALID, DBUS_TYPE_G_OBJECT_PATH, &session_id, G_TYPE_INVALID); 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"); } goto out; } seat_id = get_seat_id_for_session (connection, session_id); out: if (proxy != NULL) { g_object_unref (proxy); } g_free (session_id); return seat_id; } static gboolean get_uid_from_session_id (GdmUserManager *manager, const char *session_id, uid_t *uidp) { DBusGProxy *proxy; GError *error; guint uid; gboolean res; 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 session object"); return FALSE; } error = NULL; res = dbus_g_proxy_call (proxy, "GetUnixUser", &error, G_TYPE_INVALID, G_TYPE_UINT, &uid, G_TYPE_INVALID); g_object_unref (proxy); if (! res) { if (error != NULL) { g_warning ("Failed to query the session: %s", error->message); g_error_free (error); } else { g_warning ("Failed to query the session"); } return FALSE; } if (uidp != NULL) { *uidp = (uid_t) uid; } return TRUE; } static void seat_session_added (DBusGProxy *seat_proxy, const char *session_id, GdmUserManager *manager) { uid_t uid; gboolean res; struct passwd *pwent; GdmUser *user; gboolean is_new; g_debug ("Session added: %s", session_id); res = get_uid_from_session_id (manager, session_id, &uid); if (! res) { g_warning ("Unable to lookup user for session"); return; } errno = 0; pwent = getpwuid (uid); if (pwent == NULL) { g_warning ("Unable to lookup user ID %d: %s", (int)uid, g_strerror (errno)); return; } /* check exclusions up front */ if (user_in_exclude_list (manager, pwent->pw_name)) { g_debug ("GdmUserManager: excluding user '%s'", pwent->pw_name); return; } user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); if (user == NULL) { g_debug ("Creating new user"); user = create_user (manager); _gdm_user_update (user, pwent); is_new = TRUE; } else { is_new = FALSE; } res = maybe_add_session_for_user (manager, user, session_id); /* only add the user if we added a session */ if (is_new) { if (res) { add_user (manager, user); } else { g_object_unref (user); } } } static void seat_session_removed (DBusGProxy *seat_proxy, const char *session_id, GdmUserManager *manager) { GdmUser *user; char *username; g_debug ("Session removed: %s", session_id); /* 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, 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_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; 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; } manager->priv->seat_id = get_current_seat_id (manager->priv->connection); if (manager->priv->seat_id == NULL) { return; } g_debug ("GdmUserManager: Found current seat: %s", manager->priv->seat_id); 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"); } return; } g_signal_connect (proxy, "destroy", G_CALLBACK (on_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; } /** * gdm_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 named @username * from @manager. This pointer is not a reference, and should not be released. * * Returns: a pointer to a #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, username); if (user == NULL) { struct passwd *pwent; pwent = getpwnam (username); if (pwent != NULL) { user = add_new_user_for_pwent (manager, pwent); } } return user; } GdmUser * gdm_user_manager_get_user_by_uid (GdmUserManager *manager, uid_t uid) { GdmUser *user; struct passwd *pwent; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); pwent = getpwuid (uid); if (pwent == NULL) { g_warning ("GdmUserManager: unable to lookup uid %d", (int)uid); return NULL; } user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); if (user == NULL) { user = add_new_user_for_pwent (manager, pwent); } return user; } static void listify_hash_values_hfunc (gpointer key, gpointer value, gpointer user_data) { GSList **list = user_data; *list = g_slist_prepend (*list, value); } 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, 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 (user_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; } g_object_set (user, "login-frequency", frequency, NULL); g_signal_emit (manager, signals [USER_LOGIN_FREQUENCY_CHANGED], 0, user); g_free (username); } 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) { g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0); manager->priv->ck_history_id = 0; return FALSE; } return TRUE; } static void reload_ck_history (GdmUserManager *manager) { char *command; const char *seat_id; GError *error; gboolean res; char **argv; int standard_out; GIOChannel *channel; 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 find users: no seat-id found"); return; } command = g_strdup_printf ("ck-history --frequent --seat='%s' --session-type=''", seat_id); 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, NULL, /* 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_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); } static void add_included_user (char *username, GdmUserManager *manager) { GdmUser *user; g_debug ("Adding included user %s", username); /* * 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, username); if (user == NULL) { g_debug ("GdmUserManager: unable to lookup user '%s'", username); g_free (username); return; } } static void add_included_users (GdmUserManager *manager) { /* Add users who are specifically included */ if (manager->priv->include != NULL) { g_slist_foreach (manager->priv->include, (GFunc)add_included_user, (gpointer)manager); } } static void reload_passwd (GdmUserManager *manager) { struct passwd *pwent; GSList *old_users; GSList *new_users; GSList *list; GSList *dup; FILE *fp; 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 (manager->priv->users, listify_hash_values_hfunc, &old_users); g_slist_foreach (old_users, (GFunc) g_object_ref, NULL); /* Make sure we keep users who are logged in no matter what. */ for (list = old_users; list; list = list->next) { if (gdm_user_get_num_sessions (list->data) > 0) { g_object_freeze_notify (G_OBJECT (list->data)); _gdm_user_show_short_display_name (list->data); new_users = g_slist_prepend (new_users, g_object_ref (list->data)); } } if (manager->priv->include_all != TRUE) { g_debug ("GdmUserManager: include_all is FALSE"); } else { g_debug ("GdmUserManager: include_all is TRUE"); for (pwent = fgetpwent (fp); pwent != NULL; pwent = fgetpwent (fp)) { GdmUser *user; user = NULL; /* Skip users below MinimalUID... */ if (pwent->pw_uid < DEFAULT_MINIMAL_UID) { continue; } /* ...And users w/ invalid shells... */ if (pwent->pw_shell == NULL || !g_hash_table_lookup (manager->priv->shells, pwent->pw_shell)) { g_debug ("GdmUserManager: skipping user with bad shell: %s", pwent->pw_name); continue; } /* ...And explicitly excluded users */ if (user_in_exclude_list (manager, pwent->pw_name)) { g_debug ("GdmUserManager: explicitly skipping user: %s", pwent->pw_name); continue; } user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); /* Update users already in the *new* list */ if (g_slist_find (new_users, user)) { _gdm_user_update (user, pwent); continue; } if (user == NULL) { user = create_user (manager); } else { g_object_ref (user); } /* Freeze & update users not already in the new list */ g_object_freeze_notify (G_OBJECT (user)); _gdm_user_update (user, pwent); new_users = g_slist_prepend (new_users, user); } } /* Go through and handle removed users */ for (list = old_users; list; list = list->next) { if (! g_slist_find (new_users, list->data)) { g_signal_emit (manager, signals[USER_REMOVED], 0, list->data); g_hash_table_remove (manager->priv->users, gdm_user_get_user_name (list->data)); } } /* Go through and handle added users or update display names */ for (list = new_users; list; list = list->next) { if (g_slist_find (old_users, list->data)) { dup = g_slist_find_custom (new_users, list->data, match_real_name_cmpfunc); if (dup != NULL) { _gdm_user_show_full_display_name (list->data); _gdm_user_show_full_display_name (dup->data); } } else { add_user (manager, list->data); } } add_included_users (manager); 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_users (GdmUserManager *manager) { reload_ck_history (manager); reload_passwd (manager); } static gboolean reload_users_timeout (GdmUserManager *manager) { reload_users (manager); manager->priv->reload_id = 0; return FALSE; } static void queue_reload_users (GdmUserManager *manager) { if (manager->priv->reload_id > 0) { return; } g_signal_emit (G_OBJECT (manager), signals[LOADING_USERS], 0); manager->priv->reload_id = g_idle_add ((GSourceFunc)reload_users_timeout, manager); } 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 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); 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; } reload_passwd (manager); } static void gdm_user_manager_class_init (GdmUserManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gdm_user_manager_finalize; signals [LOADING_USERS] = g_signal_new ("loading-users", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmUserManagerClass, loading_users), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals [USERS_LOADED] = g_signal_new ("users-loaded", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmUserManagerClass, users_loaded), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals [USER_ADDED] = g_signal_new ("user-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (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_LOGIN_FREQUENCY_CHANGED] = g_signal_new ("user-login-frequency-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmUserManagerClass, user_login_frequency_changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); g_type_class_add_private (klass, sizeof (GdmUserManagerPrivate)); } static void gdm_set_string_list (char *value, GSList **retval) { char **temp_array; int i; *retval = NULL; if (value == NULL || *value == '\0') { g_debug ("Not adding NULL user"); *retval = NULL; return; } temp_array = g_strsplit (value, ",", 0); for (i = 0; temp_array[i] != NULL; i++) { g_debug ("Adding value %s", temp_array[i]); g_strstrip (temp_array[i]); *retval = g_slist_prepend (*retval, g_strdup (temp_array[i])); } g_strfreev (temp_array); } static void gdm_user_manager_init (GdmUserManager *manager) { int i; GFile *file; GError *error; char *temp; gboolean res; manager->priv = GDM_USER_MANAGER_GET_PRIVATE (manager); /* exclude/include */ g_debug ("Setting users to include:"); res = gdm_settings_client_get_string (GDM_KEY_INCLUDE, &temp); gdm_set_string_list (temp, &manager->priv->include); g_debug ("Setting users to exclude:"); res = gdm_settings_client_get_string (GDM_KEY_EXCLUDE, &temp); gdm_set_string_list (temp, &manager->priv->exclude); res = gdm_settings_client_get_boolean (GDM_KEY_INCLUDE_ALL, &manager->priv->include_all); /* sessions */ manager->priv->sessions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); /* users */ manager->priv->users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_run_dispose); if (manager->priv->include_all == TRUE) { /* /etc/shells */ manager->priv->shells = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); reload_shells (manager); 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); } get_seat_proxy (manager); queue_reload_users (manager); manager->priv->users_dirty = FALSE; } static void gdm_user_manager_finalize (GObject *object) { GdmUserManager *manager; 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->exclude != NULL) { g_slist_free (manager->priv->exclude); } if (manager->priv->include != NULL) { g_slist_free (manager->priv->include); } if (manager->priv->seat_proxy != NULL) { g_object_unref (manager->priv->seat_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->reload_id > 0) { g_source_remove (manager->priv->reload_id); manager->priv->reload_id = 0; } g_hash_table_destroy (manager->priv->sessions); g_file_monitor_cancel (manager->priv->passwd_monitor); g_hash_table_destroy (manager->priv->users); g_file_monitor_cancel (manager->priv->shells_monitor); g_hash_table_destroy (manager->priv->shells); g_free (manager->priv->seat_id); G_OBJECT_CLASS (gdm_user_manager_parent_class)->finalize (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); }