/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2006 Ray Strode <rstrode@redhat.com>
 * 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, 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 <dirent.h>
#include <errno.h>
#include <fcntl.h>

#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>

#include <locale.h>

#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <glib-object.h>
#include <gio/gio.h>

#include "gdm-session.h"
#include "gdm-session-glue.h"
#include "gdm-dbus-util.h"

#include "gdm-session.h"
#include "gdm-session-enum-types.h"
#include "gdm-session-worker-common.h"
#include "gdm-session-worker-job.h"
#include "gdm-session-worker-glue.h"
#include "gdm-common.h"

#include "gdm-settings-direct.h"
#include "gdm-settings-keys.h"

#define GDM_SESSION_DBUS_ERROR_CANCEL "org.gnome.DisplayManager.Session.Error.Cancel"
#define GDM_SESSION_DBUS_OBJECT_PATH "/org/gnome/DisplayManager/Session"

#define GDM_WORKER_DBUS_PATH "/org/gnome/DisplayManager/Worker"

typedef struct
{
        GdmSession            *session;
        GdmSessionWorkerJob   *job;
        GPid                   worker_pid;
        char                  *service_name;
        GDBusMethodInvocation *starting_invocation;
        char                  *starting_username;
        GDBusMethodInvocation *pending_invocation;
        GdmDBusWorkerManager  *worker_manager_interface;
        GdmDBusWorker         *worker_proxy;
        GCancellable          *worker_cancellable;
        char                  *session_id;
        guint32                is_stopping : 1;

        GPid                   reauth_pid_of_caller;
} GdmSessionConversation;

struct _GdmSessionPrivate
{
        /* per open scope */
        char                *selected_program;
        char                *selected_session;
        char                *saved_session;
        char                *saved_language;
        char                *selected_user;
        char                *user_x11_authority_file;

        char                *timed_login_username;
        int                  timed_login_delay;
        GList               *pending_timed_login_invocations;

        GHashTable          *conversations;

        GdmSessionConversation *session_conversation;

        char                 **conversation_environment;

        GdmDBusUserVerifier   *user_verifier_interface;
        GHashTable            *user_verifier_extensions;
        GdmDBusGreeter        *greeter_interface;
        GdmDBusRemoteGreeter  *remote_greeter_interface;
        GdmDBusChooser        *chooser_interface;

        GList               *pending_worker_connections;
        GList               *outside_connections;

        GPid                 session_pid;

        /* object lifetime scope */
        char                *session_type;
        char                *display_name;
        char                *display_hostname;
        char                *display_device;
        char                *display_seat_id;
        char                *display_x11_authority_file;
        gboolean             display_is_local;

        GdmSessionVerificationMode verification_mode;

        uid_t                allowed_user;

        char                *fallback_session_name;

        GDBusServer         *worker_server;
        GDBusServer         *outside_server;
        GHashTable          *environment;

        guint32              is_program_session : 1;
        guint32              display_is_initial : 1;
#ifdef ENABLE_WAYLAND_SUPPORT
        guint32              ignore_wayland : 1;
#endif
};

enum {
        PROP_0,
        PROP_VERIFICATION_MODE,
        PROP_ALLOWED_USER,
        PROP_DISPLAY_NAME,
        PROP_DISPLAY_HOSTNAME,
        PROP_DISPLAY_IS_LOCAL,
        PROP_DISPLAY_IS_INITIAL,
        PROP_SESSION_TYPE,
        PROP_DISPLAY_DEVICE,
        PROP_DISPLAY_SEAT_ID,
        PROP_DISPLAY_X11_AUTHORITY_FILE,
        PROP_USER_X11_AUTHORITY_FILE,
        PROP_CONVERSATION_ENVIRONMENT,
#ifdef ENABLE_WAYLAND_SUPPORT
        PROP_IGNORE_WAYLAND,
#endif
};

enum {
        CONVERSATION_STARTED = 0,
        CONVERSATION_STOPPED,
        SETUP_COMPLETE,
        CANCELLED,
        HOSTNAME_SELECTED,
        CLIENT_REJECTED,
        CLIENT_CONNECTED,
        CLIENT_DISCONNECTED,
        CLIENT_READY_FOR_SESSION_TO_START,
        DISCONNECTED,
        AUTHENTICATION_FAILED,
        VERIFICATION_COMPLETE,
        SESSION_OPENED,
        SESSION_STARTED,
        SESSION_START_FAILED,
        SESSION_EXITED,
        SESSION_DIED,
        REAUTHENTICATION_STARTED,
        REAUTHENTICATED,
        LAST_SIGNAL
};

#ifdef ENABLE_WAYLAND_SUPPORT
static gboolean gdm_session_is_wayland_session (GdmSession *self);
#endif
static void update_session_type (GdmSession *self);
static void set_session_type (GdmSession *self,
                              const char *session_type);
static guint signals [LAST_SIGNAL] = { 0, };

G_DEFINE_TYPE (GdmSession,
               gdm_session,
               G_TYPE_OBJECT);

static GdmSessionConversation *
find_conversation_by_name (GdmSession *self,
                           const char *service_name)
{
        GdmSessionConversation *conversation;

        conversation = g_hash_table_lookup (self->priv->conversations, service_name);

        if (conversation == NULL) {
                g_warning ("Tried to look up non-existent conversation %s", service_name);
        }

        return conversation;
}

static void
report_and_stop_conversation (GdmSession *self,
                              const char *service_name,
                              GError     *error)
{
        g_dbus_error_strip_remote_error (error);

        if (self->priv->user_verifier_interface != NULL) {
                if (g_error_matches (error,
                                     GDM_SESSION_WORKER_ERROR,
                                     GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE)) {
                        gdm_dbus_user_verifier_emit_service_unavailable (self->priv->user_verifier_interface,
                                                                         service_name,
                                                                         error->message);
                } else {
                        gdm_dbus_user_verifier_emit_problem (self->priv->user_verifier_interface,
                                                             service_name,
                                                             error->message);
                }
                gdm_dbus_user_verifier_emit_verification_failed (self->priv->user_verifier_interface,
                                                                 service_name);
        }

        gdm_session_stop_conversation (self, service_name);
}

static void
on_authenticate_cb (GdmDBusWorker *proxy,
                    GAsyncResult  *res,
                    gpointer       user_data)
{
        GdmSessionConversation *conversation = user_data;
        GdmSession *self;
        char *service_name;

        GError *error = NULL;
        gboolean worked;

        worked = gdm_dbus_worker_call_authenticate_finish (proxy, res, &error);

        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) ||
            g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                return;

        self = conversation->session;
        service_name = conversation->service_name;

        if (worked) {
                gdm_session_authorize (self, service_name);
        } else {
                g_signal_emit (self,
                               signals[AUTHENTICATION_FAILED],
                               0,
                               service_name,
                               conversation->worker_pid);
                report_and_stop_conversation (self, service_name, error);
        }
}

static void
on_authorize_cb (GdmDBusWorker *proxy,
                 GAsyncResult  *res,
                 gpointer       user_data)
{
        GdmSessionConversation *conversation = user_data;
        GdmSession *self;
        char *service_name;

        GError *error = NULL;
        gboolean worked;

        worked = gdm_dbus_worker_call_authorize_finish (proxy, res, &error);

        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) ||
            g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                return;

        self = conversation->session;
        service_name = conversation->service_name;

        if (worked) {
                gdm_session_accredit (self, service_name);
        } else {
                report_and_stop_conversation (self, service_name, error);
        }
}

static void
on_establish_credentials_cb (GdmDBusWorker *proxy,
                             GAsyncResult  *res,
                             gpointer       user_data)
{
        GdmSessionConversation *conversation = user_data;
        GdmSession *self;
        char *service_name;

        GError *error = NULL;
        gboolean worked;

        worked = gdm_dbus_worker_call_establish_credentials_finish (proxy, res, &error);

        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) ||
            g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                return;

        self = g_object_ref (conversation->session);
        service_name = g_strdup (conversation->service_name);

        if (worked) {
                if (self->priv->user_verifier_interface != NULL) {
                        gdm_dbus_user_verifier_emit_verification_complete (self->priv->user_verifier_interface,
                                                                           service_name);
                        g_signal_emit (self, signals[VERIFICATION_COMPLETE], 0, service_name);
                }

                switch (self->priv->verification_mode) {
                case GDM_SESSION_VERIFICATION_MODE_LOGIN:
                case GDM_SESSION_VERIFICATION_MODE_CHOOSER:
                        gdm_session_open_session (self, service_name);
                        break;
                case GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE:
                default:
                        break;
                }
        } else {
                report_and_stop_conversation (self, service_name, error);
        }

        g_free (service_name);
        g_object_unref (self);
}

static char **
get_system_session_dirs (GdmSession *self)
{
        GArray *search_array = NULL;
        char **search_dirs;

        static const char *x_search_dirs[] = {
                "/etc/X11/sessions/",
                DMCONFDIR "/Sessions/",
                DATADIR "/gdm/BuiltInSessions/",
                DATADIR "/xsessions/",
        };

        static const char *wayland_search_dir = DATADIR "/wayland-sessions/";

        search_array = g_array_new (TRUE, TRUE, sizeof (char *));

        g_array_append_vals (search_array, x_search_dirs, G_N_ELEMENTS (x_search_dirs));

#ifdef ENABLE_WAYLAND_SUPPORT
        if (!self->priv->ignore_wayland) {
#ifdef ENABLE_USER_DISPLAY_SERVER
                g_array_prepend_val (search_array, wayland_search_dir);
#else
                g_array_append_val (search_array, wayland_search_dir);
#endif
        }
#endif

        search_dirs = g_strdupv ((char **) search_array->data);

        g_array_free (search_array, TRUE);

        return search_dirs;
}

static gboolean
is_prog_in_path (const char *prog)
{
        char    *f;
        gboolean ret;

        f = g_find_program_in_path (prog);
        ret = (f != NULL);
        g_free (f);
        return ret;
}

static GKeyFile *
load_key_file_for_file (GdmSession   *self,
                        const char   *file,
                        char        **full_path)
{
        GKeyFile   *key_file;
        GError     *error;
        gboolean    res;
        char      **search_dirs;

        key_file = g_key_file_new ();

        search_dirs = get_system_session_dirs (self),
        error = NULL;
        res = g_key_file_load_from_dirs (key_file,
                                         file,
                                         (const char **) search_dirs,
                                         full_path,
                                         G_KEY_FILE_NONE,
                                         &error);
        if (! res) {
                g_debug ("GdmSession: File '%s' not found: %s", file, error->message);
                g_error_free (error);
                g_key_file_free (key_file);
                key_file = NULL;
        }

        g_strfreev (search_dirs);

        return key_file;
}

static gboolean
get_session_command_for_file (GdmSession  *self,
                              const char  *file,
                              char       **command)
{
        GKeyFile   *key_file;
        GError     *error;
        char       *exec;
        gboolean    ret;
        gboolean    res;

        exec = NULL;
        ret = FALSE;
        if (command != NULL) {
                *command = NULL;
        }

        g_debug ("GdmSession: getting session command for file '%s'", file);
        key_file = load_key_file_for_file (self, file, NULL);
        if (key_file == NULL) {
                goto out;
        }

        error = NULL;
        res = g_key_file_get_boolean (key_file,
                                      G_KEY_FILE_DESKTOP_GROUP,
                                      G_KEY_FILE_DESKTOP_KEY_HIDDEN,
                                      &error);
        if (error == NULL && res) {
                g_debug ("GdmSession: Session %s is marked as hidden", file);
                goto out;
        }

        exec = g_key_file_get_string (key_file,
                                      G_KEY_FILE_DESKTOP_GROUP,
                                      G_KEY_FILE_DESKTOP_KEY_TRY_EXEC,
                                      NULL);
        if (exec != NULL) {
                res = is_prog_in_path (exec);
                g_free (exec);
                exec = NULL;

                if (! res) {
                        g_debug ("GdmSession: Command not found: %s",
                                 G_KEY_FILE_DESKTOP_KEY_TRY_EXEC);
                        goto out;
                }
        }

        error = NULL;
        exec = g_key_file_get_string (key_file,
                                      G_KEY_FILE_DESKTOP_GROUP,
                                      G_KEY_FILE_DESKTOP_KEY_EXEC,
                                      &error);
        if (error != NULL) {
                g_debug ("GdmSession: %s key not found: %s",
                         G_KEY_FILE_DESKTOP_KEY_EXEC,
                         error->message);
                g_error_free (error);
                goto out;
        }

        if (command != NULL) {
                *command = g_strdup (exec);
        }
        ret = TRUE;

out:
        g_free (exec);

        return ret;
}

static gboolean
get_session_command_for_name (GdmSession  *self,
                              const char  *name,
                              char       **command)
{
        gboolean res;
        char    *filename;

        filename = g_strdup_printf ("%s.desktop", name);
        res = get_session_command_for_file (self, filename, command);
        g_free (filename);

        return res;
}

static const char *
get_default_language_name (GdmSession *self)
{
    const char *default_language;

    if (self->priv->saved_language != NULL) {
            return self->priv->saved_language;
    }

    default_language = g_hash_table_lookup (self->priv->environment,
                                            "LANG");

    if (default_language != NULL) {
            return default_language;
    }

    return setlocale (LC_MESSAGES, NULL);
}

