xrdp/sesman/sesman.c
matt335672 0db849fc5c Move SCP to a Unix Domain Socket
The TCP socket implementation of sesman has a number of limitations,
namely that it is affected by firewalls, and also that determining the
user on the other end requires a full authentication process.

The advantage of the TCP socket is that sesman and xrdp can be run on
separate machines. This is however not supported by the xorgxrdp
backend (shared memory), and is insecure, in that passwords are sent
in-the-clear, and the connection is susceptible to MitM attacks. This
architecture has been deprecated in release notes since xrdp v0.9.17,
and although it will continue to be supported in any further releases
in the x0.9.x series, it will not be supported in the next major
version.
2022-04-18 09:12:35 +01:00

904 lines
23 KiB
C

/**
* xrdp: A Remote Desktop Protocol server.
*
* Copyright (C) Jay Sorg 2004-2015
*
* 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.c
* @brief Main program file
* @author Jay Sorg
*
*/
#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif
#include <stdarg.h>
#include "sesman.h"
#include "xrdp_configure_options.h"
#include "string_calls.h"
#include "trans.h"
#include "scp_process.h"
#include "lock_uds.h"
/**
* Maximum number of short-lived connections to sesman
*
* At the moment, all connections to sesman are short-lived. This may change
* in the future
*/
#define MAX_SHORT_LIVED_CONNECTIONS 16
struct sesman_startup_params
{
const char *sesman_ini;
int kill;
int no_daemon;
int help;
int version;
int dump_config;
};
struct config_sesman *g_cfg;
unsigned char g_fixedkey[8] = { 23, 82, 107, 6, 35, 78, 88, 7 };
tintptr g_term_event = 0;
tintptr g_sigchld_event = 0;
tintptr g_reload_event = 0;
/**
* Items stored on the g_con_list
*/
struct sesman_con
{
struct trans *t;
};
static struct trans *g_list_trans;
/* Variables used to lock g_list_trans */
static struct lock_uds *g_list_trans_lock;
static struct list *g_con_list = NULL;
static int g_pid;
/*****************************************************************************/
/**
* @brief looks for a case-insensitive match of a string in a list
* @param candidate String to match
* @param ... NULL-terminated list of strings to compare the candidate with
* @return !=0 if the candidate is found in the list
*/
static int nocase_matches(const char *candidate, ...)
{
va_list vl;
const char *member;
int result = 0;
va_start(vl, candidate);
while ((member = va_arg(vl, const char *)) != NULL)
{
if (g_strcasecmp(candidate, member) == 0)
{
result = 1;
break;
}
}
va_end(vl);
return result;
}
/**
* Allocates a sesman_con struct
*
* @param trans Pointer to newly-allocated transport
* @return struct sesman_con pointer
*/
static struct sesman_con *
alloc_connection(struct trans *t)
{
struct sesman_con *result;
if ((result = g_new(struct sesman_con, 1)) != NULL)
{
result->t = t;
}
return result;
}
/**
* Deletes a sesman_con struct, freeing resources
*
* After this call, the passed-in pointer is invalid and must not be
* referenced.
*
* @param sc struct to de-allocate
*/
static void
delete_connection(struct sesman_con *sc)
{
trans_delete(sc->t);
g_free(sc);
}
/*****************************************************************************/
/**
*
* @brief Command line argument parser
* @param[in] argc number of command line arguments
* @param[in] argv pointer array of commandline arguments
* @param[out] sesman_startup_params Returned startup parameters
* @return 0 on success, n on nth argument is unknown
*
*/
static int
sesman_process_params(int argc, char **argv,
struct sesman_startup_params *startup_params)
{
int index;
const char *option;
const char *value;
index = 1;
while (index < argc)
{
option = argv[index];
if (index + 1 < argc)
{
value = argv[index + 1];
}
else
{
value = "";
}
if (nocase_matches(option, "-help", "--help", "-h", NULL))
{
startup_params->help = 1;
}
else if (nocase_matches(option, "-kill", "--kill", "-k", NULL))
{
startup_params->kill = 1;
}
else if (nocase_matches(option, "-nodaemon", "--nodaemon", "-n",
"-nd", "--nd", "-ns", "--ns", NULL))
{
startup_params->no_daemon = 1;
}
else if (nocase_matches(option, "-v", "--version", NULL))
{
startup_params->version = 1;
}
else if (nocase_matches(option, "--dump-config", NULL))
{
startup_params->dump_config = 1;
}
else if (nocase_matches(option, "-c", "--config", NULL))
{
index++;
startup_params->sesman_ini = value;
}
else /* unknown option */
{
return index;
}
index++;
}
return 0;
}
/******************************************************************************/
static int
create_sesman_runtime_dir(void)
{
int rv = -1;
/* Make sure if we create the directory, there's no gap where it
* may have the wrong permissions */
int entry_umask = g_umask_hex(0x755);
if (!g_directory_exist(SESMAN_RUNTIME_PATH) &&
!g_create_dir(SESMAN_RUNTIME_PATH))
{
LOG(LOG_LEVEL_ERROR,
"Can't create runtime directory '"
SESMAN_RUNTIME_PATH "' [%s]", g_get_strerror());
}
else if (g_chown(SESMAN_RUNTIME_PATH, g_getuid(), g_getuid()) != 0)
{
LOG(LOG_LEVEL_ERROR,
"Can't set ownership of sesman runtime directory [%s]",
g_get_strerror());
}
else if (g_chmod_hex(SESMAN_RUNTIME_PATH, 0x755) != 0)
{
/* This might seem redundant, but there's a chance the
* directory already exists */
LOG(LOG_LEVEL_ERROR,
"Can't set permissions of sesman runtime directory [%s]",
g_get_strerror());
}
else
{
rv = 0;
}
g_umask_hex(entry_umask);
return rv;
}
/******************************************************************************/
static int sesman_listen_test(struct config_sesman *cfg)
{
int status = sesman_create_listening_transport(cfg);
sesman_delete_listening_transport();
return status;
}
/******************************************************************************/
int
sesman_close_all(void)
{
int index;
struct sesman_con *sc;
LOG_DEVEL(LOG_LEVEL_TRACE, "sesman_close_all:");
sesman_delete_listening_transport();
for (index = 0; index < g_con_list->count; index++)
{
sc = (struct sesman_con *) list_get_item(g_con_list, index);
delete_connection(sc);
}
return 0;
}
/******************************************************************************/
static int
sesman_data_in(struct trans *self)
{
int rv;
int available;
rv = scp_msg_in_check_available(self, &available);
if (rv == 0 && available)
{
if ((rv = scp_process(self)) != 0)
{
LOG(LOG_LEVEL_ERROR, "sesman_data_in: scp_process_msg failed");
}
scp_msg_in_reset(self);
}
return rv;
}
/******************************************************************************/
static int
sesman_listen_conn_in(struct trans *self, struct trans *new_self)
{
struct sesman_con *sc;
if (g_con_list->count >= MAX_SHORT_LIVED_CONNECTIONS)
{
LOG(LOG_LEVEL_ERROR, "sesman_data_in: error, too many "
"connections, rejecting");
trans_delete(new_self);
}
else if ((sc = alloc_connection(new_self)) == NULL ||
scp_init_trans(new_self) != 0)
{
LOG(LOG_LEVEL_ERROR, "sesman_data_in: No memory to allocate "
"new connection");
trans_delete(new_self);
}
else
{
new_self->callback_data = (void *)sc;
new_self->trans_data_in = sesman_data_in;
list_add_item(g_con_list, (intptr_t) sc);
}
return 0;
}
/******************************************************************************/
/**
* 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);
}
}
/******************************************************************************/
/**
* Informs the main loop a SIGCHLD 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);
}
}
/******************************************************************************/
/**
* Informs the main loop a SIGHUP has been received
*/
static void
set_reload_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_reload_event);
}
}
/******************************************************************************/
void
sesman_delete_listening_transport(void)
{
if (g_getpid() == g_pid)
{
trans_delete(g_list_trans);
}
else
{
trans_delete_from_child(g_list_trans);
}
g_list_trans = NULL;
unlock_uds(g_list_trans_lock);
g_list_trans_lock = NULL;
}
/******************************************************************************/
int
sesman_create_listening_transport(const struct config_sesman *cfg)
{
int rv = 1;
g_list_trans = trans_create(TRANS_MODE_UNIX, 8192, 8192);
if (g_list_trans == NULL)
{
LOG(LOG_LEVEL_ERROR, "%s: trans_create failed", __func__);
}
else if ((g_list_trans_lock = lock_uds(cfg->listen_port)) != NULL)
{
/* Make sure the file is always created with the correct
* permissions, if it's not there */
int entry_umask = g_umask_hex(0x666);
LOG_DEVEL(LOG_LEVEL_DEBUG, "%s: port %s", __func__, cfg->listen_port);
rv = trans_listen_address(g_list_trans, cfg->listen_port, NULL);
if (rv != 0)
{
LOG(LOG_LEVEL_ERROR, "%s: trans_listen_address failed", __func__);
}
else if (g_chown(cfg->listen_port, g_getuid(), g_getuid()) != 0)
{
LOG(LOG_LEVEL_ERROR,
"Can't set ownership of '%s' [%s]",
cfg->listen_port, g_get_strerror());
}
else if ((rv = g_chmod_hex(cfg->listen_port, 0x666)) != 0)
{
LOG(LOG_LEVEL_ERROR, "%s: Can't set permissions on '%s' [%s]",
__func__, cfg->listen_port, g_get_strerror());
}
else
{
g_list_trans->trans_conn_in = sesman_listen_conn_in;
}
g_umask_hex(entry_umask);
}
if (rv != 0)
{
sesman_delete_listening_transport();
}
return rv;
}
/******************************************************************************/
/**
*
* @brief Starts sesman main loop
*
*/
static int
sesman_main_loop(void)
{
int error;
int robjs_count;
int wobjs_count;
int timeout;
int index;
intptr_t robjs[32];
intptr_t wobjs[32];
struct sesman_con *scon;
g_con_list = list_create();
if (g_con_list == NULL)
{
LOG(LOG_LEVEL_ERROR, "sesman_main_loop: list_create failed");
return 1;
}
if (sesman_create_listening_transport(g_cfg) != 0)
{
LOG(LOG_LEVEL_ERROR,
"sesman_main_loop: sesman_create_listening_transport failed");
list_delete(g_con_list);
return 1;
}
LOG(LOG_LEVEL_INFO, "Sesman now listening on %s", g_cfg->listen_port);
error = 0;
while (!error)
{
timeout = -1;
robjs_count = 0;
robjs[robjs_count++] = g_term_event;
robjs[robjs_count++] = g_sigchld_event;
robjs[robjs_count++] = g_reload_event;
wobjs_count = 0;
for (index = 0; index < g_con_list->count; index++)
{
scon = (struct sesman_con *)list_get_item(g_con_list, index);
if (scon != NULL)
{
error = trans_get_wait_objs_rw(scon->t,
robjs, &robjs_count,
wobjs, &wobjs_count, &timeout);
if (error != 0)
{
LOG(LOG_LEVEL_ERROR, "sesman_main_loop: "
"trans_get_wait_objs_rw failed");
break;
}
}
}
if (error != 0)
{
break;
}
if (g_list_trans != NULL)
{
/* g_list_trans might be NULL on a reconfigure if sesman
* is unable to listen again */
error = trans_get_wait_objs_rw(g_list_trans, robjs, &robjs_count,
wobjs, &wobjs_count, &timeout);
if (error != 0)
{
LOG(LOG_LEVEL_ERROR, "sesman_main_loop: "
"trans_get_wait_objs_rw failed");
break;
}
}
if (g_obj_wait(robjs, robjs_count, wobjs, wobjs_count, timeout) != 0)
{
/* should not get here */
LOG(LOG_LEVEL_WARNING, "sesman_main_loop: "
"Unexpected error from g_obj_wait()");
g_sleep(100);
}
if (g_is_wait_obj_set(g_term_event)) /* term */
{
LOG(LOG_LEVEL_INFO, "sesman_main_loop: "
"sesman asked to terminate");
break;
}
if (g_is_wait_obj_set(g_sigchld_event)) /* A child has exited */
{
g_reset_wait_obj(g_sigchld_event);
sig_sesman_session_end();
}
if (g_is_wait_obj_set(g_reload_event)) /* We're asked to reload */
{
g_reset_wait_obj(g_reload_event);
sig_sesman_reload_cfg();
}
for (index = 0; index < g_con_list->count; index++)
{
scon = (struct sesman_con *)list_get_item(g_con_list, index);
if (scon != NULL)
{
if (trans_check_wait_objs(scon->t) != 0)
{
LOG(LOG_LEVEL_ERROR, "sesman_main_loop: "
"trans_check_wait_objs failed, removing trans");
delete_connection(scon);
list_remove_item(g_con_list, index);
index--;
continue;
}
}
}
if (g_list_trans != NULL)
{
error = trans_check_wait_objs(g_list_trans);
if (error != 0)
{
LOG(LOG_LEVEL_ERROR, "sesman_main_loop: "
"trans_check_wait_objs failed");
break;
}
}
}
for (index = 0; index < g_con_list->count; index++)
{
scon = (struct sesman_con *) list_get_item(g_con_list, index);
delete_connection(scon);
}
list_delete(g_con_list);
sesman_delete_listening_transport();
return 0;
}
/*****************************************************************************/
static void
print_version(void)
{
g_writeln("xrdp-sesman %s", PACKAGE_VERSION);
g_writeln(" The xrdp session manager");
g_writeln(" Copyright (C) 2004-2020 Jay Sorg, "
"Neutrino Labs, and all contributors.");
g_writeln(" See https://github.com/neutrinolabs/xrdp for more information.");
g_writeln("%s", "");
#if defined(XRDP_CONFIGURE_OPTIONS)
g_writeln(" Configure options:");
g_writeln("%s", XRDP_CONFIGURE_OPTIONS);
#endif
}
/******************************************************************************/
static void
print_help(void)
{
g_printf("Usage: xrdp-sesman [options]\n");
g_printf(" -k, --kill shut down xrdp-sesman\n");
g_printf(" -h, --help show help\n");
g_printf(" -v, --version show version\n");
g_printf(" -n, --nodaemon don't fork into background\n");
g_printf(" -c, --config specify new path to sesman.ini\n");
g_writeln(" --dump-config display config on stdout on startup");
g_deinit();
}
/******************************************************************************/
static int
kill_running_sesman(const char *pid_file)
{
int error;
int fd;
int pid;
char pid_s[32] = {0};
/* check if sesman is running */
if (!g_file_exist(pid_file))
{
g_printf("sesman is not running (pid file not found - %s)\n", pid_file);
g_deinit();
return 1;
}
fd = g_file_open(pid_file);
if (-1 == fd)
{
g_printf("error opening pid file[%s]: %s\n", pid_file, g_get_strerror());
return 1;
}
error = g_file_read(fd, pid_s, sizeof(pid_s) - 1);
if (-1 == error)
{
g_printf("error reading pid file: %s\n", g_get_strerror());
g_file_close(fd);
g_deinit();
return 1;
}
g_file_close(fd);
pid = g_atoi(pid_s);
error = g_sigterm(pid);
if (0 != error)
{
g_printf("error killing sesman: %s\n", g_get_strerror());
}
else
{
g_file_delete(pid_file);
}
g_deinit();
return error;
}
/******************************************************************************/
int
main(int argc, char **argv)
{
int error;
enum logReturns log_error;
char text[256];
char pid_file[256];
char default_sesman_ini[256];
struct sesman_startup_params startup_params = {0};
int errored_argc;
int daemon;
g_init("xrdp-sesman");
g_snprintf(pid_file, 255, "%s/xrdp-sesman.pid", XRDP_PID_PATH);
g_snprintf(default_sesman_ini, 255, "%s/sesman.ini", XRDP_CFG_PATH);
startup_params.sesman_ini = default_sesman_ini;
errored_argc = sesman_process_params(argc, argv, &startup_params);
if (errored_argc > 0)
{
print_version();
g_writeln("%s", "");
print_help();
g_writeln("%s", "");
g_writeln("Unknown option: %s", argv[errored_argc]);
g_deinit();
g_exit(1);
}
if (startup_params.help)
{
print_help();
g_exit(0);
}
if (startup_params.version)
{
print_version();
g_exit(0);
}
if (startup_params.kill)
{
g_exit(kill_running_sesman(pid_file));
}
if (g_file_exist(pid_file))
{
g_printf("xrdp-sesman is already running.\n");
g_printf("if it's not running, try removing ");
g_printf("%s", pid_file);
g_printf("\n");
g_deinit();
g_exit(1);
}
/* starting logging subsystem */
if (!g_file_exist(startup_params.sesman_ini))
{
g_printf("Config file %s does not exist\n", startup_params.sesman_ini);
g_deinit();
g_exit(1);
}
log_error = log_start(
startup_params.sesman_ini, "xrdp-sesman",
(startup_params.dump_config) ? LOG_START_DUMP_CONFIG : 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, 255));
break;
default:
// Assume sufficient messages have already been generated
break;
}
g_deinit();
g_exit(1);
}
/* reading config */
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());
log_end();
g_deinit();
g_exit(1);
}
if (startup_params.dump_config)
{
config_dump(g_cfg);
}
LOG(LOG_LEVEL_TRACE, "config loaded in %s at %s:%d", __func__, __FILE__, __LINE__);
LOG(LOG_LEVEL_TRACE, " sesman_ini = %s", g_cfg->sesman_ini);
LOG(LOG_LEVEL_TRACE, " listen_port = %s", g_cfg->listen_port);
LOG(LOG_LEVEL_TRACE, " enable_user_wm = %d", g_cfg->enable_user_wm);
LOG(LOG_LEVEL_TRACE, " default_wm = %s", g_cfg->default_wm);
LOG(LOG_LEVEL_TRACE, " user_wm = %s", g_cfg->user_wm);
LOG(LOG_LEVEL_TRACE, " reconnect_sh = %s", g_cfg->reconnect_sh);
LOG(LOG_LEVEL_TRACE, " auth_file_path = %s", g_cfg->auth_file_path);
daemon = !startup_params.no_daemon;
if (daemon)
{
/* not to spit on the console, shut up stdout/stderr before anything's logged */
g_file_close(0);
g_file_close(1);
g_file_close(2);
if (g_file_open("/dev/null") < 0)
{
}
if (g_file_open("/dev/null") < 0)
{
}
if (g_file_open("/dev/null") < 0)
{
}
}
/* Create the runtime directory before we try to listen (or
* test-listen), so there's somewhere for the default socket to live */
if (create_sesman_runtime_dir() != 0)
{
config_free(g_cfg);
log_end();
g_deinit();
g_exit(1);
}
if (daemon)
{
/* start of daemonizing code */
if (sesman_listen_test(g_cfg) != 0)
{
LOG(LOG_LEVEL_ERROR, "Failed to start xrdp-sesman daemon, "
"possibly address already in use.");
config_free(g_cfg);
log_end();
g_deinit();
g_exit(1);
}
if (0 != g_fork())
{
config_free(g_cfg);
log_end();
g_deinit();
g_exit(0);
}
}
/* Now we've forked (if necessary), we can get the prgram PID */
g_pid = g_getpid();
/* signal handling */
g_snprintf(text, 255, "xrdp_sesman_%8.8x_main_term", g_pid);
g_term_event = g_create_wait_obj(text);
g_snprintf(text, 255, "xrdp_sesman_%8.8x_sigchld", g_pid);
g_sigchld_event = g_create_wait_obj(text);
g_snprintf(text, 255, "xrdp_sesman_%8.8x_reload", g_pid);
g_reload_event = g_create_wait_obj(text);
g_signal_hang_up(set_reload_event); /* SIGHUP */
g_signal_user_interrupt(set_term_event); /* SIGINT */
g_signal_terminate(set_term_event); /* SIGTERM */
g_signal_child_stop(set_sigchld_event); /* SIGCHLD */
if (daemon)
{
/* writing pid file */
char pid_s[32];
int fd = g_file_open(pid_file);
if (-1 == fd)
{
LOG(LOG_LEVEL_ERROR,
"error opening pid file[%s]: %s",
pid_file, g_get_strerror());
log_end();
config_free(g_cfg);
g_deinit();
g_exit(1);
}
g_sprintf(pid_s, "%d", g_pid);
g_file_write(fd, pid_s, g_strlen(pid_s));
g_file_close(fd);
}
/* start program main loop */
LOG(LOG_LEVEL_INFO,
"starting xrdp-sesman with pid %d", g_pid);
/* make sure the socket directory exists */
g_mk_socket_path("xrdp-sesman");
/* make sure the /tmp/.X11-unix directory exists */
if (!g_directory_exist("/tmp/.X11-unix"))
{
if (!g_create_dir("/tmp/.X11-unix"))
{
LOG(LOG_LEVEL_ERROR,
"sesman.c: error creating dir /tmp/.X11-unix");
}
g_chmod_hex("/tmp/.X11-unix", 0x1777);
}
error = sesman_main_loop();
/* clean up PID file on exit */
if (daemon)
{
g_file_delete(pid_file);
}
g_delete_wait_obj(g_reload_event);
g_delete_wait_obj(g_sigchld_event);
g_delete_wait_obj(g_term_event);
if (!daemon)
{
log_end();
}
config_free(g_cfg);
g_deinit();
g_exit(error);
return 0;
}