/* -*- 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-file-utils.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-dynamic-user-store.h"
#include "gdm-launch-environment.h"
#include "gdm-local-display.h"
#include "gdm-local-display-factory.h"
#include "gdm-remote-display.h"
#include "gdm-remote-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_DBUS_PATH             "/org/gnome/DisplayManager"
#define GDM_MANAGER_PATH          GDM_DBUS_PATH "/Manager"
#define GDM_MANAGER_DISPLAYS_PATH GDM_DBUS_PATH "/Displays"

#define ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT GDM_RUN_DIR "/gdm.ran-initial-setup"
#define INITIAL_SETUP_EXPORT_DIR GDM_RUN_DIR "/gnome-initial-setup"

typedef struct
{
        GdmManager *manager;
        GdmSession *session;
        char *service_name;
        guint idle_id;
} StartUserSessionOperation;

struct _GdmManager
{
        GdmDBusManagerSkeleton parent;
        GdmDynamicUserStore    *dyn_user_store;
        GdmDisplayStore        *display_store;
        GdmLocalDisplayFactory *local_factory;
#ifdef HAVE_LIBXDMCP
        GdmXdmcpDisplayFactory *xdmcp_factory;
#endif
        GdmRemoteDisplayFactory *remote_factory;
        GdmDisplay             *automatic_login_display;
        GList                  *user_sessions;
        GHashTable             *transient_sessions;
        GHashTable             *open_reauthentication_requests;
        gboolean                xdmcp_enabled;
        gboolean                remote_login_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,
        PROP_REMOTE_LOGIN_ENABLED
};

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 void     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));

#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->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 (GdmManager *manager,
                       const char *username,
                       GdmSession *dont_count_session)
{
        GList *node;

        for (node = manager->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_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)
{
        int ret;

        ret = sd_session_is_remote (session_id);

        if (ret < 0 && ret != -ENXIO) {
                g_set_error (error,
                             GDM_DISPLAY_ERROR,
                             GDM_DISPLAY_ERROR_GETTING_SESSION_INFO,
                             "%s", g_strerror (-ret));
                return FALSE;
        }

        return ret != FALSE;
}

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: %s",
                                 session_id, 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->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 (manager, username, 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->connection, NULL, 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, *display_seat_id;
        gboolean recorded = FALSE;

        display_name = NULL;
        username = NULL;
        hostname = NULL;
        display_device = NULL;
        display_seat_id = 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,
                      "display-seat-id", &display_seat_id,
                      NULL);

        if (display_name == NULL && display_device == NULL) {
                if (display_seat_id == NULL)
                        goto out;

                display_name = g_strdup ("login screen");
                display_device = g_strdup (display_seat_id);
        }

        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);
        g_free (display_seat_id);

        return recorded;
}

static GdmSession *
find_user_session_for_display (GdmManager *self,
                               GdmDisplay *display)
{

        GList *node = self->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_debug("GdmSession: Denying access to %d, only %d is allowed", 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->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;
                g_autofree char *client_session_id = NULL;

                /* 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)
{
        GdmSession *user_session;
        const char *caller_session_id;

        user_session = g_object_get_data (G_OBJECT (session), "user-session");
        caller_session_id = g_object_get_data (G_OBJECT (session), "caller-session-id");

        if (user_session != NULL) {
                g_debug ("GdmManager: reauthenticated user in frozen session '%s' with service '%s'",
                         gdm_session_get_session_id (user_session), service_name);

                switch_to_compatible_user_session (self, user_session, FALSE);
        } else if (caller_session_id != NULL) {
                g_debug ("GdmManager: reauthenticated user in unmanaged session '%s' with service '%s'",
                         caller_session_id, service_name);

                session_unlock (self, caller_session_id);
        }

        close_transient_session (self, session);
}

static char *
open_temporary_reauthentication_channel (GdmManager            *self,
                                         GdmSession            *user_session,
                                         char                  *seat_id,
                                         char                  *caller_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 (caller_session_id),
                                (GDestroyNotify)
                                g_free);
        g_object_set_data_full (G_OBJECT (session),
                                "user-session",
                                user_session? g_object_ref (user_session) : NULL,
                                (GDestroyNotify)
                                g_object_unref);
        g_object_set_data (G_OBJECT (session),
                           "caller-pid",
                           GUINT_TO_POINTER (pid));
        g_hash_table_insert (self->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
are_sessions_compatible (GdmSession *session_a,
                         GdmSession *session_b)
{
        gboolean session_a_is_local = FALSE;
        gboolean session_b_is_local = FALSE;
        const char *session_a_seat_id, *session_b_seat_id;

        g_object_get (G_OBJECT (session_a), "display-is-local", &session_a_is_local, NULL);
        g_object_get (G_OBJECT (session_b), "display-is-local", &session_b_is_local, NULL);

        session_a_seat_id = gdm_session_get_display_seat_id (session_a);
        session_b_seat_id = gdm_session_get_display_seat_id (session_b);

        if (session_a_is_local != session_b_is_local)
                return FALSE;

        if (!session_a_is_local && !session_b_is_local)
                return TRUE;

        return g_strcmp0 (session_a_seat_id, session_b_seat_id) == 0;
}

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;
        GdmSession       *login_session = NULL;
        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;
        char             *address = NULL;

        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", username);
                session = find_session_for_user (self,
                                                 username,
                                                 NULL);
                login_session = get_user_session_for_display (display);
                g_object_set_data (G_OBJECT (display), "reauth-pid-of-caller", GINT_TO_POINTER (pid));
        } else {
                g_debug ("GdmManager: looking for user session on display");
                session = get_user_session_for_display (display);
        }

        if (session != NULL && login_session != NULL &&
            !are_sessions_compatible (session, login_session)) {
                g_dbus_method_invocation_return_error_literal (invocation,
                                                               G_DBUS_ERROR,
                                                               G_DBUS_ERROR_ACCESS_DENIED,
                                                               "Login session is not compatible with user session");
                return TRUE;
        } else if (session != NULL && gdm_session_is_running (session)) {
                if (!gdm_session_is_frozen (session)) {
                        gdm_session_start_reauthentication (session, pid, uid);
                        g_hash_table_insert (self->open_reauthentication_requests,
                                             GINT_TO_POINTER (pid),
                                             invocation);
                        return TRUE;
                } else {
                        g_debug("GdmManager: user session is frozen; using temporary reauthentication channel");
                }
        } 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;
        }

        address = open_temporary_reauthentication_channel (self,
                                                           is_login_screen? session : NULL,
                                                           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 void
set_up_automatic_login_session (GdmManager *manager,
                                GdmDisplay *display)
{
        GdmSession *session;
        g_auto (GStrv) supported_session_types = NULL;

        /* 0 is root user; since the daemon talks to the session object
         * directly, itself, for automatic login
         */
        create_user_session_for_display (manager, display, 0);
        session = get_user_session_for_display (display);

        g_object_get (G_OBJECT (display),
                      "supported-session-types", &supported_session_types,
                      NULL);

        g_object_set (G_OBJECT (session),
                      "display-is-initial", FALSE,
                      "supported-session-types", supported_session_types,
                      NULL);

        g_debug ("GdmManager: Starting automatic login conversation");
        gdm_session_start_conversation (session, "gdm-autologin");
}