static const char *
get_fallback_session_name (GdmSession *self)
{
        char          **search_dirs;
        int             i;
        char           *name;
        GSequence      *sessions;
        GSequenceIter  *session;

        if (self->priv->fallback_session_name != NULL) {
                /* verify that the cached version still exists */
                if (get_session_command_for_name (self, self->priv->fallback_session_name, NULL)) {
                        goto out;
                }
        }

        name = g_strdup ("gnome");
        if (get_session_command_for_name (self, name, NULL)) {
                g_free (self->priv->fallback_session_name);
                self->priv->fallback_session_name = name;
                goto out;
        }
        g_free (name);

        sessions = g_sequence_new (g_free);

        search_dirs = get_system_session_dirs (self);
        for (i = 0; search_dirs[i] != NULL; i++) {
                GDir       *dir;
                const char *base_name;

                dir = g_dir_open (search_dirs[i], 0, NULL);

                if (dir == NULL) {
                        continue;
                }

                do {
                        base_name = g_dir_read_name (dir);

                        if (base_name == NULL) {
                                break;
                        }

                        if (!g_str_has_suffix (base_name, ".desktop")) {
                                continue;
                        }

                        if (get_session_command_for_file (self, base_name, NULL)) {
                                name = g_strndup (base_name, strlen (base_name) - strlen (".desktop"));
                                g_sequence_insert_sorted (sessions, name, (GCompareDataFunc) g_strcmp0, NULL);
                        }
                } while (base_name != NULL);

                g_dir_close (dir);
        }
        g_strfreev (search_dirs);

        name = NULL;
        session = g_sequence_get_begin_iter (sessions);

        if (g_sequence_iter_is_end (session))
                g_error ("GdmSession: no session desktop files installed, aborting...");

        do {
               name = g_sequence_get (session);
               if (name) {
                       break;
               }
               session = g_sequence_iter_next (session);
        } while (!g_sequence_iter_is_end (session));

        g_free (self->priv->fallback_session_name);
        self->priv->fallback_session_name = g_strdup (name);

        g_sequence_free (sessions);

 out:
        return self->priv->fallback_session_name;
}

static const char *
get_default_session_name (GdmSession *self)
{
        if (self->priv->saved_session != NULL) {
                return self->priv->saved_session;
        }

        return get_fallback_session_name (self);
}

static void
gdm_session_defaults_changed (GdmSession *self)
{

        update_session_type (self);

        if (self->priv->greeter_interface != NULL) {
                gdm_dbus_greeter_emit_default_language_name_changed (self->priv->greeter_interface,
                                                                     get_default_language_name (self));
                gdm_dbus_greeter_emit_default_session_name_changed (self->priv->greeter_interface,
                                                                    get_default_session_name (self));
        }
}

void
gdm_session_select_user (GdmSession *self,
                         const char *text)
{

        g_debug ("GdmSession: Setting user: '%s'", text);

        g_free (self->priv->selected_user);
        self->priv->selected_user = g_strdup (text);

        g_free (self->priv->saved_session);
        self->priv->saved_session = NULL;

        g_free (self->priv->saved_language);
        self->priv->saved_language = NULL;
}

static void
cancel_pending_query (GdmSessionConversation *conversation)
{
        if (conversation->pending_invocation == NULL) {
                return;
        }

        g_debug ("GdmSession: Cancelling pending query");

        g_dbus_method_invocation_return_dbus_error (conversation->pending_invocation,
                                                    GDM_SESSION_DBUS_ERROR_CANCEL,
                                                    "Operation cancelled");
        conversation->pending_invocation = NULL;
}

static void
answer_pending_query (GdmSessionConversation *conversation,
                      const char             *answer)
{
        g_dbus_method_invocation_return_value (conversation->pending_invocation,
                                               g_variant_new ("(s)", answer));
        conversation->pending_invocation = NULL;
}

static void
set_pending_query (GdmSessionConversation *conversation,
                   GDBusMethodInvocation  *message)
{
        g_assert (conversation->pending_invocation == NULL);

        conversation->pending_invocation = g_object_ref (message);
}

static gboolean
gdm_session_handle_choice_list_query (GdmDBusWorkerManager  *worker_manager_interface,
                                      GDBusMethodInvocation *invocation,
                                      const char            *service_name,
                                      const char            *prompt_message,
                                      GVariant              *query,
                                      GdmSession            *self)
{
        GdmSessionConversation *conversation;
        GdmDBusUserVerifierChoiceList *choice_list_interface = NULL;

        g_debug ("GdmSession: choice query for service '%s'", service_name);

        if (self->priv->user_verifier_extensions != NULL)
                choice_list_interface = g_hash_table_lookup (self->priv->user_verifier_extensions,
                                                             gdm_dbus_user_verifier_choice_list_interface_info ()->name);

        if (choice_list_interface == NULL) {
                g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                       G_DBUS_ERROR_NOT_SUPPORTED,
                                                       "ChoiceList interface not supported by client");
                return TRUE;
        }

        conversation = find_conversation_by_name (self, service_name);
        if (conversation != NULL) {
                set_pending_query (conversation, invocation);

                g_debug ("GdmSession: emitting choice query '%s'", prompt_message);
                gdm_dbus_user_verifier_choice_list_emit_choice_query (choice_list_interface,
                                                                      service_name,
                                                                      prompt_message,
                                                                      query);
        }

        return TRUE;
}

static gboolean
gdm_session_handle_info_query (GdmDBusWorkerManager  *worker_manager_interface,
                               GDBusMethodInvocation *invocation,
                               const char            *service_name,
                               const char            *query,
                               GdmSession            *self)
{
        GdmSessionConversation *conversation;

        g_return_val_if_fail (self->priv->user_verifier_interface != NULL, FALSE);

        conversation = find_conversation_by_name (self, service_name);
        if (conversation != NULL) {
                set_pending_query (conversation, invocation);

                gdm_dbus_user_verifier_emit_info_query (self->priv->user_verifier_interface,
                                                        service_name,
                                                        query);
        }

        return TRUE;
}

static gboolean
gdm_session_handle_secret_info_query (GdmDBusWorkerManager  *worker_manager_interface,
                                      GDBusMethodInvocation *invocation,
                                      const char            *service_name,
                                      const char            *query,
                                      GdmSession            *self)
{
        GdmSessionConversation *conversation;

        g_return_val_if_fail (self->priv->user_verifier_interface != NULL, FALSE);

        conversation = find_conversation_by_name (self, service_name);
        if (conversation != NULL) {
                set_pending_query (conversation, invocation);

                gdm_dbus_user_verifier_emit_secret_info_query (self->priv->user_verifier_interface,
                                                               service_name,
                                                               query);
        }

        return TRUE;
}

static gboolean
gdm_session_handle_info (GdmDBusWorkerManager  *worker_manager_interface,
                         GDBusMethodInvocation *invocation,
                         const char            *service_name,
                         const char            *info,
                         GdmSession            *self)
{
        gdm_dbus_worker_manager_complete_info (worker_manager_interface,
                                               invocation);

        if (self->priv->user_verifier_interface != NULL) {
                gdm_dbus_user_verifier_emit_info (self->priv->user_verifier_interface,
                                                  service_name,
                                                  info);
        }

        return TRUE;
}

static void
worker_on_cancel_pending_query (GdmDBusWorker          *worker,
                                GdmSessionConversation *conversation)
{
        cancel_pending_query (conversation);
}

static gboolean
gdm_session_handle_problem (GdmDBusWorkerManager  *worker_manager_interface,
                            GDBusMethodInvocation *invocation,
                            const char            *service_name,
                            const char            *problem,
                            GdmSession            *self)
{
        gdm_dbus_worker_manager_complete_problem (worker_manager_interface,
                                                  invocation);

        if (self->priv->user_verifier_interface != NULL) {
                gdm_dbus_user_verifier_emit_problem (self->priv->user_verifier_interface,
                                                     service_name,
                                                     problem);
        }
        return TRUE;
}

static void
on_opened (GdmDBusWorker *worker,
           GAsyncResult  *res,
           gpointer       user_data)
{
        GdmSessionConversation *conversation = user_data;
        GdmSession *self;
        char *service_name;

        GError *error = NULL;
        gboolean worked;
        char *session_id;

        worked = gdm_dbus_worker_call_open_finish (worker,
                                                   &session_id,
                                                   res,
                                                   &error);

        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) ||
            g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                return;

        self = conversation->session;
        service_name = conversation->service_name;

        if (worked) {
                g_clear_pointer (&conversation->session_id,
                                 (GDestroyNotify) g_free);

                conversation->session_id = g_strdup (session_id);

                if (self->priv->greeter_interface != NULL) {
                        gdm_dbus_greeter_emit_session_opened (self->priv->greeter_interface,
                                                              service_name);
                }

                if (self->priv->user_verifier_interface != NULL) {
                        gdm_dbus_user_verifier_emit_verification_complete (self->priv->user_verifier_interface,
                                                                           service_name);
                        g_signal_emit (self, signals[VERIFICATION_COMPLETE], 0, service_name);
                }

                g_debug ("GdmSession: Emitting 'session-opened' signal");
                g_signal_emit (self, signals[SESSION_OPENED], 0, service_name, session_id);
        } else {
                report_and_stop_conversation (self, service_name, error);

                g_debug ("GdmSession: Emitting 'session-start-failed' signal");
                g_signal_emit (self, signals[SESSION_START_FAILED], 0, service_name, error->message);
        }
}

static void
worker_on_username_changed (GdmDBusWorker          *worker,
                            const char             *username,
                            GdmSessionConversation *conversation)
{
        GdmSession *self = conversation->session;

        g_debug ("GdmSession: changing username from '%s' to '%s'",
                 self->priv->selected_user != NULL ? self->priv->selected_user : "<unset>",
                 (strlen (username)) ? username : "<unset>");

        gdm_session_select_user (self, (strlen (username) > 0) ? g_strdup (username) : NULL);
        gdm_session_defaults_changed (self);
}

static void
worker_on_session_exited (GdmDBusWorker          *worker,
                          const char             *service_name,
                          int                     status,
                          GdmSessionConversation *conversation)
{
        GdmSession *self = conversation->session;

        self->priv->session_conversation = NULL;

        if (WIFEXITED (status)) {
                g_debug ("GdmSession: Emitting 'session-exited' signal with exit code '%d'",
                  WEXITSTATUS (status));
                g_signal_emit (self, signals[SESSION_EXITED], 0, WEXITSTATUS (status));
        } else if (WIFSIGNALED (status)) {
                g_debug ("GdmSession: Emitting 'session-died' signal with signal number '%d'",
                  WTERMSIG (status));
                g_signal_emit (self, signals[SESSION_DIED], 0, WTERMSIG (status));
        }
}

static void
on_reauthentication_started_cb (GdmDBusWorker *worker,
                                GAsyncResult  *res,
                                gpointer       user_data)
{
        GdmSessionConversation *conversation = user_data;
        GdmSession *self;

        GError *error = NULL;
        gboolean worked;
        char *address;

        worked = gdm_dbus_worker_call_start_reauthentication_finish (worker,
                                                                     &address,
                                                                     res,
                                                                     &error);

        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) ||
            g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                return;

        self = conversation->session;

        if (worked) {
                GPid pid_of_caller = conversation->reauth_pid_of_caller;
                g_debug ("GdmSession: Emitting 'reauthentication-started' signal for caller pid '%d'", pid_of_caller);
                g_signal_emit (self, signals[REAUTHENTICATION_STARTED], 0, pid_of_caller, address);
        }

        conversation->reauth_pid_of_caller = 0;
}

static void
worker_on_reauthenticated (GdmDBusWorker          *worker,
                           const char             *service_name,
                           GdmSessionConversation *conversation)
{
        GdmSession *self = conversation->session;
        g_debug ("GdmSession: Emitting 'reauthenticated' signal ");
        g_signal_emit (self, signals[REAUTHENTICATED], 0, service_name);
}

static void
worker_on_saved_language_name_read (GdmDBusWorker          *worker,
                                    const char             *language_name,
                                    GdmSessionConversation *conversation)
{
        GdmSession *self = conversation->session;

        if (strlen (language_name) > 0) {
                g_free (self->priv->saved_language);
                self->priv->saved_language = g_strdup (language_name);

                if (self->priv->greeter_interface != NULL) {
                        gdm_dbus_greeter_emit_default_language_name_changed (self->priv->greeter_interface,
                                                                             language_name);
                }
        }
}

static void
worker_on_saved_session_name_read (GdmDBusWorker          *worker,
                                   const char             *session_name,
                                   GdmSessionConversation *conversation)
{
        GdmSession *self = conversation->session;

        if (! get_session_command_for_name (self, session_name, NULL)) {
                /* ignore sessions that don't exist */
                g_debug ("GdmSession: not using invalid .dmrc session: %s", session_name);
                g_free (self->priv->saved_session);
                self->priv->saved_session = NULL;
        } else if (strcmp (session_name,
                   get_default_session_name (self)) != 0) {
                g_free (self->priv->saved_session);
                self->priv->saved_session = g_strdup (session_name);

                if (self->priv->greeter_interface != NULL) {
                        gdm_dbus_greeter_emit_default_session_name_changed (self->priv->greeter_interface,
                                                                            session_name);
                }
        }

        update_session_type (self);

}

static GdmSessionConversation *
find_conversation_by_pid (GdmSession *self,
                          GPid        pid)
{
        GHashTableIter iter;
        gpointer key, value;

        g_hash_table_iter_init (&iter, self->priv->conversations);
        while (g_hash_table_iter_next (&iter, &key, &value)) {
                GdmSessionConversation *conversation;

                conversation = (GdmSessionConversation *) value;

                if (conversation->worker_pid == pid) {
                        return conversation;
                }
        }

        return NULL;
}

static gboolean
allow_worker_function (GDBusAuthObserver *observer,
                       GIOStream         *stream,
                       GCredentials      *credentials,
                       GdmSession        *self)
{
        uid_t connecting_user;

        connecting_user = g_credentials_get_unix_user (credentials, NULL);

        if (connecting_user == 0) {
                return TRUE;
        }

        if (connecting_user == self->priv->allowed_user) {
                return TRUE;
        }

        g_debug ("GdmSession: User not allowed");

        return FALSE;
}

