1
0
Fork 0
gdm3/daemon/gdm-session-worker.c
Daniel Baumann 83b37a3d94
Adding upstream version 48.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 19:45:29 +02:00

3660 lines
138 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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 <json-glib/json-glib.h>
#ifdef ENABLE_X11_SUPPORT
#include <X11/Xauth.h>
#endif
#include <systemd/sd-daemon.h>
#include <systemd/sd-login.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_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 _GdmSessionWorker
{
GdmDBusWorkerSkeleton parent;
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;
guint32 seat0_has_vts : 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,
GDM_PAM_EXTENSION_CUSTOM_JSON,
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))
/* 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->pam_handle != NULL);
if (pam_get_item (worker->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->auditor != NULL) {
gdm_session_auditor_set_username (worker->auditor, (char *)item);
}
return TRUE;
}
return FALSE;
}
static void
attempt_to_load_user_settings (GdmSessionWorker *worker,
const char *username)
{
if (worker->user_settings == NULL)
return;
if (gdm_session_settings_is_loaded (worker->user_settings))
return;
g_debug ("GdmSessionWorker: attempting to load user settings");
gdm_session_settings_load (worker->user_settings,
username);
}
static void
gdm_session_worker_update_username (GdmSessionWorker *worker)
{
g_autofree char *username = NULL;
gboolean res;
res = gdm_session_worker_get_username (worker, &username);
if (res) {
g_debug ("GdmSessionWorker: old-username='%s' new-username='%s'",
worker->username != NULL ? worker->username : "<unset>",
username != NULL ? username : "<unset>");
gdm_session_auditor_set_username (worker->auditor, worker->username);
if ((worker->username == username) ||
((worker->username != NULL) && (username != NULL) &&
(strcmp (worker->username, username) == 0)))
return;
g_debug ("GdmSessionWorker: setting username to '%s'", username);
g_free (worker->username);
worker->username = g_steal_pointer (&username);
gdm_dbus_worker_emit_username_changed (GDM_DBUS_WORKER (worker),
worker->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->username != NULL &&
worker->username[0] != '\0') {
attempt_to_load_user_settings (worker, worker->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->manager,
worker->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->manager,
worker->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->manager,
worker->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->manager,
worker->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)
{
g_autoptr(GVariant) choices_as_variant = NULL;
g_autoptr(GError) error = NULL;
GVariantBuilder builder;
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->manager,
worker->service,
prompt_message,
choices_as_variant,
answerp,
NULL,
&error);
if (! res) {
g_debug ("GdmSessionWorker: list request failed: %s", error->message);
} 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_custom_json_protocol (GdmSessionWorker *worker,
GdmPamExtensionJSONProtocol *request,
GdmPamExtensionJSONProtocol *response)
{
g_autoptr(GError) error = NULL;
g_autoptr(JsonParser) parser = NULL;
g_autofree char *json_reply = NULL;
g_debug ("GdmSessionWorker: sending custom JSON protocol request: %s v%d",
request->protocol_name, request->version);
g_debug ("GdmSessionWorker: (and waiting for reply)");
if (!request->json) {
g_warning ("GdmSessionWorker: custom JSON request is not valid");
return FALSE;
}
parser = json_parser_new_immutable ();
if (!json_parser_load_from_data (parser, request->json, -1, &error)) {
g_warning ("GdmSessionWorker: custom JSON request is not valid JSON: %s",
error->message);
return FALSE;
}
if (!gdm_dbus_worker_manager_call_custom_json_request_sync (worker->manager,
worker->service,
request->protocol_name,
request->version,
request->json,
&response->json,
NULL,
&error)) {
g_warning ("GdmSessionWorker: custom JSON request failed: %s",
error->message);
return FALSE;
}
if (!response->json) {
g_warning ("GdmSessionWorker: custom JSON request returned invalid data");
return FALSE;
}
/* No need to validate JSON reply again since that's what we got from
* the client and validation happens at daemon level.
*/
return TRUE;
}
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->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 if (GDM_PAM_EXTENSION_MESSAGE_MATCH (extended_message, worker->extensions, GDM_PAM_EXTENSION_CUSTOM_JSON)) {
GdmPamExtensionJSONProtocol *json_request = (GdmPamExtensionJSONProtocol *) extended_message;
g_autofree GdmPamExtensionJSONProtocol *json_response = malloc (GDM_PAM_EXTENSION_CUSTOM_JSON_SIZE);
g_debug ("GdmSessionWorker: received extended pam message '%s'", GDM_PAM_EXTENSION_CUSTOM_JSON);
GDM_PAM_EXTENSION_CUSTOM_JSON_RESPONSE_INIT (json_response,
json_request->protocol_name,
json_request->version);
if (!gdm_session_worker_process_custom_json_protocol (worker, json_request, json_response)) {
return FALSE;
}
*response = GDM_PAM_EXTENSION_MESSAGE_TO_PAM_REPLY (g_steal_pointer (&json_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)
{
g_autofree char *utf8 = NULL;
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 g_steal_pointer (&utf8);
}
static gboolean
gdm_session_worker_process_pam_message (GdmSessionWorker *worker,
const struct pam_message *query,
char **response)
{
g_autofree char *user_answer = NULL;
g_autofree char *utf8_msg = NULL;
g_autofree char *msg = NULL;
gboolean res;
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->cancelled = FALSE;
worker->timed_out = 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);
break;
#endif
default:
res = FALSE;
g_warning ("Unknown and unhandled message type %d\n",
query->msg_style);
break;
}
if (worker->timed_out) {
gdm_dbus_worker_emit_cancel_pending_query (GDM_DBUS_WORKER (worker));
worker->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_debug ("GdmSessionWorker: trying to get updated username");
res = TRUE;
}
return res;
}
static const char *
get_max_retries_error_message (GdmSessionWorker *worker)
{
if (g_strcmp0 (worker->service, "gdm-password") == 0)
return _("You reached the maximum password authentication attempts, please try another method");
if (g_strcmp0 (worker->service, "gdm-autologin") == 0)
return _("You reached the maximum auto login attempts, please try another authentication method");
if (g_strcmp0 (worker->service, "gdm-fingerprint") == 0)
return _("You reached the maximum fingerprint authentication attempts, please try another method");
if (g_strcmp0 (worker->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->service, "gdm-password") == 0)
return _("Sorry, password authentication didnt work. Please try again.");
if (g_strcmp0 (worker->service, "gdm-autologin") == 0)
return _("Sorry, auto login didnt work. Please try again.");
if (g_strcmp0 (worker->service, "gdm-fingerprint") == 0)
return _("Sorry, fingerprint authentication didnt work. Please try again.");
if (g_strcmp0 (worker->service, "gdm-smartcard") == 0)
return _("Sorry, smart card authentication didnt work. Please try again.");
return _("Sorry, that didnt 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 thats 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 || responses == NULL) {
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->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->is_program_session) {
worker->auditor = gdm_session_auditor_new (worker->hostname,
worker->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->auditor = gdm_session_solaris_auditor_new (worker->hostname,
worker->display_device);
#elif defined (HAVE_LIBAUDIT)
worker->auditor = gdm_session_linux_auditor_new (worker->hostname,
worker->display_device);
#else
worker->auditor = gdm_session_auditor_new (worker->hostname,
worker->display_device);
#endif
}
static void
gdm_session_worker_stop_auditor (GdmSessionWorker *worker)
{
g_object_unref (worker->auditor);
worker->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->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->session_tty_fd,
clear_screen_escape_sequence,
sizeof (clear_screen_escape_sequence));
fd = worker->session_tty_fd;
handle_terminal_vt_switches (worker, 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");
}
} else {
fd = active_vt_tty_fd;
}
/* 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);
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->state == state)
return;
worker->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->pam_handle == NULL)
return;
gdm_session_worker_get_username (worker, NULL);
if (worker->state >= GDM_SESSION_WORKER_STATE_SESSION_OPENED) {
pam_close_session (worker->pam_handle, 0);
gdm_session_auditor_report_logout (worker->auditor);
} else {
gdm_session_auditor_report_login_failure (worker->auditor,
status,
pam_strerror (worker->pam_handle, status));
}
if (worker->state >= GDM_SESSION_WORKER_STATE_ACCREDITED) {
pam_setcred (worker->pam_handle, PAM_DELETE_CRED);
}
pam_end (worker->pam_handle, status);
worker->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
}
#if defined(PAM_XAUTHDATA) && defined(ENABLE_X11_SUPPORT)
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 (service != NULL);
g_assert (worker->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->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->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->pam_handle, error_code));
g_set_error_literal (error,
GDM_SESSION_WORKER_ERROR,
GDM_SESSION_WORKER_ERROR_AUTHENTICATING,
"");
goto out;
}
}
/* set RHOST */
if (!display_is_local) {
if (hostname != NULL && hostname[0] != '\0') {
error_code = pam_set_item (worker->pam_handle, PAM_RHOST, hostname);
g_debug ("error informing authentication system of user's hostname %s: %s",
hostname,
pam_strerror (worker->pam_handle, error_code));
} else {
error_code = pam_set_item (worker->pam_handle, PAM_RHOST, "0.0.0.0");
g_debug ("error informing authentication system user is remote but has indeterminate hostname: %s",
pam_strerror (worker->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);
if (g_strcmp0 (seat_id, "seat0") == 0 && worker->seat0_has_vts) {
/* 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->pam_handle, PAM_TTY, tty_string);
}
if (!display_is_local)
worker->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->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->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;
#ifdef PAM_MODULE_UNKNOWN
} else if (error_code == PAM_MODULE_UNKNOWN) {
g_debug ("GdmSessionWorker: authentication module unavailable");
g_set_error_literal (error,
GDM_SESSION_WORKER_ERROR,
GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE,
"");
goto out;
#endif
} 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->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->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->is_program_session) {
g_debug ("GdmSessionWorker: authenticated user requires new auth token");
error_code = pam_chauthtok (worker->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->auditor);
} else {
gdm_session_auditor_report_password_changed (worker->auditor);
}
}
/* If the user is reauthenticating, then authorization isn't required to
* proceed, the user is already logged in after all.
*/
if (worker->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->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)
{
g_autofree char *environment_entry = NULL;
int error_code;
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->pam_handle,
environment_entry);
if (error_code != PAM_SUCCESS) {
g_warning ("cannot put %s in pam environment: %s\n",
environment_entry,
pam_strerror (worker->pam_handle, error_code));
}
g_debug ("GdmSessionWorker: Set PAM environment variable: '%s'", environment_entry);
}
static char *
gdm_session_worker_get_environment_variable (GdmSessionWorker *worker,
const char *key)
{
return g_strdup (pam_getenv (worker->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->username);
gdm_session_worker_set_environment_variable (worker, "USER", worker->username);
gdm_session_worker_set_environment_variable (worker, "USERNAME", worker->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->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->uid = uid;
worker->gid = gid;
if (setgid (gid) < 0) {
return FALSE;
}
if (initgroups (worker->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;
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)
{
g_autofree char *shell = NULL;
g_autofree char *home = NULL;
gboolean ret;
gboolean res;
uid_t uid;
gid_t gid;
int error_code;
ret = FALSE;
if (worker->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->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->pam_handle, worker->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->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->pam_handle, error_code));
goto out;
}
ret = TRUE;
out:
if (ret) {
g_debug ("GdmSessionWorker: state ACCREDITED");
ret = TRUE;
gdm_session_worker_get_username (worker, NULL);
gdm_session_auditor_report_user_accredited (worker->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->pam_handle);
}
static gboolean
run_script (GdmSessionWorker *worker,
const char *dir)
{
/* scripts are for non-program sessions only */
if (worker->is_program_session) {
return TRUE;
}
return gdm_run_script (dir,
worker->username,
worker->x11_display_name,
worker->display_is_local? NULL : worker->hostname,
worker->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->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->session_vt == 0)
return;
if (worker->session_vt == GDM_INITIAL_VT)
return;
if (g_strcmp0 (worker->display_seat_id, "seat0") != 0 || !worker->seat0_has_vts)
return;
#ifdef ENABLE_USER_DISPLAY_SERVER
jump_to_vt (worker, GDM_INITIAL_VT);
worker->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->child_pid = -1;
worker->child_watch_id = 0;
run_script (worker, GDMCONFDIR "/PostSession");
gdm_dbus_worker_emit_session_exited (GDM_DBUS_WORKER (worker),
worker->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->child_pid);
worker->child_watch_id = g_child_watch_add (worker->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--) {
g_autofree char *name_n = NULL;
g_autofree char *name_n1 = NULL;
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_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) {
g_autofree char *temp_name = NULL;
temp_name = g_strdup_printf ("%s.XXXXXXXX", filename);
fd = g_mkstemp (temp_name);
if (fd < 0) {
goto out;
}
g_warning ("session log '%s' is not appendable, logging session to '%s' instead.\n", filename,
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;
g_autofree char *filename = NULL;
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)) {
g_autofree char *filename_old = NULL;
filename_old = g_strdup_printf ("%s.old", filename);
g_rename (filename, filename_old);
}
fd = g_open (filename, O_RDWR | O_APPEND | O_CREAT, 0600);
if (fd < 0) {
g_autofree char *temp_name = NULL;
temp_name = g_strdup_printf ("%s.XXXXXXXX", filename);
fd = g_mkstemp (temp_name);
if (fd < 0) {
goto out;
}
g_warning ("session log '%s' is not appendable, logging session to '%s' instead.\n", 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:
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->username, &passwd_entry);
if (worker->is_program_session) {
g_debug ("GdmSessionWorker: opening session for program '%s'",
worker->arguments[0]);
} else {
g_debug ("GdmSessionWorker: opening user session with program '%s'",
worker->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->display_seat_id, "seat0") == 0 && worker->seat0_has_vts) {
if (worker->display_mode == GDM_SESSION_DISPLAY_MODE_NEW_VT) {
jump_to_vt (worker, worker->session_vt);
}
}
if (!worker->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->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) {
g_autofree char *home_dir = NULL;
const char * const * environment;
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->session_tty_fd > 0) {
dup2 (worker->session_tty_fd, STDIN_FILENO);
close (worker->session_tty_fd);
worker->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->is_program_session) {
stdout_fd = _open_program_session_log (worker->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->uid) < 0) {
g_debug ("GdmSessionWorker: could not reset uid: %s", g_strerror (errno));
_exit (EXIT_FAILURE);
}
#endif
if (!worker->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->arguments[0], LOG_INFO, FALSE);
stderr_fd = sd_journal_stream_fd (worker->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->is_program_session) {
if (home_dir != NULL && home_dir[0] != '\0') {
g_autofree char *cache_dir = NULL;
g_autofree char *log_dir = NULL;
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);
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);
}
} else {
stdout_fd = open ("/dev/null", O_RDWR);
stderr_fd = dup (stdout_fd);
}
}
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->arguments[0],
worker->arguments,
(char **)
environment,
TRUE);
gdm_log_init ();
g_debug ("GdmSessionWorker: child '%s' could not be started: %s",
worker->arguments[0],
g_strerror (errno));
_exit (EXIT_FAILURE);
}
if (worker->session_tty_fd > 0) {
close (worker->session_tty_fd);
worker->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->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->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->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->display_is_initial) {
worker->session_tty_fd = initial_vt_fd;
} else {
g_snprintf (tty_string, sizeof (tty_string), "/dev/tty%d", session_vt);
worker->session_tty_fd = open (tty_string, O_RDWR | O_NOCTTY);
close (initial_vt_fd);
}
pam_set_item (worker->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);
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
g_autofree char *pam_tty = NULL;
/* set TTY */
pam_tty = _get_tty_for_pam (worker->x11_display_name, worker->display_device);
if (pam_tty != NULL && pam_tty[0] != '\0') {
int error_code;
error_code = pam_set_item (worker->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->pam_handle, error_code));
g_set_error_literal (error,
GDM_SESSION_WORKER_ERROR,
GDM_SESSION_WORKER_ERROR_AUTHENTICATING,
"");
return FALSE;
}
}
#ifdef PAM_XDISPLAY
/* set XDISPLAY */
if (worker->x11_display_name != NULL && worker->x11_display_name[0] != '\0') {
int error_code;
error_code = pam_set_item (worker->pam_handle, PAM_XDISPLAY, worker->x11_display_name);
if (error_code != PAM_SUCCESS) {
g_debug ("error informing authentication system of display string %s: %s",
worker->x11_display_name,
pam_strerror (worker->pam_handle, error_code));
g_set_error_literal (error,
GDM_SESSION_WORKER_ERROR,
GDM_SESSION_WORKER_ERROR_AUTHENTICATING,
"");
return FALSE;
}
}
#endif
#if defined(PAM_XAUTHDATA) && defined(ENABLE_X11_SUPPORT)
/* set XAUTHDATA */
pam_xauth = _get_xauth_for_pam (worker->x11_authority_file);
if (pam_xauth != NULL) {
int error_code;
error_code = pam_set_item (worker->pam_handle, PAM_XAUTHDATA, pam_xauth);
if (error_code != PAM_SUCCESS) {
g_debug ("error informing authentication system of display string %s: %s",
worker->x11_display_name,
pam_strerror (worker->pam_handle, error_code));
g_free (pam_xauth);
g_set_error_literal (error,
GDM_SESSION_WORKER_ERROR,
GDM_SESSION_WORKER_ERROR_AUTHENTICATING,
"");
return FALSE;
}
g_free (pam_xauth);
}
#endif
if (g_strcmp0 (worker->display_seat_id, "seat0") == 0 && worker->seat0_has_vts) {
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 no VTs on seat");
}
return TRUE;
}
static gboolean
gdm_session_worker_open_session (GdmSessionWorker *worker,
GError **error)
{
g_autofree char *session_id = NULL;
int error_code;
int flags;
g_assert (worker->state == GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED);
g_assert (geteuid () == 0);
if (g_strcmp0 (worker->display_seat_id, "seat0") == 0 && worker->seat0_has_vts) {
switch (worker->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->is_program_session) {
flags |= PAM_SILENT;
}
error_code = pam_open_session (worker->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->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->session_id);
worker->session_id = g_steal_pointer (&session_id);
}
out:
if (error_code != PAM_SUCCESS) {
gdm_session_worker_uninitialize_pam (worker, error_code);
worker->session_vt = 0;
return FALSE;
}
gdm_session_worker_get_username (worker, NULL);
gdm_session_auditor_report_login (worker->auditor);
return TRUE;
}
static void
gdm_session_worker_set_server_address (GdmSessionWorker *worker,
const char *address)
{
g_free (worker->server_address);
worker->server_address = g_strdup (address);
}
static void
gdm_session_worker_set_is_reauth_session (GdmSessionWorker *worker,
gboolean is_reauth_session)
{
worker->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->server_address);
break;
case PROP_IS_REAUTH_SESSION:
g_value_set_boolean (value, self->is_reauth_session);
break;
case PROP_STATE:
g_value_set_enum (value, self->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->user_settings != NULL)
gdm_session_settings_set_session_name (worker->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->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->user_settings != NULL)
gdm_session_settings_set_language_name (worker->user_settings,
language_name);
gdm_dbus_worker_complete_set_language_name (object, invocation);
return TRUE;
}
static void
on_saved_language_name_read (GdmSessionWorker *worker)
{
g_autofree char *language_name = NULL;
language_name = gdm_session_settings_get_language_name (worker->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);
}
static void
on_saved_session_name_read (GdmSessionWorker *worker)
{
g_autofree char *session_name = NULL;
session_name = gdm_session_settings_get_session_name (worker->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);
}
static void
on_saved_session_type_read (GdmSessionWorker *worker)
{
g_autofree char *session_type = NULL;
session_type = gdm_session_settings_get_session_type (worker->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);
}
static void
do_setup (GdmSessionWorker *worker)
{
g_autoptr(GError) error = NULL;
gboolean res;
res = gdm_session_worker_initialize_pam (worker,
worker->service,
(const char **) worker->extensions,
worker->username,
worker->hostname,
worker->display_is_local,
worker->x11_display_name,
worker->x11_authority_file,
worker->display_device,
worker->display_seat_id,
&error);
if (res) {
g_dbus_method_invocation_return_value (worker->pending_invocation, NULL);
} else {
g_dbus_method_invocation_take_error (worker->pending_invocation,
g_steal_pointer (&error));
}
worker->pending_invocation = NULL;
}
static void
do_authenticate (GdmSessionWorker *worker)
{
g_autoptr(GError) error = NULL;
gboolean res;
/* find out who the user is and ensure they are who they say they are
*/
res = gdm_session_worker_authenticate_user (worker,
worker->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->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->pending_invocation);
} else {
g_debug ("GdmSessionWorker: Unable to verify user");
g_dbus_method_invocation_take_error (worker->pending_invocation,
g_steal_pointer (&error));
}
worker->pending_invocation = NULL;
}
static void
do_authorize (GdmSessionWorker *worker)
{
g_autoptr(GError) error = NULL;
gboolean res;
/* make sure the user is allowed to log in to this system
*/
res = gdm_session_worker_authorize_user (worker,
worker->password_is_required,
&error);
if (res) {
gdm_dbus_worker_complete_authorize (GDM_DBUS_WORKER (worker), worker->pending_invocation);
} else {
g_dbus_method_invocation_take_error (worker->pending_invocation,
g_steal_pointer (&error));
}
worker->pending_invocation = NULL;
}
static void
do_accredit (GdmSessionWorker *worker)
{
g_autoptr(GError) error = NULL;
gboolean res;
/* get kerberos tickets, setup group lists, etc
*/
res = gdm_session_worker_accredit_user (worker, &error);
if (res) {
gdm_dbus_worker_complete_establish_credentials (GDM_DBUS_WORKER (worker), worker->pending_invocation);
} else {
g_dbus_method_invocation_take_error (worker->pending_invocation,
g_steal_pointer (&error));
}
worker->pending_invocation = NULL;
}
static void
save_account_details_now (GdmSessionWorker *worker)
{
g_assert (worker->state == GDM_SESSION_WORKER_STATE_ACCREDITED);
g_debug ("GdmSessionWorker: saving account details for user %s", worker->username);
gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED);
if (worker->user_settings != NULL) {
if (!gdm_session_settings_save (worker->user_settings,
worker->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->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->user_settings,
G_CALLBACK (on_saved_session_name_read),
worker);
g_signal_handlers_disconnect_by_func (worker->user_settings,
G_CALLBACK (on_saved_language_name_read),
worker);
if (worker->state == GDM_SESSION_WORKER_STATE_NONE) {
g_debug ("GdmSessionWorker: queuing setup for user: %s %s",
worker->username, worker->display_device);
queue_state_change (worker);
} else if (worker->state == GDM_SESSION_WORKER_STATE_ACCREDITED) {
save_account_details_now (worker);
} else {
return;
}
g_signal_handlers_disconnect_by_func (G_OBJECT (worker->user_settings),
G_CALLBACK (on_settings_is_loaded_changed),
worker);
}
static void
do_save_account_details_when_ready (GdmSessionWorker *worker)
{
g_assert (worker->state == GDM_SESSION_WORKER_STATE_ACCREDITED);
if (worker->user_settings != NULL && !gdm_session_settings_is_loaded (worker->user_settings)) {
g_signal_connect (G_OBJECT (worker->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->username);
gdm_session_settings_load (worker->user_settings,
worker->username);
return;
}
save_account_details_now (worker);
}
static void
do_open_session (GdmSessionWorker *worker)
{
g_autoptr(GError) error = NULL;
gboolean res;
res = gdm_session_worker_open_session (worker, &error);
if (res) {
char *session_id = worker->session_id;
if (session_id == NULL) {
session_id = "";
}
gdm_dbus_worker_complete_open (GDM_DBUS_WORKER (worker), worker->pending_invocation, session_id);
} else {
g_dbus_method_invocation_take_error (worker->pending_invocation,
g_steal_pointer (&error));
}
worker->pending_invocation = NULL;
}
static void
do_start_session (GdmSessionWorker *worker)
{
g_autoptr(GError) error = NULL;
gboolean res;
res = gdm_session_worker_start_session (worker, &error);
if (res) {
gdm_dbus_worker_complete_start_program (GDM_DBUS_WORKER (worker),
worker->pending_invocation,
worker->child_pid);
} else {
g_dbus_method_invocation_take_error (worker->pending_invocation,
g_steal_pointer (&error));
}
worker->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->state + 1;
g_debug ("GdmSessionWorker: attempting to change state to %s",
get_state_name (new_state));
worker->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->state_change_idle_id > 0) {
return;
}
worker->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->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->state + 1));
return FALSE;
} else if (worker->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->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->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->is_reauth_session) {
worker->cred_flags = PAM_ESTABLISH_CRED;
} else {
worker->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)
{
g_autoptr(GPtrArray) array = NULL;
g_auto(GStrv) filtered_extensions = NULL;
size_t i, j;
array = g_ptr_array_new_with_free_func (g_free);
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);
return g_steal_pointer (&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->service = g_variant_dup_string (value, NULL);
} else if (g_strcmp0 (key, "extensions") == 0) {
worker->extensions = filter_extensions (g_variant_get_strv (value, NULL));
} else if (g_strcmp0 (key, "username") == 0) {
worker->username = g_variant_dup_string (value, NULL);
} else if (g_strcmp0 (key, "is-program-session") == 0) {
worker->is_program_session = g_variant_get_boolean (value);
} else if (g_strcmp0 (key, "log-file") == 0) {
worker->log_file = g_variant_dup_string (value, NULL);
} else if (g_strcmp0 (key, "x11-display-name") == 0) {
worker->x11_display_name = g_variant_dup_string (value, NULL);
} else if (g_strcmp0 (key, "x11-authority-file") == 0) {
worker->x11_authority_file = g_variant_dup_string (value, NULL);
} else if (g_strcmp0 (key, "console") == 0) {
worker->display_device = g_variant_dup_string (value, NULL);
} else if (g_strcmp0 (key, "seat-id") == 0) {
worker->display_seat_id = g_variant_dup_string (value, NULL);
} else if (g_strcmp0 (key, "hostname") == 0) {
worker->hostname = g_variant_dup_string (value, NULL);
} else if (g_strcmp0 (key, "display-is-local") == 0) {
worker->display_is_local = g_variant_get_boolean (value);
} else if (g_strcmp0 (key, "display-is-initial") == 0) {
worker->display_is_initial = g_variant_get_boolean (value);
}
}
worker->seat0_has_vts = sd_seat_can_tty ("seat0");
worker->pending_invocation = invocation;
if (!worker->is_program_session) {
worker->user_settings = gdm_session_settings_new ();
g_signal_connect_swapped (worker->user_settings,
"notify::language-name",
G_CALLBACK (on_saved_language_name_read),
worker);
g_signal_connect_swapped (worker->user_settings,
"notify::session-name",
G_CALLBACK (on_saved_session_name_read),
worker);
g_signal_connect_swapped (worker->user_settings,
"notify::session-type",
G_CALLBACK (on_saved_session_type_read),
worker);
if (worker->username) {
wait_for_settings = !gdm_session_settings_load (worker->user_settings,
worker->username);
}
}
if (wait_for_settings) {
/* Load settings from accounts daemon before continuing
*/
g_signal_connect (G_OBJECT (worker->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->service = g_strdup (service);
worker->x11_display_name = g_strdup (x11_display_name);
worker->x11_authority_file = g_strdup (x11_authority_file);
worker->display_device = g_strdup (console);
worker->display_seat_id = g_strdup (seat_id);
worker->hostname = g_strdup (hostname);
worker->display_is_local = display_is_local;
worker->display_is_initial = display_is_initial;
worker->username = NULL;
worker->user_settings = gdm_session_settings_new ();
g_signal_connect_swapped (worker->user_settings,
"notify::language-name",
G_CALLBACK (on_saved_language_name_read),
worker);
g_signal_connect_swapped (worker->user_settings,
"notify::session-name",
G_CALLBACK (on_saved_session_name_read),
worker);
g_signal_connect_swapped (worker->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->service = g_strdup (service);
worker->x11_display_name = g_strdup (x11_display_name);
worker->x11_authority_file = g_strdup (x11_authority_file);
worker->display_device = g_strdup (console);
worker->display_seat_id = g_strdup (seat_id);
worker->hostname = g_strdup (hostname);
worker->display_is_local = display_is_local;
worker->display_is_initial = display_is_initial;
worker->username = g_strdup (username);
worker->user_settings = gdm_session_settings_new ();
g_signal_connect_swapped (worker->user_settings,
"notify::language-name",
G_CALLBACK (on_saved_language_name_read),
worker);
g_signal_connect_swapped (worker->user_settings,
"notify::session-name",
G_CALLBACK (on_saved_session_name_read),
worker);
g_signal_connect_swapped (worker->user_settings,
"notify::session-type",
G_CALLBACK (on_saved_session_type_read),
worker);
/* Load settings from accounts daemon before continuing
*/
worker->pending_invocation = invocation;
if (gdm_session_settings_load (worker->user_settings, username)) {
queue_state_change (worker);
} else {
g_signal_connect (G_OBJECT (worker->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->service = g_strdup (service);
worker->x11_display_name = g_strdup (x11_display_name);
worker->x11_authority_file = g_strdup (x11_authority_file);
worker->display_device = g_strdup (console);
worker->display_seat_id = g_strdup (seat_id);
worker->hostname = g_strdup (hostname);
worker->display_is_local = display_is_local;
worker->display_is_initial = display_is_initial;
worker->username = g_strdup (username);
worker->log_file = g_strdup (log_file);
worker->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);
g_autoptr(GError) parse_error = NULL;
validate_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SESSION_STARTED);
if (worker->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->arguments, g_strfreev);
if (! g_shell_parse_argv (text, NULL, &worker->arguments, &parse_error)) {
g_dbus_method_invocation_take_error (invocation,
g_steal_pointer (&parse_error));
return TRUE;
}
worker->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->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, request->pid_of_caller);
}
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->x11_display_name,
worker->hostname,
worker->display_device,
worker->display_seat_id,
worker->x11_authority_file,
worker->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->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->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->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;
g_autoptr(GError) error = NULL;
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->server_address);
worker->connection = g_dbus_connection_new_for_address_sync (worker->server_address,
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
NULL,
NULL,
&error);
if (worker->connection == NULL) {
g_warning ("error opening connection: %s", error->message);
exit (EXIT_FAILURE);
}
worker->manager = GDM_DBUS_WORKER_MANAGER (gdm_dbus_worker_manager_proxy_new_sync (worker->connection,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL, /* dbus name */
GDM_SESSION_DBUS_PATH,
NULL,
&error));
if (worker->manager == NULL) {
g_warning ("error creating session proxy: %s", error->message);
exit (EXIT_FAILURE);
}
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (worker),
worker->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->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->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->reauthentication_requests = g_hash_table_new_full (NULL,
NULL,
NULL,
(GDestroyNotify)
reauthentication_request_free);
}
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 != NULL);
g_clear_handle_id (&worker->child_watch_id, g_source_remove);
if (worker->child_pid > 0) {
gdm_signal_pid (worker->child_pid, SIGTERM);
gdm_wait_on_pid (worker->child_pid);
}
if (worker->pam_handle != NULL) {
gdm_session_worker_uninitialize_pam (worker, PAM_SUCCESS);
}
jump_back_to_initial_vt (worker);
g_clear_object (&worker->user_settings);
g_free (worker->service);
g_free (worker->x11_display_name);
g_free (worker->x11_authority_file);
g_free (worker->display_device);
g_free (worker->display_seat_id);
g_free (worker->hostname);
g_free (worker->username);
g_free (worker->server_address);
g_strfreev (worker->arguments);
g_strfreev (worker->extensions);
g_hash_table_unref (worker->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);
}