Add CCP support to sesexec

sesexec can now tell the xrdp process to exit, and is aware when
the xrdp process exits.
This commit is contained in:
matt335672
2025-07-14 16:11:53 +01:00
parent b5ba5635e1
commit d88cf53453
6 changed files with 406 additions and 12 deletions
+2
View File
@@ -21,6 +21,8 @@ xrdp_sesexec_SOURCES = \
sesexec.h \ sesexec.h \
session.c \ session.c \
session.h \ session.h \
ccp_server.c \
ccp_server.h \
eicp_server.c \ eicp_server.c \
eicp_server.h \ eicp_server.h \
ercp_server.c \ ercp_server.c \
+53
View File
@@ -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 <config_ac.h>
#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;
}
+39
View File
@@ -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
+32 -4
View File
@@ -87,15 +87,33 @@ handle_connect_session_request(struct trans *self)
} }
else 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 // Now we've got a transport we can send data back to
// the SCP client // the SCP client
enum scp_sconnect_status scp_status; enum scp_sconnect_status scp_status;
int display_fd = -1; int display_fd = -1;
int chan_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, scp_status = get_session_fds(g_session_data, scp_flags,
&display_fd, &chan_fd); &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, rv = scp_send_connect_session_response(scp_trans, scp_status,
display_fd, chan_fd); display_fd, chan_fd);
@@ -120,10 +138,17 @@ handle_connect_session_request(struct trans *self)
session_run_reconnect_script(g_login_info, session_run_reconnect_script(g_login_info,
g_session_data); 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 // Close all our copies of file descriptors, including the
// our copies of file descriptors. // SCP transport if we failed to convert it to a CCP transport
if (display_fd >= 0) if (display_fd >= 0)
{ {
g_file_close(display_fd); g_file_close(display_fd);
@@ -132,7 +157,10 @@ handle_connect_session_request(struct trans *self)
{ {
g_file_close(chan_fd); g_file_close(chan_fd);
} }
trans_delete(scp_trans); if (scp_trans != NULL)
{
trans_delete(scp_trans);
}
} }
} }
+193 -5
View File
@@ -32,6 +32,8 @@
#include <stdarg.h> #include <stdarg.h>
#include "arch.h" #include "arch.h"
#include "ccp.h"
#include "ccp_server.h"
#include "eicp.h" #include "eicp.h"
#include "eicp_server.h" #include "eicp_server.h"
#include "ercp.h" #include "ercp.h"
@@ -52,6 +54,12 @@ struct startup_params
const char *sesman_ini; 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 * Program-scope globals
*/ */
@@ -64,10 +72,12 @@ tintptr g_term_event = 0;
tintptr g_sigchld_event = 0; tintptr g_sigchld_event = 0;
pid_t g_pid; pid_t g_pid;
struct trans *g_ecp_trans;
struct trans *g_ccp_trans;
/* /*
* Module-scope globals * Module-scope globals
*/ */
static struct trans *g_ecp_trans;
static pid_t g_ecp_pid; static pid_t g_ecp_pid;
static int g_terminate_loop = 0; static int g_terminate_loop = 0;
static int g_terminate_status = 0; static int g_terminate_status = 0;
@@ -163,6 +173,27 @@ sesexec_ercp_data_in(struct trans *self)
return rv; 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 * 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; g_ecp_pid = 0;
rv = 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) 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]", 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); 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 error = 0;
int robjs_count; int robjs_count;
#define MAX_ROBJS 32
intptr_t robjs[MAX_ROBJS]; intptr_t robjs[MAX_ROBJS];
g_terminate_loop = 0; 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 // Add any objects from the discover module
error = sesexec_discover_get_wait_objs(robjs, &robjs_count, MAX_ROBJS); error = sesexec_discover_get_wait_objs(robjs, &robjs_count, MAX_ROBJS);
if (error != 0) if (error != 0)
@@ -370,13 +432,16 @@ sesexec_main_loop(void)
session_process_sigchld_event(g_session_data); session_process_sigchld_event(g_session_data);
if (session_was_active && !session_active(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. // finish up.
if (g_ecp_trans != NULL) if (g_ecp_trans != NULL)
{ {
(void)ercp_send_session_finished_event(g_ecp_trans); (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); session_data_free(g_session_data);
g_session_data = NULL; g_session_data = NULL;
sesexec_terminate_main_loop(0); 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(); error = sesexec_discover_check_wait_objs();
if (error != 0) if (error != 0)
{ {
@@ -419,14 +511,16 @@ sesexec_main_loop(void)
sesexec_terminate_main_loop(error); sesexec_terminate_main_loop(error);
continue; continue;
} }
} }
/* close sesman communications immediately */ /* close sesman communications immediately */
sesexec_set_ecp_transport(NULL); 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; return g_terminate_status;
#undef MAX_ROBJS
} }
/******************************************************************************/ /******************************************************************************/
@@ -598,3 +692,97 @@ main(int argc, char **argv)
g_deinit(); g_deinit();
return error; 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;
}
+87 -3
View File
@@ -29,6 +29,8 @@
#include <sys/types.h> #include <sys/types.h>
#include "ccp_application_types.h"
struct config_sesman; struct config_sesman;
struct trans; struct trans;
struct login_info; struct login_info;
@@ -39,15 +41,70 @@ struct session_data;
#endif #endif
/* Globals */ /* Globals */
/**
* Pointer to config data for sesman/sesexec
*/
extern struct config_sesman *g_cfg; 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]; 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; 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; extern struct session_data *g_session_data;
/**
* Program has received a termination event
*/
extern tintptr g_term_event; extern tintptr g_term_event;
/**
* Program has received one or more SIGCHLD events
*/
extern tintptr g_sigchld_event; 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; 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 * Callback to process incoming ERCP data
@@ -55,14 +112,18 @@ extern pid_t g_pid;
int int
sesexec_ercp_data_in(struct trans *self); sesexec_ercp_data_in(struct trans *self);
/* /**
* Check for termination * Check for termination
*
* @return boolean. Set if program has been asked to terminate
*/ */
int int
sesexec_is_term(void); sesexec_is_term(void);
/* /**
* Terminate the sesexec main loop * Terminate the sesexec main loop
*
* @param status Status to return to the OS
*/ */
void void
sesexec_terminate_main_loop(int status); sesexec_terminate_main_loop(int status);
@@ -79,12 +140,35 @@ sesexec_terminate_main_loop(int status);
int int
sesexec_set_ecp_transport(struct trans *t); sesexec_set_ecp_transport(struct trans *t);
/* /**
* Is the ECP transport still active? * Is the ECP transport still active?
* *
* @result boolean * @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 int
sesexec_is_ecp_active(void); 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 #endif // SESEXEC_H