static void
on_worker_connection_closed (GDBusConnection *connection,
                             gboolean         remote_peer_vanished,
                             GError          *error,
                             GdmSession      *self)
{
        self->priv->pending_worker_connections =
            g_list_remove (self->priv->pending_worker_connections,
                           connection);
        g_object_unref (connection);
}

static gboolean
register_worker (GdmDBusWorkerManager  *worker_manager_interface,
                 GDBusMethodInvocation *invocation,
                 GdmSession            *self)
{
        GdmSessionConversation *conversation;
        GDBusConnection *connection;
        GList *connection_node;
        GCredentials *credentials;
        GPid pid;

        g_debug ("GdmSession: Authenticating new connection");

        connection = g_dbus_method_invocation_get_connection (invocation);
        connection_node = g_list_find (self->priv->pending_worker_connections, connection);

        if (connection_node == NULL) {
                g_debug ("GdmSession: Ignoring connection that we aren't tracking");
                return FALSE;
        }

        /* connection was ref'd when it was added to list, we're taking that
         * reference over and removing it from the list
         */
        self->priv->pending_worker_connections =
                g_list_delete_link (self->priv->pending_worker_connections,
                                    connection_node);

        g_signal_handlers_disconnect_by_func (connection,
                                              G_CALLBACK (on_worker_connection_closed),
                                              self);

        credentials = g_dbus_connection_get_peer_credentials (connection);
        pid = g_credentials_get_unix_pid (credentials, NULL);

        conversation = find_conversation_by_pid (self, (GPid) pid);

        if (conversation == NULL) {
                g_warning ("GdmSession: New worker connection is from unknown source");

                g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
                                                       G_DBUS_ERROR_ACCESS_DENIED,
                                                       "Connection is not from a known conversation");
                g_dbus_connection_close_sync (connection, NULL, NULL);
                return TRUE;
        }

        g_dbus_method_invocation_return_value (invocation, NULL);

        conversation->worker_proxy = gdm_dbus_worker_proxy_new_sync (connection,
                                                                     G_DBUS_PROXY_FLAGS_NONE,
                                                                     NULL,
                                                                     GDM_WORKER_DBUS_PATH,
                                                                     NULL, NULL);
        /* drop the reference we stole from the pending connections list
         * since the proxy owns the connection now */
        g_object_unref (connection);

        g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (conversation->worker_proxy), G_MAXINT);

        conversation->worker_cancellable = g_cancellable_new ();

        g_signal_connect (conversation->worker_proxy,
                          "username-changed",
                          G_CALLBACK (worker_on_username_changed), conversation);
        g_signal_connect (conversation->worker_proxy,
                          "session-exited",
                          G_CALLBACK (worker_on_session_exited), conversation);
        g_signal_connect (conversation->worker_proxy,
                          "reauthenticated",
                          G_CALLBACK (worker_on_reauthenticated), conversation);
        g_signal_connect (conversation->worker_proxy,
                          "saved-language-name-read",
                          G_CALLBACK (worker_on_saved_language_name_read), conversation);
        g_signal_connect (conversation->worker_proxy,
                          "saved-session-name-read",
                          G_CALLBACK (worker_on_saved_session_name_read), conversation);
        g_signal_connect (conversation->worker_proxy,
                          "cancel-pending-query",
                          G_CALLBACK (worker_on_cancel_pending_query), conversation);

        conversation->worker_manager_interface = g_object_ref (worker_manager_interface);
        g_debug ("GdmSession: worker connection is %p", connection);

        g_debug ("GdmSession: Emitting conversation-started signal");
        g_signal_emit (self, signals[CONVERSATION_STARTED], 0, conversation->service_name);

        if (self->priv->user_verifier_interface != NULL) {
                gdm_dbus_user_verifier_emit_conversation_started (self->priv->user_verifier_interface,
                                                                  conversation->service_name);
        }

        if (conversation->starting_invocation != NULL) {
                if (conversation->starting_username != NULL) {
                        gdm_session_setup_for_user (self, conversation->service_name, conversation->starting_username);

                        g_clear_pointer (&conversation->starting_username,
                                         (GDestroyNotify)
                                         g_free);
                } else {
                        gdm_session_setup (self, conversation->service_name);
                }
        }

        g_debug ("GdmSession: Conversation started");

        return TRUE;
}

static void
export_worker_manager_interface (GdmSession      *self,
                                 GDBusConnection *connection)
{
        GdmDBusWorkerManager *worker_manager_interface;

        worker_manager_interface = GDM_DBUS_WORKER_MANAGER (gdm_dbus_worker_manager_skeleton_new ());
        g_signal_connect (worker_manager_interface,
                          "handle-hello",
                          G_CALLBACK (register_worker),
                          self);
        g_signal_connect (worker_manager_interface,
                          "handle-info-query",
                          G_CALLBACK (gdm_session_handle_info_query),
                          self);
        g_signal_connect (worker_manager_interface,
                          "handle-secret-info-query",
                          G_CALLBACK (gdm_session_handle_secret_info_query),
                          self);
        g_signal_connect (worker_manager_interface,
                          "handle-info",
                          G_CALLBACK (gdm_session_handle_info),
                          self);
        g_signal_connect (worker_manager_interface,
                          "handle-problem",
                          G_CALLBACK (gdm_session_handle_problem),
                          self);
        g_signal_connect (worker_manager_interface,
                          "handle-choice-list-query",
                          G_CALLBACK (gdm_session_handle_choice_list_query),
                          self);

        g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (worker_manager_interface),
                                          connection,
                                          GDM_SESSION_DBUS_OBJECT_PATH,
                                          NULL);
}

static void
unexport_worker_manager_interface (GdmSession           *self,
                                   GdmDBusWorkerManager *worker_manager_interface)
{

        g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (worker_manager_interface));

        g_signal_handlers_disconnect_by_func (worker_manager_interface,
                                              G_CALLBACK (register_worker),
                                              self);
        g_signal_handlers_disconnect_by_func (worker_manager_interface,
                                              G_CALLBACK (gdm_session_handle_info_query),
                                              self);
        g_signal_handlers_disconnect_by_func (worker_manager_interface,
                                              G_CALLBACK (gdm_session_handle_secret_info_query),
                                              self);
        g_signal_handlers_disconnect_by_func (worker_manager_interface,
                                              G_CALLBACK (gdm_session_handle_info),
                                              self);
        g_signal_handlers_disconnect_by_func (worker_manager_interface,
                                              G_CALLBACK (gdm_session_handle_problem),
                                              self);
        g_signal_handlers_disconnect_by_func (worker_manager_interface,
                                              G_CALLBACK (gdm_session_handle_choice_list_query),
                                              self);
}

static gboolean
handle_connection_from_worker (GDBusServer      *server,
                               GDBusConnection  *connection,
                               GdmSession       *self)
{

        g_debug ("GdmSession: Handling new connection from worker");

        /* add to the list of pending connections.  We won't be able to
         * associate it with a specific worker conversation until we have
         * authenticated the connection (from the Hello handler).
         */
        self->priv->pending_worker_connections =
                g_list_prepend (self->priv->pending_worker_connections,
                                g_object_ref (connection));

        g_signal_connect_object (connection,
                                 "closed",
                                 G_CALLBACK (on_worker_connection_closed),
                                 self,
                                 0);

        export_worker_manager_interface (self, connection);

        return TRUE;
}

static GdmSessionConversation *
begin_verification_conversation (GdmSession            *self,
                                 GDBusMethodInvocation *invocation,
                                 const char            *service_name)
{
        GdmSessionConversation *conversation = NULL;
        gboolean conversation_started;

        conversation_started = gdm_session_start_conversation (self, service_name);

        if (conversation_started) {
                conversation = find_conversation_by_name (self, service_name);
        }

        if (conversation == NULL) {
                g_dbus_method_invocation_return_error (invocation,
                                                       G_DBUS_ERROR,
                                                       G_DBUS_ERROR_SPAWN_FAILED,
                                                       _("Could not create authentication helper process"));
        }

        return conversation;
}

static gboolean
gdm_session_handle_client_select_choice (GdmDBusUserVerifierChoiceList    *choice_list_interface,
                                         GDBusMethodInvocation            *invocation,
                                         const char                       *service_name,
                                         const char                       *answer,
                                         GdmSession                       *self)
{
        g_debug ("GdmSession: user selected choice '%s'", answer);
        gdm_dbus_user_verifier_choice_list_complete_select_choice (choice_list_interface, invocation);
        gdm_session_answer_query (self, service_name, answer);
        return TRUE;
}

static void
export_user_verifier_choice_list_interface (GdmSession      *self,
                                            GDBusConnection *connection)
{
        GdmDBusUserVerifierChoiceList   *interface;

        interface = GDM_DBUS_USER_VERIFIER_CHOICE_LIST (gdm_dbus_user_verifier_choice_list_skeleton_new ());

        g_signal_connect (interface,
                          "handle-select-choice",
                          G_CALLBACK (gdm_session_handle_client_select_choice),
                          self);

        g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (interface),
                                          connection,
                                          GDM_SESSION_DBUS_OBJECT_PATH,
                                          NULL);

        g_hash_table_insert (self->priv->user_verifier_extensions,
                             gdm_dbus_user_verifier_choice_list_interface_info ()->name,
                             interface);
}

static gboolean
gdm_session_handle_client_enable_extensions (GdmDBusUserVerifier    *user_verifier_interface,
                                             GDBusMethodInvocation  *invocation,
                                             const char * const *    extensions,
                                             GDBusConnection        *connection)
{
        GdmSession *self = g_object_get_data (G_OBJECT (connection), "gdm-session");
        size_t i;

        g_hash_table_remove_all (self->priv->user_verifier_extensions);

        for (i = 0; extensions[i] != NULL; i++) {
                if (g_hash_table_lookup (self->priv->user_verifier_extensions, extensions[i]) != NULL)
                        continue;

                if (strcmp (extensions[i],
                            gdm_dbus_user_verifier_choice_list_interface_info ()->name) == 0)
                        export_user_verifier_choice_list_interface (self, connection);

        }

        gdm_dbus_user_verifier_complete_enable_extensions (user_verifier_interface, invocation);

        return TRUE;
}
static gboolean
gdm_session_handle_client_begin_verification (GdmDBusUserVerifier    *user_verifier_interface,
                                              GDBusMethodInvocation  *invocation,
                                              const char             *service_name,
                                              GdmSession             *self)
{
        GdmSessionConversation *conversation;

        conversation = begin_verification_conversation (self, invocation, service_name);

        if (conversation != NULL) {
                conversation->starting_invocation = g_object_ref (invocation);
                conversation->starting_username = NULL;
        }

        return TRUE;
}

static gboolean
gdm_session_handle_client_begin_verification_for_user (GdmDBusUserVerifier    *user_verifier_interface,
                                                       GDBusMethodInvocation  *invocation,
                                                       const char             *service_name,
                                                       const char             *username,
                                                       GdmSession             *self)
{
        GdmSessionConversation *conversation;

        conversation = begin_verification_conversation (self, invocation, service_name);

        if (conversation != NULL) {
                conversation->starting_invocation = g_object_ref (invocation);
                conversation->starting_username = g_strdup (username);
        }

        return TRUE;
}

static gboolean
gdm_session_handle_client_answer_query (GdmDBusUserVerifier    *user_verifier_interface,
                                        GDBusMethodInvocation  *invocation,
                                        const char             *service_name,
                                        const char             *answer,
                                        GdmSession             *self)
{
        gdm_dbus_user_verifier_complete_answer_query (user_verifier_interface,
                                                      invocation);
        gdm_session_answer_query (self, service_name, answer);
        return TRUE;
}

static gboolean
gdm_session_handle_client_cancel (GdmDBusUserVerifier    *user_verifier_interface,
                                  GDBusMethodInvocation  *invocation,
                                  GdmSession             *self)
{
        gdm_dbus_user_verifier_complete_cancel (user_verifier_interface,
                                                invocation);
        gdm_session_cancel (self);
        return TRUE;
}

static gboolean
gdm_session_handle_client_select_session (GdmDBusGreeter         *greeter_interface,
                                          GDBusMethodInvocation  *invocation,
                                          const char             *session,
                                          GdmSession             *self)
{
        if (self->priv->greeter_interface != NULL) {
                gdm_dbus_greeter_complete_select_session (greeter_interface,
                                                          invocation);
        }
        gdm_session_select_session (self, session);
        return TRUE;
}

static gboolean
gdm_session_handle_client_select_user (GdmDBusGreeter        *greeter_interface,
                                       GDBusMethodInvocation *invocation,
                                       const char            *username,
                                       GdmSession            *self)
{
        if (self->priv->greeter_interface != NULL) {
                gdm_dbus_greeter_complete_select_user (greeter_interface,
                                                       invocation);
        }
        gdm_session_select_user (self, username);
        return TRUE;
}

static gboolean
gdm_session_handle_client_start_session_when_ready (GdmDBusGreeter        *greeter_interface,
                                                    GDBusMethodInvocation *invocation,
                                                    const char            *service_name,
                                                    gboolean               client_is_ready,
                                                    GdmSession            *self)
{

        if (self->priv->greeter_interface != NULL) {
                gdm_dbus_greeter_complete_start_session_when_ready (greeter_interface,
                                                                    invocation);
        }
        g_signal_emit (G_OBJECT (self),
                       signals [CLIENT_READY_FOR_SESSION_TO_START],
                       0,
                       service_name,
                       client_is_ready);
        return TRUE;
}