#ifdef HAVE_LIBXDMCP
static void
set_up_chooser_session (GdmManager *manager,
                        GdmDisplay *display)
{
        if (!gdm_display_prepare_greeter_session (display, manager->dyn_user_store, NULL)) {
                gdm_display_unmanage (display);
                gdm_display_finish (display);
                return;
        }

        gdm_display_start_greeter_session (display);
}
#endif

static void
set_up_greeter_session (GdmManager *manager,
                        GdmDisplay *display)
{
        uid_t greeter_uid;

        if (!gdm_display_prepare_greeter_session (display, manager->dyn_user_store, &greeter_uid)) {
                gdm_display_unmanage (display);
                gdm_display_finish (display);
                return;
        }

        create_user_session_for_display (manager, display, greeter_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->did_automatic_login || manager->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;
        g_autofree 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->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)) {
                                g_autofree char *session_class = NULL;

                                g_object_get (display,
                                              "session-class", &session_class,
                                              NULL);
                                if (g_strcmp0 (session_class, "greeter") == 0)
                                        set_up_session (manager, display);
                        }
                        break;
                case GDM_DISPLAY_FAILED:
                case GDM_DISPLAY_UNMANAGED:
                case GDM_DISPLAY_FINISHED:
#ifdef WITH_PLYMOUTH
                        if (quit_plymouth) {
                                plymouth_quit_without_transition ();
                                manager->plymouth_is_running = FALSE;
                        }
