/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2006 Ray Strode * Copyright (C) 2007 William Jon McCann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LOGINCAP #include #endif #include #include #include #include #include #include #include #ifdef ENABLE_SYSTEMD_JOURNAL #include #endif #ifdef HAVE_SELINUX #include #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 : ""); } 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) { if (worker->priv->user_settings == NULL) return; if (gdm_session_settings_is_loaded (worker->priv->user_settings)) return; 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 : "", username != NULL ? username : ""); 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') { 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_max_retries_error_message (GdmSessionWorker *worker) { if (g_strcmp0 (worker->priv->service, "gdm-password") == 0) return _("You reached the maximum password authentication attempts, please try another method"); if (g_strcmp0 (worker->priv->service, "gdm-autologin") == 0) return _("You reached the maximum auto login attempts, please try another authentication method"); if (g_strcmp0 (worker->priv->service, "gdm-fingerprint") == 0) return _("You reached the maximum fingerprint authentication attempts, please try another method"); if (g_strcmp0 (worker->priv->service, "gdm-smartcard") == 0) return _("You reached the maximum smart card authentication attempts, please try another method"); return _("You reached the maximum authentication attempts, please try another method"); } static const char * get_generic_error_message (GdmSessionWorker *worker) { if (g_strcmp0 (worker->priv->service, "gdm-password") == 0) return _("Sorry, password authentication didn’t work. Please try again."); if (g_strcmp0 (worker->priv->service, "gdm-autologin") == 0) return _("Sorry, auto login didn’t work. Please try again."); if (g_strcmp0 (worker->priv->service, "gdm-fingerprint") == 0) return _("Sorry, fingerprint authentication didn’t work. Please try again."); if (g_strcmp0 (worker->priv->service, "gdm-smartcard") == 0) return _("Sorry, smart card authentication didn’t work. Please try again."); return _("Sorry, that didn’t work. Please try again."); } static const char * get_friendly_error_message (GdmSessionWorker *worker, 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; case PAM_MAXTRIES: return get_max_retries_error_message (worker); default: break; } return get_generic_error_message (worker); } 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); 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE, ""); 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_AUTHENTICATING, ""); 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_AUTHENTICATING, ""); 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE, ""); goto out; } else if (error_code == PAM_MAXTRIES) { g_debug ("GdmSessionWorker: authentication service had too many retries"); g_set_error_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_TOO_MANY_RETRIES, get_friendly_error_message (worker, error_code)); 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_AUTHENTICATING, get_friendly_error_message (worker, 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_AUTHORIZING, get_friendly_error_message (worker, 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_func (const char *var, const char *value, gpointer user_data) { GdmSessionWorker *worker = user_data; gdm_session_worker_set_environment_variable (worker, var, value); } 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, _("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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_OPENING_SESSION, 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_load_env_d (load_env_func, get_var_cb, 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_AUTHENTICATING, ""); 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_AUTHENTICATING, ""); 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_AUTHENTICATING, ""); 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_literal (error, GDM_SESSION_WORKER_ERROR, GDM_SESSION_WORKER_ERROR_OPENING_SESSION, 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); if (worker->priv->user_settings != NULL) 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); if (worker->priv->user_settings != NULL) 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 on_saved_session_type_read (GdmSessionWorker *worker) { char *session_type; session_type = gdm_session_settings_get_session_type (worker->priv->user_settings); g_debug ("GdmSessionWorker: Saved session type is %s", session_type); gdm_dbus_worker_emit_saved_session_type_read (GDM_DBUS_WORKER (worker), session_type); g_free (session_type); } 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 (worker->priv->user_settings != NULL) { 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 (worker->priv->user_settings != NULL && !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) { worker->priv->user_settings = gdm_session_settings_new (); 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); g_signal_connect_swapped (worker->priv->user_settings, "notify::session-type", G_CALLBACK (on_saved_session_type_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; worker->priv->user_settings = gdm_session_settings_new (); 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); g_signal_connect_swapped (worker->priv->user_settings, "notify::session-type", G_CALLBACK (on_saved_session_type_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); worker->priv->user_settings = gdm_session_settings_new (); 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); g_signal_connect_swapped (worker->priv->user_settings, "notify::session-type", G_CALLBACK (on_saved_session_type_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->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); }