/* session.c - authenticates and authorizes users with system * * Copyright (C) 2006 Ray Strode * * 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. * * TODO: - close should be nicer and shutdown * pam etc * - message validation code is a noop right now. * either fix the validate functions, or drop them * (and the sentinal magic) * - audit libs (linux and solaris) support */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "session.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "marshal.h" #ifndef GDM_BAD_SESSION_RECORDS_FILE #define GDM_BAD_SESSION_RECORDS_FILE "/var/log/btmp" #endif #ifndef GDM_NEW_SESSION_RECORDS_FILE #define GDM_NEW_SESSION_RECORDS_FILE "/var/log/wtmp" #endif #ifndef GDM_MAX_OPEN_FILE_DESCRIPTORS #define GDM_MAX_OPEN_FILE_DESCRIPTORS 1024 #endif #ifndef GDM_OPEN_FILE_DESCRIPTORS_DIR #define GDM_OPEN_FILE_DESCRIPTORS_DIR "/proc/self/fd" #endif #ifndef GDM_MAX_MESSAGE_SIZE #define GDM_MAX_MESSAGE_SIZE (8192) #endif #ifndef GDM_MAX_SERVICE_NAME_SIZE #define GDM_MAX_SERVICE_NAME_SIZE 512 #endif #ifndef GDM_MAX_CONSOLE_NAME_SIZE #define GDM_MAX_CONSOLE_NAME_SIZE 1024 #endif #ifndef GDM_MAX_HOSTNAME_SIZE #define GDM_MAX_HOSTNAME_SIZE 1024 #endif #ifndef GDM_MAX_LOG_FILENAME_SIZE #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 #ifdef GDM_SESSION_ENABLE_TEST #include #define gdm_debug(format, args...) g_printerr(format "\n", ##args) #define gdm_error(format, args...) g_printerr(format "\n", ##args) #define gdm_open_dev_null(mode) open("/dev/null", mode) #else #include "misc.h" #endif typedef enum _GdmSessionMessageType GdmSessionMessageType; 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 enum _GdmSessionRecordType GdmSessionRecordType; typedef struct _GdmSessionWorker GdmSessionWorker; typedef enum _GdmSessionWorkerMessageType GdmSessionWorkerMessageType; 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); 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 }; enum _GdmSessionRecordType { GDM_SESSION_RECORD_TYPE_LOGIN, GDM_SESSION_RECORD_TYPE_FAILED_ATTEMPT, GDM_SESSION_RECORD_TYPE_LOGOUT, }; struct _GdmSessionPrivate { GPid pid; gchar *service_name; gchar **arguments; gchar *username; gchar *hostname; gchar *console_name; gint standard_output_fd; gint standard_error_fd; gint 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 */ gchar *query_answer; guint32 is_verified : 1; guint32 is_running : 1; }; struct _GdmSessionMessage { GdmSessionMessageType type; gsize size; }; struct _GdmSessionVerificationMessage { GdmSessionMessage header; gchar service_name[GDM_MAX_SERVICE_NAME_SIZE]; gchar console_name[GDM_MAX_CONSOLE_NAME_SIZE]; gchar hostname[GDM_MAX_HOSTNAME_SIZE]; gint standard_output_fd; gint standard_error_fd; guint32 hostname_is_provided : 1; gssize username_size; gchar username[0]; }; struct _GdmSessionStartProgramMessage { GdmSessionMessage header; gsize arguments_size; gchar arguments[0]; }; struct _GdmSessionSetEnvironmentVariableMessage { GdmSessionMessage header; gsize environment_variable_size; gchar environment_variable[0]; }; struct _GdmSessionInfoReplyMessage { GdmSessionMessage header; gssize answer_size; gchar answer[0]; }; struct _GdmSessionSecretInfoReplyMessage { GdmSessionMessage header; gssize answer_size; gchar answer[0]; }; struct _GdmSessionWorker { GMainLoop *event_loop; gint exit_code; pam_handle_t *pam_handle; gint message_pipe_fd; GSource *message_pipe_source; GSList *inherited_fd_list; GPid child_pid; GSource *child_watch_source; gchar *username; gchar **arguments; GHashTable *environment; gint standard_output_fd; gint standard_error_fd; guint32 credentials_are_established : 1; guint32 is_running : 1; }; 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, }; struct _GdmSessionWorkerMessage { GdmSessionWorkerMessageType type; gsize size; }; struct _GdmSessionWorkerInfoRequestMessage { GdmSessionWorkerMessage header; gssize question_size; gchar question[0]; }; struct _GdmSessionWorkerUsernameChangedMessage { GdmSessionWorkerMessage header; gssize username_size; char username[0]; }; struct _GdmSessionWorkerSecretInfoRequestMessage { GdmSessionWorkerMessage header; gssize question_size; gchar question[0]; }; struct _GdmSessionWorkerInfoMessage { GdmSessionWorkerMessage header; gssize info_size; gchar info[0]; }; struct _GdmSessionWorkerProblemMessage { GdmSessionWorkerMessage header; gssize problem_size; gchar problem[0]; }; struct _GdmSessionWorkerSessionStartedMessage { GdmSessionWorkerMessage header; GPid pid; }; struct _GdmSessionWorkerSessionStartupFailedMessage { GdmSessionWorkerMessage header; GQuark error_domain; gint error_code; gssize error_message_size; gchar error_message[0]; }; struct _GdmSessionWorkerSessionExitedMessage { GdmSessionWorkerMessage header; gint exit_code; }; struct _GdmSessionWorkerSessionDiedMessage { GdmSessionWorkerMessage header; gint signal_number; }; struct _GdmSessionWorkerVerifiedMessage { GdmSessionWorkerMessage header; }; struct _GdmSessionWorkerVerificationFailedMessage { GdmSessionWorkerMessage header; GQuark error_domain; gint error_code; gssize error_message_size; gchar error_message[0]; }; static void gdm_session_finalize (GObject *object); static void gdm_session_class_install_signals (GdmSessionClass *session_class); static void gdm_session_user_verification_error_handler (GdmSession *session, GError *error); static void gdm_session_started_handler (GdmSession *session, GPid pid); static void gdm_session_startup_error_handler (GdmSession *session, GError *error); static void gdm_session_exited_handler (GdmSession *session, gint exit_code); static void gdm_session_unwatch_child (GdmSession *session); static gboolean gdm_write_message (gint socket_fd, gpointer message_data, gsize message_data_size, gchar **error_message); static GdmSessionWorker *gdm_session_worker_new (void); static void gdm_session_worker_free (GdmSessionWorker *worker); static gboolean gdm_open_bidirectional_pipe (gint *fd1, gint *fd2, gchar **error_message); static void gdm_session_worker_close_open_fds (GdmSessionWorker *worker); static gboolean gdm_session_worker_get_username (GdmSessionWorker *worker, gchar **username); static void gdm_session_worker_uninitialize_pam (GdmSessionWorker *worker, int error_code); static gboolean gdm_session_worker_process_asynchronous_message (GdmSessionWorker *worker, GdmSessionMessage *message); static void gdm_session_worker_set_environment_variable (GdmSessionWorker *worker, const gchar *environment_variable); static gboolean gdm_session_worker_environment_variable_is_set (GdmSessionWorker *worker, const gchar *name); static void gdm_session_worker_update_environment_from_passwd_entry (GdmSessionWorker *worker, struct passwd *passwd_entry); 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 gchar *question); static GdmSessionWorkerMessage *gdm_session_worker_username_changed_message_new (const gchar *new_username); static GdmSessionWorkerMessage *gdm_session_worker_secret_info_request_message_new (const gchar *question); static GdmSessionWorkerMessage *gdm_session_worker_info_message_new (const gchar *info); static GdmSessionWorkerMessage *gdm_session_worker_problem_message_new (const gchar *problem); static GdmSessionWorkerMessage *gdm_session_worker_session_started_message_new (GPid pid); static GdmSessionWorkerMessage *gdm_session_worker_session_exited_message_new (gint exit_code); static GdmSessionWorkerMessage *gdm_session_worker_session_died_message_new (gint signal_number); static void gdm_session_worker_message_free (GdmSessionWorkerMessage *message); enum { USER_VERIFIED = 0, USER_VERIFICATION_ERROR, INFO, PROBLEM, INFO_QUERY, SECRET_INFO_QUERY, SESSION_STARTED, SESSION_STARTUP_ERROR, SESSION_EXITED, SESSION_DIED, NUMBER_OF_SIGNALS }; static guint gdm_session_signals[NUMBER_OF_SIGNALS]; G_DEFINE_TYPE (GdmSession, gdm_session, G_TYPE_OBJECT); static void gdm_session_class_init (GdmSessionClass *session_class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (session_class); object_class->finalize = gdm_session_finalize; gdm_session_class_install_signals (session_class); g_type_class_add_private (session_class, sizeof (GdmSessionPrivate)); } static void gdm_session_class_install_signals (GdmSessionClass *session_class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (session_class); gdm_session_signals[USER_VERIFIED] = g_signal_new ("user-verified", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GdmSessionClass, user_verified), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); session_class->user_verified = NULL; gdm_session_signals[USER_VERIFICATION_ERROR] = g_signal_new ("user-verification-error", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GdmSessionClass, user_verification_error), NULL, NULL, gdm_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); session_class->user_verification_error = gdm_session_user_verification_error_handler; gdm_session_signals[INFO_QUERY] = g_signal_new ("info-query", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmSessionClass, info_query), NULL, NULL, gdm_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); session_class->info_query = NULL; gdm_session_signals[SECRET_INFO_QUERY] = g_signal_new ("secret-info-query", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmSessionClass, secret_info_query), NULL, NULL, gdm_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); session_class->secret_info_query = NULL; gdm_session_signals[INFO] = g_signal_new ("info", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GdmSessionClass, info), NULL, NULL, gdm_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); session_class->info = NULL; gdm_session_signals[PROBLEM] = g_signal_new ("problem", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GdmSessionClass, problem), NULL, NULL, gdm_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); session_class->problem = NULL; gdm_session_signals[SESSION_STARTED] = g_signal_new ("session-started", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GdmSessionClass, session_started), NULL, NULL, gdm_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); session_class->session_started = gdm_session_started_handler; gdm_session_signals[SESSION_STARTUP_ERROR] = g_signal_new ("session-startup-error", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GdmSessionClass, session_startup_error), NULL, NULL, gdm_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); session_class->session_startup_error = gdm_session_startup_error_handler; gdm_session_signals[SESSION_EXITED] = g_signal_new ("session-exited", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GdmSessionClass, session_exited), NULL, NULL, gdm_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); session_class->session_exited = gdm_session_exited_handler; gdm_session_signals[SESSION_DIED] = g_signal_new ("session-died", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GdmSessionClass, session_exited), NULL, NULL, gdm_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); session_class->session_died = NULL; } static void gdm_session_init (GdmSession *session) { session->priv = G_TYPE_INSTANCE_GET_PRIVATE (session, GDM_TYPE_SESSION, GdmSessionPrivate); } static void gdm_session_finalize (GObject *object) { GdmSession *session; GObjectClass *parent_class; session = GDM_SESSION (object); g_strfreev (session->priv->arguments); g_free (session->priv->username); parent_class = G_OBJECT_CLASS (gdm_session_parent_class); if (parent_class->finalize != NULL) parent_class->finalize (object); } GQuark gdm_session_error_quark (void) { static GQuark error_quark = 0; if (error_quark == 0) error_quark = g_quark_from_static_string ("gdm-session"); return error_quark; } static gboolean gdm_read_message (gint socket_fd, gpointer *message_data, gsize *message_data_size, gchar **error_message) { struct msghdr message = { 0 }; struct iovec data_block = { 0 }; const gint 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) { gdm_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 gchar *error; error = g_strerror (errno); gdm_debug ("message was not received: %s", error); *error_message = g_strdup (error); return FALSE; } else if (num_bytes_received == 0) { gdm_debug ("message was not received: premature eof"); *error_message = g_strdup ("premature eof"); return FALSE; } gdm_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 gchar *service_name, const gchar *username, const gchar *hostname, const gchar *console_name, gint standard_output_fd, gint standard_error_fd) { GdmSessionVerificationMessage *message; gsize size, username_size; username_size = (username != NULL? strlen (username) + 1 : 0); size = sizeof (GdmSessionVerificationMessage) + username_size + sizeof (GDM_SESSION_MESSAGE_SENTINAL); 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 ((gchar *) (gchar *) ((guint *) message) + size - sizeof (GDM_SESSION_MESSAGE_SENTINAL), GDM_SESSION_MESSAGE_SENTINAL, sizeof (GDM_SESSION_MESSAGE_SENTINAL)); return (GdmSessionMessage *) message; } static gchar * gdm_session_flatten_arguments (const gchar * const * argv, gsize *arguments_size) { gchar *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); total_size += argument_size; } total_size++; if (arguments_size) *arguments_size = total_size; return arguments; } static gchar ** gdm_session_unflatten_arguments (const gchar *arguments) { GPtrArray *array; gchar *argument; array = g_ptr_array_new (); argument = (gchar *) arguments; while (*argument != '\0') { g_ptr_array_add (array, g_strdup (argument)); argument += strlen (argument) + 1; } g_ptr_array_add (array, NULL); return (gchar **) g_ptr_array_free (array, FALSE); } static GdmSessionMessage * gdm_session_start_program_message_new (const gchar * const * args) { GdmSessionStartProgramMessage *message; gchar *arguments; gsize size, 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 ((gchar *) ((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 gchar *name, const gchar *value) { GdmSessionSetEnvironmentVariableMessage *message; gchar *environment_variable; gsize size, environment_variable_size; gint length; 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_strlcpy ((gchar *) ((guint *) message) + size - sizeof (GDM_SESSION_MESSAGE_SENTINAL), GDM_SESSION_MESSAGE_SENTINAL, sizeof (GDM_SESSION_MESSAGE_SENTINAL)); return (GdmSessionMessage *) message; } static GdmSessionMessage * gdm_session_info_reply_message_new (const char *answer) { GdmSessionInfoReplyMessage *message; gsize size, 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); 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; g_strlcpy ((gchar *) ((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, 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; if (answer != NULL) { g_strlcpy (message->answer, answer, answer_size); message->answer_size = answer_size; } else message->answer_size = -1; g_strlcpy ((gchar *) ((guint *) message) + size - sizeof (GDM_SESSION_MESSAGE_SENTINAL), GDM_SESSION_MESSAGE_SENTINAL, sizeof (GDM_SESSION_MESSAGE_SENTINAL)); return (GdmSessionMessage *) message; } static void gdm_session_message_free (GdmSessionMessage *message) { g_slice_free1 (message->size, message); } static void gdm_session_write_record (GdmSession *session, GdmSessionRecordType record_type) { struct utmp session_record = { 0 }; GTimeVal now = { 0 }; gchar *hostname; gdm_debug ("writing %s record", record_type == GDM_SESSION_RECORD_TYPE_LOGIN? "session" : record_type == GDM_SESSION_RECORD_TYPE_LOGOUT? "logout" : "failed session attempt"); if (record_type != GDM_SESSION_RECORD_TYPE_LOGOUT) { /* it's possible that PAM failed before it mapped the user input * into a valid username, so we fallback to try using "(unknown)" */ if (session->priv->username != NULL) strncpy (session_record.ut_user, session->priv->username, sizeof (session_record.ut_user)); else strncpy (session_record.ut_user, "(unknown)", sizeof (session_record.ut_user)); } gdm_debug ("using username %.*s", sizeof (session_record.ut_user), session_record.ut_user); /* FIXME: I have no idea what to do for ut_id. */ strncpy (session_record.ut_id, session->priv->console_name + strlen (session->priv->console_name) - sizeof (session_record.ut_id), sizeof (session_record.ut_id)); gdm_debug ("using id %.*s", sizeof (session_record.ut_id), session_record.ut_id); if (g_str_has_prefix (session->priv->console_name, "/dev/")) { strncpy (session_record.ut_line, session->priv->console_name + strlen ("/dev/"), sizeof (session_record.ut_line)); } else if (g_str_has_prefix (session->priv->console_name, ":")) { strncpy (session_record.ut_line, session->priv->console_name, sizeof (session_record.ut_line)); } gdm_debug ("using line %.*s", sizeof (session_record.ut_line), session_record.ut_line); /* FIXME: this is a bit of a mess. Figure out how * wrong the logic is */ hostname = NULL; if ((session->priv->hostname != NULL) && g_str_has_prefix (session->priv->console_name, ":")) hostname = g_strdup_printf ("%s%s", session->priv->hostname, session->priv->console_name); else if ((session->priv->hostname != NULL) && !strstr (session->priv->console_name, ":")) hostname = g_strdup (session->priv->hostname); else if (!g_str_has_prefix (session->priv->console_name, ":") && strstr (session->priv->console_name, ":")) hostname = g_strdup (session->priv->console_name); if (hostname) { gdm_debug ("using hostname %.*s", sizeof (session_record.ut_host), session_record.ut_host); strncpy (session_record.ut_host, hostname, sizeof (session_record.ut_host)); g_free (hostname); } g_get_current_time (&now); session_record.ut_tv.tv_sec = now.tv_sec; session_record.ut_tv.tv_usec = now.tv_usec; gdm_debug ("using time %ld", (glong) session_record.ut_tv.tv_sec); session_record.ut_type = USER_PROCESS; gdm_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; gdm_debug ("using pid %d", (gint) session_record.ut_pid); switch (record_type) { case GDM_SESSION_RECORD_TYPE_LOGIN: gdm_debug ("writing session record to " GDM_NEW_SESSION_RECORDS_FILE); updwtmp (GDM_NEW_SESSION_RECORDS_FILE, &session_record); break; case GDM_SESSION_RECORD_TYPE_LOGOUT: gdm_debug ("writing logout record to " GDM_NEW_SESSION_RECORDS_FILE); updwtmp (GDM_NEW_SESSION_RECORDS_FILE, &session_record); break; case GDM_SESSION_RECORD_TYPE_FAILED_ATTEMPT: gdm_debug ("writing failed session attempt record to " GDM_BAD_SESSION_RECORDS_FILE); updwtmp (GDM_BAD_SESSION_RECORDS_FILE, &session_record); break; } } static void gdm_session_user_verification_error_handler (GdmSession *session, GError *error) { gdm_session_write_record (session, GDM_SESSION_RECORD_TYPE_FAILED_ATTEMPT); } static void gdm_session_started_handler (GdmSession *session, GPid pid) { gdm_session_write_record (session, GDM_SESSION_RECORD_TYPE_LOGIN); } static void gdm_session_startup_error_handler (GdmSession *session, GError *error) { gdm_session_write_record (session, GDM_SESSION_RECORD_TYPE_LOGIN); } static void gdm_session_exited_handler (GdmSession *session, gint exit_code) { gdm_session_write_record (session, GDM_SESSION_RECORD_TYPE_LOGOUT); } GdmSession * gdm_session_new (void) { GdmSession *session; session = g_object_new (GDM_TYPE_SESSION, NULL); return session; } static void gdm_session_clear_child_watch_source (GdmSession *session) { session->priv->child_watch_source = NULL; } static void gdm_session_clear_message_pipe_source (GdmSession *session) { session->priv->worker_message_pipe_source = NULL; } static void gdm_session_on_child_exited (GPid pid, gint status, GdmSession *session) { 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_spawn_close_pid (pid); session->priv->worker_pid = 0; } static gboolean gdm_session_validate_message_size (GdmSession *session, GdmSessionWorkerMessage *message, GError **error) { 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; 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: gdm_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; } if (message->size != expected_size) { gdm_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; gchar *error_message; GdmSessionWorkerMessage *message; gsize message_size; gdm_debug ("attemping to read message from worker..."); error_message = NULL; if (!gdm_read_message (session->priv->worker_message_pipe_fd, (gpointer) &message, &message_size, &error_message)) { gdm_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; } gdm_debug ("message type is '0x%x'", message->type); if (message_size != message->size) { gdm_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; } gdm_debug ("message size is '%lu'", (gulong) message_size); gdm_debug ("validating that message size is right for message " "type..."); size_error = NULL; if (!gdm_session_validate_message_size (session, message, &size_error)) { g_propagate_error (error, size_error); return NULL; } gdm_debug ("message size is valid"); switch (message->type) { case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFIED: gdm_debug ("received valid 'verified' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFICATION_FAILED: gdm_debug ("received valid 'verification failed' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_USERNAME_CHANGED: gdm_debug ("received valid 'username changed' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO_REQUEST: gdm_debug ("received valid 'info request' message from worker"); gdm_debug ("***********MESSAGE QUESTION IS '%s'********", ((GdmSessionWorkerInfoRequestMessage *) message)->question); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_SECRET_INFO_REQUEST: gdm_debug ("received valid 'secret info request' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO: gdm_debug ("received valid 'info' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_PROBLEM: gdm_debug ("received valid 'problem' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTED: gdm_debug ("received valid 'session started' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTUP_FAILED: gdm_debug ("received valid 'session startup failed' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_EXITED: gdm_debug ("received valid 'session exited' message from worker"); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_DIED: gdm_debug ("received valid 'session died' message from worker"); break; default: gdm_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) { gdm_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; gdm_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) { gdm_debug ("changing username from '%s' to '%s'", session->priv->username != NULL? session->priv->username : "", (message->username_size >= 0)? message->username : ""); 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; gdm_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); session->priv->next_expected_message = GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY; gdm_debug ("Emitting 'secret-info-query' signal"); g_signal_emit (session, gdm_session_signals[SECRET_INFO_QUERY], 0, message->question); } static void gdm_session_handle_info_message (GdmSession *session, GdmSessionWorkerInfoMessage *message) { gdm_debug ("Emitting 'info' signal"); g_signal_emit (session, gdm_session_signals[INFO], 0, message->info); } static void gdm_session_handle_problem_message (GdmSession *session, GdmSessionWorkerProblemMessage *message) { gdm_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) { gdm_debug ("Emitting 'session-started' signal with pid '%d'", (gint) message->pid); session->priv->pid = message->pid; session->priv->is_running = TRUE; g_signal_emit (session, gdm_session_signals[SESSION_STARTED], 0, (gint) message->pid); } static void gdm_session_handle_session_startup_failed_message (GdmSession *session, GdmSessionWorkerSessionStartupFailedMessage *message) { GError *error; gdm_debug ("Emitting 'session-startup-error' signal"); 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) { gdm_debug ("Emitting 'session-exited' signal with exit code '%d'", message->exit_code); 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) { gdm_debug ("Emitting 'session-died' signal with signal number '%d'", message->signal_number); session->priv->is_running = FALSE; g_signal_emit (session, gdm_session_signals[SESSION_DIED], 0, message->signal_number); } static void gdm_session_incoming_message_handler (GdmSession *session) { GdmSessionWorkerMessage *message; GError *error; error = NULL; message = gdm_session_get_incoming_message (session, &error); if (message == NULL) { g_assert (error != NULL); gdm_error ("could not receive message from parent: %s\n", error->message); g_error_free (error); /* FIXME: figure out what to do here */ return; } switch (message->type) { case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFIED: gdm_debug ("worker successfully verified user"); gdm_session_handle_verified_message (session, (GdmSessionWorkerVerifiedMessage *) message); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_VERIFICATION_FAILED: gdm_debug ("worker could not verify user"); gdm_session_handle_verification_failed_message (session, (GdmSessionWorkerVerificationFailedMessage *) message); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_USERNAME_CHANGED: gdm_debug ("received changed username from worker"); gdm_session_handle_username_changed_message (session, (GdmSessionWorkerUsernameChangedMessage *) message); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_INFO_REQUEST: gdm_debug ("received info request from worker"); gdm_session_handle_info_request_message (session, (GdmSessionWorkerInfoRequestMessage *) message); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_SECRET_INFO_REQUEST: gdm_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: gdm_debug ("received new info from worker"); gdm_session_handle_info_message (session, (GdmSessionWorkerInfoMessage *) message); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_PROBLEM: gdm_debug ("received problem from worker"); gdm_session_handle_problem_message (session, (GdmSessionWorkerProblemMessage *) message); break; case GDM_SESSION_WORKER_MESSAGE_TYPE_SESSION_STARTED: gdm_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: gdm_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: gdm_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: gdm_debug ("received session died message from worker"); gdm_session_handle_session_died_message (session, (GdmSessionWorkerSessionDiedMessage *) message); break; default: gdm_debug ("received unknown message of type '0x%x' from worker", message->type); break; } gdm_session_worker_message_free (message); } static gboolean gdm_session_data_on_message_pipe_handler (GIOChannel *channel, GIOCondition condition, GdmSession *session) { if ((condition & G_IO_IN) || (condition & G_IO_PRI)) { gdm_debug ("got message from message pipe"); gdm_session_incoming_message_handler (session); } if ((condition & G_IO_ERR) || (condition & G_IO_HUP)) { gdm_debug ("got disconnected message from message pipe"); g_error("not implemented"); /*gdm_session_disconnected_handler (session);*/ return FALSE; } return TRUE; } static void gdm_session_watch_child (GdmSession *session) { 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, (GSourceFunc) (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, (GSourceFunc) (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); } static void gdm_session_unwatch_child (GdmSession *session) { if (session->priv->child_watch_source == NULL) return; 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) { gdm_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; 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: gdm_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; } if (message->size != expected_size) { gdm_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 GdmSessionMessage * gdm_session_worker_get_incoming_message (GdmSessionWorker *worker, GError **error) { GError *size_error; gchar *error_message; GdmSessionMessage *message; gsize message_size; gdm_debug ("attemping to read message from parent..."); error_message = NULL; if (!gdm_read_message (worker->message_pipe_fd, (gpointer) &message, &message_size, &error_message)) { gdm_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; } gdm_debug ("message type is '0x%x'", message->type); if (message_size != message->size) { gdm_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; } gdm_debug ("message size is '%lu'", (gulong) message_size); gdm_debug ("validating that message size is right for message " "type..."); size_error = NULL; if (!gdm_session_worker_validate_message_size (worker, message, &size_error)) { g_propagate_error (error, size_error); return NULL; } gdm_debug ("message size is valid"); switch (message->type) { case GDM_SESSION_MESSAGE_TYPE_VERIFICATION: gdm_debug ("user session verification request received"); break; case GDM_SESSION_MESSAGE_TYPE_START_PROGRAM: gdm_debug ("session arguments received"); break; case GDM_SESSION_MESSAGE_TYPE_SET_ENVIRONMENT_VARIABLE: gdm_debug ("environment variable received"); break; case GDM_SESSION_MESSAGE_TYPE_INFO_REPLY: gdm_debug ("user session info reply received"); break; case GDM_SESSION_MESSAGE_TYPE_SECRET_INFO_REPLY: gdm_debug ("user session secret info reply received"); break; default: gdm_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; } return message; } static gchar * gdm_session_worker_ask_question (GdmSessionWorker *worker, const gchar *question) { GdmSessionWorkerMessage *message; GdmSessionMessage *reply; GdmSessionInfoReplyMessage *info_reply; gchar *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); error = NULL; do { reply = gdm_session_worker_get_incoming_message (worker, &error); if (reply == NULL) { gdm_error ("could not receive message from parent: %s", error->message); g_error_free (error); /* FIXME: figure out what to do here */ return NULL; } if (gdm_session_worker_process_asynchronous_message (worker, reply)) { gdm_session_message_free (reply); reply = NULL; } } 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) { gdm_debug ("discarding unexpected message of type 0x%x", reply->type); gdm_session_message_free (reply); reply = NULL; return NULL; } 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; return answer; } static gchar * gdm_session_worker_ask_for_secret (GdmSessionWorker *worker, const gchar *secret) { GdmSessionWorkerMessage *message; GdmSessionMessage *reply; GdmSessionSecretInfoReplyMessage *secret_info_reply; gchar *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); error = NULL; do { reply = gdm_session_worker_get_incoming_message (worker, &error); if (reply == NULL) { gdm_error ("could not receive message from parent: %s", error->message); g_error_free (error); /* FIXME: figure out what to do here */ return NULL; } if (gdm_session_worker_process_asynchronous_message (worker, reply)) { gdm_session_message_free (reply); reply = NULL; } } 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) { gdm_debug ("discarding unexpected message of type 0x%x", reply->type); gdm_session_message_free (reply); return NULL; } secret_info_reply = (GdmSessionSecretInfoReplyMessage *) reply; answer = g_strdup (secret_info_reply->answer); gdm_session_message_free (reply); gdm_debug ("answer to secret question '%s' is '%s'", secret, answer); return answer; } static void gdm_session_worker_report_info (GdmSessionWorker *worker, const gchar *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); } static void gdm_session_worker_report_problem (GdmSessionWorker *worker, const gchar *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, gchar **username) { 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 ((gchar *) item); gdm_debug ("username is '%s'", *username != NULL? *username : ""); } return TRUE; } return FALSE; } static void gdm_session_worker_update_username (GdmSessionWorker *worker) { gchar *username; username = NULL; if (gdm_session_worker_get_username (worker, &username)) { GdmSessionWorkerMessage *message; if ((worker->username == username) || ((worker->username != NULL) && (username != NULL) && (strcmp (worker->username, username) == 0))) goto out; gdm_debug ("setting username to '%s'", username); g_free (worker->username); worker->username = username; username = NULL; 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); } out: g_free (username); } static gboolean gdm_session_worker_process_pam_message (GdmSessionWorker *worker, const struct pam_message *query, char **response_text) { gchar *user_answer; gboolean was_processed; gdm_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: gdm_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); gdm_debug ("trying to get updated username"); gdm_session_worker_update_username (worker); was_processed = TRUE; } 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; gdm_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; gdm_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 gboolean gdm_session_worker_initialize_pam (GdmSessionWorker *worker, const gchar *service, const gchar *username, const gchar *hostname, const gchar *console_name, GError **error) { struct pam_conv pam_conversation; int error_code; g_assert (worker->pam_handle == NULL); gdm_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) { gdm_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")); goto out; } 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; } } if (hostname != NULL) { error_code = pam_set_item (worker->pam_handle, PAM_RHOST, hostname); 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; } } error_code = pam_set_item (worker->pam_handle, PAM_TTY, console_name); 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; } out: if (error_code != PAM_SUCCESS) { gdm_session_worker_uninitialize_pam (worker, error_code); return FALSE; } return TRUE; } static void gdm_session_worker_uninitialize_pam (GdmSessionWorker *worker, int error_code) { gdm_debug ("uninitializing PAM"); if (worker->pam_handle == NULL) return; 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_authenticate_user (GdmSessionWorker *worker, gboolean password_is_required, GError **error) { int error_code, authentication_flags; gdm_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->pam_handle, authentication_flags); if (error_code != PAM_SUCCESS) { g_set_error (error, GDM_SESSION_ERROR, GDM_SESSION_ERROR_AUTHENTICATING, "%s", pam_strerror (worker->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, authentication_flags; gdm_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->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) { gdm_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; } out: if (error_code != PAM_SUCCESS) { gdm_session_worker_uninitialize_pam (worker, error_code); return FALSE; } return TRUE; } static gboolean gdm_session_worker_give_user_credentials (GdmSessionWorker *worker, GError **error) { int error_code; struct passwd *passwd_entry, passwd_buffer; gchar *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; } worker->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 void gdm_session_worker_update_environment_from_passwd_entry (GdmSessionWorker *worker, struct passwd *passwd_entry) { gchar *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 void gdm_session_worker_update_environment_from_pam (GdmSessionWorker *worker) { gchar **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); } static void gdm_session_worker_fill_environment_array (const gchar *key, const gchar *value, GPtrArray *environment) { gchar *variable; if (value == NULL) return; variable = g_strdup_printf ("%s=%s", key, value); g_ptr_array_add (environment, variable); } static gchar ** 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 (gchar **) g_ptr_array_free (environment, FALSE); } static void gdm_session_worker_on_child_exited (GPid pid, gint status, GdmSessionWorker *worker) { GdmSessionWorkerMessage *message; message = NULL; 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)); if (message != NULL) { gdm_write_message (worker->message_pipe_fd, message, message->size, NULL); gdm_session_worker_message_free (message); } 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_clear_child_watch_source (GdmSessionWorker *worker) { worker->child_watch_source = NULL; } 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); } static void gdm_session_worker_unwatch_child (GdmSessionWorker *worker) { if (worker->child_watch_source == NULL) return; g_source_destroy (worker->child_watch_source); worker->child_watch_source = NULL; } static gboolean gdm_session_worker_open_user_session (GdmSessionWorker *worker, GError **error) { int error_code; pid_t session_pid; GdmSessionWorkerMessage *message; gchar *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; gdm_debug ("querying pam for user environment"); gdm_session_worker_update_environment_from_pam (worker); gdm_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) { gchar **environment; gchar *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) { gdm_debug ("could not reset uid - %s", g_strerror (errno)); _exit (1); } if (setsid () < 0) { gdm_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 ("/"); execve (worker->arguments[0], worker->arguments, environment); gdm_debug ("child '%s' could not be started - %s", worker->arguments[0], g_strerror (errno)); g_strfreev (environment); _exit (127); } worker->child_pid = session_pid; gdm_debug ("session opened creating reply..."); g_assert (sizeof (GPid) <= sizeof (gint)); 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)) { gdm_error ("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 gboolean gdm_session_worker_verify_user (GdmSessionWorker *worker, const gchar *service_name, const gchar *username, const gchar *hostname, const gchar *console_name, gboolean password_is_required, GError **error) { GError *pam_error; GdmSessionWorkerMessage *reply; gchar *error_message; pam_error = NULL; if (!gdm_session_worker_initialize_pam (worker, service_name, username, hostname, console_name, &pam_error)) { g_propagate_error (error, pam_error); return FALSE; } /* find out who the user is and ensure they are who they say they are */ if (!gdm_session_worker_authenticate_user (worker, password_is_required, &pam_error)) { 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 */ gdm_debug ("trying to get updated username"); gdm_session_worker_update_username (worker); /* make sure the user is allowed to log in to this system */ if (!gdm_session_worker_authorize_user (worker, password_is_required, &pam_error)) { g_propagate_error (error, pam_error); return FALSE; } /* get kerberos tickets, setup group lists, etc */ if (!gdm_session_worker_give_user_credentials (worker, &pam_error)) { g_propagate_error (error, pam_error); return FALSE; } gdm_debug ("verification process completed, creating reply..."); reply = gdm_session_worker_verified_message_new (); error_message = NULL; if (!gdm_write_message (worker->message_pipe_fd, reply, reply->size, &error_message)) { gdm_error ("could not send 'verified' reply to parent: %s\n", error_message); g_free (error_message); } gdm_session_worker_message_free (reply); return TRUE; } 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 ((gchar *) ((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 ((gchar *) ((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 gchar *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 gchar *new_username) { 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); message->header.type = GDM_SESSION_WORKER_MESSAGE_TYPE_USERNAME_CHANGED; message->header.size = size; if (new_username != NULL) { g_strlcpy (message->username, new_username, username_size); message->username_size = username_size; } else message->username_size = -1; g_strlcpy ((gchar *) ((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 gchar *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 ((gchar *) ((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 gchar *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 ((gchar *) ((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 gchar *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 ((gchar *) ((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 ((gchar *) ((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_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 ((gchar *) ((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 (gint 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 ((gchar *) ((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 (gint 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 ((gchar *) ((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 gboolean gdm_write_message (gint socket_fd, gpointer message_data, gsize message_data_size, gchar **error_message) { struct msghdr message = { 0 }; struct iovec data_block = { 0 }; const gint 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; message_was_sent = FALSE; do { gssize num_bytes_sent; num_bytes_sent = sendmsg (socket_fd, &message, flags); if (num_bytes_sent > 0) { gdm_debug ("sent '%lu' bytes over socket '%d'", (gulong) num_bytes_sent, socket_fd); message_was_sent = TRUE; } } while (!message_was_sent && (errno == EINTR)); if (!message_was_sent && (error_message != NULL)) { const gchar *error; error = g_strerror (errno); gdm_debug ("message was not sent: %s", error); *error_message = g_strdup (error); } else if (message_was_sent) gdm_debug ("message was sent!"); g_free (message.msg_control); return message_was_sent; } static void gdm_session_worker_handle_verification_message (GdmSessionWorker *worker, GdmSessionVerificationMessage *message) { GError *verification_error; GdmSessionWorkerMessage *reply; worker->standard_output_fd = message->standard_output_fd; worker->standard_error_fd = message->standard_error_fd; verification_error = NULL; if (!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)) { g_assert (verification_error != NULL); gdm_error ("%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); gdm_error ("%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; 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; if (!gdm_session_worker_open_user_session (worker, &start_error)) { GdmSessionWorkerMessage *message; g_assert (start_error != NULL); gdm_error ("%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); } } static void gdm_session_worker_set_environment_variable (GdmSessionWorker *worker, const gchar *environment_variable) { gchar **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 gboolean gdm_session_worker_environment_variable_is_set (GdmSessionWorker *worker, const gchar *name) { return g_hash_table_lookup (worker->environment, name) != NULL; } 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) { switch (message->type) { case GDM_SESSION_MESSAGE_TYPE_VERIFICATION: gdm_debug ("received verification request from parent"); gdm_session_worker_handle_verification_message (worker, (GdmSessionVerificationMessage *) message); return TRUE; case GDM_SESSION_MESSAGE_TYPE_START_PROGRAM: gdm_debug ("received new session arguments from parent"); gdm_session_worker_handle_start_program_message (worker, (GdmSessionStartProgramMessage *) message); return TRUE; case GDM_SESSION_MESSAGE_TYPE_SET_ENVIRONMENT_VARIABLE: gdm_debug ("received new environment variable from parent"); gdm_session_worker_handle_set_environment_variable_message (worker, (GdmSessionSetEnvironmentVariableMessage *) message); return TRUE; default: gdm_debug ("received unknown message with type '0x%x' from parent", message->type); return FALSE; } return FALSE; } 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); gdm_error ("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)) { gdm_debug ("got message from message pipe"); gdm_session_worker_incoming_message_handler (worker); } if ((condition & G_IO_ERR) || (condition & G_IO_HUP)) { gdm_debug ("got disconnected message from message pipe"); gdm_session_worker_disconnected_handler (worker); return FALSE; } 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_session_create_worker (GdmSession *session, gint standard_output_fd, gint standard_error_fd, gint *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)) { gdm_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 gdm_debug ("waiting for messages from parent"); child_error = NULL; gdm_session_worker_wait_for_messages (worker); gdm_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 gchar *service_name, const gchar *username, const gchar *hostname, const gchar *console_name, gint standard_output_fd, gint standard_error_fd, GError **error) { GdmSessionMessage *message; GError *worker_error; gint 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) { gdm_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 gchar *service_name, const gchar *hostname, const gchar *console_name, gint standard_output_fd, gint 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 gchar *service_name, const char *username, const gchar *hostname, const gchar *console_name, gint standard_output_fd, gint 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); } void gdm_session_start_program (GdmSession *session, const gchar * const * args) { GdmSessionMessage *message; int argc, 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 (args != NULL); g_return_if_fail (args[0] != NULL); argc = g_strv_length ((gchar **) args); session->priv->arguments = g_new0 (gchar *, (argc + 1)); for (i = 0; args[i] != NULL; i++) session->priv->arguments[i] = g_strdup (args[i]); message = gdm_session_start_program_message_new (args); gdm_write_message (session->priv->worker_message_pipe_fd, message, message->size, NULL); gdm_session_message_free (message); } void 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) 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; } session->priv->is_running = FALSE; session->priv->is_verified = FALSE; if (session->priv->service_name) { g_free (session->priv->service_name); 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; } if (session->priv->username) { g_free (session->priv->username); session->priv->username = NULL; } } gboolean gdm_session_is_running (GdmSession *session) { return session->priv->is_running; } void gdm_session_set_environment_variable (GdmSession *session, const gchar *key, const gchar *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); } void gdm_session_answer_query (GdmSession *session, const gchar *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; } gchar * gdm_session_get_username (GdmSession *session) { g_return_val_if_fail (session != NULL, NULL); return g_strdup (session->priv->username); } 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; } static void gdm_session_worker_free (GdmSessionWorker *worker) { 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_open_bidirectional_pipe (gint *fd1, gint *fd2, gchar **error_message) { gint pipe_fds[2]; g_assert (fd1 != NULL); g_assert (fd2 != NULL); 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; } 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; } 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; } *fd1 = pipe_fds[0]; *fd2 = pipe_fds[1]; return TRUE; } static gint gdm_get_max_open_fds (void) { struct rlimit open_fd_limit; const gint fallback_limit = GDM_MAX_OPEN_FILE_DESCRIPTORS; if (getrlimit (RLIMIT_NOFILE, &open_fd_limit) < 0) { gdm_debug ("could not get file descriptor limit: %s", g_strerror (errno)); gdm_debug ("returning fallback file descriptor limit of %d", fallback_limit); return fallback_limit; } if (open_fd_limit.rlim_cur == RLIM_INFINITY) { gdm_debug ("currently no file descriptor limit, returning fallback limit of %d", fallback_limit); return fallback_limit; } return (gint) open_fd_limit.rlim_cur; } static void gdm_session_worker_close_all_fds (GdmSessionWorker *worker) { int max_open_fds, fd; max_open_fds = gdm_get_max_open_fds (); gdm_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; } if (node == NULL) { gdm_debug ("closing file descriptor '%d'", fd); close (fd); } } gdm_debug ("closed first '%d' file descriptors", max_open_fds); } 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); gdm_debug ("opened '"GDM_OPEN_FILE_DESCRIPTORS_DIR"' on file descriptor '%d'", opendir_fd); } if ((dir == NULL) || (opendir_fd < 0)) { gdm_debug ("could not open "GDM_OPEN_FILE_DESCRIPTORS_DIR": %s", g_strerror (errno)); should_use_fallback = TRUE; } else { gdm_debug ("reading files in '"GDM_OPEN_FILE_DESCRIPTORS_DIR"'"); while ((entry = readdir (dir)) != NULL) { GSList *node; glong filename_as_number; gchar *byte_after_number; if (entry->d_name[0] == '.') continue; gdm_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)) { gdm_debug ("filename '%s' does not appear to represent a " "file descriptor: %s", entry->d_name, strerror (errno)); should_use_fallback = TRUE; } else { fd = (gint) filename_as_number; gdm_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)) { gdm_debug ("closing file descriptor '%d'", fd); close (fd); } else gdm_debug ("will not close file descriptor '%d' because it " "is still neded", fd); } gdm_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); } #ifdef GDM_SESSION_ENABLE_TEST #include #include #include #include static GMainLoop *loop; static void on_session_started (GdmSession *session, GPid pid) { g_print ("session started on pid %d\n", (gint) pid); } static void on_session_exited (GdmSession *session, gint exit_code) { g_print ("session exited with code %d\n", exit_code); exit (0); } static void on_session_died (GdmSession *session, gint signal_number) { g_print ("session died with signal %d, (%s)", signal_number, g_strsignal (signal_number)); exit (1); } static void on_user_verified (GdmSession *session) { gchar *username; const char *args[] = { "/usr/bin/gedit", "/tmp/foo.log", NULL }; username = gdm_session_get_username (session); g_print ("%s%ssuccessfully authenticated\n", username? username : "", username? " " : ""); g_free (username); gdm_session_start_program (session, args); } static void on_user_verification_error (GdmSession *session, GError *error) { gchar *username; username = gdm_session_get_username (session); g_print ("%s%scould not be successfully authenticated: %s\n", username? username : "", username? " " : "", error->message); g_free (username); exit (1); } static void on_info_query (GdmSession *session, const gchar *query_text) { char answer[1024]; struct termio io_info; g_print ("%s ", query_text); fgets (answer, sizeof (answer), stdin); answer[strlen(answer) - 1] = '\0'; if (answer[0] == '\0') { gdm_session_close (session); g_main_loop_quit (loop); } else gdm_session_answer_query (session, answer); } static void on_info (GdmSession *session, const gchar *info) { g_print ("\n** NOTE: %s\n", info); } static void on_problem (GdmSession *session, const gchar *problem) { g_print ("\n** WARNING: %s\n", problem); } static void on_secret_info_query (GdmSession *session, const gchar *query_text) { char answer[1024]; struct termio io_info; g_print ("%s", query_text); ioctl (0, TCGETA, &io_info); io_info.c_lflag &= ~ECHO; ioctl (0, TCSETA, &io_info); fgets (answer, sizeof (answer), stdin); answer[strlen(answer) - 1] = '\0'; ioctl (0, TCGETA, &io_info); io_info.c_lflag |= ECHO; ioctl (0, TCSETA, &io_info); g_print ("\n"); gdm_session_answer_query (session, answer); } static void import_environment (GdmSession *session) { if (g_getenv ("PATH") != NULL) gdm_session_set_environment_variable (session, "PATH", g_getenv ("PATH")); if (g_getenv ("DISPLAY") != NULL) gdm_session_set_environment_variable (session, "DISPLAY", g_getenv ("DISPLAY")); if (g_getenv ("XAUTHORITY") != NULL) gdm_session_set_environment_variable (session, "XAUTHORITY", g_getenv ("XAUTHORITY")); } int main (int argc, char *argv[]) { GdmSession *session; char *username; int exit_code; gchar **args; int i; exit_code = 0; g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); g_type_init (); do { g_message ("creating instance of 'user session' object..."); session = gdm_session_new (); g_message ("'user session' object created successfully"); if (argc <= 1) { username = NULL; gdm_session_open (session, "local", NULL /* hostname */, ttyname (STDIN_FILENO), STDOUT_FILENO, STDERR_FILENO, NULL); } else { username = argv[1]; gdm_session_open_for_user (session, "local", username, NULL, ttyname (STDIN_FILENO), STDOUT_FILENO, STDERR_FILENO, NULL); } g_signal_connect (session, "info", G_CALLBACK (on_info), NULL); g_signal_connect (session, "problem", G_CALLBACK (on_problem), NULL); g_signal_connect (session, "info-query", G_CALLBACK (on_info_query), NULL); g_signal_connect (session, "secret-info-query", G_CALLBACK (on_secret_info_query), NULL); g_signal_connect (session, "user-verified", G_CALLBACK (on_user_verified), NULL); g_signal_connect (session, "user-verification-error", G_CALLBACK (on_user_verification_error), NULL); g_signal_connect (session, "session-started", G_CALLBACK (on_session_started), NULL); g_signal_connect (session, "session-exited", G_CALLBACK (on_session_exited), NULL); g_signal_connect (session, "session-died", G_CALLBACK (on_session_died), NULL); import_environment (session); loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); g_main_loop_unref (loop); g_message ("destroying previously created 'user session' object..."); g_object_unref (session); g_message ("'user session' object destroyed successfully"); } while (1); return exit_code; } #endif /* GDM_SESSION_ENABLE_TEST */