diff options
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/Makefile.am | 25 | ||||
-rw-r--r-- | daemon/gdm-greeter-proxy.c | 12 | ||||
-rw-r--r-- | daemon/gdm-greeter-server.c | 4 | ||||
-rw-r--r-- | daemon/gdm-product-slave.c | 83 | ||||
-rw-r--r-- | daemon/gdm-session-worker-job.c | 478 | ||||
-rw-r--r-- | daemon/gdm-session-worker-job.h | 66 | ||||
-rw-r--r-- | daemon/gdm-session-worker.c | 1672 | ||||
-rw-r--r-- | daemon/gdm-session-worker.h | 74 | ||||
-rw-r--r-- | daemon/gdm-session.c | 4183 | ||||
-rw-r--r-- | daemon/gdm-session.h | 20 | ||||
-rw-r--r-- | daemon/gdm-simple-slave.c | 21 | ||||
-rw-r--r-- | daemon/session-worker-main.c | 179 | ||||
-rw-r--r-- | daemon/test-session.c | 50 |
13 files changed, 3395 insertions, 3472 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 58686e29..75aa1262 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -70,18 +70,21 @@ noinst_PROGRAMS = \ test_session_SOURCES = \ test-session.c \ gdm-session.c \ + gdm-session-worker-job.c\ $(NULL) test_session_LDADD = \ $(GLIB_LIBS) \ $(GOBJECT_LIBS) \ $(PAM_LIBS) \ + $(DBUS_LIBS) \ $(NULL) libexec_PROGRAMS = \ gdm-simple-slave \ gdm-factory-slave \ gdm-product-slave \ + gdm-session-worker \ $(NULL) gdm_simple_slave_SOURCES = \ @@ -94,6 +97,8 @@ gdm_simple_slave_SOURCES = \ gdm-server.h \ gdm-session.c \ gdm-session.h \ + gdm-session-worker-job.c \ + gdm-session-worker-job.h \ gdm-slave.c \ gdm-slave.h \ gdm-simple-slave.c \ @@ -132,8 +137,6 @@ gdm_factory_slave_SOURCES = \ gdm-server.h \ gdm-session-relay.c \ gdm-session-relay.h \ - gdm-session.c \ - gdm-session.h \ gdm-slave.c \ gdm-slave.h \ gdm-factory-slave.c \ @@ -168,6 +171,8 @@ gdm_product_slave_SOURCES = \ gdm-server.h \ gdm-session.c \ gdm-session.h \ + gdm-session-worker-job.c \ + gdm-session-worker-job.h \ gdm-slave.c \ gdm-slave.h \ gdm-product-slave.c \ @@ -196,6 +201,22 @@ gdm_product_slave_LDADD = \ -lXext \ $(NULL) +gdm_session_worker_SOURCES = \ + session-worker-main.c \ + gdm-session-worker.c \ + $(NULL) + +gdm_session_worker_LDFLAGS = \ + $(PAM_LIBS) \ + $(NULL) + +gdm_session_worker_LDADD = \ + $(GLIB_LIBS) \ + $(GOBJECT_LIBS) \ + $(top_builddir)/common/libgdmcommon.la \ + $(DBUS_LIBS) \ + $(NULL) + sbin_PROGRAMS = \ gdm-binary \ $(NULL) diff --git a/daemon/gdm-greeter-proxy.c b/daemon/gdm-greeter-proxy.c index dbad1f3d..c3167f88 100644 --- a/daemon/gdm-greeter-proxy.c +++ b/daemon/gdm-greeter-proxy.c @@ -32,10 +32,6 @@ #include <pwd.h> #include <grp.h> -#if defined (_POSIX_PRIORITY_SCHEDULING) && defined (HAVE_SCHED_YIELD) -#include <sched.h> -#endif - #include <glib.h> #include <glib/gi18n.h> #include <glib-object.h> @@ -43,10 +39,7 @@ #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> -#include <X11/Xlib.h> /* for Display */ - #include "gdm-common.h" -#include "filecheck.h" #include "gdm-greeter-proxy.h" @@ -77,7 +70,6 @@ struct GdmGreeterProxyPrivate guint child_watch_id; char *server_address; - DBusConnection *greeter_connection; }; enum { @@ -611,8 +603,8 @@ gdm_greeter_proxy_constructor (GType type, klass = GDM_GREETER_PROXY_CLASS (g_type_class_peek (GDM_TYPE_GREETER_PROXY)); greeter_proxy = GDM_GREETER_PROXY (G_OBJECT_CLASS (gdm_greeter_proxy_parent_class)->constructor (type, - n_construct_properties, - construct_properties)); + n_construct_properties, + construct_properties)); return G_OBJECT (greeter_proxy); } diff --git a/daemon/gdm-greeter-server.c b/daemon/gdm-greeter-server.c index a30809be..624ef0ab 100644 --- a/daemon/gdm-greeter-server.c +++ b/daemon/gdm-greeter-server.c @@ -226,8 +226,8 @@ generate_address (void) static DBusHandlerResult handle_answer_query (GdmGreeterServer *greeter_server, - DBusConnection *connection, - DBusMessage *message) + DBusConnection *connection, + DBusMessage *message) { DBusMessage *reply; DBusError error; diff --git a/daemon/gdm-product-slave.c b/daemon/gdm-product-slave.c index 6ccc3597..b67b42c1 100644 --- a/daemon/gdm-product-slave.c +++ b/daemon/gdm-product-slave.c @@ -79,6 +79,7 @@ struct GdmProductSlavePrivate /* user selected */ char *selected_session; char *selected_language; + char *selected_user; GdmServer *server; GdmSession *session; @@ -392,11 +393,28 @@ relay_session_started (GdmProductSlave *slave) } static void +on_open (GdmSession *session, + GdmProductSlave *slave) +{ + GError *error; + gboolean res; + + g_debug ("session open"); + res = gdm_session_begin_verification (session, + slave->priv->selected_user, + &error); + if (! res) { + g_warning ("Unable to begin verification: %s", error->message); + g_error_free (error); + } +} + +static void on_session_started (GdmSession *session, GPid pid, GdmProductSlave *slave) { - g_debug ("session started on pid %d\n", (int) pid); + g_debug ("session started on pid %d", (int) pid); g_signal_emit (slave, signals [SESSION_STARTED], 0, pid); relay_session_started (slave); } @@ -406,7 +424,7 @@ on_session_exited (GdmSession *session, int exit_code, GdmProductSlave *slave) { - g_debug ("session exited with code %d\n", exit_code); + g_debug ("session exited with code %d", exit_code); g_signal_emit (slave, signals [SESSION_EXITED], 0, exit_code); } @@ -573,11 +591,8 @@ on_user_verified (GdmSession *session, GdmProductSlave *slave) { char *username; - int argc; - char **argv; char *command; char *filename; - GError *error; gboolean res; /*gdm_greeter_server_stop (slave->priv->greeter);*/ @@ -603,20 +618,10 @@ on_user_verified (GdmSession *session, return; } - error = NULL; - res = g_shell_parse_argv (command, &argc, &argv, &error); - if (! res) { - g_warning ("Could not parse command: %s", error->message); - g_error_free (error); - } - - gdm_session_start_program (session, - argc, - (const char **)argv); + gdm_session_start_program (session, command); g_free (filename); g_free (command); - g_strfreev (argv); } static void @@ -786,32 +791,39 @@ on_relay_language_selected (DBusGProxy *proxy, slave->priv->selected_language = g_strdup (text); } +static gboolean +reset_session (GdmProductSlave *slave) +{ + gboolean res; + GError *error; + + gdm_session_close (slave->priv->session); + res = gdm_session_open (slave->priv->session, + "gdm", + "", + "/dev/console", + &error); + if (! res) { + g_warning ("Unable to open session: %s", error->message); + g_error_free (error); + } + + return res; +} + static void on_relay_user_selected (DBusGProxy *proxy, const char *text, gpointer data) { GdmProductSlave *slave = GDM_PRODUCT_SLAVE (data); - gboolean res; - GError *error; g_debug ("User: %s", text); - gdm_session_close (slave->priv->session); + g_free (slave->priv->selected_user); + slave->priv->selected_user = g_strdup (text); - error = NULL; - res = gdm_session_open_for_user (slave->priv->session, - "gdm", - text, - NULL /* hostname */, - "/dev/console", - STDOUT_FILENO, - STDERR_FILENO, - &error); - if (! res) { - g_warning ("Unable to open session: %s", error->message); - g_error_free (error); - } + reset_session (slave); } static void @@ -829,8 +841,6 @@ on_relay_open (DBusGProxy *proxy, "gdm", NULL /* hostname */, "/dev/console", - STDOUT_FILENO, - STDERR_FILENO, &error); if (! res) { g_warning ("Unable to open session: %s", error->message); @@ -844,6 +854,11 @@ create_new_session (GdmProductSlave *slave) slave->priv->session = gdm_session_new (); g_signal_connect (slave->priv->session, + "open", + G_CALLBACK (on_open), + slave); + + g_signal_connect (slave->priv->session, "info", G_CALLBACK (on_info), slave); diff --git a/daemon/gdm-session-worker-job.c b/daemon/gdm-session-worker-job.c new file mode 100644 index 00000000..dfa5cfcc --- /dev/null +++ b/daemon/gdm-session-worker-job.c @@ -0,0 +1,478 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#define DBUS_API_SUBJECT_TO_CHANGE +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include "gdm-common.h" + +#include "gdm-session-worker-job.h" + +#define GDM_SESSION_SERVER_DBUS_PATH "/org/gnome/DisplayManager/SessionServer" +#define GDM_SESSION_SERVER_DBUS_INTERFACE "org.gnome.DisplayManager.SessionServer" + +extern char **environ; + +#define GDM_SESSION_WORKER_JOB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_SESSION_WORKER_JOB, GdmSessionWorkerJobPrivate)) + +struct GdmSessionWorkerJobPrivate +{ + char *command; + GPid pid; + + guint child_watch_id; + + char *server_address; +}; + +enum { + PROP_0, + PROP_SERVER_ADDRESS, +}; + +enum { + STARTED, + STOPPED, + 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_child_setup (GdmSessionWorkerJob *session_worker_job) +{ +} + +static void +session_worker_job_child_watch (GPid pid, + int status, + GdmSessionWorkerJob *job) +{ + g_debug ("child (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -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); + } + + g_spawn_close_pid (job->priv->pid); + job->priv->pid = -1; +} + +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_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); + + g_hash_table_insert (hash, g_strdup ("GDM_SESSION_DBUS_ADDRESS"), g_strdup (job->priv->server_address)); + + 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) +{ + gchar **argv; + GError *error; + gboolean ret; + GPtrArray *env; + + ret = FALSE; + + g_debug ("Running session_worker_job process: %s", session_worker_job->priv->command); + + argv = NULL; + if (! g_shell_parse_argv (session_worker_job->priv->command, NULL, &argv, &error)) { + g_warning ("Could not parse command: %s", error->message); + g_error_free (error); + goto out; + } + + env = get_job_environment (session_worker_job); + + error = NULL; + ret = g_spawn_async_with_pipes (NULL, + argv, + (char **)env->pdata, + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + (GSpawnChildSetupFunc)session_worker_job_child_setup, + session_worker_job, + &session_worker_job->priv->pid, + NULL, + NULL, + NULL, + &error); + + 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->priv->command, + error->message); + g_error_free (error); + } else { + g_debug ("gdm_slave_session_worker_job: SessionWorkerJob on pid %d", (int)session_worker_job->priv->pid); + } + + session_worker_job->priv->child_watch_id = g_child_watch_add (session_worker_job->priv->pid, + (GChildWatchFunc)session_worker_job_child_watch, + session_worker_job); + + g_strfreev (argv); + out: + + 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) +{ + gboolean res; + + g_debug ("Starting worker..."); + + res = gdm_session_worker_job_spawn (session_worker_job); + + if (res) { + + } + + + return res; +} + +static int +signal_pid (int pid, + int signal) +{ + int status = -1; + + /* perhaps block sigchld */ + + status = kill (pid, signal); + + if (status < 0) { + if (errno == ESRCH) { + g_warning ("Child process %lu was already dead.", + (unsigned long) pid); + } else { + g_warning ("Couldn't kill child process %lu: %s", + (unsigned long) pid, + g_strerror (errno)); + } + } + + /* perhaps unblock sigchld */ + + return status; +} + +static int +wait_on_child (int pid) +{ + int status; + + wait_again: + if (waitpid (pid, &status, 0) < 0) { + if (errno == EINTR) { + goto wait_again; + } else if (errno == ECHILD) { + ; /* do nothing, child already reaped */ + } else { + g_debug ("waitpid () should not fail"); + } + } + + return status; +} + +static void +session_worker_job_died (GdmSessionWorkerJob *session_worker_job) +{ + int exit_status; + + g_debug ("Waiting on process %d", session_worker_job->priv->pid); + exit_status = wait_on_child (session_worker_job->priv->pid); + + if (WIFEXITED (exit_status) && (WEXITSTATUS (exit_status) != 0)) { + g_debug ("Wait on child process failed"); + } else { + /* exited normally */ + } + + g_spawn_close_pid (session_worker_job->priv->pid); + session_worker_job->priv->pid = -1; + + g_debug ("SessionWorkerJob died"); +} + +gboolean +gdm_session_worker_job_stop (GdmSessionWorkerJob *session_worker_job) +{ + + if (session_worker_job->priv->pid <= 1) { + return TRUE; + } + + /* remove watch source before we can wait on child */ + if (session_worker_job->priv->child_watch_id > 0) { + g_source_remove (session_worker_job->priv->child_watch_id); + session_worker_job->priv->child_watch_id = 0; + } + + g_debug ("Stopping session_worker_job"); + + signal_pid (session_worker_job->priv->pid, SIGTERM); + session_worker_job_died (session_worker_job); + + return TRUE; +} + +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->priv->server_address); + session_worker_job->priv->server_address = g_strdup (address); +} + +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; + 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->priv->server_address); + 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; + GdmSessionWorkerJobClass *klass; + + klass = GDM_SESSION_WORKER_JOB_CLASS (g_type_class_peek (GDM_TYPE_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_type_class_add_private (klass, sizeof (GdmSessionWorkerJobPrivate)); + + 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)); + signals [STARTED] = + g_signal_new ("started", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmSessionWorkerJobClass, 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 (GdmSessionWorkerJobClass, 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 (GdmSessionWorkerJobClass, 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 (GdmSessionWorkerJobClass, died), + 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->priv = GDM_SESSION_WORKER_JOB_GET_PRIVATE (session_worker_job); + + session_worker_job->priv->pid = -1; + + session_worker_job->priv->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); + + g_return_if_fail (session_worker_job->priv != NULL); + + gdm_session_worker_job_stop (session_worker_job); + + 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 00000000..5c35fe16 --- /dev/null +++ b/daemon/gdm-session-worker-job.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 ()) +#define GDM_SESSION_WORKER_JOB(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_SESSION_WORKER_JOB, GdmSessionWorkerJob)) +#define GDM_SESSION_WORKER_JOB_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_SESSION_WORKER_JOB, GdmSessionWorkerJobClass)) +#define GDM_IS_SESSION_WORKER_JOB(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_SESSION_WORKER_JOB)) +#define GDM_IS_SESSION_WORKER_JOB_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_SESSION_WORKER_JOB)) +#define GDM_SESSION_WORKER_JOB_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_SESSION_WORKER_JOB, GdmSessionWorkerJobClass)) + +typedef struct GdmSessionWorkerJobPrivate GdmSessionWorkerJobPrivate; + +typedef struct +{ + GObject parent; + GdmSessionWorkerJobPrivate *priv; +} GdmSessionWorkerJob; + +typedef struct +{ + GObjectClass parent_class; + + void (* started) (GdmSessionWorkerJob *session_worker_job); + void (* stopped) (GdmSessionWorkerJob *session_worker_job); + void (* exited) (GdmSessionWorkerJob *session_worker_job, + int exit_code); + + void (* died) (GdmSessionWorkerJob *session_worker_job, + int signal_number); +} GdmSessionWorkerJobClass; + +GType gdm_session_worker_job_get_type (void); +GdmSessionWorkerJob * gdm_session_worker_job_new (void); +void gdm_session_worker_job_set_server_address (GdmSessionWorkerJob *session_worker_job, + const char *server_address); +gboolean gdm_session_worker_job_start (GdmSessionWorkerJob *session_worker_job); +gboolean gdm_session_worker_job_stop (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 00000000..4e1d8f42 --- /dev/null +++ b/daemon/gdm-session-worker.c @@ -0,0 +1,1672 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 <grp.h> +#include <pwd.h> + +#include <security/pam_appl.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> +#define DBUS_API_SUBJECT_TO_CHANGE +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include "gdm-session-worker.h" +#include "gdm-marshal.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_INTERFACE "org.gnome.DisplayManager.Session" + +#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:/usr/X11R6/bin" +#endif + +#ifndef GDM_SESSION_ROOT_UID +#define GDM_SESSION_ROOT_UID 0 +#endif + +struct GdmSessionWorkerPrivate +{ + int exit_code; + + pam_handle_t *pam_handle; + + GPid child_pid; + guint child_watch_id; + + char *username; + char **arguments; + + GHashTable *environment; + + guint32 credentials_are_established : 1; + guint32 is_running : 1; + + char *server_address; + DBusGConnection *connection; + DBusGProxy *server_proxy; +}; + +enum { + PROP_0, + PROP_SERVER_ADDRESS, +}; + + +enum { + USER_VERIFIED = 0, + USER_VERIFICATION_ERROR, + INFO, + PROBLEM, + INFO_QUERY, + SECRET_INFO_QUERY, + SESSION_STARTED, + SESSION_STARTUP_ERROR, + SESSION_EXITED, + SESSION_DIED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +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); + +typedef int (* GdmSessionWorkerPamNewMessagesFunc) (int, + const struct pam_message **, + struct pam_response **, + gpointer); + +G_DEFINE_TYPE (GdmSessionWorker, gdm_session_worker, G_TYPE_OBJECT) + +GQuark +gdm_session_worker_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) + error_quark = g_quark_from_static_string ("gdm-session-worker"); + + return error_quark; +} + +/* 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; +} + +static void +send_user_verified (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "Verified", + &error, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send Verified: %s", error->message); + g_error_free (error); + } +} + +static void +send_startup_failed (GdmSessionWorker *worker, + const char *message) +{ + GError *error; + gboolean res; + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "StartupFailed", + &error, + G_TYPE_STRING, message, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send StartupFailed: %s", error->message); + g_error_free (error); + } +} + +static void +send_username_changed (GdmSessionWorker *worker) +{ + GError *error; + gboolean res; + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "UsernameChanged", + &error, + G_TYPE_STRING, worker->priv->username, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send UsernameChanged: %s", error->message); + g_error_free (error); + } +} + +static void +send_user_verification_error (GdmSessionWorker *worker, + const char *message) +{ + GError *error; + gboolean res; + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "VerificationFailed", + &error, + G_TYPE_STRING, message, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send VerificationFailed: %s", error->message); + g_error_free (error); + } +} + +static void +send_session_started (GdmSessionWorker *worker, + GPid pid) +{ + GError *error; + gboolean res; + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "SessionStarted", + &error, + G_TYPE_INT, (int)pid, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send SessionStarted: %s", error->message); + g_error_free (error); + } +} + +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) { + *username = g_strdup ((char *) item); + g_debug ("username is '%s'", + *username != NULL ? *username : + "<unset>"); + } + return TRUE; + } + + return FALSE; +} + +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) { + if ((worker->priv->username == username) || + ((worker->priv->username != NULL) && (username != NULL) && + (strcmp (worker->priv->username, username) == 0))) + goto out; + + g_debug ("setting username to '%s'", username); + + g_free (worker->priv->username); + worker->priv->username = username; + username = NULL; + + send_username_changed (worker); + } + + out: + g_free (username); +} + +static char * +gdm_session_worker_ask_question (GdmSessionWorker *worker, + const char *question) +{ + GError *error; + gboolean res; + char *answer; + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "InfoQuery", + &error, + G_TYPE_STRING, question, + G_TYPE_INVALID, + G_TYPE_STRING, &answer, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send InfoQuery: %s", error->message); + g_error_free (error); + } + + return answer; +} + +static char * +gdm_session_worker_ask_for_secret (GdmSessionWorker *worker, + const char *secret) +{ + GError *error; + gboolean res; + char *answer; + + g_debug ("Secret info query: %s", secret); + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "SecretInfoQuery", + &error, + G_TYPE_STRING, secret, + G_TYPE_INVALID, + G_TYPE_STRING, &answer, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send SecretInfoQuery: %s", error->message); + g_error_free (error); + } + + g_debug ("answer to secret question '%s' is '%s'", secret, answer); + return answer; +} + +static void +gdm_session_worker_report_info (GdmSessionWorker *worker, + const char *info) +{ + GError *error; + gboolean res; + + g_debug ("Info: %s", info); + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "Info", + &error, + G_TYPE_STRING, info, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send Info: %s", error->message); + g_error_free (error); + } +} + +static void +gdm_session_worker_report_problem (GdmSessionWorker *worker, + const char *problem) +{ + GError *error; + gboolean res; + + g_debug ("Problem: %s", problem); + + error = NULL; + res = dbus_g_proxy_call (worker->priv->server_proxy, + "Problem", + &error, + G_TYPE_STRING, problem, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (! res) { + g_warning ("Unable to send Problem: %s", error->message); + g_error_free (error); + } +} + +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_text) +{ + char *user_answer; + gboolean was_processed; + char *utf8_msg; + + + g_debug ("received pam message of type %u with payload '%s'", + query->msg_style, query->msg); + + utf8_msg = convert_to_utf8 (query->msg); + + user_answer = NULL; + was_processed = FALSE; + switch (query->msg_style) { + case PAM_PROMPT_ECHO_ON: + user_answer = gdm_session_worker_ask_question (worker, utf8_msg); + break; + case PAM_PROMPT_ECHO_OFF: + user_answer = gdm_session_worker_ask_for_secret (worker, utf8_msg); + break; + case PAM_TEXT_INFO: + gdm_session_worker_report_info (worker, utf8_msg); + was_processed = TRUE; + break; + case PAM_ERROR_MSG: + gdm_session_worker_report_problem (worker, utf8_msg); + was_processed = TRUE; + break; + default: + g_debug ("unknown query of type %u\n", query->msg_style); + break; + } + + if (user_answer != NULL) { + /* we strdup and g_free to make sure we return malloc'd + * instead of g_malloc'd memory + */ + if (response_text != NULL) { + *response_text = strdup (user_answer); + } + + g_free (user_answer); + + g_debug ("trying to get updated username"); + gdm_session_worker_update_username (worker); + was_processed = TRUE; + } + + g_free (utf8_msg); + + return was_processed; +} + +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 ("%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_text; + + response_text = NULL; + got_response = gdm_session_worker_process_pam_message (worker, + messages[i], + &response_text); + if (!got_response) + goto out; + + g_debug ("answered pam message %d with response '%s'", + i, response_text); + replies[i].resp = response_text; + 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; + } + + return return_value; +} + +static void +gdm_session_worker_uninitialize_pam (GdmSessionWorker *worker, + int error_code) +{ + g_debug ("uninitializing PAM"); + + if (worker->priv->pam_handle == NULL) + return; + + if (worker->priv->credentials_are_established) { + pam_setcred (worker->priv->pam_handle, PAM_DELETE_CRED); + worker->priv->credentials_are_established = FALSE; + } + + if (worker->priv->is_running) { + pam_close_session (worker->priv->pam_handle, 0); + worker->priv->is_running = FALSE; + } + + pam_end (worker->priv->pam_handle, error_code); + worker->priv->pam_handle = NULL; +} + +static gboolean +gdm_session_worker_initialize_pam (GdmSessionWorker *worker, + const char *service, + const char *username, + const char *hostname, + const char *console_name, + GError **error) +{ + struct pam_conv pam_conversation; + int error_code; + + g_assert (worker->priv->pam_handle == NULL); + + g_debug ("initializing PAM"); + + pam_conversation.conv = (GdmSessionWorkerPamNewMessagesFunc) gdm_session_worker_pam_new_messages_handler; + pam_conversation.appdata_ptr = worker; + + error_code = pam_start (service, + username, + &pam_conversation, + &worker->priv->pam_handle); + + if (error_code != PAM_SUCCESS) { + g_debug ("could not initialize pam"); + /* 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_AUTHENTICATING, + _("error initiating conversation with authentication system - %s"), + error_code == PAM_ABORT? _("general failure") : + error_code == PAM_BUF_ERR? _("out of memory") : + error_code == PAM_SYSTEM_ERR? _("application programmer error") : + _("unscoped error")); + + goto out; + } + + if (username == NULL) { + error_code = pam_set_item (worker->priv->pam_handle, PAM_USER_PROMPT, _("Username:")); + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + _("error informing authentication system of preferred username prompt - %s"), + pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + } + + if (hostname != NULL) { + error_code = pam_set_item (worker->priv->pam_handle, PAM_RHOST, hostname); + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + _("error informing authentication system of user's hostname - %s"), + pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + } + + error_code = pam_set_item (worker->priv->pam_handle, PAM_TTY, console_name); + + if (error_code != PAM_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + _("error informing authentication system of user's console - %s"), + pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + + 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 ("authenticating user"); + + 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_SUCCESS) { + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_AUTHENTICATING, + "%s", pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + + 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 ("determining if authenticated user is authorized to session"); + + 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) + error_code = pam_chauthtok (worker->priv->pam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); + + if (error_code != PAM_SUCCESS) { + g_debug ("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", pam_strerror (worker->priv->pam_handle, error_code)); + goto out; + } + + 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) +{ + /* FIXME: maybe we should use use pam_putenv instead of our + * own hash table, so pam can override our choices if it knows + * better? + */ + g_hash_table_replace (worker->priv->environment, + g_strdup (key), + g_strdup (value)); +} + +static void +gdm_session_worker_update_environment_from_passwd_entry (GdmSessionWorker *worker, + struct passwd *passwd_entry) +{ + 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", passwd_entry->pw_dir); + gdm_session_worker_set_environment_variable (worker, "SHELL", passwd_entry->pw_shell); +} + +static gboolean +gdm_session_worker_environment_variable_is_set (GdmSessionWorker *worker, + const char *name) +{ + return g_hash_table_lookup (worker->priv->environment, name) != NULL; +} + +static gboolean +gdm_session_worker_give_user_credentials (GdmSessionWorker *worker, + GError **error) +{ + int error_code; + struct passwd *passwd_entry, passwd_buffer; + char *aux_buffer; + long required_aux_buffer_size; + gsize aux_buffer_size; + + aux_buffer = NULL; + aux_buffer_size = 0; + + if (worker->priv->username == NULL) { + 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; + } + + 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 + */ + passwd_entry = NULL; + errno = getpwnam_r (worker->priv->username, + &passwd_buffer, + aux_buffer, + (size_t) aux_buffer_size, + &passwd_entry); + + if (errno != 0) { + error_code = PAM_SYSTEM_ERR; + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + "%s", + g_strerror (errno)); + goto out; + } + + if (passwd_entry == NULL) { + error_code = PAM_USER_UNKNOWN; + g_set_error (error, + GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + _("user account not available on system")); + goto out; + } + + gdm_session_worker_update_environment_from_passwd_entry (worker, passwd_entry); + + /* 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")) { + gdm_session_worker_set_environment_variable (worker, "PATH", GDM_SESSION_DEFAULT_PATH); + } + + /* 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 (passwd_entry->pw_uid, GDM_SESSION_ROOT_UID) < 0) { + error_code = PAM_SYSTEM_ERR; + g_set_error (error, GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + "%s", g_strerror (errno)); + goto out; + } + + if (setgid (passwd_entry->pw_gid) < 0) { + error_code = PAM_SYSTEM_ERR; + g_set_error (error, GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + "%s", g_strerror (errno)); + goto out; + } + + if (initgroups (passwd_entry->pw_name, passwd_entry->pw_gid) < 0) { + error_code = PAM_SYSTEM_ERR; + g_set_error (error, GDM_SESSION_WORKER_ERROR, + GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS, + "%s", g_strerror (errno)); + goto out; + } + + error_code = pam_setcred (worker->priv->pam_handle, PAM_ESTABLISH_CRED); + + 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; + } + + worker->priv->credentials_are_established = TRUE; + + out: + if (aux_buffer != NULL) { + g_assert (aux_buffer_size > 0); + g_slice_free1 (aux_buffer_size, aux_buffer); + } + + if (error_code != PAM_SUCCESS) { + gdm_session_worker_uninitialize_pam (worker, error_code); + return FALSE; + } + + return TRUE; +} + +static gboolean +gdm_session_worker_verify_user (GdmSessionWorker *worker, + const char *service_name, + const char *hostname, + const char *console_name, + const char *username, + gboolean password_is_required, + GError **error) +{ + GError *pam_error; + gboolean res; + + g_debug ("Verifying user: %s host: %s service: %s tty: %s", username, hostname, service_name, console_name); + + pam_error = NULL; + res = gdm_session_worker_initialize_pam (worker, + service_name, + username, + hostname, + console_name, + &pam_error); + if (! res) { + g_propagate_error (error, pam_error); + return FALSE; + } + + /* find out who the user is and ensure they are who they say they are + */ + res = gdm_session_worker_authenticate_user (worker, + password_is_required, + &pam_error); + if (! res) { + g_propagate_error (error, pam_error); + return FALSE; + } + + /* we're authenticated. Let's make sure we've been given + * a valid username for the system + */ + g_debug ("trying to get updated username"); + gdm_session_worker_update_username (worker); + + /* make sure the user is allowed to log in to this system + */ + res = gdm_session_worker_authorize_user (worker, + password_is_required, + &pam_error); + if (! res) { + g_propagate_error (error, pam_error); + return FALSE; + } + + /* get kerberos tickets, setup group lists, etc + */ + res = gdm_session_worker_give_user_credentials (worker, &pam_error); + if (! res) { + g_propagate_error (error, pam_error); + return FALSE; + } + + g_debug ("verification process completed, creating reply..."); + + send_user_verified (worker); + + return TRUE; +} + +static void +gdm_session_worker_update_environment_from_pam (GdmSessionWorker *worker) +{ + char **environment; + gsize i; + + environment = pam_getenvlist (worker->priv->pam_handle); + + for (i = 0; environment[i] != NULL; i++) { + char **key_and_value; + + key_and_value = g_strsplit (environment[i], "=", 2); + + gdm_session_worker_set_environment_variable (worker, key_and_value[0], key_and_value[1]); + + g_strfreev (key_and_value); + } + + for (i = 0; environment[i]; i++) { + free (environment[i]); + } + + free (environment); +} + +static void +gdm_session_worker_fill_environment_array (const char *key, + const char *value, + GPtrArray *environment) +{ + char *variable; + + if (value == NULL) + return; + + variable = g_strdup_printf ("%s=%s", key, value); + + g_ptr_array_add (environment, variable); +} + +static char ** +gdm_session_worker_get_environment (GdmSessionWorker *worker) +{ + GPtrArray *environment; + + environment = g_ptr_array_new (); + g_hash_table_foreach (worker->priv->environment, + (GHFunc) gdm_session_worker_fill_environment_array, + environment); + g_ptr_array_add (environment, NULL); + + return (char **) g_ptr_array_free (environment, FALSE); +} + +static void +session_worker_child_watch (GPid pid, + int status, + GdmSessionWorker *worker) +{ + g_debug ("child (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -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);*/ + } + + worker->priv->child_pid = -1; +} + +static void +gdm_session_worker_watch_child (GdmSessionWorker *worker) +{ + + worker->priv->child_watch_id = g_child_watch_add (worker->priv->child_pid, + (GChildWatchFunc)session_worker_child_watch, + worker); + +} + +static gboolean +gdm_session_worker_open_user_session (GdmSessionWorker *worker, + GError **error) +{ + int error_code; + pid_t session_pid; + + g_assert (!worker->priv->is_running); + g_assert (geteuid () == 0); + error_code = pam_open_session (worker->priv->pam_handle, 0); + + 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; + } + worker->priv->is_running = TRUE; + + g_debug ("querying pam for user environment"); + gdm_session_worker_update_environment_from_pam (worker); + + g_debug ("opening user session with program '%s'", + worker->priv->arguments[0]); + + 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) { + char **environment; + char *home_dir; + + if (setuid (getuid ()) < 0) { + g_debug ("could not reset uid - %s", g_strerror (errno)); + _exit (1); + } + + if (setsid () < 0) { + g_debug ("could not set pid '%u' as leader of new session and process group - %s", + (guint) getpid (), g_strerror (errno)); + _exit (2); + } + + environment = gdm_session_worker_get_environment (worker); + + g_assert (geteuid () == getuid ()); + + home_dir = g_hash_table_lookup (worker->priv->environment, + "HOME"); + + if ((home_dir == NULL) || g_chdir (home_dir) < 0) { + g_chdir ("/"); + } + + gdm_session_execute (worker->priv->arguments[0], + worker->priv->arguments, + environment, + TRUE); + + g_debug ("child '%s' could not be started - %s", + worker->priv->arguments[0], + g_strerror (errno)); + g_strfreev (environment); + + _exit (127); + } + + worker->priv->child_pid = session_pid; + + g_debug ("session opened creating reply..."); + g_assert (sizeof (GPid) <= sizeof (int)); + + send_session_started (worker, session_pid); + + 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 +gdm_session_worker_open (GdmSessionWorker *worker, + const char *service_name, + const char *hostname, + const char *console_name, + const char *username, + GError **error) +{ + GError *verification_error; + gboolean res; + gboolean ret; + + ret = FALSE; + + verification_error = NULL; + res = gdm_session_worker_verify_user (worker, + service_name, + hostname, + console_name, + username, + TRUE /* password is required */, + &verification_error); + if (! res) { + g_assert (verification_error != NULL); + + g_message ("%s", verification_error->message); + + g_propagate_error (error, verification_error); + + goto out; + } + + /* Did start_program get called early? if so, process it now, + * otherwise we'll do it asynchronously later. + */ + if ((worker->priv->arguments != NULL) && + !gdm_session_worker_open_user_session (worker, &verification_error)) { + g_assert (verification_error != NULL); + + g_message ("%s", verification_error->message); + + g_propagate_error (error, verification_error); + + goto out; + } + + ret = TRUE; + + out: + return ret; +} + +static gboolean +gdm_session_worker_start_program (GdmSessionWorker *worker, + const char *command) +{ + GError *start_error; + GError *error; + gboolean res; + + if (worker->priv->arguments != NULL) + g_strfreev (worker->priv->arguments); + + error = NULL; + if (! g_shell_parse_argv (command, NULL, &worker->priv->arguments, &error)) { + g_warning ("Unable to parse command: %s", error->message); + g_error_free (error); + return FALSE; + } + + /* Did start_program get called early? if so, we will process the request + * later, synchronously after getting credentials + */ + if (!worker->priv->credentials_are_established) { + return FALSE; + } + + start_error = NULL; + res = gdm_session_worker_open_user_session (worker, &start_error); + if (! res) { + g_assert (start_error != NULL); + + g_warning ("%s", start_error->message); + + send_startup_failed (worker, start_error->message); + return FALSE; + } + + 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_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; + 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; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_set_environment_variable (DBusGProxy *proxy, + const char *key, + const char *value, + gpointer data) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (data); + + g_debug ("set env: %s = %s", key, value); + + gdm_session_worker_set_environment_variable (worker, key, value); +} + +static void +on_start_program (DBusGProxy *proxy, + const char *text, + gpointer data) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (data); + + g_debug ("start program: %s", text); + + gdm_session_worker_start_program (worker, text); +} + +static void +on_begin_verification (DBusGProxy *proxy, + const char *service, + const char *console, + const char *hostname, + gpointer data) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (data); + GError *error; + gboolean res; + + g_debug ("begin verification: %s %s", service, console); + + error = NULL; + res = gdm_session_worker_open (worker, service, console, hostname, NULL, &error); + if (! res) { + g_debug ("Verification failed: %s", error->message); + g_error_free (error); + } +} + +static void +on_begin_verification_for_user (DBusGProxy *proxy, + const char *service, + const char *console, + const char *hostname, + const char *username, + gpointer data) +{ + GdmSessionWorker *worker = GDM_SESSION_WORKER (data); + GError *error; + gboolean res; + + g_debug ("begin verification: %s %s", service, console); + + error = NULL; + res = gdm_session_worker_open (worker, service, console, hostname, username, &error); + if (! res) { + g_debug ("Verification failed: %s", error->message); + g_error_free (error); + } +} + +static GObject * +gdm_session_worker_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmSessionWorker *worker; + GdmSessionWorkerClass *klass; + GError *error; + + klass = GDM_SESSION_WORKER_CLASS (g_type_class_peek (GDM_TYPE_SESSION_WORKER)); + + worker = GDM_SESSION_WORKER (G_OBJECT_CLASS (gdm_session_worker_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + g_debug ("connecting to address: %s", worker->priv->server_address); + + error = NULL; + worker->priv->connection = dbus_g_connection_open (worker->priv->server_address, &error); + if (worker->priv->connection == NULL) { + if (error != NULL) { + g_warning ("error opening connection: %s", error->message); + g_error_free (error); + } else { + g_warning ("Unable to open connection"); + } + exit (1); + } + + g_debug ("creating proxy for peer: %s", GDM_SESSION_DBUS_PATH); + worker->priv->server_proxy = dbus_g_proxy_new_for_peer (worker->priv->connection, + GDM_SESSION_DBUS_PATH, + GDM_SESSION_DBUS_INTERFACE); + if (worker->priv->server_proxy == NULL) { + g_warning ("Unable to create proxy for peer"); + exit (1); + } + + /*g_signal_connect (worker->priv->server_proxy, "destroy", G_CALLBACK (proxy_destroyed), NULL);*/ + + dbus_g_object_register_marshaller (gdm_marshal_VOID__STRING_STRING, + G_TYPE_NONE, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_INVALID); + dbus_g_object_register_marshaller (gdm_marshal_VOID__STRING_STRING_STRING_STRING, + G_TYPE_NONE, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_INVALID); + + /* FIXME: not sure why introspection isn't working */ + dbus_g_proxy_add_signal (worker->priv->server_proxy, "StartProgram", G_TYPE_STRING, G_TYPE_INVALID); + dbus_g_proxy_add_signal (worker->priv->server_proxy, "SetEnvironmentVariable", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); + dbus_g_proxy_add_signal (worker->priv->server_proxy, "BeginVerification", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); + dbus_g_proxy_add_signal (worker->priv->server_proxy, "BeginVerificationForUser", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); + + dbus_g_proxy_connect_signal (worker->priv->server_proxy, + "StartProgram", + G_CALLBACK (on_start_program), + worker, + NULL); + dbus_g_proxy_connect_signal (worker->priv->server_proxy, + "SetEnvironmentVariable", + G_CALLBACK (on_set_environment_variable), + worker, + NULL); + dbus_g_proxy_connect_signal (worker->priv->server_proxy, + "BeginVerification", + G_CALLBACK (on_begin_verification), + worker, + NULL); + dbus_g_proxy_connect_signal (worker->priv->server_proxy, + "BeginVerificationForUser", + G_CALLBACK (on_begin_verification_for_user), + worker, + NULL); + + return G_OBJECT (worker); +} + +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_type_class_add_private (klass, sizeof (GdmSessionWorkerPrivate)); + + 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)); +} + +static void +gdm_session_worker_init (GdmSessionWorker *worker) +{ + + worker->priv = GDM_SESSION_WORKER_GET_PRIVATE (worker); + worker->priv->environment = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_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->username != NULL) { + g_free (worker->priv->username); + worker->priv->username = NULL; + } + + if (worker->priv->arguments != NULL) { + g_strfreev (worker->priv->arguments); + worker->priv->arguments = NULL; + } + + if (worker->priv->environment != NULL) { + g_hash_table_destroy (worker->priv->environment); + worker->priv->environment = NULL; + } + + G_OBJECT_CLASS (gdm_session_worker_parent_class)->finalize (object); +} + +GdmSessionWorker * +gdm_session_worker_new (const char *address) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_SESSION_WORKER, + "server-address", address, + 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 00000000..308c3b35 --- /dev/null +++ b/daemon/gdm-session-worker.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; 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., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef __GDM_SESSION_H +#define __GDM_SESSION_H + +#include <glib-object.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)) + +#define GDM_SESSION_WORKER_ERROR (gdm_session_worker_error_quark ()) + +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_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 +} GdmSessionWorkerError; + +typedef struct GdmSessionWorkerPrivate GdmSessionWorkerPrivate; + +typedef struct +{ + GObject parent; + + /*< private > */ + GdmSessionWorkerPrivate *priv; +} GdmSessionWorker; + +typedef struct +{ + GObjectClass parent_class; + +} GdmSessionWorkerClass; + +GType gdm_session_worker_get_type (void); +GQuark gdm_session_worker_error_quark (void); + +GdmSessionWorker * gdm_session_worker_new (const char *server_address) G_GNUC_MALLOC; + +G_END_DECLS + +#endif /* GDM_SESSION_WORKER_H */ diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c index 996c4067..bafc03ab 100644 --- a/daemon/gdm-session.c +++ b/daemon/gdm-session.c @@ -3,6 +3,7 @@ * session.c - authenticates and authorizes users with system * * 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 @@ -32,8 +33,7 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> -#include <grp.h> -#include <pwd.h> + #include <stdlib.h> #include <string.h> #include <syslog.h> @@ -44,14 +44,20 @@ #include <unistd.h> #include <utmp.h> -#include <security/pam_appl.h> - #include <glib.h> #include <glib/gi18n.h> #include <glib/gstdio.h> #include <glib-object.h> +#define DBUS_API_SUBJECT_TO_CHANGE +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> #include "gdm-session.h" +#include "gdm-session-worker-job.h" + +#define GDM_SESSION_DBUS_PATH "/org/gnome/DisplayManager/Session" +#define GDM_SESSION_DBUS_INTERFACE "org.gnome.DisplayManager.Session" + #ifndef GDM_BAD_SESSION_RECORDS_FILE #define GDM_BAD_SESSION_RECORDS_FILE "/var/log/btmp" @@ -89,59 +95,6 @@ #define GDM_MAX_LOG_FILENAME_SIZE 1024 #endif -#ifndef GDM_PASSWD_AUXILLARY_BUFFER_SIZE -#define GDM_PASSWD_AUXILLARY_BUFFER_SIZE 1024 -#endif - -#ifndef GDM_SESSION_MESSAGE_SENTINAL -#define GDM_SESSION_MESSAGE_SENTINAL "sEnTInAL" -#endif - -#ifndef GDM_SESSION_WORKER_MESSAGE_SENTINAL -#define GDM_SESSION_WORKER_MESSAGE_SENTINAL "WoRkeR sEnTInAL" -#endif - -#ifndef GDM_SESSION_DEFAULT_PATH -#define GDM_SESSION_DEFAULT_PATH "/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin" -#endif - -#ifndef GDM_SESSION_ROOT_UID -#define GDM_SESSION_ROOT_UID 0 -#endif - -typedef struct _GdmSessionMessage GdmSessionMessage; -typedef struct _GdmSessionVerificationMessage GdmSessionVerificationMessage; -typedef struct _GdmSessionStartProgramMessage GdmSessionStartProgramMessage; -typedef struct _GdmSessionSetEnvironmentVariableMessage GdmSessionSetEnvironmentVariableMessage; -typedef struct _GdmSessionInfoReplyMessage GdmSessionInfoReplyMessage; -typedef struct _GdmSessionSecretInfoReplyMessage GdmSessionSecretInfoReplyMessage; -typedef struct _GdmSessionWorker GdmSessionWorker; -typedef struct _GdmSessionWorkerMessage GdmSessionWorkerMessage; -typedef struct _GdmSessionWorkerVerifiedMessage GdmSessionWorkerVerifiedMessage; -typedef struct _GdmSessionWorkerVerificationFailedMessage GdmSessionWorkerVerificationFailedMessage; -typedef struct _GdmSessionWorkerUsernameChangedMessage GdmSessionWorkerUsernameChangedMessage; -typedef struct _GdmSessionWorkerInfoRequestMessage GdmSessionWorkerInfoRequestMessage; -typedef struct _GdmSessionWorkerSecretInfoRequestMessage GdmSessionWorkerSecretInfoRequestMessage; -typedef struct _GdmSessionWorkerInfoMessage GdmSessionWorkerInfoMessage; -typedef struct _GdmSessionWorkerProblemMessage GdmSessionWorkerProblemMessage; -typedef struct _GdmSessionWorkerSessionStartedMessage GdmSessionWorkerSessionStartedMessage; -typedef struct _GdmSessionWorkerSessionStartupFailedMessage GdmSessionWorkerSessionStartupFailedMessage; -typedef struct _GdmSessionWorkerSessionExitedMessage GdmSessionWorkerSessionExitedMessage; -typedef struct _GdmSessionWorkerSessionDiedMessage GdmSessionWorkerSessionDiedMessage; - -typedef int (* GdmSessionWorkerPamNewMessagesFunc) (int, - const struct pam_message **, - struct pam_response **, - gpointer); -typedef enum _GdmSessionMessageType { - GDM_SESSION_MESSAGE_TYPE_INVALID = 0, - GDM_SESSION_MESSAGE_TYPE_VERIFICATION = 0xc001deed, - GDM_SESSION_MESSAGE_TYPE_START_PROGRAM, - GDM_SESSION_MESSAGE_TYPE_SET_ENVIRONMENT_VARIABLE, - GDM_SESSION_MESSAGE_TYPE_NEW_LOG_FILENAME, - GDM_SESSION_MESSAGE_TYPE_INFO_REPLY, - GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY -} GdmSessionMessageType; typedef enum _GdmSessionRecordType { GDM_SESSION_RECORD_TYPE_LOGIN, @@ -149,241 +102,28 @@ typedef enum _GdmSessionRecordType { GDM_SESSION_RECORD_TYPE_LOGOUT, } GdmSessionRecordType; -typedef enum _GdmSessionWorkerMessageType { - GDM_SESSION_WORKER_MESSAGE_TYPE_INVALID = 0, - GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFIED = 0xdeadbeef, - GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFICATION_FAILED, - GDM_SESSION_WORKER_MESSAGE_TYPE_USERNAME_CHANGED, - GDM_SESSION_WORKER_MESSAGE_TYPE_INFO_REQUEST, - GDM_SESSION_WORKER_MESSAGE_TYPE_SECRET_INFO_REQUEST, - GDM_SESSION_WORKER_MESSAGE_TYPE_INFO, - GDM_SESSION_WORKER_MESSAGE_TYPE_PROBLEM, - GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTED, - GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTUP_FAILED, - GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_EXITED, - GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_DIED, -} GdmSessionWorkerMessageType; - struct _GdmSessionPrivate { - GPid pid; - - char *service_name; - char **arguments; - char *username; - char *hostname; - char *console_name; - - int standard_output_fd; - int standard_error_fd; - - int worker_message_pipe_fd; - GSource *worker_message_pipe_source; - - GMainContext *context; - - GPid worker_pid; - GSource *child_watch_source; - - GdmSessionMessageType next_expected_message; - - /* variable is only alive briefly to store user - * responses set in callbacks to authentication queries - */ - char *query_answer; - - guint32 is_verified : 1; - guint32 is_running : 1; -}; - -struct _GdmSessionMessage -{ - GdmSessionMessageType type; - gsize size; -}; - -struct _GdmSessionVerificationMessage -{ - GdmSessionMessage header; - - char service_name[GDM_MAX_SERVICE_NAME_SIZE]; - char console_name[GDM_MAX_CONSOLE_NAME_SIZE]; - char hostname[GDM_MAX_HOSTNAME_SIZE]; - - int standard_output_fd; - int standard_error_fd; - - guint32 hostname_is_provided : 1; - - gssize username_size; - char username[0]; -}; - -struct _GdmSessionStartProgramMessage -{ - GdmSessionMessage header; - - gsize arguments_size; - char arguments[0]; -}; - -struct _GdmSessionSetEnvironmentVariableMessage -{ - GdmSessionMessage header; - - gsize environment_variable_size; - char environment_variable[0]; -}; - -struct _GdmSessionInfoReplyMessage -{ - GdmSessionMessage header; - - gssize answer_size; - char answer[0]; -}; - -struct _GdmSessionSecretInfoReplyMessage -{ - GdmSessionMessage header; - - gssize answer_size; - char answer[0]; -}; - -struct _GdmSessionWorker -{ - GMainLoop *event_loop; - int exit_code; - - pam_handle_t *pam_handle; - - int message_pipe_fd; - GSource *message_pipe_source; - - GSList *inherited_fd_list; - - GPid child_pid; - GSource *child_watch_source; - - char *username; - char **arguments; - - GHashTable *environment; - - int standard_output_fd; - int standard_error_fd; - - guint32 credentials_are_established : 1; - guint32 is_running : 1; -}; - -struct _GdmSessionWorkerMessage -{ - GdmSessionWorkerMessageType type; - gsize size; -}; - -struct _GdmSessionWorkerInfoRequestMessage -{ - GdmSessionWorkerMessage header; - - gssize question_size; - char question[0]; -}; - -struct _GdmSessionWorkerUsernameChangedMessage -{ - GdmSessionWorkerMessage header; - - gssize username_size; - char username[0]; -}; - -struct _GdmSessionWorkerSecretInfoRequestMessage -{ - GdmSessionWorkerMessage header; + GdmSessionWorkerJob *job; + GPid session_pid; - gssize question_size; - char question[0]; -}; - -struct _GdmSessionWorkerInfoMessage -{ - GdmSessionWorkerMessage header; - - gssize info_size; - char info[0]; -}; - -struct _GdmSessionWorkerProblemMessage -{ - GdmSessionWorkerMessage header; - - gssize problem_size; - char problem[0]; -}; - -struct _GdmSessionWorkerSessionStartedMessage -{ - GdmSessionWorkerMessage header; - GPid pid; -}; - -struct _GdmSessionWorkerSessionStartupFailedMessage -{ - GdmSessionWorkerMessage header; - GQuark error_domain; - int error_code; - - gssize error_message_size; - char error_message[0]; -}; - -struct _GdmSessionWorkerSessionExitedMessage -{ - GdmSessionWorkerMessage header; - - int exit_code; -}; + char *service_name; + char *username; + char *hostname; + char *console_name; -struct _GdmSessionWorkerSessionDiedMessage -{ - GdmSessionWorkerMessage header; + DBusMessage *message_pending_reply; - int signal_number; -}; - -struct _GdmSessionWorkerVerifiedMessage -{ - GdmSessionWorkerMessage header; -}; + GHashTable *environment; -struct _GdmSessionWorkerVerificationFailedMessage -{ - GdmSessionWorkerMessage header; - GQuark error_domain; - int error_code; + DBusServer *server; + char *server_address; + DBusConnection *worker_connection; - gssize error_message_size; - char error_message[0]; + guint32 is_verified : 1; + guint32 is_running : 1; }; -static GdmSessionWorkerMessage *gdm_session_worker_verified_message_new (void); -static GdmSessionWorkerMessage *gdm_session_worker_verification_failed_message_new (GError *error); -static GdmSessionWorkerMessage *gdm_session_worker_info_request_message_new (const char *question); -static GdmSessionWorkerMessage *gdm_session_worker_username_changed_message_new (const char *new_username); -static GdmSessionWorkerMessage *gdm_session_worker_secret_info_request_message_new (const char *question); -static GdmSessionWorkerMessage *gdm_session_worker_info_message_new (const char *info); -static GdmSessionWorkerMessage *gdm_session_worker_problem_message_new (const char *problem); -static GdmSessionWorkerMessage *gdm_session_worker_session_started_message_new (GPid pid); -static GdmSessionWorkerMessage *gdm_session_worker_session_exited_message_new (int exit_code); -static GdmSessionWorkerMessage *gdm_session_worker_session_died_message_new (int signal_number); -static void gdm_session_worker_message_free (GdmSessionWorkerMessage *message); - -static gboolean gdm_session_worker_process_asynchronous_message (GdmSessionWorker *worker, - GdmSessionMessage *message); - enum { USER_VERIFIED = 0, USER_VERIFICATION_ERROR, @@ -395,6 +135,8 @@ enum { SESSION_STARTUP_ERROR, SESSION_EXITED, SESSION_DIED, + OPENED, + CLOSED, LAST_SIGNAL }; @@ -413,6 +155,74 @@ gdm_session_error_quark (void) return error_quark; } +static gboolean +send_dbus_message (DBusConnection *connection, + DBusMessage *message) +{ + gboolean is_connected; + gboolean sent; + + g_return_val_if_fail (message != NULL, FALSE); + + if (connection == NULL) { + g_warning ("There is no valid connection"); + return FALSE; + } + + is_connected = dbus_connection_get_is_connected (connection); + if (! is_connected) { + g_warning ("Not connected!"); + return FALSE; + } + + sent = dbus_connection_send (connection, message, NULL); + + return sent; +} + +static void +send_dbus_string_signal (GdmSession *session, + const char *name, + const char *text) +{ + DBusMessage *message; + DBusMessageIter iter; + + g_return_if_fail (session != NULL); + + message = dbus_message_new_signal (GDM_SESSION_DBUS_PATH, + GDM_SESSION_DBUS_INTERFACE, + name); + + dbus_message_iter_init_append (message, &iter); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &text); + + if (! send_dbus_message (session->priv->worker_connection, message)) { + g_debug ("Could not send %s signal", name); + } + + dbus_message_unref (message); +} + +static void +send_dbus_void_signal (GdmSession *session, + const char *name) +{ + DBusMessage *message; + + g_return_if_fail (session != NULL); + + message = dbus_message_new_signal (GDM_SESSION_DBUS_PATH, + GDM_SESSION_DBUS_INTERFACE, + name); + + if (! send_dbus_message (session->priv->worker_connection, message)) { + g_debug ("Could not send %s signal", name); + } + + dbus_message_unref (message); +} + static void gdm_session_write_record (GdmSession *session, GdmSessionRecordType record_type) @@ -500,10 +310,9 @@ gdm_session_write_record (GdmSession *session, session_record.ut_type = USER_PROCESS; g_debug ("using type USER_PROCESS"); - if (session->priv->pid != 0) - session_record.ut_pid = session->priv->pid; - else - session_record.ut_pid = session->priv->worker_pid; + if (session->priv->session_pid != 0) { + session_record.ut_pid = session->priv->session_pid; + } g_debug ("using pid %d", (int) session_record.ut_pid); @@ -565,6 +374,27 @@ gdm_session_class_install_signals (GdmSessionClass *session_class) object_class = G_OBJECT_CLASS (session_class); + gdm_session_signals[OPENED] = + g_signal_new ("opened", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmSessionClass, opened), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + gdm_session_signals[CLOSED] = + g_signal_new ("closed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmSessionClass, closed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + session_class->user_verified = NULL; gdm_session_signals[USER_VERIFIED] = g_signal_new ("user-verified", G_OBJECT_CLASS_TYPE (object_class), @@ -685,7 +515,7 @@ gdm_session_class_install_signals (GdmSessionClass *session_class) g_signal_new ("session-died", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (GdmSessionClass, session_exited), + G_STRUCT_OFFSET (GdmSessionClass, session_died), NULL, NULL, g_cclosure_marshal_VOID__INT, @@ -703,11 +533,15 @@ gdm_session_finalize (GObject *object) session = GDM_SESSION (object); - g_strfreev (session->priv->arguments); g_free (session->priv->username); parent_class = G_OBJECT_CLASS (gdm_session_parent_class); + if (session->priv->environment != NULL) { + g_hash_table_destroy (session->priv->environment); + session->priv->environment = NULL; + } + if (parent_class->finalize != NULL) parent_class->finalize (object); } @@ -727,3292 +561,937 @@ gdm_session_class_init (GdmSessionClass *session_class) sizeof (GdmSessionPrivate)); } -static void -gdm_session_init (GdmSession *session) -{ - session->priv = G_TYPE_INSTANCE_GET_PRIVATE (session, - GDM_TYPE_SESSION, - GdmSessionPrivate); -} - -static gboolean -gdm_read_message (int socket_fd, - gpointer *message_data, - gsize *message_data_size, - char **error_message) -{ - struct msghdr message = { 0 }; - struct iovec data_block = { 0 }; - const int flags = MSG_NOSIGNAL; - - gssize num_bytes_received; - - message.msg_iov = &data_block; - message.msg_iovlen = 1; - - data_block.iov_base = g_malloc0 (GDM_MAX_MESSAGE_SIZE); - data_block.iov_len = (size_t) GDM_MAX_MESSAGE_SIZE; - - num_bytes_received = 0; - do { - num_bytes_received = recvmsg (socket_fd, &message, flags); - - if (num_bytes_received > 0) { - g_debug ("read '%lu' bytes over socket '%d'", - (gulong) num_bytes_received, - socket_fd); - } - } - while (!(num_bytes_received > 0) && (errno == EINTR)); - - if ((num_bytes_received < 0) && (error_message != NULL)) { - const char *error; - error = g_strerror (errno); - g_debug ("message was not received: %s", error); - *error_message = g_strdup (error); - return FALSE; - } else if (num_bytes_received == 0) { - g_debug ("message was not received: premature eof"); - *error_message = g_strdup ("premature eof"); - return FALSE; - } - - g_debug ("message was received!"); - if (message_data != NULL) { - *message_data = g_realloc (data_block.iov_base, num_bytes_received); - *message_data_size = (gsize) num_bytes_received; - } else { - g_free (message.msg_iov); - } - - return TRUE; -} - -static GdmSessionMessage * -gdm_session_verification_message_new (const char *service_name, - const char *username, - const char *hostname, - const char *console_name, - int standard_output_fd, - int standard_error_fd) +static DBusHandlerResult +gdm_session_handle_verified (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - GdmSessionVerificationMessage *message; - gsize size, username_size; - - username_size = (username != NULL? strlen (username) + 1 : 0); - size = sizeof (GdmSessionVerificationMessage) + - username_size + - sizeof (GDM_SESSION_MESSAGE_SENTINAL); + DBusMessage *reply; + DBusError error; - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_MESSAGE_TYPE_VERIFICATION; - message->header.size = size; - - g_strlcpy (message->service_name, service_name, - sizeof (message->service_name)); - - if (username != NULL) { - g_strlcpy (message->username, username, username_size); - message->username_size = username_size; - } else { - message->username_size = -1; - } - - if (hostname != NULL) { - g_strlcpy (message->hostname, hostname, sizeof (message->hostname)); - message->hostname_is_provided = TRUE; - } else { - message->hostname_is_provided = FALSE; - } - - g_strlcpy (message->console_name, console_name, sizeof (message->console_name)); - - message->standard_output_fd = standard_output_fd; - message->standard_error_fd = standard_error_fd; - - g_strlcpy ((char *) (char *) ((guint *) message) + size - - sizeof (GDM_SESSION_MESSAGE_SENTINAL), - GDM_SESSION_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_MESSAGE_SENTINAL)); - - return (GdmSessionMessage *) message; -} - -static char * -gdm_session_flatten_arguments (const char * const *argv, - gsize *arguments_size) -{ - char *arguments; - gsize i, total_size, argument_size; - - total_size = 0; - for (i = 0; argv[i] != NULL; i++) { - argument_size = strlen (argv[i]) + 1; - total_size += argument_size; - } - total_size++; - - arguments = g_slice_alloc0 (total_size); - - total_size = 0; - for (i = 0; argv[i] != NULL; i++) { - argument_size = strlen (argv[i]) + 1; - g_strlcpy (arguments + total_size, - argv[i], argument_size); + g_debug ("Emitting 'user-verified' signal"); - total_size += argument_size; - } - total_size++; + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); - if (arguments_size) - *arguments_size = total_size; + session->priv->is_verified = TRUE; + g_signal_emit (session, + gdm_session_signals[USER_VERIFIED], + 0); - return arguments; + return DBUS_HANDLER_RESULT_HANDLED; } -static char ** -gdm_session_unflatten_arguments (const char *arguments) +static DBusHandlerResult +gdm_session_handle_verification_failed (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - GPtrArray *array; - char *argument; + DBusMessage *reply; + DBusError error; + const char *text; - array = g_ptr_array_new (); - - argument = (char *) arguments; - while (*argument != '\0') { - g_ptr_array_add (array, g_strdup (argument)); - argument += strlen (argument) + 1; + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - g_ptr_array_add (array, NULL); - - return (char **) g_ptr_array_free (array, FALSE); -} - -static GdmSessionMessage * -gdm_session_start_program_message_new (const char * const * args) -{ - GdmSessionStartProgramMessage *message; - char *arguments; - gsize size; - gsize arguments_size; - - g_assert (args != NULL); - arguments_size = 0; - arguments = gdm_session_flatten_arguments (args, &arguments_size); - - size = sizeof (GdmSessionStartProgramMessage) + - arguments_size + - sizeof (GDM_SESSION_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_MESSAGE_TYPE_START_PROGRAM; - message->header.size = size; - - memcpy (message->arguments, arguments, arguments_size); - g_slice_free1 (arguments_size, arguments); - message->arguments_size = arguments_size; - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_MESSAGE_SENTINAL), - GDM_SESSION_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_MESSAGE_SENTINAL)); - - return (GdmSessionMessage *) message; -} - -static GdmSessionMessage * -gdm_session_set_environment_variable_message_new (const char *name, - const char *value) -{ - GdmSessionSetEnvironmentVariableMessage *message; - char *environment_variable; - gsize size; - gsize environment_variable_size; - int length; + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); - g_assert (name != NULL); - g_assert (strchr (name, '=') == NULL); - g_assert (value != NULL); - - environment_variable = g_strdup_printf ("%s=%s%n", name, value, &length); - environment_variable_size = length + 1; - - size = sizeof (GdmSessionSetEnvironmentVariableMessage) + - environment_variable_size + - sizeof (GDM_SESSION_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_MESSAGE_TYPE_SET_ENVIRONMENT_VARIABLE; - message->header.size = size; - - g_strlcpy (message->environment_variable, - environment_variable, environment_variable_size); - g_free (environment_variable); - message->environment_variable_size = environment_variable_size; + g_debug ("Emitting 'verification-failed' signal"); - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_MESSAGE_SENTINAL), - GDM_SESSION_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_MESSAGE_SENTINAL)); + g_signal_emit (session, + gdm_session_signals[USER_VERIFICATION_ERROR], + 0, text); - return (GdmSessionMessage *) message; + return DBUS_HANDLER_RESULT_HANDLED; } -static GdmSessionMessage * -gdm_session_info_reply_message_new (const char *answer) +static DBusHandlerResult +gdm_session_handle_username_changed (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - GdmSessionInfoReplyMessage *message; - gsize size; - gsize answer_size; - - answer_size = (answer != NULL? strlen (answer) + 1 : 0); - size = sizeof (GdmSessionInfoReplyMessage) + - answer_size + - sizeof (GDM_SESSION_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); + DBusMessage *reply; + DBusError error; + const char *text; - message->header.type = GDM_SESSION_MESSAGE_TYPE_INFO_REPLY; - message->header.size = size; - - if (answer != NULL) { - g_strlcpy (message->answer, answer, answer_size); - message->answer_size = answer_size; - } else { - message->answer_size = -1; + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_MESSAGE_SENTINAL), - GDM_SESSION_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_MESSAGE_SENTINAL)); - - return (GdmSessionMessage *) message; -} - -static GdmSessionMessage * -gdm_session_secret_info_reply_message_new (const char *answer) -{ - GdmSessionSecretInfoReplyMessage *message; - gsize size; - gsize answer_size; - - answer_size = (answer != NULL? strlen (answer) + 1 : 0); - size = sizeof (GdmSessionSecretInfoReplyMessage) + - answer_size + - sizeof (GDM_SESSION_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY; - message->header.size = size; + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); - if (answer != NULL) { - g_strlcpy (message->answer, answer, answer_size); - message->answer_size = answer_size; - } else { - message->answer_size = -1; - } + g_debug ("changing username from '%s' to '%s'", + session->priv->username != NULL ? session->priv->username : "<unset>", + (strlen (text)) ? text : "<unset>"); - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_MESSAGE_SENTINAL), - GDM_SESSION_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_MESSAGE_SENTINAL)); + g_free (session->priv->username); + session->priv->username = (strlen (text) > 0) ? g_strdup (text) : NULL; - return (GdmSessionMessage *) message; + return DBUS_HANDLER_RESULT_HANDLED; } static void -gdm_session_message_free (GdmSessionMessage *message) -{ - g_slice_free1 (message->size, message); -} - - -GdmSession * -gdm_session_new (void) +answer_pending_query (GdmSession *session, + const char *answer) { - GdmSession *session; + DBusMessage *reply; + DBusMessageIter iter; - session = g_object_new (GDM_TYPE_SESSION, NULL); + reply = dbus_message_new_method_return (session->priv->message_pending_reply); + dbus_message_iter_init_append (reply, &iter); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &answer); - return session; -} - -static void -gdm_session_clear_child_watch_source (GdmSession *session) -{ - session->priv->child_watch_source = NULL; -} + dbus_connection_send (session->priv->worker_connection, reply, NULL); + dbus_message_unref (reply); -static void -gdm_session_clear_message_pipe_source (GdmSession *session) -{ - session->priv->worker_message_pipe_source = NULL; + dbus_message_unref (session->priv->message_pending_reply); + session->priv->message_pending_reply = NULL; } static void -gdm_session_on_child_exited (GPid pid, - int status, - GdmSession *session) +set_pending_query (GdmSession *session, + DBusMessage *message) { - GError *error; - - if (WIFEXITED (status)) { - if (!session->priv->is_verified) { - error = g_error_new (GDM_SESSION_ERROR, - GDM_SESSION_ERROR_WORKER_DIED, - _("worker exited with status %d"), - WEXITSTATUS (status)); - - g_signal_emit (session, - gdm_session_signals[USER_VERIFICATION_ERROR], - 0, error); - g_error_free (error); - } else if (session->priv->is_running) { - g_signal_emit (session, - gdm_session_signals[SESSION_EXITED], - 0, WEXITSTATUS (status)); - } - } else if (WIFSIGNALED (status)) { - if (!session->priv->is_verified) { - error = g_error_new (GDM_SESSION_ERROR, - GDM_SESSION_ERROR_WORKER_DIED, - _("worker got signal '%s' and was subsequently killed"), - g_strsignal (WTERMSIG (status))); - g_signal_emit (session, - gdm_session_signals[USER_VERIFICATION_ERROR], - 0, error); - g_error_free (error); - } else if (session->priv->is_running) { - g_signal_emit (session, - gdm_session_signals[SESSION_EXITED], - 0, WTERMSIG (status)); - } - } else { - /* WIFSTOPPED (status) || WIFCONTINUED (status) */ - } + g_assert (session->priv->message_pending_reply == NULL); - g_spawn_close_pid (pid); - session->priv->worker_pid = 0; + session->priv->message_pending_reply = dbus_message_ref (message); } -static gboolean -gdm_session_validate_message_size (GdmSession *session, - GdmSessionWorkerMessage *message, - GError **error) +static DBusHandlerResult +gdm_session_handle_info_query (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - gsize expected_size; - - return TRUE; - - switch (message->type) { - case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFIED: - expected_size = (gsize) sizeof (GdmSessionWorkerVerifiedMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFICATION_FAILED: - expected_size = (gsize) sizeof (GdmSessionWorkerVerificationFailedMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_USERNAME_CHANGED: - expected_size = (gsize) sizeof (GdmSessionWorkerUsernameChangedMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO_REQUEST: - expected_size = (gsize) sizeof (GdmSessionWorkerInfoRequestMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SECRET_INFO_REQUEST: - expected_size = (gsize) sizeof (GdmSessionWorkerSecretInfoRequestMessage); - break; + DBusError error; + const char *text; - case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO: - expected_size = (gsize) sizeof (GdmSessionWorkerInfoMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_PROBLEM: - expected_size = (gsize) sizeof (GdmSessionWorkerProblemMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTED: - expected_size = (gsize) sizeof (GdmSessionWorkerSessionStartedMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTUP_FAILED: - expected_size = (gsize) sizeof (GdmSessionWorkerSessionStartupFailedMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_EXITED: - expected_size = (gsize) sizeof (GdmSessionWorkerSessionExitedMessage); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_DIED: - expected_size = (gsize) sizeof (GdmSessionWorkerSessionDiedMessage); - break; - - default: - g_debug ("do not know about message type '0x%x'", (guint) message->type); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - _("do not know about message type '0x%x'"), - (guint) message->type); - return FALSE; + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - if (message->size != expected_size) { - g_debug ("message size was '%lu', but message was supposed " - "to be '%lu'", (gulong) message->size, (gulong) expected_size); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - "message size was '%lu', but message was supposed " - "to be '%lu'", (gulong) message->size, - (gulong) expected_size); - return FALSE; - } - - return TRUE; -} - -static GdmSessionWorkerMessage * -gdm_session_get_incoming_message (GdmSession *session, - GError **error) -{ - GError *size_error; - char *error_message; - GdmSessionWorkerMessage *message; - gsize message_size; - gboolean res; - - g_debug ("attemping to read message from worker..."); - error_message = NULL; - res = gdm_read_message (session->priv->worker_message_pipe_fd, - (gpointer) &message, - &message_size, - &error_message); - if (! res) { - g_debug ("could not read message from worker: %s", error_message); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - "%s", error_message); - g_free (error_message); - return NULL; - } - g_debug ("message type is '0x%x'", message->type); - - if (message_size != message->size) { - g_debug ("message reports to be '%ld' bytes but is actually '%ld'", - (glong) message->size, (glong) message_size); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - _("specified message size does not match size of message " - "received")); - return NULL; - } - g_debug ("message size is '%lu'", (gulong) message_size); - - g_debug ("validating that message size is right for message " - "type..."); - size_error = NULL; - res = gdm_session_validate_message_size (session, message, &size_error); - if (! res) { - g_propagate_error (error, size_error); - return NULL; - } - g_debug ("message size is valid"); - - switch (message->type) { - case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFIED: - g_debug ("received valid 'verified' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFICATION_FAILED: - g_debug ("received valid 'verification failed' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_USERNAME_CHANGED: - g_debug ("received valid 'username changed' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO_REQUEST: - g_debug ("received valid 'info request' message from worker"); - g_debug ("***********MESSAGE QUESTION IS '%s'********", - ((GdmSessionWorkerInfoRequestMessage *) message)->question); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SECRET_INFO_REQUEST: - g_debug ("received valid 'secret info request' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO: - g_debug ("received valid 'info' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_PROBLEM: - g_debug ("received valid 'problem' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTED: - g_debug ("received valid 'session started' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTUP_FAILED: - g_debug ("received valid 'session startup failed' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_EXITED: - g_debug ("received valid 'session exited' message from worker"); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_DIED: - g_debug ("received valid 'session died' message from worker"); - break; - - default: - g_debug ("received unknown message of type '0x%x'", - (guint) message->type); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - _("received unknown message")); - gdm_session_worker_message_free (message); - message = NULL; - break; - } - - return message; -} - -static void -gdm_session_handle_verified_message (GdmSession *session, - GdmSessionWorkerVerifiedMessage *message) -{ - g_debug ("Emitting 'user-verified' signal"); - - session->priv->is_verified = TRUE; - g_signal_emit (session, - gdm_session_signals[USER_VERIFIED], - 0); -} - -static void -gdm_session_handle_verification_failed_message (GdmSession *session, - GdmSessionWorkerVerificationFailedMessage *message) -{ - GError *error; - - g_debug ("Emitting 'verification-failed' signal"); - - error = g_error_new (message->error_domain, - message->error_code, - "%s", message->error_message); - - g_signal_emit (session, - gdm_session_signals[USER_VERIFICATION_ERROR], - 0, error); - g_error_free (error); -} - -static void -gdm_session_handle_username_changed_message (GdmSession *session, - GdmSessionWorkerUsernameChangedMessage *message) -{ - g_debug ("changing username from '%s' to '%s'", - session->priv->username != NULL? session->priv->username : "<unset>", - (message->username_size >= 0)? message->username : "<unset>"); - g_free (session->priv->username); - session->priv->username = (message->username_size >= 0)? g_strdup (message->username) : NULL; -} - -static void -gdm_session_handle_info_request_message (GdmSession*session, - GdmSessionWorkerInfoRequestMessage *message) -{ - g_assert (session->priv->query_answer == NULL); - - session->priv->next_expected_message = GDM_SESSION_MESSAGE_TYPE_INFO_REPLY; + set_pending_query (session, message); g_debug ("Emitting 'info-query' signal"); g_signal_emit (session, gdm_session_signals[INFO_QUERY], - 0, message->question); -} - -static void -gdm_session_handle_secret_info_request_message (GdmSession *session, - GdmSessionWorkerSecretInfoRequestMessage *message) -{ - g_assert (session->priv->query_answer == NULL); + 0, text); - session->priv->next_expected_message = GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY; - - g_debug ("Emitting 'secret-info-query' signal"); - - g_signal_emit (session, - gdm_session_signals[SECRET_INFO_QUERY], - 0, message->question); + return DBUS_HANDLER_RESULT_HANDLED; } -static void -gdm_session_handle_info_message (GdmSession *session, - GdmSessionWorkerInfoMessage *message) +static DBusHandlerResult +gdm_session_handle_secret_info_query (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - g_debug ("Emitting 'info' signal"); - g_signal_emit (session, - gdm_session_signals[INFO], - 0, message->info); + DBusMessage *reply; + const char *text; -} - -static void -gdm_session_handle_problem_message (GdmSession *session, - GdmSessionWorkerProblemMessage *message) -{ - g_debug ("Emitting 'problem' signal"); - g_signal_emit (session, - gdm_session_signals[PROBLEM], - 0, message->problem); -} - -static void -gdm_session_handle_session_started_message (GdmSession *session, - GdmSessionWorkerSessionStartedMessage *message) -{ - g_debug ("Emitting 'session-started' signal with pid '%d'", - (int) message->pid); - - session->priv->pid = message->pid; - session->priv->is_running = TRUE; - - g_signal_emit (session, - gdm_session_signals[SESSION_STARTED], - 0, (int) message->pid); -} - -static void -gdm_session_handle_session_startup_failed_message (GdmSession *session, - GdmSessionWorkerSessionStartupFailedMessage *message) -{ - GError *error; + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); + } - g_debug ("Emitting 'session-startup-error' signal"); + set_pending_query (session, message); - error = g_error_new (message->error_domain, - message->error_code, - "%s", message->error_message); - g_signal_emit (session, - gdm_session_signals[SESSION_STARTUP_ERROR], - 0, error); - g_error_free (error); -} - -static void -gdm_session_handle_session_exited_message (GdmSession *session, - GdmSessionWorkerSessionExitedMessage *message) -{ - g_debug ("Emitting 'session-exited' signal with exit code '%d'", - message->exit_code); + g_debug ("Emitting 'secret-info-query' signal"); - session->priv->is_running = FALSE; g_signal_emit (session, - gdm_session_signals[SESSION_EXITED], - 0, message->exit_code); -} - -static void -gdm_session_handle_session_died_message (GdmSession *session, - GdmSessionWorkerSessionDiedMessage *message) -{ - g_debug ("Emitting 'session-died' signal with signal number '%d'", - message->signal_number); + gdm_session_signals[SECRET_INFO_QUERY], + 0, text); - session->priv->is_running = FALSE; - g_signal_emit (session, - gdm_session_signals[SESSION_DIED], - 0, message->signal_number); + return DBUS_HANDLER_RESULT_HANDLED; } -static void -gdm_session_incoming_message_handler (GdmSession *session) +static DBusHandlerResult +gdm_session_handle_info (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - GdmSessionWorkerMessage *message; - GError *error; - - error = NULL; - message = gdm_session_get_incoming_message (session, &error); + DBusMessage *reply; + DBusError error; + const char *text; - if (message == NULL) { - g_assert (error != NULL); - g_warning ("could not receive message from parent: %s\n", error->message); - g_error_free (error); - - /* FIXME: figure out what to do here - */ - return; + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - switch (message->type) { - case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFIED: - g_debug ("worker successfully verified user"); - gdm_session_handle_verified_message (session, - (GdmSessionWorkerVerifiedMessage *) - message); - - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFICATION_FAILED: - g_debug ("worker could not verify user"); - gdm_session_handle_verification_failed_message (session, - (GdmSessionWorkerVerificationFailedMessage *) - message); - - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_USERNAME_CHANGED: - g_debug ("received changed username from worker"); - gdm_session_handle_username_changed_message (session, - (GdmSessionWorkerUsernameChangedMessage *) message); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO_REQUEST: - g_debug ("received info request from worker"); - gdm_session_handle_info_request_message (session, - (GdmSessionWorkerInfoRequestMessage *) message); - break; + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); - case GDM_SESSION_WORKER_MESSAGE_TYPE_SECRET_INFO_REQUEST: - g_debug ("received secret info request from worker"); - gdm_session_handle_secret_info_request_message (session, - (GdmSessionWorkerSecretInfoRequestMessage *) message); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO: - g_debug ("received new info from worker"); - gdm_session_handle_info_message (session, - (GdmSessionWorkerInfoMessage *) message); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_PROBLEM: - g_debug ("received problem from worker"); - gdm_session_handle_problem_message (session, - (GdmSessionWorkerProblemMessage *) message); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTED: - g_debug ("received session started message from worker"); - gdm_session_handle_session_started_message (session, - (GdmSessionWorkerSessionStartedMessage *) message); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTUP_FAILED: - g_debug ("received session startup failed message from worker"); - gdm_session_handle_session_startup_failed_message (session, - (GdmSessionWorkerSessionStartupFailedMessage *) message); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_EXITED: - g_debug ("received session exited message from worker"); - gdm_session_handle_session_exited_message (session, - (GdmSessionWorkerSessionExitedMessage *) message); - break; - - case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_DIED: - g_debug ("received session died message from worker"); - gdm_session_handle_session_died_message (session, - (GdmSessionWorkerSessionDiedMessage *) message); - break; - - default: - g_debug ("received unknown message of type '0x%x' from worker", - message->type); - break; - } + g_debug ("Emitting 'info' signal"); + g_signal_emit (session, + gdm_session_signals[INFO], + 0, text); - gdm_session_worker_message_free (message); + return DBUS_HANDLER_RESULT_HANDLED; } -static gboolean -gdm_session_data_on_message_pipe_handler (GIOChannel *channel, - GIOCondition condition, - GdmSession *session) +static DBusHandlerResult +gdm_session_handle_problem (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - if ((condition & G_IO_IN) || (condition & G_IO_PRI)) { - g_debug ("got message from message pipe"); - gdm_session_incoming_message_handler (session); - } + DBusMessage *reply; + DBusError error; + const char *text; - if ((condition & G_IO_ERR) || (condition & G_IO_HUP)) { - g_debug ("got disconnected message from message pipe"); - g_error("not implemented"); - /*gdm_session_disconnected_handler (session);*/ - return FALSE; + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - return TRUE; -} + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); -static void -gdm_session_worker_clear_child_watch_source (GdmSessionWorker *worker) -{ - worker->child_watch_source = NULL; + g_debug ("Emitting 'problem' signal"); + g_signal_emit (session, + gdm_session_signals[PROBLEM], + 0, text); + return DBUS_HANDLER_RESULT_HANDLED; } -static void -gdm_session_watch_child (GdmSession *session) +static DBusHandlerResult +gdm_session_handle_session_started (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - GIOChannel *io_channel; - GIOFlags channel_flags; - - g_assert (session->priv->child_watch_source == NULL); - - session->priv->child_watch_source = g_child_watch_source_new (session->priv->worker_pid); - g_source_set_callback (session->priv->child_watch_source, - (GChildWatchFunc) gdm_session_on_child_exited, - session, - (GDestroyNotify) gdm_session_clear_child_watch_source); - g_source_attach (session->priv->child_watch_source, session->priv->context); - g_source_unref (session->priv->child_watch_source); - - io_channel = g_io_channel_unix_new (session->priv->worker_message_pipe_fd); - - channel_flags = g_io_channel_get_flags (io_channel); - g_io_channel_set_flags (io_channel, - channel_flags | G_IO_FLAG_NONBLOCK, - NULL); - - g_assert (session->priv->worker_message_pipe_source == NULL); - - session->priv->worker_message_pipe_source = g_io_create_watch (io_channel, - G_IO_IN | G_IO_HUP); - g_io_channel_unref (io_channel); - - g_source_set_callback (session->priv->worker_message_pipe_source, - (GIOFunc) gdm_session_data_on_message_pipe_handler, - session, - (GDestroyNotify) gdm_session_clear_message_pipe_source); - g_source_attach (session->priv->worker_message_pipe_source, session->priv->context); - g_source_unref (session->priv->worker_message_pipe_source); -} + DBusMessage *reply; + DBusError error; + int pid; -static void -gdm_session_unwatch_child (GdmSession *session) -{ - if (session->priv->child_watch_source == NULL) { - return; + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_INT32, &pid, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - g_source_destroy (session->priv->child_watch_source); - session->priv->child_watch_source = NULL; - - g_assert (session->priv->worker_message_pipe_source != NULL); - - g_source_destroy (session->priv->worker_message_pipe_source); - session->priv->worker_message_pipe_source = NULL; -} - -static void -gdm_session_worker_clear_message_pipe_source (GdmSessionWorker *worker) -{ - worker->message_pipe_source = NULL; -} - -static void -gdm_session_worker_disconnected_handler (GdmSessionWorker *worker) -{ - g_debug ("exiting..."); - worker->exit_code = 127; - g_main_loop_quit (worker->event_loop); -} - -static gboolean -gdm_session_worker_validate_message_size (GdmSessionWorker *worker, - GdmSessionMessage *message, - GError **error) -{ - gsize expected_size; + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); - return TRUE; - - switch (message->type) { - case GDM_SESSION_MESSAGE_TYPE_VERIFICATION: - expected_size = (gsize) sizeof (GdmSessionVerificationMessage); - break; - case GDM_SESSION_MESSAGE_TYPE_START_PROGRAM: - expected_size = (gsize) sizeof (GdmSessionStartProgramMessage); - break; - case GDM_SESSION_MESSAGE_TYPE_SET_ENVIRONMENT_VARIABLE: - expected_size = (gsize) sizeof (GdmSessionSetEnvironmentVariableMessage); - break; - case GDM_SESSION_MESSAGE_TYPE_INFO_REPLY: - expected_size = (gsize) sizeof (GdmSessionInfoReplyMessage); - break; - case GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY: - expected_size = (gsize) sizeof (GdmSessionSecretInfoReplyMessage); - break; - - default: - g_debug ("do not know about message type '0x%x'", - (guint) message->type); + g_debug ("Emitting 'session-started' signal with pid '%d'", + pid); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - _("do not know about message type '0x%x'"), - (guint) message->type); - return FALSE; - } + session->priv->session_pid = pid; + session->priv->is_running = TRUE; - if (message->size != expected_size) { - g_debug ("message size was '%lu', but message was supposed " - "to be '%lu'", - (gulong) message->size, - (gulong) expected_size); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - "message size was '%lu', but message was supposed " - "to be '%lu'", (gulong) message->size, - (gulong) expected_size); - return FALSE; - } + g_signal_emit (session, + gdm_session_signals[SESSION_STARTED], + 0, pid); - return TRUE; + return DBUS_HANDLER_RESULT_HANDLED; } -static GdmSessionMessage * -gdm_session_worker_get_incoming_message (GdmSessionWorker *worker, - GError **error) +static DBusHandlerResult +gdm_session_handle_startup_failed (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - GError *size_error; - char *error_message; - GdmSessionMessage *message; - gsize message_size; - gboolean res; - - g_debug ("attemping to read message from parent..."); - error_message = NULL; - res = gdm_read_message (worker->message_pipe_fd, - (gpointer) &message, - &message_size, - &error_message); - if (! res) { - g_debug ("could not read message from parent: %s", error_message); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - "%s", error_message); - g_free (error_message); - return NULL; - } - g_debug ("message type is '0x%x'", message->type); - - if (message_size != message->size) { - g_debug ("message reports to be '%ld' bytes but is actually '%ld'", - (glong) message->size, - (glong) message_size); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - _("specified message size does not match size of message " - "received")); - return NULL; - } - g_debug ("message size is '%lu'", (gulong) message_size); + DBusMessage *reply; + DBusError error; + const char *text; - g_debug ("validating that message size is right for message " - "type..."); - - size_error = NULL; - res = gdm_session_worker_validate_message_size (worker, message, &size_error); - if (! res) { - g_propagate_error (error, size_error); - return NULL; + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - g_debug ("message size is valid"); - switch (message->type) { - case GDM_SESSION_MESSAGE_TYPE_VERIFICATION: - g_debug ("user session verification request received"); - break; + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); - case GDM_SESSION_MESSAGE_TYPE_START_PROGRAM: - g_debug ("session arguments received"); - break; - - case GDM_SESSION_MESSAGE_TYPE_SET_ENVIRONMENT_VARIABLE: - g_debug ("environment variable received"); - break; - - case GDM_SESSION_MESSAGE_TYPE_INFO_REPLY: - g_debug ("user session info reply received"); - break; - - case GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY: - g_debug ("user session secret info reply received"); - break; + g_debug ("Emitting 'session-startup-error' signal"); - default: - g_debug ("do not know about message type '0x%x'", - (guint) message->type); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_COMMUNICATING, - _("received unknown message type")); - gdm_session_message_free (message); - message = NULL; - break; - } + g_signal_emit (session, + gdm_session_signals[SESSION_STARTUP_ERROR], + 0, text); - return message; + return DBUS_HANDLER_RESULT_HANDLED; } -static gboolean -gdm_write_message (int socket_fd, - gpointer message_data, - gsize message_data_size, - char **error_message) +static DBusHandlerResult +gdm_session_handle_session_exited (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - struct msghdr message = { 0 }; - struct iovec data_block = { 0 }; - const int flags = MSG_NOSIGNAL; - gboolean message_was_sent; - - message.msg_iov = &data_block; - message.msg_iovlen = 1; - - g_assert (message_data_size <= GDM_MAX_MESSAGE_SIZE); - - data_block.iov_base = message_data; - data_block.iov_len = (size_t) message_data_size; + DBusMessage *reply; + DBusError error; + int code; - message_was_sent = FALSE; - do { - gssize num_bytes_sent; - num_bytes_sent = sendmsg (socket_fd, &message, flags); - - if (num_bytes_sent > 0) { - g_debug ("sent '%lu' bytes over socket '%d'", - (gulong) num_bytes_sent, - socket_fd); - message_was_sent = TRUE; - } + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_INT32, &code, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - while (! message_was_sent && (errno == EINTR)); + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); - if (! message_was_sent && (error_message != NULL)) { - const char *error; - error = g_strerror (errno); - g_debug ("message was not sent: %s", error); - *error_message = g_strdup (error); - } else if (message_was_sent) { - g_debug ("message was sent!"); - } + g_debug ("Emitting 'session-exited' signal with exit code '%d'", + code); - g_free (message.msg_control); + session->priv->is_running = FALSE; + g_signal_emit (session, + gdm_session_signals[SESSION_EXITED], + 0, code); - return message_was_sent; + return DBUS_HANDLER_RESULT_HANDLED; } -static char * -gdm_session_worker_ask_question (GdmSessionWorker *worker, - const char *question) +static DBusHandlerResult +gdm_session_handle_session_died (GdmSession *session, + DBusConnection *connection, + DBusMessage *message) { - GdmSessionWorkerMessage *message; - GdmSessionMessage *reply; - GdmSessionInfoReplyMessage *info_reply; - char *answer; - GError *error; - - message = gdm_session_worker_info_request_message_new (question); - gdm_write_message (worker->message_pipe_fd, message, message->size, NULL); - gdm_session_worker_message_free (message); + DBusMessage *reply; + DBusError error; + int code; - error = NULL; - do { - gboolean res; - - reply = gdm_session_worker_get_incoming_message (worker, &error); - - if (reply == NULL) { - g_warning ("could not receive message from parent: %s", error->message); - g_error_free (error); - - /* FIXME: figure out what to do here - */ - return NULL; - } - - res = gdm_session_worker_process_asynchronous_message (worker, reply); - if (res) { - gdm_session_message_free (reply); - reply = NULL; - } + dbus_error_init (&error); + if (! dbus_message_get_args (message, &error, + DBUS_TYPE_INT32, &code, + DBUS_TYPE_INVALID)) { + g_warning ("ERROR: %s", error.message); } - while (reply == NULL); - /* FIXME: we have to do something better here. Messages aren't gauranteed to - * be delivered synchronously. We need to either fix that, or fix this to not - * expect them to be. - */ - if (reply->type != GDM_SESSION_MESSAGE_TYPE_INFO_REPLY) { - g_debug ("discarding unexpected message of type 0x%x", reply->type); - gdm_session_message_free (reply); - reply = NULL; - return NULL; - } + reply = dbus_message_new_method_return (message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); - info_reply = (GdmSessionInfoReplyMessage *) reply; - if (info_reply->answer_size >= 0) - answer = g_strdup (info_reply->answer); - else - answer = NULL; - gdm_session_message_free (reply); - reply = NULL; + g_debug ("Emitting 'session-died' signal with signal number '%d'", + code); - return answer; -} + session->priv->is_running = FALSE; + g_signal_emit (session, + gdm_session_signals[SESSION_DIED], + 0, code); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +session_worker_message (DBusConnection *connection, + DBusMessage *message, + void *user_data) +{ + GdmSession *session = GDM_SESSION (user_data); + + if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "Verified")) { + return gdm_session_handle_verified (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "VerificationFailed")) { + return gdm_session_handle_verification_failed (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "UsernameChanged")) { + return gdm_session_handle_username_changed (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "InfoQuery")) { + return gdm_session_handle_info_query (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "SecretInfoQuery")) { + return gdm_session_handle_secret_info_query (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "Info")) { + return gdm_session_handle_info (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "Problem")) { + return gdm_session_handle_problem (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "SessionStarted")) { + return gdm_session_handle_session_started (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "StartupFailed")) { + return gdm_session_handle_startup_failed (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "SessionExited")) { + return gdm_session_handle_session_exited (session, connection, message); + } else if (dbus_message_is_method_call (message, GDM_SESSION_DBUS_INTERFACE, "SessionDied")) { + return gdm_session_handle_session_died (session, connection, message); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult +do_introspect (DBusConnection *connection, + DBusMessage *message) +{ + DBusMessage *reply; + GString *xml; + char *xml_string; + + g_debug ("Do introspect"); + + /* standard header */ + xml = g_string_new ("<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" + "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" direction=\"out\" type=\"s\"/>\n" + " </method>\n" + " </interface>\n"); + + /* interface */ + xml = g_string_append (xml, + " <interface name=\"org.gnome.DisplayManager.Session\">\n" + " <method name=\"Verified\">\n" + " </method>\n" + " <method name=\"VerificationFailed\">\n" + " <arg name=\"message\" direction=\"in\" type=\"s\"/>\n" + " </method>\n" + " <method name=\"InfoQuery\">\n" + " <arg name=\"query\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"answer\" direction=\"out\" type=\"s\"/>\n" + " </method>\n" + " <method name=\"SecretInfoQuery\">\n" + " <arg name=\"query\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"answer\" direction=\"out\" type=\"s\"/>\n" + " </method>\n" + " <method name=\"Info\">\n" + " <arg name=\"text\" direction=\"in\" type=\"s\"/>\n" + " </method>\n" + " <method name=\"Problem\">\n" + " <arg name=\"text\" direction=\"in\" type=\"s\"/>\n" + " </method>\n" + " <method name=\"UsernameChanged\">\n" + " <arg name=\"text\" direction=\"in\" type=\"s\"/>\n" + " </method>\n" + " <method name=\"StartupFailed\">\n" + " <arg name=\"message\" direction=\"in\" type=\"s\"/>\n" + " </method>\n" + " <method name=\"SessionStarted\">\n" + " <arg name=\"pid\" direction=\"in\" type=\"i\"/>\n" + " </method>\n" + " <method name=\"SessionExited\">\n" + " <arg name=\"code\" direction=\"in\" type=\"i\"/>\n" + " </method>\n" + " <method name=\"SessionDied\">\n" + " <arg name=\"signal\" direction=\"in\" type=\"i\"/>\n" + " </method>\n" + " <signal name=\"BeginVerification\">\n" + " <arg name=\"service_name\" type=\"s\"/>\n" + " <arg name=\"hostname\" type=\"s\"/>\n" + " <arg name=\"console\" type=\"s\"/>\n" + " </signal>\n" + " <signal name=\"BeginVerificationForUser\">\n" + " <arg name=\"service_name\" type=\"s\"/>\n" + " <arg name=\"hostname\" type=\"s\"/>\n" + " <arg name=\"console\" type=\"s\"/>\n" + " <arg name=\"username\" type=\"s\"/>\n" + " </signal>\n" + " <signal name=\"StartProgram\">\n" + " <arg name=\"command\" type=\"s\"/>\n" + " </signal>\n" + " <signal name=\"SetEnvironmentVariable\">\n" + " <arg name=\"name\" type=\"s\"/>\n" + " <arg name=\"value\" type=\"s\"/>\n" + " </signal>\n" + " <signal name=\"Reset\">\n" + " </signal>\n" + " </interface>\n"); + + reply = dbus_message_new_method_return (message); + + xml = g_string_append (xml, "</node>\n"); + xml_string = g_string_free (xml, FALSE); + + dbus_message_append_args (reply, + DBUS_TYPE_STRING, &xml_string, + DBUS_TYPE_INVALID); + + g_free (xml_string); + + if (reply == NULL) { + g_error ("No memory"); + } + + if (! dbus_connection_send (connection, reply, NULL)) { + g_error ("No memory"); + } + + dbus_message_unref (reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +session_message_handler (DBusConnection *connection, + DBusMessage *message, + void *user_data) +{ + g_debug ("session_message_handler: destination=%s obj_path=%s interface=%s method=%s", + dbus_message_get_destination (message), + dbus_message_get_path (message), + dbus_message_get_interface (message), + dbus_message_get_member (message)); + + + if (dbus_message_is_method_call (message, "org.freedesktop.DBus", "AddMatch")) { + DBusMessage *reply; + + reply = dbus_message_new_method_return (message); + + if (reply == NULL) { + g_error ("No memory"); + } + + if (! dbus_connection_send (connection, reply, NULL)) { + g_error ("No memory"); + } + + dbus_message_unref (reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal (message, DBUS_INTERFACE_LOCAL, "Disconnected") && + strcmp (dbus_message_get_path (message), DBUS_PATH_LOCAL) == 0) { + + /*dbus_connection_unref (connection);*/ + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_method_call (message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + return do_introspect (connection, message); + } else { + return session_worker_message (connection, message, user_data); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} +/* Note: Use abstract sockets like dbus does by default on Linux. Abstract + * sockets are only available on Linux. + */ static char * -gdm_session_worker_ask_for_secret (GdmSessionWorker *worker, - const char *secret) +generate_address (void) { - GdmSessionWorkerMessage *message; - GdmSessionMessage *reply; - GdmSessionSecretInfoReplyMessage *secret_info_reply; - char *answer; - GError *error; - - message = gdm_session_worker_secret_info_request_message_new (secret); - gdm_write_message (worker->message_pipe_fd, message, message->size, NULL); - gdm_session_worker_message_free (message); + char *path; +#if defined (__linux__) + int i; + char tmp[9]; - error = NULL; - do { - gboolean res; - - reply = gdm_session_worker_get_incoming_message (worker, &error); - - if (reply == NULL) { - g_warning ("could not receive message from parent: %s", error->message); - g_error_free (error); - - /* FIXME: figure out what to do here - */ - return NULL; - } - - res = gdm_session_worker_process_asynchronous_message (worker, reply); - if (res) { - gdm_session_message_free (reply); - reply = NULL; + for (i = 0; i < 8; i++) { + if (g_random_int_range (0, 2) == 0) { + tmp[i] = g_random_int_range ('a', 'z' + 1); + } else { + tmp[i] = g_random_int_range ('A', 'Z' + 1); } } - while (reply == NULL); - - /* FIXME: we have to do something better here. Messages - * aren't gauranteed to be delivered synchronously. We need - * to either fix that, or fix this to not expect them to be. - */ - if (reply->type != GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY) { - g_debug ("discarding unexpected message of type 0x%x", reply->type); - gdm_session_message_free (reply); - return NULL; - } - secret_info_reply = (GdmSessionSecretInfoReplyMessage *) reply; + tmp[8] = '\0'; - answer = g_strdup (secret_info_reply->answer); - gdm_session_message_free (reply); - - g_debug ("answer to secret question '%s' is '%s'", secret, answer); - return answer; -} + path = g_strdup_printf ("unix:abstract=/tmp/gdm-session-%s", tmp); +#else + path = g_strdup ("unix:tmpdir=/tmp/gdm-session"); +#endif -static void -gdm_session_worker_report_info (GdmSessionWorker *worker, - const char *info) -{ - GdmSessionWorkerMessage *message; - message = gdm_session_worker_info_message_new (info); - gdm_write_message (worker->message_pipe_fd, message, message->size, NULL); - gdm_session_worker_message_free (message); + return path; } static void -gdm_session_worker_report_problem (GdmSessionWorker *worker, - const char *problem) -{ - GdmSessionWorkerMessage *message; - message = gdm_session_worker_problem_message_new (problem); - gdm_write_message (worker->message_pipe_fd, message, message->size, NULL); - gdm_session_worker_message_free (message); -} - -static gboolean -gdm_session_worker_get_username (GdmSessionWorker *worker, - char **username) +session_unregister_handler (DBusConnection *connection, + void *user_data) { - gconstpointer item; - - g_assert (worker->pam_handle != NULL); - - if (pam_get_item (worker->pam_handle, PAM_USER, &item) == PAM_SUCCESS) { - if (username) { - *username = g_strdup ((char *) item); - g_debug ("username is '%s'", - *username != NULL ? *username : - "<unset>"); - } - return TRUE; - } - - return FALSE; + g_debug ("session_unregister_handler"); } -static void -gdm_session_worker_update_username (GdmSessionWorker *worker) +static DBusHandlerResult +connection_filter_function (DBusConnection *connection, + DBusMessage *message, + void *user_data) { - char *username; - gboolean res; + GdmSession *session = GDM_SESSION (user_data); + const char *path; - username = NULL; - res = gdm_session_worker_get_username (worker, &username); - if (res) { - GdmSessionWorkerMessage *message; + path = dbus_message_get_path (message); - if ((worker->username == username) || - ((worker->username != NULL) && (username != NULL) && - (strcmp (worker->username, username) == 0))) - goto out; + g_debug ("obj_path=%s interface=%s method=%s", + dbus_message_get_path (message), + dbus_message_get_interface (message), + dbus_message_get_member (message)); - g_debug ("setting username to '%s'", username); + if (dbus_message_is_signal (message, DBUS_INTERFACE_LOCAL, "Disconnected") + && strcmp (path, DBUS_PATH_LOCAL) == 0) { - g_free (worker->username); - worker->username = username; - username = NULL; + g_debug ("Disconnected"); - message = gdm_session_worker_username_changed_message_new (worker->username); - gdm_write_message (worker->message_pipe_fd, message, message->size, NULL); - gdm_session_worker_message_free (message); - } + dbus_connection_unref (connection); + session->priv->worker_connection = NULL; - out: - g_free (username); -} + g_signal_emit (session, gdm_session_signals [CLOSED], 0); + } else if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { -static gboolean -gdm_session_worker_process_pam_message (GdmSessionWorker *worker, - const struct pam_message *query, - char **response_text) -{ - char *user_answer; - gboolean was_processed; - g_debug ("received pam message of type %u with payload '%s'", - query->msg_style, query->msg); - - user_answer = NULL; - was_processed = FALSE; - switch (query->msg_style) { - case PAM_PROMPT_ECHO_ON: - user_answer = gdm_session_worker_ask_question (worker, query->msg); - break; - case PAM_PROMPT_ECHO_OFF: - user_answer = gdm_session_worker_ask_for_secret (worker, query->msg); - break; - case PAM_TEXT_INFO: - gdm_session_worker_report_info (worker, query->msg); - was_processed = TRUE; - break; - case PAM_ERROR_MSG: - gdm_session_worker_report_problem (worker, query->msg); - was_processed = TRUE; - break; - default: - g_debug ("unknown query of type %u\n", query->msg_style); - break; - } - - if (user_answer != NULL) { - /* we strdup and g_free to make sure we return malloc'd - * instead of g_malloc'd memory - */ - if (response_text != NULL) { - *response_text = strdup (user_answer); - } - - g_free (user_answer); - - g_debug ("trying to get updated username"); - gdm_session_worker_update_username (worker); - was_processed = TRUE; + } else { + return session_message_handler (connection, message, user_data); } - return was_processed; + return DBUS_HANDLER_RESULT_HANDLED; } -static int -gdm_session_worker_pam_new_messages_handler (int number_of_messages, - const struct pam_message **messages, - struct pam_response **responses, - GdmSessionWorker *worker) +static dbus_bool_t +allow_user_function (DBusConnection *connection, + unsigned long uid, + void *data) { - struct pam_response *replies; - int return_value; - int i; - - g_debug ("%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_text; - - response_text = NULL; - got_response = gdm_session_worker_process_pam_message (worker, - messages[i], - &response_text); - if (!got_response) - goto out; - - g_debug ("answered pam message %d with response '%s'", - i, response_text); - replies[i].resp = response_text; - 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 (0 == uid) { + return TRUE; } - if (responses) { - *responses = replies; - } + g_debug ("User not allowed"); - return return_value; + return FALSE; } static void -gdm_session_worker_uninitialize_pam (GdmSessionWorker *worker, - int error_code) +handle_connection (DBusServer *server, + DBusConnection *new_connection, + void *user_data) { - g_debug ("uninitializing PAM"); - - if (worker->pam_handle == NULL) - return; + GdmSession *session = GDM_SESSION (user_data); - if (worker->credentials_are_established) { - pam_setcred (worker->pam_handle, PAM_DELETE_CRED); - worker->credentials_are_established = FALSE; - } - - if (worker->is_running) { - pam_close_session (worker->pam_handle, 0); - worker->is_running = FALSE; - } - - pam_end (worker->pam_handle, error_code); - worker->pam_handle = NULL; -} - -static gboolean -gdm_session_worker_initialize_pam (GdmSessionWorker *worker, - const char *service, - const char *username, - const char *hostname, - const char *console_name, - GError **error) -{ - struct pam_conv pam_conversation; - int error_code; - - g_assert (worker->pam_handle == NULL); - - g_debug ("initializing PAM"); - - pam_conversation.conv = (GdmSessionWorkerPamNewMessagesFunc) gdm_session_worker_pam_new_messages_handler; - pam_conversation.appdata_ptr = worker; - - error_code = pam_start (service, - username, - &pam_conversation, - &worker->pam_handle); - - if (error_code != PAM_SUCCESS) { - g_debug ("could not initialize pam"); - /* 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_ERROR, - GDM_SESSION_ERROR_AUTHENTICATING, - _("error initiating conversation with authentication system - %s"), - error_code == PAM_ABORT? _("general failure") : - error_code == PAM_BUF_ERR? _("out of memory") : - error_code == PAM_SYSTEM_ERR? _("application programmer error") : - _("unscoped error")); + g_debug ("Handing new connection"); - goto out; - } + if (session->priv->worker_connection == NULL) { + DBusObjectPathVTable vtable = { &session_unregister_handler, + &session_message_handler, + NULL, NULL, NULL, NULL + }; - if (username == NULL) { - error_code = pam_set_item (worker->pam_handle, PAM_USER_PROMPT, _("Username:")); - - if (error_code != PAM_SUCCESS) { - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_AUTHENTICATING, - _("error informing authentication system of preferred username prompt - %s"), - pam_strerror (worker->pam_handle, error_code)); - goto out; - } - } + session->priv->worker_connection = new_connection; + dbus_connection_ref (new_connection); + dbus_connection_setup_with_g_main (new_connection, NULL); - if (hostname != NULL) { - error_code = pam_set_item (worker->pam_handle, PAM_RHOST, hostname); + g_debug ("worker connection is %p", new_connection); - if (error_code != PAM_SUCCESS) { - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_AUTHENTICATING, - _("error informing authentication system of user's hostname - %s"), - pam_strerror (worker->pam_handle, error_code)); - goto out; - } - } + dbus_connection_add_filter (new_connection, + connection_filter_function, + session, + NULL); - error_code = pam_set_item (worker->pam_handle, PAM_TTY, console_name); + dbus_connection_set_unix_user_function (new_connection, + allow_user_function, + session, + NULL); - if (error_code != PAM_SUCCESS) { - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_AUTHENTICATING, - _("error informing authentication system of user's console - %s"), - pam_strerror (worker->pam_handle, error_code)); - goto out; - } + dbus_connection_register_object_path (new_connection, + "/", + &vtable, + session); - out: - if (error_code != PAM_SUCCESS) { - gdm_session_worker_uninitialize_pam (worker, error_code); - return FALSE; + g_debug ("Emitting opened signal"); + g_signal_emit (session, gdm_session_signals [OPENED], 0); } - - return TRUE; } static gboolean -gdm_session_worker_authenticate_user (GdmSessionWorker *worker, - gboolean password_is_required, - GError **error) +setup_server (GdmSession *session) { - int error_code; - int authentication_flags; + DBusError error; + gboolean ret; + char *address; + const char *auth_mechanisms[] = {"EXTERNAL", NULL}; - g_debug ("authenticating user"); + ret = FALSE; - authentication_flags = 0; + g_debug ("Creating D-Bus server for session"); - if (password_is_required) { - authentication_flags |= PAM_DISALLOW_NULL_AUTHTOK; - } + address = generate_address (); - /* blocking call, does the actual conversation - */ - error_code = pam_authenticate (worker->pam_handle, authentication_flags); + dbus_error_init (&error); + session->priv->server = dbus_server_listen (address, &error); + g_free (address); - if (error_code != PAM_SUCCESS) { - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_AUTHENTICATING, - "%s", pam_strerror (worker->pam_handle, error_code)); + if (session->priv->server == NULL) { + g_warning ("Cannot create D-BUS server for the session: %s", error.message); goto out; } - out: - if (error_code != PAM_SUCCESS) { - gdm_session_worker_uninitialize_pam (worker, error_code); - return FALSE; - } + dbus_server_setup_with_g_main (session->priv->server, NULL); + dbus_server_set_auth_mechanisms (session->priv->server, auth_mechanisms); + dbus_server_set_new_connection_function (session->priv->server, + handle_connection, + session, + NULL); + ret = TRUE; - 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 ("determining if authenticated user is authorized to session"); + g_free (session->priv->server_address); + session->priv->server_address = dbus_server_get_address (session->priv->server); - authentication_flags = 0; - - if (password_is_required) { - authentication_flags |= PAM_DISALLOW_NULL_AUTHTOK; - } - - /* check that the account isn't disabled or expired - */ - error_code = pam_acct_mgmt (worker->pam_handle, authentication_flags); - - /* it's possible that the user needs to change their password or pin code - */ - if (error_code == PAM_NEW_AUTHTOK_REQD) - error_code = pam_chauthtok (worker->pam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); - - if (error_code != PAM_SUCCESS) { - g_debug ("user is not authorized to log in: %s", - pam_strerror (worker->pam_handle, error_code)); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_AUTHORIZING, - "%s", pam_strerror (worker->pam_handle, error_code)); - goto out; - } + g_debug ("D-Bus server listening on %s", session->priv->server_address); out: - if (error_code != PAM_SUCCESS) { - gdm_session_worker_uninitialize_pam (worker, error_code); - return FALSE; - } - return TRUE; + return ret; } static void -gdm_session_worker_set_environment_variable (GdmSessionWorker *worker, - const char *environment_variable) -{ - char **key_and_value; - - key_and_value = g_strsplit (environment_variable, "=", 2); - - /* FIXME: maybe we should use use pam_putenv instead of our - * own hash table, so pam can override our choices if it knows - * better? - */ - g_hash_table_replace (worker->environment, - key_and_value[0], - key_and_value[1]); - - /* We are calling g_free instead of g_strfreev because the - * hash table is taking over ownership of the individual - * elements above - */ - g_free (key_and_value); -} - -static void -gdm_session_worker_update_environment_from_passwd_entry (GdmSessionWorker *worker, - struct passwd *passwd_entry) -{ - char *environment_variable; - - environment_variable = g_strdup_printf ("LOGNAME=%s", worker->username); - gdm_session_worker_set_environment_variable (worker, environment_variable); - g_free (environment_variable); - - environment_variable = g_strdup_printf ("USER=%s", worker->username); - gdm_session_worker_set_environment_variable (worker, environment_variable); - g_free (environment_variable); - - environment_variable = g_strdup_printf ("USERNAME=%s", worker->username); - gdm_session_worker_set_environment_variable (worker, environment_variable); - g_free (environment_variable); - - environment_variable = g_strdup_printf ("HOME=%s", passwd_entry->pw_dir); - gdm_session_worker_set_environment_variable (worker, environment_variable); - g_free (environment_variable); - - environment_variable = g_strdup_printf ("SHELL=%s", passwd_entry->pw_shell); - gdm_session_worker_set_environment_variable (worker, environment_variable); - g_free (environment_variable); -} - -static gboolean -gdm_session_worker_environment_variable_is_set (GdmSessionWorker *worker, - const char *name) -{ - return g_hash_table_lookup (worker->environment, name) != NULL; -} - -static gboolean -gdm_session_worker_give_user_credentials (GdmSessionWorker *worker, - GError **error) +gdm_session_init (GdmSession *session) { - int error_code; - struct passwd *passwd_entry, passwd_buffer; - char *aux_buffer; - long required_aux_buffer_size; - gsize aux_buffer_size; - - aux_buffer = NULL; - aux_buffer_size = 0; - - if (worker->username == NULL) { - error_code = PAM_USER_UNKNOWN; - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_GIVING_CREDENTIALS, - _("no user account available")); - goto out; - } - - 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 - */ - passwd_entry = NULL; - errno = getpwnam_r (worker->username, &passwd_buffer, - aux_buffer, (size_t) aux_buffer_size, - &passwd_entry); - - if (errno != 0) { - error_code = PAM_SYSTEM_ERR; - g_set_error (error, GDM_SESSION_ERROR, - GDM_SESSION_ERROR_GIVING_CREDENTIALS, - "%s", g_strerror (errno)); - goto out; - } - - if (passwd_entry == NULL) { - error_code = PAM_USER_UNKNOWN; - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_GIVING_CREDENTIALS, - _("user account not available on system")); - goto out; - } - - gdm_session_worker_update_environment_from_passwd_entry (worker, passwd_entry); - - /* 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")) - gdm_session_worker_set_environment_variable (worker, - "PATH=" GDM_SESSION_DEFAULT_PATH); - - /* 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 (passwd_entry->pw_uid, GDM_SESSION_ROOT_UID) < 0) { - error_code = PAM_SYSTEM_ERR; - g_set_error (error, GDM_SESSION_ERROR, - GDM_SESSION_ERROR_GIVING_CREDENTIALS, - "%s", g_strerror (errno)); - goto out; - } - - if (setgid (passwd_entry->pw_gid) < 0) { - error_code = PAM_SYSTEM_ERR; - g_set_error (error, GDM_SESSION_ERROR, - GDM_SESSION_ERROR_GIVING_CREDENTIALS, - "%s", g_strerror (errno)); - goto out; - } - - if (initgroups (passwd_entry->pw_name, passwd_entry->pw_gid) < 0) { - error_code = PAM_SYSTEM_ERR; - g_set_error (error, GDM_SESSION_ERROR, - GDM_SESSION_ERROR_GIVING_CREDENTIALS, - "%s", g_strerror (errno)); - goto out; - } - - error_code = pam_setcred (worker->pam_handle, PAM_ESTABLISH_CRED); - - if (error_code != PAM_SUCCESS) { - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_GIVING_CREDENTIALS, - "%s", pam_strerror (worker->pam_handle, error_code)); - goto out; - } + session->priv = G_TYPE_INSTANCE_GET_PRIVATE (session, + GDM_TYPE_SESSION, + GdmSessionPrivate); - worker->credentials_are_established = TRUE; + session->priv->environment = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); - out: - if (aux_buffer != NULL) { - g_assert (aux_buffer_size > 0); - g_slice_free1 (aux_buffer_size, aux_buffer); - } - - if (error_code != PAM_SUCCESS) { - gdm_session_worker_uninitialize_pam (worker, error_code); - return FALSE; - } - - return TRUE; + setup_server (session); } -static gboolean -gdm_session_worker_verify_user (GdmSessionWorker *worker, - const char *service_name, - const char *username, - const char *hostname, - const char *console_name, - gboolean password_is_required, - GError **error) +GdmSession * +gdm_session_new (void) { - GError *pam_error; - GdmSessionWorkerMessage *reply; - char *error_message; - gboolean res; - - g_debug ("Verifying user: %s host: %s service: %s tty: %s", username, hostname, service_name, console_name); - - pam_error = NULL; - res = gdm_session_worker_initialize_pam (worker, - service_name, - username, - hostname, - console_name, - &pam_error); - if (! res) { - g_propagate_error (error, pam_error); - return FALSE; - } - - /* find out who the user is and ensure they are who they say they are - */ - res = gdm_session_worker_authenticate_user (worker, - password_is_required, - &pam_error); - if (! res) { - g_propagate_error (error, pam_error); - return FALSE; - } - - /* we're authenticated. Let's make sure we've been given - * a valid username for the system - */ - g_debug ("trying to get updated username"); - gdm_session_worker_update_username (worker); - - /* make sure the user is allowed to log in to this system - */ - res = gdm_session_worker_authorize_user (worker, - password_is_required, - &pam_error); - if (! res) { - g_propagate_error (error, pam_error); - return FALSE; - } - - /* get kerberos tickets, setup group lists, etc - */ - res = gdm_session_worker_give_user_credentials (worker, &pam_error); - if (! res) { - g_propagate_error (error, pam_error); - return FALSE; - } - - g_debug ("verification process completed, creating reply..."); - reply = gdm_session_worker_verified_message_new (); - - error_message = NULL; - res = gdm_write_message (worker->message_pipe_fd, - reply, - reply->size, - &error_message); - if (! res) { - g_warning ("could not send 'verified' reply to parent: %s\n", - error_message); - g_free (error_message); - } + GdmSession *session; - gdm_session_worker_message_free (reply); + session = g_object_new (GDM_TYPE_SESSION, NULL); - return TRUE; + return session; } static void -gdm_session_worker_update_environment_from_pam (GdmSessionWorker *worker) +worker_stopped (GdmSessionWorkerJob *job, + GdmSession *session) { - char **environment; - gsize i; - - environment = pam_getenvlist (worker->pam_handle); - - for (i = 0; environment[i] != NULL; i++) - gdm_session_worker_set_environment_variable (worker, environment[i]); - - for (i = 0; environment[i]; i++) - free (environment[i]); - free (environment); + g_debug ("Worker job stopped"); } static void -gdm_session_worker_fill_environment_array (const char *key, - const char *value, - GPtrArray *environment) +worker_started (GdmSessionWorkerJob *job, + GdmSession *session) { - char *variable; - - if (value == NULL) - return; - - variable = g_strdup_printf ("%s=%s", key, value); - - g_ptr_array_add (environment, variable); -} - -static char ** -gdm_session_worker_get_environment (GdmSessionWorker *worker) -{ - GPtrArray *environment; - - environment = g_ptr_array_new (); - g_hash_table_foreach (worker->environment, - (GHFunc) gdm_session_worker_fill_environment_array, - environment); - g_ptr_array_add (environment, NULL); - - return (char **) g_ptr_array_free (environment, FALSE); + g_debug ("Worker job started"); } static void -gdm_session_worker_on_child_exited (GPid pid, - int status, - GdmSessionWorker *worker) +worker_exited (GdmSessionWorkerJob *job, + int code, + GdmSession *session) { - GdmSessionWorkerMessage *message; + g_debug ("Worker job exited: %d", code); - message = NULL; + if (!session->priv->is_verified) { + GError *error; - if (WIFEXITED (status)) - message = gdm_session_worker_session_exited_message_new (WEXITSTATUS (status)); - else if (WIFSIGNALED (status)) - message = gdm_session_worker_session_died_message_new (WTERMSIG (status)); + error = g_error_new (GDM_SESSION_ERROR, + GDM_SESSION_ERROR_WORKER_DIED, + _("worker exited with status %d"), + code); - if (message != NULL) { - gdm_write_message (worker->message_pipe_fd, message, message->size, NULL); - gdm_session_worker_message_free (message); + g_signal_emit (session, + gdm_session_signals [USER_VERIFICATION_ERROR], + 0, error); + g_error_free (error); + } else if (session->priv->is_running) { + g_signal_emit (session, + gdm_session_signals [SESSION_EXITED], + 0, code); } - - g_spawn_close_pid (pid); - worker->child_pid = 0; - - worker->exit_code = 0; - g_main_loop_quit (worker->event_loop); -} - -static void -gdm_session_worker_watch_child (GdmSessionWorker *worker) -{ - worker->child_watch_source = g_child_watch_source_new (worker->child_pid); - g_source_set_callback (worker->child_watch_source, - (GSourceFunc) (GChildWatchFunc) - gdm_session_worker_on_child_exited, - worker, - (GDestroyNotify) - gdm_session_worker_clear_child_watch_source); - g_source_attach (worker->child_watch_source, - g_main_loop_get_context (worker->event_loop)); - g_source_unref (worker->child_watch_source); } -/* 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); +worker_died (GdmSessionWorkerJob *job, + int signum, + GdmSession *session) +{ + g_debug ("Worker job died: %d", signum); + + if (!session->priv->is_verified) { + GError *error; + error = g_error_new (GDM_SESSION_ERROR, + GDM_SESSION_ERROR_WORKER_DIED, + _("worker got signal '%s' and was subsequently killed"), + g_strsignal (signum)); + g_signal_emit (session, + gdm_session_signals[USER_VERIFICATION_ERROR], + 0, error); + g_error_free (error); + } else if (session->priv->is_running) { + g_signal_emit (session, + gdm_session_signals[SESSION_EXITED], + 0, signum); } - - /* Return the error from the last attempt (probably ENOENT). */ - return -1; } static gboolean -gdm_session_worker_open_user_session (GdmSessionWorker *worker, - GError **error) +start_worker (GdmSession *session) { - int error_code; - pid_t session_pid; - GdmSessionWorkerMessage *message; - char *error_message; - - g_assert (!worker->is_running); - g_assert (geteuid () == 0); - error_code = pam_open_session (worker->pam_handle, 0); - - if (error_code != PAM_SUCCESS) { - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_OPENING_SESSION, - "%s", pam_strerror (worker->pam_handle, error_code)); - goto out; - } - worker->is_running = TRUE; - - g_debug ("querying pam for user environment"); - gdm_session_worker_update_environment_from_pam (worker); - - g_debug ("opening user session with program '%s'", - worker->arguments[0]); - - session_pid = fork (); - - if (session_pid < 0) { - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_OPENING_SESSION, - "%s", g_strerror (errno)); - error_code = PAM_ABORT; - goto out; - } - - if (session_pid == 0) { - char **environment; - char *home_dir; - int fd; - - worker->inherited_fd_list = g_slist_append (NULL, - GINT_TO_POINTER (worker->standard_output_fd)); - worker->inherited_fd_list = g_slist_append (worker->inherited_fd_list, - GINT_TO_POINTER (worker->standard_error_fd)); - -#if 0 - gdm_session_worker_close_open_fds (worker); -#endif - - if (setuid (getuid ()) < 0) { - g_debug ("could not reset uid - %s", g_strerror (errno)); - _exit (1); - } - - if (setsid () < 0) { - g_debug ("could not set pid '%u' as leader of new session and process group - %s", - (guint) getpid (), g_strerror (errno)); - _exit (2); - } - -#if 0 - fd = gdm_open_dev_null (O_RDWR); - - if (worker->standard_output_fd >= 0) { - dup2 (worker->standard_output_fd, STDOUT_FILENO); - close (worker->standard_output_fd); - worker->standard_output_fd = -1; - } else { - dup2 (fd, STDOUT_FILENO); - } - - if (worker->standard_error_fd >= 0) { - dup2 (worker->standard_error_fd, STDERR_FILENO); - close (worker->standard_error_fd); - worker->standard_error_fd = -1; - } else { - dup2 (fd, STDERR_FILENO); - } - - dup2 (fd, STDIN_FILENO); - close (fd); -#endif - - environment = gdm_session_worker_get_environment (worker); - - g_assert (geteuid () == getuid ()); - - home_dir = g_hash_table_lookup (worker->environment, - "HOME"); - - if ((home_dir == NULL) || g_chdir (home_dir) < 0) { - g_chdir ("/"); - } - - gdm_session_execute (worker->arguments[0], - worker->arguments, - environment, - TRUE); - - g_debug ("child '%s' could not be started - %s", - worker->arguments[0], - g_strerror (errno)); - g_strfreev (environment); - - _exit (127); - } - - worker->child_pid = session_pid; - - g_debug ("session opened creating reply..."); - g_assert (sizeof (GPid) <= sizeof (int)); - - message = gdm_session_worker_session_started_message_new ((GPid) session_pid); - - error_message = NULL; - if (!gdm_write_message (worker->message_pipe_fd, - message, message->size, - &error_message)) { - g_warning ("could not send 'session started' reply to parent: %s\n", - error_message); - g_free (error_message); - } - gdm_session_worker_message_free (message); - - 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 GdmSessionWorkerMessage * -gdm_session_worker_session_startup_failed_message_new (GError *error) -{ - GdmSessionWorkerSessionStartupFailedMessage *message; - gsize error_message_size, size; - - error_message_size = (error != NULL? strlen (error->message) + 1 : 0); - size = sizeof (GdmSessionWorkerSessionStartupFailedMessage) + - error_message_size + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTUP_FAILED; - message->header.size = size; - - if (error != NULL) { - message->error_domain = error->domain; - message->error_code = error->code; - g_strlcpy (message->error_message, error->message, - error_message_size); - message->error_message_size = error_message_size; - } else { - message->error_message_size = -1; - } - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static void -gdm_session_worker_handle_verification_message (GdmSessionWorker *worker, - GdmSessionVerificationMessage *message) -{ - GError *verification_error; - GdmSessionWorkerMessage *reply; - gboolean res; - - worker->standard_output_fd = message->standard_output_fd; - worker->standard_error_fd = message->standard_error_fd; - - verification_error = NULL; - res = gdm_session_worker_verify_user (worker, - message->service_name, - (message->username_size >= 0) ? message->username : NULL, - message->hostname_is_provided ? message->hostname : NULL, - message->console_name, - TRUE /* password is required */, - &verification_error); - if (! res) { - g_assert (verification_error != NULL); - - g_message ("%s", verification_error->message); - - reply = gdm_session_worker_verification_failed_message_new (verification_error); - - g_error_free (verification_error); - - gdm_write_message (worker->message_pipe_fd, reply, reply->size, NULL); - gdm_session_worker_message_free (reply); - goto out; - } - - /* Did start_program get called early? if so, process it now, - * otherwise we'll do it asynchronously later. - */ - if ((worker->arguments != NULL) && - !gdm_session_worker_open_user_session (worker, &verification_error)) { - g_assert (verification_error != NULL); - - g_message ("%s", verification_error->message); - - reply = gdm_session_worker_session_startup_failed_message_new (verification_error); - - g_error_free (verification_error); - - gdm_write_message (worker->message_pipe_fd, reply, reply->size, NULL); - gdm_session_worker_message_free (reply); - goto out; - } - - out: - ; -} - -static void -gdm_session_worker_handle_start_program_message (GdmSessionWorker *worker, - GdmSessionStartProgramMessage *message) -{ - GError *start_error; gboolean res; - if (worker->arguments != NULL) - g_strfreev (worker->arguments); - - worker->arguments = gdm_session_unflatten_arguments (message->arguments); - - /* Did start_program get called early? if so, we will process the request - * later, synchronously after getting credentials - */ - if (!worker->credentials_are_established) - return; - - start_error = NULL; - res = gdm_session_worker_open_user_session (worker, &start_error); - if (! res) { - GdmSessionWorkerMessage *message; - - g_assert (start_error != NULL); - - g_warning ("%s", start_error->message); - - message = gdm_session_worker_session_startup_failed_message_new (start_error); - - g_error_free (start_error); - - gdm_write_message (worker->message_pipe_fd, message, message->size, NULL); - gdm_session_worker_message_free (message); - } + session->priv->job = gdm_session_worker_job_new (); + gdm_session_worker_job_set_server_address (session->priv->job, session->priv->server_address); + g_signal_connect (session->priv->job, + "stopped", + G_CALLBACK (worker_stopped), + session); + g_signal_connect (session->priv->job, + "started", + G_CALLBACK (worker_started), + session); + g_signal_connect (session->priv->job, + "exited", + G_CALLBACK (worker_exited), + session); + g_signal_connect (session->priv->job, + "died", + G_CALLBACK (worker_died), + session); + + res = gdm_session_worker_job_start (session->priv->job); + + return res; } -static void -gdm_session_worker_handle_set_environment_variable_message (GdmSessionWorker *worker, - GdmSessionSetEnvironmentVariableMessage *message) -{ - gdm_session_worker_set_environment_variable (worker, - message->environment_variable); -} - -static gboolean -gdm_session_worker_process_asynchronous_message (GdmSessionWorker *worker, - GdmSessionMessage *message) +gboolean +gdm_session_open (GdmSession *session, + const char *service_name, + const char *hostname, + const char *console_name, + GError **error) { - switch (message->type) { - case GDM_SESSION_MESSAGE_TYPE_VERIFICATION: - g_debug ("received verification request from parent"); - gdm_session_worker_handle_verification_message (worker, - (GdmSessionVerificationMessage *) message); - return TRUE; + gboolean res; - case GDM_SESSION_MESSAGE_TYPE_START_PROGRAM: - g_debug ("received new session arguments from parent"); - gdm_session_worker_handle_start_program_message (worker, - (GdmSessionStartProgramMessage *) - message); - return TRUE; + g_return_val_if_fail (session != NULL, FALSE); + g_return_val_if_fail (service_name != NULL, FALSE); + g_return_val_if_fail (console_name != NULL, FALSE); + g_return_val_if_fail (hostname != NULL, FALSE); - case GDM_SESSION_MESSAGE_TYPE_SET_ENVIRONMENT_VARIABLE: - g_debug ("received new environment variable from parent"); - gdm_session_worker_handle_set_environment_variable_message (worker, - (GdmSessionSetEnvironmentVariableMessage *) - message); - return TRUE; + res = start_worker (session); - default: - g_debug ("received unknown message with type '0x%x' from parent", - message->type); - return FALSE; - } + session->priv->service_name = g_strdup (service_name); + session->priv->hostname = g_strdup (hostname); + session->priv->console_name = g_strdup (console_name); - return FALSE; + return res; } - - - - - - - - - - - - - - - - - static void -gdm_session_worker_unwatch_child (GdmSessionWorker *worker) +send_begin_verification (GdmSession *session) { - if (worker->child_watch_source == NULL) - return; + DBusMessage *message; + DBusMessageIter iter; - g_source_destroy (worker->child_watch_source); - worker->child_watch_source = NULL; -} + message = dbus_message_new_signal (GDM_SESSION_DBUS_PATH, + GDM_SESSION_DBUS_INTERFACE, + "BeginVerification"); -static int -gdm_get_max_open_fds (void) -{ - struct rlimit open_fd_limit; - const int fallback_limit = GDM_MAX_OPEN_FILE_DESCRIPTORS; - - if (getrlimit (RLIMIT_NOFILE, &open_fd_limit) < 0) { - g_debug ("could not get file descriptor limit: %s", - g_strerror (errno)); - g_debug ("returning fallback file descriptor limit of %d", - fallback_limit); - return fallback_limit; - } + dbus_message_iter_init_append (message, &iter); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &session->priv->service_name); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &session->priv->hostname); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &session->priv->console_name); - if (open_fd_limit.rlim_cur == RLIM_INFINITY) { - g_debug ("currently no file descriptor limit, returning fallback limit of %d", - fallback_limit); - return fallback_limit; + if (! send_dbus_message (session->priv->worker_connection, message)) { + g_debug ("Could not send %s signal", "BeginVerification"); } - return (int) open_fd_limit.rlim_cur; + dbus_message_unref (message); } static void -gdm_session_worker_close_all_fds (GdmSessionWorker *worker) +send_begin_verification_for_user (GdmSession *session) { - int max_open_fds, fd; - - max_open_fds = gdm_get_max_open_fds (); - g_debug ("closing all file descriptors except those that are specifically " - "excluded"); - - for (fd = 0; fd < max_open_fds; fd++) { - GSList *node; - for (node = worker->inherited_fd_list; - node != NULL; - node = node->next) { - if (fd == GPOINTER_TO_INT (node->data)) - break; - } + DBusMessage *message; + DBusMessageIter iter; - if (node == NULL) { - g_debug ("closing file descriptor '%d'", fd); - close (fd); - } - } + message = dbus_message_new_signal (GDM_SESSION_DBUS_PATH, + GDM_SESSION_DBUS_INTERFACE, + "BeginVerificationForUser"); - g_debug ("closed first '%d' file descriptors", max_open_fds); -} + dbus_message_iter_init_append (message, &iter); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &session->priv->service_name); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &session->priv->hostname); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &session->priv->console_name); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &session->priv->username); -static void -gdm_session_worker_close_open_fds (GdmSessionWorker *worker) -{ - /* using DIR instead of GDir because we need access to dirfd so - * that we can iterate through the fds and close them in one sweep. - * (if we just closed all of them then we would close the one we're using - * for reading the directory!) - */ - DIR *dir; - struct dirent *entry; - int fd, opendir_fd; - gboolean should_use_fallback; - - should_use_fallback = FALSE; - opendir_fd = -1; - return; - - dir = opendir (GDM_OPEN_FILE_DESCRIPTORS_DIR); - - if (dir != NULL) { - opendir_fd = dirfd (dir); - g_debug ("opened '"GDM_OPEN_FILE_DESCRIPTORS_DIR"' on file descriptor '%d'", opendir_fd); + if (! send_dbus_message (session->priv->worker_connection, message)) { + g_debug ("Could not send %s signal", "BeginVerificationForUser"); } - if ((dir == NULL) || (opendir_fd < 0)) { - g_debug ("could not open "GDM_OPEN_FILE_DESCRIPTORS_DIR": %s", g_strerror (errno)); - should_use_fallback = TRUE; - } else { - g_debug ("reading files in '"GDM_OPEN_FILE_DESCRIPTORS_DIR"'"); - while ((entry = readdir (dir)) != NULL) { - GSList *node; - glong filename_as_number; - char *byte_after_number; - - if (entry->d_name[0] == '.') - continue; - - g_debug ("scanning filename '%s' for file descriptor number", - entry->d_name); - fd = -1; - filename_as_number = strtol (entry->d_name, &byte_after_number, 10); - - g_assert (byte_after_number != NULL); - - if ((*byte_after_number != '\0') || - (filename_as_number < 0) || - (filename_as_number >= G_MAXINT)) { - g_debug ("filename '%s' does not appear to represent a " - "file descriptor: %s", - entry->d_name, strerror (errno)); - should_use_fallback = TRUE; - } else { - fd = (int) filename_as_number; - g_debug ("filename '%s' represents file descriptor '%d'", - entry->d_name, fd); - should_use_fallback = FALSE; - } - - for (node = worker->inherited_fd_list; - node != NULL; - node = node->next) { - if (fd == GPOINTER_TO_INT (node->data)) - break; - } - - if ((node == NULL) && - (fd != opendir_fd)) { - g_debug ("closing file descriptor '%d'", fd); - close (fd); - } else { - g_debug ("will not close file descriptor '%d' because it " - "is still neded", fd); - } - } - g_debug ("closing directory '"GDM_OPEN_FILE_DESCRIPTORS_DIR"'"); - closedir (dir); - } - - /* if /proc isn't mounted or something else is screwy, - * fall back to closing everything - */ - if (should_use_fallback) { - gdm_session_worker_close_all_fds (worker); - } + dbus_message_unref (message); } - - -static GdmSessionWorkerMessage * -gdm_session_worker_verified_message_new (void) -{ - GdmSessionWorkerVerifiedMessage *message; - gsize size; - - size = sizeof (GdmSessionWorkerVerifiedMessage) + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFIED; - message->header.size = size; - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static GdmSessionWorkerMessage * -gdm_session_worker_verification_failed_message_new (GError *error) -{ - GdmSessionWorkerVerificationFailedMessage *message; - gsize error_message_size, size; - - error_message_size = (error != NULL? strlen (error->message) + 1 : 0); - size = sizeof (GdmSessionWorkerVerificationFailedMessage) + - error_message_size + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFICATION_FAILED; - message->header.size = size; - - if (error != NULL) { - message->error_domain = error->domain; - message->error_code = error->code; - g_strlcpy (message->error_message, error->message, - error_message_size); - message->error_message_size = error_message_size; - } else { - message->error_message_size = -1; - } - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static GdmSessionWorkerMessage * -gdm_session_worker_info_request_message_new (const char *question) -{ - GdmSessionWorkerInfoRequestMessage *message; - gsize question_size, size; - - g_assert (question != NULL); - - question_size = strlen (question) + 1; - size = sizeof (GdmSessionWorkerInfoRequestMessage) + - question_size + - sizeof (GDM_SESSION_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_INFO_REQUEST; - message->header.size = size; - - g_strlcpy (message->question, question, question_size); - - return (GdmSessionWorkerMessage *) message; -} - -static GdmSessionWorkerMessage * -gdm_session_worker_username_changed_message_new (const char *new_username) +gboolean +gdm_session_begin_verification (GdmSession *session, + const char *username, + GError **error) { - GdmSessionWorkerUsernameChangedMessage *message; - gsize username_size, size; - - username_size = (new_username != NULL? strlen (new_username) + 1 : 0); - - size = sizeof (GdmSessionWorkerUsernameChangedMessage) + - username_size + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); + g_return_val_if_fail (session != NULL, FALSE); + g_return_val_if_fail (dbus_connection_get_is_connected (session->priv->worker_connection), FALSE); - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_USERNAME_CHANGED; - message->header.size = size; + session->priv->username = g_strdup (username); - if (new_username != NULL) { - g_strlcpy (message->username, new_username, username_size); - message->username_size = username_size; + if (username == NULL) { + send_begin_verification (session); } else { - message->username_size = -1; - } - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static GdmSessionWorkerMessage * -gdm_session_worker_secret_info_request_message_new (const char *question) -{ - GdmSessionWorkerSecretInfoRequestMessage *message; - gsize question_size, size; - - g_assert (question != NULL); - - question_size = strlen (question) + 1; - - size = sizeof (GdmSessionWorkerSecretInfoRequestMessage) + - question_size + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_SECRET_INFO_REQUEST; - message->header.size = size; - - g_strlcpy (message->question, question, question_size); - message->question_size = question_size; - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static GdmSessionWorkerMessage * -gdm_session_worker_info_message_new (const char *info) -{ - GdmSessionWorkerInfoMessage *message; - gsize info_size, size; - - g_assert (info != NULL); - - info_size = strlen (info) + 1; - - size = sizeof (GdmSessionWorkerInfoRequestMessage) + - info_size + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_INFO; - message->header.size = size; - - g_strlcpy (message->info, info, info_size); - message->info_size = info_size; - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static GdmSessionWorkerMessage * -gdm_session_worker_problem_message_new (const char *problem) -{ - GdmSessionWorkerProblemMessage *message; - gsize problem_size, size; - - g_assert (problem != NULL); - - problem_size = strlen (problem) + 1; - - size = sizeof (GdmSessionWorkerProblemMessage) + - problem_size + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_PROBLEM; - message->header.size = size; - - g_strlcpy (message->problem, problem, problem_size); - message->problem_size = problem_size; - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static GdmSessionWorkerMessage * -gdm_session_worker_session_started_message_new (GPid pid) -{ - GdmSessionWorkerSessionStartedMessage *message; - gsize size; - - size = sizeof (GdmSessionWorkerSessionStartedMessage) + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTED; - message->header.size = size; - - message->pid = pid; - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - - -static GdmSessionWorkerMessage * -gdm_session_worker_session_exited_message_new (int exit_code) -{ - GdmSessionWorkerSessionExitedMessage *message; - gsize size; - - size = sizeof (GdmSessionWorkerSessionExitedMessage) + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_EXITED; - message->header.size = size; - - message->exit_code = exit_code; - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static GdmSessionWorkerMessage * -gdm_session_worker_session_died_message_new (int signal_number) -{ - GdmSessionWorkerSessionDiedMessage *message; - gsize size; - - size = sizeof (GdmSessionWorkerSessionDiedMessage) + - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL); - - message = g_slice_alloc0 (size); - - message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_EXITED; - message->header.size = size; - - message->signal_number = signal_number; - - g_strlcpy ((char *) ((guint *) message) + size - - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL), - GDM_SESSION_WORKER_MESSAGE_SENTINAL, - sizeof (GDM_SESSION_WORKER_MESSAGE_SENTINAL)); - - return (GdmSessionWorkerMessage *) message; -} - -static void -gdm_session_worker_message_free (GdmSessionWorkerMessage *message) -{ - g_slice_free1 (message->size, message); -} - -static void -gdm_session_worker_incoming_message_handler (GdmSessionWorker *worker) -{ - GdmSessionMessage *message; - GError *error; - - error = NULL; - message = gdm_session_worker_get_incoming_message (worker, &error); - - if (message == NULL) { - g_assert (error != NULL); - g_warning ("could not receive message from parent: %s\n", - error->message); - g_error_free (error); - - /* FIXME: figure out what to do here - */ - return; - } - - gdm_session_worker_process_asynchronous_message (worker, message); - - gdm_session_message_free (message); -} - -static gboolean -gdm_session_worker_data_on_message_pipe_handler (GIOChannel *channel, - GIOCondition condition, - GdmSessionWorker *worker) -{ - if ((condition & G_IO_IN) || (condition & G_IO_PRI)) { - g_debug ("got message from message pipe"); - gdm_session_worker_incoming_message_handler (worker); - } - - if ((condition & G_IO_ERR) || (condition & G_IO_HUP)) { - g_debug ("got disconnected message from message pipe"); - gdm_session_worker_disconnected_handler (worker); - return FALSE; + send_begin_verification_for_user (session); } return TRUE; } static void -gdm_session_worker_watch_message_pipe (GdmSessionWorker *worker) -{ - GIOChannel *io_channel; - - io_channel = g_io_channel_unix_new (worker->message_pipe_fd); - g_io_channel_set_close_on_unref (io_channel, TRUE); - - worker->message_pipe_source = g_io_create_watch (io_channel, - G_IO_IN | G_IO_HUP); - g_io_channel_unref (io_channel); - - g_source_set_callback (worker->message_pipe_source, - (GSourceFunc) (GIOFunc) - gdm_session_worker_data_on_message_pipe_handler, - worker, - (GDestroyNotify) - gdm_session_worker_clear_message_pipe_source); - g_source_attach (worker->message_pipe_source, - g_main_loop_get_context (worker->event_loop)); - g_source_unref (worker->message_pipe_source); -} - -static void -gdm_session_worker_wait_for_messages (GdmSessionWorker *worker) -{ - gdm_session_worker_watch_message_pipe (worker); - - g_main_loop_run (worker->event_loop); -} - -static gboolean -gdm_open_bidirectional_pipe (int *fd1, - int *fd2, - char **error_message) +send_environment_variable (const char *key, + const char *value, + GdmSession *session) { - int pipe_fds[2]; - - g_assert (fd1 != NULL); - g_assert (fd2 != NULL); + DBusMessage *message; + DBusMessageIter iter; - if (socketpair (AF_UNIX, SOCK_DGRAM, 0, pipe_fds) < 0) { - if (error_message != NULL) - *error_message = g_strdup_printf ("%s", - g_strerror (errno)); - return FALSE; - } + message = dbus_message_new_signal (GDM_SESSION_DBUS_PATH, + GDM_SESSION_DBUS_INTERFACE, + "SetEnvironmentVariable"); - if (fcntl (pipe_fds[0], F_SETFD, FD_CLOEXEC) < 0) { - if (error_message != NULL) - *error_message = g_strdup_printf ("%s", - g_strerror (errno)); - close (pipe_fds[0]); - close (pipe_fds[1]); - return FALSE; - } + dbus_message_iter_init_append (message, &iter); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &key); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &value); - if (fcntl (pipe_fds[1], F_SETFD, FD_CLOEXEC) < 0) { - if (error_message != NULL) - *error_message = g_strdup_printf ("%s", - g_strerror (errno)); - close (pipe_fds[0]); - close (pipe_fds[1]); - return FALSE; + if (! send_dbus_message (session->priv->worker_connection, message)) { + g_debug ("Could not send %s signal", "SetEnvironmentVariable"); } - *fd1 = pipe_fds[0]; - *fd2 = pipe_fds[1]; - - return TRUE; -} - -static GdmSessionWorker * -gdm_session_worker_new (void) -{ - GdmSessionWorker *worker; - GMainContext *context; - - worker = g_slice_new0 (GdmSessionWorker); - - context = g_main_context_new (); - worker->event_loop = g_main_loop_new (context, FALSE); - g_main_context_unref (context); - - worker->pam_handle = NULL; - - worker->message_pipe_fd = -1; - worker->message_pipe_source = NULL; - - worker->inherited_fd_list = NULL; - - worker->username = NULL; - worker->arguments = NULL; - - worker->environment = g_hash_table_new_full (g_str_hash, - g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) g_free); - worker->standard_output_fd = -1; - worker->standard_error_fd = -1; - - worker->exit_code = 127; - - worker->credentials_are_established = FALSE; - worker->is_running = FALSE; - - return worker; + dbus_message_unref (message); } static void -gdm_session_worker_free (GdmSessionWorker *worker) +send_environment (GdmSession *session) { - if (worker == NULL) - return; - - gdm_session_worker_unwatch_child (worker); - - if (worker->message_pipe_fd != -1) { - close (worker->message_pipe_fd); - worker->message_pipe_fd = -1; - } - - if (worker->username != NULL) { - g_free (worker->username); - worker->username = NULL; - } - - if (worker->arguments != NULL) { - g_strfreev (worker->arguments); - worker->arguments = NULL; - } - - if (worker->environment != NULL) { - g_hash_table_destroy (worker->environment); - worker->environment = NULL; - } - - if (worker->standard_output_fd >= 0) { - close (worker->standard_output_fd); - worker->standard_output_fd = -1; - } - - if (worker->standard_error_fd >= 0) { - close (worker->standard_error_fd); - worker->standard_error_fd = -1; - } - if (worker->event_loop != NULL) { - g_main_loop_unref (worker->event_loop); - worker->event_loop = NULL; - } - - g_slice_free (GdmSessionWorker, worker); -} - -static gboolean -gdm_session_create_worker (GdmSession *session, - int standard_output_fd, - int standard_error_fd, - int *worker_fd, - GPid *worker_pid, - GError **error) -{ - GdmSessionWorker *worker; - int session_message_pipe_fd, worker_message_pipe_fd; - char *error_message; - pid_t child_pid; - - session_message_pipe_fd = -1; - worker_message_pipe_fd = -1; - error_message = NULL; - if (!gdm_open_bidirectional_pipe (&session_message_pipe_fd, - &worker_message_pipe_fd, - &error_message)) { - g_debug ("unable open message pipe: %s", error_message); - g_set_error (error, - GDM_SESSION_ERROR, - GDM_SESSION_ERROR_OPENING_MESSAGE_PIPE, - "%s", error_message); - g_free (error_message); - return FALSE; - } - - g_assert (session_message_pipe_fd >= 0); - g_assert (worker_message_pipe_fd >= 0); - - child_pid = fork (); - - if (child_pid < 0) { - g_set_error (error, GDM_SESSION_ERROR, - GDM_SESSION_ERROR_FORKING, - "%s", g_strerror (errno)); - return FALSE; - } - - if (child_pid == 0) { - GError *child_error; - int fd; - - setsid (); - - worker = gdm_session_worker_new (); - worker->message_pipe_fd = worker_message_pipe_fd; - worker->inherited_fd_list = - g_slist_append (worker->inherited_fd_list, - GINT_TO_POINTER (worker->message_pipe_fd)); - - if (standard_output_fd >= 0) - worker->inherited_fd_list = - g_slist_append (worker->inherited_fd_list, - GINT_TO_POINTER (standard_output_fd)); - if (standard_error_fd >= 0) - worker->inherited_fd_list = - g_slist_append (worker->inherited_fd_list, - GINT_TO_POINTER (standard_error_fd)); - -#if 0 - gdm_session_worker_close_open_fds (worker); - - fd = gdm_open_dev_null (O_RDWR); - dup2 (fd, STDIN_FILENO); - - if (standard_output_fd >= 0 && - standard_output_fd != STDOUT_FILENO) - dup2 (standard_output_fd, STDOUT_FILENO); - else - dup2 (fd, STDOUT_FILENO); - - if (standard_error_fd >= 0 && - standard_error_fd != STDERR_FILENO) - dup2 (standard_error_fd, STDERR_FILENO); - else - dup2 (fd, STDERR_FILENO); - close (fd); -#endif - - g_debug ("waiting for messages from parent"); - child_error = NULL; - - gdm_session_worker_wait_for_messages (worker); - - g_debug ("exiting with code '%d'", worker->exit_code); - gdm_session_worker_free (worker); - _exit (worker->exit_code); - } - close (worker_message_pipe_fd); - - g_assert (child_pid != 0); - if (worker_pid != NULL) - *worker_pid = (GPid) child_pid; - - if (worker_fd != NULL) - *worker_fd = session_message_pipe_fd; - - return TRUE; -} - -static gboolean -gdm_session_open_with_worker (GdmSession *session, - const char *service_name, - const char *username, - const char *hostname, - const char *console_name, - int standard_output_fd, - int standard_error_fd, - GError **error) -{ - GdmSessionMessage *message; - GError *worker_error; - int worker_message_pipe_fd; - GPid worker_pid; - gboolean worker_is_created; - - worker_error = NULL; - worker_is_created = gdm_session_create_worker (session, - standard_output_fd, - standard_error_fd, - &worker_message_pipe_fd, - &worker_pid, - &worker_error); - if (!worker_is_created) { - g_debug ("worker could not be created"); - g_propagate_error (error, worker_error); - return FALSE; - } - - session->priv->service_name = g_strdup (service_name); - - session->priv->arguments = NULL; - session->priv->username = g_strdup (username); - - session->priv->hostname = g_strdup (hostname); - session->priv->console_name = g_strdup (console_name); - session->priv->standard_output_fd = standard_output_fd; - session->priv->standard_error_fd = standard_error_fd; - - session->priv->worker_message_pipe_fd = worker_message_pipe_fd; - session->priv->worker_pid = worker_pid; - session->priv->next_expected_message = GDM_SESSION_MESSAGE_TYPE_VERIFICATION; - session->priv->query_answer = NULL; - - gdm_session_watch_child (session); - - message = gdm_session_verification_message_new (service_name, - username, - hostname, - console_name, - standard_output_fd, - standard_error_fd); - gdm_write_message (session->priv->worker_message_pipe_fd, message, message->size, - NULL); - gdm_session_message_free (message); - - return TRUE; -} - -gboolean -gdm_session_open (GdmSession *session, - const char *service_name, - const char *hostname, - const char *console_name, - int standard_output_fd, - int standard_error_fd, - GError **error) -{ - g_return_val_if_fail (session != NULL, FALSE); - g_return_val_if_fail (service_name != NULL, FALSE); - g_return_val_if_fail (console_name != NULL, FALSE); - - return gdm_session_open_with_worker (session, service_name, - NULL, hostname, console_name, - standard_output_fd, - standard_error_fd, error); -} - -gboolean -gdm_session_open_for_user (GdmSession *session, - const char *service_name, - const char *username, - const char *hostname, - const char *console_name, - int standard_output_fd, - int standard_error_fd, - GError **error) -{ - g_return_val_if_fail (session != NULL, FALSE); - g_return_val_if_fail (service_name != NULL, FALSE); - g_return_val_if_fail (username != NULL, FALSE); - g_return_val_if_fail (console_name != NULL, FALSE); - - return gdm_session_open_with_worker (session, - service_name, - username, hostname, console_name, - standard_output_fd, - standard_error_fd, error); + g_hash_table_foreach (session->priv->environment, + (GHFunc) send_environment_variable, + session); } void -gdm_session_start_program (GdmSession *session, - int argc, - const char **argv) +gdm_session_start_program (GdmSession *session, + const char *command) { - GdmSessionMessage *message; - int i; - g_return_if_fail (session != NULL); g_return_if_fail (session != NULL); g_return_if_fail (gdm_session_is_running (session) == FALSE); - g_return_if_fail (argv != NULL); - g_return_if_fail (argv[0] != NULL); + g_return_if_fail (command != NULL); - session->priv->arguments = g_new0 (char *, (argc + 1)); + send_environment (session); - for (i = 0; argv[i] != NULL; i++) { - session->priv->arguments[i] = g_strdup (argv[i]); - } - - message = gdm_session_start_program_message_new (argv); - gdm_write_message (session->priv->worker_message_pipe_fd, - message, - message->size, - NULL); - gdm_session_message_free (message); + send_dbus_string_signal (session, "StartProgram", command); } void @@ -4020,21 +1499,13 @@ gdm_session_close (GdmSession *session) { g_return_if_fail (session != NULL); - gdm_session_unwatch_child (session); - session->priv->next_expected_message = GDM_SESSION_MESSAGE_TYPE_VERIFICATION; - - if (session->priv->worker_message_pipe_fd > 0) { - close (session->priv->worker_message_pipe_fd); - session->priv->worker_message_pipe_fd = -1; - } - - if (session->priv->worker_pid > 0) { - if (session->priv->is_running) + if (session->priv->job != NULL) { + if (session->priv->is_running) { gdm_session_write_record (session, GDM_SESSION_RECORD_TYPE_LOGOUT); - kill (-session->priv->worker_pid, SIGTERM); - waitpid (session->priv->worker_pid, NULL, 0); - session->priv->worker_pid = -1; + } + + gdm_session_worker_job_stop (session->priv->job); } session->priv->is_running = FALSE; @@ -4045,11 +1516,6 @@ gdm_session_close (GdmSession *session) session->priv->service_name = NULL; } - if (session->priv->arguments) { - g_strfreev (session->priv->arguments); - session->priv->arguments = NULL; - } - if (session->priv->hostname) { g_free (session->priv->hostname); session->priv->hostname = NULL; @@ -4059,69 +1525,38 @@ gdm_session_close (GdmSession *session) g_free (session->priv->username); session->priv->username = NULL; } - } gboolean gdm_session_is_running (GdmSession *session) { + g_return_val_if_fail (session != NULL, FALSE); + return session->priv->is_running; } void -gdm_session_set_environment_variable (GdmSession *session, - const char *key, - const char *value) +gdm_session_set_environment_variable (GdmSession *session, + const char *key, + const char *value) { - GdmSessionMessage *message; - g_return_if_fail (session != NULL); g_return_if_fail (session != NULL); g_return_if_fail (key != NULL); g_return_if_fail (value != NULL); - message = gdm_session_set_environment_variable_message_new (key, value); - gdm_write_message (session->priv->worker_message_pipe_fd, message, message->size, - NULL); - gdm_session_message_free (message); + g_hash_table_replace (session->priv->environment, + g_strdup (key), + g_strdup (value)); } void -gdm_session_answer_query (GdmSession *session, - const char *answer) +gdm_session_answer_query (GdmSession *session, + const char *answer) { - GdmSessionMessage *reply; - g_return_if_fail (session != NULL); - if (session->priv->query_answer != NULL) - g_free (session->priv->query_answer); - - session->priv->query_answer = g_strdup (answer); - - reply = NULL; - switch (session->priv->next_expected_message) { - case GDM_SESSION_MESSAGE_TYPE_INFO_REPLY: - reply = gdm_session_info_reply_message_new (session->priv->query_answer); - break; - - case GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY: - reply = gdm_session_secret_info_reply_message_new (session->priv->query_answer); - break; - - default: - break; - } - g_free (session->priv->query_answer); - session->priv->query_answer = NULL; - - if (reply != NULL) { - gdm_write_message (session->priv->worker_message_pipe_fd, reply, reply->size, - NULL); - gdm_session_message_free (reply); - } - - session->priv->next_expected_message = GDM_SESSION_MESSAGE_TYPE_INVALID; + answer_pending_query (session, answer); } char * diff --git a/daemon/gdm-session.h b/daemon/gdm-session.h index cd5c0867..a1db5375 100644 --- a/daemon/gdm-session.h +++ b/daemon/gdm-session.h @@ -50,6 +50,9 @@ typedef struct GObjectClass parent_class; /* signals */ + void (* opened) (GdmSession *session); + void (* closed) (GdmSession *session); + void (* user_verified) (GdmSession *session); void (* user_verification_error) (GdmSession *session, @@ -84,7 +87,6 @@ typedef enum _GdmSessionError { GDM_SESSION_ERROR_GENERIC = 0, GDM_SESSION_ERROR_WITH_SESSION_COMMAND, GDM_SESSION_ERROR_FORKING, - GDM_SESSION_ERROR_OPENING_MESSAGE_PIPE, GDM_SESSION_ERROR_COMMUNICATING, GDM_SESSION_ERROR_WORKER_DIED, GDM_SESSION_ERROR_AUTHENTICATING, @@ -103,22 +105,13 @@ gboolean gdm_session_open (GdmSession *session, const char *service_name, const char *hostname, const char *console_name, - int standard_output_fd, - int standard_error_fd, GError **error); - -gboolean gdm_session_open_for_user (GdmSession *session, - const char *service_name, +void gdm_session_close (GdmSession *session); +gboolean gdm_session_begin_verification (GdmSession *session, const char *username, - const char *hostname, - const char *console_name, - int standard_output_fd, - int standard_error_fd, GError **error); void gdm_session_start_program (GdmSession *session, - int argc, - const char **argv); - + const char *command); void gdm_session_set_environment_variable (GdmSession *session, const char *key, const char *value); @@ -128,7 +121,6 @@ void gdm_session_answer_query (GdmSession *session, char * gdm_session_get_username (GdmSession *session); -void gdm_session_close (GdmSession *session); gboolean gdm_session_is_running (GdmSession *session); G_END_DECLS diff --git a/daemon/gdm-simple-slave.c b/daemon/gdm-simple-slave.c index ef6888d3..0be3be9d 100644 --- a/daemon/gdm-simple-slave.c +++ b/daemon/gdm-simple-slave.c @@ -549,15 +549,12 @@ setup_session_environment (GdmSimpleSlave *slave) } static void -on_user_verified (GdmSession *session, - GdmSimpleSlave *slave) +on_user_verified (GdmSession *session, + GdmSimpleSlave *slave) { char *username; - int argc; - char **argv; char *command; char *filename; - GError *error; gboolean res; gdm_greeter_proxy_stop (slave->priv->greeter); @@ -584,20 +581,10 @@ on_user_verified (GdmSession *session, return; } - error = NULL; - res = g_shell_parse_argv (command, &argc, &argv, &error); - if (! res) { - g_warning ("Could not parse command: %s", error->message); - g_error_free (error); - } - - gdm_session_start_program (session, - argc, - (const char **)argv); + gdm_session_start_program (session, command); g_free (filename); g_free (command); - g_strfreev (argv); } static void @@ -696,8 +683,6 @@ on_greeter_connected (GdmGreeterServer *greeter_server, "gdm", NULL /* hostname */, "/dev/console", - STDOUT_FILENO, - STDERR_FILENO, NULL); /* If XDMCP stop pinging */ diff --git a/daemon/session-worker-main.c b/daemon/session-worker-main.c new file mode 100644 index 00000000..2cb5671b --- /dev/null +++ b/daemon/session-worker-main.c @@ -0,0 +1,179 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 <signal.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#define DBUS_API_SUBJECT_TO_CHANGE +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include "gdm-signal-handler.h" +#include "gdm-log.h" +#include "gdm-session-worker.h" + +#define SERVER_DBUS_PATH "/org/gnome/DisplayManager/SessionServer" +#define SERVER_DBUS_INTERFACE "org.gnome.DisplayManager.SessionServer" + +static gboolean +signal_cb (int signo, + gpointer data) +{ + int ret; + + g_debug ("Got callback for signal %d", signo); + + ret = TRUE; + + switch (signo) { + case SIGSEGV: + case SIGBUS: + case SIGILL: + case SIGABRT: + g_debug ("Caught signal %d.", signo); + + ret = FALSE; + break; + + case SIGFPE: + case SIGPIPE: + /* let the fatal signals interrupt us */ + g_debug ("Caught signal %d, shutting down abnormally.", signo); + ret = FALSE; + + break; + + case SIGINT: + case SIGTERM: + /* let the fatal signals interrupt us */ + g_debug ("Caught signal %d, shutting down normally.", signo); + ret = FALSE; + + break; + + case SIGHUP: + g_debug ("Got HUP signal"); + /* FIXME: + * Reread config stuff like system config files, VPN service files, etc + */ + ret = TRUE; + + break; + + case SIGUSR1: + g_debug ("Got USR1 signal"); + /* FIXME: + * Play with log levels or something + */ + ret = TRUE; + + break; + + default: + g_debug ("Caught unhandled signal %d", signo); + ret = TRUE; + + break; + } + + return ret; +} + +int +main (int argc, + char **argv) +{ + GMainLoop *main_loop; + GOptionContext *context; + GdmSessionWorker *worker; + GdmSignalHandler *signal_handler; + const char *address; + static GOptionEntry entries [] = { + { NULL } + }; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + g_type_init (); + + 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 (); + gdm_log_set_debug (TRUE); + + address = g_getenv ("GDM_SESSION_DBUS_ADDRESS"); + if (address == NULL) { + g_warning ("GDM_SESSION_DBUS_ADDRESS not set"); + exit (1); + } + + worker = gdm_session_worker_new (address); + + main_loop = g_main_loop_new (NULL, FALSE); + + signal_handler = gdm_signal_handler_new (); + gdm_signal_handler_set_main_loop (signal_handler, main_loop); + gdm_signal_handler_add (signal_handler, SIGTERM, signal_cb, NULL); + gdm_signal_handler_add (signal_handler, SIGINT, signal_cb, NULL); + gdm_signal_handler_add (signal_handler, SIGILL, signal_cb, NULL); + gdm_signal_handler_add (signal_handler, SIGBUS, signal_cb, NULL); + gdm_signal_handler_add (signal_handler, SIGFPE, signal_cb, NULL); + gdm_signal_handler_add (signal_handler, SIGHUP, signal_cb, NULL); + gdm_signal_handler_add (signal_handler, SIGSEGV, signal_cb, NULL); + gdm_signal_handler_add (signal_handler, SIGABRT, signal_cb, NULL); + gdm_signal_handler_add (signal_handler, SIGUSR1, signal_cb, NULL); + + g_main_loop_run (main_loop); + + if (worker != NULL) { + g_object_unref (worker); + } + + if (signal_handler != NULL) { + g_object_unref (signal_handler); + } + + g_main_loop_unref (main_loop); + + out: + + g_debug ("Worker finished"); + + return 0; +} diff --git a/daemon/test-session.c b/daemon/test-session.c index 73c7ab4d..24682e0f 100644 --- a/daemon/test-session.c +++ b/daemon/test-session.c @@ -33,6 +33,25 @@ static GMainLoop *loop; static void +on_open (GdmSession *session, + const char *username) +{ + GError *error; + gboolean res; + + g_debug ("Got opened: begin auth for %s", username); + + error = NULL; + res = gdm_session_begin_verification (session, + username, + &error); + if (! res) { + g_warning ("Unable to begin verification: %s", error->message); + g_error_free (error); + } +} + +static void on_session_started (GdmSession *session, GPid pid) { @@ -61,15 +80,15 @@ static void on_user_verified (GdmSession *session) { char *username; - const char *argv[] = { "/usr/bin/gedit", "/tmp/foo.log", NULL }; + const char *command = "/usr/bin/gedit /tmp/foo.log"; username = gdm_session_get_username (session); g_print ("%s%ssuccessfully authenticated\n", - username? username : "", username? " " : ""); + username ? username : "", username ? " " : ""); g_free (username); - gdm_session_start_program (session, 2, argv); + gdm_session_start_program (session, command); } static void @@ -183,25 +202,20 @@ main (int argc, if (argc <= 1) { username = NULL; - gdm_session_open (session, - "gdm", - NULL /* hostname */, - ttyname (STDIN_FILENO), - STDOUT_FILENO, - STDERR_FILENO, - NULL); } else { username = argv[1]; - gdm_session_open_for_user (session, - "gdm", - username, - NULL, - ttyname (STDIN_FILENO), - STDOUT_FILENO, - STDERR_FILENO, - NULL); } + gdm_session_open (session, + "gdm", + "", + ttyname (STDIN_FILENO), + NULL); + + g_signal_connect (session, "opened", + G_CALLBACK (on_open), + username); + g_signal_connect (session, "info", G_CALLBACK (on_info), NULL); |