diff options
Diffstat (limited to '')
-rw-r--r-- | daemon/gdm-manager.c | 2841 |
1 files changed, 2841 insertions, 0 deletions
diff --git a/daemon/gdm-manager.c b/daemon/gdm-manager.c new file mode 100644 index 0000000..e828544 --- /dev/null +++ b/daemon/gdm-manager.c @@ -0,0 +1,2841 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include <act/act-user-manager.h> + +#include <systemd/sd-login.h> + +#include "gdm-common.h" + +#include "gdm-dbus-util.h" +#include "gdm-manager.h" +#include "gdm-manager-glue.h" +#include "gdm-display-store.h" +#include "gdm-display-factory.h" +#include "gdm-launch-environment.h" +#include "gdm-local-display.h" +#include "gdm-local-display-factory.h" +#include "gdm-session.h" +#include "gdm-session-record.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" +#include "gdm-xdmcp-display-factory.h" +#include "gdm-xdmcp-chooser-display.h" + +#define GDM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_MANAGER, GdmManagerPrivate)) + +#define GDM_DBUS_PATH "/org/gnome/DisplayManager" +#define GDM_MANAGER_PATH GDM_DBUS_PATH "/Manager" +#define GDM_MANAGER_DISPLAYS_PATH GDM_DBUS_PATH "/Displays" + +#define INITIAL_SETUP_USERNAME "gnome-initial-setup" +#define ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT GDM_RUN_DIR "/gdm.ran-initial-setup" + +typedef struct +{ + GdmManager *manager; + GdmSession *session; + char *service_name; + guint idle_id; +} StartUserSessionOperation; + +struct GdmManagerPrivate +{ + GdmDisplayStore *display_store; + GdmLocalDisplayFactory *local_factory; +#ifdef HAVE_LIBXDMCP + GdmXdmcpDisplayFactory *xdmcp_factory; +#endif + GdmDisplay *automatic_login_display; + GList *user_sessions; + GHashTable *transient_sessions; + GHashTable *open_reauthentication_requests; + gboolean xdmcp_enabled; + + gboolean started; + gboolean show_local_greeter; + + GDBusConnection *connection; + GDBusObjectManagerServer *object_manager; + +#ifdef WITH_PLYMOUTH + guint plymouth_is_running : 1; +#endif + guint did_automatic_login : 1; +}; + +enum { + PROP_0, + PROP_XDMCP_ENABLED, + PROP_SHOW_LOCAL_GREETER +}; + +enum { + DISPLAY_ADDED, + DISPLAY_REMOVED, + LAST_SIGNAL +}; + +typedef enum { + SESSION_RECORD_LOGIN, + SESSION_RECORD_LOGOUT, + SESSION_RECORD_FAILED, +} SessionRecord; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_manager_class_init (GdmManagerClass *klass); +static void gdm_manager_init (GdmManager *manager); +static void gdm_manager_dispose (GObject *object); + +static GdmSession *create_user_session_for_display (GdmManager *manager, + GdmDisplay *display, + uid_t allowed_user); +static void start_user_session (GdmManager *manager, + StartUserSessionOperation *operation); +static void clean_user_session (GdmSession *session); + +static gpointer manager_object = NULL; + +static void manager_interface_init (GdmDBusManagerIface *interface); + +G_DEFINE_TYPE_WITH_CODE (GdmManager, + gdm_manager, + GDM_DBUS_TYPE_MANAGER_SKELETON, + G_IMPLEMENT_INTERFACE (GDM_DBUS_TYPE_MANAGER, + manager_interface_init) + G_ADD_PRIVATE (GdmManager)); + +#ifdef WITH_PLYMOUTH +static gboolean +plymouth_is_running (void) +{ + int status; + gboolean res; + GError *error; + + error = NULL; + res = g_spawn_command_line_sync ("plymouth --ping", + NULL, NULL, &status, &error); + if (! res) { + g_debug ("Could not ping plymouth: %s", error->message); + g_error_free (error); + return FALSE; + } + + return WIFEXITED (status) && WEXITSTATUS (status) == 0; +} + +static void +plymouth_prepare_for_transition (void) +{ + gboolean res; + GError *error; + + error = NULL; + res = g_spawn_command_line_sync ("plymouth deactivate", + NULL, NULL, NULL, &error); + if (! res) { + g_warning ("Could not deactivate plymouth: %s", error->message); + g_error_free (error); + } +} + +static gboolean +plymouth_quit_with_transition (void) +{ + gboolean res; + GError *error; + + error = NULL; + res = g_spawn_command_line_async ("plymouth quit --retain-splash", &error); + if (! res) { + g_warning ("Could not quit plymouth: %s", error->message); + g_error_free (error); + } + + return G_SOURCE_REMOVE; +} + +static void +plymouth_quit_without_transition (void) +{ + gboolean res; + GError *error; + + error = NULL; + res = g_spawn_command_line_async ("plymouth quit", &error); + if (! res) { + g_warning ("Could not quit plymouth: %s", error->message); + g_error_free (error); + } +} +#endif + +static char * +get_session_id_for_pid (pid_t pid, + GError **error) +{ + char *session, *gsession; + int ret; + + session = NULL; + ret = sd_pid_get_session (pid, &session); + if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting session id from systemd: %s", + g_strerror (-ret)); + return NULL; + } + + if (session != NULL) { + gsession = g_strdup (session); + free (session); + + return gsession; + } else { + return NULL; + } +} + +static gboolean +get_uid_for_session_id (const char *session_id, + uid_t *uid, + GError **error) +{ + int ret; + + ret = sd_session_get_uid (session_id, uid); + if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting uid for session id %s from systemd: %s", + session_id, + g_strerror (-ret)); + return FALSE; + } + + return TRUE; +} + +static gboolean +lookup_by_session_id (const char *id, + GdmDisplay *display, + gpointer user_data) +{ + const char *looking_for = user_data; + const char *current; + + current = gdm_display_get_session_id (display); + return g_strcmp0 (current, looking_for) == 0; +} + +static gboolean +is_login_session (GdmManager *self, + const char *session_id, + GError **error) +{ + char *session_class = NULL; + int ret; + + ret = sd_session_get_class (session_id, &session_class); + + if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting class for session id %s from systemd: %s", + session_id, + g_strerror (-ret)); + return FALSE; + } + + if (g_strcmp0 (session_class, "greeter") != 0) { + g_free (session_class); + return FALSE; + } + + g_free (session_class); + return TRUE; +} + +static gboolean +session_unlock (GdmManager *manager, + const char *ssid) +{ + GError *error = NULL; + GVariant *reply; + + g_debug ("Unlocking session %s", ssid); + + reply = g_dbus_connection_call_sync (manager->priv->connection, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "UnlockSession", + g_variant_new ("(s)", ssid), + NULL, /* expected reply */ + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (reply == NULL) { + g_debug ("GdmManager: logind 'UnlockSession' %s raised:\n %s\n\n", + g_dbus_error_get_remote_error (error), error->message); + g_error_free (error); + return FALSE; + } + + g_variant_unref (reply); + + return TRUE; +} + +static GdmSession * +find_session_for_user_on_seat (GdmManager *manager, + const char *username, + const char *seat_id, + GdmSession *dont_count_session) +{ + GList *node; + + for (node = manager->priv->user_sessions; node != NULL; node = node->next) { + GdmSession *candidate_session = node->data; + const char *candidate_username, *candidate_seat_id, *candidate_session_id; + + candidate_session_id = gdm_session_get_session_id (candidate_session); + + if (candidate_session == dont_count_session) { + g_debug ("GdmSession: Ignoring session %s as requested", + candidate_session_id); + continue; + } + + if (!gdm_session_is_running (candidate_session)) { + g_debug ("GdmSession: Ignoring session %s as it isn't running", + candidate_session_id); + continue; + } + + candidate_username = gdm_session_get_username (candidate_session); + candidate_seat_id = gdm_session_get_display_seat_id (candidate_session); + + g_debug ("GdmManager: Considering session %s on seat %s belonging to user %s", + candidate_session_id, + candidate_seat_id, + candidate_username); + + if (g_strcmp0 (candidate_username, username) == 0 && + g_strcmp0 (candidate_seat_id, seat_id) == 0) { + g_debug ("GdmManager: yes, found session %s", candidate_session_id); + return candidate_session; + } + + g_debug ("GdmManager: no, will not use session %s", candidate_session_id); + } + + g_debug ("GdmManager: no matching sessions found"); + return NULL; +} + +static gboolean +is_remote_session (GdmManager *self, + const char *session_id, + GError **error) +{ + char *seat; + int ret; + gboolean is_remote; + + /* FIXME: The next release of logind is going to have explicit api for + * checking remoteness. + */ + seat = NULL; + ret = sd_session_get_seat (session_id, &seat); + + if (ret < 0 && ret != -ENXIO) { + g_debug ("GdmManager: Error while retrieving seat for session %s: %s", + session_id, strerror (-ret)); + } + + if (seat != NULL) { + is_remote = FALSE; + free (seat); + } else { + is_remote = TRUE; + } + + return is_remote; +} + +static char * +get_seat_id_for_session_id (const char *session_id, + GError **error) +{ + int ret; + char *seat, *out_seat; + + seat = NULL; + ret = sd_session_get_seat (session_id, &seat); + + if (ret == -ENXIO) { + out_seat = NULL; + } else if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting uid for session id %s from systemd: %s", + session_id, + g_strerror (-ret)); + out_seat = NULL; + } else { + out_seat = g_strdup (seat); + free (seat); + } + + return out_seat; +} + +static char * +get_tty_for_session_id (const char *session_id, + GError **error) +{ + int ret; + char *tty, *out_tty; + + ret = sd_session_get_tty (session_id, &tty); + + if (ret == -ENXIO) { + out_tty = NULL; + } else if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting tty for session id %s from systemd: %s", + session_id, + g_strerror (-ret)); + out_tty = NULL; + } else { + out_tty = g_strdup (tty); + free (tty); + } + + return out_tty; +} + +static void +get_display_and_details_for_bus_sender (GdmManager *self, + GDBusConnection *connection, + const char *sender, + GdmDisplay **out_display, + char **out_seat_id, + char **out_session_id, + char **out_tty, + GPid *out_pid, + uid_t *out_uid, + gboolean *out_is_login_screen, + gboolean *out_is_remote) +{ + GdmDisplay *display = NULL; + char *session_id = NULL; + GError *error = NULL; + int ret; + GPid pid; + uid_t caller_uid, session_uid; + + ret = gdm_dbus_get_pid_for_name (sender, &pid, &error); + + if (!ret) { + g_debug ("GdmManager: Error while retrieving pid for sender: %s", + error->message); + g_error_free (error); + goto out; + } + + if (out_pid != NULL) { + *out_pid = pid; + } + + ret = gdm_dbus_get_uid_for_name (sender, &caller_uid, &error); + + if (!ret) { + g_debug ("GdmManager: Error while retrieving uid for sender: %s", + error->message); + g_error_free (error); + goto out; + } + + ret = gdm_find_display_session (pid, caller_uid, &session_id, &error); + + if (!ret) { + g_debug ("GdmManager: Unable to find display session for uid %d: %s", + (int) caller_uid, + error->message); + g_error_free (error); + goto out; + } + + if (out_session_id != NULL) { + *out_session_id = g_strdup (session_id); + } + + if (out_is_login_screen != NULL) { + *out_is_login_screen = is_login_session (self, session_id, &error); + + if (error != NULL) { + g_debug ("GdmManager: Error while checking if sender is login screen: %s", + error->message); + g_error_free (error); + goto out; + } + } + + if (!get_uid_for_session_id (session_id, &session_uid, &error)) { + g_debug ("GdmManager: Error while retrieving uid for session: %s", + error->message); + g_error_free (error); + goto out; + } + + if (out_uid != NULL) { + *out_uid = caller_uid; + } + + if (caller_uid != session_uid) { + g_debug ("GdmManager: uid for sender and uid for session don't match"); + goto out; + } + + if (out_seat_id != NULL) { + *out_seat_id = get_seat_id_for_session_id (session_id, &error); + + if (error != NULL) { + g_debug ("GdmManager: Error while retrieving seat id for session: %s", + error->message); + g_clear_error (&error); + } + } + + if (out_is_remote != NULL) { + *out_is_remote = is_remote_session (self, session_id, &error); + + if (error != NULL) { + g_debug ("GdmManager: Error while retrieving remoteness for session: %s", + error->message); + g_clear_error (&error); + } + } + + if (out_tty != NULL) { + *out_tty = get_tty_for_session_id (session_id, &error); + + if (error != NULL) { + g_debug ("GdmManager: Error while retrieving tty for session: %s", + error->message); + g_clear_error (&error); + } + } + + display = gdm_display_store_find (self->priv->display_store, + lookup_by_session_id, + (gpointer) session_id); + +out: + if (out_display != NULL) { + *out_display = display; + } + + g_free (session_id); +} + +static gboolean +switch_to_compatible_user_session (GdmManager *manager, + GdmSession *session, + gboolean fail_if_already_switched) +{ + gboolean res; + gboolean ret; + const char *username; + const char *seat_id; + const char *ssid_to_activate; + GdmSession *existing_session; + + ret = FALSE; + + username = gdm_session_get_username (session); + seat_id = gdm_session_get_display_seat_id (session); + + if (!fail_if_already_switched) { + session = NULL; + } + + existing_session = find_session_for_user_on_seat (manager, username, seat_id, session); + + if (existing_session != NULL) { + ssid_to_activate = gdm_session_get_session_id (existing_session); + if (seat_id != NULL) { + res = gdm_activate_session_by_id (manager->priv->connection, seat_id, ssid_to_activate); + if (! res) { + g_debug ("GdmManager: unable to activate session: %s", ssid_to_activate); + goto out; + } + } + + res = session_unlock (manager, ssid_to_activate); + if (!res) { + /* this isn't fatal */ + g_debug ("GdmManager: unable to unlock session: %s", ssid_to_activate); + } + } else { + goto out; + } + + ret = TRUE; + + out: + return ret; +} + +static GdmDisplay * +get_display_for_user_session (GdmSession *session) +{ + return g_object_get_data (G_OBJECT (session), "gdm-display"); +} + +static GdmSession * +get_user_session_for_display (GdmDisplay *display) +{ + if (display == NULL) { + return NULL; + } + + return g_object_get_data (G_OBJECT (display), "gdm-user-session"); +} + +static gboolean +add_session_record (GdmManager *manager, + GdmSession *session, + GPid pid, + SessionRecord record) +{ + const char *username; + char *display_name, *hostname, *display_device; + gboolean recorded = FALSE; + + display_name = NULL; + username = NULL; + hostname = NULL; + display_device = NULL; + + username = gdm_session_get_username (session); + + if (username == NULL) { + goto out; + } + + g_object_get (G_OBJECT (session), + "display-name", &display_name, + "display-hostname", &hostname, + "display-device", &display_device, + NULL); + + if (display_name == NULL && display_device == NULL) { + goto out; + } + + switch (record) { + case SESSION_RECORD_LOGIN: + gdm_session_record_login (pid, + username, + hostname, + display_name, + display_device); + break; + case SESSION_RECORD_LOGOUT: + gdm_session_record_logout (pid, + username, + hostname, + display_name, + display_device); + break; + case SESSION_RECORD_FAILED: + gdm_session_record_failed (pid, + username, + hostname, + display_name, + display_device); + break; + } + + recorded = TRUE; +out: + g_free (display_name); + g_free (hostname); + g_free (display_device); + + return recorded; +} + +static GdmSession * +find_user_session_for_display (GdmManager *self, + GdmDisplay *display) +{ + + GList *node = self->priv->user_sessions; + + while (node != NULL) { + GdmSession *session = node->data; + GdmDisplay *candidate_display; + GList *next_node = node->next; + + candidate_display = get_display_for_user_session (session); + + if (candidate_display == display) + return session; + + node = next_node; + } + + return NULL; +} + +static gboolean +gdm_manager_handle_register_display (GdmDBusManager *manager, + GDBusMethodInvocation *invocation, + GVariant *details) +{ + GdmManager *self = GDM_MANAGER (manager); + const char *sender; + GDBusConnection *connection; + GdmDisplay *display = NULL; + GdmSession *session; + GVariantIter iter; + char *key = NULL; + char *value = NULL; + char *x11_display_name = NULL; + char *tty = NULL; + + g_debug ("GdmManager: trying to register new display"); + + sender = g_dbus_method_invocation_get_sender (invocation); + connection = g_dbus_method_invocation_get_connection (invocation); + get_display_and_details_for_bus_sender (self, connection, sender, &display, NULL, NULL, &tty, NULL, NULL, NULL, NULL); + + if (display == NULL) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("No display available")); + + return TRUE; + } + + g_variant_iter_init (&iter, details); + while (g_variant_iter_loop (&iter, "{&s&s}", &key, &value)) { + if (g_strcmp0 (key, "x11-display-name") == 0) { + x11_display_name = g_strdup (value); + break; + } + } + + session = find_user_session_for_display (self, display); + + if (session != NULL) { + GPid pid; + + if (x11_display_name != NULL) { + g_object_set (G_OBJECT (session), "display-name", x11_display_name, NULL); + g_object_set (G_OBJECT (display), "x11-display-name", x11_display_name, NULL); + } + + /* FIXME: this should happen in gdm-session.c when the session is opened + */ + if (tty != NULL) + g_object_set (G_OBJECT (session), "display-device", tty, NULL); + + pid = gdm_session_get_pid (session); + + if (pid > 0) { + add_session_record (self, session, pid, SESSION_RECORD_LOGIN); + } + } + + g_object_set (G_OBJECT (display), "status", GDM_DISPLAY_MANAGED, NULL); + + gdm_dbus_manager_complete_register_display (GDM_DBUS_MANAGER (manager), + invocation); + + g_clear_pointer (&x11_display_name, g_free); + g_clear_pointer (&tty, g_free); + return TRUE; +} + +static gboolean +gdm_manager_handle_register_session (GdmDBusManager *manager, + GDBusMethodInvocation *invocation, + GVariant *details) +{ + GdmManager *self = GDM_MANAGER (manager); + GdmDisplay *display = NULL; + const char *sender; + GDBusConnection *connection; + + sender = g_dbus_method_invocation_get_sender (invocation); + connection = g_dbus_method_invocation_get_connection (invocation); + + get_display_and_details_for_bus_sender (self, connection, sender, &display, + NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + g_debug ("GdmManager: trying to register new session on display %p", display); + + if (display != NULL) + g_object_set (G_OBJECT (display), "session-registered", TRUE, NULL); + else + g_debug ("GdmManager: No display, not registering"); + + gdm_dbus_manager_complete_register_session (GDM_DBUS_MANAGER (manager), + invocation); + + return TRUE; +} + +static gboolean +gdm_manager_handle_open_session (GdmDBusManager *manager, + GDBusMethodInvocation *invocation) +{ + GdmManager *self = GDM_MANAGER (manager); + const char *sender; + GDBusConnection *connection; + GdmDisplay *display = NULL; + GdmSession *session = NULL; + const char *address; + GPid pid = 0; + uid_t uid = (uid_t) -1; + uid_t allowed_user; + + g_debug ("GdmManager: trying to open new session"); + + sender = g_dbus_method_invocation_get_sender (invocation); + connection = g_dbus_method_invocation_get_connection (invocation); + get_display_and_details_for_bus_sender (self, connection, sender, &display, NULL, NULL, NULL, &pid, &uid, NULL, NULL); + + if (display == NULL) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("No session available")); + + return TRUE; + } + +#ifdef HAVE_LIBXDMCP + if (GDM_IS_XDMCP_CHOOSER_DISPLAY (display)) { + GdmLaunchEnvironment *launch_environment; + + g_object_get (display, "launch-environment", &launch_environment, NULL); + + if (launch_environment != NULL) { + session = gdm_launch_environment_get_session (launch_environment); + } + + if (session == NULL) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("Chooser session unavailable")); + return TRUE; + } + } +#endif + if (session == NULL) { + session = get_user_session_for_display (display); + g_debug ("GdmSession: Considering session %s for username %s", + gdm_session_get_session_id (session), + gdm_session_get_username (session)); + + if (gdm_session_is_running (session)) { + g_debug ("GdmSession: the session is running, and therefore can't be used"); + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("Can only be called before user is logged in")); + return TRUE; + } + } + + allowed_user = gdm_session_get_allowed_user (session); + + if (uid != allowed_user) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("Caller not GDM")); + return TRUE; + } + + address = gdm_session_get_server_address (session); + + if (address == NULL) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("Unable to open private communication channel")); + return TRUE; + } + + gdm_dbus_manager_complete_open_session (GDM_DBUS_MANAGER (manager), + invocation, + address); + return TRUE; +} + +static void +close_transient_session (GdmManager *self, + GdmSession *session) +{ + GPid pid; + pid = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (session), "caller-pid")); + gdm_session_close (session); + g_hash_table_remove (self->priv->transient_sessions, + GUINT_TO_POINTER (pid)); +} + +static void +on_reauthentication_client_connected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *self) +{ + g_debug ("GdmManager: client connected to reauthentication server"); +} + +static void +on_reauthentication_client_disconnected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *self) +{ + g_debug ("GdmManger: client disconnected from reauthentication server"); + close_transient_session (self, session); +} + +static void +on_reauthentication_client_rejected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *self) +{ + GPid pid; + + g_debug ("GdmManger: client with pid %ld rejected from reauthentication server", (long) pid_of_client); + + if (gdm_session_client_is_connected (session)) { + /* we already have a client connected, ignore this rejected one */ + return; + } + + pid = (GPid) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (session), "caller-pid")); + + if (pid != pid_of_client) { + const char *session_id; + char *client_session_id; + + /* rejected client isn't the process that started the + * transient reauthentication session. If it's not even from the + * same audit session, ignore it since it doesn't "own" the + * reauthentication session + */ + client_session_id = get_session_id_for_pid (pid_of_client, + NULL); + session_id = g_object_get_data (G_OBJECT (session), "caller-session-id"); + + if (g_strcmp0 (session_id, client_session_id) != 0) { + return; + } + } + + /* client was rejected, so clean up its session object + */ + close_transient_session (self, session); +} + +static void +on_reauthentication_cancelled (GdmSession *session, + GdmManager *self) +{ + g_debug ("GdmManager: client cancelled reauthentication request"); + close_transient_session (self, session); +} + +static void +on_reauthentication_conversation_started (GdmSession *session, + const char *service_name, + GdmManager *self) +{ + g_debug ("GdmManager: reauthentication service '%s' started", + service_name); +} + +static void +on_reauthentication_conversation_stopped (GdmSession *session, + const char *service_name, + GdmManager *self) +{ + g_debug ("GdmManager: reauthentication service '%s' stopped", + service_name); +} + +static void +on_reauthentication_verification_complete (GdmSession *session, + const char *service_name, + GdmManager *self) +{ + const char *session_id; + session_id = g_object_get_data (G_OBJECT (session), "caller-session-id"); + g_debug ("GdmManager: reauthenticated user in unmanaged session '%s' with service '%s'", + session_id, service_name); + session_unlock (self, session_id); + close_transient_session (self, session); +} + +static char * +open_temporary_reauthentication_channel (GdmManager *self, + char *seat_id, + char *session_id, + GPid pid, + uid_t uid, + gboolean is_remote) +{ + GdmSession *session; + char **environment; + const char *display, *auth_file; + const char *address; + + /* Note we're just using a minimal environment here rather than the + * session's environment because the caller is unprivileged and the + * associated worker will be privileged */ + environment = g_get_environ (); + display = ""; + auth_file = "/dev/null"; + + session = gdm_session_new (GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE, + uid, + display, + NULL, + NULL, + seat_id, + auth_file, + is_remote == FALSE, + (const char * const *) + environment); + g_strfreev (environment); + + g_debug ("GdmSession: Created session for temporary reauthentication channel for user %d (seat %s)", + (int) uid, + seat_id); + + g_object_set_data_full (G_OBJECT (session), + "caller-session-id", + g_strdup (session_id), + (GDestroyNotify) + g_free); + g_object_set_data (G_OBJECT (session), + "caller-pid", + GUINT_TO_POINTER (pid)); + g_hash_table_insert (self->priv->transient_sessions, + GINT_TO_POINTER (pid), + session); + + g_signal_connect (session, + "client-connected", + G_CALLBACK (on_reauthentication_client_connected), + self); + g_signal_connect (session, + "client-disconnected", + G_CALLBACK (on_reauthentication_client_disconnected), + self); + g_signal_connect (session, + "client-rejected", + G_CALLBACK (on_reauthentication_client_rejected), + self); + g_signal_connect (session, + "cancelled", + G_CALLBACK (on_reauthentication_cancelled), + self); + g_signal_connect (session, + "conversation-started", + G_CALLBACK (on_reauthentication_conversation_started), + self); + g_signal_connect (session, + "conversation-stopped", + G_CALLBACK (on_reauthentication_conversation_stopped), + self); + g_signal_connect (session, + "verification-complete", + G_CALLBACK (on_reauthentication_verification_complete), + self); + + address = gdm_session_get_server_address (session); + + return g_strdup (address); +} + +static gboolean +gdm_manager_handle_open_reauthentication_channel (GdmDBusManager *manager, + GDBusMethodInvocation *invocation, + const char *username) +{ + GdmManager *self = GDM_MANAGER (manager); + const char *sender; + GdmDisplay *display = NULL; + GdmSession *session; + GDBusConnection *connection; + char *seat_id = NULL; + char *session_id = NULL; + GPid pid = 0; + uid_t uid = (uid_t) -1; + gboolean is_login_screen = FALSE; + gboolean is_remote = FALSE; + + g_debug ("GdmManager: trying to open reauthentication channel for user %s", username); + + sender = g_dbus_method_invocation_get_sender (invocation); + connection = g_dbus_method_invocation_get_connection (invocation); + get_display_and_details_for_bus_sender (self, connection, sender, &display, &seat_id, &session_id, NULL, &pid, &uid, &is_login_screen, &is_remote); + + if (session_id == NULL || pid == 0 || uid == (uid_t) -1) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("No session available")); + + return TRUE; + } + + if (is_login_screen) { + g_debug ("GdmManager: looking for login screen session for user %s on seat %s", username, seat_id); + session = find_session_for_user_on_seat (self, + username, + seat_id, + NULL); + } else { + g_debug ("GdmManager: looking for user session on display"); + session = get_user_session_for_display (display); + } + + if (session != NULL && gdm_session_is_running (session)) { + gdm_session_start_reauthentication (session, pid, uid); + g_hash_table_insert (self->priv->open_reauthentication_requests, + GINT_TO_POINTER (pid), + invocation); + } else if (is_login_screen) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Login screen only allowed to open reauthentication channels for running sessions"); + return TRUE; + } else { + char *address; + address = open_temporary_reauthentication_channel (self, + seat_id, + session_id, + pid, + uid, + is_remote); + gdm_dbus_manager_complete_open_reauthentication_channel (GDM_DBUS_MANAGER (manager), + invocation, + address); + g_free (address); + } + + return TRUE; +} + +static void +manager_interface_init (GdmDBusManagerIface *interface) +{ + interface->handle_register_display = gdm_manager_handle_register_display; + interface->handle_register_session = gdm_manager_handle_register_session; + interface->handle_open_session = gdm_manager_handle_open_session; + interface->handle_open_reauthentication_channel = gdm_manager_handle_open_reauthentication_channel; +} + +static gboolean +display_is_on_seat0 (GdmDisplay *display) +{ + gboolean is_on_seat0 = TRUE; + char *seat_id = NULL; + + g_object_get (G_OBJECT (display), "seat-id", &seat_id, NULL); + + if (g_strcmp0 (seat_id, "seat0") != 0) { + is_on_seat0 = FALSE; + } + + g_free (seat_id); + + return is_on_seat0; +} + +static gboolean +get_timed_login_details (GdmManager *manager, + char **usernamep, + int *delayp) +{ + gboolean res; + gboolean enabled; + + int delay; + char *username = NULL; + + enabled = FALSE; + username = NULL; + delay = 0; + + res = gdm_settings_direct_get_boolean (GDM_KEY_TIMED_LOGIN_ENABLE, &enabled); + if (res && ! enabled) { + goto out; + } + + res = gdm_settings_direct_get_string (GDM_KEY_TIMED_LOGIN_USER, &username); + if (res && (username == NULL || username[0] == '\0')) { + g_clear_pointer (&username, g_free); + goto out; + } + + delay = 0; + res = gdm_settings_direct_get_int (GDM_KEY_TIMED_LOGIN_DELAY, &delay); + + if (res && delay <= 0) { + /* we don't allow the timed login to have a zero delay */ + delay = 10; + } + + out: + if (enabled) { + g_debug ("GdmDisplay: Got timed login details for display: %d %s %d", + enabled, + username, + delay); + } else { + g_debug ("GdmDisplay: Got timed login details for display: 0"); + } + + if (usernamep != NULL) { + *usernamep = username; + } else { + g_free (username); + } + if (delayp != NULL) { + *delayp = delay; + } + + return enabled; +} + +static gboolean +get_automatic_login_details (GdmManager *manager, + char **usernamep) +{ + gboolean res; + gboolean enabled; + char *username = NULL; + + enabled = FALSE; + username = NULL; + + res = gdm_settings_direct_get_boolean (GDM_KEY_AUTO_LOGIN_ENABLE, &enabled); + if (res && enabled) { + res = gdm_settings_direct_get_string (GDM_KEY_AUTO_LOGIN_USER, &username); + } + + if (enabled && res && username != NULL && username[0] != '\0') { + goto out; + } + + g_free (username); + username = NULL; + enabled = FALSE; + + out: + if (enabled) { + g_debug ("GdmDisplay: Got automatic login details for display: %d %s", + enabled, + username); + } else { + g_debug ("GdmDisplay: Got automatic login details for display: 0"); + } + + if (usernamep != NULL) { + *usernamep = username; + } else { + g_free (username); + } + + return enabled; +} + +static const char * +get_username_for_greeter_display (GdmManager *manager, + GdmDisplay *display) +{ + gboolean doing_initial_setup = FALSE; + + g_object_get (G_OBJECT (display), + "doing-initial-setup", &doing_initial_setup, + NULL); + + if (doing_initial_setup) { + return INITIAL_SETUP_USERNAME; + } else { + return GDM_USERNAME; + } +} + +static void +set_up_automatic_login_session (GdmManager *manager, + GdmDisplay *display) +{ + GdmSession *session; + char *display_session_type = NULL; + + /* 0 is root user; since the daemon talks to the session object + * directly, itself, for automatic login + */ + session = create_user_session_for_display (manager, display, 0); + + g_object_get (G_OBJECT (display), + "session-type", &display_session_type, + NULL); + + g_object_set (G_OBJECT (session), + "display-is-initial", FALSE, + NULL); + + g_debug ("GdmManager: Starting automatic login conversation"); + gdm_session_start_conversation (session, "gdm-autologin"); +} + +static void +set_up_chooser_session (GdmManager *manager, + GdmDisplay *display) +{ + const char *allowed_user; + struct passwd *passwd_entry; + + allowed_user = get_username_for_greeter_display (manager, display); + + if (!gdm_get_pwent_for_name (allowed_user, &passwd_entry)) { + g_warning ("GdmManager: couldn't look up username %s", + allowed_user); + gdm_display_unmanage (display); + gdm_display_finish (display); + return; + } + + gdm_display_start_greeter_session (display); +} + +static void +set_up_greeter_session (GdmManager *manager, + GdmDisplay *display) +{ + const char *allowed_user; + struct passwd *passwd_entry; + + allowed_user = get_username_for_greeter_display (manager, display); + + if (!gdm_get_pwent_for_name (allowed_user, &passwd_entry)) { + g_warning ("GdmManager: couldn't look up username %s", + allowed_user); + gdm_display_unmanage (display); + gdm_display_finish (display); + return; + } + + create_user_session_for_display (manager, display, passwd_entry->pw_uid); + gdm_display_start_greeter_session (display); +} + +static void +set_up_automatic_login_session_if_user_exists (GdmManager *manager, + GdmDisplay *display, + ActUser *user) +{ + if (act_user_is_nonexistent (user)) + set_up_greeter_session (manager, display); + else + set_up_automatic_login_session (manager, display); +} + +typedef struct { + GdmManager *manager; + GdmDisplay *display; + char *username; +} UsernameLookupOperation; + +static void +destroy_username_lookup_operation (UsernameLookupOperation *operation) +{ + g_object_unref (operation->manager); + g_object_unref (operation->display); + g_free (operation->username); + g_free (operation); +} + +static void +on_user_is_loaded_changed (ActUser *user, + GParamSpec *pspec, + UsernameLookupOperation *operation) +{ + if (act_user_is_loaded (user)) { + set_up_automatic_login_session_if_user_exists (operation->manager, operation->display, user); + g_signal_handlers_disconnect_by_func (G_OBJECT (user), + G_CALLBACK (on_user_is_loaded_changed), + operation); + destroy_username_lookup_operation (operation); + } +} + +static void +set_up_session (GdmManager *manager, + GdmDisplay *display) +{ + ActUserManager *user_manager; + ActUser *user; + gboolean loaded; + gboolean seat_can_autologin = FALSE, seat_did_autologin = FALSE; + gboolean autologin_enabled = FALSE; + g_autofree char *seat_id = NULL; + char *username = NULL; + + g_object_get (G_OBJECT (display), "seat-id", &seat_id, NULL); + + if (g_strcmp0 (seat_id, "seat0") == 0) + seat_can_autologin = TRUE; + + if (manager->priv->did_automatic_login || manager->priv->automatic_login_display != NULL) + seat_did_autologin = TRUE; + + if (seat_can_autologin && !seat_did_autologin) + autologin_enabled = get_automatic_login_details (manager, &username); + + if (!autologin_enabled) { + g_free (username); + +#ifdef HAVE_LIBXDMCP + if (GDM_IS_XDMCP_CHOOSER_DISPLAY (display)) { + set_up_chooser_session (manager, display); + return; + } +#endif + + set_up_greeter_session (manager, display); + return; + } + + /* Check whether the user really exists before committing to autologin. */ + user_manager = act_user_manager_get_default (); + user = act_user_manager_get_user (user_manager, username); + g_object_get (user_manager, "is-loaded", &loaded, NULL); + + if (loaded) { + set_up_automatic_login_session_if_user_exists (manager, display, user); + } else { + UsernameLookupOperation *operation; + + operation = g_new (UsernameLookupOperation, 1); + operation->manager = g_object_ref (manager); + operation->display = g_object_ref (display); + operation->username = username; + + g_signal_connect (user, + "notify::is-loaded", + G_CALLBACK (on_user_is_loaded_changed), + operation); + } +} + +static void +on_display_status_changed (GdmDisplay *display, + GParamSpec *arg1, + GdmManager *manager) +{ + int status; + int display_number = -1; + char *session_type = NULL; + gboolean doing_initial_setup = FALSE; +#ifdef WITH_PLYMOUTH + gboolean display_is_local = FALSE; + gboolean quit_plymouth = FALSE; + + g_object_get (display, + "is-local", &display_is_local, + NULL); + quit_plymouth = display_is_local && manager->priv->plymouth_is_running; +#endif + + g_object_get (display, + "x11-display-number", &display_number, + "session-type", &session_type, + "doing-initial-setup", &doing_initial_setup, + NULL); + + status = gdm_display_get_status (display); + + switch (status) { + case GDM_DISPLAY_PREPARED: + case GDM_DISPLAY_MANAGED: + if ((display_number == -1 && status == GDM_DISPLAY_PREPARED) || + (display_number != -1 && status == GDM_DISPLAY_MANAGED)) { + char *session_class; + + g_object_get (display, + "session-class", &session_class, + NULL); + if (g_strcmp0 (session_class, "greeter") == 0) + set_up_session (manager, display); + g_free (session_class); + } + break; + case GDM_DISPLAY_FAILED: + case GDM_DISPLAY_UNMANAGED: + case GDM_DISPLAY_FINISHED: +#ifdef WITH_PLYMOUTH + if (quit_plymouth) { + plymouth_quit_without_transition (); + manager->priv->plymouth_is_running = FALSE; + } +#endif + + if (display == manager->priv->automatic_login_display) { + g_clear_weak_pointer (&manager->priv->automatic_login_display); + + manager->priv->did_automatic_login = TRUE; + +#ifdef ENABLE_WAYLAND_SUPPORT + if (g_strcmp0 (session_type, "wayland") != 0 && status == GDM_DISPLAY_FAILED) { + /* we're going to fall back to X11, so try to autologin again + */ + manager->priv->did_automatic_login = FALSE; + } +#endif + } + break; + default: + break; + } + +} + +static void +on_display_removed (GdmDisplayStore *display_store, + GdmDisplay *display, + GdmManager *manager) +{ + char *id; + + gdm_display_get_id (display, &id, NULL); + g_dbus_object_manager_server_unexport (manager->priv->object_manager, id); + g_free (id); + + g_signal_handlers_disconnect_by_func (display, G_CALLBACK (on_display_status_changed), manager); + + g_signal_emit (manager, signals[DISPLAY_REMOVED], 0, display); +} + +static void +destroy_start_user_session_operation (StartUserSessionOperation *operation) +{ + g_object_set_data (G_OBJECT (operation->session), + "start-user-session-operation", + NULL); + g_object_unref (operation->session); + g_free (operation->service_name); + g_slice_free (StartUserSessionOperation, operation); +} + +static void +start_user_session (GdmManager *manager, + StartUserSessionOperation *operation) +{ + GdmDisplay *display; + + display = get_display_for_user_session (operation->session); + + if (display != NULL) { + char *auth_file; + const char *username; + gboolean is_connected = FALSE; + + g_object_get (G_OBJECT (display), "is-connected", &is_connected, NULL); + + if (is_connected) { + auth_file = NULL; + username = gdm_session_get_username (operation->session); + gdm_display_add_user_authorization (display, + username, + &auth_file, + NULL); + + g_assert (auth_file != NULL); + + g_object_set (operation->session, + "user-x11-authority-file", auth_file, + NULL); + + g_free (auth_file); + } + } + + gdm_session_start_session (operation->session, + operation->service_name); + + destroy_start_user_session_operation (operation); +} + +static void +create_display_for_user_session (GdmManager *self, + GdmSession *session, + const char *session_id) +{ + GdmDisplay *display; + /* at the moment we only create GdmLocalDisplay objects on seat0 */ + const char *seat_id = "seat0"; + + display = gdm_local_display_new (); + + g_object_set (G_OBJECT (display), + "session-class", "user", + "seat-id", seat_id, + "session-id", session_id, + NULL); + gdm_display_store_add (self->priv->display_store, + display); + g_object_set_data (G_OBJECT (session), "gdm-display", display); + g_object_set_data_full (G_OBJECT (display), + "gdm-user-session", + g_object_ref (session), + (GDestroyNotify) + clean_user_session); +} + +static gboolean +chown_file (GFile *file, + uid_t uid, + gid_t gid, + GError **error) +{ + if (!g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_UID, uid, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, error)) { + return FALSE; + } + if (!g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_GID, gid, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, error)) { + return FALSE; + } + return TRUE; +} + +static gboolean +chown_recursively (GFile *dir, + uid_t uid, + gid_t gid, + GError **error) +{ + GFile *file = NULL; + GFileInfo *info = NULL; + GFileEnumerator *enumerator = NULL; + gboolean retval = FALSE; + + if (chown_file (dir, uid, gid, error) == FALSE) { + goto out; + } + + enumerator = g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_TYPE"," + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, error); + if (!enumerator) { + goto out; + } + + while ((info = g_file_enumerator_next_file (enumerator, NULL, error)) != NULL) { + file = g_file_get_child (dir, g_file_info_get_name (info)); + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + if (chown_recursively (file, uid, gid, error) == FALSE) { + goto out; + } + } else if (chown_file (file, uid, gid, error) == FALSE) { + goto out; + } + + g_clear_object (&file); + g_clear_object (&info); + } + + if (*error) { + goto out; + } + + retval = TRUE; +out: + g_clear_object (&file); + g_clear_object (&info); + g_clear_object (&enumerator); + + return retval; +} + +static void +chown_initial_setup_home_dir (void) +{ + GFile *dir; + GError *error; + char *gis_dir_path; + char *gis_uid_path; + char *gis_uid_contents; + struct passwd *pwe; + uid_t uid; + + if (!gdm_get_pwent_for_name (INITIAL_SETUP_USERNAME, &pwe)) { + g_warning ("Unknown user %s", INITIAL_SETUP_USERNAME); + return; + } + + gis_dir_path = g_strdup (pwe->pw_dir); + + gis_uid_path = g_build_filename (gis_dir_path, + "gnome-initial-setup-uid", + NULL); + if (!g_file_get_contents (gis_uid_path, &gis_uid_contents, NULL, NULL)) { + g_warning ("Unable to read %s", gis_uid_path); + goto out; + } + + uid = (uid_t) atoi (gis_uid_contents); + pwe = getpwuid (uid); + if (uid == 0 || pwe == NULL) { + g_warning ("UID '%s' in %s is not valid", gis_uid_contents, gis_uid_path); + goto out; + } + + error = NULL; + dir = g_file_new_for_path (gis_dir_path); + if (!chown_recursively (dir, pwe->pw_uid, pwe->pw_gid, &error)) { + g_warning ("Failed to change ownership for %s: %s", gis_dir_path, error->message); + g_error_free (error); + } + g_object_unref (dir); +out: + g_free (gis_uid_contents); + g_free (gis_uid_path); + g_free (gis_dir_path); +} + +static gboolean +on_start_user_session (StartUserSessionOperation *operation) +{ + GdmManager *self = operation->manager; + gboolean migrated; + gboolean fail_if_already_switched = TRUE; + gboolean doing_initial_setup = FALSE; + GdmDisplay *display; + const char *session_id; + + g_debug ("GdmManager: start or jump to session"); + + /* If there's already a session running, jump to it. + * If the only session running is the one we just opened, + * start a session on it. + */ + migrated = switch_to_compatible_user_session (operation->manager, operation->session, fail_if_already_switched); + + g_debug ("GdmManager: migrated: %d", migrated); + if (migrated) { + /* We don't stop the manager here because + when Xorg exits it switches to the VT it was + started from. That interferes with fast + user switching. */ + gdm_session_reset (operation->session); + destroy_start_user_session_operation (operation); + goto out; + } + + display = get_display_for_user_session (operation->session); + + g_object_get (G_OBJECT (display), + "doing-initial-setup", &doing_initial_setup, + NULL); + + if (doing_initial_setup) + chown_initial_setup_home_dir (); + + session_id = gdm_session_get_conversation_session_id (operation->session, + operation->service_name); + + if (gdm_session_get_display_mode (operation->session) == GDM_SESSION_DISPLAY_MODE_REUSE_VT) { + /* In this case, the greeter's display is morphing into + * the user session display. Kill the greeter on this session + * and let the user session follow the same display. */ + gdm_display_stop_greeter_session (display); + g_object_set (G_OBJECT (display), + "session-class", "user", + "session-id", session_id, + NULL); + } else { + uid_t allowed_uid; + + g_object_ref (display); + if (doing_initial_setup) { + g_autoptr(GError) error = NULL; + + g_debug ("GdmManager: closing down initial setup display in background"); + g_object_set (G_OBJECT (display), "status", GDM_DISPLAY_WAITING_TO_FINISH, NULL); + + if (!g_file_set_contents (ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT, + "1", + 1, + &error)) { + g_warning ("GdmDisplay: Could not write initial-setup-done marker to %s: %s", + ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT, + error->message); + g_clear_error (&error); + } + } else { + g_debug ("GdmManager: session has its display server, reusing our server for another login screen"); + } + + /* The user session is going to follow the session worker + * into the new display. Untie it from this display and + * create a new session for a future user login. */ + allowed_uid = gdm_session_get_allowed_user (operation->session); + g_object_set_data (G_OBJECT (display), "gdm-user-session", NULL); + g_object_set_data (G_OBJECT (operation->session), "gdm-display", NULL); + create_user_session_for_display (operation->manager, display, allowed_uid); + + /* Give the user session a new display object for bookkeeping purposes */ + create_display_for_user_session (operation->manager, + operation->session, + session_id); + + + if (g_strcmp0 (operation->service_name, "gdm-autologin") == 0 && + !gdm_session_client_is_connected (operation->session)) { + /* remove the unused prepared greeter display since we're not going + * to have a greeter */ + gdm_display_store_remove (self->priv->display_store, display); + g_object_unref (display); + + self->priv->automatic_login_display = g_object_get_data (G_OBJECT (operation->session), "gdm-display"); + g_object_add_weak_pointer (G_OBJECT (self->priv->automatic_login_display), (gpointer *) &self->priv->automatic_login_display); + } + } + + start_user_session (operation->manager, operation); + + out: + return G_SOURCE_REMOVE; +} + +static void +queue_start_user_session (GdmManager *manager, + GdmSession *session, + const char *service_name) +{ + StartUserSessionOperation *operation; + + operation = g_slice_new0 (StartUserSessionOperation); + operation->manager = manager; + operation->session = g_object_ref (session); + operation->service_name = g_strdup (service_name); + + operation->idle_id = g_idle_add ((GSourceFunc) on_start_user_session, operation); + g_object_set_data (G_OBJECT (session), "start-user-session-operation", operation); +} + +static void +start_user_session_if_ready (GdmManager *manager, + GdmSession *session, + const char *service_name) +{ + gboolean start_when_ready; + + start_when_ready = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (session), "start-when-ready")); + if (start_when_ready) { + g_object_set_data (G_OBJECT (session), "waiting-to-start", GINT_TO_POINTER (FALSE)); + queue_start_user_session (manager, session, service_name); + } else { + g_object_set_data (G_OBJECT (session), "waiting-to-start", GINT_TO_POINTER (TRUE)); + } +} + +static void +on_session_authentication_failed (GdmSession *session, + const char *service_name, + GPid conversation_pid, + GdmManager *manager) +{ + add_session_record (manager, session, conversation_pid, SESSION_RECORD_FAILED); +} + +static void +on_user_session_opened (GdmSession *session, + const char *service_name, + const char *session_id, + GdmManager *manager) +{ + manager->priv->user_sessions = g_list_append (manager->priv->user_sessions, + g_object_ref (session)); + if (g_strcmp0 (service_name, "gdm-autologin") == 0 && + !gdm_session_client_is_connected (session)) { + /* If we're auto logging in then don't wait for the go-ahead from a greeter, + * (since there is no greeter) */ + g_object_set_data (G_OBJECT (session), "start-when-ready", GINT_TO_POINTER (TRUE)); + } + + start_user_session_if_ready (manager, session, service_name); +} + +static void +on_user_session_started (GdmSession *session, + const char *service_name, + GPid pid, + GdmManager *manager) +{ + g_debug ("GdmManager: session started %d", pid); + add_session_record (manager, session, pid, SESSION_RECORD_LOGIN); + +#ifdef WITH_PLYMOUTH + if (g_strcmp0 (service_name, "gdm-autologin") == 0) { + if (manager->priv->plymouth_is_running) { + g_timeout_add_seconds (20, (GSourceFunc) plymouth_quit_with_transition, NULL); + manager->priv->plymouth_is_running = FALSE; + } + } +#endif +} + +static void +remove_user_session (GdmManager *manager, + GdmSession *session) +{ + GList *node; + GdmDisplay *display; + + display = get_display_for_user_session (session); + + if (display != NULL) { + gdm_display_unmanage (display); + gdm_display_finish (display); + } + + node = g_list_find (manager->priv->user_sessions, session); + + if (node != NULL) { + manager->priv->user_sessions = g_list_delete_link (manager->priv->user_sessions, node); + gdm_session_close (session); + g_object_unref (session); + } +} + +static void +on_session_start_failed (GdmSession *session, + const char *service_name, + const char *message, + GdmManager *manager) +{ + g_debug ("GdmManager: session failed to start: %s", message); + remove_user_session (manager, session); +} + +static void +on_user_session_exited (GdmSession *session, + int code, + GdmManager *manager) +{ + GPid pid; + + g_debug ("GdmManager: session exited with status %d", code); + pid = gdm_session_get_pid (session); + + if (pid > 0) { + add_session_record (manager, session, pid, SESSION_RECORD_LOGOUT); + } + + remove_user_session (manager, session); +} + +static void +on_user_session_died (GdmSession *session, + int signal_number, + GdmManager *manager) +{ + g_debug ("GdmManager: session died with signal %s", strsignal (signal_number)); + remove_user_session (manager, session); +} + +static char * +get_display_device (GdmManager *manager, + GdmDisplay *display) +{ + /* systemd finds the display device out on its own based on the display */ + return NULL; +} + +static void +on_session_reauthenticated (GdmSession *session, + const char *service_name, + GdmManager *manager) +{ + gboolean fail_if_already_switched = FALSE; + + if (gdm_session_get_display_mode (session) == GDM_SESSION_DISPLAY_MODE_REUSE_VT) { + const char *seat_id; + char *session_id; + + seat_id = gdm_session_get_display_seat_id (session); + if (gdm_get_login_window_session_id (seat_id, &session_id)) { + GdmDisplay *display = gdm_display_store_find (manager->priv->display_store, + lookup_by_session_id, + (gpointer) session_id); + + if (display != NULL) { + gdm_display_stop_greeter_session (display); + gdm_display_unmanage (display); + gdm_display_finish (display); + } + g_free (session_id); + } + } + + /* There should already be a session running, so jump to its + * VT. In the event we're already on the right VT, (i.e. user + * used an unlock screen instead of a user switched login screen), + * then silently succeed and unlock the session. + */ + switch_to_compatible_user_session (manager, session, fail_if_already_switched); +} + +static void +on_session_client_ready_for_session_to_start (GdmSession *session, + const char *service_name, + gboolean client_is_ready, + GdmManager *manager) +{ + gboolean waiting_to_start_user_session; + + if (client_is_ready) { + g_debug ("GdmManager: Will start session when ready"); + } else { + g_debug ("GdmManager: Will start session when ready and told"); + } + + waiting_to_start_user_session = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (session), + "waiting-to-start")); + + g_object_set_data (G_OBJECT (session), + "start-when-ready", + GINT_TO_POINTER (client_is_ready)); + + if (client_is_ready && waiting_to_start_user_session) { + start_user_session_if_ready (manager, session, service_name); + } +} + +static void +on_session_client_connected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *manager) +{ + GdmDisplay *display; + char *username; + int delay; + gboolean enabled; + gboolean allow_timed_login = FALSE; + + g_debug ("GdmManager: client with pid %d connected", (int) pid_of_client); + + if (gdm_session_is_running (session)) { + const char *session_username; + session_username = gdm_session_get_username (session); + g_debug ("GdmManager: ignoring connection, since session already running (for user %s)", + session_username); + return; + } + + display = get_display_for_user_session (session); + + if (display == NULL) { + return; + } + + if (!display_is_on_seat0 (display)) { + return; + } + +#ifdef WITH_PLYMOUTH + if (manager->priv->plymouth_is_running) { + plymouth_quit_with_transition (); + manager->priv->plymouth_is_running = FALSE; + } +#endif + + g_object_get (G_OBJECT (display), "allow-timed-login", &allow_timed_login, NULL); + + if (!allow_timed_login) { + return; + } + + enabled = get_timed_login_details (manager, &username, &delay); + + if (! enabled) { + return; + } + + gdm_session_set_timed_login_details (session, username, delay); + + g_debug ("GdmManager: Starting automatic login conversation (for timed login)"); + gdm_session_start_conversation (session, "gdm-autologin"); + + g_free (username); + +} + +static void +on_session_client_disconnected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *manager) +{ + g_debug ("GdmManager: client with pid %d disconnected", (int) pid_of_client); +} + +typedef struct +{ + GdmManager *manager; + GdmSession *session; + guint idle_id; +} ResetSessionOperation; + +static void +destroy_reset_session_operation (ResetSessionOperation *operation) +{ + g_object_set_data (G_OBJECT (operation->session), + "reset-session-operation", + NULL); + g_object_unref (operation->session); + g_slice_free (ResetSessionOperation, operation); +} + +static gboolean +on_reset_session (ResetSessionOperation *operation) +{ + gdm_session_reset (operation->session); + + destroy_reset_session_operation (operation); + + return G_SOURCE_REMOVE; +} + +static void +queue_session_reset (GdmManager *manager, + GdmSession *session) +{ + ResetSessionOperation *operation; + + operation = g_object_get_data (G_OBJECT (session), "reset-session-operation"); + + if (operation != NULL) { + return; + } + + operation = g_slice_new0 (ResetSessionOperation); + operation->manager = manager; + operation->session = g_object_ref (session); + operation->idle_id = g_idle_add ((GSourceFunc) on_reset_session, operation); + + g_object_set_data (G_OBJECT (session), "reset-session-operation", operation); +} + +static void +on_session_cancelled (GdmSession *session, + GdmManager *manager) +{ + g_debug ("GdmManager: Session was cancelled"); + queue_session_reset (manager, session); +} + +static void +on_session_conversation_started (GdmSession *session, + const char *service_name, + GdmManager *manager) +{ + GdmDisplay *display; + gboolean enabled; + char *username; + + g_debug ("GdmManager: session conversation started for service %s on session", service_name); + + if (g_strcmp0 (service_name, "gdm-autologin") != 0) { + g_debug ("GdmManager: ignoring session conversation since its not automatic login conversation"); + return; + } + + display = get_display_for_user_session (session); + + if (display == NULL) { + g_debug ("GdmManager: conversation has no associated display"); + return; + } + + if (!display_is_on_seat0 (display)) { + return; + } + + enabled = get_automatic_login_details (manager, &username); + + if (! enabled) { + return; + } + + g_debug ("GdmManager: begin auto login for user '%s'", username); + + /* service_name will be "gdm-autologin" + */ + gdm_session_setup_for_user (session, service_name, username); + + g_free (username); +} + +static void +on_session_conversation_stopped (GdmSession *session, + const char *service_name, + GdmManager *manager) +{ + g_debug ("GdmManager: session conversation '%s' stopped", service_name); +} + +static void +on_session_reauthentication_started (GdmSession *session, + int pid_of_caller, + const char *address, + GdmManager *manager) +{ + GDBusMethodInvocation *invocation; + gpointer source_tag; + + g_debug ("GdmManager: reauthentication started"); + + source_tag = GINT_TO_POINTER (pid_of_caller); + + invocation = g_hash_table_lookup (manager->priv->open_reauthentication_requests, + source_tag); + + if (invocation != NULL) { + g_hash_table_steal (manager->priv->open_reauthentication_requests, + source_tag); + gdm_dbus_manager_complete_open_reauthentication_channel (GDM_DBUS_MANAGER (manager), + invocation, + address); + } +} + +static void +clean_user_session (GdmSession *session) +{ + g_object_set_data (G_OBJECT (session), "gdm-display", NULL); + g_object_unref (session); +} + +static GdmSession * +create_user_session_for_display (GdmManager *manager, + GdmDisplay *display, + uid_t allowed_user) +{ + GdmSession *session; + gboolean display_is_local = FALSE; + char *display_name = NULL; + char *display_device = NULL; + char *remote_hostname = NULL; + char *display_auth_file = NULL; + char *display_seat_id = NULL; + char *display_id = NULL; +#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER) + g_autofree char *display_session_type = NULL; + gboolean greeter_is_wayland; +#endif + + g_object_get (G_OBJECT (display), + "id", &display_id, + "x11-display-name", &display_name, + "is-local", &display_is_local, + "remote-hostname", &remote_hostname, + "x11-authority-file", &display_auth_file, + "seat-id", &display_seat_id, +#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER) + "session-type", &display_session_type, +#endif + NULL); + display_device = get_display_device (manager, display); + + session = gdm_session_new (GDM_SESSION_VERIFICATION_MODE_LOGIN, + allowed_user, + display_name, + remote_hostname, + display_device, + display_seat_id, + display_auth_file, + display_is_local, + NULL); + + g_debug ("GdmSession: Created user session for user %d on display %s (seat %s)", + (int) allowed_user, + display_id, + display_seat_id); + + g_free (display_name); + g_free (remote_hostname); + g_free (display_auth_file); + g_free (display_seat_id); + + g_signal_connect (session, + "reauthentication-started", + G_CALLBACK (on_session_reauthentication_started), + manager); + g_signal_connect (session, + "reauthenticated", + G_CALLBACK (on_session_reauthenticated), + manager); + g_signal_connect (session, + "client-ready-for-session-to-start", + G_CALLBACK (on_session_client_ready_for_session_to_start), + manager); + g_signal_connect (session, + "client-connected", + G_CALLBACK (on_session_client_connected), + manager); + g_signal_connect (session, + "client-disconnected", + G_CALLBACK (on_session_client_disconnected), + manager); + g_signal_connect (session, + "cancelled", + G_CALLBACK (on_session_cancelled), + manager); + g_signal_connect (session, + "conversation-started", + G_CALLBACK (on_session_conversation_started), + manager); + g_signal_connect (session, + "conversation-stopped", + G_CALLBACK (on_session_conversation_stopped), + manager); + g_signal_connect (session, + "authentication-failed", + G_CALLBACK (on_session_authentication_failed), + manager); + g_signal_connect (session, + "session-opened", + G_CALLBACK (on_user_session_opened), + manager); + g_signal_connect (session, + "session-started", + G_CALLBACK (on_user_session_started), + manager); + g_signal_connect (session, + "session-start-failed", + G_CALLBACK (on_session_start_failed), + manager); + g_signal_connect (session, + "session-exited", + G_CALLBACK (on_user_session_exited), + manager); + g_signal_connect (session, + "session-died", + G_CALLBACK (on_user_session_died), + manager); + g_object_set_data (G_OBJECT (session), "gdm-display", display); + g_object_set_data_full (G_OBJECT (display), + "gdm-user-session", + g_object_ref (session), + (GDestroyNotify) + clean_user_session); + +#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER) + greeter_is_wayland = g_strcmp0 (display_session_type, "wayland") == 0; + g_object_set (G_OBJECT (session), "ignore-wayland", !greeter_is_wayland, NULL); +#endif + + return session; +} + +static void +on_display_added (GdmDisplayStore *display_store, + const char *id, + GdmManager *manager) +{ + GdmDisplay *display; + + display = gdm_display_store_lookup (display_store, id); + + if (display != NULL) { + g_dbus_object_manager_server_export (manager->priv->object_manager, + gdm_display_get_object_skeleton (display)); + + g_signal_connect (display, "notify::status", + G_CALLBACK (on_display_status_changed), + manager); + g_signal_emit (manager, signals[DISPLAY_ADDED], 0, id); + } +} + +GQuark +gdm_manager_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gdm_manager_error"); + } + + return ret; +} + +static void +listify_display_ids (const char *id, + GdmDisplay *display, + GPtrArray **array) +{ + g_ptr_array_add (*array, g_strdup (id)); +} + +/* + Example: + dbus-send --system --dest=org.gnome.DisplayManager \ + --type=method_call --print-reply --reply-timeout=2000 \ + /org/gnome/DisplayManager/Displays \ + org.freedesktop.ObjectManager.GetAll +*/ +gboolean +gdm_manager_get_displays (GdmManager *manager, + GPtrArray **displays, + GError **error) +{ + g_return_val_if_fail (GDM_IS_MANAGER (manager), FALSE); + + if (displays == NULL) { + return FALSE; + } + + *displays = g_ptr_array_new (); + gdm_display_store_foreach (manager->priv->display_store, + (GdmDisplayStoreFunc)listify_display_ids, + displays); + + return TRUE; +} + +void +gdm_manager_stop (GdmManager *manager) +{ + g_debug ("GdmManager: GDM stopping"); + + if (manager->priv->local_factory != NULL) { + gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->priv->local_factory)); + } + +#ifdef HAVE_LIBXDMCP + if (manager->priv->xdmcp_factory != NULL) { + gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->priv->xdmcp_factory)); + } +#endif + + manager->priv->started = FALSE; +} + +void +gdm_manager_start (GdmManager *manager) +{ + g_debug ("GdmManager: GDM starting to manage displays"); + +#ifdef WITH_PLYMOUTH + manager->priv->plymouth_is_running = plymouth_is_running (); + + if (manager->priv->plymouth_is_running) { + plymouth_prepare_for_transition (); + } +#endif + if (!manager->priv->xdmcp_enabled || manager->priv->show_local_greeter) { + gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->priv->local_factory)); + } + +#ifdef HAVE_LIBXDMCP + /* Accept remote connections */ + if (manager->priv->xdmcp_enabled) { +#ifdef WITH_PLYMOUTH + /* Quit plymouth if xdmcp is the only display */ + if (!manager->priv->show_local_greeter && manager->priv->plymouth_is_running) { + plymouth_quit_without_transition (); + manager->priv->plymouth_is_running = FALSE; + } +#endif + if (manager->priv->xdmcp_factory != NULL) { + g_debug ("GdmManager: Accepting XDMCP connections..."); + gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->priv->xdmcp_factory)); + } + } +#endif + + manager->priv->started = TRUE; +} + +static gboolean +register_manager (GdmManager *manager) +{ + GError *error = NULL; + GDBusObjectManagerServer *object_server; + + error = NULL; + manager->priv->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, + NULL, + &error); + if (manager->priv->connection == NULL) { + g_critical ("error getting system bus: %s", error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + object_server = g_dbus_object_manager_server_new (GDM_MANAGER_DISPLAYS_PATH); + g_dbus_object_manager_server_set_connection (object_server, manager->priv->connection); + manager->priv->object_manager = object_server; + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (manager), + manager->priv->connection, + GDM_MANAGER_PATH, + &error)) { + g_critical ("error exporting interface to %s: %s", + GDM_MANAGER_PATH, + error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + return TRUE; +} + +void +gdm_manager_set_xdmcp_enabled (GdmManager *manager, + gboolean enabled) +{ + g_return_if_fail (GDM_IS_MANAGER (manager)); + + if (manager->priv->xdmcp_enabled != enabled) { + manager->priv->xdmcp_enabled = enabled; +#ifdef HAVE_LIBXDMCP + if (manager->priv->xdmcp_enabled) { + manager->priv->xdmcp_factory = gdm_xdmcp_display_factory_new (manager->priv->display_store); + if (manager->priv->started) { + gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->priv->xdmcp_factory)); + } + } else { + if (manager->priv->started) { + gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->priv->xdmcp_factory)); + } + + g_object_unref (manager->priv->xdmcp_factory); + manager->priv->xdmcp_factory = NULL; + } +#endif + } + +} + +void +gdm_manager_set_show_local_greeter (GdmManager *manager, + gboolean show_local_greeter) +{ + g_return_if_fail (GDM_IS_MANAGER (manager)); + + manager->priv->show_local_greeter = show_local_greeter; +} + +static void +gdm_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmManager *self; + + self = GDM_MANAGER (object); + + switch (prop_id) { + case PROP_XDMCP_ENABLED: + gdm_manager_set_xdmcp_enabled (self, g_value_get_boolean (value)); + break; + case PROP_SHOW_LOCAL_GREETER: + gdm_manager_set_show_local_greeter (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmManager *self; + + self = GDM_MANAGER (object); + + switch (prop_id) { + case PROP_XDMCP_ENABLED: + g_value_set_boolean (value, self->priv->xdmcp_enabled); + break; + case PROP_SHOW_LOCAL_GREETER: + g_value_set_boolean (value, self->priv->show_local_greeter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gdm_manager_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmManager *manager; + + manager = GDM_MANAGER (G_OBJECT_CLASS (gdm_manager_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + gdm_dbus_manager_set_version (GDM_DBUS_MANAGER (manager), PACKAGE_VERSION); + + manager->priv->local_factory = gdm_local_display_factory_new (manager->priv->display_store); + +#ifdef HAVE_LIBXDMCP + if (manager->priv->xdmcp_enabled) { + manager->priv->xdmcp_factory = gdm_xdmcp_display_factory_new (manager->priv->display_store); + } +#endif + + return G_OBJECT (manager); +} + +static void +gdm_manager_class_init (GdmManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_manager_get_property; + object_class->set_property = gdm_manager_set_property; + object_class->constructor = gdm_manager_constructor; + object_class->dispose = gdm_manager_dispose; + + signals [DISPLAY_ADDED] = + g_signal_new ("display-added", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmManagerClass, display_added), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [DISPLAY_REMOVED] = + g_signal_new ("display-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmManagerClass, display_removed), + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, G_TYPE_OBJECT); + + g_object_class_install_property (object_class, + PROP_XDMCP_ENABLED, + g_param_spec_boolean ("xdmcp-enabled", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); +} + +static void +gdm_manager_init (GdmManager *manager) +{ + + manager->priv = GDM_MANAGER_GET_PRIVATE (manager); + + manager->priv->display_store = gdm_display_store_new (); + manager->priv->user_sessions = NULL; + manager->priv->open_reauthentication_requests = g_hash_table_new_full (NULL, + NULL, + (GDestroyNotify) + NULL, + (GDestroyNotify) + g_object_unref); + manager->priv->transient_sessions = g_hash_table_new_full (NULL, + NULL, + (GDestroyNotify) + NULL, + (GDestroyNotify) + g_object_unref); + g_signal_connect (G_OBJECT (manager->priv->display_store), + "display-added", + G_CALLBACK (on_display_added), + manager); + + g_signal_connect (G_OBJECT (manager->priv->display_store), + "display-removed", + G_CALLBACK (on_display_removed), + manager); +} + +static void +unexport_display (const char *id, + GdmDisplay *display, + GdmManager *manager) +{ + if (!g_dbus_connection_is_closed (manager->priv->connection)) + g_dbus_object_manager_server_unexport (manager->priv->object_manager, id); +} + +static void +finish_display (const char *id, + GdmDisplay *display, + GdmManager *manager) +{ + gdm_display_stop_greeter_session (display); + if (gdm_display_get_status (display) == GDM_DISPLAY_MANAGED) + gdm_display_unmanage (display); + gdm_display_finish (display); +} + +static void +gdm_manager_dispose (GObject *object) +{ + GdmManager *manager; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_MANAGER (object)); + + manager = GDM_MANAGER (object); + + g_return_if_fail (manager->priv != NULL); + + g_clear_weak_pointer (&manager->priv->automatic_login_display); + +#ifdef HAVE_LIBXDMCP + g_clear_object (&manager->priv->xdmcp_factory); +#endif + g_clear_object (&manager->priv->local_factory); + g_clear_pointer (&manager->priv->open_reauthentication_requests, + g_hash_table_unref); + g_clear_pointer (&manager->priv->transient_sessions, + g_hash_table_unref); + + g_list_foreach (manager->priv->user_sessions, + (GFunc) gdm_session_close, + NULL); + g_list_free_full (manager->priv->user_sessions, (GDestroyNotify) g_object_unref); + manager->priv->user_sessions = NULL; + + g_signal_handlers_disconnect_by_func (G_OBJECT (manager->priv->display_store), + G_CALLBACK (on_display_added), + manager); + g_signal_handlers_disconnect_by_func (G_OBJECT (manager->priv->display_store), + G_CALLBACK (on_display_removed), + manager); + + if (!g_dbus_connection_is_closed (manager->priv->connection)) { + gdm_display_store_foreach (manager->priv->display_store, + (GdmDisplayStoreFunc)unexport_display, + manager); + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (manager)); + } + + gdm_display_store_foreach (manager->priv->display_store, + (GdmDisplayStoreFunc) finish_display, + manager); + + gdm_display_store_clear (manager->priv->display_store); + + g_dbus_object_manager_server_set_connection (manager->priv->object_manager, NULL); + + g_clear_object (&manager->priv->connection); + g_clear_object (&manager->priv->object_manager); + g_clear_object (&manager->priv->display_store); + + G_OBJECT_CLASS (gdm_manager_parent_class)->dispose (object); +} + +GdmManager * +gdm_manager_new (void) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + gboolean res; + + manager_object = g_object_new (GDM_TYPE_MANAGER, NULL); + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + res = register_manager (manager_object); + if (! res) { + g_object_unref (manager_object); + return NULL; + } + } + + return GDM_MANAGER (manager_object); +} |