#endif

                        g_object_set_data (G_OBJECT (display), "gdm-user-session", NULL);

                        if (display == manager->automatic_login_display) {
                                g_clear_weak_pointer (&manager->automatic_login_display);

                                manager->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->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->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) {
                gboolean is_connected = FALSE;

                g_object_get (G_OBJECT (display), "is-connected", &is_connected, NULL);

                if (is_connected) {
                        char *auth_file = NULL;
                        const char *username;

                        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 = NULL;
        const char *seat_id = gdm_session_get_display_seat_id (session);
        gboolean display_is_local;

        g_object_get (G_OBJECT (session), "display-is-local", &display_is_local, NULL);

        if (!display_is_local) {
                g_autofree char *remote_id = NULL;

                g_object_get (G_OBJECT (session),
                              "remote-id", &remote_id,
                              NULL);

                display = gdm_remote_display_new (remote_id);
        }

        if (display == NULL)
                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->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
export_initial_setup_home_dir (GdmSession *initial_setup_session)
{
        uid_t gis_uid;
        struct passwd *pwe;
        g_autoptr (GFile) gis_home = NULL;
        g_autoptr (GError) error = NULL;
        g_autofree char *user_uid_path = NULL;
        g_autofree char *user_uid_contents = NULL;
        uid_t user_uid;
        g_autoptr (GFile) gis_export = NULL;

        gis_uid = gdm_session_get_allowed_user (initial_setup_session);

        if (!gdm_get_pwent_for_uid (gis_uid, &pwe)) {
                g_warning ("Failed to resolve gnome-initial-setup UID: %d", gis_uid);
                return FALSE;
        }

        gis_home = g_file_new_for_path (pwe->pw_dir);

        user_uid_path = g_build_filename (pwe->pw_dir, "gnome-initial-setup-uid", NULL);
        if (!g_file_get_contents (user_uid_path, &user_uid_contents, NULL, &error)) {
                g_warning ("Unable to read %s: %s", user_uid_path, error->message);
                return FALSE;
        }
        user_uid = (uid_t) atoi (user_uid_contents);

        if (!gdm_get_pwent_for_uid (user_uid, &pwe)) {
                g_warning ("UID '%s' in %s is not valid", user_uid_contents, user_uid_path);
                return FALSE;
        }

        g_debug ("Moving %s to " INITIAL_SETUP_EXPORT_DIR,
                 g_file_peek_path (gis_home));

        gis_export = g_file_new_for_path (INITIAL_SETUP_EXPORT_DIR);
        if (!g_file_move (gis_home, gis_export, G_FILE_COPY_OVERWRITE, NULL,
                          NULL, NULL, &error)) {
                g_warning ("Failed to move %s to " INITIAL_SETUP_EXPORT_DIR ": %s",
                           g_file_peek_path (gis_home), error->message);
                return FALSE;
        }

        g_debug ("Changing ownership of " INITIAL_SETUP_EXPORT_DIR " to %u:%u",
                 pwe->pw_uid, pwe->pw_gid);

        if (!gdm_chown_recursively (gis_export, pwe->pw_uid, pwe->pw_gid, &error)) {
                g_warning ("Failed to change ownership of " INITIAL_SETUP_EXPORT_DIR ": %s",
                           error->message);
                return FALSE;
        }

        return TRUE;
}

static gboolean
on_start_user_session (StartUserSessionOperation *operation)
{
        GdmManager *self = operation->manager;
        gboolean migrated;
        gboolean fail_if_already_switched = TRUE;
        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);

        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 {
                gboolean doing_initial_setup = FALSE;
                uid_t allowed_uid;

                g_object_get (G_OBJECT (display),
                              "doing-initial-setup", &doing_initial_setup,
                              NULL);

                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->display_store, display);
                        g_object_unref (display);

                        self->automatic_login_display = g_object_get_data (G_OBJECT (operation->session), "gdm-display");
                        g_object_add_weak_pointer (G_OBJECT (self->automatic_login_display), (gpointer *) &self->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_session_credentials_established (GdmSession *session,
                                    const char *service_name,
                                    GdmManager *manager)
{
        GdmDisplay *display;
        gboolean    doing_initial_setup = FALSE;

        display = get_display_for_user_session (session);
        if (display == NULL) {
                g_debug ("GdmManager: session has no associated display");
                return;
        }

        /* If this is the initial setup, chown the g-i-s homedir now before the
         * PAM session is opened and the consumers are started.
         */
        g_object_get (G_OBJECT (display),
                      "doing-initial-setup", &doing_initial_setup,
                      NULL);
        if (doing_initial_setup)
                export_initial_setup_home_dir (session);
}

static void
on_user_session_opened (GdmSession       *session,
                        const char       *service_name,
                        const char       *session_id,
                        GdmManager       *manager)
{
        manager->user_sessions = g_list_append (manager->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->plymouth_is_running) {
                        g_timeout_add_seconds (20, (GSourceFunc) plymouth_quit_with_transition, NULL);
                        manager->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);
                if (gdm_display_get_status (display) != GDM_DISPLAY_FAILED)
                        gdm_display_finish (display);
        }

        node = g_list_find (manager->user_sessions, session);

        if (node != NULL) {
                manager->user_sessions = g_list_delete_link (manager->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 gboolean
lookup_by_reauth_pid (const char *id,
                      GdmDisplay *display,
                      gpointer    user_data)
{
        GPid looking_for;
        GPid current;

        looking_for = GPOINTER_TO_INT (user_data);
        current = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (display), "reauth-pid-of-caller"));

        if (looking_for == 0 || current == 0)
                return FALSE;

        return looking_for == current;
}

static void
on_session_reauthenticated (GdmSession *session,
                            const char *service_name,
                            int         pid_of_caller,
                            GdmManager *manager)
{
        gboolean fail_if_already_switched = FALSE;
        GdmDisplay *login_display = gdm_display_store_find (manager->display_store,
                                                            lookup_by_reauth_pid,
                                                            GINT_TO_POINTER (pid_of_caller));

        if (login_display != NULL) {
                if (GDM_IS_REMOTE_DISPLAY (login_display)) {
                        const char *session_id;
                        GdmDisplay *user_display;

                        session_id = gdm_session_get_session_id (session);
                        user_display = gdm_display_store_find (manager->display_store,
                                                               lookup_by_session_id,
                                                               (gpointer) session_id);

                        if (user_display != NULL && GDM_IS_REMOTE_DISPLAY (user_display)) {
                                /* Transferring the remote id from the new login screen display to the
                                 * preexisting user session display, notifies the remoting software to redirect
                                 * the connection. That same software will also ensure after the redirection has
                                 * happened that the login screen session is closed, so we don't have to manage that
                                 * ourselves.
                                 */
                                g_autofree char *remote_id = gdm_remote_display_get_remote_id (GDM_REMOTE_DISPLAY (login_display));
                                gdm_remote_display_set_remote_id (GDM_REMOTE_DISPLAY (user_display), remote_id);
                        } else {
                                g_warning ("GdmManager: Couldn't find remote display associated with reauthenticated user session");
                        }
                }

                if (gdm_session_get_display_mode (session) == GDM_SESSION_DISPLAY_MODE_REUSE_VT) {
                        gdm_display_stop_greeter_session (login_display);
                        gdm_display_unmanage (login_display);
                        gdm_display_finish (login_display);
                }
        }

        /* 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_stop_conflicting_session (GdmSession *login_session,
                             const char *username,
                             GdmManager *manager)
{
        const char *session_id;
        GdmSession *user_session;

        user_session = find_session_for_user (manager,
                                              username,
                                              NULL);
        if (user_session == NULL) {
                g_warning ("Couldn't find session for user");
                return;
        }

        if (are_sessions_compatible (login_session, user_session)) {
                g_warning ("Session requested to stop is compatible, it won't be stopped");
                return;
        }

        session_id = gdm_session_get_session_id (user_session);
        if (!gdm_terminate_session_by_id (manager->connection, NULL, session_id)) {
                g_warning ("Failed to terminate conflicting session");
                return;
        }
}

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->plymouth_is_running) {
                plymouth_quit_with_transition ();
                manager->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->open_reauthentication_requests,
                                          source_tag);

        if (invocation != NULL) {
                g_hash_table_steal (manager->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 void
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;
        g_auto (GStrv) supported_session_types = NULL;

        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,
                      "supported-session-types", &supported_session_types,
                      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);

        if (GDM_IS_REMOTE_DISPLAY (display)) {
                g_autofree char *remote_id = gdm_remote_display_get_remote_id (GDM_REMOTE_DISPLAY (display));

                g_object_set (G_OBJECT (session), "remote-id", remote_id, NULL);
        }

        g_object_set (G_OBJECT (session),
                      "supported-session-types", supported_session_types,
                      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,
                          "stop-conflicting-session",
                          G_CALLBACK (on_stop_conflicting_session),
                          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,
                          "credentials-established",
                          G_CALLBACK (on_session_credentials_established),
                          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",
                                session,
                                (GDestroyNotify)
                                clean_user_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->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->display_store,
                                   (GdmDisplayStoreFunc)listify_display_ids,
                                   displays);

        return TRUE;
}

static void
on_graphics_unsupported (GdmLocalDisplayFactory *factory,
                         GdmManager             *manager)
{
#ifdef WITH_PLYMOUTH
        if (manager->plymouth_is_running) {
                plymouth_quit_without_transition ();
                manager->plymouth_is_running = FALSE;
        }
#endif
}

void
gdm_manager_stop (GdmManager *manager)
{
        g_return_if_fail (GDM_IS_MANAGER (manager));

        g_debug ("GdmManager: GDM stopping");

        if (manager->local_factory != NULL) {
                gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->local_factory));
                g_signal_handlers_disconnect_by_func (manager->local_factory,
                                                      G_CALLBACK (on_graphics_unsupported),
                                                      manager);
        }

#ifdef HAVE_LIBXDMCP
        if (manager->xdmcp_factory != NULL) {
                gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->xdmcp_factory));
        }
#endif

        manager->started = FALSE;
}

void
gdm_manager_start (GdmManager *manager)
{
        g_return_if_fail (GDM_IS_MANAGER (manager));

        g_debug ("GdmManager: GDM starting to manage displays");

#ifdef WITH_PLYMOUTH
        manager->plymouth_is_running = plymouth_is_running ();

        if (manager->plymouth_is_running) {
                plymouth_prepare_for_transition ();
        }
#endif
        if (!manager->xdmcp_enabled || manager->show_local_greeter) {
                gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->local_factory));
                g_signal_connect (manager->local_factory,
                                  "graphics-unsupported",
                                  G_CALLBACK (on_graphics_unsupported),
                                  manager);
        }

        /* Accept remote connections */
        if (manager->remote_login_enabled) {
                gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->remote_factory));

#ifdef WITH_PLYMOUTH
                /* Quit plymouth if remote is the only display */
                if (!manager->show_local_greeter && manager->plymouth_is_running) {
                        plymouth_quit_without_transition ();
                        manager->plymouth_is_running = FALSE;
                }
#endif
        }

#ifdef HAVE_LIBXDMCP
        /* Accept xdmcp connections */
        if (manager->xdmcp_enabled) {
#ifdef WITH_PLYMOUTH
                /* Quit plymouth if xdmcp is the only display */
                if (!manager->show_local_greeter && manager->plymouth_is_running) {
                        plymouth_quit_without_transition ();
                        manager->plymouth_is_running = FALSE;
                }
#endif
                if (manager->xdmcp_factory != NULL) {
                        g_debug ("GdmManager: Accepting XDMCP connections...");
                        gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->xdmcp_factory));
                }
        }
