# # Description: Add org.gnome.DisplayManager.UserManager interface to gdmserver to be shared between greeter, configuration applet and FUSA applet # Ubuntu: https://bugs.launchpad.net/ubuntu/+source/gdm/+bug/423450 # Upstream: https://bugzilla.gnome.org/show_bug.cgi?id=593996 # From 35aa54f6fe3ff8fa8d6129f805ea243a6204aa65 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Fri, 11 Sep 2009 16:01:30 +1000 Subject: [PATCH] Add org.gnome.DisplayManager.UserManager interface to gdmserver diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/configure.ac gdm-2.28.0.new/configure.ac --- gdm-2.28.0/configure.ac 2009-10-14 18:30:50.000000000 +1100 +++ gdm-2.28.0.new/configure.ac 2009-10-14 18:30:51.000000000 +1100 @@ -73,6 +73,7 @@ polkit-gobject-1 >= $POLKIT_GOBJECT_REQUIRED_VERSION gobject-2.0 >= $GLIB_REQUIRED_VERSION gio-2.0 >= $GLIB_REQUIRED_VERSION + gconf-2.0 >= $GCONF_REQUIRED_VERSION hal ) AC_SUBST(DAEMON_CFLAGS) @@ -949,6 +950,17 @@ fi AC_SUBST(logdir, $GDM_LOG_DIR) +AC_ARG_WITH(cache-dir, + AS_HELP_STRING([--with-cache-dir=], + [cache dir])) + +if ! test -z "$with_cache_dir"; then + GDM_CACHE_DIR=$with_cache_dir +else + GDM_CACHE_DIR=/var/cache/gdm +fi +AC_SUBST(cachedir, $GDM_CACHE_DIR) + withval="" AC_ARG_WITH(at-bindir, AS_HELP_STRING([--with-at-bindir=] diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/daemon/gdm-user.c gdm-2.28.0.new/daemon/gdm-user.c --- gdm-2.28.0/daemon/gdm-user.c 1970-01-01 10:00:00.000000000 +1000 +++ gdm-2.28.0.new/daemon/gdm-user.c 2009-10-14 18:30:51.000000000 +1100 @@ -0,0 +1,596 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004-2005 James M. Cape . + * 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 + +#include +#include +#include +#include +#include + +#include +#include + +#include "gdm-user-manager.h" +#include "gdm-user-private.h" + +#define GDM_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_USER, GdmUserClass)) +#define GDM_IS_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_USER)) +#define GDM_USER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), GDM_TYPE_USER, GdmUserClass)) + +enum { + PROP_0, + PROP_REAL_NAME, + PROP_USER_NAME, + PROP_UID, + PROP_HOME_DIR, + PROP_SHELL, + PROP_ICON_URL, + PROP_LOGIN_FREQUENCY, +}; + +enum { + ICON_CHANGED, + SESSIONS_CHANGED, + LAST_SIGNAL +}; + +struct _GdmUser { + GObject parent; + + uid_t uid; + char *user_name; + char *real_name; + char *home_dir; + char *shell; + char *icon_url; + GList *sessions; + gulong login_frequency; + + GFileMonitor *icon_monitor; +}; + +typedef struct _GdmUserClass +{ + GObjectClass parent_class; + + void (* icon_changed) (GdmUser *user); + void (* sessions_changed) (GdmUser *user); +} GdmUserClass; + +static void gdm_user_finalize (GObject *object); + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GdmUser, gdm_user, G_TYPE_OBJECT) + +static int +session_compare (const char *a, + const char *b) +{ + if (a == NULL) { + return 1; + } else if (b == NULL) { + return -1; + } + + return strcmp (a, b); +} + +void +_gdm_user_add_session (GdmUser *user, + const char *ssid) +{ + GList *li; + + g_return_if_fail (GDM_IS_USER (user)); + g_return_if_fail (ssid != NULL); + + li = g_list_find_custom (user->sessions, ssid, (GCompareFunc)session_compare); + if (li == NULL) { + g_debug ("GdmUser: adding session %s", ssid); + user->sessions = g_list_prepend (user->sessions, g_strdup (ssid)); + g_signal_emit (user, signals[SESSIONS_CHANGED], 0); + } else { + g_debug ("GdmUser: session already present: %s", ssid); + } +} + +void +_gdm_user_remove_session (GdmUser *user, + const char *ssid) +{ + GList *li; + + g_return_if_fail (GDM_IS_USER (user)); + g_return_if_fail (ssid != NULL); + + li = g_list_find_custom (user->sessions, ssid, (GCompareFunc)session_compare); + if (li != NULL) { + g_debug ("GdmUser: removing session %s", ssid); + g_free (li->data); + user->sessions = g_list_delete_link (user->sessions, li); + g_signal_emit (user, signals[SESSIONS_CHANGED], 0); + } else { + g_debug ("GdmUser: session not found: %s", ssid); + } +} + +guint +gdm_user_get_num_sessions (GdmUser *user) +{ + return g_list_length (user->sessions); +} + +GList * +gdm_user_get_sessions (GdmUser *user) +{ + return user->sessions; +} + +static void +gdm_user_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmUser *user; + + user = GDM_USER (object); + + switch (param_id) { + case PROP_LOGIN_FREQUENCY: + user->login_frequency = g_value_get_ulong (value); + g_object_notify (G_OBJECT (user), "login-frequency"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gdm_user_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GdmUser *user; + + user = GDM_USER (object); + + switch (param_id) { + case PROP_USER_NAME: + g_value_set_string (value, user->user_name); + break; + case PROP_REAL_NAME: + g_value_set_string (value, user->real_name); + break; + case PROP_HOME_DIR: + g_value_set_string (value, user->home_dir); + break; + case PROP_UID: + g_value_set_ulong (value, user->uid); + break; + case PROP_SHELL: + g_value_set_string (value, user->shell); + break; + case PROP_ICON_URL: + g_value_set_string (value, user->icon_url); + break; + case PROP_LOGIN_FREQUENCY: + g_value_set_ulong (value, user->login_frequency); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gdm_user_class_init (GdmUserClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = gdm_user_set_property; + gobject_class->get_property = gdm_user_get_property; + gobject_class->finalize = gdm_user_finalize; + + g_object_class_install_property (gobject_class, + PROP_REAL_NAME, + g_param_spec_string ("real-name", + "Real Name", + "The real name to display for this user.", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, + PROP_UID, + g_param_spec_ulong ("uid", + "User ID", + "The UID for this user.", + 0, G_MAXULONG, 0, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_USER_NAME, + g_param_spec_string ("user-name", + "User Name", + "The login name for this user.", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_HOME_DIR, + g_param_spec_string ("home-directory", + "Home Directory", + "The home directory for this user.", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_SHELL, + g_param_spec_string ("shell", + "Shell", + "The shell for this user.", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_SHELL, + g_param_spec_string ("icon-url", + "Icon URL", + "The icon for this user.", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_LOGIN_FREQUENCY, + g_param_spec_ulong ("login-frequency", + "login frequency", + "login frequency", + 0, + G_MAXULONG, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + signals [ICON_CHANGED] = + g_signal_new ("icon-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmUserClass, icon_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [SESSIONS_CHANGED] = + g_signal_new ("sessions-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmUserClass, sessions_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + + +static void +on_icon_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GdmUser *user) +{ + g_debug ("Icon changed: %d", event_type); + + if (event_type != G_FILE_MONITOR_EVENT_CHANGED && + event_type != G_FILE_MONITOR_EVENT_CREATED) { + return; + } + + g_signal_emit (user, signals[ICON_CHANGED], 0); +} + +static void +update_icon_monitor (GdmUser *user) +{ + GFile *file; + GError *error = NULL; + + if (user->icon_url != NULL) { + g_free (user->icon_url); + user->icon_url = NULL; + } + if (user->home_dir != NULL) { + gchar *filename; + filename = g_build_filename (user->home_dir, ".face", NULL); + user->icon_url = g_strjoin(NULL, "file://", filename, NULL); + g_free (filename); + } + g_object_notify (G_OBJECT (user), "icon-url"); + + if (user->icon_monitor != NULL) { + g_file_monitor_cancel (user->icon_monitor); + user->icon_monitor = NULL; + } + + if (user->icon_url == NULL) { + return; + } + + g_debug ("adding monitor for '%s'", user->icon_url); + file = g_file_new_for_uri (user->icon_url); + user->icon_monitor = g_file_monitor_file (file, + G_FILE_MONITOR_NONE, + NULL, + &error); + if (user->icon_monitor != NULL) { + g_signal_connect (user->icon_monitor, + "changed", + G_CALLBACK (on_icon_monitor_changed), + user); + } else { + g_warning ("Unable to monitor %s: %s", user->icon_url, error->message); + g_error_free (error); + } + g_object_unref (file); +} + +static void +gdm_user_init (GdmUser *user) +{ + user->user_name = NULL; + user->real_name = NULL; + user->sessions = NULL; +} + +static void +gdm_user_finalize (GObject *object) +{ + GdmUser *user; + + user = GDM_USER (object); + + g_file_monitor_cancel (user->icon_monitor); + + g_free (user->user_name); + g_free (user->real_name); + + if (G_OBJECT_CLASS (gdm_user_parent_class)->finalize) + (*G_OBJECT_CLASS (gdm_user_parent_class)->finalize) (object); +} + +/** + * _gdm_user_update: + * @user: the user object to update. + * @pwent: the user data to use. + * + * Updates the properties of @user using the data in @pwent. + * + * Since: 1.0 + **/ +void +_gdm_user_update (GdmUser *user, + const struct passwd *pwent) +{ + gchar *real_name; + + g_return_if_fail (GDM_IS_USER (user)); + g_return_if_fail (pwent != NULL); + + g_object_freeze_notify (G_OBJECT (user)); + + /* Display Name */ + if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0') { + gchar *first_comma; + gchar *real_name_utf8; + + real_name_utf8 = g_locale_to_utf8 (pwent->pw_gecos, -1, NULL, NULL, NULL); + + first_comma = strchr (real_name_utf8, ','); + if (first_comma) { + real_name = g_strndup (real_name_utf8, first_comma - real_name_utf8); + g_free (real_name_utf8); + } else { + real_name = real_name_utf8; + } + + if (real_name[0] == '\0') { + g_free (real_name); + real_name = NULL; + } + } else { + real_name = NULL; + } + + if ((real_name && !user->real_name) || + (!real_name && user->real_name) || + (real_name && + user->real_name && + strcmp (real_name, user->real_name) != 0)) { + g_free (user->real_name); + user->real_name = real_name; + g_object_notify (G_OBJECT (user), "real-name"); + } else { + g_free (real_name); + } + + /* UID */ + if (pwent->pw_uid != user->uid) { + user->uid = pwent->pw_uid; + g_object_notify (G_OBJECT (user), "uid"); + } + + /* Username */ + if ((pwent->pw_name && !user->user_name) || + (!pwent->pw_name && user->user_name) || + (pwent->pw_name && + user->user_name && + strcmp (user->user_name, pwent->pw_name) != 0)) { + g_free (user->user_name); + user->user_name = g_strdup (pwent->pw_name); + g_object_notify (G_OBJECT (user), "user-name"); + } + + /* Home Directory */ + if ((pwent->pw_dir && !user->home_dir) || + (!pwent->pw_dir && user->home_dir) || + strcmp (user->home_dir, pwent->pw_dir) != 0) { + g_free (user->home_dir); + user->home_dir = g_strdup (pwent->pw_dir); + g_object_notify (G_OBJECT (user), "home-directory"); + g_signal_emit (user, signals[ICON_CHANGED], 0); + } + + /* Shell */ + if ((pwent->pw_shell && !user->shell) || + (!pwent->pw_shell && user->shell) || + (pwent->pw_shell && + user->shell && + strcmp (user->shell, pwent->pw_shell) != 0)) { + g_free (user->shell); + user->shell = g_strdup (pwent->pw_shell); + g_object_notify (G_OBJECT (user), "shell"); + } + + update_icon_monitor (user); + + g_object_thaw_notify (G_OBJECT (user)); +} + +/** + * gdm_user_get_uid: + * @user: the user object to examine. + * + * Retrieves the ID of @user. + * + * Returns: a pointer to an array of characters which must not be modified or + * freed, or %NULL. + * + * Since: 1.0 + **/ + +uid_t +gdm_user_get_uid (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), -1); + + return user->uid; +} + +/** + * gdm_user_get_real_name: + * @user: the user object to examine. + * + * Retrieves the display name of @user. + * + * Returns: a pointer to an array of characters which must not be modified or + * freed, or %NULL. + * + * Since: 1.0 + **/ +G_CONST_RETURN gchar * +gdm_user_get_real_name (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + return (user->real_name ? user->real_name : user->user_name); +} + +/** + * gdm_user_get_user_name: + * @user: the user object to examine. + * + * Retrieves the login name of @user. + * + * Returns: a pointer to an array of characters which must not be modified or + * freed, or %NULL. + * + * Since: 1.0 + **/ + +G_CONST_RETURN gchar * +gdm_user_get_user_name (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + return user->user_name; +} + +/** + * gdm_user_get_home_directory: + * @user: the user object to examine. + * + * Retrieves the home directory of @user. + * + * Returns: a pointer to an array of characters which must not be modified or + * freed, or %NULL. + * + * Since: 1.0 + **/ + +G_CONST_RETURN gchar * +gdm_user_get_home_directory (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + return user->home_dir; +} + +/** + * gdm_user_get_shell: + * @user: the user object to examine. + * + * Retrieves the login shell of @user. + * + * Returns: a pointer to an array of characters which must not be modified or + * freed, or %NULL. + * + * Since: 1.0 + **/ + +G_CONST_RETURN gchar * +gdm_user_get_shell (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + return user->shell; +} + +gulong +gdm_user_get_login_frequency (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), 0); + + return user->login_frequency; +} + +G_CONST_RETURN gchar * +gdm_user_get_icon_url (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + /* FIXME: Icon can be one of: + * ~/.face + * ~/.face.icon + * ~/.gnome/gdm:[face]picture + * ${GlobalFaceDir}/${username} + * ${GlobalFaceDir}/${username}.png + * but we only monitor the first. + */ + return user->icon_url; +} diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/daemon/gdm-user.h gdm-2.28.0.new/daemon/gdm-user.h --- gdm-2.28.0/daemon/gdm-user.h 1970-01-01 10:00:00.000000000 +1000 +++ gdm-2.28.0.new/daemon/gdm-user.h 2009-10-14 18:30:51.000000000 +1100 @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004-2005 James M. Cape . + * 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 + */ + +/* + * Facade object for user data, owned by GdmUserManager + */ + +#ifndef __GDM_USER__ +#define __GDM_USER__ 1 + +#include +#include + +G_BEGIN_DECLS + +#define GDM_TYPE_USER (gdm_user_get_type ()) +#define GDM_USER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDM_TYPE_USER, GdmUser)) +#define GDM_IS_USER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDM_TYPE_USER)) + +typedef struct _GdmUser GdmUser; + +GType gdm_user_get_type (void) G_GNUC_CONST; + +uid_t gdm_user_get_uid (GdmUser *user); +G_CONST_RETURN gchar *gdm_user_get_user_name (GdmUser *user); +G_CONST_RETURN gchar *gdm_user_get_real_name (GdmUser *user); +G_CONST_RETURN gchar *gdm_user_get_home_directory (GdmUser *user); +G_CONST_RETURN gchar *gdm_user_get_shell (GdmUser *user); +guint gdm_user_get_num_sessions (GdmUser *user); +GList *gdm_user_get_sessions (GdmUser *user); +gulong gdm_user_get_login_frequency (GdmUser *user); +G_CONST_RETURN gchar *gdm_user_get_icon_url (GdmUser *user); + +G_END_DECLS + +#endif diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/daemon/gdm-user-manager.c gdm-2.28.0.new/daemon/gdm-user-manager.c --- gdm-2.28.0/daemon/gdm-user-manager.c 1970-01-01 10:00:00.000000000 +1000 +++ gdm-2.28.0.new/daemon/gdm-user-manager.c 2009-10-14 18:31:03.000000000 +1100 @@ -0,0 +1,1538 @@ +/* -*- 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-manager-glue.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 GDM_DBUS_PATH "/org/gnome/DisplayManager" +#define GDM_USER_MANAGER_DBUS_PATH GDM_DBUS_PATH "/UserManager" +#define GDM_USER_MANAGER_DBUS_NAME "org.gnome.DisplayManager.UserManager" + +#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" + +#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 LOGIN_CACHE_FILE CACHEDIR "/login_frequency.cache" + +#define DEFAULT_EXCLUDE { "bin", \ + "root", \ + "daemon", \ + "adm", \ + "lp", \ + "sync", \ + "shutdown", \ + "halt", \ + "mail", \ + "news", \ + "uucp", \ + "operator", \ + "nobody", \ + "nobody4", \ + "noaccess", \ + GDM_USERNAME, \ + "postgres", \ + "pvm", \ + "rpm", \ + "nfsnobody", \ + "pcap", \ + NULL } + +struct GdmUserManagerPrivate +{ + GHashTable *users; + GHashTable *sessions; + GHashTable *exclusions; + GHashTable *shells; + DBusGConnection *connection; + DBusGProxy *seat_proxy; + char *seat_id; + + GFileMonitor *passwd_monitor; + GFileMonitor *shells_monitor; + + guint reload_id; + guint ck_history_id; + + guint8 loaded_passwd : 1; + guint8 loaded_cache : 1; +}; + +enum { + USERS_LOADED, + USER_ADDED, + USER_REMOVED, + USER_UPDATED, + 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); + +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 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_UPDATED], 0, gdm_user_get_uid (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 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 (g_hash_table_lookup (manager->priv->exclusions, 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, 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 void +add_user (GdmUserManager *manager, + GdmUser *user) +{ + add_sessions_for_user (manager, user); + 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, gdm_user_get_uid (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 (g_hash_table_lookup (manager->priv->exclusions, 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. + **/ +static 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; +} + +static 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); +} + +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; + struct passwd *pwent; + GdmUser *user; + + frequency = 0; + username = NULL; + res = parse_ck_history_line (line, &username, &frequency); + if (! res) { + return; + } + + if (g_hash_table_lookup (manager->priv->exclusions, username)) { + g_debug ("GdmUserManager: excluding user '%s'", username); + g_free (username); + return; + } + + /* do not show system users; we cannot use gdm_user_manager_get_user() + * here since this creates/signals users as a side effect */ + pwent = getpwnam (username); + if (pwent == NULL) { + g_warning ("Unable to lookup user name %s: %s", username, g_strerror (errno)); + return; + } + if (pwent->pw_uid < DEFAULT_MINIMAL_UID) { + g_debug ("GdmUserManager: excluding user '%s'", 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_UPDATED], 0, gdm_user_get_uid (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) { + FILE *fp; + + /* Cache login counts */ + fp = fopen (LOGIN_CACHE_FILE, "w"); + if (fp != NULL) { + GHashTableIter iter; + gpointer value; + + g_hash_table_iter_init (&iter, manager->priv->users); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + GdmUser *user = (GdmUser *) value; + fprintf (fp, "%s %lu\n", + gdm_user_get_user_name (user), + gdm_user_get_login_frequency (user)); + } + fclose (fp); + } + else + g_warning ("Unable to write to login cache file: %s", LOGIN_CACHE_FILE); + + 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 +reload_passwd (GdmUserManager *manager) +{ + struct passwd *pwent; + GSList *old_users; + GSList *new_users; + GSList *list; + 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)); + new_users = g_slist_prepend (new_users, g_object_ref (list->data)); + } + } + + 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 (g_hash_table_lookup (manager->priv->exclusions, 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, gdm_user_get_uid (GDM_USER (list->data))); + g_hash_table_remove (manager->priv->users, + gdm_user_get_user_name (list->data)); + } + } + + /* Go through and handle added users */ + for (list = new_users; list; list = list->next) { + if (!g_slist_find (old_users, list->data)) { + add_user (manager, list->data); + } + } + + if (!manager->priv->loaded_passwd) { + g_signal_emit (manager, signals[USERS_LOADED], 0); + manager->priv->loaded_passwd = TRUE; + } + + 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 +load_login_frequency_cache (GdmUserManager *manager) +{ + GIOChannel *channel; + gchar *line; + + channel = g_io_channel_new_file (LOGIN_CACHE_FILE, "r", NULL); + if (channel == NULL) + return; + + while (g_io_channel_read_line (channel, &line, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) { + process_ck_history_line (manager, line); + g_free (line); + } + + g_io_channel_close (channel); +} + +static void +reload_users (GdmUserManager *manager) +{ + reload_passwd (manager); + if (!manager->priv->loaded_cache) { + load_login_frequency_cache (manager); + manager->priv->loaded_cache = TRUE; + } + reload_ck_history (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; + } + + 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 [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__OBJECT, + 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, G_TYPE_INT64); + 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, G_TYPE_INT64); + signals [USER_UPDATED] = + g_signal_new ("user-updated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmUserManagerClass, user_updated), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_INT64); + + g_type_class_add_private (klass, sizeof (GdmUserManagerPrivate)); + + dbus_g_object_type_install_info (GDM_TYPE_USER_MANAGER, &dbus_glib_gdm_user_manager_object_info); +} + +static void +gdm_user_manager_init (GdmUserManager *manager) +{ + int i; + GFile *file; + GError *error; + const char *exclude_default[] = DEFAULT_EXCLUDE; + + 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); + + /* exclusions */ + manager->priv->exclusions = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + for (i = 0; exclude_default[i] != NULL; i++) { + g_hash_table_insert (manager->priv->exclusions, + g_strdup (exclude_default [i]), + GUINT_TO_POINTER (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 */ + manager->priv->users = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_object_run_dispose); + 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); + + dbus_g_connection_register_g_object (manager->priv->connection, GDM_USER_MANAGER_DBUS_PATH, G_OBJECT (manager)); +} + +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->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_new (void) +{ + return GDM_USER_MANAGER (g_object_new (GDM_TYPE_USER_MANAGER, NULL)); +} + +/* + Example: + dbus-send --system --print-reply --dest=org.gnome.DisplayManager \ + /org/gnome/DisplayManager/UserManager org.gnome.DisplayManager.UserManager.CountUsers +*/ +gboolean +gdm_user_manager_count_users (GdmUserManager *user_manager, + gint *user_count, + GError **error) +{ + *user_count = g_hash_table_size (user_manager->priv->users); + + return TRUE; +} + +/* + Example: + dbus-send --system --print-reply --dest=org.gnome.DisplayManager \ + /org/gnome/DisplayManager/UserManager org.gnome.DisplayManager.UserManager.GetUserList +*/ +gboolean +gdm_user_manager_get_user_list (GdmUserManager *user_manager, + GArray **user_list, + GError **error) +{ + GHashTableIter iter; + GdmUser *user; + + *user_list = g_array_new (FALSE, FALSE, sizeof (gint64)); + g_hash_table_iter_init (&iter, user_manager->priv->users); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&user)) { + gint64 uid = gdm_user_get_uid (user); + g_array_append_val (*user_list, uid); + } + + return TRUE; +} + + +/* + Example: + dbus-send --system --print-reply --dest=org.gnome.DisplayManager \ + /org/gnome/DisplayManager/UserManager org.gnome.DisplayManager.UserManager.GetUserInfo int64:1000 +*/ +gboolean +gdm_user_manager_get_user_info (GdmUserManager *user_manager, + gint64 uid, + gchar **user_name, + gchar **real_name, + gchar **shell, + gint *login_count, + gchar **icon_url, + GError **error) +{ + GdmUser *user; + + user = gdm_user_manager_get_user_by_uid (user_manager, uid); + if (user == NULL) + return FALSE; + + *user_name = g_strdup (gdm_user_get_user_name (user)); + *real_name = g_strdup (gdm_user_get_real_name (user)); + *login_count = gdm_user_get_login_frequency (user); + *shell = g_strdup (gdm_user_get_shell (user)); + *icon_url = g_strdup (gdm_user_get_icon_url (user)); + + return TRUE; +} + +/* + Example: + dbus-send --system --print-reply --dest=org.gnome.DisplayManager \ + /org/gnome/DisplayManager/UserManager org.gnome.DisplayManager.UserManager.GetUsersInfo array:int64:1000,1001 +*/ +gboolean +gdm_user_manager_get_users_info (GdmUserManager *user_manager, + GArray *uids, + GPtrArray **users_info, + GError **error) +{ + int i; + + *users_info = g_ptr_array_new (); + + for (i = 0; i < uids->len; i++) { + gint64 uid; + GdmUser *user; + GValueArray *user_info; + GValue arg = {0}; + + uid = g_array_index (uids, gint64, i); + user = gdm_user_manager_get_user_by_uid (user_manager, uid); + if (user == NULL) + continue; + + user_info = g_value_array_new (5); + + g_value_init (&arg, G_TYPE_INT64); + g_value_set_int64 (&arg, uid); + g_value_array_append (user_info, &arg); + g_value_unset (&arg); + + g_value_init (&arg, G_TYPE_STRING); + g_value_set_string (&arg, gdm_user_get_user_name (user)); + g_value_array_append (user_info, &arg); + g_value_unset (&arg); + + g_value_init (&arg, G_TYPE_STRING); + g_value_set_string (&arg, gdm_user_get_real_name (user)); + g_value_array_append (user_info, &arg); + g_value_unset (&arg); + + g_value_init (&arg, G_TYPE_STRING); + g_value_set_string (&arg, gdm_user_get_shell (user)); + g_value_array_append (user_info, &arg); + g_value_unset (&arg); + + g_value_init (&arg, G_TYPE_INT); + g_value_set_int (&arg, gdm_user_get_login_frequency (user)); + g_value_array_append (user_info, &arg); + g_value_unset (&arg); + + g_value_init (&arg, G_TYPE_STRING); + g_value_set_string (&arg, gdm_user_get_icon_url (user)); + g_value_array_append (user_info, &arg); + g_value_unset (&arg); + + g_ptr_array_add (*users_info, user_info); + } + + return TRUE; +} + +/* + Example: + dbus-send --system --print-reply --dest=org.gnome.DisplayManager \ + /org/gnome/DisplayManager/UserManager org.gnome.DisplayManager.UserManager.GetUsersLoaded +*/ +gboolean +gdm_user_manager_get_users_loaded (GdmUserManager *user_manager, + gboolean *is_loaded, + GError **error) +{ + *is_loaded = user_manager->priv->loaded_passwd; + return TRUE; +} diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/daemon/gdm-user-manager.h gdm-2.28.0.new/daemon/gdm-user-manager.h --- gdm-2.28.0/daemon/gdm-user-manager.h 1970-01-01 10:00:00.000000000 +1000 +++ gdm-2.28.0.new/daemon/gdm-user-manager.h 2009-10-14 18:30:51.000000000 +1100 @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 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. + * + */ + +#ifndef __GDM_USER_MANAGER_H +#define __GDM_USER_MANAGER_H + +#include + +#include "gdm-user.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_USER_MANAGER (gdm_user_manager_get_type ()) +#define GDM_USER_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_USER_MANAGER, GdmUserManager)) +#define GDM_USER_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_USER_MANAGER, GdmUserManagerClass)) +#define GDM_IS_USER_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_USER_MANAGER)) +#define GDM_IS_USER_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_USER_MANAGER)) +#define GDM_USER_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_USER_MANAGER, GdmUserManagerClass)) + +typedef struct GdmUserManagerPrivate GdmUserManagerPrivate; + +typedef struct +{ + GObject parent; + GdmUserManagerPrivate *priv; +} GdmUserManager; + +typedef struct +{ + GObjectClass parent_class; + + void (* users_loaded) (GdmUserManager *user_manager); + void (* user_added) (GdmUserManager *user_manager, + gint64 uid); + void (* user_removed) (GdmUserManager *user_manager, + gint64 uid); + void (* user_updated) (GdmUserManager *user_manager, + gint64 uid); +} GdmUserManagerClass; + +#define GDM_USER_MANAGER_ERROR gdm_user_manager_error_quark () + +GQuark gdm_user_manager_error_quark (void); +GType gdm_user_manager_get_type (void); + +GdmUserManager * gdm_user_manager_new (void); + +gboolean gdm_user_manager_count_users (GdmUserManager *user_manager, + gint *user_count, + GError **error); + +gboolean gdm_user_manager_get_user_list (GdmUserManager *user_manager, + GArray **user_list, + GError **error); + +gboolean gdm_user_manager_get_user_info (GdmUserManager *user_manager, + gint64 uid, + gchar **user_name, + gchar **real_name, + gchar **shell, + gint *login_count, + gchar **icon_url, + GError **error); + +gboolean gdm_user_manager_get_users_info (GdmUserManager *user_manager, + GArray *uids, + GPtrArray **user_info, + GError **error); + +gboolean gdm_user_manager_get_users_loaded (GdmUserManager *user_manager, + gboolean *is_loaded, + GError **error); + +G_END_DECLS + +#endif /* __GDM_USER_MANAGER_H */ diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/daemon/gdm-user-manager.xml gdm-2.28.0.new/daemon/gdm-user-manager.xml --- gdm-2.28.0/daemon/gdm-user-manager.xml 1970-01-01 10:00:00.000000000 +1000 +++ gdm-2.28.0.new/daemon/gdm-user-manager.xml 2009-10-14 18:30:51.000000000 +1100 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/daemon/gdm-user-private.h gdm-2.28.0.new/daemon/gdm-user-private.h --- gdm-2.28.0/daemon/gdm-user-private.h 1970-01-01 10:00:00.000000000 +1000 +++ gdm-2.28.0.new/daemon/gdm-user-private.h 2009-10-14 18:30:51.000000000 +1100 @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004-2005 James M. Cape . + * + * 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 + */ + +/* + * Private interfaces to the GdmUser object + */ + +#ifndef __GDM_USER_PRIVATE__ +#define __GDM_USER_PRIVATE__ 1 + +#include + +#include "gdm-user.h" + +G_BEGIN_DECLS + +void _gdm_user_update (GdmUser *user, + const struct passwd *pwent); +void _gdm_user_add_session (GdmUser *user, + const char *session_id); +void _gdm_user_remove_session (GdmUser *user, + const char *session_id); + +G_END_DECLS + +#endif /* !__GDM_USER_PRIVATE__ */ diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/daemon/main.c gdm-2.28.0.new/daemon/main.c --- gdm-2.28.0/daemon/main.c 2009-09-22 06:05:27.000000000 +1000 +++ gdm-2.28.0.new/daemon/main.c 2009-10-14 18:30:51.000000000 +1100 @@ -43,6 +43,7 @@ #include #include +#include "gdm-user-manager.h" #include "gdm-manager.h" #include "gdm-log.h" #include "gdm-common.h" @@ -59,6 +60,7 @@ extern char **environ; +static GdmUserManager *user_manager = NULL; static GdmManager *manager = NULL; static GdmSettings *settings = NULL; static uid_t gdm_uid = -1; @@ -576,6 +578,12 @@ goto out; } + user_manager = gdm_user_manager_new (); + if (user_manager == NULL) { + g_warning ("Could not construct user manager object"); + exit (1); + } + if (! gdm_settings_direct_init (settings, GDMCONFDIR "/gdm.schemas", "/")) { g_warning ("Unable to initialize settings"); goto out; diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/daemon/Makefile.am gdm-2.28.0.new/daemon/Makefile.am --- gdm-2.28.0/daemon/Makefile.am 2009-09-22 06:05:27.000000000 +1000 +++ gdm-2.28.0.new/daemon/Makefile.am 2009-10-14 18:30:51.000000000 +1100 @@ -9,6 +9,7 @@ -DDATADIR=\"$(datadir)\" \ -DDMCONFDIR=\"$(dmconfdir)\" \ -DGDMCONFDIR=\"$(gdmconfdir)\" \ + -DCACHEDIR=\"$(cachedir)\" \ -DLIBDIR=\"$(libdir)\" \ -DLIBEXECDIR=\"$(libexecdir)\" \ -DLOGDIR=\"$(logdir)\" \ @@ -34,6 +35,7 @@ gdm-session-direct-glue.h \ gdm-manager-glue.h \ gdm-display-glue.h \ + gdm-user-manager-glue.h \ gdm-xdmcp-greeter-display-glue.h \ gdm-xdmcp-chooser-display-glue.h \ gdm-static-display-glue.h \ @@ -45,6 +47,8 @@ gdm-manager-glue.h: gdm-manager.xml Makefile.am dbus-binding-tool --prefix=gdm_manager --mode=glib-server --output=gdm-manager-glue.h $(srcdir)/gdm-manager.xml +gdm-user-manager-glue.h: gdm-user-manager.xml Makefile.am + dbus-binding-tool --prefix=gdm_user_manager --mode=glib-server --output=gdm-user-manager-glue.h $(srcdir)/gdm-user-manager.xml gdm-slave-glue.h: gdm-slave.xml Makefile.am dbus-binding-tool --prefix=gdm_slave --mode=glib-server --output=gdm-slave-glue.h $(srcdir)/gdm-slave.xml gdm-simple-slave-glue.h: gdm-simple-slave.xml Makefile.am @@ -303,6 +307,11 @@ gdm-product-display.h \ gdm-manager.c \ gdm-manager.h \ + gdm-user.c \ + gdm-user.h \ + gdm-user-private.h \ + gdm-user-manager.c \ + gdm-user-manager.h \ gdm-slave-proxy.c \ gdm-slave-proxy.h \ $(NULL) @@ -365,6 +374,7 @@ gdm-session-direct.xml \ gdm-manager.xml \ gdm-display.xml \ + gdm-user-manager.xml \ gdm-xdmcp-greeter-display.xml \ gdm-xdmcp-chooser-display.xml \ gdm-static-display.xml \ diff -Nur -x '*.orig' -x '*~' gdm-2.28.0/data/gdm.conf.in gdm-2.28.0.new/data/gdm.conf.in --- gdm-2.28.0/data/gdm.conf.in 2009-10-14 18:30:50.000000000 +1100 +++ gdm-2.28.0.new/data/gdm.conf.in 2009-10-14 18:30:51.000000000 +1100 @@ -8,6 +8,8 @@ + @@ -28,6 +30,8 @@ + @@ -71,6 +75,22 @@ + + + + + + +