xrdp/sesman/sesexec/sesexec.c

522 lines
13 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 "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;
}
/******************************************************************************/
#if 0
static int
sesexec_scp_data_in(struct trans *self)
{
int rv;
int available;
rv = scp_msg_in_check_available(self, &available);
if (rv == 0 && available)
{
struct sesman_con *sc = (struct sesman_con *)self->callback_data;
//if ((rv = scp_process(sc)) != 0)
{
LOG(LOG_LEVEL_ERROR, "%s: scp_process failed", __func__);
}
scp_msg_in_reset(self);
}
return rv;
}
#endif
/******************************************************************************/
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)
{
g_terminate_loop = 1;
g_terminate_status = status;
}
/******************************************************************************/
static void
process_sigchld_event(void)
{
struct 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);
}
}
/******************************************************************************/
/**
*
* @brief Starts sesexec main loop
*
*/
static int
sesexec_main_loop(void)
{
int error = 0;
int robjs_count;
intptr_t robjs[32];
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;
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;
}
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))
{
// Ask the active session to terminate
LOG(LOG_LEVEL_INFO, "sesexec_main_loop: "
"sesexec asked to terminate. "
"Terminating active session");
session_send_term(g_session_data);
}
else
{
// Terminate immediately
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.
(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;
}
}
error = trans_check_wait_objs(g_ecp_trans);
if (error != 0)
{
LOG(LOG_LEVEL_ERROR, "sesexec_main_loop: "
"trans_check_wait_objs failed for ECP transport");
sesexec_terminate_main_loop(error);
continue;
}
}
login_info_free(g_login_info);
return g_terminate_status;
}
/******************************************************************************/
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();
trans_delete(g_ecp_trans);
}
g_delete_wait_obj(g_term_event);
}
config_free(g_cfg);
log_end();
}
g_deinit();
return error;
}