From 633716bbad7e1b588235bf3342131c3a3d4b494f Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 3 Dec 2020 11:54:22 +0000 Subject: [PATCH] sesrun improvements and doc fixes --- common/log.c | 11 +- common/log.h | 9 +- docs/man/xrdp-sesrun.8.in | 84 +++-- sesman/config.c | 2 +- sesman/tools/sesadmin.c | 2 +- sesman/tools/sesrun.c | 656 ++++++++++++++++++++++++++++++++------ xrdp/xrdp_mm.c | 5 + 7 files changed, 637 insertions(+), 132 deletions(-) diff --git a/common/log.c b/common/log.c index aaa19d4c..a045adfc 100644 --- a/common/log.c +++ b/common/log.c @@ -572,7 +572,7 @@ internal_log_location_overrides_level(const char *function_name, */ struct log_config * -log_config_init_for_console(enum logLevels lvl) +log_config_init_for_console(enum logLevels lvl, const char *override_name) { struct log_config *config = internalInitAndAllocStruct(); @@ -580,7 +580,14 @@ log_config_init_for_console(enum logLevels lvl) { config->program_name = ""; config->enable_console = 1; - config->console_level = lvl; + if (override_name != NULL && override_name[0] != '\0') + { + config->console_level = internal_log_text2level(override_name); + } + else + { + config->console_level = lvl; + } config->dump_on_start = 0; /* Don't need dump for console only */ } return config; diff --git a/common/log.h b/common/log.h index d94c795e..8d237435 100644 --- a/common/log.h +++ b/common/log.h @@ -275,9 +275,16 @@ log_start_from_param(const struct log_config *src_log_config); * * The config can be customised by the caller before calling * log_start_from_param() + * + * @param Default log level + * @param Log level name, or NULL. This can be used to provide an + * override to the default log level, by environment variable or + * argument. + * + * @return pointer to struct log_config. */ struct log_config* -log_config_init_for_console(enum logLevels lvl); +log_config_init_for_console(enum logLevels lvl, const char *override_name); /** * Read configuration from a file and store the values in the returned diff --git a/docs/man/xrdp-sesrun.8.in b/docs/man/xrdp-sesrun.8.in index 4cf2e2b4..f76705a6 100644 --- a/docs/man/xrdp-sesrun.8.in +++ b/docs/man/xrdp-sesrun.8.in @@ -1,42 +1,80 @@ .TH "xrdp\-sesrun" "8" "@PACKAGE_VERSION@" "xrdp team" "" .SH "NAME" -xrdp\-sesrun \- \fBxrdp-sesman\fR(8) session launcher +\fBxrdp\-sesrun\fR \- \fBxrdp\-sesman\fR(8) session launcher .SH "SYNTAX" .B xrdp\-sesrun -.I [ -C /path/to/sesman.ini ] server username password width height bpp code +.I [ options ] username .SH "DESCRIPTION" \fBxrdp\-sesrun\fR starts a session using \fBxrdp\-sesman\fR(8). .br -This is a tool useful for testing, it simply behaves like xrdp when some user logs in a new session and authenticates, thus starting a new session. +This is a tool useful for testing, it simply behaves like xrdp when some +user logs in a new session and authenticates, thus starting a new session. + +Default values for the options are set at compile-time. Run the utility without +a username to see what the defaults are for your installation. + +The utility prompts for a password if neither \fB-p\fR or \fB-F\fR is used. .SH "OPTIONS" .TP -.I \-\-config -(Optional) Specify a path to a different \fIsesman.ini\fR file. +.B -g x +Set session geometry. +.br +Note that most configurations will resize the session on connection, so this +option may not do what you expect. +.TP +.B -b +Set session bits-per-pixel (colour depth). Some session types (i.e. Xorg) +will ignore this setting. +.TP +.B -s +Server on which sesman is running (probably 'localhost'). +.br +Use of this option is discouraged as it will be removed in the future. +.TP +.B -t +Session type - one of Xorg, Xvnc or X11rdp. Alternatively, for testing +only, use the numeric session code. +.TP +.B -D +Directory to run the new session in. Defaults to $HOME for the specified user. +.TP +.B -S +Specify an alternate shell to run, instead of the default window manager. +.TP +.B -p +Password for user. USE FOR TESTING ONLY - the password will be visible +in the output of the \fBps\fR command. +.TP +.B -F +Specify a file descriptor (normally 0) to read the password in from. This +is a secure way to pass the password in to the utility. +.TP +.B -c +Specify a different sesman.ini file. This file is used to find out how to +connect to \fBxrdp\-sesman\fR. +.SH "ENVIRONMENT" .TP -.I server -Server on which sesman is running +.I SESRUN_LOG_LEVEL +Override the default logging level. One of "error", "warn", "info", +"debug", "trace" or a number 1-5. + +.SH "EXAMPLES" .TP -.I username -user name of the session being started +.B +xrdp-sesrun -F 0 user1 sesman_ini = g_strdup(sesman_ini)) != NULL) { int fd; - if ((fd = g_file_open(cfg->sesman_ini)) != -1) + if ((fd = g_file_open_ex(cfg->sesman_ini, 1, 0, 0, 0)) != -1) { struct list *sec; struct list *param_n; diff --git a/sesman/tools/sesadmin.c b/sesman/tools/sesadmin.c index 8dea0ddf..bd5144a3 100644 --- a/sesman/tools/sesadmin.c +++ b/sesman/tools/sesadmin.c @@ -62,7 +62,7 @@ int main(int argc, char **argv) serv[0] = '\0'; port[0] = '\0'; - logging = log_config_init_for_console(LOG_LEVEL_INFO); + logging = log_config_init_for_console(LOG_LEVEL_INFO, NULL); log_start_from_param(logging); log_config_free(logging); diff --git a/sesman/tools/sesrun.c b/sesman/tools/sesrun.c index c0fd98b2..59f3e42a 100644 --- a/sesman/tools/sesrun.c +++ b/sesman/tools/sesrun.c @@ -28,139 +28,587 @@ #include #endif -#include "sesman.h" +#include +#include +#include + +#include "parse.h" +#include "os_calls.h" +#include "config.h" +#include "log.h" #include "tcp.h" #if !defined(PACKAGE_VERSION) #define PACKAGE_VERSION "???" #endif -struct config_sesman *g_cfg; /* config.h */ +#ifndef MAX_PASSWORD_LEN +# define MAX_PASSWORD_LEN 512 +#endif + +#ifndef DEFAULT_WIDTH +# define DEFAULT_WIDTH 1280 +#endif + +#ifndef DEFAULT_HEIGHT +# define DEFAULT_HEIGHT 1024 +#endif + +/* Default setting used by Windows 10 mstsc.exe */ +#ifndef DEFAULT_BPP +# define DEFAULT_BPP 32 +#endif + +#ifndef DEFAULT_SERVER +# define DEFAULT_SERVER "localhost" +#endif + +#ifndef DEFAULT_TYPE +# define DEFAULT_TYPE "Xorg" +#endif + +/** + * Maps session type strings to internal code numbers + */ +static struct +{ + const char *name; + int code; +} type_map[] = +{ + { "Xvnc", 0}, + { "X11rdp", 10}, + { "Xorg", 20}, + { NULL, -1} +}; + +/** + * Parameters needed for a session + */ +struct session_params +{ + int width; + int height; + int bpp; + int session_code; + const char *server; + + const char *domain; /* Currently unused by sesman */ + const char *directory; + const char *shell; + const char *client_ip; + + const char *username; + char password[MAX_PASSWORD_LEN + 1]; +}; + +/**************************************************************************//** + * Maps a string to a session code + * + * @param t session type + * @return session code, or -1 if not found + */ +static +int get_session_type_code(const char *t) +{ + unsigned int i; + for (i = 0 ; type_map[i].name != NULL; ++i) + { + if (g_strcasecmp(type_map[i].name, t) == 0) + { + return type_map[i].code; + } + } + + return -1; +} + +/**************************************************************************//** + * Returns a list of supported session types + * + * Caller supplies a buffer. Buffer handling and buffer overflow detection are + * the same as snprint() + * + * @param buff area for result + * @param bufflen Size of result + * @return number of characters for the output string + */ +static +unsigned int get_session_type_list(char *buff, unsigned int bufflen) +{ + unsigned int i; + unsigned int ret = 0; + const char *sep = ""; + + for (i = 0 ; type_map[i].name != NULL; ++i) + { + if (ret < bufflen) + { + ret += g_snprintf(buff + ret, bufflen - ret, + "%s%s", sep, type_map[i].name); + sep = ", "; + } + } + + return ret; +} + +/**************************************************************************//** + * Prints a brief summary of options and defaults + */ +static void +usage(void) +{ + char sesstype_list[64]; + + (void)get_session_type_list(sesstype_list, sizeof(sesstype_list)); + + g_printf("xrdp session starter v" PACKAGE_VERSION "\n"); + g_printf("\nusage:\n"); + g_printf("sesrun [options] username\n\n"); + g_printf("options:\n"); + g_printf(" -g Default:%dx%d\n", + DEFAULT_WIDTH, DEFAULT_HEIGHT); + g_printf(" -b Default:%d\n", DEFAULT_BPP); + /* Don't encourage use of this one - we need to move to local sockets */ + g_printf(" -s Default:%s (Deprecated)\n", + DEFAULT_SERVER); + g_printf(" -t Default:%s\n", DEFAULT_TYPE); + g_printf(" -D Default: $HOME\n" + " -S Default: Defined window manager\n" + " -p TESTING ONLY - DO NOT USE IN PRODUCTION\n" + " -F Read password from this file descriptor\n" + " -c Alternative sesman.ini file\n"); + g_printf("Supported types are %s or use int for internal code\n", + sesstype_list); + g_printf("Password is prompted if -p or -F are not specified\n"); +} + + +/**************************************************************************//** + * Parses a string x + * + * @param geom_str Input string + * @param sp Session parameter structure for resulting width and height + * @return !=0 for success + */ +static int +parse_geometry_string(const char *geom_str, struct session_params *sp) +{ + int result = 0; + unsigned int sep_count = 0; /* Count of 'x' separators */ + unsigned int other_count = 0; /* Count of non-digits and non separators */ + const char *sepp = NULL; /* Pointer to the 'x' */ + const char *p = geom_str; + + while (*p != '\0') + { + if (!isdigit(*p)) + { + if (*p == 'x' || *p == 'X') + { + ++sep_count; + sepp = p; + } + else + { + ++other_count; + } + } + ++p; + } + + if (sep_count != 1 || other_count > 0 || + sepp == geom_str || /* Separator at start of string */ + sepp == (p - 1) ) /* Separator at end of string */ + { + LOG(LOG_LEVEL_ERROR, "Invalid geometry string '%s'", geom_str); + } + else + { + sp->width = atoi(geom_str); + sp->height = atoi(sepp + 1); + result = 1; + } + + return result; +} + + +/**************************************************************************//** + * Read a password from a file descriptor + * + * @param fd_str string representing file descriptor + * @param sp Session parameter structure for resulting password + * @return !=0 for success + */ +static int +read_password_from_fd(const char *fd_str, struct session_params *sp) +{ + int result = 0; + int s = g_file_read(atoi(fd_str), sp->password, sizeof (sp->password) - 1); + if (s < 0) + { + LOG(LOG_LEVEL_ERROR, "Can't read password from fd %s - %s", + fd_str, g_get_strerror()); + sp->password[0] = '\0'; + } + else + { + sp->password[s] = '\0'; + if (s > 0 && sp->password[s - 1] == '\n') + { + sp->password[s - 1] = '\0'; + } + result = 1; + } + return result; +} + +/**************************************************************************//** + * Parses the program args + * + * @param argc Passed to main + * @param @argv Passed to main + * @param sp Session parameter structure for resulting values + * @param sesman_ini Pointer to an alternative config file if one is specified + * @return !=0 for success + */ +static int +parse_program_args(int argc, char *argv[], struct session_params *sp, + const char **sesman_ini) +{ + int params_ok = 1; + int opt; + bool_t password_set = 0; + + sp->width = DEFAULT_WIDTH; + sp->height = DEFAULT_HEIGHT; + sp->bpp = DEFAULT_BPP; + sp->session_code = get_session_type_code(DEFAULT_TYPE); + sp->server = DEFAULT_SERVER; + + sp->domain = ""; + sp->directory = ""; + sp->shell = ""; + sp->client_ip = ""; + + sp->username = NULL; + sp->password[0] = '\0'; + + while ((opt = getopt(argc, argv, "g:b:s:t:D:S:p:F:c:")) != -1) + { + switch (opt) + { + case 'g': + if (!parse_geometry_string(optarg, sp)) + { + params_ok = 0; + } + break; + + case 'b': + sp->bpp = atoi(optarg); + break; + + case 's': + LOG(LOG_LEVEL_WARNING, "Using deprecated option '-s'"); + sp->server = optarg; + break; + + case 't': + if (isdigit(optarg[0])) + { + sp->session_code = atoi(optarg); + } + else + { + sp->session_code = get_session_type_code(optarg); + if (sp->session_code < 0) + { + LOG(LOG_LEVEL_ERROR, "Unrecognised session type '%s'", + optarg); + params_ok = 0; + } + } + break; + + case 'D': + sp->directory = optarg; + break; + + case 'S': + sp->shell = optarg; + break; + + case 'p': + if (password_set) + { + LOG(LOG_LEVEL_WARNING, + "Ignoring option '%c' - password already set ", + (char)opt); + } + else + { + g_strncpy(sp->password, optarg, sizeof(sp->password) - 1); + password_set = 1; + } + break; + + case 'F': + if (password_set) + { + LOG(LOG_LEVEL_WARNING, + "Ignoring option '%c' - password already set ", + (char)opt); + } + else + { + if (read_password_from_fd(optarg, sp)) + { + password_set = 1; + } + else + { + params_ok = 0; + } + } + break; + + case 'c': + *sesman_ini = optarg; + break; + + default: + LOG(LOG_LEVEL_ERROR, "Unrecognised switch '%c'", (char)opt); + params_ok = 0; + } + } + + if (argc <= optind) + { + LOG(LOG_LEVEL_ERROR, "No user name speciified"); + params_ok = 0; + } + else if ((argc - optind) > 1) + { + LOG(LOG_LEVEL_ERROR, "Unexpected arguments after username"); + params_ok = 0; + } + else + { + sp->username = argv[optind]; + } + + if (params_ok && !password_set) + { + const char *p = getpass("Password: "); + if (p != NULL) + { + g_strcpy(sp->password, p); + } + } + + return params_ok; +} + +/**************************************************************************//** + * Helper function for send_scpv0_auth_request() + * + * @param s Output string + * @param str String to write to s + */ +static void +out_string16(struct stream *s, const char *str) +{ + int i = g_strlen(str); + out_uint16_be(s, i); + out_uint8a(s, str, i); +} + +/**************************************************************************//** + * Sends an SCP V0 authorization request + * + * @param sck file descriptor to send request on + * @param sp Data for request + * + * @todo This code duplicates functionality in the XRDP function + * xrdp_mm_send_login(). When SCP is reworked, a common library + * function should be used + */ +static void +send_scpv0_auth_request(int sck, const struct session_params *sp) +{ + struct stream *out_s; + + LOG(LOG_LEVEL_DEBUG, + "width:%d height:%d bpp:%d code:%d\n" + "server:\"%s\" domain:\"%s\" directory:\"%s\"\n" + "shell:\"%s\" client_ip:\"%s\"", + sp->width, sp->height, sp->bpp, sp->session_code, + sp->server, sp->domain, sp->directory, + sp->shell, sp->client_ip); + /* Only log the password in development builds */ + LOG_DEVEL(LOG_LEVEL_DEBUG, "password:\"%s\"", sp->password); + + make_stream(out_s); + init_stream(out_s, 8192); + + s_push_layer(out_s, channel_hdr, 8); + out_uint16_be(out_s, sp->session_code); + out_string16(out_s, sp->username); + out_string16(out_s, sp->password); + out_uint16_be(out_s, sp->width); + out_uint16_be(out_s, sp->height); + out_uint16_be(out_s, sp->bpp); + out_string16(out_s, sp->domain); + out_string16(out_s, sp->shell); + out_string16(out_s, sp->directory); + out_string16(out_s, sp->client_ip); + s_mark_end(out_s); + + s_pop_layer(out_s, channel_hdr); + out_uint32_be(out_s, 0); /* version */ + out_uint32_be(out_s, out_s->end - out_s->data); /* size */ + tcp_force_send(sck, out_s->data, out_s->end - out_s->data); + + free_stream(out_s); +} + +/**************************************************************************//** + * Receives an SCP V0 authorization reply + * + * @param sck file descriptor to receive reply on + * + * @todo This code duplicates functionality in the XRDP function + * xrdp_mm_process_login_response(). When SCP is reworked, a + * common library function should be used + */ +static int +handle_scpv0_auth_reply(int sck) +{ + int result = 1; + int packet_ok = 0; + + struct stream *in_s; + + make_stream(in_s); + init_stream(in_s, 8192); + + if (tcp_force_recv(sck, in_s->data, 8) == 0) + { + int version; + int size; + int code; + int data; + int display; + + in_uint32_be(in_s, version); + in_uint32_be(in_s, size); + if (version == 0 && size >= 14) + { + init_stream(in_s, 8192); + if (tcp_force_recv(sck, in_s->data, size - 8) == 0) + { + in_s->end = in_s->data + (size - 8); + + in_uint16_be(in_s, code); + in_uint16_be(in_s, data); + in_uint16_be(in_s, display); + + if (code == 3) + { + packet_ok = 1; + + if (data == 0) + { + g_printf("Connection denied (authentication error)\n"); + } + else + { + char guid[16]; + char guid_str[64]; + if (s_check_rem(in_s, 16) != 0) + { + in_uint8a(in_s, guid, 16); + g_bytes_to_hexstr(guid, 16, guid_str, 64); + } + else + { + g_strcpy(guid_str, ""); + } + + g_printf("ok data=%d display=:%d GUID=%s\n", + (int)data, display, guid_str); + result = 0; + } + } + } + } + } + + if (!packet_ok) + { + LOG(LOG_LEVEL_ERROR, "Corrupt reply packet"); + } + free_stream(in_s); + + return result; +} /******************************************************************************/ int main(int argc, char **argv) { + const char *sesman_ini = XRDP_CFG_PATH "/sesman.ini"; + struct config_sesman *cfg = NULL; + int sck = -1; - int code; - int i; - int size; - int version; - int width; - int height; - int bpp; - int display; - int session_code; - struct stream *in_s; - struct stream *out_s; - char *username; - char *password; - long data; - const char *sesman_ini; - char default_sesman_ini[256]; + struct session_params sp; + + struct log_config *logging; int status = 1; - /* User specified a different config file? */ - if (argc > 2 && (g_strcmp(argv[1], "-C") == 0)) + logging = log_config_init_for_console(LOG_LEVEL_WARNING, + g_getenv("SESRUN_LOG_LEVEL")); + log_start_from_param(logging); + log_config_free(logging); + + if (!parse_program_args(argc, argv, &sp, &sesman_ini)) { - sesman_ini = argv[2]; - argv += 2; - argc -= 2; + usage(); + } + else if ((cfg = config_read(sesman_ini)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "error reading config file %s : %s", + sesman_ini, g_get_strerror()); } else { - g_snprintf(default_sesman_ini, 255, "%s/sesman.ini", XRDP_CFG_PATH); - sesman_ini = default_sesman_ini; - } - - if (argc != 8) - { - g_printf("xrdp session starter v" PACKAGE_VERSION "\n"); - g_printf("\nusage:\n"); - g_printf("xrdp-sesrun [-C /path/to/sesman.ini] " - " " - " \n"); - g_printf("session code 0 for Xvnc, 10 for X11RDP, 20 for Xorg\n"); - } - else if ((g_cfg = config_read(sesman_ini)) == NULL) - { - g_printf("xrdp-sesrun: error reading config %s. quitting.\n", - sesman_ini); - } - else - { - username = argv[2]; - password = argv[3]; - width = g_atoi(argv[4]); - height = g_atoi(argv[5]); - bpp = g_atoi(argv[6]); - session_code = g_atoi(argv[7]); - make_stream(in_s); - init_stream(in_s, 8192); - make_stream(out_s); - init_stream(out_s, 8192); - sck = g_tcp_socket(); if (sck < 0) { - g_printf("socket error\n"); + LOG(LOG_LEVEL_ERROR, "socket error - %s", g_get_strerror()); } - else if (g_tcp_connect(sck, argv[1], g_cfg->listen_port) != 0) + else if (g_tcp_connect(sck, sp.server, cfg->listen_port) != 0) { - g_printf("connect error\n"); + LOG(LOG_LEVEL_ERROR, "connect error - %s", g_get_strerror()); } else { - s_push_layer(out_s, channel_hdr, 8); - out_uint16_be(out_s, session_code); /* code */ - i = g_strlen(username); - out_uint16_be(out_s, i); - out_uint8a(out_s, username, i); - i = g_strlen(password); - out_uint16_be(out_s, i); - out_uint8a(out_s, password, i); - out_uint16_be(out_s, width); - out_uint16_be(out_s, height); - out_uint16_be(out_s, bpp); - s_mark_end(out_s); - s_pop_layer(out_s, channel_hdr); - out_uint32_be(out_s, 0); /* version */ - out_uint32_be(out_s, out_s->end - out_s->data); /* size */ - tcp_force_send(sck, out_s->data, out_s->end - out_s->data); - - if (tcp_force_recv(sck, in_s->data, 8) == 0) - { - in_uint32_be(in_s, version); - in_uint32_be(in_s, size); - init_stream(in_s, 8192); - - if (tcp_force_recv(sck, in_s->data, size - 8) == 0) - { - if (version == 0) - { - in_uint16_be(in_s, code); - - if (code == 3) - { - in_uint16_be(in_s, data); - in_uint16_be(in_s, display); - g_printf("ok %d display %d\n", (int)data, display); - status = 0; - } - } - } - } + send_scpv0_auth_request(sck, &sp); + status = handle_scpv0_auth_reply(sck); } - - if (sck >= 0) - { - g_tcp_close(sck); - } - free_stream(in_s); - free_stream(out_s); } - config_free(g_cfg); + if (sck >= 0) + { + g_tcp_close(sck); + } + + g_memset(sp.password, '\0', sizeof(sp.password)); + config_free(cfg); + log_end(); return status; } diff --git a/xrdp/xrdp_mm.c b/xrdp/xrdp_mm.c index 25c4f58e..0e225b33 100644 --- a/xrdp/xrdp_mm.c +++ b/xrdp/xrdp_mm.c @@ -161,6 +161,9 @@ xrdp_mm_delete(struct xrdp_mm *self) /*****************************************************************************/ /* Send login information to sesman */ +/* FIXME : This code duplicates functionality in the sesman tools sesrun.c. + * When SCP is reworked, a common library function should be used */ + static int xrdp_mm_send_login(struct xrdp_mm *self) { @@ -1553,6 +1556,8 @@ xrdp_mm_update_allowed_channels(struct xrdp_mm *self) } /*****************************************************************************/ +/* FIXME : This code duplicates functionality in the sesman tools sesrun.c. + * When SCP is reworked, a common library function should be used */ static int xrdp_mm_process_login_response(struct xrdp_mm *self, struct stream *s) {