From c5971b535d99725e40330e1b7504320c8ad62745 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:37:57 +0100 Subject: [PATCH] sesexec: Changes to existing files from sesman env.c : The value of XRDP_SESSION in the environment is now set to the PID of the sesexec process, which ties up the session with the output of "xrdp-sesadmin -c=list". Later versions of xrdp-sesadmin can use this value to get information about the current process. --- sesman/sesexec/env.c | 42 +- sesman/sesexec/session.c | 1108 ++++++++++++++++---------------------- sesman/sesexec/session.h | 92 +++- sesman/sesexec/xwait.c | 3 +- 4 files changed, 573 insertions(+), 672 deletions(-) diff --git a/sesman/sesexec/env.c b/sesman/sesexec/env.c index 5e019299..a7e55659 100644 --- a/sesman/sesexec/env.c +++ b/sesman/sesexec/env.c @@ -35,7 +35,7 @@ #include "list.h" #include "log.h" #include "os_calls.h" -#include "sesman.h" +#include "sesexec.h" #include "ssl_calls.h" #include "string_calls.h" #include "xrdp_sockets.h" @@ -62,10 +62,10 @@ env_check_password_file(const char *filename, const char *passwd) ssl_sha1_transform(sha1, passwd, passwd_bytes); ssl_sha1_complete(sha1, passwd_hash); ssl_sha1_info_delete(sha1); - g_snprintf(passwd_hash_text, 39, "%2.2x%2.2x%2.2x%2.2x", + g_snprintf(passwd_hash_text, sizeof(passwd_hash_text), + "%2.2x%2.2x%2.2x%2.2x", (tui8)passwd_hash[0], (tui8)passwd_hash[1], (tui8)passwd_hash[2], (tui8)passwd_hash[3]); - passwd_hash_text[39] = 0; passwd = passwd_hash_text; /* create file from password */ @@ -143,21 +143,23 @@ env_set_user(int uid, char **passwd_file, int display, g_setenv("SHELL", pw_shell, 1); g_setenv("USER", pw_username, 1); g_setenv("LOGNAME", pw_username, 1); - g_sprintf(text, "%d", uid); + g_snprintf(text, sizeof(text), "%d", uid); g_setenv("UID", text, 1); g_setenv("HOME", pw_dir, 1); g_set_current_dir(pw_dir); - g_sprintf(text, ":%d.0", display); + g_snprintf(text, sizeof(text), ":%d.0", display); g_setenv("DISPLAY", text, 1); - g_setenv("XRDP_SESSION", "1", 1); + // Use our PID as the XRDP_SESSION value + g_snprintf(text, sizeof(text), "%d", g_pid); + g_setenv("XRDP_SESSION", text, 1); /* XRDP_SOCKET_PATH should be set even here. It's used by * xorgxrdp and the pulseaudio plugin */ g_setenv("XRDP_SOCKET_PATH", XRDP_SOCKET_PATH, 1); /* pulse sink socket */ - g_snprintf(text, sizeof(text) - 1, CHANSRV_PORT_OUT_BASE_STR, display); + g_snprintf(text, sizeof(text), CHANSRV_PORT_OUT_BASE_STR, display); g_setenv("XRDP_PULSE_SINK_SOCKET", text, 1); /* pulse source socket */ - g_snprintf(text, sizeof(text) - 1, CHANSRV_PORT_IN_BASE_STR, display); + g_snprintf(text, sizeof(text), CHANSRV_PORT_IN_BASE_STR, display); g_setenv("XRDP_PULSE_SOURCE_SOCKET", text, 1); if ((env_names != 0) && (env_values != 0) && (env_names->count == env_values->count)) @@ -189,29 +191,33 @@ env_set_user(int uid, char **passwd_file, int display, len = g_snprintf(NULL, 0, "%s/.vnc/sesman_passwd-%s@%s:%d", pw_dir, pw_username, hostname, display); + ++len; // Allow for terminator - *passwd_file = (char *) g_malloc(len + 1, 1); + *passwd_file = (char *) g_malloc(len, 1); if (*passwd_file != NULL) { /* Try legacy names first, remove if found */ - g_sprintf(*passwd_file, "%s/.vnc/sesman_%s_passwd:%d", - pw_dir, pw_username, display); + g_snprintf(*passwd_file, len, + "%s/.vnc/sesman_%s_passwd:%d", + pw_dir, pw_username, display); if (g_file_exist(*passwd_file)) { LOG(LOG_LEVEL_WARNING, "Removing old " "password file %s", *passwd_file); g_file_delete(*passwd_file); } - g_sprintf(*passwd_file, "%s/.vnc/sesman_%s_passwd", - pw_dir, pw_username); + g_snprintf(*passwd_file, len, + "%s/.vnc/sesman_%s_passwd", + pw_dir, pw_username); if (g_file_exist(*passwd_file)) { LOG(LOG_LEVEL_WARNING, "Removing insecure " "password file %s", *passwd_file); g_file_delete(*passwd_file); } - g_sprintf(*passwd_file, "%s/.vnc/sesman_passwd-%s@%s:%d", - pw_dir, pw_username, hostname, display); + g_snprintf(*passwd_file, len, + "%s/.vnc/sesman_passwd-%s@%s:%d", + pw_dir, pw_username, hostname, display); } } else @@ -219,10 +225,12 @@ env_set_user(int uid, char **passwd_file, int display, /* we use auth_file_path as requested */ len = g_snprintf(NULL, 0, g_cfg->auth_file_path, pw_username); - *passwd_file = (char *) g_malloc(len + 1, 1); + ++len; // Allow for terminator + *passwd_file = (char *) g_malloc(len, 1); if (*passwd_file != NULL) { - g_sprintf(*passwd_file, g_cfg->auth_file_path, pw_username); + g_snprintf(*passwd_file, len, + g_cfg->auth_file_path, pw_username); } } diff --git a/sesman/sesexec/session.c b/sesman/sesexec/session.c index b2318820..d9a0c54c 100644 --- a/sesman/sesexec/session.c +++ b/sesman/sesexec/session.c @@ -48,8 +48,9 @@ #include "guid.h" #include "list.h" #include "log.h" +#include "login_info.h" #include "os_calls.h" -#include "sesman.h" +#include "sesexec.h" #include "string_calls.h" #include "xauth.h" #include "xwait.h" @@ -59,19 +60,101 @@ #define PR_SET_NO_NEW_PRIVS 38 #endif -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) -#define USE_EXTRA_SESSION_FORK -#define USE_BSD_SETLOGIN +struct session_data +{ + pid_t x_server; ///< PID of X server + pid_t win_mgr; ///< PID of window manager + pid_t chansrv; //< PID of chansrv + time_t start_time; + struct session_parameters params; + // Flexible array member used to store strings in params and ip_addr; +#ifdef __cplusplus + char strings[1]; +#else + char strings[]; +#endif +}; + +/******************************************************************************/ +/** + * Create a new session_data structure from a session_parameters object + * + * @param sp Session parameters passed to session_start() + * @return semi-initialised session_data struct + */ +static struct session_data * +session_data_new(const struct session_parameters *sp) +{ + unsigned int string_length = 0; + // What string length do we need? + string_length += g_strlen(sp->shell) + 1; + string_length += g_strlen(sp->directory) + 1; + + struct session_data *sd = (struct session_data *)g_malloc(sizeof(*sd) + string_length, 0); + + if (sd == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating session data struct"); + } + else + { + sd->win_mgr = -1; + sd->x_server = -1; + sd->chansrv = -1; + sd->start_time = 0; + + /* Copy all the non-string session parameters... */ + sd->params = *sp; + + /* ...and then the strings */ + char *memptr = sd->strings; + +#define COPY_STRING(dest,src) \ + (dest) = memptr; \ + strcpy(memptr, src); \ + memptr += strlen(memptr) + 1 + + COPY_STRING(sd->params.shell, sp->shell); + COPY_STRING(sd->params.directory, sp->directory); + +#undef COPY_STRING + } + + return sd; +} + +/******************************************************************************/ +void +session_data_free(struct session_data *session_data) +{ + if (session_data != NULL) + { +#ifdef USE_DEVEL_LOGGING + if (session_data->win_mgr > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid window manager PID %d", + session_data->win_mgr); + } + if (session_data->x_server > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid X server PID %d", + session_data->x_server); + } + if (session_data->chansrv > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid chansrv PID %d", + session_data->chansrv); + } #endif -/* Module globals */ - -/* Currently, these duplicate module names in sesman.c. This is fine, and - * will be fully resolved when sesman is split into two separate excutables */ -static tintptr g_term_event = 0; -static tintptr g_sigchld_event = 0; -static int g_pid = 0; // PID of sesexec process (sesman sub-process) + free(session_data); + } +} +/******************************************************************************/ /** * Creates a string consisting of all parameters that is hosted in the param list * @param self @@ -108,29 +191,7 @@ dumpItemsToString(struct list *self, char *outstr, int len) /******************************************************************************/ static void -set_term_event(int sig) -{ - /* Don't try to use a wait obj in a child process */ - if (g_getpid() == g_pid) - { - g_set_wait_obj(g_term_event); - } -} - -/******************************************************************************/ -static void -set_sigchld_event(int sig) -{ - /* Don't try to use a wait obj in a child process */ - if (g_getpid() == g_pid) - { - g_set_wait_obj(g_sigchld_event); - } -} - -/******************************************************************************/ -static void -start_chansrv(struct auth_info *auth_info, +start_chansrv(struct login_info *login_info, const struct session_parameters *s) { struct list *chansrv_params = list_create(); @@ -152,7 +213,7 @@ start_chansrv(struct auth_info *auth_info, } else { - env_set_user(s->uid, 0, s->display, + env_set_user(login_info->uid, 0, s->display, g_cfg->env_names, g_cfg->env_values); @@ -166,115 +227,20 @@ start_chansrv(struct auth_info *auth_info, } } -/******************************************************************************/ -static int -cleanup_sockets(int display) -{ - LOG(LOG_LEVEL_INFO, "cleanup_sockets:"); - char file[256]; - int error; - - error = 0; - - g_snprintf(file, 255, CHANSRV_PORT_OUT_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, 255, CHANSRV_PORT_IN_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, 255, XRDP_CHANSRV_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, 255, CHANSRV_API_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - /* the following files should be deleted by xorgxrdp - * but just in case the deletion failed */ - - g_snprintf(file, 255, XRDP_X11RDP_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, 255, XRDP_DISCONNECT_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - return error; - -} - /******************************************************************************/ static void -start_window_manager(struct auth_info *auth_info, +start_window_manager(struct login_info *login_info, const struct session_parameters *s) { char text[256]; - env_set_user(s->uid, + env_set_user(login_info->uid, 0, s->display, g_cfg->env_names, g_cfg->env_values); - auth_set_env(auth_info); + auth_set_env(login_info->auth_info); LOG_DEVEL_LEAKING_FDS("window manager", 3, -1); if (s->directory[0] != '\0') @@ -480,7 +446,7 @@ prepare_xvnc_xserver_params(const struct session_parameters *s, /******************************************************************************/ /* Either execs the X server, or returns */ static void -start_x_server(struct auth_info *auth_info, +start_x_server(struct login_info *login_info, const struct session_parameters *s) { char authfile[256]; /* The filename for storing xauth information */ @@ -491,7 +457,7 @@ start_x_server(struct auth_info *auth_info, if (s->type == SCP_SESSION_TYPE_XVNC) { - env_set_user(s->uid, + env_set_user(login_info->uid, &passwd_file, s->display, g_cfg->env_names, @@ -499,7 +465,7 @@ start_x_server(struct auth_info *auth_info, } else { - env_set_user(s->uid, + env_set_user(login_info->uid, 0, s->display, g_cfg->env_names, @@ -573,30 +539,279 @@ start_x_server(struct auth_info *auth_info, } /******************************************************************************/ -/** - * Convert a UID to a username - * - * @param uid UID - * @param uname pointer to output buffer - * @param uname_len Length of output buffer - * @return 0 for success. - */ +/* + * Simple helper process to fork a child and log errors */ static int -username_from_uid(int uid, char *uname, int uname_len) +fork_child( + void (*runproc)(struct login_info *, const struct session_parameters *), + struct login_info *login_info, + const struct session_parameters *s, + pid_t group_pid) { - char *ustr; - int rv = g_getuser_info_by_uid(uid, &ustr, NULL, NULL, NULL, NULL); - - if (rv == 0) + int pid = g_fork(); + if (pid == 0) { - g_snprintf(uname, uname_len, "%s", ustr); - g_free(ustr); + /* Child process */ + if (group_pid >= 0) + { + (void)g_setpgid(0, group_pid); + } + runproc(login_info, s); + g_exit(0); + } + + if (pid < 0) + { + LOG(LOG_LEVEL_ERROR, "Fork failed [%s]", g_get_strerror()); + } + + return pid; +} + +/******************************************************************************/ +enum scp_screate_status +session_start_wrapped(struct login_info *login_info, + const struct session_parameters *s, + struct session_data *sd) +{ + int chansrv_pid; + int display_pid; + int window_manager_pid; + enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + + auth_start_session(login_info->auth_info, s->display); +#ifdef USE_BSD_SETLOGIN + /** + * Create a new session and process group since the 4.4BSD + * setlogin() affects the entire process group + */ + if (g_setsid() < 0) + { + LOG(LOG_LEVEL_WARNING, + "[session start] (display %d): setsid failed - pid %d", + s->display, g_getpid()); + } + + if (g_setlogin(login_info->username) < 0) + { + LOG(LOG_LEVEL_WARNING, + "[session start] (display %d): setlogin failed for user %s - pid %d", + s->display, login_info->username, g_getpid()); + } +#endif + + /* Set the secondary groups before starting the session to prevent + * problems on PAM-based systems (see Linux pam_setcred(3)). + * If we have *BSD setusercontext() this is not done here */ +#ifndef HAVE_SETUSERCONTEXT + if (g_initgroups(login_info->username) != 0) + { + LOG(LOG_LEVEL_ERROR, + "Failed to initialise secondary groups for %s: %s", + login_info->username, g_get_strerror()); + return E_SCP_SCREATE_GENERAL_ERROR; + } +#endif + + /* start the X server in a new process group. + * + * We group the X server, window manager and chansrv in a single + * process group, as it allows signals to be sent to the user session + * without affecting sesexec (and vice-versa). This is particularly + * important when debugging sesexec as we don't want a SIGINT in + * the debugger to be passed to the children */ + display_pid = fork_child(start_x_server, login_info, s, 0); + if (display_pid > 0) + { + enum xwait_status xws; + xws = wait_for_xserver(login_info->uid, + g_cfg->env_names, + g_cfg->env_values, + s->display); + + if (xws != XW_STATUS_OK) + { + switch (xws) + { + case XW_STATUS_TIMED_OUT: + LOG(LOG_LEVEL_ERROR, "Timed out waiting for X server"); + break; + case XW_STATUS_FAILED_TO_START: + LOG(LOG_LEVEL_ERROR, "X server failed to start"); + break; + default: + LOG(LOG_LEVEL_ERROR, + "An error occurred waiting for the X server"); + } + status = E_SCP_SCREATE_X_SERVER_FAIL; + /* Kill it anyway in case it did start and we just failed to + * pick up on it */ + g_sigterm(display_pid); + g_waitpid(display_pid); + } + else + { + LOG(LOG_LEVEL_INFO, "X server :%d is working", s->display); + LOG(LOG_LEVEL_INFO, "Starting window manager for display :%d", + s->display); + + window_manager_pid = fork_child(start_window_manager, + login_info, s, display_pid); + if (window_manager_pid < 0) + { + g_sigterm(display_pid); + g_waitpid(display_pid); + } + else + { + LOG(LOG_LEVEL_INFO, + "Starting the xrdp channel server for display :%d", + s->display); + + chansrv_pid = fork_child(start_chansrv, login_info, + s, display_pid); + + // Tell the caller we've started + LOG(LOG_LEVEL_INFO, + "Session in progress on display :%d. Waiting until the " + "window manager (pid %d) exits to end the session", + s->display, window_manager_pid); + + sd->win_mgr = window_manager_pid; + sd->x_server = display_pid; + sd->chansrv = chansrv_pid; + sd->start_time = g_time1(); + status = E_SCP_SCREATE_OK; + } + } + } + + return status; +} + + +/******************************************************************************/ +enum scp_screate_status +session_start(struct login_info *login_info, + const struct session_parameters *sp, + struct session_data **session_data) +{ + enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + /* Create the session_data struct first */ + struct session_data *sd = session_data_new(sp); + if (sd == NULL) + { + status = E_SCP_SCREATE_NO_MEMORY; } else { - g_snprintf(uname, uname_len, ""); + status = session_start_wrapped(login_info, sp, sd); + if (status == E_SCP_SCREATE_OK) + { + *session_data = sd; + } + else + { + *session_data = NULL; + session_data_free(sd); + } } - return rv; + + return status; +} + +/******************************************************************************/ +static int +cleanup_sockets(int display) +{ + LOG(LOG_LEVEL_INFO, "cleanup_sockets:"); + char file[256]; + int error; + + error = 0; + + g_snprintf(file, 255, CHANSRV_PORT_OUT_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, 255, CHANSRV_PORT_IN_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, 255, XRDP_CHANSRV_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, 255, CHANSRV_API_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + /* the following files should be deleted by xorgxrdp + * but just in case the deletion failed */ + + g_snprintf(file, 255, XRDP_X11RDP_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, 255, XRDP_DISCONNECT_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + return error; } /******************************************************************************/ @@ -627,527 +842,146 @@ exit_status_to_str(const struct exit_status *e, char buff[], int bufflen) } /******************************************************************************/ -static void -run_xrdp_session(const struct session_parameters *s, - struct auth_info *auth_info, - int window_manager_pid, - int display_pid, - int chansrv_pid) +void +session_process_child_exit(struct session_data *sd, + int pid, + const struct exit_status *e) { - int wm_wait_time; - struct exit_status wm_exit_status = {.reason = E_XR_UNEXPECTED, .val = 0}; - int wm_running = 1; - - /* Monitor the amount of time we wait for the - * window manager. This is approximately how long the window - * manager was running for */ - LOG(LOG_LEVEL_INFO, "Session in progress on display :%d. Waiting " - "until the window manager (pid %d) exits to end the session", - s->display, window_manager_pid); - wm_wait_time = g_time1(); - - /* Wait for the window manager to terminate - * - * We can't use g_waitpid() variants for this, as these aren't - * interruptible by a SIGTERM */ - while (wm_running) + if (pid == sd->x_server) { - int robjs_count; - intptr_t robjs[32]; - int pid; - struct exit_status e; - - robjs_count = 0; - robjs[robjs_count++] = g_term_event; - robjs[robjs_count++] = g_sigchld_event; - - if (g_obj_wait(robjs, robjs_count, NULL, 0, 0) != 0) - { - /* should not get here */ - LOG(LOG_LEVEL_WARNING, "run_xrdp_session: " - "Unexpected error from g_obj_wait()"); - g_sleep(100); - continue; - } - - if (g_is_wait_obj_set(g_term_event)) - { - g_reset_wait_obj(g_term_event); - LOG(LOG_LEVEL_INFO, "Received SIGTERM"); - // Pass it on to the window manager - g_sigterm(window_manager_pid); - } - - // Check for any finished children - g_reset_wait_obj(g_sigchld_event); - while ((pid = g_waitchild(&e)) > 0) - { - if (pid == window_manager_pid) - { - wm_running = 0; - wm_exit_status = e; - } - else if (pid == display_pid) - { - LOG(LOG_LEVEL_INFO, "X server pid %d on display :%d finished", - display_pid, s->display); - display_pid = -1; - // No other action - window manager should be going soon - } - else if (pid == chansrv_pid) - { - LOG(LOG_LEVEL_INFO, - "xrdp channel server pid %d on display :%d finished", - chansrv_pid, s->display); - chansrv_pid = -1; - } - } - } - wm_wait_time = g_time1() - wm_wait_time; - - if (wm_exit_status.reason == E_XR_STATUS_CODE && wm_exit_status.val == 0) - { - LOG(LOG_LEVEL_INFO, - "Window manager (pid %d, display %d) finished normally in %d secs", - window_manager_pid, s->display, wm_wait_time); - } - else - { - char reason[128]; - exit_status_to_str(&wm_exit_status, reason, sizeof(reason)); - - LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " - "exited with %s. This " - "could indicate a window manager config problem", - window_manager_pid, s->display, reason); - } - if (wm_wait_time < 10) - { - /* This could be a config issue. Log a significant error */ - LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " - "exited quickly (%d secs). This could indicate a window " - "manager config problem", - window_manager_pid, s->display, wm_wait_time); - } - - if (display_pid > 0) - { - LOG(LOG_LEVEL_INFO, "Terminating X server (pid %d) on display :%d", - display_pid, s->display); - g_sigterm(display_pid); - } - - if (chansrv_pid > 0) - { - LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) " - "on display :%d", chansrv_pid, s->display); - g_sigterm(chansrv_pid); - } - - /* make sure all children are gone before socket cleanup happens */ - if (display_pid > 0) - { - g_waitpid(display_pid); LOG(LOG_LEVEL_INFO, "X server pid %d on display :%d finished", - display_pid, s->display); + sd->x_server, sd->params.display); + sd->x_server = -1; + // No other action - window manager should be going soon } - - if (chansrv_pid > 0) + else if (pid == sd->chansrv) { - g_waitpid(chansrv_pid); LOG(LOG_LEVEL_INFO, "xrdp channel server pid %d on display :%d finished", - chansrv_pid, s->display); + sd->chansrv, sd->params.display); + sd->chansrv = -1; } - - cleanup_sockets(s->display); - g_deinit(); -} - -/******************************************************************************/ -/* - * Simple helper process to fork a child and log errors */ -static int -fork_child( - void (*runproc)(struct auth_info *, const struct session_parameters *), - struct auth_info *auth_info, - const struct session_parameters *s) -{ - int pid = g_fork(); - if (pid == 0) + else if (pid == sd->win_mgr) { - /* Child process */ - runproc(auth_info, s); - g_exit(0); - } + int wm_wait_time = g_time1() - sd->start_time; - if (pid < 0) - { - LOG(LOG_LEVEL_ERROR, "Fork failed [%s]", g_get_strerror()); - } - - return pid; -} - -/******************************************************************************/ -/** - * Sub-process to start a session - * - * @param auth_info Authentication info - * @param s Session parameters - * @param success_fd File descriptor to write to on success - * - * @return status - * - * This routine returns a status on failure. On success, a character is - * written to the file descriptor to indicate a success, and then the - * routine runs for the lifetime of the session. - */ -enum scp_screate_status -session_start_subprocess(struct auth_info *auth_info, - const struct session_parameters *s, - int success_fd) -{ - char username[256]; - int chansrv_pid; - int display_pid; - int window_manager_pid; - enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; - char text[64]; - - /* Set up wait objects so we can detect signals */ - g_pid = g_getpid(); - g_snprintf(text, sizeof(text), "xrdp_sesexec_%8.8x_main_term", - g_pid); - g_term_event = g_create_wait_obj(text); - g_signal_terminate(set_term_event); - g_snprintf(text, sizeof(text), "xrdp_sesexec_%8.8x_sigchld", - g_pid); - g_sigchld_event = g_create_wait_obj(text); - g_signal_child_stop(set_sigchld_event); - - /* Get the username for display purposes */ - username_from_uid(s->uid, username, sizeof(username)); - -#ifdef USE_BSD_SETLOGIN - /** - * Create a new session and process group since the 4.4BSD - * setlogin() affects the entire process group - */ - if (g_setsid() < 0) - { - LOG(LOG_LEVEL_WARNING, - "[session start] (display %d): setsid failed - pid %d", - s->display, g_getpid()); - } - - if (g_setlogin(username) < 0) - { - LOG(LOG_LEVEL_WARNING, - "[session start] (display %d): setlogin failed for user %s - pid %d", - s->display, username, g_getpid()); - } -#endif - - /* Set the secondary groups before starting the session to prevent - * problems on PAM-based systems (see Linux pam_setcred(3)). - * If we have *BSD setusercontext() this is not done here */ -#ifndef HAVE_SETUSERCONTEXT - if (g_initgroups(username) != 0) - { - LOG(LOG_LEVEL_ERROR, - "Failed to initialise secondary groups for %s: %s", - username, g_get_strerror()); - return E_SCP_SCREATE_GENERAL_ERROR; - } -#endif - - display_pid = fork_child(start_x_server, auth_info, s); - if (display_pid > 0) - { - enum xwait_status xws; - xws = wait_for_xserver(s->uid, - g_cfg->env_names, - g_cfg->env_values, - s->display); - - if (xws != XW_STATUS_OK) + if (e->reason == E_XR_STATUS_CODE && e->val == 0) { - switch (xws) - { - case XW_STATUS_TIMED_OUT: - LOG(LOG_LEVEL_ERROR, "Timed out waiting for X server"); - break; - case XW_STATUS_FAILED_TO_START: - LOG(LOG_LEVEL_ERROR, "X server failed to start"); - break; - default: - LOG(LOG_LEVEL_ERROR, - "An error occurred waiting for the X server"); - } - status = E_SCP_SCREATE_X_SERVER_FAIL; - /* Kill it anyway in case it did start and we just failed to - * pick up on it */ - g_sigterm(display_pid); - g_waitpid(display_pid); + LOG(LOG_LEVEL_INFO, + "Window manager (pid %d, display %d) " + "finished normally in %d secs", + sd->win_mgr, sd->params.display, wm_wait_time); } else { - LOG(LOG_LEVEL_INFO, "X server :%d is working", s->display); - LOG(LOG_LEVEL_INFO, "Starting window manager for display :%d", - s->display); - window_manager_pid = fork_child(start_window_manager, - auth_info, s); - if (window_manager_pid < 0) - { - g_sigterm(display_pid); - g_waitpid(display_pid); - } - else - { - LOG(LOG_LEVEL_INFO, - "Starting the xrdp channel server for display :%d", - s->display); + char reason[128]; + exit_status_to_str(e, reason, sizeof(reason)); - chansrv_pid = fork_child(start_chansrv, auth_info, s); - - // Tell the caller we've started - char zero = 0; - g_file_write(success_fd, &zero, 1); - status = E_SCP_SCREATE_OK; - - /* This call does not return until the session is done */ - run_xrdp_session(s, auth_info, window_manager_pid, - display_pid, chansrv_pid); - } + LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " + "exited with %s. This " + "could indicate a window manager config problem", + sd->win_mgr, sd->params.display, reason); } - } - - return status; -} - -#ifdef USE_EXTRA_SESSION_FORK -/* - * FreeBSD bug - * ports/157282: effective login name is not set by xrdp-sesman - * http://www.freebsd.org/cgi/query-pr.cgi?pr=157282 - * - * from: - * $OpenBSD: session.c,v 1.252 2010/03/07 11:57:13 dtucker Exp $ - * with some ideas about BSD process grouping to xrdp - */ -static int -run_extra_fork(void) -{ - char text[64]; - struct exit_status e; - int stat; - - pid_t bsdsespid = g_fork(); - - if (bsdsespid <= 0) - { - /* Error, or child */ - return bsdsespid; - } - /* - * intermediate sesman should return the status of its own child, and - * kill the child if we get a sigterm - */ - - /* Set up wait objects so we can detect signals */ - g_pid = g_getpid(); - g_snprintf(text, sizeof(text), "xrdp_intermediate_%8.8x_main_term", - g_pid); - g_term_event = g_create_wait_obj(text); - g_signal_terminate(set_term_event); - g_snprintf(text, sizeof(text), "xrdp_intermediate_%8.8x_sigchld", - g_pid); - g_sigchld_event = g_create_wait_obj(text); - g_signal_child_stop(set_sigchld_event); - - // Wait for a SIGCHLD event. We've only got one child so we know where - // it's come from! - while (!g_is_wait_obj_set(g_sigchld_event)) - { - int robjs_count; - intptr_t robjs[4]; - - robjs_count = 0; - robjs[robjs_count++] = g_term_event; - robjs[robjs_count++] = g_sigchld_event; - - if (g_obj_wait(robjs, robjs_count, NULL, 0, 0) != 0) + if (wm_wait_time < 10) { - /* should not get here */ - LOG(LOG_LEVEL_WARNING, "run_extra_fork: " - "Unexpected error from g_obj_wait()"); - g_sleep(100); + /* This could be a config issue. Log a significant error */ + LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " + "exited quickly (%d secs). This could indicate a window " + "manager config problem", + sd->win_mgr, sd->params.display, wm_wait_time); } - else if (g_is_wait_obj_set(g_term_event)) + + sd->win_mgr = -1; + + if (sd->x_server > 0) { - g_reset_wait_obj(g_term_event); - LOG(LOG_LEVEL_INFO, "Received SIGTERM"); - // Pass it on to the BSD session leader - g_sigterm(bsdsespid); + LOG(LOG_LEVEL_INFO, "Terminating X server (pid %d) on display :%d", + sd->x_server, sd->params.display); + g_sigterm(sd->x_server); + } + + if (sd->chansrv > 0) + { + LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) " + "on display :%d", sd->chansrv, sd->params.display); + g_sigterm(sd->chansrv); } } - e = g_waitpid_status(bsdsespid); - stat = (e.reason == E_XR_STATUS_CODE) ? e.val : E_SCP_SCREATE_GENERAL_ERROR; - g_exit(stat); - return -1; + if (!session_active(sd)) + { + cleanup_sockets(sd->params.display); + } } -#endif /******************************************************************************/ -enum scp_screate_status -session_start(struct auth_info *auth_info, - const struct session_parameters *s, - int *pid) +unsigned int +session_active(const struct session_data *sd) { - int fd[2]; - enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + return + (sd == NULL) + ? 0 + : (sd->win_mgr > 0) + (sd->x_server > 0) + (sd->chansrv > 0); +} - if (g_pipe(fd) != 0) +/******************************************************************************/ +time_t +session_get_start_time(const struct session_data *sd) +{ + return (sd == NULL) ? 0 : sd->start_time; +} + +/******************************************************************************/ +void +session_send_term(struct session_data *sd) +{ + if (sd != NULL && sd->win_mgr > 0) { - LOG(LOG_LEVEL_ERROR, "Cant create a pipe [%s]", g_get_strerror()); + g_sigterm(sd->win_mgr); + } +} + +/******************************************************************************/ +static void +start_reconnect_script(struct login_info *login_info, + const struct session_parameters *s) +{ + env_set_user(login_info->uid, 0, s->display, + g_cfg->env_names, + g_cfg->env_values); + + auth_set_env(login_info->auth_info); + + if (g_file_exist(g_cfg->reconnect_sh)) + { + LOG_DEVEL_LEAKING_FDS("reconnect script", 3, -1); + + LOG(LOG_LEVEL_INFO, + "Starting session reconnection script on display %d: %s", + s->display, g_cfg->reconnect_sh); + g_execlp3(g_cfg->reconnect_sh, g_cfg->reconnect_sh, 0); + + /* should not get here */ + LOG(LOG_LEVEL_ERROR, + "Error starting session reconnection script on display %d: %s", + s->display, g_cfg->reconnect_sh); } else { - *pid = g_fork(); - if (*pid == 0) - { - /** - * We're now forked from the main sesman process, so we - * can close file descriptors that we no longer need - * - * Set FD_CLOEXEC on the FD used to send our status back to - * sesman, as our sub-processes shouldn't be able to see it */ - g_file_close(fd[0]); - g_file_set_cloexec(fd[1], 1); - - sesman_close_all(0); - - /* Wait objects created in a parent are not valid in a child */ - sesman_delete_wait_objects(); - - LOG(LOG_LEVEL_INFO, - "calling auth_start_session for uid=%d from pid %d", - s->uid, g_getpid()); - auth_start_session(auth_info, s->display); - - /* Run the child */ -#ifdef USE_EXTRA_SESSION_FORK - if (run_extra_fork() < 0) - { - return E_SCP_SCREATE_GENERAL_ERROR; - } -#endif - status = session_start_subprocess(auth_info, s, fd[1]); - - LOG(LOG_LEVEL_INFO, - "Calling auth_stop_session from pid %d", - g_getpid()); - auth_stop_session(auth_info); - g_exit(status); - } - - g_file_close(fd[1]); - if (*pid == -1) - { - LOG(LOG_LEVEL_ERROR, "Cant fork [%s]", g_get_strerror()); - } - else - { - /* Wait for the child to signal success, or return an error */ - int err; - char buff; - do - { - err = g_file_read(fd[0], &buff, 1); - } - while (err == -1 && g_get_errno() == EINTR); - if (err < 0) - { - /* Read problem */ - LOG(LOG_LEVEL_ERROR, "Can't read pipe [%s]", g_get_strerror()); - } - else if (err > 0) - { - /* Session is up and running */ - status = E_SCP_SCREATE_OK; - } - else - { - /* Process has failed. Get the exit status of the child */ - struct exit_status e; - e = g_waitpid_status(*pid); - if (e.reason == E_XR_STATUS_CODE) - { - status = (enum scp_screate_status)e.val; - } - else - { - char reason[128]; - exit_status_to_str(&e, reason, sizeof(reason)); - LOG(LOG_LEVEL_ERROR, "Child exited with %s", reason); - } - } - } - g_file_close(fd[0]); + LOG(LOG_LEVEL_WARNING, + "Session reconnection script file does not exist: %s", + g_cfg->reconnect_sh); } - - return status; } /******************************************************************************/ -int -session_reconnect(int display, int uid, - struct auth_info *auth_info) +void +session_reconnect(struct login_info *login_info, + struct session_data *sd) { - int pid; - - pid = g_fork(); - - if (pid == -1) + if (fork_child(start_reconnect_script, + login_info, &sd->params, sd->x_server) < 0) { LOG(LOG_LEVEL_ERROR, "Failed to fork for session reconnection script"); } - else if (pid == 0) - { - env_set_user(uid, - 0, - display, - g_cfg->env_names, - g_cfg->env_values); - auth_set_env(auth_info); - - if (g_file_exist(g_cfg->reconnect_sh)) - { - LOG_DEVEL_LEAKING_FDS("reconnect script", 3, -1); - - LOG(LOG_LEVEL_INFO, - "Starting session reconnection script on display %d: %s", - display, g_cfg->reconnect_sh); - g_execlp3(g_cfg->reconnect_sh, g_cfg->reconnect_sh, 0); - - /* should not get here */ - LOG(LOG_LEVEL_ERROR, - "Error starting session reconnection script on display %d: %s", - display, g_cfg->reconnect_sh); - } - else - { - LOG(LOG_LEVEL_WARNING, - "Session reconnection script file does not exist: %s", - g_cfg->reconnect_sh); - } - - /* TODO: why is this existing with a success error code when the - reconnect script failed to be executed? */ - g_exit(0); - } - - return display; } diff --git a/sesman/sesexec/session.h b/sesman/sesexec/session.h index bd79397f..dee9fe36 100644 --- a/sesman/sesexec/session.h +++ b/sesman/sesexec/session.h @@ -34,7 +34,8 @@ #include "scp_application_types.h" #include "xrdp_constants.h" -struct auth_info; +struct login_info; +struct exit_status; /** * Information used to start a session @@ -42,35 +43,94 @@ struct auth_info; struct session_parameters { unsigned int display; - int uid; - struct guid guid; enum scp_session_type type; - unsigned short height; unsigned short width; + unsigned short height; unsigned char bpp; - char shell[INFO_CLIENT_MAX_CB_LEN]; - char directory[INFO_CLIENT_MAX_CB_LEN]; + struct guid guid; + const char *shell; // Must not be NULL + const char *directory; // Must not be NULL }; + +/** + * Data involved in running a session (opaque type) + * + * Allocate with session_start() and free with + * session_data_free() once session_active() returns zero. + */ +struct session_data; + /** * * @brief starts a session * - * @param auth_info Authentication info + * @param login_info info for logged in user * @param s Session parameters - * @param[out] pid PID of sub-process - * @return status + * @param[out] session_data Pointer to session data for the session * - * The returned PID is only valid if the status returned is - * E_SCP_SCREATE_OK + * session_data is only set if E_SCP_CREATE_OK is returned + * @return status */ enum scp_screate_status -session_start(struct auth_info *auth_info, +session_start(struct login_info *login_info, const struct session_parameters *s, - int *pid); + struct session_data **session_data); -int -session_reconnect(int display, int uid, - struct auth_info *auth_info); +/** + * Processes an exited child process + * + * The PID of the child process is removed from the session_data. + * + * @param sd session_data for this session + * @param pid PID of exited process + * @param e Exit status of the exited process + */ +void +session_process_child_exit(struct session_data *sd, + int pid, + const struct exit_status *e); + +/** + * Returns a count of active processes in the session + * + * @param sd session_data for this session + */ +unsigned int +session_active(const struct session_data *sd); + +/** + * Returns the start time for an active session + * + * @param sd session_data for this session + */ +time_t +session_get_start_time(const struct session_data *sd); + +/*** + * Ask a session to terminate by signalling the window manager + * + * @param sd session_data for this session + */ +void +session_send_term(struct session_data *sd); + +/** + * Frees a session_data object + * + * @param sd session_data for this session + * + * Do not call this until session_active() returns zero, or you + * lose the ability to track the session PIDs + */ +void +session_data_free(struct session_data *session_data); + +/** + * Runs the reconnect script for the session + */ +void +session_reconnect(struct login_info *login_info, + struct session_data *sd); #endif // SESSION_H diff --git a/sesman/sesexec/xwait.c b/sesman/sesexec/xwait.c index 659af64d..271804e6 100644 --- a/sesman/sesexec/xwait.c +++ b/sesman/sesexec/xwait.c @@ -103,8 +103,7 @@ wait_for_xserver(uid_t uid, pid_t pid = g_fork(); if (pid < 0) { - LOG(LOG_LEVEL_ERROR, "Can't create pipe : %s", - g_get_strerror()); + // Error already logged } else if (pid == 0) {