From 991770cc5df33ddaa7b24238ded84b63c809dc8b Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Fri, 17 Mar 2023 12:08:22 +0000 Subject: [PATCH] Refactored session.c to support X server validation --- sesman/scp_process.c | 115 ++--- sesman/session.c | 1164 +++++++++++++++++++++++++++--------------- sesman/session.h | 18 +- 3 files changed, 817 insertions(+), 480 deletions(-) diff --git a/sesman/scp_process.c b/sesman/scp_process.c index b6df1d6a..b70d1fe4 100644 --- a/sesman/scp_process.c +++ b/sesman/scp_process.c @@ -157,8 +157,8 @@ authenticate_and_authorize_connection(struct sesman_con *sc, } else { - LOG(LOG_LEVEL_INFO, "Access permitted for user: %s", - username); + LOG(LOG_LEVEL_INFO, "Access permitted for user=%s uid=%d", + username, uid); sc->auth_info = auth_info; sc->uid = uid; sc->username = dup_username; @@ -210,6 +210,7 @@ allocate_and_start_session(struct auth_info *auth_info, { int pid = 0; struct session_chain *temp = (struct session_chain *)NULL; + enum scp_screate_status status; /* check to limit concurrent sessions */ if (session_list_get_count() >= (unsigned int)g_cfg->sess.max_sessions) @@ -238,70 +239,39 @@ allocate_and_start_session(struct auth_info *auth_info, return E_SCP_SCREATE_NO_MEMORY; } - pid = g_fork(); - if (pid == -1) + status = session_start(auth_info, params, &pid); + if (status == E_SCP_SCREATE_OK) { - LOG(LOG_LEVEL_ERROR, - "[session start] (display %d): Failed to fork for scp with " - "errno: %d, description: %s", - params->display, g_get_errno(), g_get_strerror()); - g_free(temp->item); - g_free(temp); - return E_SCP_SCREATE_GENERAL_ERROR; + if (ip_addr[0] != '\0') + { + LOG(LOG_LEVEL_INFO, "++ created session: username %s, ip %s", + username, ip_addr); + } + else + { + LOG(LOG_LEVEL_INFO, "++ created session: username %s", username); + } + + temp->item->pid = pid; + temp->item->display = params->display; + temp->item->width = params->width; + temp->item->height = params->height; + temp->item->bpp = params->bpp; + temp->item->auth_info = auth_info; + g_strncpy(temp->item->start_ip_addr, ip_addr, + sizeof(temp->item->start_ip_addr) - 1); + temp->item->uid = params->uid; + temp->item->guid = params->guid; + + temp->item->start_time = g_time1(); + + temp->item->type = params->type; + temp->item->status = SESMAN_SESSION_STATUS_ACTIVE; + + session_list_add(temp); } - if (pid == 0) - { - /** - * We're now forked from the main sesman process, so we - * can close file descriptors that we no longer need */ - - sesman_close_all(0); - - /* Wait objects created in a parent are not valid in a child */ - g_delete_wait_obj(g_reload_event); - g_delete_wait_obj(g_sigchld_event); - g_delete_wait_obj(g_term_event); - - session_start(auth_info, params); - g_exit(1); /* Should not get here */ - } - - if (ip_addr[0] != '\0') - { - LOG(LOG_LEVEL_INFO, "++ created session: username %s, ip %s", - username, ip_addr); - } - else - { - LOG(LOG_LEVEL_INFO, "++ created session: username %s", username); - } - - LOG(LOG_LEVEL_INFO, "Starting session: session_pid %d, " - "display :%d.0, width %d, height %d, bpp %d, client ip %s, " - "UID %d", - pid, params->display, params->width, params->height, params->bpp, - ip_addr, params->uid); - - temp->item->pid = pid; - temp->item->display = params->display; - temp->item->width = params->width; - temp->item->height = params->height; - temp->item->bpp = params->bpp; - temp->item->auth_info = auth_info; - g_strncpy(temp->item->start_ip_addr, ip_addr, - sizeof(temp->item->start_ip_addr) - 1); - temp->item->uid = params->uid; - temp->item->guid = params->guid; - - temp->item->start_time = g_time1(); - - temp->item->type = params->type; - temp->item->status = SESMAN_SESSION_STATUS_ACTIVE; - - session_list_add(temp); - - return E_SCP_SCREATE_OK; + return status; } /******************************************************************************/ @@ -489,6 +459,8 @@ process_create_session_request(struct sesman_con *sc) // Parameters for a new session (if required). Filled in as // we go along. struct session_parameters sp = {0}; + const char *shellptr; + const char *dirptr; enum scp_screate_status status = E_SCP_SCREATE_OK; int display = 0; @@ -498,7 +470,7 @@ process_create_session_request(struct sesman_con *sc) rv = scp_get_create_session_request(sc->t, &sp.type, &sp.width, &sp.height, - &sp.bpp, &sp.shell, &sp.directory); + &sp.bpp, &shellptr, &dirptr); if (rv == 0) { @@ -509,8 +481,12 @@ process_create_session_request(struct sesman_con *sc) else { LOG(LOG_LEVEL_INFO, - "Received request from %s to create a session for user %s", - sc->peername, sc->username); + "Received request from %s to create a session for user %s" + " type=%s" + " geometry=%dx%d, bpp=%d, shell=\"%s\", dir=\"%s\"", + sc->peername, sc->username, + SCP_SESSION_TYPE_TO_STR(sp.type), + sp.width, sp.height, sp.bpp, shellptr, dirptr); struct session_item *s_item = session_list_get_bydata(sc->uid, sp.type, sp.width, sp.height, @@ -546,9 +522,13 @@ process_create_session_request(struct sesman_con *sc) guid = guid_new(); display = session_list_get_available_display(); - sp.guid = guid; sp.display = display; sp.uid = sc->uid; + sp.guid = guid; + // These need to be copied so they are available + // when the sub-process closes all the connections + g_snprintf(sp.shell, sizeof(sp.shell), "%s", shellptr); + g_snprintf(sp.directory, sizeof(sp.directory), "%s", dirptr); if (display == 0) { @@ -696,4 +676,3 @@ scp_process(struct sesman_con *sc) } return rv; } - diff --git a/sesman/session.c b/sesman/session.c index 9cd04358..409ecffb 100644 --- a/sesman/session.c +++ b/sesman/session.c @@ -37,6 +37,8 @@ #include #endif +#include + #include "arch.h" #include "session.h" @@ -57,6 +59,19 @@ #define PR_SET_NO_NEW_PRIVS 38 #endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#define USE_EXTRA_SESSION_FORK +#define USE_BSD_SETLOGIN +#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) + /** * Creates a string consisting of all parameters that is hosted in the param list * @param self @@ -92,38 +107,61 @@ dumpItemsToString(struct list *self, char *outstr, int len) } /******************************************************************************/ -static int -session_start_chansrv(int uid, int display) +static void +set_term_event(int sig) { - struct list *chansrv_params; - const char *exe_path = XRDP_SBIN_PATH "/xrdp-chansrv"; - int chansrv_pid; - - chansrv_pid = g_fork(); - if (chansrv_pid == 0) + /* 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, + const struct session_parameters *s) +{ + struct list *chansrv_params = list_create(); + const char *exe_path = XRDP_SBIN_PATH "/xrdp-chansrv"; + + if (chansrv_params != NULL) { - chansrv_params = list_create(); chansrv_params->auto_free = 1; + if (!list_add_strdup(chansrv_params, exe_path)) + { + list_delete(chansrv_params); + chansrv_params = NULL; + } + } - /* building parameters */ - - list_add_strdup(chansrv_params, exe_path); - - env_set_user(uid, 0, display, + if (chansrv_params == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory starting chansrv"); + } + else + { + env_set_user(s->uid, 0, s->display, g_cfg->env_names, g_cfg->env_values); - LOG(LOG_LEVEL_INFO, - "Starting the xrdp channel server for display %d", display); - /* executing chansrv */ g_execvp_list(exe_path, chansrv_params); /* should not get here */ list_delete(chansrv_params); - g_exit(1); } - return chansrv_pid; } /******************************************************************************/ @@ -221,6 +259,294 @@ cleanup_sockets(int display) } +/******************************************************************************/ +static void +start_window_manager(struct auth_info *auth_info, + const struct session_parameters *s) +{ + char text[256]; + + env_set_user(s->uid, + 0, + s->display, + g_cfg->env_names, + g_cfg->env_values); + + auth_set_env(auth_info); + if (s->directory[0] != '\0') + { + g_set_current_dir(s->directory); + } + + if (s->shell[0] != '\0') + { + if (g_strchr(s->shell, ' ') != 0 || g_strchr(s->shell, '\t') != 0) + { + LOG(LOG_LEVEL_INFO, + "Using user requested window manager on " + "display %u with embedded arguments using a shell: %s", + s->display, s->shell); + const char *argv[] = {"sh", "-c", s->shell, NULL}; + g_execvp("/bin/sh", (char **)argv); + } + else + { + LOG(LOG_LEVEL_INFO, + "Using user requested window manager on " + "display %d: %s", s->display, s->shell); + g_execlp3(s->shell, s->shell, 0); + } + } + else + { + LOG(LOG_LEVEL_DEBUG, "The user session on display %u did " + "not request a specific window manager", s->display); + } + + /* try to execute user window manager if enabled */ + if (g_cfg->enable_user_wm) + { + g_snprintf(text, sizeof(text), "%s/%s", + g_getenv("HOME"), g_cfg->user_wm); + if (g_file_exist(text)) + { + LOG(LOG_LEVEL_INFO, + "Using window manager on display %u" + " from user home directory: %s", s->display, text); + g_execlp3(text, g_cfg->user_wm, 0); + } + else + { + LOG(LOG_LEVEL_DEBUG, + "The user home directory window manager configuration " + "is enabled but window manager program does not exist: %s", + text); + } + } + + LOG(LOG_LEVEL_INFO, + "Using the default window manager on display %u: %s", + s->display, g_cfg->default_wm); + g_execlp3(g_cfg->default_wm, g_cfg->default_wm, 0); + + /* still a problem starting window manager just start xterm */ + LOG(LOG_LEVEL_WARNING, + "No window manager on display %u started, " + "so falling back to starting xterm for user debugging", + s->display); + g_execlp3("xterm", "xterm", 0); + + /* should not get here */ + LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting to start " + "the window manager on display %u, aborting connection", + s->display); +} + +/******************************************************************************/ +static struct list * +prepare_xorg_xserver_params(const struct session_parameters *s, + const char *authfile) +{ + + char screen[32]; /* display number */ + char text[128]; + const char *xserver; + + struct list *params = list_create(); + if (params != NULL) + { + params->auto_free = 1; + +#ifdef HAVE_SYS_PRCTL_H + /* + * Make sure Xorg doesn't run setuid root. Root access is not + * needed. Xorg can fail when run as root and the user has no + * console permissions. + * PR_SET_NO_NEW_PRIVS requires Linux kernel 3.5 and newer. + */ + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) + { + LOG(LOG_LEVEL_WARNING, + "[session start] (display %u): Failed to disable " + "setuid on X server: %s", + s->display, g_get_strerror()); + } +#endif + + g_snprintf(screen, sizeof(screen), ":%u", s->display); + + /* some args are passed via env vars */ + g_snprintf(text, sizeof(text), "%d", s->width); + g_setenv("XRDP_START_WIDTH", text, 1); + + g_snprintf(text, sizeof(text), "%d", s->height); + g_setenv("XRDP_START_HEIGHT", text, 1); + + g_snprintf(text, sizeof(text), "%d", g_cfg->sess.max_idle_time); + g_setenv("XRDP_SESMAN_MAX_IDLE_TIME", text, 1); + + g_snprintf(text, sizeof(text), "%d", g_cfg->sess.max_disc_time); + g_setenv("XRDP_SESMAN_MAX_DISC_TIME", text, 1); + + g_snprintf(text, sizeof(text), "%d", g_cfg->sess.kill_disconnected); + g_setenv("XRDP_SESMAN_KILL_DISCONNECTED", text, 1); + + g_setenv("XRDP_SOCKET_PATH", XRDP_SOCKET_PATH, 1); + + /* get path of Xorg from config */ + xserver = (const char *)list_get_item(g_cfg->xorg_params, 0); + + /* these are the must have parameters */ + list_add_strdup_multi(params, + xserver, screen, + "-auth", authfile, + NULL); + + /* additional parameters from sesman.ini file */ + list_append_list_strdup(g_cfg->xorg_params, params, 1); + } + + return params; +} + +/******************************************************************************/ +static struct list * +prepare_xvnc_xserver_params(const struct session_parameters *s, + const char *authfile, + const char *passwd_file) +{ + char screen[32] = {0}; /* display number */ + char geometry[32] = {0}; + char depth[32] = {0}; + char guid_str[GUID_STR_SIZE]; + const char *xserver; + + struct list *params = list_create(); + if (params != NULL) + { + params->auto_free = 1; + + g_snprintf(screen, sizeof(screen), ":%u", s->display); + g_snprintf(geometry, sizeof(geometry), "%dx%d", s->width, s->height); + g_snprintf(depth, sizeof(depth), "%d", s->bpp); + + guid_to_str(&s->guid, guid_str); + env_check_password_file(passwd_file, guid_str); + + /* get path of Xvnc from config */ + xserver = (const char *)list_get_item(g_cfg->vnc_params, 0); + + /* these are the must have parameters */ + list_add_strdup_multi(params, + xserver, screen, + "-auth", authfile, + "-geometry", geometry, + "-depth", depth, + "-rfbauth", passwd_file, + NULL); + + /* additional parameters from sesman.ini file */ + //config_read_xserver_params(SCP_SESSION_TYPE_XVNC, + // xserver_params); + list_append_list_strdup(g_cfg->vnc_params, params, 1); + } + return params; +} + +/******************************************************************************/ +/* Either execs the X server, or returns */ +static void +start_x_server(struct auth_info *auth_info, + const struct session_parameters *s) +{ + char authfile[256]; /* The filename for storing xauth information */ + char execvpparams[2048]; + char *passwd_file = NULL; + struct list *xserver_params = NULL; + int unknown_session_type = 0; + + if (s->type == SCP_SESSION_TYPE_XVNC) + { + env_set_user(s->uid, + &passwd_file, + s->display, + g_cfg->env_names, + g_cfg->env_values); + } + else + { + env_set_user(s->uid, + 0, + s->display, + g_cfg->env_names, + g_cfg->env_values); + } + + /* prepare the Xauthority stuff */ + if (g_getenv("XAUTHORITY") != NULL) + { + g_snprintf(authfile, sizeof(authfile), "%s", + g_getenv("XAUTHORITY")); + } + else + { + g_snprintf(authfile, sizeof(authfile), "%s", ".Xauthority"); + } + + /* Add the entry in XAUTHORITY file or exit if error */ + if (add_xauth_cookie(s->display, authfile) != 0) + { + LOG(LOG_LEVEL_ERROR, + "Error setting the xauth cookie for display %u in file %s", + s->display, authfile); + } + else + { + switch (s->type) + { + case SCP_SESSION_TYPE_XORG: + xserver_params = prepare_xorg_xserver_params(s, authfile); + break; + + case SCP_SESSION_TYPE_XVNC: + xserver_params = prepare_xvnc_xserver_params(s, authfile, + passwd_file); + break; + + default: + unknown_session_type = 1; + } + + g_free(passwd_file); + passwd_file = NULL; + + if (xserver_params == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating X server params"); + } + else if (unknown_session_type) + { + LOG(LOG_LEVEL_ERROR, "Unknown session type: %d", + s->type); + } + else + { + /* fire up X server */ + LOG(LOG_LEVEL_INFO, "Starting X server on display %u: %s", + s->display, + dumpItemsToString(xserver_params, execvpparams, 2048)); + g_execvp_list((const char *)xserver_params->items[0], + xserver_params); + } + } + + /* should not get here */ + list_delete(xserver_params); + LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting " + "to start the X server on display %u, aborting connection", + s->display); +} + /******************************************************************************/ /** * Convert a UID to a username @@ -275,38 +601,228 @@ 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) +{ + 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) + { + 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); + } + + if (chansrv_pid > 0) + { + g_waitpid(chansrv_pid); + LOG(LOG_LEVEL_INFO, + "xrdp channel server pid %d on display :%d finished", + chansrv_pid, s->display); + } + + cleanup_sockets(s->display); + g_deinit(); +} /******************************************************************************/ -void -session_start(struct auth_info *auth_info, - const struct session_parameters *s) +/* + * 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) + { + /* Child process */ + runproc(auth_info, s); + g_exit(0); + } + + 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 geometry[32]; - char depth[32]; - char screen[32]; /* display number */ - char text[256]; char username[256]; - char execvpparams[2048]; - char *xserver = NULL; /* absolute/relative path to Xorg/Xvnc */ - char *passwd_file = NULL; - struct list *xserver_params = (struct list *)NULL; - char authfile[256]; /* The filename for storing xauth information */ int chansrv_pid; int display_pid; int window_manager_pid; + enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + char text[64]; - /* initialize (zero out) local variables: */ - g_memset(geometry, 0, sizeof(char) * 32); - g_memset(depth, 0, sizeof(char) * 32); - g_memset(screen, 0, sizeof(char) * 32); - g_memset(text, 0, sizeof(char) * 256); + /* 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)); - LOG(LOG_LEVEL_INFO, - "[session start] (display %d): calling auth_start_session from pid %d", - s->display, g_getpid()); +#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)). @@ -317,149 +833,20 @@ session_start(struct auth_info *auth_info, LOG(LOG_LEVEL_ERROR, "Failed to initialise secondary groups for %s: %s", username, g_get_strerror()); - g_exit(1); + return E_SCP_SCREATE_GENERAL_ERROR; } #endif - auth_start_session(auth_info, s->display); - g_sprintf(geometry, "%dx%d", s->width, s->height); - g_sprintf(depth, "%d", s->bpp); - g_sprintf(screen, ":%d", s->display); -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) - /* - * 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 - */ - pid_t bsdsespid = g_fork(); - - if (bsdsespid == -1) - { - } - else if (bsdsespid == 0) /* BSD session leader */ - { - /** - * 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()); - } - } - - g_waitpid(bsdsespid); - - if (bsdsespid > 0) - { - g_exit(0); - /* - * intermediate sesman should exit here after WM exits. - * do not execute the following codes. - */ - } -#endif - window_manager_pid = g_fork(); /* parent becomes X, - child forks wm, and waits, todo */ - if (window_manager_pid == -1) - { - LOG(LOG_LEVEL_ERROR, - "Failed to fork for the window manager on display %d", s->display); - } - else if (window_manager_pid == 0) + 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); - env_set_user(s->uid, - 0, - s->display, - g_cfg->env_names, - g_cfg->env_values); - xws = wait_for_xserver(s->display); - - if (xws == XW_STATUS_OK) - { - auth_set_env(auth_info); - if (s->directory != 0) - { - if (s->directory[0] != 0) - { - g_set_current_dir(s->directory); - } - } - if (s->shell != 0 && s->shell[0] != 0) - { - if (g_strchr(s->shell, ' ') != 0 || g_strchr(s->shell, '\t') != 0) - { - LOG(LOG_LEVEL_INFO, - "Starting user requested window manager on " - "display %d with embedded arguments using a shell: %s", - s->display, s->shell); - const char *argv[] = {"sh", "-c", s->shell, NULL}; - g_execvp("/bin/sh", (char **)argv); - } - else - { - LOG(LOG_LEVEL_INFO, - "Starting user requested window manager on " - "display %d: %s", s->display, s->shell); - g_execlp3(s->shell, s->shell, 0); - } - } - else - { - LOG(LOG_LEVEL_DEBUG, "The user session on display %d did " - "not request a specific window manager", s->display); - } - - /* try to execute user window manager if enabled */ - if (g_cfg->enable_user_wm) - { - g_sprintf(text, "%s/%s", g_getenv("HOME"), g_cfg->user_wm); - if (g_file_exist(text)) - { - LOG(LOG_LEVEL_INFO, - "Starting window manager on display %d" - " from user home directory: %s", s->display, text); - g_execlp3(text, g_cfg->user_wm, 0); - } - else - { - LOG(LOG_LEVEL_DEBUG, - "The user home directory window manager configuration " - "is enabled but window manager program does not exist: %s", - text); - } - } - - LOG(LOG_LEVEL_INFO, - "Starting the default window manager on display %d: %s", - s->display, g_cfg->default_wm); - g_execlp3(g_cfg->default_wm, g_cfg->default_wm, 0); - - /* still a problem starting window manager just start xterm */ - LOG(LOG_LEVEL_WARNING, - "No window manager on display %d started, " - "so falling back to starting xterm for user debugging", - s->display); - g_execlp3("xterm", "xterm", 0); - - /* should not get here */ - } - else + if (xws != XW_STATUS_OK) { switch (xws) { @@ -473,252 +860,217 @@ session_start(struct auth_info *auth_info, LOG(LOG_LEVEL_ERROR, "An error occurred waiting for the X server"); } - } - - LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting to start " - "the window manager on display %d, aborting connection", - s->display); - g_exit(0); - } - else - { - display_pid = g_fork(); /* parent becomes scp, - child becomes X */ - if (display_pid == -1) - { - LOG(LOG_LEVEL_ERROR, - "Failed to fork for the X server on display %d", s->display); - } - else if (display_pid == 0) /* child */ - { - if (s->type == SCP_SESSION_TYPE_XVNC) - { - env_set_user(s->uid, - &passwd_file, - s->display, - g_cfg->env_names, - g_cfg->env_values); - } - else - { - env_set_user(s->uid, - 0, - s->display, - g_cfg->env_names, - g_cfg->env_values); - } - - /* setting Xserver environment variables */ - g_snprintf(text, 255, "%d", g_cfg->sess.max_idle_time); - g_setenv("XRDP_SESMAN_MAX_IDLE_TIME", text, 1); - g_snprintf(text, 255, "%d", g_cfg->sess.max_disc_time); - g_setenv("XRDP_SESMAN_MAX_DISC_TIME", text, 1); - g_snprintf(text, 255, "%d", g_cfg->sess.kill_disconnected); - g_setenv("XRDP_SESMAN_KILL_DISCONNECTED", text, 1); - g_setenv("XRDP_SOCKET_PATH", XRDP_SOCKET_PATH, 1); - - /* prepare the Xauthority stuff */ - if (g_getenv("XAUTHORITY") != NULL) - { - g_snprintf(authfile, 255, "%s", g_getenv("XAUTHORITY")); - } - else - { - g_snprintf(authfile, 255, "%s", ".Xauthority"); - } - - /* Add the entry in XAUTHORITY file or exit if error */ - if (add_xauth_cookie(s->display, authfile) != 0) - { - LOG(LOG_LEVEL_ERROR, - "Error setting the xauth cookie for display %d in file %s", - s->display, authfile); - - LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting to start " - "the X server on display %d, aborting connection", - s->display); - g_exit(1); - } - - if (s->type == SCP_SESSION_TYPE_XORG) - { -#ifdef HAVE_SYS_PRCTL_H - /* - * Make sure Xorg doesn't run setuid root. Root access is not - * needed. Xorg can fail when run as root and the user has no - * console permissions. - * PR_SET_NO_NEW_PRIVS requires Linux kernel 3.5 and newer. - */ - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) - { - LOG(LOG_LEVEL_WARNING, - "[session start] (display %d): Failed to disable " - "setuid on X server: %s", - s->display, g_get_strerror()); - } -#endif - - xserver_params = list_create(); - xserver_params->auto_free = 1; - - /* get path of Xorg from config */ - xserver = g_strdup((const char *)list_get_item(g_cfg->xorg_params, 0)); - - /* these are the must have parameters */ - list_add_strdup_multi(xserver_params, - xserver, screen, - "-auth", authfile, - NULL); - - /* additional parameters from sesman.ini file */ - list_append_list_strdup(g_cfg->xorg_params, xserver_params, 1); - - /* some args are passed via env vars */ - g_sprintf(geometry, "%d", s->width); - g_setenv("XRDP_START_WIDTH", geometry, 1); - - g_sprintf(geometry, "%d", s->height); - g_setenv("XRDP_START_HEIGHT", geometry, 1); - } - else if (s->type == SCP_SESSION_TYPE_XVNC) - { - char guid_str[GUID_STR_SIZE]; - guid_to_str(&s->guid, guid_str); - env_check_password_file(passwd_file, guid_str); - xserver_params = list_create(); - xserver_params->auto_free = 1; - - /* get path of Xvnc from config */ - xserver = g_strdup((const char *)list_get_item(g_cfg->vnc_params, 0)); - - /* these are the must have parameters */ - list_add_strdup_multi(xserver_params, - xserver, screen, - "-auth", authfile, - "-geometry", geometry, - "-depth", depth, - "-rfbauth", passwd_file, - NULL); - g_free(passwd_file); - - /* additional parameters from sesman.ini file */ - //config_read_xserver_params(SCP_SESSION_TYPE_XVNC, - // xserver_params); - list_append_list_strdup(g_cfg->vnc_params, xserver_params, 1); - } - else - { - LOG(LOG_LEVEL_ERROR, "Unknown session type: %d", - s->type); - LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting " - "to start the X server on display %d, aborting connection", - s->display); - g_exit(1); - } - - /* fire up X server */ - LOG(LOG_LEVEL_INFO, "Starting X server on display %d: %s", - s->display, dumpItemsToString(xserver_params, execvpparams, 2048)); - g_execvp_list(xserver, xserver_params); - - /* should not get here */ - LOG(LOG_LEVEL_ERROR, - "Error starting X server on display %d", s->display); - LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting " - "to start the X server on display %d, aborting connection", - s->display); - - list_delete(xserver_params); - g_exit(1); + 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 { - int wm_wait_time; - struct exit_status wm_exit_status; - struct exit_status xserver_exit_status; - struct exit_status chansrv_exit_status; - char reason[128]; + 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); - chansrv_pid = session_start_chansrv(s->uid, s->display); + 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); + } + } + } + + 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) + { + /* should not get here */ + LOG(LOG_LEVEL_WARNING, "run_extra_fork: " + "Unexpected error from g_obj_wait()"); + g_sleep(100); + } + else 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 BSD session leader + g_sigterm(bsdsespid); + } + } + + e = g_waitpid_status(bsdsespid); + stat = (e.reason == E_XR_STATUS_CODE) ? e.val : E_SCP_SCREATE_GENERAL_ERROR; + g_exit(stat); + return -1; +} +#endif + +/******************************************************************************/ +enum scp_screate_status +session_start(struct auth_info *auth_info, + const struct session_parameters *s, + int *pid) +{ + int fd[2]; + enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + + if (g_pipe(fd) != 0) + { + LOG(LOG_LEVEL_ERROR, "Cant create a pipe [%s]", g_get_strerror()); + } + 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 */ + + g_file_close(fd[0]); + + sesman_close_all(0); + + /* Wait objects created in a parent are not valid in a child */ + sesman_delete_wait_objects(); LOG(LOG_LEVEL_INFO, - "Session started successfully for user %s on display %d", - username, s->display); + "calling auth_start_session for uid=%d from pid %d", + s->uid, g_getpid()); + auth_start_session(auth_info, s->display); - /* 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(); - wm_exit_status = g_waitpid_status(window_manager_pid); - wm_wait_time = g_time1() - wm_wait_time; - if (wm_exit_status.reason == E_XR_STATUS_CODE && - wm_exit_status.val == 0) + /* Run the child */ +#ifdef USE_EXTRA_SESSION_FORK + if (run_extra_fork() < 0) { - // Normal exit + return E_SCP_SCREATE_GENERAL_ERROR; } - else - { - exit_status_to_str(&wm_exit_status, reason, sizeof(reason)); +#endif + status = session_start_subprocess(auth_info, s, fd[1]); - 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); - } - else - { - LOG(LOG_LEVEL_DEBUG, "Window manager (pid %d, display %d) " - "was running for %d seconds.", - window_manager_pid, s->display, wm_wait_time); - } LOG(LOG_LEVEL_INFO, "Calling auth_stop_session from pid %d", g_getpid()); auth_stop_session(auth_info); - // auth_end() is called from the main process currently, - // as this called auth_start() - //auth_end(auth_info); - - LOG(LOG_LEVEL_INFO, - "Terminating X server (pid %d) on display %d", - display_pid, s->display); - g_sigterm(display_pid); - - LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) " - "on display %d", chansrv_pid, s->display); - g_sigterm(chansrv_pid); - - /* make sure socket cleanup happen after child process exit */ - xserver_exit_status = g_waitpid_status(display_pid); - exit_status_to_str(&xserver_exit_status, reason, sizeof(reason)); - LOG(LOG_LEVEL_INFO, - "X server on display %d (pid %d) exited with %s", - s->display, display_pid, reason); - - chansrv_exit_status = g_waitpid_status(chansrv_pid); - exit_status_to_str(&chansrv_exit_status, reason, sizeof(reason)); - LOG(LOG_LEVEL_INFO, - "xrdp channel server for display %d (pid %d)" - " exited with %s", - s->display, chansrv_pid, reason); - - cleanup_sockets(s->display); - g_deinit(); - g_exit(0); + 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]); } + + return status; } /******************************************************************************/ diff --git a/sesman/session.h b/sesman/session.h index 27f785d3..bd79397f 100644 --- a/sesman/session.h +++ b/sesman/session.h @@ -32,6 +32,7 @@ #include "guid.h" #include "scp_application_types.h" +#include "xrdp_constants.h" struct auth_info; @@ -47,21 +48,26 @@ struct session_parameters unsigned short height; unsigned short width; unsigned char bpp; - const char *shell; - const char *directory; + char shell[INFO_CLIENT_MAX_CB_LEN]; + char directory[INFO_CLIENT_MAX_CB_LEN]; }; /** * * @brief starts a session * - * This call does not return. + * @param auth_info Authentication info + * @param s Session parameters + * @param[out] pid PID of sub-process + * @return status * - * @return Connection status. + * The returned PID is only valid if the status returned is + * E_SCP_SCREATE_OK */ -void +enum scp_screate_status session_start(struct auth_info *auth_info, - const struct session_parameters *s); + const struct session_parameters *s, + int *pid); int session_reconnect(int display, int uid,