bee806d3af
The module is a dummy to be filled in later Other structural changes to sesexec:- 1) A failure of sesman needs to be detected and handled without causing sesexec to exit 2) If sesexec exits, the session can never be rediscovered. sesexec must be robust enough to stay up for the lifetime of the session so that the discovery function always works. 3) There is a mechanism for sesexec to terminate the session, but it doesn't work, as SIGCHLD is not processed while we are waiting for the session to finish. This needs fixing.
587 lines
15 KiB
C
587 lines
15 KiB
C
/**
|
|
* xrdp: A Remote Desktop Protocol server.
|
|
*
|
|
* Copyright (C) Matt Burt 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 sesexec.c
|
|
* @brief Main program file for session executive process
|
|
* @author Matt Burt
|
|
*
|
|
*/
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include <config_ac.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "arch.h"
|
|
#include "eicp.h"
|
|
#include "eicp_server.h"
|
|
#include "ercp.h"
|
|
#include "ercp_server.h"
|
|
#include "login_info.h"
|
|
#include "sesexec.h"
|
|
#include "sesexec_discover.h"
|
|
#include "sesman_config.h"
|
|
#include "log.h"
|
|
#include "os_calls.h"
|
|
#include "session.h"
|
|
#include "string_calls.h"
|
|
#include "trans.h"
|
|
#include "xrdp_sockets.h"
|
|
|
|
struct startup_params
|
|
{
|
|
const char *sesman_ini;
|
|
};
|
|
|
|
/*
|
|
* Program-scope globals
|
|
*/
|
|
struct config_sesman *g_cfg;
|
|
unsigned char g_fixedkey[8] = { 23, 82, 107, 6, 35, 78, 88, 7 };
|
|
struct login_info *g_login_info;
|
|
struct session_data *g_session_data;
|
|
|
|
tintptr g_term_event = 0;
|
|
tintptr g_sigchld_event = 0;
|
|
pid_t g_pid;
|
|
|
|
/*
|
|
* Module-scope globals
|
|
*/
|
|
static struct trans *g_ecp_trans;
|
|
static int g_terminate_loop = 0;
|
|
static int g_terminate_status = 0;
|
|
|
|
/*****************************************************************************/
|
|
/**
|
|
* Command line argument parser
|
|
* @param[in] argc number of command line arguments
|
|
* @param[in] argv pointer array of commandline arguments
|
|
* @param[out] startup_params Returned startup parameters
|
|
* @return 0 on success
|
|
*/
|
|
static int
|
|
process_params(int argc, char **argv,
|
|
struct startup_params *startup_params)
|
|
{
|
|
int index;
|
|
const char *option;
|
|
const char *value;
|
|
|
|
startup_params->sesman_ini = DEFAULT_SESMAN_INI;
|
|
|
|
index = 1;
|
|
|
|
while (index < argc)
|
|
{
|
|
option = argv[index];
|
|
|
|
if (index + 1 < argc)
|
|
{
|
|
value = argv[index + 1];
|
|
}
|
|
else
|
|
{
|
|
value = "";
|
|
}
|
|
|
|
if (g_strcmp(option, "-c") == 0)
|
|
{
|
|
index++;
|
|
startup_params->sesman_ini = value;
|
|
}
|
|
else /* unknown option */
|
|
{
|
|
return index;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
sesexec_eicp_data_in(struct trans *self)
|
|
{
|
|
int rv;
|
|
int available;
|
|
|
|
rv = eicp_msg_in_check_available(self, &available);
|
|
|
|
if (rv == 0 && available)
|
|
{
|
|
if ((rv = eicp_server(self)) != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "%s: eicp_server failed", __func__);
|
|
}
|
|
eicp_msg_in_reset(self);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
int
|
|
sesexec_ercp_data_in(struct trans *self)
|
|
{
|
|
int rv;
|
|
int available;
|
|
|
|
rv = ercp_msg_in_check_available(self, &available);
|
|
|
|
if (rv == 0 && available)
|
|
{
|
|
if ((rv = ercp_server(self)) != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "%s: ercp_server failed", __func__);
|
|
}
|
|
ercp_msg_in_reset(self);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
* Informs the main loop a termination signal has been received
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* No-op signal handler.
|
|
*/
|
|
static void
|
|
sig_no_op(int sig)
|
|
{
|
|
/* no-op */
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
* Informs the main loop a child exiting signal has been received
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
int
|
|
sesexec_is_term(void)
|
|
{
|
|
return g_terminate_loop || g_is_wait_obj_set(g_term_event);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
void
|
|
sesexec_terminate_main_loop(int status)
|
|
{
|
|
// Only take the first request to terminate the loop
|
|
if (!g_terminate_loop)
|
|
{
|
|
g_terminate_loop = 1;
|
|
g_terminate_status = status;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static void
|
|
process_sigchld_event(void)
|
|
{
|
|
struct proc_exit_status e;
|
|
int pid;
|
|
|
|
// Check for any finished children
|
|
while ((pid = g_waitchild(&e)) > 0)
|
|
{
|
|
session_process_child_exit(g_session_data, pid, &e);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static void
|
|
sesexec_terminate_session_and_wait(void)
|
|
{
|
|
session_send_term(g_session_data);
|
|
do
|
|
{
|
|
g_sleep(1000);
|
|
// Process SIGCHLD events while waiting for the session
|
|
// to exit.
|
|
if (g_is_wait_obj_set(g_sigchld_event))
|
|
{
|
|
g_reset_wait_obj(g_sigchld_event);
|
|
process_sigchld_event();
|
|
}
|
|
}
|
|
while (session_active(g_session_data));
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static void
|
|
sesexec_main_loop_cleanup(void)
|
|
{
|
|
login_info_free(g_login_info);
|
|
|
|
/* This session is no longer discoverable */
|
|
sesexec_discover_disable();
|
|
|
|
/* Don't allow sesexec to terminate with an active
|
|
session, as we can't connect to such a session */
|
|
if (session_active(g_session_data))
|
|
{
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Stopping session on xrdp-sesexec exit");
|
|
sesexec_terminate_session_and_wait();
|
|
}
|
|
session_data_free(g_session_data);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
*
|
|
* @brief Starts sesexec main loop
|
|
*
|
|
*/
|
|
static int
|
|
sesexec_main_loop(void)
|
|
{
|
|
int error = 0;
|
|
int robjs_count;
|
|
#define MAX_ROBJS 32
|
|
intptr_t robjs[MAX_ROBJS];
|
|
|
|
g_terminate_loop = 0;
|
|
g_terminate_status = 0;
|
|
g_login_info = NULL;
|
|
|
|
while (!g_terminate_loop)
|
|
{
|
|
robjs_count = 0;
|
|
robjs[robjs_count++] = g_term_event;
|
|
robjs[robjs_count++] = g_sigchld_event;
|
|
|
|
// ECP transport may be null if sesman has gone away
|
|
if (g_ecp_trans != NULL)
|
|
{
|
|
error = trans_get_wait_objs(g_ecp_trans, robjs, &robjs_count);
|
|
if (error != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "sesexec_main_loop: "
|
|
"trans_get_wait_objs(ECP) 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)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "sesexec_main_loop: "
|
|
"sesexec_discover_get_wait_objs() failed");
|
|
sesexec_terminate_main_loop(error);
|
|
continue;
|
|
}
|
|
|
|
if (g_obj_wait(robjs, robjs_count, NULL, 0, -1) != 0)
|
|
{
|
|
/* should not get here */
|
|
LOG(LOG_LEVEL_WARNING, "sesexec_main_loop: "
|
|
"Unexpected error from g_obj_wait()");
|
|
g_sleep(100);
|
|
continue;
|
|
}
|
|
|
|
if (g_is_wait_obj_set(g_term_event)) /* term */
|
|
{
|
|
g_reset_wait_obj(g_term_event);
|
|
if (session_active(g_session_data))
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "sesexec_main_loop: "
|
|
"sesexec asked to terminate with active session.");
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "sesexec_main_loop: "
|
|
"sesexec asked to terminate. "
|
|
"No session is active");
|
|
}
|
|
sesexec_terminate_main_loop(0);
|
|
continue;
|
|
}
|
|
|
|
if (g_is_wait_obj_set(g_sigchld_event)) /* SIGCHLD */
|
|
{
|
|
g_reset_wait_obj(g_sigchld_event);
|
|
|
|
// See whether the session goes from active to inactive
|
|
// after processing SIGCHLD
|
|
int session_was_active = session_active(g_session_data);
|
|
process_sigchld_event();
|
|
if (session_was_active && !session_active(g_session_data))
|
|
{
|
|
// We've finished the session. Tell sesman and
|
|
// finish up.
|
|
if (g_ecp_trans != NULL)
|
|
{
|
|
(void)ercp_send_session_finished_event(g_ecp_trans);
|
|
}
|
|
|
|
session_data_free(g_session_data);
|
|
g_session_data = NULL;
|
|
sesexec_terminate_main_loop(0);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (g_ecp_trans != NULL)
|
|
{
|
|
error = trans_check_wait_objs(g_ecp_trans);
|
|
if (error != 0)
|
|
{
|
|
if (g_ecp_trans->status != TRANS_STATUS_UP &&
|
|
session_active(g_session_data))
|
|
{
|
|
// sesman has gone away. We have an active session
|
|
// to keep track of, so sesman can pick it up when it
|
|
// restarts
|
|
LOG(LOG_LEVEL_INFO, "sesexec_main_loop: "
|
|
"sesman has exited");
|
|
trans_delete(g_ecp_trans);
|
|
g_ecp_trans = NULL;
|
|
}
|
|
else
|
|
{
|
|
// A callback has failed, or sesman has gone away and
|
|
// we have no active session
|
|
LOG(LOG_LEVEL_ERROR, "sesexec_main_loop: "
|
|
"trans_check_wait_objs failed for ECP transport");
|
|
sesexec_terminate_main_loop(error);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
error = sesexec_discover_check_wait_objs();
|
|
if (error != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "sesexec_main_loop: "
|
|
"sesexec_discover_check_wait_objs failed");
|
|
sesexec_terminate_main_loop(error);
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
/* close sesman communications immediately */
|
|
trans_delete(g_ecp_trans);
|
|
g_ecp_trans = NULL;
|
|
|
|
return g_terminate_status;
|
|
#undef MAX_ROBJS
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int start_logging(const char *sesman_ini)
|
|
{
|
|
char text[256];
|
|
int rv = 1;
|
|
if (!g_file_exist(sesman_ini))
|
|
{
|
|
g_printf("Config file %s does not exist\n", sesman_ini);
|
|
}
|
|
else
|
|
{
|
|
enum logReturns log_error;
|
|
log_error = log_start(sesman_ini, "xrdp-sesexec", 0);
|
|
|
|
if (log_error != LOG_STARTUP_OK)
|
|
{
|
|
switch (log_error)
|
|
{
|
|
case LOG_ERROR_MALLOC:
|
|
g_writeln("error on malloc. cannot start logging. quitting.");
|
|
break;
|
|
case LOG_ERROR_FILE_OPEN:
|
|
g_writeln("error opening log file [%s]. quitting.",
|
|
getLogFile(text, sizeof(text) - 1));
|
|
break;
|
|
default:
|
|
// Assume sufficient messages have already been generated
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rv = 0;
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
get_eicp_fd(char errstr[], unsigned int errstr_size)
|
|
{
|
|
const char *s = g_getenv("EICP_FD");
|
|
const char *p;
|
|
int fd;
|
|
|
|
errstr[0] = '\0';
|
|
|
|
if (s == NULL || s[0] == '\0')
|
|
{
|
|
g_snprintf(errstr, errstr_size,
|
|
"Can't read EICP_FD environment variable");
|
|
return -1;
|
|
}
|
|
|
|
for (p = s ; isdigit(*p) ; ++p)
|
|
{
|
|
;
|
|
}
|
|
|
|
if (*p != '\0')
|
|
{
|
|
g_snprintf(errstr, errstr_size, "EICP_FD has non-digit char '%c'", *p);
|
|
return -1;
|
|
}
|
|
|
|
if ((p - s) > 4)
|
|
{
|
|
g_snprintf(errstr, errstr_size, "EICP_FD has too many digits");
|
|
return -1;
|
|
}
|
|
|
|
fd = g_atoi(s);
|
|
if (!g_file_is_open(fd))
|
|
{
|
|
g_snprintf(errstr, errstr_size, "EICP_FD %d is not open", fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int error = 1;
|
|
struct startup_params startup_params = {0};
|
|
int errored_argc;
|
|
int eicp_fd;
|
|
char eicp_errstr[128];
|
|
/*
|
|
* Check the EICP transport file descriptor is provided and open
|
|
* before opening any log files, config files, etc. We then open
|
|
* log files, and log errors at that point */
|
|
eicp_fd = get_eicp_fd(eicp_errstr, sizeof(eicp_errstr));
|
|
|
|
g_init("xrdp-sesexec");
|
|
|
|
//g_sleep(15 * 1000);
|
|
errored_argc = process_params(argc, argv, &startup_params);
|
|
if (errored_argc > 0)
|
|
{
|
|
g_writeln("Unknown option: %s", argv[errored_argc]);
|
|
}
|
|
/* starting logging subsystem
|
|
*
|
|
* For historic reasons, we share a log file with sesman */
|
|
else if (start_logging(startup_params.sesman_ini) == 0)
|
|
{
|
|
/* reading config
|
|
*
|
|
* For historic reasons, we share a config with sesman */
|
|
if ((g_cfg = config_read(startup_params.sesman_ini)) == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ALWAYS, "error reading config %s: %s",
|
|
startup_params.sesman_ini, g_get_strerror());
|
|
}
|
|
else if (eicp_fd < 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "%s", eicp_errstr);
|
|
}
|
|
else
|
|
{
|
|
char text[128];
|
|
|
|
g_pid = g_getpid();
|
|
|
|
/* signal handling */
|
|
g_snprintf(text, sizeof(text), "xrdp_sesexec_%8.8x_main_term",
|
|
g_pid);
|
|
g_term_event = g_create_wait_obj(text);
|
|
g_snprintf(text, sizeof(text), "xrdp_sesexec_%8.8x_sigchld",
|
|
g_pid);
|
|
g_sigchld_event = g_create_wait_obj(text);
|
|
|
|
// No need to terminate on SIGINT for sesexec. This can
|
|
// also make it hard to debug sessions.
|
|
//g_signal_user_interrupt(set_term_event);
|
|
g_signal_terminate(set_term_event); /* SIGTERM */
|
|
g_signal_pipe(sig_no_op); /* SIGPIPE */
|
|
g_signal_child_stop(set_sigchld_event);
|
|
|
|
/* Set up an EICP process handler
|
|
* Errors are logged by this call if necessary */
|
|
g_ecp_trans = eicp_init_trans_from_fd(eicp_fd,
|
|
TRANS_TYPE_SERVER,
|
|
sesexec_is_term);
|
|
if (g_ecp_trans != NULL)
|
|
{
|
|
g_ecp_trans->trans_data_in = sesexec_eicp_data_in;
|
|
g_ecp_trans->callback_data = NULL;
|
|
|
|
/* start program main loop */
|
|
LOG(LOG_LEVEL_INFO, "starting xrdp-sesexec with pid %d", g_pid);
|
|
error = sesexec_main_loop();
|
|
sesexec_main_loop_cleanup();
|
|
}
|
|
g_delete_wait_obj(g_term_event);
|
|
}
|
|
config_free(g_cfg);
|
|
log_end();
|
|
}
|
|
|
|
g_deinit();
|
|
return error;
|
|
}
|