static gboolean
gdm_session_handle_get_timed_login_details (GdmDBusGreeter        *greeter_interface,
                                            GDBusMethodInvocation *invocation,
                                            GdmSession            *self)
{

        if (self->priv->greeter_interface != NULL) {
                gdm_dbus_greeter_complete_get_timed_login_details (greeter_interface,
                                                                   invocation,
                                                                   self->priv->timed_login_username != NULL,
                                                                   self->priv->timed_login_username != NULL? self->priv->timed_login_username : "",
                                                                   self->priv->timed_login_delay);
                if (self->priv->timed_login_username != NULL) {
                        gdm_dbus_greeter_emit_timed_login_requested (self->priv->greeter_interface,
                                                                     self->priv->timed_login_username,
                                                                     self->priv->timed_login_delay);
                }
        }
        return TRUE;
}

static gboolean
gdm_session_handle_client_begin_auto_login (GdmDBusGreeter        *greeter_interface,
                                            GDBusMethodInvocation *invocation,
                                            const char            *username,
                                            GdmSession            *self)
{
        if (self->priv->greeter_interface != NULL) {
                gdm_dbus_greeter_complete_begin_auto_login (greeter_interface,
                                                            invocation);
        }

        g_debug ("GdmSession: begin auto login for user '%s'", username);

        gdm_session_setup_for_user (self, "gdm-autologin", username);

        return TRUE;
}

static void
export_user_verifier_interface (GdmSession      *self,
                                GDBusConnection *connection)
{
        GdmDBusUserVerifier   *user_verifier_interface;
        user_verifier_interface = GDM_DBUS_USER_VERIFIER (gdm_dbus_user_verifier_skeleton_new ());

        g_object_set_data (G_OBJECT (connection), "gdm-session", self);

        g_signal_connect (user_verifier_interface,
                          "handle-enable-extensions",
                          G_CALLBACK (gdm_session_handle_client_enable_extensions),
                          connection);
        g_signal_connect (user_verifier_interface,
                          "handle-begin-verification",
                          G_CALLBACK (gdm_session_handle_client_begin_verification),
                          self);
        g_signal_connect (user_verifier_interface,
                          "handle-begin-verification-for-user",
                          G_CALLBACK (gdm_session_handle_client_begin_verification_for_user),
                          self);
        g_signal_connect (user_verifier_interface,
                          "handle-answer-query",
                          G_CALLBACK (gdm_session_handle_client_answer_query),
                          self);
        g_signal_connect (user_verifier_interface,
                          "handle-cancel",
                          G_CALLBACK (gdm_session_handle_client_cancel),
                          self);

        g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (user_verifier_interface),
                                          connection,
                                          GDM_SESSION_DBUS_OBJECT_PATH,
                                          NULL);

        self->priv->user_verifier_interface = user_verifier_interface;
}

static void
export_greeter_interface (GdmSession      *self,
                          GDBusConnection *connection)
{
        GdmDBusGreeter *greeter_interface;

        greeter_interface = GDM_DBUS_GREETER (gdm_dbus_greeter_skeleton_new ());

        g_signal_connect (greeter_interface,
                          "handle-begin-auto-login",
                          G_CALLBACK (gdm_session_handle_client_begin_auto_login),
                          self);
        g_signal_connect (greeter_interface,
                          "handle-select-session",
                          G_CALLBACK (gdm_session_handle_client_select_session),
                          self);
        g_signal_connect (greeter_interface,
                          "handle-select-user",
                          G_CALLBACK (gdm_session_handle_client_select_user),
                          self);
        g_signal_connect (greeter_interface,
                          "handle-start-session-when-ready",
                          G_CALLBACK (gdm_session_handle_client_start_session_when_ready),
                          self);
        g_signal_connect (greeter_interface,
                          "handle-get-timed-login-details",
                          G_CALLBACK (gdm_session_handle_get_timed_login_details),
                          self);

        g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (greeter_interface),
                                          connection,
                                          GDM_SESSION_DBUS_OBJECT_PATH,
                                          NULL);

        self->priv->greeter_interface = greeter_interface;

}

static gboolean
gdm_session_handle_client_disconnect (GdmDBusChooser        *chooser_interface,
                                      GDBusMethodInvocation *invocation,
                                      GdmSession            *self)
{
        gdm_dbus_chooser_complete_disconnect (chooser_interface,
                                              invocation);
        g_signal_emit (self, signals[DISCONNECTED], 0);
        return TRUE;
}

static void
export_remote_greeter_interface (GdmSession      *self,
                                 GDBusConnection *connection)
{
        GdmDBusRemoteGreeter *remote_greeter_interface;

        remote_greeter_interface = GDM_DBUS_REMOTE_GREETER (gdm_dbus_remote_greeter_skeleton_new ());

        g_signal_connect (remote_greeter_interface,
                          "handle-disconnect",
                          G_CALLBACK (gdm_session_handle_client_disconnect),
                          self);

        g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (remote_greeter_interface),
                                          connection,
                                          GDM_SESSION_DBUS_OBJECT_PATH,
                                          NULL);

        self->priv->remote_greeter_interface = remote_greeter_interface;

}

static gboolean
gdm_session_handle_client_select_hostname (GdmDBusChooser        *chooser_interface,
                                           GDBusMethodInvocation *invocation,
                                           const char            *hostname,
                                           GdmSession            *self)
{

        gdm_dbus_chooser_complete_select_hostname (chooser_interface,
                                                   invocation);
        g_signal_emit (self, signals[HOSTNAME_SELECTED], 0, hostname);
        return TRUE;
}

static void
export_chooser_interface (GdmSession      *self,
                          GDBusConnection *connection)
{
        GdmDBusChooser *chooser_interface;

        chooser_interface = GDM_DBUS_CHOOSER (gdm_dbus_chooser_skeleton_new ());

        g_signal_connect (chooser_interface,
                          "handle-select-hostname",
                          G_CALLBACK (gdm_session_handle_client_select_hostname),
                          self);

        g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (chooser_interface),
                                          connection,
                                          GDM_SESSION_DBUS_OBJECT_PATH,
                                          NULL);

        self->priv->chooser_interface = chooser_interface;
}

static void
on_outside_connection_closed (GDBusConnection *connection,
                              gboolean         remote_peer_vanished,
                              GError          *error,
                              GdmSession      *self)
{
        GCredentials *credentials;
        GPid          pid_of_client;

        g_debug ("GdmSession: external connection closed");

        self->priv->outside_connections =
            g_list_remove (self->priv->outside_connections,
                            connection);

        credentials = g_dbus_connection_get_peer_credentials (connection);
        pid_of_client = g_credentials_get_unix_pid (credentials, NULL);

        g_signal_emit (G_OBJECT (self),
                       signals [CLIENT_DISCONNECTED],
                       0,
                       credentials,
                       (guint)
                       pid_of_client);

        g_object_unref (connection);
}

static gboolean
handle_connection_from_outside (GDBusServer      *server,
                                GDBusConnection  *connection,
                                GdmSession       *self)
{
        GCredentials *credentials;
        GPid          pid_of_client;

        g_debug ("GdmSession: Handling new connection from outside");

        self->priv->outside_connections =
            g_list_prepend (self->priv->outside_connections,
                            g_object_ref (connection));

        g_signal_connect_object (connection,
                                 "closed",
                                 G_CALLBACK (on_outside_connection_closed),
                                 self,
                                 0);

        export_user_verifier_interface (self, connection);

        switch (self->priv->verification_mode) {
                case GDM_SESSION_VERIFICATION_MODE_LOGIN:
                        export_greeter_interface (self, connection);
                break;

                case GDM_SESSION_VERIFICATION_MODE_CHOOSER:
                        export_chooser_interface (self, connection);
                break;

                default:
                break;
        }

        if (!self->priv->display_is_local) {
                export_remote_greeter_interface (self, connection);
        }

        credentials = g_dbus_connection_get_peer_credentials (connection);
        pid_of_client = g_credentials_get_unix_pid (credentials, NULL);

        g_signal_emit (G_OBJECT (self),
                       signals [CLIENT_CONNECTED],
                       0,
                       credentials,
                       (guint)
                       pid_of_client);

        return TRUE;
}

static void
setup_worker_server (GdmSession *self)
{
        GDBusAuthObserver *observer;
        GDBusServer *server;
        GError *error = NULL;

        g_debug ("GdmSession: Creating D-Bus server for worker for session");

        observer = g_dbus_auth_observer_new ();
        g_signal_connect_object (observer,
                                 "authorize-authenticated-peer",
                                 G_CALLBACK (allow_worker_function),
                                 self,
                                 0);

        server = gdm_dbus_setup_private_server (observer, &error);
        g_object_unref (observer);

        if (server == NULL) {
                g_warning ("Cannot create worker D-Bus server for the session: %s",
                           error->message);
                return;
        }

        g_signal_connect_object (server,
                                 "new-connection",
                                 G_CALLBACK (handle_connection_from_worker),
                                 self,
                                 0);
        self->priv->worker_server = server;

        g_dbus_server_start (server);

        g_debug ("GdmSession: D-Bus server for workers listening on %s",
        g_dbus_server_get_client_address (self->priv->worker_server));
}

static gboolean
allow_user_function (GDBusAuthObserver *observer,
                     GIOStream         *stream,
                     GCredentials      *credentials,
                     GdmSession        *self)
{
        uid_t client_uid;
        GPid  pid_of_client;

        client_uid = g_credentials_get_unix_user (credentials, NULL);
        if (client_uid == self->priv->allowed_user) {
                return TRUE;
        }

        g_debug ("GdmSession: User not allowed");

        pid_of_client = g_credentials_get_unix_pid (credentials, NULL);
        g_signal_emit (G_OBJECT (self),
                       signals [CLIENT_REJECTED],
                       0,
                       credentials,
                       (guint)
                       pid_of_client);


        return FALSE;
}

static void
setup_outside_server (GdmSession *self)
{
        GDBusAuthObserver *observer;
        GDBusServer *server;
        GError *error = NULL;

        g_debug ("GdmSession: Creating D-Bus server for greeters and such");

        observer = g_dbus_auth_observer_new ();
        g_signal_connect_object (observer,
                                 "authorize-authenticated-peer",
                                 G_CALLBACK (allow_user_function),
                                 self,
                                 0);

        server = gdm_dbus_setup_private_server (observer, &error);
        g_object_unref (observer);

        if (server == NULL) {
                g_warning ("Cannot create greeter D-Bus server for the session: %s",
                           error->message);
                return;
        }

        g_signal_connect_object (server,
                                 "new-connection",
                                 G_CALLBACK (handle_connection_from_outside),
                                 self,
                                 0);
        self->priv->outside_server = server;

        g_dbus_server_start (server);

        g_debug ("GdmSession: D-Bus server for greeters listening on %s",
        g_dbus_server_get_client_address (self->priv->outside_server));
}

static void
free_conversation (GdmSessionConversation *conversation)
{
        if (conversation->job != NULL) {
                g_warning ("Freeing conversation '%s' with active job", conversation->service_name);
        }

        g_free (conversation->service_name);
        g_free (conversation->starting_username);
        g_free (conversation->session_id);
        g_clear_object (&conversation->worker_manager_interface);

        g_cancellable_cancel (conversation->worker_cancellable);
        g_clear_object (&conversation->worker_cancellable);

        if (conversation->worker_proxy != NULL) {
                g_signal_handlers_disconnect_by_func (conversation->worker_proxy,
                                                      G_CALLBACK (worker_on_username_changed),
                                                      conversation);
                g_signal_handlers_disconnect_by_func (conversation->worker_proxy,
                                                      G_CALLBACK (worker_on_session_exited),
                                                      conversation);
                g_signal_handlers_disconnect_by_func (conversation->worker_proxy,
                                                      G_CALLBACK (worker_on_reauthenticated),
                                                      conversation);
                g_signal_handlers_disconnect_by_func (conversation->worker_proxy,
                                                      G_CALLBACK (worker_on_saved_language_name_read),
                                                      conversation);
                g_signal_handlers_disconnect_by_func (conversation->worker_proxy,
                                                      G_CALLBACK (worker_on_saved_session_name_read),
                                                      conversation);
                g_signal_handlers_disconnect_by_func (conversation->worker_proxy,
                                                      G_CALLBACK (worker_on_cancel_pending_query),
                                                      conversation);
                g_clear_object (&conversation->worker_proxy);
        }
        g_clear_object (&conversation->session);
        g_free (conversation);
}

static void
load_lang_config_file (GdmSession *self)
{
        static const char *config_file = LANG_CONFIG_FILE;
        gchar         *contents = NULL;
        gchar         *p;
        gchar         *key;
        gchar         *value;
        gsize          length;
        GError        *error;
        GString       *line;
        GRegex        *re;

        if (!g_file_test (config_file, G_FILE_TEST_EXISTS)) {
                g_debug ("Cannot access '%s'", config_file);
                return;
        }

        error = NULL;
        if (!g_file_get_contents (config_file, &contents, &length, &error)) {
                g_debug ("Failed to parse '%s': %s",
                         LANG_CONFIG_FILE,
                         (error && error->message) ? error->message : "(null)");
                g_error_free (error);
                return;
        }

        if (!g_utf8_validate (contents, length, NULL)) {
                g_warning ("Invalid UTF-8 in '%s'", config_file);
                g_free (contents);
                return;
        }

        re = g_regex_new ("(?P<key>(LANG|LANGUAGE|LC_CTYPE|LC_NUMERIC|LC_TIME|LC_COLLATE|LC_MONETARY|LC_MESSAGES|LC_PAPER|LC_NAME|LC_ADDRESS|LC_TELEPHONE|LC_MEASUREMENT|LC_IDENTIFICATION|LC_ALL))=(\")?(?P<value>[^\"]*)?(\")?", 0, 0, &error);
        if (re == NULL) {
                g_warning ("Failed to regex: %s",
                           (error && error->message) ? error->message : "(null)");
                g_error_free (error);
                g_free (contents);
                return;
        }

        line = g_string_new ("");
        for (p = contents; p && *p; p = g_utf8_find_next_char (p, NULL)) {
                gunichar ch;
                GMatchInfo *match_info = NULL;

                ch = g_utf8_get_char (p);
                if ((ch != '\n') && (ch != '\0')) {
                        g_string_append_unichar (line, ch);
                        continue;
                }

                if (line->str && g_utf8_get_char (line->str) == '#') {
                        goto next_line;
                }

                if (!g_regex_match (re, line->str, 0, &match_info)) {
                        goto next_line;
                }

                if (!g_match_info_matches (match_info)) {
                        goto next_line;
                }

                key = g_match_info_fetch_named (match_info, "key");
                value = g_match_info_fetch_named (match_info, "value");

                if (key && *key && value && *value) {
			g_setenv (key, value, TRUE);
                }

                g_free (key);
                g_free (value);
next_line:
                g_match_info_free (match_info);
                g_string_set_size (line, 0);
        }

        g_string_free (line, TRUE);
        g_regex_unref (re);
        g_free (contents);
}

