diff --git a/sesman/Makefile.am b/sesman/Makefile.am index 9ddef8e8..74dc7a85 100644 --- a/sesman/Makefile.am +++ b/sesman/Makefile.am @@ -32,6 +32,8 @@ xrdp_sesman_SOURCES = \ sesexec_control.h \ session_list.c \ session_list.h \ + sesman_restart.c \ + sesman_restart.h \ sig.c \ sig.h diff --git a/sesman/sesman.c b/sesman/sesman.c index e1396623..62ad68eb 100644 --- a/sesman/sesman.c +++ b/sesman/sesman.c @@ -46,6 +46,7 @@ #include "scp.h" #include "scp_process.h" #include "sesexec_control.h" +#include "sesman_restart.h" #include "sig.h" #include "string_calls.h" #include "trans.h" @@ -78,7 +79,7 @@ struct sesman_startup_params }; struct config_sesman *g_cfg; -static tintptr g_term_event = 0; +tintptr g_term_event = 0; static tintptr g_sigchld_event = 0; static tintptr g_reload_event = 0; @@ -945,7 +946,8 @@ main(int argc, char **argv) } if ((error = pre_session_list_init(MAX_PRE_SESSION_ITEMS)) == 0 && - (error = session_list_init()) == 0) + (error = session_list_init()) == 0 && + (error = sesman_restart_discover_sessions()) == 0) { error = sesman_main_loop(); } diff --git a/sesman/sesman.h b/sesman/sesman.h index ba5a595b..6c60de73 100644 --- a/sesman/sesman.h +++ b/sesman/sesman.h @@ -32,6 +32,7 @@ struct trans; /* Globals */ extern struct config_sesman *g_cfg; +extern tintptr g_term_event; /* * Remove the listening transport diff --git a/sesman/sesman_restart.c b/sesman/sesman_restart.c new file mode 100644 index 00000000..7b5062d2 --- /dev/null +++ b/sesman/sesman_restart.c @@ -0,0 +1,291 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Matt Burt 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file sesman_restart.c + * @brief Sesman restart definitions + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include + +#include "os_calls.h" +#include "sesman.h" +#include "sesman_config.h" +#include "session_list.h" + +#include "ercp.h" + +#include "sesman_restart.h" +#include "xrdp_sockets.h" + +// Result of calling init_restart_directory() +enum init_restart_dir_status +{ + E_RESTART_DIR_CREATED_OK, ///< All good. Dir created + E_RESTART_DIR_ALREADY_EXISTS, ///< All good. Dir already existed + E_RESTART_DIR_ERROR ///< Not good +}; + +enum +{ + MAX_DISCOVERY_WAIT_TIME = 5000 // Milli-seconds +}; + +/******************************************************************************/ +static enum init_restart_dir_status +init_restart_directory(const char *restart_dir) +{ + enum init_restart_dir_status rv; + + if (g_directory_exist(restart_dir)) + { + rv = E_RESTART_DIR_ALREADY_EXISTS; + } + else + { + // Create the restart directory for the next run + if (g_mkdir(restart_dir) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't create restart directory %s [%s]", + restart_dir, g_get_strerror()); + rv = E_RESTART_DIR_ERROR; + } + else + { + rv = E_RESTART_DIR_CREATED_OK; + } + } + + if (rv != E_RESTART_DIR_ERROR) + { + // Always set the permissions on the restart directory, whether + // or not we created it + if (g_chown(restart_dir, g_getuid(), g_getuid()) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't set ownership of '%s' [%s]", + restart_dir, g_get_strerror()); + rv = E_RESTART_DIR_ERROR; + } + else if (g_chmod_hex(restart_dir, 0x700) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't set permissions on '%s' [%s]", + restart_dir, g_get_strerror()); + rv = E_RESTART_DIR_ERROR; + } + } + return rv; +} + +/******************************************************************************/ +/** + * Attempts to add a sesexec Unix Domain Socket to the process_list + * @param filename Name of UDS + * @return Boolean for success + * + * The credentials of the process on the other end are checked. + * We don't take the max session limit into account when adding sessions here, + * as this could result in orphaned sessions. + */ +static int +add_sesexec_fd_to_session_list(const char *filename) +{ + int status = 0; + struct trans *t = NULL; + + // Check filename is a socket + if (g_socket_exist(filename)) + { + // Try to connect to the session + if ((t = ercp_connect(filename, sesman_is_term)) != NULL) + { + int sesexec_pid; + int sesexec_uid; + int sesexec_gid; + + // Find the credentials of the sesexec process on the other end + if (g_sck_get_peer_cred(t->sck, &sesexec_pid, + &sesexec_uid, &sesexec_gid) == 0) + { + // Don't talk to unprivileged processes. It's a big concern + // if we find one. + if (sesexec_uid != 0 || sesexec_gid != 0) + { + LOG(LOG_LEVEL_ALWAYS, + "Unexpected sesexec owner %d:%d" + " for PID %d listening on %s", + sesexec_uid, sesexec_gid, sesexec_pid, filename); + } + else + { + struct session_item *s_item; + if ((s_item = session_list_new()) != NULL) + { + // Finalise the session for I/O + t->trans_data_in = sesman_ercp_data_in; + t->callback_data = (void *)s_item; + + // Complete the session fields for the + // E_SESSION_STARTING state + s_item->sesexec_trans = t; + s_item->sesexec_pid = sesexec_pid; + s_item->display = -1; + + // Tell the caller we've added one + status = 1; + } + } + } + } + } + + // Clean up an unused transport + if (status == 0) + { + trans_delete(t); + } + + return 1; +} + +/******************************************************************************/ +static int +discover_sessions(const char *restart_dir) +{ + int rv = 0; + struct list *dirnames = g_readdir(restart_dir); + unsigned int start_time = g_get_elapsed_ms(); + unsigned int session_count; + unsigned int elapsed; + int robjs_count; + intptr_t robjs[1024]; + int timeout; + + if (dirnames == NULL) + { + LOG(LOG_LEVEL_ERROR, + "Can't read restart directory to discover sessions [%s]", + g_get_strerror()); + } + else + { + // Iterate over the restart directory, and add any sesexec + // processes we discover to the session list, in + // E_SESSION_STARTING state. + char filename[XRDP_SOCKETS_MAXPATH]; + int i; + + for (i = 0 ; i < dirnames->count; ++i) + { + g_snprintf(filename, sizeof(filename), "%s/%s", + restart_dir, (const char *)dirnames->items[i]); + + (void)add_sesexec_fd_to_session_list(filename); + } + + list_delete(dirnames); + dirnames = NULL; + } + + // Process session list messages until either all sessions have + // started (or failed), or we hit a timeout. + while (1) + { + session_count = session_list_get_count_by_state(E_SESSION_STARTING); + elapsed = (g_get_elapsed_ms() - start_time); + if (session_count == 0 || elapsed >= MAX_DISCOVERY_WAIT_TIME) + { + break; + } + + robjs_count = 0; + robjs[robjs_count++] = g_term_event; + (void)session_list_get_wait_objs(robjs, &robjs_count); + + timeout = MAX_DISCOVERY_WAIT_TIME - elapsed; // > 0 + if (g_obj_wait(robjs, robjs_count, NULL, 0, timeout) != 0) + { + /* should not get here */ + g_sleep(100); + continue; + } + + if (g_is_wait_obj_set(g_term_event)) /* term */ + { + LOG(LOG_LEVEL_INFO, "discover_sessions: sesman asked to terminate"); + rv = 1; + break; + } + + (void)session_list_check_wait_objs(); + } + + if (rv == 0) + { + LOG(LOG_LEVEL_INFO, + "Session discovery took %d secs and loaded %u sessions", + elapsed, session_list_get_count_by_state(E_SESSION_RUNNING)); + + if (session_count > 0) + { + LOG(LOG_LEVEL_WARNING, + "%u sessions have not responded at end of discovery", + session_count); + } + } + + return rv; +} + +/******************************************************************************/ +int +sesman_restart_discover_sessions(void) +{ + int rv = 1; + // The restart directory contains Unix Domain sockets, so can't + // exceed XRDP_SOCKETS_MAXPATH in length + char restart_dir[XRDP_SOCKETS_MAXPATH]; + + // sizeof(g_cfg->listen_port) is guaranteed to be smaller than + // XRDP_SOCKETS_MAXPATH + g_snprintf(restart_dir, sizeof(restart_dir), + "%s.r", g_cfg->listen_port); + switch (init_restart_directory(restart_dir)) + { + case E_RESTART_DIR_CREATED_OK: + // Nothing to discover + rv = 0; + break; + + case E_RESTART_DIR_ALREADY_EXISTS: + // Look for sessions from previous run + rv = discover_sessions(restart_dir); + break; + + default: + ; + } + + return rv; +} diff --git a/sesman/sesman_restart.h b/sesman/sesman_restart.h new file mode 100644 index 00000000..bbfa11b4 --- /dev/null +++ b/sesman/sesman_restart.h @@ -0,0 +1,40 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Matt Burt 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file sesman_restart.h + * @brief Sesman restart declarations + * @author Matt Burt + * + */ + + +#ifndef SESMAN_RESTART_H +#define SESMAN_RESTART_H + +/** + * Discover sessions from a previous sesman run + * @return 0 for success + * + * Errors are logged + */ +int +sesman_restart_discover_sessions(void); + +#endif // SESMAN_RESTART_H