From d88cf53453cfdce3d37f68f951349455eee450db Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:11:53 +0100 Subject: [PATCH] Add CCP support to sesexec sesexec can now tell the xrdp process to exit, and is aware when the xrdp process exits. --- sesman/sesexec/Makefile.am | 2 + sesman/sesexec/ccp_server.c | 53 ++++++++++ sesman/sesexec/ccp_server.h | 39 +++++++ sesman/sesexec/ercp_server.c | 36 ++++++- sesman/sesexec/sesexec.c | 198 ++++++++++++++++++++++++++++++++++- sesman/sesexec/sesexec.h | 90 +++++++++++++++- 6 files changed, 406 insertions(+), 12 deletions(-) create mode 100644 sesman/sesexec/ccp_server.c create mode 100644 sesman/sesexec/ccp_server.h diff --git a/sesman/sesexec/Makefile.am b/sesman/sesexec/Makefile.am index ac8a2c42..d444cedf 100644 --- a/sesman/sesexec/Makefile.am +++ b/sesman/sesexec/Makefile.am @@ -21,6 +21,8 @@ xrdp_sesexec_SOURCES = \ sesexec.h \ session.c \ session.h \ + ccp_server.c \ + ccp_server.h \ eicp_server.c \ eicp_server.h \ ercp_server.c \ diff --git a/sesman/sesexec/ccp_server.c b/sesman/sesexec/ccp_server.c new file mode 100644 index 00000000..c973288f --- /dev/null +++ b/sesman/sesexec/ccp_server.c @@ -0,0 +1,53 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2023 + * + * 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 eicp_server.c + * @brief eicp (executive initialisation control protocol) server function + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include "trans.h" + +#include "ccp.h" +#include "ccp_server.h" + +/******************************************************************************/ +int +ccp_server(struct trans *self) +{ + int rv = 0; + enum ccp_msg_code msgno; + + switch ((msgno = ccp_msg_in_get_msgno(self))) + { + default: + { + char buff[64]; + ccp_msgno_to_str(msgno, buff, sizeof(buff)); + LOG(LOG_LEVEL_ERROR, "Ignored CCP message %s", buff); + } + } + return rv; +} diff --git a/sesman/sesexec/ccp_server.h b/sesman/sesexec/ccp_server.h new file mode 100644 index 00000000..83d022c8 --- /dev/null +++ b/sesman/sesexec/ccp_server.h @@ -0,0 +1,39 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2023 + * + * 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 ccp_server.h + * @brief ccp (connection control protocol) server function + * @author Matt Burt + * + */ + +#ifndef CCP_SERVER_H +#define CCP_SERVER_H + +/** + * + * @brief Processes an CCP message + * @param self The CCP transport the message is coming in on + * + */ +int +ccp_server(struct trans *self); + +#endif // CCP_SERVER_H diff --git a/sesman/sesexec/ercp_server.c b/sesman/sesexec/ercp_server.c index 539473b0..434de3f7 100644 --- a/sesman/sesexec/ercp_server.c +++ b/sesman/sesexec/ercp_server.c @@ -87,15 +87,33 @@ handle_connect_session_request(struct trans *self) } else { - scp_fd = -1; // Don't close this twice! + // Ownership of the file descriptor is passed to scp_trans; + // don't delete it separately. + scp_fd = -1; // Now we've got a transport we can send data back to // the SCP client enum scp_sconnect_status scp_status; int display_fd = -1; int chan_fd = -1; + + // Terminate any existing xrdp process to sesexec + if (g_ccp_trans != NULL) + { + sesexec_terminate_connected_xrdp_process( + CCP_CLOSE_DISCONNECTED_BY_OTHERCONNECTION); + g_sleep(500); + } scp_status = get_session_fds(g_session_data, scp_flags, &display_fd, &chan_fd); + + // Tell sesman about the new client connection + if (g_ecp_trans != NULL) + { + /// TODO: client connect event + } + + // Pass the session file descriptors to the client rv = scp_send_connect_session_response(scp_trans, scp_status, display_fd, chan_fd); @@ -120,10 +138,17 @@ handle_connect_session_request(struct trans *self) session_run_reconnect_script(g_login_info, g_session_data); } + + // Convert the SCP transport to a CCP transport, and + // record it + if (sesexec_set_ccp_trans(scp_trans) == 0) + { + scp_trans = NULL; // Prevent transport being deleted. + } } - // Regardless of the result of the send, we must close all - // our copies of file descriptors. + // Close all our copies of file descriptors, including the + // SCP transport if we failed to convert it to a CCP transport if (display_fd >= 0) { g_file_close(display_fd); @@ -132,7 +157,10 @@ handle_connect_session_request(struct trans *self) { g_file_close(chan_fd); } - trans_delete(scp_trans); + if (scp_trans != NULL) + { + trans_delete(scp_trans); + } } } diff --git a/sesman/sesexec/sesexec.c b/sesman/sesexec/sesexec.c index 2f614ff3..788b7908 100644 --- a/sesman/sesexec/sesexec.c +++ b/sesman/sesexec/sesexec.c @@ -32,6 +32,8 @@ #include #include "arch.h" +#include "ccp.h" +#include "ccp_server.h" #include "eicp.h" #include "eicp_server.h" #include "ercp.h" @@ -52,6 +54,12 @@ struct startup_params const char *sesman_ini; }; +enum +{ + MAX_ROBJS = 32, ///< Maximum number of file objects in use at any one time + XRDP_EXIT_TIMEOUT = 2500 ///< Time to wait for xrdp process to exit +}; + /* * Program-scope globals */ @@ -64,10 +72,12 @@ tintptr g_term_event = 0; tintptr g_sigchld_event = 0; pid_t g_pid; +struct trans *g_ecp_trans; +struct trans *g_ccp_trans; + /* * Module-scope globals */ -static struct trans *g_ecp_trans; static pid_t g_ecp_pid; static int g_terminate_loop = 0; static int g_terminate_status = 0; @@ -163,6 +173,27 @@ sesexec_ercp_data_in(struct trans *self) return rv; } +/******************************************************************************/ +static int +sesexec_ccp_data_in(struct trans *self) +{ + int rv; + int available; + + rv = ccp_msg_in_check_available(self, &available); + + if (rv == 0 && available) + { + if ((rv = ccp_server(self)) != 0) + { + LOG(LOG_LEVEL_ERROR, "%s: ccp_server failed", __func__); + } + ccp_msg_in_reset(self); + } + + return rv; +} + /******************************************************************************/ /** * Informs the main loop a termination signal has been received @@ -235,6 +266,12 @@ sesexec_set_ecp_transport(struct trans *t) g_ecp_pid = 0; rv = 0; } + else if (t == g_ecp_trans) + { + // This would break the memory subsystem! + LOG(LOG_LEVEL_ERROR, "%s: programming error", __func__); + rv = 1; + } else if ((rv = g_sck_get_peer_cred(t->sck, &pid, &uid, &gid)) != 0) { LOG(LOG_LEVEL_ERROR, "Can't get credentials of sesman socket [%s]", @@ -286,6 +323,19 @@ sesexec_main_loop_cleanup(void) session_data_free(g_session_data); } +/******************************************************************************/ +/** + * Close the CCP trans unconditionally + * + * Use this call if you are certain the other end has gone away + */ +static void +close_ccp_trans(void) +{ + trans_delete(g_ccp_trans); + g_ccp_trans = NULL; +} + /******************************************************************************/ /** * @@ -297,7 +347,6 @@ sesexec_main_loop(void) { int error = 0; int robjs_count; -#define MAX_ROBJS 32 intptr_t robjs[MAX_ROBJS]; g_terminate_loop = 0; @@ -323,6 +372,19 @@ sesexec_main_loop(void) } } + // CCP transport is set if we have an xrdp connection + if (g_ccp_trans != NULL) + { + error = trans_get_wait_objs(g_ccp_trans, robjs, &robjs_count); + if (error != 0) + { + LOG(LOG_LEVEL_ERROR, "sesexec_main_loop: " + "trans_get_wait_objs(CCP) failed"); + sesexec_terminate_main_loop(error); + continue; + } + } + // Add any objects from the discover module error = sesexec_discover_get_wait_objs(robjs, &robjs_count, MAX_ROBJS); if (error != 0) @@ -370,13 +432,16 @@ sesexec_main_loop(void) session_process_sigchld_event(g_session_data); if (session_was_active && !session_active(g_session_data)) { - // We've finished the session. Tell sesman and + // We've finished the session. Tell sesman, xrdp and // finish up. if (g_ecp_trans != NULL) { (void)ercp_send_session_finished_event(g_ecp_trans); } + sesexec_terminate_connected_xrdp_process( + CCP_CLOSE_LOGOFF_BY_USER); + session_data_free(g_session_data); g_session_data = NULL; sesexec_terminate_main_loop(0); @@ -411,6 +476,33 @@ sesexec_main_loop(void) } } + if (g_ccp_trans != NULL) + { + error = trans_check_wait_objs(g_ccp_trans); + if (error != 0) + { + if (g_ccp_trans->status != TRANS_STATUS_UP) + { + // xrdp has gone away. + LOG(LOG_LEVEL_INFO, "sesexec_main_loop: " + "xrdp has exited"); + // TODO: Tell sesman xrdp has exited + close_ccp_trans(); + } + else + { + // A callback has failed. This shouldn't really happen. + // Try to signal a software failure to the xrdp process + LOG(LOG_LEVEL_ERROR, "sesexec_main_loop: " + "trans_check_wait_objs failed for CCP transport"); + sesexec_terminate_connected_xrdp_process( + CCP_CLOSE_SOFTWARE_FAILURE); + + } + continue; + } + } + error = sesexec_discover_check_wait_objs(); if (error != 0) { @@ -419,14 +511,16 @@ sesexec_main_loop(void) sesexec_terminate_main_loop(error); continue; } - } /* close sesman communications immediately */ sesexec_set_ecp_transport(NULL); + /* We should already have notified xrdp of the reason why we are + * closing, in which case this call has no effect */ + sesexec_terminate_connected_xrdp_process(CCP_CLOSE_SOFTWARE_FAILURE); + return g_terminate_status; -#undef MAX_ROBJS } /******************************************************************************/ @@ -598,3 +692,97 @@ main(int argc, char **argv) g_deinit(); return error; } + +/******************************************************************************/ +void +sesexec_terminate_connected_xrdp_process(enum ccp_close_reason_type reason) +{ + if (g_ccp_trans != NULL && g_ccp_trans->status == TRANS_STATUS_UP) + { + // Ask xrdp to exit, specifying the reason to return to + // the RDP client (if possible) + (void)ccp_send_close_connection_request(g_ccp_trans, reason); + + unsigned int start_ms = g_get_elapsed_ms(); + while (1) + { + int robjs_count = 0; + intptr_t robjs[MAX_ROBJS]; + + // How long have we been waiting for xrdp to exit? + unsigned int elapsed = g_get_elapsed_ms() - start_ms; + if (elapsed > XRDP_EXIT_TIMEOUT) + { + // A timeout has occurred + LOG(LOG_LEVEL_WARNING, + "xrdp process failed to exit after %u ms", elapsed); + break; + } + + robjs[robjs_count++] = g_term_event; + if (trans_get_wait_objs(g_ccp_trans, robjs, &robjs_count) != 0) + { + // Transport has gone away + LOG(LOG_LEVEL_WARNING, + "xrdp process exited after %u ms", elapsed); + break; + } + if (g_obj_wait(robjs, robjs_count, NULL, 0, + XRDP_EXIT_TIMEOUT - elapsed) != 0) + { + /* should not get here */ + g_sleep(100); + } + else if (g_is_wait_obj_set(g_term_event)) + { + // Get out as quickly as possible + break; + } + else + { + // This will cause trans_get_wait_objs() to return a failure + // when the end of the data on the socket is reached. + (void)trans_check_wait_objs(g_ccp_trans); + } + } + } + + close_ccp_trans(); +} + +/******************************************************************************/ +int +sesexec_set_ccp_trans(struct trans *scp_trans) +{ + int rv = 1; + pid_t pid; + + if (scp_trans == NULL) + { + close_ccp_trans(); + rv = 0; + } + else if (scp_trans == g_ccp_trans) + { + // This would break the memory subsystem! + LOG(LOG_LEVEL_ERROR, "%s: programming error", __func__); + rv = 1; + } + else if ((rv = g_sck_get_peer_cred(scp_trans->sck, &pid, NULL, NULL)) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't get credentials of xrdp socket [%s]", + g_get_strerror()); + } + else + { + // Convert the transport to a CCP transport + ccp_trans_from_scp_trans(scp_trans, + sesexec_ccp_data_in, + NULL); + trans_delete(g_ccp_trans); + g_ccp_trans = scp_trans; + rv = 0; + } + + return rv; +} diff --git a/sesman/sesexec/sesexec.h b/sesman/sesexec/sesexec.h index 7d476b2c..fa0dc2d5 100644 --- a/sesman/sesexec/sesexec.h +++ b/sesman/sesexec/sesexec.h @@ -29,6 +29,8 @@ #include +#include "ccp_application_types.h" + struct config_sesman; struct trans; struct login_info; @@ -39,15 +41,70 @@ struct session_data; #endif /* Globals */ +/** + * Pointer to config data for sesman/sesexec + */ extern struct config_sesman *g_cfg; + +/** + * DES key used to obfuscate VNC password files. + * + * This key is not documented in RFC6143, but can readily be found by + * searching VNC sources. + * + * You can also find the 'reversed' form "e84ad660c4721ae0" + * on the net, particulary for openssl one-liners to decrypt VNC + * password files. + */ extern unsigned char g_fixedkey[8]; + +/** + * Information about the logged-in user + * + * This is set when the user successfully passes authentication + */ extern struct login_info *g_login_info; + +/** + * Information about the user session (opaque type) + * + * Set when the user is succesfully logged in + */ extern struct session_data *g_session_data; +/** + * Program has received a termination event + */ extern tintptr g_term_event; + +/** + * Program has received one or more SIGCHLD events + */ extern tintptr g_sigchld_event; + +/** + * PID of sesexec process + * + * Used to detect when we are running in a form of the original process + */ extern pid_t g_pid; +/** + * EICP/ERCP transport + * + * Used to communicate with the sesman process + * + * Use sesexec_is_ecp_active() if you need to check sesman is + * truly there. + */ +extern struct trans *g_ecp_trans; + +/** + * CCP transport + * + * Used to communicate with the currently connected xrdp process + */ +extern struct trans *g_ccp_trans; /** * Callback to process incoming ERCP data @@ -55,14 +112,18 @@ extern pid_t g_pid; int sesexec_ercp_data_in(struct trans *self); -/* +/** * Check for termination + * + * @return boolean. Set if program has been asked to terminate */ int sesexec_is_term(void); -/* +/** * Terminate the sesexec main loop + * + * @param status Status to return to the OS */ void sesexec_terminate_main_loop(int status); @@ -79,12 +140,35 @@ sesexec_terminate_main_loop(int status); int sesexec_set_ecp_transport(struct trans *t); -/* +/** * Is the ECP transport still active? * * @result boolean + * + * This is intended to be used as a guard to prevent an active ECP + * transport being overwritten. Do not use it to check if sesman is + * active before sending messages, as this introduces a race condition. */ int sesexec_is_ecp_active(void); +/** + * Terminate an active xrdp process + * + * @param Reason to pass back to the xrdp process (if connected) + * + * After this call, g_ccp_trans will be NULL + */ +void +sesexec_terminate_connected_xrdp_process(enum ccp_close_reason_type reason); + +/** + * Set the CCP transport from an SCP transport + * + * This call is intended to be used at the end of a connection + * event, to record our end of the connection to the xrdp process + */ +int +sesexec_set_ccp_trans(struct trans *scp_trans); + #endif // SESEXEC_H