#endif

        manager->started = TRUE;
}

static gboolean
register_manager (GdmManager *manager)
{
        GError *error = NULL;
        GDBusObjectManagerServer *object_server;

        error = NULL;
        manager->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
                                              NULL,
                                              &error);
        if (manager->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->connection);
        manager->object_manager = object_server;

        if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (manager),
                                               manager->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->xdmcp_enabled != enabled) {
                manager->xdmcp_enabled = enabled;
#ifdef HAVE_LIBXDMCP
                if (manager->xdmcp_enabled) {
                        manager->xdmcp_factory = gdm_xdmcp_display_factory_new (manager->display_store);
                        if (manager->started) {
                                gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->xdmcp_factory));
                        }
                } else {
                        if (manager->started) {
                                gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->xdmcp_factory));
                        }

                        g_object_unref (manager->xdmcp_factory);
                        manager->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->show_local_greeter = show_local_greeter;
}

void
gdm_manager_set_remote_login_enabled (GdmManager *manager,
                                      gboolean    enabled)
{
        g_return_if_fail (GDM_IS_MANAGER (manager));

        if (manager->remote_login_enabled != enabled) {
                manager->remote_login_enabled = enabled;
                if (manager->remote_login_enabled) {
                        manager->remote_factory = gdm_remote_display_factory_new (manager->display_store);
                } else {
                        g_clear_object (&manager->remote_factory);
                }
        }
}

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;
        case PROP_REMOTE_LOGIN_ENABLED:
                gdm_manager_set_remote_login_enabled (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->xdmcp_enabled);
                break;
        case PROP_SHOW_LOCAL_GREETER:
                g_value_set_boolean (value, self->show_local_greeter);
                break;
        case PROP_REMOTE_LOGIN_ENABLED:
                g_value_set_boolean (value, self->remote_login_enabled);
                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->local_factory = gdm_local_display_factory_new (manager->display_store);

        if (manager->remote_login_enabled) {
                manager->remote_factory = gdm_remote_display_factory_new (manager->display_store);
        }

