diff options
Diffstat (limited to 'daemon/gdm-session-worker.c')
-rw-r--r-- | daemon/gdm-session-worker.c | 3642 |
1 files changed, 3642 insertions, 0 deletions
diff --git a/daemon/gdm-session-worker.c b/daemon/gdm-session-worker.c new file mode 100644 index 0000000..36c3e5d --- /dev/null +++ b/daemon/gdm-session-worker.c @@ -0,0 +1,3642 @@ +/* -*- 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <sys/vt.h> +#include <sys/kd.h> +#include <errno.h> +#include <grp.h> +#include <pwd.h> + +#include <security/pam_appl.h> + +#ifdef HAVE_LOGINCAP +#include <login_cap.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> +#include <gio/gio.h> + +#include <X11/Xauth.h> + +#include <systemd/sd-daemon.h> + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include <systemd/sd-journal.h> +#endif + +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif /* HAVE_SELINUX */ + +#include "gdm-common.h" +#include "gdm-log.h" + +#ifdef SUPPORTS_PAM_EXTENSIONS +#include "gdm-pam-extensions.h" +#endif + +#include "gdm-dbus-glue.h" +#include "gdm-session-worker.h" +#include "gdm-session-glue.h" +#include "gdm-session.h" + +#if defined (HAVE_ADT) +#include "gdm-session-solaris-auditor.h" +#elif defined (HAVE_LIBAUDIT) +#include "gdm-session-linux-auditor.h" +#else +#include "gdm-session-auditor.h" +#endif + +#include "gdm-session-settings.h" + +#define GDM_SESSION_WORKER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_SESSION_WORKER, GdmSessionWorkerPrivate)) + +#define GDM_SESSION_DBUS_PATH "/org/gnome/DisplayManager/Session" +#define GDM_SESSION_DBUS_NAME "org.gnome.DisplayManager.Session" +#define GDM_SESSION_DBUS_ERROR_CANCEL "org.gnome.DisplayManager.Session.Error.Cancel" + +#define GDM_WORKER_DBUS_PATH "/org/gnome/DisplayManager/Worker" + +#ifndef GDM_PASSWD_AUXILLARY_BUFFER_SIZE +#define GDM_PASSWD_AUXILLARY_BUFFER_SIZE 1024 +#endif + +#ifndef GDM_SESSION_DEFAULT_PATH +#define GDM_SESSION_DEFAULT_PATH "/usr/local/bin:/usr/bin:/bin" +#endif + +#ifndef GDM_SESSION_ROOT_UID +#define GDM_SESSION_ROOT_UID 0 +#endif + +#ifndef GDM_SESSION_LOG_FILENAME +#define GDM_SESSION_LOG_FILENAME "session.log" +#endif + +#define MAX_FILE_SIZE 65536 +#define MAX_LOGS 5 + +#define RELEASE_DISPLAY_SIGNAL (SIGRTMAX) +#define ACQUIRE_DISPLAY_SIGNAL (SIGRTMAX - 1) + +typedef struct +{ + GdmSessionWorker *worker; + GdmSession *session; + GPid pid_of_caller; + uid_t uid_of_caller; + +} ReauthenticationRequest; + +struct GdmSessionWorkerPrivate +{ + GdmSessionWorkerState state; + + int exit_code; + + pam_handle_t *pam_handle; + + GPid child_pid; + guint child_watch_id; + + /* from Setup */ + char *service; + char *x11_display_name; + char *x11_authority_file; + char *display_device; + char *display_seat_id; + char *hostname; + char *username; + char *log_file; + char *session_id; + uid_t uid; + gid_t gid; + gboolean password_is_required; + char **extensions; + + int cred_flags; + int session_vt; + int session_tty_fd; + + char **arguments; + guint32 cancelled : 1; + guint32 timed_out : 1; + guint32 is_program_session : 1; + guint32 is_reauth_session : 1; + guint32 display_is_local : 1; + guint32 display_is_initial : 1; + guint state_change_idle_id; + GdmSessionDisplayMode display_mode; + + char *server_address; + GDBusConnection *connection; + GdmDBusWorkerManager *manager; + + GHashTable *reauthentication_requests; + + GdmSessionAuditor *auditor; + GdmSessionSettings *user_settings; + + GDBusMethodInvocation *pending_invocation; +}; + +#ifdef SUPPORTS_PAM_EXTENSIONS +static char gdm_pam_extension_environment_block[_POSIX_ARG_MAX]; + +static const char * const +gdm_supported_pam_extensions[] = { + GDM_PAM_EXTENSION_CHOICE_LIST, + NULL +}; +#endif + +enum { + PROP_0, + PROP_SERVER_ADDRESS, + PROP_IS_REAUTH_SESSION, + PROP_STATE, +}; + +static void gdm_session_worker_class_init (GdmSessionWorkerClass *klass); +static void gdm_session_worker_init (GdmSessionWorker *session_worker); +static void gdm_session_worker_finalize (GObject *object); + +static void gdm_session_worker_set_environment_variable (GdmSessionWorker *worker, + const char *key, + const char *value); + +static void queue_state_change (GdmSessionWorker *worker); + +static void worker_interface_init (GdmDBusWorkerIface *iface); + + +typedef int (* GdmSessionWorkerPamNewMessagesFunc) (int, + const struct pam_message **, + struct pam_response **, + gpointer); + +G_DEFINE_TYPE_WITH_CODE (GdmSessionWorker, + gdm_session_worker, + GDM_DBUS_TYPE_WORKER_SKELETON, + G_IMPLEMENT_INTERFACE (GDM_DBUS_TYPE_WORKER, + worker_interface_init) + G_ADD_PRIVATE (GdmSessionWorker)) + +/* adapted from glib script_execute */ +static void +script_execute (const gchar *file, + char **argv, + char **envp, + gboolean search_path) +{ + /* Count the arguments. */ + int argc = 0; + + while (argv[argc]) { + ++argc; + } + + /* Construct an argument list for the shell. */ + { + char **new_argv; + + new_argv = g_new0 (gchar*, argc + 2); /* /bin/sh and NULL */ + + new_argv[0] = (char *) "/bin/sh"; + new_argv[1] = (char *) file; + while (argc > 0) { + new_argv[argc + 1] = argv[argc]; + --argc; + } + + /* Execute the shell. */ + if (envp) { + execve (new_argv[0], new_argv, envp); + } else { + execv (new_argv[0], new_argv); + } + + g_free (new_argv); + } +} + +static char * +my_strchrnul (const char *str, char c) +{ + char *p = (char*) str; + while (*p && (*p != c)) { + ++p; + } + + return p; +} + +/* adapted from glib g_execute */ +static gint +gdm_session_execute (const char *file, + char **argv, + char **envp, + gboolean search_path) +{ + if (*file == '\0') { + /* We check the simple case first. */ + errno = ENOENT; + return -1; + } + + if (!search_path || strchr (file, '/') != NULL) { + /* Don't search when it contains a slash. */ + if (envp) { + execve (file, argv, envp); + } else { + execv (file, argv); + } + + if (errno == ENOEXEC) { + script_execute (file, argv, envp, FALSE); + } + } else { + gboolean got_eacces = 0; + const char *path, *p; + char *name, *freeme; + gsize len; + gsize pathlen; + + path = g_getenv ("PATH"); + if (path == NULL) { + /* There is no `PATH' in the environment. The default + * search path in libc is the current directory followed by + * the path `confstr' returns for `_CS_PATH'. + */ + + /* In GLib we put . last, for security, and don't use the + * unportable confstr(); UNIX98 does not actually specify + * what to search if PATH is unset. POSIX may, dunno. + */ + + path = "/bin:/usr/bin:."; + } + + len = strlen (file) + 1; + pathlen = strlen (path); + freeme = name = g_malloc (pathlen + len + 1); + + /* Copy the file name at the top, including '\0' */ + memcpy (name + pathlen + 1, file, len); + name = name + pathlen; + /* And add the slash before the filename */ + *name = '/'; + + p = path; + do { + char *startp; + + path = p; + p = my_strchrnul (path, ':'); + + if (p == path) { + /* Two adjacent colons, or a colon at the beginning or the end + * of `PATH' means to search the current directory. + */ + startp = name + 1; + } else { + startp = memcpy (name - (p - path), path, p - path); + } + + /* Try to execute this name. If it works, execv will not return. */ + if (envp) { + execve (startp, argv, envp); + } else { + execv (startp, argv); + } + + if (errno == ENOEXEC) { + script_execute (startp, argv, envp, search_path); + } + + switch (errno) { + case EACCES: + /* Record the we got a `Permission denied' error. If we end + * up finding no executable we can use, we want to diagnose + * that we did find one but were denied access. + */ + got_eacces = TRUE; + + /* FALL THRU */ + + case ENOENT: +#ifdef ESTALE + case ESTALE: +#endif +#ifdef ENOTDIR + case ENOTDIR: +#endif + /* Those errors indicate the file is missing or not executable + * by us, in which case we want to just try the next path + * directory. + */ + break; + + default: + /* Some other error means we found an executable file, but + * something went wrong executing it; return the error to our + * caller. + */ + g_free (freeme); + return -1; + } + } while (*p++ != '\0'); + + /* We tried every element and none of them worked. */ + if (got_eacces) { + /* At least one failure was due to permissions, so report that + * error. + */ + errno = EACCES; + } + + g_free (freeme); + } + + /* Return the error from the last attempt (probably ENOENT). */ + return -1; +} + +/* + * This function is called with username set to NULL to update the + * auditor username value. + */ +static gboolean +gdm_session_worker_get_username (GdmSessionWorker *worker, + char **username) +{ + gconstpointer item; + + g_assert (worker->priv->pam_handle != NULL); + + if (pam_get_item (worker->priv->pam_handle, PAM_USER, &item) == PAM_SUCCESS) { + if (username != NULL) { + *username = g_strdup ((char *) item); + g_debug ("GdmSessionWorker: username is '%s'", + *username != NULL ? *username : "<unset>"); + } + + if (worker->priv->auditor != NULL) { + gdm_session_auditor_set_username (worker->priv->auditor, (char *)item); + } + + return TRUE; + } + + return FALSE; +} + +static void +attempt_to_load_user_settings (GdmSessionWorker *worker, + const char *username) +{ + g_debug ("GdmSessionWorker: attempting to load user settings"); + gdm_session_settings_load (worker->priv->user_settings, + username); +} + +static void +gdm_session_worker_update_username (GdmSessionWorker *worker) +{ + char *username; + gboolean res; + + username = NULL; + res = gdm_session_worker_get_username (worker, &username); + if (res) { + g_debug ("GdmSessionWorker: old-username='%s' new-username='%s'", + worker->priv->username != NULL ? worker->priv->username : "<unset>", + username != NULL ? username : "<unset>"); + + + gdm_session_auditor_set_username (worker->priv->auditor, worker->priv->username); + + if ((worker->priv->username == username) || + ((worker->priv->username != NULL) && (username != NULL) && + (strcmp (worker->priv->username, username) == 0))) + goto out; + + g_debug ("GdmSessionWorker: setting username to '%s'", username); + + g_free (worker->priv->username); + worker->priv->username = username; + username = NULL; + + gdm_dbus_worker_emit_username_changed (GDM_DBUS_WORKER (worker), + worker->priv->username); + + /* We have a new username to try. If we haven't been able to + * read user settings up until now, then give it a go now + * (see the comment in do_setup for rationale on why it's useful + * to keep trying to read settings) + */ + if (worker->priv->username != NULL && + worker->priv->username[0] != '\0' && + !gdm_session_settings_is_loaded (worker->priv->user_settings)) { + attempt_to_load_user_settings (worker, worker->priv->username); + } + } + + out: + g_free (username); +} + +static gboolean +gdm_session_worker_ask_question (GdmSessionWorker *worker, + const char *question, + char **answerp) +{ + return gdm_dbus_worker_manager_call_info_query_sync (worker->priv->manager, + worker->priv->service, + question, + answerp, + NULL, + NULL); +} + +static gboolean +gdm_session_worker_ask_for_secret (GdmSessionWorker *worker, + const char *question, + char **answerp) +{ + return gdm_dbus_worker_manager_call_secret_info_query_sync (worker->priv->manager, + worker->priv->service, + question, + answerp, + NULL, + NULL); +} + +static gboolean +gdm_session_worker_report_info (GdmSessionWorker *worker, + const char *info) +{ + return gdm_dbus_worker_manager_call_info_sync (worker->priv->manager, + worker->priv->service, + info, + NULL, + NULL); +} + +static gboolean +gdm_session_worker_report_problem (GdmSessionWorker *worker, + const char *problem) +{ + return gdm_dbus_worker_manager_call_problem_sync (worker->priv->manager, + worker->priv->service, + problem, + NULL, + NULL); +} + +#ifdef SUPPORTS_PAM_EXTENSIONS +static gboolean +gdm_session_worker_ask_list_of_choices (GdmSessionWorker *worker, + const char *prompt_message, + GdmChoiceList *list, + char **answerp) +{ + GVariantBuilder builder; + GVariant *choices_as_variant; + GError *error = NULL; + gboolean res; + size_t i; + + g_debug ("GdmSessionWorker: presenting user with list of choices:"); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + + for (i = 0; i < list->number_of_items; i++) { + if (list->items[i].key == NULL) { + g_warning ("choice list contains item with NULL key"); + g_variant_builder_clear (&builder); + return FALSE; + } + g_debug ("GdmSessionWorker: choices['%s'] = \"%s\"", list->items[i].key, list->items[i].text); + g_variant_builder_add (&builder, "{ss}", list->items[i].key, list->items[i].text); + } + g_debug ("GdmSessionWorker: (and waiting for reply)"); + + choices_as_variant = g_variant_builder_end (&builder); + + res = gdm_dbus_worker_manager_call_choice_list_query_sync (worker->priv->manager, + worker->priv->service, + prompt_message, + choices_as_variant, + answerp, + NULL, + &error); + + if (! res) { + g_debug ("GdmSessionWorker: list request failed: %s", error->message); + g_clear_error (&error); + } else { + g_debug ("GdmSessionWorker: user selected '%s'", *answerp); + } + + return res; +} + +static gboolean +gdm_session_worker_process_choice_list_request (GdmSessionWorker *worker, + GdmPamExtensionChoiceListRequest *request, + GdmPamExtensionChoiceListResponse *response) +{ + return gdm_session_worker_ask_list_of_choices (worker, request->prompt_message, &request->list, &response->key); +} + +static gboolean +gdm_session_worker_process_extended_pam_message (GdmSessionWorker *worker, + const struct pam_message *query, + char **response) +{ + GdmPamExtensionMessage *extended_message; + gboolean res; + + extended_message = GDM_PAM_EXTENSION_MESSAGE_FROM_PAM_MESSAGE (query); + + if (GDM_PAM_EXTENSION_MESSAGE_TRUNCATED (extended_message)) { + g_warning ("PAM service requested binary response for truncated query"); + return FALSE; + } + + if (GDM_PAM_EXTENSION_MESSAGE_INVALID_TYPE (extended_message)) { + g_warning ("PAM service requested binary response for unadvertised query type"); + return FALSE; + } + + if (GDM_PAM_EXTENSION_MESSAGE_MATCH (extended_message, worker->priv->extensions, GDM_PAM_EXTENSION_CHOICE_LIST)) { + GdmPamExtensionChoiceListRequest *list_request = (GdmPamExtensionChoiceListRequest *) extended_message; + GdmPamExtensionChoiceListResponse *list_response = malloc (GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_SIZE); + + g_debug ("GdmSessionWorker: received extended pam message '%s'", GDM_PAM_EXTENSION_CHOICE_LIST); + + GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_INIT (list_response); + + res = gdm_session_worker_process_choice_list_request (worker, list_request, list_response); + + if (! res) { + g_free (list_response); + return FALSE; + } + + *response = GDM_PAM_EXTENSION_MESSAGE_TO_PAM_REPLY (list_response); + return TRUE; + } else { + g_debug ("GdmSessionWorker: received extended pam message of unknown type %u", (unsigned int) extended_message->type); + return FALSE; + + } + + return TRUE; +} +#endif + +static char * +convert_to_utf8 (const char *str) +{ + char *utf8; + utf8 = g_locale_to_utf8 (str, + -1, + NULL, + NULL, + NULL); + + /* if we couldn't convert text from locale then + * assume utf-8 and hope for the best */ + if (utf8 == NULL) { + char *p; + char *q; + + utf8 = g_strdup (str); + + p = utf8; + while (*p != '\0' && !g_utf8_validate ((const char *)p, -1, (const char **)&q)) { + *q = '?'; + p = q + 1; + } + } + + return utf8; +} + +static gboolean +gdm_session_worker_process_pam_message (GdmSessionWorker *worker, + const struct pam_message *query, + char **response) +{ + char *user_answer; + gboolean res; + char *utf8_msg; + char *msg; + + if (response != NULL) { + *response = NULL; + } + + gdm_session_worker_update_username (worker); + +#ifdef SUPPORTS_PAM_EXTENSIONS + if (query->msg_style == PAM_BINARY_PROMPT) + return gdm_session_worker_process_extended_pam_message (worker, query, response); +#endif + + g_debug ("GdmSessionWorker: received pam message of type %u with payload '%s'", + query->msg_style, query->msg); + + utf8_msg = convert_to_utf8 (query->msg); + + worker->priv->cancelled = FALSE; + worker->priv->timed_out = FALSE; + + user_answer = NULL; + res = FALSE; + switch (query->msg_style) { + case PAM_PROMPT_ECHO_ON: + res = gdm_session_worker_ask_question (worker, utf8_msg, &user_answer); + break; + case PAM_PROMPT_ECHO_OFF: + res = gdm_session_worker_ask_for_secret (worker, utf8_msg, &user_answer); + break; + case PAM_TEXT_INFO: + res = gdm_session_worker_report_info (worker, utf8_msg); + break; + case PAM_ERROR_MSG: + res = gdm_session_worker_report_problem (worker, utf8_msg); + break; +#ifdef PAM_RADIO_TYPE + case PAM_RADIO_TYPE: + msg = g_strdup_printf ("%s (yes/no)", utf8_msg); + res = gdm_session_worker_ask_question (worker, msg, &user_answer); + g_free (msg); + break; +#endif + default: + res = FALSE; + g_warning ("Unknown and unhandled message type %d\n", + query->msg_style); + + break; + } + + if (worker->priv->timed_out) { + gdm_dbus_worker_emit_cancel_pending_query (GDM_DBUS_WORKER (worker)); + worker->priv->timed_out = FALSE; + } + + if (user_answer != NULL) { + /* we strndup and g_free to make sure we return malloc'd + * instead of g_malloc'd memory. PAM_MAX_RESP_SIZE includes + * the '\0' terminating character, thus the "- 1". + */ + if (res && response != NULL) { + *response = strndup (user_answer, PAM_MAX_RESP_SIZE - 1); + } + + memset (user_answer, '\0', strlen (user_answer)); + g_free (user_answer); + + g_debug ("GdmSessionWorker: trying to get updated username"); + + res = TRUE; + } + + g_free (utf8_msg); + + return res; +} + +static const char * +get_friendly_error_message (int error_code) +{ + switch (error_code) { + case PAM_SUCCESS: + case PAM_IGNORE: + return ""; + break; + + case PAM_ACCT_EXPIRED: + case PAM_AUTHTOK_EXPIRED: + return _("Your account was given a time limit that’s now passed."); + break; + + default: + break; + } + + return _("Sorry, that didn’t work. Please try again."); +} + +static int +gdm_session_worker_pam_new_messages_handler (int number_of_messages, + const struct pam_message **messages, + struct pam_response **responses, + GdmSessionWorker *worker) +{ + struct pam_response *replies; + int return_value; + int i; + + g_debug ("GdmSessionWorker: %d new messages received from PAM\n", number_of_messages); + + return_value = PAM_CONV_ERR; + + if (number_of_messages < 0) { + return PAM_CONV_ERR; + } + + if (number_of_messages == 0) { + if (responses) { + *responses = NULL; + } + + return PAM_SUCCESS; + } + + /* we want to generate one reply for every question + */ + replies = (struct pam_response *) calloc (number_of_messages, + sizeof (struct pam_response)); + for (i = 0; i < number_of_messages; i++) { + gboolean got_response; + char *response; + + response = NULL; + got_response = gdm_session_worker_process_pam_message (worker, + messages[i], + &response); + if (!got_response) { + goto out; + } + + replies[i].resp = response; + replies[i].resp_retcode = PAM_SUCCESS; + } + + return_value = PAM_SUCCESS; + + out: + if (return_value != PAM_SUCCESS) { + for (i = 0; i < number_of_messages; i++) { + if (replies[i].resp != NULL) { + memset (replies[i].resp, 0, strlen (replies[i].resp)); + free (replies[i].resp); + } + memset (&replies[i], 0, sizeof (replies[i])); + } + free (replies); + replies = NULL; + } + + if (responses) { + *responses = replies; + } + + g_debug ("GdmSessionWorker: PAM conversation returning %d: %s", + return_value, + pam_strerror (worker->priv->pam_handle, return_value)); + + return return_value; +} + +static void +gdm_session_worker_start_auditor (GdmSessionWorker *worker) +{ + /* Use dummy auditor so program session doesn't pollute user audit logs + */ + if (worker->priv->is_program_session) { + worker->priv->auditor = gdm_session_auditor_new (worker->priv->hostname, + worker->priv->display_device); + return; + } + +/* FIXME: it may make sense at some point to keep a list of + * auditors, instead of assuming they are mutually exclusive + */ +#if defined (HAVE_ADT) + worker->priv->auditor = gdm_session_solaris_auditor_new (worker->priv->hostname, + worker->priv->display_device); +#elif defined (HAVE_LIBAUDIT) + worker->priv->auditor = gdm_session_linux_auditor_new (worker->priv->hostname, + worker->priv->display_device); +#else + worker->priv->auditor = gdm_session_auditor_new (worker->priv->hostname, + worker->priv->display_device); +#endif +} + +static void +gdm_session_worker_stop_auditor (GdmSessionWorker *worker) +{ + g_object_unref (worker->priv->auditor); + worker->priv->auditor = NULL; +} + +static void +on_release_display (int signal) +{ + int fd; + + fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); + ioctl(fd, VT_RELDISP, 1); + close(fd); +} + +static void +on_acquire_display (int signal) +{ + int fd; + + fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); + ioctl(fd, VT_RELDISP, VT_ACKACQ); + close(fd); +} + +static gboolean +handle_terminal_vt_switches (GdmSessionWorker *worker, + int tty_fd) +{ + struct vt_mode setmode_request = { 0 }; + gboolean succeeded = TRUE; + + setmode_request.mode = VT_PROCESS; + setmode_request.relsig = RELEASE_DISPLAY_SIGNAL; + setmode_request.acqsig = ACQUIRE_DISPLAY_SIGNAL; + + if (ioctl (tty_fd, VT_SETMODE, &setmode_request) < 0) { + g_debug ("GdmSessionWorker: couldn't manage VTs manually: %m"); + succeeded = FALSE; + } + + signal (RELEASE_DISPLAY_SIGNAL, on_release_display); + signal (ACQUIRE_DISPLAY_SIGNAL, on_acquire_display); + + return succeeded; +} + +static void +fix_terminal_vt_mode (GdmSessionWorker *worker, + int tty_fd) +{ + struct vt_mode getmode_reply = { 0 }; + int kernel_display_mode = 0; + gboolean mode_fixed = FALSE; + gboolean succeeded = TRUE; + + if (ioctl (tty_fd, VT_GETMODE, &getmode_reply) < 0) { + g_debug ("GdmSessionWorker: couldn't query VT mode: %m"); + succeeded = FALSE; + } + + if (getmode_reply.mode != VT_AUTO) { + goto out; + } + + if (ioctl (tty_fd, KDGETMODE, &kernel_display_mode) < 0) { + g_debug ("GdmSessionWorker: couldn't query kernel display mode: %m"); + succeeded = FALSE; + } + + if (kernel_display_mode == KD_TEXT) { + goto out; + } + + /* VT is in the anti-social state of VT_AUTO + KD_GRAPHICS, + * fix it. + */ + succeeded = handle_terminal_vt_switches (worker, tty_fd); + mode_fixed = TRUE; +out: + if (!succeeded) { + g_error ("GdmSessionWorker: couldn't set up terminal, aborting..."); + return; + } + + g_debug ("GdmSessionWorker: VT mode did %sneed to be fixed", + mode_fixed? "" : "not "); +} + +static void +jump_to_vt (GdmSessionWorker *worker, + int vt_number) +{ + int fd; + int active_vt_tty_fd; + int active_vt = -1; + struct vt_stat vt_state = { 0 }; + + g_debug ("GdmSessionWorker: jumping to VT %d", vt_number); + active_vt_tty_fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); + + if (worker->priv->session_tty_fd != -1) { + static const char *clear_screen_escape_sequence = "\33[H\33[2J"; + + /* let's make sure the new VT is clear */ + write (worker->priv->session_tty_fd, + clear_screen_escape_sequence, + sizeof (clear_screen_escape_sequence)); + + fd = worker->priv->session_tty_fd; + + g_debug ("GdmSessionWorker: first setting graphics mode to prevent flicker"); + if (ioctl (fd, KDSETMODE, KD_GRAPHICS) < 0) { + g_debug ("GdmSessionWorker: couldn't set graphics mode: %m"); + } + + /* It's possible that the current VT was left in a broken + * combination of states (KD_GRAPHICS with VT_AUTO), that + * can't be switched away from. This call makes sure things + * are set in a way that VT_ACTIVATE should work and + * VT_WAITACTIVE shouldn't hang. + */ + fix_terminal_vt_mode (worker, active_vt_tty_fd); + } else { + fd = active_vt_tty_fd; + } + + handle_terminal_vt_switches (worker, fd); + + if (ioctl (fd, VT_GETSTATE, &vt_state) < 0) { + g_debug ("GdmSessionWorker: couldn't get current VT: %m"); + } else { + active_vt = vt_state.v_active; + } + + if (active_vt != vt_number) { + if (ioctl (fd, VT_ACTIVATE, vt_number) < 0) { + g_debug ("GdmSessionWorker: couldn't initiate jump to VT %d: %m", + vt_number); + } else if (ioctl (fd, VT_WAITACTIVE, vt_number) < 0) { + g_debug ("GdmSessionWorker: couldn't finalize jump to VT %d: %m", + vt_number); + } + } + + close (active_vt_tty_fd); +} + +static void +gdm_session_worker_set_state (GdmSessionWorker *worker, + GdmSessionWorkerState state) +{ + if (worker->priv->state == state) + return; + + worker->priv->state = state; + g_object_notify (G_OBJECT (worker), "state"); +} + +static void +gdm_session_worker_uninitialize_pam (GdmSessionWorker *worker, + int status) +{ + g_debug ("GdmSessionWorker: uninitializing PAM"); + + if (worker->priv->pam_handle == NULL) + return; + + gdm_session_worker_get_username (worker, NULL); + + if (worker->priv->state >= GDM_SESSION_WORKER_STATE_SESSION_OPENED) { + pam_close_session (worker->priv->pam_handle, 0); + gdm_session_auditor_report_logout (worker->priv->auditor); + } else { + gdm_session_auditor_report_login_failure (worker->priv->auditor, + status, + pam_strerror (worker->priv->pam_handle, status)); + } + + if (worker->priv->state >= GDM_SESSION_WORKER_STATE_ACCREDITED) { + pam_setcred (worker->priv->pam_handle, PAM_DELETE_CRED); + } + + pam_end (worker->priv->pam_handle, status); + worker->priv->pam_handle = NULL; + + gdm_session_worker_stop_auditor (worker); + + worker->priv->session_vt = 0; + + g_debug ("GdmSessionWorker: state NONE"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_NONE); +} + +static char * +_get_tty_for_pam (const char *x11_display_name, + const char *display_device) +{ +#ifdef __sun + return g_strdup (display_device); +#else + return g_strdup (x11_display_name); +#endif +} + +#ifdef PAM_XAUTHDATA +static struct pam_xauth_data * +_get_xauth_for_pam (const char *x11_authority_file) +{ + FILE *fh; + Xauth *auth = NULL; + struct pam_xauth_data *retval = NULL; + gsize len = sizeof (*retval) + 1; + + fh = fopen (x11_authority_file, "r"); + if (fh) { + auth = XauReadAuth (fh); + fclose (fh); + } + if (auth) { + len += auth->name_length + auth->data_length; + retval = g_malloc0 (len); + } + if (retval) { + retval->namelen = auth->name_length; + retval->name = (char *) (retval + 1); + memcpy (retval->name, auth->name, auth->name_length); + retval->datalen = auth->data_length; + retval->data = retval->name + auth->name_length + 1; + memcpy (retval->data, auth->data, auth->data_length); + } + XauDisposeAuth (auth); + return retval; +} +#endif + +static gboolean +gdm_session_worker_initialize_pam (GdmSessionWorker *worker, + const char *service, + const char * const *extensions, + const char *username, + const char *hostname, + gboolean display_is_local, + const char *x11_display_name, + const char *x11_authority_file, + const char *display_device, + const char *seat_id, + GError **error) +{ + struct pam_conv pam_conversation; + int error_code; + char tty_string[256]; + + g_assert (worker->priv->pam_handle == NULL); + + g_debug ("GdmSessionWorker: initializing PAM; service=%s username=%s seat=%s", + service ? service : "(null)", + username ? username : "(null)", + seat_id ? seat_id : "(null)"); + +#ifdef SUPPORTS_PAM_EXTENSIONS + if (extensions != NULL) { + GDM_PAM_EXTENSION_ADVERTISE_SUPPORTED_EXTENSIONS (gdm_pam_extension_environment_block, extensions); + } +#endif + + pam_conversation.conv = (GdmSessionWorkerPamNewMessagesFunc) gdm_session_worker_pam_new_messages_handler; + pam_conversation.appdata_ptr = worker; + + gdm_session_worker_start_auditor (worker); + error_code = pam_start (service, + username, + &pam_conversation, + &worker->priv->pam_handle); + if (error_code != PAM_SUCCESS) { + g_debug ("GdmSessionWorker: could not initialize PAM: (error code %d)", error_code); + /* we don't use pam_strerror here because it requires a valid + * pam handle, and if pam_start fails pam_handle is undefined + */ + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE, + "%s", ""); + + goto out; + } + + /* set USER PROMPT */ + if (username == NULL) { + error_code = pam_set_item (worker->priv->pam_handle, PAM_USER_PROMPT, _("Username:")); + + if (error_code != PAM_SUCCESS) { + g_debug ("GdmSessionWorker: error informing authentication system of preferred username prompt: %s", + pam_strerror (worker->priv->pam_handle, error_code)); + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + } + + /* set RHOST */ + if (hostname != NULL && hostname[0] != '\0') { + error_code = pam_set_item (worker->priv->pam_handle, PAM_RHOST, hostname); + g_debug ("error informing authentication system of user's hostname %s: %s", + hostname, + pam_strerror (worker->priv->pam_handle, error_code)); + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + } + + /* set seat ID */ + if (seat_id != NULL && seat_id[0] != '\0') { + gdm_session_worker_set_environment_variable (worker, "XDG_SEAT", seat_id); + } + + if (strcmp (service, "gdm-launch-environment") == 0) { + gdm_session_worker_set_environment_variable (worker, "XDG_SESSION_CLASS", "greeter"); + } + + g_debug ("GdmSessionWorker: state SETUP_COMPLETE"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE); + + /* Temporarily set PAM_TTY with the login VT, + PAM_TTY will be reset with the users VT right before the user session is opened */ + g_snprintf (tty_string, 256, "/dev/tty%d", GDM_INITIAL_VT); + pam_set_item (worker->priv->pam_handle, PAM_TTY, tty_string); + if (!display_is_local) + worker->priv->password_is_required = TRUE; + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static gboolean +gdm_session_worker_authenticate_user (GdmSessionWorker *worker, + gboolean password_is_required, + GError **error) +{ + int error_code; + int authentication_flags; + + g_debug ("GdmSessionWorker: authenticating user %s", worker->priv->username); + + authentication_flags = 0; + + if (password_is_required) { + authentication_flags |= PAM_DISALLOW_NULL_AUTHTOK; + } + + /* blocking call, does the actual conversation */ + error_code = pam_authenticate (worker->priv->pam_handle, authentication_flags); + + if (error_code == PAM_AUTHINFO_UNAVAIL) { + g_debug ("GdmSessionWorker: authentication service unavailable"); + + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE, + "%s", ""); + goto out; + } else if (error_code != PAM_SUCCESS) { + g_debug ("GdmSessionWorker: authentication returned %d: %s", error_code, pam_strerror (worker->priv->pam_handle, error_code)); + + /* + * Do not display a different message for user unknown versus + * a failed password for a valid user. + */ + if (error_code == PAM_USER_UNKNOWN) { + error_code = PAM_AUTH_ERR; + } + + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", get_friendly_error_message (error_code)); + goto out; + } + + g_debug ("GdmSessionWorker: state AUTHENTICATED"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_AUTHENTICATED); + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static gboolean +gdm_session_worker_authorize_user (GdmSessionWorker *worker, + gboolean password_is_required, + GError **error) +{ + int error_code; + int authentication_flags; + + g_debug ("GdmSessionWorker: determining if authenticated user (password required:%d) is authorized to session", + password_is_required); + + authentication_flags = 0; + + if (password_is_required) { + authentication_flags |= PAM_DISALLOW_NULL_AUTHTOK; + } + + /* check that the account isn't disabled or expired + */ + error_code = pam_acct_mgmt (worker->priv->pam_handle, authentication_flags); + + /* it's possible that the user needs to change their password or pin code + */ + if (error_code == PAM_NEW_AUTHTOK_REQD && !worker->priv->is_program_session) { + g_debug ("GdmSessionWorker: authenticated user requires new auth token"); + error_code = pam_chauthtok (worker->priv->pam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); + + gdm_session_worker_get_username (worker, NULL); + + if (error_code != PAM_SUCCESS) { + gdm_session_auditor_report_password_change_failure (worker->priv->auditor); + } else { + gdm_session_auditor_report_password_changed (worker->priv->auditor); + } + } + + /* If the user is reauthenticating, then authorization isn't required to + * proceed, the user is already logged in after all. + */ + if (worker->priv->is_reauth_session) { + error_code = PAM_SUCCESS; + } + + if (error_code != PAM_SUCCESS) { + g_debug ("GdmSessionWorker: user is not authorized to log in: %s", + pam_strerror (worker->priv->pam_handle, error_code)); + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHORIZING, + "%s", get_friendly_error_message (error_code)); + goto out; + } + + g_debug ("GdmSessionWorker: state AUTHORIZED"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_AUTHORIZED); + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static void +gdm_session_worker_set_environment_variable (GdmSessionWorker *worker, + const char *key, + const char *value) +{ + int error_code; + char *environment_entry; + + if (value != NULL) { + environment_entry = g_strdup_printf ("%s=%s", key, value); + } else { + /* empty value means "remove from environment" */ + environment_entry = g_strdup (key); + } + + error_code = pam_putenv (worker->priv->pam_handle, + environment_entry); + + if (error_code != PAM_SUCCESS) { + g_warning ("cannot put %s in pam environment: %s\n", + environment_entry, + pam_strerror (worker->priv->pam_handle, error_code)); + } + g_debug ("GdmSessionWorker: Set PAM environment variable: '%s'", environment_entry); + g_free (environment_entry); +} + +static char * +gdm_session_worker_get_environment_variable (GdmSessionWorker *worker, + const char *key) +{ + return g_strdup (pam_getenv (worker->priv->pam_handle, key)); +} + +static void +gdm_session_worker_update_environment_from_passwd_info (GdmSessionWorker *worker, + uid_t uid, + gid_t gid, + const char *home, + const char *shell) +{ + gdm_session_worker_set_environment_variable (worker, "LOGNAME", worker->priv->username); + gdm_session_worker_set_environment_variable (worker, "USER", worker->priv->username); + gdm_session_worker_set_environment_variable (worker, "USERNAME", worker->priv->username); + gdm_session_worker_set_environment_variable (worker, "HOME", home); + gdm_session_worker_set_environment_variable (worker, "PWD", home); + gdm_session_worker_set_environment_variable (worker, "SHELL", shell); +} + +static gboolean +gdm_session_worker_environment_variable_is_set (GdmSessionWorker *worker, + const char *key) +{ + return pam_getenv (worker->priv->pam_handle, key) != NULL; +} + +static gboolean +_change_user (GdmSessionWorker *worker, + uid_t uid, + gid_t gid) +{ +#ifdef THE_MAN_PAGE_ISNT_LYING + /* pam_setcred wants to be called as the authenticated user + * but pam_open_session needs to be called as super-user. + * + * Set the real uid and gid to the user and give the user a + * temporary super-user effective id. + */ + if (setreuid (uid, GDM_SESSION_ROOT_UID) < 0) { + return FALSE; + } +#endif + worker->priv->uid = uid; + worker->priv->gid = gid; + + if (setgid (gid) < 0) { + return FALSE; + } + + if (initgroups (worker->priv->username, gid) < 0) { + return FALSE; + } + + return TRUE; +} + +static gboolean +_lookup_passwd_info (const char *username, + uid_t *uidp, + gid_t *gidp, + char **homep, + char **shellp) +{ + gboolean ret; + struct passwd *passwd_entry; + struct passwd passwd_buffer; + char *aux_buffer; + long required_aux_buffer_size; + gsize aux_buffer_size; + + ret = FALSE; + aux_buffer = NULL; + aux_buffer_size = 0; + + required_aux_buffer_size = sysconf (_SC_GETPW_R_SIZE_MAX); + + if (required_aux_buffer_size < 0) { + aux_buffer_size = GDM_PASSWD_AUXILLARY_BUFFER_SIZE; + } else { + aux_buffer_size = (gsize) required_aux_buffer_size; + } + + aux_buffer = g_slice_alloc0 (aux_buffer_size); + + /* we use the _r variant of getpwnam() + * (with its weird semantics) so that the + * passwd_entry doesn't potentially get stomped on + * by a PAM module + */ + again: + passwd_entry = NULL; +#ifdef HAVE_POSIX_GETPWNAM_R + errno = getpwnam_r (username, + &passwd_buffer, + aux_buffer, + (size_t) aux_buffer_size, + &passwd_entry); +#else + passwd_entry = getpwnam_r (username, + &passwd_buffer, + aux_buffer, + (size_t) aux_buffer_size); + errno = 0; +#endif /* !HAVE_POSIX_GETPWNAM_R */ + if (errno == EINTR) { + g_debug ("%s", g_strerror (errno)); + goto again; + } else if (errno != 0) { + g_warning ("%s", g_strerror (errno)); + goto out; + } + + if (passwd_entry == NULL) { + goto out; + } + + if (uidp != NULL) { + *uidp = passwd_entry->pw_uid; + } + if (gidp != NULL) { + *gidp = passwd_entry->pw_gid; + } + if (homep != NULL) { + if (passwd_entry->pw_dir != NULL && passwd_entry->pw_dir[0] != '\0') { + *homep = g_strdup (passwd_entry->pw_dir); + } else { + *homep = g_strdup ("/"); + } + } + if (shellp != NULL) { + if (passwd_entry->pw_shell != NULL && passwd_entry->pw_shell[0] != '\0') { + *shellp = g_strdup (passwd_entry->pw_shell); + } else { + *shellp = g_strdup ("/bin/bash"); + } + } + ret = TRUE; + out: + if (aux_buffer != NULL) { + g_assert (aux_buffer_size > 0); + g_slice_free1 (aux_buffer_size, aux_buffer); + } + + return ret; +} + +static char * +get_var_cb (const char *key, + gpointer user_data) +{ + return gdm_session_worker_get_environment_variable (user_data, key); +} + +static void +load_env_file (GdmSessionWorker *worker, + GFile *file) +{ + gchar *contents; + gchar **lines; + gchar *line, *p; + gchar *var, *var_end; + gchar *expanded; + char *filename; + int i; + + filename = g_file_get_path (file); + g_debug ("Loading env vars from %s\n", filename); + g_free (filename); + + if (g_file_load_contents (file, NULL, &contents, NULL, NULL, NULL)) { + lines = g_strsplit (contents, "\n", -1); + g_free (contents); + for (i = 0; lines[i] != NULL; i++) { + line = lines[i]; + p = line; + while (g_ascii_isspace (*p)) + p++; + if (*p == '#' || *p == '\0') + continue; + var = p; + while (gdm_shell_var_is_valid_char (*p, p == var)) + p++; + var_end = p; + while (g_ascii_isspace (*p)) + p++; + if (var == var_end || *p != '=') { + g_warning ("Invalid env.d line '%s'\n", line); + continue; + } + *var_end = 0; + p++; /* Skip = */ + while (g_ascii_isspace (*p)) + p++; + + expanded = gdm_shell_expand (p, get_var_cb, worker); + expanded = g_strchomp (expanded); + gdm_session_worker_set_environment_variable (worker, var, expanded); + g_free (expanded); + } + g_strfreev (lines); + } +} + +static gint +compare_str (gconstpointer a, + gconstpointer b) +{ + return strcmp (*(const char **)a, *(const char **)b); +} + +static void +gdm_session_worker_load_env_dir (GdmSessionWorker *worker, + GFile *dir) +{ + GFileInfo *info = NULL; + GFileEnumerator *enumerator = NULL; + GPtrArray *names = NULL; + GFile *file; + const gchar *name; + int i; + + enumerator = g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_TYPE"," + G_FILE_ATTRIBUTE_STANDARD_NAME"," + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN"," + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + if (!enumerator) { + goto out; + } + + names = g_ptr_array_new_with_free_func (g_free); + while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) { + if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR && + !g_file_info_get_is_hidden (info) && + g_str_has_suffix (g_file_info_get_name (info), ".env")) + g_ptr_array_add (names, g_strdup (g_file_info_get_name (info))); + + g_clear_object (&info); + } + + g_ptr_array_sort (names, compare_str); + + for (i = 0; i < names->len; i++) { + name = g_ptr_array_index (names, i); + file = g_file_get_child (dir, name); + load_env_file (worker, file); + g_object_unref (file); + } + + out: + g_clear_pointer (&names, g_ptr_array_unref); + g_clear_object (&enumerator); +} + +static void +gdm_session_worker_load_env_d (GdmSessionWorker *worker) +{ + GFile *dir; + + dir = g_file_new_for_path (DATADIR "/gdm/env.d"); + gdm_session_worker_load_env_dir (worker, dir); + g_object_unref (dir); + + dir = g_file_new_for_path (GDMCONFDIR "/env.d"); + gdm_session_worker_load_env_dir (worker, dir); + g_object_unref (dir); +} + +static gboolean +gdm_session_worker_accredit_user (GdmSessionWorker *worker, + GError **error) +{ + gboolean ret; + gboolean res; + uid_t uid; + gid_t gid; + char *shell; + char *home; + int error_code; + + ret = FALSE; + + home = NULL; + shell = NULL; + + if (worker->priv->username == NULL) { + g_debug ("GdmSessionWorker: Username not set"); + error_code = PAM_USER_UNKNOWN; + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + _("no user account available")); + goto out; + } + + uid = 0; + gid = 0; + res = _lookup_passwd_info (worker->priv->username, + &uid, + &gid, + &home, + &shell); + if (! res) { + g_debug ("GdmSessionWorker: Unable to lookup account info"); + error_code = PAM_AUTHINFO_UNAVAIL; + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + _("no user account available")); + goto out; + } + + gdm_session_worker_update_environment_from_passwd_info (worker, + uid, + gid, + home, + shell); + + /* Let's give the user a default PATH if he doesn't already have one + */ + if (!gdm_session_worker_environment_variable_is_set (worker, "PATH")) { + if (strcmp (BINDIR, "/usr/bin") == 0) { + gdm_session_worker_set_environment_variable (worker, "PATH", + GDM_SESSION_DEFAULT_PATH); + } else { + gdm_session_worker_set_environment_variable (worker, "PATH", + BINDIR ":" GDM_SESSION_DEFAULT_PATH); + } + } + + if (! _change_user (worker, uid, gid)) { + g_debug ("GdmSessionWorker: Unable to change to user"); + error_code = PAM_SYSTEM_ERR; + g_set_error (error, GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + "%s", _("Unable to change to user")); + goto out; + } + + error_code = pam_setcred (worker->priv->pam_handle, worker->priv->cred_flags); + + /* If the user is reauthenticating and they've made it this far, then there + * is no reason we should lock them out of their session. They've already + * proved they are they same person who logged in, and that's all we care + * about. + */ + if (worker->priv->is_reauth_session) { + error_code = PAM_SUCCESS; + } + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + "%s", + pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + + ret = TRUE; + + out: + g_free (home); + g_free (shell); + if (ret) { + g_debug ("GdmSessionWorker: state ACCREDITED"); + ret = TRUE; + + gdm_session_worker_get_username (worker, NULL); + gdm_session_auditor_report_user_accredited (worker->priv->auditor); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_ACCREDITED); + } else { + gdm_session_worker_uninitialize_pam (worker, error_code); + } + + return ret; +} + +static const char * const * +gdm_session_worker_get_environment (GdmSessionWorker *worker) +{ + return (const char * const *) pam_getenvlist (worker->priv->pam_handle); +} + +static gboolean +run_script (GdmSessionWorker *worker, + const char *dir) +{ + /* scripts are for non-program sessions only */ + if (worker->priv->is_program_session) { + return TRUE; + } + + return gdm_run_script (dir, + worker->priv->username, + worker->priv->x11_display_name, + worker->priv->display_is_local? NULL : worker->priv->hostname, + worker->priv->x11_authority_file); +} + +static void +wait_until_dbus_signal_emission_to_manager_finishes (GdmSessionWorker *worker) +{ + g_autoptr (GdmDBusPeer) peer_proxy = NULL; + g_autoptr (GError) error = NULL; + gboolean pinged; + + peer_proxy = gdm_dbus_peer_proxy_new_sync (worker->priv->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + "/org/freedesktop/DBus", + NULL, + &error); + + if (peer_proxy == NULL) { + g_debug ("GdmSessionWorker: could not create peer proxy to daemon: %s", + error->message); + return; + } + + pinged = gdm_dbus_peer_call_ping_sync (peer_proxy, NULL, &error); + + if (!pinged) { + g_debug ("GdmSessionWorker: could not ping daemon: %s", + error->message); + return; + } +} + +static void +jump_back_to_initial_vt (GdmSessionWorker *worker) +{ + if (worker->priv->session_vt == 0) + return; + + if (worker->priv->session_vt == GDM_INITIAL_VT) + return; + + if (g_strcmp0 (worker->priv->display_seat_id, "seat0") != 0) + return; + +#ifdef ENABLE_USER_DISPLAY_SERVER + jump_to_vt (worker, GDM_INITIAL_VT); + worker->priv->session_vt = 0; +#endif +} + +static void +session_worker_child_watch (GPid pid, + int status, + GdmSessionWorker *worker) +{ + g_debug ("GdmSessionWorker: child (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -1); + + gdm_session_worker_uninitialize_pam (worker, PAM_SUCCESS); + + worker->priv->child_pid = -1; + worker->priv->child_watch_id = 0; + run_script (worker, GDMCONFDIR "/PostSession"); + + gdm_dbus_worker_emit_session_exited (GDM_DBUS_WORKER (worker), + worker->priv->service, + status); + + killpg (pid, SIGHUP); + + /* FIXME: It's important to give the manager an opportunity to process the + * session-exited emission above before switching VTs. + * + * This is because switching VTs makes the manager try to put a login screen + * up on VT 1, but it may actually want to try to auto login again in response + * to session-exited. + * + * This function just does a manager roundtrip over the bus to make sure the + * signal has been dispatched before jumping. + * + * Ultimately, we may want to improve the manager<->worker interface. + * + * See: + * + * https://gitlab.gnome.org/GNOME/gdm/-/merge_requests/123 + * + * for some ideas and more discussion. + * + */ + wait_until_dbus_signal_emission_to_manager_finishes (worker); + + jump_back_to_initial_vt (worker); +} + +static void +gdm_session_worker_watch_child (GdmSessionWorker *worker) +{ + g_debug ("GdmSession worker: watching pid %d", worker->priv->child_pid); + worker->priv->child_watch_id = g_child_watch_add (worker->priv->child_pid, + (GChildWatchFunc)session_worker_child_watch, + worker); + +} + +static gboolean +_is_loggable_file (const char* filename) +{ + struct stat file_info; + + if (g_lstat (filename, &file_info) < 0) { + return FALSE; + } + + return S_ISREG (file_info.st_mode) && g_access (filename, R_OK | W_OK) == 0; +} + +static void +rotate_logs (const char *path, + guint n_copies) +{ + int i; + + for (i = n_copies - 1; i > 0; i--) { + char *name_n; + char *name_n1; + + name_n = g_strdup_printf ("%s.%d", path, i); + if (i > 1) { + name_n1 = g_strdup_printf ("%s.%d", path, i - 1); + } else { + name_n1 = g_strdup (path); + } + + g_unlink (name_n); + g_rename (name_n1, name_n); + + g_free (name_n1); + g_free (name_n); + } + + g_unlink (path); +} + +static int +_open_program_session_log (const char *filename) +{ + int fd; + + rotate_logs (filename, MAX_LOGS); + + fd = g_open (filename, O_WRONLY | O_APPEND | O_CREAT, 0600); + + if (fd < 0) { + char *temp_name; + + temp_name = g_strdup_printf ("%s.XXXXXXXX", filename); + + fd = g_mkstemp (temp_name); + + if (fd < 0) { + g_free (temp_name); + goto out; + } + + g_warning ("session log '%s' is not appendable, logging session to '%s' instead.\n", filename, + temp_name); + g_free (temp_name); + } else { + if (ftruncate (fd, 0) < 0) { + close (fd); + fd = -1; + goto out; + } + } + + if (fchmod (fd, 0644) < 0) { + close (fd); + fd = -1; + goto out; + } + + +out: + if (fd < 0) { + g_warning ("unable to log program session"); + fd = g_open ("/dev/null", O_RDWR); + } + + return fd; +} + +static int +_open_user_session_log (const char *dir) +{ + int fd; + char *filename; + + filename = g_build_filename (dir, GDM_SESSION_LOG_FILENAME, NULL); + + if (g_access (dir, R_OK | W_OK | X_OK) == 0 && _is_loggable_file (filename)) { + char *filename_old; + + filename_old = g_strdup_printf ("%s.old", filename); + g_rename (filename, filename_old); + g_free (filename_old); + } + + fd = g_open (filename, O_RDWR | O_APPEND | O_CREAT, 0600); + + if (fd < 0) { + char *temp_name; + + temp_name = g_strdup_printf ("%s.XXXXXXXX", filename); + + fd = g_mkstemp (temp_name); + + if (fd < 0) { + g_free (temp_name); + goto out; + } + + g_warning ("session log '%s' is not appendable, logging session to '%s' instead.\n", filename, + temp_name); + g_free (filename); + filename = temp_name; + } else { + if (ftruncate (fd, 0) < 0) { + close (fd); + fd = -1; + goto out; + } + } + + if (fchmod (fd, 0600) < 0) { + close (fd); + fd = -1; + goto out; + } + + +out: + g_free (filename); + + if (fd < 0) { + g_warning ("unable to log session"); + fd = g_open ("/dev/null", O_RDWR); + } + + return fd; +} + +static gboolean +gdm_session_worker_start_session (GdmSessionWorker *worker, + GError **error) +{ + struct passwd *passwd_entry; + pid_t session_pid; + int error_code; + + gdm_get_pwent_for_name (worker->priv->username, &passwd_entry); + if (worker->priv->is_program_session) { + g_debug ("GdmSessionWorker: opening session for program '%s'", + worker->priv->arguments[0]); + } else { + g_debug ("GdmSessionWorker: opening user session with program '%s'", + worker->priv->arguments[0]); + } + + error_code = PAM_SUCCESS; + + /* If we're in new vt mode, jump to the new vt now. There's no need to jump for + * the other two modes: in the logind case, the session will activate itself when + * ready, and in the reuse server case, we're already on the correct VT. */ + if (g_strcmp0 (worker->priv->display_seat_id, "seat0") == 0) { + if (worker->priv->display_mode == GDM_SESSION_DISPLAY_MODE_NEW_VT) { + jump_to_vt (worker, worker->priv->session_vt); + } + } + + if (!worker->priv->is_program_session && !run_script (worker, GDMCONFDIR "/PostLogin")) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "Failed to execute PostLogin script"); + error_code = PAM_ABORT; + goto out; + } + + if (!worker->priv->is_program_session && !run_script (worker, GDMCONFDIR "/PreSession")) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "Failed to execute PreSession script"); + error_code = PAM_ABORT; + goto out; + } + + session_pid = fork (); + + if (session_pid < 0) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "%s", g_strerror (errno)); + error_code = PAM_ABORT; + goto out; + } + + if (session_pid == 0) { + const char * const * environment; + char *home_dir; + int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1; + gboolean has_journald = FALSE, needs_controlling_terminal = FALSE; + /* Leak the TTY into the session as stdin so that it stays open + * without any races. */ + if (worker->priv->session_tty_fd > 0) { + dup2 (worker->priv->session_tty_fd, STDIN_FILENO); + close (worker->priv->session_tty_fd); + worker->priv->session_tty_fd = -1; + needs_controlling_terminal = TRUE; + } else { + stdin_fd = open ("/dev/null", O_RDWR); + dup2 (stdin_fd, STDIN_FILENO); + close (stdin_fd); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + has_journald = sd_booted() > 0; +#endif + if (!has_journald && worker->priv->is_program_session) { + stdout_fd = _open_program_session_log (worker->priv->log_file); + stderr_fd = dup (stdout_fd); + } + + if (setsid () < 0) { + g_debug ("GdmSessionWorker: could not set pid '%u' as leader of new session and process group: %s", + (guint) getpid (), g_strerror (errno)); + _exit (EXIT_FAILURE); + } + + /* Take control of the tty + */ + if (needs_controlling_terminal) { + if (ioctl (STDIN_FILENO, TIOCSCTTY, 0) < 0) { + g_debug ("GdmSessionWorker: could not take control of tty: %m"); + } + } + +#ifdef HAVE_LOGINCAP + if (setusercontext (NULL, passwd_entry, passwd_entry->pw_uid, LOGIN_SETALL) < 0) { + g_debug ("GdmSessionWorker: setusercontext() failed for user %s: %s", + passwd_entry->pw_name, g_strerror (errno)); + _exit (EXIT_FAILURE); + } +#else + if (setuid (worker->priv->uid) < 0) { + g_debug ("GdmSessionWorker: could not reset uid: %s", g_strerror (errno)); + _exit (EXIT_FAILURE); + } +#endif + + if (!worker->priv->is_program_session) { + gdm_session_worker_load_env_d (worker); + } + + environment = gdm_session_worker_get_environment (worker); + + g_assert (geteuid () == getuid ()); + + home_dir = gdm_session_worker_get_environment_variable (worker, "HOME"); + if ((home_dir == NULL) || g_chdir (home_dir) < 0) { + g_chdir ("/"); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + if (has_journald) { + stdout_fd = sd_journal_stream_fd (worker->priv->arguments[0], LOG_INFO, FALSE); + stderr_fd = sd_journal_stream_fd (worker->priv->arguments[0], LOG_WARNING, FALSE); + + /* Unset the CLOEXEC flags, because sd_journal_stream_fd + * gives it to us by default. + */ + gdm_clear_close_on_exec_flag (stdout_fd); + gdm_clear_close_on_exec_flag (stderr_fd); + } +#endif + if (!has_journald && !worker->priv->is_program_session) { + if (home_dir != NULL && home_dir[0] != '\0') { + char *cache_dir; + char *log_dir; + + cache_dir = gdm_session_worker_get_environment_variable (worker, "XDG_CACHE_HOME"); + if (cache_dir == NULL || cache_dir[0] == '\0') { + cache_dir = g_build_filename (home_dir, ".cache", NULL); + } + + log_dir = g_build_filename (cache_dir, "gdm", NULL); + g_free (cache_dir); + + if (g_mkdir_with_parents (log_dir, S_IRWXU) == 0) { + stdout_fd = _open_user_session_log (log_dir); + stderr_fd = dup (stdout_fd); + } else { + stdout_fd = open ("/dev/null", O_RDWR); + stderr_fd = dup (stdout_fd); + } + g_free (log_dir); + } else { + stdout_fd = open ("/dev/null", O_RDWR); + stderr_fd = dup (stdout_fd); + } + } + g_free (home_dir); + + if (stdout_fd != -1) { + dup2 (stdout_fd, STDOUT_FILENO); + close (stdout_fd); + } + + if (stderr_fd != -1) { + dup2 (stderr_fd, STDERR_FILENO); + close (stderr_fd); + } + + gdm_log_shutdown (); + + /* + * Reset SIGPIPE to default so that any process in the user + * session get the default SIGPIPE behavior instead of ignoring + * SIGPIPE. + */ + signal (SIGPIPE, SIG_DFL); + + gdm_session_execute (worker->priv->arguments[0], + worker->priv->arguments, + (char **) + environment, + TRUE); + + gdm_log_init (); + g_debug ("GdmSessionWorker: child '%s' could not be started: %s", + worker->priv->arguments[0], + g_strerror (errno)); + + _exit (EXIT_FAILURE); + } + + if (worker->priv->session_tty_fd > 0) { + close (worker->priv->session_tty_fd); + worker->priv->session_tty_fd = -1; + } + + /* If we end up execing again, make sure we don't use the executable context set up + * by pam_selinux durin pam_open_session + */ +#ifdef HAVE_SELINUX + setexeccon (NULL); +#endif + + worker->priv->child_pid = session_pid; + + g_debug ("GdmSessionWorker: session opened creating reply..."); + g_assert (sizeof (GPid) <= sizeof (int)); + + g_debug ("GdmSessionWorker: state SESSION_STARTED"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SESSION_STARTED); + + gdm_session_worker_watch_child (worker); + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static gboolean +set_up_for_new_vt (GdmSessionWorker *worker) +{ + int initial_vt_fd; + char vt_string[256], tty_string[256]; + int session_vt = 0; + + /* open the initial vt. We need it for two scenarios: + * + * 1) display_is_initial is TRUE. We need it directly. + * 2) display_is_initial is FALSE. We need it to mark + * the initial VT as "in use" so it doesn't get returned + * by VT_OPENQRY + * */ + g_snprintf (tty_string, sizeof (tty_string), "/dev/tty%d", GDM_INITIAL_VT); + initial_vt_fd = open (tty_string, O_RDWR | O_NOCTTY); + + if (initial_vt_fd < 0) { + g_debug ("GdmSessionWorker: couldn't open console of initial fd: %m"); + return FALSE; + } + + if (worker->priv->display_is_initial) { + session_vt = GDM_INITIAL_VT; + } else { + + /* Typically VT_OPENQRY is called on /dev/tty0, but we already + * have /dev/tty1 open above, so might as well use it. + */ + if (ioctl (initial_vt_fd, VT_OPENQRY, &session_vt) < 0) { + g_debug ("GdmSessionWorker: couldn't open new VT: %m"); + goto fail; + } + } + + worker->priv->session_vt = session_vt; + + g_assert (session_vt > 0); + + g_snprintf (vt_string, sizeof (vt_string), "%d", session_vt); + + /* Set the VTNR. This is used by logind to configure a session in + * the logind-managed case, but it doesn't hurt to set it always. + * When logind gains support for XDG_VTNR=auto, we can make the + * OPENQRY and this whole path only used by the new VT code. */ + gdm_session_worker_set_environment_variable (worker, + "XDG_VTNR", + vt_string); + + if (worker->priv->display_is_initial) { + worker->priv->session_tty_fd = initial_vt_fd; + } else { + g_snprintf (tty_string, sizeof (tty_string), "/dev/tty%d", session_vt); + worker->priv->session_tty_fd = open (tty_string, O_RDWR | O_NOCTTY); + close (initial_vt_fd); + } + + pam_set_item (worker->priv->pam_handle, PAM_TTY, tty_string); + + return TRUE; + +fail: + close (initial_vt_fd); + return FALSE; +} + +static gboolean +set_xdg_vtnr_to_current_vt (GdmSessionWorker *worker) +{ + int fd; + char vt_string[256]; + struct vt_stat vt_state = { 0 }; + + fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); + + if (fd < 0) { + g_debug ("GdmSessionWorker: couldn't open VT master: %m"); + return FALSE; + } + + if (ioctl (fd, VT_GETSTATE, &vt_state) < 0) { + g_debug ("GdmSessionWorker: couldn't get current VT: %m"); + goto fail; + } + + close (fd); + fd = -1; + + g_snprintf (vt_string, sizeof (vt_string), "%d", vt_state.v_active); + + gdm_session_worker_set_environment_variable (worker, + "XDG_VTNR", + vt_string); + + return TRUE; + +fail: + close (fd); + return FALSE; +} + +static gboolean +set_up_for_current_vt (GdmSessionWorker *worker, + GError **error) +{ +#ifdef PAM_XAUTHDATA + struct pam_xauth_data *pam_xauth; +#endif + int error_code = PAM_SUCCESS; + char *pam_tty; + + /* set TTY */ + pam_tty = _get_tty_for_pam (worker->priv->x11_display_name, worker->priv->display_device); + if (pam_tty != NULL && pam_tty[0] != '\0') { + error_code = pam_set_item (worker->priv->pam_handle, PAM_TTY, pam_tty); + + if (error_code != PAM_SUCCESS) { + g_debug ("error informing authentication system of user's console %s: %s", + pam_tty, + pam_strerror (worker->priv->pam_handle, error_code)); + g_free (pam_tty); + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + } + g_free (pam_tty); + +#ifdef PAM_XDISPLAY + /* set XDISPLAY */ + if (worker->priv->x11_display_name != NULL && worker->priv->x11_display_name[0] != '\0') { + error_code = pam_set_item (worker->priv->pam_handle, PAM_XDISPLAY, worker->priv->x11_display_name); + if (error_code != PAM_SUCCESS) { + g_debug ("error informing authentication system of display string %s: %s", + worker->priv->x11_display_name, + pam_strerror (worker->priv->pam_handle, error_code)); + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + } +#endif +#ifdef PAM_XAUTHDATA + /* set XAUTHDATA */ + pam_xauth = _get_xauth_for_pam (worker->priv->x11_authority_file); + if (pam_xauth != NULL) { + error_code = pam_set_item (worker->priv->pam_handle, PAM_XAUTHDATA, pam_xauth); + if (error_code != PAM_SUCCESS) { + g_debug ("error informing authentication system of display string %s: %s", + worker->priv->x11_display_name, + pam_strerror (worker->priv->pam_handle, error_code)); + g_free (pam_xauth); + + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + g_free (pam_xauth); + } +#endif + + if (g_strcmp0 (worker->priv->display_seat_id, "seat0") == 0) { + g_debug ("GdmSessionWorker: setting XDG_VTNR to current vt"); + set_xdg_vtnr_to_current_vt (worker); + } else { + g_debug ("GdmSessionWorker: not setting XDG_VTNR since not seat0"); + } + + return TRUE; +out: + return FALSE; +} + +static gboolean +gdm_session_worker_open_session (GdmSessionWorker *worker, + GError **error) +{ + int error_code; + int flags; + char *session_id = NULL; + + g_assert (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED); + g_assert (geteuid () == 0); + + switch (worker->priv->display_mode) { + case GDM_SESSION_DISPLAY_MODE_REUSE_VT: + if (!set_up_for_current_vt (worker, error)) { + return FALSE; + } + break; + case GDM_SESSION_DISPLAY_MODE_NEW_VT: + case GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED: + if (!set_up_for_new_vt (worker)) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "Unable to open VT"); + return FALSE; + } + break; + } + + flags = 0; + + if (worker->priv->is_program_session) { + flags |= PAM_SILENT; + } + + error_code = pam_open_session (worker->priv->pam_handle, flags); + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "%s", pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + + g_debug ("GdmSessionWorker: state SESSION_OPENED"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SESSION_OPENED); + + session_id = gdm_session_worker_get_environment_variable (worker, "XDG_SESSION_ID"); + + if (session_id != NULL) { + g_free (worker->priv->session_id); + worker->priv->session_id = session_id; + } + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + worker->priv->session_vt = 0; + return FALSE; + } + + gdm_session_worker_get_username (worker, NULL); + gdm_session_auditor_report_login (worker->priv->auditor); + + return TRUE; +} + +static void +gdm_session_worker_set_server_address (GdmSessionWorker *worker, + const char *address) +{ + g_free (worker->priv->server_address); + worker->priv->server_address = g_strdup (address); +} + +static void +gdm_session_worker_set_is_reauth_session (GdmSessionWorker *worker, + gboolean is_reauth_session) +{ + worker->priv->is_reauth_session = is_reauth_session; +} + +static void +gdm_session_worker_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmSessionWorker *self; + + self = GDM_SESSION_WORKER (object); + + switch (prop_id) { + case PROP_SERVER_ADDRESS: + gdm_session_worker_set_server_address (self, g_value_get_string (value)); + break; + case PROP_IS_REAUTH_SESSION: + gdm_session_worker_set_is_reauth_session (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_session_worker_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmSessionWorker *self; + + self = GDM_SESSION_WORKER (object); + + switch (prop_id) { + case PROP_SERVER_ADDRESS: + g_value_set_string (value, self->priv->server_address); + break; + case PROP_IS_REAUTH_SESSION: + g_value_set_boolean (value, self->priv->is_reauth_session); + break; + case PROP_STATE: + g_value_set_enum (value, self->priv->state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gdm_session_worker_handle_set_environment_variable (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *key, + const char *value) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + gdm_session_worker_set_environment_variable (worker, key, value); + gdm_dbus_worker_complete_set_environment_variable (object, invocation); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_set_session_name (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *session_name) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + g_debug ("GdmSessionWorker: session name set to %s", session_name); + gdm_session_settings_set_session_name (worker->priv->user_settings, + session_name); + gdm_dbus_worker_complete_set_session_name (object, invocation); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_set_session_display_mode (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *str) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + g_debug ("GdmSessionWorker: session display mode set to %s", str); + worker->priv->display_mode = gdm_session_display_mode_from_string (str); + gdm_dbus_worker_complete_set_session_display_mode (object, invocation); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_set_language_name (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *language_name) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + g_debug ("GdmSessionWorker: language name set to %s", language_name); + gdm_session_settings_set_language_name (worker->priv->user_settings, + language_name); + gdm_dbus_worker_complete_set_language_name (object, invocation); + return TRUE; +} + +static void +on_saved_language_name_read (GdmSessionWorker *worker) +{ + char *language_name; + + language_name = gdm_session_settings_get_language_name (worker->priv->user_settings); + + g_debug ("GdmSessionWorker: Saved language is %s", language_name); + gdm_dbus_worker_emit_saved_language_name_read (GDM_DBUS_WORKER (worker), + language_name); + g_free (language_name); +} + +static void +on_saved_session_name_read (GdmSessionWorker *worker) +{ + char *session_name; + + session_name = gdm_session_settings_get_session_name (worker->priv->user_settings); + + g_debug ("GdmSessionWorker: Saved session is %s", session_name); + gdm_dbus_worker_emit_saved_session_name_read (GDM_DBUS_WORKER (worker), + session_name); + g_free (session_name); +} + +static void +do_setup (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + error = NULL; + res = gdm_session_worker_initialize_pam (worker, + worker->priv->service, + (const char **) worker->priv->extensions, + worker->priv->username, + worker->priv->hostname, + worker->priv->display_is_local, + worker->priv->x11_display_name, + worker->priv->x11_authority_file, + worker->priv->display_device, + worker->priv->display_seat_id, + &error); + + if (res) { + g_dbus_method_invocation_return_value (worker->priv->pending_invocation, NULL); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +do_authenticate (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + /* find out who the user is and ensure they are who they say they are + */ + error = NULL; + res = gdm_session_worker_authenticate_user (worker, + worker->priv->password_is_required, + &error); + if (res) { + /* we're authenticated. Let's make sure we've been given + * a valid username for the system + */ + if (!worker->priv->is_program_session) { + g_debug ("GdmSessionWorker: trying to get updated username"); + gdm_session_worker_update_username (worker); + } + + gdm_dbus_worker_complete_authenticate (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation); + } else { + g_debug ("GdmSessionWorker: Unable to verify user"); + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +do_authorize (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + /* make sure the user is allowed to log in to this system + */ + error = NULL; + res = gdm_session_worker_authorize_user (worker, + worker->priv->password_is_required, + &error); + if (res) { + gdm_dbus_worker_complete_authorize (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +do_accredit (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + /* get kerberos tickets, setup group lists, etc + */ + error = NULL; + res = gdm_session_worker_accredit_user (worker, &error); + + if (res) { + gdm_dbus_worker_complete_establish_credentials (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +save_account_details_now (GdmSessionWorker *worker) +{ + g_assert (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCREDITED); + + g_debug ("GdmSessionWorker: saving account details for user %s", worker->priv->username); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED); + if (!gdm_session_settings_save (worker->priv->user_settings, + worker->priv->username)) { + g_warning ("could not save session and language settings"); + } + queue_state_change (worker); +} + +static void +on_settings_is_loaded_changed (GdmSessionSettings *user_settings, + GParamSpec *pspec, + GdmSessionWorker *worker) +{ + if (!gdm_session_settings_is_loaded (worker->priv->user_settings)) { + return; + } + + /* These signal handlers should be disconnected after the loading, + * so that gdm_session_settings_set_* APIs don't cause the emitting + * of Saved*NameRead D-Bus signals any more. + */ + g_signal_handlers_disconnect_by_func (worker->priv->user_settings, + G_CALLBACK (on_saved_session_name_read), + worker); + + g_signal_handlers_disconnect_by_func (worker->priv->user_settings, + G_CALLBACK (on_saved_language_name_read), + worker); + + if (worker->priv->state == GDM_SESSION_WORKER_STATE_NONE) { + g_debug ("GdmSessionWorker: queuing setup for user: %s %s", + worker->priv->username, worker->priv->display_device); + queue_state_change (worker); + } else if (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCREDITED) { + save_account_details_now (worker); + } else { + return; + } + + g_signal_handlers_disconnect_by_func (G_OBJECT (worker->priv->user_settings), + G_CALLBACK (on_settings_is_loaded_changed), + worker); +} + +static void +do_save_account_details_when_ready (GdmSessionWorker *worker) +{ + g_assert (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCREDITED); + + if (!gdm_session_settings_is_loaded (worker->priv->user_settings)) { + g_signal_connect (G_OBJECT (worker->priv->user_settings), + "notify::is-loaded", + G_CALLBACK (on_settings_is_loaded_changed), + worker); + g_debug ("GdmSessionWorker: user %s, not fully loaded yet, will save account details later", + worker->priv->username); + gdm_session_settings_load (worker->priv->user_settings, + worker->priv->username); + return; + } + + save_account_details_now (worker); +} + +static void +do_open_session (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + error = NULL; + res = gdm_session_worker_open_session (worker, &error); + + if (res) { + char *session_id = worker->priv->session_id; + if (session_id == NULL) { + session_id = ""; + } + + gdm_dbus_worker_complete_open (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation, session_id); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +do_start_session (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + error = NULL; + res = gdm_session_worker_start_session (worker, &error); + if (res) { + gdm_dbus_worker_complete_start_program (GDM_DBUS_WORKER (worker), + worker->priv->pending_invocation, + worker->priv->child_pid); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static const char * +get_state_name (int state) +{ + const char *name; + + name = NULL; + + switch (state) { + case GDM_SESSION_WORKER_STATE_NONE: + name = "NONE"; + break; + case GDM_SESSION_WORKER_STATE_SETUP_COMPLETE: + name = "SETUP_COMPLETE"; + break; + case GDM_SESSION_WORKER_STATE_AUTHENTICATED: + name = "AUTHENTICATED"; + break; + case GDM_SESSION_WORKER_STATE_AUTHORIZED: + name = "AUTHORIZED"; + break; + case GDM_SESSION_WORKER_STATE_ACCREDITED: + name = "ACCREDITED"; + break; + case GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED: + name = "ACCOUNT_DETAILS_SAVED"; + break; + case GDM_SESSION_WORKER_STATE_SESSION_OPENED: + name = "SESSION_OPENED"; + break; + case GDM_SESSION_WORKER_STATE_SESSION_STARTED: + name = "SESSION_STARTED"; + break; + default: + g_assert_not_reached (); + break; + } + + return name; +} + +static gboolean +state_change_idle (GdmSessionWorker *worker) +{ + int new_state; + + new_state = worker->priv->state + 1; + g_debug ("GdmSessionWorker: attempting to change state to %s", + get_state_name (new_state)); + + worker->priv->state_change_idle_id = 0; + + switch (new_state) { + case GDM_SESSION_WORKER_STATE_SETUP_COMPLETE: + do_setup (worker); + break; + case GDM_SESSION_WORKER_STATE_AUTHENTICATED: + do_authenticate (worker); + break; + case GDM_SESSION_WORKER_STATE_AUTHORIZED: + do_authorize (worker); + break; + case GDM_SESSION_WORKER_STATE_ACCREDITED: + do_accredit (worker); + break; + case GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED: + do_save_account_details_when_ready (worker); + break; + case GDM_SESSION_WORKER_STATE_SESSION_OPENED: + do_open_session (worker); + break; + case GDM_SESSION_WORKER_STATE_SESSION_STARTED: + do_start_session (worker); + break; + case GDM_SESSION_WORKER_STATE_NONE: + default: + g_assert_not_reached (); + } + return FALSE; +} + +static void +queue_state_change (GdmSessionWorker *worker) +{ + if (worker->priv->state_change_idle_id > 0) { + return; + } + + worker->priv->state_change_idle_id = g_idle_add ((GSourceFunc)state_change_idle, worker); +} + +static gboolean +validate_state_change (GdmSessionWorker *worker, + GDBusMethodInvocation *invocation, + int new_state) +{ + if (worker->priv->pending_invocation != NULL) { + g_dbus_method_invocation_return_error (invocation, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OUTSTANDING_REQUEST, + "Cannot process state change to %s, as there is already an outstanding request to move to state %s", + get_state_name (new_state), + get_state_name (worker->priv->state + 1)); + return FALSE; + } else if (worker->priv->state != new_state - 1) { + g_dbus_method_invocation_return_error (invocation, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_WRONG_STATE, + "Cannot move to state %s, in state %s, not %s", + get_state_name (new_state), + get_state_name (worker->priv->state), + get_state_name (new_state - 1)); + return FALSE; + } + + return TRUE; +} + +static void +validate_and_queue_state_change (GdmSessionWorker *worker, + GDBusMethodInvocation *invocation, + int new_state) +{ + if (validate_state_change (worker, invocation, new_state)) { + worker->priv->pending_invocation = invocation; + queue_state_change (worker); + } +} + +static gboolean +gdm_session_worker_handle_authenticate (GdmDBusWorker *object, + GDBusMethodInvocation *invocation) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_AUTHENTICATED); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_authorize (GdmDBusWorker *object, + GDBusMethodInvocation *invocation) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_AUTHORIZED); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_establish_credentials (GdmDBusWorker *object, + GDBusMethodInvocation *invocation) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_ACCREDITED); + + if (!worker->priv->is_reauth_session) { + worker->priv->cred_flags = PAM_ESTABLISH_CRED; + } else { + worker->priv->cred_flags = PAM_REINITIALIZE_CRED; + } + + return TRUE; +} + +static gboolean +gdm_session_worker_handle_open (GdmDBusWorker *object, + GDBusMethodInvocation *invocation) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED); + return TRUE; +} + +static char ** +filter_extensions (const char * const *extensions) +{ + size_t i, j; + GPtrArray *array = NULL; + char **filtered_extensions = NULL; + + array = g_ptr_array_new (); + + for (i = 0; extensions[i] != NULL; i++) { + for (j = 0; gdm_supported_pam_extensions[j] != NULL; j++) { + if (g_strcmp0 (extensions[i], gdm_supported_pam_extensions[j]) == 0) { + g_ptr_array_add (array, g_strdup (gdm_supported_pam_extensions[j])); + break; + } + } + } + g_ptr_array_add (array, NULL); + + filtered_extensions = g_strdupv ((char **) array->pdata); + + g_ptr_array_free (array, TRUE); + + return filtered_extensions; +} + +static gboolean +gdm_session_worker_handle_initialize (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + GVariant *details) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + GVariantIter iter; + char *key; + GVariant *value; + gboolean wait_for_settings = FALSE; + + if (!validate_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE)) + return TRUE; + + g_variant_iter_init (&iter, details); + while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) { + if (g_strcmp0 (key, "service") == 0) { + worker->priv->service = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "extensions") == 0) { + worker->priv->extensions = filter_extensions (g_variant_get_strv (value, NULL)); + } else if (g_strcmp0 (key, "username") == 0) { + worker->priv->username = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "is-program-session") == 0) { + worker->priv->is_program_session = g_variant_get_boolean (value); + } else if (g_strcmp0 (key, "log-file") == 0) { + worker->priv->log_file = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "x11-display-name") == 0) { + worker->priv->x11_display_name = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "x11-authority-file") == 0) { + worker->priv->x11_authority_file = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "console") == 0) { + worker->priv->display_device = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "seat-id") == 0) { + worker->priv->display_seat_id = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "hostname") == 0) { + worker->priv->hostname = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "display-is-local") == 0) { + worker->priv->display_is_local = g_variant_get_boolean (value); + } else if (g_strcmp0 (key, "display-is-initial") == 0) { + worker->priv->display_is_initial = g_variant_get_boolean (value); + } + } + + worker->priv->pending_invocation = invocation; + + if (!worker->priv->is_program_session) { + g_signal_connect_swapped (worker->priv->user_settings, + "notify::language-name", + G_CALLBACK (on_saved_language_name_read), + worker); + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::session-name", + G_CALLBACK (on_saved_session_name_read), + worker); + + if (worker->priv->username) { + wait_for_settings = !gdm_session_settings_load (worker->priv->user_settings, + worker->priv->username); + } + } + + if (wait_for_settings) { + /* Load settings from accounts daemon before continuing + */ + g_signal_connect (G_OBJECT (worker->priv->user_settings), + "notify::is-loaded", + G_CALLBACK (on_settings_is_loaded_changed), + worker); + } else { + queue_state_change (worker); + } + + return TRUE; +} + +static gboolean +gdm_session_worker_handle_setup (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *service, + const char *x11_display_name, + const char *x11_authority_file, + const char *console, + const char *seat_id, + const char *hostname, + gboolean display_is_local, + gboolean display_is_initial) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE); + + worker->priv->service = g_strdup (service); + worker->priv->x11_display_name = g_strdup (x11_display_name); + worker->priv->x11_authority_file = g_strdup (x11_authority_file); + worker->priv->display_device = g_strdup (console); + worker->priv->display_seat_id = g_strdup (seat_id); + worker->priv->hostname = g_strdup (hostname); + worker->priv->display_is_local = display_is_local; + worker->priv->display_is_initial = display_is_initial; + worker->priv->username = NULL; + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::language-name", + G_CALLBACK (on_saved_language_name_read), + worker); + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::session-name", + G_CALLBACK (on_saved_session_name_read), + worker); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_setup_for_user (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *service, + const char *username, + const char *x11_display_name, + const char *x11_authority_file, + const char *console, + const char *seat_id, + const char *hostname, + gboolean display_is_local, + gboolean display_is_initial) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + + if (!validate_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE)) + return TRUE; + + worker->priv->service = g_strdup (service); + worker->priv->x11_display_name = g_strdup (x11_display_name); + worker->priv->x11_authority_file = g_strdup (x11_authority_file); + worker->priv->display_device = g_strdup (console); + worker->priv->display_seat_id = g_strdup (seat_id); + worker->priv->hostname = g_strdup (hostname); + worker->priv->display_is_local = display_is_local; + worker->priv->display_is_initial = display_is_initial; + worker->priv->username = g_strdup (username); + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::language-name", + G_CALLBACK (on_saved_language_name_read), + worker); + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::session-name", + G_CALLBACK (on_saved_session_name_read), + worker); + + /* Load settings from accounts daemon before continuing + */ + worker->priv->pending_invocation = invocation; + if (gdm_session_settings_load (worker->priv->user_settings, username)) { + queue_state_change (worker); + } else { + g_signal_connect (G_OBJECT (worker->priv->user_settings), + "notify::is-loaded", + G_CALLBACK (on_settings_is_loaded_changed), + worker); + } + + return TRUE; +} + +static gboolean +gdm_session_worker_handle_setup_for_program (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *service, + const char *username, + const char *x11_display_name, + const char *x11_authority_file, + const char *console, + const char *seat_id, + const char *hostname, + gboolean display_is_local, + gboolean display_is_initial, + const char *log_file) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE); + + worker->priv->service = g_strdup (service); + worker->priv->x11_display_name = g_strdup (x11_display_name); + worker->priv->x11_authority_file = g_strdup (x11_authority_file); + worker->priv->display_device = g_strdup (console); + worker->priv->display_seat_id = g_strdup (seat_id); + worker->priv->hostname = g_strdup (hostname); + worker->priv->display_is_local = display_is_local; + worker->priv->display_is_initial = display_is_initial; + worker->priv->username = g_strdup (username); + worker->priv->log_file = g_strdup (log_file); + worker->priv->is_program_session = TRUE; + + return TRUE; +} + +static gboolean +gdm_session_worker_handle_start_program (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *text) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + GError *parse_error = NULL; + validate_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SESSION_STARTED); + + if (worker->priv->is_reauth_session) { + g_dbus_method_invocation_return_error (invocation, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_IN_REAUTH_SESSION, + "Cannot start a program while in a reauth session"); + return TRUE; + } + + g_debug ("GdmSessionWorker: start program: %s", text); + + g_clear_pointer (&worker->priv->arguments, g_strfreev); + if (! g_shell_parse_argv (text, NULL, &worker->priv->arguments, &parse_error)) { + g_dbus_method_invocation_take_error (invocation, parse_error); + return TRUE; + } + + worker->priv->pending_invocation = invocation; + queue_state_change (worker); + + return TRUE; +} + +static void +on_reauthentication_client_connected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + ReauthenticationRequest *request) +{ + g_debug ("GdmSessionWorker: client connected to reauthentication server"); +} + +static void +on_reauthentication_client_disconnected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + ReauthenticationRequest *request) +{ + GdmSessionWorker *worker; + + g_debug ("GdmSessionWorker: client disconnected from reauthentication server"); + + worker = request->worker; + g_hash_table_remove (worker->priv->reauthentication_requests, + GINT_TO_POINTER (pid_of_client)); +} + +static void +on_reauthentication_cancelled (GdmSession *session, + ReauthenticationRequest *request) +{ + g_debug ("GdmSessionWorker: client cancelled reauthentication request"); + gdm_session_reset (session); +} + +static void +on_reauthentication_conversation_started (GdmSession *session, + const char *service_name, + ReauthenticationRequest *request) +{ + g_debug ("GdmSessionWorker: reauthentication service '%s' started", + service_name); +} + +static void +on_reauthentication_conversation_stopped (GdmSession *session, + const char *service_name, + ReauthenticationRequest *request) +{ + g_debug ("GdmSessionWorker: reauthentication service '%s' stopped", + service_name); +} + +static void +on_reauthentication_verification_complete (GdmSession *session, + const char *service_name, + ReauthenticationRequest *request) +{ + GdmSessionWorker *worker; + + worker = request->worker; + + g_debug ("GdmSessionWorker: pid %d reauthenticated user %d with service '%s'", + (int) request->pid_of_caller, + (int) request->uid_of_caller, + service_name); + gdm_session_reset (session); + + gdm_dbus_worker_emit_reauthenticated (GDM_DBUS_WORKER (worker), service_name); +} + +static ReauthenticationRequest * +reauthentication_request_new (GdmSessionWorker *worker, + GPid pid_of_caller, + uid_t uid_of_caller, + GDBusMethodInvocation *invocation) +{ + ReauthenticationRequest *request; + const char * const * environment; + const char *address; + + environment = gdm_session_worker_get_environment (worker); + + request = g_slice_new (ReauthenticationRequest); + + request->worker = worker; + request->pid_of_caller = pid_of_caller; + request->uid_of_caller = uid_of_caller; + request->session = gdm_session_new (GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE, + uid_of_caller, + worker->priv->x11_display_name, + worker->priv->hostname, + worker->priv->display_device, + worker->priv->display_seat_id, + worker->priv->x11_authority_file, + worker->priv->display_is_local, + environment); + + g_signal_connect (request->session, + "client-connected", + G_CALLBACK (on_reauthentication_client_connected), + request); + g_signal_connect (request->session, + "client-disconnected", + G_CALLBACK (on_reauthentication_client_disconnected), + request); + g_signal_connect (request->session, + "cancelled", + G_CALLBACK (on_reauthentication_cancelled), + request); + g_signal_connect (request->session, + "conversation-started", + G_CALLBACK (on_reauthentication_conversation_started), + request); + g_signal_connect (request->session, + "conversation-stopped", + G_CALLBACK (on_reauthentication_conversation_stopped), + request); + g_signal_connect (request->session, + "verification-complete", + G_CALLBACK (on_reauthentication_verification_complete), + request); + + address = gdm_session_get_server_address (request->session); + + gdm_dbus_worker_complete_start_reauthentication (GDM_DBUS_WORKER (worker), + invocation, + address); + + return request; +} + +static gboolean +gdm_session_worker_handle_start_reauthentication (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + int pid_of_caller, + int uid_of_caller) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + ReauthenticationRequest *request; + + if (worker->priv->state != GDM_SESSION_WORKER_STATE_SESSION_STARTED) { + g_dbus_method_invocation_return_error (invocation, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_WRONG_STATE, + "Cannot reauthenticate while in state %s", + get_state_name (worker->priv->state)); + return TRUE; + } + + g_debug ("GdmSessionWorker: start reauthentication"); + + request = reauthentication_request_new (worker, pid_of_caller, uid_of_caller, invocation); + g_hash_table_replace (worker->priv->reauthentication_requests, + GINT_TO_POINTER (pid_of_caller), + request); + return TRUE; +} + +static GObject * +gdm_session_worker_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmSessionWorker *worker; + GError *error; + + worker = GDM_SESSION_WORKER (G_OBJECT_CLASS (gdm_session_worker_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + g_debug ("GdmSessionWorker: connecting to address: %s", worker->priv->server_address); + + error = NULL; + worker->priv->connection = g_dbus_connection_new_for_address_sync (worker->priv->server_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, + NULL, + &error); + if (worker->priv->connection == NULL) { + g_warning ("error opening connection: %s", error->message); + g_clear_error (&error); + + exit (EXIT_FAILURE); + } + + worker->priv->manager = GDM_DBUS_WORKER_MANAGER (gdm_dbus_worker_manager_proxy_new_sync (worker->priv->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, /* dbus name */ + GDM_SESSION_DBUS_PATH, + NULL, + &error)); + if (worker->priv->manager == NULL) { + g_warning ("error creating session proxy: %s", error->message); + g_clear_error (&error); + + exit (EXIT_FAILURE); + } + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (worker), + worker->priv->connection, + GDM_WORKER_DBUS_PATH, + &error)) { + g_warning ("Error while exporting object: %s", error->message); + exit (EXIT_FAILURE); + } + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (worker->priv->manager), G_MAXINT); + + /* Send an initial Hello message so that the session can associate + * the conversation we manage with our pid. + */ + gdm_dbus_worker_manager_call_hello_sync (worker->priv->manager, + NULL, + NULL); + + return G_OBJECT (worker); +} + +static void +worker_interface_init (GdmDBusWorkerIface *interface) +{ + interface->handle_initialize = gdm_session_worker_handle_initialize; + /* The next three are for backward compat only */ + interface->handle_setup = gdm_session_worker_handle_setup; + interface->handle_setup_for_user = gdm_session_worker_handle_setup_for_user; + interface->handle_setup_for_program = gdm_session_worker_handle_setup_for_program; + interface->handle_authenticate = gdm_session_worker_handle_authenticate; + interface->handle_authorize = gdm_session_worker_handle_authorize; + interface->handle_establish_credentials = gdm_session_worker_handle_establish_credentials; + interface->handle_open = gdm_session_worker_handle_open; + interface->handle_set_language_name = gdm_session_worker_handle_set_language_name; + interface->handle_set_session_name = gdm_session_worker_handle_set_session_name; + interface->handle_set_session_display_mode = gdm_session_worker_handle_set_session_display_mode; + interface->handle_set_environment_variable = gdm_session_worker_handle_set_environment_variable; + interface->handle_start_program = gdm_session_worker_handle_start_program; + interface->handle_start_reauthentication = gdm_session_worker_handle_start_reauthentication; +} + +static void +gdm_session_worker_class_init (GdmSessionWorkerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_session_worker_get_property; + object_class->set_property = gdm_session_worker_set_property; + object_class->constructor = gdm_session_worker_constructor; + object_class->finalize = gdm_session_worker_finalize; + + g_object_class_install_property (object_class, + PROP_SERVER_ADDRESS, + g_param_spec_string ("server-address", + "server address", + "server address", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_IS_REAUTH_SESSION, + g_param_spec_boolean ("is-reauth-session", + "is reauth session", + "is reauth session", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_STATE, + g_param_spec_enum ("state", + "state", + "state", + GDM_TYPE_SESSION_WORKER_STATE, + GDM_SESSION_WORKER_STATE_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static void +reauthentication_request_free (ReauthenticationRequest *request) +{ + + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_client_connected), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_client_disconnected), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_cancelled), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_conversation_started), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_conversation_stopped), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_verification_complete), + request); + g_clear_object (&request->session); + g_slice_free (ReauthenticationRequest, request); +} + +static void +gdm_session_worker_init (GdmSessionWorker *worker) +{ + worker->priv = GDM_SESSION_WORKER_GET_PRIVATE (worker); + + worker->priv->user_settings = gdm_session_settings_new (); + worker->priv->reauthentication_requests = g_hash_table_new_full (NULL, + NULL, + NULL, + (GDestroyNotify) + reauthentication_request_free); +} + +static void +gdm_session_worker_unwatch_child (GdmSessionWorker *worker) +{ + if (worker->priv->child_watch_id == 0) + return; + + g_source_remove (worker->priv->child_watch_id); + worker->priv->child_watch_id = 0; +} + + +static void +gdm_session_worker_finalize (GObject *object) +{ + GdmSessionWorker *worker; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_SESSION_WORKER (object)); + + worker = GDM_SESSION_WORKER (object); + + g_return_if_fail (worker->priv != NULL); + + gdm_session_worker_unwatch_child (worker); + + if (worker->priv->child_pid > 0) { + gdm_signal_pid (worker->priv->child_pid, SIGTERM); + gdm_wait_on_pid (worker->priv->child_pid); + } + + if (worker->priv->pam_handle != NULL) { + gdm_session_worker_uninitialize_pam (worker, PAM_SUCCESS); + } + + jump_back_to_initial_vt (worker); + + g_object_unref (worker->priv->user_settings); + g_free (worker->priv->service); + g_free (worker->priv->x11_display_name); + g_free (worker->priv->x11_authority_file); + g_free (worker->priv->display_device); + g_free (worker->priv->display_seat_id); + g_free (worker->priv->hostname); + g_free (worker->priv->username); + g_free (worker->priv->server_address); + g_strfreev (worker->priv->arguments); + g_strfreev (worker->priv->extensions); + + g_hash_table_unref (worker->priv->reauthentication_requests); + + G_OBJECT_CLASS (gdm_session_worker_parent_class)->finalize (object); +} + +GdmSessionWorker * +gdm_session_worker_new (const char *address, + gboolean is_reauth_session) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_SESSION_WORKER, + "server-address", address, + "is-reauth-session", is_reauth_session, + NULL); + + return GDM_SESSION_WORKER (object); +} |