static void
unexport_and_free_user_verifier_extension (GDBusInterfaceSkeleton *interface)
{
        g_dbus_interface_skeleton_unexport (interface);

        g_object_run_dispose (G_OBJECT (interface));
        g_object_unref (interface);
}

static void
gdm_session_init (GdmSession *self)
{
        self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
                                                  GDM_TYPE_SESSION,
                                                  GdmSessionPrivate);

        self->priv->conversations = g_hash_table_new_full (g_str_hash,
                                                           g_str_equal,
                                                           (GDestroyNotify) g_free,
                                                           (GDestroyNotify)
                                                           free_conversation);
        self->priv->environment = g_hash_table_new_full (g_str_hash,
                                                         g_str_equal,
                                                         (GDestroyNotify) g_free,
                                                         (GDestroyNotify) g_free);
        self->priv->user_verifier_extensions = g_hash_table_new_full (g_str_hash,
                                                                      g_str_equal,
                                                                      NULL,
                                                                      (GDestroyNotify)
                                                                      unexport_and_free_user_verifier_extension);

        load_lang_config_file (self);
        setup_worker_server (self);
        setup_outside_server (self);
}

static void
worker_started (GdmSessionWorkerJob    *job,
                GdmSessionConversation *conversation)
{
        g_debug ("GdmSession: Worker job started");

}

static void
worker_exited (GdmSessionWorkerJob    *job,
               int                     code,
               GdmSessionConversation *conversation)
{
        GdmSession *self = conversation->session;

        g_debug ("GdmSession: Worker job exited: %d", code);

        g_hash_table_steal (self->priv->conversations, conversation->service_name);

        g_object_ref (conversation->job);
        if (self->priv->session_conversation == conversation) {
                g_signal_emit (self, signals[SESSION_EXITED], 0, code);
                self->priv->session_conversation = NULL;
        }

        g_debug ("GdmSession: Emitting conversation-stopped signal");
        g_signal_emit (self, signals[CONVERSATION_STOPPED], 0, conversation->service_name);
        if (self->priv->user_verifier_interface != NULL) {
                gdm_dbus_user_verifier_emit_conversation_stopped (self->priv->user_verifier_interface,
                                                                  conversation->service_name);
        }
        g_object_unref (conversation->job);

        if (conversation->is_stopping) {
                g_object_unref (conversation->job);
                conversation->job = NULL;
        }

        free_conversation (conversation);
}

static void
worker_died (GdmSessionWorkerJob    *job,
             int                     signum,
             GdmSessionConversation *conversation)
{
        GdmSession *self = conversation->session;

        g_debug ("GdmSession: Worker job died: %d", signum);

        g_hash_table_steal (self->priv->conversations, conversation->service_name);

        g_object_ref (conversation->job);
        if (self->priv->session_conversation == conversation) {
                g_signal_emit (self, signals[SESSION_DIED], 0, signum);
                self->priv->session_conversation = NULL;
        }

        g_debug ("GdmSession: Emitting conversation-stopped signal");
        g_signal_emit (self, signals[CONVERSATION_STOPPED], 0, conversation->service_name);
        if (self->priv->user_verifier_interface != NULL) {
                gdm_dbus_user_verifier_emit_conversation_stopped (self->priv->user_verifier_interface,
                                                                  conversation->service_name);
        }
        g_object_unref (conversation->job);

        if (conversation->is_stopping) {
                g_object_unref (conversation->job);
                conversation->job = NULL;
        }

        free_conversation (conversation);
}

static GdmSessionConversation *
start_conversation (GdmSession *self,
                    const char *service_name)
{
        GdmSessionConversation *conversation;
        char                   *job_name;

        conversation = g_new0 (GdmSessionConversation, 1);
        conversation->session = g_object_ref (self);
        conversation->service_name = g_strdup (service_name);
        conversation->worker_pid = -1;
        conversation->job = gdm_session_worker_job_new ();
        gdm_session_worker_job_set_server_address (conversation->job,
                                                   g_dbus_server_get_client_address (self->priv->worker_server));
        gdm_session_worker_job_set_for_reauth (conversation->job,
                                               self->priv->verification_mode == GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE);

        if (self->priv->conversation_environment != NULL) {
                gdm_session_worker_job_set_environment (conversation->job,
                                                        (const char * const *)
                                                        self->priv->conversation_environment);

        }
        g_signal_connect (conversation->job,
                          "started",
                          G_CALLBACK (worker_started),
                          conversation);
        g_signal_connect (conversation->job,
                          "exited",
                          G_CALLBACK (worker_exited),
                          conversation);
        g_signal_connect (conversation->job,
                          "died",
                          G_CALLBACK (worker_died),
                          conversation);

        job_name = g_strdup_printf ("gdm-session-worker [pam/%s]", service_name);
        if (!gdm_session_worker_job_start (conversation->job, job_name)) {
                g_object_unref (conversation->job);
                g_free (conversation->service_name);
                g_free (conversation);
                g_free (job_name);
                return NULL;
        }

        g_free (job_name);

        conversation->worker_pid = gdm_session_worker_job_get_pid (conversation->job);

        return conversation;
}

static void
close_conversation (GdmSessionConversation *conversation)
{
        GdmSession *self = conversation->session;

        if (conversation->worker_manager_interface != NULL) {
                unexport_worker_manager_interface (self, conversation->worker_manager_interface);
                g_clear_object (&conversation->worker_manager_interface);
        }

        if (conversation->worker_proxy != NULL) {
                GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (conversation->worker_proxy));
                g_dbus_connection_close_sync (connection, NULL, NULL);
        }
}

static void
stop_conversation (GdmSessionConversation *conversation)
{
        close_conversation (conversation);

        conversation->is_stopping = TRUE;
        gdm_session_worker_job_stop (conversation->job);
}

static void
stop_conversation_now (GdmSessionConversation *conversation)
{
        close_conversation (conversation);

        gdm_session_worker_job_stop_now (conversation->job);
        g_clear_object (&conversation->job);
}

#ifdef ENABLE_WAYLAND_SUPPORT
void
gdm_session_set_ignore_wayland (GdmSession *self,
                                gboolean    ignore_wayland)
{
        self->priv->ignore_wayland = ignore_wayland;
}
#endif

gboolean
gdm_session_start_conversation (GdmSession *self,
                                const char *service_name)
{
        GdmSessionConversation *conversation;

        g_return_val_if_fail (GDM_IS_SESSION (self), FALSE);

        conversation = g_hash_table_lookup (self->priv->conversations,
                                            service_name);

        if (conversation != NULL) {
                if (!conversation->is_stopping) {
                        g_warning ("GdmSession: conversation %s started more than once", service_name);
                        return FALSE;
                }
                g_debug ("GdmSession: stopping old conversation %s", service_name);
                gdm_session_worker_job_stop_now (conversation->job);
                g_object_unref (conversation->job);
                conversation->job = NULL;
        }

        g_debug ("GdmSession: starting conversation %s", service_name);

        conversation = start_conversation (self, service_name);

        g_hash_table_insert (self->priv->conversations,
                             g_strdup (service_name), conversation);
        return TRUE;
}

void
gdm_session_stop_conversation (GdmSession *self,
                               const char *service_name)
{
        GdmSessionConversation *conversation;

        g_return_if_fail (GDM_IS_SESSION (self));

        g_debug ("GdmSession: stopping conversation %s", service_name);

        conversation = find_conversation_by_name (self, service_name);

        if (conversation != NULL) {
                stop_conversation (conversation);
        }
}

static void
on_initialization_complete_cb (GdmDBusWorker *proxy,
                               GAsyncResult  *res,
                               gpointer       user_data)
{
        GdmSessionConversation *conversation = user_data;
        GdmSession *self;
        char *service_name;

        GError *error = NULL;
        GVariant *ret;

        ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);

        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) ||
            g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                return;

        self = conversation->session;
        service_name = conversation->service_name;

        if (ret != NULL) {
                if (conversation->starting_invocation) {
                        g_dbus_method_invocation_return_value (conversation->starting_invocation,
                                                               NULL);
                }

                g_signal_emit (G_OBJECT (self),
                               signals [SETUP_COMPLETE],
                               0,
                               service_name);

                gdm_session_authenticate (self, service_name);
                g_variant_unref (ret);

        } else {
                g_dbus_method_invocation_return_gerror (conversation->starting_invocation, error);
                report_and_stop_conversation (self, service_name, error);
                g_error_free (error);
        }

        g_clear_object (&conversation->starting_invocation);
}

static void
initialize (GdmSession *self,
            const char *service_name,
            const char *username,
            const char *log_file)
{
        GVariantBuilder          details;
        const char             **extensions;
        GdmSessionConversation  *conversation;

        g_assert (service_name != NULL);

        g_variant_builder_init (&details, G_VARIANT_TYPE ("a{sv}"));

        g_variant_builder_add_parsed (&details, "{'service', <%s>}", service_name);
        extensions = (const char **) g_hash_table_get_keys_as_array (self->priv->user_verifier_extensions, NULL);

        g_variant_builder_add_parsed (&details, "{'extensions', <%^as>}", extensions);

        if (username != NULL)
                g_variant_builder_add_parsed (&details, "{'username', <%s>}", username);

        if (log_file != NULL)
                g_variant_builder_add_parsed (&details, "{'log-file', <%s>}", log_file);

        if (self->priv->is_program_session)
                g_variant_builder_add_parsed (&details, "{'is-program-session', <%b>}", self->priv->is_program_session);

        if (self->priv->display_name != NULL)
                g_variant_builder_add_parsed (&details, "{'x11-display-name', <%s>}", self->priv->display_name);

        if (self->priv->display_hostname != NULL)
                g_variant_builder_add_parsed (&details, "{'hostname', <%s>}", self->priv->display_hostname);

        if (self->priv->display_is_local)
                g_variant_builder_add_parsed (&details, "{'display-is-local', <%b>}", self->priv->display_is_local);

        if (self->priv->display_is_initial)
                g_variant_builder_add_parsed (&details, "{'display-is-initial', <%b>}", self->priv->display_is_initial);

        if (self->priv->display_device != NULL)
                g_variant_builder_add_parsed (&details, "{'console', <%s>}", self->priv->display_device);

        if (self->priv->display_seat_id != NULL)
                g_variant_builder_add_parsed (&details, "{'seat-id', <%s>}", self->priv->display_seat_id);

        if (self->priv->display_x11_authority_file != NULL)
                g_variant_builder_add_parsed (&details, "{'x11-authority-file', <%s>}", self->priv->display_x11_authority_file);

        g_debug ("GdmSession: Beginning initialization");

        conversation = find_conversation_by_name (self, service_name);
        if (conversation != NULL) {
                gdm_dbus_worker_call_initialize (conversation->worker_proxy,
                                                 g_variant_builder_end (&details),

                                                 conversation->worker_cancellable,
                                                 (GAsyncReadyCallback) on_initialization_complete_cb,
                                                 conversation);
        }

        g_free (extensions);
}

void
gdm_session_setup (GdmSession *self,
                   const char *service_name)
{

        g_return_if_fail (GDM_IS_SESSION (self));

        update_session_type (self);

        initialize (self, service_name, NULL, NULL);
        gdm_session_defaults_changed (self);
}


void
gdm_session_setup_for_user (GdmSession *self,
                            const char *service_name,
                            const char *username)
{

        g_return_if_fail (GDM_IS_SESSION (self));
        g_return_if_fail (username != NULL);

        update_session_type (self);

        gdm_session_select_user (self, username);

        self->priv->is_program_session = FALSE;
        initialize (self, service_name, self->priv->selected_user, NULL);
        gdm_session_defaults_changed (self);
}

void
gdm_session_setup_for_program (GdmSession *self,
                               const char *service_name,
                               const char *username,
                               const char *log_file)
{

        g_return_if_fail (GDM_IS_SESSION (self));

        self->priv->is_program_session = TRUE;
        initialize (self, service_name, username, log_file);
}

void
gdm_session_authenticate (GdmSession *self,
                          const char *service_name)
{
        GdmSessionConversation *conversation;

        g_return_if_fail (GDM_IS_SESSION (self));

        conversation = find_conversation_by_name (self, service_name);
        if (conversation != NULL) {
                gdm_dbus_worker_call_authenticate (conversation->worker_proxy,
                                                   conversation->worker_cancellable,
                                                   (GAsyncReadyCallback) on_authenticate_cb,
                                                   conversation);
        }
}