#ifdef HAVE_LIBXDMCP
        if (manager->xdmcp_enabled) {
                manager->xdmcp_factory = gdm_xdmcp_display_factory_new (manager->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,
                              0,
                              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,
                              0,
                              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));

        g_object_class_install_property (object_class,
                                         PROP_REMOTE_LOGIN_ENABLED,
                                         g_param_spec_boolean ("remote-login-enabled",
                                                               NULL,
                                                               NULL,
                                                               FALSE,
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
}

static void
gdm_manager_init (GdmManager *manager)
{
        manager->dyn_user_store = gdm_dynamic_user_store_new ();
        manager->display_store = gdm_display_store_new ();
        manager->user_sessions = NULL;
        manager->open_reauthentication_requests = g_hash_table_new_full (NULL,
                                                                         NULL,
                                                                         (GDestroyNotify)
                                                                         NULL,
                                                                         (GDestroyNotify)
                                                                         g_object_unref);
        manager->transient_sessions = g_hash_table_new_full (NULL,
                                                             NULL,
                                                             (GDestroyNotify)
                                                             NULL,
                                                             (GDestroyNotify)
                                                             g_object_unref);
        g_signal_connect (G_OBJECT (manager->display_store),
                          "display-added",
                          G_CALLBACK (on_display_added),
                          manager);

        g_signal_connect (G_OBJECT (manager->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->connection))
                g_dbus_object_manager_server_unexport (manager->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 != NULL);

        gdm_manager_stop (manager);

        g_clear_weak_pointer (&manager->automatic_login_display);

#ifdef HAVE_LIBXDMCP
        g_clear_object (&manager->xdmcp_factory);
#endif
        g_clear_object (&manager->local_factory);
        g_clear_object (&manager->remote_factory);
        g_clear_pointer (&manager->open_reauthentication_requests,
                         g_hash_table_unref);
        g_clear_pointer (&manager->transient_sessions,
                         g_hash_table_unref);

        g_list_foreach (manager->user_sessions,
                        (GFunc) gdm_session_close,
                        NULL);
        g_list_free_full (manager->user_sessions, (GDestroyNotify) g_object_unref);
        manager->user_sessions = NULL;

        g_signal_handlers_disconnect_by_func (G_OBJECT (manager->display_store),
                                              G_CALLBACK (on_display_added),
                                              manager);
        g_signal_handlers_disconnect_by_func (G_OBJECT (manager->display_store),
                                              G_CALLBACK (on_display_removed),
                                              manager);

        if (!g_dbus_connection_is_closed (manager->connection)) {
                gdm_display_store_foreach (manager->display_store,
                                           (GdmDisplayStoreFunc)unexport_display,
                                           manager);
                g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (manager));
        }

        gdm_display_store_foreach (manager->display_store,
                                   (GdmDisplayStoreFunc) finish_display,
                                   manager);

        gdm_display_store_clear (manager->display_store);

        g_dbus_object_manager_server_set_connection (manager->object_manager, NULL);

        g_clear_object (&manager->connection);
        g_clear_object (&manager->object_manager);
        g_clear_object (&manager->display_store);
        g_clear_object (&manager->dyn_user_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);
}
