diff options
Diffstat (limited to 'daemon')
63 files changed, 28248 insertions, 0 deletions
diff --git a/daemon/gdm-dbus-util.c b/daemon/gdm-dbus-util.c new file mode 100644 index 0000000..b71da7f --- /dev/null +++ b/daemon/gdm-dbus-util.c @@ -0,0 +1,185 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * 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 "gdm-dbus-util.h" +#include <string.h> + +#include <glib/gstdio.h> +#include <gio/gunixsocketaddress.h> + +/* a subset of org.freedesktop.DBus interface, to be used by internal servers */ +static const char *dbus_introspection = +"<node name=\"/org/freedesktop/DBus\">" +" <interface name=\"org.freedesktop.DBus\">" +" <method name=\"AddMatch\">" +" <arg name=\"match_rule\" type=\"s\" direction=\"in\" />" +" </method>" +" </interface>" +"</node>"; + +static void +handle_bus_method (GDBusConnection *connection, + const char *sender, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + g_dbus_method_invocation_return_value (invocation, NULL); +} + +static gboolean +handle_connection (GDBusServer *server, + GDBusConnection *new_connection, + gpointer user_data) +{ + GDBusInterfaceVTable bus_vtable = { handle_bus_method }; + GDBusNodeInfo *bus_info; + + bus_info = g_dbus_node_info_new_for_xml (dbus_introspection, + NULL); + + g_debug ("GdmDBusServer: new connection %p", new_connection); + + g_dbus_connection_register_object (new_connection, + "/org/freedesktop/DBus", + bus_info->interfaces[0], + &bus_vtable, + NULL, NULL, NULL); + g_dbus_node_info_unref (bus_info); + + /* We're not handling the signal */ + return FALSE; +} + +GDBusServer * +gdm_dbus_setup_private_server (GDBusAuthObserver *observer, + GError **error) +{ + char *guid; + const char *client_address; + GDBusServer *server; + + guid = g_dbus_generate_guid (); + + server = g_dbus_server_new_sync ("unix:tmpdir=/tmp", + G_DBUS_SERVER_FLAGS_NONE, + guid, + observer, + NULL, + error); + + client_address = g_dbus_server_get_client_address (server); + + if (g_str_has_prefix (client_address, "unix:path=")) { + client_address += strlen("unix:path="); + g_chmod (client_address, 0666); + } + + g_signal_connect (server, "new-connection", + G_CALLBACK (handle_connection), + NULL); + + g_free (guid); + + return server; +} + +gboolean +gdm_dbus_get_pid_for_name (const char *system_bus_name, + pid_t *out_pid, + GError **error) +{ + GDBusConnection *bus; + GVariant *reply; + gboolean retval = FALSE; + unsigned int v; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); + if (bus == NULL) { + return FALSE; + } + + reply = g_dbus_connection_call_sync (bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + g_variant_new ("(s)", system_bus_name), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, error); + if (reply == NULL) { + goto out; + } + + g_variant_get (reply, "(u)", &v); + *out_pid = v; + g_variant_unref (reply); + + retval = TRUE; + out: + g_object_unref (bus); + + return retval; +} + +gboolean +gdm_dbus_get_uid_for_name (const char *system_bus_name, + uid_t *out_uid, + GError **error) +{ + GDBusConnection *bus; + GVariant *reply; + gboolean retval = FALSE; + unsigned int v; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); + if (bus == NULL) { + return FALSE; + } + + reply = g_dbus_connection_call_sync (bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + g_variant_new ("(s)", system_bus_name), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, error); + if (reply == NULL) { + goto out; + } + + g_variant_get (reply, "(u)", &v); + *out_uid = v; + g_variant_unref (reply); + + retval = TRUE; + out: + g_object_unref (bus); + + return retval; +} diff --git a/daemon/gdm-dbus-util.h b/daemon/gdm-dbus-util.h new file mode 100644 index 0000000..47654de --- /dev/null +++ b/daemon/gdm-dbus-util.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Giovanni Campagna <scampa.giovanni@gmail.com> + * + * 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. + * + */ + +#ifndef __GDM_DBUS_UTIL_H +#define __GDM_DBUS_UTIL_H + +#include <gio/gio.h> +#include <unistd.h> +#include <sys/types.h> + +GDBusServer *gdm_dbus_setup_private_server (GDBusAuthObserver *observer, + GError **error); + +gboolean gdm_dbus_get_pid_for_name (const char *system_bus_name, + pid_t *out_pid, + GError **error); + +gboolean gdm_dbus_get_uid_for_name (const char *system_bus_name, + uid_t *out_uid, + GError **error); +#endif diff --git a/daemon/gdm-display-access-file.c b/daemon/gdm-display-access-file.c new file mode 100644 index 0000000..87e523e --- /dev/null +++ b/daemon/gdm-display-access-file.c @@ -0,0 +1,606 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * gdm-display-access-file.c - Abstraction around xauth cookies + * + * Copyright (C) 2007 Ray Strode <rstrode@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "config.h" + +#include <errno.h> +#include <limits.h> +#include <pwd.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <glib.h> +#include <glib-object.h> +#include <glib/gstdio.h> +#include <glib/gi18n.h> + +#include <X11/Xauth.h> + +#include "gdm-display-access-file.h" +#include "gdm-common.h" + +struct _GdmDisplayAccessFile +{ + GObject parent; + + char *username; + FILE *fp; + char *path; +}; + +#ifndef GDM_DISPLAY_ACCESS_COOKIE_SIZE +#define GDM_DISPLAY_ACCESS_COOKIE_SIZE 16 +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +static void gdm_display_access_file_finalize (GObject * object); + +enum +{ + PROP_0 = 0, + PROP_USERNAME, + PROP_PATH +}; + +G_DEFINE_TYPE (GdmDisplayAccessFile, gdm_display_access_file, G_TYPE_OBJECT) + +static void +gdm_display_access_file_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmDisplayAccessFile *access_file; + + access_file = GDM_DISPLAY_ACCESS_FILE (object); + + switch (prop_id) { + case PROP_USERNAME: + g_value_set_string (value, access_file->username); + break; + + case PROP_PATH: + g_value_set_string (value, access_file->path); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gdm_display_access_file_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmDisplayAccessFile *access_file; + + access_file = GDM_DISPLAY_ACCESS_FILE (object); + + switch (prop_id) { + case PROP_USERNAME: + g_assert (access_file->username == NULL); + access_file->username = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gdm_display_access_file_class_init (GdmDisplayAccessFileClass *access_file_class) +{ + GObjectClass *object_class; + GParamSpec *param_spec; + + object_class = G_OBJECT_CLASS (access_file_class); + + object_class->finalize = gdm_display_access_file_finalize; + object_class->get_property = gdm_display_access_file_get_property; + object_class->set_property = gdm_display_access_file_set_property; + + param_spec = g_param_spec_string ("username", + "Username", + "Owner of Xauthority file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_USERNAME, param_spec); + param_spec = g_param_spec_string ("path", + "Path", + "Path to Xauthority file", + NULL, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_PATH, param_spec); +} + +static void +gdm_display_access_file_init (GdmDisplayAccessFile *access_file) +{ +} + +static void +gdm_display_access_file_finalize (GObject *object) +{ + GdmDisplayAccessFile *file; + GObjectClass *parent_class; + + file = GDM_DISPLAY_ACCESS_FILE (object); + parent_class = G_OBJECT_CLASS (gdm_display_access_file_parent_class); + + if (file->fp != NULL) { + gdm_display_access_file_close (file); + } + g_assert (file->path == NULL); + + if (file->username != NULL) { + g_free (file->username); + file->username = NULL; + g_object_notify (object, "username"); + } + + if (parent_class->finalize != NULL) { + parent_class->finalize (object); + } +} + +GQuark +gdm_display_access_file_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) { + error_quark = g_quark_from_static_string ("gdm-display-access-file"); + } + + return error_quark; +} + +GdmDisplayAccessFile * +gdm_display_access_file_new (const char *username) +{ + GdmDisplayAccessFile *access_file; + g_return_val_if_fail (username != NULL, NULL); + + access_file = g_object_new (GDM_TYPE_DISPLAY_ACCESS_FILE, + "username", username, + NULL); + + return access_file; +} + +static gboolean +_get_uid_and_gid_for_user (const char *username, + uid_t *uid, + gid_t *gid) +{ + struct passwd *passwd_entry; + + g_assert (username != NULL); + g_assert (uid != NULL); + g_assert (gid != NULL); + + errno = 0; + gdm_get_pwent_for_name (username, &passwd_entry); + + if (passwd_entry == NULL) { + return FALSE; + } + + *uid = passwd_entry->pw_uid; + *gid = passwd_entry->pw_gid; + + return TRUE; +} + +static void +clean_up_stale_auth_subdirs (void) +{ + GDir *dir; + const char *filename; + + dir = g_dir_open (GDM_XAUTH_DIR, 0, NULL); + + if (dir == NULL) { + return; + } + + while ((filename = g_dir_read_name (dir)) != NULL) { + char *path; + + path = g_build_filename (GDM_XAUTH_DIR, filename, NULL); + + /* Will only succeed if the directory is empty + */ + g_rmdir (path); + g_free (path); + } + g_dir_close (dir); +} + +static FILE * +_create_xauth_file_for_user (const char *username, + char **filename, + GError **error) +{ + char *template; + const char *dir_name; + char *auth_filename; + int fd; + FILE *fp; + uid_t uid; + gid_t gid; + + g_assert (filename != NULL); + + *filename = NULL; + + template = NULL; + auth_filename = NULL; + fp = NULL; + fd = -1; + + /* Create directory if not exist, then set permission 0711 and ownership root:gdm */ + if (g_file_test (GDM_XAUTH_DIR, G_FILE_TEST_IS_DIR) == FALSE) { + g_remove (GDM_XAUTH_DIR); + if (g_mkdir (GDM_XAUTH_DIR, 0711) != 0) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); + goto out; + } + + g_chmod (GDM_XAUTH_DIR, 0711); + _get_uid_and_gid_for_user (GDM_USERNAME, &uid, &gid); + if (chown (GDM_XAUTH_DIR, 0, gid) != 0) { + g_warning ("Unable to change owner of '%s'", + GDM_XAUTH_DIR); + } + } else { + /* if it does exist make sure it has correct mode 0711 */ + g_chmod (GDM_XAUTH_DIR, 0711); + + /* and clean up any stale auth subdirs */ + clean_up_stale_auth_subdirs (); + } + + if (!_get_uid_and_gid_for_user (username, &uid, &gid)) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_USER_INFO, + _("could not find user “%s” on system"), + username); + goto out; + + } + + template = g_strdup_printf (GDM_XAUTH_DIR + "/auth-for-%s-XXXXXX", + username); + + g_debug ("GdmDisplayAccessFile: creating xauth directory %s", template); + /* Initially create with mode 01700 then later chmod after we create database */ + errno = 0; + dir_name = g_mkdtemp (template); + if (dir_name == NULL) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "Unable to create temp dir from tempalte '%s': %s", + template, + g_strerror (errno)); + goto out; + } + + g_debug ("GdmDisplayAccessFile: chowning %s to %u:%u", + dir_name, (guint)uid, (guint)gid); + errno = 0; + if (chown (dir_name, uid, gid) < 0) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "Unable to change permission of '%s': %s", + dir_name, + g_strerror (errno)); + goto out; + } + + auth_filename = g_build_filename (dir_name, "database", NULL); + + g_debug ("GdmDisplayAccessFile: creating %s", auth_filename); + /* mode 00600 */ + errno = 0; + fd = g_open (auth_filename, + O_RDWR | O_CREAT | O_EXCL | O_BINARY, + S_IRUSR | S_IWUSR); + + if (fd < 0) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "Unable to open '%s': %s", + auth_filename, + g_strerror (errno)); + goto out; + } + + g_debug ("GdmDisplayAccessFile: chowning %s to %u:%u", auth_filename, (guint)uid, (guint)gid); + errno = 0; + if (fchown (fd, uid, gid) < 0) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "Unable to change owner for '%s': %s", + auth_filename, + g_strerror (errno)); + close (fd); + fd = -1; + goto out; + } + + /* now open up permissions on per-session directory */ + g_debug ("GdmDisplayAccessFile: chmoding %s to 0711", dir_name); + g_chmod (dir_name, 0711); + + errno = 0; + fp = fdopen (fd, "w"); + if (fp == NULL) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); + close (fd); + fd = -1; + goto out; + } + + *filename = auth_filename; + auth_filename = NULL; + + /* don't close it */ + fd = -1; +out: + g_free (template); + g_free (auth_filename); + if (fd != -1) { + close (fd); + } + + return fp; +} + +gboolean +gdm_display_access_file_open (GdmDisplayAccessFile *file, + GError **error) +{ + GError *create_error; + + g_return_val_if_fail (file != NULL, FALSE); + g_return_val_if_fail (file->fp == NULL, FALSE); + g_return_val_if_fail (file->path == NULL, FALSE); + + create_error = NULL; + file->fp = _create_xauth_file_for_user (file->username, + &file->path, + &create_error); + + if (file->fp == NULL) { + g_propagate_error (error, create_error); + return FALSE; + } + + return TRUE; +} + +static void +_get_auth_info_for_display (GdmDisplayAccessFile *file, + GdmDisplay *display, + unsigned short *family, + unsigned short *address_length, + char **address, + unsigned short *number_length, + char **number, + unsigned short *name_length, + char **name) +{ + int display_number; + gboolean is_local; + + gdm_display_is_local (display, &is_local, NULL); + + if (is_local) { + /* We could just use FamilyWild here except xauth + * (and by extension su and ssh) doesn't support it yet + * + * https://bugs.freedesktop.org/show_bug.cgi?id=43425 + */ + char localhost[HOST_NAME_MAX + 1] = ""; + *family = FamilyLocal; + if (gethostname (localhost, HOST_NAME_MAX) == 0) { + *address = g_strdup (localhost); + } else { + *address = g_strdup ("localhost"); + } + } else { + *family = FamilyWild; + gdm_display_get_remote_hostname (display, address, NULL); + } + *address_length = strlen (*address); + + gdm_display_get_x11_display_number (display, &display_number, NULL); + *number = g_strdup_printf ("%d", display_number); + *number_length = strlen (*number); + + *name = g_strdup ("MIT-MAGIC-COOKIE-1"); + *name_length = strlen (*name); +} + +gboolean +gdm_display_access_file_add_display (GdmDisplayAccessFile *file, + GdmDisplay *display, + char **cookie, + gsize *cookie_size, + GError **error) +{ + GError *add_error; + gboolean display_added; + + g_return_val_if_fail (file != NULL, FALSE); + g_return_val_if_fail (file->path != NULL, FALSE); + g_return_val_if_fail (cookie != NULL, FALSE); + + add_error = NULL; + *cookie = gdm_generate_random_bytes (GDM_DISPLAY_ACCESS_COOKIE_SIZE, + &add_error); + + if (*cookie == NULL) { + g_propagate_error (error, add_error); + return FALSE; + } + + *cookie_size = GDM_DISPLAY_ACCESS_COOKIE_SIZE; + + display_added = gdm_display_access_file_add_display_with_cookie (file, display, + *cookie, + *cookie_size, + &add_error); + if (!display_added) { + g_free (*cookie); + *cookie = NULL; + g_propagate_error (error, add_error); + return FALSE; + } + + return TRUE; +} + +gboolean +gdm_display_access_file_add_display_with_cookie (GdmDisplayAccessFile *file, + GdmDisplay *display, + const char *cookie, + gsize cookie_size, + GError **error) +{ + Xauth auth_entry; + gboolean display_added; + + g_return_val_if_fail (file != NULL, FALSE); + g_return_val_if_fail (file->path != NULL, FALSE); + g_return_val_if_fail (cookie != NULL, FALSE); + + _get_auth_info_for_display (file, display, + &auth_entry.family, + &auth_entry.address_length, + &auth_entry.address, + &auth_entry.number_length, + &auth_entry.number, + &auth_entry.name_length, + &auth_entry.name); + + auth_entry.data = (char *) cookie; + auth_entry.data_length = cookie_size; + + /* FIXME: We should lock the file in case the X server is + * trying to use it, too. + */ + if (!XauWriteAuth (file->fp, &auth_entry) + || fflush (file->fp) == EOF) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); + display_added = FALSE; + } else { + display_added = TRUE; + } + + /* If we wrote a FamilyLocal entry, we still want a FamilyWild + * entry, because it's more resiliant against hostname changes + * + */ + if (auth_entry.family == FamilyLocal) { + auth_entry.family = FamilyWild; + + if (XauWriteAuth (file->fp, &auth_entry) + && fflush (file->fp) != EOF) { + display_added = TRUE; + } + } + + g_free (auth_entry.address); + g_free (auth_entry.number); + g_free (auth_entry.name); + + return display_added; +} + +void +gdm_display_access_file_close (GdmDisplayAccessFile *file) +{ + char *auth_dir; + + g_return_if_fail (file != NULL); + g_return_if_fail (file->fp != NULL); + g_return_if_fail (file->path != NULL); + + errno = 0; + if (g_unlink (file->path) != 0) { + g_warning ("GdmDisplayAccessFile: Unable to remove X11 authority database '%s': %s", + file->path, + g_strerror (errno)); + } + + /* still try to remove dir even if file remove failed, + may have already been removed by someone else */ + /* we own the parent directory too */ + auth_dir = g_path_get_dirname (file->path); + if (auth_dir != NULL) { + errno = 0; + if (g_rmdir (auth_dir) != 0) { + g_warning ("GdmDisplayAccessFile: Unable to remove X11 authority directory '%s': %s", + auth_dir, + g_strerror (errno)); + } + g_free (auth_dir); + } + + g_free (file->path); + file->path = NULL; + g_object_notify (G_OBJECT (file), "path"); + + fclose (file->fp); + file->fp = NULL; +} + +char * +gdm_display_access_file_get_path (GdmDisplayAccessFile *access_file) +{ + return g_strdup (access_file->path); +} diff --git a/daemon/gdm-display-access-file.h b/daemon/gdm-display-access-file.h new file mode 100644 index 0000000..559d3ed --- /dev/null +++ b/daemon/gdm-display-access-file.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * gdm-display-access-file.h - Abstraction around xauth cookies + * + * Copyright (C) 2007 Ray Strode <rstrode@redhat.com> + * + * Written by Ray Strode <rstrode@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#ifndef __GDM_DISPLAY_ACCESS_FILE_H__ +#define __GDM_DISPLAY_ACCESS_FILE_H__ + +#include <glib.h> +#include <glib-object.h> + +#include "gdm-display.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_DISPLAY_ACCESS_FILE (gdm_display_access_file_get_type ()) +G_DECLARE_FINAL_TYPE (GdmDisplayAccessFile, gdm_display_access_file, GDM, DISPLAY_ACCESS_FILE, GObject) + +#define GDM_DISPLAY_ACCESS_FILE_ERROR (gdm_display_access_file_error_quark ()) + +typedef enum _GdmDisplayAccessFileError GdmDisplayAccessFileError; + +enum _GdmDisplayAccessFileError +{ + GDM_DISPLAY_ACCESS_FILE_ERROR_GENERAL = 0, + GDM_DISPLAY_ACCESS_FILE_ERROR_FINDING_AUTH_ENTRY +}; + +GQuark gdm_display_access_file_error_quark (void); + +GdmDisplayAccessFile *gdm_display_access_file_new (const char *username); +gboolean gdm_display_access_file_open (GdmDisplayAccessFile *file, + GError **error); +gboolean gdm_display_access_file_add_display (GdmDisplayAccessFile *file, + GdmDisplay *display, + char **cookie, + gsize *cookie_size, + GError **error); +gboolean gdm_display_access_file_add_display_with_cookie (GdmDisplayAccessFile *file, + GdmDisplay *display, + const char *cookie, + gsize cookie_size, + GError **error); + +void gdm_display_access_file_close (GdmDisplayAccessFile *file); +char *gdm_display_access_file_get_path (GdmDisplayAccessFile *file); + +G_END_DECLS +#endif /* __GDM_DISPLAY_ACCESS_FILE_H__ */ diff --git a/daemon/gdm-display-factory.c b/daemon/gdm-display-factory.c new file mode 100644 index 0000000..1214f7f --- /dev/null +++ b/daemon/gdm-display-factory.c @@ -0,0 +1,240 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-display-factory.h" +#include "gdm-display-store.h" + +typedef struct _GdmDisplayFactoryPrivate +{ + GdmDisplayStore *display_store; + guint purge_displays_id; +} GdmDisplayFactoryPrivate; + +enum { + PROP_0, + PROP_DISPLAY_STORE, +}; + +static void gdm_display_factory_class_init (GdmDisplayFactoryClass *klass); +static void gdm_display_factory_init (GdmDisplayFactory *factory); +static void gdm_display_factory_finalize (GObject *object); + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdmDisplayFactory, gdm_display_factory, G_TYPE_OBJECT) + +GQuark +gdm_display_factory_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gdm_display_factory_error"); + } + + return ret; +} + +static gboolean +purge_display (char *id, + GdmDisplay *display, + gpointer user_data) +{ + int status; + + status = gdm_display_get_status (display); + + switch (status) { + case GDM_DISPLAY_FINISHED: + case GDM_DISPLAY_FAILED: + return TRUE; + default: + return FALSE; + } +} + +static void +purge_displays (GdmDisplayFactory *factory) +{ + GdmDisplayFactoryPrivate *priv; + + priv = gdm_display_factory_get_instance_private (factory); + priv->purge_displays_id = 0; + gdm_display_store_foreach_remove (priv->display_store, + (GdmDisplayStoreFunc)purge_display, + NULL); +} + +void +gdm_display_factory_queue_purge_displays (GdmDisplayFactory *factory) +{ + GdmDisplayFactoryPrivate *priv; + + priv = gdm_display_factory_get_instance_private (factory); + if (priv->purge_displays_id == 0) { + priv->purge_displays_id = g_idle_add ((GSourceFunc) purge_displays, factory); + } +} + +GdmDisplayStore * +gdm_display_factory_get_display_store (GdmDisplayFactory *factory) +{ + GdmDisplayFactoryPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY_FACTORY (factory), NULL); + + priv = gdm_display_factory_get_instance_private (factory); + return priv->display_store; +} + +gboolean +gdm_display_factory_start (GdmDisplayFactory *factory) +{ + gboolean ret; + + g_return_val_if_fail (GDM_IS_DISPLAY_FACTORY (factory), FALSE); + + g_object_ref (factory); + ret = GDM_DISPLAY_FACTORY_GET_CLASS (factory)->start (factory); + g_object_unref (factory); + + return ret; +} + +gboolean +gdm_display_factory_stop (GdmDisplayFactory *factory) +{ + gboolean ret; + + g_return_val_if_fail (GDM_IS_DISPLAY_FACTORY (factory), FALSE); + + g_object_ref (factory); + ret = GDM_DISPLAY_FACTORY_GET_CLASS (factory)->stop (factory); + g_object_unref (factory); + + return ret; +} + +static void +gdm_display_factory_set_display_store (GdmDisplayFactory *factory, + GdmDisplayStore *display_store) +{ + GdmDisplayFactoryPrivate *priv; + + priv = gdm_display_factory_get_instance_private (factory); + g_clear_object (&priv->display_store); + + if (display_store != NULL) { + priv->display_store = g_object_ref (display_store); + } +} + +static void +gdm_display_factory_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmDisplayFactory *self; + + self = GDM_DISPLAY_FACTORY (object); + + switch (prop_id) { + case PROP_DISPLAY_STORE: + gdm_display_factory_set_display_store (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_display_factory_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmDisplayFactory *self; + GdmDisplayFactoryPrivate *priv; + + self = GDM_DISPLAY_FACTORY (object); + priv = gdm_display_factory_get_instance_private (self); + + switch (prop_id) { + case PROP_DISPLAY_STORE: + g_value_set_object (value, priv->display_store); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_display_factory_class_init (GdmDisplayFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_display_factory_get_property; + object_class->set_property = gdm_display_factory_set_property; + object_class->finalize = gdm_display_factory_finalize; + + g_object_class_install_property (object_class, + PROP_DISPLAY_STORE, + g_param_spec_object ("display-store", + "display store", + "display store", + GDM_TYPE_DISPLAY_STORE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +gdm_display_factory_init (GdmDisplayFactory *factory) +{ +} + +static void +gdm_display_factory_finalize (GObject *object) +{ + GdmDisplayFactory *factory; + GdmDisplayFactoryPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_DISPLAY_FACTORY (object)); + + factory = GDM_DISPLAY_FACTORY (object); + priv = gdm_display_factory_get_instance_private (factory); + + g_return_if_fail (priv != NULL); + + if (priv->purge_displays_id != 0) { + g_source_remove (priv->purge_displays_id); + priv->purge_displays_id = 0; + } + + G_OBJECT_CLASS (gdm_display_factory_parent_class)->finalize (object); +} diff --git a/daemon/gdm-display-factory.h b/daemon/gdm-display-factory.h new file mode 100644 index 0000000..b17cb1c --- /dev/null +++ b/daemon/gdm-display-factory.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_DISPLAY_FACTORY_H +#define __GDM_DISPLAY_FACTORY_H + +#include <glib-object.h> + +#include "gdm-display-store.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_DISPLAY_FACTORY (gdm_display_factory_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GdmDisplayFactory, gdm_display_factory, GDM, DISPLAY_FACTORY, GObject) + +struct _GdmDisplayFactoryClass +{ + GObjectClass parent_class; + + gboolean (*start) (GdmDisplayFactory *factory); + gboolean (*stop) (GdmDisplayFactory *factory); +}; + +typedef enum +{ + GDM_DISPLAY_FACTORY_ERROR_GENERAL +} GdmDisplayFactoryError; + +#define GDM_DISPLAY_FACTORY_ERROR gdm_display_factory_error_quark () + +GQuark gdm_display_factory_error_quark (void); +GType gdm_display_factory_get_type (void); + +gboolean gdm_display_factory_start (GdmDisplayFactory *manager); +gboolean gdm_display_factory_stop (GdmDisplayFactory *manager); +GdmDisplayStore * gdm_display_factory_get_display_store (GdmDisplayFactory *manager); +void gdm_display_factory_queue_purge_displays (GdmDisplayFactory *manager); + +G_END_DECLS + +#endif /* __GDM_DISPLAY_FACTORY_H */ diff --git a/daemon/gdm-display-store.c b/daemon/gdm-display-store.c new file mode 100644 index 0000000..7df69d9 --- /dev/null +++ b/daemon/gdm-display-store.c @@ -0,0 +1,331 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-display-store.h" +#include "gdm-display.h" + +struct GdmDisplayStorePrivate +{ + GHashTable *displays; +}; + +typedef struct +{ + GdmDisplayStore *store; + GdmDisplay *display; +} StoredDisplay; + +enum { + DISPLAY_ADDED, + DISPLAY_REMOVED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_display_store_class_init (GdmDisplayStoreClass *klass); +static void gdm_display_store_init (GdmDisplayStore *display_store); +static void gdm_display_store_finalize (GObject *object); + +G_DEFINE_TYPE_WITH_PRIVATE (GdmDisplayStore, gdm_display_store, G_TYPE_OBJECT) + +static StoredDisplay * +stored_display_new (GdmDisplayStore *store, + GdmDisplay *display) +{ + StoredDisplay *stored_display; + + stored_display = g_slice_new (StoredDisplay); + stored_display->store = store; + stored_display->display = g_object_ref (display); + + return stored_display; +} + +static void +stored_display_free (StoredDisplay *stored_display) +{ + g_signal_emit (G_OBJECT (stored_display->store), + signals[DISPLAY_REMOVED], + 0, + stored_display->display); + + g_debug ("GdmDisplayStore: Unreffing display: %p", + stored_display->display); + g_object_unref (stored_display->display); + + g_slice_free (StoredDisplay, stored_display); +} + +GQuark +gdm_display_store_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gdm_display_store_error"); + } + + return ret; +} + +void +gdm_display_store_clear (GdmDisplayStore *store) +{ + g_return_if_fail (store != NULL); + g_debug ("GdmDisplayStore: Clearing display store"); + g_hash_table_remove_all (store->priv->displays); +} + +static gboolean +remove_display (char *id, + GdmDisplay *display, + GdmDisplay *display_to_remove) +{ + if (display == display_to_remove) { + return TRUE; + } + return FALSE; +} + +gboolean +gdm_display_store_remove (GdmDisplayStore *store, + GdmDisplay *display) +{ + g_return_val_if_fail (store != NULL, FALSE); + + gdm_display_store_foreach_remove (store, + (GdmDisplayStoreFunc)remove_display, + display); + return FALSE; +} + +typedef struct +{ + GdmDisplayStoreFunc predicate; + gpointer user_data; +} FindClosure; + +static gboolean +find_func (const char *id, + StoredDisplay *stored_display, + FindClosure *closure) +{ + return closure->predicate (id, + stored_display->display, + closure->user_data); +} + +static void +foreach_func (const char *id, + StoredDisplay *stored_display, + FindClosure *closure) +{ + (void) closure->predicate (id, + stored_display->display, + closure->user_data); +} + +void +gdm_display_store_foreach (GdmDisplayStore *store, + GdmDisplayStoreFunc func, + gpointer user_data) +{ + FindClosure closure; + + g_return_if_fail (store != NULL); + g_return_if_fail (func != NULL); + + closure.predicate = func; + closure.user_data = user_data; + + g_hash_table_foreach (store->priv->displays, + (GHFunc) foreach_func, + &closure); +} + +GdmDisplay * +gdm_display_store_lookup (GdmDisplayStore *store, + const char *id) +{ + StoredDisplay *stored_display; + + g_return_val_if_fail (store != NULL, NULL); + g_return_val_if_fail (id != NULL, NULL); + + stored_display = g_hash_table_lookup (store->priv->displays, + id); + if (stored_display == NULL) { + return NULL; + } + + return stored_display->display; +} + +GdmDisplay * +gdm_display_store_find (GdmDisplayStore *store, + GdmDisplayStoreFunc predicate, + gpointer user_data) +{ + StoredDisplay *stored_display; + FindClosure closure; + + g_return_val_if_fail (store != NULL, NULL); + g_return_val_if_fail (predicate != NULL, NULL); + + closure.predicate = predicate; + closure.user_data = user_data; + + stored_display = g_hash_table_find (store->priv->displays, + (GHRFunc) find_func, + &closure); + + if (stored_display == NULL) { + return NULL; + } + + return stored_display->display; +} + +guint +gdm_display_store_foreach_remove (GdmDisplayStore *store, + GdmDisplayStoreFunc func, + gpointer user_data) +{ + FindClosure closure; + guint ret; + + g_return_val_if_fail (store != NULL, 0); + g_return_val_if_fail (func != NULL, 0); + + closure.predicate = func; + closure.user_data = user_data; + + ret = g_hash_table_foreach_remove (store->priv->displays, + (GHRFunc) find_func, + &closure); + return ret; +} + +void +gdm_display_store_add (GdmDisplayStore *store, + GdmDisplay *display) +{ + char *id; + StoredDisplay *stored_display; + + g_return_if_fail (store != NULL); + g_return_if_fail (display != NULL); + + gdm_display_get_id (display, &id, NULL); + + g_debug ("GdmDisplayStore: Adding display %s to store", id); + + stored_display = stored_display_new (store, display); + g_hash_table_insert (store->priv->displays, + id, + stored_display); + + g_signal_emit (G_OBJECT (store), + signals[DISPLAY_ADDED], + 0, + id); +} + +static void +gdm_display_store_class_init (GdmDisplayStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gdm_display_store_finalize; + + signals [DISPLAY_ADDED] = + g_signal_new ("display-added", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmDisplayStoreClass, display_added), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [DISPLAY_REMOVED] = + g_signal_new ("display-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmDisplayStoreClass, display_removed), + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, G_TYPE_OBJECT); +} + +static void +gdm_display_store_init (GdmDisplayStore *store) +{ + + store->priv = gdm_display_store_get_instance_private (store); + + store->priv->displays = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) + stored_display_free); +} + +static void +gdm_display_store_finalize (GObject *object) +{ + GdmDisplayStore *store; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_DISPLAY_STORE (object)); + + store = GDM_DISPLAY_STORE (object); + + g_return_if_fail (store->priv != NULL); + + g_hash_table_destroy (store->priv->displays); + + G_OBJECT_CLASS (gdm_display_store_parent_class)->finalize (object); +} + +GdmDisplayStore * +gdm_display_store_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_DISPLAY_STORE, + NULL); + + return GDM_DISPLAY_STORE (object); +} diff --git a/daemon/gdm-display-store.h b/daemon/gdm-display-store.h new file mode 100644 index 0000000..0aff8ee --- /dev/null +++ b/daemon/gdm-display-store.h @@ -0,0 +1,92 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_DISPLAY_STORE_H +#define __GDM_DISPLAY_STORE_H + +#include <glib-object.h> +#include "gdm-display.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_DISPLAY_STORE (gdm_display_store_get_type ()) +#define GDM_DISPLAY_STORE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_DISPLAY_STORE, GdmDisplayStore)) +#define GDM_DISPLAY_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_DISPLAY_STORE, GdmDisplayStoreClass)) +#define GDM_IS_DISPLAY_STORE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_DISPLAY_STORE)) +#define GDM_IS_DISPLAY_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_DISPLAY_STORE)) +#define GDM_DISPLAY_STORE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_DISPLAY_STORE, GdmDisplayStoreClass)) + +typedef struct GdmDisplayStorePrivate GdmDisplayStorePrivate; + +typedef struct +{ + GObject parent; + GdmDisplayStorePrivate *priv; +} GdmDisplayStore; + +typedef struct +{ + GObjectClass parent_class; + + void (* display_added) (GdmDisplayStore *display_store, + const char *id); + void (* display_removed) (GdmDisplayStore *display_store, + GdmDisplay *display); +} GdmDisplayStoreClass; + +typedef enum +{ + GDM_DISPLAY_STORE_ERROR_GENERAL +} GdmDisplayStoreError; + +#define GDM_DISPLAY_STORE_ERROR gdm_display_store_error_quark () + +typedef gboolean (*GdmDisplayStoreFunc) (const char *id, + GdmDisplay *display, + gpointer user_data); + +GQuark gdm_display_store_error_quark (void); +GType gdm_display_store_get_type (void); + +GdmDisplayStore * gdm_display_store_new (void); + +void gdm_display_store_add (GdmDisplayStore *store, + GdmDisplay *display); +void gdm_display_store_clear (GdmDisplayStore *store); +gboolean gdm_display_store_remove (GdmDisplayStore *store, + GdmDisplay *display); +void gdm_display_store_foreach (GdmDisplayStore *store, + GdmDisplayStoreFunc func, + gpointer user_data); +guint gdm_display_store_foreach_remove (GdmDisplayStore *store, + GdmDisplayStoreFunc func, + gpointer user_data); +GdmDisplay * gdm_display_store_lookup (GdmDisplayStore *store, + const char *id); + +GdmDisplay * gdm_display_store_find (GdmDisplayStore *store, + GdmDisplayStoreFunc predicate, + gpointer user_data); + + +G_END_DECLS + +#endif /* __GDM_DISPLAY_STORE_H */ diff --git a/daemon/gdm-display.c b/daemon/gdm-display.c new file mode 100644 index 0000000..687e7da --- /dev/null +++ b/daemon/gdm-display.c @@ -0,0 +1,1931 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include <xcb/xcb.h> +#include <X11/Xlib.h> + +#include "gdm-common.h" +#include "gdm-display.h" +#include "gdm-display-glue.h" +#include "gdm-display-access-file.h" +#include "gdm-launch-environment.h" + +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +#include "gdm-launch-environment.h" +#include "gdm-dbus-util.h" + +#define GNOME_SESSION_SESSIONS_PATH DATADIR "/gnome-session/sessions" + +typedef struct _GdmDisplayPrivate +{ + GObject parent; + + char *id; + char *seat_id; + char *session_id; + char *session_class; + char *session_type; + + char *remote_hostname; + int x11_display_number; + char *x11_display_name; + int status; + time_t creation_time; + + char *x11_cookie; + gsize x11_cookie_size; + GdmDisplayAccessFile *access_file; + + guint finish_idle_id; + + xcb_connection_t *xcb_connection; + int xcb_screen_number; + + GDBusConnection *connection; + GdmDisplayAccessFile *user_access_file; + + GdmDBusDisplay *display_skeleton; + GDBusObjectSkeleton *object_skeleton; + + GDBusProxy *accountsservice_proxy; + + /* this spawns and controls the greeter session */ + GdmLaunchEnvironment *launch_environment; + + guint is_local : 1; + guint is_initial : 1; + guint allow_timed_login : 1; + guint have_existing_user_accounts : 1; + guint doing_initial_setup : 1; + guint session_registered : 1; +} GdmDisplayPrivate; + +enum { + PROP_0, + PROP_ID, + PROP_STATUS, + PROP_SEAT_ID, + PROP_SESSION_ID, + PROP_SESSION_CLASS, + PROP_SESSION_TYPE, + PROP_REMOTE_HOSTNAME, + PROP_X11_DISPLAY_NUMBER, + PROP_X11_DISPLAY_NAME, + PROP_X11_COOKIE, + PROP_X11_AUTHORITY_FILE, + PROP_IS_CONNECTED, + PROP_IS_LOCAL, + PROP_LAUNCH_ENVIRONMENT, + PROP_IS_INITIAL, + PROP_ALLOW_TIMED_LOGIN, + PROP_HAVE_EXISTING_USER_ACCOUNTS, + PROP_DOING_INITIAL_SETUP, + PROP_SESSION_REGISTERED, +}; + +static void gdm_display_class_init (GdmDisplayClass *klass); +static void gdm_display_init (GdmDisplay *self); +static void gdm_display_finalize (GObject *object); +static void queue_finish (GdmDisplay *self); +static void _gdm_display_set_status (GdmDisplay *self, + int status); +static gboolean wants_initial_setup (GdmDisplay *self); +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdmDisplay, gdm_display, G_TYPE_OBJECT) + +GQuark +gdm_display_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gdm_display_error"); + } + + return ret; +} + +time_t +gdm_display_get_creation_time (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), 0); + + priv = gdm_display_get_instance_private (self); + return priv->creation_time; +} + +int +gdm_display_get_status (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), 0); + + priv = gdm_display_get_instance_private (self); + return priv->status; +} + +const char * +gdm_display_get_session_id (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + return priv->session_id; +} + +static GdmDisplayAccessFile * +_create_access_file_for_user (GdmDisplay *self, + const char *username, + GError **error) +{ + GdmDisplayAccessFile *access_file; + GError *file_error; + + access_file = gdm_display_access_file_new (username); + + file_error = NULL; + if (!gdm_display_access_file_open (access_file, &file_error)) { + g_propagate_error (error, file_error); + return NULL; + } + + return access_file; +} + +gboolean +gdm_display_create_authority (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + GdmDisplayAccessFile *access_file; + GError *error; + gboolean res; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + g_return_val_if_fail (priv->access_file == NULL, FALSE); + + error = NULL; + access_file = _create_access_file_for_user (self, GDM_USERNAME, &error); + + if (access_file == NULL) { + g_critical ("could not create display access file: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_free (priv->x11_cookie); + priv->x11_cookie = NULL; + res = gdm_display_access_file_add_display (access_file, + self, + &priv->x11_cookie, + &priv->x11_cookie_size, + &error); + + if (! res) { + + g_critical ("could not add display to access file: %s", error->message); + g_error_free (error); + gdm_display_access_file_close (access_file); + g_object_unref (access_file); + return FALSE; + } + + priv->access_file = access_file; + + return TRUE; +} + +static void +setup_xhost_auth (XHostAddress *host_entries) +{ + host_entries[0].family = FamilyServerInterpreted; + host_entries[0].address = "localuser\0root"; + host_entries[0].length = sizeof ("localuser\0root"); + host_entries[1].family = FamilyServerInterpreted; + host_entries[1].address = "localuser\0" GDM_USERNAME; + host_entries[1].length = sizeof ("localuser\0" GDM_USERNAME); + host_entries[2].family = FamilyServerInterpreted; + host_entries[2].address = "localuser\0gnome-initial-setup"; + host_entries[2].length = sizeof ("localuser\0gnome-initial-setup"); +} + +gboolean +gdm_display_add_user_authorization (GdmDisplay *self, + const char *username, + char **filename, + GError **error) +{ + GdmDisplayPrivate *priv; + GdmDisplayAccessFile *access_file; + GError *access_file_error; + gboolean res; + + int i; + XHostAddress host_entries[3]; + xcb_void_cookie_t cookies[3]; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + + g_debug ("GdmDisplay: Adding authorization for user:%s on display %s", username, priv->x11_display_name); + + if (priv->user_access_file != NULL) { + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "user access already assigned"); + return FALSE; + } + + g_debug ("GdmDisplay: Adding user authorization for %s", username); + + access_file_error = NULL; + access_file = _create_access_file_for_user (self, + username, + &access_file_error); + + if (access_file == NULL) { + g_propagate_error (error, access_file_error); + return FALSE; + } + + res = gdm_display_access_file_add_display_with_cookie (access_file, + self, + priv->x11_cookie, + priv->x11_cookie_size, + &access_file_error); + if (! res) { + g_debug ("GdmDisplay: Unable to add user authorization for %s: %s", + username, + access_file_error->message); + g_propagate_error (error, access_file_error); + gdm_display_access_file_close (access_file); + g_object_unref (access_file); + return FALSE; + } + + *filename = gdm_display_access_file_get_path (access_file); + priv->user_access_file = access_file; + + g_debug ("GdmDisplay: Added user authorization for %s: %s", username, *filename); + /* Remove access for the programs run by greeter now that the + * user session is starting. + */ + setup_xhost_auth (host_entries); + + for (i = 0; i < G_N_ELEMENTS (host_entries); i++) { + cookies[i] = xcb_change_hosts_checked (priv->xcb_connection, + XCB_HOST_MODE_DELETE, + host_entries[i].family, + host_entries[i].length, + (uint8_t *) host_entries[i].address); + } + + for (i = 0; i < G_N_ELEMENTS (cookies); i++) { + xcb_generic_error_t *xcb_error; + + xcb_error = xcb_request_check (priv->xcb_connection, cookies[i]); + + if (xcb_error != NULL) { + g_warning ("Failed to remove greeter program access to the display. Trying to proceed."); + free (xcb_error); + } + } + + return TRUE; +} + +gboolean +gdm_display_remove_user_authorization (GdmDisplay *self, + const char *username, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + + g_debug ("GdmDisplay: Removing authorization for user:%s on display %s", username, priv->x11_display_name); + + gdm_display_access_file_close (priv->user_access_file); + + return TRUE; +} + +gboolean +gdm_display_get_x11_cookie (GdmDisplay *self, + const char **x11_cookie, + gsize *x11_cookie_size, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + + if (x11_cookie != NULL) { + *x11_cookie = priv->x11_cookie; + } + + if (x11_cookie_size != NULL) { + *x11_cookie_size = priv->x11_cookie_size; + } + + return TRUE; +} + +gboolean +gdm_display_get_x11_authority_file (GdmDisplay *self, + char **filename, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + priv = gdm_display_get_instance_private (self); + if (priv->access_file != NULL) { + *filename = gdm_display_access_file_get_path (priv->access_file); + } else { + *filename = NULL; + } + + return TRUE; +} + +gboolean +gdm_display_get_remote_hostname (GdmDisplay *self, + char **hostname, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + if (hostname != NULL) { + *hostname = g_strdup (priv->remote_hostname); + } + + return TRUE; +} + +gboolean +gdm_display_get_x11_display_number (GdmDisplay *self, + int *number, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + if (number != NULL) { + *number = priv->x11_display_number; + } + + return TRUE; +} + +gboolean +gdm_display_get_seat_id (GdmDisplay *self, + char **seat_id, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + if (seat_id != NULL) { + *seat_id = g_strdup (priv->seat_id); + } + + return TRUE; +} + +gboolean +gdm_display_is_initial (GdmDisplay *self, + gboolean *is_initial, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + if (is_initial != NULL) { + *is_initial = priv->is_initial; + } + + return TRUE; +} + +static gboolean +finish_idle (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + priv->finish_idle_id = 0; + /* finish may end up finalizing object */ + gdm_display_finish (self); + return FALSE; +} + +static void +queue_finish (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + if (priv->finish_idle_id == 0) { + priv->finish_idle_id = g_idle_add ((GSourceFunc)finish_idle, self); + } +} + +static void +_gdm_display_set_status (GdmDisplay *self, + int status) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + if (status != priv->status) { + priv->status = status; + g_object_notify (G_OBJECT (self), "status"); + } +} + +static gboolean +gdm_display_real_prepare (GdmDisplay *self) +{ + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + g_debug ("GdmDisplay: prepare display"); + + _gdm_display_set_status (self, GDM_DISPLAY_PREPARED); + + return TRUE; +} + +static gboolean +look_for_existing_users_sync (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + GError *error = NULL; + GVariant *call_result; + GVariant *user_list; + + priv = gdm_display_get_instance_private (self); + priv->accountsservice_proxy = g_dbus_proxy_new_sync (priv->connection, + 0, NULL, + "org.freedesktop.Accounts", + "/org/freedesktop/Accounts", + "org.freedesktop.Accounts", + NULL, + &error); + + if (!priv->accountsservice_proxy) { + g_critical ("Failed to contact accountsservice: %s", error->message); + goto out; + } + + call_result = g_dbus_proxy_call_sync (priv->accountsservice_proxy, + "ListCachedUsers", + NULL, + 0, + -1, + NULL, + &error); + + if (!call_result) { + g_critical ("Failed to list cached users: %s", error->message); + goto out; + } + + g_variant_get (call_result, "(@ao)", &user_list); + priv->have_existing_user_accounts = g_variant_n_children (user_list) > 0; + g_variant_unref (user_list); + g_variant_unref (call_result); +out: + g_clear_error (&error); + return priv->accountsservice_proxy != NULL && call_result != NULL; +} + +gboolean +gdm_display_prepare (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + gboolean ret; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + + g_debug ("GdmDisplay: Preparing display: %s", priv->id); + + /* FIXME: we should probably do this in a more global place, + * asynchronously + */ + if (!look_for_existing_users_sync (self)) { + exit (EXIT_FAILURE); + } + + priv->doing_initial_setup = wants_initial_setup (self); + + g_object_ref (self); + ret = GDM_DISPLAY_GET_CLASS (self)->prepare (self); + g_object_unref (self); + + return ret; +} + +gboolean +gdm_display_manage (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + gboolean res; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + + g_debug ("GdmDisplay: Managing display: %s", priv->id); + + /* If not explicitly prepared, do it now */ + if (priv->status == GDM_DISPLAY_UNMANAGED) { + res = gdm_display_prepare (self); + if (! res) { + return FALSE; + } + } + + if (g_strcmp0 (priv->session_class, "greeter") == 0) { + if (GDM_DISPLAY_GET_CLASS (self)->manage != NULL) { + GDM_DISPLAY_GET_CLASS (self)->manage (self); + } + } + + return TRUE; +} + +gboolean +gdm_display_finish (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + if (priv->finish_idle_id != 0) { + g_source_remove (priv->finish_idle_id); + priv->finish_idle_id = 0; + } + + _gdm_display_set_status (self, GDM_DISPLAY_FINISHED); + + g_debug ("GdmDisplay: finish display"); + + return TRUE; +} + +static void +gdm_display_disconnect (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + /* These 3 bits are reserved/unused by the X protocol */ + guint32 unused_bits = 0b11100000000000000000000000000000; + XID highest_client, client; + guint32 client_increment; + const xcb_setup_t *setup; + + priv = gdm_display_get_instance_private (self); + + if (priv->xcb_connection == NULL) { + return; + } + + setup = xcb_get_setup (priv->xcb_connection); + + /* resource_id_mask is the bits given to each client for + * addressing resources */ + highest_client = (XID) ~unused_bits & ~setup->resource_id_mask; + client_increment = setup->resource_id_mask + 1; + + /* Kill every client but ourselves, then close our own connection + */ + for (client = 0; + client <= highest_client; + client += client_increment) { + + if (client != setup->resource_id_base) + xcb_kill_client (priv->xcb_connection, client); + } + + xcb_flush (priv->xcb_connection); + + g_clear_pointer (&priv->xcb_connection, xcb_disconnect); +} + +gboolean +gdm_display_unmanage (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + + g_debug ("GdmDisplay: unmanage display"); + + gdm_display_disconnect (self); + + if (priv->user_access_file != NULL) { + gdm_display_access_file_close (priv->user_access_file); + g_object_unref (priv->user_access_file); + priv->user_access_file = NULL; + } + + if (priv->access_file != NULL) { + gdm_display_access_file_close (priv->access_file); + g_object_unref (priv->access_file); + priv->access_file = NULL; + } + + if (!priv->session_registered) { + g_warning ("GdmDisplay: Session never registered, failing"); + _gdm_display_set_status (self, GDM_DISPLAY_FAILED); + } else { + _gdm_display_set_status (self, GDM_DISPLAY_UNMANAGED); + } + + return TRUE; +} + +gboolean +gdm_display_get_id (GdmDisplay *self, + char **id, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + if (id != NULL) { + *id = g_strdup (priv->id); + } + + return TRUE; +} + +gboolean +gdm_display_get_x11_display_name (GdmDisplay *self, + char **x11_display, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + if (x11_display != NULL) { + *x11_display = g_strdup (priv->x11_display_name); + } + + return TRUE; +} + +gboolean +gdm_display_is_local (GdmDisplay *self, + gboolean *local, + GError **error) +{ + GdmDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_DISPLAY (self), FALSE); + + priv = gdm_display_get_instance_private (self); + if (local != NULL) { + *local = priv->is_local; + } + + return TRUE; +} + +static void +_gdm_display_set_id (GdmDisplay *self, + const char *id) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: id: %s", id); + g_free (priv->id); + priv->id = g_strdup (id); +} + +static void +_gdm_display_set_seat_id (GdmDisplay *self, + const char *seat_id) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: seat id: %s", seat_id); + g_free (priv->seat_id); + priv->seat_id = g_strdup (seat_id); +} + +static void +_gdm_display_set_session_id (GdmDisplay *self, + const char *session_id) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: session id: %s", session_id); + g_free (priv->session_id); + priv->session_id = g_strdup (session_id); +} + +static void +_gdm_display_set_session_class (GdmDisplay *self, + const char *session_class) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: session class: %s", session_class); + g_free (priv->session_class); + priv->session_class = g_strdup (session_class); +} + +static void +_gdm_display_set_session_type (GdmDisplay *self, + const char *session_type) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: session type: %s", session_type); + g_free (priv->session_type); + priv->session_type = g_strdup (session_type); +} + +static void +_gdm_display_set_remote_hostname (GdmDisplay *self, + const char *hostname) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_free (priv->remote_hostname); + priv->remote_hostname = g_strdup (hostname); +} + +static void +_gdm_display_set_x11_display_number (GdmDisplay *self, + int num) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + priv->x11_display_number = num; +} + +static void +_gdm_display_set_x11_display_name (GdmDisplay *self, + const char *x11_display) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_free (priv->x11_display_name); + priv->x11_display_name = g_strdup (x11_display); +} + +static void +_gdm_display_set_x11_cookie (GdmDisplay *self, + const char *x11_cookie) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_free (priv->x11_cookie); + priv->x11_cookie = g_strdup (x11_cookie); +} + +static void +_gdm_display_set_is_local (GdmDisplay *self, + gboolean is_local) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: local: %s", is_local? "yes" : "no"); + priv->is_local = is_local; +} + +static void +_gdm_display_set_session_registered (GdmDisplay *self, + gboolean registered) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: session registered: %s", registered? "yes" : "no"); + priv->session_registered = registered; +} + +static void +_gdm_display_set_launch_environment (GdmDisplay *self, + GdmLaunchEnvironment *launch_environment) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + + g_clear_object (&priv->launch_environment); + + priv->launch_environment = g_object_ref (launch_environment); +} + +static void +_gdm_display_set_is_initial (GdmDisplay *self, + gboolean initial) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: initial: %s", initial? "yes" : "no"); + priv->is_initial = initial; +} + +static void +_gdm_display_set_allow_timed_login (GdmDisplay *self, + gboolean allow_timed_login) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + g_debug ("GdmDisplay: allow timed login: %s", allow_timed_login? "yes" : "no"); + priv->allow_timed_login = allow_timed_login; +} + +static void +gdm_display_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmDisplay *self; + + self = GDM_DISPLAY (object); + + switch (prop_id) { + case PROP_ID: + _gdm_display_set_id (self, g_value_get_string (value)); + break; + case PROP_STATUS: + _gdm_display_set_status (self, g_value_get_int (value)); + break; + case PROP_SEAT_ID: + _gdm_display_set_seat_id (self, g_value_get_string (value)); + break; + case PROP_SESSION_ID: + _gdm_display_set_session_id (self, g_value_get_string (value)); + break; + case PROP_SESSION_CLASS: + _gdm_display_set_session_class (self, g_value_get_string (value)); + break; + case PROP_SESSION_TYPE: + _gdm_display_set_session_type (self, g_value_get_string (value)); + break; + case PROP_REMOTE_HOSTNAME: + _gdm_display_set_remote_hostname (self, g_value_get_string (value)); + break; + case PROP_X11_DISPLAY_NUMBER: + _gdm_display_set_x11_display_number (self, g_value_get_int (value)); + break; + case PROP_X11_DISPLAY_NAME: + _gdm_display_set_x11_display_name (self, g_value_get_string (value)); + break; + case PROP_X11_COOKIE: + _gdm_display_set_x11_cookie (self, g_value_get_string (value)); + break; + case PROP_IS_LOCAL: + _gdm_display_set_is_local (self, g_value_get_boolean (value)); + break; + case PROP_ALLOW_TIMED_LOGIN: + _gdm_display_set_allow_timed_login (self, g_value_get_boolean (value)); + break; + case PROP_LAUNCH_ENVIRONMENT: + _gdm_display_set_launch_environment (self, g_value_get_object (value)); + break; + case PROP_IS_INITIAL: + _gdm_display_set_is_initial (self, g_value_get_boolean (value)); + break; + case PROP_SESSION_REGISTERED: + _gdm_display_set_session_registered (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_display_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmDisplay *self; + GdmDisplayPrivate *priv; + + self = GDM_DISPLAY (object); + priv = gdm_display_get_instance_private (self); + + switch (prop_id) { + case PROP_ID: + g_value_set_string (value, priv->id); + break; + case PROP_STATUS: + g_value_set_int (value, priv->status); + break; + case PROP_SEAT_ID: + g_value_set_string (value, priv->seat_id); + break; + case PROP_SESSION_ID: + g_value_set_string (value, priv->session_id); + break; + case PROP_SESSION_CLASS: + g_value_set_string (value, priv->session_class); + break; + case PROP_SESSION_TYPE: + g_value_set_string (value, priv->session_type); + break; + case PROP_REMOTE_HOSTNAME: + g_value_set_string (value, priv->remote_hostname); + break; + case PROP_X11_DISPLAY_NUMBER: + g_value_set_int (value, priv->x11_display_number); + break; + case PROP_X11_DISPLAY_NAME: + g_value_set_string (value, priv->x11_display_name); + break; + case PROP_X11_COOKIE: + g_value_set_string (value, priv->x11_cookie); + break; + case PROP_X11_AUTHORITY_FILE: + g_value_take_string (value, + priv->access_file? + gdm_display_access_file_get_path (priv->access_file) : NULL); + break; + case PROP_IS_LOCAL: + g_value_set_boolean (value, priv->is_local); + break; + case PROP_IS_CONNECTED: + g_value_set_boolean (value, priv->xcb_connection != NULL); + break; + case PROP_LAUNCH_ENVIRONMENT: + g_value_set_object (value, priv->launch_environment); + break; + case PROP_IS_INITIAL: + g_value_set_boolean (value, priv->is_initial); + break; + case PROP_HAVE_EXISTING_USER_ACCOUNTS: + g_value_set_boolean (value, priv->have_existing_user_accounts); + break; + case PROP_DOING_INITIAL_SETUP: + g_value_set_boolean (value, priv->doing_initial_setup); + break; + case PROP_SESSION_REGISTERED: + g_value_set_boolean (value, priv->session_registered); + break; + case PROP_ALLOW_TIMED_LOGIN: + g_value_set_boolean (value, priv->allow_timed_login); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +handle_get_id (GdmDBusDisplay *skeleton, + GDBusMethodInvocation *invocation, + GdmDisplay *self) +{ + char *id; + + gdm_display_get_id (self, &id, NULL); + + gdm_dbus_display_complete_get_id (skeleton, invocation, id); + + g_free (id); + return TRUE; +} + +static gboolean +handle_get_remote_hostname (GdmDBusDisplay *skeleton, + GDBusMethodInvocation *invocation, + GdmDisplay *self) +{ + char *hostname; + + gdm_display_get_remote_hostname (self, &hostname, NULL); + + gdm_dbus_display_complete_get_remote_hostname (skeleton, + invocation, + hostname ? hostname : ""); + + g_free (hostname); + return TRUE; +} + +static gboolean +handle_get_seat_id (GdmDBusDisplay *skeleton, + GDBusMethodInvocation *invocation, + GdmDisplay *self) +{ + char *seat_id; + + seat_id = NULL; + gdm_display_get_seat_id (self, &seat_id, NULL); + + if (seat_id == NULL) { + seat_id = g_strdup (""); + } + gdm_dbus_display_complete_get_seat_id (skeleton, invocation, seat_id); + + g_free (seat_id); + return TRUE; +} + +static gboolean +handle_get_x11_display_name (GdmDBusDisplay *skeleton, + GDBusMethodInvocation *invocation, + GdmDisplay *self) +{ + char *name; + + gdm_display_get_x11_display_name (self, &name, NULL); + + gdm_dbus_display_complete_get_x11_display_name (skeleton, invocation, name); + + g_free (name); + return TRUE; +} + +static gboolean +handle_is_local (GdmDBusDisplay *skeleton, + GDBusMethodInvocation *invocation, + GdmDisplay *self) +{ + gboolean is_local; + + gdm_display_is_local (self, &is_local, NULL); + + gdm_dbus_display_complete_is_local (skeleton, invocation, is_local); + + return TRUE; +} + +static gboolean +handle_is_initial (GdmDBusDisplay *skeleton, + GDBusMethodInvocation *invocation, + GdmDisplay *self) +{ + gboolean is_initial = FALSE; + + gdm_display_is_initial (self, &is_initial, NULL); + + gdm_dbus_display_complete_is_initial (skeleton, invocation, is_initial); + + return TRUE; +} + +static gboolean +register_display (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + GError *error = NULL; + + priv = gdm_display_get_instance_private (self); + + error = NULL; + priv->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (priv->connection == NULL) { + g_critical ("error getting system bus: %s", error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + priv->object_skeleton = g_dbus_object_skeleton_new (priv->id); + priv->display_skeleton = GDM_DBUS_DISPLAY (gdm_dbus_display_skeleton_new ()); + + g_signal_connect_object (priv->display_skeleton, "handle-get-id", + G_CALLBACK (handle_get_id), self, 0); + g_signal_connect_object (priv->display_skeleton, "handle-get-remote-hostname", + G_CALLBACK (handle_get_remote_hostname), self, 0); + g_signal_connect_object (priv->display_skeleton, "handle-get-seat-id", + G_CALLBACK (handle_get_seat_id), self, 0); + g_signal_connect_object (priv->display_skeleton, "handle-get-x11-display-name", + G_CALLBACK (handle_get_x11_display_name), self, 0); + g_signal_connect_object (priv->display_skeleton, "handle-is-local", + G_CALLBACK (handle_is_local), self, 0); + g_signal_connect_object (priv->display_skeleton, "handle-is-initial", + G_CALLBACK (handle_is_initial), self, 0); + + g_dbus_object_skeleton_add_interface (priv->object_skeleton, + G_DBUS_INTERFACE_SKELETON (priv->display_skeleton)); + + return TRUE; +} + +/* + dbus-send --system --print-reply --dest=org.gnome.DisplayManager /org/gnome/DisplayManager/Displays/1 org.freedesktop.DBus.Introspectable.Introspect +*/ + +static GObject * +gdm_display_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmDisplay *self; + GdmDisplayPrivate *priv; + gboolean res; + + self = GDM_DISPLAY (G_OBJECT_CLASS (gdm_display_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + priv = gdm_display_get_instance_private (self); + + g_free (priv->id); + priv->id = g_strdup_printf ("/org/gnome/DisplayManager/Displays/%lu", + (gulong) self); + + res = register_display (self); + if (! res) { + g_warning ("Unable to register display with system bus"); + } + + return G_OBJECT (self); +} + +static void +gdm_display_dispose (GObject *object) +{ + GdmDisplay *self; + GdmDisplayPrivate *priv; + + self = GDM_DISPLAY (object); + priv = gdm_display_get_instance_private (self); + + g_debug ("GdmDisplay: Disposing display"); + + if (priv->finish_idle_id != 0) { + g_source_remove (priv->finish_idle_id); + priv->finish_idle_id = 0; + } + g_clear_object (&priv->launch_environment); + + g_warn_if_fail (priv->status != GDM_DISPLAY_MANAGED); + g_warn_if_fail (priv->user_access_file == NULL); + g_warn_if_fail (priv->access_file == NULL); + + G_OBJECT_CLASS (gdm_display_parent_class)->dispose (object); +} + +static void +gdm_display_class_init (GdmDisplayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_display_get_property; + object_class->set_property = gdm_display_set_property; + object_class->constructor = gdm_display_constructor; + object_class->dispose = gdm_display_dispose; + object_class->finalize = gdm_display_finalize; + + klass->prepare = gdm_display_real_prepare; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "id", + "id", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_REMOTE_HOSTNAME, + g_param_spec_string ("remote-hostname", + "remote-hostname", + "remote-hostname", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_DISPLAY_NUMBER, + g_param_spec_int ("x11-display-number", + "x11 display number", + "x11 display number", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_DISPLAY_NAME, + g_param_spec_string ("x11-display-name", + "x11-display-name", + "x11-display-name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_SEAT_ID, + g_param_spec_string ("seat-id", + "seat id", + "seat id", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_SESSION_ID, + g_param_spec_string ("session-id", + "session id", + "session id", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_SESSION_CLASS, + g_param_spec_string ("session-class", + NULL, + NULL, + "greeter", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_SESSION_TYPE, + g_param_spec_string ("session-type", + NULL, + NULL, + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_IS_INITIAL, + g_param_spec_boolean ("is-initial", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_ALLOW_TIMED_LOGIN, + g_param_spec_boolean ("allow-timed-login", + NULL, + NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_COOKIE, + g_param_spec_string ("x11-cookie", + "cookie", + "cookie", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_AUTHORITY_FILE, + g_param_spec_string ("x11-authority-file", + "authority file", + "authority file", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_IS_LOCAL, + g_param_spec_boolean ("is-local", + NULL, + NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_IS_CONNECTED, + g_param_spec_boolean ("is-connected", + NULL, + NULL, + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_HAVE_EXISTING_USER_ACCOUNTS, + g_param_spec_boolean ("have-existing-user-accounts", + NULL, + NULL, + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DOING_INITIAL_SETUP, + g_param_spec_boolean ("doing-initial-setup", + NULL, + NULL, + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_SESSION_REGISTERED, + g_param_spec_boolean ("session-registered", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_LAUNCH_ENVIRONMENT, + g_param_spec_object ("launch-environment", + NULL, + NULL, + GDM_TYPE_LAUNCH_ENVIRONMENT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_STATUS, + g_param_spec_int ("status", + "status", + "status", + -1, + G_MAXINT, + GDM_DISPLAY_UNMANAGED, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); +} + +static void +gdm_display_init (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + + priv->creation_time = time (NULL); +} + +static void +gdm_display_finalize (GObject *object) +{ + GdmDisplay *self; + GdmDisplayPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_DISPLAY (object)); + + self = GDM_DISPLAY (object); + priv = gdm_display_get_instance_private (self); + + g_return_if_fail (priv != NULL); + + g_debug ("GdmDisplay: Finalizing display: %s", priv->id); + g_free (priv->id); + g_free (priv->seat_id); + g_free (priv->session_class); + g_free (priv->remote_hostname); + g_free (priv->x11_display_name); + g_free (priv->x11_cookie); + + g_clear_object (&priv->display_skeleton); + g_clear_object (&priv->object_skeleton); + g_clear_object (&priv->connection); + g_clear_object (&priv->accountsservice_proxy); + + if (priv->access_file != NULL) { + g_object_unref (priv->access_file); + } + + if (priv->user_access_file != NULL) { + g_object_unref (priv->user_access_file); + } + + G_OBJECT_CLASS (gdm_display_parent_class)->finalize (object); +} + +GDBusObjectSkeleton * +gdm_display_get_object_skeleton (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + return priv->object_skeleton; +} + +static void +on_launch_environment_session_opened (GdmLaunchEnvironment *launch_environment, + GdmDisplay *self) +{ + char *session_id; + + g_debug ("GdmDisplay: Greeter session opened"); + session_id = gdm_launch_environment_get_session_id (launch_environment); + _gdm_display_set_session_id (self, session_id); + g_free (session_id); +} + +static void +on_launch_environment_session_started (GdmLaunchEnvironment *launch_environment, + GdmDisplay *self) +{ + g_debug ("GdmDisplay: Greeter started"); +} + +static void +self_destruct (GdmDisplay *self) +{ + g_object_ref (self); + if (gdm_display_get_status (self) == GDM_DISPLAY_MANAGED) { + gdm_display_unmanage (self); + } + + if (gdm_display_get_status (self) != GDM_DISPLAY_FINISHED) { + queue_finish (self); + } + g_object_unref (self); +} + +static void +on_launch_environment_session_stopped (GdmLaunchEnvironment *launch_environment, + GdmDisplay *self) +{ + g_debug ("GdmDisplay: Greeter stopped"); + self_destruct (self); +} + +static void +on_launch_environment_session_exited (GdmLaunchEnvironment *launch_environment, + int code, + GdmDisplay *self) +{ + g_debug ("GdmDisplay: Greeter exited: %d", code); + self_destruct (self); +} + +static void +on_launch_environment_session_died (GdmLaunchEnvironment *launch_environment, + int signal, + GdmDisplay *self) +{ + g_debug ("GdmDisplay: Greeter died: %d", signal); + self_destruct (self); +} + +static gboolean +can_create_environment (const char *session_id) +{ + char *path; + gboolean session_exists; + + path = g_strdup_printf (GNOME_SESSION_SESSIONS_PATH "/%s.session", session_id); + session_exists = g_file_test (path, G_FILE_TEST_EXISTS); + + g_free (path); + + return session_exists; +} + +#define ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT GDM_RUN_DIR "/gdm.ran-initial-setup" + +static gboolean +already_done_initial_setup_on_this_boot (void) +{ + if (g_file_test (ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT, G_FILE_TEST_EXISTS)) + return TRUE; + + return FALSE; +} + +static gboolean +kernel_cmdline_initial_setup_argument (const gchar *contents, + gchar **initial_setup_argument, + GError **error) +{ + GRegex *regex = NULL; + GMatchInfo *match_info = NULL; + gchar *match_group = NULL; + + g_return_val_if_fail (initial_setup_argument != NULL, FALSE); + + regex = g_regex_new ("\\bgnome.initial-setup=([^\\s]*)\\b", 0, 0, error); + + if (!regex) + return FALSE; + + if (!g_regex_match (regex, contents, 0, &match_info)) { + g_free (match_info); + g_free (regex); + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not match gnome.initial-setup= in kernel cmdline"); + + return FALSE; + } + + match_group = g_match_info_fetch (match_info, 1); + + if (!match_group) { + g_free (match_info); + g_free (regex); + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not match gnome.initial-setup= in kernel cmdline"); + + return FALSE; + } + + *initial_setup_argument = match_group; + + g_free (match_info); + g_free (regex); + + return TRUE; +} + +/* Function returns true if we had a force state in the kernel + * cmdline */ +static gboolean +kernel_cmdline_initial_setup_force_state (gboolean *force_state) +{ + GError *error = NULL; + gchar *contents = NULL; + gchar *setup_argument = NULL; + + g_return_val_if_fail (force_state != NULL, FALSE); + + if (!g_file_get_contents ("/proc/cmdline", &contents, NULL, &error)) { + g_debug ("GdmDisplay: Could not check kernel parameters, not forcing initial setup: %s", + error->message); + g_clear_error (&error); + return FALSE; + } + + g_debug ("GdmDisplay: Checking kernel command buffer %s", contents); + + if (!kernel_cmdline_initial_setup_argument (contents, &setup_argument, &error)) { + g_debug ("GdmDisplay: Failed to read kernel commandline: %s", error->message); + g_clear_pointer (&contents, g_free); + return FALSE; + } + + g_clear_pointer (&contents, g_free); + + /* Poor-man's check for truthy or falsey values */ + *force_state = setup_argument[0] == '1'; + + g_free (setup_argument); + return TRUE; +} + +static gboolean +wants_initial_setup (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + gboolean enabled = FALSE; + gboolean forced = FALSE; + + priv = gdm_display_get_instance_private (self); + + if (already_done_initial_setup_on_this_boot ()) { + return FALSE; + } + + if (kernel_cmdline_initial_setup_force_state (&forced)) { + if (forced) { + g_debug ("GdmDisplay: Forcing gnome-initial-setup"); + return TRUE; + } + + g_debug ("GdmDisplay: Forcing no gnome-initial-setup"); + return FALSE; + } + + /* don't run initial-setup on remote displays + */ + if (!priv->is_local) { + return FALSE; + } + + /* don't run if the system has existing users */ + if (priv->have_existing_user_accounts) { + return FALSE; + } + + /* don't run if initial-setup is unavailable */ + if (!can_create_environment ("gnome-initial-setup")) { + return FALSE; + } + + if (!gdm_settings_direct_get_boolean (GDM_KEY_INITIAL_SETUP_ENABLE, &enabled)) { + return FALSE; + } + + return enabled; +} + +void +gdm_display_start_greeter_session (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + GdmSession *session; + char *display_name; + char *seat_id; + char *hostname; + char *auth_file = NULL; + + priv = gdm_display_get_instance_private (self); + g_return_if_fail (g_strcmp0 (priv->session_class, "greeter") == 0); + + g_debug ("GdmDisplay: Running greeter"); + + display_name = NULL; + seat_id = NULL; + hostname = NULL; + + g_object_get (self, + "x11-display-name", &display_name, + "seat-id", &seat_id, + "remote-hostname", &hostname, + NULL); + if (priv->access_file != NULL) { + auth_file = gdm_display_access_file_get_path (priv->access_file); + } + + g_debug ("GdmDisplay: Creating greeter for %s %s", display_name, hostname); + + g_signal_connect_object (priv->launch_environment, + "opened", + G_CALLBACK (on_launch_environment_session_opened), + self, 0); + g_signal_connect_object (priv->launch_environment, + "started", + G_CALLBACK (on_launch_environment_session_started), + self, 0); + g_signal_connect_object (priv->launch_environment, + "stopped", + G_CALLBACK (on_launch_environment_session_stopped), + self, 0); + g_signal_connect_object (priv->launch_environment, + "exited", + G_CALLBACK (on_launch_environment_session_exited), + self, 0); + g_signal_connect_object (priv->launch_environment, + "died", + G_CALLBACK (on_launch_environment_session_died), + self, 0); + + if (auth_file != NULL) { + g_object_set (priv->launch_environment, + "x11-authority-file", auth_file, + NULL); + } + + gdm_launch_environment_start (priv->launch_environment); + + session = gdm_launch_environment_get_session (priv->launch_environment); + g_object_set (G_OBJECT (session), + "display-is-initial", priv->is_initial, + NULL); + + g_free (display_name); + g_free (seat_id); + g_free (hostname); + g_free (auth_file); +} + +void +gdm_display_stop_greeter_session (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + + priv = gdm_display_get_instance_private (self); + + if (priv->launch_environment != NULL) { + + g_signal_handlers_disconnect_by_func (priv->launch_environment, + G_CALLBACK (on_launch_environment_session_opened), + self); + g_signal_handlers_disconnect_by_func (priv->launch_environment, + G_CALLBACK (on_launch_environment_session_started), + self); + g_signal_handlers_disconnect_by_func (priv->launch_environment, + G_CALLBACK (on_launch_environment_session_stopped), + self); + g_signal_handlers_disconnect_by_func (priv->launch_environment, + G_CALLBACK (on_launch_environment_session_exited), + self); + g_signal_handlers_disconnect_by_func (priv->launch_environment, + G_CALLBACK (on_launch_environment_session_died), + self); + gdm_launch_environment_stop (priv->launch_environment); + g_clear_object (&priv->launch_environment); + } +} + +static xcb_window_t +get_root_window (xcb_connection_t *connection, + int screen_number) +{ + xcb_screen_t *screen = NULL; + xcb_screen_iterator_t iter; + + iter = xcb_setup_roots_iterator (xcb_get_setup (connection)); + while (iter.rem) { + if (screen_number == 0) + screen = iter.data; + screen_number--; + xcb_screen_next (&iter); + } + + if (screen != NULL) { + return screen->root; + } + + return XCB_WINDOW_NONE; +} + +static void +gdm_display_set_windowpath (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + /* setting WINDOWPATH for clients */ + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply = NULL; + xcb_get_property_cookie_t get_property_cookie; + xcb_get_property_reply_t *get_property_reply = NULL; + xcb_window_t root_window = XCB_WINDOW_NONE; + const char *windowpath; + char *newwindowpath; + uint32_t num; + char nums[10]; + int numn; + + priv = gdm_display_get_instance_private (self); + + atom_cookie = xcb_intern_atom (priv->xcb_connection, 0, strlen("XFree86_VT"), "XFree86_VT"); + atom_reply = xcb_intern_atom_reply (priv->xcb_connection, atom_cookie, NULL); + + if (atom_reply == NULL) { + g_debug ("no XFree86_VT atom\n"); + goto out; + } + + root_window = get_root_window (priv->xcb_connection, + priv->xcb_screen_number); + + if (root_window == XCB_WINDOW_NONE) { + g_debug ("couldn't find root window\n"); + goto out; + } + + get_property_cookie = xcb_get_property (priv->xcb_connection, + FALSE, + root_window, + atom_reply->atom, + XCB_ATOM_INTEGER, + 0, + 1); + + get_property_reply = xcb_get_property_reply (priv->xcb_connection, get_property_cookie, NULL); + + if (get_property_reply == NULL) { + g_debug ("no XFree86_VT property\n"); + goto out; + } + + num = ((uint32_t *) xcb_get_property_value (get_property_reply))[0]; + + windowpath = getenv ("WINDOWPATH"); + numn = snprintf (nums, sizeof (nums), "%u", num); + if (!windowpath) { + newwindowpath = malloc (numn + 1); + sprintf (newwindowpath, "%s", nums); + } else { + newwindowpath = malloc (strlen (windowpath) + 1 + numn + 1); + sprintf (newwindowpath, "%s:%s", windowpath, nums); + } + + g_setenv ("WINDOWPATH", newwindowpath, TRUE); +out: + g_clear_pointer (&atom_reply, free); + g_clear_pointer (&get_property_reply, free); +} + +gboolean +gdm_display_connect (GdmDisplay *self) +{ + GdmDisplayPrivate *priv; + xcb_auth_info_t *auth_info = NULL; + gboolean ret; + + priv = gdm_display_get_instance_private (self); + ret = FALSE; + + g_debug ("GdmDisplay: Server is ready - opening display %s", priv->x11_display_name); + + /* Get access to the display independent of current hostname */ + if (priv->x11_cookie != NULL) { + auth_info = g_alloca (sizeof (xcb_auth_info_t)); + + auth_info->namelen = strlen ("MIT-MAGIC-COOKIE-1"); + auth_info->name = "MIT-MAGIC-COOKIE-1"; + auth_info->datalen = priv->x11_cookie_size; + auth_info->data = priv->x11_cookie; + + } + + priv->xcb_connection = xcb_connect_to_display_with_auth_info (priv->x11_display_name, + auth_info, + &priv->xcb_screen_number); + + if (xcb_connection_has_error (priv->xcb_connection)) { + g_clear_pointer (&priv->xcb_connection, xcb_disconnect); + g_warning ("Unable to connect to display %s", priv->x11_display_name); + ret = FALSE; + } else if (priv->is_local) { + XHostAddress host_entries[3]; + xcb_void_cookie_t cookies[3]; + int i; + + g_debug ("GdmDisplay: Connected to display %s", priv->x11_display_name); + ret = TRUE; + + /* Give programs access to the display independent of current hostname + */ + setup_xhost_auth (host_entries); + + for (i = 0; i < G_N_ELEMENTS (host_entries); i++) { + cookies[i] = xcb_change_hosts_checked (priv->xcb_connection, + XCB_HOST_MODE_INSERT, + host_entries[i].family, + host_entries[i].length, + (uint8_t *) host_entries[i].address); + } + + for (i = 0; i < G_N_ELEMENTS (cookies); i++) { + xcb_generic_error_t *xcb_error; + + xcb_error = xcb_request_check (priv->xcb_connection, cookies[i]); + + if (xcb_error != NULL) { + g_debug ("Failed to give system user '%s' access to the display. Trying to proceed.", host_entries[i].address + sizeof ("localuser")); + free (xcb_error); + } else { + g_debug ("Gave system user '%s' access to the display.", host_entries[i].address + sizeof ("localuser")); + } + } + + gdm_display_set_windowpath (self); + } else { + g_debug ("GdmDisplay: Connected to display %s", priv->x11_display_name); + ret = TRUE; + } + + if (ret == TRUE) { + g_object_notify (G_OBJECT (self), "is-connected"); + } + + return ret; +} + diff --git a/daemon/gdm-display.h b/daemon/gdm-display.h new file mode 100644 index 0000000..ef3736c --- /dev/null +++ b/daemon/gdm-display.h @@ -0,0 +1,129 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_DISPLAY_H +#define __GDM_DISPLAY_H + +#include <glib-object.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_DISPLAY (gdm_display_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GdmDisplay, gdm_display, GDM, DISPLAY, GObject) + +typedef enum { + GDM_DISPLAY_UNMANAGED = 0, + GDM_DISPLAY_PREPARED, + GDM_DISPLAY_MANAGED, + GDM_DISPLAY_WAITING_TO_FINISH, + GDM_DISPLAY_FINISHED, + GDM_DISPLAY_FAILED, +} GdmDisplayStatus; + +struct _GdmDisplayClass +{ + GObjectClass parent_class; + + /* methods */ + gboolean (*prepare) (GdmDisplay *display); + void (*manage) (GdmDisplay *self); +}; + +typedef enum +{ + GDM_DISPLAY_ERROR_GENERAL, + GDM_DISPLAY_ERROR_GETTING_USER_INFO, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, +} GdmDisplayError; + +#define GDM_DISPLAY_ERROR gdm_display_error_quark () + +GQuark gdm_display_error_quark (void); + +int gdm_display_get_status (GdmDisplay *display); +time_t gdm_display_get_creation_time (GdmDisplay *display); +char * gdm_display_open_session_sync (GdmDisplay *display, + GPid pid_of_caller, + uid_t uid_of_caller, + GCancellable *cancellable, + GError **error); + +char * gdm_display_open_reauthentication_channel_sync (GdmDisplay *display, + const char *username, + GPid pid_of_caller, + uid_t uid_of_caller, + GCancellable *cancellable, + GError **error); +const char * gdm_display_get_session_id (GdmDisplay *display); +gboolean gdm_display_create_authority (GdmDisplay *display); +gboolean gdm_display_prepare (GdmDisplay *display); +gboolean gdm_display_manage (GdmDisplay *display); +gboolean gdm_display_finish (GdmDisplay *display); +gboolean gdm_display_unmanage (GdmDisplay *display); + +GDBusObjectSkeleton *gdm_display_get_object_skeleton (GdmDisplay *display); + +/* exported to bus */ +gboolean gdm_display_get_id (GdmDisplay *display, + char **id, + GError **error); +gboolean gdm_display_get_remote_hostname (GdmDisplay *display, + char **hostname, + GError **error); +gboolean gdm_display_get_x11_display_number (GdmDisplay *display, + int *number, + GError **error); +gboolean gdm_display_get_x11_display_name (GdmDisplay *display, + char **x11_display, + GError **error); +gboolean gdm_display_get_seat_id (GdmDisplay *display, + char **seat_id, + GError **error); +gboolean gdm_display_is_local (GdmDisplay *display, + gboolean *local, + GError **error); +gboolean gdm_display_is_initial (GdmDisplay *display, + gboolean *initial, + GError **error); + +gboolean gdm_display_get_x11_cookie (GdmDisplay *display, + const char **x11_cookie, + gsize *x11_cookie_size, + GError **error); +gboolean gdm_display_get_x11_authority_file (GdmDisplay *display, + char **filename, + GError **error); +gboolean gdm_display_add_user_authorization (GdmDisplay *display, + const char *username, + char **filename, + GError **error); +gboolean gdm_display_remove_user_authorization (GdmDisplay *display, + const char *username, + GError **error); +void gdm_display_start_greeter_session (GdmDisplay *display); +void gdm_display_stop_greeter_session (GdmDisplay *display); + +gboolean gdm_display_connect (GdmDisplay *self); + +G_END_DECLS + +#endif /* __GDM_DISPLAY_H */ diff --git a/daemon/gdm-display.xml b/daemon/gdm-display.xml new file mode 100644 index 0000000..3e9b5d8 --- /dev/null +++ b/daemon/gdm-display.xml @@ -0,0 +1,23 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.gnome.DisplayManager.Display"> + <method name="GetId"> + <arg name="id" direction="out" type="o"/> + </method> + <method name="GetX11DisplayName"> + <arg name="name" direction="out" type="s"/> + </method> + <method name="GetSeatId"> + <arg name="filename" direction="out" type="s"/> + </method> + <method name="IsInitial"> + <arg name="initial" direction="out" type="b"/> + </method> + <method name="GetRemoteHostname"> + <arg name="hostname" direction="out" type="s"/> + </method> + <method name="IsLocal"> + <arg name="local" direction="out" type="b"/> + </method> + </interface> +</node> diff --git a/daemon/gdm-launch-environment.c b/daemon/gdm-launch-environment.c new file mode 100644 index 0000000..feccf05 --- /dev/null +++ b/daemon/gdm-launch-environment.c @@ -0,0 +1,1012 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com> + * + * 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 <gio/gio.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 <errno.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> +#include <signal.h> +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include "gdm-common.h" + +#include "gdm-session-enum-types.h" +#include "gdm-launch-environment.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +#define INITIAL_SETUP_USERNAME "gnome-initial-setup" +#define GDM_SESSION_MODE "gdm" +#define INITIAL_SETUP_SESSION_MODE "initial-setup" +#define GNOME_SESSION_SESSIONS_PATH DATADIR "/gnome-session/sessions" + +extern char **environ; + +struct GdmLaunchEnvironmentPrivate +{ + GdmSession *session; + char *command; + GPid pid; + + GdmSessionVerificationMode verification_mode; + + char *user_name; + char *runtime_dir; + + char *session_id; + char *session_type; + char *session_mode; + char *x11_display_name; + char *x11_display_seat_id; + char *x11_display_device; + char *x11_display_hostname; + char *x11_authority_file; + gboolean x11_display_is_local; +}; + +enum { + PROP_0, + PROP_VERIFICATION_MODE, + PROP_SESSION_TYPE, + PROP_SESSION_MODE, + PROP_X11_DISPLAY_NAME, + PROP_X11_DISPLAY_SEAT_ID, + PROP_X11_DISPLAY_DEVICE, + PROP_X11_DISPLAY_HOSTNAME, + PROP_X11_AUTHORITY_FILE, + PROP_X11_DISPLAY_IS_LOCAL, + PROP_USER_NAME, + PROP_RUNTIME_DIR, + PROP_COMMAND, +}; + +enum { + OPENED, + STARTED, + STOPPED, + EXITED, + DIED, + HOSTNAME_SELECTED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_launch_environment_class_init (GdmLaunchEnvironmentClass *klass); +static void gdm_launch_environment_init (GdmLaunchEnvironment *launch_environment); +static void gdm_launch_environment_finalize (GObject *object); + +G_DEFINE_TYPE_WITH_PRIVATE (GdmLaunchEnvironment, gdm_launch_environment, G_TYPE_OBJECT) + +static GHashTable * +build_launch_environment (GdmLaunchEnvironment *launch_environment, + gboolean start_session) +{ + GHashTable *hash; + struct passwd *pwent; + static const char *const optional_environment[] = { + "GI_TYPELIB_PATH", + "LANG", + "LANGUAGE", + "LC_ADDRESS", + "LC_ALL", + "LC_COLLATE", + "LC_CTYPE", + "LC_IDENTIFICATION", + "LC_MEASUREMENT", + "LC_MESSAGES", + "LC_MONETARY", + "LC_NAME", + "LC_NUMERIC", + "LC_PAPER", + "LC_TELEPHONE", + "LC_TIME", + "LD_LIBRARY_PATH", + "PATH", + "WINDOWPATH", + "XCURSOR_PATH", + "XDG_CONFIG_DIRS", + NULL + }; + char *system_data_dirs; + int i; + + /* create a hash table of current environment, then update keys has necessary */ + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + for (i = 0; optional_environment[i] != NULL; i++) { + if (g_getenv (optional_environment[i]) == NULL) { + continue; + } + + g_hash_table_insert (hash, + g_strdup (optional_environment[i]), + g_strdup (g_getenv (optional_environment[i]))); + } + + system_data_dirs = g_strjoinv (":", (char **) g_get_system_data_dirs ()); + + g_hash_table_insert (hash, + g_strdup ("XDG_DATA_DIRS"), + g_strdup_printf ("%s:%s", + DATADIR "/gdm/greeter", + system_data_dirs)); + g_free (system_data_dirs); + + if (launch_environment->priv->x11_authority_file != NULL) + g_hash_table_insert (hash, g_strdup ("XAUTHORITY"), g_strdup (launch_environment->priv->x11_authority_file)); + + if (launch_environment->priv->session_mode != NULL) { + g_hash_table_insert (hash, g_strdup ("GNOME_SHELL_SESSION_MODE"), g_strdup (launch_environment->priv->session_mode)); + + if (strcmp (launch_environment->priv->session_mode, INITIAL_SETUP_SESSION_MODE) != 0) { + /* gvfs is needed for fetching remote avatars in the initial setup. Disable it otherwise. */ + g_hash_table_insert (hash, g_strdup ("GVFS_DISABLE_FUSE"), g_strdup ("1")); + g_hash_table_insert (hash, g_strdup ("GIO_USE_VFS"), g_strdup ("local")); + g_hash_table_insert (hash, g_strdup ("GVFS_REMOTE_VOLUME_MONITOR_IGNORE"), g_strdup ("1")); + + /* The locked down dconf profile should not be used for the initial setup session. + * This allows overridden values from the user profile to take effect. + */ + g_hash_table_insert (hash, g_strdup ("DCONF_PROFILE"), g_strdup ("gdm")); + } + } + + g_hash_table_insert (hash, g_strdup ("LOGNAME"), g_strdup (launch_environment->priv->user_name)); + g_hash_table_insert (hash, g_strdup ("USER"), g_strdup (launch_environment->priv->user_name)); + g_hash_table_insert (hash, g_strdup ("USERNAME"), g_strdup (launch_environment->priv->user_name)); + + g_hash_table_insert (hash, g_strdup ("GDM_VERSION"), g_strdup (VERSION)); + g_hash_table_remove (hash, "MAIL"); + + g_hash_table_insert (hash, g_strdup ("HOME"), g_strdup ("/")); + g_hash_table_insert (hash, g_strdup ("PWD"), g_strdup ("/")); + g_hash_table_insert (hash, g_strdup ("SHELL"), g_strdup ("/bin/sh")); + + gdm_get_pwent_for_name (launch_environment->priv->user_name, &pwent); + if (pwent != NULL) { + if (pwent->pw_dir != NULL && pwent->pw_dir[0] != '\0') { + g_hash_table_insert (hash, g_strdup ("HOME"), g_strdup (pwent->pw_dir)); + g_hash_table_insert (hash, g_strdup ("PWD"), g_strdup (pwent->pw_dir)); + } + + g_hash_table_insert (hash, g_strdup ("SHELL"), g_strdup (pwent->pw_shell)); + } + + if (start_session && launch_environment->priv->x11_display_seat_id != NULL) { + char *seat_id; + + seat_id = launch_environment->priv->x11_display_seat_id; + + g_hash_table_insert (hash, g_strdup ("GDM_SEAT_ID"), g_strdup (seat_id)); + } + + g_hash_table_insert (hash, g_strdup ("RUNNING_UNDER_GDM"), g_strdup ("true")); + + return hash; +} + +static void +on_session_setup_complete (GdmSession *session, + const char *service_name, + GdmLaunchEnvironment *launch_environment) +{ + GHashTable *hash; + GHashTableIter iter; + gpointer key, value; + + hash = build_launch_environment (launch_environment, TRUE); + + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, &key, &value)) { + gdm_session_set_environment_variable (launch_environment->priv->session, key, value); + } + g_hash_table_destroy (hash); +} + +static void +on_session_opened (GdmSession *session, + const char *service_name, + const char *session_id, + GdmLaunchEnvironment *launch_environment) +{ + launch_environment->priv->session_id = g_strdup (session_id); + + g_signal_emit (G_OBJECT (launch_environment), signals [OPENED], 0); + gdm_session_start_session (launch_environment->priv->session, service_name); +} + +static void +on_session_started (GdmSession *session, + const char *service_name, + int pid, + GdmLaunchEnvironment *launch_environment) +{ + launch_environment->priv->pid = pid; + g_signal_emit (G_OBJECT (launch_environment), signals [STARTED], 0); +} + +static void +on_session_exited (GdmSession *session, + int exit_code, + GdmLaunchEnvironment *launch_environment) +{ + gdm_session_stop_conversation (launch_environment->priv->session, "gdm-launch-environment"); + + g_signal_emit (G_OBJECT (launch_environment), signals [EXITED], 0, exit_code); +} + +static void +on_session_died (GdmSession *session, + int signal_number, + GdmLaunchEnvironment *launch_environment) +{ + gdm_session_stop_conversation (launch_environment->priv->session, "gdm-launch-environment"); + + g_signal_emit (G_OBJECT (launch_environment), signals [DIED], 0, signal_number); +} + +static void +on_hostname_selected (GdmSession *session, + const char *hostname, + GdmLaunchEnvironment *launch_environment) +{ + g_debug ("GdmSession: hostname selected: %s", hostname); + g_signal_emit (launch_environment, signals [HOSTNAME_SELECTED], 0, hostname); +} + +static void +on_conversation_started (GdmSession *session, + const char *service_name, + GdmLaunchEnvironment *launch_environment) +{ + char *log_path; + char *log_file; + + if (launch_environment->priv->x11_display_name != NULL) + log_file = g_strdup_printf ("%s-greeter.log", launch_environment->priv->x11_display_name); + else + log_file = g_strdup ("greeter.log"); + + log_path = g_build_filename (LOGDIR, log_file, NULL); + g_free (log_file); + + gdm_session_setup_for_program (launch_environment->priv->session, + "gdm-launch-environment", + launch_environment->priv->user_name, + log_path); + g_free (log_path); +} + +static void +on_conversation_stopped (GdmSession *session, + const char *service_name, + GdmLaunchEnvironment *launch_environment) +{ + GdmSession *conversation_session; + + conversation_session = launch_environment->priv->session; + launch_environment->priv->session = NULL; + + g_debug ("GdmLaunchEnvironment: conversation stopped"); + + if (launch_environment->priv->pid > 1) { + gdm_signal_pid (-launch_environment->priv->pid, SIGTERM); + g_signal_emit (G_OBJECT (launch_environment), signals [STOPPED], 0); + } + + if (conversation_session != NULL) { + gdm_session_close (conversation_session); + g_object_unref (conversation_session); + } +} + +static gboolean +ensure_directory_with_uid_gid (const char *path, + uid_t uid, + gid_t gid, + GError **error) +{ + if (mkdir (path, 0700) == -1 && errno != EEXIST) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to create directory %s: %s", path, + g_strerror (errno)); + return FALSE; + } + if (chown (path, uid, gid) == -1) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to set owner of %s: %s", path, + g_strerror (errno)); + return FALSE; + } + return TRUE; +} + +/** + * gdm_launch_environment_start: + * @disp: Pointer to a GdmDisplay structure + * + * Starts a local X launch_environment. Handles retries and fatal errors properly. + */ +gboolean +gdm_launch_environment_start (GdmLaunchEnvironment *launch_environment) +{ + gboolean res = FALSE; + GError *local_error = NULL; + GError **error = &local_error; + struct passwd *passwd_entry; + uid_t uid; + gid_t gid; + + g_debug ("GdmLaunchEnvironment: Starting..."); + + if (!gdm_get_pwent_for_name (launch_environment->priv->user_name, &passwd_entry)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown user %s", + launch_environment->priv->user_name); + goto out; + } + + uid = passwd_entry->pw_uid; + gid = passwd_entry->pw_gid; + + g_debug ("GdmLaunchEnvironment: Setting up run time dir %s", + launch_environment->priv->runtime_dir); + if (!ensure_directory_with_uid_gid (launch_environment->priv->runtime_dir, uid, gid, error)) { + goto out; + } + + /* Create the home directory too */ + if (!ensure_directory_with_uid_gid (passwd_entry->pw_dir, uid, gid, error)) + goto out; + + launch_environment->priv->session = gdm_session_new (launch_environment->priv->verification_mode, + uid, + launch_environment->priv->x11_display_name, + launch_environment->priv->x11_display_hostname, + launch_environment->priv->x11_display_device, + launch_environment->priv->x11_display_seat_id, + launch_environment->priv->x11_authority_file, + launch_environment->priv->x11_display_is_local, + NULL); + + g_signal_connect_object (launch_environment->priv->session, + "conversation-started", + G_CALLBACK (on_conversation_started), + launch_environment, + 0); + g_signal_connect_object (launch_environment->priv->session, + "conversation-stopped", + G_CALLBACK (on_conversation_stopped), + launch_environment, + 0); + g_signal_connect_object (launch_environment->priv->session, + "setup-complete", + G_CALLBACK (on_session_setup_complete), + launch_environment, + 0); + g_signal_connect_object (launch_environment->priv->session, + "session-opened", + G_CALLBACK (on_session_opened), + launch_environment, + 0); + g_signal_connect_object (launch_environment->priv->session, + "session-started", + G_CALLBACK (on_session_started), + launch_environment, + 0); + g_signal_connect_object (launch_environment->priv->session, + "session-exited", + G_CALLBACK (on_session_exited), + launch_environment, + 0); + g_signal_connect_object (launch_environment->priv->session, + "session-died", + G_CALLBACK (on_session_died), + launch_environment, + 0); + g_signal_connect_object (launch_environment->priv->session, + "hostname-selected", + G_CALLBACK (on_hostname_selected), + launch_environment, + 0); + + gdm_session_start_conversation (launch_environment->priv->session, "gdm-launch-environment"); + gdm_session_select_program (launch_environment->priv->session, launch_environment->priv->command); + + if (launch_environment->priv->session_type != NULL) { + g_object_set (G_OBJECT (launch_environment->priv->session), + "session-type", + launch_environment->priv->session_type, + NULL); + } + + res = TRUE; + out: + if (local_error) { + g_critical ("GdmLaunchEnvironment: %s", local_error->message); + g_clear_error (&local_error); + } + return res; +} + +gboolean +gdm_launch_environment_stop (GdmLaunchEnvironment *launch_environment) +{ + if (launch_environment->priv->pid > 1) { + gdm_signal_pid (-launch_environment->priv->pid, SIGTERM); + } + + if (launch_environment->priv->session != NULL) { + gdm_session_close (launch_environment->priv->session); + + g_clear_object (&launch_environment->priv->session); + } + + g_signal_emit (G_OBJECT (launch_environment), signals [STOPPED], 0); + + return TRUE; +} + +GdmSession * +gdm_launch_environment_get_session (GdmLaunchEnvironment *launch_environment) +{ + return launch_environment->priv->session; +} + +char * +gdm_launch_environment_get_session_id (GdmLaunchEnvironment *launch_environment) +{ + return g_strdup (launch_environment->priv->session_id); +} + +static void +_gdm_launch_environment_set_verification_mode (GdmLaunchEnvironment *launch_environment, + GdmSessionVerificationMode verification_mode) +{ + launch_environment->priv->verification_mode = verification_mode; +} + +static void +_gdm_launch_environment_set_session_type (GdmLaunchEnvironment *launch_environment, + const char *session_type) +{ + g_free (launch_environment->priv->session_type); + launch_environment->priv->session_type = g_strdup (session_type); +} + +static void +_gdm_launch_environment_set_session_mode (GdmLaunchEnvironment *launch_environment, + const char *session_mode) +{ + g_free (launch_environment->priv->session_mode); + launch_environment->priv->session_mode = g_strdup (session_mode); +} + +static void +_gdm_launch_environment_set_x11_display_name (GdmLaunchEnvironment *launch_environment, + const char *name) +{ + g_free (launch_environment->priv->x11_display_name); + launch_environment->priv->x11_display_name = g_strdup (name); +} + +static void +_gdm_launch_environment_set_x11_display_seat_id (GdmLaunchEnvironment *launch_environment, + const char *sid) +{ + g_free (launch_environment->priv->x11_display_seat_id); + launch_environment->priv->x11_display_seat_id = g_strdup (sid); +} + +static void +_gdm_launch_environment_set_x11_display_hostname (GdmLaunchEnvironment *launch_environment, + const char *name) +{ + g_free (launch_environment->priv->x11_display_hostname); + launch_environment->priv->x11_display_hostname = g_strdup (name); +} + +static void +_gdm_launch_environment_set_x11_display_device (GdmLaunchEnvironment *launch_environment, + const char *name) +{ + g_free (launch_environment->priv->x11_display_device); + launch_environment->priv->x11_display_device = g_strdup (name); +} + +static void +_gdm_launch_environment_set_x11_display_is_local (GdmLaunchEnvironment *launch_environment, + gboolean is_local) +{ + launch_environment->priv->x11_display_is_local = is_local; +} + +static void +_gdm_launch_environment_set_x11_authority_file (GdmLaunchEnvironment *launch_environment, + const char *file) +{ + g_free (launch_environment->priv->x11_authority_file); + launch_environment->priv->x11_authority_file = g_strdup (file); +} + +static void +_gdm_launch_environment_set_user_name (GdmLaunchEnvironment *launch_environment, + const char *name) +{ + g_free (launch_environment->priv->user_name); + launch_environment->priv->user_name = g_strdup (name); +} + +static void +_gdm_launch_environment_set_runtime_dir (GdmLaunchEnvironment *launch_environment, + const char *dir) +{ + g_free (launch_environment->priv->runtime_dir); + launch_environment->priv->runtime_dir = g_strdup (dir); +} + +static void +_gdm_launch_environment_set_command (GdmLaunchEnvironment *launch_environment, + const char *name) +{ + g_free (launch_environment->priv->command); + launch_environment->priv->command = g_strdup (name); +} + +static void +gdm_launch_environment_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmLaunchEnvironment *self; + + self = GDM_LAUNCH_ENVIRONMENT (object); + + switch (prop_id) { + case PROP_VERIFICATION_MODE: + _gdm_launch_environment_set_verification_mode (self, g_value_get_enum (value)); + break; + case PROP_SESSION_TYPE: + _gdm_launch_environment_set_session_type (self, g_value_get_string (value)); + break; + case PROP_SESSION_MODE: + _gdm_launch_environment_set_session_mode (self, g_value_get_string (value)); + break; + case PROP_X11_DISPLAY_NAME: + _gdm_launch_environment_set_x11_display_name (self, g_value_get_string (value)); + break; + case PROP_X11_DISPLAY_SEAT_ID: + _gdm_launch_environment_set_x11_display_seat_id (self, g_value_get_string (value)); + break; + case PROP_X11_DISPLAY_HOSTNAME: + _gdm_launch_environment_set_x11_display_hostname (self, g_value_get_string (value)); + break; + case PROP_X11_DISPLAY_DEVICE: + _gdm_launch_environment_set_x11_display_device (self, g_value_get_string (value)); + break; + case PROP_X11_DISPLAY_IS_LOCAL: + _gdm_launch_environment_set_x11_display_is_local (self, g_value_get_boolean (value)); + break; + case PROP_X11_AUTHORITY_FILE: + _gdm_launch_environment_set_x11_authority_file (self, g_value_get_string (value)); + break; + case PROP_USER_NAME: + _gdm_launch_environment_set_user_name (self, g_value_get_string (value)); + break; + case PROP_RUNTIME_DIR: + _gdm_launch_environment_set_runtime_dir (self, g_value_get_string (value)); + break; + case PROP_COMMAND: + _gdm_launch_environment_set_command (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_launch_environment_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmLaunchEnvironment *self; + + self = GDM_LAUNCH_ENVIRONMENT (object); + + switch (prop_id) { + case PROP_VERIFICATION_MODE: + g_value_set_enum (value, self->priv->verification_mode); + break; + case PROP_SESSION_TYPE: + g_value_set_string (value, self->priv->session_type); + break; + case PROP_SESSION_MODE: + g_value_set_string (value, self->priv->session_mode); + break; + case PROP_X11_DISPLAY_NAME: + g_value_set_string (value, self->priv->x11_display_name); + break; + case PROP_X11_DISPLAY_SEAT_ID: + g_value_set_string (value, self->priv->x11_display_seat_id); + break; + case PROP_X11_DISPLAY_HOSTNAME: + g_value_set_string (value, self->priv->x11_display_hostname); + break; + case PROP_X11_DISPLAY_DEVICE: + g_value_set_string (value, self->priv->x11_display_device); + break; + case PROP_X11_DISPLAY_IS_LOCAL: + g_value_set_boolean (value, self->priv->x11_display_is_local); + break; + case PROP_X11_AUTHORITY_FILE: + g_value_set_string (value, self->priv->x11_authority_file); + break; + case PROP_USER_NAME: + g_value_set_string (value, self->priv->user_name); + break; + case PROP_RUNTIME_DIR: + g_value_set_string (value, self->priv->runtime_dir); + break; + case PROP_COMMAND: + g_value_set_string (value, self->priv->command); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_launch_environment_class_init (GdmLaunchEnvironmentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_launch_environment_get_property; + object_class->set_property = gdm_launch_environment_set_property; + object_class->finalize = gdm_launch_environment_finalize; + + g_object_class_install_property (object_class, + PROP_VERIFICATION_MODE, + g_param_spec_enum ("verification-mode", + "verification mode", + "verification mode", + GDM_TYPE_SESSION_VERIFICATION_MODE, + GDM_SESSION_VERIFICATION_MODE_LOGIN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_SESSION_TYPE, + g_param_spec_string ("session-type", + NULL, + NULL, + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_SESSION_MODE, + g_param_spec_string ("session-mode", + NULL, + NULL, + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_DISPLAY_NAME, + g_param_spec_string ("x11-display-name", + "name", + "name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_DISPLAY_SEAT_ID, + g_param_spec_string ("x11-display-seat-id", + "seat id", + "seat id", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_DISPLAY_HOSTNAME, + g_param_spec_string ("x11-display-hostname", + "hostname", + "hostname", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_DISPLAY_DEVICE, + g_param_spec_string ("x11-display-device", + "device", + "device", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_DISPLAY_IS_LOCAL, + g_param_spec_boolean ("x11-display-is-local", + "is local", + "is local", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_X11_AUTHORITY_FILE, + g_param_spec_string ("x11-authority-file", + "authority file", + "authority file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_USER_NAME, + g_param_spec_string ("user-name", + "user name", + "user name", + GDM_USERNAME, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_RUNTIME_DIR, + g_param_spec_string ("runtime-dir", + "runtime dir", + "runtime dir", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_COMMAND, + g_param_spec_string ("command", + "command", + "command", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + signals [OPENED] = + g_signal_new ("opened", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLaunchEnvironmentClass, opened), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [STARTED] = + g_signal_new ("started", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLaunchEnvironmentClass, started), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [STOPPED] = + g_signal_new ("stopped", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLaunchEnvironmentClass, stopped), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [EXITED] = + g_signal_new ("exited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLaunchEnvironmentClass, exited), + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + signals [DIED] = + g_signal_new ("died", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLaunchEnvironmentClass, died), + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + signals [HOSTNAME_SELECTED] = + g_signal_new ("hostname-selected", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLaunchEnvironmentClass, hostname_selected), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); +} + +static void +gdm_launch_environment_init (GdmLaunchEnvironment *launch_environment) +{ + + launch_environment->priv = gdm_launch_environment_get_instance_private (launch_environment); + + launch_environment->priv->command = NULL; + launch_environment->priv->session = NULL; +} + +static void +gdm_launch_environment_finalize (GObject *object) +{ + GdmLaunchEnvironment *launch_environment; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_LAUNCH_ENVIRONMENT (object)); + + launch_environment = GDM_LAUNCH_ENVIRONMENT (object); + + g_return_if_fail (launch_environment->priv != NULL); + + gdm_launch_environment_stop (launch_environment); + + if (launch_environment->priv->session) { + g_object_unref (launch_environment->priv->session); + } + + g_free (launch_environment->priv->command); + g_free (launch_environment->priv->user_name); + g_free (launch_environment->priv->runtime_dir); + g_free (launch_environment->priv->x11_display_name); + g_free (launch_environment->priv->x11_display_seat_id); + g_free (launch_environment->priv->x11_display_device); + g_free (launch_environment->priv->x11_display_hostname); + g_free (launch_environment->priv->x11_authority_file); + g_free (launch_environment->priv->session_id); + g_free (launch_environment->priv->session_type); + + G_OBJECT_CLASS (gdm_launch_environment_parent_class)->finalize (object); +} + +static GdmLaunchEnvironment * +create_gnome_session_environment (const char *session_id, + const char *user_name, + const char *display_name, + const char *seat_id, + const char *session_type, + const char *session_mode, + const char *display_hostname, + gboolean display_is_local) +{ + gboolean debug = FALSE; + char *command; + GdmLaunchEnvironment *launch_environment; + char **argv; + GPtrArray *args; + + gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug); + + args = g_ptr_array_new (); + g_ptr_array_add (args, "gnome-session"); + + g_ptr_array_add (args, "--autostart"); + g_ptr_array_add (args, DATADIR "/gdm/greeter/autostart"); + + if (debug) { + g_ptr_array_add (args, "--debug"); + } + + if (session_id != NULL) { + g_ptr_array_add (args, " --session"); + g_ptr_array_add (args, (char *) session_id); + } + + g_ptr_array_add (args, NULL); + + argv = (char **) g_ptr_array_free (args, FALSE); + command = g_strjoinv (" ", argv); + g_free (argv); + + launch_environment = g_object_new (GDM_TYPE_LAUNCH_ENVIRONMENT, + "command", command, + "user-name", user_name, + "session-type", session_type, + "session-mode", session_mode, + "x11-display-name", display_name, + "x11-display-seat-id", seat_id, + "x11-display-hostname", display_hostname, + "x11-display-is-local", display_is_local, + "runtime-dir", GDM_SCREENSHOT_DIR, + NULL); + + g_free (command); + return launch_environment; +} + +GdmLaunchEnvironment * +gdm_create_greeter_launch_environment (const char *display_name, + const char *seat_id, + const char *session_type, + const char *display_hostname, + gboolean display_is_local) +{ + const char *session_name = NULL; + + return create_gnome_session_environment (session_name, + GDM_USERNAME, + display_name, + seat_id, + session_type, + GDM_SESSION_MODE, + display_hostname, + display_is_local); +} + +GdmLaunchEnvironment * +gdm_create_initial_setup_launch_environment (const char *display_name, + const char *seat_id, + const char *session_type, + const char *display_hostname, + gboolean display_is_local) +{ + return create_gnome_session_environment ("gnome-initial-setup", + INITIAL_SETUP_USERNAME, + display_name, + seat_id, + session_type, + INITIAL_SETUP_SESSION_MODE, + display_hostname, + display_is_local); +} + +GdmLaunchEnvironment * +gdm_create_chooser_launch_environment (const char *display_name, + const char *seat_id, + const char *display_hostname) + +{ + GdmLaunchEnvironment *launch_environment; + + launch_environment = g_object_new (GDM_TYPE_LAUNCH_ENVIRONMENT, + "command", LIBEXECDIR "/gdm-simple-chooser", + "verification-mode", GDM_SESSION_VERIFICATION_MODE_CHOOSER, + "user-name", GDM_USERNAME, + "x11-display-name", display_name, + "x11-display-seat-id", seat_id, + "x11-display-hostname", display_hostname, + "x11-display-is-local", FALSE, + "runtime-dir", GDM_SCREENSHOT_DIR, + NULL); + + return launch_environment; +} + diff --git a/daemon/gdm-launch-environment.h b/daemon/gdm-launch-environment.h new file mode 100644 index 0000000..00ac2a0 --- /dev/null +++ b/daemon/gdm-launch-environment.h @@ -0,0 +1,89 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com> + * + * 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. + * + */ + + +#ifndef __GDM_LAUNCH_ENVIRONMENT_H +#define __GDM_LAUNCH_ENVIRONMENT_H + +#include <glib-object.h> +#include "gdm-session.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_LAUNCH_ENVIRONMENT (gdm_launch_environment_get_type ()) +#define GDM_LAUNCH_ENVIRONMENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_LAUNCH_ENVIRONMENT, GdmLaunchEnvironment)) +#define GDM_LAUNCH_ENVIRONMENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_LAUNCH_ENVIRONMENT, GdmLaunchEnvironmentClass)) +#define GDM_IS_LAUNCH_ENVIRONMENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_LAUNCH_ENVIRONMENT)) +#define GDM_IS_LAUNCH_ENVIRONMENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_LAUNCH_ENVIRONMENT)) +#define GDM_LAUNCH_ENVIRONMENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_LAUNCH_ENVIRONMENT, GdmLaunchEnvironmentClass)) + +typedef struct GdmLaunchEnvironmentPrivate GdmLaunchEnvironmentPrivate; + +typedef struct +{ + GObject parent; + GdmLaunchEnvironmentPrivate *priv; +} GdmLaunchEnvironment; + +typedef struct +{ + GObjectClass parent_class; + + /* methods */ + gboolean (*start) (GdmLaunchEnvironment *launch_environment); + gboolean (*stop) (GdmLaunchEnvironment *launch_environment); + + + /* signals */ + void (* opened) (GdmLaunchEnvironment *launch_environment); + void (* started) (GdmLaunchEnvironment *launch_environment); + void (* stopped) (GdmLaunchEnvironment *launch_environment); + void (* exited) (GdmLaunchEnvironment *launch_environment, + int exit_code); + void (* died) (GdmLaunchEnvironment *launch_environment, + int signal_number); + void (* hostname_selected) (GdmLaunchEnvironment *launch_environment, + const char *hostname); +} GdmLaunchEnvironmentClass; + +GType gdm_launch_environment_get_type (void); + +gboolean gdm_launch_environment_start (GdmLaunchEnvironment *launch_environment); +gboolean gdm_launch_environment_stop (GdmLaunchEnvironment *launch_environment); +GdmSession * gdm_launch_environment_get_session (GdmLaunchEnvironment *launch_environment); +char * gdm_launch_environment_get_session_id (GdmLaunchEnvironment *launch_environment); + +GdmLaunchEnvironment *gdm_create_greeter_launch_environment (const char *display_name, + const char *seat_id, + const char *session_type, + const char *display_hostname, + gboolean display_is_local); +GdmLaunchEnvironment *gdm_create_initial_setup_launch_environment (const char *display_name, + const char *seat_id, + const char *session_type, + const char *display_hostname, + gboolean display_is_local); +GdmLaunchEnvironment *gdm_create_chooser_launch_environment (const char *display_name, + const char *seat_id, + const char *display_hostname); + +G_END_DECLS + +#endif /* __GDM_LAUNCH_ENVIRONMENT_H */ diff --git a/daemon/gdm-legacy-display.c b/daemon/gdm-legacy-display.c new file mode 100644 index 0000000..e53d82b --- /dev/null +++ b/daemon/gdm-legacy-display.c @@ -0,0 +1,303 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <pwd.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-common.h" +#include "gdm-display.h" +#include "gdm-launch-environment.h" +#include "gdm-legacy-display.h" +#include "gdm-local-display-glue.h" +#include "gdm-server.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +struct _GdmLegacyDisplay +{ + GdmDisplay parent; + + GdmDBusLocalDisplay *skeleton; + + GdmServer *server; +}; + +static void gdm_legacy_display_class_init (GdmLegacyDisplayClass *klass); +static void gdm_legacy_display_init (GdmLegacyDisplay *legacy_display); + +G_DEFINE_TYPE (GdmLegacyDisplay, gdm_legacy_display, GDM_TYPE_DISPLAY) + +static GObject * +gdm_legacy_display_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmLegacyDisplay *display; + + display = GDM_LEGACY_DISPLAY (G_OBJECT_CLASS (gdm_legacy_display_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + display->skeleton = GDM_DBUS_LOCAL_DISPLAY (gdm_dbus_local_display_skeleton_new ()); + + g_dbus_object_skeleton_add_interface (gdm_display_get_object_skeleton (GDM_DISPLAY (display)), + G_DBUS_INTERFACE_SKELETON (display->skeleton)); + + return G_OBJECT (display); +} + +static void +gdm_legacy_display_finalize (GObject *object) +{ + GdmLegacyDisplay *display = GDM_LEGACY_DISPLAY (object); + + g_clear_object (&display->skeleton); + g_clear_object (&display->server); + + G_OBJECT_CLASS (gdm_legacy_display_parent_class)->finalize (object); +} + +static gboolean +gdm_legacy_display_prepare (GdmDisplay *display) +{ + GdmLegacyDisplay *self = GDM_LEGACY_DISPLAY (display); + GdmLaunchEnvironment *launch_environment; + char *display_name; + char *seat_id; + gboolean doing_initial_setup = FALSE; + + display_name = NULL; + seat_id = NULL; + + g_object_get (self, + "x11-display-name", &display_name, + "seat-id", &seat_id, + "doing-initial-setup", &doing_initial_setup, + NULL); + + if (!doing_initial_setup) { + launch_environment = gdm_create_greeter_launch_environment (display_name, + seat_id, + NULL, + NULL, + TRUE); + } else { + launch_environment = gdm_create_initial_setup_launch_environment (display_name, + seat_id, + NULL, + NULL, + TRUE); + } + + g_object_set (self, "launch-environment", launch_environment, NULL); + g_object_unref (launch_environment); + + if (!gdm_display_create_authority (display)) { + g_warning ("Unable to set up access control for display %s", + display_name); + return FALSE; + } + + return GDM_DISPLAY_CLASS (gdm_legacy_display_parent_class)->prepare (display); +} + +static void +on_server_ready (GdmServer *server, + GdmLegacyDisplay *self) +{ + gboolean ret; + + ret = gdm_display_connect (GDM_DISPLAY (self)); + + if (!ret) { + g_debug ("GdmDisplay: could not connect to display"); + gdm_display_unmanage (GDM_DISPLAY (self)); + } else { + GdmLaunchEnvironment *launch_environment; + char *display_device; + + display_device = gdm_server_get_display_device (server); + + g_object_get (G_OBJECT (self), + "launch-environment", &launch_environment, + NULL); + g_object_set (G_OBJECT (launch_environment), + "x11-display-device", + display_device, + NULL); + g_clear_pointer(&display_device, g_free); + g_clear_object (&launch_environment); + + g_debug ("GdmDisplay: connected to display"); + g_object_set (G_OBJECT (self), "status", GDM_DISPLAY_MANAGED, NULL); + } +} + +static void +on_server_exited (GdmServer *server, + int exit_code, + GdmDisplay *self) +{ + g_debug ("GdmDisplay: server exited with code %d\n", exit_code); + + gdm_display_unmanage (GDM_DISPLAY (self)); +} + +static void +on_server_died (GdmServer *server, + int signal_number, + GdmDisplay *self) +{ + g_debug ("GdmDisplay: server died with signal %d, (%s)", + signal_number, + g_strsignal (signal_number)); + + gdm_display_unmanage (GDM_DISPLAY (self)); +} + +static void +gdm_legacy_display_manage (GdmDisplay *display) +{ + GdmLegacyDisplay *self = GDM_LEGACY_DISPLAY (display); + char *display_name; + char *auth_file; + char *seat_id; + gboolean is_initial; + gboolean res; + gboolean disable_tcp; + + g_object_get (G_OBJECT (self), + "x11-display-name", &display_name, + "x11-authority-file", &auth_file, + "seat-id", &seat_id, + "is-initial", &is_initial, + NULL); + + self->server = gdm_server_new (display_name, seat_id, auth_file, is_initial); + + g_free (display_name); + g_free (auth_file); + g_free (seat_id); + + disable_tcp = TRUE; + if (gdm_settings_direct_get_boolean (GDM_KEY_DISALLOW_TCP, &disable_tcp)) { + g_object_set (self->server, + "disable-tcp", disable_tcp, + NULL); + } + + g_signal_connect (self->server, + "exited", + G_CALLBACK (on_server_exited), + self); + g_signal_connect (self->server, + "died", + G_CALLBACK (on_server_died), + self); + g_signal_connect (self->server, + "ready", + G_CALLBACK (on_server_ready), + self); + + res = gdm_server_start (self->server); + if (! res) { + g_warning (_("Could not start the X " + "server (your graphical environment) " + "due to an internal error. " + "Please contact your system administrator " + "or check your syslog to diagnose. " + "In the meantime this display will be " + "disabled. Please restart GDM when " + "the problem is corrected.")); + gdm_display_unmanage (GDM_DISPLAY (self)); + } + + g_debug ("GdmDisplay: Started X server"); + +} + +static void +gdm_legacy_display_class_init (GdmLegacyDisplayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdmDisplayClass *display_class = GDM_DISPLAY_CLASS (klass); + + object_class->constructor = gdm_legacy_display_constructor; + object_class->finalize = gdm_legacy_display_finalize; + + display_class->prepare = gdm_legacy_display_prepare; + display_class->manage = gdm_legacy_display_manage; +} + +static void +on_display_status_changed (GdmLegacyDisplay *self) +{ + int status; + + status = gdm_display_get_status (GDM_DISPLAY (self)); + + switch (status) { + case GDM_DISPLAY_UNMANAGED: + if (self->server != NULL) + gdm_server_stop (self->server); + break; + default: + break; + } +} + +static void +gdm_legacy_display_init (GdmLegacyDisplay *legacy_display) +{ + g_signal_connect (legacy_display, "notify::status", + G_CALLBACK (on_display_status_changed), + NULL); +} + +GdmDisplay * +gdm_legacy_display_new (int display_number) +{ + GObject *object; + char *x11_display; + + x11_display = g_strdup_printf (":%d", display_number); + object = g_object_new (GDM_TYPE_LEGACY_DISPLAY, + "x11-display-number", display_number, + "x11-display-name", x11_display, + NULL); + g_free (x11_display); + + return GDM_DISPLAY (object); +} diff --git a/daemon/gdm-legacy-display.h b/daemon/gdm-legacy-display.h new file mode 100644 index 0000000..9f98c83 --- /dev/null +++ b/daemon/gdm-legacy-display.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_LEGACY_DISPLAY_H +#define __GDM_LEGACY_DISPLAY_H + +#include <glib-object.h> +#include "gdm-display.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_LEGACY_DISPLAY (gdm_legacy_display_get_type ()) +G_DECLARE_FINAL_TYPE (GdmLegacyDisplay, gdm_legacy_display, GDM, LEGACY_DISPLAY, GdmDisplay) + +GdmDisplay * gdm_legacy_display_new (int display_number); + + +G_END_DECLS + +#endif /* __GDM_LEGACY_DISPLAY_H */ diff --git a/daemon/gdm-local-display-factory.c b/daemon/gdm-local-display-factory.c new file mode 100644 index 0000000..e7cafeb --- /dev/null +++ b/daemon/gdm-local-display-factory.c @@ -0,0 +1,1142 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gio/gio.h> + +#include <systemd/sd-login.h> + +#include "gdm-common.h" +#include "gdm-manager.h" +#include "gdm-display-factory.h" +#include "gdm-local-display-factory.h" +#include "gdm-local-display-factory-glue.h" + +#include "gdm-settings-keys.h" +#include "gdm-settings-direct.h" +#include "gdm-display-store.h" +#include "gdm-local-display.h" +#include "gdm-legacy-display.h" + +#define GDM_DBUS_PATH "/org/gnome/DisplayManager" +#define GDM_LOCAL_DISPLAY_FACTORY_DBUS_PATH GDM_DBUS_PATH "/LocalDisplayFactory" +#define GDM_MANAGER_DBUS_NAME "org.gnome.DisplayManager.LocalDisplayFactory" + +#define MAX_DISPLAY_FAILURES 5 +#define WAIT_TO_FINISH_TIMEOUT 10 /* seconds */ + +struct _GdmLocalDisplayFactory +{ + GdmDisplayFactory parent; + + GdmDBusLocalDisplayFactory *skeleton; + GDBusConnection *connection; + GHashTable *used_display_numbers; + + /* FIXME: this needs to be per seat? */ + guint num_failures; + + guint seat_new_id; + guint seat_removed_id; + +#if defined(ENABLE_USER_DISPLAY_SERVER) + unsigned int active_vt; + guint active_vt_watch_id; + guint wait_to_finish_timeout_id; +#endif +}; + +enum { + PROP_0, +}; + +static void gdm_local_display_factory_class_init (GdmLocalDisplayFactoryClass *klass); +static void gdm_local_display_factory_init (GdmLocalDisplayFactory *factory); +static void gdm_local_display_factory_finalize (GObject *object); + +static GdmDisplay *create_display (GdmLocalDisplayFactory *factory, + const char *seat_id, + const char *session_type, + gboolean initial_display); + +static void on_display_status_changed (GdmDisplay *display, + GParamSpec *arg1, + GdmLocalDisplayFactory *factory); + +static gboolean gdm_local_display_factory_sync_seats (GdmLocalDisplayFactory *factory); +static gpointer local_display_factory_object = NULL; +static gboolean lookup_by_session_id (const char *id, + GdmDisplay *display, + gpointer user_data); + +G_DEFINE_TYPE (GdmLocalDisplayFactory, gdm_local_display_factory, GDM_TYPE_DISPLAY_FACTORY) + +GQuark +gdm_local_display_factory_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gdm_local_display_factory_error"); + } + + return ret; +} + +static void +listify_hash (gpointer key, + GdmDisplay *display, + GList **list) +{ + *list = g_list_prepend (*list, key); +} + +static int +sort_nums (gpointer a, + gpointer b) +{ + guint32 num_a; + guint32 num_b; + + num_a = GPOINTER_TO_UINT (a); + num_b = GPOINTER_TO_UINT (b); + + if (num_a > num_b) { + return 1; + } else if (num_a < num_b) { + return -1; + } else { + return 0; + } +} + +static guint32 +take_next_display_number (GdmLocalDisplayFactory *factory) +{ + GList *list; + GList *l; + guint32 ret; + + ret = 0; + list = NULL; + + g_hash_table_foreach (factory->used_display_numbers, (GHFunc)listify_hash, &list); + if (list == NULL) { + goto out; + } + + /* sort low to high */ + list = g_list_sort (list, (GCompareFunc)sort_nums); + + g_debug ("GdmLocalDisplayFactory: Found the following X displays:"); + for (l = list; l != NULL; l = l->next) { + g_debug ("GdmLocalDisplayFactory: %u", GPOINTER_TO_UINT (l->data)); + } + + for (l = list; l != NULL; l = l->next) { + guint32 num; + num = GPOINTER_TO_UINT (l->data); + + /* always fill zero */ + if (l->prev == NULL && num != 0) { + ret = 0; + break; + } + /* now find the first hole */ + if (l->next == NULL || GPOINTER_TO_UINT (l->next->data) != (num + 1)) { + ret = num + 1; + break; + } + } + out: + + /* now reserve this number */ + g_debug ("GdmLocalDisplayFactory: Reserving X display: %u", ret); + g_hash_table_insert (factory->used_display_numbers, GUINT_TO_POINTER (ret), NULL); + + return ret; +} + +static void +on_display_disposed (GdmLocalDisplayFactory *factory, + GdmDisplay *display) +{ + g_debug ("GdmLocalDisplayFactory: Display %p disposed", display); +} + +static void +store_display (GdmLocalDisplayFactory *factory, + GdmDisplay *display) +{ + GdmDisplayStore *store; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_add (store, display); +} + +static gboolean +gdm_local_display_factory_use_wayland (void) +{ +#ifdef ENABLE_WAYLAND_SUPPORT + gboolean wayland_enabled = FALSE; + if (gdm_settings_direct_get_boolean (GDM_KEY_WAYLAND_ENABLE, &wayland_enabled)) { + if (wayland_enabled && g_file_test ("/usr/bin/Xwayland", G_FILE_TEST_IS_EXECUTABLE) ) + return TRUE; + } +#endif + return FALSE; +} + +/* + Example: + dbus-send --system --dest=org.gnome.DisplayManager \ + --type=method_call --print-reply --reply-timeout=2000 \ + /org/gnome/DisplayManager/Manager \ + org.gnome.DisplayManager.Manager.GetDisplays +*/ +gboolean +gdm_local_display_factory_create_transient_display (GdmLocalDisplayFactory *factory, + char **id, + GError **error) +{ + gboolean ret; + GdmDisplay *display = NULL; + gboolean is_initial = FALSE; + + g_return_val_if_fail (GDM_IS_LOCAL_DISPLAY_FACTORY (factory), FALSE); + + ret = FALSE; + + g_debug ("GdmLocalDisplayFactory: Creating transient display"); + +#ifdef ENABLE_USER_DISPLAY_SERVER + display = gdm_local_display_new (); + if (gdm_local_display_factory_use_wayland ()) + g_object_set (G_OBJECT (display), "session-type", "wayland", NULL); + is_initial = TRUE; +#else + if (display == NULL) { + guint32 num; + + num = take_next_display_number (factory); + + display = gdm_legacy_display_new (num); + } +#endif + + g_object_set (display, + "seat-id", "seat0", + "allow-timed-login", FALSE, + "is-initial", is_initial, + NULL); + + store_display (factory, display); + + if (! gdm_display_manage (display)) { + display = NULL; + goto out; + } + + if (! gdm_display_get_id (display, id, NULL)) { + display = NULL; + goto out; + } + + ret = TRUE; + out: + /* ref either held by store or not at all */ + g_object_unref (display); + + return ret; +} + +static void +finish_display_on_seat_if_waiting (GdmDisplayStore *display_store, + GdmDisplay *display, + const char *seat_id) +{ + if (gdm_display_get_status (display) != GDM_DISPLAY_WAITING_TO_FINISH) + return; + + g_debug ("GdmLocalDisplayFactory: finish background display\n"); + gdm_display_stop_greeter_session (display); + gdm_display_unmanage (display); + gdm_display_finish (display); +} + +static void +finish_waiting_displays_on_seat (GdmLocalDisplayFactory *factory, + const char *seat_id) +{ + GdmDisplayStore *store; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + gdm_display_store_foreach (store, + (GdmDisplayStoreFunc) finish_display_on_seat_if_waiting, + (gpointer) + seat_id); +} + +static gboolean +on_finish_waiting_for_seat0_displays_timeout (GdmLocalDisplayFactory *factory) +{ + g_debug ("GdmLocalDisplayFactory: timeout following VT switch to registered session complete, looking for any background displays to kill"); + finish_waiting_displays_on_seat (factory, "seat0"); + return G_SOURCE_REMOVE; +} + +static void +on_session_registered_cb (GObject *gobject, + GParamSpec *pspec, + gpointer user_data) +{ + GdmDisplay *display = GDM_DISPLAY (gobject); + GdmLocalDisplayFactory *factory = GDM_LOCAL_DISPLAY_FACTORY (user_data); + gboolean registered; + + g_object_get (display, "session-registered", ®istered, NULL); + + if (!registered) + return; + + g_debug ("GdmLocalDisplayFactory: session registered on display, looking for any background displays to kill"); + + finish_waiting_displays_on_seat (factory, "seat0"); +} + +static void +on_display_status_changed (GdmDisplay *display, + GParamSpec *arg1, + GdmLocalDisplayFactory *factory) +{ + int status; + int num; + char *seat_id = NULL; + char *session_type = NULL; + char *session_class = NULL; + gboolean is_initial = TRUE; + gboolean is_local = TRUE; + + num = -1; + gdm_display_get_x11_display_number (display, &num, NULL); + + g_object_get (display, + "seat-id", &seat_id, + "is-initial", &is_initial, + "is-local", &is_local, + "session-type", &session_type, + "session-class", &session_class, + NULL); + + status = gdm_display_get_status (display); + + g_debug ("GdmLocalDisplayFactory: display status changed: %d", status); + switch (status) { + case GDM_DISPLAY_FINISHED: + /* remove the display number from factory->used_display_numbers + so that it may be reused */ + if (num != -1) { + g_hash_table_remove (factory->used_display_numbers, GUINT_TO_POINTER (num)); + } + gdm_display_factory_queue_purge_displays (GDM_DISPLAY_FACTORY (factory)); + + /* if this is a local display, do a full resync. Only + * seats without displays will get created anyway. This + * ensures we get a new login screen when the user logs out, + * if there isn't one. + */ + if (is_local && g_strcmp0 (session_class, "greeter") != 0) { + /* reset num failures */ + factory->num_failures = 0; + + gdm_local_display_factory_sync_seats (factory); + } + break; + case GDM_DISPLAY_FAILED: + /* leave the display number in factory->used_display_numbers + so that it doesn't get reused */ + gdm_display_factory_queue_purge_displays (GDM_DISPLAY_FACTORY (factory)); + + /* Create a new equivalent display if it was static */ + if (is_local) { + + factory->num_failures++; + + if (factory->num_failures > MAX_DISPLAY_FAILURES) { + /* oh shit */ + g_warning ("GdmLocalDisplayFactory: maximum number of X display failures reached: check X server log for errors"); + } else { +#ifdef ENABLE_WAYLAND_SUPPORT + if (g_strcmp0 (session_type, "wayland") == 0) { + g_free (session_type); + session_type = NULL; + } + +#endif + create_display (factory, seat_id, session_type, is_initial); + } + } + break; + case GDM_DISPLAY_UNMANAGED: + break; + case GDM_DISPLAY_PREPARED: + break; + case GDM_DISPLAY_MANAGED: +#if defined(ENABLE_USER_DISPLAY_SERVER) + g_signal_connect_object (display, + "notify::session-registered", + G_CALLBACK (on_session_registered_cb), + factory, + 0); +#endif + break; + case GDM_DISPLAY_WAITING_TO_FINISH: + break; + default: + g_assert_not_reached (); + break; + } + + g_free (seat_id); + g_free (session_type); + g_free (session_class); +} + +static gboolean +lookup_by_seat_id (const char *id, + GdmDisplay *display, + gpointer user_data) +{ + const char *looking_for = user_data; + char *current; + gboolean res; + + g_object_get (G_OBJECT (display), "seat-id", ¤t, NULL); + + res = g_strcmp0 (current, looking_for) == 0; + + g_free(current); + + return res; +} + +static gboolean +lookup_prepared_display_by_seat_id (const char *id, + GdmDisplay *display, + gpointer user_data) +{ + int status; + + status = gdm_display_get_status (display); + + if (status != GDM_DISPLAY_PREPARED) + return FALSE; + + return lookup_by_seat_id (id, display, user_data); +} + +static GdmDisplay * +create_display (GdmLocalDisplayFactory *factory, + const char *seat_id, + const char *session_type, + gboolean initial) +{ + GdmDisplayStore *store; + GdmDisplay *display = NULL; + g_autofree char *login_session_id = NULL; + + g_debug ("GdmLocalDisplayFactory: %s login display for seat %s requested", + session_type? : "X11", seat_id); + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + if (sd_seat_can_multi_session (seat_id)) + display = gdm_display_store_find (store, lookup_prepared_display_by_seat_id, (gpointer) seat_id); + else + display = gdm_display_store_find (store, lookup_by_seat_id, (gpointer) seat_id); + + /* Ensure we don't create the same display more than once */ + if (display != NULL) { + g_debug ("GdmLocalDisplayFactory: display already created"); + return NULL; + } + + /* If we already have a login window, switch to it */ + if (gdm_get_login_window_session_id (seat_id, &login_session_id)) { + GdmDisplay *display; + + display = gdm_display_store_find (store, + lookup_by_session_id, + (gpointer) login_session_id); + if (display != NULL && + (gdm_display_get_status (display) == GDM_DISPLAY_MANAGED || + gdm_display_get_status (display) == GDM_DISPLAY_WAITING_TO_FINISH)) { + g_object_set (G_OBJECT (display), "status", GDM_DISPLAY_MANAGED, NULL); + g_debug ("GdmLocalDisplayFactory: session %s found, activating.", + login_session_id); + gdm_activate_session_by_id (factory->connection, seat_id, login_session_id); + return NULL; + } + } + + g_debug ("GdmLocalDisplayFactory: Adding display on seat %s", seat_id); + +#ifdef ENABLE_USER_DISPLAY_SERVER + if (g_strcmp0 (seat_id, "seat0") == 0) { + display = gdm_local_display_new (); + if (session_type != NULL) { + g_object_set (G_OBJECT (display), "session-type", session_type, NULL); + } + } +#endif + + if (display == NULL) { + guint32 num; + + num = take_next_display_number (factory); + + display = gdm_legacy_display_new (num); + } + + g_object_set (display, "seat-id", seat_id, NULL); + g_object_set (display, "is-initial", initial, NULL); + + store_display (factory, display); + + /* let store own the ref */ + g_object_unref (display); + + if (! gdm_display_manage (display)) { + gdm_display_unmanage (display); + } + + return display; +} + +static void +delete_display (GdmLocalDisplayFactory *factory, + const char *seat_id) { + + GdmDisplayStore *store; + + g_debug ("GdmLocalDisplayFactory: Removing used_display_numbers on seat %s", seat_id); + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_foreach_remove (store, lookup_by_seat_id, (gpointer) seat_id); +} + +static gboolean +gdm_local_display_factory_sync_seats (GdmLocalDisplayFactory *factory) +{ + GError *error = NULL; + GVariant *result; + GVariant *array; + GVariantIter iter; + const char *seat; + + g_debug ("GdmLocalDisplayFactory: enumerating seats from logind"); + result = g_dbus_connection_call_sync (factory->connection, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ListSeats", + NULL, + G_VARIANT_TYPE ("(a(so))"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, &error); + + if (!result) { + g_warning ("GdmLocalDisplayFactory: Failed to issue method call: %s", error->message); + g_clear_error (&error); + return FALSE; + } + + array = g_variant_get_child_value (result, 0); + g_variant_iter_init (&iter, array); + + while (g_variant_iter_loop (&iter, "(&so)", &seat, NULL)) { + gboolean is_initial; + const char *session_type = NULL; + + if (g_strcmp0 (seat, "seat0") == 0) { + is_initial = TRUE; + if (gdm_local_display_factory_use_wayland ()) + session_type = "wayland"; + } else { + is_initial = FALSE; + } + + create_display (factory, seat, session_type, is_initial); + } + + g_variant_unref (result); + g_variant_unref (array); + return TRUE; +} + +static void +on_seat_new (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + const char *seat; + + g_variant_get (parameters, "(&s&o)", &seat, NULL); + create_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat, NULL, FALSE); +} + +static void +on_seat_removed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + const char *seat; + + g_variant_get (parameters, "(&s&o)", &seat, NULL); + delete_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat); +} + +static gboolean +lookup_by_session_id (const char *id, + GdmDisplay *display, + gpointer user_data) +{ + const char *looking_for = user_data; + const char *current; + + current = gdm_display_get_session_id (display); + return g_strcmp0 (current, looking_for) == 0; +} + +static gboolean +lookup_by_tty (const char *id, + GdmDisplay *display, + gpointer user_data) +{ + const char *tty_to_find = user_data; + g_autofree char *tty_to_check = NULL; + const char *session_id; + int ret; + + session_id = gdm_display_get_session_id (display); + + if (!session_id) + return FALSE; + + ret = sd_session_get_tty (session_id, &tty_to_check); + + if (ret != 0) + return FALSE; + + return g_strcmp0 (tty_to_check, tty_to_find) == 0; +} + +#if defined(ENABLE_USER_DISPLAY_SERVER) +static void +maybe_stop_greeter_in_background (GdmLocalDisplayFactory *factory, + GdmDisplay *display) +{ + gboolean doing_initial_setup = FALSE; + + if (gdm_display_get_status (display) != GDM_DISPLAY_MANAGED) { + g_debug ("GdmLocalDisplayFactory: login window not in managed state, so ignoring"); + return; + } + + g_object_get (G_OBJECT (display), + "doing-initial-setup", &doing_initial_setup, + NULL); + + /* we don't ever stop initial-setup implicitly */ + if (doing_initial_setup) { + g_debug ("GdmLocalDisplayFactory: login window is performing initial-setup, so ignoring"); + return; + } + + g_debug ("GdmLocalDisplayFactory: killing login window once its unused"); + + g_object_set (G_OBJECT (display), "status", GDM_DISPLAY_WAITING_TO_FINISH, NULL); +} + +static gboolean +on_vt_changed (GIOChannel *source, + GIOCondition condition, + GdmLocalDisplayFactory *factory) +{ + GdmDisplayStore *store; + GIOStatus status; + g_autofree char *tty_of_active_vt = NULL; + g_autofree char *login_session_id = NULL; + g_autofree char *active_session_id = NULL; + unsigned int previous_vt, new_vt, login_window_vt = 0; + const char *session_type = NULL; + int ret, n_returned; + + g_debug ("GdmLocalDisplayFactory: received VT change event"); + g_io_channel_seek_position (source, 0, G_SEEK_SET, NULL); + + if (condition & G_IO_PRI) { + g_autoptr (GError) error = NULL; + status = g_io_channel_read_line (source, &tty_of_active_vt, NULL, NULL, &error); + + if (error != NULL) { + g_warning ("could not read active VT from kernel: %s", error->message); + } + switch (status) { + case G_IO_STATUS_ERROR: + return G_SOURCE_REMOVE; + case G_IO_STATUS_EOF: + return G_SOURCE_REMOVE; + case G_IO_STATUS_AGAIN: + return G_SOURCE_CONTINUE; + case G_IO_STATUS_NORMAL: + break; + } + } + + if ((condition & G_IO_ERR) || (condition & G_IO_HUP)) { + g_debug ("GdmLocalDisplayFactory: kernel hung up active vt watch"); + return G_SOURCE_REMOVE; + } + + if (tty_of_active_vt == NULL) { + g_debug ("GdmLocalDisplayFactory: unable to read active VT from kernel"); + return G_SOURCE_CONTINUE; + } + + g_strchomp (tty_of_active_vt); + + errno = 0; + n_returned = sscanf (tty_of_active_vt, "tty%u", &new_vt); + + if (n_returned != 1 || errno != 0) { + g_critical ("GdmLocalDisplayFactory: Couldn't read active VT (got '%s')", + tty_of_active_vt); + return G_SOURCE_CONTINUE; + } + + /* don't do anything if we're on the same VT we were before */ + if (new_vt == factory->active_vt) { + g_debug ("GdmLocalDisplayFactory: VT changed to the same VT, ignoring"); + return G_SOURCE_CONTINUE; + } + + previous_vt = factory->active_vt; + factory->active_vt = new_vt; + + /* don't do anything at start up */ + if (previous_vt == 0) { + g_debug ("GdmLocalDisplayFactory: VT is %u at startup", + factory->active_vt); + return G_SOURCE_CONTINUE; + } + + g_debug ("GdmLocalDisplayFactory: VT changed from %u to %u", + previous_vt, factory->active_vt); + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + /* if the old VT was running a wayland login screen kill it + */ + if (gdm_get_login_window_session_id ("seat0", &login_session_id)) { + ret = sd_session_get_vt (login_session_id, &login_window_vt); + if (ret == 0 && login_window_vt != 0) { + g_debug ("GdmLocalDisplayFactory: VT of login window is %u", login_window_vt); + if (login_window_vt == previous_vt) { + GdmDisplay *display; + + g_debug ("GdmLocalDisplayFactory: VT switched from login window"); + + display = gdm_display_store_find (store, + lookup_by_session_id, + (gpointer) login_session_id); + if (display != NULL) + maybe_stop_greeter_in_background (factory, display); + } else { + g_debug ("GdmLocalDisplayFactory: VT not switched from login window"); + } + } + } + + /* If we jumped to a registered user session, we can kill + * the login screen (after a suitable timeout to avoid flicker) + */ + if (factory->active_vt != login_window_vt) { + GdmDisplay *display; + + display = gdm_display_store_find (store, + lookup_by_tty, + (gpointer) tty_of_active_vt); + + if (display != NULL) { + gboolean registered; + + g_object_get (display, "session-registered", ®istered, NULL); + + if (registered) { + g_debug ("GdmLocalDisplayFactory: switched to registered user session, so reaping login screen in %d seconds", + WAIT_TO_FINISH_TIMEOUT); + if (factory->wait_to_finish_timeout_id != 0) { + g_debug ("GdmLocalDisplayFactory: deferring previous login screen clean up operation"); + g_source_remove (factory->wait_to_finish_timeout_id); + } + + factory->wait_to_finish_timeout_id = g_timeout_add_seconds (WAIT_TO_FINISH_TIMEOUT, + (GSourceFunc) + on_finish_waiting_for_seat0_displays_timeout, + factory); + } + } + } + + /* if user jumped back to initial vt and it's empty put a login screen + * on it (unless a login screen is already running elsewhere, then + * jump to that login screen) + */ + if (factory->active_vt != GDM_INITIAL_VT) { + g_debug ("GdmLocalDisplayFactory: active VT is not initial VT, so ignoring"); + return G_SOURCE_CONTINUE; + } + + if (gdm_local_display_factory_use_wayland ()) + session_type = "wayland"; + + g_debug ("GdmLocalDisplayFactory: creating new display on seat0 because of VT change"); + + create_display (factory, "seat0", session_type, TRUE); + + return G_SOURCE_CONTINUE; +} +#endif + +static void +gdm_local_display_factory_start_monitor (GdmLocalDisplayFactory *factory) +{ + g_autoptr (GIOChannel) io_channel = NULL; + + factory->seat_new_id = g_dbus_connection_signal_subscribe (factory->connection, + "org.freedesktop.login1", + "org.freedesktop.login1.Manager", + "SeatNew", + "/org/freedesktop/login1", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_seat_new, + g_object_ref (factory), + g_object_unref); + factory->seat_removed_id = g_dbus_connection_signal_subscribe (factory->connection, + "org.freedesktop.login1", + "org.freedesktop.login1.Manager", + "SeatRemoved", + "/org/freedesktop/login1", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_seat_removed, + g_object_ref (factory), + g_object_unref); + +#if defined(ENABLE_USER_DISPLAY_SERVER) + io_channel = g_io_channel_new_file ("/sys/class/tty/tty0/active", "r", NULL); + + if (io_channel != NULL) { + factory->active_vt_watch_id = + g_io_add_watch (io_channel, + G_IO_PRI, + (GIOFunc) + on_vt_changed, + factory); + } +#endif +} + +static void +gdm_local_display_factory_stop_monitor (GdmLocalDisplayFactory *factory) +{ + if (factory->seat_new_id) { + g_dbus_connection_signal_unsubscribe (factory->connection, + factory->seat_new_id); + factory->seat_new_id = 0; + } + if (factory->seat_removed_id) { + g_dbus_connection_signal_unsubscribe (factory->connection, + factory->seat_removed_id); + factory->seat_removed_id = 0; + } +#if defined(ENABLE_USER_DISPLAY_SERVER) + if (factory->active_vt_watch_id) { + g_source_remove (factory->active_vt_watch_id); + factory->active_vt_watch_id = 0; + } + if (factory->wait_to_finish_timeout_id != 0) { + g_source_remove (factory->wait_to_finish_timeout_id); + factory->wait_to_finish_timeout_id = 0; + } +#endif +} + +static void +on_display_added (GdmDisplayStore *display_store, + const char *id, + GdmLocalDisplayFactory *factory) +{ + GdmDisplay *display; + + display = gdm_display_store_lookup (display_store, id); + + if (display != NULL) { + g_signal_connect_object (display, "notify::status", + G_CALLBACK (on_display_status_changed), + factory, + 0); + + g_object_weak_ref (G_OBJECT (display), (GWeakNotify)on_display_disposed, factory); + } +} + +static void +on_display_removed (GdmDisplayStore *display_store, + GdmDisplay *display, + GdmLocalDisplayFactory *factory) +{ + g_signal_handlers_disconnect_by_func (display, G_CALLBACK (on_display_status_changed), factory); + g_object_weak_unref (G_OBJECT (display), (GWeakNotify)on_display_disposed, factory); +} + +static gboolean +gdm_local_display_factory_start (GdmDisplayFactory *base_factory) +{ + GdmLocalDisplayFactory *factory = GDM_LOCAL_DISPLAY_FACTORY (base_factory); + GdmDisplayStore *store; + + g_return_val_if_fail (GDM_IS_LOCAL_DISPLAY_FACTORY (factory), FALSE); + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + g_signal_connect_object (G_OBJECT (store), + "display-added", + G_CALLBACK (on_display_added), + factory, + 0); + + g_signal_connect_object (G_OBJECT (store), + "display-removed", + G_CALLBACK (on_display_removed), + factory, + 0); + + gdm_local_display_factory_start_monitor (factory); + return gdm_local_display_factory_sync_seats (factory); +} + +static gboolean +gdm_local_display_factory_stop (GdmDisplayFactory *base_factory) +{ + GdmLocalDisplayFactory *factory = GDM_LOCAL_DISPLAY_FACTORY (base_factory); + GdmDisplayStore *store; + + g_return_val_if_fail (GDM_IS_LOCAL_DISPLAY_FACTORY (factory), FALSE); + + gdm_local_display_factory_stop_monitor (factory); + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + g_signal_handlers_disconnect_by_func (G_OBJECT (store), + G_CALLBACK (on_display_added), + factory); + g_signal_handlers_disconnect_by_func (G_OBJECT (store), + G_CALLBACK (on_display_removed), + factory); + + return TRUE; +} + +static void +gdm_local_display_factory_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_local_display_factory_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +handle_create_transient_display (GdmDBusLocalDisplayFactory *skeleton, + GDBusMethodInvocation *invocation, + GdmLocalDisplayFactory *factory) +{ + GError *error = NULL; + gboolean created; + char *id = NULL; + + created = gdm_local_display_factory_create_transient_display (factory, + &id, + &error); + if (!created) { + g_dbus_method_invocation_return_gerror (invocation, error); + } else { + gdm_dbus_local_display_factory_complete_create_transient_display (skeleton, invocation, id); + } + + g_free (id); + return TRUE; +} + +static gboolean +register_factory (GdmLocalDisplayFactory *factory) +{ + GError *error = NULL; + + error = NULL; + factory->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (factory->connection == NULL) { + g_critical ("error getting system bus: %s", error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + factory->skeleton = GDM_DBUS_LOCAL_DISPLAY_FACTORY (gdm_dbus_local_display_factory_skeleton_new ()); + + g_signal_connect (factory->skeleton, + "handle-create-transient-display", + G_CALLBACK (handle_create_transient_display), + factory); + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (factory->skeleton), + factory->connection, + GDM_LOCAL_DISPLAY_FACTORY_DBUS_PATH, + &error)) { + g_critical ("error exporting LocalDisplayFactory object: %s", error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + return TRUE; +} + +static GObject * +gdm_local_display_factory_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmLocalDisplayFactory *factory; + gboolean res; + + factory = GDM_LOCAL_DISPLAY_FACTORY (G_OBJECT_CLASS (gdm_local_display_factory_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + res = register_factory (factory); + if (! res) { + g_warning ("Unable to register local display factory with system bus"); + } + + return G_OBJECT (factory); +} + +static void +gdm_local_display_factory_class_init (GdmLocalDisplayFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdmDisplayFactoryClass *factory_class = GDM_DISPLAY_FACTORY_CLASS (klass); + + object_class->get_property = gdm_local_display_factory_get_property; + object_class->set_property = gdm_local_display_factory_set_property; + object_class->finalize = gdm_local_display_factory_finalize; + object_class->constructor = gdm_local_display_factory_constructor; + + factory_class->start = gdm_local_display_factory_start; + factory_class->stop = gdm_local_display_factory_stop; +} + +static void +gdm_local_display_factory_init (GdmLocalDisplayFactory *factory) +{ + factory->used_display_numbers = g_hash_table_new (NULL, NULL); +} + +static void +gdm_local_display_factory_finalize (GObject *object) +{ + GdmLocalDisplayFactory *factory; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_LOCAL_DISPLAY_FACTORY (object)); + + factory = GDM_LOCAL_DISPLAY_FACTORY (object); + + g_return_if_fail (factory != NULL); + + g_clear_object (&factory->connection); + g_clear_object (&factory->skeleton); + + g_hash_table_destroy (factory->used_display_numbers); + + gdm_local_display_factory_stop_monitor (factory); + + G_OBJECT_CLASS (gdm_local_display_factory_parent_class)->finalize (object); +} + +GdmLocalDisplayFactory * +gdm_local_display_factory_new (GdmDisplayStore *store) +{ + if (local_display_factory_object != NULL) { + g_object_ref (local_display_factory_object); + } else { + local_display_factory_object = g_object_new (GDM_TYPE_LOCAL_DISPLAY_FACTORY, + "display-store", store, + NULL); + g_object_add_weak_pointer (local_display_factory_object, + (gpointer *) &local_display_factory_object); + } + + return GDM_LOCAL_DISPLAY_FACTORY (local_display_factory_object); +} diff --git a/daemon/gdm-local-display-factory.h b/daemon/gdm-local-display-factory.h new file mode 100644 index 0000000..49c7140 --- /dev/null +++ b/daemon/gdm-local-display-factory.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_LOCAL_DISPLAY_FACTORY_H +#define __GDM_LOCAL_DISPLAY_FACTORY_H + +#include <glib-object.h> + +#include "gdm-display-factory.h" +#include "gdm-display-store.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_LOCAL_DISPLAY_FACTORY (gdm_local_display_factory_get_type ()) +G_DECLARE_FINAL_TYPE (GdmLocalDisplayFactory, gdm_local_display_factory, GDM, LOCAL_DISPLAY_FACTORY, GdmDisplayFactory) + +typedef enum +{ + GDM_LOCAL_DISPLAY_FACTORY_ERROR_GENERAL +} GdmLocalDisplayFactoryError; + +#define GDM_LOCAL_DISPLAY_FACTORY_ERROR gdm_local_display_factory_error_quark () + +GQuark gdm_local_display_factory_error_quark (void); + +GdmLocalDisplayFactory * gdm_local_display_factory_new (GdmDisplayStore *display_store); + +gboolean gdm_local_display_factory_create_transient_display (GdmLocalDisplayFactory *factory, + char **id, + GError **error); +G_END_DECLS + +#endif /* __GDM_LOCAL_DISPLAY_FACTORY_H */ diff --git a/daemon/gdm-local-display-factory.xml b/daemon/gdm-local-display-factory.xml new file mode 100644 index 0000000..515d991 --- /dev/null +++ b/daemon/gdm-local-display-factory.xml @@ -0,0 +1,8 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/org/gnome/DisplayManager/LocalDisplayFactory"> + <interface name="org.gnome.DisplayManager.LocalDisplayFactory"> + <method name="CreateTransientDisplay"> + <arg name="id" direction="out" type="o"/> + </method> + </interface> +</node> diff --git a/daemon/gdm-local-display.c b/daemon/gdm-local-display.c new file mode 100644 index 0000000..69945d9 --- /dev/null +++ b/daemon/gdm-local-display.c @@ -0,0 +1,166 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <pwd.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-common.h" +#include "gdm-display.h" +#include "gdm-launch-environment.h" +#include "gdm-local-display.h" +#include "gdm-local-display-glue.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +struct _GdmLocalDisplay +{ + GdmDisplay parent; + GdmDBusLocalDisplay *skeleton; +}; + +static void gdm_local_display_class_init (GdmLocalDisplayClass *klass); +static void gdm_local_display_init (GdmLocalDisplay *local_display); + +G_DEFINE_TYPE (GdmLocalDisplay, gdm_local_display, GDM_TYPE_DISPLAY) + +static GObject * +gdm_local_display_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmLocalDisplay *display; + + display = GDM_LOCAL_DISPLAY (G_OBJECT_CLASS (gdm_local_display_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + display->skeleton = GDM_DBUS_LOCAL_DISPLAY (gdm_dbus_local_display_skeleton_new ()); + + g_dbus_object_skeleton_add_interface (gdm_display_get_object_skeleton (GDM_DISPLAY (display)), + G_DBUS_INTERFACE_SKELETON (display->skeleton)); + + return G_OBJECT (display); +} + +static void +gdm_local_display_finalize (GObject *object) +{ + GdmLocalDisplay *display = GDM_LOCAL_DISPLAY (object); + + g_clear_object (&display->skeleton); + + G_OBJECT_CLASS (gdm_local_display_parent_class)->finalize (object); +} + +static gboolean +gdm_local_display_prepare (GdmDisplay *display) +{ + GdmLocalDisplay *self = GDM_LOCAL_DISPLAY (display); + GdmLaunchEnvironment *launch_environment; + char *seat_id; + char *session_class; + char *session_type; + gboolean doing_initial_setup = FALSE; + gboolean failed = FALSE; + + seat_id = NULL; + + g_object_get (self, + "seat-id", &seat_id, + "doing-initial-setup", &doing_initial_setup, + "session-class", &session_class, + "session-type", &session_type, + NULL); + + if (g_strcmp0 (session_class, "greeter") != 0) { + goto out; + } + + g_debug ("doing initial setup? %s", doing_initial_setup? "yes" : "no"); + + if (!doing_initial_setup) { + launch_environment = gdm_create_greeter_launch_environment (NULL, + seat_id, + session_type, + NULL, + TRUE); + } else { + launch_environment = gdm_create_initial_setup_launch_environment (NULL, + seat_id, + session_type, + NULL, + TRUE); + } + + g_object_set (self, "launch-environment", launch_environment, NULL); + g_object_unref (launch_environment); + +out: + g_free (seat_id); + g_free (session_class); + g_free (session_type); + + if (failed) { + return FALSE; + } + return GDM_DISPLAY_CLASS (gdm_local_display_parent_class)->prepare (display); +} + +static void +gdm_local_display_class_init (GdmLocalDisplayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdmDisplayClass *display_class = GDM_DISPLAY_CLASS (klass); + + object_class->constructor = gdm_local_display_constructor; + object_class->finalize = gdm_local_display_finalize; + + display_class->prepare = gdm_local_display_prepare; +} + +static void +gdm_local_display_init (GdmLocalDisplay *local_display) +{ +} + +GdmDisplay * +gdm_local_display_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_LOCAL_DISPLAY, NULL); + + return GDM_DISPLAY (object); +} diff --git a/daemon/gdm-local-display.h b/daemon/gdm-local-display.h new file mode 100644 index 0000000..5bd7f92 --- /dev/null +++ b/daemon/gdm-local-display.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_LOCAL_DISPLAY_H +#define __GDM_LOCAL_DISPLAY_H + +#include <glib-object.h> +#include "gdm-display.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_LOCAL_DISPLAY (gdm_local_display_get_type ()) +G_DECLARE_FINAL_TYPE (GdmLocalDisplay, gdm_local_display, GDM, LOCAL_DISPLAY, GdmDisplay) + +GdmDisplay * gdm_local_display_new (void); + + +G_END_DECLS + +#endif /* __GDM_LOCAL_DISPLAY_H */ diff --git a/daemon/gdm-local-display.xml b/daemon/gdm-local-display.xml new file mode 100644 index 0000000..3d52dd2 --- /dev/null +++ b/daemon/gdm-local-display.xml @@ -0,0 +1,5 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.gnome.DisplayManager.LocalDisplay"> + </interface> +</node> diff --git a/daemon/gdm-manager.c b/daemon/gdm-manager.c new file mode 100644 index 0000000..e828544 --- /dev/null +++ b/daemon/gdm-manager.c @@ -0,0 +1,2841 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include <act/act-user-manager.h> + +#include <systemd/sd-login.h> + +#include "gdm-common.h" + +#include "gdm-dbus-util.h" +#include "gdm-manager.h" +#include "gdm-manager-glue.h" +#include "gdm-display-store.h" +#include "gdm-display-factory.h" +#include "gdm-launch-environment.h" +#include "gdm-local-display.h" +#include "gdm-local-display-factory.h" +#include "gdm-session.h" +#include "gdm-session-record.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" +#include "gdm-xdmcp-display-factory.h" +#include "gdm-xdmcp-chooser-display.h" + +#define GDM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_MANAGER, GdmManagerPrivate)) + +#define GDM_DBUS_PATH "/org/gnome/DisplayManager" +#define GDM_MANAGER_PATH GDM_DBUS_PATH "/Manager" +#define GDM_MANAGER_DISPLAYS_PATH GDM_DBUS_PATH "/Displays" + +#define INITIAL_SETUP_USERNAME "gnome-initial-setup" +#define ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT GDM_RUN_DIR "/gdm.ran-initial-setup" + +typedef struct +{ + GdmManager *manager; + GdmSession *session; + char *service_name; + guint idle_id; +} StartUserSessionOperation; + +struct GdmManagerPrivate +{ + GdmDisplayStore *display_store; + GdmLocalDisplayFactory *local_factory; +#ifdef HAVE_LIBXDMCP + GdmXdmcpDisplayFactory *xdmcp_factory; +#endif + GdmDisplay *automatic_login_display; + GList *user_sessions; + GHashTable *transient_sessions; + GHashTable *open_reauthentication_requests; + gboolean xdmcp_enabled; + + gboolean started; + gboolean show_local_greeter; + + GDBusConnection *connection; + GDBusObjectManagerServer *object_manager; + +#ifdef WITH_PLYMOUTH + guint plymouth_is_running : 1; +#endif + guint did_automatic_login : 1; +}; + +enum { + PROP_0, + PROP_XDMCP_ENABLED, + PROP_SHOW_LOCAL_GREETER +}; + +enum { + DISPLAY_ADDED, + DISPLAY_REMOVED, + LAST_SIGNAL +}; + +typedef enum { + SESSION_RECORD_LOGIN, + SESSION_RECORD_LOGOUT, + SESSION_RECORD_FAILED, +} SessionRecord; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_manager_class_init (GdmManagerClass *klass); +static void gdm_manager_init (GdmManager *manager); +static void gdm_manager_dispose (GObject *object); + +static GdmSession *create_user_session_for_display (GdmManager *manager, + GdmDisplay *display, + uid_t allowed_user); +static void start_user_session (GdmManager *manager, + StartUserSessionOperation *operation); +static void clean_user_session (GdmSession *session); + +static gpointer manager_object = NULL; + +static void manager_interface_init (GdmDBusManagerIface *interface); + +G_DEFINE_TYPE_WITH_CODE (GdmManager, + gdm_manager, + GDM_DBUS_TYPE_MANAGER_SKELETON, + G_IMPLEMENT_INTERFACE (GDM_DBUS_TYPE_MANAGER, + manager_interface_init) + G_ADD_PRIVATE (GdmManager)); + +#ifdef WITH_PLYMOUTH +static gboolean +plymouth_is_running (void) +{ + int status; + gboolean res; + GError *error; + + error = NULL; + res = g_spawn_command_line_sync ("plymouth --ping", + NULL, NULL, &status, &error); + if (! res) { + g_debug ("Could not ping plymouth: %s", error->message); + g_error_free (error); + return FALSE; + } + + return WIFEXITED (status) && WEXITSTATUS (status) == 0; +} + +static void +plymouth_prepare_for_transition (void) +{ + gboolean res; + GError *error; + + error = NULL; + res = g_spawn_command_line_sync ("plymouth deactivate", + NULL, NULL, NULL, &error); + if (! res) { + g_warning ("Could not deactivate plymouth: %s", error->message); + g_error_free (error); + } +} + +static gboolean +plymouth_quit_with_transition (void) +{ + gboolean res; + GError *error; + + error = NULL; + res = g_spawn_command_line_async ("plymouth quit --retain-splash", &error); + if (! res) { + g_warning ("Could not quit plymouth: %s", error->message); + g_error_free (error); + } + + return G_SOURCE_REMOVE; +} + +static void +plymouth_quit_without_transition (void) +{ + gboolean res; + GError *error; + + error = NULL; + res = g_spawn_command_line_async ("plymouth quit", &error); + if (! res) { + g_warning ("Could not quit plymouth: %s", error->message); + g_error_free (error); + } +} +#endif + +static char * +get_session_id_for_pid (pid_t pid, + GError **error) +{ + char *session, *gsession; + int ret; + + session = NULL; + ret = sd_pid_get_session (pid, &session); + if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting session id from systemd: %s", + g_strerror (-ret)); + return NULL; + } + + if (session != NULL) { + gsession = g_strdup (session); + free (session); + + return gsession; + } else { + return NULL; + } +} + +static gboolean +get_uid_for_session_id (const char *session_id, + uid_t *uid, + GError **error) +{ + int ret; + + ret = sd_session_get_uid (session_id, uid); + if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting uid for session id %s from systemd: %s", + session_id, + g_strerror (-ret)); + return FALSE; + } + + return TRUE; +} + +static gboolean +lookup_by_session_id (const char *id, + GdmDisplay *display, + gpointer user_data) +{ + const char *looking_for = user_data; + const char *current; + + current = gdm_display_get_session_id (display); + return g_strcmp0 (current, looking_for) == 0; +} + +static gboolean +is_login_session (GdmManager *self, + const char *session_id, + GError **error) +{ + char *session_class = NULL; + int ret; + + ret = sd_session_get_class (session_id, &session_class); + + if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting class for session id %s from systemd: %s", + session_id, + g_strerror (-ret)); + return FALSE; + } + + if (g_strcmp0 (session_class, "greeter") != 0) { + g_free (session_class); + return FALSE; + } + + g_free (session_class); + return TRUE; +} + +static gboolean +session_unlock (GdmManager *manager, + const char *ssid) +{ + GError *error = NULL; + GVariant *reply; + + g_debug ("Unlocking session %s", ssid); + + reply = g_dbus_connection_call_sync (manager->priv->connection, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "UnlockSession", + g_variant_new ("(s)", ssid), + NULL, /* expected reply */ + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (reply == NULL) { + g_debug ("GdmManager: logind 'UnlockSession' %s raised:\n %s\n\n", + g_dbus_error_get_remote_error (error), error->message); + g_error_free (error); + return FALSE; + } + + g_variant_unref (reply); + + return TRUE; +} + +static GdmSession * +find_session_for_user_on_seat (GdmManager *manager, + const char *username, + const char *seat_id, + GdmSession *dont_count_session) +{ + GList *node; + + for (node = manager->priv->user_sessions; node != NULL; node = node->next) { + GdmSession *candidate_session = node->data; + const char *candidate_username, *candidate_seat_id, *candidate_session_id; + + candidate_session_id = gdm_session_get_session_id (candidate_session); + + if (candidate_session == dont_count_session) { + g_debug ("GdmSession: Ignoring session %s as requested", + candidate_session_id); + continue; + } + + if (!gdm_session_is_running (candidate_session)) { + g_debug ("GdmSession: Ignoring session %s as it isn't running", + candidate_session_id); + continue; + } + + candidate_username = gdm_session_get_username (candidate_session); + candidate_seat_id = gdm_session_get_display_seat_id (candidate_session); + + g_debug ("GdmManager: Considering session %s on seat %s belonging to user %s", + candidate_session_id, + candidate_seat_id, + candidate_username); + + if (g_strcmp0 (candidate_username, username) == 0 && + g_strcmp0 (candidate_seat_id, seat_id) == 0) { + g_debug ("GdmManager: yes, found session %s", candidate_session_id); + return candidate_session; + } + + g_debug ("GdmManager: no, will not use session %s", candidate_session_id); + } + + g_debug ("GdmManager: no matching sessions found"); + return NULL; +} + +static gboolean +is_remote_session (GdmManager *self, + const char *session_id, + GError **error) +{ + char *seat; + int ret; + gboolean is_remote; + + /* FIXME: The next release of logind is going to have explicit api for + * checking remoteness. + */ + seat = NULL; + ret = sd_session_get_seat (session_id, &seat); + + if (ret < 0 && ret != -ENXIO) { + g_debug ("GdmManager: Error while retrieving seat for session %s: %s", + session_id, strerror (-ret)); + } + + if (seat != NULL) { + is_remote = FALSE; + free (seat); + } else { + is_remote = TRUE; + } + + return is_remote; +} + +static char * +get_seat_id_for_session_id (const char *session_id, + GError **error) +{ + int ret; + char *seat, *out_seat; + + seat = NULL; + ret = sd_session_get_seat (session_id, &seat); + + if (ret == -ENXIO) { + out_seat = NULL; + } else if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting uid for session id %s from systemd: %s", + session_id, + g_strerror (-ret)); + out_seat = NULL; + } else { + out_seat = g_strdup (seat); + free (seat); + } + + return out_seat; +} + +static char * +get_tty_for_session_id (const char *session_id, + GError **error) +{ + int ret; + char *tty, *out_tty; + + ret = sd_session_get_tty (session_id, &tty); + + if (ret == -ENXIO) { + out_tty = NULL; + } else if (ret < 0) { + g_set_error (error, + GDM_DISPLAY_ERROR, + GDM_DISPLAY_ERROR_GETTING_SESSION_INFO, + "Error getting tty for session id %s from systemd: %s", + session_id, + g_strerror (-ret)); + out_tty = NULL; + } else { + out_tty = g_strdup (tty); + free (tty); + } + + return out_tty; +} + +static void +get_display_and_details_for_bus_sender (GdmManager *self, + GDBusConnection *connection, + const char *sender, + GdmDisplay **out_display, + char **out_seat_id, + char **out_session_id, + char **out_tty, + GPid *out_pid, + uid_t *out_uid, + gboolean *out_is_login_screen, + gboolean *out_is_remote) +{ + GdmDisplay *display = NULL; + char *session_id = NULL; + GError *error = NULL; + int ret; + GPid pid; + uid_t caller_uid, session_uid; + + ret = gdm_dbus_get_pid_for_name (sender, &pid, &error); + + if (!ret) { + g_debug ("GdmManager: Error while retrieving pid for sender: %s", + error->message); + g_error_free (error); + goto out; + } + + if (out_pid != NULL) { + *out_pid = pid; + } + + ret = gdm_dbus_get_uid_for_name (sender, &caller_uid, &error); + + if (!ret) { + g_debug ("GdmManager: Error while retrieving uid for sender: %s", + error->message); + g_error_free (error); + goto out; + } + + ret = gdm_find_display_session (pid, caller_uid, &session_id, &error); + + if (!ret) { + g_debug ("GdmManager: Unable to find display session for uid %d: %s", + (int) caller_uid, + error->message); + g_error_free (error); + goto out; + } + + if (out_session_id != NULL) { + *out_session_id = g_strdup (session_id); + } + + if (out_is_login_screen != NULL) { + *out_is_login_screen = is_login_session (self, session_id, &error); + + if (error != NULL) { + g_debug ("GdmManager: Error while checking if sender is login screen: %s", + error->message); + g_error_free (error); + goto out; + } + } + + if (!get_uid_for_session_id (session_id, &session_uid, &error)) { + g_debug ("GdmManager: Error while retrieving uid for session: %s", + error->message); + g_error_free (error); + goto out; + } + + if (out_uid != NULL) { + *out_uid = caller_uid; + } + + if (caller_uid != session_uid) { + g_debug ("GdmManager: uid for sender and uid for session don't match"); + goto out; + } + + if (out_seat_id != NULL) { + *out_seat_id = get_seat_id_for_session_id (session_id, &error); + + if (error != NULL) { + g_debug ("GdmManager: Error while retrieving seat id for session: %s", + error->message); + g_clear_error (&error); + } + } + + if (out_is_remote != NULL) { + *out_is_remote = is_remote_session (self, session_id, &error); + + if (error != NULL) { + g_debug ("GdmManager: Error while retrieving remoteness for session: %s", + error->message); + g_clear_error (&error); + } + } + + if (out_tty != NULL) { + *out_tty = get_tty_for_session_id (session_id, &error); + + if (error != NULL) { + g_debug ("GdmManager: Error while retrieving tty for session: %s", + error->message); + g_clear_error (&error); + } + } + + display = gdm_display_store_find (self->priv->display_store, + lookup_by_session_id, + (gpointer) session_id); + +out: + if (out_display != NULL) { + *out_display = display; + } + + g_free (session_id); +} + +static gboolean +switch_to_compatible_user_session (GdmManager *manager, + GdmSession *session, + gboolean fail_if_already_switched) +{ + gboolean res; + gboolean ret; + const char *username; + const char *seat_id; + const char *ssid_to_activate; + GdmSession *existing_session; + + ret = FALSE; + + username = gdm_session_get_username (session); + seat_id = gdm_session_get_display_seat_id (session); + + if (!fail_if_already_switched) { + session = NULL; + } + + existing_session = find_session_for_user_on_seat (manager, username, seat_id, session); + + if (existing_session != NULL) { + ssid_to_activate = gdm_session_get_session_id (existing_session); + if (seat_id != NULL) { + res = gdm_activate_session_by_id (manager->priv->connection, seat_id, ssid_to_activate); + if (! res) { + g_debug ("GdmManager: unable to activate session: %s", ssid_to_activate); + goto out; + } + } + + res = session_unlock (manager, ssid_to_activate); + if (!res) { + /* this isn't fatal */ + g_debug ("GdmManager: unable to unlock session: %s", ssid_to_activate); + } + } else { + goto out; + } + + ret = TRUE; + + out: + return ret; +} + +static GdmDisplay * +get_display_for_user_session (GdmSession *session) +{ + return g_object_get_data (G_OBJECT (session), "gdm-display"); +} + +static GdmSession * +get_user_session_for_display (GdmDisplay *display) +{ + if (display == NULL) { + return NULL; + } + + return g_object_get_data (G_OBJECT (display), "gdm-user-session"); +} + +static gboolean +add_session_record (GdmManager *manager, + GdmSession *session, + GPid pid, + SessionRecord record) +{ + const char *username; + char *display_name, *hostname, *display_device; + gboolean recorded = FALSE; + + display_name = NULL; + username = NULL; + hostname = NULL; + display_device = NULL; + + username = gdm_session_get_username (session); + + if (username == NULL) { + goto out; + } + + g_object_get (G_OBJECT (session), + "display-name", &display_name, + "display-hostname", &hostname, + "display-device", &display_device, + NULL); + + if (display_name == NULL && display_device == NULL) { + goto out; + } + + switch (record) { + case SESSION_RECORD_LOGIN: + gdm_session_record_login (pid, + username, + hostname, + display_name, + display_device); + break; + case SESSION_RECORD_LOGOUT: + gdm_session_record_logout (pid, + username, + hostname, + display_name, + display_device); + break; + case SESSION_RECORD_FAILED: + gdm_session_record_failed (pid, + username, + hostname, + display_name, + display_device); + break; + } + + recorded = TRUE; +out: + g_free (display_name); + g_free (hostname); + g_free (display_device); + + return recorded; +} + +static GdmSession * +find_user_session_for_display (GdmManager *self, + GdmDisplay *display) +{ + + GList *node = self->priv->user_sessions; + + while (node != NULL) { + GdmSession *session = node->data; + GdmDisplay *candidate_display; + GList *next_node = node->next; + + candidate_display = get_display_for_user_session (session); + + if (candidate_display == display) + return session; + + node = next_node; + } + + return NULL; +} + +static gboolean +gdm_manager_handle_register_display (GdmDBusManager *manager, + GDBusMethodInvocation *invocation, + GVariant *details) +{ + GdmManager *self = GDM_MANAGER (manager); + const char *sender; + GDBusConnection *connection; + GdmDisplay *display = NULL; + GdmSession *session; + GVariantIter iter; + char *key = NULL; + char *value = NULL; + char *x11_display_name = NULL; + char *tty = NULL; + + g_debug ("GdmManager: trying to register new display"); + + sender = g_dbus_method_invocation_get_sender (invocation); + connection = g_dbus_method_invocation_get_connection (invocation); + get_display_and_details_for_bus_sender (self, connection, sender, &display, NULL, NULL, &tty, NULL, NULL, NULL, NULL); + + if (display == NULL) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("No display available")); + + return TRUE; + } + + g_variant_iter_init (&iter, details); + while (g_variant_iter_loop (&iter, "{&s&s}", &key, &value)) { + if (g_strcmp0 (key, "x11-display-name") == 0) { + x11_display_name = g_strdup (value); + break; + } + } + + session = find_user_session_for_display (self, display); + + if (session != NULL) { + GPid pid; + + if (x11_display_name != NULL) { + g_object_set (G_OBJECT (session), "display-name", x11_display_name, NULL); + g_object_set (G_OBJECT (display), "x11-display-name", x11_display_name, NULL); + } + + /* FIXME: this should happen in gdm-session.c when the session is opened + */ + if (tty != NULL) + g_object_set (G_OBJECT (session), "display-device", tty, NULL); + + pid = gdm_session_get_pid (session); + + if (pid > 0) { + add_session_record (self, session, pid, SESSION_RECORD_LOGIN); + } + } + + g_object_set (G_OBJECT (display), "status", GDM_DISPLAY_MANAGED, NULL); + + gdm_dbus_manager_complete_register_display (GDM_DBUS_MANAGER (manager), + invocation); + + g_clear_pointer (&x11_display_name, g_free); + g_clear_pointer (&tty, g_free); + return TRUE; +} + +static gboolean +gdm_manager_handle_register_session (GdmDBusManager *manager, + GDBusMethodInvocation *invocation, + GVariant *details) +{ + GdmManager *self = GDM_MANAGER (manager); + GdmDisplay *display = NULL; + const char *sender; + GDBusConnection *connection; + + sender = g_dbus_method_invocation_get_sender (invocation); + connection = g_dbus_method_invocation_get_connection (invocation); + + get_display_and_details_for_bus_sender (self, connection, sender, &display, + NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + g_debug ("GdmManager: trying to register new session on display %p", display); + + if (display != NULL) + g_object_set (G_OBJECT (display), "session-registered", TRUE, NULL); + else + g_debug ("GdmManager: No display, not registering"); + + gdm_dbus_manager_complete_register_session (GDM_DBUS_MANAGER (manager), + invocation); + + return TRUE; +} + +static gboolean +gdm_manager_handle_open_session (GdmDBusManager *manager, + GDBusMethodInvocation *invocation) +{ + GdmManager *self = GDM_MANAGER (manager); + const char *sender; + GDBusConnection *connection; + GdmDisplay *display = NULL; + GdmSession *session = NULL; + const char *address; + GPid pid = 0; + uid_t uid = (uid_t) -1; + uid_t allowed_user; + + g_debug ("GdmManager: trying to open new session"); + + sender = g_dbus_method_invocation_get_sender (invocation); + connection = g_dbus_method_invocation_get_connection (invocation); + get_display_and_details_for_bus_sender (self, connection, sender, &display, NULL, NULL, NULL, &pid, &uid, NULL, NULL); + + if (display == NULL) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("No session available")); + + return TRUE; + } + +#ifdef HAVE_LIBXDMCP + if (GDM_IS_XDMCP_CHOOSER_DISPLAY (display)) { + GdmLaunchEnvironment *launch_environment; + + g_object_get (display, "launch-environment", &launch_environment, NULL); + + if (launch_environment != NULL) { + session = gdm_launch_environment_get_session (launch_environment); + } + + if (session == NULL) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("Chooser session unavailable")); + return TRUE; + } + } +#endif + if (session == NULL) { + session = get_user_session_for_display (display); + g_debug ("GdmSession: Considering session %s for username %s", + gdm_session_get_session_id (session), + gdm_session_get_username (session)); + + if (gdm_session_is_running (session)) { + g_debug ("GdmSession: the session is running, and therefore can't be used"); + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("Can only be called before user is logged in")); + return TRUE; + } + } + + allowed_user = gdm_session_get_allowed_user (session); + + if (uid != allowed_user) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("Caller not GDM")); + return TRUE; + } + + address = gdm_session_get_server_address (session); + + if (address == NULL) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("Unable to open private communication channel")); + return TRUE; + } + + gdm_dbus_manager_complete_open_session (GDM_DBUS_MANAGER (manager), + invocation, + address); + return TRUE; +} + +static void +close_transient_session (GdmManager *self, + GdmSession *session) +{ + GPid pid; + pid = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (session), "caller-pid")); + gdm_session_close (session); + g_hash_table_remove (self->priv->transient_sessions, + GUINT_TO_POINTER (pid)); +} + +static void +on_reauthentication_client_connected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *self) +{ + g_debug ("GdmManager: client connected to reauthentication server"); +} + +static void +on_reauthentication_client_disconnected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *self) +{ + g_debug ("GdmManger: client disconnected from reauthentication server"); + close_transient_session (self, session); +} + +static void +on_reauthentication_client_rejected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *self) +{ + GPid pid; + + g_debug ("GdmManger: client with pid %ld rejected from reauthentication server", (long) pid_of_client); + + if (gdm_session_client_is_connected (session)) { + /* we already have a client connected, ignore this rejected one */ + return; + } + + pid = (GPid) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (session), "caller-pid")); + + if (pid != pid_of_client) { + const char *session_id; + char *client_session_id; + + /* rejected client isn't the process that started the + * transient reauthentication session. If it's not even from the + * same audit session, ignore it since it doesn't "own" the + * reauthentication session + */ + client_session_id = get_session_id_for_pid (pid_of_client, + NULL); + session_id = g_object_get_data (G_OBJECT (session), "caller-session-id"); + + if (g_strcmp0 (session_id, client_session_id) != 0) { + return; + } + } + + /* client was rejected, so clean up its session object + */ + close_transient_session (self, session); +} + +static void +on_reauthentication_cancelled (GdmSession *session, + GdmManager *self) +{ + g_debug ("GdmManager: client cancelled reauthentication request"); + close_transient_session (self, session); +} + +static void +on_reauthentication_conversation_started (GdmSession *session, + const char *service_name, + GdmManager *self) +{ + g_debug ("GdmManager: reauthentication service '%s' started", + service_name); +} + +static void +on_reauthentication_conversation_stopped (GdmSession *session, + const char *service_name, + GdmManager *self) +{ + g_debug ("GdmManager: reauthentication service '%s' stopped", + service_name); +} + +static void +on_reauthentication_verification_complete (GdmSession *session, + const char *service_name, + GdmManager *self) +{ + const char *session_id; + session_id = g_object_get_data (G_OBJECT (session), "caller-session-id"); + g_debug ("GdmManager: reauthenticated user in unmanaged session '%s' with service '%s'", + session_id, service_name); + session_unlock (self, session_id); + close_transient_session (self, session); +} + +static char * +open_temporary_reauthentication_channel (GdmManager *self, + char *seat_id, + char *session_id, + GPid pid, + uid_t uid, + gboolean is_remote) +{ + GdmSession *session; + char **environment; + const char *display, *auth_file; + const char *address; + + /* Note we're just using a minimal environment here rather than the + * session's environment because the caller is unprivileged and the + * associated worker will be privileged */ + environment = g_get_environ (); + display = ""; + auth_file = "/dev/null"; + + session = gdm_session_new (GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE, + uid, + display, + NULL, + NULL, + seat_id, + auth_file, + is_remote == FALSE, + (const char * const *) + environment); + g_strfreev (environment); + + g_debug ("GdmSession: Created session for temporary reauthentication channel for user %d (seat %s)", + (int) uid, + seat_id); + + g_object_set_data_full (G_OBJECT (session), + "caller-session-id", + g_strdup (session_id), + (GDestroyNotify) + g_free); + g_object_set_data (G_OBJECT (session), + "caller-pid", + GUINT_TO_POINTER (pid)); + g_hash_table_insert (self->priv->transient_sessions, + GINT_TO_POINTER (pid), + session); + + g_signal_connect (session, + "client-connected", + G_CALLBACK (on_reauthentication_client_connected), + self); + g_signal_connect (session, + "client-disconnected", + G_CALLBACK (on_reauthentication_client_disconnected), + self); + g_signal_connect (session, + "client-rejected", + G_CALLBACK (on_reauthentication_client_rejected), + self); + g_signal_connect (session, + "cancelled", + G_CALLBACK (on_reauthentication_cancelled), + self); + g_signal_connect (session, + "conversation-started", + G_CALLBACK (on_reauthentication_conversation_started), + self); + g_signal_connect (session, + "conversation-stopped", + G_CALLBACK (on_reauthentication_conversation_stopped), + self); + g_signal_connect (session, + "verification-complete", + G_CALLBACK (on_reauthentication_verification_complete), + self); + + address = gdm_session_get_server_address (session); + + return g_strdup (address); +} + +static gboolean +gdm_manager_handle_open_reauthentication_channel (GdmDBusManager *manager, + GDBusMethodInvocation *invocation, + const char *username) +{ + GdmManager *self = GDM_MANAGER (manager); + const char *sender; + GdmDisplay *display = NULL; + GdmSession *session; + GDBusConnection *connection; + char *seat_id = NULL; + char *session_id = NULL; + GPid pid = 0; + uid_t uid = (uid_t) -1; + gboolean is_login_screen = FALSE; + gboolean is_remote = FALSE; + + g_debug ("GdmManager: trying to open reauthentication channel for user %s", username); + + sender = g_dbus_method_invocation_get_sender (invocation); + connection = g_dbus_method_invocation_get_connection (invocation); + get_display_and_details_for_bus_sender (self, connection, sender, &display, &seat_id, &session_id, NULL, &pid, &uid, &is_login_screen, &is_remote); + + if (session_id == NULL || pid == 0 || uid == (uid_t) -1) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + _("No session available")); + + return TRUE; + } + + if (is_login_screen) { + g_debug ("GdmManager: looking for login screen session for user %s on seat %s", username, seat_id); + session = find_session_for_user_on_seat (self, + username, + seat_id, + NULL); + } else { + g_debug ("GdmManager: looking for user session on display"); + session = get_user_session_for_display (display); + } + + if (session != NULL && gdm_session_is_running (session)) { + gdm_session_start_reauthentication (session, pid, uid); + g_hash_table_insert (self->priv->open_reauthentication_requests, + GINT_TO_POINTER (pid), + invocation); + } else if (is_login_screen) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Login screen only allowed to open reauthentication channels for running sessions"); + return TRUE; + } else { + char *address; + address = open_temporary_reauthentication_channel (self, + seat_id, + session_id, + pid, + uid, + is_remote); + gdm_dbus_manager_complete_open_reauthentication_channel (GDM_DBUS_MANAGER (manager), + invocation, + address); + g_free (address); + } + + return TRUE; +} + +static void +manager_interface_init (GdmDBusManagerIface *interface) +{ + interface->handle_register_display = gdm_manager_handle_register_display; + interface->handle_register_session = gdm_manager_handle_register_session; + interface->handle_open_session = gdm_manager_handle_open_session; + interface->handle_open_reauthentication_channel = gdm_manager_handle_open_reauthentication_channel; +} + +static gboolean +display_is_on_seat0 (GdmDisplay *display) +{ + gboolean is_on_seat0 = TRUE; + char *seat_id = NULL; + + g_object_get (G_OBJECT (display), "seat-id", &seat_id, NULL); + + if (g_strcmp0 (seat_id, "seat0") != 0) { + is_on_seat0 = FALSE; + } + + g_free (seat_id); + + return is_on_seat0; +} + +static gboolean +get_timed_login_details (GdmManager *manager, + char **usernamep, + int *delayp) +{ + gboolean res; + gboolean enabled; + + int delay; + char *username = NULL; + + enabled = FALSE; + username = NULL; + delay = 0; + + res = gdm_settings_direct_get_boolean (GDM_KEY_TIMED_LOGIN_ENABLE, &enabled); + if (res && ! enabled) { + goto out; + } + + res = gdm_settings_direct_get_string (GDM_KEY_TIMED_LOGIN_USER, &username); + if (res && (username == NULL || username[0] == '\0')) { + g_clear_pointer (&username, g_free); + goto out; + } + + delay = 0; + res = gdm_settings_direct_get_int (GDM_KEY_TIMED_LOGIN_DELAY, &delay); + + if (res && delay <= 0) { + /* we don't allow the timed login to have a zero delay */ + delay = 10; + } + + out: + if (enabled) { + g_debug ("GdmDisplay: Got timed login details for display: %d %s %d", + enabled, + username, + delay); + } else { + g_debug ("GdmDisplay: Got timed login details for display: 0"); + } + + if (usernamep != NULL) { + *usernamep = username; + } else { + g_free (username); + } + if (delayp != NULL) { + *delayp = delay; + } + + return enabled; +} + +static gboolean +get_automatic_login_details (GdmManager *manager, + char **usernamep) +{ + gboolean res; + gboolean enabled; + char *username = NULL; + + enabled = FALSE; + username = NULL; + + res = gdm_settings_direct_get_boolean (GDM_KEY_AUTO_LOGIN_ENABLE, &enabled); + if (res && enabled) { + res = gdm_settings_direct_get_string (GDM_KEY_AUTO_LOGIN_USER, &username); + } + + if (enabled && res && username != NULL && username[0] != '\0') { + goto out; + } + + g_free (username); + username = NULL; + enabled = FALSE; + + out: + if (enabled) { + g_debug ("GdmDisplay: Got automatic login details for display: %d %s", + enabled, + username); + } else { + g_debug ("GdmDisplay: Got automatic login details for display: 0"); + } + + if (usernamep != NULL) { + *usernamep = username; + } else { + g_free (username); + } + + return enabled; +} + +static const char * +get_username_for_greeter_display (GdmManager *manager, + GdmDisplay *display) +{ + gboolean doing_initial_setup = FALSE; + + g_object_get (G_OBJECT (display), + "doing-initial-setup", &doing_initial_setup, + NULL); + + if (doing_initial_setup) { + return INITIAL_SETUP_USERNAME; + } else { + return GDM_USERNAME; + } +} + +static void +set_up_automatic_login_session (GdmManager *manager, + GdmDisplay *display) +{ + GdmSession *session; + char *display_session_type = NULL; + + /* 0 is root user; since the daemon talks to the session object + * directly, itself, for automatic login + */ + session = create_user_session_for_display (manager, display, 0); + + g_object_get (G_OBJECT (display), + "session-type", &display_session_type, + NULL); + + g_object_set (G_OBJECT (session), + "display-is-initial", FALSE, + NULL); + + g_debug ("GdmManager: Starting automatic login conversation"); + gdm_session_start_conversation (session, "gdm-autologin"); +} + +static void +set_up_chooser_session (GdmManager *manager, + GdmDisplay *display) +{ + const char *allowed_user; + struct passwd *passwd_entry; + + allowed_user = get_username_for_greeter_display (manager, display); + + if (!gdm_get_pwent_for_name (allowed_user, &passwd_entry)) { + g_warning ("GdmManager: couldn't look up username %s", + allowed_user); + gdm_display_unmanage (display); + gdm_display_finish (display); + return; + } + + gdm_display_start_greeter_session (display); +} + +static void +set_up_greeter_session (GdmManager *manager, + GdmDisplay *display) +{ + const char *allowed_user; + struct passwd *passwd_entry; + + allowed_user = get_username_for_greeter_display (manager, display); + + if (!gdm_get_pwent_for_name (allowed_user, &passwd_entry)) { + g_warning ("GdmManager: couldn't look up username %s", + allowed_user); + gdm_display_unmanage (display); + gdm_display_finish (display); + return; + } + + create_user_session_for_display (manager, display, passwd_entry->pw_uid); + gdm_display_start_greeter_session (display); +} + +static void +set_up_automatic_login_session_if_user_exists (GdmManager *manager, + GdmDisplay *display, + ActUser *user) +{ + if (act_user_is_nonexistent (user)) + set_up_greeter_session (manager, display); + else + set_up_automatic_login_session (manager, display); +} + +typedef struct { + GdmManager *manager; + GdmDisplay *display; + char *username; +} UsernameLookupOperation; + +static void +destroy_username_lookup_operation (UsernameLookupOperation *operation) +{ + g_object_unref (operation->manager); + g_object_unref (operation->display); + g_free (operation->username); + g_free (operation); +} + +static void +on_user_is_loaded_changed (ActUser *user, + GParamSpec *pspec, + UsernameLookupOperation *operation) +{ + if (act_user_is_loaded (user)) { + set_up_automatic_login_session_if_user_exists (operation->manager, operation->display, user); + g_signal_handlers_disconnect_by_func (G_OBJECT (user), + G_CALLBACK (on_user_is_loaded_changed), + operation); + destroy_username_lookup_operation (operation); + } +} + +static void +set_up_session (GdmManager *manager, + GdmDisplay *display) +{ + ActUserManager *user_manager; + ActUser *user; + gboolean loaded; + gboolean seat_can_autologin = FALSE, seat_did_autologin = FALSE; + gboolean autologin_enabled = FALSE; + g_autofree char *seat_id = NULL; + char *username = NULL; + + g_object_get (G_OBJECT (display), "seat-id", &seat_id, NULL); + + if (g_strcmp0 (seat_id, "seat0") == 0) + seat_can_autologin = TRUE; + + if (manager->priv->did_automatic_login || manager->priv->automatic_login_display != NULL) + seat_did_autologin = TRUE; + + if (seat_can_autologin && !seat_did_autologin) + autologin_enabled = get_automatic_login_details (manager, &username); + + if (!autologin_enabled) { + g_free (username); + +#ifdef HAVE_LIBXDMCP + if (GDM_IS_XDMCP_CHOOSER_DISPLAY (display)) { + set_up_chooser_session (manager, display); + return; + } +#endif + + set_up_greeter_session (manager, display); + return; + } + + /* Check whether the user really exists before committing to autologin. */ + user_manager = act_user_manager_get_default (); + user = act_user_manager_get_user (user_manager, username); + g_object_get (user_manager, "is-loaded", &loaded, NULL); + + if (loaded) { + set_up_automatic_login_session_if_user_exists (manager, display, user); + } else { + UsernameLookupOperation *operation; + + operation = g_new (UsernameLookupOperation, 1); + operation->manager = g_object_ref (manager); + operation->display = g_object_ref (display); + operation->username = username; + + g_signal_connect (user, + "notify::is-loaded", + G_CALLBACK (on_user_is_loaded_changed), + operation); + } +} + +static void +on_display_status_changed (GdmDisplay *display, + GParamSpec *arg1, + GdmManager *manager) +{ + int status; + int display_number = -1; + char *session_type = NULL; + gboolean doing_initial_setup = FALSE; +#ifdef WITH_PLYMOUTH + gboolean display_is_local = FALSE; + gboolean quit_plymouth = FALSE; + + g_object_get (display, + "is-local", &display_is_local, + NULL); + quit_plymouth = display_is_local && manager->priv->plymouth_is_running; +#endif + + g_object_get (display, + "x11-display-number", &display_number, + "session-type", &session_type, + "doing-initial-setup", &doing_initial_setup, + NULL); + + status = gdm_display_get_status (display); + + switch (status) { + case GDM_DISPLAY_PREPARED: + case GDM_DISPLAY_MANAGED: + if ((display_number == -1 && status == GDM_DISPLAY_PREPARED) || + (display_number != -1 && status == GDM_DISPLAY_MANAGED)) { + char *session_class; + + g_object_get (display, + "session-class", &session_class, + NULL); + if (g_strcmp0 (session_class, "greeter") == 0) + set_up_session (manager, display); + g_free (session_class); + } + break; + case GDM_DISPLAY_FAILED: + case GDM_DISPLAY_UNMANAGED: + case GDM_DISPLAY_FINISHED: +#ifdef WITH_PLYMOUTH + if (quit_plymouth) { + plymouth_quit_without_transition (); + manager->priv->plymouth_is_running = FALSE; + } +#endif + + if (display == manager->priv->automatic_login_display) { + g_clear_weak_pointer (&manager->priv->automatic_login_display); + + manager->priv->did_automatic_login = TRUE; + +#ifdef ENABLE_WAYLAND_SUPPORT + if (g_strcmp0 (session_type, "wayland") != 0 && status == GDM_DISPLAY_FAILED) { + /* we're going to fall back to X11, so try to autologin again + */ + manager->priv->did_automatic_login = FALSE; + } +#endif + } + break; + default: + break; + } + +} + +static void +on_display_removed (GdmDisplayStore *display_store, + GdmDisplay *display, + GdmManager *manager) +{ + char *id; + + gdm_display_get_id (display, &id, NULL); + g_dbus_object_manager_server_unexport (manager->priv->object_manager, id); + g_free (id); + + g_signal_handlers_disconnect_by_func (display, G_CALLBACK (on_display_status_changed), manager); + + g_signal_emit (manager, signals[DISPLAY_REMOVED], 0, display); +} + +static void +destroy_start_user_session_operation (StartUserSessionOperation *operation) +{ + g_object_set_data (G_OBJECT (operation->session), + "start-user-session-operation", + NULL); + g_object_unref (operation->session); + g_free (operation->service_name); + g_slice_free (StartUserSessionOperation, operation); +} + +static void +start_user_session (GdmManager *manager, + StartUserSessionOperation *operation) +{ + GdmDisplay *display; + + display = get_display_for_user_session (operation->session); + + if (display != NULL) { + char *auth_file; + const char *username; + gboolean is_connected = FALSE; + + g_object_get (G_OBJECT (display), "is-connected", &is_connected, NULL); + + if (is_connected) { + auth_file = NULL; + username = gdm_session_get_username (operation->session); + gdm_display_add_user_authorization (display, + username, + &auth_file, + NULL); + + g_assert (auth_file != NULL); + + g_object_set (operation->session, + "user-x11-authority-file", auth_file, + NULL); + + g_free (auth_file); + } + } + + gdm_session_start_session (operation->session, + operation->service_name); + + destroy_start_user_session_operation (operation); +} + +static void +create_display_for_user_session (GdmManager *self, + GdmSession *session, + const char *session_id) +{ + GdmDisplay *display; + /* at the moment we only create GdmLocalDisplay objects on seat0 */ + const char *seat_id = "seat0"; + + display = gdm_local_display_new (); + + g_object_set (G_OBJECT (display), + "session-class", "user", + "seat-id", seat_id, + "session-id", session_id, + NULL); + gdm_display_store_add (self->priv->display_store, + display); + g_object_set_data (G_OBJECT (session), "gdm-display", display); + g_object_set_data_full (G_OBJECT (display), + "gdm-user-session", + g_object_ref (session), + (GDestroyNotify) + clean_user_session); +} + +static gboolean +chown_file (GFile *file, + uid_t uid, + gid_t gid, + GError **error) +{ + if (!g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_UID, uid, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, error)) { + return FALSE; + } + if (!g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_GID, gid, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, error)) { + return FALSE; + } + return TRUE; +} + +static gboolean +chown_recursively (GFile *dir, + uid_t uid, + gid_t gid, + GError **error) +{ + GFile *file = NULL; + GFileInfo *info = NULL; + GFileEnumerator *enumerator = NULL; + gboolean retval = FALSE; + + if (chown_file (dir, uid, gid, error) == FALSE) { + goto out; + } + + enumerator = g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_TYPE"," + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, error); + if (!enumerator) { + goto out; + } + + while ((info = g_file_enumerator_next_file (enumerator, NULL, error)) != NULL) { + file = g_file_get_child (dir, g_file_info_get_name (info)); + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + if (chown_recursively (file, uid, gid, error) == FALSE) { + goto out; + } + } else if (chown_file (file, uid, gid, error) == FALSE) { + goto out; + } + + g_clear_object (&file); + g_clear_object (&info); + } + + if (*error) { + goto out; + } + + retval = TRUE; +out: + g_clear_object (&file); + g_clear_object (&info); + g_clear_object (&enumerator); + + return retval; +} + +static void +chown_initial_setup_home_dir (void) +{ + GFile *dir; + GError *error; + char *gis_dir_path; + char *gis_uid_path; + char *gis_uid_contents; + struct passwd *pwe; + uid_t uid; + + if (!gdm_get_pwent_for_name (INITIAL_SETUP_USERNAME, &pwe)) { + g_warning ("Unknown user %s", INITIAL_SETUP_USERNAME); + return; + } + + gis_dir_path = g_strdup (pwe->pw_dir); + + gis_uid_path = g_build_filename (gis_dir_path, + "gnome-initial-setup-uid", + NULL); + if (!g_file_get_contents (gis_uid_path, &gis_uid_contents, NULL, NULL)) { + g_warning ("Unable to read %s", gis_uid_path); + goto out; + } + + uid = (uid_t) atoi (gis_uid_contents); + pwe = getpwuid (uid); + if (uid == 0 || pwe == NULL) { + g_warning ("UID '%s' in %s is not valid", gis_uid_contents, gis_uid_path); + goto out; + } + + error = NULL; + dir = g_file_new_for_path (gis_dir_path); + if (!chown_recursively (dir, pwe->pw_uid, pwe->pw_gid, &error)) { + g_warning ("Failed to change ownership for %s: %s", gis_dir_path, error->message); + g_error_free (error); + } + g_object_unref (dir); +out: + g_free (gis_uid_contents); + g_free (gis_uid_path); + g_free (gis_dir_path); +} + +static gboolean +on_start_user_session (StartUserSessionOperation *operation) +{ + GdmManager *self = operation->manager; + gboolean migrated; + gboolean fail_if_already_switched = TRUE; + gboolean doing_initial_setup = FALSE; + GdmDisplay *display; + const char *session_id; + + g_debug ("GdmManager: start or jump to session"); + + /* If there's already a session running, jump to it. + * If the only session running is the one we just opened, + * start a session on it. + */ + migrated = switch_to_compatible_user_session (operation->manager, operation->session, fail_if_already_switched); + + g_debug ("GdmManager: migrated: %d", migrated); + if (migrated) { + /* We don't stop the manager here because + when Xorg exits it switches to the VT it was + started from. That interferes with fast + user switching. */ + gdm_session_reset (operation->session); + destroy_start_user_session_operation (operation); + goto out; + } + + display = get_display_for_user_session (operation->session); + + g_object_get (G_OBJECT (display), + "doing-initial-setup", &doing_initial_setup, + NULL); + + if (doing_initial_setup) + chown_initial_setup_home_dir (); + + session_id = gdm_session_get_conversation_session_id (operation->session, + operation->service_name); + + if (gdm_session_get_display_mode (operation->session) == GDM_SESSION_DISPLAY_MODE_REUSE_VT) { + /* In this case, the greeter's display is morphing into + * the user session display. Kill the greeter on this session + * and let the user session follow the same display. */ + gdm_display_stop_greeter_session (display); + g_object_set (G_OBJECT (display), + "session-class", "user", + "session-id", session_id, + NULL); + } else { + uid_t allowed_uid; + + g_object_ref (display); + if (doing_initial_setup) { + g_autoptr(GError) error = NULL; + + g_debug ("GdmManager: closing down initial setup display in background"); + g_object_set (G_OBJECT (display), "status", GDM_DISPLAY_WAITING_TO_FINISH, NULL); + + if (!g_file_set_contents (ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT, + "1", + 1, + &error)) { + g_warning ("GdmDisplay: Could not write initial-setup-done marker to %s: %s", + ALREADY_RAN_INITIAL_SETUP_ON_THIS_BOOT, + error->message); + g_clear_error (&error); + } + } else { + g_debug ("GdmManager: session has its display server, reusing our server for another login screen"); + } + + /* The user session is going to follow the session worker + * into the new display. Untie it from this display and + * create a new session for a future user login. */ + allowed_uid = gdm_session_get_allowed_user (operation->session); + g_object_set_data (G_OBJECT (display), "gdm-user-session", NULL); + g_object_set_data (G_OBJECT (operation->session), "gdm-display", NULL); + create_user_session_for_display (operation->manager, display, allowed_uid); + + /* Give the user session a new display object for bookkeeping purposes */ + create_display_for_user_session (operation->manager, + operation->session, + session_id); + + + if (g_strcmp0 (operation->service_name, "gdm-autologin") == 0 && + !gdm_session_client_is_connected (operation->session)) { + /* remove the unused prepared greeter display since we're not going + * to have a greeter */ + gdm_display_store_remove (self->priv->display_store, display); + g_object_unref (display); + + self->priv->automatic_login_display = g_object_get_data (G_OBJECT (operation->session), "gdm-display"); + g_object_add_weak_pointer (G_OBJECT (self->priv->automatic_login_display), (gpointer *) &self->priv->automatic_login_display); + } + } + + start_user_session (operation->manager, operation); + + out: + return G_SOURCE_REMOVE; +} + +static void +queue_start_user_session (GdmManager *manager, + GdmSession *session, + const char *service_name) +{ + StartUserSessionOperation *operation; + + operation = g_slice_new0 (StartUserSessionOperation); + operation->manager = manager; + operation->session = g_object_ref (session); + operation->service_name = g_strdup (service_name); + + operation->idle_id = g_idle_add ((GSourceFunc) on_start_user_session, operation); + g_object_set_data (G_OBJECT (session), "start-user-session-operation", operation); +} + +static void +start_user_session_if_ready (GdmManager *manager, + GdmSession *session, + const char *service_name) +{ + gboolean start_when_ready; + + start_when_ready = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (session), "start-when-ready")); + if (start_when_ready) { + g_object_set_data (G_OBJECT (session), "waiting-to-start", GINT_TO_POINTER (FALSE)); + queue_start_user_session (manager, session, service_name); + } else { + g_object_set_data (G_OBJECT (session), "waiting-to-start", GINT_TO_POINTER (TRUE)); + } +} + +static void +on_session_authentication_failed (GdmSession *session, + const char *service_name, + GPid conversation_pid, + GdmManager *manager) +{ + add_session_record (manager, session, conversation_pid, SESSION_RECORD_FAILED); +} + +static void +on_user_session_opened (GdmSession *session, + const char *service_name, + const char *session_id, + GdmManager *manager) +{ + manager->priv->user_sessions = g_list_append (manager->priv->user_sessions, + g_object_ref (session)); + if (g_strcmp0 (service_name, "gdm-autologin") == 0 && + !gdm_session_client_is_connected (session)) { + /* If we're auto logging in then don't wait for the go-ahead from a greeter, + * (since there is no greeter) */ + g_object_set_data (G_OBJECT (session), "start-when-ready", GINT_TO_POINTER (TRUE)); + } + + start_user_session_if_ready (manager, session, service_name); +} + +static void +on_user_session_started (GdmSession *session, + const char *service_name, + GPid pid, + GdmManager *manager) +{ + g_debug ("GdmManager: session started %d", pid); + add_session_record (manager, session, pid, SESSION_RECORD_LOGIN); + +#ifdef WITH_PLYMOUTH + if (g_strcmp0 (service_name, "gdm-autologin") == 0) { + if (manager->priv->plymouth_is_running) { + g_timeout_add_seconds (20, (GSourceFunc) plymouth_quit_with_transition, NULL); + manager->priv->plymouth_is_running = FALSE; + } + } +#endif +} + +static void +remove_user_session (GdmManager *manager, + GdmSession *session) +{ + GList *node; + GdmDisplay *display; + + display = get_display_for_user_session (session); + + if (display != NULL) { + gdm_display_unmanage (display); + gdm_display_finish (display); + } + + node = g_list_find (manager->priv->user_sessions, session); + + if (node != NULL) { + manager->priv->user_sessions = g_list_delete_link (manager->priv->user_sessions, node); + gdm_session_close (session); + g_object_unref (session); + } +} + +static void +on_session_start_failed (GdmSession *session, + const char *service_name, + const char *message, + GdmManager *manager) +{ + g_debug ("GdmManager: session failed to start: %s", message); + remove_user_session (manager, session); +} + +static void +on_user_session_exited (GdmSession *session, + int code, + GdmManager *manager) +{ + GPid pid; + + g_debug ("GdmManager: session exited with status %d", code); + pid = gdm_session_get_pid (session); + + if (pid > 0) { + add_session_record (manager, session, pid, SESSION_RECORD_LOGOUT); + } + + remove_user_session (manager, session); +} + +static void +on_user_session_died (GdmSession *session, + int signal_number, + GdmManager *manager) +{ + g_debug ("GdmManager: session died with signal %s", strsignal (signal_number)); + remove_user_session (manager, session); +} + +static char * +get_display_device (GdmManager *manager, + GdmDisplay *display) +{ + /* systemd finds the display device out on its own based on the display */ + return NULL; +} + +static void +on_session_reauthenticated (GdmSession *session, + const char *service_name, + GdmManager *manager) +{ + gboolean fail_if_already_switched = FALSE; + + if (gdm_session_get_display_mode (session) == GDM_SESSION_DISPLAY_MODE_REUSE_VT) { + const char *seat_id; + char *session_id; + + seat_id = gdm_session_get_display_seat_id (session); + if (gdm_get_login_window_session_id (seat_id, &session_id)) { + GdmDisplay *display = gdm_display_store_find (manager->priv->display_store, + lookup_by_session_id, + (gpointer) session_id); + + if (display != NULL) { + gdm_display_stop_greeter_session (display); + gdm_display_unmanage (display); + gdm_display_finish (display); + } + g_free (session_id); + } + } + + /* There should already be a session running, so jump to its + * VT. In the event we're already on the right VT, (i.e. user + * used an unlock screen instead of a user switched login screen), + * then silently succeed and unlock the session. + */ + switch_to_compatible_user_session (manager, session, fail_if_already_switched); +} + +static void +on_session_client_ready_for_session_to_start (GdmSession *session, + const char *service_name, + gboolean client_is_ready, + GdmManager *manager) +{ + gboolean waiting_to_start_user_session; + + if (client_is_ready) { + g_debug ("GdmManager: Will start session when ready"); + } else { + g_debug ("GdmManager: Will start session when ready and told"); + } + + waiting_to_start_user_session = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (session), + "waiting-to-start")); + + g_object_set_data (G_OBJECT (session), + "start-when-ready", + GINT_TO_POINTER (client_is_ready)); + + if (client_is_ready && waiting_to_start_user_session) { + start_user_session_if_ready (manager, session, service_name); + } +} + +static void +on_session_client_connected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *manager) +{ + GdmDisplay *display; + char *username; + int delay; + gboolean enabled; + gboolean allow_timed_login = FALSE; + + g_debug ("GdmManager: client with pid %d connected", (int) pid_of_client); + + if (gdm_session_is_running (session)) { + const char *session_username; + session_username = gdm_session_get_username (session); + g_debug ("GdmManager: ignoring connection, since session already running (for user %s)", + session_username); + return; + } + + display = get_display_for_user_session (session); + + if (display == NULL) { + return; + } + + if (!display_is_on_seat0 (display)) { + return; + } + +#ifdef WITH_PLYMOUTH + if (manager->priv->plymouth_is_running) { + plymouth_quit_with_transition (); + manager->priv->plymouth_is_running = FALSE; + } +#endif + + g_object_get (G_OBJECT (display), "allow-timed-login", &allow_timed_login, NULL); + + if (!allow_timed_login) { + return; + } + + enabled = get_timed_login_details (manager, &username, &delay); + + if (! enabled) { + return; + } + + gdm_session_set_timed_login_details (session, username, delay); + + g_debug ("GdmManager: Starting automatic login conversation (for timed login)"); + gdm_session_start_conversation (session, "gdm-autologin"); + + g_free (username); + +} + +static void +on_session_client_disconnected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + GdmManager *manager) +{ + g_debug ("GdmManager: client with pid %d disconnected", (int) pid_of_client); +} + +typedef struct +{ + GdmManager *manager; + GdmSession *session; + guint idle_id; +} ResetSessionOperation; + +static void +destroy_reset_session_operation (ResetSessionOperation *operation) +{ + g_object_set_data (G_OBJECT (operation->session), + "reset-session-operation", + NULL); + g_object_unref (operation->session); + g_slice_free (ResetSessionOperation, operation); +} + +static gboolean +on_reset_session (ResetSessionOperation *operation) +{ + gdm_session_reset (operation->session); + + destroy_reset_session_operation (operation); + + return G_SOURCE_REMOVE; +} + +static void +queue_session_reset (GdmManager *manager, + GdmSession *session) +{ + ResetSessionOperation *operation; + + operation = g_object_get_data (G_OBJECT (session), "reset-session-operation"); + + if (operation != NULL) { + return; + } + + operation = g_slice_new0 (ResetSessionOperation); + operation->manager = manager; + operation->session = g_object_ref (session); + operation->idle_id = g_idle_add ((GSourceFunc) on_reset_session, operation); + + g_object_set_data (G_OBJECT (session), "reset-session-operation", operation); +} + +static void +on_session_cancelled (GdmSession *session, + GdmManager *manager) +{ + g_debug ("GdmManager: Session was cancelled"); + queue_session_reset (manager, session); +} + +static void +on_session_conversation_started (GdmSession *session, + const char *service_name, + GdmManager *manager) +{ + GdmDisplay *display; + gboolean enabled; + char *username; + + g_debug ("GdmManager: session conversation started for service %s on session", service_name); + + if (g_strcmp0 (service_name, "gdm-autologin") != 0) { + g_debug ("GdmManager: ignoring session conversation since its not automatic login conversation"); + return; + } + + display = get_display_for_user_session (session); + + if (display == NULL) { + g_debug ("GdmManager: conversation has no associated display"); + return; + } + + if (!display_is_on_seat0 (display)) { + return; + } + + enabled = get_automatic_login_details (manager, &username); + + if (! enabled) { + return; + } + + g_debug ("GdmManager: begin auto login for user '%s'", username); + + /* service_name will be "gdm-autologin" + */ + gdm_session_setup_for_user (session, service_name, username); + + g_free (username); +} + +static void +on_session_conversation_stopped (GdmSession *session, + const char *service_name, + GdmManager *manager) +{ + g_debug ("GdmManager: session conversation '%s' stopped", service_name); +} + +static void +on_session_reauthentication_started (GdmSession *session, + int pid_of_caller, + const char *address, + GdmManager *manager) +{ + GDBusMethodInvocation *invocation; + gpointer source_tag; + + g_debug ("GdmManager: reauthentication started"); + + source_tag = GINT_TO_POINTER (pid_of_caller); + + invocation = g_hash_table_lookup (manager->priv->open_reauthentication_requests, + source_tag); + + if (invocation != NULL) { + g_hash_table_steal (manager->priv->open_reauthentication_requests, + source_tag); + gdm_dbus_manager_complete_open_reauthentication_channel (GDM_DBUS_MANAGER (manager), + invocation, + address); + } +} + +static void +clean_user_session (GdmSession *session) +{ + g_object_set_data (G_OBJECT (session), "gdm-display", NULL); + g_object_unref (session); +} + +static GdmSession * +create_user_session_for_display (GdmManager *manager, + GdmDisplay *display, + uid_t allowed_user) +{ + GdmSession *session; + gboolean display_is_local = FALSE; + char *display_name = NULL; + char *display_device = NULL; + char *remote_hostname = NULL; + char *display_auth_file = NULL; + char *display_seat_id = NULL; + char *display_id = NULL; +#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER) + g_autofree char *display_session_type = NULL; + gboolean greeter_is_wayland; +#endif + + g_object_get (G_OBJECT (display), + "id", &display_id, + "x11-display-name", &display_name, + "is-local", &display_is_local, + "remote-hostname", &remote_hostname, + "x11-authority-file", &display_auth_file, + "seat-id", &display_seat_id, +#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER) + "session-type", &display_session_type, +#endif + NULL); + display_device = get_display_device (manager, display); + + session = gdm_session_new (GDM_SESSION_VERIFICATION_MODE_LOGIN, + allowed_user, + display_name, + remote_hostname, + display_device, + display_seat_id, + display_auth_file, + display_is_local, + NULL); + + g_debug ("GdmSession: Created user session for user %d on display %s (seat %s)", + (int) allowed_user, + display_id, + display_seat_id); + + g_free (display_name); + g_free (remote_hostname); + g_free (display_auth_file); + g_free (display_seat_id); + + g_signal_connect (session, + "reauthentication-started", + G_CALLBACK (on_session_reauthentication_started), + manager); + g_signal_connect (session, + "reauthenticated", + G_CALLBACK (on_session_reauthenticated), + manager); + g_signal_connect (session, + "client-ready-for-session-to-start", + G_CALLBACK (on_session_client_ready_for_session_to_start), + manager); + g_signal_connect (session, + "client-connected", + G_CALLBACK (on_session_client_connected), + manager); + g_signal_connect (session, + "client-disconnected", + G_CALLBACK (on_session_client_disconnected), + manager); + g_signal_connect (session, + "cancelled", + G_CALLBACK (on_session_cancelled), + manager); + g_signal_connect (session, + "conversation-started", + G_CALLBACK (on_session_conversation_started), + manager); + g_signal_connect (session, + "conversation-stopped", + G_CALLBACK (on_session_conversation_stopped), + manager); + g_signal_connect (session, + "authentication-failed", + G_CALLBACK (on_session_authentication_failed), + manager); + g_signal_connect (session, + "session-opened", + G_CALLBACK (on_user_session_opened), + manager); + g_signal_connect (session, + "session-started", + G_CALLBACK (on_user_session_started), + manager); + g_signal_connect (session, + "session-start-failed", + G_CALLBACK (on_session_start_failed), + manager); + g_signal_connect (session, + "session-exited", + G_CALLBACK (on_user_session_exited), + manager); + g_signal_connect (session, + "session-died", + G_CALLBACK (on_user_session_died), + manager); + g_object_set_data (G_OBJECT (session), "gdm-display", display); + g_object_set_data_full (G_OBJECT (display), + "gdm-user-session", + g_object_ref (session), + (GDestroyNotify) + clean_user_session); + +#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER) + greeter_is_wayland = g_strcmp0 (display_session_type, "wayland") == 0; + g_object_set (G_OBJECT (session), "ignore-wayland", !greeter_is_wayland, NULL); +#endif + + return session; +} + +static void +on_display_added (GdmDisplayStore *display_store, + const char *id, + GdmManager *manager) +{ + GdmDisplay *display; + + display = gdm_display_store_lookup (display_store, id); + + if (display != NULL) { + g_dbus_object_manager_server_export (manager->priv->object_manager, + gdm_display_get_object_skeleton (display)); + + g_signal_connect (display, "notify::status", + G_CALLBACK (on_display_status_changed), + manager); + g_signal_emit (manager, signals[DISPLAY_ADDED], 0, id); + } +} + +GQuark +gdm_manager_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gdm_manager_error"); + } + + return ret; +} + +static void +listify_display_ids (const char *id, + GdmDisplay *display, + GPtrArray **array) +{ + g_ptr_array_add (*array, g_strdup (id)); +} + +/* + Example: + dbus-send --system --dest=org.gnome.DisplayManager \ + --type=method_call --print-reply --reply-timeout=2000 \ + /org/gnome/DisplayManager/Displays \ + org.freedesktop.ObjectManager.GetAll +*/ +gboolean +gdm_manager_get_displays (GdmManager *manager, + GPtrArray **displays, + GError **error) +{ + g_return_val_if_fail (GDM_IS_MANAGER (manager), FALSE); + + if (displays == NULL) { + return FALSE; + } + + *displays = g_ptr_array_new (); + gdm_display_store_foreach (manager->priv->display_store, + (GdmDisplayStoreFunc)listify_display_ids, + displays); + + return TRUE; +} + +void +gdm_manager_stop (GdmManager *manager) +{ + g_debug ("GdmManager: GDM stopping"); + + if (manager->priv->local_factory != NULL) { + gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->priv->local_factory)); + } + +#ifdef HAVE_LIBXDMCP + if (manager->priv->xdmcp_factory != NULL) { + gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->priv->xdmcp_factory)); + } +#endif + + manager->priv->started = FALSE; +} + +void +gdm_manager_start (GdmManager *manager) +{ + g_debug ("GdmManager: GDM starting to manage displays"); + +#ifdef WITH_PLYMOUTH + manager->priv->plymouth_is_running = plymouth_is_running (); + + if (manager->priv->plymouth_is_running) { + plymouth_prepare_for_transition (); + } +#endif + if (!manager->priv->xdmcp_enabled || manager->priv->show_local_greeter) { + gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->priv->local_factory)); + } + +#ifdef HAVE_LIBXDMCP + /* Accept remote connections */ + if (manager->priv->xdmcp_enabled) { +#ifdef WITH_PLYMOUTH + /* Quit plymouth if xdmcp is the only display */ + if (!manager->priv->show_local_greeter && manager->priv->plymouth_is_running) { + plymouth_quit_without_transition (); + manager->priv->plymouth_is_running = FALSE; + } +#endif + if (manager->priv->xdmcp_factory != NULL) { + g_debug ("GdmManager: Accepting XDMCP connections..."); + gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->priv->xdmcp_factory)); + } + } +#endif + + manager->priv->started = TRUE; +} + +static gboolean +register_manager (GdmManager *manager) +{ + GError *error = NULL; + GDBusObjectManagerServer *object_server; + + error = NULL; + manager->priv->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, + NULL, + &error); + if (manager->priv->connection == NULL) { + g_critical ("error getting system bus: %s", error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + object_server = g_dbus_object_manager_server_new (GDM_MANAGER_DISPLAYS_PATH); + g_dbus_object_manager_server_set_connection (object_server, manager->priv->connection); + manager->priv->object_manager = object_server; + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (manager), + manager->priv->connection, + GDM_MANAGER_PATH, + &error)) { + g_critical ("error exporting interface to %s: %s", + GDM_MANAGER_PATH, + error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + return TRUE; +} + +void +gdm_manager_set_xdmcp_enabled (GdmManager *manager, + gboolean enabled) +{ + g_return_if_fail (GDM_IS_MANAGER (manager)); + + if (manager->priv->xdmcp_enabled != enabled) { + manager->priv->xdmcp_enabled = enabled; +#ifdef HAVE_LIBXDMCP + if (manager->priv->xdmcp_enabled) { + manager->priv->xdmcp_factory = gdm_xdmcp_display_factory_new (manager->priv->display_store); + if (manager->priv->started) { + gdm_display_factory_start (GDM_DISPLAY_FACTORY (manager->priv->xdmcp_factory)); + } + } else { + if (manager->priv->started) { + gdm_display_factory_stop (GDM_DISPLAY_FACTORY (manager->priv->xdmcp_factory)); + } + + g_object_unref (manager->priv->xdmcp_factory); + manager->priv->xdmcp_factory = NULL; + } +#endif + } + +} + +void +gdm_manager_set_show_local_greeter (GdmManager *manager, + gboolean show_local_greeter) +{ + g_return_if_fail (GDM_IS_MANAGER (manager)); + + manager->priv->show_local_greeter = show_local_greeter; +} + +static void +gdm_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmManager *self; + + self = GDM_MANAGER (object); + + switch (prop_id) { + case PROP_XDMCP_ENABLED: + gdm_manager_set_xdmcp_enabled (self, g_value_get_boolean (value)); + break; + case PROP_SHOW_LOCAL_GREETER: + gdm_manager_set_show_local_greeter (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmManager *self; + + self = GDM_MANAGER (object); + + switch (prop_id) { + case PROP_XDMCP_ENABLED: + g_value_set_boolean (value, self->priv->xdmcp_enabled); + break; + case PROP_SHOW_LOCAL_GREETER: + g_value_set_boolean (value, self->priv->show_local_greeter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gdm_manager_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmManager *manager; + + manager = GDM_MANAGER (G_OBJECT_CLASS (gdm_manager_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + gdm_dbus_manager_set_version (GDM_DBUS_MANAGER (manager), PACKAGE_VERSION); + + manager->priv->local_factory = gdm_local_display_factory_new (manager->priv->display_store); + +#ifdef HAVE_LIBXDMCP + if (manager->priv->xdmcp_enabled) { + manager->priv->xdmcp_factory = gdm_xdmcp_display_factory_new (manager->priv->display_store); + } +#endif + + return G_OBJECT (manager); +} + +static void +gdm_manager_class_init (GdmManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_manager_get_property; + object_class->set_property = gdm_manager_set_property; + object_class->constructor = gdm_manager_constructor; + object_class->dispose = gdm_manager_dispose; + + signals [DISPLAY_ADDED] = + g_signal_new ("display-added", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmManagerClass, display_added), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [DISPLAY_REMOVED] = + g_signal_new ("display-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmManagerClass, display_removed), + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, G_TYPE_OBJECT); + + g_object_class_install_property (object_class, + PROP_XDMCP_ENABLED, + g_param_spec_boolean ("xdmcp-enabled", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); +} + +static void +gdm_manager_init (GdmManager *manager) +{ + + manager->priv = GDM_MANAGER_GET_PRIVATE (manager); + + manager->priv->display_store = gdm_display_store_new (); + manager->priv->user_sessions = NULL; + manager->priv->open_reauthentication_requests = g_hash_table_new_full (NULL, + NULL, + (GDestroyNotify) + NULL, + (GDestroyNotify) + g_object_unref); + manager->priv->transient_sessions = g_hash_table_new_full (NULL, + NULL, + (GDestroyNotify) + NULL, + (GDestroyNotify) + g_object_unref); + g_signal_connect (G_OBJECT (manager->priv->display_store), + "display-added", + G_CALLBACK (on_display_added), + manager); + + g_signal_connect (G_OBJECT (manager->priv->display_store), + "display-removed", + G_CALLBACK (on_display_removed), + manager); +} + +static void +unexport_display (const char *id, + GdmDisplay *display, + GdmManager *manager) +{ + if (!g_dbus_connection_is_closed (manager->priv->connection)) + g_dbus_object_manager_server_unexport (manager->priv->object_manager, id); +} + +static void +finish_display (const char *id, + GdmDisplay *display, + GdmManager *manager) +{ + gdm_display_stop_greeter_session (display); + if (gdm_display_get_status (display) == GDM_DISPLAY_MANAGED) + gdm_display_unmanage (display); + gdm_display_finish (display); +} + +static void +gdm_manager_dispose (GObject *object) +{ + GdmManager *manager; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_MANAGER (object)); + + manager = GDM_MANAGER (object); + + g_return_if_fail (manager->priv != NULL); + + g_clear_weak_pointer (&manager->priv->automatic_login_display); + +#ifdef HAVE_LIBXDMCP + g_clear_object (&manager->priv->xdmcp_factory); +#endif + g_clear_object (&manager->priv->local_factory); + g_clear_pointer (&manager->priv->open_reauthentication_requests, + g_hash_table_unref); + g_clear_pointer (&manager->priv->transient_sessions, + g_hash_table_unref); + + g_list_foreach (manager->priv->user_sessions, + (GFunc) gdm_session_close, + NULL); + g_list_free_full (manager->priv->user_sessions, (GDestroyNotify) g_object_unref); + manager->priv->user_sessions = NULL; + + g_signal_handlers_disconnect_by_func (G_OBJECT (manager->priv->display_store), + G_CALLBACK (on_display_added), + manager); + g_signal_handlers_disconnect_by_func (G_OBJECT (manager->priv->display_store), + G_CALLBACK (on_display_removed), + manager); + + if (!g_dbus_connection_is_closed (manager->priv->connection)) { + gdm_display_store_foreach (manager->priv->display_store, + (GdmDisplayStoreFunc)unexport_display, + manager); + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (manager)); + } + + gdm_display_store_foreach (manager->priv->display_store, + (GdmDisplayStoreFunc) finish_display, + manager); + + gdm_display_store_clear (manager->priv->display_store); + + g_dbus_object_manager_server_set_connection (manager->priv->object_manager, NULL); + + g_clear_object (&manager->priv->connection); + g_clear_object (&manager->priv->object_manager); + g_clear_object (&manager->priv->display_store); + + G_OBJECT_CLASS (gdm_manager_parent_class)->dispose (object); +} + +GdmManager * +gdm_manager_new (void) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + gboolean res; + + manager_object = g_object_new (GDM_TYPE_MANAGER, NULL); + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + res = register_manager (manager_object); + if (! res) { + g_object_unref (manager_object); + return NULL; + } + } + + return GDM_MANAGER (manager_object); +} diff --git a/daemon/gdm-manager.h b/daemon/gdm-manager.h new file mode 100644 index 0000000..c8fb3f2 --- /dev/null +++ b/daemon/gdm-manager.h @@ -0,0 +1,82 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 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. + * + */ + + +#ifndef __GDM_MANAGER_H +#define __GDM_MANAGER_H + +#include <glib-object.h> + +#include "gdm-display.h" +#include "gdm-manager-glue.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_MANAGER (gdm_manager_get_type ()) +#define GDM_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_MANAGER, GdmManager)) +#define GDM_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_MANAGER, GdmManagerClass)) +#define GDM_IS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_MANAGER)) +#define GDM_IS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_MANAGER)) +#define GDM_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_MANAGER, GdmManagerClass)) + +typedef struct GdmManagerPrivate GdmManagerPrivate; + +typedef struct +{ + GdmDBusManagerSkeleton parent; + GdmManagerPrivate *priv; +} GdmManager; + +typedef struct +{ + GdmDBusManagerSkeletonClass parent_class; + + void (* display_added) (GdmManager *manager, + const char *id); + void (* display_removed) (GdmManager *manager, + GdmDisplay *display); +} GdmManagerClass; + +typedef enum +{ + GDM_MANAGER_ERROR_GENERAL +} GdmManagerError; + +#define GDM_MANAGER_ERROR gdm_manager_error_quark () + +GQuark gdm_manager_error_quark (void); +GType gdm_manager_get_type (void); + +GdmManager * gdm_manager_new (void); +void gdm_manager_start (GdmManager *manager); +void gdm_manager_stop (GdmManager *manager); + +void gdm_manager_set_xdmcp_enabled (GdmManager *manager, + gboolean enabled); +void gdm_manager_set_show_local_greeter (GdmManager *manager, + gboolean show_local_greeter); +gboolean gdm_manager_get_displays (GdmManager *manager, + GPtrArray **displays, + GError **error); + + +G_END_DECLS + +#endif /* __GDM_MANAGER_H */ diff --git a/daemon/gdm-manager.xml b/daemon/gdm-manager.xml new file mode 100644 index 0000000..92ef1d0 --- /dev/null +++ b/daemon/gdm-manager.xml @@ -0,0 +1,19 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/org/gnome/DisplayManager/Manager"> + <interface name="org.gnome.DisplayManager.Manager"> + <method name="RegisterDisplay"> + <arg name="details" direction="in" type="a{ss}"/> + </method> + <method name="RegisterSession"> + <arg name="details" direction="in" type="a{sv}"/> + </method> + <method name="OpenSession"> + <arg name="address" direction="out" type="s"/> + </method> + <method name="OpenReauthenticationChannel"> + <arg name="username" direction="in" type="s"/> + <arg name="address" direction="out" type="s"/> + </method> + <property name="Version" type="s" access="read"/> + </interface> +</node> diff --git a/daemon/gdm-server.c b/daemon/gdm-server.c new file mode 100644 index 0000000..1ba00d4 --- /dev/null +++ b/daemon/gdm-server.c @@ -0,0 +1,1088 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/resource.h> + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#ifdef WITH_PLYMOUTH +#include <linux/vt.h> +#endif + +#include <systemd/sd-daemon.h> + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include <systemd/sd-journal.h> +#endif + +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gio/gio.h> + +#include <X11/Xlib.h> /* for Display */ + +#include "gdm-common.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +#include "gdm-server.h" + +extern char **environ; + +#define MAX_LOGS 5 + +struct _GdmServer +{ + GObject parent; + + char *command; + GPid pid; + + gboolean disable_tcp; + int priority; + char *user_name; + char *session_args; + + char *log_dir; + char *display_name; + char *display_device; + char *display_seat_id; + char *auth_file; + + guint child_watch_id; + + gboolean is_initial; +}; + +enum { + PROP_0, + PROP_DISPLAY_NAME, + PROP_DISPLAY_SEAT_ID, + PROP_DISPLAY_DEVICE, + PROP_AUTH_FILE, + PROP_USER_NAME, + PROP_DISABLE_TCP, + PROP_IS_INITIAL, +}; + +enum { + READY, + EXITED, + DIED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_server_class_init (GdmServerClass *klass); +static void gdm_server_init (GdmServer *server); +static void gdm_server_finalize (GObject *object); + +G_DEFINE_TYPE (GdmServer, gdm_server, G_TYPE_OBJECT) + +char * +gdm_server_get_display_device (GdmServer *server) +{ + /* systemd finds the display device out on its own based on the display */ + return NULL; +} + +static void +gdm_server_ready (GdmServer *server) +{ + g_debug ("GdmServer: Got USR1 from X server - emitting READY"); + + gdm_run_script (GDMCONFDIR "/Init", GDM_USERNAME, + server->display_name, + NULL, /* hostname */ + server->auth_file); + + g_signal_emit (server, signals[READY], 0); +} + +static GSList *active_servers; +static gboolean sigusr1_thread_running; +static GCond sigusr1_thread_cond; +static GMutex sigusr1_thread_mutex; + +static gboolean +got_sigusr1 (gpointer user_data) +{ + GPid pid = GPOINTER_TO_UINT (user_data); + GSList *l; + + g_debug ("GdmServer: got SIGUSR1 from PID %d", pid); + + for (l = active_servers; l; l = l->next) { + GdmServer *server = l->data; + + if (server->pid == pid) + gdm_server_ready (server); + } + + return G_SOURCE_REMOVE; +} + +static gpointer +sigusr1_thread_main (gpointer user_data) +{ + sigset_t sigusr1_mask; + + /* Handle only SIGUSR1 */ + sigemptyset (&sigusr1_mask); + sigaddset (&sigusr1_mask, SIGUSR1); + sigprocmask (SIG_SETMASK, &sigusr1_mask, NULL); + + g_mutex_lock (&sigusr1_thread_mutex); + sigusr1_thread_running = TRUE; + g_cond_signal (&sigusr1_thread_cond); + g_mutex_unlock (&sigusr1_thread_mutex); + + /* Spin waiting for a SIGUSR1 */ + while (TRUE) { + siginfo_t info; + + if (sigwaitinfo (&sigusr1_mask, &info) == -1) + continue; + + g_idle_add (got_sigusr1, GUINT_TO_POINTER (info.si_pid)); + } + + return NULL; +} + +static void +gdm_server_launch_sigusr1_thread_if_needed (void) +{ + static GThread *sigusr1_thread; + + if (sigusr1_thread == NULL) { + sigusr1_thread = g_thread_new ("gdm SIGUSR1 catcher", sigusr1_thread_main, NULL); + + g_mutex_lock (&sigusr1_thread_mutex); + while (!sigusr1_thread_running) + g_cond_wait (&sigusr1_thread_cond, &sigusr1_thread_mutex); + g_mutex_unlock (&sigusr1_thread_mutex); + } +} + +static void +gdm_server_init_command (GdmServer *server) +{ + gboolean debug = FALSE; + const char *debug_options; + const char *verbosity = ""; + + if (server->command != NULL) { + return; + } + + gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug); + if (debug) { + debug_options = " -logverbose 7 -core "; + } else { + debug_options = ""; + } + +#define X_SERVER_ARG_FORMAT " -background none -noreset -verbose %s%s" + + /* This is a temporary hack to work around the fact that XOrg + * currently lacks support for multi-seat hotplugging for + * display devices. This bit should be removed as soon as XOrg + * gains native support for automatically enumerating usb + * based graphics adapters at start-up via udev. */ + + /* systemd ships an X server wrapper tool which simply invokes + * the usual X but ensures it only uses the display devices of + * the seat. */ + + /* We do not rely on this wrapper server if, a) the machine + * wasn't booted using systemd, or b) the wrapper tool is + * missing, or c) we are running for the main seat 'seat0'. */ + +#ifdef ENABLE_SYSTEMD_JOURNAL + /* For systemd, we don't have a log file but instead log to stdout, + so set it to the xserver's built-in default verbosity */ + if (debug) + verbosity = "7 -logfile /dev/null"; + else + verbosity = "3 -logfile /dev/null"; +#endif + + if (g_access (SYSTEMD_X_SERVER, X_OK) < 0) { + goto fallback; + } + + if (server->display_seat_id == NULL || + strcmp (server->display_seat_id, "seat0") == 0) { + goto fallback; + } + + server->command = g_strdup_printf (SYSTEMD_X_SERVER X_SERVER_ARG_FORMAT, verbosity, debug_options); + return; + +fallback: + server->command = g_strdup_printf (X_SERVER X_SERVER_ARG_FORMAT, verbosity, debug_options); + +} + +static gboolean +gdm_server_resolve_command_line (GdmServer *server, + const char *vtarg, + int *argcp, + char ***argvp) +{ + int argc; + char **argv; + int len; + int i; + gboolean gotvtarg = FALSE; + gboolean query_in_arglist = FALSE; + + gdm_server_init_command (server); + + g_shell_parse_argv (server->command, &argc, &argv, NULL); + + for (len = 0; argv != NULL && argv[len] != NULL; len++) { + char *arg = argv[len]; + + /* HACK! Not to add vt argument to servers that already force + * allocation. Mostly for backwards compat only */ + if (strncmp (arg, "vt", 2) == 0 && + isdigit (arg[2]) && + (arg[3] == '\0' || + (isdigit (arg[3]) && arg[4] == '\0'))) + gotvtarg = TRUE; + if (strcmp (arg, "-query") == 0 || + strcmp (arg, "-indirect") == 0) + query_in_arglist = TRUE; + } + + argv = g_renew (char *, argv, len + 12); + /* shift args down one */ + for (i = len - 1; i >= 1; i--) { + argv[i+1] = argv[i]; + } + + /* server number is the FIRST argument, before any others */ + argv[1] = g_strdup (server->display_name); + len++; + + if (server->auth_file != NULL) { + argv[len++] = g_strdup ("-auth"); + argv[len++] = g_strdup (server->auth_file); + } + + if (server->display_seat_id != NULL) { + argv[len++] = g_strdup ("-seat"); + argv[len++] = g_strdup (server->display_seat_id); + } + + /* If we were compiled with Xserver >= 1.17 we need to specify + * '-listen tcp' as the X server dosen't listen on tcp sockets + * by default anymore. In older versions we need to pass + * -nolisten tcp to disable listening on tcp sockets. + */ +#ifdef HAVE_XSERVER_THAT_DEFAULTS_TO_LOCAL_ONLY + if (!server->disable_tcp && ! query_in_arglist) { + argv[len++] = g_strdup ("-listen"); + argv[len++] = g_strdup ("tcp"); + } +#else + if (server->disable_tcp && ! query_in_arglist) { + argv[len++] = g_strdup ("-nolisten"); + argv[len++] = g_strdup ("tcp"); + } + +#endif + + if (vtarg != NULL && ! gotvtarg) { + argv[len++] = g_strdup (vtarg); + } + + argv[len++] = NULL; + + *argvp = argv; + *argcp = len; + + return TRUE; +} + +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); + } + + VE_IGNORE_EINTR (g_unlink (name_n)); + VE_IGNORE_EINTR (g_rename (name_n1, name_n)); + + g_free (name_n1); + g_free (name_n); + } + + VE_IGNORE_EINTR (g_unlink (path)); +} + +static void +change_user (GdmServer *server) +{ + struct passwd *pwent; + + if (server->user_name == NULL) { + return; + } + + gdm_get_pwent_for_name (server->user_name, &pwent); + if (pwent == NULL) { + g_warning (_("Server was to be spawned by user %s but that user doesn’t exist"), + server->user_name); + _exit (EXIT_FAILURE); + } + + g_debug ("GdmServer: Changing (uid:gid) for child process to (%d:%d)", + pwent->pw_uid, + pwent->pw_gid); + + if (pwent->pw_uid != 0) { + if (setgid (pwent->pw_gid) < 0) { + g_warning (_("Couldn’t set groupid to %d"), + pwent->pw_gid); + _exit (EXIT_FAILURE); + } + + if (initgroups (pwent->pw_name, pwent->pw_gid) < 0) { + g_warning (_("initgroups () failed for %s"), + pwent->pw_name); + _exit (EXIT_FAILURE); + } + + if (setuid (pwent->pw_uid) < 0) { + g_warning (_("Couldn’t set userid to %d"), + (int)pwent->pw_uid); + _exit (EXIT_FAILURE); + } + } else { + gid_t groups[1] = { 0 }; + + if (setgid (0) < 0) { + g_warning (_("Couldn’t set groupid to %d"), 0); + /* Don't error out, it's not fatal, if it fails we'll + * just still be */ + } + + /* this will get rid of any suplementary groups etc... */ + setgroups (1, groups); + } +} + +static gboolean +gdm_server_setup_journal_fds (GdmServer *server) +{ +#ifdef ENABLE_SYSTEMD_JOURNAL + if (sd_booted () > 0) { + int out, err; + const char *prefix = "gdm-Xorg-"; + char *identifier; + gsize size; + + size = strlen (prefix) + strlen (server->display_name) + 1; + identifier = g_alloca (size); + strcpy (identifier, prefix); + strcat (identifier, server->display_name); + identifier[size - 1] = '\0'; + + out = sd_journal_stream_fd (identifier, LOG_INFO, FALSE); + if (out < 0) + return FALSE; + + err = sd_journal_stream_fd (identifier, LOG_WARNING, FALSE); + if (err < 0) { + close (out); + return FALSE; + } + + VE_IGNORE_EINTR (dup2 (out, 1)); + VE_IGNORE_EINTR (dup2 (err, 2)); + return TRUE; + } +#endif + return FALSE; +} + +static void +gdm_server_setup_logfile (GdmServer *server) +{ + int logfd; + char *log_file; + char *log_path; + + log_file = g_strdup_printf ("%s.log", server->display_name); + log_path = g_build_filename (server->log_dir, log_file, NULL); + g_free (log_file); + + /* Rotate the X server logs */ + rotate_logs (log_path, MAX_LOGS); + + g_debug ("GdmServer: Opening logfile for server %s", log_path); + + VE_IGNORE_EINTR (g_unlink (log_path)); + VE_IGNORE_EINTR (logfd = open (log_path, O_CREAT|O_APPEND|O_TRUNC|O_WRONLY|O_EXCL, 0644)); + + g_free (log_path); + + if (logfd != -1) { + VE_IGNORE_EINTR (dup2 (logfd, 1)); + VE_IGNORE_EINTR (dup2 (logfd, 2)); + close (logfd); + } else { + g_warning (_("%s: Could not open log file for display %s!"), + "gdm_server_spawn", + server->display_name); + } +} + +static void +server_child_setup (GdmServer *server) +{ + struct sigaction ign_signal; + sigset_t mask; + + if (!gdm_server_setup_journal_fds(server)) + gdm_server_setup_logfile(server); + + /* The X server expects USR1/TTIN/TTOU to be SIG_IGN */ + ign_signal.sa_handler = SIG_IGN; + ign_signal.sa_flags = SA_RESTART; + sigemptyset (&ign_signal.sa_mask); + + if (sigaction (SIGUSR1, &ign_signal, NULL) < 0) { + g_warning (_("%s: Error setting %s to %s"), + "gdm_server_spawn", "USR1", "SIG_IGN"); + _exit (EXIT_FAILURE); + } + + if (sigaction (SIGTTIN, &ign_signal, NULL) < 0) { + g_warning (_("%s: Error setting %s to %s"), + "gdm_server_spawn", "TTIN", "SIG_IGN"); + _exit (EXIT_FAILURE); + } + + if (sigaction (SIGTTOU, &ign_signal, NULL) < 0) { + g_warning (_("%s: Error setting %s to %s"), + "gdm_server_spawn", "TTOU", "SIG_IGN"); + _exit (EXIT_FAILURE); + } + + /* And HUP and TERM are at SIG_DFL from gdm_unset_signals, + we also have an empty mask and all that fun stuff */ + + /* unblock signals (especially HUP/TERM/USR1) so that we + * can control the X server */ + sigemptyset (&mask); + sigprocmask (SIG_SETMASK, &mask, NULL); + + /* Terminate the process when the parent dies */ +#ifdef HAVE_SYS_PRCTL_H + prctl (PR_SET_PDEATHSIG, SIGTERM); +#endif + + if (server->priority != 0) { + if (setpriority (PRIO_PROCESS, 0, server->priority)) { + g_warning (_("%s: Server priority couldn’t be set to %d: %s"), + "gdm_server_spawn", + server->priority, + g_strerror (errno)); + } + } + + setpgid (0, 0); + + change_user (server); +} + +static void +listify_hash (const char *key, + const char *value, + GPtrArray *env) +{ + char *str; + str = g_strdup_printf ("%s=%s", key, value); + g_ptr_array_add (env, str); +} + +static GPtrArray * +get_server_environment (GdmServer *server) +{ + GPtrArray *env; + char **l; + GHashTable *hash; + + env = g_ptr_array_new (); + + /* create a hash table of current environment, then update keys has necessary */ + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + for (l = environ; *l != NULL; l++) { + char **str; + str = g_strsplit (*l, "=", 2); + g_hash_table_insert (hash, str[0], str[1]); + g_free (str); + } + + /* modify environment here */ + g_hash_table_insert (hash, g_strdup ("DISPLAY"), g_strdup (server->display_name)); + + if (server->user_name != NULL) { + struct passwd *pwent; + + gdm_get_pwent_for_name (server->user_name, &pwent); + + if (pwent->pw_dir != NULL + && g_file_test (pwent->pw_dir, G_FILE_TEST_EXISTS)) { + g_hash_table_insert (hash, g_strdup ("HOME"), g_strdup (pwent->pw_dir)); + } else { + /* Hack */ + g_hash_table_insert (hash, g_strdup ("HOME"), g_strdup ("/")); + } + g_hash_table_insert (hash, g_strdup ("SHELL"), g_strdup (pwent->pw_shell)); + g_hash_table_remove (hash, "MAIL"); + } + + g_hash_table_foreach (hash, (GHFunc)listify_hash, env); + g_hash_table_destroy (hash); + + g_ptr_array_add (env, NULL); + + return env; +} + +static void +server_add_xserver_args (GdmServer *server, + int *argc, + char ***argv) +{ + int count; + char **args; + int len; + int i; + + len = *argc; + g_shell_parse_argv (server->session_args, &count, &args, NULL); + *argv = g_renew (char *, *argv, len + count + 1); + + for (i=0; i < count;i++) { + *argv[len++] = g_strdup (args[i]); + } + + *argc += count; + + argv[len] = NULL; + g_strfreev (args); +} + +static void +server_child_watch (GPid pid, + int status, + GdmServer *server) +{ + g_debug ("GdmServer: child (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -1); + + g_object_ref (server); + + if (WIFEXITED (status)) { + int code = WEXITSTATUS (status); + g_signal_emit (server, signals [EXITED], 0, code); + } else if (WIFSIGNALED (status)) { + int num = WTERMSIG (status); + g_signal_emit (server, signals [DIED], 0, num); + } + + g_spawn_close_pid (server->pid); + server->pid = -1; + + g_object_unref (server); +} + +static void +prune_active_servers_list (GdmServer *server) +{ + active_servers = g_slist_remove (active_servers, server); +} + +static gboolean +gdm_server_spawn (GdmServer *server, + const char *vtarg, + GError **error) +{ + int argc; + gchar **argv = NULL; + GPtrArray *env = NULL; + gboolean ret = FALSE; + char *freeme; + + /* Figure out the server command */ + argv = NULL; + argc = 0; + gdm_server_resolve_command_line (server, + vtarg, + &argc, + &argv); + + if (server->session_args) { + server_add_xserver_args (server, &argc, &argv); + } + + if (argv[0] == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("%s: Empty server command for display %s"), + "gdm_server_spawn", + server->display_name); + goto out; + } + + env = get_server_environment (server); + + freeme = g_strjoinv (" ", argv); + g_debug ("GdmServer: Starting X server process: %s", freeme); + g_free (freeme); + + active_servers = g_slist_append (active_servers, server); + + g_object_weak_ref (G_OBJECT (server), + (GWeakNotify) + prune_active_servers_list, + server); + + gdm_server_launch_sigusr1_thread_if_needed (); + + if (!g_spawn_async_with_pipes (NULL, + argv, + (char **)env->pdata, + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + (GSpawnChildSetupFunc)server_child_setup, + server, + &server->pid, + NULL, + NULL, + NULL, + error)) + goto out; + + g_debug ("GdmServer: Started X server process %d - waiting for READY", (int)server->pid); + + server->child_watch_id = g_child_watch_add (server->pid, + (GChildWatchFunc)server_child_watch, + server); + + ret = TRUE; + out: + g_strfreev (argv); + if (env) { + g_ptr_array_foreach (env, (GFunc)g_free, NULL); + g_ptr_array_free (env, TRUE); + } + return ret; +} + +/** + * gdm_server_start: + * @disp: Pointer to a GdmDisplay structure + * + * Starts a local X server. Handles retries and fatal errors properly. + */ + +gboolean +gdm_server_start (GdmServer *server) +{ + gboolean res = FALSE; + const char *vtarg = NULL; + GError *local_error = NULL; + GError **error = &local_error; + + /* Hardcode the VT for the initial X server, but nothing else */ + if (server->is_initial) { + vtarg = "vt" G_STRINGIFY (GDM_INITIAL_VT); + } + + /* fork X server process */ + if (!gdm_server_spawn (server, vtarg, error)) { + goto out; + } + + res = TRUE; + out: + if (local_error) { + g_printerr ("%s\n", local_error->message); + g_clear_error (&local_error); + } + return res; +} + +static void +server_died (GdmServer *server) +{ + int exit_status; + + g_debug ("GdmServer: Waiting on process %d", server->pid); + exit_status = gdm_wait_on_pid (server->pid); + + if (WIFEXITED (exit_status) && (WEXITSTATUS (exit_status) != 0)) { + g_debug ("GdmServer: Wait on child process failed"); + } else { + /* exited normally */ + } + + g_spawn_close_pid (server->pid); + server->pid = -1; + + if (server->display_device != NULL) { + g_free (server->display_device); + server->display_device = NULL; + g_object_notify (G_OBJECT (server), "display-device"); + } + + g_debug ("GdmServer: Server died"); +} + +gboolean +gdm_server_stop (GdmServer *server) +{ + int res; + + if (server->pid <= 1) { + return TRUE; + } + + /* remove watch source before we can wait on child */ + if (server->child_watch_id > 0) { + g_source_remove (server->child_watch_id); + server->child_watch_id = 0; + } + + g_debug ("GdmServer: Stopping server"); + + res = gdm_signal_pid (server->pid, SIGTERM); + if (res < 0) { + } else { + server_died (server); + } + + return TRUE; +} + + +static void +_gdm_server_set_display_name (GdmServer *server, + const char *name) +{ + g_free (server->display_name); + server->display_name = g_strdup (name); +} + +static void +_gdm_server_set_display_seat_id (GdmServer *server, + const char *name) +{ + g_free (server->display_seat_id); + server->display_seat_id = g_strdup (name); +} + +static void +_gdm_server_set_auth_file (GdmServer *server, + const char *auth_file) +{ + g_free (server->auth_file); + server->auth_file = g_strdup (auth_file); +} + +static void +_gdm_server_set_user_name (GdmServer *server, + const char *name) +{ + g_free (server->user_name); + server->user_name = g_strdup (name); +} + +static void +_gdm_server_set_disable_tcp (GdmServer *server, + gboolean disabled) +{ + server->disable_tcp = disabled; +} + +static void +_gdm_server_set_is_initial (GdmServer *server, + gboolean initial) +{ + server->is_initial = initial; +} + +static void +gdm_server_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmServer *self; + + self = GDM_SERVER (object); + + switch (prop_id) { + case PROP_DISPLAY_NAME: + _gdm_server_set_display_name (self, g_value_get_string (value)); + break; + case PROP_DISPLAY_SEAT_ID: + _gdm_server_set_display_seat_id (self, g_value_get_string (value)); + break; + case PROP_AUTH_FILE: + _gdm_server_set_auth_file (self, g_value_get_string (value)); + break; + case PROP_USER_NAME: + _gdm_server_set_user_name (self, g_value_get_string (value)); + break; + case PROP_DISABLE_TCP: + _gdm_server_set_disable_tcp (self, g_value_get_boolean (value)); + break; + case PROP_IS_INITIAL: + _gdm_server_set_is_initial (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_server_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmServer *self; + + self = GDM_SERVER (object); + + switch (prop_id) { + case PROP_DISPLAY_NAME: + g_value_set_string (value, self->display_name); + break; + case PROP_DISPLAY_SEAT_ID: + g_value_set_string (value, self->display_seat_id); + break; + case PROP_DISPLAY_DEVICE: + g_value_take_string (value, + gdm_server_get_display_device (self)); + break; + case PROP_AUTH_FILE: + g_value_set_string (value, self->auth_file); + break; + case PROP_USER_NAME: + g_value_set_string (value, self->user_name); + break; + case PROP_DISABLE_TCP: + g_value_set_boolean (value, self->disable_tcp); + break; + case PROP_IS_INITIAL: + g_value_set_boolean (value, self->is_initial); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_server_class_init (GdmServerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_server_get_property; + object_class->set_property = gdm_server_set_property; + object_class->finalize = gdm_server_finalize; + + signals [READY] = + g_signal_new ("ready", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [EXITED] = + g_signal_new ("exited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + signals [DIED] = + g_signal_new ("died", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + g_object_class_install_property (object_class, + PROP_DISPLAY_NAME, + g_param_spec_string ("display-name", + "name", + "name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISPLAY_SEAT_ID, + g_param_spec_string ("display-seat-id", + "Seat ID", + "ID of the seat this display is running on", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISPLAY_DEVICE, + g_param_spec_string ("display-device", + "Display Device", + "Path to terminal display is running on", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_AUTH_FILE, + g_param_spec_string ("auth-file", + "Authorization File", + "Path to X authorization file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_USER_NAME, + g_param_spec_string ("user-name", + "user name", + "user name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISABLE_TCP, + g_param_spec_boolean ("disable-tcp", + NULL, + NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_IS_INITIAL, + g_param_spec_boolean ("is-initial", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); +} + +static void +gdm_server_init (GdmServer *server) +{ + server->pid = -1; + + server->log_dir = g_strdup (LOGDIR); +} + +static void +gdm_server_finalize (GObject *object) +{ + GdmServer *server; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_SERVER (object)); + + server = GDM_SERVER (object); + + gdm_server_stop (server); + + g_free (server->command); + g_free (server->user_name); + g_free (server->session_args); + g_free (server->log_dir); + g_free (server->display_name); + g_free (server->display_seat_id); + g_free (server->display_device); + g_free (server->auth_file); + + G_OBJECT_CLASS (gdm_server_parent_class)->finalize (object); +} + +GdmServer * +gdm_server_new (const char *display_name, + const char *seat_id, + const char *auth_file, + gboolean initial) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_SERVER, + "display-name", display_name, + "display-seat-id", seat_id, + "auth-file", auth_file, + "is-initial", initial, + NULL); + + return GDM_SERVER (object); +} diff --git a/daemon/gdm-server.h b/daemon/gdm-server.h new file mode 100644 index 0000000..d175f9b --- /dev/null +++ b/daemon/gdm-server.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_SERVER_H +#define __GDM_SERVER_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_SERVER (gdm_server_get_type ()) +G_DECLARE_FINAL_TYPE (GdmServer, gdm_server, GDM, SERVER, GObject); + +GdmServer * gdm_server_new (const char *display_id, + const char *seat_id, + const char *auth_file, + gboolean initial); +gboolean gdm_server_start (GdmServer *server); +gboolean gdm_server_stop (GdmServer *server); +char * gdm_server_get_display_device (GdmServer *server); + +G_END_DECLS + +#endif /* __GDM_SERVER_H */ diff --git a/daemon/gdm-session-auditor.c b/daemon/gdm-session-auditor.c new file mode 100644 index 0000000..5f569a9 --- /dev/null +++ b/daemon/gdm-session-auditor.c @@ -0,0 +1,322 @@ +/* gdm-session-auditor.c - Object for auditing session login/logout + * + * Copyright (C) 2004, 2008 Sun Microsystems, Inc. + * Copyright (C) 2005, 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Written by: Brian A. Cameron <Brian.Cameron@sun.com> + * Gary Winiger <Gary.Winiger@sun.com> + * Ray Strode <rstrode@redhat.com> + * Steve Grubb <sgrubb@redhat.com> + */ +#include "config.h" +#include "gdm-session-auditor.h" + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <glib.h> +#include <glib-object.h> +#include <glib/gi18n.h> + +typedef struct _GdmSessionAuditorPrivate +{ + char *username; + char *hostname; + char *display_device; +} GdmSessionAuditorPrivate; + +static void gdm_session_auditor_finalize (GObject *object); +static void gdm_session_auditor_class_install_properties (GdmSessionAuditorClass * + auditor_class); + +static void gdm_session_auditor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gdm_session_auditor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +enum { + PROP_0 = 0, + PROP_USERNAME, + PROP_HOSTNAME, + PROP_DISPLAY_DEVICE +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GdmSessionAuditor, gdm_session_auditor, G_TYPE_OBJECT) + +static void +gdm_session_auditor_class_init (GdmSessionAuditorClass *auditor_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (auditor_class); + + object_class->finalize = gdm_session_auditor_finalize; + + gdm_session_auditor_class_install_properties (auditor_class); +} + +static void +gdm_session_auditor_class_install_properties (GdmSessionAuditorClass *auditor_class) +{ + GObjectClass *object_class; + GParamSpec *param_spec; + + object_class = G_OBJECT_CLASS (auditor_class); + object_class->set_property = gdm_session_auditor_set_property; + object_class->get_property = gdm_session_auditor_get_property; + + param_spec = g_param_spec_string ("username", _("Username"), + _("The username"), + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_USERNAME, param_spec); + + param_spec = g_param_spec_string ("hostname", _("Hostname"), + _("The hostname"), + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_HOSTNAME, param_spec); + + param_spec = g_param_spec_string ("display-device", _("Display Device"), + _("The display device"), + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_DISPLAY_DEVICE, param_spec); +} + +static void +gdm_session_auditor_init (GdmSessionAuditor *auditor) +{ +} + +static void +gdm_session_auditor_finalize (GObject *object) +{ + GdmSessionAuditor *auditor; + GdmSessionAuditorPrivate *priv; + GObjectClass *parent_class; + + auditor = GDM_SESSION_AUDITOR (object); + priv = gdm_session_auditor_get_instance_private (auditor); + + g_free (priv->username); + g_free (priv->hostname); + g_free (priv->display_device); + + parent_class = G_OBJECT_CLASS (gdm_session_auditor_parent_class); + + if (parent_class->finalize != NULL) { + parent_class->finalize (object); + } +} + +void +gdm_session_auditor_set_username (GdmSessionAuditor *auditor, + const char *username) +{ + GdmSessionAuditorPrivate *priv; + + g_return_if_fail (GDM_IS_SESSION_AUDITOR (auditor)); + + priv = gdm_session_auditor_get_instance_private (auditor); + + if (username == priv->username) { + return; + } + + if ((username == NULL || priv->username == NULL) || + strcmp (username, priv->username) != 0) { + priv->username = g_strdup (username); + g_object_notify (G_OBJECT (auditor), "username"); + } +} + +static void +gdm_session_auditor_set_hostname (GdmSessionAuditor *auditor, + const char *hostname) +{ + GdmSessionAuditorPrivate *priv; + + g_return_if_fail (GDM_IS_SESSION_AUDITOR (auditor)); + + priv = gdm_session_auditor_get_instance_private (auditor); + priv->hostname = g_strdup (hostname); +} + +static void +gdm_session_auditor_set_display_device (GdmSessionAuditor *auditor, + const char *display_device) +{ + GdmSessionAuditorPrivate *priv; + + g_return_if_fail (GDM_IS_SESSION_AUDITOR (auditor)); + + priv = gdm_session_auditor_get_instance_private (auditor); + priv->display_device = g_strdup (display_device); +} + +static char * +gdm_session_auditor_get_username (GdmSessionAuditor *auditor) +{ + GdmSessionAuditorPrivate *priv; + + priv = gdm_session_auditor_get_instance_private (auditor); + return g_strdup (priv->username); +} + +static char * +gdm_session_auditor_get_hostname (GdmSessionAuditor *auditor) +{ + GdmSessionAuditorPrivate *priv; + + priv = gdm_session_auditor_get_instance_private (auditor); + return g_strdup (priv->hostname); +} + +static char * +gdm_session_auditor_get_display_device (GdmSessionAuditor *auditor) +{ + GdmSessionAuditorPrivate *priv; + + priv = gdm_session_auditor_get_instance_private (auditor); + return g_strdup (priv->display_device); +} + +static void +gdm_session_auditor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmSessionAuditor *auditor; + + auditor = GDM_SESSION_AUDITOR (object); + + switch (prop_id) { + case PROP_USERNAME: + gdm_session_auditor_set_username (auditor, g_value_get_string (value)); + break; + + case PROP_HOSTNAME: + gdm_session_auditor_set_hostname (auditor, g_value_get_string (value)); + break; + + case PROP_DISPLAY_DEVICE: + gdm_session_auditor_set_display_device (auditor, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gdm_session_auditor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmSessionAuditor *auditor; + + auditor = GDM_SESSION_AUDITOR (object); + + switch (prop_id) { + case PROP_USERNAME: + g_value_take_string (value, gdm_session_auditor_get_username (auditor)); + break; + + case PROP_HOSTNAME: + g_value_take_string (value, gdm_session_auditor_get_hostname (auditor)); + break; + + case PROP_DISPLAY_DEVICE: + g_value_take_string (value, gdm_session_auditor_get_display_device (auditor)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +GdmSessionAuditor * +gdm_session_auditor_new (const char *hostname, + const char *display_device) +{ + GdmSessionAuditor *auditor; + + auditor = g_object_new (GDM_TYPE_SESSION_AUDITOR, + "hostname", hostname, + "display-device", display_device, + NULL); + + return auditor; +} + +void +gdm_session_auditor_report_password_changed (GdmSessionAuditor *auditor) +{ + if (GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_password_changed != NULL) { + GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_password_changed (auditor); + } +} + +void +gdm_session_auditor_report_password_change_failure (GdmSessionAuditor *auditor) +{ + if (GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_password_change_failure != NULL) { + GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_password_change_failure (auditor); + } +} + +void +gdm_session_auditor_report_user_accredited (GdmSessionAuditor *auditor) +{ + if (GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_user_accredited != NULL) { + GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_user_accredited (auditor); + } +} + +void +gdm_session_auditor_report_login (GdmSessionAuditor *auditor) +{ + if (GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_login != NULL) { + GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_login (auditor); + } +} + +void +gdm_session_auditor_report_login_failure (GdmSessionAuditor *auditor, + int error_code, + const char *error_message) +{ + if (GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_login_failure != NULL) { + GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_login_failure (auditor, error_code, error_message); + } +} + +void +gdm_session_auditor_report_logout (GdmSessionAuditor *auditor) +{ + if (GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_logout != NULL) { + GDM_SESSION_AUDITOR_GET_CLASS (auditor)->report_logout (auditor); + } +} diff --git a/daemon/gdm-session-auditor.h b/daemon/gdm-session-auditor.h new file mode 100644 index 0000000..dde00f6 --- /dev/null +++ b/daemon/gdm-session-auditor.h @@ -0,0 +1,65 @@ +/* gdm-session-auditor.h - Object for auditing session login/logout + * + * Copyright (C) 2004, 2008 Sun Microsystems, Inc. + * Copyright (C) 2005, 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Written by: Brian A. Cameron <Brian.Cameron@sun.com> + * Gary Winiger <Gary.Winiger@sun.com> + * Ray Strode <rstrode@redhat.com> + * Steve Grubb <sgrubb@redhat.com> + */ +#ifndef GDM_SESSION_AUDITOR_H +#define GDM_SESSION_AUDITOR_H + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_SESSION_AUDITOR (gdm_session_auditor_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GdmSessionAuditor, gdm_session_auditor, GDM, SESSION_AUDITOR, GObject) + +struct _GdmSessionAuditorClass +{ + GObjectClass parent_class; + + void (* report_password_changed) (GdmSessionAuditor *auditor); + void (* report_password_change_failure) (GdmSessionAuditor *auditor); + void (* report_user_accredited) (GdmSessionAuditor *auditor); + void (* report_login) (GdmSessionAuditor *auditor); + void (* report_login_failure) (GdmSessionAuditor *auditor, + int error_code, + const char *error_message); + void (* report_logout) (GdmSessionAuditor *auditor); +}; + +GdmSessionAuditor *gdm_session_auditor_new (const char *hostname, + const char *display_device); +void gdm_session_auditor_set_username (GdmSessionAuditor *auditor, + const char *username); +void gdm_session_auditor_report_password_changed (GdmSessionAuditor *auditor); +void gdm_session_auditor_report_password_change_failure (GdmSessionAuditor *auditor); +void gdm_session_auditor_report_user_accredited (GdmSessionAuditor *auditor); +void gdm_session_auditor_report_login (GdmSessionAuditor *auditor); +void gdm_session_auditor_report_login_failure (GdmSessionAuditor *auditor, + int error_code, + const char *error_message); +void gdm_session_auditor_report_logout (GdmSessionAuditor *auditor); + +G_END_DECLS +#endif /* GDM_SESSION_AUDITOR_H */ diff --git a/daemon/gdm-session-enum-types.c.in b/daemon/gdm-session-enum-types.c.in new file mode 100644 index 0000000..c028690 --- /dev/null +++ b/daemon/gdm-session-enum-types.c.in @@ -0,0 +1,42 @@ +/*** BEGIN file-header ***/ + +#include <glib-object.h> + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +#include "@filename@" +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; + +GType +@enum_name@_get_type (void) +{ + static GType etype = 0; + + if (G_UNLIKELY(etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + } + + return etype; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ + /**/ +/*** END file-tail ***/ diff --git a/daemon/gdm-session-enum-types.h.in b/daemon/gdm-session-enum-types.h.in new file mode 100644 index 0000000..b6934c1 --- /dev/null +++ b/daemon/gdm-session-enum-types.h.in @@ -0,0 +1,24 @@ +/*** BEGIN file-header ***/ +#ifndef GDM_SESSION_ENUM_TYPES_H +#define GDM_SESSION_ENUM_TYPES_H + +#include <glib-object.h> + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* GDM_SESSION_ENUM_TYPES_H */ +/*** END file-tail ***/ diff --git a/daemon/gdm-session-linux-auditor.c b/daemon/gdm-session-linux-auditor.c new file mode 100644 index 0000000..0390ade --- /dev/null +++ b/daemon/gdm-session-linux-auditor.c @@ -0,0 +1,165 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004, 2008 Sun Microsystems, Inc. + * Copyright (C) 2005, 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Written by: Brian A. Cameron <Brian.Cameron@sun.com> + * Gary Winiger <Gary.Winiger@sun.com> + * Ray Strode <rstrode@redhat.com> + * Steve Grubb <sgrubb@redhat.com> + */ +#include "config.h" +#include "gdm-session-linux-auditor.h" + +#include <fcntl.h> +#include <pwd.h> +#include <syslog.h> +#include <unistd.h> + +#include <libaudit.h> + +#include <glib.h> + +#include "gdm-common.h" + +struct _GdmSessionLinuxAuditor +{ + GdmSessionAuditor parent; + int audit_fd; +}; + +static void gdm_session_linux_auditor_finalize (GObject *object); + +G_DEFINE_TYPE (GdmSessionLinuxAuditor, gdm_session_linux_auditor, GDM_TYPE_SESSION_AUDITOR) + +static void +log_user_message (GdmSessionAuditor *auditor, + gint type, + gint result) +{ + GdmSessionLinuxAuditor *linux_auditor; + char buf[512]; + char *username; + char *hostname; + char *display_device; + struct passwd *pw; + + linux_auditor = GDM_SESSION_LINUX_AUDITOR (auditor); + + g_object_get (G_OBJECT (auditor), "username", &username, NULL); + g_object_get (G_OBJECT (auditor), "hostname", &hostname, NULL); + g_object_get (G_OBJECT (auditor), "display-device", &display_device, NULL); + + if (username != NULL) { + gdm_get_pwent_for_name (username, &pw); + } else { + username = g_strdup ("unknown"); + pw = NULL; + } + + if (pw != NULL) { + g_snprintf (buf, sizeof (buf), "uid=%d", pw->pw_uid); + audit_log_user_message (linux_auditor->audit_fd, type, + buf, hostname, NULL, display_device, + result); + } else { + g_snprintf (buf, sizeof (buf), "acct=%s", username); + audit_log_user_message (linux_auditor->audit_fd, type, + buf, hostname, NULL, display_device, + result); + } + + g_free (username); + g_free (hostname); + g_free (display_device); +} + +static void +gdm_session_linux_auditor_report_login (GdmSessionAuditor *auditor) +{ + log_user_message (auditor, AUDIT_USER_LOGIN, 1); +} + +static void +gdm_session_linux_auditor_report_login_failure (GdmSessionAuditor *auditor, + int pam_error_code, + const char *pam_error_string) +{ + log_user_message (auditor, AUDIT_USER_LOGIN, 0); +} + +static void +gdm_session_linux_auditor_report_logout (GdmSessionAuditor *auditor) +{ + log_user_message (auditor, AUDIT_USER_LOGOUT, 1); +} + +static void +gdm_session_linux_auditor_class_init (GdmSessionLinuxAuditorClass *klass) +{ + GObjectClass *object_class; + GdmSessionAuditorClass *auditor_class; + + object_class = G_OBJECT_CLASS (klass); + auditor_class = GDM_SESSION_AUDITOR_CLASS (klass); + + object_class->finalize = gdm_session_linux_auditor_finalize; + + auditor_class->report_login = gdm_session_linux_auditor_report_login; + auditor_class->report_login_failure = gdm_session_linux_auditor_report_login_failure; + auditor_class->report_logout = gdm_session_linux_auditor_report_logout; +} + +static void +gdm_session_linux_auditor_init (GdmSessionLinuxAuditor *auditor) +{ + auditor->audit_fd = audit_open (); +} + +static void +gdm_session_linux_auditor_finalize (GObject *object) +{ + GdmSessionLinuxAuditor *linux_auditor; + GObjectClass *parent_class; + + linux_auditor = GDM_SESSION_LINUX_AUDITOR (object); + + close (linux_auditor->audit_fd); + + parent_class = G_OBJECT_CLASS (gdm_session_linux_auditor_parent_class); + if (parent_class->finalize != NULL) { + parent_class->finalize (object); + } +} + + +GdmSessionAuditor * +gdm_session_linux_auditor_new (const char *hostname, + const char *display_device) +{ + GObject *auditor; + + auditor = g_object_new (GDM_TYPE_SESSION_LINUX_AUDITOR, + "hostname", hostname, + "display-device", display_device, + NULL); + + return GDM_SESSION_AUDITOR (auditor); +} + + diff --git a/daemon/gdm-session-linux-auditor.h b/daemon/gdm-session-linux-auditor.h new file mode 100644 index 0000000..1e55728 --- /dev/null +++ b/daemon/gdm-session-linux-auditor.h @@ -0,0 +1,43 @@ +/* gdm-linux-session-auditor.h - Object for linux auditing of session login/logout + * + * Copyright (C) 2004, 2008 Sun Microsystems, Inc. + * Copyright (C) 2005, 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Written by: Brian A. Cameron <Brian.Cameron@sun.com> + * Gary Winiger <Gary.Winiger@sun.com> + * Ray Strode <rstrode@redhat.com> + * Steve Grubb <sgrubb@redhat.com> + */ +#ifndef GDM_SESSION_LINUX_AUDITOR_H +#define GDM_SESSION_LINUX_AUDITOR_H + +#include <glib.h> +#include <glib-object.h> + +#include "gdm-session-auditor.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_SESSION_LINUX_AUDITOR (gdm_session_linux_auditor_get_type ()) +G_DECLARE_FINAL_TYPE (GdmSessionLinuxAuditor, gdm_session_linux_auditor, GDM, SESSION_LINUX_AUDITOR, GdmSessionAuditor) + +GdmSessionAuditor *gdm_session_linux_auditor_new (const char *hostname, + const char *display_device); + +G_END_DECLS +#endif /* GDM_SESSION_LINUX_AUDITOR_H */ diff --git a/daemon/gdm-session-record.c b/daemon/gdm-session-record.c new file mode 100644 index 0000000..7719d0a --- /dev/null +++ b/daemon/gdm-session-record.c @@ -0,0 +1,305 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Ray Strode <rstrode@redhat.com> + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#if defined(HAVE_UTMPX_H) +#include <utmpx.h> +#endif + +#if defined(HAVE_UTMP_H) +#include <utmp.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include "gdm-session-record.h" + +#ifndef GDM_BAD_SESSION_RECORDS_FILE +#define GDM_BAD_SESSION_RECORDS_FILE "/var/log/btmp" +#endif + +#if !defined(GDM_NEW_SESSION_RECORDS_FILE) +# if defined(WTMPX_FILE) +# define GDM_NEW_SESSION_RECORDS_FILE WTMPX_FILE +# elif defined(_PATH_WTMPX) +# define GDM_NEW_SESSION_RECORDS_FILE _PATH_WTMPX +# elif defined(WTMPX_FILENAME) +# define GDM_NEW_SESSION_RECORDS_FILE WTMPX_FILENAME +# elif defined(WTMP_FILE) +# define GDM_NEW_SESSION_RECORDS_FILE WTMP_FILE +# elif defined(_PATH_WTMP) /* BSD systems */ +# define GDM_NEW_SESSION_RECORDS_FILE _PATH_WTMP +# else +# define GDM_NEW_SESSION_RECORDS_FILE "/var/log/wtmp" +# endif +#endif + +static void +record_set_username (UTMP *u, + const char *username) +{ +#if defined(HAVE_UT_UT_USER) + memccpy (u->ut_user, username, '\0', sizeof (u->ut_user)); + g_debug ("using ut_user %.*s", + (int) sizeof (u->ut_user), + u->ut_user); +#elif defined(HAVE_UT_UT_NAME) + memccpy (u->ut_name, username, '\0', sizeof (u->ut_name)); + g_debug ("using ut_name %.*s", + (int) sizeof (u->ut_name), + u->ut_name); +#endif +} + +static void +record_set_timestamp (UTMP *u) +{ +#if defined(HAVE_UT_UT_TV) + GTimeVal now = { 0 }; + + /* Set time in TV format */ + g_get_current_time (&now); + u->ut_tv.tv_sec = now.tv_sec; + u->ut_tv.tv_usec = now.tv_usec; + g_debug ("using ut_tv time %ld", + (glong) u->ut_tv.tv_sec); +#elif defined(HAVE_UT_UT_TIME) + /* Set time in time format */ + time (&u->ut_time); + g_debug ("using ut_time %ld", + (glong) u->ut_time); +#endif +} + +static void +record_set_pid (UTMP *u, + GPid pid) +{ +#if defined(HAVE_UT_UT_PID) + /* Set pid */ + if (pid != 0) { + u->ut_pid = pid; + } + g_debug ("using ut_pid %d", (int) u->ut_pid); +#endif +} + +static void +record_set_host (UTMP *u, + const char *x11_display_name, + const char *host_name) +{ + char *hostname; + +#if defined(HAVE_UT_UT_HOST) + hostname = NULL; + + /* + * Set ut_host to hostname:$DISPLAY if remote, otherwise set + * to $DISPLAY + */ + if (host_name != NULL + && x11_display_name != NULL + && g_str_has_prefix (x11_display_name, ":")) { + hostname = g_strdup_printf ("%s%s", host_name, x11_display_name); + } else { + hostname = g_strdup (x11_display_name); + } + + if (hostname != NULL) { + memccpy (u->ut_host, hostname, '\0', sizeof (u->ut_host)); + g_debug ("using ut_host %.*s", (int) sizeof (u->ut_host), u->ut_host); +#ifdef HAVE_UT_UT_SYSLEN + u->ut_syslen = MIN (strlen (hostname), sizeof (u->ut_host)); +#endif + g_free (hostname); + } +#endif +} + +static void +record_set_line (UTMP *u, + const char *display_device, + const char *x11_display_name) +{ + /* + * Set ut_line to the device name associated with this display + * but remove the "/dev/" prefix. If no device, then use the + * $DISPLAY value. + */ + if (display_device != NULL + && g_str_has_prefix (display_device, "/dev/")) { + memccpy (u->ut_line, + display_device + strlen ("/dev/"), + '\0', + sizeof (u->ut_line)); + } else if (x11_display_name != NULL) { + memccpy (u->ut_line, + x11_display_name, + '\0', + sizeof (u->ut_line)); + } + + g_debug ("using ut_line %.*s", (int) sizeof (u->ut_line), u->ut_line); +} + +void +gdm_session_record_login (GPid session_pid, + const char *user_name, + const char *host_name, + const char *x11_display_name, + const char *display_device) +{ + UTMP session_record = { 0 }; + + if (x11_display_name == NULL) + x11_display_name = display_device; + + record_set_username (&session_record, user_name); + + g_debug ("Writing login record"); + +#if defined(HAVE_UT_UT_TYPE) + session_record.ut_type = USER_PROCESS; + g_debug ("using ut_type USER_PROCESS"); +#endif + + record_set_timestamp (&session_record); + record_set_pid (&session_record, session_pid); + record_set_host (&session_record, x11_display_name, host_name); + record_set_line (&session_record, display_device, x11_display_name); + + /* Handle wtmp */ + g_debug ("Writing wtmp session record to " GDM_NEW_SESSION_RECORDS_FILE); +#if defined(HAVE_UPDWTMPX) + updwtmpx (GDM_NEW_SESSION_RECORDS_FILE, &session_record); +#elif defined(HAVE_UPDWTMP) + updwtmp (GDM_NEW_SESSION_RECORDS_FILE, &session_record); +#elif defined(HAVE_LOGWTMP) && defined(HAVE_UT_UT_HOST) +#if defined(HAVE_UT_UT_USER) + logwtmp (session_record.ut_line, session_record.ut_user, session_record.ut_host); +#elif defined(HAVE_UT_UT_NAME) + logwtmp (session_record.ut_line, session_record.ut_name, session_record.ut_host); +#endif +#endif + + /* Handle utmp */ +#if defined(HAVE_GETUTXENT) + g_debug ("Adding or updating utmp record for login"); + setutxent(); + pututxline (&session_record); + endutxent(); +#elif defined(HAVE_LOGIN) + login (&session_record); +#endif +} + +void +gdm_session_record_logout (GPid session_pid, + const char *user_name, + const char *host_name, + const char *x11_display_name, + const char *display_device) +{ + UTMP session_record = { 0 }; + + if (x11_display_name == NULL) + x11_display_name = display_device; + + g_debug ("Writing logout record"); + +#if defined(HAVE_UT_UT_TYPE) + session_record.ut_type = DEAD_PROCESS; + g_debug ("using ut_type DEAD_PROCESS"); +#endif + + record_set_timestamp (&session_record); + record_set_pid (&session_record, session_pid); + record_set_host (&session_record, x11_display_name, host_name); + record_set_line (&session_record, display_device, x11_display_name); + + /* Handle wtmp */ + g_debug ("Writing wtmp logout record to " GDM_NEW_SESSION_RECORDS_FILE); +#if defined(HAVE_UPDWTMPX) + updwtmpx (GDM_NEW_SESSION_RECORDS_FILE, &session_record); +#elif defined (HAVE_UPDWTMP) + updwtmp (GDM_NEW_SESSION_RECORDS_FILE, &session_record); +#elif defined(HAVE_LOGWTMP) + logwtmp (session_record.ut_line, "", ""); +#endif + + /* Handle utmp */ +#if defined(HAVE_GETUTXENT) + g_debug ("Adding or updating utmp record for logout"); + setutxent(); + pututxline (&session_record); + endutxent(); +#elif defined(HAVE_LOGOUT) + logout (session_record.ut_line); +#endif +} + +void +gdm_session_record_failed (GPid session_pid, + const char *user_name, + const char *host_name, + const char *x11_display_name, + const char *display_device) +{ + UTMP session_record = { 0 }; + + if (x11_display_name == NULL) + x11_display_name = display_device; + + record_set_username (&session_record, user_name); + + g_debug ("Writing failed session attempt record"); + +#if defined(HAVE_UT_UT_TYPE) + session_record.ut_type = USER_PROCESS; + g_debug ("using ut_type USER_PROCESS"); +#endif + + record_set_timestamp (&session_record); + record_set_pid (&session_record, session_pid); + record_set_host (&session_record, x11_display_name, host_name); + record_set_line (&session_record, display_device, x11_display_name); + +#if defined(HAVE_UPDWTMPX) || defined(HAVE_UPDWTMP) + /* Handle btmp */ + g_debug ("Writing btmp failed session attempt record to " + GDM_BAD_SESSION_RECORDS_FILE); +#endif + +#if defined(HAVE_UPDWTMPX) + updwtmpx (GDM_BAD_SESSION_RECORDS_FILE, &session_record); +#elif defined(HAVE_UPDWTMP) + updwtmp(GDM_BAD_SESSION_RECORDS_FILE, &session_record); +#endif + +} diff --git a/daemon/gdm-session-record.h b/daemon/gdm-session-record.h new file mode 100644 index 0000000..3c53268 --- /dev/null +++ b/daemon/gdm-session-record.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Ray Strode <rstrode@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef __GDM_SESSION_RECORD_H +#define __GDM_SESSION_RECORD_H + +#include <glib.h> + +G_BEGIN_DECLS + +void +gdm_session_record_login (GPid session_pid, + const char *user_name, + const char *host_name, + const char *x11_display_name, + const char *display_device); +void +gdm_session_record_logout (GPid session_pid, + const char *user_name, + const char *host_name, + const char *x11_display_name, + const char *display_device); +void +gdm_session_record_failed (GPid session_pid, + const char *user_name, + const char *host_name, + const char *x11_display_name, + const char *display_device); + + +G_END_DECLS + +#endif /* GDM_SESSION_RECORD_H */ diff --git a/daemon/gdm-session-settings.c b/daemon/gdm-session-settings.c new file mode 100644 index 0000000..484a3b5 --- /dev/null +++ b/daemon/gdm-session-settings.c @@ -0,0 +1,361 @@ +/* gdm-session-settings.c - Loads session and language from ~/.dmrc + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Written by: Ray Strode <rstrode@redhat.com> + */ +#include "config.h" +#include "gdm-session-settings.h" + +#include <errno.h> +#include <pwd.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include <glib.h> +#include <glib-object.h> +#include <glib/gi18n.h> + +#include <act/act-user-manager.h> + +struct _GdmSessionSettingsPrivate +{ + ActUserManager *user_manager; + ActUser *user; + char *session_name; + char *language_name; +}; + +static void gdm_session_settings_finalize (GObject *object); +static void gdm_session_settings_class_install_properties (GdmSessionSettingsClass * + settings_class); + +static void gdm_session_settings_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gdm_session_settings_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +enum { + PROP_0 = 0, + PROP_SESSION_NAME, + PROP_LANGUAGE_NAME, + PROP_IS_LOADED +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GdmSessionSettings, + gdm_session_settings, + G_TYPE_OBJECT) + +static void +gdm_session_settings_class_init (GdmSessionSettingsClass *settings_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (settings_class); + + object_class->finalize = gdm_session_settings_finalize; + + gdm_session_settings_class_install_properties (settings_class); +} + +static void +gdm_session_settings_class_install_properties (GdmSessionSettingsClass *settings_class) +{ + GObjectClass *object_class; + GParamSpec *param_spec; + + object_class = G_OBJECT_CLASS (settings_class); + object_class->set_property = gdm_session_settings_set_property; + object_class->get_property = gdm_session_settings_get_property; + + param_spec = g_param_spec_string ("session-name", "Session Name", + "The name of the session", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SESSION_NAME, param_spec); + + param_spec = g_param_spec_string ("language-name", "Language Name", + "The name of the language", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_LANGUAGE_NAME, param_spec); + + param_spec = g_param_spec_boolean ("is-loaded", NULL, NULL, + FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_IS_LOADED, param_spec); +} + +static void +gdm_session_settings_init (GdmSessionSettings *settings) +{ + settings->priv = G_TYPE_INSTANCE_GET_PRIVATE (settings, + GDM_TYPE_SESSION_SETTINGS, + GdmSessionSettingsPrivate); + + settings->priv->user_manager = act_user_manager_get_default (); + +} + +static void +gdm_session_settings_finalize (GObject *object) +{ + GdmSessionSettings *settings; + GObjectClass *parent_class; + + settings = GDM_SESSION_SETTINGS (object); + + if (settings->priv->user != NULL) { + g_object_unref (settings->priv->user); + } + + g_free (settings->priv->session_name); + g_free (settings->priv->language_name); + + parent_class = G_OBJECT_CLASS (gdm_session_settings_parent_class); + + if (parent_class->finalize != NULL) { + parent_class->finalize (object); + } +} + +void +gdm_session_settings_set_language_name (GdmSessionSettings *settings, + const char *language_name) +{ + g_return_if_fail (GDM_IS_SESSION_SETTINGS (settings)); + + if (settings->priv->language_name == NULL || + strcmp (settings->priv->language_name, language_name) != 0) { + settings->priv->language_name = g_strdup (language_name); + g_object_notify (G_OBJECT (settings), "language-name"); + } +} + +void +gdm_session_settings_set_session_name (GdmSessionSettings *settings, + const char *session_name) +{ + g_return_if_fail (GDM_IS_SESSION_SETTINGS (settings)); + + if (settings->priv->session_name == NULL || + strcmp (settings->priv->session_name, session_name) != 0) { + settings->priv->session_name = g_strdup (session_name); + g_object_notify (G_OBJECT (settings), "session-name"); + } +} + +char * +gdm_session_settings_get_language_name (GdmSessionSettings *settings) +{ + g_return_val_if_fail (GDM_IS_SESSION_SETTINGS (settings), NULL); + return g_strdup (settings->priv->language_name); +} + +char * +gdm_session_settings_get_session_name (GdmSessionSettings *settings) +{ + g_return_val_if_fail (GDM_IS_SESSION_SETTINGS (settings), NULL); + return g_strdup (settings->priv->session_name); +} + +static void +gdm_session_settings_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmSessionSettings *settings; + + settings = GDM_SESSION_SETTINGS (object); + + switch (prop_id) { + case PROP_LANGUAGE_NAME: + gdm_session_settings_set_language_name (settings, g_value_get_string (value)); + break; + + case PROP_SESSION_NAME: + gdm_session_settings_set_session_name (settings, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gdm_session_settings_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmSessionSettings *settings; + + settings = GDM_SESSION_SETTINGS (object); + + switch (prop_id) { + case PROP_SESSION_NAME: + g_value_set_string (value, settings->priv->session_name); + break; + + case PROP_LANGUAGE_NAME: + g_value_set_string (value, settings->priv->language_name); + break; + + case PROP_IS_LOADED: + g_value_set_boolean (value, gdm_session_settings_is_loaded (settings)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +GdmSessionSettings * +gdm_session_settings_new (void) +{ + GdmSessionSettings *settings; + + settings = g_object_new (GDM_TYPE_SESSION_SETTINGS, + NULL); + + return settings; +} + +gboolean +gdm_session_settings_is_loaded (GdmSessionSettings *settings) +{ + if (settings->priv->user == NULL) { + return FALSE; + } + + return act_user_is_loaded (settings->priv->user); +} + +static void +load_settings_from_user (GdmSessionSettings *settings) +{ + const char *session_name; + const char *language_name; + + if (!act_user_is_loaded (settings->priv->user)) { + g_warning ("GdmSessionSettings: trying to load user settings from unloaded user"); + return; + } + + session_name = act_user_get_x_session (settings->priv->user); + g_debug ("GdmSessionSettings: saved session is %s", session_name); + + if (session_name != NULL) { + gdm_session_settings_set_session_name (settings, session_name); + } + + language_name = act_user_get_language (settings->priv->user); + + g_debug ("GdmSessionSettings: saved language is %s", language_name); + if (language_name != NULL) { + gdm_session_settings_set_language_name (settings, language_name); + } + + g_object_notify (G_OBJECT (settings), "is-loaded"); +} + +static void +on_user_is_loaded_changed (ActUser *user, + GParamSpec *pspec, + GdmSessionSettings *settings) +{ + if (act_user_is_loaded (settings->priv->user)) { + load_settings_from_user (settings); + g_signal_handlers_disconnect_by_func (G_OBJECT (settings->priv->user), + G_CALLBACK (on_user_is_loaded_changed), + settings); + } +} + +gboolean +gdm_session_settings_load (GdmSessionSettings *settings, + const char *username) +{ + ActUser *old_user; + + g_return_val_if_fail (settings != NULL, FALSE); + g_return_val_if_fail (username != NULL, FALSE); + g_return_val_if_fail (!gdm_session_settings_is_loaded (settings), FALSE); + + if (settings->priv->user != NULL) { + old_user = settings->priv->user; + + g_signal_handlers_disconnect_by_func (G_OBJECT (settings->priv->user), + G_CALLBACK (on_user_is_loaded_changed), + settings); + } else { + old_user = NULL; + } + + settings->priv->user = act_user_manager_get_user (settings->priv->user_manager, + username); + + g_clear_object (&old_user); + + if (!act_user_is_loaded (settings->priv->user)) { + g_signal_connect (settings->priv->user, + "notify::is-loaded", + G_CALLBACK (on_user_is_loaded_changed), + settings); + return FALSE; + } + + load_settings_from_user (settings); + + return TRUE; +} + +gboolean +gdm_session_settings_save (GdmSessionSettings *settings, + const char *username) +{ + ActUser *user; + + g_return_val_if_fail (GDM_IS_SESSION_SETTINGS (settings), FALSE); + g_return_val_if_fail (username != NULL, FALSE); + g_return_val_if_fail (gdm_session_settings_is_loaded (settings), FALSE); + + user = act_user_manager_get_user (settings->priv->user_manager, + username); + + + if (!act_user_is_loaded (user)) { + g_object_unref (user); + return FALSE; + } + + if (settings->priv->session_name != NULL) { + act_user_set_x_session (user, settings->priv->session_name); + } + + if (settings->priv->language_name != NULL) { + act_user_set_language (user, settings->priv->language_name); + } + g_object_unref (user); + + return TRUE; +} diff --git a/daemon/gdm-session-settings.h b/daemon/gdm-session-settings.h new file mode 100644 index 0000000..20946bf --- /dev/null +++ b/daemon/gdm-session-settings.h @@ -0,0 +1,69 @@ +/* gdm-session-settings.h - Object for auditing session login/logout + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Written by: Ray Strode <rstrode@redhat.com> + */ +#ifndef GDM_SESSION_SETTINGS_H +#define GDM_SESSION_SETTINGS_H + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS +#define GDM_TYPE_SESSION_SETTINGS (gdm_session_settings_get_type ()) +#define GDM_SESSION_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_SESSION_SETTINGS, GdmSessionSettings)) +#define GDM_SESSION_SETTINGS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_SESSION_SETTINGS, GdmSessionSettingsClass)) +#define GDM_IS_SESSION_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_SESSION_SETTINGS)) +#define GDM_IS_SESSION_SETTINGS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_SESSION_SETTINGS)) +#define GDM_SESSION_SETTINGS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_SESSION_SETTINGS, GdmSessionSettingsClass)) +#define GDM_SESSION_SETTINGS_ERROR (gdm_session_settings_error_quark ()) +typedef struct _GdmSessionSettings GdmSessionSettings; +typedef struct _GdmSessionSettingsClass GdmSessionSettingsClass; +typedef struct _GdmSessionSettingsPrivate GdmSessionSettingsPrivate; + +struct _GdmSessionSettings +{ + GObject parent; + + /*< private > */ + GdmSessionSettingsPrivate *priv; +}; + +struct _GdmSessionSettingsClass +{ + GObjectClass parent_class; +}; + +GType gdm_session_settings_get_type (void); +GdmSessionSettings *gdm_session_settings_new (void); + +gboolean gdm_session_settings_load (GdmSessionSettings *settings, + const char *username); +gboolean gdm_session_settings_save (GdmSessionSettings *settings, + const char *username); +gboolean gdm_session_settings_is_loaded (GdmSessionSettings *settings); +char *gdm_session_settings_get_language_name (GdmSessionSettings *settings); +char *gdm_session_settings_get_session_name (GdmSessionSettings *settings); +void gdm_session_settings_set_language_name (GdmSessionSettings *settings, + const char *language_name); +void gdm_session_settings_set_session_name (GdmSessionSettings *settings, + const char *session_name); + +G_END_DECLS +#endif /* GDM_SESSION_SETTINGS_H */ diff --git a/daemon/gdm-session-solaris-auditor.c b/daemon/gdm-session-solaris-auditor.c new file mode 100644 index 0000000..f632804 --- /dev/null +++ b/daemon/gdm-session-solaris-auditor.c @@ -0,0 +1,387 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004, 2008 Sun Microsystems, Inc. + * Copyright (C) 2005, 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Written by: Brian A. Cameron <Brian.Cameron@sun.com> + * Gary Winiger <Gary.Winiger@sun.com> + * Ray Strode <rstrode@redhat.com> + * Steve Grubb <sgrubb@redhat.com> + */ +#include "config.h" +#include "gdm-session-solaris-auditor.h" + +#include <syslog.h> +#include <security/pam_appl.h> +#include <pwd.h> + +#include <fcntl.h> +#include <bsm/adt.h> +#include <bsm/adt_event.h> + +#include <glib.h> +#include <glib-object.h> +#include <glib/gi18n.h> + +struct GdmSessionSolarisAuditor +{ + GdmSessionAuditor parent; + + adt_session_data_t *audit_session_handle; + + guint password_change_initiated : 1; + guint password_changed : 1; + guint user_accredited : 1; + + /* cached values to prevent repeated calls + * to getpwnam + */ + char *username; + uid_t uid; + gid_t gid; +}; + +static void gdm_session_solaris_auditor_finalize (GObject *object); + +G_DEFINE_TYPE (GdmSessionSolarisAuditor, gdm_session_solaris_auditor, GDM_TYPE_SESSION_AUDITOR) + +static void +gdm_session_solaris_auditor_report_password_changed (GdmSessionAuditor *auditor) +{ + GdmSessionSolarisAuditor *solaris_auditor; + + solaris_auditor = GDM_SESSION_SOLARIS_AUDITOR (auditor); + solaris_auditor->password_change_initiated = TRUE; + solaris_auditor->password_changed = TRUE; +} + +static void +gdm_session_solaris_auditor_report_password_change_failure (GdmSessionAuditor *auditor) +{ + GdmSessionSolarisAuditor *solaris_auditor; + + solaris_auditor = GDM_SESSION_SOLARIS_AUDITOR (auditor); + solaris_auditor->password_change_initiated = TRUE; + solaris_auditor->password_changed = FALSE; +} + +static void +gdm_session_solaris_auditor_report_user_accredited (GdmSessionAuditor *auditor) +{ + GdmSessionSolarisAuditor *solaris_auditor; + + solaris_auditor = GDM_SESSION_SOLARIS_AUDITOR (auditor); + solaris_auditor->user_accredited = TRUE; +} + +static void +gdm_session_solaris_auditor_report_login (GdmSessionAuditor *auditor) +{ + GdmSessionSolarisAuditor *solaris_auditor; + adt_session_data_t *adt_ah; /* Audit session handle */ + adt_event_data_t *event; /* Event to generate */ + + solaris_auditor = GDM_SESSION_SOLARIS_AUDITOR (auditor); + + g_return_if_fail (solaris_auditor->username != NULL); + + adt_ah = NULL; + if (adt_start_session (&adt_ah, NULL, ADT_USE_PROC_DATA) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_start_session (ADT_login): %m"); + goto cleanup; + } + + if (adt_set_user (adt_ah, solaris_auditor->uid, + solaris_auditor->gid, solaris_auditor->uid, + solaris_auditor->gid, NULL, ADT_USER) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_set_user (ADT_login, %s): %m", + solaris_auditor->username); + } + + event = adt_alloc_event (adt_ah, ADT_login); + if (event == NULL) { + syslog (LOG_AUTH | LOG_ALERT, "adt_alloc_event (ADT_login): %m"); + } else if (adt_put_event (event, ADT_SUCCESS, ADT_SUCCESS) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_put_event (ADT_login, ADT_SUCCESS): %m"); + } + + if (solaris_auditor->password_changed) { + + g_assert (solaris_auditor->password_change_initiated); + + /* Also audit password change */ + adt_free_event (event); + event = adt_alloc_event (adt_ah, ADT_passwd); + if (event == NULL) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_alloc_event (ADT_passwd): %m"); + } else if (adt_put_event (event, ADT_SUCCESS, + ADT_SUCCESS) != 0) { + + syslog (LOG_AUTH | LOG_ALERT, + "adt_put_event (ADT_passwd, ADT_SUCCESS): %m"); + } + } + + adt_free_event (event); + +cleanup: + solaris_auditor->audit_session_handle = adt_ah; +} + +static void +gdm_session_solaris_auditor_report_login_failure (GdmSessionAuditor *auditor, + int pam_error_code, + const char *pam_error_string) +{ + GdmSessionSolarisAuditor *solaris_auditor; + char *hostname; + char *display_device; + adt_session_data_t *ah; /* Audit session handle */ + adt_event_data_t *event; /* Event to generate */ + adt_termid_t *tid; /* Terminal ID for failures */ + + solaris_auditor = GDM_SESSION_SOLARIS_AUDITOR (auditor); + g_object_get (G_OBJECT (auditor), + "hostname", &hostname, + "display-device", &display_device, NULL); + + if (solaris_auditor->user_accredited) { + if (adt_start_session (&ah, NULL, ADT_USE_PROC_DATA) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_start_session (ADT_login, ADT_FAILURE): %m"); + goto cleanup; + } + } else { + if (adt_start_session (&ah, NULL, 0) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_start_session (ADT_login, ADT_FAILURE): %m"); + goto cleanup; + } + + /* If display is on console or VT */ + if (hostname != NULL && hostname[0] != '\0') { + /* Login from a remote host */ + if (adt_load_hostname (hostname, &tid) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_loadhostname (%s): %m", hostname); + } + } else { + /* login from the local host */ + if (adt_load_ttyname (display_device, &tid) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_loadhostname (localhost): %m"); + } + } + + if (adt_set_user (ah, + solaris_auditor->username != NULL ? solaris_auditor->uid : ADT_NO_ATTRIB, + solaris_auditor->username != NULL ? solaris_auditor->gid : ADT_NO_ATTRIB, + solaris_auditor->username != NULL ? solaris_auditor->uid : ADT_NO_ATTRIB, + solaris_auditor->username != NULL ? solaris_auditor->gid : ADT_NO_ATTRIB, + tid, ADT_NEW) != 0) { + + syslog (LOG_AUTH | LOG_ALERT, + "adt_set_user (%s): %m", + solaris_auditor->username != NULL ? solaris_auditor->username : "ADT_NO_ATTRIB"); + } + } + + event = adt_alloc_event (ah, ADT_login); + + if (event == NULL) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_alloc_event (ADT_login, ADT_FAILURE): %m"); + goto done; + } else if (adt_put_event (event, ADT_FAILURE, + ADT_FAIL_PAM + pam_error_code) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_put_event (ADT_login (ADT_FAIL, %s): %m", + pam_error_string); + } + + if (solaris_auditor->password_change_initiated) { + /* Also audit password change */ + adt_free_event (event); + + event = adt_alloc_event (ah, ADT_passwd); + if (event == NULL) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_alloc_event (ADT_passwd): %m"); + goto done; + } + + if (solaris_auditor->password_changed) { + if (adt_put_event (event, ADT_SUCCESS, + ADT_SUCCESS) != 0) { + + syslog (LOG_AUTH | LOG_ALERT, + "adt_put_event (ADT_passwd, ADT_SUCCESS): " + "%m"); + } + } else { + if (adt_put_event (event, ADT_FAILURE, + ADT_FAIL_PAM + pam_error_code) != 0) { + + syslog (LOG_AUTH | LOG_ALERT, + "adt_put_event (ADT_passwd, ADT_FAILURE): " + "%m"); + } + } + } + adt_free_event (event); + +done: + /* Reset process audit state. this process is being reused.*/ + if ((adt_set_user (ah, ADT_NO_AUDIT, ADT_NO_AUDIT, ADT_NO_AUDIT, + ADT_NO_AUDIT, NULL, ADT_NEW) != 0) || + (adt_set_proc (ah) != 0)) { + + syslog (LOG_AUTH | LOG_ALERT, + "adt_put_event (ADT_login (ADT_FAILURE reset, %m)"); + } + (void) adt_end_session (ah); + +cleanup: + g_free (hostname); + g_free (display_device); +} + +static void +gdm_session_solaris_auditor_report_logout (GdmSessionAuditor *auditor) +{ + GdmSessionSolarisAuditor *solaris_auditor; + adt_session_data_t *adt_ah; /* Audit session handle */ + adt_event_data_t *event; /* Event to generate */ + + solaris_auditor = GDM_SESSION_SOLARIS_AUDITOR (auditor); + + adt_ah = solaris_auditor->audit_session_handle; + + event = adt_alloc_event (adt_ah, ADT_logout); + if (event == NULL) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_alloc_event (ADT_logout): %m"); + } else if (adt_put_event (event, ADT_SUCCESS, ADT_SUCCESS) != 0) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_put_event (ADT_logout, ADT_SUCCESS): %m"); + } + + adt_free_event (event); + + /* Reset process audit state. this process is being reused. */ + if ((adt_set_user (adt_ah, ADT_NO_AUDIT, ADT_NO_AUDIT, ADT_NO_AUDIT, + ADT_NO_AUDIT, NULL, ADT_NEW) != 0) || + (adt_set_proc (adt_ah) != 0)) { + syslog (LOG_AUTH | LOG_ALERT, + "adt_set_proc (ADT_logout reset): %m"); + } + + (void) adt_end_session (adt_ah); + solaris_auditor->audit_session_handle = NULL; +} + +static void +gdm_session_solaris_auditor_class_init (GdmSessionSolarisAuditorClass *klass) +{ + GObjectClass *object_class; + GdmSessionAuditorClass *auditor_class; + + object_class = G_OBJECT_CLASS (klass); + auditor_class = GDM_SESSION_AUDITOR_CLASS (klass); + + object_class->finalize = gdm_session_solaris_auditor_finalize; + + auditor_class->report_password_changed = gdm_session_solaris_auditor_report_password_changed; + auditor_class->report_password_change_failure = gdm_session_solaris_auditor_report_password_change_failure; + auditor_class->report_user_accredited = gdm_session_solaris_auditor_report_user_accredited; + auditor_class->report_login = gdm_session_solaris_auditor_report_login; + auditor_class->report_login_failure = gdm_session_solaris_auditor_report_login_failure; + auditor_class->report_logout = gdm_session_solaris_auditor_report_logout; +} + +static void +on_username_set (GdmSessionSolarisAuditor *auditor) +{ + char *username; + struct passwd *passwd_entry; + + g_object_get (G_OBJECT (auditor), "username", &username, NULL); + + gdm_get_pwent_for_name (username, &passwd_entry); + + if (passwd_entry != NULL) { + auditor->uid = passwd_entry->pw_uid; + auditor->gid = passwd_entry->pw_gid; + auditor->username = g_strdup (passwd_entry->pw_name); + } else { + g_free (auditor->username); + auditor->username = NULL; + auditor->uid = (uid_t) -1; + auditor->gid = (gid_t) -1; + } + + g_free (username); +} + +static void +gdm_session_solaris_auditor_init (GdmSessionSolarisAuditor *auditor) +{ + g_signal_connect (G_OBJECT (auditor), "notify::username", + G_CALLBACK (on_username_set), NULL); + + auditor->uid = (uid_t) -1; + auditor->gid = (gid_t) -1; +} + +static void +gdm_session_solaris_auditor_finalize (GObject *object) +{ + GdmSessionSolarisAuditor *solaris_auditor; + GObjectClass *parent_class; + + solaris_auditor = GDM_SESSION_SOLARIS_AUDITOR (object); + + g_free (solaris_auditor->username); + solaris_auditor->username = NULL; + + parent_class = G_OBJECT_CLASS (gdm_session_solaris_auditor_parent_class); + + if (parent_class->finalize != NULL) { + parent_class->finalize (object); + } +} + +GdmSessionAuditor * +gdm_session_solaris_auditor_new (const char *hostname, + const char *display_device) +{ + GObject *auditor; + + auditor = g_object_new (GDM_TYPE_SESSION_SOLARIS_AUDITOR, + "hostname", hostname, + "display-device", display_device, + NULL); + + return GDM_SESSION_AUDITOR (auditor); +} + + diff --git a/daemon/gdm-session-solaris-auditor.h b/daemon/gdm-session-solaris-auditor.h new file mode 100644 index 0000000..dd58532 --- /dev/null +++ b/daemon/gdm-session-solaris-auditor.h @@ -0,0 +1,42 @@ +/* gdm-solaris-session-auditor.h - Object for solaris auditing of session login/logout + * + * Copyright (C) 2004, 2008 Sun Microsystems, Inc. + * Copyright (C) 2005, 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Written by: Brian A. Cameron <Brian.Cameron@sun.com> + * Gary Winiger <Gary.Winiger@sun.com> + * Ray Strode <rstrode@redhat.com> + * Steve Grubb <sgrubb@redhat.com> + */ +#ifndef GDM_SESSION_SOLARIS_AUDITOR_H +#define GDM_SESSION_SOLARIS_AUDITOR_H + +#include <glib.h> +#include <glib-object.h> + +#include "gdm-session-auditor.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_SESSION_SOLARIS_AUDITOR (gdm_session_solaris_auditor_get_type ()) +G_DECLARE_FINAL_TYPE (GdmSessionSolarisAuditor, gdm_session_solaris_auditor, GDM, SESSION_SOLARIS_AUDITOR, GdmSessionAuditor) + +GdmSessionAuditor *gdm_session_solaris_auditor_new (const char *hostname, + const char *display_device); +G_END_DECLS +#endif /* GDM_SESSION_SOLARIS_AUDITOR_H */ diff --git a/daemon/gdm-session-worker-common.c b/daemon/gdm-session-worker-common.c new file mode 100644 index 0000000..a1c90c9 --- /dev/null +++ b/daemon/gdm-session-worker-common.c @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Ray Strode <rstrode@redhat.com> + * Copyright (C) 2012 Jasper St. Pierre <jstpierre@mecheye.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "config.h" + +#include <gio/gio.h> + +#include "gdm-session-worker-common.h" + +static const GDBusErrorEntry gdm_session_worker_error_entries[] = { + { GDM_SESSION_WORKER_ERROR_GENERIC , "org.gnome.DisplayManager.SessionWorker.Error.Generic", }, + { GDM_SESSION_WORKER_ERROR_WITH_SESSION_COMMAND , "org.gnome.DisplayManager.SessionWorker.Error.WithSessionCommand" }, + { GDM_SESSION_WORKER_ERROR_FORKING , "org.gnome.DisplayManager.SessionWorker.Error.Forking" }, + { GDM_SESSION_WORKER_ERROR_OPENING_MESSAGE_PIPE , "org.gnome.DisplayManager.SessionWorker.Error.OpeningMessagePipe" }, + { GDM_SESSION_WORKER_ERROR_COMMUNICATING , "org.gnome.DisplayManager.SessionWorker.Error.Communicating" }, + { GDM_SESSION_WORKER_ERROR_WORKER_DIED , "org.gnome.DisplayManager.SessionWorker.Error.WorkerDied" }, + { GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE , "org.gnome.DisplayManager.SessionWorker.Error.ServiceUnavailable" }, + { GDM_SESSION_WORKER_ERROR_AUTHENTICATING , "org.gnome.DisplayManager.SessionWorker.Error.Authenticating" }, + { GDM_SESSION_WORKER_ERROR_AUTHORIZING , "org.gnome.DisplayManager.SessionWorker.Error.Authorizing" }, + { GDM_SESSION_WORKER_ERROR_OPENING_LOG_FILE , "org.gnome.DisplayManager.SessionWorker.Error.OpeningLogFile" }, + { GDM_SESSION_WORKER_ERROR_OPENING_SESSION , "org.gnome.DisplayManager.SessionWorker.Error.OpeningSession" }, + { GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS , "org.gnome.DisplayManager.SessionWorker.Error.GivingCredentials" }, + { GDM_SESSION_WORKER_ERROR_WRONG_STATE , "org.gnome.DisplayManager.SessionWorker.Error.WrongState" }, + { GDM_SESSION_WORKER_ERROR_OUTSTANDING_REQUEST , "org.gnome.DisplayManager.SessionWorker.Error.OutstandingRequest" }, + { GDM_SESSION_WORKER_ERROR_IN_REAUTH_SESSION , "org.gnome.DisplayManager.SessionWorker.Error.InReauthSession" } +}; + +GQuark +gdm_session_worker_error_quark (void) +{ + static volatile gsize error_quark = 0; + + g_dbus_error_register_error_domain ("gdm-session-worker-error-quark", + &error_quark, + gdm_session_worker_error_entries, + G_N_ELEMENTS (gdm_session_worker_error_entries)); + + return (GQuark) error_quark; +} diff --git a/daemon/gdm-session-worker-common.h b/daemon/gdm-session-worker-common.h new file mode 100644 index 0000000..a55e2b2 --- /dev/null +++ b/daemon/gdm-session-worker-common.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Ray Strode <rstrode@redhat.com> + * Copyright (C) 2012 Jasper St. Pierre <jstpierre@mecheye.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef __GDM_SESSION_WORKER_COMMON_H +#define __GDM_SESSION_WORKER_COMMON_H + +#include <glib-object.h> + +#define GDM_SESSION_WORKER_ERROR (gdm_session_worker_error_quark ()) + +GQuark gdm_session_worker_error_quark (void); + +typedef enum _GdmSessionWorkerError { + GDM_SESSION_WORKER_ERROR_GENERIC = 0, + GDM_SESSION_WORKER_ERROR_WITH_SESSION_COMMAND, + GDM_SESSION_WORKER_ERROR_FORKING, + GDM_SESSION_WORKER_ERROR_OPENING_MESSAGE_PIPE, + GDM_SESSION_WORKER_ERROR_COMMUNICATING, + GDM_SESSION_WORKER_ERROR_WORKER_DIED, + GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + GDM_SESSION_WORKER_ERROR_AUTHORIZING, + GDM_SESSION_WORKER_ERROR_OPENING_LOG_FILE, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + GDM_SESSION_WORKER_ERROR_WRONG_STATE, + GDM_SESSION_WORKER_ERROR_OUTSTANDING_REQUEST, + GDM_SESSION_WORKER_ERROR_IN_REAUTH_SESSION, +} GdmSessionWorkerError; + +#endif /* GDM_SESSION_WORKER_COMMON_H */ diff --git a/daemon/gdm-session-worker-enum-types.c.in b/daemon/gdm-session-worker-enum-types.c.in new file mode 100644 index 0000000..c028690 --- /dev/null +++ b/daemon/gdm-session-worker-enum-types.c.in @@ -0,0 +1,42 @@ +/*** BEGIN file-header ***/ + +#include <glib-object.h> + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +#include "@filename@" +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; + +GType +@enum_name@_get_type (void) +{ + static GType etype = 0; + + if (G_UNLIKELY(etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + } + + return etype; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ + /**/ +/*** END file-tail ***/ diff --git a/daemon/gdm-session-worker-enum-types.h.in b/daemon/gdm-session-worker-enum-types.h.in new file mode 100644 index 0000000..64f4b4b --- /dev/null +++ b/daemon/gdm-session-worker-enum-types.h.in @@ -0,0 +1,24 @@ +/*** BEGIN file-header ***/ +#ifndef GDM_SESSION_WORKER_ENUM_TYPES_H +#define GDM_SESSION_WORKER_ENUM_TYPES_H + +#include <glib-object.h> + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* GDM_SESSION_WORKER_ENUM_TYPES_H */ +/*** END file-tail ***/ diff --git a/daemon/gdm-session-worker-job.c b/daemon/gdm-session-worker-job.c new file mode 100644 index 0000000..3ac018f --- /dev/null +++ b/daemon/gdm-session-worker-job.c @@ -0,0 +1,590 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> +#include <signal.h> +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#include <systemd/sd-daemon.h> + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include <systemd/sd-journal.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-common.h" + +#include "gdm-session-worker-job.h" + +extern char **environ; + +struct _GdmSessionWorkerJob +{ + GObject parent; + + char *command; + GPid pid; + gboolean for_reauth; + + guint child_watch_id; + + char *server_address; + char **environment; +}; + +enum { + PROP_0, + PROP_SERVER_ADDRESS, + PROP_ENVIRONMENT, + PROP_FOR_REAUTH, +}; + +enum { + STARTED, + EXITED, + DIED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_session_worker_job_class_init (GdmSessionWorkerJobClass *klass); +static void gdm_session_worker_job_init (GdmSessionWorkerJob *session_worker_job); +static void gdm_session_worker_job_finalize (GObject *object); + +G_DEFINE_TYPE (GdmSessionWorkerJob, gdm_session_worker_job, G_TYPE_OBJECT) + +static void +session_worker_job_setup_journal_fds (void) +{ +#ifdef ENABLE_SYSTEMD_JOURNAL + if (sd_booted () > 0) { + const char *identifier = "gdm-session-worker"; + int out, err; + + out = sd_journal_stream_fd (identifier, LOG_INFO, FALSE); + if (out < 0) + return; + + err = sd_journal_stream_fd (identifier, LOG_WARNING, FALSE); + if (err < 0) { + close (out); + return; + } + + VE_IGNORE_EINTR (dup2 (out, 1)); + VE_IGNORE_EINTR (dup2 (err, 2)); + return; + } +#endif + return; +} + +static void +session_worker_job_child_setup (GdmSessionWorkerJob *session_worker_job) +{ + sigset_t mask; + session_worker_job_setup_journal_fds (); + + /* Terminate the process when the parent dies */ +#ifdef HAVE_SYS_PRCTL_H + prctl (PR_SET_PDEATHSIG, SIGTERM); +#endif + /* + * Reset signal mask to default since it was altered by the + * manager process + */ + sigemptyset (&mask); + sigprocmask (SIG_SETMASK, &mask, NULL); +} + +static void +session_worker_job_child_watch (GPid pid, + int status, + GdmSessionWorkerJob *job) +{ + g_debug ("GdmSessionWorkerJob: child (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -1); + + g_spawn_close_pid (job->pid); + job->pid = -1; + + if (WIFEXITED (status)) { + int code = WEXITSTATUS (status); + g_signal_emit (job, signals [EXITED], 0, code); + } else if (WIFSIGNALED (status)) { + int num = WTERMSIG (status); + g_signal_emit (job, signals [DIED], 0, num); + } +} + +static void +listify_hash (const char *key, + const char *value, + GPtrArray *env) +{ + char *str; + + if (value == NULL) + value = ""; + + str = g_strdup_printf ("%s=%s", key, value); + g_ptr_array_add (env, str); +} + +static void +copy_environment_to_hash (GdmSessionWorkerJob *job, + GHashTable *hash) +{ + char **environment; + gint i; + + if (job->environment != NULL) { + environment = g_strdupv (job->environment); + } else { + environment = g_get_environ (); + } + for (i = 0; environment[i]; i++) { + char **parts; + + parts = g_strsplit (environment[i], "=", 2); + + if (parts[0] != NULL && parts[1] != NULL) { + g_hash_table_insert (hash, g_strdup (parts[0]), g_strdup (parts[1])); + } + + g_strfreev (parts); + } + + g_strfreev (environment); +} + +static GPtrArray * +get_job_arguments (GdmSessionWorkerJob *job, + const char *name) +{ + GPtrArray *args; + GError *error; + char **argv; + int i; + + args = NULL; + argv = NULL; + error = NULL; + if (! g_shell_parse_argv (job->command, NULL, &argv, &error)) { + g_warning ("Could not parse command: %s", error->message); + g_error_free (error); + goto out; + } + + args = g_ptr_array_new (); + g_ptr_array_add (args, g_strdup (argv[0])); + g_ptr_array_add (args, g_strdup (name)); + for (i = 1; argv[i] != NULL; i++) { + g_ptr_array_add (args, g_strdup (argv[i])); + } + g_strfreev (argv); + + g_ptr_array_add (args, NULL); +out: + return args; +} + +static GPtrArray * +get_job_environment (GdmSessionWorkerJob *job) +{ + GPtrArray *env; + GHashTable *hash; + + env = g_ptr_array_new (); + + /* create a hash table of current environment, then update keys has necessary */ + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + copy_environment_to_hash (job, hash); + + g_hash_table_insert (hash, g_strdup ("GDM_SESSION_DBUS_ADDRESS"), g_strdup (job->server_address)); + + if (job->for_reauth) { + g_hash_table_insert (hash, g_strdup ("GDM_SESSION_FOR_REAUTH"), g_strdup ("1")); + } + + g_hash_table_foreach (hash, (GHFunc)listify_hash, env); + g_hash_table_destroy (hash); + + g_ptr_array_add (env, NULL); + + return env; +} + +static gboolean +gdm_session_worker_job_spawn (GdmSessionWorkerJob *session_worker_job, + const char *name) +{ + GError *error; + gboolean ret; + GPtrArray *args; + GPtrArray *env; + + ret = FALSE; + + g_debug ("GdmSessionWorkerJob: Running session_worker_job process: %s %s", + name != NULL? name : "", session_worker_job->command); + + args = get_job_arguments (session_worker_job, name); + + if (args == NULL) { + return FALSE; + } + env = get_job_environment (session_worker_job); + + error = NULL; + ret = g_spawn_async_with_pipes (NULL, + (char **) args->pdata, + (char **)env->pdata, + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_FILE_AND_ARGV_ZERO, + (GSpawnChildSetupFunc)session_worker_job_child_setup, + session_worker_job, + &session_worker_job->pid, + NULL, + NULL, + NULL, + &error); + + g_ptr_array_foreach (args, (GFunc)g_free, NULL); + g_ptr_array_free (args, TRUE); + + g_ptr_array_foreach (env, (GFunc)g_free, NULL); + g_ptr_array_free (env, TRUE); + + if (! ret) { + g_warning ("Could not start command '%s': %s", + session_worker_job->command, + error->message); + g_error_free (error); + } else { + g_debug ("GdmSessionWorkerJob: : SessionWorkerJob on pid %d", (int)session_worker_job->pid); + } + + session_worker_job->child_watch_id = g_child_watch_add (session_worker_job->pid, + (GChildWatchFunc)session_worker_job_child_watch, + session_worker_job); + + return ret; +} + +/** + * gdm_session_worker_job_start: + * @disp: Pointer to a GdmDisplay structure + * + * Starts a local X session_worker_job. Handles retries and fatal errors properly. + */ +gboolean +gdm_session_worker_job_start (GdmSessionWorkerJob *session_worker_job, + const char *name) +{ + gboolean res; + + g_debug ("GdmSessionWorkerJob: Starting worker..."); + + res = gdm_session_worker_job_spawn (session_worker_job, name); + + return res; +} + +static void +handle_session_worker_job_death (GdmSessionWorkerJob *session_worker_job) +{ + int exit_status; + + g_debug ("GdmSessionWorkerJob: Waiting on process %d", session_worker_job->pid); + exit_status = gdm_wait_on_and_disown_pid (session_worker_job->pid, 5); + + if (WIFEXITED (exit_status) && (WEXITSTATUS (exit_status) != 0)) { + g_debug ("GdmSessionWorkerJob: Wait on child process failed"); + } else { + /* exited normally */ + } + + g_spawn_close_pid (session_worker_job->pid); + session_worker_job->pid = -1; + + g_debug ("GdmSessionWorkerJob: SessionWorkerJob died"); +} + +void +gdm_session_worker_job_stop_now (GdmSessionWorkerJob *session_worker_job) +{ + if (session_worker_job->pid <= 1) { + return; + } + + /* remove watch source before we can wait on child */ + if (session_worker_job->child_watch_id > 0) { + g_source_remove (session_worker_job->child_watch_id); + session_worker_job->child_watch_id = 0; + } + + gdm_session_worker_job_stop (session_worker_job); + handle_session_worker_job_death (session_worker_job); +} + +void +gdm_session_worker_job_stop (GdmSessionWorkerJob *session_worker_job) +{ + int res; + + if (session_worker_job->pid <= 1) { + return; + } + + g_debug ("GdmSessionWorkerJob: Stopping job pid:%d", session_worker_job->pid); + + res = gdm_signal_pid (session_worker_job->pid, SIGTERM); + + if (res < 0) { + g_warning ("Unable to kill session worker process"); + } +} + +GPid +gdm_session_worker_job_get_pid (GdmSessionWorkerJob *session_worker_job) +{ + g_return_val_if_fail (GDM_IS_SESSION_WORKER_JOB (session_worker_job), 0); + return session_worker_job->pid; +} + +void +gdm_session_worker_job_set_server_address (GdmSessionWorkerJob *session_worker_job, + const char *address) +{ + g_return_if_fail (GDM_IS_SESSION_WORKER_JOB (session_worker_job)); + + g_free (session_worker_job->server_address); + session_worker_job->server_address = g_strdup (address); +} + +void +gdm_session_worker_job_set_for_reauth (GdmSessionWorkerJob *session_worker_job, + gboolean for_reauth) +{ + g_return_if_fail (GDM_IS_SESSION_WORKER_JOB (session_worker_job)); + + session_worker_job->for_reauth = for_reauth; +} + +void +gdm_session_worker_job_set_environment (GdmSessionWorkerJob *session_worker_job, + const char * const *environment) +{ + g_return_if_fail (GDM_IS_SESSION_WORKER_JOB (session_worker_job)); + + session_worker_job->environment = g_strdupv ((char **) environment); +} + +static void +gdm_session_worker_job_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmSessionWorkerJob *self; + + self = GDM_SESSION_WORKER_JOB (object); + + switch (prop_id) { + case PROP_SERVER_ADDRESS: + gdm_session_worker_job_set_server_address (self, g_value_get_string (value)); + break; + case PROP_FOR_REAUTH: + gdm_session_worker_job_set_for_reauth (self, g_value_get_boolean (value)); + break; + case PROP_ENVIRONMENT: + gdm_session_worker_job_set_environment (self, g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_session_worker_job_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmSessionWorkerJob *self; + + self = GDM_SESSION_WORKER_JOB (object); + + switch (prop_id) { + case PROP_SERVER_ADDRESS: + g_value_set_string (value, self->server_address); + break; + case PROP_FOR_REAUTH: + g_value_set_boolean (value, self->for_reauth); + break; + case PROP_ENVIRONMENT: + g_value_set_pointer (value, self->environment); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gdm_session_worker_job_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmSessionWorkerJob *session_worker_job; + + session_worker_job = GDM_SESSION_WORKER_JOB (G_OBJECT_CLASS (gdm_session_worker_job_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + return G_OBJECT (session_worker_job); +} + +static void +gdm_session_worker_job_class_init (GdmSessionWorkerJobClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_session_worker_job_get_property; + object_class->set_property = gdm_session_worker_job_set_property; + object_class->constructor = gdm_session_worker_job_constructor; + object_class->finalize = gdm_session_worker_job_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_FOR_REAUTH, + g_param_spec_boolean ("for-reauth", + "for reauth", + "for reauth", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_ENVIRONMENT, + g_param_spec_pointer ("environment", + "environment", + "environment", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + signals [STARTED] = + g_signal_new ("started", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [EXITED] = + g_signal_new ("exited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + signals [DIED] = + g_signal_new ("died", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); +} + +static void +gdm_session_worker_job_init (GdmSessionWorkerJob *session_worker_job) +{ + session_worker_job->pid = -1; + + session_worker_job->command = g_strdup (LIBEXECDIR "/gdm-session-worker"); +} + +static void +gdm_session_worker_job_finalize (GObject *object) +{ + GdmSessionWorkerJob *session_worker_job; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_SESSION_WORKER_JOB (object)); + + session_worker_job = GDM_SESSION_WORKER_JOB (object); + + gdm_session_worker_job_stop (session_worker_job); + + g_free (session_worker_job->command); + g_free (session_worker_job->server_address); + + G_OBJECT_CLASS (gdm_session_worker_job_parent_class)->finalize (object); +} + +GdmSessionWorkerJob * +gdm_session_worker_job_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_SESSION_WORKER_JOB, + NULL); + + return GDM_SESSION_WORKER_JOB (object); +} diff --git a/daemon/gdm-session-worker-job.h b/daemon/gdm-session-worker-job.h new file mode 100644 index 0000000..e9b9c8d --- /dev/null +++ b/daemon/gdm-session-worker-job.h @@ -0,0 +1,48 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_SESSION_WORKER_JOB_H +#define __GDM_SESSION_WORKER_JOB_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_SESSION_WORKER_JOB (gdm_session_worker_job_get_type ()) +G_DECLARE_FINAL_TYPE (GdmSessionWorkerJob, gdm_session_worker_job, GDM, SESSION_WORKER_JOB, GObject) + +GdmSessionWorkerJob * gdm_session_worker_job_new (void); +void gdm_session_worker_job_set_server_address (GdmSessionWorkerJob *session_worker_job, + const char *server_address); +void gdm_session_worker_job_set_for_reauth (GdmSessionWorkerJob *session_worker_job, + gboolean for_reauth); +void gdm_session_worker_job_set_environment (GdmSessionWorkerJob *session_worker_job, + const char * const *environment); +gboolean gdm_session_worker_job_start (GdmSessionWorkerJob *session_worker_job, + const char *name); +void gdm_session_worker_job_stop (GdmSessionWorkerJob *session_worker_job); +void gdm_session_worker_job_stop_now (GdmSessionWorkerJob *session_worker_job); + +GPid gdm_session_worker_job_get_pid (GdmSessionWorkerJob *session_worker_job); + +G_END_DECLS + +#endif /* __GDM_SESSION_WORKER_JOB_H */ diff --git a/daemon/gdm-session-worker.c b/daemon/gdm-session-worker.c new file mode 100644 index 0000000..36c3e5d --- /dev/null +++ b/daemon/gdm-session-worker.c @@ -0,0 +1,3642 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Ray Strode <rstrode@redhat.com> + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <sys/vt.h> +#include <sys/kd.h> +#include <errno.h> +#include <grp.h> +#include <pwd.h> + +#include <security/pam_appl.h> + +#ifdef HAVE_LOGINCAP +#include <login_cap.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> +#include <gio/gio.h> + +#include <X11/Xauth.h> + +#include <systemd/sd-daemon.h> + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include <systemd/sd-journal.h> +#endif + +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif /* HAVE_SELINUX */ + +#include "gdm-common.h" +#include "gdm-log.h" + +#ifdef SUPPORTS_PAM_EXTENSIONS +#include "gdm-pam-extensions.h" +#endif + +#include "gdm-dbus-glue.h" +#include "gdm-session-worker.h" +#include "gdm-session-glue.h" +#include "gdm-session.h" + +#if defined (HAVE_ADT) +#include "gdm-session-solaris-auditor.h" +#elif defined (HAVE_LIBAUDIT) +#include "gdm-session-linux-auditor.h" +#else +#include "gdm-session-auditor.h" +#endif + +#include "gdm-session-settings.h" + +#define GDM_SESSION_WORKER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_SESSION_WORKER, GdmSessionWorkerPrivate)) + +#define GDM_SESSION_DBUS_PATH "/org/gnome/DisplayManager/Session" +#define GDM_SESSION_DBUS_NAME "org.gnome.DisplayManager.Session" +#define GDM_SESSION_DBUS_ERROR_CANCEL "org.gnome.DisplayManager.Session.Error.Cancel" + +#define GDM_WORKER_DBUS_PATH "/org/gnome/DisplayManager/Worker" + +#ifndef GDM_PASSWD_AUXILLARY_BUFFER_SIZE +#define GDM_PASSWD_AUXILLARY_BUFFER_SIZE 1024 +#endif + +#ifndef GDM_SESSION_DEFAULT_PATH +#define GDM_SESSION_DEFAULT_PATH "/usr/local/bin:/usr/bin:/bin" +#endif + +#ifndef GDM_SESSION_ROOT_UID +#define GDM_SESSION_ROOT_UID 0 +#endif + +#ifndef GDM_SESSION_LOG_FILENAME +#define GDM_SESSION_LOG_FILENAME "session.log" +#endif + +#define MAX_FILE_SIZE 65536 +#define MAX_LOGS 5 + +#define RELEASE_DISPLAY_SIGNAL (SIGRTMAX) +#define ACQUIRE_DISPLAY_SIGNAL (SIGRTMAX - 1) + +typedef struct +{ + GdmSessionWorker *worker; + GdmSession *session; + GPid pid_of_caller; + uid_t uid_of_caller; + +} ReauthenticationRequest; + +struct GdmSessionWorkerPrivate +{ + GdmSessionWorkerState state; + + int exit_code; + + pam_handle_t *pam_handle; + + GPid child_pid; + guint child_watch_id; + + /* from Setup */ + char *service; + char *x11_display_name; + char *x11_authority_file; + char *display_device; + char *display_seat_id; + char *hostname; + char *username; + char *log_file; + char *session_id; + uid_t uid; + gid_t gid; + gboolean password_is_required; + char **extensions; + + int cred_flags; + int session_vt; + int session_tty_fd; + + char **arguments; + guint32 cancelled : 1; + guint32 timed_out : 1; + guint32 is_program_session : 1; + guint32 is_reauth_session : 1; + guint32 display_is_local : 1; + guint32 display_is_initial : 1; + guint state_change_idle_id; + GdmSessionDisplayMode display_mode; + + char *server_address; + GDBusConnection *connection; + GdmDBusWorkerManager *manager; + + GHashTable *reauthentication_requests; + + GdmSessionAuditor *auditor; + GdmSessionSettings *user_settings; + + GDBusMethodInvocation *pending_invocation; +}; + +#ifdef SUPPORTS_PAM_EXTENSIONS +static char gdm_pam_extension_environment_block[_POSIX_ARG_MAX]; + +static const char * const +gdm_supported_pam_extensions[] = { + GDM_PAM_EXTENSION_CHOICE_LIST, + NULL +}; +#endif + +enum { + PROP_0, + PROP_SERVER_ADDRESS, + PROP_IS_REAUTH_SESSION, + PROP_STATE, +}; + +static void gdm_session_worker_class_init (GdmSessionWorkerClass *klass); +static void gdm_session_worker_init (GdmSessionWorker *session_worker); +static void gdm_session_worker_finalize (GObject *object); + +static void gdm_session_worker_set_environment_variable (GdmSessionWorker *worker, + const char *key, + const char *value); + +static void queue_state_change (GdmSessionWorker *worker); + +static void worker_interface_init (GdmDBusWorkerIface *iface); + + +typedef int (* GdmSessionWorkerPamNewMessagesFunc) (int, + const struct pam_message **, + struct pam_response **, + gpointer); + +G_DEFINE_TYPE_WITH_CODE (GdmSessionWorker, + gdm_session_worker, + GDM_DBUS_TYPE_WORKER_SKELETON, + G_IMPLEMENT_INTERFACE (GDM_DBUS_TYPE_WORKER, + worker_interface_init) + G_ADD_PRIVATE (GdmSessionWorker)) + +/* adapted from glib script_execute */ +static void +script_execute (const gchar *file, + char **argv, + char **envp, + gboolean search_path) +{ + /* Count the arguments. */ + int argc = 0; + + while (argv[argc]) { + ++argc; + } + + /* Construct an argument list for the shell. */ + { + char **new_argv; + + new_argv = g_new0 (gchar*, argc + 2); /* /bin/sh and NULL */ + + new_argv[0] = (char *) "/bin/sh"; + new_argv[1] = (char *) file; + while (argc > 0) { + new_argv[argc + 1] = argv[argc]; + --argc; + } + + /* Execute the shell. */ + if (envp) { + execve (new_argv[0], new_argv, envp); + } else { + execv (new_argv[0], new_argv); + } + + g_free (new_argv); + } +} + +static char * +my_strchrnul (const char *str, char c) +{ + char *p = (char*) str; + while (*p && (*p != c)) { + ++p; + } + + return p; +} + +/* adapted from glib g_execute */ +static gint +gdm_session_execute (const char *file, + char **argv, + char **envp, + gboolean search_path) +{ + if (*file == '\0') { + /* We check the simple case first. */ + errno = ENOENT; + return -1; + } + + if (!search_path || strchr (file, '/') != NULL) { + /* Don't search when it contains a slash. */ + if (envp) { + execve (file, argv, envp); + } else { + execv (file, argv); + } + + if (errno == ENOEXEC) { + script_execute (file, argv, envp, FALSE); + } + } else { + gboolean got_eacces = 0; + const char *path, *p; + char *name, *freeme; + gsize len; + gsize pathlen; + + path = g_getenv ("PATH"); + if (path == NULL) { + /* There is no `PATH' in the environment. The default + * search path in libc is the current directory followed by + * the path `confstr' returns for `_CS_PATH'. + */ + + /* In GLib we put . last, for security, and don't use the + * unportable confstr(); UNIX98 does not actually specify + * what to search if PATH is unset. POSIX may, dunno. + */ + + path = "/bin:/usr/bin:."; + } + + len = strlen (file) + 1; + pathlen = strlen (path); + freeme = name = g_malloc (pathlen + len + 1); + + /* Copy the file name at the top, including '\0' */ + memcpy (name + pathlen + 1, file, len); + name = name + pathlen; + /* And add the slash before the filename */ + *name = '/'; + + p = path; + do { + char *startp; + + path = p; + p = my_strchrnul (path, ':'); + + if (p == path) { + /* Two adjacent colons, or a colon at the beginning or the end + * of `PATH' means to search the current directory. + */ + startp = name + 1; + } else { + startp = memcpy (name - (p - path), path, p - path); + } + + /* Try to execute this name. If it works, execv will not return. */ + if (envp) { + execve (startp, argv, envp); + } else { + execv (startp, argv); + } + + if (errno == ENOEXEC) { + script_execute (startp, argv, envp, search_path); + } + + switch (errno) { + case EACCES: + /* Record the we got a `Permission denied' error. If we end + * up finding no executable we can use, we want to diagnose + * that we did find one but were denied access. + */ + got_eacces = TRUE; + + /* FALL THRU */ + + case ENOENT: +#ifdef ESTALE + case ESTALE: +#endif +#ifdef ENOTDIR + case ENOTDIR: +#endif + /* Those errors indicate the file is missing or not executable + * by us, in which case we want to just try the next path + * directory. + */ + break; + + default: + /* Some other error means we found an executable file, but + * something went wrong executing it; return the error to our + * caller. + */ + g_free (freeme); + return -1; + } + } while (*p++ != '\0'); + + /* We tried every element and none of them worked. */ + if (got_eacces) { + /* At least one failure was due to permissions, so report that + * error. + */ + errno = EACCES; + } + + g_free (freeme); + } + + /* Return the error from the last attempt (probably ENOENT). */ + return -1; +} + +/* + * This function is called with username set to NULL to update the + * auditor username value. + */ +static gboolean +gdm_session_worker_get_username (GdmSessionWorker *worker, + char **username) +{ + gconstpointer item; + + g_assert (worker->priv->pam_handle != NULL); + + if (pam_get_item (worker->priv->pam_handle, PAM_USER, &item) == PAM_SUCCESS) { + if (username != NULL) { + *username = g_strdup ((char *) item); + g_debug ("GdmSessionWorker: username is '%s'", + *username != NULL ? *username : "<unset>"); + } + + if (worker->priv->auditor != NULL) { + gdm_session_auditor_set_username (worker->priv->auditor, (char *)item); + } + + return TRUE; + } + + return FALSE; +} + +static void +attempt_to_load_user_settings (GdmSessionWorker *worker, + const char *username) +{ + g_debug ("GdmSessionWorker: attempting to load user settings"); + gdm_session_settings_load (worker->priv->user_settings, + username); +} + +static void +gdm_session_worker_update_username (GdmSessionWorker *worker) +{ + char *username; + gboolean res; + + username = NULL; + res = gdm_session_worker_get_username (worker, &username); + if (res) { + g_debug ("GdmSessionWorker: old-username='%s' new-username='%s'", + worker->priv->username != NULL ? worker->priv->username : "<unset>", + username != NULL ? username : "<unset>"); + + + gdm_session_auditor_set_username (worker->priv->auditor, worker->priv->username); + + if ((worker->priv->username == username) || + ((worker->priv->username != NULL) && (username != NULL) && + (strcmp (worker->priv->username, username) == 0))) + goto out; + + g_debug ("GdmSessionWorker: setting username to '%s'", username); + + g_free (worker->priv->username); + worker->priv->username = username; + username = NULL; + + gdm_dbus_worker_emit_username_changed (GDM_DBUS_WORKER (worker), + worker->priv->username); + + /* We have a new username to try. If we haven't been able to + * read user settings up until now, then give it a go now + * (see the comment in do_setup for rationale on why it's useful + * to keep trying to read settings) + */ + if (worker->priv->username != NULL && + worker->priv->username[0] != '\0' && + !gdm_session_settings_is_loaded (worker->priv->user_settings)) { + attempt_to_load_user_settings (worker, worker->priv->username); + } + } + + out: + g_free (username); +} + +static gboolean +gdm_session_worker_ask_question (GdmSessionWorker *worker, + const char *question, + char **answerp) +{ + return gdm_dbus_worker_manager_call_info_query_sync (worker->priv->manager, + worker->priv->service, + question, + answerp, + NULL, + NULL); +} + +static gboolean +gdm_session_worker_ask_for_secret (GdmSessionWorker *worker, + const char *question, + char **answerp) +{ + return gdm_dbus_worker_manager_call_secret_info_query_sync (worker->priv->manager, + worker->priv->service, + question, + answerp, + NULL, + NULL); +} + +static gboolean +gdm_session_worker_report_info (GdmSessionWorker *worker, + const char *info) +{ + return gdm_dbus_worker_manager_call_info_sync (worker->priv->manager, + worker->priv->service, + info, + NULL, + NULL); +} + +static gboolean +gdm_session_worker_report_problem (GdmSessionWorker *worker, + const char *problem) +{ + return gdm_dbus_worker_manager_call_problem_sync (worker->priv->manager, + worker->priv->service, + problem, + NULL, + NULL); +} + +#ifdef SUPPORTS_PAM_EXTENSIONS +static gboolean +gdm_session_worker_ask_list_of_choices (GdmSessionWorker *worker, + const char *prompt_message, + GdmChoiceList *list, + char **answerp) +{ + GVariantBuilder builder; + GVariant *choices_as_variant; + GError *error = NULL; + gboolean res; + size_t i; + + g_debug ("GdmSessionWorker: presenting user with list of choices:"); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + + for (i = 0; i < list->number_of_items; i++) { + if (list->items[i].key == NULL) { + g_warning ("choice list contains item with NULL key"); + g_variant_builder_clear (&builder); + return FALSE; + } + g_debug ("GdmSessionWorker: choices['%s'] = \"%s\"", list->items[i].key, list->items[i].text); + g_variant_builder_add (&builder, "{ss}", list->items[i].key, list->items[i].text); + } + g_debug ("GdmSessionWorker: (and waiting for reply)"); + + choices_as_variant = g_variant_builder_end (&builder); + + res = gdm_dbus_worker_manager_call_choice_list_query_sync (worker->priv->manager, + worker->priv->service, + prompt_message, + choices_as_variant, + answerp, + NULL, + &error); + + if (! res) { + g_debug ("GdmSessionWorker: list request failed: %s", error->message); + g_clear_error (&error); + } else { + g_debug ("GdmSessionWorker: user selected '%s'", *answerp); + } + + return res; +} + +static gboolean +gdm_session_worker_process_choice_list_request (GdmSessionWorker *worker, + GdmPamExtensionChoiceListRequest *request, + GdmPamExtensionChoiceListResponse *response) +{ + return gdm_session_worker_ask_list_of_choices (worker, request->prompt_message, &request->list, &response->key); +} + +static gboolean +gdm_session_worker_process_extended_pam_message (GdmSessionWorker *worker, + const struct pam_message *query, + char **response) +{ + GdmPamExtensionMessage *extended_message; + gboolean res; + + extended_message = GDM_PAM_EXTENSION_MESSAGE_FROM_PAM_MESSAGE (query); + + if (GDM_PAM_EXTENSION_MESSAGE_TRUNCATED (extended_message)) { + g_warning ("PAM service requested binary response for truncated query"); + return FALSE; + } + + if (GDM_PAM_EXTENSION_MESSAGE_INVALID_TYPE (extended_message)) { + g_warning ("PAM service requested binary response for unadvertised query type"); + return FALSE; + } + + if (GDM_PAM_EXTENSION_MESSAGE_MATCH (extended_message, worker->priv->extensions, GDM_PAM_EXTENSION_CHOICE_LIST)) { + GdmPamExtensionChoiceListRequest *list_request = (GdmPamExtensionChoiceListRequest *) extended_message; + GdmPamExtensionChoiceListResponse *list_response = malloc (GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_SIZE); + + g_debug ("GdmSessionWorker: received extended pam message '%s'", GDM_PAM_EXTENSION_CHOICE_LIST); + + GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_INIT (list_response); + + res = gdm_session_worker_process_choice_list_request (worker, list_request, list_response); + + if (! res) { + g_free (list_response); + return FALSE; + } + + *response = GDM_PAM_EXTENSION_MESSAGE_TO_PAM_REPLY (list_response); + return TRUE; + } else { + g_debug ("GdmSessionWorker: received extended pam message of unknown type %u", (unsigned int) extended_message->type); + return FALSE; + + } + + return TRUE; +} +#endif + +static char * +convert_to_utf8 (const char *str) +{ + char *utf8; + utf8 = g_locale_to_utf8 (str, + -1, + NULL, + NULL, + NULL); + + /* if we couldn't convert text from locale then + * assume utf-8 and hope for the best */ + if (utf8 == NULL) { + char *p; + char *q; + + utf8 = g_strdup (str); + + p = utf8; + while (*p != '\0' && !g_utf8_validate ((const char *)p, -1, (const char **)&q)) { + *q = '?'; + p = q + 1; + } + } + + return utf8; +} + +static gboolean +gdm_session_worker_process_pam_message (GdmSessionWorker *worker, + const struct pam_message *query, + char **response) +{ + char *user_answer; + gboolean res; + char *utf8_msg; + char *msg; + + if (response != NULL) { + *response = NULL; + } + + gdm_session_worker_update_username (worker); + +#ifdef SUPPORTS_PAM_EXTENSIONS + if (query->msg_style == PAM_BINARY_PROMPT) + return gdm_session_worker_process_extended_pam_message (worker, query, response); +#endif + + g_debug ("GdmSessionWorker: received pam message of type %u with payload '%s'", + query->msg_style, query->msg); + + utf8_msg = convert_to_utf8 (query->msg); + + worker->priv->cancelled = FALSE; + worker->priv->timed_out = FALSE; + + user_answer = NULL; + res = FALSE; + switch (query->msg_style) { + case PAM_PROMPT_ECHO_ON: + res = gdm_session_worker_ask_question (worker, utf8_msg, &user_answer); + break; + case PAM_PROMPT_ECHO_OFF: + res = gdm_session_worker_ask_for_secret (worker, utf8_msg, &user_answer); + break; + case PAM_TEXT_INFO: + res = gdm_session_worker_report_info (worker, utf8_msg); + break; + case PAM_ERROR_MSG: + res = gdm_session_worker_report_problem (worker, utf8_msg); + break; +#ifdef PAM_RADIO_TYPE + case PAM_RADIO_TYPE: + msg = g_strdup_printf ("%s (yes/no)", utf8_msg); + res = gdm_session_worker_ask_question (worker, msg, &user_answer); + g_free (msg); + break; +#endif + default: + res = FALSE; + g_warning ("Unknown and unhandled message type %d\n", + query->msg_style); + + break; + } + + if (worker->priv->timed_out) { + gdm_dbus_worker_emit_cancel_pending_query (GDM_DBUS_WORKER (worker)); + worker->priv->timed_out = FALSE; + } + + if (user_answer != NULL) { + /* we strndup and g_free to make sure we return malloc'd + * instead of g_malloc'd memory. PAM_MAX_RESP_SIZE includes + * the '\0' terminating character, thus the "- 1". + */ + if (res && response != NULL) { + *response = strndup (user_answer, PAM_MAX_RESP_SIZE - 1); + } + + memset (user_answer, '\0', strlen (user_answer)); + g_free (user_answer); + + g_debug ("GdmSessionWorker: trying to get updated username"); + + res = TRUE; + } + + g_free (utf8_msg); + + return res; +} + +static const char * +get_friendly_error_message (int error_code) +{ + switch (error_code) { + case PAM_SUCCESS: + case PAM_IGNORE: + return ""; + break; + + case PAM_ACCT_EXPIRED: + case PAM_AUTHTOK_EXPIRED: + return _("Your account was given a time limit that’s now passed."); + break; + + default: + break; + } + + return _("Sorry, that didn’t work. Please try again."); +} + +static int +gdm_session_worker_pam_new_messages_handler (int number_of_messages, + const struct pam_message **messages, + struct pam_response **responses, + GdmSessionWorker *worker) +{ + struct pam_response *replies; + int return_value; + int i; + + g_debug ("GdmSessionWorker: %d new messages received from PAM\n", number_of_messages); + + return_value = PAM_CONV_ERR; + + if (number_of_messages < 0) { + return PAM_CONV_ERR; + } + + if (number_of_messages == 0) { + if (responses) { + *responses = NULL; + } + + return PAM_SUCCESS; + } + + /* we want to generate one reply for every question + */ + replies = (struct pam_response *) calloc (number_of_messages, + sizeof (struct pam_response)); + for (i = 0; i < number_of_messages; i++) { + gboolean got_response; + char *response; + + response = NULL; + got_response = gdm_session_worker_process_pam_message (worker, + messages[i], + &response); + if (!got_response) { + goto out; + } + + replies[i].resp = response; + replies[i].resp_retcode = PAM_SUCCESS; + } + + return_value = PAM_SUCCESS; + + out: + if (return_value != PAM_SUCCESS) { + for (i = 0; i < number_of_messages; i++) { + if (replies[i].resp != NULL) { + memset (replies[i].resp, 0, strlen (replies[i].resp)); + free (replies[i].resp); + } + memset (&replies[i], 0, sizeof (replies[i])); + } + free (replies); + replies = NULL; + } + + if (responses) { + *responses = replies; + } + + g_debug ("GdmSessionWorker: PAM conversation returning %d: %s", + return_value, + pam_strerror (worker->priv->pam_handle, return_value)); + + return return_value; +} + +static void +gdm_session_worker_start_auditor (GdmSessionWorker *worker) +{ + /* Use dummy auditor so program session doesn't pollute user audit logs + */ + if (worker->priv->is_program_session) { + worker->priv->auditor = gdm_session_auditor_new (worker->priv->hostname, + worker->priv->display_device); + return; + } + +/* FIXME: it may make sense at some point to keep a list of + * auditors, instead of assuming they are mutually exclusive + */ +#if defined (HAVE_ADT) + worker->priv->auditor = gdm_session_solaris_auditor_new (worker->priv->hostname, + worker->priv->display_device); +#elif defined (HAVE_LIBAUDIT) + worker->priv->auditor = gdm_session_linux_auditor_new (worker->priv->hostname, + worker->priv->display_device); +#else + worker->priv->auditor = gdm_session_auditor_new (worker->priv->hostname, + worker->priv->display_device); +#endif +} + +static void +gdm_session_worker_stop_auditor (GdmSessionWorker *worker) +{ + g_object_unref (worker->priv->auditor); + worker->priv->auditor = NULL; +} + +static void +on_release_display (int signal) +{ + int fd; + + fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); + ioctl(fd, VT_RELDISP, 1); + close(fd); +} + +static void +on_acquire_display (int signal) +{ + int fd; + + fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); + ioctl(fd, VT_RELDISP, VT_ACKACQ); + close(fd); +} + +static gboolean +handle_terminal_vt_switches (GdmSessionWorker *worker, + int tty_fd) +{ + struct vt_mode setmode_request = { 0 }; + gboolean succeeded = TRUE; + + setmode_request.mode = VT_PROCESS; + setmode_request.relsig = RELEASE_DISPLAY_SIGNAL; + setmode_request.acqsig = ACQUIRE_DISPLAY_SIGNAL; + + if (ioctl (tty_fd, VT_SETMODE, &setmode_request) < 0) { + g_debug ("GdmSessionWorker: couldn't manage VTs manually: %m"); + succeeded = FALSE; + } + + signal (RELEASE_DISPLAY_SIGNAL, on_release_display); + signal (ACQUIRE_DISPLAY_SIGNAL, on_acquire_display); + + return succeeded; +} + +static void +fix_terminal_vt_mode (GdmSessionWorker *worker, + int tty_fd) +{ + struct vt_mode getmode_reply = { 0 }; + int kernel_display_mode = 0; + gboolean mode_fixed = FALSE; + gboolean succeeded = TRUE; + + if (ioctl (tty_fd, VT_GETMODE, &getmode_reply) < 0) { + g_debug ("GdmSessionWorker: couldn't query VT mode: %m"); + succeeded = FALSE; + } + + if (getmode_reply.mode != VT_AUTO) { + goto out; + } + + if (ioctl (tty_fd, KDGETMODE, &kernel_display_mode) < 0) { + g_debug ("GdmSessionWorker: couldn't query kernel display mode: %m"); + succeeded = FALSE; + } + + if (kernel_display_mode == KD_TEXT) { + goto out; + } + + /* VT is in the anti-social state of VT_AUTO + KD_GRAPHICS, + * fix it. + */ + succeeded = handle_terminal_vt_switches (worker, tty_fd); + mode_fixed = TRUE; +out: + if (!succeeded) { + g_error ("GdmSessionWorker: couldn't set up terminal, aborting..."); + return; + } + + g_debug ("GdmSessionWorker: VT mode did %sneed to be fixed", + mode_fixed? "" : "not "); +} + +static void +jump_to_vt (GdmSessionWorker *worker, + int vt_number) +{ + int fd; + int active_vt_tty_fd; + int active_vt = -1; + struct vt_stat vt_state = { 0 }; + + g_debug ("GdmSessionWorker: jumping to VT %d", vt_number); + active_vt_tty_fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); + + if (worker->priv->session_tty_fd != -1) { + static const char *clear_screen_escape_sequence = "\33[H\33[2J"; + + /* let's make sure the new VT is clear */ + write (worker->priv->session_tty_fd, + clear_screen_escape_sequence, + sizeof (clear_screen_escape_sequence)); + + fd = worker->priv->session_tty_fd; + + g_debug ("GdmSessionWorker: first setting graphics mode to prevent flicker"); + if (ioctl (fd, KDSETMODE, KD_GRAPHICS) < 0) { + g_debug ("GdmSessionWorker: couldn't set graphics mode: %m"); + } + + /* It's possible that the current VT was left in a broken + * combination of states (KD_GRAPHICS with VT_AUTO), that + * can't be switched away from. This call makes sure things + * are set in a way that VT_ACTIVATE should work and + * VT_WAITACTIVE shouldn't hang. + */ + fix_terminal_vt_mode (worker, active_vt_tty_fd); + } else { + fd = active_vt_tty_fd; + } + + handle_terminal_vt_switches (worker, fd); + + if (ioctl (fd, VT_GETSTATE, &vt_state) < 0) { + g_debug ("GdmSessionWorker: couldn't get current VT: %m"); + } else { + active_vt = vt_state.v_active; + } + + if (active_vt != vt_number) { + if (ioctl (fd, VT_ACTIVATE, vt_number) < 0) { + g_debug ("GdmSessionWorker: couldn't initiate jump to VT %d: %m", + vt_number); + } else if (ioctl (fd, VT_WAITACTIVE, vt_number) < 0) { + g_debug ("GdmSessionWorker: couldn't finalize jump to VT %d: %m", + vt_number); + } + } + + close (active_vt_tty_fd); +} + +static void +gdm_session_worker_set_state (GdmSessionWorker *worker, + GdmSessionWorkerState state) +{ + if (worker->priv->state == state) + return; + + worker->priv->state = state; + g_object_notify (G_OBJECT (worker), "state"); +} + +static void +gdm_session_worker_uninitialize_pam (GdmSessionWorker *worker, + int status) +{ + g_debug ("GdmSessionWorker: uninitializing PAM"); + + if (worker->priv->pam_handle == NULL) + return; + + gdm_session_worker_get_username (worker, NULL); + + if (worker->priv->state >= GDM_SESSION_WORKER_STATE_SESSION_OPENED) { + pam_close_session (worker->priv->pam_handle, 0); + gdm_session_auditor_report_logout (worker->priv->auditor); + } else { + gdm_session_auditor_report_login_failure (worker->priv->auditor, + status, + pam_strerror (worker->priv->pam_handle, status)); + } + + if (worker->priv->state >= GDM_SESSION_WORKER_STATE_ACCREDITED) { + pam_setcred (worker->priv->pam_handle, PAM_DELETE_CRED); + } + + pam_end (worker->priv->pam_handle, status); + worker->priv->pam_handle = NULL; + + gdm_session_worker_stop_auditor (worker); + + worker->priv->session_vt = 0; + + g_debug ("GdmSessionWorker: state NONE"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_NONE); +} + +static char * +_get_tty_for_pam (const char *x11_display_name, + const char *display_device) +{ +#ifdef __sun + return g_strdup (display_device); +#else + return g_strdup (x11_display_name); +#endif +} + +#ifdef PAM_XAUTHDATA +static struct pam_xauth_data * +_get_xauth_for_pam (const char *x11_authority_file) +{ + FILE *fh; + Xauth *auth = NULL; + struct pam_xauth_data *retval = NULL; + gsize len = sizeof (*retval) + 1; + + fh = fopen (x11_authority_file, "r"); + if (fh) { + auth = XauReadAuth (fh); + fclose (fh); + } + if (auth) { + len += auth->name_length + auth->data_length; + retval = g_malloc0 (len); + } + if (retval) { + retval->namelen = auth->name_length; + retval->name = (char *) (retval + 1); + memcpy (retval->name, auth->name, auth->name_length); + retval->datalen = auth->data_length; + retval->data = retval->name + auth->name_length + 1; + memcpy (retval->data, auth->data, auth->data_length); + } + XauDisposeAuth (auth); + return retval; +} +#endif + +static gboolean +gdm_session_worker_initialize_pam (GdmSessionWorker *worker, + const char *service, + const char * const *extensions, + const char *username, + const char *hostname, + gboolean display_is_local, + const char *x11_display_name, + const char *x11_authority_file, + const char *display_device, + const char *seat_id, + GError **error) +{ + struct pam_conv pam_conversation; + int error_code; + char tty_string[256]; + + g_assert (worker->priv->pam_handle == NULL); + + g_debug ("GdmSessionWorker: initializing PAM; service=%s username=%s seat=%s", + service ? service : "(null)", + username ? username : "(null)", + seat_id ? seat_id : "(null)"); + +#ifdef SUPPORTS_PAM_EXTENSIONS + if (extensions != NULL) { + GDM_PAM_EXTENSION_ADVERTISE_SUPPORTED_EXTENSIONS (gdm_pam_extension_environment_block, extensions); + } +#endif + + pam_conversation.conv = (GdmSessionWorkerPamNewMessagesFunc) gdm_session_worker_pam_new_messages_handler; + pam_conversation.appdata_ptr = worker; + + gdm_session_worker_start_auditor (worker); + error_code = pam_start (service, + username, + &pam_conversation, + &worker->priv->pam_handle); + if (error_code != PAM_SUCCESS) { + g_debug ("GdmSessionWorker: could not initialize PAM: (error code %d)", error_code); + /* we don't use pam_strerror here because it requires a valid + * pam handle, and if pam_start fails pam_handle is undefined + */ + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE, + "%s", ""); + + goto out; + } + + /* set USER PROMPT */ + if (username == NULL) { + error_code = pam_set_item (worker->priv->pam_handle, PAM_USER_PROMPT, _("Username:")); + + if (error_code != PAM_SUCCESS) { + g_debug ("GdmSessionWorker: error informing authentication system of preferred username prompt: %s", + pam_strerror (worker->priv->pam_handle, error_code)); + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + } + + /* set RHOST */ + if (hostname != NULL && hostname[0] != '\0') { + error_code = pam_set_item (worker->priv->pam_handle, PAM_RHOST, hostname); + g_debug ("error informing authentication system of user's hostname %s: %s", + hostname, + pam_strerror (worker->priv->pam_handle, error_code)); + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + } + + /* set seat ID */ + if (seat_id != NULL && seat_id[0] != '\0') { + gdm_session_worker_set_environment_variable (worker, "XDG_SEAT", seat_id); + } + + if (strcmp (service, "gdm-launch-environment") == 0) { + gdm_session_worker_set_environment_variable (worker, "XDG_SESSION_CLASS", "greeter"); + } + + g_debug ("GdmSessionWorker: state SETUP_COMPLETE"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE); + + /* Temporarily set PAM_TTY with the login VT, + PAM_TTY will be reset with the users VT right before the user session is opened */ + g_snprintf (tty_string, 256, "/dev/tty%d", GDM_INITIAL_VT); + pam_set_item (worker->priv->pam_handle, PAM_TTY, tty_string); + if (!display_is_local) + worker->priv->password_is_required = TRUE; + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static gboolean +gdm_session_worker_authenticate_user (GdmSessionWorker *worker, + gboolean password_is_required, + GError **error) +{ + int error_code; + int authentication_flags; + + g_debug ("GdmSessionWorker: authenticating user %s", worker->priv->username); + + authentication_flags = 0; + + if (password_is_required) { + authentication_flags |= PAM_DISALLOW_NULL_AUTHTOK; + } + + /* blocking call, does the actual conversation */ + error_code = pam_authenticate (worker->priv->pam_handle, authentication_flags); + + if (error_code == PAM_AUTHINFO_UNAVAIL) { + g_debug ("GdmSessionWorker: authentication service unavailable"); + + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE, + "%s", ""); + goto out; + } else if (error_code != PAM_SUCCESS) { + g_debug ("GdmSessionWorker: authentication returned %d: %s", error_code, pam_strerror (worker->priv->pam_handle, error_code)); + + /* + * Do not display a different message for user unknown versus + * a failed password for a valid user. + */ + if (error_code == PAM_USER_UNKNOWN) { + error_code = PAM_AUTH_ERR; + } + + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", get_friendly_error_message (error_code)); + goto out; + } + + g_debug ("GdmSessionWorker: state AUTHENTICATED"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_AUTHENTICATED); + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static gboolean +gdm_session_worker_authorize_user (GdmSessionWorker *worker, + gboolean password_is_required, + GError **error) +{ + int error_code; + int authentication_flags; + + g_debug ("GdmSessionWorker: determining if authenticated user (password required:%d) is authorized to session", + password_is_required); + + authentication_flags = 0; + + if (password_is_required) { + authentication_flags |= PAM_DISALLOW_NULL_AUTHTOK; + } + + /* check that the account isn't disabled or expired + */ + error_code = pam_acct_mgmt (worker->priv->pam_handle, authentication_flags); + + /* it's possible that the user needs to change their password or pin code + */ + if (error_code == PAM_NEW_AUTHTOK_REQD && !worker->priv->is_program_session) { + g_debug ("GdmSessionWorker: authenticated user requires new auth token"); + error_code = pam_chauthtok (worker->priv->pam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); + + gdm_session_worker_get_username (worker, NULL); + + if (error_code != PAM_SUCCESS) { + gdm_session_auditor_report_password_change_failure (worker->priv->auditor); + } else { + gdm_session_auditor_report_password_changed (worker->priv->auditor); + } + } + + /* If the user is reauthenticating, then authorization isn't required to + * proceed, the user is already logged in after all. + */ + if (worker->priv->is_reauth_session) { + error_code = PAM_SUCCESS; + } + + if (error_code != PAM_SUCCESS) { + g_debug ("GdmSessionWorker: user is not authorized to log in: %s", + pam_strerror (worker->priv->pam_handle, error_code)); + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHORIZING, + "%s", get_friendly_error_message (error_code)); + goto out; + } + + g_debug ("GdmSessionWorker: state AUTHORIZED"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_AUTHORIZED); + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static void +gdm_session_worker_set_environment_variable (GdmSessionWorker *worker, + const char *key, + const char *value) +{ + int error_code; + char *environment_entry; + + if (value != NULL) { + environment_entry = g_strdup_printf ("%s=%s", key, value); + } else { + /* empty value means "remove from environment" */ + environment_entry = g_strdup (key); + } + + error_code = pam_putenv (worker->priv->pam_handle, + environment_entry); + + if (error_code != PAM_SUCCESS) { + g_warning ("cannot put %s in pam environment: %s\n", + environment_entry, + pam_strerror (worker->priv->pam_handle, error_code)); + } + g_debug ("GdmSessionWorker: Set PAM environment variable: '%s'", environment_entry); + g_free (environment_entry); +} + +static char * +gdm_session_worker_get_environment_variable (GdmSessionWorker *worker, + const char *key) +{ + return g_strdup (pam_getenv (worker->priv->pam_handle, key)); +} + +static void +gdm_session_worker_update_environment_from_passwd_info (GdmSessionWorker *worker, + uid_t uid, + gid_t gid, + const char *home, + const char *shell) +{ + gdm_session_worker_set_environment_variable (worker, "LOGNAME", worker->priv->username); + gdm_session_worker_set_environment_variable (worker, "USER", worker->priv->username); + gdm_session_worker_set_environment_variable (worker, "USERNAME", worker->priv->username); + gdm_session_worker_set_environment_variable (worker, "HOME", home); + gdm_session_worker_set_environment_variable (worker, "PWD", home); + gdm_session_worker_set_environment_variable (worker, "SHELL", shell); +} + +static gboolean +gdm_session_worker_environment_variable_is_set (GdmSessionWorker *worker, + const char *key) +{ + return pam_getenv (worker->priv->pam_handle, key) != NULL; +} + +static gboolean +_change_user (GdmSessionWorker *worker, + uid_t uid, + gid_t gid) +{ +#ifdef THE_MAN_PAGE_ISNT_LYING + /* pam_setcred wants to be called as the authenticated user + * but pam_open_session needs to be called as super-user. + * + * Set the real uid and gid to the user and give the user a + * temporary super-user effective id. + */ + if (setreuid (uid, GDM_SESSION_ROOT_UID) < 0) { + return FALSE; + } +#endif + worker->priv->uid = uid; + worker->priv->gid = gid; + + if (setgid (gid) < 0) { + return FALSE; + } + + if (initgroups (worker->priv->username, gid) < 0) { + return FALSE; + } + + return TRUE; +} + +static gboolean +_lookup_passwd_info (const char *username, + uid_t *uidp, + gid_t *gidp, + char **homep, + char **shellp) +{ + gboolean ret; + struct passwd *passwd_entry; + struct passwd passwd_buffer; + char *aux_buffer; + long required_aux_buffer_size; + gsize aux_buffer_size; + + ret = FALSE; + aux_buffer = NULL; + aux_buffer_size = 0; + + required_aux_buffer_size = sysconf (_SC_GETPW_R_SIZE_MAX); + + if (required_aux_buffer_size < 0) { + aux_buffer_size = GDM_PASSWD_AUXILLARY_BUFFER_SIZE; + } else { + aux_buffer_size = (gsize) required_aux_buffer_size; + } + + aux_buffer = g_slice_alloc0 (aux_buffer_size); + + /* we use the _r variant of getpwnam() + * (with its weird semantics) so that the + * passwd_entry doesn't potentially get stomped on + * by a PAM module + */ + again: + passwd_entry = NULL; +#ifdef HAVE_POSIX_GETPWNAM_R + errno = getpwnam_r (username, + &passwd_buffer, + aux_buffer, + (size_t) aux_buffer_size, + &passwd_entry); +#else + passwd_entry = getpwnam_r (username, + &passwd_buffer, + aux_buffer, + (size_t) aux_buffer_size); + errno = 0; +#endif /* !HAVE_POSIX_GETPWNAM_R */ + if (errno == EINTR) { + g_debug ("%s", g_strerror (errno)); + goto again; + } else if (errno != 0) { + g_warning ("%s", g_strerror (errno)); + goto out; + } + + if (passwd_entry == NULL) { + goto out; + } + + if (uidp != NULL) { + *uidp = passwd_entry->pw_uid; + } + if (gidp != NULL) { + *gidp = passwd_entry->pw_gid; + } + if (homep != NULL) { + if (passwd_entry->pw_dir != NULL && passwd_entry->pw_dir[0] != '\0') { + *homep = g_strdup (passwd_entry->pw_dir); + } else { + *homep = g_strdup ("/"); + } + } + if (shellp != NULL) { + if (passwd_entry->pw_shell != NULL && passwd_entry->pw_shell[0] != '\0') { + *shellp = g_strdup (passwd_entry->pw_shell); + } else { + *shellp = g_strdup ("/bin/bash"); + } + } + ret = TRUE; + out: + if (aux_buffer != NULL) { + g_assert (aux_buffer_size > 0); + g_slice_free1 (aux_buffer_size, aux_buffer); + } + + return ret; +} + +static char * +get_var_cb (const char *key, + gpointer user_data) +{ + return gdm_session_worker_get_environment_variable (user_data, key); +} + +static void +load_env_file (GdmSessionWorker *worker, + GFile *file) +{ + gchar *contents; + gchar **lines; + gchar *line, *p; + gchar *var, *var_end; + gchar *expanded; + char *filename; + int i; + + filename = g_file_get_path (file); + g_debug ("Loading env vars from %s\n", filename); + g_free (filename); + + if (g_file_load_contents (file, NULL, &contents, NULL, NULL, NULL)) { + lines = g_strsplit (contents, "\n", -1); + g_free (contents); + for (i = 0; lines[i] != NULL; i++) { + line = lines[i]; + p = line; + while (g_ascii_isspace (*p)) + p++; + if (*p == '#' || *p == '\0') + continue; + var = p; + while (gdm_shell_var_is_valid_char (*p, p == var)) + p++; + var_end = p; + while (g_ascii_isspace (*p)) + p++; + if (var == var_end || *p != '=') { + g_warning ("Invalid env.d line '%s'\n", line); + continue; + } + *var_end = 0; + p++; /* Skip = */ + while (g_ascii_isspace (*p)) + p++; + + expanded = gdm_shell_expand (p, get_var_cb, worker); + expanded = g_strchomp (expanded); + gdm_session_worker_set_environment_variable (worker, var, expanded); + g_free (expanded); + } + g_strfreev (lines); + } +} + +static gint +compare_str (gconstpointer a, + gconstpointer b) +{ + return strcmp (*(const char **)a, *(const char **)b); +} + +static void +gdm_session_worker_load_env_dir (GdmSessionWorker *worker, + GFile *dir) +{ + GFileInfo *info = NULL; + GFileEnumerator *enumerator = NULL; + GPtrArray *names = NULL; + GFile *file; + const gchar *name; + int i; + + enumerator = g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_TYPE"," + G_FILE_ATTRIBUTE_STANDARD_NAME"," + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN"," + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + if (!enumerator) { + goto out; + } + + names = g_ptr_array_new_with_free_func (g_free); + while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) { + if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR && + !g_file_info_get_is_hidden (info) && + g_str_has_suffix (g_file_info_get_name (info), ".env")) + g_ptr_array_add (names, g_strdup (g_file_info_get_name (info))); + + g_clear_object (&info); + } + + g_ptr_array_sort (names, compare_str); + + for (i = 0; i < names->len; i++) { + name = g_ptr_array_index (names, i); + file = g_file_get_child (dir, name); + load_env_file (worker, file); + g_object_unref (file); + } + + out: + g_clear_pointer (&names, g_ptr_array_unref); + g_clear_object (&enumerator); +} + +static void +gdm_session_worker_load_env_d (GdmSessionWorker *worker) +{ + GFile *dir; + + dir = g_file_new_for_path (DATADIR "/gdm/env.d"); + gdm_session_worker_load_env_dir (worker, dir); + g_object_unref (dir); + + dir = g_file_new_for_path (GDMCONFDIR "/env.d"); + gdm_session_worker_load_env_dir (worker, dir); + g_object_unref (dir); +} + +static gboolean +gdm_session_worker_accredit_user (GdmSessionWorker *worker, + GError **error) +{ + gboolean ret; + gboolean res; + uid_t uid; + gid_t gid; + char *shell; + char *home; + int error_code; + + ret = FALSE; + + home = NULL; + shell = NULL; + + if (worker->priv->username == NULL) { + g_debug ("GdmSessionWorker: Username not set"); + error_code = PAM_USER_UNKNOWN; + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + _("no user account available")); + goto out; + } + + uid = 0; + gid = 0; + res = _lookup_passwd_info (worker->priv->username, + &uid, + &gid, + &home, + &shell); + if (! res) { + g_debug ("GdmSessionWorker: Unable to lookup account info"); + error_code = PAM_AUTHINFO_UNAVAIL; + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + _("no user account available")); + goto out; + } + + gdm_session_worker_update_environment_from_passwd_info (worker, + uid, + gid, + home, + shell); + + /* Let's give the user a default PATH if he doesn't already have one + */ + if (!gdm_session_worker_environment_variable_is_set (worker, "PATH")) { + if (strcmp (BINDIR, "/usr/bin") == 0) { + gdm_session_worker_set_environment_variable (worker, "PATH", + GDM_SESSION_DEFAULT_PATH); + } else { + gdm_session_worker_set_environment_variable (worker, "PATH", + BINDIR ":" GDM_SESSION_DEFAULT_PATH); + } + } + + if (! _change_user (worker, uid, gid)) { + g_debug ("GdmSessionWorker: Unable to change to user"); + error_code = PAM_SYSTEM_ERR; + g_set_error (error, GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + "%s", _("Unable to change to user")); + goto out; + } + + error_code = pam_setcred (worker->priv->pam_handle, worker->priv->cred_flags); + + /* If the user is reauthenticating and they've made it this far, then there + * is no reason we should lock them out of their session. They've already + * proved they are they same person who logged in, and that's all we care + * about. + */ + if (worker->priv->is_reauth_session) { + error_code = PAM_SUCCESS; + } + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + "%s", + pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + + ret = TRUE; + + out: + g_free (home); + g_free (shell); + if (ret) { + g_debug ("GdmSessionWorker: state ACCREDITED"); + ret = TRUE; + + gdm_session_worker_get_username (worker, NULL); + gdm_session_auditor_report_user_accredited (worker->priv->auditor); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_ACCREDITED); + } else { + gdm_session_worker_uninitialize_pam (worker, error_code); + } + + return ret; +} + +static const char * const * +gdm_session_worker_get_environment (GdmSessionWorker *worker) +{ + return (const char * const *) pam_getenvlist (worker->priv->pam_handle); +} + +static gboolean +run_script (GdmSessionWorker *worker, + const char *dir) +{ + /* scripts are for non-program sessions only */ + if (worker->priv->is_program_session) { + return TRUE; + } + + return gdm_run_script (dir, + worker->priv->username, + worker->priv->x11_display_name, + worker->priv->display_is_local? NULL : worker->priv->hostname, + worker->priv->x11_authority_file); +} + +static void +wait_until_dbus_signal_emission_to_manager_finishes (GdmSessionWorker *worker) +{ + g_autoptr (GdmDBusPeer) peer_proxy = NULL; + g_autoptr (GError) error = NULL; + gboolean pinged; + + peer_proxy = gdm_dbus_peer_proxy_new_sync (worker->priv->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + "/org/freedesktop/DBus", + NULL, + &error); + + if (peer_proxy == NULL) { + g_debug ("GdmSessionWorker: could not create peer proxy to daemon: %s", + error->message); + return; + } + + pinged = gdm_dbus_peer_call_ping_sync (peer_proxy, NULL, &error); + + if (!pinged) { + g_debug ("GdmSessionWorker: could not ping daemon: %s", + error->message); + return; + } +} + +static void +jump_back_to_initial_vt (GdmSessionWorker *worker) +{ + if (worker->priv->session_vt == 0) + return; + + if (worker->priv->session_vt == GDM_INITIAL_VT) + return; + + if (g_strcmp0 (worker->priv->display_seat_id, "seat0") != 0) + return; + +#ifdef ENABLE_USER_DISPLAY_SERVER + jump_to_vt (worker, GDM_INITIAL_VT); + worker->priv->session_vt = 0; +#endif +} + +static void +session_worker_child_watch (GPid pid, + int status, + GdmSessionWorker *worker) +{ + g_debug ("GdmSessionWorker: child (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -1); + + gdm_session_worker_uninitialize_pam (worker, PAM_SUCCESS); + + worker->priv->child_pid = -1; + worker->priv->child_watch_id = 0; + run_script (worker, GDMCONFDIR "/PostSession"); + + gdm_dbus_worker_emit_session_exited (GDM_DBUS_WORKER (worker), + worker->priv->service, + status); + + killpg (pid, SIGHUP); + + /* FIXME: It's important to give the manager an opportunity to process the + * session-exited emission above before switching VTs. + * + * This is because switching VTs makes the manager try to put a login screen + * up on VT 1, but it may actually want to try to auto login again in response + * to session-exited. + * + * This function just does a manager roundtrip over the bus to make sure the + * signal has been dispatched before jumping. + * + * Ultimately, we may want to improve the manager<->worker interface. + * + * See: + * + * https://gitlab.gnome.org/GNOME/gdm/-/merge_requests/123 + * + * for some ideas and more discussion. + * + */ + wait_until_dbus_signal_emission_to_manager_finishes (worker); + + jump_back_to_initial_vt (worker); +} + +static void +gdm_session_worker_watch_child (GdmSessionWorker *worker) +{ + g_debug ("GdmSession worker: watching pid %d", worker->priv->child_pid); + worker->priv->child_watch_id = g_child_watch_add (worker->priv->child_pid, + (GChildWatchFunc)session_worker_child_watch, + worker); + +} + +static gboolean +_is_loggable_file (const char* filename) +{ + struct stat file_info; + + if (g_lstat (filename, &file_info) < 0) { + return FALSE; + } + + return S_ISREG (file_info.st_mode) && g_access (filename, R_OK | W_OK) == 0; +} + +static void +rotate_logs (const char *path, + guint n_copies) +{ + int i; + + for (i = n_copies - 1; i > 0; i--) { + char *name_n; + char *name_n1; + + name_n = g_strdup_printf ("%s.%d", path, i); + if (i > 1) { + name_n1 = g_strdup_printf ("%s.%d", path, i - 1); + } else { + name_n1 = g_strdup (path); + } + + g_unlink (name_n); + g_rename (name_n1, name_n); + + g_free (name_n1); + g_free (name_n); + } + + g_unlink (path); +} + +static int +_open_program_session_log (const char *filename) +{ + int fd; + + rotate_logs (filename, MAX_LOGS); + + fd = g_open (filename, O_WRONLY | O_APPEND | O_CREAT, 0600); + + if (fd < 0) { + char *temp_name; + + temp_name = g_strdup_printf ("%s.XXXXXXXX", filename); + + fd = g_mkstemp (temp_name); + + if (fd < 0) { + g_free (temp_name); + goto out; + } + + g_warning ("session log '%s' is not appendable, logging session to '%s' instead.\n", filename, + temp_name); + g_free (temp_name); + } else { + if (ftruncate (fd, 0) < 0) { + close (fd); + fd = -1; + goto out; + } + } + + if (fchmod (fd, 0644) < 0) { + close (fd); + fd = -1; + goto out; + } + + +out: + if (fd < 0) { + g_warning ("unable to log program session"); + fd = g_open ("/dev/null", O_RDWR); + } + + return fd; +} + +static int +_open_user_session_log (const char *dir) +{ + int fd; + char *filename; + + filename = g_build_filename (dir, GDM_SESSION_LOG_FILENAME, NULL); + + if (g_access (dir, R_OK | W_OK | X_OK) == 0 && _is_loggable_file (filename)) { + char *filename_old; + + filename_old = g_strdup_printf ("%s.old", filename); + g_rename (filename, filename_old); + g_free (filename_old); + } + + fd = g_open (filename, O_RDWR | O_APPEND | O_CREAT, 0600); + + if (fd < 0) { + char *temp_name; + + temp_name = g_strdup_printf ("%s.XXXXXXXX", filename); + + fd = g_mkstemp (temp_name); + + if (fd < 0) { + g_free (temp_name); + goto out; + } + + g_warning ("session log '%s' is not appendable, logging session to '%s' instead.\n", filename, + temp_name); + g_free (filename); + filename = temp_name; + } else { + if (ftruncate (fd, 0) < 0) { + close (fd); + fd = -1; + goto out; + } + } + + if (fchmod (fd, 0600) < 0) { + close (fd); + fd = -1; + goto out; + } + + +out: + g_free (filename); + + if (fd < 0) { + g_warning ("unable to log session"); + fd = g_open ("/dev/null", O_RDWR); + } + + return fd; +} + +static gboolean +gdm_session_worker_start_session (GdmSessionWorker *worker, + GError **error) +{ + struct passwd *passwd_entry; + pid_t session_pid; + int error_code; + + gdm_get_pwent_for_name (worker->priv->username, &passwd_entry); + if (worker->priv->is_program_session) { + g_debug ("GdmSessionWorker: opening session for program '%s'", + worker->priv->arguments[0]); + } else { + g_debug ("GdmSessionWorker: opening user session with program '%s'", + worker->priv->arguments[0]); + } + + error_code = PAM_SUCCESS; + + /* If we're in new vt mode, jump to the new vt now. There's no need to jump for + * the other two modes: in the logind case, the session will activate itself when + * ready, and in the reuse server case, we're already on the correct VT. */ + if (g_strcmp0 (worker->priv->display_seat_id, "seat0") == 0) { + if (worker->priv->display_mode == GDM_SESSION_DISPLAY_MODE_NEW_VT) { + jump_to_vt (worker, worker->priv->session_vt); + } + } + + if (!worker->priv->is_program_session && !run_script (worker, GDMCONFDIR "/PostLogin")) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "Failed to execute PostLogin script"); + error_code = PAM_ABORT; + goto out; + } + + if (!worker->priv->is_program_session && !run_script (worker, GDMCONFDIR "/PreSession")) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "Failed to execute PreSession script"); + error_code = PAM_ABORT; + goto out; + } + + session_pid = fork (); + + if (session_pid < 0) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "%s", g_strerror (errno)); + error_code = PAM_ABORT; + goto out; + } + + if (session_pid == 0) { + const char * const * environment; + char *home_dir; + int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1; + gboolean has_journald = FALSE, needs_controlling_terminal = FALSE; + /* Leak the TTY into the session as stdin so that it stays open + * without any races. */ + if (worker->priv->session_tty_fd > 0) { + dup2 (worker->priv->session_tty_fd, STDIN_FILENO); + close (worker->priv->session_tty_fd); + worker->priv->session_tty_fd = -1; + needs_controlling_terminal = TRUE; + } else { + stdin_fd = open ("/dev/null", O_RDWR); + dup2 (stdin_fd, STDIN_FILENO); + close (stdin_fd); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + has_journald = sd_booted() > 0; +#endif + if (!has_journald && worker->priv->is_program_session) { + stdout_fd = _open_program_session_log (worker->priv->log_file); + stderr_fd = dup (stdout_fd); + } + + if (setsid () < 0) { + g_debug ("GdmSessionWorker: could not set pid '%u' as leader of new session and process group: %s", + (guint) getpid (), g_strerror (errno)); + _exit (EXIT_FAILURE); + } + + /* Take control of the tty + */ + if (needs_controlling_terminal) { + if (ioctl (STDIN_FILENO, TIOCSCTTY, 0) < 0) { + g_debug ("GdmSessionWorker: could not take control of tty: %m"); + } + } + +#ifdef HAVE_LOGINCAP + if (setusercontext (NULL, passwd_entry, passwd_entry->pw_uid, LOGIN_SETALL) < 0) { + g_debug ("GdmSessionWorker: setusercontext() failed for user %s: %s", + passwd_entry->pw_name, g_strerror (errno)); + _exit (EXIT_FAILURE); + } +#else + if (setuid (worker->priv->uid) < 0) { + g_debug ("GdmSessionWorker: could not reset uid: %s", g_strerror (errno)); + _exit (EXIT_FAILURE); + } +#endif + + if (!worker->priv->is_program_session) { + gdm_session_worker_load_env_d (worker); + } + + environment = gdm_session_worker_get_environment (worker); + + g_assert (geteuid () == getuid ()); + + home_dir = gdm_session_worker_get_environment_variable (worker, "HOME"); + if ((home_dir == NULL) || g_chdir (home_dir) < 0) { + g_chdir ("/"); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + if (has_journald) { + stdout_fd = sd_journal_stream_fd (worker->priv->arguments[0], LOG_INFO, FALSE); + stderr_fd = sd_journal_stream_fd (worker->priv->arguments[0], LOG_WARNING, FALSE); + + /* Unset the CLOEXEC flags, because sd_journal_stream_fd + * gives it to us by default. + */ + gdm_clear_close_on_exec_flag (stdout_fd); + gdm_clear_close_on_exec_flag (stderr_fd); + } +#endif + if (!has_journald && !worker->priv->is_program_session) { + if (home_dir != NULL && home_dir[0] != '\0') { + char *cache_dir; + char *log_dir; + + cache_dir = gdm_session_worker_get_environment_variable (worker, "XDG_CACHE_HOME"); + if (cache_dir == NULL || cache_dir[0] == '\0') { + cache_dir = g_build_filename (home_dir, ".cache", NULL); + } + + log_dir = g_build_filename (cache_dir, "gdm", NULL); + g_free (cache_dir); + + if (g_mkdir_with_parents (log_dir, S_IRWXU) == 0) { + stdout_fd = _open_user_session_log (log_dir); + stderr_fd = dup (stdout_fd); + } else { + stdout_fd = open ("/dev/null", O_RDWR); + stderr_fd = dup (stdout_fd); + } + g_free (log_dir); + } else { + stdout_fd = open ("/dev/null", O_RDWR); + stderr_fd = dup (stdout_fd); + } + } + g_free (home_dir); + + if (stdout_fd != -1) { + dup2 (stdout_fd, STDOUT_FILENO); + close (stdout_fd); + } + + if (stderr_fd != -1) { + dup2 (stderr_fd, STDERR_FILENO); + close (stderr_fd); + } + + gdm_log_shutdown (); + + /* + * Reset SIGPIPE to default so that any process in the user + * session get the default SIGPIPE behavior instead of ignoring + * SIGPIPE. + */ + signal (SIGPIPE, SIG_DFL); + + gdm_session_execute (worker->priv->arguments[0], + worker->priv->arguments, + (char **) + environment, + TRUE); + + gdm_log_init (); + g_debug ("GdmSessionWorker: child '%s' could not be started: %s", + worker->priv->arguments[0], + g_strerror (errno)); + + _exit (EXIT_FAILURE); + } + + if (worker->priv->session_tty_fd > 0) { + close (worker->priv->session_tty_fd); + worker->priv->session_tty_fd = -1; + } + + /* If we end up execing again, make sure we don't use the executable context set up + * by pam_selinux durin pam_open_session + */ +#ifdef HAVE_SELINUX + setexeccon (NULL); +#endif + + worker->priv->child_pid = session_pid; + + g_debug ("GdmSessionWorker: session opened creating reply..."); + g_assert (sizeof (GPid) <= sizeof (int)); + + g_debug ("GdmSessionWorker: state SESSION_STARTED"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SESSION_STARTED); + + gdm_session_worker_watch_child (worker); + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static gboolean +set_up_for_new_vt (GdmSessionWorker *worker) +{ + int initial_vt_fd; + char vt_string[256], tty_string[256]; + int session_vt = 0; + + /* open the initial vt. We need it for two scenarios: + * + * 1) display_is_initial is TRUE. We need it directly. + * 2) display_is_initial is FALSE. We need it to mark + * the initial VT as "in use" so it doesn't get returned + * by VT_OPENQRY + * */ + g_snprintf (tty_string, sizeof (tty_string), "/dev/tty%d", GDM_INITIAL_VT); + initial_vt_fd = open (tty_string, O_RDWR | O_NOCTTY); + + if (initial_vt_fd < 0) { + g_debug ("GdmSessionWorker: couldn't open console of initial fd: %m"); + return FALSE; + } + + if (worker->priv->display_is_initial) { + session_vt = GDM_INITIAL_VT; + } else { + + /* Typically VT_OPENQRY is called on /dev/tty0, but we already + * have /dev/tty1 open above, so might as well use it. + */ + if (ioctl (initial_vt_fd, VT_OPENQRY, &session_vt) < 0) { + g_debug ("GdmSessionWorker: couldn't open new VT: %m"); + goto fail; + } + } + + worker->priv->session_vt = session_vt; + + g_assert (session_vt > 0); + + g_snprintf (vt_string, sizeof (vt_string), "%d", session_vt); + + /* Set the VTNR. This is used by logind to configure a session in + * the logind-managed case, but it doesn't hurt to set it always. + * When logind gains support for XDG_VTNR=auto, we can make the + * OPENQRY and this whole path only used by the new VT code. */ + gdm_session_worker_set_environment_variable (worker, + "XDG_VTNR", + vt_string); + + if (worker->priv->display_is_initial) { + worker->priv->session_tty_fd = initial_vt_fd; + } else { + g_snprintf (tty_string, sizeof (tty_string), "/dev/tty%d", session_vt); + worker->priv->session_tty_fd = open (tty_string, O_RDWR | O_NOCTTY); + close (initial_vt_fd); + } + + pam_set_item (worker->priv->pam_handle, PAM_TTY, tty_string); + + return TRUE; + +fail: + close (initial_vt_fd); + return FALSE; +} + +static gboolean +set_xdg_vtnr_to_current_vt (GdmSessionWorker *worker) +{ + int fd; + char vt_string[256]; + struct vt_stat vt_state = { 0 }; + + fd = open ("/dev/tty0", O_RDWR | O_NOCTTY); + + if (fd < 0) { + g_debug ("GdmSessionWorker: couldn't open VT master: %m"); + return FALSE; + } + + if (ioctl (fd, VT_GETSTATE, &vt_state) < 0) { + g_debug ("GdmSessionWorker: couldn't get current VT: %m"); + goto fail; + } + + close (fd); + fd = -1; + + g_snprintf (vt_string, sizeof (vt_string), "%d", vt_state.v_active); + + gdm_session_worker_set_environment_variable (worker, + "XDG_VTNR", + vt_string); + + return TRUE; + +fail: + close (fd); + return FALSE; +} + +static gboolean +set_up_for_current_vt (GdmSessionWorker *worker, + GError **error) +{ +#ifdef PAM_XAUTHDATA + struct pam_xauth_data *pam_xauth; +#endif + int error_code = PAM_SUCCESS; + char *pam_tty; + + /* set TTY */ + pam_tty = _get_tty_for_pam (worker->priv->x11_display_name, worker->priv->display_device); + if (pam_tty != NULL && pam_tty[0] != '\0') { + error_code = pam_set_item (worker->priv->pam_handle, PAM_TTY, pam_tty); + + if (error_code != PAM_SUCCESS) { + g_debug ("error informing authentication system of user's console %s: %s", + pam_tty, + pam_strerror (worker->priv->pam_handle, error_code)); + g_free (pam_tty); + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + } + g_free (pam_tty); + +#ifdef PAM_XDISPLAY + /* set XDISPLAY */ + if (worker->priv->x11_display_name != NULL && worker->priv->x11_display_name[0] != '\0') { + error_code = pam_set_item (worker->priv->pam_handle, PAM_XDISPLAY, worker->priv->x11_display_name); + if (error_code != PAM_SUCCESS) { + g_debug ("error informing authentication system of display string %s: %s", + worker->priv->x11_display_name, + pam_strerror (worker->priv->pam_handle, error_code)); + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + } +#endif +#ifdef PAM_XAUTHDATA + /* set XAUTHDATA */ + pam_xauth = _get_xauth_for_pam (worker->priv->x11_authority_file); + if (pam_xauth != NULL) { + error_code = pam_set_item (worker->priv->pam_handle, PAM_XAUTHDATA, pam_xauth); + if (error_code != PAM_SUCCESS) { + g_debug ("error informing authentication system of display string %s: %s", + worker->priv->x11_display_name, + pam_strerror (worker->priv->pam_handle, error_code)); + g_free (pam_xauth); + + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", ""); + goto out; + } + g_free (pam_xauth); + } +#endif + + if (g_strcmp0 (worker->priv->display_seat_id, "seat0") == 0) { + g_debug ("GdmSessionWorker: setting XDG_VTNR to current vt"); + set_xdg_vtnr_to_current_vt (worker); + } else { + g_debug ("GdmSessionWorker: not setting XDG_VTNR since not seat0"); + } + + return TRUE; +out: + return FALSE; +} + +static gboolean +gdm_session_worker_open_session (GdmSessionWorker *worker, + GError **error) +{ + int error_code; + int flags; + char *session_id = NULL; + + g_assert (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED); + g_assert (geteuid () == 0); + + switch (worker->priv->display_mode) { + case GDM_SESSION_DISPLAY_MODE_REUSE_VT: + if (!set_up_for_current_vt (worker, error)) { + return FALSE; + } + break; + case GDM_SESSION_DISPLAY_MODE_NEW_VT: + case GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED: + if (!set_up_for_new_vt (worker)) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "Unable to open VT"); + return FALSE; + } + break; + } + + flags = 0; + + if (worker->priv->is_program_session) { + flags |= PAM_SILENT; + } + + error_code = pam_open_session (worker->priv->pam_handle, flags); + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OPENING_SESSION, + "%s", pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + + g_debug ("GdmSessionWorker: state SESSION_OPENED"); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SESSION_OPENED); + + session_id = gdm_session_worker_get_environment_variable (worker, "XDG_SESSION_ID"); + + if (session_id != NULL) { + g_free (worker->priv->session_id); + worker->priv->session_id = session_id; + } + + out: + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + worker->priv->session_vt = 0; + return FALSE; + } + + gdm_session_worker_get_username (worker, NULL); + gdm_session_auditor_report_login (worker->priv->auditor); + + return TRUE; +} + +static void +gdm_session_worker_set_server_address (GdmSessionWorker *worker, + const char *address) +{ + g_free (worker->priv->server_address); + worker->priv->server_address = g_strdup (address); +} + +static void +gdm_session_worker_set_is_reauth_session (GdmSessionWorker *worker, + gboolean is_reauth_session) +{ + worker->priv->is_reauth_session = is_reauth_session; +} + +static void +gdm_session_worker_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmSessionWorker *self; + + self = GDM_SESSION_WORKER (object); + + switch (prop_id) { + case PROP_SERVER_ADDRESS: + gdm_session_worker_set_server_address (self, g_value_get_string (value)); + break; + case PROP_IS_REAUTH_SESSION: + gdm_session_worker_set_is_reauth_session (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_session_worker_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmSessionWorker *self; + + self = GDM_SESSION_WORKER (object); + + switch (prop_id) { + case PROP_SERVER_ADDRESS: + g_value_set_string (value, self->priv->server_address); + break; + case PROP_IS_REAUTH_SESSION: + g_value_set_boolean (value, self->priv->is_reauth_session); + break; + case PROP_STATE: + g_value_set_enum (value, self->priv->state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gdm_session_worker_handle_set_environment_variable (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *key, + const char *value) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + gdm_session_worker_set_environment_variable (worker, key, value); + gdm_dbus_worker_complete_set_environment_variable (object, invocation); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_set_session_name (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *session_name) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + g_debug ("GdmSessionWorker: session name set to %s", session_name); + gdm_session_settings_set_session_name (worker->priv->user_settings, + session_name); + gdm_dbus_worker_complete_set_session_name (object, invocation); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_set_session_display_mode (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *str) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + g_debug ("GdmSessionWorker: session display mode set to %s", str); + worker->priv->display_mode = gdm_session_display_mode_from_string (str); + gdm_dbus_worker_complete_set_session_display_mode (object, invocation); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_set_language_name (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *language_name) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + g_debug ("GdmSessionWorker: language name set to %s", language_name); + gdm_session_settings_set_language_name (worker->priv->user_settings, + language_name); + gdm_dbus_worker_complete_set_language_name (object, invocation); + return TRUE; +} + +static void +on_saved_language_name_read (GdmSessionWorker *worker) +{ + char *language_name; + + language_name = gdm_session_settings_get_language_name (worker->priv->user_settings); + + g_debug ("GdmSessionWorker: Saved language is %s", language_name); + gdm_dbus_worker_emit_saved_language_name_read (GDM_DBUS_WORKER (worker), + language_name); + g_free (language_name); +} + +static void +on_saved_session_name_read (GdmSessionWorker *worker) +{ + char *session_name; + + session_name = gdm_session_settings_get_session_name (worker->priv->user_settings); + + g_debug ("GdmSessionWorker: Saved session is %s", session_name); + gdm_dbus_worker_emit_saved_session_name_read (GDM_DBUS_WORKER (worker), + session_name); + g_free (session_name); +} + +static void +do_setup (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + error = NULL; + res = gdm_session_worker_initialize_pam (worker, + worker->priv->service, + (const char **) worker->priv->extensions, + worker->priv->username, + worker->priv->hostname, + worker->priv->display_is_local, + worker->priv->x11_display_name, + worker->priv->x11_authority_file, + worker->priv->display_device, + worker->priv->display_seat_id, + &error); + + if (res) { + g_dbus_method_invocation_return_value (worker->priv->pending_invocation, NULL); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +do_authenticate (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + /* find out who the user is and ensure they are who they say they are + */ + error = NULL; + res = gdm_session_worker_authenticate_user (worker, + worker->priv->password_is_required, + &error); + if (res) { + /* we're authenticated. Let's make sure we've been given + * a valid username for the system + */ + if (!worker->priv->is_program_session) { + g_debug ("GdmSessionWorker: trying to get updated username"); + gdm_session_worker_update_username (worker); + } + + gdm_dbus_worker_complete_authenticate (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation); + } else { + g_debug ("GdmSessionWorker: Unable to verify user"); + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +do_authorize (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + /* make sure the user is allowed to log in to this system + */ + error = NULL; + res = gdm_session_worker_authorize_user (worker, + worker->priv->password_is_required, + &error); + if (res) { + gdm_dbus_worker_complete_authorize (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +do_accredit (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + /* get kerberos tickets, setup group lists, etc + */ + error = NULL; + res = gdm_session_worker_accredit_user (worker, &error); + + if (res) { + gdm_dbus_worker_complete_establish_credentials (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +save_account_details_now (GdmSessionWorker *worker) +{ + g_assert (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCREDITED); + + g_debug ("GdmSessionWorker: saving account details for user %s", worker->priv->username); + gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED); + if (!gdm_session_settings_save (worker->priv->user_settings, + worker->priv->username)) { + g_warning ("could not save session and language settings"); + } + queue_state_change (worker); +} + +static void +on_settings_is_loaded_changed (GdmSessionSettings *user_settings, + GParamSpec *pspec, + GdmSessionWorker *worker) +{ + if (!gdm_session_settings_is_loaded (worker->priv->user_settings)) { + return; + } + + /* These signal handlers should be disconnected after the loading, + * so that gdm_session_settings_set_* APIs don't cause the emitting + * of Saved*NameRead D-Bus signals any more. + */ + g_signal_handlers_disconnect_by_func (worker->priv->user_settings, + G_CALLBACK (on_saved_session_name_read), + worker); + + g_signal_handlers_disconnect_by_func (worker->priv->user_settings, + G_CALLBACK (on_saved_language_name_read), + worker); + + if (worker->priv->state == GDM_SESSION_WORKER_STATE_NONE) { + g_debug ("GdmSessionWorker: queuing setup for user: %s %s", + worker->priv->username, worker->priv->display_device); + queue_state_change (worker); + } else if (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCREDITED) { + save_account_details_now (worker); + } else { + return; + } + + g_signal_handlers_disconnect_by_func (G_OBJECT (worker->priv->user_settings), + G_CALLBACK (on_settings_is_loaded_changed), + worker); +} + +static void +do_save_account_details_when_ready (GdmSessionWorker *worker) +{ + g_assert (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCREDITED); + + if (!gdm_session_settings_is_loaded (worker->priv->user_settings)) { + g_signal_connect (G_OBJECT (worker->priv->user_settings), + "notify::is-loaded", + G_CALLBACK (on_settings_is_loaded_changed), + worker); + g_debug ("GdmSessionWorker: user %s, not fully loaded yet, will save account details later", + worker->priv->username); + gdm_session_settings_load (worker->priv->user_settings, + worker->priv->username); + return; + } + + save_account_details_now (worker); +} + +static void +do_open_session (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + error = NULL; + res = gdm_session_worker_open_session (worker, &error); + + if (res) { + char *session_id = worker->priv->session_id; + if (session_id == NULL) { + session_id = ""; + } + + gdm_dbus_worker_complete_open (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation, session_id); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static void +do_start_session (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + error = NULL; + res = gdm_session_worker_start_session (worker, &error); + if (res) { + gdm_dbus_worker_complete_start_program (GDM_DBUS_WORKER (worker), + worker->priv->pending_invocation, + worker->priv->child_pid); + } else { + g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error); + } + worker->priv->pending_invocation = NULL; +} + +static const char * +get_state_name (int state) +{ + const char *name; + + name = NULL; + + switch (state) { + case GDM_SESSION_WORKER_STATE_NONE: + name = "NONE"; + break; + case GDM_SESSION_WORKER_STATE_SETUP_COMPLETE: + name = "SETUP_COMPLETE"; + break; + case GDM_SESSION_WORKER_STATE_AUTHENTICATED: + name = "AUTHENTICATED"; + break; + case GDM_SESSION_WORKER_STATE_AUTHORIZED: + name = "AUTHORIZED"; + break; + case GDM_SESSION_WORKER_STATE_ACCREDITED: + name = "ACCREDITED"; + break; + case GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED: + name = "ACCOUNT_DETAILS_SAVED"; + break; + case GDM_SESSION_WORKER_STATE_SESSION_OPENED: + name = "SESSION_OPENED"; + break; + case GDM_SESSION_WORKER_STATE_SESSION_STARTED: + name = "SESSION_STARTED"; + break; + default: + g_assert_not_reached (); + break; + } + + return name; +} + +static gboolean +state_change_idle (GdmSessionWorker *worker) +{ + int new_state; + + new_state = worker->priv->state + 1; + g_debug ("GdmSessionWorker: attempting to change state to %s", + get_state_name (new_state)); + + worker->priv->state_change_idle_id = 0; + + switch (new_state) { + case GDM_SESSION_WORKER_STATE_SETUP_COMPLETE: + do_setup (worker); + break; + case GDM_SESSION_WORKER_STATE_AUTHENTICATED: + do_authenticate (worker); + break; + case GDM_SESSION_WORKER_STATE_AUTHORIZED: + do_authorize (worker); + break; + case GDM_SESSION_WORKER_STATE_ACCREDITED: + do_accredit (worker); + break; + case GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED: + do_save_account_details_when_ready (worker); + break; + case GDM_SESSION_WORKER_STATE_SESSION_OPENED: + do_open_session (worker); + break; + case GDM_SESSION_WORKER_STATE_SESSION_STARTED: + do_start_session (worker); + break; + case GDM_SESSION_WORKER_STATE_NONE: + default: + g_assert_not_reached (); + } + return FALSE; +} + +static void +queue_state_change (GdmSessionWorker *worker) +{ + if (worker->priv->state_change_idle_id > 0) { + return; + } + + worker->priv->state_change_idle_id = g_idle_add ((GSourceFunc)state_change_idle, worker); +} + +static gboolean +validate_state_change (GdmSessionWorker *worker, + GDBusMethodInvocation *invocation, + int new_state) +{ + if (worker->priv->pending_invocation != NULL) { + g_dbus_method_invocation_return_error (invocation, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_OUTSTANDING_REQUEST, + "Cannot process state change to %s, as there is already an outstanding request to move to state %s", + get_state_name (new_state), + get_state_name (worker->priv->state + 1)); + return FALSE; + } else if (worker->priv->state != new_state - 1) { + g_dbus_method_invocation_return_error (invocation, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_WRONG_STATE, + "Cannot move to state %s, in state %s, not %s", + get_state_name (new_state), + get_state_name (worker->priv->state), + get_state_name (new_state - 1)); + return FALSE; + } + + return TRUE; +} + +static void +validate_and_queue_state_change (GdmSessionWorker *worker, + GDBusMethodInvocation *invocation, + int new_state) +{ + if (validate_state_change (worker, invocation, new_state)) { + worker->priv->pending_invocation = invocation; + queue_state_change (worker); + } +} + +static gboolean +gdm_session_worker_handle_authenticate (GdmDBusWorker *object, + GDBusMethodInvocation *invocation) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_AUTHENTICATED); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_authorize (GdmDBusWorker *object, + GDBusMethodInvocation *invocation) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_AUTHORIZED); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_establish_credentials (GdmDBusWorker *object, + GDBusMethodInvocation *invocation) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_ACCREDITED); + + if (!worker->priv->is_reauth_session) { + worker->priv->cred_flags = PAM_ESTABLISH_CRED; + } else { + worker->priv->cred_flags = PAM_REINITIALIZE_CRED; + } + + return TRUE; +} + +static gboolean +gdm_session_worker_handle_open (GdmDBusWorker *object, + GDBusMethodInvocation *invocation) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED); + return TRUE; +} + +static char ** +filter_extensions (const char * const *extensions) +{ + size_t i, j; + GPtrArray *array = NULL; + char **filtered_extensions = NULL; + + array = g_ptr_array_new (); + + for (i = 0; extensions[i] != NULL; i++) { + for (j = 0; gdm_supported_pam_extensions[j] != NULL; j++) { + if (g_strcmp0 (extensions[i], gdm_supported_pam_extensions[j]) == 0) { + g_ptr_array_add (array, g_strdup (gdm_supported_pam_extensions[j])); + break; + } + } + } + g_ptr_array_add (array, NULL); + + filtered_extensions = g_strdupv ((char **) array->pdata); + + g_ptr_array_free (array, TRUE); + + return filtered_extensions; +} + +static gboolean +gdm_session_worker_handle_initialize (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + GVariant *details) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + GVariantIter iter; + char *key; + GVariant *value; + gboolean wait_for_settings = FALSE; + + if (!validate_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE)) + return TRUE; + + g_variant_iter_init (&iter, details); + while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) { + if (g_strcmp0 (key, "service") == 0) { + worker->priv->service = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "extensions") == 0) { + worker->priv->extensions = filter_extensions (g_variant_get_strv (value, NULL)); + } else if (g_strcmp0 (key, "username") == 0) { + worker->priv->username = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "is-program-session") == 0) { + worker->priv->is_program_session = g_variant_get_boolean (value); + } else if (g_strcmp0 (key, "log-file") == 0) { + worker->priv->log_file = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "x11-display-name") == 0) { + worker->priv->x11_display_name = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "x11-authority-file") == 0) { + worker->priv->x11_authority_file = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "console") == 0) { + worker->priv->display_device = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "seat-id") == 0) { + worker->priv->display_seat_id = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "hostname") == 0) { + worker->priv->hostname = g_variant_dup_string (value, NULL); + } else if (g_strcmp0 (key, "display-is-local") == 0) { + worker->priv->display_is_local = g_variant_get_boolean (value); + } else if (g_strcmp0 (key, "display-is-initial") == 0) { + worker->priv->display_is_initial = g_variant_get_boolean (value); + } + } + + worker->priv->pending_invocation = invocation; + + if (!worker->priv->is_program_session) { + g_signal_connect_swapped (worker->priv->user_settings, + "notify::language-name", + G_CALLBACK (on_saved_language_name_read), + worker); + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::session-name", + G_CALLBACK (on_saved_session_name_read), + worker); + + if (worker->priv->username) { + wait_for_settings = !gdm_session_settings_load (worker->priv->user_settings, + worker->priv->username); + } + } + + if (wait_for_settings) { + /* Load settings from accounts daemon before continuing + */ + g_signal_connect (G_OBJECT (worker->priv->user_settings), + "notify::is-loaded", + G_CALLBACK (on_settings_is_loaded_changed), + worker); + } else { + queue_state_change (worker); + } + + return TRUE; +} + +static gboolean +gdm_session_worker_handle_setup (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *service, + const char *x11_display_name, + const char *x11_authority_file, + const char *console, + const char *seat_id, + const char *hostname, + gboolean display_is_local, + gboolean display_is_initial) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE); + + worker->priv->service = g_strdup (service); + worker->priv->x11_display_name = g_strdup (x11_display_name); + worker->priv->x11_authority_file = g_strdup (x11_authority_file); + worker->priv->display_device = g_strdup (console); + worker->priv->display_seat_id = g_strdup (seat_id); + worker->priv->hostname = g_strdup (hostname); + worker->priv->display_is_local = display_is_local; + worker->priv->display_is_initial = display_is_initial; + worker->priv->username = NULL; + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::language-name", + G_CALLBACK (on_saved_language_name_read), + worker); + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::session-name", + G_CALLBACK (on_saved_session_name_read), + worker); + return TRUE; +} + +static gboolean +gdm_session_worker_handle_setup_for_user (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *service, + const char *username, + const char *x11_display_name, + const char *x11_authority_file, + const char *console, + const char *seat_id, + const char *hostname, + gboolean display_is_local, + gboolean display_is_initial) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + + if (!validate_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE)) + return TRUE; + + worker->priv->service = g_strdup (service); + worker->priv->x11_display_name = g_strdup (x11_display_name); + worker->priv->x11_authority_file = g_strdup (x11_authority_file); + worker->priv->display_device = g_strdup (console); + worker->priv->display_seat_id = g_strdup (seat_id); + worker->priv->hostname = g_strdup (hostname); + worker->priv->display_is_local = display_is_local; + worker->priv->display_is_initial = display_is_initial; + worker->priv->username = g_strdup (username); + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::language-name", + G_CALLBACK (on_saved_language_name_read), + worker); + + g_signal_connect_swapped (worker->priv->user_settings, + "notify::session-name", + G_CALLBACK (on_saved_session_name_read), + worker); + + /* Load settings from accounts daemon before continuing + */ + worker->priv->pending_invocation = invocation; + if (gdm_session_settings_load (worker->priv->user_settings, username)) { + queue_state_change (worker); + } else { + g_signal_connect (G_OBJECT (worker->priv->user_settings), + "notify::is-loaded", + G_CALLBACK (on_settings_is_loaded_changed), + worker); + } + + return TRUE; +} + +static gboolean +gdm_session_worker_handle_setup_for_program (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *service, + const char *username, + const char *x11_display_name, + const char *x11_authority_file, + const char *console, + const char *seat_id, + const char *hostname, + gboolean display_is_local, + gboolean display_is_initial, + const char *log_file) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + validate_and_queue_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE); + + worker->priv->service = g_strdup (service); + worker->priv->x11_display_name = g_strdup (x11_display_name); + worker->priv->x11_authority_file = g_strdup (x11_authority_file); + worker->priv->display_device = g_strdup (console); + worker->priv->display_seat_id = g_strdup (seat_id); + worker->priv->hostname = g_strdup (hostname); + worker->priv->display_is_local = display_is_local; + worker->priv->display_is_initial = display_is_initial; + worker->priv->username = g_strdup (username); + worker->priv->log_file = g_strdup (log_file); + worker->priv->is_program_session = TRUE; + + return TRUE; +} + +static gboolean +gdm_session_worker_handle_start_program (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + const char *text) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + GError *parse_error = NULL; + validate_state_change (worker, invocation, GDM_SESSION_WORKER_STATE_SESSION_STARTED); + + if (worker->priv->is_reauth_session) { + g_dbus_method_invocation_return_error (invocation, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_IN_REAUTH_SESSION, + "Cannot start a program while in a reauth session"); + return TRUE; + } + + g_debug ("GdmSessionWorker: start program: %s", text); + + g_clear_pointer (&worker->priv->arguments, g_strfreev); + if (! g_shell_parse_argv (text, NULL, &worker->priv->arguments, &parse_error)) { + g_dbus_method_invocation_take_error (invocation, parse_error); + return TRUE; + } + + worker->priv->pending_invocation = invocation; + queue_state_change (worker); + + return TRUE; +} + +static void +on_reauthentication_client_connected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + ReauthenticationRequest *request) +{ + g_debug ("GdmSessionWorker: client connected to reauthentication server"); +} + +static void +on_reauthentication_client_disconnected (GdmSession *session, + GCredentials *credentials, + GPid pid_of_client, + ReauthenticationRequest *request) +{ + GdmSessionWorker *worker; + + g_debug ("GdmSessionWorker: client disconnected from reauthentication server"); + + worker = request->worker; + g_hash_table_remove (worker->priv->reauthentication_requests, + GINT_TO_POINTER (pid_of_client)); +} + +static void +on_reauthentication_cancelled (GdmSession *session, + ReauthenticationRequest *request) +{ + g_debug ("GdmSessionWorker: client cancelled reauthentication request"); + gdm_session_reset (session); +} + +static void +on_reauthentication_conversation_started (GdmSession *session, + const char *service_name, + ReauthenticationRequest *request) +{ + g_debug ("GdmSessionWorker: reauthentication service '%s' started", + service_name); +} + +static void +on_reauthentication_conversation_stopped (GdmSession *session, + const char *service_name, + ReauthenticationRequest *request) +{ + g_debug ("GdmSessionWorker: reauthentication service '%s' stopped", + service_name); +} + +static void +on_reauthentication_verification_complete (GdmSession *session, + const char *service_name, + ReauthenticationRequest *request) +{ + GdmSessionWorker *worker; + + worker = request->worker; + + g_debug ("GdmSessionWorker: pid %d reauthenticated user %d with service '%s'", + (int) request->pid_of_caller, + (int) request->uid_of_caller, + service_name); + gdm_session_reset (session); + + gdm_dbus_worker_emit_reauthenticated (GDM_DBUS_WORKER (worker), service_name); +} + +static ReauthenticationRequest * +reauthentication_request_new (GdmSessionWorker *worker, + GPid pid_of_caller, + uid_t uid_of_caller, + GDBusMethodInvocation *invocation) +{ + ReauthenticationRequest *request; + const char * const * environment; + const char *address; + + environment = gdm_session_worker_get_environment (worker); + + request = g_slice_new (ReauthenticationRequest); + + request->worker = worker; + request->pid_of_caller = pid_of_caller; + request->uid_of_caller = uid_of_caller; + request->session = gdm_session_new (GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE, + uid_of_caller, + worker->priv->x11_display_name, + worker->priv->hostname, + worker->priv->display_device, + worker->priv->display_seat_id, + worker->priv->x11_authority_file, + worker->priv->display_is_local, + environment); + + g_signal_connect (request->session, + "client-connected", + G_CALLBACK (on_reauthentication_client_connected), + request); + g_signal_connect (request->session, + "client-disconnected", + G_CALLBACK (on_reauthentication_client_disconnected), + request); + g_signal_connect (request->session, + "cancelled", + G_CALLBACK (on_reauthentication_cancelled), + request); + g_signal_connect (request->session, + "conversation-started", + G_CALLBACK (on_reauthentication_conversation_started), + request); + g_signal_connect (request->session, + "conversation-stopped", + G_CALLBACK (on_reauthentication_conversation_stopped), + request); + g_signal_connect (request->session, + "verification-complete", + G_CALLBACK (on_reauthentication_verification_complete), + request); + + address = gdm_session_get_server_address (request->session); + + gdm_dbus_worker_complete_start_reauthentication (GDM_DBUS_WORKER (worker), + invocation, + address); + + return request; +} + +static gboolean +gdm_session_worker_handle_start_reauthentication (GdmDBusWorker *object, + GDBusMethodInvocation *invocation, + int pid_of_caller, + int uid_of_caller) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (object); + ReauthenticationRequest *request; + + if (worker->priv->state != GDM_SESSION_WORKER_STATE_SESSION_STARTED) { + g_dbus_method_invocation_return_error (invocation, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_WRONG_STATE, + "Cannot reauthenticate while in state %s", + get_state_name (worker->priv->state)); + return TRUE; + } + + g_debug ("GdmSessionWorker: start reauthentication"); + + request = reauthentication_request_new (worker, pid_of_caller, uid_of_caller, invocation); + g_hash_table_replace (worker->priv->reauthentication_requests, + GINT_TO_POINTER (pid_of_caller), + request); + return TRUE; +} + +static GObject * +gdm_session_worker_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmSessionWorker *worker; + GError *error; + + worker = GDM_SESSION_WORKER (G_OBJECT_CLASS (gdm_session_worker_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + g_debug ("GdmSessionWorker: connecting to address: %s", worker->priv->server_address); + + error = NULL; + worker->priv->connection = g_dbus_connection_new_for_address_sync (worker->priv->server_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, + NULL, + &error); + if (worker->priv->connection == NULL) { + g_warning ("error opening connection: %s", error->message); + g_clear_error (&error); + + exit (EXIT_FAILURE); + } + + worker->priv->manager = GDM_DBUS_WORKER_MANAGER (gdm_dbus_worker_manager_proxy_new_sync (worker->priv->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, /* dbus name */ + GDM_SESSION_DBUS_PATH, + NULL, + &error)); + if (worker->priv->manager == NULL) { + g_warning ("error creating session proxy: %s", error->message); + g_clear_error (&error); + + exit (EXIT_FAILURE); + } + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (worker), + worker->priv->connection, + GDM_WORKER_DBUS_PATH, + &error)) { + g_warning ("Error while exporting object: %s", error->message); + exit (EXIT_FAILURE); + } + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (worker->priv->manager), G_MAXINT); + + /* Send an initial Hello message so that the session can associate + * the conversation we manage with our pid. + */ + gdm_dbus_worker_manager_call_hello_sync (worker->priv->manager, + NULL, + NULL); + + return G_OBJECT (worker); +} + +static void +worker_interface_init (GdmDBusWorkerIface *interface) +{ + interface->handle_initialize = gdm_session_worker_handle_initialize; + /* The next three are for backward compat only */ + interface->handle_setup = gdm_session_worker_handle_setup; + interface->handle_setup_for_user = gdm_session_worker_handle_setup_for_user; + interface->handle_setup_for_program = gdm_session_worker_handle_setup_for_program; + interface->handle_authenticate = gdm_session_worker_handle_authenticate; + interface->handle_authorize = gdm_session_worker_handle_authorize; + interface->handle_establish_credentials = gdm_session_worker_handle_establish_credentials; + interface->handle_open = gdm_session_worker_handle_open; + interface->handle_set_language_name = gdm_session_worker_handle_set_language_name; + interface->handle_set_session_name = gdm_session_worker_handle_set_session_name; + interface->handle_set_session_display_mode = gdm_session_worker_handle_set_session_display_mode; + interface->handle_set_environment_variable = gdm_session_worker_handle_set_environment_variable; + interface->handle_start_program = gdm_session_worker_handle_start_program; + interface->handle_start_reauthentication = gdm_session_worker_handle_start_reauthentication; +} + +static void +gdm_session_worker_class_init (GdmSessionWorkerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_session_worker_get_property; + object_class->set_property = gdm_session_worker_set_property; + object_class->constructor = gdm_session_worker_constructor; + object_class->finalize = gdm_session_worker_finalize; + + g_object_class_install_property (object_class, + PROP_SERVER_ADDRESS, + g_param_spec_string ("server-address", + "server address", + "server address", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_IS_REAUTH_SESSION, + g_param_spec_boolean ("is-reauth-session", + "is reauth session", + "is reauth session", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_STATE, + g_param_spec_enum ("state", + "state", + "state", + GDM_TYPE_SESSION_WORKER_STATE, + GDM_SESSION_WORKER_STATE_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static void +reauthentication_request_free (ReauthenticationRequest *request) +{ + + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_client_connected), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_client_disconnected), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_cancelled), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_conversation_started), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_conversation_stopped), + request); + g_signal_handlers_disconnect_by_func (request->session, + G_CALLBACK (on_reauthentication_verification_complete), + request); + g_clear_object (&request->session); + g_slice_free (ReauthenticationRequest, request); +} + +static void +gdm_session_worker_init (GdmSessionWorker *worker) +{ + worker->priv = GDM_SESSION_WORKER_GET_PRIVATE (worker); + + worker->priv->user_settings = gdm_session_settings_new (); + worker->priv->reauthentication_requests = g_hash_table_new_full (NULL, + NULL, + NULL, + (GDestroyNotify) + reauthentication_request_free); +} + +static void +gdm_session_worker_unwatch_child (GdmSessionWorker *worker) +{ + if (worker->priv->child_watch_id == 0) + return; + + g_source_remove (worker->priv->child_watch_id); + worker->priv->child_watch_id = 0; +} + + +static void +gdm_session_worker_finalize (GObject *object) +{ + GdmSessionWorker *worker; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_SESSION_WORKER (object)); + + worker = GDM_SESSION_WORKER (object); + + g_return_if_fail (worker->priv != NULL); + + gdm_session_worker_unwatch_child (worker); + + if (worker->priv->child_pid > 0) { + gdm_signal_pid (worker->priv->child_pid, SIGTERM); + gdm_wait_on_pid (worker->priv->child_pid); + } + + if (worker->priv->pam_handle != NULL) { + gdm_session_worker_uninitialize_pam (worker, PAM_SUCCESS); + } + + jump_back_to_initial_vt (worker); + + g_object_unref (worker->priv->user_settings); + g_free (worker->priv->service); + g_free (worker->priv->x11_display_name); + g_free (worker->priv->x11_authority_file); + g_free (worker->priv->display_device); + g_free (worker->priv->display_seat_id); + g_free (worker->priv->hostname); + g_free (worker->priv->username); + g_free (worker->priv->server_address); + g_strfreev (worker->priv->arguments); + g_strfreev (worker->priv->extensions); + + g_hash_table_unref (worker->priv->reauthentication_requests); + + G_OBJECT_CLASS (gdm_session_worker_parent_class)->finalize (object); +} + +GdmSessionWorker * +gdm_session_worker_new (const char *address, + gboolean is_reauth_session) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_SESSION_WORKER, + "server-address", address, + "is-reauth-session", is_reauth_session, + NULL); + + return GDM_SESSION_WORKER (object); +} diff --git a/daemon/gdm-session-worker.h b/daemon/gdm-session-worker.h new file mode 100644 index 0000000..2814eab --- /dev/null +++ b/daemon/gdm-session-worker.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Ray Strode <rstrode@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef __GDM_SESSION_WORKER_H +#define __GDM_SESSION_WORKER_H + +#include <glib-object.h> + +#include "gdm-session-worker-glue.h" +#include "gdm-session-worker-common.h" +#include "gdm-session-worker-enum-types.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_SESSION_WORKER (gdm_session_worker_get_type ()) +#define GDM_SESSION_WORKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_SESSION_WORKER, GdmSessionWorker)) +#define GDM_SESSION_WORKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_SESSION_WORKER, GdmSessionWorkerClass)) +#define GDM_IS_SESSION_WORKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_SESSION_WORKER)) +#define GDM_IS_SESSION_WORKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_SESSION_WORKER)) +#define GDM_SESSION_WORKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_SESSION_WORKER, GdmSessionWorkerClass)) + +typedef enum { + GDM_SESSION_WORKER_STATE_NONE = 0, + GDM_SESSION_WORKER_STATE_SETUP_COMPLETE, + GDM_SESSION_WORKER_STATE_AUTHENTICATED, + GDM_SESSION_WORKER_STATE_AUTHORIZED, + GDM_SESSION_WORKER_STATE_ACCREDITED, + GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED, + GDM_SESSION_WORKER_STATE_SESSION_OPENED, + GDM_SESSION_WORKER_STATE_SESSION_STARTED +} GdmSessionWorkerState; + +typedef struct GdmSessionWorkerPrivate GdmSessionWorkerPrivate; + +typedef struct +{ + GdmDBusWorkerSkeleton parent; + GdmSessionWorkerPrivate *priv; +} GdmSessionWorker; + +typedef struct +{ + GdmDBusWorkerSkeletonClass parent_class; +} GdmSessionWorkerClass; + +GType gdm_session_worker_get_type (void); + +GdmSessionWorker * gdm_session_worker_new (const char *server_address, + gboolean is_for_reauth) G_GNUC_MALLOC; +G_END_DECLS +#endif /* GDM_SESSION_WORKER_H */ diff --git a/daemon/gdm-session-worker.xml b/daemon/gdm-session-worker.xml new file mode 100644 index 0000000..4280fe0 --- /dev/null +++ b/daemon/gdm-session-worker.xml @@ -0,0 +1,90 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/org/gnome/DisplayManager/Worker"> + <interface name="org.gnome.DisplayManager.Worker"> + <method name="Authenticate" /> + <method name="Authorize" /> + <method name="EstablishCredentials" /> + <method name="Open"> + <arg name="session_id" direction="out" type="s"/> + </method> + <method name="SetLanguageName"> + <arg name="language" direction="in" type="s"/> + </method> + <method name="SetSessionName"> + <arg name="session_name" direction="in" type="s" /> + </method> + <method name="SetSessionDisplayMode"> + <arg name="mode" direction="in" type="s"/> + </method> + <method name="SetEnvironmentVariable"> + <arg name="name" direction="in" type="s"/> + <arg name="value" direction="in" type="s"/> + </method> + <method name="StartProgram"> + <arg name="command" direction="in" type="s"/> + <arg name="child_pid" direction="out" type="i"/> + </method> + <method name="Initialize"> + <arg name="details" direction="in" type="a{sv}"/> + </method> + <method name="Setup"> + <arg name="service_name" direction="in" type="s"/> + <arg name="x11_display_name" direction="in" type="s"/> + <arg name="x11_authority_file" direction="in" type="s"/> + <arg name="display_device" direction="in" type="s"/> + <arg name="display_seat" direction="in" type="s"/> + <arg name="hostname" direction="in" type="s"/> + <arg name="display_is_local" direction="in" type="b"/> + <arg name="display_is_initial" direction="in" type="b"/> + </method> + <method name="SetupForUser"> + <arg name="service_name" direction="in" type="s"/> + <arg name="user_name" direction="in" type="s"/> + <arg name="x11_display_name" direction="in" type="s"/> + <arg name="x11_authority_file" direction="in" type="s"/> + <arg name="display_device" direction="in" type="s"/> + <arg name="display_seat" direction="in" type="s"/> + <arg name="hostname" direction="in" type="s"/> + <arg name="display_is_local" direction="in" type="b"/> + <arg name="display_is_initial" direction="in" type="b"/> + </method> + <method name="SetupForProgram"> + <arg name="service_name" direction="in" type="s"/> + <arg name="user_name" direction="in" type="s"/> + <arg name="x11_display_name" direction="in" type="s"/> + <arg name="x11_authority_file" direction="in" type="s"/> + <arg name="display_device" direction="in" type="s"/> + <arg name="display_seat" direction="in" type="s"/> + <arg name="hostname" direction="in" type="s"/> + <arg name="display_is_local" direction="in" type="b"/> + <arg name="display_is_initial" direction="in" type="b"/> + <arg name="log_file" direction="in" type="s"/> + </method> + <method name="StartReauthentication"> + <arg name="pid_of_caller" direction="in" type="i"/> + <arg name="uid_of_caller" direction="in" type="i"/> + <arg name="address" direction="out" type="s"/> + </method> + + <signal name="SessionExited"> + <arg name="service_name" type="s" /> + <!-- This is a combination of exit code and exit + signal. Use macros in sys/wait.h to handle it. --> + <arg name="status" type="i" /> + </signal> + <signal name="SavedLanguageNameRead"> + <arg name="language_name" type="s"/> + </signal> + <signal name="SavedSessionNameRead"> + <arg name="session_name" type="s"/> + </signal> + <signal name="UsernameChanged"> + <arg name="new_username" type="s"/> + </signal> + <signal name="Reauthenticated"> + <arg name="service_name" type="s"/> + </signal> + <signal name="CancelPendingQuery"> + </signal> + </interface> +</node> diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c new file mode 100644 index 0000000..ca7d98f --- /dev/null +++ b/daemon/gdm-session.c @@ -0,0 +1,4063 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Ray Strode <rstrode@redhat.com> + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "config.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> + +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <pwd.h> +#include <grp.h> + +#include <locale.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> +#include <gio/gio.h> + +#include "gdm-session.h" +#include "gdm-session-glue.h" +#include "gdm-dbus-util.h" + +#include "gdm-session.h" +#include "gdm-session-enum-types.h" +#include "gdm-session-worker-common.h" +#include "gdm-session-worker-job.h" +#include "gdm-session-worker-glue.h" +#include "gdm-common.h" + +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +#define GDM_SESSION_DBUS_ERROR_CANCEL "org.gnome.DisplayManager.Session.Error.Cancel" +#define GDM_SESSION_DBUS_OBJECT_PATH "/org/gnome/DisplayManager/Session" + +#define GDM_WORKER_DBUS_PATH "/org/gnome/DisplayManager/Worker" + +typedef struct +{ + GdmSession *session; + GdmSessionWorkerJob *job; + GPid worker_pid; + char *service_name; + GDBusMethodInvocation *starting_invocation; + char *starting_username; + GDBusMethodInvocation *pending_invocation; + GdmDBusWorkerManager *worker_manager_interface; + GdmDBusWorker *worker_proxy; + GCancellable *worker_cancellable; + char *session_id; + guint32 is_stopping : 1; + + GPid reauth_pid_of_caller; +} GdmSessionConversation; + +struct _GdmSession +{ + GObject parent; + + /* per open scope */ + char *selected_program; + char *selected_session; + char *saved_session; + char *saved_language; + char *selected_user; + char *user_x11_authority_file; + + char *timed_login_username; + int timed_login_delay; + GList *pending_timed_login_invocations; + + GHashTable *conversations; + + GdmSessionConversation *session_conversation; + + char **conversation_environment; + + GdmDBusUserVerifier *user_verifier_interface; + GHashTable *user_verifier_extensions; + GdmDBusGreeter *greeter_interface; + GdmDBusRemoteGreeter *remote_greeter_interface; + GdmDBusChooser *chooser_interface; + + GList *pending_worker_connections; + GList *outside_connections; + + GPid session_pid; + + /* object lifetime scope */ + char *session_type; + char *display_name; + char *display_hostname; + char *display_device; + char *display_seat_id; + char *display_x11_authority_file; + gboolean display_is_local; + + GdmSessionVerificationMode verification_mode; + + uid_t allowed_user; + + char *fallback_session_name; + + GDBusServer *worker_server; + GDBusServer *outside_server; + GHashTable *environment; + + guint32 is_program_session : 1; + guint32 display_is_initial : 1; +#ifdef ENABLE_WAYLAND_SUPPORT + guint32 ignore_wayland : 1; +#endif +}; + +enum { + PROP_0, + PROP_VERIFICATION_MODE, + PROP_ALLOWED_USER, + PROP_DISPLAY_NAME, + PROP_DISPLAY_HOSTNAME, + PROP_DISPLAY_IS_LOCAL, + PROP_DISPLAY_IS_INITIAL, + PROP_SESSION_TYPE, + PROP_DISPLAY_DEVICE, + PROP_DISPLAY_SEAT_ID, + PROP_DISPLAY_X11_AUTHORITY_FILE, + PROP_USER_X11_AUTHORITY_FILE, + PROP_CONVERSATION_ENVIRONMENT, +#ifdef ENABLE_WAYLAND_SUPPORT + PROP_IGNORE_WAYLAND, +#endif +}; + +enum { + CONVERSATION_STARTED = 0, + CONVERSATION_STOPPED, + SETUP_COMPLETE, + CANCELLED, + HOSTNAME_SELECTED, + CLIENT_REJECTED, + CLIENT_CONNECTED, + CLIENT_DISCONNECTED, + CLIENT_READY_FOR_SESSION_TO_START, + DISCONNECTED, + AUTHENTICATION_FAILED, + VERIFICATION_COMPLETE, + SESSION_OPENED, + SESSION_STARTED, + SESSION_START_FAILED, + SESSION_EXITED, + SESSION_DIED, + REAUTHENTICATION_STARTED, + REAUTHENTICATED, + LAST_SIGNAL +}; + +#ifdef ENABLE_WAYLAND_SUPPORT +static gboolean gdm_session_is_wayland_session (GdmSession *self); +#endif +static void update_session_type (GdmSession *self); +static void set_session_type (GdmSession *self, + const char *session_type); +static void close_conversation (GdmSessionConversation *conversation); + +static guint signals [LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE (GdmSession, + gdm_session, + G_TYPE_OBJECT); + +static GdmSessionConversation * +find_conversation_by_name (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + conversation = g_hash_table_lookup (self->conversations, service_name); + + if (conversation == NULL) { + g_warning ("Tried to look up non-existent conversation %s", service_name); + } + + return conversation; +} + +static void +report_and_stop_conversation (GdmSession *self, + const char *service_name, + GError *error) +{ + g_dbus_error_strip_remote_error (error); + + if (self->user_verifier_interface != NULL) { + if (g_error_matches (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE)) { + gdm_dbus_user_verifier_emit_service_unavailable (self->user_verifier_interface, + service_name, + error->message); + } else { + gdm_dbus_user_verifier_emit_problem (self->user_verifier_interface, + service_name, + error->message); + } + gdm_dbus_user_verifier_emit_verification_failed (self->user_verifier_interface, + service_name); + } + + gdm_session_stop_conversation (self, service_name); +} + +static void +on_authenticate_cb (GdmDBusWorker *proxy, + GAsyncResult *res, + gpointer user_data) +{ + GdmSessionConversation *conversation = user_data; + GdmSession *self; + char *service_name; + + GError *error = NULL; + gboolean worked; + + worked = gdm_dbus_worker_call_authenticate_finish (proxy, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = conversation->session; + service_name = conversation->service_name; + + if (worked) { + gdm_session_authorize (self, service_name); + } else { + g_signal_emit (self, + signals[AUTHENTICATION_FAILED], + 0, + service_name, + conversation->worker_pid); + report_and_stop_conversation (self, service_name, error); + } +} + +static void +on_authorize_cb (GdmDBusWorker *proxy, + GAsyncResult *res, + gpointer user_data) +{ + GdmSessionConversation *conversation = user_data; + GdmSession *self; + char *service_name; + + GError *error = NULL; + gboolean worked; + + worked = gdm_dbus_worker_call_authorize_finish (proxy, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = conversation->session; + service_name = conversation->service_name; + + if (worked) { + gdm_session_accredit (self, service_name); + } else { + report_and_stop_conversation (self, service_name, error); + } +} + +static void +on_establish_credentials_cb (GdmDBusWorker *proxy, + GAsyncResult *res, + gpointer user_data) +{ + GdmSessionConversation *conversation = user_data; + GdmSession *self; + char *service_name; + + GError *error = NULL; + gboolean worked; + + worked = gdm_dbus_worker_call_establish_credentials_finish (proxy, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = g_object_ref (conversation->session); + service_name = g_strdup (conversation->service_name); + + if (worked) { + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_verification_complete (self->user_verifier_interface, + service_name); + g_signal_emit (self, signals[VERIFICATION_COMPLETE], 0, service_name); + } + + switch (self->verification_mode) { + case GDM_SESSION_VERIFICATION_MODE_LOGIN: + case GDM_SESSION_VERIFICATION_MODE_CHOOSER: + gdm_session_open_session (self, service_name); + break; + case GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE: + default: + break; + } + } else { + report_and_stop_conversation (self, service_name, error); + } + + g_free (service_name); + g_object_unref (self); +} + +static char ** +get_system_session_dirs (GdmSession *self) +{ + GArray *search_array = NULL; + char **search_dirs; + int i; + const gchar * const *system_data_dirs = g_get_system_data_dirs (); + + static const char *x_search_dirs[] = { + "/etc/X11/sessions/", + DMCONFDIR "/Sessions/", + DATADIR "/gdm/BuiltInSessions/", + DATADIR "/xsessions/", + }; + + static const char *wayland_search_dir = DATADIR "/wayland-sessions/"; + + search_array = g_array_new (TRUE, TRUE, sizeof (char *)); + + for (i = 0; system_data_dirs[i]; i++) { + gchar *dir = g_build_filename (system_data_dirs[i], "xsessions", NULL); + g_array_append_val (search_array, dir); + } + + g_array_append_vals (search_array, x_search_dirs, G_N_ELEMENTS (x_search_dirs)); + +#ifdef ENABLE_WAYLAND_SUPPORT + if (!self->ignore_wayland) { +#ifdef ENABLE_USER_DISPLAY_SERVER + g_array_prepend_val (search_array, wayland_search_dir); + + for (i = 0; system_data_dirs[i]; i++) { + gchar *dir = g_build_filename (system_data_dirs[i], "wayland-sessions", NULL); + g_array_insert_val (search_array, i, dir); + } +#else + for (i = 0; system_data_dirs[i]; i++) { + gchar *dir = g_build_filename (system_data_dirs[i], "wayland-sessions", NULL); + g_array_append_val (search_array, dir); + } + + g_array_append_val (search_array, wayland_search_dir); +#endif + } +#endif + + search_dirs = g_strdupv ((char **) search_array->data); + + g_array_free (search_array, TRUE); + + return search_dirs; +} + +static gboolean +is_prog_in_path (const char *prog) +{ + char *f; + gboolean ret; + + f = g_find_program_in_path (prog); + ret = (f != NULL); + g_free (f); + return ret; +} + +static GKeyFile * +load_key_file_for_file (GdmSession *self, + const char *file, + char **full_path) +{ + GKeyFile *key_file; + GError *error; + gboolean res; + char **search_dirs; + + key_file = g_key_file_new (); + + search_dirs = get_system_session_dirs (self), + error = NULL; + res = g_key_file_load_from_dirs (key_file, + file, + (const char **) search_dirs, + full_path, + G_KEY_FILE_NONE, + &error); + if (! res) { + g_debug ("GdmSession: File '%s' not found: %s", file, error->message); + g_error_free (error); + g_key_file_free (key_file); + key_file = NULL; + } + + g_strfreev (search_dirs); + + return key_file; +} + +static gboolean +get_session_command_for_file (GdmSession *self, + const char *file, + char **command) +{ + GKeyFile *key_file; + GError *error; + char *exec; + gboolean ret; + gboolean res; + + exec = NULL; + ret = FALSE; + if (command != NULL) { + *command = NULL; + } + + g_debug ("GdmSession: getting session command for file '%s'", file); + key_file = load_key_file_for_file (self, file, NULL); + if (key_file == NULL) { + goto out; + } + + error = NULL; + res = g_key_file_get_boolean (key_file, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_HIDDEN, + &error); + if (error == NULL && res) { + g_debug ("GdmSession: Session %s is marked as hidden", file); + goto out; + } + + exec = g_key_file_get_string (key_file, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, + NULL); + if (exec != NULL) { + res = is_prog_in_path (exec); + g_free (exec); + exec = NULL; + + if (! res) { + g_debug ("GdmSession: Command not found: %s", + G_KEY_FILE_DESKTOP_KEY_TRY_EXEC); + goto out; + } + } + + error = NULL; + exec = g_key_file_get_string (key_file, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_EXEC, + &error); + if (error != NULL) { + g_debug ("GdmSession: %s key not found: %s", + G_KEY_FILE_DESKTOP_KEY_EXEC, + error->message); + g_error_free (error); + goto out; + } + + if (command != NULL) { + *command = g_strdup (exec); + } + ret = TRUE; + +out: + g_free (exec); + + return ret; +} + +static gboolean +get_session_command_for_name (GdmSession *self, + const char *name, + char **command) +{ + gboolean res; + char *filename; + + filename = g_strdup_printf ("%s.desktop", name); + res = get_session_command_for_file (self, filename, command); + g_free (filename); + + return res; +} + +static const char * +get_default_language_name (GdmSession *self) +{ + const char *default_language; + + if (self->saved_language != NULL) { + return self->saved_language; + } + + default_language = g_hash_table_lookup (self->environment, + "LANG"); + + if (default_language != NULL) { + return default_language; + } + + return setlocale (LC_MESSAGES, NULL); +} + +static const char * +get_fallback_session_name (GdmSession *self) +{ + char **search_dirs; + int i; + char *name; + GSequence *sessions; + GSequenceIter *session; + + if (self->fallback_session_name != NULL) { + /* verify that the cached version still exists */ + if (get_session_command_for_name (self, self->fallback_session_name, NULL)) { + goto out; + } + } + + name = g_strdup ("gnome"); + if (get_session_command_for_name (self, name, NULL)) { + g_free (self->fallback_session_name); + self->fallback_session_name = name; + goto out; + } + g_free (name); + + sessions = g_sequence_new (g_free); + + search_dirs = get_system_session_dirs (self); + for (i = 0; search_dirs[i] != NULL; i++) { + GDir *dir; + const char *base_name; + + dir = g_dir_open (search_dirs[i], 0, NULL); + + if (dir == NULL) { + continue; + } + + do { + base_name = g_dir_read_name (dir); + + if (base_name == NULL) { + break; + } + + if (!g_str_has_suffix (base_name, ".desktop")) { + continue; + } + + if (get_session_command_for_file (self, base_name, NULL)) { + name = g_strndup (base_name, strlen (base_name) - strlen (".desktop")); + g_sequence_insert_sorted (sessions, name, (GCompareDataFunc) g_strcmp0, NULL); + } + } while (base_name != NULL); + + g_dir_close (dir); + } + g_strfreev (search_dirs); + + name = NULL; + session = g_sequence_get_begin_iter (sessions); + + if (g_sequence_iter_is_end (session)) + g_error ("GdmSession: no session desktop files installed, aborting..."); + + do { + name = g_sequence_get (session); + if (name) { + break; + } + session = g_sequence_iter_next (session); + } while (!g_sequence_iter_is_end (session)); + + g_free (self->fallback_session_name); + self->fallback_session_name = g_strdup (name); + + g_sequence_free (sessions); + + out: + return self->fallback_session_name; +} + +static const char * +get_default_session_name (GdmSession *self) +{ + if (self->saved_session != NULL) { + return self->saved_session; + } + + return get_fallback_session_name (self); +} + +static void +gdm_session_defaults_changed (GdmSession *self) +{ + + update_session_type (self); + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_emit_default_language_name_changed (self->greeter_interface, + get_default_language_name (self)); + gdm_dbus_greeter_emit_default_session_name_changed (self->greeter_interface, + get_default_session_name (self)); + } +} + +void +gdm_session_select_user (GdmSession *self, + const char *text) +{ + + g_debug ("GdmSession: selecting user '%s' for session '%s' (%p)", + text, + gdm_session_get_session_id (self), + self); + + g_free (self->selected_user); + self->selected_user = g_strdup (text); + + g_free (self->saved_session); + self->saved_session = NULL; + + g_free (self->saved_language); + self->saved_language = NULL; +} + +static void +cancel_pending_query (GdmSessionConversation *conversation) +{ + if (conversation->pending_invocation == NULL) { + return; + } + + g_debug ("GdmSession: Cancelling pending query"); + + g_dbus_method_invocation_return_dbus_error (conversation->pending_invocation, + GDM_SESSION_DBUS_ERROR_CANCEL, + "Operation cancelled"); + conversation->pending_invocation = NULL; +} + +static void +answer_pending_query (GdmSessionConversation *conversation, + const char *answer) +{ + g_dbus_method_invocation_return_value (conversation->pending_invocation, + g_variant_new ("(s)", answer)); + conversation->pending_invocation = NULL; +} + +static void +set_pending_query (GdmSessionConversation *conversation, + GDBusMethodInvocation *message) +{ + g_assert (conversation->pending_invocation == NULL); + + conversation->pending_invocation = g_object_ref (message); +} + +static gboolean +gdm_session_handle_choice_list_query (GdmDBusWorkerManager *worker_manager_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + const char *prompt_message, + GVariant *query, + GdmSession *self) +{ + GdmSessionConversation *conversation; + GdmDBusUserVerifierChoiceList *choice_list_interface = NULL; + + g_debug ("GdmSession: choice query for service '%s'", service_name); + + if (self->user_verifier_extensions != NULL) + choice_list_interface = g_hash_table_lookup (self->user_verifier_extensions, + gdm_dbus_user_verifier_choice_list_interface_info ()->name); + + if (choice_list_interface == NULL) { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_NOT_SUPPORTED, + "ChoiceList interface not supported by client"); + return TRUE; + } + + conversation = find_conversation_by_name (self, service_name); + if (conversation != NULL) { + set_pending_query (conversation, invocation); + + g_debug ("GdmSession: emitting choice query '%s'", prompt_message); + gdm_dbus_user_verifier_choice_list_emit_choice_query (choice_list_interface, + service_name, + prompt_message, + query); + } + + return TRUE; +} + +static gboolean +gdm_session_handle_info_query (GdmDBusWorkerManager *worker_manager_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + const char *query, + GdmSession *self) +{ + GdmSessionConversation *conversation; + + g_return_val_if_fail (self->user_verifier_interface != NULL, FALSE); + + conversation = find_conversation_by_name (self, service_name); + if (conversation != NULL) { + set_pending_query (conversation, invocation); + + gdm_dbus_user_verifier_emit_info_query (self->user_verifier_interface, + service_name, + query); + } + + return TRUE; +} + +static gboolean +gdm_session_handle_secret_info_query (GdmDBusWorkerManager *worker_manager_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + const char *query, + GdmSession *self) +{ + GdmSessionConversation *conversation; + + g_return_val_if_fail (self->user_verifier_interface != NULL, FALSE); + + conversation = find_conversation_by_name (self, service_name); + if (conversation != NULL) { + set_pending_query (conversation, invocation); + + gdm_dbus_user_verifier_emit_secret_info_query (self->user_verifier_interface, + service_name, + query); + } + + return TRUE; +} + +static gboolean +gdm_session_handle_info (GdmDBusWorkerManager *worker_manager_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + const char *info, + GdmSession *self) +{ + gdm_dbus_worker_manager_complete_info (worker_manager_interface, + invocation); + + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_info (self->user_verifier_interface, + service_name, + info); + } + + return TRUE; +} + +static void +worker_on_cancel_pending_query (GdmDBusWorker *worker, + GdmSessionConversation *conversation) +{ + cancel_pending_query (conversation); +} + +static gboolean +gdm_session_handle_problem (GdmDBusWorkerManager *worker_manager_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + const char *problem, + GdmSession *self) +{ + gdm_dbus_worker_manager_complete_problem (worker_manager_interface, + invocation); + + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_problem (self->user_verifier_interface, + service_name, + problem); + } + return TRUE; +} + +static void +on_opened (GdmDBusWorker *worker, + GAsyncResult *res, + gpointer user_data) +{ + GdmSessionConversation *conversation = user_data; + GdmSession *self; + char *service_name; + + GError *error = NULL; + gboolean worked; + char *session_id; + + worked = gdm_dbus_worker_call_open_finish (worker, + &session_id, + res, + &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = conversation->session; + service_name = conversation->service_name; + + if (worked) { + g_clear_pointer (&conversation->session_id, + (GDestroyNotify) g_free); + + conversation->session_id = g_strdup (session_id); + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_emit_session_opened (self->greeter_interface, + service_name); + } + + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_verification_complete (self->user_verifier_interface, + service_name); + g_signal_emit (self, signals[VERIFICATION_COMPLETE], 0, service_name); + } + + g_debug ("GdmSession: Emitting 'session-opened' signal"); + g_signal_emit (self, signals[SESSION_OPENED], 0, service_name, session_id); + } else { + report_and_stop_conversation (self, service_name, error); + + g_debug ("GdmSession: Emitting 'session-start-failed' signal"); + g_signal_emit (self, signals[SESSION_START_FAILED], 0, service_name, error->message); + } +} + +static void +worker_on_username_changed (GdmDBusWorker *worker, + const char *username, + GdmSessionConversation *conversation) +{ + GdmSession *self = conversation->session; + + g_debug ("GdmSession: changing username from '%s' to '%s'", + self->selected_user != NULL ? self->selected_user : "<unset>", + (strlen (username)) ? username : "<unset>"); + + gdm_session_select_user (self, (strlen (username) > 0) ? g_strdup (username) : NULL); + gdm_session_defaults_changed (self); +} + +static void +worker_on_session_exited (GdmDBusWorker *worker, + const char *service_name, + int status, + GdmSessionConversation *conversation) +{ + GdmSession *self = conversation->session; + + self->session_conversation = NULL; + + if (WIFEXITED (status)) { + g_debug ("GdmSession: Emitting 'session-exited' signal with exit code '%d'", + WEXITSTATUS (status)); + g_signal_emit (self, signals[SESSION_EXITED], 0, WEXITSTATUS (status)); + } else if (WIFSIGNALED (status)) { + g_debug ("GdmSession: Emitting 'session-died' signal with signal number '%d'", + WTERMSIG (status)); + g_signal_emit (self, signals[SESSION_DIED], 0, WTERMSIG (status)); + } +} + +static void +on_reauthentication_started_cb (GdmDBusWorker *worker, + GAsyncResult *res, + gpointer user_data) +{ + GdmSessionConversation *conversation = user_data; + GdmSession *self; + + GError *error = NULL; + gboolean worked; + char *address; + + worked = gdm_dbus_worker_call_start_reauthentication_finish (worker, + &address, + res, + &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = conversation->session; + + if (worked) { + GPid pid_of_caller = conversation->reauth_pid_of_caller; + g_debug ("GdmSession: Emitting 'reauthentication-started' signal for caller pid '%d'", pid_of_caller); + g_signal_emit (self, signals[REAUTHENTICATION_STARTED], 0, pid_of_caller, address); + } + + conversation->reauth_pid_of_caller = 0; +} + +static void +worker_on_reauthenticated (GdmDBusWorker *worker, + const char *service_name, + GdmSessionConversation *conversation) +{ + GdmSession *self = conversation->session; + g_debug ("GdmSession: Emitting 'reauthenticated' signal "); + g_signal_emit (self, signals[REAUTHENTICATED], 0, service_name); +} + +static void +worker_on_saved_language_name_read (GdmDBusWorker *worker, + const char *language_name, + GdmSessionConversation *conversation) +{ + GdmSession *self = conversation->session; + + if (strlen (language_name) > 0) { + g_free (self->saved_language); + self->saved_language = g_strdup (language_name); + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_emit_default_language_name_changed (self->greeter_interface, + language_name); + } + } +} + +static void +worker_on_saved_session_name_read (GdmDBusWorker *worker, + const char *session_name, + GdmSessionConversation *conversation) +{ + GdmSession *self = conversation->session; + + if (! get_session_command_for_name (self, session_name, NULL)) { + /* ignore sessions that don't exist */ + g_debug ("GdmSession: not using invalid .dmrc session: %s", session_name); + g_free (self->saved_session); + self->saved_session = NULL; + } else if (strcmp (session_name, + get_default_session_name (self)) != 0) { + g_free (self->saved_session); + self->saved_session = g_strdup (session_name); + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_emit_default_session_name_changed (self->greeter_interface, + session_name); + } + } + + update_session_type (self); + +} + +static GdmSessionConversation * +find_conversation_by_pid (GdmSession *self, + GPid pid) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, self->conversations); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GdmSessionConversation *conversation; + + conversation = (GdmSessionConversation *) value; + + if (conversation->worker_pid == pid) { + return conversation; + } + } + + return NULL; +} + +static gboolean +allow_worker_function (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials, + GdmSession *self) +{ + uid_t connecting_user; + + connecting_user = g_credentials_get_unix_user (credentials, NULL); + + if (connecting_user == 0) { + return TRUE; + } + + if (connecting_user == self->allowed_user) { + return TRUE; + } + + g_debug ("GdmSession: User not allowed"); + + return FALSE; +} + +static void +on_worker_connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + GdmSession *self) +{ + self->pending_worker_connections = + g_list_remove (self->pending_worker_connections, + connection); + g_object_unref (connection); +} + +static gboolean +register_worker (GdmDBusWorkerManager *worker_manager_interface, + GDBusMethodInvocation *invocation, + GdmSession *self) +{ + GdmSessionConversation *conversation; + GDBusConnection *connection; + GList *connection_node; + GCredentials *credentials; + GPid pid; + + g_debug ("GdmSession: Authenticating new connection"); + + connection = g_dbus_method_invocation_get_connection (invocation); + connection_node = g_list_find (self->pending_worker_connections, connection); + + if (connection_node == NULL) { + g_debug ("GdmSession: Ignoring connection that we aren't tracking"); + return FALSE; + } + + /* connection was ref'd when it was added to list, we're taking that + * reference over and removing it from the list + */ + self->pending_worker_connections = + g_list_delete_link (self->pending_worker_connections, + connection_node); + + g_signal_handlers_disconnect_by_func (connection, + G_CALLBACK (on_worker_connection_closed), + self); + + credentials = g_dbus_connection_get_peer_credentials (connection); + pid = g_credentials_get_unix_pid (credentials, NULL); + + conversation = find_conversation_by_pid (self, (GPid) pid); + + if (conversation == NULL) { + g_warning ("GdmSession: New worker connection is from unknown source"); + + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Connection is not from a known conversation"); + g_dbus_connection_close_sync (connection, NULL, NULL); + return TRUE; + } + + g_dbus_method_invocation_return_value (invocation, NULL); + + conversation->worker_proxy = gdm_dbus_worker_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + GDM_WORKER_DBUS_PATH, + NULL, NULL); + /* drop the reference we stole from the pending connections list + * since the proxy owns the connection now */ + g_object_unref (connection); + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (conversation->worker_proxy), G_MAXINT); + + conversation->worker_cancellable = g_cancellable_new (); + + g_signal_connect (conversation->worker_proxy, + "username-changed", + G_CALLBACK (worker_on_username_changed), conversation); + g_signal_connect (conversation->worker_proxy, + "session-exited", + G_CALLBACK (worker_on_session_exited), conversation); + g_signal_connect (conversation->worker_proxy, + "reauthenticated", + G_CALLBACK (worker_on_reauthenticated), conversation); + g_signal_connect (conversation->worker_proxy, + "saved-language-name-read", + G_CALLBACK (worker_on_saved_language_name_read), conversation); + g_signal_connect (conversation->worker_proxy, + "saved-session-name-read", + G_CALLBACK (worker_on_saved_session_name_read), conversation); + g_signal_connect (conversation->worker_proxy, + "cancel-pending-query", + G_CALLBACK (worker_on_cancel_pending_query), conversation); + + conversation->worker_manager_interface = g_object_ref (worker_manager_interface); + g_debug ("GdmSession: worker connection is %p", connection); + + g_debug ("GdmSession: Emitting conversation-started signal"); + g_signal_emit (self, signals[CONVERSATION_STARTED], 0, conversation->service_name); + + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_conversation_started (self->user_verifier_interface, + conversation->service_name); + } + + if (conversation->starting_invocation != NULL) { + if (conversation->starting_username != NULL) { + gdm_session_setup_for_user (self, conversation->service_name, conversation->starting_username); + + g_clear_pointer (&conversation->starting_username, + (GDestroyNotify) + g_free); + } else { + gdm_session_setup (self, conversation->service_name); + } + } + + g_debug ("GdmSession: Conversation started"); + + return TRUE; +} + +static void +export_worker_manager_interface (GdmSession *self, + GDBusConnection *connection) +{ + GdmDBusWorkerManager *worker_manager_interface; + + worker_manager_interface = GDM_DBUS_WORKER_MANAGER (gdm_dbus_worker_manager_skeleton_new ()); + g_signal_connect (worker_manager_interface, + "handle-hello", + G_CALLBACK (register_worker), + self); + g_signal_connect (worker_manager_interface, + "handle-info-query", + G_CALLBACK (gdm_session_handle_info_query), + self); + g_signal_connect (worker_manager_interface, + "handle-secret-info-query", + G_CALLBACK (gdm_session_handle_secret_info_query), + self); + g_signal_connect (worker_manager_interface, + "handle-info", + G_CALLBACK (gdm_session_handle_info), + self); + g_signal_connect (worker_manager_interface, + "handle-problem", + G_CALLBACK (gdm_session_handle_problem), + self); + g_signal_connect (worker_manager_interface, + "handle-choice-list-query", + G_CALLBACK (gdm_session_handle_choice_list_query), + self); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (worker_manager_interface), + connection, + GDM_SESSION_DBUS_OBJECT_PATH, + NULL); +} + +static void +unexport_worker_manager_interface (GdmSession *self, + GdmDBusWorkerManager *worker_manager_interface) +{ + + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (worker_manager_interface)); + + g_signal_handlers_disconnect_by_func (worker_manager_interface, + G_CALLBACK (register_worker), + self); + g_signal_handlers_disconnect_by_func (worker_manager_interface, + G_CALLBACK (gdm_session_handle_info_query), + self); + g_signal_handlers_disconnect_by_func (worker_manager_interface, + G_CALLBACK (gdm_session_handle_secret_info_query), + self); + g_signal_handlers_disconnect_by_func (worker_manager_interface, + G_CALLBACK (gdm_session_handle_info), + self); + g_signal_handlers_disconnect_by_func (worker_manager_interface, + G_CALLBACK (gdm_session_handle_problem), + self); + g_signal_handlers_disconnect_by_func (worker_manager_interface, + G_CALLBACK (gdm_session_handle_choice_list_query), + self); +} + +static gboolean +handle_connection_from_worker (GDBusServer *server, + GDBusConnection *connection, + GdmSession *self) +{ + + g_debug ("GdmSession: Handling new connection from worker"); + + /* add to the list of pending connections. We won't be able to + * associate it with a specific worker conversation until we have + * authenticated the connection (from the Hello handler). + */ + self->pending_worker_connections = + g_list_prepend (self->pending_worker_connections, + g_object_ref (connection)); + + g_signal_connect_object (connection, + "closed", + G_CALLBACK (on_worker_connection_closed), + self, + 0); + + export_worker_manager_interface (self, connection); + + return TRUE; +} + +static GdmSessionConversation * +begin_verification_conversation (GdmSession *self, + GDBusMethodInvocation *invocation, + const char *service_name) +{ + GdmSessionConversation *conversation = NULL; + gboolean conversation_started; + + conversation_started = gdm_session_start_conversation (self, service_name); + + if (conversation_started) { + conversation = find_conversation_by_name (self, service_name); + } + + if (conversation == NULL) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_SPAWN_FAILED, + _("Could not create authentication helper process")); + } + + return conversation; +} + +static gboolean +gdm_session_handle_client_select_choice (GdmDBusUserVerifierChoiceList *choice_list_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + const char *answer, + GdmSession *self) +{ + g_debug ("GdmSession: user selected choice '%s'", answer); + gdm_dbus_user_verifier_choice_list_complete_select_choice (choice_list_interface, invocation); + gdm_session_answer_query (self, service_name, answer); + return TRUE; +} + +static void +export_user_verifier_choice_list_interface (GdmSession *self, + GDBusConnection *connection) +{ + GdmDBusUserVerifierChoiceList *interface; + + interface = GDM_DBUS_USER_VERIFIER_CHOICE_LIST (gdm_dbus_user_verifier_choice_list_skeleton_new ()); + + g_signal_connect (interface, + "handle-select-choice", + G_CALLBACK (gdm_session_handle_client_select_choice), + self); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (interface), + connection, + GDM_SESSION_DBUS_OBJECT_PATH, + NULL); + + g_hash_table_insert (self->user_verifier_extensions, + gdm_dbus_user_verifier_choice_list_interface_info ()->name, + interface); +} + +static gboolean +gdm_session_handle_client_enable_extensions (GdmDBusUserVerifier *user_verifier_interface, + GDBusMethodInvocation *invocation, + const char * const * extensions, + GDBusConnection *connection) +{ + GdmSession *self = g_object_get_data (G_OBJECT (connection), "gdm-session"); + size_t i; + + g_hash_table_remove_all (self->user_verifier_extensions); + + for (i = 0; extensions[i] != NULL; i++) { + if (g_hash_table_lookup (self->user_verifier_extensions, extensions[i]) != NULL) + continue; + + if (strcmp (extensions[i], + gdm_dbus_user_verifier_choice_list_interface_info ()->name) == 0) + export_user_verifier_choice_list_interface (self, connection); + + } + + gdm_dbus_user_verifier_complete_enable_extensions (user_verifier_interface, invocation); + + return TRUE; +} +static gboolean +gdm_session_handle_client_begin_verification (GdmDBusUserVerifier *user_verifier_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + GdmSession *self) +{ + GdmSessionConversation *conversation; + + conversation = begin_verification_conversation (self, invocation, service_name); + + if (conversation != NULL) { + conversation->starting_invocation = g_object_ref (invocation); + conversation->starting_username = NULL; + } + + return TRUE; +} + +static gboolean +gdm_session_handle_client_begin_verification_for_user (GdmDBusUserVerifier *user_verifier_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + const char *username, + GdmSession *self) +{ + GdmSessionConversation *conversation; + + conversation = begin_verification_conversation (self, invocation, service_name); + + if (conversation != NULL) { + conversation->starting_invocation = g_object_ref (invocation); + conversation->starting_username = g_strdup (username); + } + + return TRUE; +} + +static gboolean +gdm_session_handle_client_answer_query (GdmDBusUserVerifier *user_verifier_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + const char *answer, + GdmSession *self) +{ + gdm_dbus_user_verifier_complete_answer_query (user_verifier_interface, + invocation); + gdm_session_answer_query (self, service_name, answer); + return TRUE; +} + +static gboolean +gdm_session_handle_client_cancel (GdmDBusUserVerifier *user_verifier_interface, + GDBusMethodInvocation *invocation, + GdmSession *self) +{ + gdm_dbus_user_verifier_complete_cancel (user_verifier_interface, + invocation); + gdm_session_cancel (self); + return TRUE; +} + +static gboolean +gdm_session_handle_client_select_session (GdmDBusGreeter *greeter_interface, + GDBusMethodInvocation *invocation, + const char *session, + GdmSession *self) +{ + if (gdm_session_is_running (self)) { + const char *username; + + username = gdm_session_get_username (self); + g_debug ("GdmSession: refusing to select session %s since it's already running (for user %s)", + session, + username); + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Session already running for user %s", + username); + return TRUE; + } + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_complete_select_session (greeter_interface, + invocation); + } + gdm_session_select_session (self, session); + return TRUE; +} + +static gboolean +gdm_session_handle_client_select_user (GdmDBusGreeter *greeter_interface, + GDBusMethodInvocation *invocation, + const char *username, + GdmSession *self) +{ + if (gdm_session_is_running (self)) { + const char *session_username; + + session_username = gdm_session_get_username (self); + g_debug ("GdmSession: refusing to select user %s, since session (%p) already running (for user %s)", + username, + self, + session_username); + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Session already running for user %s", + session_username); + return TRUE; + } + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_complete_select_user (greeter_interface, + invocation); + } + g_debug ("GdmSession: client selected user '%s' on session (%p)", username, self); + gdm_session_select_user (self, username); + return TRUE; +} + +static gboolean +gdm_session_handle_client_start_session_when_ready (GdmDBusGreeter *greeter_interface, + GDBusMethodInvocation *invocation, + const char *service_name, + gboolean client_is_ready, + GdmSession *self) +{ + if (gdm_session_is_running (self)) { + const char *username; + + username = gdm_session_get_username (self); + g_debug ("GdmSession: refusing to start session (%p), since it's already running (for user %s)", + self, + username); + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Session already running for user %s", + username); + return TRUE; + } + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_complete_start_session_when_ready (greeter_interface, + invocation); + } + g_signal_emit (G_OBJECT (self), + signals [CLIENT_READY_FOR_SESSION_TO_START], + 0, + service_name, + client_is_ready); + return TRUE; +} + +static gboolean +gdm_session_handle_get_timed_login_details (GdmDBusGreeter *greeter_interface, + GDBusMethodInvocation *invocation, + GdmSession *self) +{ + if (gdm_session_is_running (self)) { + const char *username; + + username = gdm_session_get_username (self); + g_debug ("GdmSession: refusing to give timed login details, session (%p) already running (for user %s)", + self, + username); + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Session already running for user %s", + username); + return TRUE; + } + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_complete_get_timed_login_details (greeter_interface, + invocation, + self->timed_login_username != NULL, + self->timed_login_username != NULL? self->timed_login_username : "", + self->timed_login_delay); + if (self->timed_login_username != NULL) { + gdm_dbus_greeter_emit_timed_login_requested (self->greeter_interface, + self->timed_login_username, + self->timed_login_delay); + } + } + return TRUE; +} + +static gboolean +gdm_session_handle_client_begin_auto_login (GdmDBusGreeter *greeter_interface, + GDBusMethodInvocation *invocation, + const char *username, + GdmSession *self) +{ + const char *session_username; + + if (gdm_session_is_running (self)) { + session_username = gdm_session_get_username (self); + g_debug ("GdmSession: refusing auto login operation, session (%p) already running for user %s (%s requested)", + self, + session_username, + username); + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Session already owned by user %s", + session_username); + return TRUE; + } + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_complete_begin_auto_login (greeter_interface, + invocation); + } + + g_debug ("GdmSession: client requesting automatic login for user '%s' on session '%s' (%p)", + username, + gdm_session_get_session_id (self), + self); + + gdm_session_setup_for_user (self, "gdm-autologin", username); + + return TRUE; +} + +static void +export_user_verifier_interface (GdmSession *self, + GDBusConnection *connection) +{ + GdmDBusUserVerifier *user_verifier_interface; + user_verifier_interface = GDM_DBUS_USER_VERIFIER (gdm_dbus_user_verifier_skeleton_new ()); + + g_object_set_data (G_OBJECT (connection), "gdm-session", self); + + g_signal_connect (user_verifier_interface, + "handle-enable-extensions", + G_CALLBACK (gdm_session_handle_client_enable_extensions), + connection); + g_signal_connect (user_verifier_interface, + "handle-begin-verification", + G_CALLBACK (gdm_session_handle_client_begin_verification), + self); + g_signal_connect (user_verifier_interface, + "handle-begin-verification-for-user", + G_CALLBACK (gdm_session_handle_client_begin_verification_for_user), + self); + g_signal_connect (user_verifier_interface, + "handle-answer-query", + G_CALLBACK (gdm_session_handle_client_answer_query), + self); + g_signal_connect (user_verifier_interface, + "handle-cancel", + G_CALLBACK (gdm_session_handle_client_cancel), + self); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (user_verifier_interface), + connection, + GDM_SESSION_DBUS_OBJECT_PATH, + NULL); + + self->user_verifier_interface = user_verifier_interface; +} + +static void +export_greeter_interface (GdmSession *self, + GDBusConnection *connection) +{ + GdmDBusGreeter *greeter_interface; + + greeter_interface = GDM_DBUS_GREETER (gdm_dbus_greeter_skeleton_new ()); + + g_signal_connect (greeter_interface, + "handle-begin-auto-login", + G_CALLBACK (gdm_session_handle_client_begin_auto_login), + self); + g_signal_connect (greeter_interface, + "handle-select-session", + G_CALLBACK (gdm_session_handle_client_select_session), + self); + g_signal_connect (greeter_interface, + "handle-select-user", + G_CALLBACK (gdm_session_handle_client_select_user), + self); + g_signal_connect (greeter_interface, + "handle-start-session-when-ready", + G_CALLBACK (gdm_session_handle_client_start_session_when_ready), + self); + g_signal_connect (greeter_interface, + "handle-get-timed-login-details", + G_CALLBACK (gdm_session_handle_get_timed_login_details), + self); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (greeter_interface), + connection, + GDM_SESSION_DBUS_OBJECT_PATH, + NULL); + + self->greeter_interface = greeter_interface; + +} + +static gboolean +gdm_session_handle_client_disconnect (GdmDBusChooser *chooser_interface, + GDBusMethodInvocation *invocation, + GdmSession *self) +{ + gdm_dbus_chooser_complete_disconnect (chooser_interface, + invocation); + g_signal_emit (self, signals[DISCONNECTED], 0); + return TRUE; +} + +static void +export_remote_greeter_interface (GdmSession *self, + GDBusConnection *connection) +{ + GdmDBusRemoteGreeter *remote_greeter_interface; + + remote_greeter_interface = GDM_DBUS_REMOTE_GREETER (gdm_dbus_remote_greeter_skeleton_new ()); + + g_signal_connect (remote_greeter_interface, + "handle-disconnect", + G_CALLBACK (gdm_session_handle_client_disconnect), + self); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (remote_greeter_interface), + connection, + GDM_SESSION_DBUS_OBJECT_PATH, + NULL); + + self->remote_greeter_interface = remote_greeter_interface; + +} + +static gboolean +gdm_session_handle_client_select_hostname (GdmDBusChooser *chooser_interface, + GDBusMethodInvocation *invocation, + const char *hostname, + GdmSession *self) +{ + + gdm_dbus_chooser_complete_select_hostname (chooser_interface, + invocation); + g_signal_emit (self, signals[HOSTNAME_SELECTED], 0, hostname); + return TRUE; +} + +static void +export_chooser_interface (GdmSession *self, + GDBusConnection *connection) +{ + GdmDBusChooser *chooser_interface; + + chooser_interface = GDM_DBUS_CHOOSER (gdm_dbus_chooser_skeleton_new ()); + + g_signal_connect (chooser_interface, + "handle-select-hostname", + G_CALLBACK (gdm_session_handle_client_select_hostname), + self); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (chooser_interface), + connection, + GDM_SESSION_DBUS_OBJECT_PATH, + NULL); + + self->chooser_interface = chooser_interface; +} + +static void +on_outside_connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + GdmSession *self) +{ + GCredentials *credentials; + GPid pid_of_client; + + g_debug ("GdmSession: external connection closed"); + + self->outside_connections = g_list_remove (self->outside_connections, + connection); + + credentials = g_dbus_connection_get_peer_credentials (connection); + pid_of_client = g_credentials_get_unix_pid (credentials, NULL); + + g_signal_emit (G_OBJECT (self), + signals [CLIENT_DISCONNECTED], + 0, + credentials, + (guint) + pid_of_client); + + g_object_unref (connection); +} + +static gboolean +handle_connection_from_outside (GDBusServer *server, + GDBusConnection *connection, + GdmSession *self) +{ + GCredentials *credentials; + GPid pid_of_client; + + g_debug ("GdmSession: Handling new connection from outside"); + + self->outside_connections = g_list_prepend (self->outside_connections, + g_object_ref (connection)); + + g_signal_connect_object (connection, + "closed", + G_CALLBACK (on_outside_connection_closed), + self, + 0); + + export_user_verifier_interface (self, connection); + + switch (self->verification_mode) { + case GDM_SESSION_VERIFICATION_MODE_LOGIN: + export_greeter_interface (self, connection); + break; + + case GDM_SESSION_VERIFICATION_MODE_CHOOSER: + export_chooser_interface (self, connection); + break; + + default: + break; + } + + if (!self->display_is_local) { + export_remote_greeter_interface (self, connection); + } + + credentials = g_dbus_connection_get_peer_credentials (connection); + pid_of_client = g_credentials_get_unix_pid (credentials, NULL); + + g_signal_emit (G_OBJECT (self), + signals [CLIENT_CONNECTED], + 0, + credentials, + (guint) + pid_of_client); + + return TRUE; +} + +static void +setup_worker_server (GdmSession *self) +{ + GDBusAuthObserver *observer; + GDBusServer *server; + GError *error = NULL; + + g_debug ("GdmSession: Creating D-Bus server for worker for session"); + + observer = g_dbus_auth_observer_new (); + g_signal_connect_object (observer, + "authorize-authenticated-peer", + G_CALLBACK (allow_worker_function), + self, + 0); + + server = gdm_dbus_setup_private_server (observer, &error); + g_object_unref (observer); + + if (server == NULL) { + g_warning ("Cannot create worker D-Bus server for the session: %s", + error->message); + return; + } + + g_signal_connect_object (server, + "new-connection", + G_CALLBACK (handle_connection_from_worker), + self, + 0); + self->worker_server = server; + + g_dbus_server_start (server); + + g_debug ("GdmSession: D-Bus server for workers listening on %s", + g_dbus_server_get_client_address (self->worker_server)); +} + +static gboolean +allow_user_function (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials, + GdmSession *self) +{ + uid_t client_uid; + GPid pid_of_client; + + client_uid = g_credentials_get_unix_user (credentials, NULL); + if (client_uid == self->allowed_user) { + return TRUE; + } + + g_debug ("GdmSession: User not allowed"); + + pid_of_client = g_credentials_get_unix_pid (credentials, NULL); + g_signal_emit (G_OBJECT (self), + signals [CLIENT_REJECTED], + 0, + credentials, + (guint) + pid_of_client); + + + return FALSE; +} + +static void +setup_outside_server (GdmSession *self) +{ + GDBusAuthObserver *observer; + GDBusServer *server; + GError *error = NULL; + + g_debug ("GdmSession: Creating D-Bus server for greeters and such for session %s (%p)", + gdm_session_get_session_id (self), + self); + + observer = g_dbus_auth_observer_new (); + g_signal_connect_object (observer, + "authorize-authenticated-peer", + G_CALLBACK (allow_user_function), + self, + 0); + + server = gdm_dbus_setup_private_server (observer, &error); + g_object_unref (observer); + + if (server == NULL) { + g_warning ("Cannot create greeter D-Bus server for the session: %s", + error->message); + return; + } + + g_signal_connect_object (server, + "new-connection", + G_CALLBACK (handle_connection_from_outside), + self, + 0); + self->outside_server = server; + + g_dbus_server_start (server); + + g_debug ("GdmSession: D-Bus server for greeters listening on %s", + g_dbus_server_get_client_address (self->outside_server)); +} + +static void +free_conversation (GdmSessionConversation *conversation) +{ + close_conversation (conversation); + + if (conversation->job != NULL) { + g_warning ("Freeing conversation '%s' with active job", conversation->service_name); + } + + g_free (conversation->service_name); + g_free (conversation->starting_username); + g_free (conversation->session_id); + g_clear_object (&conversation->worker_manager_interface); + + g_cancellable_cancel (conversation->worker_cancellable); + g_clear_object (&conversation->worker_cancellable); + + if (conversation->worker_proxy != NULL) { + g_signal_handlers_disconnect_by_func (conversation->worker_proxy, + G_CALLBACK (worker_on_username_changed), + conversation); + g_signal_handlers_disconnect_by_func (conversation->worker_proxy, + G_CALLBACK (worker_on_session_exited), + conversation); + g_signal_handlers_disconnect_by_func (conversation->worker_proxy, + G_CALLBACK (worker_on_reauthenticated), + conversation); + g_signal_handlers_disconnect_by_func (conversation->worker_proxy, + G_CALLBACK (worker_on_saved_language_name_read), + conversation); + g_signal_handlers_disconnect_by_func (conversation->worker_proxy, + G_CALLBACK (worker_on_saved_session_name_read), + conversation); + g_signal_handlers_disconnect_by_func (conversation->worker_proxy, + G_CALLBACK (worker_on_cancel_pending_query), + conversation); + g_clear_object (&conversation->worker_proxy); + } + g_clear_object (&conversation->session); + g_free (conversation); +} + +static void +load_lang_config_file (GdmSession *self) +{ + static const char *config_file = LANG_CONFIG_FILE; + gchar *contents = NULL; + gchar *p; + gchar *key; + gchar *value; + gsize length; + GError *error; + GString *line; + GRegex *re; + + if (!g_file_test (config_file, G_FILE_TEST_EXISTS)) { + g_debug ("Cannot access '%s'", config_file); + return; + } + + error = NULL; + if (!g_file_get_contents (config_file, &contents, &length, &error)) { + g_debug ("Failed to parse '%s': %s", + LANG_CONFIG_FILE, + (error && error->message) ? error->message : "(null)"); + g_error_free (error); + return; + } + + if (!g_utf8_validate (contents, length, NULL)) { + g_warning ("Invalid UTF-8 in '%s'", config_file); + g_free (contents); + return; + } + + re = g_regex_new ("(?P<key>(LANG|LANGUAGE|LC_CTYPE|LC_NUMERIC|LC_TIME|LC_COLLATE|LC_MONETARY|LC_MESSAGES|LC_PAPER|LC_NAME|LC_ADDRESS|LC_TELEPHONE|LC_MEASUREMENT|LC_IDENTIFICATION|LC_ALL))=(\")?(?P<value>[^\"]*)?(\")?", 0, 0, &error); + if (re == NULL) { + g_warning ("Failed to regex: %s", + (error && error->message) ? error->message : "(null)"); + g_error_free (error); + g_free (contents); + return; + } + + line = g_string_new (""); + for (p = contents; p && *p; p = g_utf8_find_next_char (p, NULL)) { + gunichar ch; + GMatchInfo *match_info = NULL; + + ch = g_utf8_get_char (p); + if ((ch != '\n') && (ch != '\0')) { + g_string_append_unichar (line, ch); + continue; + } + + if (line->str && g_utf8_get_char (line->str) == '#') { + goto next_line; + } + + if (!g_regex_match (re, line->str, 0, &match_info)) { + goto next_line; + } + + if (!g_match_info_matches (match_info)) { + goto next_line; + } + + key = g_match_info_fetch_named (match_info, "key"); + value = g_match_info_fetch_named (match_info, "value"); + + if (key && *key && value && *value) { + g_setenv (key, value, TRUE); + } + + g_free (key); + g_free (value); +next_line: + g_match_info_free (match_info); + g_string_set_size (line, 0); + } + + g_string_free (line, TRUE); + g_regex_unref (re); + g_free (contents); +} + +static void +unexport_and_free_user_verifier_extension (GDBusInterfaceSkeleton *interface) +{ + g_dbus_interface_skeleton_unexport (interface); + + g_object_run_dispose (G_OBJECT (interface)); + g_object_unref (interface); +} + +static void +gdm_session_init (GdmSession *self) +{ + self->conversations = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) + free_conversation); + self->environment = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + self->user_verifier_extensions = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) + unexport_and_free_user_verifier_extension); + + load_lang_config_file (self); + setup_worker_server (self); + setup_outside_server (self); +} + +static void +worker_started (GdmSessionWorkerJob *job, + GdmSessionConversation *conversation) +{ + g_debug ("GdmSession: Worker job started"); + +} + +static void +worker_exited (GdmSessionWorkerJob *job, + int code, + GdmSessionConversation *conversation) +{ + GdmSession *self = conversation->session; + + g_debug ("GdmSession: Worker job exited: %d", code); + + g_hash_table_steal (self->conversations, conversation->service_name); + + g_object_ref (conversation->job); + if (self->session_conversation == conversation) { + g_signal_emit (self, signals[SESSION_EXITED], 0, code); + self->session_conversation = NULL; + } + + g_debug ("GdmSession: Emitting conversation-stopped signal"); + g_signal_emit (self, signals[CONVERSATION_STOPPED], 0, conversation->service_name); + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_conversation_stopped (self->user_verifier_interface, + conversation->service_name); + } + g_object_unref (conversation->job); + + if (conversation->is_stopping) { + g_object_unref (conversation->job); + conversation->job = NULL; + } + + free_conversation (conversation); +} + +static void +worker_died (GdmSessionWorkerJob *job, + int signum, + GdmSessionConversation *conversation) +{ + GdmSession *self = conversation->session; + + g_debug ("GdmSession: Worker job died: %d", signum); + + g_hash_table_steal (self->conversations, conversation->service_name); + + g_object_ref (conversation->job); + if (self->session_conversation == conversation) { + g_signal_emit (self, signals[SESSION_DIED], 0, signum); + self->session_conversation = NULL; + } + + g_debug ("GdmSession: Emitting conversation-stopped signal"); + g_signal_emit (self, signals[CONVERSATION_STOPPED], 0, conversation->service_name); + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_conversation_stopped (self->user_verifier_interface, + conversation->service_name); + } + g_object_unref (conversation->job); + + if (conversation->is_stopping) { + g_object_unref (conversation->job); + conversation->job = NULL; + } + + free_conversation (conversation); +} + +static GdmSessionConversation * +start_conversation (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + char *job_name; + + conversation = g_new0 (GdmSessionConversation, 1); + conversation->session = g_object_ref (self); + conversation->service_name = g_strdup (service_name); + conversation->worker_pid = -1; + conversation->job = gdm_session_worker_job_new (); + gdm_session_worker_job_set_server_address (conversation->job, + g_dbus_server_get_client_address (self->worker_server)); + gdm_session_worker_job_set_for_reauth (conversation->job, + self->verification_mode == GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE); + + if (self->conversation_environment != NULL) { + gdm_session_worker_job_set_environment (conversation->job, + (const char * const *) + self->conversation_environment); + + } + g_signal_connect (conversation->job, + "started", + G_CALLBACK (worker_started), + conversation); + g_signal_connect (conversation->job, + "exited", + G_CALLBACK (worker_exited), + conversation); + g_signal_connect (conversation->job, + "died", + G_CALLBACK (worker_died), + conversation); + + job_name = g_strdup_printf ("gdm-session-worker [pam/%s]", service_name); + if (!gdm_session_worker_job_start (conversation->job, job_name)) { + g_object_unref (conversation->job); + g_free (conversation->service_name); + g_free (conversation); + g_free (job_name); + return NULL; + } + + g_free (job_name); + + conversation->worker_pid = gdm_session_worker_job_get_pid (conversation->job); + + return conversation; +} + +static void +close_conversation (GdmSessionConversation *conversation) +{ + GdmSession *self = conversation->session; + + if (conversation->worker_manager_interface != NULL) { + unexport_worker_manager_interface (self, conversation->worker_manager_interface); + g_clear_object (&conversation->worker_manager_interface); + } + + if (conversation->worker_proxy != NULL) { + GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (conversation->worker_proxy)); + g_dbus_connection_close_sync (connection, NULL, NULL); + } +} + +static void +stop_conversation (GdmSessionConversation *conversation) +{ + close_conversation (conversation); + + conversation->is_stopping = TRUE; + gdm_session_worker_job_stop (conversation->job); +} + +static void +stop_conversation_now (GdmSessionConversation *conversation) +{ + close_conversation (conversation); + + gdm_session_worker_job_stop_now (conversation->job); + g_clear_object (&conversation->job); +} + +#ifdef ENABLE_WAYLAND_SUPPORT +void +gdm_session_set_ignore_wayland (GdmSession *self, + gboolean ignore_wayland) +{ + self->ignore_wayland = ignore_wayland; +} +#endif + +gboolean +gdm_session_start_conversation (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + g_return_val_if_fail (GDM_IS_SESSION (self), FALSE); + + conversation = g_hash_table_lookup (self->conversations, + service_name); + + if (conversation != NULL) { + if (!conversation->is_stopping) { + g_warning ("GdmSession: conversation %s started more than once", service_name); + return FALSE; + } + g_debug ("GdmSession: stopping old conversation %s", service_name); + gdm_session_worker_job_stop_now (conversation->job); + g_object_unref (conversation->job); + conversation->job = NULL; + } + + g_debug ("GdmSession: starting conversation %s for session (%p)", service_name, self); + + conversation = start_conversation (self, service_name); + + g_hash_table_insert (self->conversations, + g_strdup (service_name), conversation); + return TRUE; +} + +void +gdm_session_stop_conversation (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + g_return_if_fail (GDM_IS_SESSION (self)); + + g_debug ("GdmSession: stopping conversation %s", service_name); + + conversation = find_conversation_by_name (self, service_name); + + if (conversation != NULL) { + stop_conversation (conversation); + } +} + +static void +on_initialization_complete_cb (GdmDBusWorker *proxy, + GAsyncResult *res, + gpointer user_data) +{ + GdmSessionConversation *conversation = user_data; + GdmSession *self; + char *service_name; + + GError *error = NULL; + GVariant *ret; + + ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = conversation->session; + service_name = conversation->service_name; + + if (ret != NULL) { + if (conversation->starting_invocation) { + g_dbus_method_invocation_return_value (conversation->starting_invocation, + NULL); + } + + g_signal_emit (G_OBJECT (self), + signals [SETUP_COMPLETE], + 0, + service_name); + + gdm_session_authenticate (self, service_name); + g_variant_unref (ret); + + } else { + g_dbus_method_invocation_return_gerror (conversation->starting_invocation, error); + report_and_stop_conversation (self, service_name, error); + g_error_free (error); + } + + g_clear_object (&conversation->starting_invocation); +} + +static void +initialize (GdmSession *self, + const char *service_name, + const char *username, + const char *log_file) +{ + GVariantBuilder details; + const char **extensions; + GdmSessionConversation *conversation; + + g_assert (service_name != NULL); + + g_variant_builder_init (&details, G_VARIANT_TYPE ("a{sv}")); + + g_variant_builder_add_parsed (&details, "{'service', <%s>}", service_name); + extensions = (const char **) g_hash_table_get_keys_as_array (self->user_verifier_extensions, NULL); + + g_variant_builder_add_parsed (&details, "{'extensions', <%^as>}", extensions); + + if (username != NULL) + g_variant_builder_add_parsed (&details, "{'username', <%s>}", username); + + if (log_file != NULL) + g_variant_builder_add_parsed (&details, "{'log-file', <%s>}", log_file); + + if (self->is_program_session) + g_variant_builder_add_parsed (&details, "{'is-program-session', <%b>}", self->is_program_session); + + if (self->display_name != NULL) + g_variant_builder_add_parsed (&details, "{'x11-display-name', <%s>}", self->display_name); + + if (self->display_hostname != NULL) + g_variant_builder_add_parsed (&details, "{'hostname', <%s>}", self->display_hostname); + + if (self->display_is_local) + g_variant_builder_add_parsed (&details, "{'display-is-local', <%b>}", self->display_is_local); + + if (self->display_is_initial) + g_variant_builder_add_parsed (&details, "{'display-is-initial', <%b>}", self->display_is_initial); + + if (self->display_device != NULL) + g_variant_builder_add_parsed (&details, "{'console', <%s>}", self->display_device); + + if (self->display_seat_id != NULL) + g_variant_builder_add_parsed (&details, "{'seat-id', <%s>}", self->display_seat_id); + + if (self->display_x11_authority_file != NULL) + g_variant_builder_add_parsed (&details, "{'x11-authority-file', <%s>}", self->display_x11_authority_file); + + g_debug ("GdmSession: Beginning initialization"); + + conversation = find_conversation_by_name (self, service_name); + if (conversation != NULL) { + gdm_dbus_worker_call_initialize (conversation->worker_proxy, + g_variant_builder_end (&details), + + conversation->worker_cancellable, + (GAsyncReadyCallback) on_initialization_complete_cb, + conversation); + } + + g_free (extensions); +} + +void +gdm_session_setup (GdmSession *self, + const char *service_name) +{ + + g_return_if_fail (GDM_IS_SESSION (self)); + + update_session_type (self); + + initialize (self, service_name, NULL, NULL); + gdm_session_defaults_changed (self); +} + + +void +gdm_session_setup_for_user (GdmSession *self, + const char *service_name, + const char *username) +{ + + g_return_if_fail (GDM_IS_SESSION (self)); + g_return_if_fail (username != NULL); + + update_session_type (self); + + g_debug ("GdmSession: Set up service %s for username %s on session (%p)", + service_name, + username, + self); + gdm_session_select_user (self, username); + + self->is_program_session = FALSE; + initialize (self, service_name, self->selected_user, NULL); + gdm_session_defaults_changed (self); +} + +void +gdm_session_setup_for_program (GdmSession *self, + const char *service_name, + const char *username, + const char *log_file) +{ + + g_return_if_fail (GDM_IS_SESSION (self)); + + self->is_program_session = TRUE; + initialize (self, service_name, username, log_file); +} + +void +gdm_session_authenticate (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + g_return_if_fail (GDM_IS_SESSION (self)); + + conversation = find_conversation_by_name (self, service_name); + if (conversation != NULL) { + gdm_dbus_worker_call_authenticate (conversation->worker_proxy, + conversation->worker_cancellable, + (GAsyncReadyCallback) on_authenticate_cb, + conversation); + } +} + +void +gdm_session_authorize (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + g_return_if_fail (GDM_IS_SESSION (self)); + + conversation = find_conversation_by_name (self, service_name); + if (conversation != NULL) { + gdm_dbus_worker_call_authorize (conversation->worker_proxy, + conversation->worker_cancellable, + (GAsyncReadyCallback) on_authorize_cb, + conversation); + } +} + +void +gdm_session_accredit (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + g_return_if_fail (GDM_IS_SESSION (self)); + + conversation = find_conversation_by_name (self, service_name); + if (conversation != NULL) { + gdm_dbus_worker_call_establish_credentials (conversation->worker_proxy, + conversation->worker_cancellable, + (GAsyncReadyCallback) on_establish_credentials_cb, + conversation); + } + +} + +static void +send_environment_variable (const char *key, + const char *value, + GdmSessionConversation *conversation) +{ + gdm_dbus_worker_call_set_environment_variable (conversation->worker_proxy, + key, value, + conversation->worker_cancellable, + NULL, NULL); +} + +static void +send_environment (GdmSession *self, + GdmSessionConversation *conversation) +{ + + g_hash_table_foreach (self->environment, + (GHFunc) send_environment_variable, + conversation); +} + +void +gdm_session_send_environment (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + g_return_if_fail (GDM_IS_SESSION (self)); + + conversation = find_conversation_by_name (self, service_name); + if (conversation != NULL) { + send_environment (self, conversation); + } +} + +static const char * +get_session_name (GdmSession *self) +{ + /* FIXME: test the session names before we use them? */ + + if (self->selected_session != NULL) { + return self->selected_session; + } + + return get_default_session_name (self); +} + +static char * +get_session_command (GdmSession *self) +{ + gboolean res; + char *command; + const char *session_name; + + session_name = get_session_name (self); + + command = NULL; + res = get_session_command_for_name (self, session_name, &command); + if (! res) { + g_critical ("Cannot find a command for specified session: %s", session_name); + exit (EXIT_FAILURE); + } + + return command; +} + +static gchar * +get_session_desktop_names (GdmSession *self) +{ + gchar *filename; + GKeyFile *keyfile; + gchar *desktop_names = NULL; + + if (self->selected_program != NULL) { + return g_strdup ("GNOME-Greeter:GNOME"); + } + + filename = g_strdup_printf ("%s.desktop", get_session_name (self)); + g_debug ("GdmSession: getting desktop names for file '%s'", filename); + keyfile = load_key_file_for_file (self, filename, NULL); + if (keyfile != NULL) { + gchar **names; + + names = g_key_file_get_string_list (keyfile, G_KEY_FILE_DESKTOP_GROUP, + "DesktopNames", NULL, NULL); + if (names != NULL) { + desktop_names = g_strjoinv (":", names); + + g_strfreev (names); + } + } + + g_key_file_free (keyfile); + g_free (filename); + return desktop_names; +} + +void +gdm_session_set_environment_variable (GdmSession *self, + const char *key, + const char *value) +{ + g_return_if_fail (key != NULL); + g_return_if_fail (value != NULL); + + g_hash_table_replace (self->environment, + g_strdup (key), + g_strdup (value)); +} + +static void +set_up_session_language (GdmSession *self) +{ + char **environment; + int i; + const char *value; + + environment = g_listenv (); + for (i = 0; environment[i] != NULL; i++) { + if (strcmp (environment[i], "LANG") != 0 && + strcmp (environment[i], "LANGUAGE") != 0 && + !g_str_has_prefix (environment[i], "LC_")) { + continue; + } + + value = g_getenv (environment[i]); + + gdm_session_set_environment_variable (self, + environment[i], + value); + } + g_strfreev (environment); +} + +static void +set_up_session_environment (GdmSession *self) +{ + GdmSessionDisplayMode display_mode; + gchar *desktop_names; + char *locale; + + if (self->selected_program == NULL) { + gdm_session_set_environment_variable (self, + "GDMSESSION", + get_session_name (self)); + gdm_session_set_environment_variable (self, + "DESKTOP_SESSION", + get_session_name (self)); + gdm_session_set_environment_variable (self, + "XDG_SESSION_DESKTOP", + get_session_name (self)); + } + + desktop_names = get_session_desktop_names (self); + if (desktop_names != NULL) { + gdm_session_set_environment_variable (self, "XDG_CURRENT_DESKTOP", desktop_names); + } + + set_up_session_language (self); + + locale = g_strdup (get_default_language_name (self)); + + if (locale != NULL && locale[0] != '\0') { + gdm_session_set_environment_variable (self, + "LANG", + locale); + gdm_session_set_environment_variable (self, + "GDM_LANG", + locale); + } + + g_free (locale); + + display_mode = gdm_session_get_display_mode (self); + if (display_mode == GDM_SESSION_DISPLAY_MODE_REUSE_VT) { + gdm_session_set_environment_variable (self, + "DISPLAY", + self->display_name); + + if (self->user_x11_authority_file != NULL) { + gdm_session_set_environment_variable (self, + "XAUTHORITY", + self->user_x11_authority_file); + } + } + + if (g_getenv ("WINDOWPATH") != NULL) { + gdm_session_set_environment_variable (self, + "WINDOWPATH", + g_getenv ("WINDOWPATH")); + } + + g_free (desktop_names); +} + +static void +send_display_mode (GdmSession *self, + GdmSessionConversation *conversation) +{ + GdmSessionDisplayMode mode; + + mode = gdm_session_get_display_mode (self); + gdm_dbus_worker_call_set_session_display_mode (conversation->worker_proxy, + gdm_session_display_mode_to_string (mode), + conversation->worker_cancellable, + NULL, NULL); +} + +static void +send_session_type (GdmSession *self, + GdmSessionConversation *conversation) +{ + const char *session_type = "x11"; + + if (self->session_type != NULL) { + session_type = self->session_type; + } + + gdm_dbus_worker_call_set_environment_variable (conversation->worker_proxy, + "XDG_SESSION_TYPE", + session_type, + conversation->worker_cancellable, + NULL, NULL); +} + +void +gdm_session_open_session (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + g_return_if_fail (GDM_IS_SESSION (self)); + + conversation = find_conversation_by_name (self, service_name); + + if (conversation != NULL) { + send_display_mode (self, conversation); + send_session_type (self, conversation); + + gdm_dbus_worker_call_open (conversation->worker_proxy, + conversation->worker_cancellable, + (GAsyncReadyCallback) on_opened, conversation); + } +} + +static void +stop_all_other_conversations (GdmSession *self, + GdmSessionConversation *conversation_to_keep, + gboolean now) +{ + GHashTableIter iter; + gpointer key, value; + + if (self->conversations == NULL) { + return; + } + + if (conversation_to_keep == NULL) { + g_debug ("GdmSession: Stopping all conversations"); + } else { + g_debug ("GdmSession: Stopping all conversations except for %s", + conversation_to_keep->service_name); + } + + g_hash_table_iter_init (&iter, self->conversations); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GdmSessionConversation *conversation; + + conversation = (GdmSessionConversation *) value; + + if (conversation == conversation_to_keep) { + if (now) { + g_hash_table_iter_steal (&iter); + g_free (key); + } + } else { + if (now) { + stop_conversation_now (conversation); + } else { + stop_conversation (conversation); + } + } + } + + if (now) { + g_hash_table_remove_all (self->conversations); + + if (conversation_to_keep != NULL) { + g_hash_table_insert (self->conversations, + g_strdup (conversation_to_keep->service_name), + conversation_to_keep); + } + + if (self->session_conversation != conversation_to_keep) { + self->session_conversation = NULL; + } + } + +} + +static void +on_start_program_cb (GdmDBusWorker *worker, + GAsyncResult *res, + gpointer user_data) +{ + GdmSessionConversation *conversation = user_data; + GdmSession *self; + char *service_name; + + GError *error = NULL; + gboolean worked; + GPid pid; + + worked = gdm_dbus_worker_call_start_program_finish (worker, + &pid, + res, + &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = conversation->session; + service_name = conversation->service_name; + + if (worked) { + self->session_pid = pid; + self->session_conversation = conversation; + + g_debug ("GdmSession: Emitting 'session-started' signal with pid '%d'", pid); + g_signal_emit (self, signals[SESSION_STARTED], 0, service_name, pid); + } else { + gdm_session_stop_conversation (self, service_name); + + g_debug ("GdmSession: Emitting 'session-start-failed' signal"); + g_signal_emit (self, signals[SESSION_START_FAILED], 0, service_name, error->message); + } +} + +void +gdm_session_start_session (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + GdmSessionDisplayMode display_mode; + gboolean is_x11 = TRUE; + gboolean run_launcher = FALSE; + gboolean allow_remote_connections = FALSE; + char *command; + char *program; + gboolean register_session; + + g_return_if_fail (GDM_IS_SESSION (self)); + g_return_if_fail (self->session_conversation == NULL); + + conversation = find_conversation_by_name (self, service_name); + + if (conversation == NULL) { + g_warning ("GdmSession: Tried to start session of " + "nonexistent conversation %s", service_name); + return; + } + + stop_all_other_conversations (self, conversation, FALSE); + + display_mode = gdm_session_get_display_mode (self); + +#ifdef ENABLE_WAYLAND_SUPPORT + is_x11 = g_strcmp0 (self->session_type, "wayland") != 0; +#endif + + if (display_mode == GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED || + display_mode == GDM_SESSION_DISPLAY_MODE_NEW_VT) { + run_launcher = TRUE; + } + + register_session = !gdm_session_session_registers (self); + + if (self->selected_program == NULL) { + gboolean run_xsession_script; + + command = get_session_command (self); + + run_xsession_script = !gdm_session_bypasses_xsession (self); + + if (self->display_is_local) { + gboolean disallow_tcp = TRUE; + gdm_settings_direct_get_boolean (GDM_KEY_DISALLOW_TCP, &disallow_tcp); + allow_remote_connections = !disallow_tcp; + } else { + allow_remote_connections = TRUE; + } + + if (run_launcher) { + if (is_x11) { + program = g_strdup_printf (LIBEXECDIR "/gdm-x-session %s%s %s\"%s\"", + register_session ? "--register-session " : "", + run_xsession_script? "--run-script " : "", + allow_remote_connections? "--allow-remote-connections " : "", + command); + } else { + program = g_strdup_printf (LIBEXECDIR "/gdm-wayland-session %s\"%s\"", + register_session ? "--register-session " : "", + command); + } + } else if (run_xsession_script) { + program = g_strdup_printf (GDMCONFDIR "/Xsession \"%s\"", command); + } else { + program = g_strdup (command); + } + + g_free (command); + } else { + /* FIXME: + * Always use a separate DBus bus for each greeter session. + * Firstly, this means that if we run multiple greeter session + * (which we really should not do, but have to currently), then + * each one will get its own DBus session bus. + * But, we also explicitly do this for seat0, because that way + * it cannot make use of systemd to run the GNOME session. This + * prevents the session lookup logic from getting confused. + * This has a similar effect as passing --builtin to gnome-session. + * + * We really should not be doing this. But the fix is to use + * separate dynamically created users and that requires some + * major refactorings. + */ + if (run_launcher) { + if (is_x11) { + program = g_strdup_printf (LIBEXECDIR "/gdm-x-session %s\"dbus-run-session -- %s\"", + register_session ? "--register-session " : "", + self->selected_program); + } else { + program = g_strdup_printf (LIBEXECDIR "/gdm-wayland-session %s\"dbus-run-session -- %s\"", + register_session ? "--register-session " : "", + self->selected_program); + } + } else { + program = g_strdup_printf ("dbus-run-session -- %s", + self->selected_program); + } + } + + set_up_session_environment (self); + send_environment (self, conversation); + + gdm_dbus_worker_call_start_program (conversation->worker_proxy, + program, + conversation->worker_cancellable, + (GAsyncReadyCallback) on_start_program_cb, + conversation); + g_free (program); +} + +static void +stop_all_conversations (GdmSession *self) +{ + stop_all_other_conversations (self, NULL, TRUE); +} + +static void +do_reset (GdmSession *self) +{ + stop_all_conversations (self); + + g_list_free_full (self->pending_worker_connections, g_object_unref); + self->pending_worker_connections = NULL; + + g_free (self->selected_user); + self->selected_user = NULL; + + g_free (self->selected_session); + self->selected_session = NULL; + + g_free (self->saved_session); + self->saved_session = NULL; + + g_free (self->saved_language); + self->saved_language = NULL; + + g_free (self->user_x11_authority_file); + self->user_x11_authority_file = NULL; + + g_hash_table_remove_all (self->environment); + + self->session_pid = -1; + self->session_conversation = NULL; +} + +void +gdm_session_close (GdmSession *self) +{ + + g_return_if_fail (GDM_IS_SESSION (self)); + + g_debug ("GdmSession: Closing session"); + do_reset (self); + + g_list_free_full (self->outside_connections, g_object_unref); + self->outside_connections = NULL; +} + +void +gdm_session_answer_query (GdmSession *self, + const char *service_name, + const char *text) +{ + GdmSessionConversation *conversation; + + g_return_if_fail (GDM_IS_SESSION (self)); + + conversation = find_conversation_by_name (self, service_name); + + if (conversation != NULL) { + answer_pending_query (conversation, text); + } +} + +void +gdm_session_cancel (GdmSession *self) +{ + g_return_if_fail (GDM_IS_SESSION (self)); + + g_signal_emit (G_OBJECT (self), signals [CANCELLED], 0); +} + +void +gdm_session_reset (GdmSession *self) +{ + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_reset (self->user_verifier_interface); + } + + do_reset (self); +} + +void +gdm_session_set_timed_login_details (GdmSession *self, + const char *username, + int delay) +{ + g_debug ("GdmSession: timed login details %s %d", username, delay); + self->timed_login_username = g_strdup (username); + self->timed_login_delay = delay; +} + +gboolean +gdm_session_is_running (GdmSession *self) +{ + return self->session_pid > 0; +} + +gboolean +gdm_session_client_is_connected (GdmSession *self) +{ + g_return_val_if_fail (GDM_IS_SESSION (self), FALSE); + + return self->outside_connections != NULL; +} + +uid_t +gdm_session_get_allowed_user (GdmSession *self) +{ + return self->allowed_user; +} + +void +gdm_session_start_reauthentication (GdmSession *session, + GPid pid_of_caller, + uid_t uid_of_caller) +{ + GdmSessionConversation *conversation = session->session_conversation; + + g_return_if_fail (conversation != NULL); + + g_debug ("GdmSession: starting reauthentication for session %s for client with pid %d", + conversation->session_id, + (int) uid_of_caller); + + conversation->reauth_pid_of_caller = pid_of_caller; + + gdm_dbus_worker_call_start_reauthentication (conversation->worker_proxy, + (int) pid_of_caller, + (int) uid_of_caller, + conversation->worker_cancellable, + (GAsyncReadyCallback) on_reauthentication_started_cb, + conversation); +} + +const char * +gdm_session_get_server_address (GdmSession *self) +{ + g_return_val_if_fail (GDM_IS_SESSION (self), NULL); + + return g_dbus_server_get_client_address (self->outside_server); +} + +const char * +gdm_session_get_username (GdmSession *self) +{ + g_return_val_if_fail (GDM_IS_SESSION (self), NULL); + + return self->selected_user; +} + +const char * +gdm_session_get_display_device (GdmSession *self) +{ + g_return_val_if_fail (GDM_IS_SESSION (self), NULL); + + return self->display_device; +} + +const char * +gdm_session_get_display_seat_id (GdmSession *self) +{ + g_return_val_if_fail (GDM_IS_SESSION (self), NULL); + + return g_strdup (self->display_seat_id); +} + +const char * +gdm_session_get_session_id (GdmSession *self) +{ + GdmSessionConversation *conversation; + + g_return_val_if_fail (GDM_IS_SESSION (self), NULL); + + conversation = self->session_conversation; + + if (conversation == NULL) { + return NULL; + } + + return conversation->session_id; +} + +const char * +gdm_session_get_conversation_session_id (GdmSession *self, + const char *service_name) +{ + GdmSessionConversation *conversation; + + g_return_val_if_fail (GDM_IS_SESSION (self), NULL); + + conversation = find_conversation_by_name (self, service_name); + + if (conversation == NULL) { + return NULL; + } + + return conversation->session_id; +} + +static char * +get_session_filename (GdmSession *self) +{ + return g_strdup_printf ("%s.desktop", get_session_name (self)); +} + +#ifdef ENABLE_WAYLAND_SUPPORT +static gboolean +gdm_session_is_wayland_session (GdmSession *self) +{ + GKeyFile *key_file; + gboolean is_wayland_session = FALSE; + char *filename; + char *full_path = NULL; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (GDM_IS_SESSION (self), FALSE); + + filename = get_session_filename (self); + + key_file = load_key_file_for_file (self, filename, &full_path); + + if (key_file == NULL) { + goto out; + } + + if (full_path != NULL && strstr (full_path, "/wayland-sessions/") != NULL) { + is_wayland_session = TRUE; + } + g_debug ("GdmSession: checking if file '%s' is wayland session: %s", filename, is_wayland_session? "yes" : "no"); + +out: + g_clear_pointer (&key_file, g_key_file_free); + g_free (filename); + return is_wayland_session; +} +#endif + +static void +update_session_type (GdmSession *self) +{ +#ifdef ENABLE_WAYLAND_SUPPORT + gboolean is_wayland_session; + + is_wayland_session = gdm_session_is_wayland_session (self); + if (is_wayland_session) { + set_session_type (self, "wayland"); + } else { + set_session_type (self, NULL); + } +#endif +} + +gboolean +gdm_session_session_registers (GdmSession *self) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GKeyFile) key_file = NULL; + gboolean session_registers = FALSE; + g_autofree char *filename = NULL; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (GDM_IS_SESSION (self), FALSE); + + filename = get_session_filename (self); + + key_file = load_key_file_for_file (self, filename, NULL); + + session_registers = g_key_file_get_boolean (key_file, + G_KEY_FILE_DESKTOP_GROUP, + "X-GDM-SessionRegisters", + &error); + if (!session_registers && + error != NULL && + !g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { + g_warning ("GdmSession: Couldn't read session file '%s'", filename); + return FALSE; + } + + g_debug ("GdmSession: '%s' %s self", filename, + session_registers ? "registers" : "does not register"); + + return session_registers; +} + +gboolean +gdm_session_bypasses_xsession (GdmSession *self) +{ + GError *error; + GKeyFile *key_file; + gboolean res; + gboolean bypasses_xsession = FALSE; + char *filename = NULL; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (GDM_IS_SESSION (self), FALSE); + +#ifdef ENABLE_WAYLAND_SUPPORT + if (gdm_session_is_wayland_session (self)) { + bypasses_xsession = TRUE; + goto out; + } +#endif + + filename = get_session_filename (self); + + key_file = load_key_file_for_file (self, filename, NULL); + + error = NULL; + res = g_key_file_has_key (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GDM-BypassXsession", NULL); + if (!res) { + goto out; + } else { + bypasses_xsession = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GDM-BypassXsession", &error); + if (error) { + bypasses_xsession = FALSE; + g_error_free (error); + goto out; + } + } + +out: + if (bypasses_xsession) { + g_debug ("GdmSession: Session %s bypasses Xsession wrapper script", filename); + } + g_free (filename); + return bypasses_xsession; +} + +GdmSessionDisplayMode +gdm_session_get_display_mode (GdmSession *self) +{ + g_debug ("GdmSession: type %s, program? %s, seat %s", + self->session_type, + self->is_program_session? "yes" : "no", + self->display_seat_id); + + /* Non-seat0 sessions share their X server with their login screen + * for now. + */ + if (g_strcmp0 (self->display_seat_id, "seat0") != 0) { + return GDM_SESSION_DISPLAY_MODE_REUSE_VT; + } + +#ifdef ENABLE_USER_DISPLAY_SERVER + /* All other cases (wayland login screen, X login screen, + * wayland user session, X user session) use the NEW_VT + * display mode. That display mode means that GDM allocates + * a new VT and jumps to it before starting the session. The + * session is expected to use logind to gain access to the + * display and input devices. + * + * GDM also has a LOGIND_MANAGED display mode which we can't + * use yet. The difference between it and NEW_VT, is with it, + * GDM doesn't do any VT handling at all, expecting the session + * and logind to do everything. The problem is, for wayland + * sessions it will cause flicker until * this bug is fixed: + * + * https://bugzilla.gnome.org/show_bug.cgi?id=745141 + * + * Likewise, for X sessions it's problematic because + * 1) X doesn't call TakeControl before switching VTs + * 2) X doesn't support getting started "in the background" + * right now. It will die with an error if logind devices + * are paused when handed out. + */ + return GDM_SESSION_DISPLAY_MODE_NEW_VT; +#else + +#ifdef ENABLE_WAYLAND_SUPPORT + /* Wayland sessions are for now assumed to run in a + * mutter-launch-like environment, so we allocate + * a new VT for them. */ + if (g_strcmp0 (self->session_type, "wayland") == 0) { + return GDM_SESSION_DISPLAY_MODE_NEW_VT; + } +#endif + return GDM_SESSION_DISPLAY_MODE_REUSE_VT; +#endif +} + +void +gdm_session_select_program (GdmSession *self, + const char *text) +{ + + g_free (self->selected_program); + + self->selected_program = g_strdup (text); +} + +void +gdm_session_select_session (GdmSession *self, + const char *text) +{ + GHashTableIter iter; + gpointer key, value; + + g_debug ("GdmSession: selecting session '%s'", text); + + g_free (self->selected_session); + self->selected_session = g_strdup (text); + + update_session_type (self); + + g_hash_table_iter_init (&iter, self->conversations); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GdmSessionConversation *conversation; + + conversation = (GdmSessionConversation *) value; + + gdm_dbus_worker_call_set_session_name (conversation->worker_proxy, + get_session_name (self), + conversation->worker_cancellable, + NULL, NULL); + } +} + +static void +set_display_name (GdmSession *self, + const char *name) +{ + g_free (self->display_name); + self->display_name = g_strdup (name); +} + +static void +set_display_hostname (GdmSession *self, + const char *name) +{ + g_free (self->display_hostname); + self->display_hostname = g_strdup (name); +} + +static void +set_display_device (GdmSession *self, + const char *name) +{ + g_debug ("GdmSession: Setting display device: %s", name); + g_free (self->display_device); + self->display_device = g_strdup (name); +} + +static void +set_display_seat_id (GdmSession *self, + const char *name) +{ + g_free (self->display_seat_id); + self->display_seat_id = g_strdup (name); +} + +static void +set_user_x11_authority_file (GdmSession *self, + const char *name) +{ + g_free (self->user_x11_authority_file); + self->user_x11_authority_file = g_strdup (name); +} + +static void +set_display_x11_authority_file (GdmSession *self, + const char *name) +{ + g_free (self->display_x11_authority_file); + self->display_x11_authority_file = g_strdup (name); +} + +static void +set_display_is_local (GdmSession *self, + gboolean is_local) +{ + self->display_is_local = is_local; +} + +static void +set_display_is_initial (GdmSession *self, + gboolean is_initial) +{ + self->display_is_initial = is_initial; +} + +static void +set_verification_mode (GdmSession *self, + GdmSessionVerificationMode verification_mode) +{ + self->verification_mode = verification_mode; +} + +static void +set_allowed_user (GdmSession *self, + uid_t allowed_user) +{ + self->allowed_user = allowed_user; +} + +static void +set_conversation_environment (GdmSession *self, + char **environment) +{ + g_strfreev (self->conversation_environment); + self->conversation_environment = g_strdupv (environment); +} + +static void +set_session_type (GdmSession *self, + const char *session_type) +{ + + if (g_strcmp0 (self->session_type, session_type) != 0) { + g_debug ("GdmSession: setting session to type '%s'", session_type? session_type : ""); + g_free (self->session_type); + self->session_type = g_strdup (session_type); + } +} + +static void +gdm_session_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmSession *self; + + self = GDM_SESSION (object); + + switch (prop_id) { + case PROP_SESSION_TYPE: + set_session_type (self, g_value_get_string (value)); + break; + case PROP_DISPLAY_NAME: + set_display_name (self, g_value_get_string (value)); + break; + case PROP_DISPLAY_HOSTNAME: + set_display_hostname (self, g_value_get_string (value)); + break; + case PROP_DISPLAY_DEVICE: + set_display_device (self, g_value_get_string (value)); + break; + case PROP_DISPLAY_SEAT_ID: + set_display_seat_id (self, g_value_get_string (value)); + break; + case PROP_USER_X11_AUTHORITY_FILE: + set_user_x11_authority_file (self, g_value_get_string (value)); + break; + case PROP_DISPLAY_X11_AUTHORITY_FILE: + set_display_x11_authority_file (self, g_value_get_string (value)); + break; + case PROP_DISPLAY_IS_LOCAL: + set_display_is_local (self, g_value_get_boolean (value)); + break; + case PROP_DISPLAY_IS_INITIAL: + set_display_is_initial (self, g_value_get_boolean (value)); + break; + case PROP_VERIFICATION_MODE: + set_verification_mode (self, g_value_get_enum (value)); + break; + case PROP_ALLOWED_USER: + set_allowed_user (self, g_value_get_uint (value)); + break; + case PROP_CONVERSATION_ENVIRONMENT: + set_conversation_environment (self, g_value_get_pointer (value)); + break; +#ifdef ENABLE_WAYLAND_SUPPORT + case PROP_IGNORE_WAYLAND: + gdm_session_set_ignore_wayland (self, g_value_get_boolean (value)); + break; +#endif + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_session_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmSession *self; + + self = GDM_SESSION (object); + + switch (prop_id) { + case PROP_SESSION_TYPE: + g_value_set_string (value, self->session_type); + break; + case PROP_DISPLAY_NAME: + g_value_set_string (value, self->display_name); + break; + case PROP_DISPLAY_HOSTNAME: + g_value_set_string (value, self->display_hostname); + break; + case PROP_DISPLAY_DEVICE: + g_value_set_string (value, self->display_device); + break; + case PROP_DISPLAY_SEAT_ID: + g_value_set_string (value, self->display_seat_id); + break; + case PROP_USER_X11_AUTHORITY_FILE: + g_value_set_string (value, self->user_x11_authority_file); + break; + case PROP_DISPLAY_X11_AUTHORITY_FILE: + g_value_set_string (value, self->display_x11_authority_file); + break; + case PROP_DISPLAY_IS_LOCAL: + g_value_set_boolean (value, self->display_is_local); + break; + case PROP_DISPLAY_IS_INITIAL: + g_value_set_boolean (value, self->display_is_initial); + break; + case PROP_VERIFICATION_MODE: + g_value_set_enum (value, self->verification_mode); + break; + case PROP_ALLOWED_USER: + g_value_set_uint (value, self->allowed_user); + break; + case PROP_CONVERSATION_ENVIRONMENT: + g_value_set_pointer (value, self->environment); + break; +#ifdef ENABLE_WAYLAND_SUPPORT + case PROP_IGNORE_WAYLAND: + g_value_set_boolean (value, self->ignore_wayland); + break; +#endif + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_session_dispose (GObject *object) +{ + GdmSession *self; + + self = GDM_SESSION (object); + + g_debug ("GdmSession: Disposing session"); + + gdm_session_close (self); + + g_clear_pointer (&self->conversations, + g_hash_table_unref); + + g_clear_object (&self->user_verifier_interface); + g_clear_pointer (&self->user_verifier_extensions, + g_hash_table_unref); + g_clear_object (&self->greeter_interface); + g_clear_object (&self->chooser_interface); + + g_free (self->display_name); + self->display_name = NULL; + + g_free (self->display_hostname); + self->display_hostname = NULL; + + g_free (self->display_device); + self->display_device = NULL; + + g_free (self->display_seat_id); + self->display_seat_id = NULL; + + g_free (self->display_x11_authority_file); + self->display_x11_authority_file = NULL; + + g_strfreev (self->conversation_environment); + self->conversation_environment = NULL; + + if (self->worker_server != NULL) { + g_dbus_server_stop (self->worker_server); + g_clear_object (&self->worker_server); + } + + if (self->outside_server != NULL) { + g_dbus_server_stop (self->outside_server); + g_clear_object (&self->outside_server); + } + + if (self->environment != NULL) { + g_hash_table_destroy (self->environment); + self->environment = NULL; + } + + G_OBJECT_CLASS (gdm_session_parent_class)->dispose (object); +} + +static void +gdm_session_finalize (GObject *object) +{ + GdmSession *self; + GObjectClass *parent_class; + + self = GDM_SESSION (object); + + g_free (self->selected_user); + g_free (self->selected_session); + g_free (self->saved_session); + g_free (self->saved_language); + + g_free (self->fallback_session_name); + + parent_class = G_OBJECT_CLASS (gdm_session_parent_class); + + if (parent_class->finalize != NULL) + parent_class->finalize (object); +} + +static GObject * +gdm_session_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmSession *self; + + self = GDM_SESSION (G_OBJECT_CLASS (gdm_session_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + return G_OBJECT (self); +} + +static void +gdm_session_class_init (GdmSessionClass *session_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (session_class); + + object_class->get_property = gdm_session_get_property; + object_class->set_property = gdm_session_set_property; + object_class->constructor = gdm_session_constructor; + object_class->dispose = gdm_session_dispose; + object_class->finalize = gdm_session_finalize; + + signals [CONVERSATION_STARTED] = + g_signal_new ("conversation-started", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [CONVERSATION_STOPPED] = + g_signal_new ("conversation-stopped", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [SETUP_COMPLETE] = + g_signal_new ("setup-complete", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + signals [AUTHENTICATION_FAILED] = + g_signal_new ("authentication-failed", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_INT); + signals [VERIFICATION_COMPLETE] = + g_signal_new ("verification-complete", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + signals [SESSION_OPENED] = + g_signal_new ("session-opened", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); + signals [SESSION_STARTED] = + g_signal_new ("session-started", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_INT); + signals [SESSION_START_FAILED] = + g_signal_new ("session-start-failed", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, + G_TYPE_STRING, G_TYPE_STRING); + signals [SESSION_EXITED] = + g_signal_new ("session-exited", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + signals [SESSION_DIED] = + g_signal_new ("session-died", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + signals [REAUTHENTICATION_STARTED] = + g_signal_new ("reauthentication-started", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_STRING); + signals [REAUTHENTICATED] = + g_signal_new ("reauthenticated", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + signals [CANCELLED] = + g_signal_new ("cancelled", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + signals [CLIENT_REJECTED] = + g_signal_new ("client-rejected", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_CREDENTIALS, + G_TYPE_UINT); + + signals [CLIENT_CONNECTED] = + g_signal_new ("client-connected", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_CREDENTIALS, + G_TYPE_UINT); + + signals [CLIENT_DISCONNECTED] = + g_signal_new ("client-disconnected", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_CREDENTIALS, + G_TYPE_UINT); + signals [CLIENT_READY_FOR_SESSION_TO_START] = + g_signal_new ("client-ready-for-session-to-start", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_BOOLEAN); + + signals [HOSTNAME_SELECTED] = + g_signal_new ("hostname-selected", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + signals [DISCONNECTED] = + g_signal_new ("disconnected", + GDM_TYPE_SESSION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_object_class_install_property (object_class, + PROP_VERIFICATION_MODE, + g_param_spec_enum ("verification-mode", + "verification mode", + "verification mode", + GDM_TYPE_SESSION_VERIFICATION_MODE, + GDM_SESSION_VERIFICATION_MODE_LOGIN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_ALLOWED_USER, + g_param_spec_uint ("allowed-user", + "allowed user", + "allowed user ", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_CONVERSATION_ENVIRONMENT, + g_param_spec_pointer ("conversation-environment", + "conversation environment", + "conversation environment", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_SESSION_TYPE, + g_param_spec_string ("session-type", + NULL, + NULL, + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISPLAY_NAME, + g_param_spec_string ("display-name", + "display name", + "display name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISPLAY_HOSTNAME, + g_param_spec_string ("display-hostname", + "display hostname", + "display hostname", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISPLAY_IS_LOCAL, + g_param_spec_boolean ("display-is-local", + "display is local", + "display is local", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISPLAY_IS_INITIAL, + g_param_spec_boolean ("display-is-initial", + "display is initial", + "display is initial", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISPLAY_X11_AUTHORITY_FILE, + g_param_spec_string ("display-x11-authority-file", + "display x11 authority file", + "display x11 authority file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + /* not construct only */ + g_object_class_install_property (object_class, + PROP_USER_X11_AUTHORITY_FILE, + g_param_spec_string ("user-x11-authority-file", + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_DISPLAY_DEVICE, + g_param_spec_string ("display-device", + "display device", + "display device", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_DISPLAY_SEAT_ID, + g_param_spec_string ("display-seat-id", + "display seat id", + "display seat id", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + +#ifdef ENABLE_WAYLAND_SUPPORT + g_object_class_install_property (object_class, + PROP_IGNORE_WAYLAND, + g_param_spec_boolean ("ignore-wayland", + "ignore wayland", + "ignore wayland", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); +#endif +} + +GdmSession * +gdm_session_new (GdmSessionVerificationMode verification_mode, + uid_t allowed_user, + const char *display_name, + const char *display_hostname, + const char *display_device, + const char *display_seat_id, + const char *display_x11_authority_file, + gboolean display_is_local, + const char * const *environment) +{ + GdmSession *self; + + self = g_object_new (GDM_TYPE_SESSION, + "verification-mode", verification_mode, + "allowed-user", (guint) allowed_user, + "display-name", display_name, + "display-hostname", display_hostname, + "display-device", display_device, + "display-seat-id", display_seat_id, + "display-x11-authority-file", display_x11_authority_file, + "display-is-local", display_is_local, + "conversation-environment", environment, + NULL); + + return self; +} + +GdmSessionDisplayMode +gdm_session_display_mode_from_string (const char *str) +{ + if (strcmp (str, "reuse-vt") == 0) + return GDM_SESSION_DISPLAY_MODE_REUSE_VT; + if (strcmp (str, "new-vt") == 0) + return GDM_SESSION_DISPLAY_MODE_NEW_VT; + if (strcmp (str, "logind-managed") == 0) + return GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED; + + g_warning ("Unknown GdmSessionDisplayMode %s", str); + return -1; +} + +const char * +gdm_session_display_mode_to_string (GdmSessionDisplayMode mode) +{ + switch (mode) { + case GDM_SESSION_DISPLAY_MODE_REUSE_VT: + return "reuse-vt"; + case GDM_SESSION_DISPLAY_MODE_NEW_VT: + return "new-vt"; + case GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED: + return "logind-managed"; + default: + break; + } + + g_warning ("Unknown GdmSessionDisplayMode %d", mode); + return ""; +} + +GPid +gdm_session_get_pid (GdmSession *session) +{ + return session->session_pid; +} diff --git a/daemon/gdm-session.h b/daemon/gdm-session.h new file mode 100644 index 0000000..3b64ecd --- /dev/null +++ b/daemon/gdm-session.h @@ -0,0 +1,138 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Ray Strode <rstrode@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef __GDM_SESSION_H +#define __GDM_SESSION_H + +#include <glib-object.h> +#include <sys/types.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_SESSION (gdm_session_get_type ()) +G_DECLARE_FINAL_TYPE (GdmSession, gdm_session, GDM, SESSION, GObject) + +typedef enum +{ + GDM_SESSION_VERIFICATION_MODE_LOGIN, + GDM_SESSION_VERIFICATION_MODE_CHOOSER, + GDM_SESSION_VERIFICATION_MODE_REAUTHENTICATE +} GdmSessionVerificationMode; + +typedef enum { + /* We reuse the existing display server, e.g. X server + * in "classic" mode from the greeter for the first seat. */ + GDM_SESSION_DISPLAY_MODE_REUSE_VT, + + /* Doesn't know anything about VTs. Tries to set DRM + * master and will throw a tantrum if something bad + * happens. e.g. weston-launch or mutter-launch. */ + GDM_SESSION_DISPLAY_MODE_NEW_VT, + + /* Uses logind sessions to manage itself. We need to set an + * XDG_VTNR and it will switch to the correct VT on startup. + * e.g. mutter-wayland with logind integration, X server with + * logind integration. */ + GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED, +} GdmSessionDisplayMode; + +GdmSessionDisplayMode gdm_session_display_mode_from_string (const char *str); +const char * gdm_session_display_mode_to_string (GdmSessionDisplayMode mode); + +GdmSession *gdm_session_new (GdmSessionVerificationMode verification_mode, + uid_t allowed_user, + const char *display_name, + const char *display_hostname, + const char *display_device, + const char *display_seat_id, + const char *display_x11_authority_file, + gboolean display_is_local, + const char * const *environment); +uid_t gdm_session_get_allowed_user (GdmSession *session); +void gdm_session_start_reauthentication (GdmSession *session, + GPid pid_of_caller, + uid_t uid_of_caller); + +const char *gdm_session_get_server_address (GdmSession *session); +const char *gdm_session_get_username (GdmSession *session); +const char *gdm_session_get_display_device (GdmSession *session); +const char *gdm_session_get_display_seat_id (GdmSession *session); +const char *gdm_session_get_session_id (GdmSession *session); +gboolean gdm_session_bypasses_xsession (GdmSession *session); +gboolean gdm_session_session_registers (GdmSession *session); +GdmSessionDisplayMode gdm_session_get_display_mode (GdmSession *session); + +#ifdef ENABLE_WAYLAND_SUPPORT +void gdm_session_set_ignore_wayland (GdmSession *session, + gboolean ignore_wayland); +#endif +gboolean gdm_session_start_conversation (GdmSession *session, + const char *service_name); +void gdm_session_stop_conversation (GdmSession *session, + const char *service_name); +const char *gdm_session_get_conversation_session_id (GdmSession *session, + const char *service_name); +void gdm_session_setup (GdmSession *session, + const char *service_name); +void gdm_session_setup_for_user (GdmSession *session, + const char *service_name, + const char *username); +void gdm_session_setup_for_program (GdmSession *session, + const char *service_name, + const char *username, + const char *log_file); +void gdm_session_set_environment_variable (GdmSession *session, + const char *key, + const char *value); +void gdm_session_send_environment (GdmSession *self, + const char *service_name); +void gdm_session_reset (GdmSession *session); +void gdm_session_cancel (GdmSession *session); +void gdm_session_authenticate (GdmSession *session, + const char *service_name); +void gdm_session_authorize (GdmSession *session, + const char *service_name); +void gdm_session_accredit (GdmSession *session, + const char *service_name); +void gdm_session_open_session (GdmSession *session, + const char *service_name); +void gdm_session_start_session (GdmSession *session, + const char *service_name); +void gdm_session_close (GdmSession *session); + +void gdm_session_answer_query (GdmSession *session, + const char *service_name, + const char *text); +void gdm_session_select_program (GdmSession *session, + const char *command_line); +void gdm_session_select_session (GdmSession *session, + const char *session_name); +void gdm_session_select_user (GdmSession *session, + const char *username); +void gdm_session_set_timed_login_details (GdmSession *session, + const char *username, + int delay); +gboolean gdm_session_client_is_connected (GdmSession *session); +gboolean gdm_session_is_running (GdmSession *session); +GPid gdm_session_get_pid (GdmSession *session); + +G_END_DECLS + +#endif /* GDM_SESSION_H */ diff --git a/daemon/gdm-session.xml b/daemon/gdm-session.xml new file mode 100644 index 0000000..137be5e --- /dev/null +++ b/daemon/gdm-session.xml @@ -0,0 +1,146 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/org/gnome/DisplayManager/Session"> + <!-- methods are called by the session worker, + signals are emitted by the main daemon --> + <interface name="org.gnome.DisplayManager.WorkerManager"> + <method name="Hello" /> + <method name="InfoQuery"> + <arg name="service_name" direction="in" type="s"/> + <arg name="query" direction="in" type="s"/> + <arg name="answer" direction="out" type="s"/> + </method> + <method name="SecretInfoQuery"> + <arg name="service_name" direction="in" type="s"/> + <arg name="query" direction="in" type="s"/> + <arg name="answer" direction="out" type="s"/> + </method> + <method name="Info"> + <arg name="service_name" direction="in" type="s"/> + <arg name="info" direction="in" type="s"/> + </method> + <method name="Problem"> + <arg name="service_name" direction="in" type="s"/> + <arg name="problem" direction="in" type="s"/> + </method> + <method name="ChoiceListQuery"> + <arg name="service_name" direction="in" type="s"/> + <arg name="prompt_message" direction="in" type="s"/> + <arg name="query" direction="in" type="a{ss}"/> + <arg name="answer" direction="out" type="s"/> + </method> + </interface> + <interface name="org.gnome.DisplayManager.UserVerifier"> + <method name="EnableExtensions"> + <arg name="extensions" direction="in" type="as"/> + </method> + <method name="BeginVerification"> + <arg name="service_name" direction="in" type="s"/> + </method> + <method name="BeginVerificationForUser"> + <arg name="service_name" direction="in" type="s"/> + <arg name="username" direction="in" type="s"/> + </method> + <method name="AnswerQuery"> + <arg name="service_name" direction="in" type="s"/> + <arg name="answer" direction="in" type="s"/> + </method> + <method name="Cancel"> + </method> + <signal name="ConversationStarted"> + <arg name="service_name" type="s"/> + </signal> + <signal name="ConversationStopped"> + <arg name="service_name" type="s"/> + </signal> + <signal name="ReauthenticationStarted"> + <arg name="pid_of_caller" type="i"/> + </signal> + <signal name="Info"> + <arg name="service_name" type="s"/> + <arg name="info" type="s"/> + </signal> + <signal name="Problem"> + <arg name="service_name" type="s"/> + <arg name="problem" type="s"/> + </signal> + <signal name="InfoQuery"> + <arg name="service_name" type="s"/> + <arg name="query" type="s"/> + </signal> + <signal name="SecretInfoQuery"> + <arg name="service_name" type="s"/> + <arg name="query" type="s"/> + </signal> + <signal name="Reset"> + </signal> + <signal name="ServiceUnavailable"> + <arg name="service_name" type="s"/> + <arg name="message" type="s"/> + </signal> + <signal name="VerificationFailed"> + <arg name="service_name" type="s"/> + </signal> + <signal name="VerificationComplete"> + <arg name="service_name" type="s"/> + </signal> + </interface> + <interface name="org.gnome.DisplayManager.UserVerifier.ChoiceList"> + <method name="SelectChoice"> + <arg name="service_name" direction="in" type="s"/> + <arg name="choice" direction="in" type="s"/> + </method> + <signal name="ChoiceQuery"> + <arg name="service_name" type="s"/> + <arg name="prompt_message" type="s"/> + <arg name="list" type="a{ss}"/> + </signal> + </interface> + <interface name="org.gnome.DisplayManager.Greeter"> + <method name="SelectSession"> + <arg name="session" direction="in" type="s"/> + </method> + <method name="SelectUser"> + <arg name="username" direction="in" type="s"/> + </method> + <method name="BeginAutoLogin"> + <arg name="username" direction="in" type="s"/> + </method> + <method name="GetTimedLoginDetails"> + <arg name="enabled" direction="out" type="b"/> + <arg name="username" direction="out" type="s"/> + <arg name="delay" direction="out" type="i"/> + </method> + <method name="StartSessionWhenReady"> + <arg name="service_name" direction="in" type="s"/> + <arg name="should_start_session" direction="in" type="b"/> + </method> + <signal name="SelectedUserChanged"> + <arg name="username" type="s"/> + </signal> + <signal name="DefaultLanguageNameChanged"> + <arg name="language_name" type="s"/> + </signal> + <signal name="DefaultSessionNameChanged"> + <arg name="session_name" type="s"/> + </signal> + <signal name="TimedLoginRequested"> + <arg name="username" type="s"/> + <arg name="delay" type="i"/> + </signal> + <signal name="SessionOpened"> + <arg name="service_name" type="s"/> + </signal> + <signal name="Reauthenticated"> + <arg name="service_name" type="s"/> + </signal> + </interface> + <interface name="org.gnome.DisplayManager.RemoteGreeter"> + <method name="Disconnect" /> + </interface> + <interface name="org.gnome.DisplayManager.Chooser"> + <method name="SelectHostname"> + <arg name="hostname" direction="in" type="s"/> + </method> + <method name="Disconnect" /> + </interface> +</node> diff --git a/daemon/gdm-wayland-session.c b/daemon/gdm-wayland-session.c new file mode 100644 index 0000000..35679b1 --- /dev/null +++ b/daemon/gdm-wayland-session.c @@ -0,0 +1,600 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#include "config.h" + +#include <locale.h> +#include <sysexits.h> + +#include "gdm-common.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" +#include "gdm-log.h" + +#include "gdm-manager-glue.h" + +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-unix.h> +#include <glib.h> + +#include <gio/gunixinputstream.h> + +#define BUS_ADDRESS_FILENO (STDERR_FILENO + 1) + +typedef struct +{ + GdmSettings *settings; + GCancellable *cancellable; + + GSubprocess *bus_subprocess; + GDBusConnection *bus_connection; + GdmDBusManager *display_manager_proxy; + char *bus_address; + + char **environment; + + GSubprocess *session_subprocess; + char *session_command; + int session_exit_status; + + guint register_session_id; + + GMainLoop *main_loop; + + guint32 debug_enabled : 1; +} State; + +static void +on_bus_finished (GSubprocess *subprocess, + GAsyncResult *result, + State *state) +{ + gboolean cancelled; + + cancelled = !g_subprocess_wait_finish (subprocess, result, NULL); + + if (cancelled) { + goto out; + } + + if (g_subprocess_get_if_exited (subprocess)) { + int exit_status; + + exit_status = g_subprocess_get_exit_status (subprocess); + + g_debug ("message bus exited with status %d", exit_status); + } else { + int signal_number; + + signal_number = g_subprocess_get_term_sig (subprocess); + g_debug ("message bus was killed with status %d", signal_number); + } + + g_clear_object (&state->bus_subprocess); +out: + g_main_loop_quit (state->main_loop); +} + +static gboolean +spawn_bus (State *state, + GCancellable *cancellable) +{ + GDBusConnection *bus_connection = NULL; + GPtrArray *arguments = NULL; + GSubprocessLauncher *launcher = NULL; + GSubprocess *subprocess = NULL; + GInputStream *input_stream = NULL; + GDataInputStream *data_stream = NULL; + GError *error = NULL; + char *bus_address_fd_string = NULL; + char *bus_address = NULL; + gsize bus_address_size; + + gboolean is_running = FALSE; + int ret; + int pipe_fds[2]; + + g_debug ("Running session message bus"); + + bus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION, + cancellable, + NULL); + + if (bus_connection != NULL) { + g_debug ("session message bus already running, not starting another one"); + state->bus_connection = bus_connection; + return TRUE; + } + + ret = g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error); + + if (!ret) { + g_debug ("could not open pipe: %s", error->message); + goto out; + } + + arguments = g_ptr_array_new (); + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); + g_subprocess_launcher_take_fd (launcher, pipe_fds[1], BUS_ADDRESS_FILENO); + + bus_address_fd_string = g_strdup_printf ("%d", BUS_ADDRESS_FILENO); + + g_ptr_array_add (arguments, "dbus-daemon"); + + g_ptr_array_add (arguments, "--print-address"); + g_ptr_array_add (arguments, bus_address_fd_string); + g_ptr_array_add (arguments, "--session"); + g_ptr_array_add (arguments, NULL); + + subprocess = g_subprocess_launcher_spawnv (launcher, + (const char * const *) arguments->pdata, + &error); + g_free (bus_address_fd_string); + g_clear_object (&launcher); + g_ptr_array_free (arguments, TRUE); + + if (subprocess == NULL) { + g_debug ("could not start dbus-daemon: %s", error->message); + goto out; + } + + input_stream = g_unix_input_stream_new (pipe_fds[0], TRUE); + data_stream = g_data_input_stream_new (input_stream); + g_clear_object (&input_stream); + + bus_address = g_data_input_stream_read_line (data_stream, + &bus_address_size, + cancellable, + &error); + + if (error != NULL) { + g_debug ("could not read address from session message bus: %s", error->message); + goto out; + } + + if (bus_address == NULL) { + g_debug ("session message bus did not write address"); + goto out; + } + + state->bus_address = bus_address; + + state->bus_subprocess = g_object_ref (subprocess); + + g_subprocess_wait_async (state->bus_subprocess, + cancellable, + (GAsyncReadyCallback) + on_bus_finished, + state); + + bus_connection = g_dbus_connection_new_for_address_sync (state->bus_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, + cancellable, + &error); + + if (bus_connection == NULL) { + g_debug ("could not open connection to session bus: %s", + error->message); + goto out; + } + + state->bus_connection = bus_connection; + is_running = TRUE; +out: + g_clear_object (&data_stream); + g_clear_object (&subprocess); + g_clear_object (&launcher); + g_clear_error (&error); + + return is_running; +} + +static gboolean +import_environment (State *state, + GCancellable *cancellable) +{ + g_autoptr(GVariant) reply = NULL; + g_autoptr(GVariant) environment_variant = NULL; + g_autoptr(GError) error = NULL; + + reply = g_dbus_connection_call_sync (state->bus_connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "org.freedesktop.systemd1.Manager", + "Environment"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, cancellable, &error); + + if (reply == NULL) { + g_debug ("could not fetch environment: %s", error->message); + return FALSE; + } + + g_variant_get (reply, "(v)", &environment_variant); + + state->environment = g_variant_dup_strv (environment_variant, NULL); + + return TRUE; +} + +static void +on_session_finished (GSubprocess *subprocess, + GAsyncResult *result, + State *state) +{ + gboolean cancelled; + + cancelled = !g_subprocess_wait_finish (subprocess, result, NULL); + + if (cancelled) { + goto out; + } + + if (g_subprocess_get_if_exited (subprocess)) { + int exit_status; + + exit_status = g_subprocess_get_exit_status (subprocess); + + g_debug ("session exited with status %d", exit_status); + + state->session_exit_status = exit_status; + } else { + int signal_number; + + signal_number = g_subprocess_get_term_sig (subprocess); + g_debug ("session was killed with status %d", signal_number); + } + + g_clear_object (&state->session_subprocess); +out: + g_main_loop_quit (state->main_loop); +} + +static gboolean +spawn_session (State *state, + GCancellable *cancellable) +{ + GSubprocessLauncher *launcher = NULL; + GSubprocess *subprocess = NULL; + GError *error = NULL; + gboolean is_running = FALSE; + int ret; + char **argv = NULL; + static const char *session_variables[] = { "DISPLAY", + "XAUTHORITY", + "WAYLAND_DISPLAY", + "WAYLAND_SOCKET", + "GNOME_SHELL_SESSION_MODE", + NULL }; + + g_debug ("Running wayland session"); + + ret = g_shell_parse_argv (state->session_command, + NULL, + &argv, + &error); + + if (!ret) { + g_debug ("could not parse session arguments: %s", error->message); + goto out; + } + + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); + + if (state->environment != NULL) { + size_t i; + + for (i = 0; state->environment[i] != NULL; i++) { + g_auto(GStrv) environment_entry = NULL; + + if (state->environment[i][0] == '\0') { + continue; + } + + environment_entry = g_strsplit (state->environment[i], "=", 2); + + if (environment_entry[0] == NULL || environment_entry[1] == NULL) { + continue; + } + + g_subprocess_launcher_setenv (launcher, environment_entry[0], environment_entry[1], FALSE); + } + + /* Don't allow session specific environment variables from earlier sessions to + * leak through */ + for (i = 0; session_variables[i] != NULL; i++) { + if (g_getenv (session_variables[i]) == NULL) { + g_subprocess_launcher_unsetenv (launcher, session_variables[i]); + } + } + } + + if (state->bus_address != NULL) { + g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", state->bus_address, TRUE); + } + + subprocess = g_subprocess_launcher_spawnv (launcher, + (const char * const *) argv, + &error); + g_strfreev (argv); + + if (subprocess == NULL) { + g_debug ("could not start session: %s", error->message); + goto out; + } + + state->session_subprocess = g_object_ref (subprocess); + + g_subprocess_wait_async (state->session_subprocess, + cancellable, + (GAsyncReadyCallback) + on_session_finished, + state); + + is_running = TRUE; +out: + g_clear_object (&subprocess); + return is_running; +} + +static void +signal_subprocesses (State *state) +{ + if (state->session_subprocess != NULL) { + g_subprocess_send_signal (state->session_subprocess, SIGTERM); + } + + if (state->bus_subprocess != NULL) { + g_subprocess_send_signal (state->bus_subprocess, SIGTERM); + } +} + +static void +wait_on_subprocesses (State *state) +{ + if (state->bus_subprocess != NULL) { + g_subprocess_wait (state->bus_subprocess, NULL, NULL); + } + + if (state->session_subprocess != NULL) { + g_subprocess_wait (state->session_subprocess, NULL, NULL); + } +} + +static gboolean +register_display (State *state, + GCancellable *cancellable) +{ + GError *error = NULL; + gboolean registered = FALSE; + GVariantBuilder details; + + g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_add (&details, "{ss}", "session-type", "wayland"); + + registered = gdm_dbus_manager_call_register_display_sync (state->display_manager_proxy, + g_variant_builder_end (&details), + cancellable, + &error); + if (error != NULL) { + g_debug ("Could not register display: %s", error->message); + g_error_free (error); + } + + return registered; +} + +static void +init_state (State **state) +{ + static State state_allocation; + + *state = &state_allocation; +} + +static void +clear_state (State **out_state) +{ + State *state = *out_state; + + g_clear_object (&state->cancellable); + g_clear_object (&state->bus_connection); + g_clear_object (&state->session_subprocess); + g_clear_pointer (&state->environment, g_strfreev); + g_clear_pointer (&state->main_loop, g_main_loop_unref); + g_clear_handle_id (&state->register_session_id, g_source_remove); + *out_state = NULL; +} + +static gboolean +on_sigterm (State *state) +{ + g_cancellable_cancel (state->cancellable); + + if (g_main_loop_is_running (state->main_loop)) { + g_main_loop_quit (state->main_loop); + } + + return G_SOURCE_CONTINUE; +} + +static gboolean +register_session_timeout_cb (gpointer user_data) +{ + State *state; + GError *error = NULL; + + state = (State *) user_data; + + gdm_dbus_manager_call_register_session_sync (state->display_manager_proxy, + g_variant_new ("a{sv}", NULL), + state->cancellable, + &error); + + if (error != NULL) { + g_warning ("Could not register session: %s", error->message); + g_error_free (error); + } + + return G_SOURCE_REMOVE; +} + +static gboolean +connect_to_display_manager (State *state) +{ + g_autoptr (GError) error = NULL; + + state->display_manager_proxy = gdm_dbus_manager_proxy_new_for_bus_sync ( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + "org.gnome.DisplayManager", + "/org/gnome/DisplayManager/Manager", + state->cancellable, + &error); + + if (state->display_manager_proxy == NULL) { + g_printerr ("gdm-wayland-session: could not contact display manager: %s\n", + error->message); + return FALSE; + } + + return TRUE; +} + +int +main (int argc, + char **argv) +{ + State *state = NULL; + GOptionContext *context = NULL; + static char **args = NULL; + gboolean debug = FALSE; + gboolean ret; + int exit_status = EX_OK; + static gboolean register_session = FALSE; + + static GOptionEntry entries [] = { + { "register-session", 0, 0, G_OPTION_ARG_NONE, ®ister_session, "Register session after a delay", NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, "", "" }, + { NULL } + }; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + gdm_log_init (); + + context = g_option_context_new (_("GNOME Display Manager Wayland Session Launcher")); + g_option_context_add_main_entries (context, entries, NULL); + + g_option_context_parse (context, &argc, &argv, NULL); + g_option_context_free (context); + + if (args == NULL || args[0] == NULL || args[1] != NULL) { + g_warning ("gdm-wayland-session takes one argument (the session)"); + exit_status = EX_USAGE; + goto out; + } + + init_state (&state); + + state->session_command = args[0]; + + state->settings = gdm_settings_new (); + ret = gdm_settings_direct_init (state->settings, DATADIR "/gdm/gdm.schemas", "/"); + + if (!ret) { + g_printerr ("Unable to initialize settings\n"); + exit_status = EX_DATAERR; + goto out; + } + + gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug); + state->debug_enabled = debug; + + gdm_log_set_debug (debug); + + state->main_loop = g_main_loop_new (NULL, FALSE); + state->cancellable = g_cancellable_new (); + + g_unix_signal_add (SIGTERM, (GSourceFunc) on_sigterm, state); + + ret = spawn_bus (state, state->cancellable); + + if (!ret) { + g_printerr ("Unable to run session message bus\n"); + exit_status = EX_SOFTWARE; + goto out; + } + + import_environment (state, state->cancellable); + + ret = spawn_session (state, state->cancellable); + + if (!ret) { + g_printerr ("Unable to run session\n"); + exit_status = EX_SOFTWARE; + goto out; + } + + if (!connect_to_display_manager (state)) + goto out; + + ret = register_display (state, state->cancellable); + + if (!ret) { + g_printerr ("Unable to register display with display manager\n"); + exit_status = EX_SOFTWARE; + goto out; + } + + if (register_session) { + g_debug ("gdm-wayland-session: Will register session in %d seconds", REGISTER_SESSION_TIMEOUT); + state->register_session_id = g_timeout_add_seconds (REGISTER_SESSION_TIMEOUT, + register_session_timeout_cb, + state); + } else { + g_debug ("gdm-wayland-session: Session will register itself"); + } + + g_main_loop_run (state->main_loop); + + /* Only use exit status of session if we're here because it exit */ + + if (state->session_subprocess == NULL) { + exit_status = state->session_exit_status; + } + +out: + if (state != NULL) { + signal_subprocesses (state); + wait_on_subprocesses (state); + clear_state (&state); + } + + return exit_status; +} diff --git a/daemon/gdm-x-session.c b/daemon/gdm-x-session.c new file mode 100644 index 0000000..b154836 --- /dev/null +++ b/daemon/gdm-x-session.c @@ -0,0 +1,977 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#include "config.h" + +#include <locale.h> +#include <sysexits.h> + +#include "gdm-common.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" +#include "gdm-log.h" + +#include "gdm-manager-glue.h" + +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-unix.h> +#include <glib.h> +#include <gio/gunixinputstream.h> +#include <glib-unix.h> +#include <X11/Xauth.h> + +#define DISPLAY_FILENO (STDERR_FILENO + 1) +#define BUS_ADDRESS_FILENO (DISPLAY_FILENO + 1) + +typedef struct +{ + GdmSettings *settings; + GCancellable *cancellable; + + GSubprocess *x_subprocess; + char *auth_file; + char *display_name; + + GSubprocess *bus_subprocess; + GDBusConnection *bus_connection; + GdmDBusManager *display_manager_proxy; + char *bus_address; + + char **environment; + + GSubprocess *session_subprocess; + char *session_command; + int session_exit_status; + + guint register_session_id; + + GMainLoop *main_loop; + + guint32 debug_enabled : 1; +} State; + +static FILE * +create_auth_file (char **filename) +{ + char *auth_dir = NULL; + char *auth_file = NULL; + int fd; + FILE *fp = NULL; + + auth_dir = g_build_filename (g_get_user_runtime_dir (), + "gdm", + NULL); + + g_mkdir_with_parents (auth_dir, 0711); + auth_file = g_build_filename (auth_dir, "Xauthority", NULL); + g_clear_pointer (&auth_dir, g_free); + + fd = g_open (auth_file, O_RDWR | O_CREAT | O_TRUNC, 0700); + + if (fd < 0) { + g_debug ("could not open %s to store auth cookie: %m", + auth_file); + g_clear_pointer (&auth_file, g_free); + goto out; + } + + fp = fdopen (fd, "w+"); + + if (fp == NULL) { + g_debug ("could not set up stream for auth cookie file: %m"); + g_clear_pointer (&auth_file, g_free); + close (fd); + goto out; + } + + *filename = auth_file; +out: + return fp; +} + +static char * +prepare_auth_file (void) +{ + FILE *fp = NULL; + char *filename = NULL; + GError *error = NULL; + gboolean prepared = FALSE; + Xauth auth_entry = { 0 }; + char localhost[HOST_NAME_MAX + 1] = ""; + + g_debug ("Preparing auth file for X server"); + + fp = create_auth_file (&filename); + + if (fp == NULL) { + return NULL; + } + + if (gethostname (localhost, HOST_NAME_MAX) < 0) { + strncpy (localhost, "localhost", sizeof (localhost) - 1); + } + + auth_entry.family = FamilyLocal; + auth_entry.address = localhost; + auth_entry.address_length = strlen (auth_entry.address); + auth_entry.name = "MIT-MAGIC-COOKIE-1"; + auth_entry.name_length = strlen (auth_entry.name); + + auth_entry.data_length = 16; + auth_entry.data = gdm_generate_random_bytes (auth_entry.data_length, &error); + + if (error != NULL) { + goto out; + } + + if (!XauWriteAuth (fp, &auth_entry) || fflush (fp) == EOF) { + goto out; + } + + auth_entry.family = FamilyWild; + if (!XauWriteAuth (fp, &auth_entry) || fflush (fp) == EOF) { + goto out; + } + + prepared = TRUE; + +out: + g_clear_pointer (&auth_entry.data, g_free); + g_clear_pointer (&fp, fclose); + + if (!prepared) { + g_clear_pointer (&filename, g_free); + } + + return filename; +} + +static void +on_x_server_finished (GSubprocess *subprocess, + GAsyncResult *result, + State *state) +{ + gboolean cancelled; + + cancelled = !g_subprocess_wait_finish (subprocess, result, NULL); + + if (cancelled) { + goto out; + } + + if (g_subprocess_get_if_exited (subprocess)) { + int exit_status; + + exit_status = g_subprocess_get_exit_status (subprocess); + + g_debug ("X server exited with status %d", exit_status); + } else { + int signal_number; + + signal_number = g_subprocess_get_term_sig (subprocess); + g_debug ("X server was killed with status %d", signal_number); + } + + g_clear_object (&state->x_subprocess); +out: + g_main_loop_quit (state->main_loop); +} + +static gboolean +spawn_x_server (State *state, + gboolean allow_remote_connections, + GCancellable *cancellable) +{ + GPtrArray *arguments = NULL; + GSubprocessLauncher *launcher = NULL; + GSubprocess *subprocess = NULL; + GInputStream *input_stream = NULL; + GDataInputStream *data_stream = NULL; + GError *error = NULL; + + char *auth_file; + gboolean is_running = FALSE; + int ret; + int pipe_fds[2]; + char *display_fd_string = NULL; + char *vt_string = NULL; + char *display_number; + gsize display_number_size; + + auth_file = prepare_auth_file (); + + g_debug ("Running X server"); + + ret = g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error); + + if (!ret) { + g_debug ("could not open pipe: %s", error->message); + goto out; + } + + arguments = g_ptr_array_new (); + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_INHERIT); + g_subprocess_launcher_setenv (launcher, "XORG_RUN_AS_USER_OK", "1", TRUE); + g_subprocess_launcher_take_fd (launcher, pipe_fds[1], DISPLAY_FILENO); + + if (g_getenv ("XDG_VTNR") != NULL) { + int vt; + + vt = atoi (g_getenv ("XDG_VTNR")); + + if (vt > 0 && vt < 64) { + vt_string = g_strdup_printf ("vt%d", vt); + } + } + + display_fd_string = g_strdup_printf ("%d", DISPLAY_FILENO); + + g_ptr_array_add (arguments, X_SERVER); + + if (vt_string != NULL) { + g_ptr_array_add (arguments, vt_string); + } + + g_ptr_array_add (arguments, "-displayfd"); + g_ptr_array_add (arguments, display_fd_string); + + g_ptr_array_add (arguments, "-auth"); + g_ptr_array_add (arguments, auth_file); + + /* If we were compiled with Xserver >= 1.17 we need to specify + * '-listen tcp' as the X server doesn't listen on tcp sockets + * by default anymore. In older versions we need to pass + * -nolisten tcp to disable listening on tcp sockets. + */ +#ifdef HAVE_XSERVER_THAT_DEFAULTS_TO_LOCAL_ONLY + if (allow_remote_connections) { + g_ptr_array_add (arguments, "-listen"); + g_ptr_array_add (arguments, "tcp"); + } +#else + if (!allow_remote_connections) { + g_ptr_array_add (arguments, "-nolisten"); + g_ptr_array_add (arguments, "tcp"); + } +#endif + + g_ptr_array_add (arguments, "-background"); + g_ptr_array_add (arguments, "none"); + + g_ptr_array_add (arguments, "-noreset"); + g_ptr_array_add (arguments, "-keeptty"); + g_ptr_array_add (arguments, "-novtswitch"); + + g_ptr_array_add (arguments, "-verbose"); + if (state->debug_enabled) { + g_ptr_array_add (arguments, "7"); + } else { + g_ptr_array_add (arguments, "3"); + } + + if (state->debug_enabled) { + g_ptr_array_add (arguments, "-core"); + } + g_ptr_array_add (arguments, NULL); + + subprocess = g_subprocess_launcher_spawnv (launcher, + (const char * const *) arguments->pdata, + &error); + g_free (display_fd_string); + g_clear_object (&launcher); + g_ptr_array_free (arguments, TRUE); + + if (subprocess == NULL) { + g_debug ("could not start X server: %s", error->message); + goto out; + } + + input_stream = g_unix_input_stream_new (pipe_fds[0], TRUE); + data_stream = g_data_input_stream_new (input_stream); + g_clear_object (&input_stream); + + display_number = g_data_input_stream_read_line (data_stream, + &display_number_size, + cancellable, + &error); + + if (error != NULL) { + g_debug ("could not read display string from X server: %s", error->message); + goto out; + } + + if (display_number == NULL) { + g_debug ("X server did not write display string"); + goto out; + } + + state->display_name = g_strdup_printf (":%s", display_number); + g_clear_pointer (&display_number, g_free); + + state->auth_file = g_strdup (auth_file); + state->x_subprocess = g_object_ref (subprocess); + + g_subprocess_wait_async (state->x_subprocess, + cancellable, + (GAsyncReadyCallback) + on_x_server_finished, + state); + + is_running = TRUE; +out: + g_clear_pointer (&auth_file, g_free); + g_clear_object (&data_stream); + g_clear_object (&subprocess); + g_clear_object (&launcher); + g_clear_error (&error); + + return is_running; +} + +static void +on_bus_finished (GSubprocess *subprocess, + GAsyncResult *result, + State *state) +{ + gboolean cancelled; + + cancelled = !g_subprocess_wait_finish (subprocess, result, NULL); + + if (cancelled) { + goto out; + } + + if (g_subprocess_get_if_exited (subprocess)) { + int exit_status; + + exit_status = g_subprocess_get_exit_status (subprocess); + + g_debug ("message bus exited with status %d", exit_status); + } else { + int signal_number; + + signal_number = g_subprocess_get_term_sig (subprocess); + g_debug ("message bus was killed with status %d", signal_number); + } + + g_clear_object (&state->bus_subprocess); +out: + g_main_loop_quit (state->main_loop); +} + +static gboolean +update_bus_environment (State *state, + GCancellable *cancellable) +{ + GVariantBuilder builder; + GVariant *reply = NULL; + GError *error = NULL; + gboolean environment_updated = FALSE; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_add (&builder, "{ss}", "DISPLAY", state->display_name); + g_variant_builder_add (&builder, "{ss}", "XAUTHORITY", state->auth_file); + + reply = g_dbus_connection_call_sync (state->bus_connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "UpdateActivationEnvironment", + g_variant_new ("(@a{ss})", + g_variant_builder_end (&builder)), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, cancellable, &error); + + if (reply == NULL) { + g_debug ("could not update activation environment: %s", error->message); + goto out; + } + + g_variant_unref (reply); + + environment_updated = TRUE; + +out: + g_clear_error (&error); + + return environment_updated; +} + +static gboolean +spawn_bus (State *state, + GCancellable *cancellable) +{ + GDBusConnection *bus_connection = NULL; + GPtrArray *arguments = NULL; + GSubprocessLauncher *launcher = NULL; + GSubprocess *subprocess = NULL; + GInputStream *input_stream = NULL; + GDataInputStream *data_stream = NULL; + GError *error = NULL; + char *bus_address_fd_string; + char *bus_address = NULL; + gsize bus_address_size; + + gboolean is_running = FALSE; + int ret; + int pipe_fds[2]; + + g_debug ("Running session message bus"); + + bus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION, + cancellable, + NULL); + + if (bus_connection != NULL) { + g_debug ("session message bus already running, not starting another one"); + state->bus_connection = bus_connection; + return TRUE; + } + + ret = g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error); + + if (!ret) { + g_debug ("could not open pipe: %s", error->message); + goto out; + } + + arguments = g_ptr_array_new (); + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); + + g_subprocess_launcher_take_fd (launcher, pipe_fds[1], BUS_ADDRESS_FILENO); + + bus_address_fd_string = g_strdup_printf ("%d", BUS_ADDRESS_FILENO); + + g_ptr_array_add (arguments, "dbus-daemon"); + + g_ptr_array_add (arguments, "--print-address"); + g_ptr_array_add (arguments, bus_address_fd_string); + g_ptr_array_add (arguments, "--session"); + g_ptr_array_add (arguments, NULL); + + subprocess = g_subprocess_launcher_spawnv (launcher, + (const char * const *) arguments->pdata, + &error); + g_free (bus_address_fd_string); + g_clear_object (&launcher); + g_ptr_array_free (arguments, TRUE); + + if (subprocess == NULL) { + g_debug ("could not start dbus-daemon: %s", error->message); + goto out; + } + + input_stream = g_unix_input_stream_new (pipe_fds[0], TRUE); + data_stream = g_data_input_stream_new (input_stream); + g_clear_object (&input_stream); + + bus_address = g_data_input_stream_read_line (data_stream, + &bus_address_size, + cancellable, + &error); + + if (error != NULL) { + g_debug ("could not read address from session message bus: %s", error->message); + goto out; + } + + if (bus_address == NULL) { + g_debug ("session message bus did not write address"); + goto out; + } + + state->bus_address = bus_address; + + state->bus_subprocess = g_object_ref (subprocess); + + g_subprocess_wait_async (state->bus_subprocess, + cancellable, + (GAsyncReadyCallback) + on_bus_finished, + state); + + + bus_connection = g_dbus_connection_new_for_address_sync (state->bus_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, + cancellable, + &error); + + if (bus_connection == NULL) { + g_debug ("could not open connection to session bus: %s", + error->message); + goto out; + } + + state->bus_connection = bus_connection; + + is_running = TRUE; +out: + g_clear_object (&data_stream); + g_clear_object (&subprocess); + g_clear_object (&launcher); + g_clear_error (&error); + + return is_running; +} + +static gboolean +import_environment (State *state, + GCancellable *cancellable) +{ + g_autoptr(GVariant) reply = NULL; + g_autoptr(GVariant) environment_variant = NULL; + g_autoptr(GError) error = NULL; + + reply = g_dbus_connection_call_sync (state->bus_connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "org.freedesktop.systemd1.Manager", + "Environment"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, cancellable, &error); + + if (reply == NULL) { + g_debug ("could not fetch environment: %s", error->message); + return FALSE; + } + + g_variant_get (reply, "(v)", &environment_variant); + + state->environment = g_variant_dup_strv (environment_variant, NULL); + + return TRUE; +} + +static void +on_session_finished (GSubprocess *subprocess, + GAsyncResult *result, + State *state) +{ + gboolean cancelled; + + cancelled = !g_subprocess_wait_finish (subprocess, result, NULL); + + if (cancelled) { + goto out; + } + + if (g_subprocess_get_if_exited (subprocess)) { + int exit_status; + + exit_status = g_subprocess_get_exit_status (subprocess); + + g_debug ("session exited with status %d", exit_status); + + state->session_exit_status = exit_status; + } else { + int signal_number; + + signal_number = g_subprocess_get_term_sig (subprocess); + g_debug ("session was killed with status %d", signal_number); + } + + g_clear_object (&state->session_subprocess); +out: + g_main_loop_quit (state->main_loop); +} + +static gboolean +spawn_session (State *state, + gboolean run_script, + GCancellable *cancellable) +{ + GSubprocessLauncher *launcher = NULL; + GSubprocess *subprocess = NULL; + GError *error = NULL; + gboolean is_running = FALSE; + const char *vt; + static const char *session_variables[] = { "DISPLAY", + "XAUTHORITY", + "WAYLAND_DISPLAY", + "WAYLAND_SOCKET", + "GNOME_SHELL_SESSION_MODE", + NULL }; + + g_debug ("Running X session"); + + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); + + if (state->environment != NULL) { + size_t i; + + for (i = 0; state->environment[i] != NULL; i++) { + g_auto(GStrv) environment_entry = NULL; + + if (state->environment[i][0] == '\0') { + continue; + } + + environment_entry = g_strsplit (state->environment[i], "=", 2); + + if (environment_entry[0] == NULL || environment_entry[1] == NULL) { + continue; + } + + g_subprocess_launcher_setenv (launcher, environment_entry[0], environment_entry[1], FALSE); + } + + /* Don't allow session specific environment variables from earlier sessions to + * leak through */ + for (i = 0; session_variables[i] != NULL; i++) { + if (g_getenv (session_variables[i]) == NULL) { + g_subprocess_launcher_unsetenv (launcher, session_variables[i]); + } + } + } + + g_subprocess_launcher_setenv (launcher, "DISPLAY", state->display_name, TRUE); + g_subprocess_launcher_setenv (launcher, "XAUTHORITY", state->auth_file, TRUE); + + if (state->bus_address != NULL) { + g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", state->bus_address, TRUE); + } + + vt = g_getenv ("XDG_VTNR"); + + if (vt != NULL) { + g_subprocess_launcher_setenv (launcher, "WINDOWPATH", vt, TRUE); + } + + if (run_script) { + subprocess = g_subprocess_launcher_spawn (launcher, + &error, + GDMCONFDIR "/Xsession", + state->session_command, + NULL); + } else { + int ret; + char **argv; + + ret = g_shell_parse_argv (state->session_command, + NULL, + &argv, + &error); + + if (!ret) { + g_debug ("could not parse session arguments: %s", error->message); + goto out; + } + subprocess = g_subprocess_launcher_spawnv (launcher, + (const char * const *) argv, + &error); + g_strfreev (argv); + } + + if (subprocess == NULL) { + g_debug ("could not start session: %s", error->message); + goto out; + } + + state->session_subprocess = g_object_ref (subprocess); + + g_subprocess_wait_async (state->session_subprocess, + cancellable, + (GAsyncReadyCallback) + on_session_finished, + state); + + is_running = TRUE; +out: + g_clear_object (&subprocess); + return is_running; +} + +static void +signal_subprocesses (State *state) +{ + if (state->session_subprocess != NULL) { + g_subprocess_send_signal (state->session_subprocess, SIGTERM); + } + + if (state->bus_subprocess != NULL) { + g_subprocess_send_signal (state->bus_subprocess, SIGTERM); + } + + if (state->x_subprocess != NULL) { + g_subprocess_send_signal (state->x_subprocess, SIGTERM); + } +} + +static void +wait_on_subprocesses (State *state) +{ + if (state->x_subprocess != NULL) { + g_subprocess_wait (state->x_subprocess, NULL, NULL); + } + + if (state->bus_subprocess != NULL) { + g_subprocess_wait (state->bus_subprocess, NULL, NULL); + } + + if (state->session_subprocess != NULL) { + g_subprocess_wait (state->session_subprocess, NULL, NULL); + } +} + +static gboolean +register_display (State *state, + GCancellable *cancellable) +{ + GError *error = NULL; + gboolean registered = FALSE; + GVariantBuilder details; + + g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_add (&details, "{ss}", "session-type", "x11"); + g_variant_builder_add (&details, "{ss}", "x11-display-name", state->display_name); + + registered = gdm_dbus_manager_call_register_display_sync (state->display_manager_proxy, + g_variant_builder_end (&details), + cancellable, + &error); + if (error != NULL) { + g_debug ("Could not register display: %s", error->message); + g_error_free (error); + } + + return registered; +} + +static void +init_state (State **state) +{ + static State state_allocation; + + *state = &state_allocation; +} + +static void +clear_state (State **out_state) +{ + State *state = *out_state; + + g_clear_object (&state->cancellable); + g_clear_object (&state->bus_connection); + g_clear_object (&state->session_subprocess); + g_clear_object (&state->x_subprocess); + g_clear_pointer (&state->environment, g_strfreev); + g_clear_pointer (&state->auth_file, g_free); + g_clear_pointer (&state->display_name, g_free); + g_clear_pointer (&state->main_loop, g_main_loop_unref); + g_clear_handle_id (&state->register_session_id, g_source_remove); + *out_state = NULL; +} + +static gboolean +on_sigterm (State *state) +{ + g_cancellable_cancel (state->cancellable); + + if (g_main_loop_is_running (state->main_loop)) { + g_main_loop_quit (state->main_loop); + } + + return G_SOURCE_CONTINUE; +} + +static gboolean +register_session_timeout_cb (gpointer user_data) +{ + State *state; + GError *error = NULL; + + state = (State *) user_data; + + gdm_dbus_manager_call_register_session_sync (state->display_manager_proxy, + g_variant_new ("a{sv}", NULL), + state->cancellable, + &error); + + if (error != NULL) { + g_warning ("Could not register session: %s", error->message); + g_error_free (error); + } + + return G_SOURCE_REMOVE; +} + +static gboolean +connect_to_display_manager (State *state) +{ + g_autoptr (GError) error = NULL; + + state->display_manager_proxy = gdm_dbus_manager_proxy_new_for_bus_sync ( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + "org.gnome.DisplayManager", + "/org/gnome/DisplayManager/Manager", + state->cancellable, + &error); + + if (state->display_manager_proxy == NULL) { + g_printerr ("gdm-x-session: could not contact display manager: %s\n", + error->message); + return FALSE; + } + + return TRUE; +} + +int +main (int argc, + char **argv) +{ + State *state = NULL; + GOptionContext *context = NULL; + static char **args = NULL; + static gboolean run_script = FALSE; + static gboolean allow_remote_connections = FALSE; + gboolean debug = FALSE; + gboolean ret; + int exit_status = EX_OK; + static gboolean register_session = FALSE; + + static GOptionEntry entries [] = { + { "run-script", 'r', 0, G_OPTION_ARG_NONE, &run_script, N_("Run program through /etc/gdm/Xsession wrapper script"), NULL }, + { "allow-remote-connections", 'a', 0, G_OPTION_ARG_NONE, &allow_remote_connections, N_("Listen on TCP socket"), NULL }, + { "register-session", 0, 0, G_OPTION_ARG_NONE, ®ister_session, "Register session after a delay", NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, "", "" }, + { NULL } + }; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + gdm_log_init (); + + context = g_option_context_new (_("GNOME Display Manager X Session Launcher")); + g_option_context_add_main_entries (context, entries, NULL); + + g_option_context_parse (context, &argc, &argv, NULL); + g_option_context_free (context); + + if (args == NULL || args[0] == NULL || args[1] != NULL) { + g_warning ("gdm-x-session takes one argument (the session)"); + exit_status = EX_USAGE; + goto out; + } + + init_state (&state); + + state->session_command = args[0]; + + state->settings = gdm_settings_new (); + ret = gdm_settings_direct_init (state->settings, DATADIR "/gdm/gdm.schemas", "/"); + + if (!ret) { + g_printerr ("Unable to initialize settings\n"); + exit_status = EX_DATAERR; + goto out; + } + + gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug); + state->debug_enabled = debug; + + gdm_log_set_debug (debug); + + state->main_loop = g_main_loop_new (NULL, FALSE); + state->cancellable = g_cancellable_new (); + + g_unix_signal_add (SIGTERM, (GSourceFunc) on_sigterm, state); + + ret = spawn_x_server (state, allow_remote_connections, state->cancellable); + + if (!ret) { + g_printerr ("Unable to run X server\n"); + exit_status = EX_SOFTWARE; + goto out; + } + + ret = spawn_bus (state, state->cancellable); + + if (!ret) { + g_printerr ("Unable to run session message bus\n"); + exit_status = EX_SOFTWARE; + goto out; + } + + import_environment (state, state->cancellable); + + ret = update_bus_environment (state, state->cancellable); + + if (!ret) { + g_printerr ("Unable to update bus environment\n"); + exit_status = EX_SOFTWARE; + goto out; + } + + if (!connect_to_display_manager (state)) + goto out; + + ret = register_display (state, state->cancellable); + + if (!ret) { + g_printerr ("Unable to register display with display manager\n"); + exit_status = EX_SOFTWARE; + goto out; + } + + ret = spawn_session (state, run_script, state->cancellable); + + if (!ret) { + g_printerr ("Unable to run session\n"); + exit_status = EX_SOFTWARE; + goto out; + } + + if (register_session) { + g_debug ("gdm-x-session: Will register session in %d seconds", REGISTER_SESSION_TIMEOUT); + state->register_session_id = g_timeout_add_seconds (REGISTER_SESSION_TIMEOUT, + register_session_timeout_cb, + state); + } else { + g_debug ("gdm-x-session: Session will register itself"); + } + + g_main_loop_run (state->main_loop); + + /* Only use exit status of session if we're here because it exit */ + + if (state->session_subprocess == NULL) { + exit_status = state->session_exit_status; + } + +out: + if (state != NULL) { + signal_subprocesses (state); + wait_on_subprocesses (state); + clear_state (&state); + } + + return exit_status; +} diff --git a/daemon/gdm-xdmcp-chooser-display.c b/daemon/gdm-xdmcp-chooser-display.c new file mode 100644 index 0000000..d593cb4 --- /dev/null +++ b/daemon/gdm-xdmcp-chooser-display.c @@ -0,0 +1,146 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-display.h" +#include "gdm-launch-environment.h" +#include "gdm-xdmcp-chooser-display.h" + +#include "gdm-common.h" +#include "gdm-address.h" + +enum { + HOSTNAME_SELECTED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_xdmcp_chooser_display_class_init (GdmXdmcpChooserDisplayClass *klass); +static void gdm_xdmcp_chooser_display_init (GdmXdmcpChooserDisplay *xdmcp_chooser_display); +static gboolean gdm_xdmcp_chooser_display_prepare (GdmDisplay *display); + +G_DEFINE_TYPE (GdmXdmcpChooserDisplay, gdm_xdmcp_chooser_display, GDM_TYPE_XDMCP_DISPLAY) + +static void +on_hostname_selected (GdmLaunchEnvironment *launch_environment, + const char *hostname, + GdmXdmcpChooserDisplay *display) +{ + g_debug ("GdmXdmcpChooserDisplay: hostname selected: %s", hostname); + g_signal_emit (display, signals [HOSTNAME_SELECTED], 0, hostname); +} + +static void +gdm_xdmcp_chooser_display_class_init (GdmXdmcpChooserDisplayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdmDisplayClass *display_class = GDM_DISPLAY_CLASS (klass); + + display_class->prepare = gdm_xdmcp_chooser_display_prepare; + + signals [HOSTNAME_SELECTED] = + g_signal_new ("hostname-selected", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmXdmcpChooserDisplayClass, hostname_selected), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); +} + +static void +gdm_xdmcp_chooser_display_init (GdmXdmcpChooserDisplay *xdmcp_chooser_display) +{ +} + +static gboolean +gdm_xdmcp_chooser_display_prepare (GdmDisplay *display) +{ + GdmXdmcpDisplay *self = GDM_XDMCP_DISPLAY (display); + GdmLaunchEnvironment *launch_environment; + char *display_name; + char *seat_id; + char *hostname; + + launch_environment = NULL; + display_name = NULL; + seat_id = NULL; + hostname = NULL; + + g_object_get (self, + "x11-display-name", &display_name, + "seat-id", &seat_id, + "remote-hostname", &hostname, + NULL); + + launch_environment = gdm_create_chooser_launch_environment (display_name, + seat_id, + hostname); + g_object_set (self, "launch-environment", launch_environment, NULL); + g_object_unref (launch_environment); + + g_signal_connect (launch_environment, "hostname-selected", + G_CALLBACK (on_hostname_selected), display); + + return GDM_DISPLAY_CLASS (gdm_xdmcp_chooser_display_parent_class)->prepare (display); +} + +GdmDisplay * +gdm_xdmcp_chooser_display_new (const char *hostname, + int number, + GdmAddress *address, + gint32 session_number) +{ + GObject *object; + char *x11_display; + + x11_display = g_strdup_printf ("%s:%d", hostname, number); + object = g_object_new (GDM_TYPE_XDMCP_CHOOSER_DISPLAY, + "remote-hostname", hostname, + "x11-display-number", number, + "x11-display-name", x11_display, + "is-local", FALSE, + "remote-address", address, + "session-number", session_number, + NULL); + g_free (x11_display); + + return GDM_DISPLAY (object); +} diff --git a/daemon/gdm-xdmcp-chooser-display.h b/daemon/gdm-xdmcp-chooser-display.h new file mode 100644 index 0000000..7890afa --- /dev/null +++ b/daemon/gdm-xdmcp-chooser-display.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com> + * + * 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. + * + */ + + +#ifndef __GDM_XDMCP_CHOOSER_DISPLAY_H +#define __GDM_XDMCP_CHOOSER_DISPLAY_H + +#include <sys/types.h> +#include <sys/socket.h> +#include <glib-object.h> + +#include "gdm-xdmcp-display.h" +#include "gdm-address.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_XDMCP_CHOOSER_DISPLAY (gdm_xdmcp_chooser_display_get_type ()) +#define GDM_XDMCP_CHOOSER_DISPLAY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_XDMCP_CHOOSER_DISPLAY, GdmXdmcpChooserDisplay)) +#define GDM_XDMCP_CHOOSER_DISPLAY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_XDMCP_CHOOSER_DISPLAY, GdmXdmcpChooserDisplayClass)) +#define GDM_IS_XDMCP_CHOOSER_DISPLAY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_XDMCP_CHOOSER_DISPLAY)) +#define GDM_IS_XDMCP_CHOOSER_DISPLAY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_XDMCP_CHOOSER_DISPLAY)) +#define GDM_XDMCP_CHOOSER_DISPLAY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_XDMCP_CHOOSER_DISPLAY, GdmXdmcpChooserDisplayClass)) + +typedef struct GdmXdmcpChooserDisplayPrivate GdmXdmcpChooserDisplayPrivate; + +typedef struct +{ + GdmXdmcpDisplay parent; +} GdmXdmcpChooserDisplay; + +typedef struct +{ + GdmXdmcpDisplayClass parent_class; + + void (* hostname_selected) (GdmXdmcpChooserDisplay *display, + const char *hostname); +} GdmXdmcpChooserDisplayClass; + +GType gdm_xdmcp_chooser_display_get_type (void); + + +GdmDisplay * gdm_xdmcp_chooser_display_new (const char *hostname, + int number, + GdmAddress *addr, + gint32 serial_number); + +G_END_DECLS + +#endif /* __GDM_XDMCP_CHOOSER_DISPLAY_H */ diff --git a/daemon/gdm-xdmcp-display-factory.c b/daemon/gdm-xdmcp-display-factory.c new file mode 100644 index 0000000..220ee3a --- /dev/null +++ b/daemon/gdm-xdmcp-display-factory.c @@ -0,0 +1,3478 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 1998, 1999, 2000 Martin K. Petersen <mkp@mkp.net> + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/utsname.h> + +#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif +#include <sys/ioctl.h> + +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include <X11/Xlib.h> +#include <X11/Xmd.h> +#include <X11/Xdmcp.h> + +#include "gdm-common.h" +#include "gdm-xdmcp-chooser-display.h" +#include "gdm-display-factory.h" +#include "gdm-launch-environment.h" +#include "gdm-xdmcp-display-factory.h" +#include "gdm-display-store.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +/* + * On Sun, we need to define allow_severity and deny_severity to link + * against libwrap. + */ +#ifdef __sun +#include <syslog.h> +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif + +#define DEFAULT_PORT 177 +#define DEFAULT_USE_MULTICAST FALSE +#define DEFAULT_MULTICAST_ADDRESS "ff02::1" +#define DEFAULT_HONOR_INDIRECT TRUE +#define DEFAULT_MAX_DISPLAYS_PER_HOST 1 +#define DEFAULT_MAX_DISPLAYS 16 +#define DEFAULT_MAX_PENDING_DISPLAYS 4 +#define DEFAULT_MAX_WAIT 30 +#define DEFAULT_MAX_WAIT_INDIRECT 30 +#define DEFAULT_WILLING_SCRIPT GDMCONFDIR "/Xwilling" + +#define GDM_MAX_FORWARD_QUERIES 10 +#define GDM_FORWARD_QUERY_TIMEOUT 30 +#define MANAGED_FORWARD_INTERVAL 1500 /* 1.5 seconds */ + +/* some extra XDMCP opcodes that xdm will happily ignore since they'll be + * the wrong XDMCP version anyway */ +#define GDM_XDMCP_PROTOCOL_VERSION 1001 +enum { + GDM_XDMCP_FIRST_OPCODE = 1000, /*just a marker, not an opcode */ + + GDM_XDMCP_MANAGED_FORWARD = 1000, + /* manager (master) -> manager + * A packet with MANAGED_FORWARD is sent to the + * manager that sent the forward query from the manager to + * which forward query was sent. It indicates that the forward + * was fully processed and that the client now has either + * a managed session, or has been sent denial, refuse or failed. + * (if the denial gets lost then client gets dumped into the + * chooser again). This should be resent a few times + * until some (short) timeout or until GOT_MANAGED_FORWARD + * is sent. GDM sends at most 3 packates with 1.5 seconds + * between each. + * + * Argument is ARRAY8 with the address of the originating host */ + GDM_XDMCP_GOT_MANAGED_FORWARD, + /* manager -> manager (master) + * A single packet with GOT_MANAGED_FORWARD is sent to indicate + * that we did receive the MANAGED_FORWARD packet. The argument + * must match the MANAGED_FORWARD one or it will just be ignored. + * + * Argument is ARRAY8 with the address of the originating host */ + GDM_XDMCP_LAST_OPCODE /*just a marker, not an opcode */ +}; + +/* + * We don't support XDM-AUTHENTICATION-1 and XDM-AUTHORIZATION-1. + * + * The latter would be quite useful to avoid sending unencrypted + * cookies over the wire. Unfortunately it isn't supported without + * XDM-AUTHENTICATION-1 which requires a key database with private + * keys from all X terminals on your LAN. Fun, fun, fun. + * + * Furthermore user passwords go over the wire in cleartext anyway, + * so protecting cookies is not that important. + */ + +typedef struct _XdmAuth { + ARRAY8 authentication; + ARRAY8 authorization; +} XdmAuthRec, *XdmAuthPtr; + +static XdmAuthRec serv_authlist = { + { (CARD16) 0, (CARD8 *) 0 }, + { (CARD16) 0, (CARD8 *) 0 } +}; + +/* NOTE: Timeout and max are hardcoded */ +typedef struct _ForwardQuery { + time_t acctime; + GdmAddress *dsp_address; + GdmAddress *from_address; +} ForwardQuery; + +typedef struct _IndirectClient { + int id; + GdmAddress *dsp_address; + GdmAddress *chosen_address; + time_t acctime; +} IndirectClient; + +typedef struct { + int times; + guint handler; + GdmAddress *manager; + GdmAddress *origin; + GdmXdmcpDisplayFactory *xdmcp_display_factory; +} ManagedForward; + +struct _GdmXdmcpDisplayFactory +{ + GdmDisplayFactory parent; + + GSList *forward_queries; + GSList *managed_forwards; + GSList *indirect_clients; + + int socket_fd; + gint32 session_serial; + guint socket_watch_id; + XdmcpBuffer buf; + + guint num_sessions; + guint num_pending_sessions; + + char *sysid; + char *hostname; + ARRAY8 servhost; + + /* configuration */ + guint port; + gboolean use_multicast; + char *multicast_address; + gboolean honor_indirect; + char *willing_script; + guint max_displays_per_host; + guint max_displays; + guint max_pending_displays; + guint max_wait; + guint max_wait_indirect; +}; + +enum { + PROP_0, + PROP_PORT, + PROP_USE_MULTICAST, + PROP_MULTICAST_ADDRESS, + PROP_HONOR_INDIRECT, + PROP_WILLING_SCRIPT, + PROP_MAX_DISPLAYS_PER_HOST, + PROP_MAX_DISPLAYS, + PROP_MAX_PENDING_DISPLAYS, + PROP_MAX_WAIT, + PROP_MAX_WAIT_INDIRECT, +}; + +static void gdm_xdmcp_display_factory_class_init (GdmXdmcpDisplayFactoryClass *klass); +static void gdm_xdmcp_display_factory_init (GdmXdmcpDisplayFactory *manager); +static void gdm_xdmcp_display_factory_finalize (GObject *object); +static void gdm_xdmcp_send_alive (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD16 dspnum, + CARD32 sessid); +static gpointer xdmcp_display_factory_object = NULL; + +G_DEFINE_TYPE (GdmXdmcpDisplayFactory, gdm_xdmcp_display_factory, GDM_TYPE_DISPLAY_FACTORY) + +/* Theory of operation: + * + * Process idles waiting for UDP packets on port 177. + * Incoming packets are decoded and checked against tcp_wrapper. + * + * A typical session looks like this: + * + * Display sends Query/BroadcastQuery to Manager. + * + * Manager selects an appropriate authentication scheme from the + * display's list of supported ones and sends Willing/Unwilling. + * + * Assuming the display accepts the auth. scheme it sends back a + * Request. + * + * If the manager accepts to service the display (i.e. loadavg is low) + * it sends back an Accept containing a unique SessionID. The + * SessionID is stored in an accept queue by the Manager. Should the + * manager refuse to start a session a Decline is sent to the display. + * + * The display returns a Manage request containing the supplied + * SessionID. The manager will then start a session on the display. In + * case the SessionID is not on the accept queue the manager returns + * Refuse. If the manager fails to open the display for connections + * Failed is returned. + * + * During the session the display periodically sends KeepAlive packets + * to the manager. The manager responds with Alive. + * + * Similarly the manager xpings the display once in a while and shuts + * down the connection on failure. + * + */ + +GQuark +gdm_xdmcp_display_factory_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gdm_xdmcp_display_factory_error"); + } + + return ret; +} + +static gint32 +get_next_session_serial (GdmXdmcpDisplayFactory *factory) +{ + gint32 serial; + + again: + if (factory->session_serial != G_MAXINT32) { + serial = factory->session_serial++; + } else { + serial = g_random_int (); + } + + if (serial == 0) { + goto again; + } + + return serial; +} + +/* for debugging */ +static const char * +ai_family_str (struct addrinfo *ai) +{ + const char *str; + switch (ai->ai_family) { + case AF_INET: + str = "inet"; + break; + case AF_INET6: + str = "inet6"; + break; + case AF_UNIX: + str = "unix"; + break; + case AF_UNSPEC: + str = "unspecified"; + break; + default: + str = "unknown"; + break; + } + return str; +} + +/* for debugging */ +static const char * +ai_type_str (struct addrinfo *ai) +{ + const char *str; + switch (ai->ai_socktype) { + case SOCK_STREAM: + str = "stream"; + break; + case SOCK_DGRAM: + str = "datagram"; + break; + case SOCK_SEQPACKET: + str = "seqpacket"; + break; + case SOCK_RAW: + str = "raw"; + break; + default: + str = "unknown"; + break; + } + return str; +} + +/* for debugging */ +static const char * +ai_protocol_str (struct addrinfo *ai) +{ + const char *str; + switch (ai->ai_protocol) { + case 0: + str = "default"; + break; + case IPPROTO_TCP: + str = "TCP"; + break; + case IPPROTO_UDP: + str = "UDP"; + break; + case IPPROTO_RAW: + str = "raw"; + break; + default: + str = "unknown"; + break; + } + + return str; +} + +/* for debugging */ +static char * +ai_flags_str (struct addrinfo *ai) +{ + GString *str; + + str = g_string_new (""); + if (ai->ai_flags == 0) { + g_string_append (str, "none"); + } else { + if (ai->ai_flags & AI_PASSIVE) { + g_string_append (str, "passive "); + } + if (ai->ai_flags & AI_CANONNAME) { + g_string_append (str, "canon "); + } + if (ai->ai_flags & AI_NUMERICHOST) { + g_string_append (str, "numhost "); + } + if (ai->ai_flags & AI_NUMERICSERV) { + g_string_append (str, "numserv "); + } +#ifdef AI_V4MAPPEP + if (ai->ai_flags & AI_V4MAPPED) { + g_string_append (str, "v4mapped "); + } +#endif +#ifdef AI_ALL + if (ai->ai_flags & AI_ALL) { + g_string_append (str, "all "); + } +#endif + } + return g_string_free (str, FALSE); +} + +/* for debugging */ +static void +debug_addrinfo (struct addrinfo *ai) +{ + char *str; + str = ai_flags_str (ai); + g_debug ("GdmXdmcpDisplayFactory: addrinfo family=%s type=%s proto=%s flags=%s", + ai_family_str (ai), + ai_type_str (ai), + ai_protocol_str (ai), + str); + g_free (str); +} + +static int +create_socket (struct addrinfo *ai) +{ + int sock; + + sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) { + g_warning ("socket: %s", g_strerror (errno)); + return sock; + } + +#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) + if (ai->ai_family == AF_INET6) { + int zero = 0; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) < 0) + g_warning("setsockopt(IPV6_V6ONLY): %s", g_strerror(errno)); + } +#endif + + if (bind (sock, ai->ai_addr, ai->ai_addrlen) < 0) { + g_warning ("bind: %s", g_strerror (errno)); + close (sock); + return -1; + } + + return sock; +} + +static int +do_bind (guint port, + int family, + struct sockaddr_storage * hostaddr) +{ + struct addrinfo hints; + struct addrinfo *ai_list; + struct addrinfo *ai; + char strport[NI_MAXSERV]; + int gaierr; + int sock; + + sock = -1; + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + snprintf (strport, sizeof (strport), "%u", port); + + ai_list = NULL; + if ((gaierr = getaddrinfo (NULL, strport, &hints, &ai_list)) != 0) { + g_error ("Unable to connect to socket: %s", gai_strerror (gaierr)); + } + + /* should only be one but.. */ + for (ai = ai_list; ai != NULL; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) { + continue; + } + + debug_addrinfo (ai); + + if (sock < 0) { + char *host; + char *serv; + GdmAddress *addr; + + addr = gdm_address_new_from_sockaddr (ai->ai_addr, ai->ai_addrlen); + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (addr, &host, &serv); + g_debug ("GdmXdmcpDisplayFactory: Attempting to bind to host %s port %s", + host ? host : "(null)", serv ? serv : "(null)"); + g_free (host); + g_free (serv); + gdm_address_free (addr); + + sock = create_socket (ai); + if (sock >= 0) { + if (hostaddr != NULL) { + memcpy (hostaddr, ai->ai_addr, ai->ai_addrlen); + } + } + } + } + + freeaddrinfo (ai_list); + + return sock; +} + +static void +setup_multicast (GdmXdmcpDisplayFactory *factory) +{ +#ifdef ENABLE_IPV6 + /* Checking and Setting Multicast options */ + { + /* + * socktemp is a temporary socket for getting info about + * available interfaces + */ + int socktemp; + int i; + int num; + char *buf; + struct ipv6_mreq mreq; + + /* For interfaces' list */ + struct ifconf ifc; + struct ifreq *ifr; + + socktemp = socket (AF_INET, SOCK_DGRAM, 0); +#ifdef SIOCGIFNUM + if (ioctl (socktemp, SIOCGIFNUM, &num) < 0) { + num = 64; + } +#else + num = 64; +#endif /* SIOCGIFNUM */ + ifc.ifc_len = sizeof (struct ifreq) * num; + ifc.ifc_buf = buf = malloc (ifc.ifc_len); + + if (ioctl (socktemp, SIOCGIFCONF, &ifc) >= 0) { + ifr = ifc.ifc_req; + num = ifc.ifc_len / sizeof (struct ifreq); /* No of interfaces */ + + /* Joining multicast group with all interfaces */ + for (i = 0 ; i < num ; i++) { + struct ifreq ifreq; + int ifindex; + + memset (&ifreq, 0, sizeof (ifreq)); + strncpy (ifreq.ifr_name, ifr[i].ifr_name, sizeof (ifreq.ifr_name)); + /* paranoia */ + ifreq.ifr_name[sizeof (ifreq.ifr_name) - 1] = '\0'; + + if (ioctl (socktemp, SIOCGIFFLAGS, &ifreq) < 0) { + g_debug ("GdmXdmcpDisplayFactory: Could not get SIOCGIFFLAGS for %s", + ifr[i].ifr_name); + } + + ifindex = if_nametoindex (ifr[i].ifr_name); + + if ((!(ifreq.ifr_flags & IFF_UP) || + (ifreq.ifr_flags & IFF_LOOPBACK)) || + ((ifindex == 0 ) && (errno == ENXIO))) { + /* Not a valid interface or loopback interface*/ + continue; + } + + mreq.ipv6mr_interface = ifindex; + inet_pton (AF_INET6, + factory->multicast_address, + &mreq.ipv6mr_multiaddr); + + setsockopt (factory->socket_fd, + IPPROTO_IPV6, + IPV6_JOIN_GROUP, + &mreq, + sizeof (mreq)); + } + } + g_free (buf); + close (socktemp); + } +#endif /* ENABLE_IPV6 */ +} + +static void +fd_set_close_on_exec (int fd) +{ + int flags; + + flags = fcntl (fd, F_GETFD, 0); + if (flags < 0) { + return; + } + + flags |= FD_CLOEXEC; + + fcntl (fd, F_SETFD, flags); +} + +static gboolean +open_port (GdmXdmcpDisplayFactory *factory) +{ + struct sockaddr_storage serv_sa = { 0 }; + + g_debug ("GdmXdmcpDisplayFactory: Start up on host %s, port %d", + factory->hostname ? factory->hostname : "(null)", + factory->port); + + /* Open socket for communications */ +#ifdef ENABLE_IPV6 + factory->socket_fd = do_bind (factory->port, AF_INET6, &serv_sa); + if (factory->socket_fd < 0) +#endif + factory->socket_fd = do_bind (factory->port, AF_INET, &serv_sa); + + if G_UNLIKELY (factory->socket_fd < 0) { + g_warning (_("Could not create socket!")); + return FALSE; + } + + fd_set_close_on_exec (factory->socket_fd); + + if (factory->use_multicast) { + setup_multicast (factory); + } + + return TRUE; +} + +#ifdef HAVE_TCPWRAPPERS + + /* + * Avoids a warning, my tcpd.h file doesn't include this prototype, even + * though the library does include the function and the manpage mentions it + */ + extern int hosts_ctl (char *daemon, + char *client_name, + char *client_addr, + char *client_user); +#endif + +static gboolean +gdm_xdmcp_host_allow (GdmAddress *address) +{ +#ifdef HAVE_TCPWRAPPERS + char *client; + char *host; + gboolean ret; + + host = NULL; + client = NULL; + + /* Find client hostname */ + gdm_address_get_hostname (address, &client); + gdm_address_get_numeric_info (address, &host, NULL); + + /* Check with tcp_wrappers if client is allowed to access */ + ret = hosts_ctl ("gdm", client, host, ""); + + g_free (host); + g_free (client); + + return ret; +#else /* HAVE_TCPWRAPPERS */ + return (TRUE); +#endif /* HAVE_TCPWRAPPERS */ +} + +typedef struct { + GdmAddress *address; + int count; +} CountDisplayData; + +static void +count_displays_from_host (const char *id, + GdmDisplay *display, + CountDisplayData *data) +{ + GdmAddress *address; + + if (GDM_IS_XDMCP_DISPLAY (display)) { + address = gdm_xdmcp_display_get_remote_address (GDM_XDMCP_DISPLAY (display)); + + if (gdm_address_equal (address, data->address)) { + data->count++; + } + } +} + +static int +gdm_xdmcp_num_displays_from_host (GdmXdmcpDisplayFactory *factory, + GdmAddress *address) +{ + CountDisplayData data; + GdmDisplayStore *store; + + data.count = 0; + data.address = address; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_foreach (store, + (GdmDisplayStoreFunc)count_displays_from_host, + &data); + + return data.count; +} + +typedef struct { + GdmAddress *address; + int display_num; +} LookupHostData; + +static gboolean +lookup_by_host (const char *id, + GdmDisplay *display, + LookupHostData *data) +{ + GdmAddress *this_address; + int disp_num; + + if (! GDM_IS_XDMCP_DISPLAY (display)) { + return FALSE; + } + + this_address = gdm_xdmcp_display_get_remote_address (GDM_XDMCP_DISPLAY (display)); + gdm_display_get_x11_display_number (display, &disp_num, NULL); + + if (gdm_address_equal (this_address, data->address) + && disp_num == data->display_num) { + return TRUE; + } + + return FALSE; +} + +static GdmDisplay * +gdm_xdmcp_display_lookup_by_host (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int display_num) +{ + GdmDisplay *display; + LookupHostData *data; + GdmDisplayStore *store; + + data = g_new0 (LookupHostData, 1); + data->address = address; + data->display_num = display_num; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + display = gdm_display_store_find (store, + (GdmDisplayStoreFunc)lookup_by_host, + data); + g_free (data); + + return display; +} + +static char * +get_willing_output (GdmXdmcpDisplayFactory *factory) +{ + char *output; + char **argv; + FILE *fd; + char buf[256]; + + output = NULL; + buf[0] = '\0'; + + if (factory->willing_script == NULL) { + goto out; + } + + argv = NULL; + if (! g_shell_parse_argv (factory->willing_script, NULL, &argv, NULL)) { + goto out; + } + + if (argv == NULL || + argv[0] == NULL || + g_access (argv[0], X_OK) != 0) { + goto out; + } + + fd = popen (factory->willing_script, "r"); + if (fd == NULL) { + goto out; + } + + if (fgets (buf, sizeof (buf), fd) == NULL) { + pclose (fd); + goto out; + } + + pclose (fd); + + output = g_strdup (buf); + + out: + return output; +} + +static void +gdm_xdmcp_send_willing (GdmXdmcpDisplayFactory *factory, + GdmAddress *address) +{ + ARRAY8 status; + XdmcpHeader header; + static char *last_status = NULL; + static time_t last_willing = 0; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending WILLING to %s", + host ? host : "(null)"); + g_free (host); + + if (last_willing == 0 || time (NULL) - 3 > last_willing) { + char *s; + + g_free (last_status); + + s = get_willing_output (factory); + if (s != NULL) { + last_status = s; + } else { + last_status = g_strdup (factory->sysid); + } + } + + if (! gdm_address_is_local (address) && + gdm_xdmcp_num_displays_from_host (factory, address) >= factory->max_displays_per_host) { + /* + * Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii + */ + status.data = (CARD8 *) g_strdup_printf ("%s (Server is busy)", + last_status); + } else { + status.data = (CARD8 *) g_strdup (last_status); + } + + status.length = strlen ((char *) status.data); + + header.opcode = (CARD16) WILLING; + header.length = 6 + serv_authlist.authentication.length; + header.length += factory->servhost.length + status.length; + header.version = XDM_PROTOCOL_VERSION; + XdmcpWriteHeader (&factory->buf, &header); + + /* Hardcoded authentication */ + XdmcpWriteARRAY8 (&factory->buf, &serv_authlist.authentication); + XdmcpWriteARRAY8 (&factory->buf, &factory->servhost); + XdmcpWriteARRAY8 (&factory->buf, &status); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + g_free (status.data); +} + +static void +gdm_xdmcp_send_unwilling (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int type) +{ + ARRAY8 status; + XdmcpHeader header; + static time_t last_time = 0; + char *host; + + /* only send at most one packet per second, + no harm done if we don't send it at all */ + if (last_time + 1 >= time (NULL)) { + return; + } + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending UNWILLING to %s", + host ? host : "(null)"); + g_warning ("Denied XDMCP query from host %s", + host ? host : "(null)"); + g_free (host); + + /* + * Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii + */ + status.data = (CARD8 *) "Display not authorized to connect"; + status.length = strlen ((char *) status.data); + + header.opcode = (CARD16) UNWILLING; + header.length = 4 + factory->servhost.length + status.length; + header.version = XDM_PROTOCOL_VERSION; + XdmcpWriteHeader (&factory->buf, &header); + + XdmcpWriteARRAY8 (&factory->buf, &factory->servhost); + XdmcpWriteARRAY8 (&factory->buf, &status); + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + last_time = time (NULL); +} + +#define SIN(__s) ((struct sockaddr_in *) __s) +#define SIN6(__s) ((struct sockaddr_in6 *) __s) + +static void +set_port_for_request (GdmAddress *address, + ARRAY8 *port) +{ + struct sockaddr_storage *ss; + + ss = gdm_address_peek_sockaddr_storage (address); + + /* we depend on this being 2 elsewhere as well */ + port->length = 2; + + switch (ss->ss_family) { + case AF_INET: + port->data = (CARD8 *)g_memdup (&(SIN (ss)->sin_port), port->length); + break; + case AF_INET6: + port->data = (CARD8 *)g_memdup (&(SIN6 (ss)->sin6_port), port->length); + break; + default: + port->data = NULL; + break; + } +} + +static void +set_address_for_request (GdmAddress *address, + ARRAY8 *addr) +{ + struct sockaddr_storage *ss; + + ss = gdm_address_peek_sockaddr_storage (address); + + switch (ss->ss_family) { + case AF_INET: + addr->length = sizeof (struct in_addr); + addr->data = g_memdup (&SIN (ss)->sin_addr, addr->length); + break; + case AF_INET6: + addr->length = sizeof (struct in6_addr); + addr->data = g_memdup (&SIN6 (ss)->sin6_addr, addr->length); + break; + default: + addr->length = 0; + addr->data = NULL; + break; + } + +} + +static void +gdm_xdmcp_send_forward_query (GdmXdmcpDisplayFactory *factory, + IndirectClient *ic, + GdmAddress *address, + GdmAddress *display_address, + ARRAYofARRAY8Ptr authlist) +{ + XdmcpHeader header; + int i; + ARRAY8 addr; + ARRAY8 port; + char *host; + char *serv; + + g_assert (ic != NULL); + g_assert (ic->chosen_address != NULL); + + host = NULL; + gdm_address_get_numeric_info (ic->chosen_address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending forward query to %s", + host ? host : "(null)"); + g_free (host); + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (display_address, &host, &serv); + g_debug ("GdmXdmcpDisplayFactory: Query contains %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + g_free (host); + g_free (serv); + + set_port_for_request (address, &port); + set_address_for_request (display_address, &addr); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) FORWARD_QUERY; + header.length = 0; + header.length += 2 + addr.length; + header.length += 2 + port.length; + header.length += 1; + for (i = 0; i < authlist->length; i++) { + header.length += 2 + authlist->data[i].length; + } + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteARRAY8 (&factory->buf, &addr); + XdmcpWriteARRAY8 (&factory->buf, &port); + XdmcpWriteARRAYofARRAY8 (&factory->buf, authlist); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (ic->chosen_address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (ic->chosen_address))); + + g_free (port.data); + g_free (addr.data); +} + +static void +handle_any_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + ARRAYofARRAY8Ptr authentication_names, + int type) +{ + gdm_xdmcp_send_willing (factory, address); +} + +static void +handle_direct_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len, + int type) +{ + ARRAYofARRAY8 clnt_authlist; + int expected_len; + int i; + int res; + + res = XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_authlist); + if G_UNLIKELY (! res) { + g_warning ("Could not extract authlist from packet"); + return; + } + + expected_len = 1; + + for (i = 0 ; i < clnt_authlist.length ; i++) { + expected_len += 2 + clnt_authlist.data[i].length; + } + + if (len == expected_len) { + handle_any_query (factory, address, &clnt_authlist, type); + } else { + g_warning ("Error in checksum"); + } + + XdmcpDisposeARRAYofARRAY8 (&clnt_authlist); +} + +static void +gdm_xdmcp_handle_broadcast_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + if (gdm_xdmcp_host_allow (address)) { + handle_direct_query (factory, address, len, BROADCAST_QUERY); + } else { + /* just ignore it */ + } +} + +static void +gdm_xdmcp_handle_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + if (gdm_xdmcp_host_allow (address)) { + handle_direct_query (factory, address, len, QUERY); + } else { + gdm_xdmcp_send_unwilling (factory, address, QUERY); + } +} + +static IndirectClient * +indirect_client_create (GdmXdmcpDisplayFactory *factory, + GdmAddress *dsp_address) +{ + IndirectClient *ic; + + ic = g_new0 (IndirectClient, 1); + ic->dsp_address = gdm_address_copy (dsp_address); + + factory->indirect_clients = g_slist_prepend (factory->indirect_clients, ic); + + return ic; +} + +static void +indirect_client_destroy (GdmXdmcpDisplayFactory *factory, + IndirectClient *ic) +{ + if (ic == NULL) { + return; + } + + factory->indirect_clients = g_slist_remove (factory->indirect_clients, ic); + + ic->acctime = 0; + + { + char *host; + + host = NULL; + gdm_address_get_numeric_info (ic->dsp_address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Disposing IndirectClient for %s", + host ? host : "(null)"); + g_free (host); + } + + g_free (ic->dsp_address); + ic->dsp_address = NULL; + g_free (ic->chosen_address); + ic->chosen_address = NULL; + + g_free (ic); +} + +static IndirectClient * +indirect_client_lookup_by_chosen (GdmXdmcpDisplayFactory *factory, + GdmAddress *chosen_address, + GdmAddress *origin_address) +{ + GSList *li; + char *host; + IndirectClient *ret; + + g_assert (chosen_address != NULL); + g_assert (origin_address != NULL); + + ret = NULL; + + for (li = factory->indirect_clients; li != NULL; li = li->next) { + IndirectClient *ic = li->data; + + if (ic != NULL + && ic->chosen_address != NULL + && gdm_address_equal (ic->chosen_address, chosen_address)) { + if (gdm_address_equal (ic->dsp_address, origin_address)) { + ret = ic; + goto out; + } else if (gdm_address_is_loopback (ic->dsp_address) + && gdm_address_is_local (origin_address)) { + ret = ic; + goto out; + } + } + } + + gdm_address_get_numeric_info (chosen_address, &host, NULL); + + g_debug ("GdmXdmcpDisplayFactory: Chosen %s host not found", + host ? host : "(null)"); + g_free (host); + out: + return ret; +} + +/* lookup by origin */ +static IndirectClient * +indirect_client_lookup (GdmXdmcpDisplayFactory *factory, + GdmAddress *address) +{ + GSList *li; + GSList *qlist; + IndirectClient *ret; + time_t curtime; + + g_assert (address != NULL); + + curtime = time (NULL); + ret = NULL; + + qlist = g_slist_copy (factory->indirect_clients); + + for (li = qlist; li != NULL; li = li->next) { + IndirectClient *ic; + char *host; + char *serv; + + ic = (IndirectClient *) li->data; + + if (ic == NULL) { + continue; + } + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (ic->dsp_address, &host, &serv); + + g_debug ("GdmXdmcpDisplayFactory: comparing %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + if (gdm_address_equal (ic->dsp_address, address)) { + ret = ic; + g_free (host); + g_free (serv); + break; + } + + if (ic->acctime > 0 && curtime > ic->acctime + factory->max_wait_indirect) { + g_debug ("GdmXdmcpDisplayFactory: Disposing stale forward query from %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + + indirect_client_destroy (factory, ic); + } + + g_free (host); + g_free (serv); + } + + g_slist_free (qlist); + + if (ret == NULL) { + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Host %s not found", + host ? host : "(null)"); + g_free (host); + } + + return ret; +} + +static void +gdm_xdmcp_handle_indirect_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + ARRAYofARRAY8 clnt_authlist; + int expected_len; + int i; + int res; + IndirectClient *ic; + + if (! gdm_xdmcp_host_allow (address)) { + /* ignore the request */ + return; + } + + if (! factory->honor_indirect) { + /* ignore it */ + return; + } + + if (factory->num_sessions > factory->max_displays || + (!gdm_address_is_local (address) && + gdm_xdmcp_num_displays_from_host (factory, address) > factory->max_displays_per_host)) { + g_debug ("GdmXdmcpDisplayFactory: reached maximum number of clients - ignoring indirect query"); + return; + } + + res = XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_authlist); + if G_UNLIKELY (! res) { + g_warning ("Could not extract authlist from packet"); + return; + } + + expected_len = 1; + + for (i = 0 ; i < clnt_authlist.length ; i++) { + expected_len += 2 + clnt_authlist.data[i].length; + } + + /* Try to look up the display in + * the pending list. If found send a FORWARD_QUERY to the + * chosen manager. Otherwise alloc a new indirect display. */ + + if (len != expected_len) { + g_warning ("Error in checksum"); + goto out; + } + + + ic = indirect_client_lookup (factory, address); + + if (ic != NULL && ic->chosen_address != NULL) { + /* if user chose us, then just send willing */ + if (gdm_address_is_local (ic->chosen_address)) { + g_debug ("GdmXdmcpDisplayFactory: the chosen address is local - dropping indirect"); + + /* get rid of indirect, so that we don't get + * the chooser */ + indirect_client_destroy (factory, ic); + gdm_xdmcp_send_willing (factory, address); + } else if (gdm_address_is_loopback (address)) { + /* woohoo! fun, I have no clue how to get + * the correct ip, SO I just send forward + * queries with all the different IPs */ + const GList *list = gdm_address_peek_local_list (); + + g_debug ("GdmXdmcpDisplayFactory: the chosen address is a loopback"); + + while (list != NULL) { + GdmAddress *saddr = list->data; + + if (! gdm_address_is_loopback (saddr)) { + /* forward query to * chosen host */ + gdm_xdmcp_send_forward_query (factory, + ic, + address, + saddr, + &clnt_authlist); + } + + list = list->next; + } + } else { + /* or send forward query to chosen host */ + gdm_xdmcp_send_forward_query (factory, + ic, + address, + address, + &clnt_authlist); + } + } else if (ic == NULL) { + ic = indirect_client_create (factory, address); + if (ic != NULL) { + gdm_xdmcp_send_willing (factory, address); + } + } else { + gdm_xdmcp_send_willing (factory, address); + } + +out: + XdmcpDisposeARRAYofARRAY8 (&clnt_authlist); +} + +static void +forward_query_destroy (GdmXdmcpDisplayFactory *factory, + ForwardQuery *q) +{ + if (q == NULL) { + return; + } + + factory->forward_queries = g_slist_remove (factory->forward_queries, q); + + q->acctime = 0; + + { + char *host; + + host = NULL; + gdm_address_get_numeric_info (q->dsp_address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Disposing %s", + host ? host : "(null)"); + g_free (host); + } + + g_free (q->dsp_address); + q->dsp_address = NULL; + g_free (q->from_address); + q->from_address = NULL; + + g_free (q); +} + +static gboolean +remove_oldest_forward (GdmXdmcpDisplayFactory *factory) +{ + GSList *li; + ForwardQuery *oldest = NULL; + + for (li = factory->forward_queries; li != NULL; li = li->next) { + ForwardQuery *query = li->data; + + if (oldest == NULL || query->acctime < oldest->acctime) { + oldest = query; + } + } + + if (oldest != NULL) { + forward_query_destroy (factory, oldest); + return TRUE; + } else { + return FALSE; + } +} + +static ForwardQuery * +forward_query_create (GdmXdmcpDisplayFactory *factory, + GdmAddress *mgr_address, + GdmAddress *dsp_address) +{ + ForwardQuery *q; + int count; + + count = g_slist_length (factory->forward_queries); + + while (count > GDM_MAX_FORWARD_QUERIES && remove_oldest_forward (factory)) { + count--; + } + + q = g_new0 (ForwardQuery, 1); + q->dsp_address = gdm_address_copy (dsp_address); + q->from_address = gdm_address_copy (mgr_address); + + factory->forward_queries = g_slist_prepend (factory->forward_queries, q); + + return q; +} + +static ForwardQuery * +forward_query_lookup (GdmXdmcpDisplayFactory *factory, + GdmAddress *address) +{ + GSList *li; + GSList *qlist; + ForwardQuery *ret; + time_t curtime; + + curtime = time (NULL); + ret = NULL; + + qlist = g_slist_copy (factory->forward_queries); + + for (li = qlist; li != NULL; li = li->next) { + ForwardQuery *q; + char *host; + char *serv; + + q = (ForwardQuery *) li->data; + + if (q == NULL) { + continue; + } + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (q->dsp_address, &host, &serv); + + g_debug ("GdmXdmcpDisplayFactory: comparing %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + if (gdm_address_equal (q->dsp_address, address)) { + ret = q; + g_free (host); + g_free (serv); + break; + } + + if (q->acctime > 0 && curtime > q->acctime + GDM_FORWARD_QUERY_TIMEOUT) { + g_debug ("GdmXdmcpDisplayFactory: Disposing stale forward query from %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + + forward_query_destroy (factory, q); + } + + g_free (host); + g_free (serv); + } + + g_slist_free (qlist); + + if (ret == NULL) { + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Host %s not found", + host ? host : "(null)"); + g_free (host); + } + + return ret; +} + +static gboolean +create_address_from_request (ARRAY8 *req_addr, + ARRAY8 *req_port, + int family, + GdmAddress **address) +{ + uint16_t port; + char host_buf [NI_MAXHOST]; + char serv_buf [NI_MAXSERV]; + char *serv; + const char *host; + struct addrinfo hints; + struct addrinfo *ai_list; + struct addrinfo *ai; + int gaierr; + gboolean found; + + if (address != NULL) { + *address = NULL; + } + + if (req_addr == NULL) { + return FALSE; + } + + serv = NULL; + if (req_port != NULL) { + /* port must always be length 2 */ + if (req_port->length != 2) { + return FALSE; + } + + memcpy (&port, req_port->data, 2); + snprintf (serv_buf, sizeof (serv_buf), "%d", ntohs (port)); + serv = serv_buf; + } else { + /* assume XDM_UDP_PORT */ + snprintf (serv_buf, sizeof (serv_buf), "%d", XDM_UDP_PORT); + serv = serv_buf; + } + + host = NULL; + if (req_addr->length == 4) { + host = inet_ntop (AF_INET, + (const void *)req_addr->data, + host_buf, + sizeof (host_buf)); + } else if (req_addr->length == 16) { + host = inet_ntop (AF_INET6, + (const void *)req_addr->data, + host_buf, + sizeof (host_buf)); + } + + if (host == NULL) { + g_warning ("Bad address"); + return FALSE; + } + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = family; + /* this should convert IPv4 address to IPv6 if needed */ +#ifdef AI_V4MAPPED + hints.ai_flags = AI_V4MAPPED; +#endif + hints.ai_socktype = SOCK_DGRAM; + + if ((gaierr = getaddrinfo (host, serv, &hints, &ai_list)) != 0) { + g_warning ("Unable to get address: %s", gai_strerror (gaierr)); + return FALSE; + } + + /* just take the first one */ + ai = ai_list; + + found = FALSE; + if (ai != NULL) { + found = TRUE; + if (address != NULL) { + *address = gdm_address_new_from_sockaddr (ai->ai_addr, ai->ai_addrlen); + } + } + + freeaddrinfo (ai_list); + + return found; +} + +static void +gdm_xdmcp_whack_queued_managed_forwards (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + GdmAddress *origin) +{ + GSList *li; + + for (li = factory->managed_forwards; li != NULL; li = li->next) { + ManagedForward *mf = li->data; + + if (gdm_address_equal (mf->manager, address) && + gdm_address_equal (mf->origin, origin)) { + factory->managed_forwards = g_slist_remove_link (factory->managed_forwards, li); + g_slist_free_1 (li); + g_source_remove (mf->handler); + /* mf freed by glib */ + return; + } + } +} + +static void +gdm_xdmcp_handle_forward_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + ARRAY8 clnt_addr; + ARRAY8 clnt_port; + ARRAYofARRAY8 clnt_authlist; + int i; + int explen; + GdmAddress *disp_address; + char *host; + char *serv; + + disp_address = NULL; + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + char *host2; + + host2 = NULL; + gdm_address_get_numeric_info (address, &host2, NULL); + + g_warning ("%s: Got FORWARD_QUERY from banned host %s", + "gdm_xdmcp_handle_forward query", + host2 ? host2 : "(null)"); + g_free (host2); + return; + } + + /* Read display address */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_addr)) { + g_warning ("%s: Could not read display address", + "gdm_xdmcp_handle_forward_query"); + return; + } + + /* Read display port */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_port)) { + XdmcpDisposeARRAY8 (&clnt_addr); + g_warning ("%s: Could not read display port number", + "gdm_xdmcp_handle_forward_query"); + return; + } + + /* Extract array of authentication names from Xdmcp packet */ + if G_UNLIKELY (! XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_authlist)) { + XdmcpDisposeARRAY8 (&clnt_addr); + XdmcpDisposeARRAY8 (&clnt_port); + g_warning ("%s: Could not extract authlist from packet", + "gdm_xdmcp_handle_forward_query"); + return; + } + + /* Crude checksumming */ + explen = 1; + explen += 2 + clnt_addr.length; + explen += 2 + clnt_port.length; + + for (i = 0 ; i < clnt_authlist.length ; i++) { + char *s = g_strndup ((char *) clnt_authlist.data[i].data, + clnt_authlist.length); + g_debug ("GdmXdmcpDisplayFactory: authlist: %s", s); + g_free (s); + + explen += 2 + clnt_authlist.data[i].length; + } + + if G_UNLIKELY (len != explen) { + g_warning ("%s: Error in checksum", + "gdm_xdmcp_handle_forward_query"); + goto out; + } + + if (! create_address_from_request (&clnt_addr, &clnt_port, gdm_address_get_family_type (address), &disp_address)) { + g_warning ("Unable to parse address for request"); + goto out; + } + + gdm_xdmcp_whack_queued_managed_forwards (factory, + address, + disp_address); + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (disp_address, &host, &serv); + g_debug ("GdmXdmcpDisplayFactory: Got FORWARD_QUERY for display: %s, port %s", + host ? host : "(null)", serv ? serv : "(null)"); + g_free (host); + g_free (serv); + + /* Check with tcp_wrappers if display is allowed to access */ + if (gdm_xdmcp_host_allow (disp_address)) { + ForwardQuery *q; + + q = forward_query_lookup (factory, disp_address); + if (q != NULL) { + forward_query_destroy (factory, q); + } + + forward_query_create (factory, address, disp_address); + + gdm_xdmcp_send_willing (factory, disp_address); + } + + out: + + gdm_address_free (disp_address); + + XdmcpDisposeARRAYofARRAY8 (&clnt_authlist); + XdmcpDisposeARRAY8 (&clnt_port); + XdmcpDisposeARRAY8 (&clnt_addr); +} + +static void +gdm_xdmcp_really_send_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + GdmAddress *origin) +{ + ARRAY8 addr; + XdmcpHeader header; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending MANAGED_FORWARD to %s", + host ? host : "(null)"); + g_free (host); + + set_address_for_request (origin, &addr); + + header.opcode = (CARD16) GDM_XDMCP_MANAGED_FORWARD; + header.length = 4 + addr.length; + header.version = GDM_XDMCP_PROTOCOL_VERSION; + XdmcpWriteHeader (&factory->buf, &header); + + XdmcpWriteARRAY8 (&factory->buf, &addr); + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + g_free (addr.data); +} + +static gboolean +managed_forward_handler (ManagedForward *mf) +{ + if (mf->xdmcp_display_factory->socket_fd > 0) { + gdm_xdmcp_really_send_managed_forward (mf->xdmcp_display_factory, + mf->manager, + mf->origin); + } + + mf->times++; + if (mf->xdmcp_display_factory->socket_fd <= 0 || mf->times >= 2) { + mf->xdmcp_display_factory->managed_forwards = g_slist_remove (mf->xdmcp_display_factory->managed_forwards, mf); + mf->handler = 0; + /* mf freed by glib */ + return FALSE; + } + return TRUE; +} + +static void +managed_forward_free (ManagedForward *mf) +{ + gdm_address_free (mf->origin); + gdm_address_free (mf->manager); + g_free (mf); +} + +static void +gdm_xdmcp_send_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + GdmAddress *origin) +{ + ManagedForward *mf; + + gdm_xdmcp_really_send_managed_forward (factory, address, origin); + + mf = g_new0 (ManagedForward, 1); + mf->times = 0; + mf->xdmcp_display_factory = factory; + + mf->manager = gdm_address_copy (address); + mf->origin = gdm_address_copy (origin); + + mf->handler = g_timeout_add_full (G_PRIORITY_DEFAULT, + MANAGED_FORWARD_INTERVAL, + (GSourceFunc)managed_forward_handler, + mf, + (GDestroyNotify)managed_forward_free); + factory->managed_forwards = g_slist_prepend (factory->managed_forwards, mf); +} + +static void +gdm_xdmcp_send_got_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + GdmAddress *origin) +{ + ARRAY8 addr; + XdmcpHeader header; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending GOT_MANAGED_FORWARD to %s", + host ? host : "(null)"); + g_free (host); + + set_address_for_request (origin, &addr); + + header.opcode = (CARD16) GDM_XDMCP_GOT_MANAGED_FORWARD; + header.length = 4 + addr.length; + header.version = GDM_XDMCP_PROTOCOL_VERSION; + XdmcpWriteHeader (&factory->buf, &header); + + XdmcpWriteARRAY8 (&factory->buf, &addr); + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); +} + +static void +count_sessions (const char *id, + GdmDisplay *display, + GdmXdmcpDisplayFactory *factory) +{ + if (GDM_IS_XDMCP_DISPLAY (display)) { + int status; + + status = gdm_display_get_status (display); + + if (status == GDM_DISPLAY_MANAGED) { + factory->num_sessions++; + } else if (status == GDM_DISPLAY_UNMANAGED) { + factory->num_pending_sessions++; + } + } +} + +static void +gdm_xdmcp_recount_sessions (GdmXdmcpDisplayFactory *factory) +{ + GdmDisplayStore *store; + + factory->num_sessions = 0; + factory->num_pending_sessions = 0; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_foreach (store, + (GdmDisplayStoreFunc)count_sessions, + factory); +} + +static gboolean +purge_displays (const char *id, + GdmDisplay *display, + GdmXdmcpDisplayFactory *factory) +{ + if (GDM_IS_XDMCP_DISPLAY (display)) { + int status; + time_t currtime; + time_t acctime; + + currtime = time (NULL); + status = gdm_display_get_status (display); + acctime = gdm_display_get_creation_time (display); + + if (status == GDM_DISPLAY_UNMANAGED && + currtime > acctime + factory->max_wait) { + /* return TRUE to remove display */ + return TRUE; + } + } + + return FALSE; +} + +static void +gdm_xdmcp_displays_purge (GdmXdmcpDisplayFactory *factory) +{ + GdmDisplayStore *store; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + gdm_display_store_foreach_remove (store, + (GdmDisplayStoreFunc)purge_displays, + factory); + + gdm_xdmcp_recount_sessions (factory); +} + +typedef struct { + const char *hostname; + int display_num; +} RemoveHostData; + +static gboolean +remove_host (const char *id, + GdmDisplay *display, + RemoveHostData *data) +{ + char *hostname; + int disp_num; + + if (! GDM_IS_XDMCP_DISPLAY (display)) { + return FALSE; + } + + gdm_display_get_remote_hostname (display, &hostname, NULL); + gdm_display_get_x11_display_number (display, &disp_num, NULL); + + if (disp_num == data->display_num && + hostname != NULL && + data->hostname != NULL && + strcmp (hostname, data->hostname) == 0) { + /* return TRUE to remove */ + return TRUE; + } + + return FALSE; +} + +static void +display_dispose_check (GdmXdmcpDisplayFactory *factory, + const char *hostname, + int display_num) +{ + RemoveHostData *data; + GdmDisplayStore *store; + + if (hostname == NULL) { + return; + } + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + g_debug ("GdmXdmcpDisplayFactory: display_dispose_check (%s:%d)", + hostname ? hostname : "(null)", display_num); + + data = g_new0 (RemoveHostData, 1); + data->hostname = hostname; + data->display_num = display_num; + gdm_display_store_foreach_remove (store, + (GdmDisplayStoreFunc)remove_host, + data); + g_free (data); + + gdm_xdmcp_recount_sessions (factory); +} + +static void +gdm_xdmcp_send_decline (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + const char *reason) +{ + XdmcpHeader header; + ARRAY8 authentype; + ARRAY8 authendata; + ARRAY8 status; + ForwardQuery *fq; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending DECLINE to %s", + host ? host : "(null)"); + g_free (host); + + authentype.data = (CARD8 *) 0; + authentype.length = (CARD16) 0; + + authendata.data = (CARD8 *) 0; + authendata.length = (CARD16) 0; + + status.data = (CARD8 *) reason; + status.length = strlen ((char *) status.data); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) DECLINE; + header.length = 2 + status.length; + header.length += 2 + authentype.length; + header.length += 2 + authendata.length; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteARRAY8 (&factory->buf, &status); + XdmcpWriteARRAY8 (&factory->buf, &authentype); + XdmcpWriteARRAY8 (&factory->buf, &authendata); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + /* Send MANAGED_FORWARD to indicate that the connection + * reached some sort of resolution */ + fq = forward_query_lookup (factory, address); + if (fq != NULL) { + gdm_xdmcp_send_managed_forward (factory, fq->from_address, address); + forward_query_destroy (factory, fq); + } +} + +static void +on_hostname_selected (GdmXdmcpChooserDisplay *display, + const char *hostname, + GdmXdmcpDisplayFactory *factory) +{ + struct addrinfo hints; + struct addrinfo *ai_list; + struct addrinfo *ai; + int gaierr; + GdmAddress *address; + IndirectClient *ic; + gchar *xdmcp_port; + + g_debug ("GdmXdmcpDisplayFactory: hostname selected: %s", + hostname ? hostname : "(null)"); + + address = gdm_xdmcp_display_get_remote_address (GDM_XDMCP_DISPLAY (display)); + + g_assert (address != NULL); + + ic = indirect_client_lookup (factory, address); + + if (ic->chosen_address != NULL) { + gdm_address_free (ic->chosen_address); + ic->chosen_address = NULL; + } + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = gdm_address_get_family_type (address); + /* this should convert IPv4 address to IPv6 if needed */ +#ifdef AI_V4MAPPED + hints.ai_flags = AI_V4MAPPED; +#endif + + xdmcp_port = g_strdup_printf ("%d", XDM_UDP_PORT); + if ((gaierr = getaddrinfo (hostname, xdmcp_port, &hints, &ai_list)) != 0) { + g_warning ("Unable to get address: %s", gai_strerror (gaierr)); + g_free (xdmcp_port); + return; + } + g_free (xdmcp_port); + + /* just take the first one */ + ai = ai_list; + + if (ai != NULL) { + char *ip; + ic->chosen_address = gdm_address_new_from_sockaddr (ai->ai_addr, ai->ai_addrlen); + + ip = NULL; + gdm_address_get_numeric_info (ic->chosen_address, &ip, NULL); + g_debug ("GdmXdmcpDisplayFactory: hostname resolves to %s", + ip ? ip : "(null)"); + g_free (ip); + } + + freeaddrinfo (ai_list); +} + +static void +on_client_disconnected (GdmDisplay *display) +{ + if (gdm_display_get_status (display) != GDM_DISPLAY_MANAGED) + return; + + gdm_display_stop_greeter_session (display); + gdm_display_unmanage (display); + gdm_display_finish (display); +} + +static void +on_display_status_changed (GdmDisplay *display, + GParamSpec *arg1, + GdmXdmcpDisplayFactory *factory) +{ + int status; + GdmLaunchEnvironment *launch_environment; + GdmSession *session; + GdmAddress *address; + gint32 session_number; + int display_number; + + launch_environment = NULL; + g_object_get (display, "launch-environment", &launch_environment, NULL); + + session = NULL; + if (launch_environment != NULL) { + session = gdm_launch_environment_get_session (launch_environment); + } + + status = gdm_display_get_status (display); + + g_debug ("GdmXdmcpDisplayFactory: xdmcp display status changed: %d", status); + switch (status) { + case GDM_DISPLAY_FINISHED: + g_object_get (display, + "remote-address", &address, + "x11-display-number", &display_number, + "session-number", &session_number, + NULL); + gdm_xdmcp_send_alive (factory, address, display_number, session_number); + + gdm_display_factory_queue_purge_displays (GDM_DISPLAY_FACTORY (factory)); + break; + case GDM_DISPLAY_FAILED: + gdm_display_factory_queue_purge_displays (GDM_DISPLAY_FACTORY (factory)); + break; + case GDM_DISPLAY_UNMANAGED: + if (session != NULL) { + g_signal_handlers_disconnect_by_func (G_OBJECT (session), + G_CALLBACK (on_client_disconnected), + display); + } + break; + case GDM_DISPLAY_PREPARED: + break; + case GDM_DISPLAY_MANAGED: + if (session != NULL) { + g_signal_connect_object (G_OBJECT (session), + "client-disconnected", + G_CALLBACK (on_client_disconnected), + display, G_CONNECT_SWAPPED); + g_signal_connect_object (G_OBJECT (session), + "disconnected", + G_CALLBACK (on_client_disconnected), + display, G_CONNECT_SWAPPED); + } + break; + default: + g_assert_not_reached (); + break; + } +} + +static GdmDisplay * +gdm_xdmcp_display_create (GdmXdmcpDisplayFactory *factory, + const char *hostname, + GdmAddress *address, + int displaynum) +{ + GdmDisplay *display; + GdmDisplayStore *store; + gboolean use_chooser; + + g_debug ("GdmXdmcpDisplayFactory: Creating xdmcp display for %s:%d", + hostname ? hostname : "(null)", displaynum); + + use_chooser = FALSE; + if (factory->honor_indirect) { + IndirectClient *ic; + + ic = indirect_client_lookup (factory, address); + + /* This was an indirect thingie and nothing was yet chosen, + * use a chooser */ + if (ic != NULL && ic->chosen_address == NULL) { + use_chooser = TRUE; + } + } + + if (use_chooser) { + display = gdm_xdmcp_chooser_display_new (hostname, + displaynum, + address, + get_next_session_serial (factory)); + g_signal_connect (display, "hostname-selected", G_CALLBACK (on_hostname_selected), factory); + } else { + display = gdm_xdmcp_display_new (hostname, + displaynum, + address, + get_next_session_serial (factory)); + } + + if (display == NULL) { + goto out; + } + + if (! gdm_display_prepare (display)) { + gdm_display_unmanage (display); + g_object_unref (display); + display = NULL; + goto out; + } + + g_signal_connect_after (display, + "notify::status", + G_CALLBACK (on_display_status_changed), + factory); + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_add (store, display); + + factory->num_pending_sessions++; + out: + + return display; +} + +static void +gdm_xdmcp_send_accept (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD32 session_id, + ARRAY8Ptr authentication_name, + ARRAY8Ptr authentication_data, + ARRAY8Ptr authorization_name, + ARRAY8Ptr authorization_data) +{ + XdmcpHeader header; + char *host; + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) ACCEPT; + header.length = 4; + header.length += 2 + authentication_name->length; + header.length += 2 + authentication_data->length; + header.length += 2 + authorization_name->length; + header.length += 2 + authorization_data->length; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteCARD32 (&factory->buf, session_id); + XdmcpWriteARRAY8 (&factory->buf, authentication_name); + XdmcpWriteARRAY8 (&factory->buf, authentication_data); + XdmcpWriteARRAY8 (&factory->buf, authorization_name); + XdmcpWriteARRAY8 (&factory->buf, authorization_data); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending ACCEPT to %s with SessionID=%ld", + host ? host : "(null)", + (long)session_id); + g_free (host); +} + +static void +gdm_xdmcp_handle_request (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + CARD16 clnt_dspnum; + ARRAY16 clnt_conntyp; + ARRAYofARRAY8 clnt_addr; + ARRAY8 clnt_authname; + ARRAY8 clnt_authdata; + ARRAYofARRAY8 clnt_authorization_names; + ARRAY8 clnt_manufacturer; + int explen; + int i; + gboolean mitauth; + gboolean entered; + char *hostname; + + mitauth = FALSE; + entered = FALSE; + + hostname = NULL; + gdm_address_get_numeric_info (address, &hostname, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got REQUEST from %s", + hostname ? hostname : "(null)"); + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("%s: Got REQUEST from banned host %s", + "gdm_xdmcp_handle_request", + hostname ? hostname : "(null)"); + goto out; + } + + gdm_xdmcp_displays_purge (factory); /* Purge pending displays */ + + /* Remote display number */ + if G_UNLIKELY (! XdmcpReadCARD16 (&factory->buf, &clnt_dspnum)) { + g_warning ("%s: Could not read Display Number", + "gdm_xdmcp_handle_request"); + goto out; + } + + /* We don't care about connection type. Address says it all */ + if G_UNLIKELY (! XdmcpReadARRAY16 (&factory->buf, &clnt_conntyp)) { + g_warning ("%s: Could not read Connection Type", + "gdm_xdmcp_handle_request"); + goto out; + } + + /* This is TCP/IP - we don't care */ + if G_UNLIKELY (! XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_addr)) { + g_warning ("%s: Could not read Client Address", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAY16 (&clnt_conntyp); + goto out; + } + + /* Read authentication type */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_authname)) { + g_warning ("%s: Could not read Authentication Names", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAY16 (&clnt_conntyp); + goto out; + } + + /* Read authentication data */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_authdata)) { + g_warning ("%s: Could not read Authentication Data", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAY16 (&clnt_conntyp); + XdmcpDisposeARRAY8 (&clnt_authname); + goto out; + } + + /* Read and select from supported authorization list */ + if G_UNLIKELY (! XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_authorization_names)) { + g_warning ("%s: Could not read Authorization List", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAY8 (&clnt_authdata); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAY16 (&clnt_conntyp); + XdmcpDisposeARRAY8 (&clnt_authname); + goto out; + } + + /* libXdmcp doesn't terminate strings properly so we cheat and use strncmp () */ + for (i = 0 ; i < clnt_authorization_names.length ; i++) { + if (clnt_authorization_names.data[i].length == 18 && + strncmp ((char *) clnt_authorization_names.data[i].data, "MIT-MAGIC-COOKIE-1", 18) == 0) { + mitauth = TRUE; + } + } + + /* Manufacturer ID */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_manufacturer)) { + g_warning ("%s: Could not read Manufacturer ID", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAY8 (&clnt_authname); + XdmcpDisposeARRAY8 (&clnt_authdata); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAYofARRAY8 (&clnt_authorization_names); + XdmcpDisposeARRAY16 (&clnt_conntyp); + goto out; + } + + /* Crude checksumming */ + explen = 2; /* Display Number */ + explen += 1 + 2 * clnt_conntyp.length; /* Connection Type */ + explen += 1; /* Connection Address */ + for (i = 0 ; i < clnt_addr.length ; i++) { + explen += 2 + clnt_addr.data[i].length; + } + explen += 2 + clnt_authname.length; /* Authentication Name */ + explen += 2 + clnt_authdata.length; /* Authentication Data */ + explen += 1; /* Authorization Names */ + for (i = 0 ; i < clnt_authorization_names.length ; i++) { + explen += 2 + clnt_authorization_names.data[i].length; + } + + explen += 2 + clnt_manufacturer.length; + + if G_UNLIKELY (explen != len) { + g_warning ("%s: Failed checksum from %s", + "gdm_xdmcp_handle_request", + hostname ? hostname : "(null)"); + + XdmcpDisposeARRAY8 (&clnt_authname); + XdmcpDisposeARRAY8 (&clnt_authdata); + XdmcpDisposeARRAY8 (&clnt_manufacturer); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAYofARRAY8 (&clnt_authorization_names); + XdmcpDisposeARRAY16 (&clnt_conntyp); + goto out; + } + + { + char *s = g_strndup ((char *) clnt_manufacturer.data, clnt_manufacturer.length); + g_debug ("GdmXdmcpDisplayFactory: xdmcp_pending=%d, MaxPending=%d, xdmcp_sessions=%d, MaxSessions=%d, ManufacturerID=%s", + factory->num_pending_sessions, + factory->max_pending_displays, + factory->num_sessions, + factory->max_displays, + s != NULL ? s : ""); + g_free (s); + } + + /* Check if ok to manage display */ + if (mitauth && + factory->num_sessions < factory->max_displays && + (gdm_address_is_local (address) || + gdm_xdmcp_num_displays_from_host (factory, address) < factory->max_displays_per_host)) { + entered = TRUE; + } + + if (entered) { + + /* Check if we are already talking to this host */ + display_dispose_check (factory, hostname, clnt_dspnum); + + if (factory->num_pending_sessions >= factory->max_pending_displays) { + g_debug ("GdmXdmcpDisplayFactory: maximum pending"); + /* Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii */ + gdm_xdmcp_send_decline (factory, address, "Maximum pending servers"); + } else { + GdmDisplay *display; + + display = gdm_xdmcp_display_create (factory, + hostname, + address, + clnt_dspnum); + + if (display != NULL) { + ARRAY8 authentication_name; + ARRAY8 authentication_data; + ARRAY8 authorization_name; + ARRAY8 authorization_data; + gint32 session_number; + const char *x11_cookie; + gsize x11_cookie_size; + char *name; + + x11_cookie = NULL; + x11_cookie_size = 0; + gdm_display_get_x11_cookie (display, &x11_cookie, &x11_cookie_size, NULL); + + name = NULL; + gdm_display_get_x11_display_name (display, &name, NULL); + + g_debug ("GdmXdmcpDisplayFactory: Sending authorization key for display %s", name ? name : "(null)"); + g_free (name); + + g_debug ("GdmXdmcpDisplayFactory: cookie len %d", (int) x11_cookie_size); + + session_number = gdm_xdmcp_display_get_session_number (GDM_XDMCP_DISPLAY (display)); + + /* the send accept will fail if cookie is null */ + g_assert (x11_cookie != NULL); + + authentication_name.data = NULL; + authentication_name.length = 0; + authentication_data.data = NULL; + authentication_data.length = 0; + + authorization_name.data = (CARD8 *) "MIT-MAGIC-COOKIE-1"; + authorization_name.length = strlen ((char *) authorization_name.data); + + authorization_data.data = (CARD8 *) x11_cookie; + authorization_data.length = x11_cookie_size; + + /* the addrs are NOT copied */ + gdm_xdmcp_send_accept (factory, + address, + session_number, + &authentication_name, + &authentication_data, + &authorization_name, + &authorization_data); + } + } + } else { + /* Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii */ + if ( ! mitauth) { + gdm_xdmcp_send_decline (factory, + address, + "Only MIT-MAGIC-COOKIE-1 supported"); + } else if (factory->num_sessions >= factory->max_displays) { + g_warning ("Maximum number of open XDMCP sessions reached"); + gdm_xdmcp_send_decline (factory, + address, + "Maximum number of open sessions reached"); + } else { + g_debug ("GdmXdmcpDisplayFactory: Maximum number of open XDMCP sessions from host %s reached", + hostname ? hostname : "(null)"); + gdm_xdmcp_send_decline (factory, + address, + "Maximum number of open sessions from your host reached"); + } + } + + XdmcpDisposeARRAY8 (&clnt_authname); + XdmcpDisposeARRAY8 (&clnt_authdata); + XdmcpDisposeARRAY8 (&clnt_manufacturer); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAYofARRAY8 (&clnt_authorization_names); + XdmcpDisposeARRAY16 (&clnt_conntyp); + out: + g_free (hostname); +} + +static gboolean +lookup_by_session_id (const char *id, + GdmDisplay *display, + gpointer data) +{ + CARD32 sessid; + CARD32 session_id; + + sessid = GPOINTER_TO_INT (data); + + if (! GDM_IS_XDMCP_DISPLAY (display)) { + return FALSE; + } + + session_id = gdm_xdmcp_display_get_session_number (GDM_XDMCP_DISPLAY (display)); + + if (session_id == sessid) { + return TRUE; + } + + return FALSE; +} + +static GdmDisplay * +gdm_xdmcp_display_lookup (GdmXdmcpDisplayFactory *factory, + CARD32 sessid) +{ + GdmDisplay *display; + GdmDisplayStore *store; + + if (sessid == 0) { + return NULL; + } + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + display = gdm_display_store_find (store, + (GdmDisplayStoreFunc)lookup_by_session_id, + GINT_TO_POINTER (sessid)); + + return display; +} + +static void +gdm_xdmcp_send_failed (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD32 sessid) +{ + XdmcpHeader header; + ARRAY8 status; + + g_debug ("GdmXdmcpDisplayFactory: Sending FAILED to %ld", (long)sessid); + + /* + * Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii + */ + status.data = (CARD8 *) "Failed to start session"; + status.length = strlen ((char *) status.data); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) FAILED; + header.length = 6 + status.length; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteCARD32 (&factory->buf, sessid); + XdmcpWriteARRAY8 (&factory->buf, &status); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); +} + +static void +gdm_xdmcp_send_refuse (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD32 sessid) +{ + XdmcpHeader header; + ForwardQuery *fq; + + g_debug ("GdmXdmcpDisplayFactory: Sending REFUSE to %ld", + (long)sessid); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) REFUSE; + header.length = 4; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteCARD32 (&factory->buf, sessid); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + /* + * This was from a forwarded query quite apparently so + * send MANAGED_FORWARD + */ + fq = forward_query_lookup (factory, address); + if (fq != NULL) { + gdm_xdmcp_send_managed_forward (factory, fq->from_address, address); + forward_query_destroy (factory, fq); + } +} + +static void +gdm_xdmcp_handle_manage (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + CARD32 clnt_sessid; + CARD16 clnt_dspnum; + ARRAY8 clnt_dspclass; + GdmDisplay *display; + ForwardQuery *fq; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got MANAGE from %s", + host ? host : "(null)"); + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("%s: Got Manage from banned host %s", + "gdm_xdmcp_handle_manage", + host ? host : "(null)"); + g_free (host); + return; + } + + /* SessionID */ + if G_UNLIKELY (! XdmcpReadCARD32 (&factory->buf, &clnt_sessid)) { + g_warning ("%s: Could not read Session ID", + "gdm_xdmcp_handle_manage"); + goto out; + } + + /* Remote display number */ + if G_UNLIKELY (! XdmcpReadCARD16 (&factory->buf, &clnt_dspnum)) { + g_warning ("%s: Could not read Display Number", + "gdm_xdmcp_handle_manage"); + goto out; + } + + /* Display Class */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_dspclass)) { + g_warning ("%s: Could not read Display Class", + "gdm_xdmcp_handle_manage"); + goto out; + } + + { + char *s = g_strndup ((char *) clnt_dspclass.data, clnt_dspclass.length); + g_debug ("GdmXdmcpDisplayFactory: Got display=%d, SessionID=%ld Class=%s from %s", + (int)clnt_dspnum, + (long)clnt_sessid, + s != NULL ? s : "", + host); + + g_free (s); + } + + display = gdm_xdmcp_display_lookup (factory, clnt_sessid); + if (display != NULL && + gdm_display_get_status (display) == GDM_DISPLAY_PREPARED) { + char *name; + + name = NULL; + gdm_display_get_x11_display_name (display, &name, NULL); + g_debug ("GdmXdmcpDisplayFactory: Looked up %s", + name ? name : "(null)"); + g_free (name); + + if (factory->honor_indirect) { + IndirectClient *ic; + + ic = indirect_client_lookup (factory, address); + + /* This was an indirect thingie and nothing was yet chosen, + * use a chooser */ + if (ic != NULL && ic->chosen_address == NULL) { + g_debug ("GdmXdmcpDisplayFactory: use chooser"); + /*d->use_chooser = TRUE; + d->indirect_id = ic->id;*/ + } else { + /*d->indirect_id = 0; + d->use_chooser = FALSE;*/ + if (ic != NULL) { + indirect_client_destroy (factory, ic); + } + } + } else { + + } + + /* this was from a forwarded query quite apparently so + * send MANAGED_FORWARD */ + fq = forward_query_lookup (factory, address); + if (fq != NULL) { + gdm_xdmcp_send_managed_forward (factory, fq->from_address, address); + forward_query_destroy (factory, fq); + } + + factory->num_sessions++; + factory->num_pending_sessions--; + + /* Start greeter/session */ + if (! gdm_display_manage (display)) { + gdm_xdmcp_send_failed (factory, address, clnt_sessid); + g_debug ("GdmXdmcpDisplayFactory: Failed to manage display"); + } + } else if (display != NULL && + gdm_display_get_status (display) == GDM_DISPLAY_MANAGED) { + g_debug ("GdmXdmcpDisplayFactory: Session ID %ld already managed", + (long)clnt_sessid); + } else { + g_warning ("GdmXdmcpDisplayFactory: Failed to look up session ID %ld", + (long)clnt_sessid); + gdm_xdmcp_send_refuse (factory, address, clnt_sessid); + } + + out: + XdmcpDisposeARRAY8 (&clnt_dspclass); + g_free (host); +} + +static void +gdm_xdmcp_handle_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + ARRAY8 clnt_address; + char *host; + GdmAddress *disp_address; + IndirectClient *ic; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got MANAGED_FORWARD from %s", + host ? host : "(null)"); + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("GdmXdmcpDisplayFactory: Got MANAGED_FORWARD from banned host %s", + host ? host : "(null)"); + g_free (host); + return; + } + g_free (host); + + /* Hostname */ + if G_UNLIKELY ( ! XdmcpReadARRAY8 (&factory->buf, &clnt_address)) { + g_warning ("%s: Could not read address", + "gdm_xdmcp_handle_managed_forward"); + return; + } + + disp_address = NULL; + if (! create_address_from_request (&clnt_address, NULL, gdm_address_get_family_type (address), &disp_address)) { + g_warning ("Unable to parse address for request"); + XdmcpDisposeARRAY8 (&clnt_address); + return; + } + + ic = indirect_client_lookup_by_chosen (factory, address, disp_address); + if (ic != NULL) { + indirect_client_destroy (factory, ic); + } + + /* Note: we send GOT even on not found, just in case our previous + * didn't get through and this was a second managed forward */ + gdm_xdmcp_send_got_managed_forward (factory, address, disp_address); + + gdm_address_free (disp_address); + + XdmcpDisposeARRAY8 (&clnt_address); +} + +static void +gdm_xdmcp_handle_got_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + GdmAddress *disp_address; + ARRAY8 clnt_address; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got MANAGED_FORWARD from %s", + host ? host : "(null)"); + + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("%s: Got GOT_MANAGED_FORWARD from banned host %s", + "gdm_xdmcp_handle_request", host ? host : "(null)"); + g_free (host); + return; + } + g_free (host); + + /* Hostname */ + if G_UNLIKELY ( ! XdmcpReadARRAY8 (&factory->buf, &clnt_address)) { + g_warning ("%s: Could not read address", + "gdm_xdmcp_handle_got_managed_forward"); + return; + } + + if (! create_address_from_request (&clnt_address, NULL, gdm_address_get_family_type (address), &disp_address)) { + g_warning ("%s: Could not read address", + "gdm_xdmcp_handle_got_managed_forward"); + XdmcpDisposeARRAY8 (&clnt_address); + return; + } + + gdm_xdmcp_whack_queued_managed_forwards (factory, address, disp_address); + + gdm_address_free (disp_address); + + XdmcpDisposeARRAY8 (&clnt_address); +} + +static void +gdm_xdmcp_send_alive (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD16 dspnum, + CARD32 sessid) +{ + XdmcpHeader header; + GdmDisplay *display; + int send_running = 0; + CARD32 send_sessid = 0; + + display = gdm_xdmcp_display_lookup (factory, sessid); + if (display == NULL) { + display = gdm_xdmcp_display_lookup_by_host (factory, address, dspnum); + } + + if (display != NULL) { + int status; + + send_sessid = gdm_xdmcp_display_get_session_number (GDM_XDMCP_DISPLAY (display)); + status = gdm_display_get_status (display); + + if (status == GDM_DISPLAY_MANAGED) { + send_running = 1; + } + } + + g_debug ("GdmXdmcpDisplayFactory: Sending ALIVE to %ld (running %d, sessid %ld)", + (long)sessid, + send_running, + (long)send_sessid); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) ALIVE; + header.length = 5; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteCARD8 (&factory->buf, send_running); + XdmcpWriteCARD32 (&factory->buf, send_sessid); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); +} + +static void +gdm_xdmcp_handle_keepalive (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + CARD16 clnt_dspnum; + CARD32 clnt_sessid; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got KEEPALIVE from %s", + host ? host : "(null)"); + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("%s: Got KEEPALIVE from banned host %s", + "gdm_xdmcp_handle_keepalive", + host ? host : "(null)"); + g_free (host); + return; + } + g_free (host); + + /* Remote display number */ + if G_UNLIKELY (! XdmcpReadCARD16 (&factory->buf, &clnt_dspnum)) { + g_warning ("%s: Could not read Display Number", + "gdm_xdmcp_handle_keepalive"); + return; + } + + /* SessionID */ + if G_UNLIKELY (! XdmcpReadCARD32 (&factory->buf, &clnt_sessid)) { + g_warning ("%s: Could not read Session ID", + "gdm_xdmcp_handle_keepalive"); + return; + } + + gdm_xdmcp_send_alive (factory, address, clnt_dspnum, clnt_sessid); +} + +static const char * +opcode_string (int opcode) +{ + static const char * const opcode_names[] = { + NULL, + "BROADCAST_QUERY", + "QUERY", + "INDIRECT_QUERY", + "FORWARD_QUERY", + "WILLING", + "UNWILLING", + "REQUEST", + "ACCEPT", + "DECLINE", + "MANAGE", + "REFUSE", + "FAILED", + "KEEPALIVE", + "ALIVE" + }; + static const char * const gdm_opcode_names[] = { + "MANAGED_FORWARD", + "GOT_MANAGED_FORWARD" + }; + + + if (opcode < G_N_ELEMENTS (opcode_names)) { + return opcode_names [opcode]; + } else if (opcode >= GDM_XDMCP_FIRST_OPCODE && + opcode < GDM_XDMCP_LAST_OPCODE) { + return gdm_opcode_names [opcode - GDM_XDMCP_FIRST_OPCODE]; + } else { + return "UNKNOWN"; + } +} + +static gboolean +decode_packet (GIOChannel *source, + GIOCondition cond, + GdmXdmcpDisplayFactory *factory) +{ + struct sockaddr_storage clnt_ss; + GdmAddress *address; + gint ss_len; + XdmcpHeader header; + char *host; + char *port; + int res; + + g_debug ("GdmXdmcpDisplayFactory: decode_packet: GIOCondition %d", (int)cond); + + if ( ! (cond & G_IO_IN)) { + return TRUE; + } + + ss_len = (int) sizeof (clnt_ss); + + res = XdmcpFill (factory->socket_fd, &factory->buf, (XdmcpNetaddr)&clnt_ss, &ss_len); + if G_UNLIKELY (! res) { + g_debug ("GdmXdmcpDisplayFactory: Could not create XDMCP buffer!"); + return TRUE; + } + + res = XdmcpReadHeader (&factory->buf, &header); + if G_UNLIKELY (! res) { + g_warning ("GdmXdmcpDisplayFactory: Could not read XDMCP header!"); + return TRUE; + } + + if G_UNLIKELY (header.version != XDM_PROTOCOL_VERSION && + header.version != GDM_XDMCP_PROTOCOL_VERSION) { + g_warning ("XDMCP: Incorrect XDMCP version!"); + return TRUE; + } + + address = gdm_address_new_from_sockaddr ((struct sockaddr *) &clnt_ss, ss_len); + if (address == NULL) { + g_warning ("XDMCP: Unable to parse address"); + return TRUE; + } + + gdm_address_debug (address); + + host = NULL; + port = NULL; + gdm_address_get_numeric_info (address, &host, &port); + + g_debug ("GdmXdmcpDisplayFactory: Received opcode %s from client %s : %s", + opcode_string (header.opcode), + host ? host : "(null)", + port ? port : "(null)"); + + switch (header.opcode) { + case BROADCAST_QUERY: + gdm_xdmcp_handle_broadcast_query (factory, address, header.length); + break; + + case QUERY: + gdm_xdmcp_handle_query (factory, address, header.length); + break; + + case INDIRECT_QUERY: + gdm_xdmcp_handle_indirect_query (factory, address, header.length); + break; + + case FORWARD_QUERY: + gdm_xdmcp_handle_forward_query (factory, address, header.length); + break; + + case REQUEST: + gdm_xdmcp_handle_request (factory, address, header.length); + break; + + case MANAGE: + gdm_xdmcp_handle_manage (factory, address, header.length); + break; + + case KEEPALIVE: + gdm_xdmcp_handle_keepalive (factory, address, header.length); + break; + + case GDM_XDMCP_MANAGED_FORWARD: + gdm_xdmcp_handle_managed_forward (factory, address, header.length); + break; + + case GDM_XDMCP_GOT_MANAGED_FORWARD: + gdm_xdmcp_handle_got_managed_forward (factory, address, header.length); + break; + + default: + g_debug ("GdmXdmcpDisplayFactory: Unknown opcode from client %s : %s", + host ? host : "(null)", + port ? port : "(null)"); + + break; + } + + g_free (host); + g_free (port); + + gdm_address_free (address); + + return TRUE; +} + +static gboolean +gdm_xdmcp_display_factory_start (GdmDisplayFactory *base_factory) +{ + gboolean ret; + GIOChannel *ioc; + GdmXdmcpDisplayFactory *factory = GDM_XDMCP_DISPLAY_FACTORY (base_factory); + gboolean res; + + g_return_val_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory), FALSE); + g_return_val_if_fail (factory->socket_fd == -1, FALSE); + + /* read configuration */ + res = gdm_settings_direct_get_uint (GDM_KEY_UDP_PORT, + &(factory->port)); + res = res && gdm_settings_direct_get_boolean (GDM_KEY_MULTICAST, + &(factory->use_multicast)); + res = res && gdm_settings_direct_get_string (GDM_KEY_MULTICAST_ADDR, + &(factory->multicast_address)); + res = res && gdm_settings_direct_get_boolean (GDM_KEY_INDIRECT, + &(factory->honor_indirect)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_DISPLAYS_PER_HOST, + &(factory->max_displays_per_host)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_MAX_SESSIONS, + &(factory->max_displays)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_MAX_PENDING, + &(factory->max_pending_displays)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_MAX_WAIT, + &(factory->max_wait)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_MAX_WAIT_INDIRECT, + &(factory->max_wait_indirect)); + res = res && gdm_settings_direct_get_string (GDM_KEY_WILLING, + &(factory->willing_script)); + + if (! res) { + return res; + } + + ret = open_port (factory); + if (! ret) { + return ret; + } + + g_debug ("GdmXdmcpDisplayFactory: Starting to listen on XDMCP port"); + + ioc = g_io_channel_unix_new (factory->socket_fd); + + g_io_channel_set_encoding (ioc, NULL, NULL); + g_io_channel_set_buffered (ioc, FALSE); + + factory->socket_watch_id = g_io_add_watch_full (ioc, + G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc)decode_packet, + factory, + NULL); + g_io_channel_unref (ioc); + + return ret; +} + +static gboolean +gdm_xdmcp_display_factory_stop (GdmDisplayFactory *base_factory) +{ + GdmXdmcpDisplayFactory *factory = GDM_XDMCP_DISPLAY_FACTORY (base_factory); + + g_return_val_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory), FALSE); + g_return_val_if_fail (factory->socket_fd != -1, FALSE); + + if (factory->socket_watch_id > 0) { + g_source_remove (factory->socket_watch_id); + factory->socket_watch_id = 0; + } + + if (factory->socket_fd > 0) { + VE_IGNORE_EINTR (close (factory->socket_fd)); + factory->socket_fd = -1; + } + + return TRUE; +} + +void +gdm_xdmcp_display_factory_set_port (GdmXdmcpDisplayFactory *factory, + guint port) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->port = port; +} + +static void +gdm_xdmcp_display_factory_set_use_multicast (GdmXdmcpDisplayFactory *factory, + gboolean use_multicast) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->use_multicast = use_multicast; +} + +static void +gdm_xdmcp_display_factory_set_multicast_address (GdmXdmcpDisplayFactory *factory, + const char *address) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + g_free (factory->multicast_address); + factory->multicast_address = g_strdup (address); +} + +static void +gdm_xdmcp_display_factory_set_honor_indirect (GdmXdmcpDisplayFactory *factory, + gboolean honor_indirect) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->honor_indirect = honor_indirect; +} + +static void +gdm_xdmcp_display_factory_set_max_displays_per_host (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_displays_per_host = num; +} + +static void +gdm_xdmcp_display_factory_set_max_displays (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_displays = num; +} + +static void +gdm_xdmcp_display_factory_set_max_pending_displays (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_pending_displays = num; +} + +static void +gdm_xdmcp_display_factory_set_max_wait (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_wait = num; +} + +static void +gdm_xdmcp_display_factory_set_max_wait_indirect (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_wait_indirect = num; +} + +static void +gdm_xdmcp_display_factory_set_willing_script (GdmXdmcpDisplayFactory *factory, + const char *script) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + g_free (factory->willing_script); + factory->willing_script = g_strdup (script); +} + +static void +gdm_xdmcp_display_factory_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmXdmcpDisplayFactory *self; + + self = GDM_XDMCP_DISPLAY_FACTORY (object); + + switch (prop_id) { + case PROP_PORT: + gdm_xdmcp_display_factory_set_port (self, g_value_get_uint (value)); + break; + case PROP_USE_MULTICAST: + gdm_xdmcp_display_factory_set_use_multicast (self, g_value_get_boolean (value)); + break; + case PROP_MULTICAST_ADDRESS: + gdm_xdmcp_display_factory_set_multicast_address (self, g_value_get_string (value)); + break; + case PROP_HONOR_INDIRECT: + gdm_xdmcp_display_factory_set_honor_indirect (self, g_value_get_boolean (value)); + break; + case PROP_MAX_DISPLAYS_PER_HOST: + gdm_xdmcp_display_factory_set_max_displays_per_host (self, g_value_get_uint (value)); + break; + case PROP_MAX_DISPLAYS: + gdm_xdmcp_display_factory_set_max_displays (self, g_value_get_uint (value)); + break; + case PROP_MAX_PENDING_DISPLAYS: + gdm_xdmcp_display_factory_set_max_pending_displays (self, g_value_get_uint (value)); + break; + case PROP_MAX_WAIT: + gdm_xdmcp_display_factory_set_max_wait (self, g_value_get_uint (value)); + break; + case PROP_MAX_WAIT_INDIRECT: + gdm_xdmcp_display_factory_set_max_wait_indirect (self, g_value_get_uint (value)); + break; + case PROP_WILLING_SCRIPT: + gdm_xdmcp_display_factory_set_willing_script (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_xdmcp_display_factory_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmXdmcpDisplayFactory *self; + + self = GDM_XDMCP_DISPLAY_FACTORY (object); + + switch (prop_id) { + case PROP_PORT: + g_value_set_uint (value, self->port); + break; + case PROP_USE_MULTICAST: + g_value_set_boolean (value, self->use_multicast); + break; + case PROP_MULTICAST_ADDRESS: + g_value_set_string (value, self->multicast_address); + break; + case PROP_HONOR_INDIRECT: + g_value_set_boolean (value, self->honor_indirect); + break; + case PROP_MAX_DISPLAYS_PER_HOST: + g_value_set_uint (value, self->max_displays_per_host); + break; + case PROP_MAX_DISPLAYS: + g_value_set_uint (value, self->max_displays); + break; + case PROP_MAX_PENDING_DISPLAYS: + g_value_set_uint (value, self->max_pending_displays); + break; + case PROP_MAX_WAIT: + g_value_set_uint (value, self->max_wait); + break; + case PROP_MAX_WAIT_INDIRECT: + g_value_set_uint (value, self->max_wait_indirect); + break; + case PROP_WILLING_SCRIPT: + g_value_set_string (value, self->willing_script); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_xdmcp_display_factory_class_init (GdmXdmcpDisplayFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdmDisplayFactoryClass *factory_class = GDM_DISPLAY_FACTORY_CLASS (klass); + + object_class->get_property = gdm_xdmcp_display_factory_get_property; + object_class->set_property = gdm_xdmcp_display_factory_set_property; + object_class->finalize = gdm_xdmcp_display_factory_finalize; + + factory_class->start = gdm_xdmcp_display_factory_start; + factory_class->stop = gdm_xdmcp_display_factory_stop; + + g_object_class_install_property (object_class, + PROP_PORT, + g_param_spec_uint ("port", + "UDP port", + "UDP port", + 0, + G_MAXINT, + DEFAULT_PORT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_USE_MULTICAST, + g_param_spec_boolean ("use-multicast", + NULL, + NULL, + DEFAULT_USE_MULTICAST, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MULTICAST_ADDRESS, + g_param_spec_string ("multicast-address", + "multicast-address", + "multicast-address", + DEFAULT_MULTICAST_ADDRESS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_HONOR_INDIRECT, + g_param_spec_boolean ("honor-indirect", + NULL, + NULL, + DEFAULT_HONOR_INDIRECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_WILLING_SCRIPT, + g_param_spec_string ("willing-script", + "willing-script", + "willing-script", + DEFAULT_WILLING_SCRIPT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_DISPLAYS_PER_HOST, + g_param_spec_uint ("max-displays-per-host", + "max-displays-per-host", + "max-displays-per-host", + 0, + G_MAXINT, + DEFAULT_MAX_DISPLAYS_PER_HOST, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_DISPLAYS, + g_param_spec_uint ("max-displays", + "max-displays", + "max-displays", + 0, + G_MAXINT, + DEFAULT_MAX_DISPLAYS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_PENDING_DISPLAYS, + g_param_spec_uint ("max-pending-displays", + "max-pending-displays", + "max-pending-displays", + 0, + G_MAXINT, + DEFAULT_MAX_PENDING_DISPLAYS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_WAIT, + g_param_spec_uint ("max-wait", + "max-wait", + "max-wait", + 0, + G_MAXINT, + DEFAULT_MAX_WAIT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_WAIT_INDIRECT, + g_param_spec_uint ("max-wait-indirect", + "max-wait-indirect", + "max-wait-indirect", + 0, + G_MAXINT, + DEFAULT_MAX_WAIT_INDIRECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); +} + +static void +gdm_xdmcp_display_factory_init (GdmXdmcpDisplayFactory *factory) +{ + char hostbuf[1024]; + struct utsname name; + + factory->socket_fd = -1; + + factory->session_serial = g_random_int (); + + /* Fetch and store local hostname in XDMCP friendly format */ + hostbuf[1023] = '\0'; + if G_UNLIKELY (gethostname (hostbuf, 1023) != 0) { + g_warning ("Could not get server hostname: %s!", g_strerror (errno)); + strcpy (hostbuf, "localhost.localdomain"); + } + + uname (&name); + factory->sysid = g_strconcat (name.sysname, + " ", + name.release, + NULL); + + factory->hostname = g_strdup (hostbuf); + + factory->servhost.data = (CARD8 *) g_strdup (hostbuf); + factory->servhost.length = strlen ((char *) factory->servhost.data); +} + +static void +gdm_xdmcp_display_factory_finalize (GObject *object) +{ + GdmXdmcpDisplayFactory *factory; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (object)); + + factory = GDM_XDMCP_DISPLAY_FACTORY (object); + + g_return_if_fail (factory != NULL); + + if (factory->socket_watch_id > 0) { + g_source_remove (factory->socket_watch_id); + } + + if (factory->socket_fd > 0) { + close (factory->socket_fd); + factory->socket_fd = -1; + } + + g_slist_free (factory->forward_queries); + g_slist_free (factory->managed_forwards); + + g_free (factory->sysid); + g_free (factory->hostname); + g_free (factory->multicast_address); + g_free (factory->willing_script); + + /* FIXME: Free servhost */ + + G_OBJECT_CLASS (gdm_xdmcp_display_factory_parent_class)->finalize (object); +} + +GdmXdmcpDisplayFactory * +gdm_xdmcp_display_factory_new (GdmDisplayStore *store) +{ + if (xdmcp_display_factory_object != NULL) { + g_object_ref (xdmcp_display_factory_object); + } else { + xdmcp_display_factory_object = g_object_new (GDM_TYPE_XDMCP_DISPLAY_FACTORY, + "display-store", store, + NULL); + g_object_add_weak_pointer (xdmcp_display_factory_object, + (gpointer *) &xdmcp_display_factory_object); + } + + return GDM_XDMCP_DISPLAY_FACTORY (xdmcp_display_factory_object); +} diff --git a/daemon/gdm-xdmcp-display-factory.h b/daemon/gdm-xdmcp-display-factory.h new file mode 100644 index 0000000..2a2e821 --- /dev/null +++ b/daemon/gdm-xdmcp-display-factory.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_XDMCP_DISPLAY_FACTORY_H +#define __GDM_XDMCP_DISPLAY_FACTORY_H + +#include <glib-object.h> + +#include "gdm-display-factory.h" +#include "gdm-display-store.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_XDMCP_DISPLAY_FACTORY (gdm_xdmcp_display_factory_get_type ()) +G_DECLARE_FINAL_TYPE (GdmXdmcpDisplayFactory, gdm_xdmcp_display_factory, GDM, XDMCP_DISPLAY_FACTORY, GdmDisplayFactory) + +typedef enum +{ + GDM_XDMCP_DISPLAY_FACTORY_ERROR_GENERAL +} GdmXdmcpDisplayFactoryError; + +#define GDM_XDMCP_DISPLAY_FACTORY_ERROR gdm_xdmcp_display_factory_error_quark () + +GQuark gdm_xdmcp_display_factory_error_quark (void); + +GdmXdmcpDisplayFactory * gdm_xdmcp_display_factory_new (GdmDisplayStore *display_store); + +void gdm_xdmcp_display_factory_set_port (GdmXdmcpDisplayFactory *manager, + guint port); + +G_END_DECLS + +#endif /* __GDM_XDMCP_DISPLAY_FACTORY_H */ diff --git a/daemon/gdm-xdmcp-display.c b/daemon/gdm-xdmcp-display.c new file mode 100644 index 0000000..18e2575 --- /dev/null +++ b/daemon/gdm-xdmcp-display.c @@ -0,0 +1,295 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-display.h" +#include "gdm-launch-environment.h" +#include "gdm-xdmcp-display.h" + +#include "gdm-common.h" +#include "gdm-address.h" + +#include "gdm-settings.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +typedef struct _GdmXdmcpDisplayPrivate +{ + GdmAddress *remote_address; + gint32 session_number; + guint connection_attempts; +} GdmXdmcpDisplayPrivate; + +enum { + PROP_0, + PROP_REMOTE_ADDRESS, + PROP_SESSION_NUMBER, +}; + +#define MAX_CONNECT_ATTEMPTS 10 + +static void gdm_xdmcp_display_class_init (GdmXdmcpDisplayClass *klass); +static void gdm_xdmcp_display_init (GdmXdmcpDisplay *xdmcp_display); + +G_DEFINE_TYPE_WITH_PRIVATE (GdmXdmcpDisplay, gdm_xdmcp_display, GDM_TYPE_DISPLAY) + +gint32 +gdm_xdmcp_display_get_session_number (GdmXdmcpDisplay *display) +{ + GdmXdmcpDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_XDMCP_DISPLAY (display), 0); + + priv = gdm_xdmcp_display_get_instance_private (display); + return priv->session_number; +} + +GdmAddress * +gdm_xdmcp_display_get_remote_address (GdmXdmcpDisplay *display) +{ + GdmXdmcpDisplayPrivate *priv; + + g_return_val_if_fail (GDM_IS_XDMCP_DISPLAY (display), NULL); + + priv = gdm_xdmcp_display_get_instance_private (display); + return priv->remote_address; +} + +static void +_gdm_xdmcp_display_set_remote_address (GdmXdmcpDisplay *display, + GdmAddress *address) +{ + GdmXdmcpDisplayPrivate *priv; + + priv = gdm_xdmcp_display_get_instance_private (display); + if (priv->remote_address != NULL) { + gdm_address_free (priv->remote_address); + } + + g_assert (address != NULL); + + gdm_address_debug (address); + priv->remote_address = gdm_address_copy (address); +} + +static void +gdm_xdmcp_display_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmXdmcpDisplay *self; + GdmXdmcpDisplayPrivate *priv; + + self = GDM_XDMCP_DISPLAY (object); + priv = gdm_xdmcp_display_get_instance_private (self); + + switch (prop_id) { + case PROP_REMOTE_ADDRESS: + _gdm_xdmcp_display_set_remote_address (self, g_value_get_boxed (value)); + break; + case PROP_SESSION_NUMBER: + priv->session_number = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_xdmcp_display_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmXdmcpDisplay *self; + GdmXdmcpDisplayPrivate *priv; + + self = GDM_XDMCP_DISPLAY (object); + priv = gdm_xdmcp_display_get_instance_private (self); + + switch (prop_id) { + case PROP_REMOTE_ADDRESS: + g_value_set_boxed (value, priv->remote_address); + break; + case PROP_SESSION_NUMBER: + g_value_set_int (value, priv->session_number); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gdm_xdmcp_display_prepare (GdmDisplay *display) +{ + GdmXdmcpDisplay *self = GDM_XDMCP_DISPLAY (display); + GdmLaunchEnvironment *launch_environment; + char *display_name; + char *seat_id; + char *hostname; + + launch_environment = NULL; + display_name = NULL; + seat_id = NULL; + hostname = NULL; + + g_object_get (self, + "x11-display-name", &display_name, + "seat-id", &seat_id, + "remote-hostname", &hostname, + "launch-environment", &launch_environment, + NULL); + + if (launch_environment == NULL) { + launch_environment = gdm_create_greeter_launch_environment (display_name, + seat_id, + NULL, + hostname, + FALSE); + g_object_set (self, "launch-environment", launch_environment, NULL); + g_object_unref (launch_environment); + } + + if (!gdm_display_create_authority (display)) { + g_warning ("Unable to set up access control for display %s", + display_name); + return FALSE; + } + + return GDM_DISPLAY_CLASS (gdm_xdmcp_display_parent_class)->prepare (display); +} + +static gboolean +idle_connect_to_display (GdmXdmcpDisplay *self) +{ + GdmXdmcpDisplayPrivate *priv; + gboolean res; + + priv = gdm_xdmcp_display_get_instance_private (self); + priv->connection_attempts++; + + res = gdm_display_connect (GDM_DISPLAY (self)); + if (res) { + g_object_set (G_OBJECT (self), "status", GDM_DISPLAY_MANAGED, NULL); + } else { + if (priv->connection_attempts >= MAX_CONNECT_ATTEMPTS) { + g_warning ("Unable to connect to display after %d tries - bailing out", priv->connection_attempts); + gdm_display_unmanage (GDM_DISPLAY (self)); + return FALSE; + } + return TRUE; + } + + return FALSE; +} + +static void +gdm_xdmcp_display_manage (GdmDisplay *display) +{ + GdmXdmcpDisplay *self = GDM_XDMCP_DISPLAY (display); + + g_timeout_add (500, (GSourceFunc)idle_connect_to_display, self); +} + +static void +gdm_xdmcp_display_class_init (GdmXdmcpDisplayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdmDisplayClass *display_class = GDM_DISPLAY_CLASS (klass); + + object_class->get_property = gdm_xdmcp_display_get_property; + object_class->set_property = gdm_xdmcp_display_set_property; + + display_class->prepare = gdm_xdmcp_display_prepare; + display_class->manage = gdm_xdmcp_display_manage; + + g_object_class_install_property (object_class, + PROP_REMOTE_ADDRESS, + g_param_spec_boxed ("remote-address", + "Remote address", + "Remote address", + GDM_TYPE_ADDRESS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_SESSION_NUMBER, + g_param_spec_int ("session-number", + "session-number", + "session-number", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + +} + +static void +gdm_xdmcp_display_init (GdmXdmcpDisplay *xdmcp_display) +{ + + gboolean allow_remote_autologin; + + allow_remote_autologin = FALSE; + gdm_settings_direct_get_boolean (GDM_KEY_ALLOW_REMOTE_AUTOLOGIN, &allow_remote_autologin); + + g_object_set (G_OBJECT (xdmcp_display), "allow-timed-login", allow_remote_autologin, NULL); +} + +GdmDisplay * +gdm_xdmcp_display_new (const char *hostname, + int number, + GdmAddress *address, + gint32 session_number) +{ + GObject *object; + char *x11_display; + + x11_display = g_strdup_printf ("%s:%d", hostname, number); + object = g_object_new (GDM_TYPE_XDMCP_DISPLAY, + "remote-hostname", hostname, + "x11-display-number", number, + "x11-display-name", x11_display, + "is-local", FALSE, + "remote-address", address, + "session-number", session_number, + NULL); + g_free (x11_display); + + return GDM_DISPLAY (object); +} diff --git a/daemon/gdm-xdmcp-display.h b/daemon/gdm-xdmcp-display.h new file mode 100644 index 0000000..c103eaa --- /dev/null +++ b/daemon/gdm-xdmcp-display.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#ifndef __GDM_XDMCP_DISPLAY_H +#define __GDM_XDMCP_DISPLAY_H + +#include <sys/types.h> +#include <sys/socket.h> +#include <glib-object.h> + +#include "gdm-display.h" +#include "gdm-address.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_XDMCP_DISPLAY (gdm_xdmcp_display_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GdmXdmcpDisplay, gdm_xdmcp_display, GDM, XDMCP_DISPLAY, GdmDisplay) + +struct _GdmXdmcpDisplayClass +{ + GdmDisplayClass parent_class; +}; + +gint32 gdm_xdmcp_display_get_session_number (GdmXdmcpDisplay *display); +GdmAddress * gdm_xdmcp_display_get_remote_address (GdmXdmcpDisplay *display); + +GdmDisplay * gdm_xdmcp_display_new (const char *hostname, + int number, + GdmAddress *address, + gint32 session_number); + +G_END_DECLS + +#endif /* __GDM_XDMCP_DISPLAY_H */ diff --git a/daemon/main.c b/daemon/main.c new file mode 100644 index 0000000..1b893fe --- /dev/null +++ b/daemon/main.c @@ -0,0 +1,471 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include <sys/wait.h> +#include <locale.h> +#include <signal.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> +#include <gio/gio.h> + +#include "gdm-manager.h" +#include "gdm-log.h" +#include "gdm-common.h" + +#include "gdm-settings.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +#define GDM_DBUS_NAME "org.gnome.DisplayManager" + +static GDBusConnection *get_system_bus (void); +static gboolean bus_reconnect (void); + +extern char **environ; + +static GdmManager *manager = NULL; +static int name_id = -1; +static GdmSettings *settings = NULL; +static uid_t gdm_uid = -1; +static gid_t gdm_gid = -1; + +static gboolean +timed_exit_cb (GMainLoop *loop) +{ + g_main_loop_quit (loop); + return FALSE; +} + +static void +bus_connection_closed (void) +{ + g_debug ("Disconnected from D-Bus"); + + if (manager == NULL) { + /* probably shutting down or something */ + return; + } + + g_clear_object (&manager); + + g_timeout_add_seconds (3, (GSourceFunc)bus_reconnect, NULL); +} + +static GDBusConnection * +get_system_bus (void) +{ + GError *error; + GDBusConnection *bus; + + error = NULL; + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (bus == NULL) { + g_warning ("Couldn't connect to system bus: %s", + error->message); + g_error_free (error); + goto out; + } + + g_signal_connect (bus, "closed", + G_CALLBACK (bus_connection_closed), NULL); + g_dbus_connection_set_exit_on_close (bus, FALSE); + + out: + return bus; +} + +static void +delete_pid (void) +{ + g_unlink (GDM_PID_FILE); +} + +static void +write_pid (void) +{ + int pf; + ssize_t written; + char pid[9]; + + errno = 0; + pf = open (GDM_PID_FILE, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644); + if (pf < 0) { + g_warning (_("Cannot write PID file %s: possibly out of disk space: %s"), + GDM_PID_FILE, + g_strerror (errno)); + + return; + } + + snprintf (pid, sizeof (pid), "%lu\n", (long unsigned) getpid ()); + errno = 0; + written = write (pf, pid, strlen (pid)); + close (pf); + + if (written < 0) { + g_warning (_("Cannot write PID file %s: possibly out of disk space: %s"), + GDM_PID_FILE, + g_strerror (errno)); + return; + } + + atexit (delete_pid); +} + +static gboolean +ensure_dir_with_perms (const char *path, + uid_t uid, + gid_t gid, + mode_t mode, + GError **error) +{ + gboolean ret = FALSE; + + if (g_mkdir_with_parents (path, 0755) == -1) { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno), g_strerror (errno)); + goto out; + } + if (g_chmod (path, mode) == -1) { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno), g_strerror (errno)); + goto out; + } + if (chown (path, uid, gid) == -1) { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno), g_strerror (errno)); + goto out; + } + + ret = TRUE; + out: + return ret; +} + +static void +gdm_daemon_ensure_dirs (uid_t uid, + gid_t gid) +{ + GError *error = NULL; + + /* Set up /var/run/gdm */ + if (!ensure_dir_with_perms (GDM_RAN_ONCE_MARKER_DIR, 0, gid, 0711, &error)) { + gdm_fail (_("Failed to create ran once marker dir %s: %s"), + GDM_RAN_ONCE_MARKER_DIR, error->message); + } + + /* Set up /var/log/gdm */ + if (!ensure_dir_with_perms (LOGDIR, 0, gid, 0711, &error)) { + gdm_fail (_("Failed to create LogDir %s: %s"), + LOGDIR, error->message); + } +} + +static void +gdm_daemon_lookup_user (uid_t *uidp, + gid_t *gidp) +{ + char *username; + char *groupname; + uid_t uid; + gid_t gid; + struct passwd *pwent; + struct group *grent; + + username = NULL; + groupname = NULL; + uid = 0; + gid = 0; + + gdm_settings_direct_get_string (GDM_KEY_USER, &username); + gdm_settings_direct_get_string (GDM_KEY_GROUP, &groupname); + + if (username == NULL || groupname == NULL) { + return; + } + + g_debug ("Changing user:group to %s:%s", username, groupname); + + /* Lookup user and groupid for the GDM user */ + gdm_get_pwent_for_name (username, &pwent); + + /* Set uid and gid */ + if G_UNLIKELY (pwent == NULL) { + gdm_fail (_("Can’t find the GDM user “%s”. Aborting!"), username); + } else { + uid = pwent->pw_uid; + } + + if G_UNLIKELY (uid == 0) { + gdm_fail (_("The GDM user should not be root. Aborting!")); + } + + grent = getgrnam (groupname); + + if G_UNLIKELY (grent == NULL) { + gdm_fail (_("Can’t find the GDM group “%s”. Aborting!"), groupname); + } else { + gid = grent->gr_gid; + } + + if G_UNLIKELY (gid == 0) { + gdm_fail (_("The GDM group should not be root. Aborting!")); + } + + if (uidp != NULL) { + *uidp = uid; + } + + if (gidp != NULL) { + *gidp = gid; + } + + g_free (username); + g_free (groupname); +} + +static gboolean +on_shutdown_signal_cb (gpointer user_data) +{ + GMainLoop *mainloop = user_data; + + g_main_loop_quit (mainloop); + + return FALSE; +} + +static gboolean +on_sighup_cb (gpointer user_data) +{ + g_debug ("Got HUP signal"); + /* Reread config stuff like system config files, VPN service + * files, etc + */ + g_object_unref (settings); + settings = gdm_settings_new (); + if (settings != NULL) { + if (! gdm_settings_direct_init (settings, DATADIR "/gdm/gdm.schemas", "/")) { + g_warning ("Unable to initialize settings"); + } + } + + return TRUE; +} + +static gboolean +is_debug_set (void) +{ + gboolean debug; + gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug); + return debug; +} + +/* SIGUSR1 is used by the X server to tell us that we're ready, so + * block it. We'll unblock it in the worker thread in gdm-server.c + */ +static void +block_sigusr1 (void) +{ + sigset_t mask; + + sigemptyset (&mask); + sigaddset (&mask, SIGUSR1); + sigprocmask (SIG_BLOCK, &mask, NULL); +} + +int +main (int argc, + char **argv) +{ + GMainLoop *main_loop; + GOptionContext *context; + GError *error = NULL; + gboolean res; + static gboolean do_timed_exit = FALSE; + static gboolean print_version = FALSE; + static gboolean fatal_warnings = FALSE; + static GOptionEntry entries [] = { + { "fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &fatal_warnings, N_("Make all warnings fatal"), NULL }, + { "timed-exit", 0, 0, G_OPTION_ARG_NONE, &do_timed_exit, N_("Exit after a time (for debugging)"), NULL }, + { "version", 0, 0, G_OPTION_ARG_NONE, &print_version, N_("Print GDM version"), NULL }, + + { NULL } + }; + + block_sigusr1 (); + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + context = g_option_context_new (_("GNOME Display Manager")); + g_option_context_add_main_entries (context, entries, NULL); + + error = NULL; + res = g_option_context_parse (context, &argc, &argv, &error); + g_option_context_free (context); + if (! res) { + g_printerr ("Failed to parse options: %s\n", error->message); + g_error_free (error); + return EXIT_FAILURE; + } + + if (print_version) { + g_print ("GDM %s\n", VERSION); + return EXIT_SUCCESS; + } + + /* XDM compliant error message */ + if (getuid () != 0) { + /* make sure the pid file doesn't get wiped */ + g_printerr ("%s\n", _("Only the root user can run GDM")); + return EXIT_FAILURE; + } + + if (fatal_warnings) { + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal (fatal_mask); + } + + gdm_log_init (); + + settings = gdm_settings_new (); + if (! gdm_settings_direct_init (settings, DATADIR "/gdm/gdm.schemas", "/")) { + g_warning ("Unable to initialize settings"); + return EXIT_FAILURE; + } + + gdm_log_set_debug (is_debug_set ()); + + gdm_daemon_lookup_user (&gdm_uid, &gdm_gid); + + gdm_daemon_ensure_dirs (gdm_uid, gdm_gid); + + /* Connect to the bus, own the name and start the manager */ + bus_reconnect (); + + /* pid file */ + delete_pid (); + write_pid (); + + g_chdir ("/"); + + main_loop = g_main_loop_new (NULL, FALSE); + + g_unix_signal_add (SIGTERM, on_shutdown_signal_cb, main_loop); + g_unix_signal_add (SIGINT, on_shutdown_signal_cb, main_loop); + g_unix_signal_add (SIGHUP, on_sighup_cb, NULL); + + if (do_timed_exit) { + g_timeout_add_seconds (30, (GSourceFunc) timed_exit_cb, main_loop); + } + + g_main_loop_run (main_loop); + + g_debug ("GDM finished, cleaning up..."); + + g_clear_object (&manager); + g_clear_object (&settings); + + gdm_settings_direct_shutdown (); + gdm_log_shutdown (); + + g_main_loop_unref (main_loop); + + return EXIT_SUCCESS; +} + +static void +on_name_acquired (GDBusConnection *bus, + const char *name, + gpointer user_data) +{ + gboolean xdmcp_enabled; + gboolean show_local_greeter; + + manager = gdm_manager_new (); + if (manager == NULL) { + g_warning ("Could not construct manager object"); + exit (EXIT_FAILURE); + } + + g_debug ("Successfully connected to D-Bus"); + + show_local_greeter = TRUE; + gdm_settings_direct_get_boolean (GDM_KEY_SHOW_LOCAL_GREETER, &show_local_greeter); + gdm_manager_set_show_local_greeter (manager, show_local_greeter); + + xdmcp_enabled = FALSE; + gdm_settings_direct_get_boolean (GDM_KEY_XDMCP_ENABLE, &xdmcp_enabled); + gdm_manager_set_xdmcp_enabled (manager, xdmcp_enabled); + + gdm_manager_start (manager); +} + +static void +on_name_lost (GDBusConnection *bus, + const char *name, + gpointer user_data) +{ + g_debug ("Lost GDM name on bus"); + + bus_connection_closed (); +} + +static gboolean +bus_reconnect () +{ + GDBusConnection *bus; + gboolean ret; + + ret = TRUE; + + bus = get_system_bus (); + if (bus == NULL) { + goto out; + } + + name_id = g_bus_own_name_on_connection (bus, + GDM_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + ret = FALSE; + out: + return ret; +} diff --git a/daemon/meson.build b/daemon/meson.build new file mode 100644 index 0000000..2e61b64 --- /dev/null +++ b/daemon/meson.build @@ -0,0 +1,213 @@ +# D-Bus interfaces +dbus_gen = gnome.gdbus_codegen('gdm-dbus-glue', + 'org.freedesktop.DBus.xml', + namespace: 'GdmDBus', + interface_prefix: 'org.freedesktop.DBus', + autocleanup: 'all', +) +display_dbus_gen = gnome.gdbus_codegen('gdm-display-glue', + 'gdm-display.xml', + namespace: 'GdmDBus', + interface_prefix: 'org.gnome.DisplayManager', + autocleanup: 'all', +) +local_display_dbus_gen = gnome.gdbus_codegen('gdm-local-display-glue', + 'gdm-local-display.xml', + namespace: 'GdmDBus', + interface_prefix: 'org.gnome.DisplayManager', + autocleanup: 'all', +) +local_display_factory_dbus_gen = gnome.gdbus_codegen('gdm-local-display-factory-glue', + 'gdm-local-display-factory.xml', + namespace: 'GdmDBus', + interface_prefix: 'org.gnome.DisplayManager', + autocleanup: 'all', +) +manager_dbus_gen = gnome.gdbus_codegen('gdm-manager-glue', + 'gdm-manager.xml', + namespace: 'GdmDBus', + interface_prefix: 'org.gnome.DisplayManager', + autocleanup: 'all', +) +session_dbus_gen = gnome.gdbus_codegen('gdm-session-glue', + 'gdm-session.xml', + namespace: 'GdmDBus', + interface_prefix: 'org.gnome.DisplayManager', + autocleanup: 'all', +) +session_worker_dbus_gen = gnome.gdbus_codegen('gdm-session-worker-glue', + 'gdm-session-worker.xml', + namespace: 'GdmDBus', + interface_prefix: 'org.gnome.DisplayManager', + autocleanup: 'all', +) + +gdm_session_enums = gnome.mkenums('gdm-session-enum-types', + h_template: 'gdm-session-enum-types.h.in', + c_template: 'gdm-session-enum-types.c.in', + sources: 'gdm-session.h', +) +gdm_session_worker_enums = gnome.mkenums('gdm-session-worker-enum-types', + h_template: 'gdm-session-worker-enum-types.h.in', + c_template: 'gdm-session-worker-enum-types.c.in', + sources: 'gdm-session-worker.h', +) + +# Daemons deps +gdm_daemon_deps = [ + libgdmcommon_dep, + accountsservice_dep, + gobject_dep, + gio_dep, + gio_unix_dep, + libpam_dep, + x_deps, + xcb_dep, +] + +if xdmcp_dep.found() and get_option('tcp-wrappers') + gdm_daemon_deps += libwrap_dep +endif + +# test-session-client +test_session_client_src = [ + 'test-session-client.c', + session_dbus_gen, + manager_dbus_gen, +] + +test_session_client = executable('test-session-client', + test_session_client_src, + dependencies: gdm_daemon_deps, + include_directories: config_h_dir, +) + +# Session worker +gdm_session_worker_src = [ + 'session-worker-main.c', + 'gdm-session.c', + 'gdm-session-settings.c', + 'gdm-session-auditor.c', + 'gdm-session-record.c', + 'gdm-session-worker.c', + 'gdm-session-worker-job.c', + 'gdm-session-worker-common.c', + 'gdm-dbus-util.c', + dbus_gen, + session_dbus_gen, + session_worker_dbus_gen, + gdm_session_enums, + gdm_session_worker_enums, +] + +gdm_session_worker_deps = [ + gdm_daemon_deps, +] + +gdm_session_worker_includes = [ + config_h_dir, +] + +if pam_extensions_supported + gdm_session_worker_src += '../pam-extensions/gdm-pam-extensions.h' + gdm_session_worker_includes += pam_extensions_inc +endif + +if libaudit_dep.found() + gdm_session_worker_deps += libaudit_dep + + gdm_session_worker_src += [ + 'gdm-session-linux-auditor.c', + ] +endif + +if have_adt + gdm_session_worker_src += 'gdm-session-solaris-auditor.c' +endif + +gdm_session_worker = executable('gdm-session-worker', + gdm_session_worker_src, + dependencies: gdm_session_worker_deps, + include_directories: gdm_session_worker_includes, + install: true, + install_dir: get_option('libexecdir'), +) + +# Wayland session +gdm_wayland_session_src = [ + 'gdm-wayland-session.c', + manager_dbus_gen, +] + +gdm_wayland_session = executable('gdm-wayland-session', + gdm_wayland_session_src, + dependencies: gdm_daemon_deps, + include_directories: gdm_session_worker_includes, + install: true, + install_dir: get_option('libexecdir'), +) + +# X session +gdm_x_session_src = [ + 'gdm-x-session.c', + manager_dbus_gen, +] + +gdm_x_session = executable('gdm-x-session', + gdm_x_session_src, + dependencies: gdm_daemon_deps, + include_directories: gdm_session_worker_includes, + install: true, + install_dir: get_option('libexecdir'), +) + +# GDM daemon +gdm_daemon_sources = files( + 'gdm-dbus-util.c', + 'gdm-display-access-file.c', + 'gdm-display-factory.c', + 'gdm-display-store.c', + 'gdm-display.c', + 'gdm-launch-environment.c', + 'gdm-legacy-display.c', + 'gdm-local-display-factory.c', + 'gdm-local-display.c', + 'gdm-manager.c', + 'gdm-server.c', + 'gdm-session-record.c', + 'gdm-session-worker-common.c', + 'gdm-session-worker-job.c', + 'gdm-session.c', + 'main.c', +) + +gdm_daemon_gen_sources = [ + display_dbus_gen, + local_display_factory_dbus_gen, + manager_dbus_gen, + local_display_dbus_gen, + session_dbus_gen, + session_worker_dbus_gen, + gdm_session_enums, +] + +if xdmcp_dep.found() + gdm_daemon_deps += xdmcp_dep + + gdm_daemon_sources = [ + gdm_daemon_sources, + files( + 'gdm-xdmcp-display-factory.c', + 'gdm-xdmcp-display.c', + 'gdm-xdmcp-chooser-display.c', + ), + ] +endif + +gdm_daemon = executable('gdm', + [ gdm_daemon_sources, gdm_daemon_gen_sources ], + dependencies: gdm_daemon_deps, + include_directories: config_h_dir, + install: true, + install_dir: get_option('sbindir') +) diff --git a/daemon/org.freedesktop.DBus.xml b/daemon/org.freedesktop.DBus.xml new file mode 100644 index 0000000..5e0814b --- /dev/null +++ b/daemon/org.freedesktop.DBus.xml @@ -0,0 +1,12 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.freedesktop.DBus.Peer"> + <method name="GetMachineId"> + <arg direction="out" type="s"/> + </method> + <method name="Ping"> + </method> + </interface> +</node> + diff --git a/daemon/session-worker-main.c b/daemon/session-worker-main.c new file mode 100644 index 0000000..d96844d --- /dev/null +++ b/daemon/session-worker-main.c @@ -0,0 +1,173 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <fcntl.h> +#include <signal.h> +#include <locale.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-common.h" +#include "gdm-log.h" +#include "gdm-session-worker.h" + +#include "gdm-settings.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +static GdmSettings *settings = NULL; + +static gboolean +on_sigusr1_cb (gpointer user_data) +{ + g_debug ("Got USR1 signal"); + + gdm_log_toggle_debug (); + + return TRUE; +} + +static gboolean +is_debug_set (void) +{ + gboolean debug; + gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug); + return debug; +} + +static gboolean +on_shutdown_signal_cb (gpointer user_data) +{ + GMainLoop *mainloop = user_data; + + g_main_loop_quit (mainloop); + + return FALSE; +} + +static void +on_state_changed (GdmSessionWorker *worker, + GParamSpec *pspec, + GMainLoop *main_loop) +{ + GdmSessionWorkerState state; + + g_object_get (G_OBJECT (worker), "state", &state, NULL); + + if (state != GDM_SESSION_WORKER_STATE_SESSION_STARTED) + return; + + g_unix_signal_add (SIGTERM, on_shutdown_signal_cb, main_loop); +} + +static void +on_sigterm_cb (int signal_number) +{ + _exit (EXIT_SUCCESS); +} + +int +main (int argc, + char **argv) +{ + GMainLoop *main_loop; + GOptionContext *context; + GdmSessionWorker *worker; + const char *address; + gboolean is_for_reauth; + static GOptionEntry entries [] = { + { NULL } + }; + + signal (SIGTERM, on_sigterm_cb); + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + /* Translators: worker is a helper process that does the work + of starting up a session */ + context = g_option_context_new (_("GNOME Display Manager Session Worker")); + g_option_context_add_main_entries (context, entries, NULL); + + g_option_context_parse (context, &argc, &argv, NULL); + g_option_context_free (context); + + gdm_log_init (); + + settings = gdm_settings_new (); + if (settings == NULL) { + g_warning ("Unable to initialize settings"); + exit (EXIT_FAILURE); + } + + if (! gdm_settings_direct_init (settings, DATADIR "/gdm/gdm.schemas", "/")) { + g_warning ("Unable to initialize settings"); + exit (EXIT_FAILURE); + } + + gdm_log_set_debug (is_debug_set ()); + + address = g_getenv ("GDM_SESSION_DBUS_ADDRESS"); + if (address == NULL) { + g_warning ("GDM_SESSION_DBUS_ADDRESS not set"); + exit (EXIT_FAILURE); + } + + is_for_reauth = g_getenv ("GDM_SESSION_FOR_REAUTH") != NULL; + + worker = gdm_session_worker_new (address, is_for_reauth); + + main_loop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (G_OBJECT (worker), + "notify::state", + G_CALLBACK (on_state_changed), + main_loop); + + g_unix_signal_add (SIGUSR1, on_sigusr1_cb, NULL); + + g_main_loop_run (main_loop); + + if (worker != NULL) { + g_signal_handlers_disconnect_by_func (worker, + G_CALLBACK (on_state_changed), + main_loop); + g_object_unref (worker); + } + + g_main_loop_unref (main_loop); + + g_debug ("Worker finished"); + + return 0; +} diff --git a/daemon/test-session-client.c b/daemon/test-session-client.c new file mode 100644 index 0000000..ae4f59d --- /dev/null +++ b/daemon/test-session-client.c @@ -0,0 +1,257 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * 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 <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <termios.h> + +#include <glib.h> + +#include "gdm-manager-glue.h" +#include "gdm-session-glue.h" + +static GMainLoop *loop; + +static void +on_conversation_stopped (GdmDBusUserVerifier *user_verifier, + const char *service_name) +{ + g_print ("\n** WARNING: conversation stopped\n"); + + g_main_loop_quit (loop); +} + +static void +on_reset (GdmDBusUserVerifier *user_verifier) +{ + g_print ("\n** NOTE: reset\n"); + + g_main_loop_quit (loop); +} + +static void +on_verification_complete (GdmDBusUserVerifier *user_verifier, + const char *service_name) +{ + g_print ("\n** INFO: verification complete\n"); + + g_main_loop_quit (loop); +} + +static void +on_info_query (GdmDBusUserVerifier *user_verifier, + const char *service_name, + const char *query_text) +{ + char answer[1024]; + char *res; + + g_print ("%s ", query_text); + + answer[0] = '\0'; + res = fgets (answer, sizeof (answer), stdin); + if (res == NULL) { + g_warning ("Couldn't get an answer"); + } + + answer[strlen (answer) - 1] = '\0'; + + if (answer[0] == '\0') { + gdm_dbus_user_verifier_call_cancel_sync (user_verifier, + NULL, + NULL); + g_main_loop_quit (loop); + } else { + gdm_dbus_user_verifier_call_answer_query_sync (user_verifier, + service_name, + answer, + NULL, + NULL); + } +} + +static void +on_info (GdmDBusUserVerifier *user_verifier, + const char *service_name, + const char *info) +{ + g_print ("\n** NOTE: %s\n", info); +} + +static void +on_problem (GdmDBusUserVerifier *user_verifier, + const char *service_name, + const char *problem) +{ + g_print ("\n** WARNING: %s\n", problem); +} + +static void +on_secret_info_query (GdmDBusUserVerifier *user_verifier, + const char *service_name, + const char *query_text) +{ + char answer[1024]; + char *res; + struct termios ts0; + struct termios ts1; + + tcgetattr (fileno (stdin), &ts0); + ts1 = ts0; + ts1.c_lflag &= ~ECHO; + + g_print ("%s", query_text); + + if (tcsetattr (fileno (stdin), TCSAFLUSH, &ts1) != 0) { + fprintf (stderr, "Could not set terminal attributes\n"); + exit (EXIT_FAILURE); + } + + answer[0] = '\0'; + res = fgets (answer, sizeof (answer), stdin); + answer[strlen (answer) - 1] = '\0'; + if (res == NULL) { + g_warning ("Couldn't get an answer"); + } + + tcsetattr (fileno (stdin), TCSANOW, &ts0); + + g_print ("\n"); + + gdm_dbus_user_verifier_call_answer_query_sync (user_verifier, + service_name, + answer, + NULL, + NULL); +} + +int +main (int argc, + char *argv[]) +{ + GError *error; + GdmDBusManager *manager; + GdmDBusUserVerifier *user_verifier; + GDBusConnection *system_bus; + GDBusConnection *connection; + char *address; + gboolean ok; + + g_debug ("creating instance of GdmDBusDisplay object..."); + + error = NULL; + system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (system_bus == NULL) { + g_critical ("Failed connecting to the system bus (this is pretty bad): %s", error->message); + exit (EXIT_FAILURE); + } + + manager = GDM_DBUS_MANAGER (gdm_dbus_manager_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.DisplayManager", + "/org/gnome/DisplayManager/Manager", + NULL, + &error)); + if (manager == NULL) { + g_critical ("Failed creating display proxy: %s", error->message); + exit (EXIT_FAILURE); + } + + address = NULL; + gdm_dbus_manager_call_open_reauthentication_channel_sync (manager, + g_get_user_name (), + &address, + NULL, + &error); + if (address == NULL) { + g_critical ("Failed opening reauthentication channel: %s", error->message); + exit (EXIT_FAILURE); + } + + connection = g_dbus_connection_new_for_address_sync (address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, + NULL, + &error); + if (connection == NULL) { + g_critical ("Failed connecting to the manager: %s", error->message); + exit (EXIT_FAILURE); + } + + user_verifier = GDM_DBUS_USER_VERIFIER (gdm_dbus_user_verifier_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "/org/gnome/DisplayManager/Session", + NULL, + &error)); + if (user_verifier == NULL) { + g_critical ("Failed creating user verifier proxy: %s", error->message); + exit (EXIT_FAILURE); + } + + g_signal_connect (user_verifier, + "info", + G_CALLBACK (on_info), + NULL); + g_signal_connect (user_verifier, + "problem", + G_CALLBACK (on_problem), + NULL); + g_signal_connect (user_verifier, + "info-query", + G_CALLBACK (on_info_query), + NULL); + g_signal_connect (user_verifier, + "secret-info-query", + G_CALLBACK (on_secret_info_query), + NULL); + g_signal_connect (user_verifier, + "conversation-stopped", + G_CALLBACK (on_conversation_stopped), + NULL); + g_signal_connect (user_verifier, + "verification-complete", + G_CALLBACK (on_verification_complete), + NULL); + g_signal_connect (user_verifier, + "reset", + G_CALLBACK (on_reset), + NULL); + + ok = gdm_dbus_user_verifier_call_begin_verification_for_user_sync (user_verifier, + "gdm-password", + g_get_user_name (), + NULL, + &error); + if (!ok) { + g_critical ("Failed to start PAM session: %s", error->message); + exit (EXIT_FAILURE); + } + + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + return 0; +} |