void
gdm_session_authorize (GdmSession *self,
                       const char *service_name)
{
        GdmSessionConversation *conversation;

        g_return_if_fail (GDM_IS_SESSION (self));

        conversation = find_conversation_by_name (self, service_name);
        if (conversation != NULL) {
                gdm_dbus_worker_call_authorize (conversation->worker_proxy,
                                                conversation->worker_cancellable,
                                                (GAsyncReadyCallback) on_authorize_cb,
                                                conversation);
        }
}

void
gdm_session_accredit (GdmSession *self,
                      const char *service_name)
{
        GdmSessionConversation *conversation;

        g_return_if_fail (GDM_IS_SESSION (self));

        conversation = find_conversation_by_name (self, service_name);
        if (conversation != NULL) {
                gdm_dbus_worker_call_establish_credentials (conversation->worker_proxy,
                                                            conversation->worker_cancellable,
                                                            (GAsyncReadyCallback) on_establish_credentials_cb,
                                                            conversation);
        }

}

static void
send_environment_variable (const char             *key,
                           const char             *value,
                           GdmSessionConversation *conversation)
{
        gdm_dbus_worker_call_set_environment_variable (conversation->worker_proxy,
                                                       key, value,
                                                       conversation->worker_cancellable,
                                                       NULL, NULL);
}

static void
send_environment (GdmSession             *self,
                  GdmSessionConversation *conversation)
{

        g_hash_table_foreach (self->priv->environment,
                              (GHFunc) send_environment_variable,
                              conversation);
}

void
gdm_session_send_environment (GdmSession *self,
                              const char *service_name)
{
        GdmSessionConversation *conversation;

        g_return_if_fail (GDM_IS_SESSION (self));

        conversation = find_conversation_by_name (self, service_name);
        if (conversation != NULL) {
                send_environment (self, conversation);
        }
}

static const char *
get_session_name (GdmSession *self)
{
        /* FIXME: test the session names before we use them? */

        if (self->priv->selected_session != NULL) {
                return self->priv->selected_session;
        }

        return get_default_session_name (self);
}

static char *
get_session_command (GdmSession *self)
{
        gboolean    res;
        char       *command;
        const char *session_name;

        session_name = get_session_name (self);

        command = NULL;
        res = get_session_command_for_name (self, session_name, &command);
        if (! res) {
                g_critical ("Cannot find a command for specified session: %s", session_name);
                exit (EXIT_FAILURE);
        }

        return command;
}

static gchar *
get_session_desktop_names (GdmSession *self)
{
        gchar *filename;
        GKeyFile *keyfile;
        gchar *desktop_names = NULL;

        if (self->priv->selected_program != NULL) {
                return g_strdup ("GNOME-Greeter:GNOME");
        }

        filename = g_strdup_printf ("%s.desktop", get_session_name (self));
        g_debug ("GdmSession: getting desktop names for file '%s'", filename);
        keyfile = load_key_file_for_file (self, filename, NULL);
        if (keyfile != NULL) {
              gchar **names;

              names = g_key_file_get_string_list (keyfile, G_KEY_FILE_DESKTOP_GROUP,
                                                  "DesktopNames", NULL, NULL);
              if (names != NULL) {
                      desktop_names = g_strjoinv (":", names);

                      g_strfreev (names);
              }
        }

        g_key_file_free (keyfile);
        g_free (filename);
        return desktop_names;
}

void
gdm_session_set_environment_variable (GdmSession *self,
                                      const char *key,
                                      const char *value)
{
        g_return_if_fail (key != NULL);
        g_return_if_fail (value != NULL);

        g_hash_table_replace (self->priv->environment,
                              g_strdup (key),
                              g_strdup (value));
}

static void
set_up_session_language (GdmSession *self)
{
        char **environment;
        int i;
        const char *value;

        environment = g_listenv ();
        for (i = 0; environment[i] != NULL; i++) {
                if (strcmp (environment[i], "LANG") != 0 &&
                    strcmp (environment[i], "LANGUAGE") != 0 &&
                    !g_str_has_prefix (environment[i], "LC_")) {
                    continue;
                }

                value = g_getenv (environment[i]);

                gdm_session_set_environment_variable (self,
                                                      environment[i],
                                                      value);
        }
        g_strfreev (environment);
}

static void
set_up_session_environment (GdmSession *self)
{
        GdmSessionDisplayMode display_mode;
        gchar *desktop_names;
        char *locale;

        if (self->priv->selected_program == NULL) {
                gdm_session_set_environment_variable (self,
                                                      "GDMSESSION",
                                                      get_session_name (self));
                gdm_session_set_environment_variable (self,
                                                      "DESKTOP_SESSION",
                                                      get_session_name (self));
                gdm_session_set_environment_variable (self,
                                                      "XDG_SESSION_DESKTOP",
                                                      get_session_name (self));
        }

        desktop_names = get_session_desktop_names (self);
        if (desktop_names != NULL) {
                gdm_session_set_environment_variable (self, "XDG_CURRENT_DESKTOP", desktop_names);
        }

        set_up_session_language (self);

        locale = g_strdup (get_default_language_name (self));

        if (locale != NULL && locale[0] != '\0') {
                gdm_session_set_environment_variable (self,
                                                      "LANG",
                                                      locale);
                gdm_session_set_environment_variable (self,
                                                      "GDM_LANG",
                                                      locale);
        }

        g_free (locale);

        display_mode = gdm_session_get_display_mode (self);
        if (display_mode == GDM_SESSION_DISPLAY_MODE_REUSE_VT) {
                gdm_session_set_environment_variable (self,
                                                      "DISPLAY",
                                                      self->priv->display_name);

                if (self->priv->user_x11_authority_file != NULL) {
                        gdm_session_set_environment_variable (self,
                                                              "XAUTHORITY",
                                                              self->priv->user_x11_authority_file);
                }
        }

        if (g_getenv ("WINDOWPATH") != NULL) {
                gdm_session_set_environment_variable (self,
                                                      "WINDOWPATH",
                                                      g_getenv ("WINDOWPATH"));
        }

        g_free (desktop_names);
}

static void
send_display_mode (GdmSession *self,
                   GdmSessionConversation *conversation)
{
        GdmSessionDisplayMode mode;

        mode = gdm_session_get_display_mode (self);
        gdm_dbus_worker_call_set_session_display_mode (conversation->worker_proxy,
                                                       gdm_session_display_mode_to_string (mode),
                                                       conversation->worker_cancellable,
                                                       NULL, NULL);
}

static void
send_session_type (GdmSession *self,
                   GdmSessionConversation *conversation)
{
        const char *session_type = "x11";

        if (self->priv->session_type != NULL) {
                session_type = self->priv->session_type;
        }

        gdm_dbus_worker_call_set_environment_variable (conversation->worker_proxy,
                                                       "XDG_SESSION_TYPE",
                                                       session_type,
                                                       conversation->worker_cancellable,
                                                       NULL, NULL);
}

void
gdm_session_open_session (GdmSession *self,
                          const char *service_name)
{
        GdmSessionConversation *conversation;

        g_return_if_fail (GDM_IS_SESSION (self));

        conversation = find_conversation_by_name (self, service_name);

        if (conversation != NULL) {
                send_display_mode (self, conversation);
                send_session_type (self, conversation);

                gdm_dbus_worker_call_open (conversation->worker_proxy,
                                           conversation->worker_cancellable,
                                           (GAsyncReadyCallback) on_opened, conversation);
        }
}

static void
stop_all_other_conversations (GdmSession             *self,
                              GdmSessionConversation *conversation_to_keep,
                              gboolean                now)
{
        GHashTableIter iter;
        gpointer key, value;

        if (self->priv->conversations == NULL) {
                return;
        }

        if (conversation_to_keep == NULL) {
                g_debug ("GdmSession: Stopping all conversations");
        } else {
                g_debug ("GdmSession: Stopping all conversations except for %s",
                         conversation_to_keep->service_name);
        }

        g_hash_table_iter_init (&iter, self->priv->conversations);
        while (g_hash_table_iter_next (&iter, &key, &value)) {
                GdmSessionConversation *conversation;

                conversation = (GdmSessionConversation *) value;

                if (conversation == conversation_to_keep) {
                        if (now) {
                                g_hash_table_iter_steal (&iter);
                                g_free (key);
                        }
                } else {
                        if (now) {
                                stop_conversation_now (conversation);
                        } else {
                                stop_conversation (conversation);
                        }
                }
        }

        if (now) {
                g_hash_table_remove_all (self->priv->conversations);

                if (conversation_to_keep != NULL) {
                        g_hash_table_insert (self->priv->conversations,
                                             g_strdup (conversation_to_keep->service_name),
                                             conversation_to_keep);
                }

                if (self->priv->session_conversation != conversation_to_keep) {
                        self->priv->session_conversation = NULL;
                }
        }

}

static void
on_start_program_cb (GdmDBusWorker *worker,
                     GAsyncResult  *res,
                     gpointer       user_data)
{
        GdmSessionConversation *conversation = user_data;
        GdmSession *self;
        char *service_name;

        GError *error = NULL;
        gboolean worked;
        GPid pid;

        worked = gdm_dbus_worker_call_start_program_finish (worker,
                                                            &pid,
                                                            res,
                                                            &error);

        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) ||
            g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                return;

        self = conversation->session;
        service_name = conversation->service_name;

        if (worked) {
                self->priv->session_pid = pid;
                self->priv->session_conversation = conversation;

                g_debug ("GdmSession: Emitting 'session-started' signal with pid '%d'", pid);
                g_signal_emit (self, signals[SESSION_STARTED], 0, service_name, pid);
        } else {
                gdm_session_stop_conversation (self, service_name);

                g_debug ("GdmSession: Emitting 'session-start-failed' signal");
                g_signal_emit (self, signals[SESSION_START_FAILED], 0, service_name, error->message);
        }
}

void
gdm_session_start_session (GdmSession *self,
                           const char *service_name)
{
        GdmSessionConversation *conversation;
        GdmSessionDisplayMode   display_mode;
        gboolean                is_x11 = TRUE;
        gboolean                run_launcher = FALSE;
        gboolean                allow_remote_connections = FALSE;
        char                   *command;
        char                   *program;

        g_return_if_fail (GDM_IS_SESSION (self));
        g_return_if_fail (self->priv->session_conversation == NULL);

        conversation = find_conversation_by_name (self, service_name);

        if (conversation == NULL) {
                g_warning ("GdmSession: Tried to start session of "
                           "nonexistent conversation %s", service_name);
                return;
        }

        stop_all_other_conversations (self, conversation, FALSE);

        display_mode = gdm_session_get_display_mode (self);

#ifdef ENABLE_WAYLAND_SUPPORT
        is_x11 = g_strcmp0 (self->priv->session_type, "wayland") != 0;
#endif

        if (display_mode == GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED ||
            display_mode == GDM_SESSION_DISPLAY_MODE_NEW_VT) {
                run_launcher = TRUE;
        }

        if (self->priv->selected_program == NULL) {
                gboolean run_xsession_script;

                command = get_session_command (self);

                run_xsession_script = !gdm_session_bypasses_xsession (self);

                if (self->priv->display_is_local) {
                        gboolean disallow_tcp = TRUE;
                        gdm_settings_direct_get_boolean (GDM_KEY_DISALLOW_TCP, &disallow_tcp);
                        allow_remote_connections = !disallow_tcp;
                } else {
                        allow_remote_connections = TRUE;
                }

                if (run_launcher) {
                        if (is_x11) {
                                program = g_strdup_printf (LIBEXECDIR "/gdm-x-session %s %s\"%s\"",
                                                           run_xsession_script? "--run-script " : "",
                                                           allow_remote_connections? "--allow-remote-connections " : "",
                                                           command);
                        } else {
                                program = g_strdup_printf (LIBEXECDIR "/gdm-wayland-session \"%s\"",
                                                           command);
                        }
                } else if (run_xsession_script) {
                        program = g_strdup_printf (GDMCONFDIR "/Xsession \"%s\"", command);
                } else {
                        program = g_strdup (command);
                }

                g_free (command);
        } else {
                if (run_launcher) {
                        if (is_x11) {
                                program = g_strdup_printf (LIBEXECDIR "/gdm-x-session \"%s\"",
                                                           self->priv->selected_program);
                        } else {
                                program = g_strdup_printf (LIBEXECDIR "/gdm-wayland-session \"%s\"",
                                                           self->priv->selected_program);
                        }
                } else {
                        if (g_strcmp0 (self->priv->display_seat_id, "seat0") != 0) {
                                program = g_strdup_printf ("dbus-run-session -- %s",
                                                           self->priv->selected_program);
                        } else {
                                program = g_strdup (self->priv->selected_program);
                        }
                }
        }

        set_up_session_environment (self);
        send_environment (self, conversation);

        gdm_dbus_worker_call_start_program (conversation->worker_proxy,
                                            program,
                                            conversation->worker_cancellable,
                                            (GAsyncReadyCallback) on_start_program_cb,
                                            conversation);
        g_free (program);
}

static void
stop_all_conversations (GdmSession *self)
{
        stop_all_other_conversations (self, NULL, TRUE);
}

static void
do_reset (GdmSession *self)
{
        stop_all_conversations (self);

        g_list_free_full (self->priv->pending_worker_connections, g_object_unref);
        self->priv->pending_worker_connections = NULL;

        g_free (self->priv->selected_user);
        self->priv->selected_user = NULL;

        g_free (self->priv->selected_session);
        self->priv->selected_session = NULL;

        g_free (self->priv->saved_session);
        self->priv->saved_session = NULL;

        g_free (self->priv->saved_language);
        self->priv->saved_language = NULL;

        g_free (self->priv->user_x11_authority_file);
        self->priv->user_x11_authority_file = NULL;

        g_hash_table_remove_all (self->priv->environment);

        self->priv->session_pid = -1;
        self->priv->session_conversation = NULL;
}

void
gdm_session_close (GdmSession *self)
{

        g_return_if_fail (GDM_IS_SESSION (self));

        g_debug ("GdmSession: Closing session");
        do_reset (self);

        g_list_free_full (self->priv->outside_connections, g_object_unref);
        self->priv->outside_connections = NULL;
}

void
gdm_session_answer_query (GdmSession *self,
                          const char *service_name,
                          const char *text)
{
        GdmSessionConversation *conversation;

        g_return_if_fail (GDM_IS_SESSION (self));

        conversation = find_conversation_by_name (self, service_name);

        if (conversation != NULL) {
                answer_pending_query (conversation, text);
        }
}

void
gdm_session_cancel  (GdmSession *self)
{
        g_return_if_fail (GDM_IS_SESSION (self));

        g_signal_emit (G_OBJECT (self), signals [CANCELLED], 0);
}

void
gdm_session_reset (GdmSession *self)
{
        if (self->priv->user_verifier_interface != NULL) {
                gdm_dbus_user_verifier_emit_reset (self->priv->user_verifier_interface);
        }

        do_reset (self);
}

void
gdm_session_set_timed_login_details (GdmSession *self,
                                     const char *username,
                                     int         delay)
{
        g_debug ("GdmSession: timed login details %s %d", username, delay);
        self->priv->timed_login_username = g_strdup (username);
        self->priv->timed_login_delay = delay;
}

gboolean
gdm_session_is_running (GdmSession *self)
{
        return self->priv->session_pid > 0;
}

gboolean
gdm_session_client_is_connected (GdmSession *self)
{
        g_return_val_if_fail (GDM_IS_SESSION (self), FALSE);

        return self->priv->outside_connections != NULL;
}

uid_t
gdm_session_get_allowed_user (GdmSession *self)
{
        return self->priv->allowed_user;
}

void
gdm_session_start_reauthentication (GdmSession *session,
                                    GPid        pid_of_caller,
                                    uid_t       uid_of_caller)
{
        GdmSessionConversation *conversation = session->priv->session_conversation;

        g_return_if_fail (conversation != NULL);

        conversation->reauth_pid_of_caller = pid_of_caller;

        gdm_dbus_worker_call_start_reauthentication (conversation->worker_proxy,
                                                     (int) pid_of_caller,
                                                     (int) uid_of_caller,
                                                     conversation->worker_cancellable,
                                                     (GAsyncReadyCallback) on_reauthentication_started_cb,
                                                     conversation);
}

const char *
gdm_session_get_server_address (GdmSession *self)
{
        g_return_val_if_fail (GDM_IS_SESSION (self), NULL);

        return g_dbus_server_get_client_address (self->priv->outside_server);
}

const char *
gdm_session_get_username (GdmSession *self)
{
        g_return_val_if_fail (GDM_IS_SESSION (self), NULL);

        return self->priv->selected_user;
}

const char *
gdm_session_get_display_device (GdmSession *self)
{
        g_return_val_if_fail (GDM_IS_SESSION (self), NULL);

        return self->priv->display_device;
}

const char *
gdm_session_get_display_seat_id (GdmSession *self)
{
        g_return_val_if_fail (GDM_IS_SESSION (self), NULL);

        return g_strdup (self->priv->display_seat_id);
}

const char *
gdm_session_get_session_id (GdmSession *self)
{
        GdmSessionConversation *conversation;

        g_return_val_if_fail (GDM_IS_SESSION (self), NULL);

        conversation = self->priv->session_conversation;

        if (conversation == NULL) {
                return NULL;
        }

        return conversation->session_id;
}

const char *
gdm_session_get_conversation_session_id (GdmSession *self,
                                         const char *service_name)
{
        GdmSessionConversation *conversation;

        g_return_val_if_fail (GDM_IS_SESSION (self), NULL);

        conversation = find_conversation_by_name (self, service_name);

        if (conversation == NULL) {
                return NULL;
        }

        return conversation->session_id;
}

static char *
get_session_filename (GdmSession *self)
{
        return g_strdup_printf ("%s.desktop", get_session_name (self));
}

#ifdef ENABLE_WAYLAND_SUPPORT
static gboolean
gdm_session_is_wayland_session (GdmSession *self)
{
        GKeyFile   *key_file;
        gboolean    is_wayland_session = FALSE;
        char       *filename;
        char       *full_path = NULL;

        g_return_val_if_fail (self != NULL, FALSE);
        g_return_val_if_fail (GDM_IS_SESSION (self), FALSE);

        filename = get_session_filename (self);

        key_file = load_key_file_for_file (self, filename, &full_path);

        if (key_file == NULL) {
                goto out;
        }

        if (full_path != NULL && strstr (full_path, "/wayland-sessions/") != NULL) {
                is_wayland_session = TRUE;
        }
        g_debug ("GdmSession: checking if file '%s' is wayland session: %s", filename, is_wayland_session? "yes" : "no");

out:
        g_clear_pointer (&key_file, (GDestroyNotify) g_key_file_free);
        g_free (filename);
        return is_wayland_session;
}
#endif

static void
update_session_type (GdmSession *self)
{
#ifdef ENABLE_WAYLAND_SUPPORT
        gboolean is_wayland_session;

        is_wayland_session = gdm_session_is_wayland_session (self);
        if (is_wayland_session) {
                set_session_type (self, "wayland");
        } else {
                set_session_type (self, NULL);
        }
#endif
}

gboolean
gdm_session_bypasses_xsession (GdmSession *self)
{
        GError     *error;
        GKeyFile   *key_file;
        gboolean    res;
        gboolean    bypasses_xsession = FALSE;
        char       *filename = NULL;

        g_return_val_if_fail (self != NULL, FALSE);
        g_return_val_if_fail (GDM_IS_SESSION (self), FALSE);

#ifdef ENABLE_WAYLAND_SUPPORT
        if (gdm_session_is_wayland_session (self)) {
                bypasses_xsession = TRUE;
                goto out;
        }
#endif

        filename = get_session_filename (self);

        key_file = load_key_file_for_file (self, filename, NULL);

        error = NULL;
        res = g_key_file_has_key (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GDM-BypassXsession", NULL);
        if (!res) {
                goto out;
        } else {
                bypasses_xsession = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GDM-BypassXsession", &error);
                if (error) {
                        bypasses_xsession = FALSE;
                        g_error_free (error);
                        goto out;
                }
        }

out:
        if (bypasses_xsession) {
                g_debug ("GdmSession: Session %s bypasses Xsession wrapper script", filename);
        }
        g_free (filename);
        return bypasses_xsession;
}

GdmSessionDisplayMode
gdm_session_get_display_mode (GdmSession *self)
{
        g_debug ("GdmSession: type %s, program? %s, seat %s",
                 self->priv->session_type,
                 self->priv->is_program_session? "yes" : "no",
                 self->priv->display_seat_id);

        /* Non-seat0 sessions share their X server with their login screen
         * for now.
         */
        if (g_strcmp0 (self->priv->display_seat_id, "seat0") != 0) {
                return GDM_SESSION_DISPLAY_MODE_REUSE_VT;
        }

#ifdef ENABLE_USER_DISPLAY_SERVER
        /* All other cases (wayland login screen, X login screen,
         * wayland user session, X user session) use the NEW_VT
         * display mode.  That display mode means that GDM allocates
         * a new VT and jumps to it before starting the session. The
         * session is expected to use logind to gain access to the
         * display and input devices.
         *
         * GDM also has a LOGIND_MANAGED display mode which we can't
         * use yet. The difference between it and NEW_VT, is with it,
         * GDM doesn't do any VT handling at all, expecting the session
         * and logind to do everything.  The problem is, for wayland
         * sessions it will cause flicker until * this bug is fixed:
         *
         * https://bugzilla.gnome.org/show_bug.cgi?id=745141
         *
         * Likewise, for X sessions it's problematic because
         *   1) X doesn't call TakeControl before switching VTs
         *   2) X doesn't support getting started "in the background"
         *   right now.  It will die with an error if logind devices
         *   are paused when handed out.
         */
        return GDM_SESSION_DISPLAY_MODE_NEW_VT;
#else

#ifdef ENABLE_WAYLAND_SUPPORT
        /* Wayland sessions are for now assumed to run in a
         * mutter-launch-like environment, so we allocate
         * a new VT for them. */
        if (g_strcmp0 (self->priv->session_type, "wayland") == 0) {
                return GDM_SESSION_DISPLAY_MODE_NEW_VT;
        }
#endif
        return GDM_SESSION_DISPLAY_MODE_REUSE_VT;
#endif
}

void
gdm_session_select_program (GdmSession *self,
                            const char *text)
{

        g_free (self->priv->selected_program);

        self->priv->selected_program = g_strdup (text);
}

void
gdm_session_select_session (GdmSession *self,
                            const char *text)
{
        GHashTableIter iter;
        gpointer key, value;

        g_debug ("GdmSession: selecting session '%s'", text);

        g_free (self->priv->selected_session);
        self->priv->selected_session = g_strdup (text);

        update_session_type (self);

        g_hash_table_iter_init (&iter, self->priv->conversations);
        while (g_hash_table_iter_next (&iter, &key, &value)) {
                GdmSessionConversation *conversation;

                conversation = (GdmSessionConversation *) value;

                gdm_dbus_worker_call_set_session_name (conversation->worker_proxy,
                                                       get_session_name (self),
                                                       conversation->worker_cancellable,
                                                       NULL, NULL);
        }
}

static void
set_display_name (GdmSession *self,
                  const char *name)
{
        g_free (self->priv->display_name);
        self->priv->display_name = g_strdup (name);
}

static void
set_display_hostname (GdmSession *self,
                      const char *name)
{
        g_free (self->priv->display_hostname);
        self->priv->display_hostname = g_strdup (name);
}

static void
set_display_device (GdmSession *self,
                    const char *name)
{
        g_debug ("GdmSession: Setting display device: %s", name);
        g_free (self->priv->display_device);
        self->priv->display_device = g_strdup (name);
}

static void
set_display_seat_id (GdmSession *self,
                     const char *name)
{
        g_free (self->priv->display_seat_id);
        self->priv->display_seat_id = g_strdup (name);
}

static void
set_user_x11_authority_file (GdmSession *self,
                             const char *name)
{
        g_free (self->priv->user_x11_authority_file);
        self->priv->user_x11_authority_file = g_strdup (name);
}

static void
set_display_x11_authority_file (GdmSession *self,
                                const char *name)
{
        g_free (self->priv->display_x11_authority_file);
        self->priv->display_x11_authority_file = g_strdup (name);
}

static void
set_display_is_local (GdmSession *self,
                      gboolean    is_local)
{
        self->priv->display_is_local = is_local;
}

static void
set_display_is_initial (GdmSession *self,
                        gboolean    is_initial)
{
        self->priv->display_is_initial = is_initial;
}

static void
set_verification_mode (GdmSession                 *self,
                       GdmSessionVerificationMode  verification_mode)
{
        self->priv->verification_mode = verification_mode;
}

static void
set_allowed_user (GdmSession *self,
                  uid_t       allowed_user)
{
        self->priv->allowed_user = allowed_user;
}

static void
set_conversation_environment (GdmSession  *self,
                              char       **environment)
{
        g_strfreev (self->priv->conversation_environment);
        self->priv->conversation_environment = g_strdupv (environment);
}

static void
set_session_type (GdmSession *self,
                  const char *session_type)
{

        if (g_strcmp0 (self->priv->session_type, session_type) != 0) {
                g_debug ("GdmSession: setting session to type '%s'", session_type? session_type : "");
                g_free (self->priv->session_type);
                self->priv->session_type = g_strdup (session_type);
        }
}

static void
gdm_session_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
        GdmSession *self;

        self = GDM_SESSION (object);

        switch (prop_id) {
        case PROP_SESSION_TYPE:
                set_session_type (self, g_value_get_string (value));
                break;
        case PROP_DISPLAY_NAME:
                set_display_name (self, g_value_get_string (value));
                break;
        case PROP_DISPLAY_HOSTNAME:
                set_display_hostname (self, g_value_get_string (value));
                break;
        case PROP_DISPLAY_DEVICE:
                set_display_device (self, g_value_get_string (value));
                break;
        case PROP_DISPLAY_SEAT_ID:
                set_display_seat_id (self, g_value_get_string (value));
                break;
        case PROP_USER_X11_AUTHORITY_FILE:
                set_user_x11_authority_file (self, g_value_get_string (value));
                break;
        case PROP_DISPLAY_X11_AUTHORITY_FILE:
                set_display_x11_authority_file (self, g_value_get_string (value));
                break;
        case PROP_DISPLAY_IS_LOCAL:
                set_display_is_local (self, g_value_get_boolean (value));
                break;
        case PROP_DISPLAY_IS_INITIAL:
                set_display_is_initial (self, g_value_get_boolean (value));
                break;
        case PROP_VERIFICATION_MODE:
                set_verification_mode (self, g_value_get_enum (value));
                break;
        case PROP_ALLOWED_USER:
                set_allowed_user (self, g_value_get_uint (value));
                break;
        case PROP_CONVERSATION_ENVIRONMENT:
                set_conversation_environment (self, g_value_get_pointer (value));
                break;
#ifdef ENABLE_WAYLAND_SUPPORT
        case PROP_IGNORE_WAYLAND:
                gdm_session_set_ignore_wayland (self, g_value_get_boolean (value));
                break;
#endif
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
        }
}

static void
gdm_session_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
        GdmSession *self;

        self = GDM_SESSION (object);

        switch (prop_id) {
        case PROP_SESSION_TYPE:
                g_value_set_string (value, self->priv->session_type);
                break;
        case PROP_DISPLAY_NAME:
                g_value_set_string (value, self->priv->display_name);
                break;
        case PROP_DISPLAY_HOSTNAME:
                g_value_set_string (value, self->priv->display_hostname);
                break;
        case PROP_DISPLAY_DEVICE:
                g_value_set_string (value, self->priv->display_device);
                break;
        case PROP_DISPLAY_SEAT_ID:
                g_value_set_string (value, self->priv->display_seat_id);
                break;
        case PROP_USER_X11_AUTHORITY_FILE:
                g_value_set_string (value, self->priv->user_x11_authority_file);
                break;
        case PROP_DISPLAY_X11_AUTHORITY_FILE:
                g_value_set_string (value, self->priv->display_x11_authority_file);
                break;
        case PROP_DISPLAY_IS_LOCAL:
                g_value_set_boolean (value, self->priv->display_is_local);
                break;
        case PROP_DISPLAY_IS_INITIAL:
                g_value_set_boolean (value, self->priv->display_is_initial);
                break;
        case PROP_VERIFICATION_MODE:
                g_value_set_enum (value, self->priv->verification_mode);
                break;
        case PROP_ALLOWED_USER:
                g_value_set_uint (value, self->priv->allowed_user);
                break;
        case PROP_CONVERSATION_ENVIRONMENT:
                g_value_set_pointer (value, self->priv->environment);
                break;
#ifdef ENABLE_WAYLAND_SUPPORT
        case PROP_IGNORE_WAYLAND:
                g_value_set_boolean (value, self->priv->ignore_wayland);
                break;
#endif
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
        }
}

static void
gdm_session_dispose (GObject *object)
{
        GdmSession *self;

        self = GDM_SESSION (object);

        g_debug ("GdmSession: Disposing session");

        gdm_session_close (self);

        g_clear_pointer (&self->priv->conversations,
                         g_hash_table_unref);

        g_clear_object (&self->priv->user_verifier_interface);
        g_clear_pointer (&self->priv->user_verifier_extensions,
                         g_hash_table_unref);
        g_clear_object (&self->priv->greeter_interface);
        g_clear_object (&self->priv->chooser_interface);

        g_free (self->priv->display_name);
        self->priv->display_name = NULL;

        g_free (self->priv->display_hostname);
        self->priv->display_hostname = NULL;

        g_free (self->priv->display_device);
        self->priv->display_device = NULL;

        g_free (self->priv->display_seat_id);
        self->priv->display_seat_id = NULL;

        g_free (self->priv->display_x11_authority_file);
        self->priv->display_x11_authority_file = NULL;

        g_strfreev (self->priv->conversation_environment);
        self->priv->conversation_environment = NULL;

        if (self->priv->worker_server != NULL) {
                g_dbus_server_stop (self->priv->worker_server);
                g_clear_object (&self->priv->worker_server);
        }

        if (self->priv->outside_server != NULL) {
                g_dbus_server_stop (self->priv->outside_server);
                g_clear_object (&self->priv->outside_server);
        }

        if (self->priv->environment != NULL) {
                g_hash_table_destroy (self->priv->environment);
                self->priv->environment = NULL;
        }

        G_OBJECT_CLASS (gdm_session_parent_class)->dispose (object);
}

static void
gdm_session_finalize (GObject *object)
{
        GdmSession   *self;
        GObjectClass *parent_class;

        self = GDM_SESSION (object);

        g_free (self->priv->selected_user);
        g_free (self->priv->selected_session);
        g_free (self->priv->saved_session);
        g_free (self->priv->saved_language);

        g_free (self->priv->fallback_session_name);

        parent_class = G_OBJECT_CLASS (gdm_session_parent_class);

        if (parent_class->finalize != NULL)
                parent_class->finalize (object);
}

static GObject *
gdm_session_constructor (GType                  type,
                         guint                  n_construct_properties,
                         GObjectConstructParam *construct_properties)
{
        GdmSession *self;

        self = GDM_SESSION (G_OBJECT_CLASS (gdm_session_parent_class)->constructor (type,
                                                                                    n_construct_properties,
                                                                                    construct_properties));
        return G_OBJECT (self);
}

static void
gdm_session_class_init (GdmSessionClass *session_class)
{
        GObjectClass *object_class;

        object_class = G_OBJECT_CLASS (session_class);

        object_class->get_property = gdm_session_get_property;
        object_class->set_property = gdm_session_set_property;
        object_class->constructor = gdm_session_constructor;
        object_class->dispose = gdm_session_dispose;
        object_class->finalize = gdm_session_finalize;

        g_type_class_add_private (session_class, sizeof (GdmSessionPrivate));

        signals [CONVERSATION_STARTED] =
                g_signal_new ("conversation-started",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, conversation_started),
                              NULL,
                              NULL,
                              g_cclosure_marshal_VOID__STRING,
                              G_TYPE_NONE,
                              1, G_TYPE_STRING);
        signals [CONVERSATION_STOPPED] =
                g_signal_new ("conversation-stopped",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, conversation_stopped),
                              NULL,
                              NULL,
                              g_cclosure_marshal_VOID__STRING,
                              G_TYPE_NONE,
                              1, G_TYPE_STRING);
        signals [SETUP_COMPLETE] =
                g_signal_new ("setup-complete",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, setup_complete),
                              NULL,
                              NULL,
                              g_cclosure_marshal_VOID__STRING,
                              G_TYPE_NONE,
                              1,
                              G_TYPE_STRING);

        signals [AUTHENTICATION_FAILED] =
                g_signal_new ("authentication-failed",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, authentication_failed),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_STRING,
                              G_TYPE_INT);
        signals [VERIFICATION_COMPLETE] =
                g_signal_new ("verification-complete",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, verification_complete),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              1,
                              G_TYPE_STRING);
        signals [SESSION_OPENED] =
                g_signal_new ("session-opened",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, session_opened),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_STRING,
                              G_TYPE_STRING);
        signals [SESSION_STARTED] =
                g_signal_new ("session-started",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, session_started),
                              NULL,
                              NULL,
                              g_cclosure_marshal_generic,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_STRING,
                              G_TYPE_INT);
        signals [SESSION_START_FAILED] =
                g_signal_new ("session-start-failed",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, session_start_failed),
                              NULL,
                              NULL,
                              g_cclosure_marshal_generic,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_STRING, G_TYPE_STRING);
        signals [SESSION_EXITED] =
                g_signal_new ("session-exited",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, session_exited),
                              NULL,
                              NULL,
                              g_cclosure_marshal_VOID__INT,
                              G_TYPE_NONE,
                              1,
                              G_TYPE_INT);
        signals [SESSION_DIED] =
                g_signal_new ("session-died",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, session_died),
                              NULL,
                              NULL,
                              g_cclosure_marshal_VOID__INT,
                              G_TYPE_NONE,
                              1,
                              G_TYPE_INT);

        signals [REAUTHENTICATION_STARTED] =
                g_signal_new ("reauthentication-started",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, reauthentication_started),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_INT,
                              G_TYPE_STRING);
        signals [REAUTHENTICATED] =
                g_signal_new ("reauthenticated",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, reauthenticated),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              1,
                              G_TYPE_STRING);
        signals [CANCELLED] =
                g_signal_new ("cancelled",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, cancelled),
                              NULL,
                              NULL,
                              g_cclosure_marshal_VOID__VOID,
                              G_TYPE_NONE,
                              0);

        signals [CLIENT_REJECTED] =
                g_signal_new ("client-rejected",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, client_rejected),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_CREDENTIALS,
                              G_TYPE_UINT);

        signals [CLIENT_CONNECTED] =
                g_signal_new ("client-connected",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, client_connected),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_CREDENTIALS,
                              G_TYPE_UINT);

        signals [CLIENT_DISCONNECTED] =
                g_signal_new ("client-disconnected",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, client_disconnected),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_CREDENTIALS,
                              G_TYPE_UINT);
        signals [CLIENT_READY_FOR_SESSION_TO_START] =
                g_signal_new ("client-ready-for-session-to-start",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, client_ready_for_session_to_start),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              2,
                              G_TYPE_STRING,
                              G_TYPE_BOOLEAN);

        signals [HOSTNAME_SELECTED] =
                g_signal_new ("hostname-selected",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, disconnected),
                              NULL,
                              NULL,
                              NULL,
                              G_TYPE_NONE,
                              1,
                              G_TYPE_STRING);
        signals [DISCONNECTED] =
                g_signal_new ("disconnected",
                              GDM_TYPE_SESSION,
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GdmSessionClass, disconnected),
                              NULL,
                              NULL,
                              g_cclosure_marshal_VOID__VOID,
                              G_TYPE_NONE,
                              0);

        g_object_class_install_property (object_class,
                                         PROP_VERIFICATION_MODE,
                                         g_param_spec_enum ("verification-mode",
                                                            "verification mode",
                                                            "verification mode",
                                                            GDM_TYPE_SESSION_VERIFICATION_MODE,
                                                            GDM_SESSION_VERIFICATION_MODE_LOGIN,
                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        g_object_class_install_property (object_class,
                                         PROP_ALLOWED_USER,
                                         g_param_spec_uint ("allowed-user",
                                                            "allowed user",
                                                            "allowed user ",
                                                            0,
                                                            G_MAXUINT,
                                                            0,
                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        g_object_class_install_property (object_class,
                                         PROP_CONVERSATION_ENVIRONMENT,
                                         g_param_spec_pointer ("conversation-environment",
                                                               "conversation environment",
                                                               "conversation environment",
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

        g_object_class_install_property (object_class,
                                         PROP_SESSION_TYPE,
                                         g_param_spec_string ("session-type",
                                                              NULL,
                                                              NULL,
                                                              NULL,
                                                              G_PARAM_READWRITE));
        g_object_class_install_property (object_class,
                                         PROP_DISPLAY_NAME,
                                         g_param_spec_string ("display-name",
                                                              "display name",
                                                              "display name",
                                                              NULL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
        g_object_class_install_property (object_class,
                                         PROP_DISPLAY_HOSTNAME,
                                         g_param_spec_string ("display-hostname",
                                                              "display hostname",
                                                              "display hostname",
                                                              NULL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        g_object_class_install_property (object_class,
                                         PROP_DISPLAY_IS_LOCAL,
                                         g_param_spec_boolean ("display-is-local",
                                                               "display is local",
                                                               "display is local",
                                                               TRUE,
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        g_object_class_install_property (object_class,
                                         PROP_DISPLAY_IS_INITIAL,
                                         g_param_spec_boolean ("display-is-initial",
                                                               "display is initial",
                                                               "display is initial",
                                                               FALSE,
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
        g_object_class_install_property (object_class,
                                         PROP_DISPLAY_X11_AUTHORITY_FILE,
                                         g_param_spec_string ("display-x11-authority-file",
                                                              "display x11 authority file",
                                                              "display x11 authority file",
                                                              NULL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        /* not construct only */
        g_object_class_install_property (object_class,
                                         PROP_USER_X11_AUTHORITY_FILE,
                                         g_param_spec_string ("user-x11-authority-file",
                                                              "",
                                                              "",
                                                              NULL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
        g_object_class_install_property (object_class,
                                         PROP_DISPLAY_DEVICE,
                                         g_param_spec_string ("display-device",
                                                              "display device",
                                                              "display device",
                                                              NULL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

        g_object_class_install_property (object_class,
                                         PROP_DISPLAY_SEAT_ID,
                                         g_param_spec_string ("display-seat-id",
                                                              "display seat id",
                                                              "display seat id",
                                                              NULL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

#ifdef ENABLE_WAYLAND_SUPPORT
        g_object_class_install_property (object_class,
                                         PROP_IGNORE_WAYLAND,
                                         g_param_spec_boolean ("ignore-wayland",
                                                               "ignore wayland",
                                                               "ignore wayland",
                                                               FALSE,
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
#endif
}

GdmSession *
gdm_session_new (GdmSessionVerificationMode  verification_mode,
                 uid_t                       allowed_user,
                 const char                 *display_name,
                 const char                 *display_hostname,
                 const char                 *display_device,
                 const char                 *display_seat_id,
                 const char                 *display_x11_authority_file,
                 gboolean                    display_is_local,
                 const char * const         *environment)
{
        GdmSession *self;

        self = g_object_new (GDM_TYPE_SESSION,
                             "verification-mode", verification_mode,
                             "allowed-user", (guint) allowed_user,
                             "display-name", display_name,
                             "display-hostname", display_hostname,
                             "display-device", display_device,
                             "display-seat-id", display_seat_id,
                             "display-x11-authority-file", display_x11_authority_file,
                             "display-is-local", display_is_local,
                             "conversation-environment", environment,
                             NULL);

        return self;
}

GdmSessionDisplayMode
gdm_session_display_mode_from_string (const char *str)
{
        if (strcmp (str, "reuse-vt") == 0)
                return GDM_SESSION_DISPLAY_MODE_REUSE_VT;
        if (strcmp (str, "new-vt") == 0)
                return GDM_SESSION_DISPLAY_MODE_NEW_VT;
        if (strcmp (str, "logind-managed") == 0)
                return GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED;

        g_warning ("Unknown GdmSessionDisplayMode %s", str);
        return -1;
}

const char *
gdm_session_display_mode_to_string (GdmSessionDisplayMode mode)
{
        switch (mode) {
        case GDM_SESSION_DISPLAY_MODE_REUSE_VT:
                return "reuse-vt";
        case GDM_SESSION_DISPLAY_MODE_NEW_VT:
                return "new-vt";
        case GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED:
                return "logind-managed";
        default:
                break;
        }

        g_warning ("Unknown GdmSessionDisplayMode %d", mode);
        return "";
}

GPid
gdm_session_get_pid (GdmSession *session)
{
        return session->priv->session_pid;
}
