From ff24984cf3e64cf13e30fe37bd93c6d2b84acb56 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:39:00 +0100 Subject: [PATCH 01/22] os_calls: Add g_file_is_open() --- common/os_calls.c | 7 +++++++ common/os_calls.h | 6 ++++++ tests/common/test_os_calls.c | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/common/os_calls.c b/common/os_calls.c index c10d6215..ac386a18 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -2160,6 +2160,13 @@ g_file_close(int fd) return 0; } +/*****************************************************************************/ +int +g_file_is_open(int fd) +{ + return (fcntl(fd, F_GETFD) >= 0); +} + /*****************************************************************************/ /* read from file, returns the number of bytes read or -1 on error */ int diff --git a/common/os_calls.h b/common/os_calls.h index c7fdcf3c..c00cafda 100644 --- a/common/os_calls.h +++ b/common/os_calls.h @@ -210,6 +210,12 @@ int g_file_open(const char *file_name); int g_file_open_ex(const char *file_name, int aread, int awrite, int acreate, int atrunc); int g_file_close(int fd); +/** + * Returns 1 if a file is open (i.e. the file descriptor is valid) + * @param fd File descriptor + * @return 1 for file open, 0 for not open + */ +int g_file_is_open(int fd); int g_file_read(int fd, char *ptr, int len); int g_file_write(int fd, const char *ptr, int len); int g_file_seek(int fd, int offset); diff --git a/tests/common/test_os_calls.c b/tests/common/test_os_calls.c index 9343e85d..fb49dbe9 100644 --- a/tests/common/test_os_calls.c +++ b/tests/common/test_os_calls.c @@ -262,6 +262,23 @@ START_TEST(test_g_file_get_open_fds) } END_TEST + +/******************************************************************************/ +START_TEST(test_g_file_is_open) +{ + int devzerofd = g_file_open("/dev/zero"); + ck_assert(devzerofd >= 0); + + // Check open file comes up as open + ck_assert_int_ne(g_file_is_open(devzerofd), 0); + + g_file_close(devzerofd); + + // Check the now-closed file no longer registers as open + ck_assert_int_eq(g_file_is_open(devzerofd), 0); +} +END_TEST + /******************************************************************************/ START_TEST(test_g_sck_fd_passing) { @@ -448,6 +465,7 @@ make_suite_test_os_calls(void) #endif tcase_add_test(tc_os_calls, test_g_file_cloexec); tcase_add_test(tc_os_calls, test_g_file_get_open_fds); + tcase_add_test(tc_os_calls, test_g_file_is_open); tcase_add_test(tc_os_calls, test_g_sck_fd_passing); tcase_add_test(tc_os_calls, test_g_sck_fd_overflow); return s; From 65ff618479ceeee5b5baddaaa89abf7258fc2362 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:09:31 +0100 Subject: [PATCH 02/22] os_calls: Add g_executable_exist() --- common/os_calls.c | 8 ++++++++ common/os_calls.h | 1 + 2 files changed, 9 insertions(+) diff --git a/common/os_calls.c b/common/os_calls.c index ac386a18..4c9d7327 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -2556,6 +2556,14 @@ g_directory_exist(const char *dirname) #endif } +/*****************************************************************************/ +/* returns boolean, non zero if the file exists and is a readable executable */ +int +g_executable_exist(const char *exename) +{ + return access(exename, R_OK | X_OK) == 0; +} + /*****************************************************************************/ /* returns boolean */ int diff --git a/common/os_calls.h b/common/os_calls.h index c00cafda..6c10f045 100644 --- a/common/os_calls.h +++ b/common/os_calls.h @@ -242,6 +242,7 @@ int g_set_current_dir(const char *dirname); int g_file_exist(const char *filename); int g_file_readable(const char *filename); int g_directory_exist(const char *dirname); +int g_executable_exist(const char *dirname); int g_create_dir(const char *dirname); int g_create_path(const char *path); int g_remove_dir(const char *dirname); From cf5e1961d3fc6091ce8fc3e5ba9a308649f9992f Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 17 Apr 2023 15:19:38 +0100 Subject: [PATCH 03/22] os_calls: Add g_setpgid() --- common/os_calls.c | 20 +++++++++++++++++++- common/os_calls.h | 9 +++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/common/os_calls.c b/common/os_calls.c index 4c9d7327..dbbfdbc2 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -3158,7 +3158,7 @@ g_waitchild(struct exit_status *e) e->reason = E_XR_UNEXPECTED; e->val = 0; - rv = waitpid(0, &wstat, WNOHANG); + rv = waitpid(-1, &wstat, WNOHANG); if (rv == -1) { @@ -3253,6 +3253,24 @@ g_waitpid_status(int pid) return exit_status; } +/*****************************************************************************/ +int +g_setpgid(int pid, int pgid) +{ + int rv = setpgid(pid, pgid); + if (rv < 0) + { + if (pid == 0) + { + pid = getpid(); + } + LOG(LOG_LEVEL_ERROR, "Can't set process group ID of %d to %d [%s]", + pid, pgid, g_get_strerror()); + } + + return rv; +} + /*****************************************************************************/ /* does not work in win32 */ void diff --git a/common/os_calls.h b/common/os_calls.h index 6c10f045..33b8eeef 100644 --- a/common/os_calls.h +++ b/common/os_calls.h @@ -297,6 +297,15 @@ int g_set_allusercontext(int uid); int g_waitchild(struct exit_status *e); int g_waitpid(int pid); struct exit_status g_waitpid_status(int pid); +/* + * Sets the process group ID of the indicated process to the specified value. + * (POSIX.1) + * + * Errors are logged. + * + * May do nothing if process groups are not supported + */ +int g_setpgid(int pid, int pgid); void g_clearenv(void); int g_setenv(const char *name, const char *value, int rewrite); char *g_getenv(const char *name); From e96d77bac1cc73fdec468528b73f01413022cc44 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 3 Apr 2023 10:13:51 +0100 Subject: [PATCH 04/22] Remove g_mk_socket_path() from codepaths The socket dir is only used if we are starting a session with sesman. Consequently, it only makes sense to create this directory within sesman itself. --- common/os_calls.c | 11 ++--------- common/os_calls.h | 2 +- sesman/env.c | 2 -- sesman/sesman.c | 2 +- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/common/os_calls.c b/common/os_calls.c index dbbfdbc2..1ce05924 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -133,7 +133,7 @@ g_rm_temp_dir(void) /*****************************************************************************/ int -g_mk_socket_path(const char *app_name) +g_mk_socket_path(void) { if (!g_directory_exist(XRDP_SOCKET_PATH)) { @@ -176,8 +176,6 @@ g_init(const char *app_name) /* use en_US.UTF-8 instead if not available */ setlocale(LC_CTYPE, "en_US.UTF-8"); } - - g_mk_socket_path(app_name); } /*****************************************************************************/ @@ -2881,7 +2879,6 @@ g_execlp3(const char *a1, const char *a2, const char *a3) "returned errno: %d, description: %s", a1, args_str, g_get_errno(), g_get_strerror()); - g_mk_socket_path(0); return rv; #endif } @@ -2991,11 +2988,7 @@ g_fork(void) rv = fork(); - if (rv == 0) /* child */ - { - g_mk_socket_path(0); - } - else if (rv == -1) /* error */ + if (rv == -1) /* error */ { LOG(LOG_LEVEL_ERROR, "Process fork failed with errno: %d, description: %s", diff --git a/common/os_calls.h b/common/os_calls.h index 33b8eeef..dcf4eb33 100644 --- a/common/os_calls.h +++ b/common/os_calls.h @@ -53,7 +53,7 @@ struct list; #define g_close_wait_obj g_delete_wait_obj int g_rm_temp_dir(void); -int g_mk_socket_path(const char *app_name); +int g_mk_socket_path(void); void g_init(const char *app_name); void g_deinit(void); void g_printf(const char *format, ...) printflike(1, 2); diff --git a/sesman/env.c b/sesman/env.c index 31ae1292..5e019299 100644 --- a/sesman/env.c +++ b/sesman/env.c @@ -138,8 +138,6 @@ env_set_user(int uid, char **passwd_file, int display, g_setenv("PATH", "/sbin:/bin:/usr/bin:/usr/local/bin", 1); } #endif - g_mk_socket_path(0); - if (error == 0) { g_setenv("SHELL", pw_shell, 1); diff --git a/sesman/sesman.c b/sesman/sesman.c index 9ee20802..c4809a92 100644 --- a/sesman/sesman.c +++ b/sesman/sesman.c @@ -953,7 +953,7 @@ main(int argc, char **argv) "starting xrdp-sesman with pid %d", g_pid); /* make sure the socket directory exists */ - g_mk_socket_path("xrdp-sesman"); + g_mk_socket_path(); /* make sure the /tmp/.X11-unix directory exists */ if (!g_directory_exist("/tmp/.X11-unix")) From f79f8bfa70c052a15bfee12e527cc0ada3c6d16c Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 19 Jan 2023 14:21:39 +0000 Subject: [PATCH 05/22] SCP: Add scp_init_trans_from_fd() --- libipm/scp.c | 31 +++++++++++++++++++++++++++++++ libipm/scp.h | 18 +++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/libipm/scp.c b/libipm/scp.c index 149eccf9..de0028d7 100644 --- a/libipm/scp.c +++ b/libipm/scp.c @@ -195,7 +195,38 @@ scp_init_trans(struct trans *trans) } /*****************************************************************************/ +struct trans * +scp_init_trans_from_fd(int fd, int trans_type, int (*term_func)(void)) +{ + struct trans *result; + if ((result = trans_create(TRANS_MODE_UNIX, 128, 128)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't create SCP transport [%s]", + g_get_strerror()); + } + else + { + result->sck = fd; + result->type1 = trans_type; + result->status = TRANS_STATUS_UP; + result->is_term = term_func; + // Make sure child processes don't inherit our FD + (void)g_file_set_cloexec(result->sck, 1); + + if (scp_init_trans(result) != 0) + { + LOG(LOG_LEVEL_ERROR, "scp_init_trans() call failed"); + trans_delete(result); + result = NULL; + } + } + + return result; +} + + +/*****************************************************************************/ int scp_msg_in_check_available(struct trans *trans, int *available) { diff --git a/libipm/scp.h b/libipm/scp.h index edb51536..eec86c4f 100644 --- a/libipm/scp.h +++ b/libipm/scp.h @@ -125,7 +125,7 @@ scp_connect(const char *port, * Converts a standard trans connected to an SCP endpoint to an SCP transport * * If you are running on a client, you may wish to use - * scp_send_set_peername_request() after the commnect to inform the + * scp_send_set_peername_request() after the connect to inform the * server who you are. * * @param trans connected endpoint @@ -134,6 +134,22 @@ scp_connect(const char *port, int scp_init_trans(struct trans *trans); +/** + * Creates an SCP transport from a file descriptor + * + * If you are running on a client, you may wish to use + * scp_send_set_peername_request() after the connect to inform the + * server who you are. + * + * @param fd file descriptor + * @param trans_type TRANS_TYPE_SERVER or TRANS_TYPE_CLIENT + * @param term_func Function to poll during connection for program + * termination, or NULL for none. + * @return SCP transport, or NULL + */ +struct trans * +scp_init_trans_from_fd(int fd, int trans_type, int (*term_func)(void)); + /** * Checks an SCP transport to see if a complete message is * available for parsing From c3f02f510720e69177dfd5104584c59cb5e005c2 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Tue, 17 Jan 2023 12:01:03 +0000 Subject: [PATCH 06/22] libipm: Add EICP --- libipm/Makefile.am | 2 + libipm/eicp.c | 324 +++++++++++++++++++++++++++++++++++++ libipm/eicp.h | 302 ++++++++++++++++++++++++++++++++++ libipm/libipm_facilities.h | 2 + 4 files changed, 630 insertions(+) create mode 100644 libipm/eicp.c create mode 100644 libipm/eicp.h diff --git a/libipm/Makefile.am b/libipm/Makefile.am index d4d30987..f0bd2a1a 100644 --- a/libipm/Makefile.am +++ b/libipm/Makefile.am @@ -13,6 +13,8 @@ libipm_la_SOURCES = \ libipm_recv.c \ libipm_facilities.h \ libipm_private.h \ + eicp.h \ + eicp.c \ scp.h \ scp.c \ scp_application_types.h \ diff --git a/libipm/eicp.c b/libipm/eicp.c new file mode 100644 index 00000000..0e4719a8 --- /dev/null +++ b/libipm/eicp.c @@ -0,0 +1,324 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2022, all xrdp contributors + * + * 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 libipm/eicp.c + * @brief EICP definitions + * @author Matt Burt + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include "eicp.h" +#include "libipm.h" +#include "guid.h" +#include "os_calls.h" +#include "trans.h" + +/*****************************************************************************/ +static const char * +msgno_to_str(unsigned short n) +{ + return + (n == E_EICP_SYS_LOGIN_REQUEST) ? "EICP_SYS_LOGIN_REQUEST" : + (n == E_EICP_SYS_LOGIN_RESPONSE) ? "EICP_SYS_LOGIN_RESPONSE" : + + (n == E_EICP_LOGOUT_REQUEST) ? "EICP_LOGOUT_REQUEST" : + + (n == E_EICP_CREATE_SESSION_REQUEST) ? "EICP_CREATE_SESSION_REQUEST" : + + NULL; +} + +/*****************************************************************************/ +const char * +eicp_msgno_to_str(enum eicp_msg_code n, char *buff, unsigned int buff_size) +{ + const char *str = msgno_to_str((unsigned short)n); + + if (str == NULL) + { + g_snprintf(buff, buff_size, "[code #%d]", (int)n); + } + else + { + g_snprintf(buff, buff_size, "%s", str); + } + + return buff; +} + +/*****************************************************************************/ +int +eicp_init_trans(struct trans *trans) +{ + return libipm_init_trans(trans, LIBIPM_FAC_EICP, msgno_to_str); +} + +/*****************************************************************************/ +struct trans * +eicp_init_trans_from_fd(int fd, int trans_type, int (*term_func)(void)) +{ + struct trans *result; + if ((result = trans_create(TRANS_MODE_UNIX, 128, 128)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't create ECP transport [%s]", + g_get_strerror()); + } + else + { + result->sck = fd; + result->type1 = trans_type; + result->status = TRANS_STATUS_UP; + result->is_term = term_func; + + // Make sure child processes don't inherit our FD + (void)g_file_set_cloexec(result->sck, 1); + + if (eicp_init_trans(result) != 0) + { + LOG(LOG_LEVEL_ERROR, "eicp_init_trans() call failed"); + trans_delete(result); + result = NULL; + } + } + + return result; +} + + +/*****************************************************************************/ + +int +eicp_msg_in_check_available(struct trans *trans, int *available) +{ + return libipm_msg_in_check_available(trans, available); +} + +/*****************************************************************************/ + +int +eicp_msg_in_wait_available(struct trans *trans) +{ + return libipm_msg_in_wait_available(trans); +} + +/*****************************************************************************/ + +enum eicp_msg_code +eicp_msg_in_get_msgno(const struct trans *trans) +{ + return (enum eicp_msg_code)libipm_msg_in_get_msgno(trans); +} + +/*****************************************************************************/ + +void +eicp_msg_in_reset(struct trans *trans) +{ + libipm_msg_in_reset(trans); +} + +/*****************************************************************************/ +int +eicp_send_sys_login_request(struct trans *trans, + const char *username, + const char *password, + const char *ip_addr, + int scp_fd) +{ + int rv; + + rv = libipm_msg_out_simple_send( + trans, + (int)E_EICP_SYS_LOGIN_REQUEST, + "sssh", + username, + password, + ip_addr, + scp_fd); + + /* Wipe the output buffer to remove the password */ + libipm_msg_out_erase(trans); + + return rv; +} + +/*****************************************************************************/ + +int +eicp_get_sys_login_request(struct trans *trans, + const char **username, + const char **password, + const char **ip_addr, + int *scp_fd) +{ + /* Make sure the buffer is cleared after processing this message */ + libipm_set_flags(trans, LIBIPM_E_MSG_IN_ERASE_AFTER_USE); + + return libipm_msg_in_parse( trans, "sssh", + username, password, ip_addr, scp_fd); +} + +/*****************************************************************************/ + +int +eicp_send_sys_login_response(struct trans *trans, + int is_logged_in, + uid_t uid, + int scp_fd) +{ + int rv; + + if (is_logged_in) + { + rv = libipm_msg_out_simple_send( + trans, + (int)E_EICP_SYS_LOGIN_RESPONSE, + "bih", + 1, + uid, + scp_fd); + } + else + { + rv = libipm_msg_out_simple_send( + trans, (int)E_EICP_SYS_LOGIN_RESPONSE, "b", 0); + } + + return rv; +} + +/*****************************************************************************/ + +int +eicp_get_sys_login_response(struct trans *trans, + int *is_logged_in, + uid_t *uid, + int *scp_fd) +{ + int rv; + + if ((rv = libipm_msg_in_parse(trans, "b", is_logged_in)) == 0) + { + if (*is_logged_in) + { + int32_t i_uid; + + rv = libipm_msg_in_parse( + trans, + "ih", + &i_uid, + scp_fd); + + if (rv == 0) + { + *uid = (uid_t)i_uid; + } + } + else + { + *uid = (uid_t) -1; + *scp_fd = -1; + } + } + + return rv; +} + +/*****************************************************************************/ +int +eicp_send_logout_request(struct trans *trans) +{ + return libipm_msg_out_simple_send(trans, (int)E_EICP_LOGOUT_REQUEST, NULL); +} + +/*****************************************************************************/ + +int +eicp_send_create_session_request(struct trans *trans, + int scp_fd, + unsigned int display, + enum scp_session_type type, + unsigned short width, + unsigned short height, + unsigned char bpp, + const char *shell, + const char *directory) +{ + return libipm_msg_out_simple_send( + trans, + (int)E_EICP_CREATE_SESSION_REQUEST, + "huyqqyss", + scp_fd, + display, + type, + width, + height, + bpp, + shell, + directory); +} + +/*****************************************************************************/ + +int +eicp_get_create_session_request(struct trans *trans, + int *scp_fd, + unsigned int *display, + enum scp_session_type *type, + unsigned short *width, + unsigned short *height, + unsigned char *bpp, + const char **shell, + const char **directory) +{ + /* Intermediate values */ + uint32_t i_display; + uint8_t i_type; + uint16_t i_width; + uint16_t i_height; + uint8_t i_bpp; + + int rv = libipm_msg_in_parse( + trans, + "huyqqyss", + scp_fd, + &i_display, + &i_type, + &i_width, + &i_height, + &i_bpp, + shell, + directory); + + if (rv == 0) + { + *display = i_display; + *type = (enum scp_session_type)i_type; + *width = i_width; + *height = i_height; + /* bpp is fixed for Xorg session types */ + *bpp = (*type == SCP_SESSION_TYPE_XORG) ? 24 : i_bpp; + } + + return rv; +} diff --git a/libipm/eicp.h b/libipm/eicp.h new file mode 100644 index 00000000..fa581d69 --- /dev/null +++ b/libipm/eicp.h @@ -0,0 +1,302 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2022, all xrdp contributors + * + * 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 libipm/eicp.h + * @brief EICP declarations + * @author Matt Burt + * + * Functions in this file use the following naming conventions:- + * + * E_EICP_{msg}_REQUEST is sent by eicp_send_{msg}_request() + * E_EICP_{msg}_REQUEST is parsed by eicp_get_{msg}_request() + * E_EICP_{msg}_RESPONSE is sent by eicp_send_{msg}_response() + * E_EICP_{msg}_RESPONSE is parsed by eicp_get_{msg}_response() + */ + +#ifndef EICP_H +#define EICP_H + +#include "arch.h" +#include "scp_application_types.h" + +struct trans; +struct guid; + +/* Message codes */ +enum eicp_msg_code +{ + E_EICP_SYS_LOGIN_REQUEST, + E_EICP_SYS_LOGIN_RESPONSE, + + E_EICP_LOGOUT_REQUEST, + // No E_EICP_LOGOUT_RESPONSE + + E_EICP_CREATE_SESSION_REQUEST + // No E_EICP_CREATE_SESSION_RESPONSE +}; + +/* Common facilities */ + +/** + * Convert a message code to a string for output + * @param n Message code + * @param buff to contain string + * @param buff_size length of buff + * @return buff is returned for convenience. + */ +const char * +eicp_msgno_to_str(enum eicp_msg_code n, char *buff, unsigned int buff_size); + +/* Connection management facilities */ + +/** + * Converts a standard trans connected to an EICP endpoint to an EICP transport + * + * @param trans connected endpoint + * @return != 0 for error + */ +int +eicp_init_trans(struct trans *trans); + +/** + * Creates an EICP transport from a file descriptor + * + * @param fd file descriptor + * @param trans_type TRANS_TYPE_SERVER or TRANS_TYPE_CLIENT + * @param term_func Function to poll during connection for program + * termination, or NULL for none. + * @return SCP transport, or NULL + */ +struct trans * +eicp_init_trans_from_fd(int fd, int trans_type, int (*term_func)(void)); + + +/** + * Checks an EICP transport to see if a complete message is + * available for parsing + * + * @param trans EICP transport + * @param[out] available != 0 if a complete message is available + * @return != 0 for error + */ +int +eicp_msg_in_check_available(struct trans *trans, int *available); + +/** + * Waits on a single transport for an EICP message to be available for + * parsing + * + * @param trans libipm transport + * @return != 0 for error + * + * While the call is active, data-in callbacks for the transport are + * disabled. + * + * Only use this call if you have nothing to do until a message + * arrives on the transport. If you have other transports to service, use + * eicp_msg_in_check_available() + */ +int +eicp_msg_in_wait_available(struct trans *trans); + + +/** + * Gets the EICP message number of an incoming message + * + * @param trans EICP transport + * @return message in the buffer + * + * The results of calling this routine before eicp_msg_in_check_available() + * states a message is available are undefined. + */ +enum eicp_msg_code +eicp_msg_in_get_msgno(const struct trans *trans); + +/** + * Resets an EICP message buffer ready to receive the next message + * + * @param trans libipm transport + */ +void +eicp_msg_in_reset(struct trans *trans); + +/* -------------------- Connect messages-------------------- */ + +/** + * Send an E_EICP_SYS_LOGIN_REQUEST + * + * @param trans EICP transport + * @param username Username + * @param password Password + * @param ip_addr IP address for the client (or "" if not known) + * @param scp_fd SCP file descriptor from sesman client + * @return != 0 for error + * + * sesexec replies (eventually) with E_EICP_SYS_LOGIN_RESPONSE + * + * Once this message has been sent, sesman can close its own SCP transport + * down, as sesexec is responsible for client communication. When sesexec + * responds, sesman can recreate the SCP transport if necessary. + * + * While E_EICP_SYS_LOGIN_REQUEST is being processed, sesman must assume + * sesexec will be unresponsive to other EICP messages (although a + * SIGTERM should be effective). + */ +int +eicp_send_sys_login_request(struct trans *trans, + const char *username, + const char *password, + const char *ip_addr, + int scp_fd); + +/** + * Parse an incoming E_EICP_SYS_LOGIN_REQUEST message (sesexec) + * + * @param trans EICP transport + * @param[out] username Username + * @param[out] password Password + * @param[out] ip_addr IP address for the client (or "" if not known) + * @param [out] scp_fd SCP file descriptor from sesman client + * @return != 0 for error + */ +int +eicp_get_sys_login_request(struct trans *trans, + const char **username, + const char **password, + const char **ip_addr, + int *scp_fd); + +/** + * Send an E_EICP_SYS_LOGIN_RESPONSE (sesexec) + * + * @param trans EICP transport + * @param is_logged_in true if the SCP client is logged in + * @param uid UID of connected user + * @param scp_fd File descriptor of sesman client + * @return != 0 for error + * + * The uid and scp_fd are ignored unless is_logged_in is true. + * + * If is_logged_in is false, it is assumed that sesexec has properly + * closed the connection to the SCP client. + */ +int +eicp_send_sys_login_response(struct trans *trans, + int is_logged_in, + uid_t uid, + int scp_fd); + +/** + * Parses an incoming E_EICP_SYS_LOGIN_RESPONSE (sesexec) + * + * @param trans EICP transport + * @param[out] is_logged_in true if the SCP client is logged in + * @param[out] uid UID of connected user + * @param[out] scp_fd File descriptor of sesman client + * @return != 0 for error + * + * The uid and client_fd are returned as (uid_t)-1 and -1 respectively + * unless is_logged_in is true + */ +int +eicp_get_sys_login_response(struct trans *trans, + int *is_logged_in, + uid_t *uid, + int *scp_fd); + +/** + * Send an E_EICP_LOGOUT_REQUEST (sesexec) + * + * @param trans EICP transport + * @return != 0 for error + * + * The sesexec process will exit normally + */ +int +eicp_send_logout_request(struct trans *trans); + +/* -------------------- Session messages-------------------- */ + +/** + * Send an E_EICP_CREATE_SESSION_REQUEST (sesman) + * + * @param trans EICP transport + * @param scp_fd SCP file descriptor from sesman client + * @param display X display number to use + * @param type Session type + * @param width Initial session width + * @param height Initial session height + * @param bpp Session bits-per-pixel (ignored for Xorg sessions) + * @param shell User program to run. May be "" + * @param directory Directory to run the program in. May be "" + * @return != 0 for error + * + * The UID for the session comes from one of two places:- + * - The UID for a sys login request is used if one has successfully + * been executed. + * - If no sys login request is used, the UID is taken from the scp_fd + * + * Following a successful request, the session creation can be + * considered to be underway. The result of this operation is + * conveyed back to the caller as an ERCP event. The caller must use + * ercp_trans_from_eicp_trans() on 'trans' to convert the transport to + * an ERCP transport to receive this (and other) session run-time events. + */ +int +eicp_send_create_session_request(struct trans *trans, + int scp_fd, + unsigned int display, + enum scp_session_type type, + unsigned short width, + unsigned short height, + unsigned char bpp, + const char *shell, + const char *directory); + + +/** + * Parse an incoming E_EICP_CREATE_SESSION_REQUEST (sesexec) + * + * @param trans EICP transport + * @param[out] scp_fd SCP file descriptor from sesman client + * @param[out] display X display number to use + * @param[out] type Session type + * @param[out] width Initial session width + * @param[out] height Initial session height + * @param[out] bpp Session bits-per-pixel (ignored for Xorg sessions) + * @param[out] shell User program to run. May be "" + * @param[out] directory Directory to run the program in. May be "" + * @return != 0 for error + * + * Returned string pointers are valid until scp_msg_in_reset() is + * called for the transport + */ +int +eicp_get_create_session_request(struct trans *trans, + int *scp_fd, + unsigned int *display, + enum scp_session_type *type, + unsigned short *width, + unsigned short *height, + unsigned char *bpp, + const char **shell, + const char **directory); + +#endif /* EICP_H */ diff --git a/libipm/libipm_facilities.h b/libipm/libipm_facilities.h index 71ce2900..aa5482ac 100644 --- a/libipm/libipm_facilities.h +++ b/libipm/libipm_facilities.h @@ -28,6 +28,8 @@ enum libipm_facility { LIBIPM_FAC_SCP = 1, /**< SCP - Sesman Control Protocol */ + LIBIPM_FAC_EICP, /**< EICP - Executive Initialization Control Protocol */ + LIBIPM_FAC_TEST = 65535 /**< Used for unit testing */ }; From 8064a463c944f90babab59ab9de54677f6203b65 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Wed, 22 Mar 2023 14:48:20 +0000 Subject: [PATCH 07/22] libipm: Add libipm_change_facility() call --- libipm/libipm.c | 22 ++++++++++++++++++++++ libipm/libipm.h | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/libipm/libipm.c b/libipm/libipm.c index 737d7e5f..7d56f196 100644 --- a/libipm/libipm.c +++ b/libipm/libipm.c @@ -216,3 +216,25 @@ libipm_clear_flags(struct trans *trans, unsigned int flags) priv->flags &= ~flags; } } + +/*****************************************************************************/ + +void +libipm_change_facility(struct trans *trans, + enum libipm_facility old_facility, + enum libipm_facility new_facility) +{ + struct libipm_priv *priv = (struct libipm_priv *)trans->extra_data; + + if (priv != NULL) + { + if (priv->facility != old_facility) + { + LOG(LOG_LEVEL_WARNING, "Not changing libipm facility - bad value"); + } + else + { + priv->facility = new_facility; + } + } +} diff --git a/libipm/libipm.h b/libipm/libipm.h index 6f9ace7e..fb75fc97 100644 --- a/libipm/libipm.h +++ b/libipm/libipm.h @@ -119,6 +119,26 @@ libipm_set_flags(struct trans *trans, unsigned int flags); void libipm_clear_flags(struct trans *trans, unsigned int flags); + +/** + * Change the facility number for the transport + * @param trans libipm transport + * @param old_facility old transport facility + * @param new_facility new transport facility + * + * This call is required if a libipm transport changes a facility number. + * This can be used to implement switches from one functional server state to + * another. + * + * The caller must be aware of the previous facility to change the facility. + * In the event of a mismatch, a message is logged and no action is taken. + */ +void +libipm_change_facility(struct trans *trans, + enum libipm_facility old_facility, + enum libipm_facility new_facility); + + /** * Initialise an output message * From dec05f91fad217df7d23cbe9b0addc60c446f877 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Wed, 22 Mar 2023 14:48:58 +0000 Subject: [PATCH 08/22] libipm: Add ERCP --- libipm/Makefile.am | 2 + libipm/ercp.c | 216 +++++++++++++++++++++++++++++++++ libipm/ercp.h | 236 +++++++++++++++++++++++++++++++++++++ libipm/libipm_facilities.h | 1 + 4 files changed, 455 insertions(+) create mode 100644 libipm/ercp.c create mode 100644 libipm/ercp.h diff --git a/libipm/Makefile.am b/libipm/Makefile.am index f0bd2a1a..84ea0948 100644 --- a/libipm/Makefile.am +++ b/libipm/Makefile.am @@ -15,6 +15,8 @@ libipm_la_SOURCES = \ libipm_private.h \ eicp.h \ eicp.c \ + ercp.h \ + ercp.c \ scp.h \ scp.c \ scp_application_types.h \ diff --git a/libipm/ercp.c b/libipm/ercp.c new file mode 100644 index 00000000..a5ba61c9 --- /dev/null +++ b/libipm/ercp.c @@ -0,0 +1,216 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2022, all xrdp contributors + * + * 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 libipm/ercp.c + * @brief ERCP definitions + * @author Matt Burt + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include "ercp.h" +#include "libipm.h" +#include "guid.h" +#include "os_calls.h" +#include "trans.h" + +/*****************************************************************************/ +static const char * +msgno_to_str(unsigned short n) +{ + return + (n == E_ERCP_SESSION_ANNOUNCE_EVENT) ? "ERCP_SESSION_ANNOUNCE_EVENT" : + (n == E_ERCP_SESSION_FINISHED_EVENT) ? "ERCP_SESSION_FINISHED_EVENT" : + NULL; +} + +/*****************************************************************************/ +const char * +ercp_msgno_to_str(enum ercp_msg_code n, char *buff, unsigned int buff_size) +{ + const char *str = msgno_to_str((unsigned short)n); + + if (str == NULL) + { + g_snprintf(buff, buff_size, "[code #%d]", (int)n); + } + else + { + g_snprintf(buff, buff_size, "%s", str); + } + + return buff; +} + +/*****************************************************************************/ +int +ercp_init_trans(struct trans *trans) +{ + return libipm_init_trans(trans, LIBIPM_FAC_ERCP, msgno_to_str); +} + +/*****************************************************************************/ +void +ercp_trans_from_eicp_trans(struct trans *trans, + ttrans_data_in callback_func, + void *callback_data) +{ + libipm_change_facility(trans, LIBIPM_FAC_EICP, LIBIPM_FAC_ERCP); + trans->trans_data_in = callback_func; + trans->callback_data = callback_data; +} + +/*****************************************************************************/ + +int +ercp_msg_in_check_available(struct trans *trans, int *available) +{ + return libipm_msg_in_check_available(trans, available); +} + +/*****************************************************************************/ + +int +ercp_msg_in_wait_available(struct trans *trans) +{ + return libipm_msg_in_wait_available(trans); +} + +/*****************************************************************************/ + +enum ercp_msg_code +ercp_msg_in_get_msgno(const struct trans *trans) +{ + return (enum ercp_msg_code)libipm_msg_in_get_msgno(trans); +} + +/*****************************************************************************/ + +void +ercp_msg_in_reset(struct trans *trans) +{ + libipm_msg_in_reset(trans); +} + +/*****************************************************************************/ + +int +ercp_send_session_announce_event(struct trans *trans, + unsigned int display, + uid_t uid, + enum scp_session_type type, + unsigned short start_width, + unsigned short start_height, + unsigned char bpp, + const struct guid *guid, + const char *start_ip_addr, + time_t start_time) +{ + struct libipm_fsb guid_descriptor = { (void *)guid, sizeof(*guid) }; + + return libipm_msg_out_simple_send( + trans, + (int)E_ERCP_SESSION_ANNOUNCE_EVENT, + "uiyqqyBsx", + display, + uid, + type, + start_width, + start_height, + bpp, + &guid_descriptor, + start_ip_addr, + start_time); +} + +/*****************************************************************************/ + +int +ercp_get_session_announce_event(struct trans *trans, + unsigned int *display, + uid_t *uid, + enum scp_session_type *type, + unsigned short *start_width, + unsigned short *start_height, + unsigned char *bpp, + struct guid *guid, + const char **start_ip_addr, + time_t *start_time) +{ + /* Intermediate values */ + uint32_t i_display; + int32_t i_uid; + uint8_t i_type; + uint16_t i_width; + uint16_t i_height; + uint8_t i_bpp; + int64_t i_start_time; + + const struct libipm_fsb guid_descriptor = { (void *)guid, sizeof(*guid) }; + + int rv = libipm_msg_in_parse( + trans, + "uiyqqyBsx", + &i_display, + &i_uid, + &i_type, + &i_width, + &i_height, + &i_bpp, + &guid_descriptor, + start_ip_addr, + &i_start_time); + + if (rv == 0) + { + if (display != NULL) + { + *display = i_display; + } + *uid = (uid_t)i_uid; + *type = (enum scp_session_type)i_type; + *start_width = i_width; + *start_height = i_height; + *bpp = i_bpp; + *start_time = (time_t)i_start_time; + } + + return rv; +} + +/*****************************************************************************/ + +int +ercp_send_session_finished_event(struct trans *trans) +{ + return libipm_msg_out_simple_send( + trans, (int)E_ERCP_SESSION_FINISHED_EVENT, NULL); +} + +/*****************************************************************************/ + +int +ercp_send_session_reconnect_event(struct trans *trans) +{ + return libipm_msg_out_simple_send( + trans, (int)E_ERCP_SESSION_RECONNECT_EVENT, NULL); +} diff --git a/libipm/ercp.h b/libipm/ercp.h new file mode 100644 index 00000000..aac0a8d6 --- /dev/null +++ b/libipm/ercp.h @@ -0,0 +1,236 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2022, all xrdp contributors + * + * 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 libipm/ercp.h + * @brief ERCP declarations + * @author Matt Burt + * + * Functions in this file use the following naming conventions:- + * + * E_ERCP_{msg}_REQUEST is sent by ercp_send_{msg}_request() + * E_ERCP_{msg}_REQUEST is parsed by ercp_get_{msg}_request() + * E_ERCP_{msg}_RESPONSE is sent by ercp_send_{msg}_response() + * E_ERCP_{msg}_RESPONSE is parsed by ercp_get_{msg}_response() + * E_ERCP_{msg}_EVENT is sent by ercp_send_{msg}_event() + * E_ERCP_{msg}_EVENT is parsed by ercp_get_{msg}_event() + */ + +#ifndef ERCP_H +#define ERCP_H + +#include "arch.h" +#include "scp_application_types.h" +#include "trans.h" + +struct guid; + +/* Message codes */ +enum ercp_msg_code +{ + E_ERCP_SESSION_ANNOUNCE_EVENT, + E_ERCP_SESSION_FINISHED_EVENT, + + E_ERCP_SESSION_RECONNECT_EVENT +}; + +/* Common facilities */ + +/** + * Convert a message code to a string for output + * @param n Message code + * @param buff to contain string + * @param buff_size length of buff + * @return buff is returned for convenience. + */ +const char * +ercp_msgno_to_str(enum ercp_msg_code n, char *buff, unsigned int buff_size); + +/* Connection management facilities */ + +/** + * Converts a standard trans connected to an ERCP endpoint to an ERCP transport + * + * @param trans connected endpoint + * @return != 0 for error + */ +int +ercp_init_trans(struct trans *trans); + +/** + * Converts an EICP transport to an ERCP transport. + * + * This is done following successful transmission or receipt of an + * E_EICP_CREATE_SESSION_REQUEST. + * + * @param trans connected endpoint + * @param callback_func New callback function for ERCP messages. + * @param callback_data New argument for callback function + */ +void +ercp_trans_from_eicp_trans(struct trans *trans, + ttrans_data_in callback_func, + void *callback_data); + + +/** + * Checks an ERCP transport to see if a complete message is + * available for parsing + * + * @param trans ERCP transport + * @param[out] available != 0 if a complete message is available + * @return != 0 for error + */ +int +ercp_msg_in_check_available(struct trans *trans, int *available); + +/** + * Waits on a single transport for an ERCP message to be available for + * parsing + * + * @param trans libipm transport + * @return != 0 for error + * + * While the call is active, data-in callbacks for the transport are + * disabled. + * + * Only use this call if you have nothing to do until a message + * arrives on the transport. If you have other transports to service, use + * ercp_msg_in_check_available() + */ +int +ercp_msg_in_wait_available(struct trans *trans); + + +/** + * Gets the ERCP message number of an incoming message + * + * @param trans ERCP transport + * @return message in the buffer + * + * The results of calling this routine before ercp_msg_in_check_available() + * states a message is available are undefined. + */ +enum ercp_msg_code +ercp_msg_in_get_msgno(const struct trans *trans); + +/** + * Resets an ERCP message buffer ready to receive the next message + * + * @param trans libipm transport + */ +void +ercp_msg_in_reset(struct trans *trans); + +/* -------------------- Session event messages-------------------- */ +/** + * Send an E_ERCP_SESSION_ANNOUNCE_EVENT + * + * Direction : sesexec -> sesman + * + * This event contains all the information known about a session + * + * @param trans EICP transport + * @param display Display used by session + * @param uid UID of user logged in to session + * @param type Session type + * @param start_width Starting width of seenio + * @param start_height Starting height of session + * @param bpp Bits-per-pixel for session + * @param guid Session GUID + * @param start_ip_addr Starting IP address of client + * @param start_time Session start time + * @return != 0 for error + */ +int +ercp_send_session_announce_event(struct trans *trans, + unsigned int display, + uid_t uid, + enum scp_session_type type, + unsigned short start_width, + unsigned short start_height, + unsigned char bpp, + const struct guid *guid, + const char *start_ip_addr, + time_t start_time); + + +/** + * Parse an incoming E_ERCP_SESSION_ANNOUNCE_EVENT + * + * This event contains all the information known about a session + * + * @param trans EICP transport + * @param[out] display Display used by session. + * Pointer can be NULL if this is already known. + * @param[out] uid UID of user logged in to session + * @param[out] type Session type + * @param[out] start_width Starting width of seenio + * @param[out] start_height Starting height of session + * @param[out] bpp Bits-per-pixel for session + * @param[out] guid Session GUID + * @param[out] start_ip_addr Starting IP address of client + * @param[out] start_time Session start time + * @return != 0 for error + */ +int +ercp_get_session_announce_event(struct trans *trans, + unsigned int *display, + uid_t *uid, + enum scp_session_type *type, + unsigned short *start_width, + unsigned short *start_height, + unsigned char *bpp, + struct guid *guid, + const char **start_ip_addr, + time_t *start_time); + + +/** + * Send an E_ERCP_SESSION_FINISHED_EVENT + * + * Direction : sesexec -> sesman + * + * This event simply states the attached session has finished and can be + * removed from any data structures held by sesman + * + * @param trans EICP transport + * @return != 0 for error + */ +int +ercp_send_session_finished_event(struct trans *trans); + + + +/** + * Send an E_ERCP_SESSION_RECONNECT_EVENT + * + * Direction : sesman -> sesexec + * + * This event tells sesexec that a reconnection is about to occur, and + * sesexec should run the reconnect script. + * + * @param trans EICP transport + * @return != 0 for error + */ +int +ercp_send_session_reconnect_event(struct trans *trans); + + +#endif /* ERCP_H */ diff --git a/libipm/libipm_facilities.h b/libipm/libipm_facilities.h index aa5482ac..16b1cd2c 100644 --- a/libipm/libipm_facilities.h +++ b/libipm/libipm_facilities.h @@ -29,6 +29,7 @@ enum libipm_facility { LIBIPM_FAC_SCP = 1, /**< SCP - Sesman Control Protocol */ LIBIPM_FAC_EICP, /**< EICP - Executive Initialization Control Protocol */ + LIBIPM_FAC_ERCP, /**< ERCP - Executive Run-time Control Protocol */ LIBIPM_FAC_TEST = 65535 /**< Used for unit testing */ }; From 970d9361065087c36e92928669e4486727c1addf Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 23 Jan 2023 15:17:34 +0000 Subject: [PATCH 09/22] libsesman config: Define default sesman.ini name --- sesman/libsesman/sesman_config.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sesman/libsesman/sesman_config.h b/sesman/libsesman/sesman_config.h index 8570fed7..ea6cf2be 100644 --- a/sesman/libsesman/sesman_config.h +++ b/sesman/libsesman/sesman_config.h @@ -43,6 +43,11 @@ enum SESMAN_CFG_SESS_POLICY_BITS SESMAN_CFG_SESS_POLICY_I = (1 << 5) }; +/** + * Name of default sesman.ini file + */ +#define DEFAULT_SESMAN_INI XRDP_CFG_PATH "/sesman.ini" + /** * * @struct config_security From 06580ec448ca9ed543699d85afc4e4b97ec75b65 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 6 Feb 2023 16:14:36 +0000 Subject: [PATCH 10/22] sesman config: Add MaxDisplayNumber When allocating a display number, we should be aware that IANA only allow TCP displays up to :63. This PR adds that restriction in to sesman.ini as a default, to prevent us allocating unavailable TCP ports. By default TCP ports are not enabled for X servers, but users can easily change this if they wish to access X displays directly over the network. This restriction is in addition to the MaxSessions limit already present in sesman.ini --- docs/man/sesman.ini.5.in | 7 +++++++ sesman/libsesman/sesman_config.c | 12 ++++++++++++ sesman/libsesman/sesman_config.h | 5 +++++ sesman/sesman.ini.in | 9 +++++++++ 4 files changed, 33 insertions(+) diff --git a/docs/man/sesman.ini.5.in b/docs/man/sesman.ini.5.in index 45c256f9..474591c0 100644 --- a/docs/man/sesman.ini.5.in +++ b/docs/man/sesman.ini.5.in @@ -155,6 +155,13 @@ defaults to \fI10\fR. Sets the maximum number of simultaneous sessions. If not set or set to \fI0\fR, unlimited session are allowed. +.TP +\fBMaxDisplayNumber\fR=\fInumber\fR +Sets the maximum number which can be assigned to an X11 $DISPLAY. The +default is compatible with IANA TCP port allocations. If you are not +allowing TCP connections to your X servers you may safely increase this +number. + .TP \fBKillDisconnected\fR=\fI[true|false]\fR If set to \fB1\fR, \fBtrue\fR or \fByes\fR, every session will be killed diff --git a/sesman/libsesman/sesman_config.c b/sesman/libsesman/sesman_config.c index 2c545a1e..0568a25f 100644 --- a/sesman/libsesman/sesman_config.c +++ b/sesman/libsesman/sesman_config.c @@ -77,6 +77,7 @@ #define SESMAN_CFG_SESS_IDLE_LIMIT "IdleTimeLimit" #define SESMAN_CFG_SESS_DISC_LIMIT "DisconnectedTimeLimit" #define SESMAN_CFG_SESS_X11DISPLAYOFFSET "X11DisplayOffset" +#define SESMAN_CFG_SESS_MAX_DISPLAY "MaxDisplayNumber" #define SESMAN_CFG_SESS_POLICY_S "Policy" #define SESMAN_CFG_SESS_POLICY_DFLT_S "Default" @@ -410,6 +411,8 @@ config_read_sessions(int file, struct config_sessions *se, struct list *param_n, /* setting defaults */ se->x11_display_offset = 10; + // https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml` + se->max_display_number = 63; se->max_sessions = 0; se->max_idle_time = 0; se->max_disc_time = 0; @@ -428,6 +431,15 @@ config_read_sessions(int file, struct config_sessions *se, struct list *param_n, se->x11_display_offset = g_atoi(value); } + else if (0 == g_strcasecmp(buf, SESMAN_CFG_SESS_MAX_DISPLAY)) + { + int mdn = g_atoi(value); + if (mdn > 0) + { + se->max_display_number = mdn; + } + } + else if (0 == g_strcasecmp(buf, SESMAN_CFG_SESS_MAX)) { se->max_sessions = g_atoi(value); diff --git a/sesman/libsesman/sesman_config.h b/sesman/libsesman/sesman_config.h index ea6cf2be..545728a2 100644 --- a/sesman/libsesman/sesman_config.h +++ b/sesman/libsesman/sesman_config.h @@ -118,6 +118,11 @@ struct config_sessions * @brief X11 TCP port offset. default value: 10 */ int x11_display_offset; + /** + * @var max_display_number + * @brief Highest X11 display number considered for allocation + */ + unsigned int max_display_number; /** * @var max_sessions * @brief maximum number of allowed sessions. 0 for unlimited diff --git a/sesman/sesman.ini.in b/sesman/sesman.ini.in index f2ce82c1..dd615858 100644 --- a/sesman/sesman.ini.in +++ b/sesman/sesman.ini.in @@ -51,6 +51,15 @@ X11DisplayOffset=10 ; Default: 0 MaxSessions=50 +;; MaxDisplayNumer - maximum number considered for an X display +; Type: integer +; Default: 63 +; +; IANA only allocates TCP ports up to 6063 for X servers. If you are not +; allowing TCP connections to your X servers you may safely increase this +; number. +#MaxDisplayNumber=63 + ;; KillDisconnected - kill disconnected sessions ; Type: boolean ; Default: false From 82ede293886094dc5438fa4db8136e0ed2e36dd5 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 23 Mar 2023 20:32:28 +0000 Subject: [PATCH 11/22] libsesman: Make x11_display_offset and max_sessions unsigned --- sesman/libsesman/sesman_config.c | 12 ++++++++++-- sesman/libsesman/sesman_config.h | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/sesman/libsesman/sesman_config.c b/sesman/libsesman/sesman_config.c index 0568a25f..8494563b 100644 --- a/sesman/libsesman/sesman_config.c +++ b/sesman/libsesman/sesman_config.c @@ -428,7 +428,11 @@ config_read_sessions(int file, struct config_sessions *se, struct list *param_n, if (0 == g_strcasecmp(buf, SESMAN_CFG_SESS_X11DISPLAYOFFSET)) { - se->x11_display_offset = g_atoi(value); + int x11off = g_atoi(value); + if (x11off >= 0) + { + se->x11_display_offset = x11off; + } } else if (0 == g_strcasecmp(buf, SESMAN_CFG_SESS_MAX_DISPLAY)) @@ -442,7 +446,11 @@ config_read_sessions(int file, struct config_sessions *se, struct list *param_n, else if (0 == g_strcasecmp(buf, SESMAN_CFG_SESS_MAX)) { - se->max_sessions = g_atoi(value); + int sm = g_atoi(value); + if (sm >= 0) + { + se->max_sessions = sm; + } } else if (0 == g_strcasecmp(buf, SESMAN_CFG_SESS_KILL_DISC)) diff --git a/sesman/libsesman/sesman_config.h b/sesman/libsesman/sesman_config.h index 545728a2..c3b4871a 100644 --- a/sesman/libsesman/sesman_config.h +++ b/sesman/libsesman/sesman_config.h @@ -117,7 +117,7 @@ struct config_sessions * @var x11_display_offset * @brief X11 TCP port offset. default value: 10 */ - int x11_display_offset; + unsigned int x11_display_offset; /** * @var max_display_number * @brief Highest X11 display number considered for allocation @@ -127,7 +127,7 @@ struct config_sessions * @var max_sessions * @brief maximum number of allowed sessions. 0 for unlimited */ - int max_sessions; + unsigned int max_sessions; /** * @var max_idle_time * @brief maximum idle time for each session From 1a9d15bef021d966e9ff3a9819ff1ccac7350f99 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:43:14 +0100 Subject: [PATCH 12/22] Remove explicit auth_stop_session() call Now that authentication/authorization and session creation are happening in the same process, there is no need for a separate call to finish an auth session. This change prevents the upper software layers from needing to track whether auth_start_session() has been called or not. --- sesman/libsesman/sesman_auth.h | 13 +++---------- sesman/libsesman/verify_user.c | 8 -------- sesman/libsesman/verify_user_kerberos.c | 8 -------- sesman/libsesman/verify_user_pam.c | 2 +- sesman/libsesman/verify_user_pam_userpass.c | 2 +- sesman/tools/authtest.c | 1 - 6 files changed, 5 insertions(+), 29 deletions(-) diff --git a/sesman/libsesman/sesman_auth.h b/sesman/libsesman/sesman_auth.h index e27684e9..61537ab2 100644 --- a/sesman/libsesman/sesman_auth.h +++ b/sesman/libsesman/sesman_auth.h @@ -53,6 +53,7 @@ auth_userpass(const char *user, const char *pass, * * @param uid User ID * @param[out] Error code for the operation. E_SCP_LOGIN_OK on success. + * Can be NULL if this information isn't required. * @return auth handle on success, NULL on failure * */ @@ -66,20 +67,12 @@ auth_uds(const char *user, enum scp_login_status *errorcode); * @param display_num Display number * @return 0 on success, 1 on failure * + * The resources allocated when the session is started are de-allocated + * by auth_end() - there is no separate way to do this. */ int auth_start_session(struct auth_info *auth_info, int display_num); -/** - * - * @brief Stops a session previously started with auth_start_session() - * @param auth_info. Auth handle created by auth_userpass - * @return 0 on success, 1 on failure - * - */ -int -auth_stop_session(struct auth_info *auth_info); - /** * * @brief Deallocates an auth handle and releases all resources diff --git a/sesman/libsesman/verify_user.c b/sesman/libsesman/verify_user.c index 43740a08..ccd289d2 100644 --- a/sesman/libsesman/verify_user.c +++ b/sesman/libsesman/verify_user.c @@ -180,14 +180,6 @@ auth_start_session(struct auth_info *auth_info, int display_num) return 0; } -/******************************************************************************/ -/* returns error */ -int -auth_stop_session(struct auth_info *auth_info) -{ - return 0; -} - /******************************************************************************/ int auth_end(struct auth_info *auth_info) diff --git a/sesman/libsesman/verify_user_kerberos.c b/sesman/libsesman/verify_user_kerberos.c index 3c6a9bc2..c2bde414 100644 --- a/sesman/libsesman/verify_user_kerberos.c +++ b/sesman/libsesman/verify_user_kerberos.c @@ -229,14 +229,6 @@ auth_start_session(struct auth_info *auth_info, int display_num) return 0; } -/******************************************************************************/ -/* returns error */ -int -auth_stop_session(struct auth_info *auth_info) -{ - return 0; -} - /******************************************************************************/ int auth_set_env(struct auth_info *auth_info) diff --git a/sesman/libsesman/verify_user_pam.c b/sesman/libsesman/verify_user_pam.c index 2a5ce8f8..9591d211 100644 --- a/sesman/libsesman/verify_user_pam.c +++ b/sesman/libsesman/verify_user_pam.c @@ -438,7 +438,7 @@ auth_start_session(struct auth_info *auth_info, int display_num) /******************************************************************************/ /* returns error */ -int +static int auth_stop_session(struct auth_info *auth_info) { int rv = 0; diff --git a/sesman/libsesman/verify_user_pam_userpass.c b/sesman/libsesman/verify_user_pam_userpass.c index 411931f5..2b6cf0fc 100644 --- a/sesman/libsesman/verify_user_pam_userpass.c +++ b/sesman/libsesman/verify_user_pam_userpass.c @@ -248,7 +248,7 @@ auth_start_session(struct auth_info *auth_info, int display_num) /******************************************************************************/ /* returns error */ -int +static int auth_stop_session(struct auth_info *auth_info) { int rv = 0; diff --git a/sesman/tools/authtest.c b/sesman/tools/authtest.c index 4a62ad10..48d8cca2 100644 --- a/sesman/tools/authtest.c +++ b/sesman/tools/authtest.c @@ -308,7 +308,6 @@ main(int argc, char **argv) rv = g_system(amp.command); LOG(LOG_LEVEL_INFO, "command \"%s\" returned %d", amp.command, rv); - auth_stop_session(auth_info); } } if (auth_info != NULL) From 3d95954d87654d3f1307ef18162f579bce15e69e Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Wed, 22 Mar 2023 16:25:28 +0000 Subject: [PATCH 13/22] Move session_list to struct list * This commit now uses the standard list module to manage the active session list, rather than having special code to do this. --- sesman/scp_process.c | 52 ++-- sesman/sesman.c | 8 +- sesman/session_list.c | 597 ++++++++++++++++++++++-------------------- sesman/session_list.h | 49 ++-- 4 files changed, 376 insertions(+), 330 deletions(-) diff --git a/sesman/scp_process.c b/sesman/scp_process.c index 79ccbc50..0d69f810 100644 --- a/sesman/scp_process.c +++ b/sesman/scp_process.c @@ -209,8 +209,8 @@ allocate_and_start_session(struct auth_info *auth_info, const struct session_parameters *params) { int pid = 0; - struct session_chain *temp = (struct session_chain *)NULL; enum scp_screate_status status; + struct session_item *si; /* check to limit concurrent sessions */ if (session_list_get_count() >= (unsigned int)g_cfg->sess.max_sessions) @@ -220,22 +220,11 @@ allocate_and_start_session(struct auth_info *auth_info, return E_SCP_SCREATE_MAX_REACHED; } - temp = (struct session_chain *)g_malloc(sizeof(struct session_chain), 0); - - if (temp == 0) + si = session_new(); + if (si == NULL) { LOG(LOG_LEVEL_ERROR, "Out of memory error: cannot create new session " - "chain element - user %s", username); - return E_SCP_SCREATE_NO_MEMORY; - } - - temp->item = (struct session_item *)g_malloc(sizeof(struct session_item), 0); - - if (temp->item == 0) - { - g_free(temp); - LOG(LOG_LEVEL_ERROR, "Out of memory error: cannot create new session " - "item - user %s", username); + "element - user %s", username); return E_SCP_SCREATE_NO_MEMORY; } @@ -252,23 +241,26 @@ allocate_and_start_session(struct auth_info *auth_info, LOG(LOG_LEVEL_INFO, "++ created session: username %s", username); } - temp->item->pid = pid; - temp->item->display = params->display; - temp->item->width = params->width; - temp->item->height = params->height; - temp->item->bpp = params->bpp; - temp->item->auth_info = auth_info; - g_strncpy(temp->item->start_ip_addr, ip_addr, - sizeof(temp->item->start_ip_addr) - 1); - temp->item->uid = params->uid; - temp->item->guid = params->guid; + si->pid = pid; + si->display = params->display; + si->width = params->width; + si->height = params->height; + si->bpp = params->bpp; + si->auth_info = auth_info; + g_strncpy(si->start_ip_addr, ip_addr, + sizeof(si->start_ip_addr) - 1); + si->uid = params->uid; + si->guid = params->guid; - temp->item->start_time = g_time1(); + si->start_time = g_time1(); - temp->item->type = params->type; - temp->item->status = SESMAN_SESSION_STATUS_ACTIVE; - - session_list_add(temp); + si->type = params->type; + si->status = SESMAN_SESSION_STATUS_ACTIVE; + } + else + { + // Remove session item from the list + session_list_kill(-1); } return status; diff --git a/sesman/sesman.c b/sesman/sesman.c index c4809a92..8b122dc4 100644 --- a/sesman/sesman.c +++ b/sesman/sesman.c @@ -35,6 +35,7 @@ #include "sesman_auth.h" #include "sesman_config.h" +#include "session_list.h" #include "lock_uds.h" #include "os_calls.h" #include "scp.h" @@ -966,7 +967,12 @@ main(int argc, char **argv) g_chmod_hex("/tmp/.X11-unix", 0x1777); } - error = sesman_main_loop(); + error = session_module_init(); + if (error == 0) + { + error = sesman_main_loop(); + session_module_cleanup(); + } /* clean up PID file on exit */ if (daemon) diff --git a/sesman/session_list.c b/sesman/session_list.c index 6b63b40d..25eb8102 100644 --- a/sesman/session_list.c +++ b/sesman/session_list.c @@ -44,119 +44,100 @@ #include "string_calls.h" #include "xrdp_sockets.h" -static struct session_chain *g_sessions; -static int g_session_count; +static struct list *g_session_list = NULL; + +#define SESSION_IN_USE(si) \ + ((si) != NULL && \ + (si)->display >= 0 && \ + (si)->pid > 0) + +/******************************************************************************/ +int +session_module_init(void) +{ + int rv = 1; + if (g_session_list == NULL) + { + g_session_list = list_create_sized(g_cfg->sess.max_sessions); + } + + if (g_session_list == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't allocate session list"); + } + else + { + g_session_list->auto_free = 0; + rv = 0; + } + + return rv; +} + +/******************************************************************************/ +/** + * Frees resources allocated to a session_item + * + * @param si Session item + * + * @note Any pointer to this item on g_session_list will be invalid + * after this call. + */ +static void +free_session(struct session_item *si) +{ + if (si != NULL) + { + if (si->auth_info != NULL) + { + auth_end(si->auth_info); + } + g_free(si); + } +} + +/******************************************************************************/ +void +session_module_cleanup(void) +{ + if (g_session_list != NULL) + { + int i; + for (i = 0 ; i < g_session_list->count ; ++i) + { + struct session_item *si; + si = (struct session_item *)list_get_item(g_session_list, i); + free_session(si); + } + list_delete(g_session_list); + g_session_list = NULL; + } +} /******************************************************************************/ unsigned int session_list_get_count(void) { - return g_session_count; -} - -/******************************************************************************/ -void -session_list_add(struct session_chain *element) -{ - element->next = g_sessions; - g_sessions = element; - g_session_count++; + return g_session_list->count; } /******************************************************************************/ struct session_item * -session_list_get_bydata(uid_t uid, - enum scp_session_type type, - unsigned short width, - unsigned short height, - unsigned char bpp, - const char *ip_addr) +session_new(void) { - char policy_str[64]; - struct session_chain *tmp; - int policy = g_cfg->sess.policy; - - if ((policy & SESMAN_CFG_SESS_POLICY_DEFAULT) != 0) + struct session_item *result = g_new0(struct session_item, 1); + if (result != NULL) { - /* In the past (i.e. xrdp before v0.9.14), the default - * session policy varied by type. If this is needed again - * in the future, here is the place to add it */ - policy = SESMAN_CFG_SESS_POLICY_U | SESMAN_CFG_SESS_POLICY_B; + result->pid = -1; + result->display = -1; + if (!list_add_item(g_session_list, (tintptr)result)) + { + g_free(result); + result = NULL; + } } - config_output_policy_string(policy, policy_str, sizeof(policy_str)); - - LOG(LOG_LEVEL_DEBUG, - "%s: search policy=%s type=%s U=%d B=%d D=(%dx%d) I=%s", - __func__, - policy_str, SCP_SESSION_TYPE_TO_STR(type), - uid, bpp, width, height, - ip_addr); - - /* 'Separate' policy never matches */ - if (policy & SESMAN_CFG_SESS_POLICY_SEPARATE) - { - LOG(LOG_LEVEL_DEBUG, "%s: No matches possible", __func__); - return NULL; - } - - for (tmp = g_sessions ; tmp != 0 ; tmp = tmp->next) - { - struct session_item *item = tmp->item; - - LOG(LOG_LEVEL_DEBUG, - "%s: try %p type=%s U=%d B=%d D=(%dx%d) I=%s", - __func__, - item, - SCP_SESSION_TYPE_TO_STR(item->type), - item->uid, - item->bpp, - item->width, item->height, - item->start_ip_addr); - - if (item->type != type) - { - LOG(LOG_LEVEL_DEBUG, "%s: Type doesn't match", __func__); - continue; - } - - if ((policy & SESMAN_CFG_SESS_POLICY_U) && (int)uid != item->uid) - { - LOG(LOG_LEVEL_DEBUG, - "%s: UID doesn't match for 'U' policy", __func__); - continue; - } - - if ((policy & SESMAN_CFG_SESS_POLICY_B) && item->bpp != bpp) - { - LOG(LOG_LEVEL_DEBUG, - "%s: bpp doesn't match for 'B' policy", __func__); - continue; - } - - if ((policy & SESMAN_CFG_SESS_POLICY_D) && - (item->width != width || item->height != height)) - { - LOG(LOG_LEVEL_DEBUG, - "%s: Dimensions don't match for 'D' policy", __func__); - continue; - } - - if ((policy & SESMAN_CFG_SESS_POLICY_I) && - g_strcmp(item->start_ip_addr, ip_addr) != 0) - { - LOG(LOG_LEVEL_DEBUG, - "%s: IPs don't match for 'I' policy", __func__); - continue; - } - - LOG(LOG_LEVEL_DEBUG, - "%s: Got match, display=%d", __func__, item->display); - return item; - } - - LOG(LOG_LEVEL_DEBUG, "%s: No matches found", __func__); - return 0; + return result; } /******************************************************************************/ @@ -261,56 +242,211 @@ x_server_running_check_ports(int display) } /******************************************************************************/ -/* called with the main thread - returns boolean */ +/* Helper function for get_sorted_display_list():qsort() */ static int -is_display_in_chain(int display) +icmp(const void *i1, const void *i2) { - struct session_chain *chain; - struct session_item *item; + return *(const unsigned int *)i2 - *(const unsigned int *)i1; +} - chain = g_sessions; +/******************************************************************************/ +/** + * Get a sorted array of all the displays allocated to sessions + * @param[out] cnt Count of displays in list + * @return Allocated array of displays or NULL for no memory + * + * Result must always be freed, even if cnt == 0 + */ - while (chain != 0) +static unsigned int * +get_sorted_session_displays(unsigned int *cnt) +{ + unsigned int *displays; + + *cnt = 0; + displays = g_new(unsigned int, session_list_get_count() + 1); + if (displays == NULL) { - item = chain->item; + LOG(LOG_LEVEL_ERROR, "Can't allocate memory for display list"); + } + else if (g_session_list != NULL) + { + int i; - if (item->display == display) + for (i = 0 ; i < g_session_list->count ; ++i) { - return 1; + const struct session_item *si; + si = (const struct session_item *)list_get_item(g_session_list, i); + if (SESSION_IN_USE(si) && si->display >= 0) + { + displays[(*cnt)++] = si->display; + } } - - chain = chain->next; + qsort(displays, *cnt, sizeof(displays[0]), icmp); } - return 0; + return displays; } /******************************************************************************/ int session_list_get_available_display(void) { - int display; + int rv = -1; + unsigned int max_alloc = 0; - display = g_cfg->sess.x11_display_offset; - - while ((display - g_cfg->sess.x11_display_offset) <= g_cfg->sess.max_sessions) + // Find all displays already allocated. We do this to prevent + // unnecessary file system accesses, and also to prevent us allocating + // the same display number to two callers who call in quick + // succession i.e. if the first caller has not created its X server + // by the time we service the second request + unsigned int *allocated_displays = get_sorted_session_displays(&max_alloc); + if (allocated_displays != NULL) { - if (!is_display_in_chain(display)) + unsigned int i = 0; + unsigned int display; + + for (display = g_cfg->sess.x11_display_offset; + display <= g_cfg->sess.max_display_number; + ++display) { + // Have we already allocated this one? + while (i < max_alloc && display > allocated_displays[i]) + { + ++i; + } + if (i < max_alloc && display == allocated_displays[i]) + { + continue; // Already allocated + } + if (!x_server_running_check_ports(display)) { - return display; + break; } } - display++; + g_free(allocated_displays); + + if (display > g_cfg->sess.max_display_number) + { + LOG(LOG_LEVEL_ERROR, + "X server -- no display in range (%d to %d) is available", + g_cfg->sess.x11_display_offset, + g_cfg->sess.max_display_number); + } + else + { + rv = display; + } } - LOG(LOG_LEVEL_ERROR, "X server -- no display in range (%d to %d) is available", - g_cfg->sess.x11_display_offset, - g_cfg->sess.x11_display_offset + g_cfg->sess.max_sessions); - return 0; + return rv; +} + +/******************************************************************************/ +struct session_item * +session_list_get_bydata(uid_t uid, + enum scp_session_type type, + unsigned short width, + unsigned short height, + unsigned char bpp, + const char *ip_addr) +{ + char policy_str[64]; + int policy = g_cfg->sess.policy; + int i; + + if (ip_addr == NULL) + { + ip_addr = ""; + } + + if ((policy & SESMAN_CFG_SESS_POLICY_DEFAULT) != 0) + { + /* Before xrdp v0.9.14, the default + * session policy varied by type. If this is needed again + * in the future, here is the place to add it */ + policy = SESMAN_CFG_SESS_POLICY_U | SESMAN_CFG_SESS_POLICY_B; + } + + config_output_policy_string(policy, policy_str, sizeof(policy_str)); + + LOG(LOG_LEVEL_DEBUG, + "%s: search policy=%s type=%s U=%d B=%d D=(%dx%d) I=%s", + __func__, + policy_str, SCP_SESSION_TYPE_TO_STR(type), + uid, bpp, width, height, + ip_addr); + + /* 'Separate' policy never matches */ + if (policy & SESMAN_CFG_SESS_POLICY_SEPARATE) + { + LOG(LOG_LEVEL_DEBUG, "%s: No matches possible", __func__); + return NULL; + } + + for (i = 0 ; i < g_session_list->count ; ++i) + { + struct session_item *si; + si = (struct session_item *)list_get_item(g_session_list, i); + if (!SESSION_IN_USE(si)) + { + continue; + } + + LOG(LOG_LEVEL_DEBUG, + "%s: try %p type=%s U=%d B=%d D=(%dx%d) I=%s", + __func__, + si, + SCP_SESSION_TYPE_TO_STR(si->type), + si->uid, si->bpp, + si->width, si->height, + si->start_ip_addr); + + if (si->type != type) + { + LOG(LOG_LEVEL_DEBUG, "%s: Type doesn't match", __func__); + continue; + } + + if ((policy & SESMAN_CFG_SESS_POLICY_U) && (int)uid != si->uid) + { + LOG(LOG_LEVEL_DEBUG, + "%s: UID doesn't match for 'U' policy", __func__); + continue; + } + + if ((policy & SESMAN_CFG_SESS_POLICY_B) && si->bpp != bpp) + { + LOG(LOG_LEVEL_DEBUG, + "%s: bpp doesn't match for 'B' policy", __func__); + continue; + } + + if ((policy & SESMAN_CFG_SESS_POLICY_D) && + (si->width != width || si->height != height)) + { + LOG(LOG_LEVEL_DEBUG, + "%s: Dimensions don't match for 'D' policy", __func__); + continue; + } + + if ((policy & SESMAN_CFG_SESS_POLICY_I) && + g_strcmp(si->start_ip_addr, ip_addr) != 0) + { + LOG(LOG_LEVEL_DEBUG, + "%s: IPs don't match for 'I' policy", __func__); + continue; + } + + LOG(LOG_LEVEL_DEBUG, + "%s: Got match, display=%d", __func__, si->display); + return si; + } + + LOG(LOG_LEVEL_DEBUG, "%s: No matches found", __func__); + return NULL; } /******************************************************************************/ @@ -344,172 +480,83 @@ username_from_uid(int uid, char *uname, int uname_len) enum session_kill_status session_list_kill(int pid) { - struct session_chain *tmp; - struct session_chain *prev; + int i = 0; + enum session_kill_status status = SESMAN_SESSION_KILL_NOTFOUND; - tmp = g_sessions; - prev = 0; - - while (tmp != 0) + while (i < g_session_list->count) { - if (tmp->item == 0) + struct session_item *si; + si = (struct session_item *)list_get_item(g_session_list, i); + if (si->pid == pid) { - LOG(LOG_LEVEL_ERROR, "session descriptor for " - "pid %d is null!", pid); - - if (prev == 0) + status = SESMAN_SESSION_KILL_OK; + if (pid > 0) { - /* prev does no exist, so it's the first element - so we set - g_sessions */ - g_sessions = tmp->next; - } - else - { - prev->next = tmp->next; - } + char username[256]; + username_from_uid(si->uid, username, sizeof(username)); - return SESMAN_SESSION_KILL_NULLITEM; - } + /* Log the deletion */ + if (si->auth_info != NULL) + { + LOG(LOG_LEVEL_INFO, + "Calling auth_end for pid %d from pid %d", + pid, g_getpid()); + } - if (tmp->item->pid == pid) - { - char username[256]; - username_from_uid(tmp->item->uid, username, sizeof(username)); - - /* deleting the session */ - if (tmp->item->auth_info != NULL) - { LOG(LOG_LEVEL_INFO, - "Calling auth_end for pid %d from pid %d", - pid, g_getpid()); - auth_end(tmp->item->auth_info); - tmp->item->auth_info = NULL; - } - LOG(LOG_LEVEL_INFO, - "++ terminated session: UID %d (%s), display :%d.0, " - "session_pid %d, ip %s", - tmp->item->uid, username, tmp->item->display, - tmp->item->pid, tmp->item->start_ip_addr); - g_free(tmp->item); - - if (prev == 0) - { - /* prev does no exist, so it's the first element - so we set - g_sessions */ - g_sessions = tmp->next; - } - else - { - prev->next = tmp->next; + "++ terminated session: UID %d (%s), display :%d.0, " + "session_pid %d, ip %s", + si->uid, username, si->display, + si->pid, si->start_ip_addr); } - g_free(tmp); - g_session_count--; - return SESMAN_SESSION_KILL_OK; + free_session(si); + } + else + { + ++i; } - - /* go on */ - prev = tmp; - tmp = tmp->next; } - return SESMAN_SESSION_KILL_NOTFOUND; + return status; } /******************************************************************************/ void session_list_sigkill_all(void) { - struct session_chain *tmp; + int i; - tmp = g_sessions; - - while (tmp != 0) + for (i = 0 ; i < g_session_list->count ; ++i) { - if (tmp->item == 0) + struct session_item *si; + si = (struct session_item *)list_get_item(g_session_list, i); + if (si->pid > 0) { - LOG(LOG_LEVEL_ERROR, "found null session descriptor!"); + g_sigterm(si->pid); } - else - { - g_sigterm(tmp->item->pid); - } - - /* go on */ - tmp = tmp->next; } } -/******************************************************************************/ -struct session_item * -session_list_get_bypid(int pid) -{ - struct session_chain *tmp; - struct session_item *dummy; - - dummy = g_new0(struct session_item, 1); - - if (0 == dummy) - { - LOG(LOG_LEVEL_ERROR, "session_get_bypid: out of memory"); - return 0; - } - - tmp = g_sessions; - - while (tmp != 0) - { - if (tmp->item == 0) - { - LOG(LOG_LEVEL_ERROR, "session descriptor for pid %d is null!", pid); - g_free(dummy); - return 0; - } - - if (tmp->item->pid == pid) - { - g_memcpy(dummy, tmp->item, sizeof(struct session_item)); - return dummy; - } - - /* go on */ - tmp = tmp->next; - } - - g_free(dummy); - return 0; -} - /******************************************************************************/ struct scp_session_info * session_list_get_byuid(int uid, unsigned int *cnt, unsigned char flags) { - struct session_chain *tmp; + int i; struct scp_session_info *sess; int count; int index; count = 0; - tmp = g_sessions; - - LOG(LOG_LEVEL_DEBUG, "searching for session by UID: %d", uid); - while (tmp != 0) + for (i = 0 ; i < g_session_list->count ; ++i) { - if (uid == tmp->item->uid) + const struct session_item *si; + si = (const struct session_item *)list_get_item(g_session_list, i); + if (SESSION_IN_USE(si) && uid == si->uid && (si->status & flags) != 0) { - LOG(LOG_LEVEL_DEBUG, "session_list_get_byuid: status=%d, flags=%d, " - "result=%d", (tmp->item->status), flags, - ((tmp->item->status) & flags)); - - if ((tmp->item->status) & flags) - { - count++; - } + count++; } - - /* go on */ - tmp = tmp->next; } if (count == 0) @@ -527,38 +574,32 @@ session_list_get_byuid(int uid, unsigned int *cnt, unsigned char flags) return 0; } - tmp = g_sessions; index = 0; - - while (tmp != 0 && index < count) + for (i = 0 ; i < g_session_list->count ; ++i) { - if (uid == tmp->item->uid) + const struct session_item *si; + si = (const struct session_item *)list_get_item(g_session_list, i); + if (SESSION_IN_USE(si) && uid == si->uid && (si->status & flags) != 0) { - if ((tmp->item->status) & flags) + (sess[index]).sid = si->pid; + (sess[index]).display = si->display; + (sess[index]).type = si->type; + (sess[index]).height = si->height; + (sess[index]).width = si->width; + (sess[index]).bpp = si->bpp; + (sess[index]).start_time = si->start_time; + (sess[index]).uid = si->uid; + (sess[index]).start_ip_addr = g_strdup(si->start_ip_addr); + + /* Check for string allocation failures */ + if ((sess[index]).start_ip_addr == NULL) { - (sess[index]).sid = tmp->item->pid; - (sess[index]).display = tmp->item->display; - (sess[index]).type = tmp->item->type; - (sess[index]).height = tmp->item->height; - (sess[index]).width = tmp->item->width; - (sess[index]).bpp = tmp->item->bpp; - (sess[index]).start_time = tmp->item->start_time; - (sess[index]).uid = tmp->item->uid; - (sess[index]).start_ip_addr = g_strdup(tmp->item->start_ip_addr); - - /* Check for string allocation failures */ - if ((sess[index]).start_ip_addr == NULL) - { - free_session_info_list(sess, *cnt); - (*cnt) = 0; - return 0; - } - index++; + free_session_info_list(sess, *cnt); + (*cnt) = 0; + return 0; } + index++; } - - /* go on */ - tmp = tmp->next; } (*cnt) = count; diff --git a/sesman/session_list.h b/sesman/session_list.h index 64fe7fd4..e32178e2 100644 --- a/sesman/session_list.h +++ b/sesman/session_list.h @@ -47,7 +47,6 @@ struct session_parameters; enum session_kill_status { SESMAN_SESSION_KILL_OK = 0, - SESMAN_SESSION_KILL_NULLITEM, SESMAN_SESSION_KILL_NOTFOUND }; @@ -78,11 +77,20 @@ struct session_item struct guid guid; }; -struct session_chain -{ - struct session_chain *next; - struct session_item *item; -}; +/** + * Initialise the module + * @return 0 for success + * + * Errors are logged + */ +int +session_module_init(void); + +/** + * Clean up the module on program exit + */ +void +session_module_cleanup(void); /** * Returns the number of sessions currently active @@ -92,10 +100,21 @@ unsigned int session_list_get_count(void); /** - * Adds a new session item to the chain + * Allocates a new session + * + * The PID and display for the allocated session will be -1 and all other + * fields will be blank + * + * @return pointer to new session object or NULL for no memory + * + * After allocating the session successfully, you must initialise the + * PID and display fields with valid numbers. + * + * If you allocate a session and want to remove it due to other problems, + * use session_kill_pid(-1); */ -void -session_list_add(struct session_chain *element); +struct session_item * +session_new(void); /** * Get the next available display @@ -103,7 +122,6 @@ session_list_add(struct session_chain *element); int session_list_get_available_display(void); - /** * * @brief finds a session matching the supplied parameters @@ -138,17 +156,6 @@ void session_list_sigkill_all(void); /** - * - * @brief retrieves a session's descriptor - * @param pid the session pid - * @return a pointer to the session descriptor on success, NULL otherwise - * - */ -struct session_item * -session_list_get_bypid(int pid); - -/** - * * @brief retrieves session descriptions * @param UID the UID for the descriptions * @return A block of session descriptions From dadb3934433f2cbb6d4c21bf753aa995a4c94314 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 23 Jan 2023 14:31:32 +0000 Subject: [PATCH 14/22] Add sesexec control module This module provides a secure way for sesman to start the sesexec program and establish a private communications channel with it. --- sesman/Makefile.am | 2 + sesman/sesexec_control.c | 228 +++++++++++++++++++++++++++++++++++++++ sesman/sesexec_control.h | 49 +++++++++ 3 files changed, 279 insertions(+) create mode 100644 sesman/sesexec_control.c create mode 100644 sesman/sesexec_control.h diff --git a/sesman/Makefile.am b/sesman/Makefile.am index 88684274..83101983 100644 --- a/sesman/Makefile.am +++ b/sesman/Makefile.am @@ -24,6 +24,8 @@ xrdp_sesman_SOURCES = \ scp_process.h \ sesman.c \ sesman.h \ + sesexec_control.c \ + sesexec_control.h \ session.c \ session.h \ session_list.c \ diff --git a/sesman/sesexec_control.c b/sesman/sesexec_control.c new file mode 100644 index 00000000..422c3c22 --- /dev/null +++ b/sesman/sesexec_control.c @@ -0,0 +1,228 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) 2023 Matt Burt + * + * 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_control.c + * @brief Start/stop session executive process + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include "sesman_config.h" +#include "eicp.h" +#include "log.h" +#include "os_calls.h" +#include "pre_session_list.h" +#include "string_calls.h" +#include "sesexec_control.h" +#include "sesman.h" +#include "trans.h" +#include "xrdp_sockets.h" + +#define SESEXEC_SHORTNAME "xrdp-sesexec" +#define SESEXEC_LONGNAME XRDP_LIBEXEC_PATH "/" SESEXEC_SHORTNAME + +// Some platforms using setsid() benefit from sesexec being +// forked again, so the UNIX session can be created cleanly +// See FreeBSD bug +// ports/157282: effective login name is not set by xrdp-sesman +// https://www.freebsd.org/cgi/query-pr.cgi?pr=157282 + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#define USE_BSD_SETLOGIN 1 +#endif + +/*****************************************************************************/ +/** + * Adds entries to the args list for running sesexec + * + * @param args Argument list + * @return 0 for memory allocation failure, 1 for success + */ +static int +create_exec_args_add_entries(struct list *args) +{ + if (!list_add_strdup(args, SESEXEC_SHORTNAME)) + { + return 0; + } + + if (g_strcmp(g_cfg->sesman_ini, DEFAULT_SESMAN_INI) != 0) + { + if (!list_add_strdup_multi(args, "-c", g_cfg->sesman_ini, NULL)) + { + return 0; + } + } + + return 1; +} + +/*****************************************************************************/ +/** + * Create an args list for sesexec + * @return NULL if memory could not be allocated + * + * The result must be freed with list_delete() after use + */ +static struct list * +create_exec_args(void) +{ + struct list *result = list_create(); + if (result != NULL) + { + result->auto_free = 1; + if (!create_exec_args_add_entries(result)) + { + list_delete(result); + result = NULL; + } + } + + return result; +} + +/*****************************************************************************/ +int +sesexec_start(struct pre_session_item *psi) +{ + // Local socket pair used to set up the EICP channel for sesexec + // We also use the socket pair to communicate the PID of sesexec back + // to sesman. Technically we only need this if USE_BSD_SETLOGIN + // is set, but removing this variable complicates the code so + // much it isn't worth it. + int sck[2] = {-1, -1}; + int rv = -1; + int size; + const char *exe = SESEXEC_LONGNAME; + struct list *args = NULL; + + if (!g_executable_exist(exe)) + { + LOG(LOG_LEVEL_ERROR, "Can't execute %s", exe); + } + else if ((args = create_exec_args()) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory running sesexec"); + } + else if (g_sck_local_socketpair(sck) < 0) + { + LOG(LOG_LEVEL_ERROR, "Can't create sesexec EICP socket [%s]", + g_get_strerror()); + } + else + { + int pid = g_fork(); + if (pid == -1) + { + // Error already logged + g_file_close(sck[0]); + g_file_close(sck[1]); + } + else if (pid == 0) + { + /* Sesexec process */ + g_file_close(sck[0]); + +#if USE_BSD_SETLOGIN + if (g_fork() != 0) + { + g_exit(0); + } +#endif + // Send our pid back to sesman + pid = g_getpid(); + size = g_file_write(sck[1], (const char *)&pid, sizeof(pid)); + + if (size != sizeof(pid)) + { + LOG(LOG_LEVEL_ERROR, "Can't write to PID socket [%s]", + g_get_strerror()); + } + else + { + /* Put the number of the file descriptor in EICP_FD + * in the environment */ + char buff[64]; + g_snprintf(buff, sizeof(buff), "%d", sck[1]); + g_setenv("EICP_FD", buff, 1); + + /* [Development] Log all file descriptors not marked cloexec + * other than stdin, stdout, stderr, and the EICP fd in sck[1]. + */ + if (sck[1] < 3) + { + // EICP fd has overwritten one of the standard descriptors + LOG_DEVEL_LEAKING_FDS("xrdp-sesexec", 3, -1); + } + else + { + LOG_DEVEL_LEAKING_FDS("xrdp-sesexec", 3, sck[1]); + LOG_DEVEL_LEAKING_FDS("xrdp-sesexec", sck[1] + 1, -1); + } + + g_execvp_list(exe, args); + + // Shouldn't get here. Errors are logged if we do. + } + g_exit(1); + } + else + { + g_file_close(sck[1]); + + // Get the PID from the child (or the grancdchild) + int size = g_file_read(sck[0], (char *)&pid, sizeof(pid)); + + if (size != sizeof(pid)) + { + LOG(LOG_LEVEL_ERROR, "Can't read PID of sesexec process [%s]", + g_get_strerror()); + g_file_close(sck[0]); + } + else + { + struct trans *t = eicp_init_trans_from_fd(sck[0], + TRANS_TYPE_CLIENT, + sesman_is_term); + if (t == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't create sesexec transport [%s]", + g_get_strerror()); + g_file_close(sck[0]); + } + else + { + t->trans_data_in = sesman_eicp_data_in; + t->callback_data = (void *)psi; + psi->sesexec_trans = t; + psi->sesexec_pid = pid; + rv = 0; + } + } + } + } + + list_delete(args); + return rv; +} diff --git a/sesman/sesexec_control.h b/sesman/sesexec_control.h new file mode 100644 index 00000000..5359f1fd --- /dev/null +++ b/sesman/sesexec_control.h @@ -0,0 +1,49 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) 2023 Matt Burt + * + * 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_control.h + * @brief Start/stop session executive process + * @author Matt Burt + * + */ + + +#ifndef SESEXEC_H +#define SESEXEC_H + +#include + +struct trans; +struct pre_session_item; + +/** + * Start a session executive + * @param psi Pre-session item to allocate EICP transport to + * @result 0 for success + * + * If non-zero is returned, all errors have been logged. + * If zero is returned, the sesexec_trans and sesexec_pid fields of + * the pre-session-item have been initialised. + */ + +int +sesexec_start(struct pre_session_item *psi); + +#endif // SESEXEC_H From 8e291846d5c2dcbcb8fadb0f372bb51a5bf54061 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Wed, 29 Mar 2023 15:26:38 +0100 Subject: [PATCH 15/22] Create pre-session list This is made from the old sesman_con structure. It describes a connection to sesman which is not yet running a session. --- sesman/Makefile.am | 2 + sesman/pre_session_list.c | 261 ++++++++++++++++++++++++++++++++++++++ sesman/pre_session_list.h | 148 +++++++++++++++++++++ 3 files changed, 411 insertions(+) create mode 100644 sesman/pre_session_list.c create mode 100644 sesman/pre_session_list.h diff --git a/sesman/Makefile.am b/sesman/Makefile.am index 83101983..a2d64f00 100644 --- a/sesman/Makefile.am +++ b/sesman/Makefile.am @@ -20,6 +20,8 @@ xrdp_sesman_SOURCES = \ env.h \ lock_uds.c \ lock_uds.h \ + pre_session_list.c \ + pre_session_list.h \ scp_process.c \ scp_process.h \ sesman.c \ diff --git a/sesman/pre_session_list.c b/sesman/pre_session_list.c new file mode 100644 index 00000000..ebc15af6 --- /dev/null +++ b/sesman/pre_session_list.c @@ -0,0 +1,261 @@ +/** + * 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 pre_session_list.h + * @brief List of pre-session connections to sesman (definitions) + * + * @author Matt Burt + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + + +#include "arch.h" +#include "list.h" +#include "os_calls.h" +#include "pre_session_list.h" +#include "trans.h" + +#define PRE_SESSION_IN_USE(si) \ + ( \ + (si) != NULL && \ + ( \ + ((si)->client_trans != NULL && (si)->client_trans->status == TRANS_STATUS_UP) || \ + ((si)->sesexec_trans != NULL && (si)->sesexec_trans->status == TRANS_STATUS_UP) \ + ) \ + ) + +static struct list *g_pre_session_list = NULL; + +/** + * Deletes a pre_session_item, freeing resources + * + * After this call, the passed-in pointer is invalid and must not be + * referenced. + * + * Any auth_info struct found in the sesman_con is also deallocated. + * + * @param sc struct to de-allocate + */ +static void +free_pre_session_item(struct pre_session_item *psi) +{ + if (psi != NULL) + { + trans_delete(psi->client_trans); + trans_delete(psi->sesexec_trans); + g_free(psi->username); + g_free(psi); + } +} + +/******************************************************************************/ +int +pre_session_list_init(unsigned int list_size) +{ + int rv = 1; + if (g_pre_session_list == NULL) + { + g_pre_session_list = list_create_sized(list_size); + } + + if (g_pre_session_list == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't allocate pre-session list"); + } + else + { + g_pre_session_list->auto_free = 0; + rv = 0; + } + + return rv; +} + +/******************************************************************************/ +void +pre_session_list_cleanup(void) +{ + if (g_pre_session_list != NULL) + { + int i; + for (i = 0 ; i < g_pre_session_list->count ; ++i) + { + struct pre_session_item *p; + p = (struct pre_session_item *)list_get_item(g_pre_session_list, i); + free_pre_session_item(p); + } + list_delete(g_pre_session_list); + g_pre_session_list = NULL; + } +} + +/******************************************************************************/ +unsigned int +pre_session_list_get_count(void) +{ + return g_pre_session_list->count; +} + +/******************************************************************************/ +struct pre_session_item * +pre_session_list_new(void) +{ + struct pre_session_item *result = g_new0(struct pre_session_item, 1); + if (result != NULL) + { + g_snprintf(result->peername, sizeof(result->peername), "unknown"); + result->uid = (uid_t) -1; + + if (!list_add_item(g_pre_session_list, (tintptr)result)) + { + g_free(result); + result = NULL; + } + } + + return result; +} + +/*****************************************************************************/ +int +pre_session_list_set_peername(struct pre_session_item *psi, const char *name) +{ + int rv = 1; + + if (psi != NULL && name != NULL) + { + g_snprintf(psi->peername, sizeof(psi->peername), "%s", name); + rv = 0; + } + + return rv; +} + +/******************************************************************************/ +int +pre_session_list_get_wait_objs(tbus robjs[], int *robjs_count) +{ + int i = 0; + + while (i < g_pre_session_list->count) + { + struct pre_session_item *psi; + psi = (struct pre_session_item *)list_get_item(g_pre_session_list, i); + int psi_in_use = 0; + + if (psi != NULL) + { + if (psi->client_trans != NULL && + psi->client_trans->status == TRANS_STATUS_UP) + { + robjs[(*robjs_count)++] = psi->client_trans->sck; + psi_in_use = 1; + } + + if (psi->sesexec_trans != NULL && + psi->sesexec_trans->status == TRANS_STATUS_UP) + { + robjs[(*robjs_count)++] = psi->sesexec_trans->sck; + psi_in_use = 1; + } + } + + if (psi_in_use) + { + ++i; + } + else + { + free_pre_session_item(psi); + list_remove_item(g_pre_session_list, i); + } + } + + return 0; +} + +/******************************************************************************/ +int +pre_session_list_check_wait_objs(void) +{ + int i = 0; + + while (i < g_pre_session_list->count) + { + struct pre_session_item *psi; + enum pre_session_dispatcher_action action; + + psi = (struct pre_session_item *)list_get_item(g_pre_session_list, i); + action = E_PSD_TERMINATE_PRE_SESSION; + + if (PRE_SESSION_IN_USE(psi)) + { + if (psi->client_trans != NULL && + psi->client_trans->status == TRANS_STATUS_UP) + { + if (trans_check_wait_objs(psi->client_trans) != 0) + { + LOG(LOG_LEVEL_ERROR, "pre_session_list_check_wait_objs: " + "trans_check_wait_objs(1) failed, removing trans"); + psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; + } + } + + if (psi->sesexec_trans != NULL && + psi->sesexec_trans->status == TRANS_STATUS_UP) + { + if (trans_check_wait_objs(psi->sesexec_trans) != 0) + { + LOG(LOG_LEVEL_ERROR, "pre_session_list_check_wait_objs: " + "trans_check_wait_objs(2) failed, removing trans"); + psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; + } + } + + /* Get any action, and reset the requested one */ + action = psi->dispatcher_action; + psi->dispatcher_action = E_PSD_NONE; + } + + switch (action) + { + case E_PSD_NONE: + /* On to the next item on the list */ + ++i; + break; + + case E_PSD_REMOVE_CLIENT_TRANS: + trans_delete(psi->client_trans); + psi->client_trans = NULL; + /* On to the next item on the list */ + ++i; + break; + case E_PSD_TERMINATE_PRE_SESSION: + free_pre_session_item(psi); + list_remove_item(g_pre_session_list, i); + break; + } + } + + return 0; +} diff --git a/sesman/pre_session_list.h b/sesman/pre_session_list.h new file mode 100644 index 00000000..5f217731 --- /dev/null +++ b/sesman/pre_session_list.h @@ -0,0 +1,148 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2013 + * + * 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 pre_session_list.h + * @brief List of pre-session connections to sesman (declarations) + * + * Items on this list are moved to the session list once they have + * authenticated and a session is started. + * + * @author Matt Burt + * + */ + +#ifndef PRE_SESSION_LIST_H +#define PRE_SESSION_LIST_H + +#include + +#include "xrdp_constants.h" + +/** + * Type describing the login state of a pre-session item + */ +enum ps_login_state +{ + E_PS_LOGIN_NOT_LOGGED_IN = 0, + E_PS_LOGIN_SYS, + E_PS_LOGIN_UDS +}; + +/** + * Action we require the dispatcher to do for us + * + * We can't do some things in an SCP or EICP callback, so we have to + * ask the dispatcher to do them. For example, we can't delete the + * client_trans as the callback stack won't be expecting this. + */ +enum pre_session_dispatcher_action +{ + E_PSD_NONE = 0, + E_PSD_REMOVE_CLIENT_TRANS, + E_PSD_TERMINATE_PRE_SESSION +}; + +/** + * Type for managing sesman connections from SCP clients (xrdp, etc) + * and any sesexec processes we've created for them. + */ +struct pre_session_item +{ + struct trans *client_trans; ///< SCP link to sesman client + struct trans *sesexec_trans; ///< ECP link to sesexec + pid_t sesexec_pid; ///< PID of sesexec (if sesexec is active) + char peername[15 + 1]; ///< Name of peer, if known, for logging + enum ps_login_state login_state; ///< Login state + /** + * Any action which a callback requires the dispatcher to + * do out of scope of the callback */ + enum pre_session_dispatcher_action dispatcher_action; + uid_t uid; ///< User + char *username; ///< Username from UID (at time of logon) + char start_ip_addr[MAX_PEER_ADDRSTRLEN]; +}; + + +/** + * Initialise the module + * @param list_size Number of pre-session items allowed + * @return 0 for success + * + * Errors are logged + */ +int +pre_session_list_init(unsigned int list_size); + +/** + * Clean up the module on program exit + */ +void +pre_session_list_cleanup(void); + +/** + * Returns the number of items on the pre-session list + * @return Item count + */ +unsigned int +pre_session_list_get_count(void); + +/** + * Allocates a new pre-session item on the list + * + * @return pointer to new pre-session object or NULL for no memory + * + * After allocating the session, you must initialise the sesexec_trans field + * with a valid transport. + * + * The session is removed by pre_session_list_get_wait_objs() or + * pre_session_check_wait_objs() when the client + * transport goes down (or wasn't allocated in the first place). + */ +struct pre_session_item * +pre_session_list_new(void); + +/** + * Set the peername of a pre-session + * + * @param psi pre-session-item + * @param name Name to set + * @result 0 for success + */ +int +pre_session_list_set_peername(struct pre_session_item *psi, const char *name); + +/** + * @brief Get the wait objs for the pre-session list module + * @param @robjs Objects array to update + * @param robjs_count Elements in robjs (by reference) + * @return 0 for success + */ +int +pre_session_list_get_wait_objs(tbus robjs[], int *robjs_count); + + +/** + * @brief Check the wait objs for the pre-session list module + * @return 0 for success + */ +int +pre_session_list_check_wait_objs(void); + +#endif // PRE_SESSION_LIST_H From 9c2c43693c4c7a731a1848bf28f1048d8c32aaac Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 23 Mar 2023 11:27:28 +0000 Subject: [PATCH 16/22] Move files from sesman to sesexec directory --- sesman/{ => sesexec}/env.c | 0 sesman/{ => sesexec}/env.h | 0 sesman/{ => sesexec}/session.c | 0 sesman/{ => sesexec}/session.h | 0 sesman/{ => sesexec}/xauth.c | 0 sesman/{ => sesexec}/xauth.h | 0 sesman/{ => sesexec}/xwait.c | 0 sesman/{ => sesexec}/xwait.h | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename sesman/{ => sesexec}/env.c (100%) rename sesman/{ => sesexec}/env.h (100%) rename sesman/{ => sesexec}/session.c (100%) rename sesman/{ => sesexec}/session.h (100%) rename sesman/{ => sesexec}/xauth.c (100%) rename sesman/{ => sesexec}/xauth.h (100%) rename sesman/{ => sesexec}/xwait.c (100%) rename sesman/{ => sesexec}/xwait.h (100%) diff --git a/sesman/env.c b/sesman/sesexec/env.c similarity index 100% rename from sesman/env.c rename to sesman/sesexec/env.c diff --git a/sesman/env.h b/sesman/sesexec/env.h similarity index 100% rename from sesman/env.h rename to sesman/sesexec/env.h diff --git a/sesman/session.c b/sesman/sesexec/session.c similarity index 100% rename from sesman/session.c rename to sesman/sesexec/session.c diff --git a/sesman/session.h b/sesman/sesexec/session.h similarity index 100% rename from sesman/session.h rename to sesman/sesexec/session.h diff --git a/sesman/xauth.c b/sesman/sesexec/xauth.c similarity index 100% rename from sesman/xauth.c rename to sesman/sesexec/xauth.c diff --git a/sesman/xauth.h b/sesman/sesexec/xauth.h similarity index 100% rename from sesman/xauth.h rename to sesman/sesexec/xauth.h diff --git a/sesman/xwait.c b/sesman/sesexec/xwait.c similarity index 100% rename from sesman/xwait.c rename to sesman/sesexec/xwait.c diff --git a/sesman/xwait.h b/sesman/sesexec/xwait.h similarity index 100% rename from sesman/xwait.h rename to sesman/sesexec/xwait.h From 3895954b757912cbed5365af50b00640d641e615 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 23 Mar 2023 12:50:14 +0000 Subject: [PATCH 17/22] Add libipm interfaces to sesman Add modules to sesman to handle incoming EICP and ERCP messages --- sesman/eicp_process.c | 111 ++++++++++++++++++++++++++++++++++++++++++ sesman/eicp_process.h | 41 ++++++++++++++++ sesman/ercp_process.c | 102 ++++++++++++++++++++++++++++++++++++++ sesman/ercp_process.h | 41 ++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 sesman/eicp_process.c create mode 100644 sesman/eicp_process.h create mode 100644 sesman/ercp_process.c create mode 100644 sesman/ercp_process.h diff --git a/sesman/eicp_process.c b/sesman/eicp_process.c new file mode 100644 index 00000000..6705f7e9 --- /dev/null +++ b/sesman/eicp_process.c @@ -0,0 +1,111 @@ +/** + * 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_process.c + * @brief eicp (executive initialisation control protocol) handler function + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include "trans.h" + +#include "eicp.h" +#include "eicp_process.h" +#include "os_calls.h" +#include "pre_session_list.h" +#include "scp.h" +#include "sesman.h" + +/******************************************************************************/ + +static int +process_sys_login_response(struct pre_session_item *psi) +{ + int rv; + int is_logged_in; + uid_t uid; + int scp_fd; + + rv = eicp_get_sys_login_response(psi->sesexec_trans, &is_logged_in, + &uid, &scp_fd); + if (rv == 0) + { + LOG(LOG_LEVEL_INFO, "Received sys login status for %s : %s", + psi->username, + (is_logged_in) ? "logged in" : "not logged in"); + + if (!is_logged_in) + { + // This shouldn't happen. Close the connection to the + // client immediately. + psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; + } + else + { + /* We've been handed back the client connection */ + psi->client_trans = scp_init_trans_from_fd(scp_fd, + TRANS_TYPE_SERVER, + sesman_is_term); + if (psi->client_trans == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't re-create client connection"); + g_file_close(scp_fd); + psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; + } + else + { + psi->client_trans->trans_data_in = sesman_scp_data_in; + psi->client_trans->callback_data = (void *)psi; + psi->login_state = E_PS_LOGIN_SYS; + psi->uid = uid; + } + } + } + + return rv; +} + +/******************************************************************************/ +int +eicp_process(struct pre_session_item *psi) +{ + enum eicp_msg_code msgno; + int rv = 0; + + switch ((msgno = eicp_msg_in_get_msgno(psi->sesexec_trans))) + { + case E_EICP_SYS_LOGIN_RESPONSE: + rv = process_sys_login_response(psi); + break; + + default: + { + char buff[64]; + eicp_msgno_to_str(msgno, buff, sizeof(buff)); + LOG(LOG_LEVEL_ERROR, "Ignored EICP message %s", buff); + } + } + return rv; +} + diff --git a/sesman/eicp_process.h b/sesman/eicp_process.h new file mode 100644 index 00000000..8bb3cd30 --- /dev/null +++ b/sesman/eicp_process.h @@ -0,0 +1,41 @@ +/** + * 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_process.h + * @brief eicp (executive initialisation control protocol) handler function + * @author Matt Burt + * + */ + +#ifndef EICP_PROCESS_H +#define EICP_PROCESS_H + +struct pre_session_item; + +/** + * + * @brief Processes an EICP message + * @param sc the sesman connection + * + */ +int +eicp_process(struct pre_session_item *psi); + +#endif diff --git a/sesman/ercp_process.c b/sesman/ercp_process.c new file mode 100644 index 00000000..4ac95990 --- /dev/null +++ b/sesman/ercp_process.c @@ -0,0 +1,102 @@ +/** + * 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 ercp_process.c + * @brief ERCP (executive run-time control protocol) handler function + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include + +#include "trans.h" + +#include "ercp.h" +#include "ercp_process.h" +#include "session_list.h" + +/******************************************************************************/ +static int +process_session_announce_event(struct session_item *si) +{ + int rv; + const char *start_ip_addr; + + rv = ercp_get_session_announce_event(si->sesexec_trans, + NULL, + &si->uid, + &si->type, + &si->start_width, + &si->start_height, + &si->bpp, + &si->guid, + &start_ip_addr, + &si->start_time); + if (rv == 0) + { + snprintf(si->start_ip_addr, sizeof(si->start_ip_addr), + "%s", start_ip_addr); + si->state = E_SESSION_RUNNING; + } + + return rv; +} + +/******************************************************************************/ +static void +process_session_finished_event(struct session_item *si) +{ + LOG(LOG_LEVEL_INFO, "Session on display %d has finished.", + si->display); + // Setting the transport down will remove this connection from the list + si->sesexec_trans->status = TRANS_STATUS_DOWN; +} + +/******************************************************************************/ +int +ercp_process(struct session_item *si) +{ + enum ercp_msg_code msgno; + int rv = 0; + + switch ((msgno = ercp_msg_in_get_msgno(si->sesexec_trans))) + { + case E_ERCP_SESSION_ANNOUNCE_EVENT: + rv = process_session_announce_event(si); + break; + + case E_ERCP_SESSION_FINISHED_EVENT: + process_session_finished_event(si); + break; + + default: + { + char buff[64]; + ercp_msgno_to_str(msgno, buff, sizeof(buff)); + LOG(LOG_LEVEL_ERROR, "Ignored EICP message %s", buff); + } + } + return rv; +} + diff --git a/sesman/ercp_process.h b/sesman/ercp_process.h new file mode 100644 index 00000000..d6593aad --- /dev/null +++ b/sesman/ercp_process.h @@ -0,0 +1,41 @@ +/** + * 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 ercp_process.h + * @brief ERCP (executive run-time control protocol) handler function + * @author Matt Burt + * + */ + +#ifndef ERCP_PROCESS_H +#define ERCP_PROCESS_H + +struct session_item; + +/** + * + * @brief Processes an ERCP message + * @param sc the sesman connection + * + */ +int +ercp_process(struct session_item *si); + +#endif // ERCP_PROCESS_H From 74cd7d18376cca6c2249cf57b65f2ab9f8ba5d0e Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:32:00 +0100 Subject: [PATCH 18/22] Rework sesman with new files --- sesman/Makefile.am | 14 +- sesman/scp_process.c | 685 ++++++++++++++++++------------------------ sesman/scp_process.h | 4 +- sesman/sesman.c | 336 ++++++++++----------- sesman/sesman.h | 71 ++--- sesman/session_list.c | 188 +++++------- sesman/session_list.h | 120 ++++---- sesman/sig.c | 21 -- sesman/sig.h | 8 - 9 files changed, 615 insertions(+), 832 deletions(-) diff --git a/sesman/Makefile.am b/sesman/Makefile.am index a2d64f00..ab097910 100644 --- a/sesman/Makefile.am +++ b/sesman/Makefile.am @@ -16,8 +16,10 @@ sbin_PROGRAMS = \ xrdp-sesman xrdp_sesman_SOURCES = \ - env.c \ - env.h \ + eicp_process.c \ + eicp_process.h \ + ercp_process.c \ + ercp_process.h \ lock_uds.c \ lock_uds.h \ pre_session_list.c \ @@ -28,16 +30,10 @@ xrdp_sesman_SOURCES = \ sesman.h \ sesexec_control.c \ sesexec_control.h \ - session.c \ - session.h \ session_list.c \ session_list.h \ sig.c \ - sig.h \ - xauth.c \ - xauth.h \ - xwait.c \ - xwait.h + sig.h xrdp_sesman_LDADD = \ $(top_builddir)/sesman/libsesman/libsesman.la \ diff --git a/sesman/scp_process.c b/sesman/scp_process.c index 0d69f810..f2d0b976 100644 --- a/sesman/scp_process.c +++ b/sesman/scp_process.c @@ -30,168 +30,37 @@ #include "trans.h" #include "os_calls.h" +#include "eicp.h" +#include "ercp.h" #include "scp.h" -#include "sesman_config.h" #include "scp_process.h" +#include "sesman.h" #include "sesman_access.h" #include "sesman_auth.h" -#include "guid.h" +#include "sesman_config.h" #include "os_calls.h" +#include "pre_session_list.h" #include "session_list.h" -#include "session.h" -#include "sesman.h" +#include "sesexec_control.h" #include "string_calls.h" -/**************************************************************************//** - * Logs an authentication failure message - * - * @param username Username - * @param ip_addr IP address, if known - * - * The message is intended for use by fail2ban. Make changes with care. - */ -static void -log_authfail_message(const char *username, const char *ip_addr) -{ - if (ip_addr == NULL || ip_addr[0] == '\0') - { - ip_addr = "unknown"; - } - LOG(LOG_LEVEL_INFO, "AUTHFAIL: user=%s ip=%s time=%d", - username, ip_addr, g_time1()); -} - -/******************************************************************************/ - -/** - * Mode parameter for authenticate_and_authorize_connection() - */ -enum login_mode -{ - AM_SYSTEM, - AM_UDS -}; - -/** - * Authenticate and authorize the connection - * - * @param sc Connection to sesman - * @param login_mode Describes the type of login in use - * @param uid UID for user - * @param username Name for user - * @param password Password (AM_SYSTEM) or NULL. - * @param ip_addr Remote IP address (AM_SYSTEM) or NULL. - * @return Status for the operation - * - * @pre sc->auth_info, sc->username and sc->ip_addr must be NULL - * - * @post If E_SCP_LOGIN_OK is returned, sc->auth_info is non-NULL - * @post If E_SCP_LOGIN_OK is returned, sc->username is non-NULL - * @post If E_SCP_LOGIN_OK is returned, sc->ip_addr is non-NULL - * - */ -static enum scp_login_status -authenticate_and_authorize_connection(struct sesman_con *sc, - enum login_mode login_mode, - int uid, - const char *username, - const char *password, - const char *ip_addr) -{ - enum scp_login_status status = E_SCP_LOGIN_GENERAL_ERROR; - struct auth_info *auth_info = NULL; - - /* Check preconditions */ - if (sc->auth_info != NULL || sc->username != NULL || sc->ip_addr != NULL) - { - LOG(LOG_LEVEL_ERROR, - "Internal error - connection already logged in"); - } - else - { - switch (login_mode) - { - case AM_SYSTEM: - auth_info = auth_userpass(username, password, ip_addr, &status); - break; - case AM_UDS: - auth_info = auth_uds(username, &status); - break; - default: - LOG(LOG_LEVEL_ERROR, "%s called with invalid mode %d", - __func__, (int)login_mode); - } - - if (auth_info != NULL) - { - if (status != E_SCP_LOGIN_OK) - { - /* This shouldn't happen */ - LOG(LOG_LEVEL_ERROR, - "Unexpected status return %d from auth call", - (int)status); - } - else if (!access_login_allowed(&g_cfg->sec, username)) - { - status = E_SCP_LOGIN_NOT_AUTHORIZED; - LOG(LOG_LEVEL_INFO, "Username okay but group problem for " - "user: %s", username); - } - - /* If all is well, put the auth_info in the sesman connection - * for later use. If not, remove the auth_info */ - if (status == E_SCP_LOGIN_OK) - { - char *dup_username = g_strdup(username); - char *dup_ip_addr = - (ip_addr == NULL) ? g_strdup("") : g_strdup(ip_addr); - - if (dup_username == NULL || dup_ip_addr == NULL) - { - LOG(LOG_LEVEL_ERROR, "%s : Memory allocation failed", - __func__); - g_free(dup_username); - g_free(dup_ip_addr); - status = E_SCP_LOGIN_NO_MEMORY; - } - else - { - LOG(LOG_LEVEL_INFO, "Access permitted for user=%s uid=%d", - username, uid); - sc->auth_info = auth_info; - sc->uid = uid; - sc->username = dup_username; - sc->ip_addr = dup_ip_addr; - } - } - - if (status != E_SCP_LOGIN_OK) - { - auth_end(auth_info); - } - } - } - - return status; -} - /******************************************************************************/ static int -process_set_peername_request(struct sesman_con *sc) +process_set_peername_request(struct pre_session_item *psi) { int rv; const char *peername; - rv = scp_get_set_peername_request(sc->t, &peername); + rv = scp_get_set_peername_request(psi->client_trans, &peername); if (rv == 0) { - if (sesman_set_connection_peername(sc, peername) != 0) + if (pre_session_list_set_peername(psi, peername) != 0) { LOG(LOG_LEVEL_WARNING, "Failed to set connection peername from %s to %s", - sc->peername, peername); + psi->peername, peername); } } @@ -199,68 +68,142 @@ process_set_peername_request(struct sesman_con *sc) } /******************************************************************************/ -/** - * Allocates a chain item and starts the session - */ -static enum scp_screate_status -allocate_and_start_session(struct auth_info *auth_info, - const char *username, - const char *ip_addr, - const struct session_parameters *params) +static int +process_sys_login_request(struct pre_session_item *psi) { - int pid = 0; - enum scp_screate_status status; - struct session_item *si; + int rv; + const char *username; + const char *password; + const char *ip_addr; + int send_client_reply = 1; - /* check to limit concurrent sessions */ - if (session_list_get_count() >= (unsigned int)g_cfg->sess.max_sessions) + rv = scp_get_sys_login_request(psi->client_trans, &username, + &password, &ip_addr); + if (rv == 0) { - LOG(LOG_LEVEL_ERROR, "max concurrent session limit " - "exceeded. login for user %s denied", username); - return E_SCP_SCREATE_MAX_REACHED; - } + enum scp_login_status errorcode; - si = session_new(); - if (si == NULL) - { - LOG(LOG_LEVEL_ERROR, "Out of memory error: cannot create new session " - "element - user %s", username); - return E_SCP_SCREATE_NO_MEMORY; - } + LOG(LOG_LEVEL_INFO, + "Received system login request from %s for user: %s IP: %s", + psi->peername, username, ip_addr); - status = session_start(auth_info, params, &pid); - if (status == E_SCP_SCREATE_OK) - { - if (ip_addr[0] != '\0') + if (psi->login_state != E_PS_LOGIN_NOT_LOGGED_IN) { - LOG(LOG_LEVEL_INFO, "++ created session: username %s, ip %s", - username, ip_addr); + errorcode = E_SCP_LOGIN_ALREADY_LOGGED_IN; + LOG(LOG_LEVEL_ERROR, "Connection is already logged in for %s", + psi->username); + } + else if ((psi->username = g_strdup(username)) == NULL) + { + errorcode = E_SCP_LOGIN_NO_MEMORY; + LOG(LOG_LEVEL_ERROR, "Memory allocation failure logging in %s", + username); } else { - LOG(LOG_LEVEL_INFO, "++ created session: username %s", username); + /* Create a sesexec process to handle the login + * + * We won't check for the user being valid here, as this might + * lead to information leakage */ + if (sesexec_start(psi) != 0) + { + LOG(LOG_LEVEL_ERROR, + "Can't start sesexec to authenticate user"); + errorcode = E_SCP_LOGIN_GENERAL_ERROR; + } + else + { + int eicp_stat; + eicp_stat = eicp_send_sys_login_request(psi->sesexec_trans, + username, + password, + ip_addr, + psi->client_trans->sck); + if (eicp_stat != 0) + { + LOG(LOG_LEVEL_ERROR, + "Can't ask sesexec to authenticate user"); + errorcode = E_SCP_LOGIN_GENERAL_ERROR; + } + else + { + /* We've handed over responsibility for the + * SCP communication */ + send_client_reply = 0; + psi->dispatcher_action = E_PSD_REMOVE_CLIENT_TRANS; + } + } } - si->pid = pid; - si->display = params->display; - si->width = params->width; - si->height = params->height; - si->bpp = params->bpp; - si->auth_info = auth_info; - g_strncpy(si->start_ip_addr, ip_addr, - sizeof(si->start_ip_addr) - 1); - si->uid = params->uid; - si->guid = params->guid; - - si->start_time = g_time1(); - - si->type = params->type; - si->status = SESMAN_SESSION_STATUS_ACTIVE; + if (send_client_reply) + { + /* We only get here if something has gone + * wrong with the handover to sesexec */ + rv = scp_send_login_response(psi->client_trans, errorcode, 1); + psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; + } } - else + + return rv; +} + +/******************************************************************************/ + +/** + * Authenticate and authorize a UDS connection + * + * @param psi Connection to sesman + * @param uid UID for user + * @param username Name for user + * @return Status for the operation + * + * @post If E_SCP_LOGIN_OK is returned, psi->username is non-NULL + */ +static enum scp_login_status +authenticate_and_authorize_uds_connection(struct pre_session_item *psi, + int uid, + const char *username) +{ + enum scp_login_status status = E_SCP_LOGIN_GENERAL_ERROR; + struct auth_info *auth_info = auth_uds(username, &status); + if (auth_info != NULL) { - // Remove session item from the list - session_list_kill(-1); + if (status != E_SCP_LOGIN_OK) + { + /* This shouldn't happen */ + LOG(LOG_LEVEL_ERROR, + "Unexpected status return %d from auth_uds call", + (int)status); + } + else if (!access_login_allowed(&g_cfg->sec, username)) + { + status = E_SCP_LOGIN_NOT_AUTHORIZED; + LOG(LOG_LEVEL_INFO, "Username okay but group problem for " + "user: %s", username); + } + + /* If all is well, add info to the sesman connection for later use */ + if (status == E_SCP_LOGIN_OK) + { + if ((psi->username = g_strdup(username)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "%s : Memory allocation failed", + __func__); + g_free(psi->username); + psi->username = NULL; + status = E_SCP_LOGIN_NO_MEMORY; + } + else + { + LOG(LOG_LEVEL_INFO, "Access permitted for user: %s", + username); + psi->login_state = E_PS_LOGIN_UDS; + psi->uid = uid; + psi->start_ip_addr[0] = '\0'; + } + } + + auth_end(auth_info); } return status; @@ -269,101 +212,7 @@ allocate_and_start_session(struct auth_info *auth_info, /******************************************************************************/ static int -process_sys_login_request(struct sesman_con *sc) -{ - int rv; - const char *supplied_username; - const char *password; - const char *ip_addr; - - rv = scp_get_sys_login_request(sc->t, &supplied_username, - &password, &ip_addr); - if (rv == 0) - { - enum scp_login_status errorcode; - int server_closed = 1; - int uid; - char *username = NULL; - - LOG(LOG_LEVEL_INFO, - "Received system login request from %s for user: %s IP: %s", - sc->peername, supplied_username, ip_addr); - - if (sc->auth_info != NULL) - { - errorcode = E_SCP_LOGIN_ALREADY_LOGGED_IN; - LOG(LOG_LEVEL_ERROR, "Connection is already logged in for %s", - sc->username); - } - else if (g_getuser_info_by_name(supplied_username, - &uid, NULL, NULL, NULL, NULL) != 0) - { - /* we can't get a UID for the user */ - errorcode = E_SCP_LOGIN_NOT_AUTHENTICATED; - LOG(LOG_LEVEL_ERROR, "Can't get UID for user %s", - supplied_username); - log_authfail_message(username, ip_addr); - } - else if (g_getuser_info_by_uid(uid, - &username, NULL, NULL, NULL, NULL) != 0) - { - errorcode = E_SCP_LOGIN_GENERAL_ERROR; - LOG(LOG_LEVEL_ERROR, "Can't reverse lookup UID %d", uid); - } - else - { - if (g_strcmp(supplied_username, username) != 0) - { - /* - * If using a federated naming service (e.g. AD), the - * username supplied may not match that name mapped to by - * the UID. We will generate a warning in this instance so - * the user can see what is being used within sesman - */ - LOG(LOG_LEVEL_WARNING, - "Using username %s for the session (from UID %d)", - username, uid); - } - - errorcode = authenticate_and_authorize_connection( - sc, AM_SYSTEM, - uid, username, - password, ip_addr); - if (errorcode == E_SCP_LOGIN_OK) - { - server_closed = 0; - } - else if (errorcode == E_SCP_LOGIN_NOT_AUTHENTICATED) - { - log_authfail_message(username, ip_addr); - if (sc->auth_retry_count > 0) - { - /* Password problem? Invite the user to retry */ - server_closed = 0; - --sc->auth_retry_count; - } - } - - g_free(username); - } - - if (server_closed) - { - /* Expecting no more client messages. Close the connection - * after returning from this callback */ - sc->close_requested = 1; - } - - rv = scp_send_login_response(sc->t, errorcode, server_closed); - } - - return rv; -} - -/******************************************************************************/ - -static int -process_uds_login_request(struct sesman_con *sc) +process_uds_login_request(struct pre_session_item *psi) { enum scp_login_status errorcode; int rv; @@ -372,25 +221,25 @@ process_uds_login_request(struct sesman_con *sc) char *username = NULL; int server_closed = 1; - rv = g_sck_get_peer_cred(sc->t->sck, &pid, &uid, NULL); + rv = g_sck_get_peer_cred(psi->client_trans->sck, &pid, &uid, NULL); if (rv != 0) { LOG(LOG_LEVEL_INFO, "Unable to get peer credentials for socket %d", - (int)sc->t->sck); + (int)psi->client_trans->sck); errorcode = E_SCP_LOGIN_GENERAL_ERROR; } else { LOG(LOG_LEVEL_INFO, "Received UDS login request from %s for UID: %d from PID: %d", - sc->peername, uid, pid); + psi->peername, uid, pid); - if (sc->auth_info != NULL) + if (psi->login_state != E_PS_LOGIN_NOT_LOGGED_IN) { errorcode = E_SCP_LOGIN_ALREADY_LOGGED_IN; LOG(LOG_LEVEL_ERROR, "Connection is already logged in for %s", - sc->username); + psi->username); } else if (g_getuser_info_by_uid(uid, &username, NULL, NULL, NULL, NULL) != 0) @@ -400,10 +249,8 @@ process_uds_login_request(struct sesman_con *sc) } else { - errorcode = authenticate_and_authorize_connection( - sc, AM_UDS, - uid, username, - NULL, NULL); + errorcode = authenticate_and_authorize_uds_connection( + psi, uid, username); g_free(username); if (errorcode == E_SCP_LOGIN_OK) @@ -416,27 +263,40 @@ process_uds_login_request(struct sesman_con *sc) if (server_closed) { /* Close the connection after returning from this callback */ - sc->close_requested = 1; + psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; } - return scp_send_login_response(sc->t, errorcode, server_closed); + return scp_send_login_response(psi->client_trans, errorcode, server_closed); +} + +/******************************************************************************/ + +static void +logout_pre_session(struct pre_session_item *psi) +{ + if (psi->login_state != E_PS_LOGIN_NOT_LOGGED_IN) + { + (void)eicp_send_logout_request(psi->sesexec_trans); + trans_delete(psi->sesexec_trans); + psi->sesexec_trans = NULL; + psi->uid = (uid_t) -1; + g_free(psi->username); + psi->username = NULL; + psi->start_ip_addr[0] = '\0'; + + psi->login_state = E_PS_LOGIN_NOT_LOGGED_IN; + } } /******************************************************************************/ static int -process_logout_request(struct sesman_con *sc) +process_logout_request(struct pre_session_item *psi) { - if (sc->auth_info != NULL) + if (psi->login_state != E_PS_LOGIN_NOT_LOGGED_IN) { - LOG(LOG_LEVEL_INFO, "Logging out %s from sesman", sc->username); - auth_end(sc->auth_info); - sc->auth_info = NULL; - sc->uid = -1; - g_free(sc->username); - sc->username = NULL; - g_free(sc->ip_addr); - sc->ip_addr = NULL; + LOG(LOG_LEVEL_INFO, "Logging out %s from sesman", psi->username); + logout_pre_session(psi); } return 0; @@ -445,117 +305,146 @@ process_logout_request(struct sesman_con *sc) /******************************************************************************/ static int -process_create_session_request(struct sesman_con *sc) +process_create_session_request(struct pre_session_item *psi) { int rv; - // Parameters for a new session (if required). Filled in as - // we go along. - struct session_parameters sp = {0}; - const char *shellptr; - const char *dirptr; + /* Client parameters describing new session*/ + enum scp_session_type type; + unsigned short width; + unsigned short height; + unsigned char bpp; + const char *shell; + const char *directory; + + struct guid guid; + int display = 0; + struct session_item *s_item = NULL; + int send_client_reply = 1; + enum scp_screate_status status = E_SCP_SCREATE_OK; - int display = 0; - struct guid guid; - - guid_clear(&guid); - - rv = scp_get_create_session_request(sc->t, - &sp.type, &sp.width, &sp.height, - &sp.bpp, &shellptr, &dirptr); + rv = scp_get_create_session_request(psi->client_trans, + &type, &width, &height, + &bpp, &shell, &directory); if (rv == 0) { - if (sc->auth_info == NULL) + if (psi->login_state == E_PS_LOGIN_NOT_LOGGED_IN) { status = E_SCP_SCREATE_NOT_LOGGED_IN; } else { LOG(LOG_LEVEL_INFO, - "Received request from %s to create a session for user %s" - " type=%s" - " geometry=%dx%d, bpp=%d, shell=\"%s\", dir=\"%s\"", - sc->peername, sc->username, - SCP_SESSION_TYPE_TO_STR(sp.type), - sp.width, sp.height, sp.bpp, shellptr, dirptr); + "Received request from %s to create a session for user %s", + psi->peername, psi->username); - struct session_item *s_item = - session_list_get_bydata(sc->uid, sp.type, sp.width, sp.height, - sp.bpp, sc->ip_addr); - if (s_item != 0) + s_item = session_list_get_bydata(psi->uid, type, width, height, + bpp, psi->start_ip_addr); + if (s_item != NULL) { // Found an existing session - if (sc->ip_addr[0] != '\0') + display = s_item->display; + guid = s_item->guid; + + // Tell the existing session to run the reconnect script. + // We ignore errors at this level, as any comms errors + // will be picked up in the main loop + (void)ercp_send_session_reconnect_event(s_item->sesexec_trans); + + if (psi->start_ip_addr[0] != '\0') { LOG( LOG_LEVEL_INFO, "++ reconnected session: username %s, " "display :%d.0, session_pid %d, ip %s", - sc->username, s_item->display, s_item->pid, - sc->ip_addr); + psi->username, display, + s_item->sesexec_pid, psi->start_ip_addr); } else { LOG(LOG_LEVEL_INFO, "++ reconnected session: username %s, " "display :%d.0, session_pid %d", - sc->username, s_item->display, s_item->pid); + psi->username, display, s_item->sesexec_pid); } - // Get values for response to SCP client - display = s_item->display; - guid = s_item->guid; - - session_reconnect(s_item->display, sc->uid, sc->auth_info); + // If we created an authentication process for this SCP + // connection, close it gracefully + logout_pre_session(psi); + } + // Need to create a new session + else if (g_cfg->sess.max_sessions > 0 && + session_list_get_count() >= g_cfg->sess.max_sessions) + { + status = E_SCP_SCREATE_MAX_REACHED; + } + else if ((display = session_list_get_available_display()) < 0) + { + status = E_SCP_SCREATE_NO_DISPLAY; + } + // Create an entry on the session list for the new session + else if ((s_item = session_list_new()) == NULL) + { + status = E_SCP_SCREATE_NO_MEMORY; + } + // Create a sesexec process if we don't have one (UDS login) + else if (psi->sesexec_trans == NULL && sesexec_start(psi) != 0) + { + LOG(LOG_LEVEL_ERROR, + "Can't start sesexec to authenticate user"); + status = E_SCP_SCREATE_GENERAL_ERROR; } else { - // Need to create a new session - // - // Get the rest of the parameters for the session - guid = guid_new(); - display = session_list_get_available_display(); + // Pass the session create request to sesexec + int eicp_stat; + eicp_stat = eicp_send_create_session_request( + psi->sesexec_trans, + psi->client_trans->sck, + display, + type, width, height, + bpp, shell, directory); - sp.display = display; - sp.uid = sc->uid; - sp.guid = guid; - // These need to be copied so they are available - // when the sub-process closes all the connections - g_snprintf(sp.shell, sizeof(sp.shell), "%s", shellptr); - g_snprintf(sp.directory, sizeof(sp.directory), "%s", dirptr); - - if (display == 0) + if (eicp_stat != 0) { - status = E_SCP_SCREATE_NO_DISPLAY; + LOG(LOG_LEVEL_ERROR, + "Can't ask sesexec to authenticate user"); + status = E_SCP_SCREATE_GENERAL_ERROR; } else { - // The new session will have a lifetime longer than - // the sesman connection, and so needs to own - // the auth_info struct. - // - // Copy the auth_info struct out of the connection and pass - // it to the session - struct auth_info *auth_info = sc->auth_info; - sc->auth_info = NULL; + // We've handed over responsibility for the + // SCP communication + send_client_reply = 0; - status = allocate_and_start_session(auth_info, - sc->username, - sc->ip_addr, - &sp); - if (status != E_SCP_SCREATE_OK) - { - // Close the auth session down as it can't be re-used. - auth_end(auth_info); - } + // Further comms from sesexec comes over the ERCP + // protocol + ercp_trans_from_eicp_trans(psi->sesexec_trans, + sesman_ercp_data_in, + (void *)s_item); + + // Move the transport over to the session list item + s_item->sesexec_trans = psi->sesexec_trans; + s_item->sesexec_pid = psi->sesexec_pid; + psi->sesexec_trans = NULL; + psi->sesexec_pid = 0; + + // Add the display to the session item so we don't try + // to allocate it to another session + s_item->display = display; } } } - /* Currently a create session request is the last thing on a - * connection, and results in automatic closure */ - sc->close_requested = 1; - - rv = scp_send_create_session_response(sc->t, status, - display, &guid); + // Currently a create session request is the last thing on a + // connection, and results in automatic closure + // + // We may have passed the client_trans over to sesexec. If so, + // we can't send a reply here. + psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; + if (send_client_reply) + { + rv = scp_send_create_session_response(psi->client_trans, + status, display, &guid); + } } return rv; @@ -564,7 +453,7 @@ process_create_session_request(struct sesman_con *sc) /******************************************************************************/ static int -process_list_sessions_request(struct sesman_con *sc) +process_list_sessions_request(struct pre_session_item *psi) { int rv = 0; @@ -572,9 +461,9 @@ process_list_sessions_request(struct sesman_con *sc) unsigned int cnt = 0; unsigned int i; - if (sc->auth_info == NULL) + if (psi->login_state == E_PS_LOGIN_NOT_LOGGED_IN) { - rv = scp_send_list_sessions_response(sc->t, + rv = scp_send_list_sessions_response(psi->client_trans, E_SCP_LS_NOT_LOGGED_IN, NULL); } @@ -582,14 +471,13 @@ process_list_sessions_request(struct sesman_con *sc) { LOG(LOG_LEVEL_INFO, "Received request from %s to list sessions for user %s", - sc->peername, sc->username); + psi->peername, psi->username); - info = session_list_get_byuid(sc->uid, &cnt, - SESMAN_SESSION_STATUS_ALL); + info = session_list_get_byuid(psi->uid, &cnt, 0); for (i = 0; rv == 0 && i < cnt; ++i) { - rv = scp_send_list_sessions_response(sc->t, + rv = scp_send_list_sessions_response(psi->client_trans, E_SCP_LS_SESSION_INFO, &info[i]); } @@ -597,7 +485,7 @@ process_list_sessions_request(struct sesman_con *sc) if (rv == 0) { - rv = scp_send_list_sessions_response(sc->t, + rv = scp_send_list_sessions_response(psi->client_trans, E_SCP_LS_END_OF_LIST, NULL); } @@ -609,54 +497,54 @@ process_list_sessions_request(struct sesman_con *sc) /******************************************************************************/ static int -process_close_connection_request(struct sesman_con *sc) +process_close_connection_request(struct pre_session_item *psi) { int rv = 0; LOG(LOG_LEVEL_INFO, "Received request to close connection from %s", - sc->peername); + psi->peername); /* Expecting no more client messages. Close the connection * after returning from this callback */ - sc->close_requested = 1; + psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; return rv; } /******************************************************************************/ int -scp_process(struct sesman_con *sc) +scp_process(struct pre_session_item *psi) { enum scp_msg_code msgno; int rv = 0; - switch ((msgno = scp_msg_in_get_msgno(sc->t))) + switch ((msgno = scp_msg_in_get_msgno(psi->client_trans))) { case E_SCP_SET_PEERNAME_REQUEST: - rv = process_set_peername_request(sc); + rv = process_set_peername_request(psi); break; case E_SCP_SYS_LOGIN_REQUEST: - rv = process_sys_login_request(sc); + rv = process_sys_login_request(psi); break; case E_SCP_UDS_LOGIN_REQUEST: - rv = process_uds_login_request(sc); + rv = process_uds_login_request(psi); break; case E_SCP_LOGOUT_REQUEST: - rv = process_logout_request(sc); + rv = process_logout_request(psi); break; case E_SCP_CREATE_SESSION_REQUEST: - rv = process_create_session_request(sc); + rv = process_create_session_request(psi); break; case E_SCP_LIST_SESSIONS_REQUEST: - rv = process_list_sessions_request(sc); + rv = process_list_sessions_request(psi); break; case E_SCP_CLOSE_CONNECTION_REQUEST: - rv = process_close_connection_request(sc); + rv = process_close_connection_request(psi); break; default: @@ -668,3 +556,4 @@ scp_process(struct sesman_con *sc) } return rv; } + diff --git a/sesman/scp_process.h b/sesman/scp_process.h index f53875c2..9305bfa3 100644 --- a/sesman/scp_process.h +++ b/sesman/scp_process.h @@ -27,7 +27,7 @@ #ifndef SCP_PROCESS_H #define SCP_PROCESS_H -struct sesman_con; +struct pre_session_item; /** * @@ -36,6 +36,6 @@ struct sesman_con; * */ int -scp_process(struct sesman_con *sc); +scp_process(struct pre_session_item *sc); #endif diff --git a/sesman/sesman.c b/sesman/sesman.c index 8b122dc4..312574c8 100644 --- a/sesman/sesman.c +++ b/sesman/sesman.c @@ -35,23 +35,26 @@ #include "sesman_auth.h" #include "sesman_config.h" +#include "eicp.h" +#include "eicp_process.h" +#include "ercp.h" +#include "ercp_process.h" +#include "pre_session_list.h" #include "session_list.h" #include "lock_uds.h" #include "os_calls.h" #include "scp.h" #include "scp_process.h" +#include "sesexec_control.h" #include "sig.h" #include "string_calls.h" #include "trans.h" #include "xrdp_configure_options.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 + * Maximum number of pre-session items */ -#define MAX_SHORT_LIVED_CONNECTIONS 16 +#define MAX_PRE_SESSION_ITEMS 16 /** * Define the mode of operation of the program @@ -74,7 +77,6 @@ struct sesman_startup_params }; struct config_sesman *g_cfg; -unsigned char g_fixedkey[8] = { 23, 82, 107, 6, 35, 78, 88, 7 }; static tintptr g_term_event = 0; static tintptr g_sigchld_event = 0; static tintptr g_reload_event = 0; @@ -114,68 +116,6 @@ static int nocase_matches(const char *candidate, ...) 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_new0(struct sesman_con, 1)) != NULL) - { - g_snprintf(result->peername, sizeof(result->peername), "%s", "unknown"); - result->t = t; - result->auth_retry_count = g_cfg->sec.login_retry; - } - - return result; -} - -/** - * Deletes a sesman_con struct, freeing resources - * - * After this call, the passed-in pointer is invalid and must not be - * referenced. - * - * Any auth_info struct found in the sesman_con is also deallocated. - * - * @param sc struct to de-allocate - */ -static void -delete_connection(struct sesman_con *sc) -{ - if (sc != NULL) - { - trans_delete(sc->t); - if (sc->auth_info != NULL) - { - auth_end(sc->auth_info); - } - g_free(sc->username); - g_free(sc->ip_addr); - g_free(sc); - } -} - -/*****************************************************************************/ -int -sesman_set_connection_peername(struct sesman_con *sc, const char *name) -{ - int rv = 1; - - if (sc != NULL && name != NULL) - { - g_snprintf(sc->peername, sizeof(sc->peername), "%s", name); - rv = 0; - } - - return rv; -} - /*****************************************************************************/ /** * @@ -301,40 +241,25 @@ static int sesman_listen_test(struct config_sesman *cfg) /******************************************************************************/ int -sesman_close_all(unsigned int flags) +sesman_close_all(void) { - int index; - struct sesman_con *sc; - LOG_DEVEL(LOG_LEVEL_TRACE, "sesman_close_all:"); + pre_session_list_cleanup(); + session_list_cleanup(); + + g_delete_wait_obj(g_reload_event); + g_delete_wait_obj(g_sigchld_event); + g_delete_wait_obj(g_term_event); sesman_delete_listening_transport(); - for (index = 0; index < g_con_list->count; index++) - { - sc = (struct sesman_con *) list_get_item(g_con_list, index); - if (sc != NULL && (flags & SCA_CLOSE_AUTH_INFO) == 0) - { - // Prevent delete_connection() closing the auth_info down - sc->auth_info = NULL; - } - delete_connection(sc); - } + return 0; } /******************************************************************************/ -void -sesman_delete_wait_objects(void) -{ - g_delete_wait_obj(g_reload_event); - g_delete_wait_obj(g_sigchld_event); - g_delete_wait_obj(g_term_event); -} - -/******************************************************************************/ -static int -sesman_data_in(struct trans *self) +int +sesman_scp_data_in(struct trans *self) { int rv; int available; @@ -343,8 +268,10 @@ sesman_data_in(struct trans *self) if (rv == 0 && available) { - struct sesman_con *sc = (struct sesman_con *)self->callback_data; - if ((rv = scp_process(sc)) != 0) + struct pre_session_item *psi; + psi = (struct pre_session_item *)self->callback_data; + + if ((rv = scp_process(psi)) != 0) { LOG(LOG_LEVEL_ERROR, "sesman_data_in: scp_process_msg failed"); } @@ -358,30 +285,79 @@ sesman_data_in(struct trans *self) 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) + struct pre_session_item *psi; + if (pre_session_list_get_count() >= MAX_PRE_SESSION_ITEMS) { - LOG(LOG_LEVEL_ERROR, "sesman_data_in: error, too many " + LOG(LOG_LEVEL_ERROR, "sesman_listen_conn_in: error, too many " "connections, rejecting"); trans_delete(new_self); } - else if ((sc = alloc_connection(new_self)) == NULL || - scp_init_trans(new_self) != 0) + else if ((psi = pre_session_list_new()) == NULL) { LOG(LOG_LEVEL_ERROR, "sesman_data_in: No memory to allocate " "new connection"); - delete_connection(sc); + trans_delete(new_self); + } + else if (scp_init_trans(new_self) != 0) + { + LOG(LOG_LEVEL_ERROR, "sesman_data_in: Can't init SCP 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); + new_self->callback_data = (void *)psi; + new_self->trans_data_in = sesman_scp_data_in; + psi->client_trans = new_self; } return 0; } +/******************************************************************************/ +int +sesman_eicp_data_in(struct trans *self) +{ + int rv; + int available; + + rv = eicp_msg_in_check_available(self, &available); + + if (rv == 0 && available) + { + struct pre_session_item *psi; + psi = (struct pre_session_item *)self->callback_data; + if ((rv = eicp_process(psi)) != 0) + { + LOG(LOG_LEVEL_ERROR, "sesman_eicp_data_in: eicp_process_msg failed"); + } + eicp_msg_in_reset(self); + } + + return rv; +} + +/******************************************************************************/ +int +sesman_ercp_data_in(struct trans *self) +{ + int rv; + int available; + + rv = ercp_msg_in_check_available(self, &available); + + if (rv == 0 && available) + { + struct session_item *si = (struct session_item *)self->callback_data; + if ((rv = ercp_process(si)) != 0) + { + LOG(LOG_LEVEL_ERROR, "sesman_ercp_data_in: ercp_process_msg failed"); + } + ercp_msg_in_reset(self); + } + + return rv; +} + /******************************************************************************/ /** * Informs the main loop a termination signal has been received @@ -396,9 +372,21 @@ set_term_event(int sig) } } +/*****************************************************************************/ +/* No-op signal handler. + */ +static void +sig_no_op(int sig) +{ + /* no-op */ +} + /******************************************************************************/ /** - * Informs the main loop a SIGCHLD has been received + * Catch a SIGCHLD and ignore the main loop + * + * In theory we could use waitpid() in the signal handler, but that + * would prevent us adding any logging */ static void set_sigchld_event(int sig) @@ -438,7 +426,7 @@ sesman_delete_listening_transport(void) } g_list_trans = NULL; - unlock_uds(g_list_trans_lock); + unlock_uds(g_list_trans_lock); // Won't unlock anything for a child process g_list_trans_lock = NULL; } @@ -489,6 +477,13 @@ sesman_create_listening_transport(const struct config_sesman *cfg) return rv; } +/******************************************************************************/ +int +sesman_is_term(void) +{ + return g_is_wait_obj_set(g_term_event); +} + /******************************************************************************/ /** * @@ -500,12 +495,7 @@ 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; + intptr_t robjs[1024]; g_con_list = list_create(); if (g_con_list == NULL) @@ -525,47 +515,41 @@ sesman_main_loop(void) 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); + error = trans_get_wait_objs(g_list_trans, robjs, &robjs_count); if (error != 0) { LOG(LOG_LEVEL_ERROR, "sesman_main_loop: " - "trans_get_wait_objs_rw failed"); + "trans_get_wait_objs failed"); break; } } - if (g_obj_wait(robjs, robjs_count, wobjs, wobjs_count, timeout) != 0) + error = pre_session_list_get_wait_objs(robjs, &robjs_count); + if (error != 0) + { + LOG(LOG_LEVEL_ERROR, "sesman_main_loop: " + "pre_session_list_get_wait_objs failed"); + break; + } + + error = session_list_get_wait_objs(robjs, &robjs_count); + if (error != 0) + { + LOG(LOG_LEVEL_ERROR, "sesman_main_loop: " + "session_list_get_wait_objs failed"); + break; + } + + if (g_obj_wait(robjs, robjs_count, NULL, 0, -1) != 0) { /* should not get here */ LOG(LOG_LEVEL_WARNING, "sesman_main_loop: " @@ -580,10 +564,14 @@ sesman_main_loop(void) break; } - if (g_is_wait_obj_set(g_sigchld_event)) /* A child has exited */ + if (g_is_wait_obj_set(g_sigchld_event)) /* term */ { g_reset_wait_obj(g_sigchld_event); - sig_sesman_session_end(); + // Prevent any zombies from hanging around + while (g_waitchild(NULL) > 0) + { + ; + } } if (g_is_wait_obj_set(g_reload_event)) /* We're asked to reload */ @@ -592,33 +580,6 @@ sesman_main_loop(void) sig_sesman_reload_cfg(); } - index = 0; - while (index < g_con_list->count) - { - int remove_con = 0; - scon = (struct sesman_con *)list_get_item(g_con_list, index); - if (trans_check_wait_objs(scon->t) != 0) - { - LOG(LOG_LEVEL_ERROR, "sesman_main_loop: " - "trans_check_wait_objs failed, removing trans"); - remove_con = 1; - } - else if (scon->close_requested) - { - remove_con = 1; - } - - if (remove_con) - { - delete_connection(scon); - list_remove_item(g_con_list, index); - } - else - { - ++index; - } - } - if (g_list_trans != NULL) { error = trans_check_wait_objs(g_list_trans); @@ -629,11 +590,25 @@ sesman_main_loop(void) break; } } + + error = pre_session_list_check_wait_objs(); + if (error != 0) + { + LOG(LOG_LEVEL_ERROR, "sesman_main_loop: " + "pre_session_list_check_wait_objs failed"); + break; + } + + error = session_list_check_wait_objs(); + if (error != 0) + { + LOG(LOG_LEVEL_ERROR, "sesman_main_loop: " + "session_list_check_wait_objs failed"); + break; + } } - sesman_close_all(SCA_CLOSE_AUTH_INFO); - list_delete(g_con_list); - return 0; + return error; } /*****************************************************************************/ @@ -642,7 +617,7 @@ print_version(void) { g_writeln("xrdp-sesman %s", PACKAGE_VERSION); g_writeln(" The xrdp session manager"); - g_writeln(" Copyright (C) 2004-2020 Jay Sorg, " + g_writeln(" Copyright (C) 2004-2023 Jay Sorg, " "Neutrino Labs, and all contributors."); g_writeln(" See https://github.com/neutrinolabs/xrdp for more information."); g_writeln("%s", ""); @@ -715,16 +690,14 @@ main(int argc, char **argv) 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; + startup_params.sesman_ini = DEFAULT_SESMAN_INI; errored_argc = sesman_process_params(argc, argv, &startup_params); if (errored_argc > 0) @@ -922,10 +895,11 @@ main(int argc, char **argv) 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_pipe(sig_no_op); /* SIGPIPE */ g_signal_child_stop(set_sigchld_event); /* SIGCHLD */ + g_signal_hang_up(set_reload_event); /* SIGHUP */ if (daemon) { @@ -967,11 +941,10 @@ main(int argc, char **argv) g_chmod_hex("/tmp/.X11-unix", 0x1777); } - error = session_module_init(); - if (error == 0) + if ((error = pre_session_list_init(MAX_PRE_SESSION_ITEMS)) == 0 && + (error = session_list_init()) == 0) { error = sesman_main_loop(); - session_module_cleanup(); } /* clean up PID file on exit */ @@ -980,12 +953,9 @@ main(int argc, char **argv) g_file_delete(pid_file); } - sesman_delete_wait_objects(); + sesman_close_all(); - if (!daemon) - { - log_end(); - } + log_end(); config_free(g_cfg); g_deinit(); diff --git a/sesman/sesman.h b/sesman/sesman.h index e46fe239..5f7c06d2 100644 --- a/sesman/sesman.h +++ b/sesman/sesman.h @@ -27,33 +27,11 @@ #ifndef SESMAN_H #define SESMAN_H -/** - * Type for managing sesman connections from xrdp (etc) - */ -struct sesman_con -{ - struct trans *t; - char peername[15 + 1]; /* Name of peer, if known, for logging */ - int close_requested; /* Set to close the connection normally */ - unsigned int auth_retry_count; - struct auth_info *auth_info; /* non-NULL for an authenticated connection */ - int uid; /* User */ - char *username; /* Username from UID (at time of logon) */ - char *ip_addr; /* Connecting IP address */ -}; +struct config_sesman; +struct trans; /* Globals */ extern struct config_sesman *g_cfg; -extern unsigned char g_fixedkey[8]; - -/** - * Set the peername of a connection - * - * @param name Name to set - * @result 0 for success - */ -int -sesman_set_connection_peername(struct sesman_con *sc, const char *name); /** * Close all file descriptors used by sesman. @@ -61,24 +39,13 @@ sesman_set_connection_peername(struct sesman_con *sc, const char *name); * This is generally used after forking, to make sure the * file descriptors used by the main process are not disturbed * - * This call will also release all trans and SCP_SESSION objects - * held by sesman - * - * @param flags Set SCA_CLOSE_AUTH_INFO to close any open auth_info - * objects. By default these are not cleared, and should - * only be done so when exiting sesman. + * This call will also :- + * - release all trans objects held by sesman + * - Delete sesman wait objects + * - Call sesman_delete_listening_transport() */ -#define SCA_CLOSE_AUTH_INFO (1<<0) int -sesman_close_all(unsigned int flags); - -/** - * Delete sesman wait objects. - * - * Call after forking so we don't break sesman's wait objects - */ -void -sesman_delete_wait_objects(void); +sesman_close_all(void); /* * Remove the listening transport @@ -96,4 +63,28 @@ sesman_delete_listening_transport(void); int sesman_create_listening_transport(const struct config_sesman *cfg); +/** + * Callback to process incoming SCP data + */ +int +sesman_scp_data_in(struct trans *self); + +/** + * Callback to process incoming EICP data + */ +int +sesman_eicp_data_in(struct trans *self); + +/** + * Callback to process incoming ERCP data + */ +int +sesman_ercp_data_in(struct trans *self); + +/* + * Check for termination + */ +int +sesman_is_term(void); + #endif diff --git a/sesman/session_list.c b/sesman/session_list.c index 25eb8102..36a6c9f9 100644 --- a/sesman/session_list.c +++ b/sesman/session_list.c @@ -33,11 +33,16 @@ #include "config_ac.h" #endif +#ifdef HAVE_SYS_PRCTL_H +#include +#endif + #include "arch.h" #include "session_list.h" +#include "trans.h" -#include "sesman_auth.h" #include "sesman_config.h" +#include "list.h" #include "log.h" #include "os_calls.h" #include "sesman.h" @@ -48,12 +53,12 @@ static struct list *g_session_list = NULL; #define SESSION_IN_USE(si) \ ((si) != NULL && \ - (si)->display >= 0 && \ - (si)->pid > 0) + (si)->sesexec_trans != NULL && \ + (si)->sesexec_trans->status == TRANS_STATUS_UP) /******************************************************************************/ int -session_module_init(void) +session_list_init(void) { int rv = 1; if (g_session_list == NULL) @@ -88,9 +93,9 @@ free_session(struct session_item *si) { if (si != NULL) { - if (si->auth_info != NULL) + if (si->sesexec_trans != NULL) { - auth_end(si->auth_info); + trans_delete(si->sesexec_trans); } g_free(si); } @@ -98,7 +103,7 @@ free_session(struct session_item *si) /******************************************************************************/ void -session_module_cleanup(void) +session_list_cleanup(void) { if (g_session_list != NULL) { @@ -123,13 +128,12 @@ session_list_get_count(void) /******************************************************************************/ struct session_item * -session_new(void) +session_list_new(void) { struct session_item *result = g_new0(struct session_item, 1); if (result != NULL) { - result->pid = -1; - result->display = -1; + result->state = E_SESSION_STARTING; if (!list_add_item(g_session_list, (tintptr)result)) { g_free(result); @@ -401,7 +405,7 @@ session_list_get_bydata(uid_t uid, si, SCP_SESSION_TYPE_TO_STR(si->type), si->uid, si->bpp, - si->width, si->height, + si->start_width, si->start_height, si->start_ip_addr); if (si->type != type) @@ -410,7 +414,7 @@ session_list_get_bydata(uid_t uid, continue; } - if ((policy & SESMAN_CFG_SESS_POLICY_U) && (int)uid != si->uid) + if ((policy & SESMAN_CFG_SESS_POLICY_U) && uid != si->uid) { LOG(LOG_LEVEL_DEBUG, "%s: UID doesn't match for 'U' policy", __func__); @@ -425,7 +429,8 @@ session_list_get_bydata(uid_t uid, } if ((policy & SESMAN_CFG_SESS_POLICY_D) && - (si->width != width || si->height != height)) + (si->start_width != width || + si->start_height != height)) { LOG(LOG_LEVEL_DEBUG, "%s: Dimensions don't match for 'D' policy", __func__); @@ -449,98 +454,9 @@ session_list_get_bydata(uid_t uid, return NULL; } -/******************************************************************************/ -/** - * Convert a UID to a username - * - * @param uid UID - * @param uname pointer to output buffer - * @param uname_len Length of output buffer - * @return 0 for success. - */ -static int -username_from_uid(int uid, char *uname, int uname_len) -{ - char *ustr; - int rv = g_getuser_info_by_uid(uid, &ustr, NULL, NULL, NULL, NULL); - - if (rv == 0) - { - g_snprintf(uname, uname_len, "%s", ustr); - g_free(ustr); - } - else - { - g_snprintf(uname, uname_len, ""); - } - return rv; -} - -/******************************************************************************/ -enum session_kill_status -session_list_kill(int pid) -{ - int i = 0; - enum session_kill_status status = SESMAN_SESSION_KILL_NOTFOUND; - - while (i < g_session_list->count) - { - struct session_item *si; - si = (struct session_item *)list_get_item(g_session_list, i); - if (si->pid == pid) - { - status = SESMAN_SESSION_KILL_OK; - if (pid > 0) - { - char username[256]; - username_from_uid(si->uid, username, sizeof(username)); - - /* Log the deletion */ - if (si->auth_info != NULL) - { - LOG(LOG_LEVEL_INFO, - "Calling auth_end for pid %d from pid %d", - pid, g_getpid()); - } - - LOG(LOG_LEVEL_INFO, - "++ terminated session: UID %d (%s), display :%d.0, " - "session_pid %d, ip %s", - si->uid, username, si->display, - si->pid, si->start_ip_addr); - } - - free_session(si); - } - else - { - ++i; - } - } - - return status; -} - -/******************************************************************************/ -void -session_list_sigkill_all(void) -{ - int i; - - for (i = 0 ; i < g_session_list->count ; ++i) - { - struct session_item *si; - si = (struct session_item *)list_get_item(g_session_list, i); - if (si->pid > 0) - { - g_sigterm(si->pid); - } - } -} - /******************************************************************************/ struct scp_session_info * -session_list_get_byuid(int uid, unsigned int *cnt, unsigned char flags) +session_list_get_byuid(uid_t uid, unsigned int *cnt, unsigned int flags) { int i; struct scp_session_info *sess; @@ -549,11 +465,13 @@ session_list_get_byuid(int uid, unsigned int *cnt, unsigned char flags) count = 0; + LOG(LOG_LEVEL_DEBUG, "searching for session by UID: %d", uid); + for (i = 0 ; i < g_session_list->count ; ++i) { const struct session_item *si; si = (const struct session_item *)list_get_item(g_session_list, i); - if (SESSION_IN_USE(si) && uid == si->uid && (si->status & flags) != 0) + if (SESSION_IN_USE(si) && uid == si->uid) { count++; } @@ -579,13 +497,14 @@ session_list_get_byuid(int uid, unsigned int *cnt, unsigned char flags) { const struct session_item *si; si = (const struct session_item *)list_get_item(g_session_list, i); - if (SESSION_IN_USE(si) && uid == si->uid && (si->status & flags) != 0) + + if (SESSION_IN_USE(si) && uid == si->uid) { - (sess[index]).sid = si->pid; + (sess[index]).sid = si->sesexec_pid; (sess[index]).display = si->display; (sess[index]).type = si->type; - (sess[index]).height = si->height; - (sess[index]).width = si->width; + (sess[index]).height = si->start_height; + (sess[index]).width = si->start_width; (sess[index]).bpp = si->bpp; (sess[index]).start_time = si->start_time; (sess[index]).uid = si->uid; @@ -621,3 +540,56 @@ free_session_info_list(struct scp_session_info *sesslist, unsigned int cnt) g_free(sesslist); } + +/******************************************************************************/ +int +session_list_get_wait_objs(tbus robjs[], int *robjs_count) +{ + int i; + + for (i = 0 ; i < g_session_list->count; ++i) + { + const struct session_item *si; + si = (const struct session_item *)list_get_item(g_session_list, i); + if (SESSION_IN_USE(si)) + { + robjs[(*robjs_count)++] = si->sesexec_trans->sck; + } + } + + return 0; +} + +/******************************************************************************/ +int +session_list_check_wait_objs(void) +{ + int i; + + for (i = 0 ; i < g_session_list->count; ++i) + { + struct session_item *si; + si = (struct session_item *)list_get_item(g_session_list, i); + if (SESSION_IN_USE(si)) + { + if (trans_check_wait_objs(si->sesexec_trans) != 0) + { + LOG(LOG_LEVEL_ERROR, "sesman_check_wait_objs: " + "trans_check_wait_objs failed, removing trans"); + si->sesexec_trans->status = TRANS_STATUS_DOWN; + } + } + + if (SESSION_IN_USE(si)) + { + ++i; + } + else + { + free_session(si); + list_remove_item(g_session_list, i); + } + } + + return 0; +} diff --git a/sesman/session_list.h b/sesman/session_list.h index e32178e2..b018afad 100644 --- a/sesman/session_list.h +++ b/sesman/session_list.h @@ -1,7 +1,7 @@ /** * xrdp: A Remote Desktop Protocol server. * - * Copyright (C) Jay Sorg 2004-2013 + * 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. @@ -28,53 +28,44 @@ #ifndef SESSION_LIST_H #define SESSION_LIST_H -#include +#include #include "guid.h" #include "scp_application_types.h" #include "xrdp_constants.h" -struct session_parameters; - -#define SESMAN_SESSION_STATUS_ACTIVE 0x01 -#define SESMAN_SESSION_STATUS_IDLE 0x02 -#define SESMAN_SESSION_STATUS_DISCONNECTED 0x04 -/* future expansion -#define SESMAN_SESSION_STATUS_REMCONTROL 0x08 -*/ -#define SESMAN_SESSION_STATUS_ALL 0xFF - -enum session_kill_status +enum session_state { - SESMAN_SESSION_KILL_OK = 0, - SESMAN_SESSION_KILL_NOTFOUND + /** + * Session definition is little more than a sesexec process. We're + * waiting for more details of the session from sesexec */ + E_SESSION_STARTING, + /** Session is fully active */ + E_SESSION_RUNNING }; -struct scp_session_info; - /** * Object describing a session + * + * Unless otherwide noted, fields are only valid if + * the status is E_SESSION_RUNNING */ struct session_item { - int uid; /* UID of session */ - int pid; /* pid of sesman waiting for wm to end */ + enum session_state state; + struct trans *sesexec_trans; // trans for sesexec process. Always valid. + pid_t sesexec_pid; // pid for sesexec process. Always valid + /** + * May be valid if known when the session is starting, otherwise -1 */ int display; - int width; - int height; - int bpp; - struct auth_info *auth_info; - - /* status info */ - unsigned char status; + uid_t uid; enum scp_session_type type; - - /* time data */ - time_t start_time; - // struct session_date disconnect_time; // Currently unused - // struct session_date idle_time; // Currently unused - char start_ip_addr[MAX_PEER_ADDRSTRLEN]; + unsigned short start_width; + unsigned short start_height; + unsigned char bpp; struct guid guid; + char start_ip_addr[MAX_PEER_ADDRSTRLEN]; + time_t start_time; }; /** @@ -84,13 +75,13 @@ struct session_item * Errors are logged */ int -session_module_init(void); +session_list_init(void); /** * Clean up the module on program exit */ void -session_module_cleanup(void); +session_list_cleanup(void); /** * Returns the number of sessions currently active @@ -100,24 +91,27 @@ unsigned int session_list_get_count(void); /** - * Allocates a new session + * Allocates a new session on the list * - * The PID and display for the allocated session will be -1 and all other - * fields will be blank + * state will be E_SESSION_STARTING. Other data must be filled in by + * the caller as appropriate. * * @return pointer to new session object or NULL for no memory * - * After allocating the session successfully, you must initialise the - * PID and display fields with valid numbers. + * After allocating the session, you must initialise the sesexec_trans field + * with a valid transport. * - * If you allocate a session and want to remove it due to other problems, - * use session_kill_pid(-1); + * The session is removed by session_check_wait_objs() when the transport + * goes down (or wasn't allocated in the first place). */ struct session_item * -session_new(void); +session_list_new(void); /** * Get the next available display + * + * The display isn't reserved until the caller has allocated a new session + * (with session_list_new()) and put the new display in it. */ int session_list_get_available_display(void); @@ -136,35 +130,18 @@ session_list_get_bydata(uid_t uid, unsigned char bpp, const char *ip_addr); -/** - * - * @brief kills a session - * @param pid the pid of the session to be killed - * @return - * - */ -enum session_kill_status -session_list_kill(int pid); - -/** - * - * @brief sends sigkill to all sessions - * @return - * - */ -void -session_list_sigkill_all(void); - /** * @brief retrieves session descriptions - * @param UID the UID for the descriptions + * @param uid the UID for the descriptions + * @param[out] cnt The number of sessions returned + * @param flags Future expansion * @return A block of session descriptions * * Pass the return result to free_session_info_list() after use * */ struct scp_session_info * -session_list_get_byuid(int uid, unsigned int *cnt, unsigned char flags); +session_list_get_byuid(uid_t uid, unsigned int *cnt, unsigned int flags); /** * @@ -175,4 +152,21 @@ session_list_get_byuid(int uid, unsigned int *cnt, unsigned char flags); void free_session_info_list(struct scp_session_info *sesslist, unsigned int cnt); +/** + * @brief Get the wait objs for the session list module + * @param @robjs Objects array to update + * @param robjs_count Elements in robjs (by reference) + * @return 0 for success + */ +int +session_list_get_wait_objs(tbus robjs[], int *robjs_count); + + +/** + * @brief Check the wait objs for the session list module + * @return 0 for success + */ +int +session_list_check_wait_objs(void); + #endif // SESSION_LIST_H diff --git a/sesman/sig.c b/sesman/sig.c index 12d195a8..8cdd508a 100644 --- a/sesman/sig.c +++ b/sesman/sig.c @@ -95,24 +95,3 @@ sig_sesman_reload_cfg(void) LOG(LOG_LEVEL_INFO, "configuration reloaded, log subsystem restarted"); } - -/******************************************************************************/ -void -sig_sesman_session_end(void) -{ - int pid; - - LOG(LOG_LEVEL_DEBUG, "receiving SIGCHLD"); - do - { - pid = g_waitchild(NULL); - - if (pid > 0) - { - LOG(LOG_LEVEL_INFO, "Process %d has exited", pid); - - session_list_kill(pid); - } - } - while (pid > 0); -} diff --git a/sesman/sig.h b/sesman/sig.h index 447dde63..8bc3d441 100644 --- a/sesman/sig.h +++ b/sesman/sig.h @@ -35,12 +35,4 @@ void sig_sesman_reload_cfg(void); -/** - * - * @brief SIGCHLD handling code - * - */ -void -sig_sesman_session_end(void); - #endif From c5971b535d99725e40330e1b7504320c8ad62745 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:37:57 +0100 Subject: [PATCH 19/22] sesexec: Changes to existing files from sesman env.c : The value of XRDP_SESSION in the environment is now set to the PID of the sesexec process, which ties up the session with the output of "xrdp-sesadmin -c=list". Later versions of xrdp-sesadmin can use this value to get information about the current process. --- sesman/sesexec/env.c | 42 +- sesman/sesexec/session.c | 1108 ++++++++++++++++---------------------- sesman/sesexec/session.h | 92 +++- sesman/sesexec/xwait.c | 3 +- 4 files changed, 573 insertions(+), 672 deletions(-) diff --git a/sesman/sesexec/env.c b/sesman/sesexec/env.c index 5e019299..a7e55659 100644 --- a/sesman/sesexec/env.c +++ b/sesman/sesexec/env.c @@ -35,7 +35,7 @@ #include "list.h" #include "log.h" #include "os_calls.h" -#include "sesman.h" +#include "sesexec.h" #include "ssl_calls.h" #include "string_calls.h" #include "xrdp_sockets.h" @@ -62,10 +62,10 @@ env_check_password_file(const char *filename, const char *passwd) ssl_sha1_transform(sha1, passwd, passwd_bytes); ssl_sha1_complete(sha1, passwd_hash); ssl_sha1_info_delete(sha1); - g_snprintf(passwd_hash_text, 39, "%2.2x%2.2x%2.2x%2.2x", + g_snprintf(passwd_hash_text, sizeof(passwd_hash_text), + "%2.2x%2.2x%2.2x%2.2x", (tui8)passwd_hash[0], (tui8)passwd_hash[1], (tui8)passwd_hash[2], (tui8)passwd_hash[3]); - passwd_hash_text[39] = 0; passwd = passwd_hash_text; /* create file from password */ @@ -143,21 +143,23 @@ env_set_user(int uid, char **passwd_file, int display, g_setenv("SHELL", pw_shell, 1); g_setenv("USER", pw_username, 1); g_setenv("LOGNAME", pw_username, 1); - g_sprintf(text, "%d", uid); + g_snprintf(text, sizeof(text), "%d", uid); g_setenv("UID", text, 1); g_setenv("HOME", pw_dir, 1); g_set_current_dir(pw_dir); - g_sprintf(text, ":%d.0", display); + g_snprintf(text, sizeof(text), ":%d.0", display); g_setenv("DISPLAY", text, 1); - g_setenv("XRDP_SESSION", "1", 1); + // Use our PID as the XRDP_SESSION value + g_snprintf(text, sizeof(text), "%d", g_pid); + g_setenv("XRDP_SESSION", text, 1); /* XRDP_SOCKET_PATH should be set even here. It's used by * xorgxrdp and the pulseaudio plugin */ g_setenv("XRDP_SOCKET_PATH", XRDP_SOCKET_PATH, 1); /* pulse sink socket */ - g_snprintf(text, sizeof(text) - 1, CHANSRV_PORT_OUT_BASE_STR, display); + g_snprintf(text, sizeof(text), CHANSRV_PORT_OUT_BASE_STR, display); g_setenv("XRDP_PULSE_SINK_SOCKET", text, 1); /* pulse source socket */ - g_snprintf(text, sizeof(text) - 1, CHANSRV_PORT_IN_BASE_STR, display); + g_snprintf(text, sizeof(text), CHANSRV_PORT_IN_BASE_STR, display); g_setenv("XRDP_PULSE_SOURCE_SOCKET", text, 1); if ((env_names != 0) && (env_values != 0) && (env_names->count == env_values->count)) @@ -189,29 +191,33 @@ env_set_user(int uid, char **passwd_file, int display, len = g_snprintf(NULL, 0, "%s/.vnc/sesman_passwd-%s@%s:%d", pw_dir, pw_username, hostname, display); + ++len; // Allow for terminator - *passwd_file = (char *) g_malloc(len + 1, 1); + *passwd_file = (char *) g_malloc(len, 1); if (*passwd_file != NULL) { /* Try legacy names first, remove if found */ - g_sprintf(*passwd_file, "%s/.vnc/sesman_%s_passwd:%d", - pw_dir, pw_username, display); + g_snprintf(*passwd_file, len, + "%s/.vnc/sesman_%s_passwd:%d", + pw_dir, pw_username, display); if (g_file_exist(*passwd_file)) { LOG(LOG_LEVEL_WARNING, "Removing old " "password file %s", *passwd_file); g_file_delete(*passwd_file); } - g_sprintf(*passwd_file, "%s/.vnc/sesman_%s_passwd", - pw_dir, pw_username); + g_snprintf(*passwd_file, len, + "%s/.vnc/sesman_%s_passwd", + pw_dir, pw_username); if (g_file_exist(*passwd_file)) { LOG(LOG_LEVEL_WARNING, "Removing insecure " "password file %s", *passwd_file); g_file_delete(*passwd_file); } - g_sprintf(*passwd_file, "%s/.vnc/sesman_passwd-%s@%s:%d", - pw_dir, pw_username, hostname, display); + g_snprintf(*passwd_file, len, + "%s/.vnc/sesman_passwd-%s@%s:%d", + pw_dir, pw_username, hostname, display); } } else @@ -219,10 +225,12 @@ env_set_user(int uid, char **passwd_file, int display, /* we use auth_file_path as requested */ len = g_snprintf(NULL, 0, g_cfg->auth_file_path, pw_username); - *passwd_file = (char *) g_malloc(len + 1, 1); + ++len; // Allow for terminator + *passwd_file = (char *) g_malloc(len, 1); if (*passwd_file != NULL) { - g_sprintf(*passwd_file, g_cfg->auth_file_path, pw_username); + g_snprintf(*passwd_file, len, + g_cfg->auth_file_path, pw_username); } } diff --git a/sesman/sesexec/session.c b/sesman/sesexec/session.c index b2318820..d9a0c54c 100644 --- a/sesman/sesexec/session.c +++ b/sesman/sesexec/session.c @@ -48,8 +48,9 @@ #include "guid.h" #include "list.h" #include "log.h" +#include "login_info.h" #include "os_calls.h" -#include "sesman.h" +#include "sesexec.h" #include "string_calls.h" #include "xauth.h" #include "xwait.h" @@ -59,19 +60,101 @@ #define PR_SET_NO_NEW_PRIVS 38 #endif -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) -#define USE_EXTRA_SESSION_FORK -#define USE_BSD_SETLOGIN +struct session_data +{ + pid_t x_server; ///< PID of X server + pid_t win_mgr; ///< PID of window manager + pid_t chansrv; //< PID of chansrv + time_t start_time; + struct session_parameters params; + // Flexible array member used to store strings in params and ip_addr; +#ifdef __cplusplus + char strings[1]; +#else + char strings[]; +#endif +}; + +/******************************************************************************/ +/** + * Create a new session_data structure from a session_parameters object + * + * @param sp Session parameters passed to session_start() + * @return semi-initialised session_data struct + */ +static struct session_data * +session_data_new(const struct session_parameters *sp) +{ + unsigned int string_length = 0; + // What string length do we need? + string_length += g_strlen(sp->shell) + 1; + string_length += g_strlen(sp->directory) + 1; + + struct session_data *sd = (struct session_data *)g_malloc(sizeof(*sd) + string_length, 0); + + if (sd == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating session data struct"); + } + else + { + sd->win_mgr = -1; + sd->x_server = -1; + sd->chansrv = -1; + sd->start_time = 0; + + /* Copy all the non-string session parameters... */ + sd->params = *sp; + + /* ...and then the strings */ + char *memptr = sd->strings; + +#define COPY_STRING(dest,src) \ + (dest) = memptr; \ + strcpy(memptr, src); \ + memptr += strlen(memptr) + 1 + + COPY_STRING(sd->params.shell, sp->shell); + COPY_STRING(sd->params.directory, sp->directory); + +#undef COPY_STRING + } + + return sd; +} + +/******************************************************************************/ +void +session_data_free(struct session_data *session_data) +{ + if (session_data != NULL) + { +#ifdef USE_DEVEL_LOGGING + if (session_data->win_mgr > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid window manager PID %d", + session_data->win_mgr); + } + if (session_data->x_server > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid X server PID %d", + session_data->x_server); + } + if (session_data->chansrv > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid chansrv PID %d", + session_data->chansrv); + } #endif -/* Module globals */ - -/* Currently, these duplicate module names in sesman.c. This is fine, and - * will be fully resolved when sesman is split into two separate excutables */ -static tintptr g_term_event = 0; -static tintptr g_sigchld_event = 0; -static int g_pid = 0; // PID of sesexec process (sesman sub-process) + free(session_data); + } +} +/******************************************************************************/ /** * Creates a string consisting of all parameters that is hosted in the param list * @param self @@ -108,29 +191,7 @@ dumpItemsToString(struct list *self, char *outstr, int len) /******************************************************************************/ 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); - } -} - -/******************************************************************************/ -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); - } -} - -/******************************************************************************/ -static void -start_chansrv(struct auth_info *auth_info, +start_chansrv(struct login_info *login_info, const struct session_parameters *s) { struct list *chansrv_params = list_create(); @@ -152,7 +213,7 @@ start_chansrv(struct auth_info *auth_info, } else { - env_set_user(s->uid, 0, s->display, + env_set_user(login_info->uid, 0, s->display, g_cfg->env_names, g_cfg->env_values); @@ -166,115 +227,20 @@ start_chansrv(struct auth_info *auth_info, } } -/******************************************************************************/ -static int -cleanup_sockets(int display) -{ - LOG(LOG_LEVEL_INFO, "cleanup_sockets:"); - char file[256]; - int error; - - error = 0; - - g_snprintf(file, 255, CHANSRV_PORT_OUT_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, 255, CHANSRV_PORT_IN_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, 255, XRDP_CHANSRV_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, 255, CHANSRV_API_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - /* the following files should be deleted by xorgxrdp - * but just in case the deletion failed */ - - g_snprintf(file, 255, XRDP_X11RDP_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, 255, XRDP_DISCONNECT_STR, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - return error; - -} - /******************************************************************************/ static void -start_window_manager(struct auth_info *auth_info, +start_window_manager(struct login_info *login_info, const struct session_parameters *s) { char text[256]; - env_set_user(s->uid, + env_set_user(login_info->uid, 0, s->display, g_cfg->env_names, g_cfg->env_values); - auth_set_env(auth_info); + auth_set_env(login_info->auth_info); LOG_DEVEL_LEAKING_FDS("window manager", 3, -1); if (s->directory[0] != '\0') @@ -480,7 +446,7 @@ prepare_xvnc_xserver_params(const struct session_parameters *s, /******************************************************************************/ /* Either execs the X server, or returns */ static void -start_x_server(struct auth_info *auth_info, +start_x_server(struct login_info *login_info, const struct session_parameters *s) { char authfile[256]; /* The filename for storing xauth information */ @@ -491,7 +457,7 @@ start_x_server(struct auth_info *auth_info, if (s->type == SCP_SESSION_TYPE_XVNC) { - env_set_user(s->uid, + env_set_user(login_info->uid, &passwd_file, s->display, g_cfg->env_names, @@ -499,7 +465,7 @@ start_x_server(struct auth_info *auth_info, } else { - env_set_user(s->uid, + env_set_user(login_info->uid, 0, s->display, g_cfg->env_names, @@ -573,30 +539,279 @@ start_x_server(struct auth_info *auth_info, } /******************************************************************************/ -/** - * Convert a UID to a username - * - * @param uid UID - * @param uname pointer to output buffer - * @param uname_len Length of output buffer - * @return 0 for success. - */ +/* + * Simple helper process to fork a child and log errors */ static int -username_from_uid(int uid, char *uname, int uname_len) +fork_child( + void (*runproc)(struct login_info *, const struct session_parameters *), + struct login_info *login_info, + const struct session_parameters *s, + pid_t group_pid) { - char *ustr; - int rv = g_getuser_info_by_uid(uid, &ustr, NULL, NULL, NULL, NULL); - - if (rv == 0) + int pid = g_fork(); + if (pid == 0) { - g_snprintf(uname, uname_len, "%s", ustr); - g_free(ustr); + /* Child process */ + if (group_pid >= 0) + { + (void)g_setpgid(0, group_pid); + } + runproc(login_info, s); + g_exit(0); + } + + if (pid < 0) + { + LOG(LOG_LEVEL_ERROR, "Fork failed [%s]", g_get_strerror()); + } + + return pid; +} + +/******************************************************************************/ +enum scp_screate_status +session_start_wrapped(struct login_info *login_info, + const struct session_parameters *s, + struct session_data *sd) +{ + int chansrv_pid; + int display_pid; + int window_manager_pid; + enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + + auth_start_session(login_info->auth_info, s->display); +#ifdef USE_BSD_SETLOGIN + /** + * Create a new session and process group since the 4.4BSD + * setlogin() affects the entire process group + */ + if (g_setsid() < 0) + { + LOG(LOG_LEVEL_WARNING, + "[session start] (display %d): setsid failed - pid %d", + s->display, g_getpid()); + } + + if (g_setlogin(login_info->username) < 0) + { + LOG(LOG_LEVEL_WARNING, + "[session start] (display %d): setlogin failed for user %s - pid %d", + s->display, login_info->username, g_getpid()); + } +#endif + + /* Set the secondary groups before starting the session to prevent + * problems on PAM-based systems (see Linux pam_setcred(3)). + * If we have *BSD setusercontext() this is not done here */ +#ifndef HAVE_SETUSERCONTEXT + if (g_initgroups(login_info->username) != 0) + { + LOG(LOG_LEVEL_ERROR, + "Failed to initialise secondary groups for %s: %s", + login_info->username, g_get_strerror()); + return E_SCP_SCREATE_GENERAL_ERROR; + } +#endif + + /* start the X server in a new process group. + * + * We group the X server, window manager and chansrv in a single + * process group, as it allows signals to be sent to the user session + * without affecting sesexec (and vice-versa). This is particularly + * important when debugging sesexec as we don't want a SIGINT in + * the debugger to be passed to the children */ + display_pid = fork_child(start_x_server, login_info, s, 0); + if (display_pid > 0) + { + enum xwait_status xws; + xws = wait_for_xserver(login_info->uid, + g_cfg->env_names, + g_cfg->env_values, + s->display); + + if (xws != XW_STATUS_OK) + { + switch (xws) + { + case XW_STATUS_TIMED_OUT: + LOG(LOG_LEVEL_ERROR, "Timed out waiting for X server"); + break; + case XW_STATUS_FAILED_TO_START: + LOG(LOG_LEVEL_ERROR, "X server failed to start"); + break; + default: + LOG(LOG_LEVEL_ERROR, + "An error occurred waiting for the X server"); + } + status = E_SCP_SCREATE_X_SERVER_FAIL; + /* Kill it anyway in case it did start and we just failed to + * pick up on it */ + g_sigterm(display_pid); + g_waitpid(display_pid); + } + else + { + LOG(LOG_LEVEL_INFO, "X server :%d is working", s->display); + LOG(LOG_LEVEL_INFO, "Starting window manager for display :%d", + s->display); + + window_manager_pid = fork_child(start_window_manager, + login_info, s, display_pid); + if (window_manager_pid < 0) + { + g_sigterm(display_pid); + g_waitpid(display_pid); + } + else + { + LOG(LOG_LEVEL_INFO, + "Starting the xrdp channel server for display :%d", + s->display); + + chansrv_pid = fork_child(start_chansrv, login_info, + s, display_pid); + + // Tell the caller we've started + LOG(LOG_LEVEL_INFO, + "Session in progress on display :%d. Waiting until the " + "window manager (pid %d) exits to end the session", + s->display, window_manager_pid); + + sd->win_mgr = window_manager_pid; + sd->x_server = display_pid; + sd->chansrv = chansrv_pid; + sd->start_time = g_time1(); + status = E_SCP_SCREATE_OK; + } + } + } + + return status; +} + + +/******************************************************************************/ +enum scp_screate_status +session_start(struct login_info *login_info, + const struct session_parameters *sp, + struct session_data **session_data) +{ + enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + /* Create the session_data struct first */ + struct session_data *sd = session_data_new(sp); + if (sd == NULL) + { + status = E_SCP_SCREATE_NO_MEMORY; } else { - g_snprintf(uname, uname_len, ""); + status = session_start_wrapped(login_info, sp, sd); + if (status == E_SCP_SCREATE_OK) + { + *session_data = sd; + } + else + { + *session_data = NULL; + session_data_free(sd); + } } - return rv; + + return status; +} + +/******************************************************************************/ +static int +cleanup_sockets(int display) +{ + LOG(LOG_LEVEL_INFO, "cleanup_sockets:"); + char file[256]; + int error; + + error = 0; + + g_snprintf(file, 255, CHANSRV_PORT_OUT_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, 255, CHANSRV_PORT_IN_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, 255, XRDP_CHANSRV_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, 255, CHANSRV_API_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + /* the following files should be deleted by xorgxrdp + * but just in case the deletion failed */ + + g_snprintf(file, 255, XRDP_X11RDP_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, 255, XRDP_DISCONNECT_STR, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + return error; } /******************************************************************************/ @@ -627,527 +842,146 @@ exit_status_to_str(const struct exit_status *e, char buff[], int bufflen) } /******************************************************************************/ -static void -run_xrdp_session(const struct session_parameters *s, - struct auth_info *auth_info, - int window_manager_pid, - int display_pid, - int chansrv_pid) +void +session_process_child_exit(struct session_data *sd, + int pid, + const struct exit_status *e) { - int wm_wait_time; - struct exit_status wm_exit_status = {.reason = E_XR_UNEXPECTED, .val = 0}; - int wm_running = 1; - - /* Monitor the amount of time we wait for the - * window manager. This is approximately how long the window - * manager was running for */ - LOG(LOG_LEVEL_INFO, "Session in progress on display :%d. Waiting " - "until the window manager (pid %d) exits to end the session", - s->display, window_manager_pid); - wm_wait_time = g_time1(); - - /* Wait for the window manager to terminate - * - * We can't use g_waitpid() variants for this, as these aren't - * interruptible by a SIGTERM */ - while (wm_running) + if (pid == sd->x_server) { - int robjs_count; - intptr_t robjs[32]; - int pid; - struct exit_status e; - - robjs_count = 0; - robjs[robjs_count++] = g_term_event; - robjs[robjs_count++] = g_sigchld_event; - - if (g_obj_wait(robjs, robjs_count, NULL, 0, 0) != 0) - { - /* should not get here */ - LOG(LOG_LEVEL_WARNING, "run_xrdp_session: " - "Unexpected error from g_obj_wait()"); - g_sleep(100); - continue; - } - - if (g_is_wait_obj_set(g_term_event)) - { - g_reset_wait_obj(g_term_event); - LOG(LOG_LEVEL_INFO, "Received SIGTERM"); - // Pass it on to the window manager - g_sigterm(window_manager_pid); - } - - // Check for any finished children - g_reset_wait_obj(g_sigchld_event); - while ((pid = g_waitchild(&e)) > 0) - { - if (pid == window_manager_pid) - { - wm_running = 0; - wm_exit_status = e; - } - else if (pid == display_pid) - { - LOG(LOG_LEVEL_INFO, "X server pid %d on display :%d finished", - display_pid, s->display); - display_pid = -1; - // No other action - window manager should be going soon - } - else if (pid == chansrv_pid) - { - LOG(LOG_LEVEL_INFO, - "xrdp channel server pid %d on display :%d finished", - chansrv_pid, s->display); - chansrv_pid = -1; - } - } - } - wm_wait_time = g_time1() - wm_wait_time; - - if (wm_exit_status.reason == E_XR_STATUS_CODE && wm_exit_status.val == 0) - { - LOG(LOG_LEVEL_INFO, - "Window manager (pid %d, display %d) finished normally in %d secs", - window_manager_pid, s->display, wm_wait_time); - } - else - { - char reason[128]; - exit_status_to_str(&wm_exit_status, reason, sizeof(reason)); - - LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " - "exited with %s. This " - "could indicate a window manager config problem", - window_manager_pid, s->display, reason); - } - if (wm_wait_time < 10) - { - /* This could be a config issue. Log a significant error */ - LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " - "exited quickly (%d secs). This could indicate a window " - "manager config problem", - window_manager_pid, s->display, wm_wait_time); - } - - if (display_pid > 0) - { - LOG(LOG_LEVEL_INFO, "Terminating X server (pid %d) on display :%d", - display_pid, s->display); - g_sigterm(display_pid); - } - - if (chansrv_pid > 0) - { - LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) " - "on display :%d", chansrv_pid, s->display); - g_sigterm(chansrv_pid); - } - - /* make sure all children are gone before socket cleanup happens */ - if (display_pid > 0) - { - g_waitpid(display_pid); LOG(LOG_LEVEL_INFO, "X server pid %d on display :%d finished", - display_pid, s->display); + sd->x_server, sd->params.display); + sd->x_server = -1; + // No other action - window manager should be going soon } - - if (chansrv_pid > 0) + else if (pid == sd->chansrv) { - g_waitpid(chansrv_pid); LOG(LOG_LEVEL_INFO, "xrdp channel server pid %d on display :%d finished", - chansrv_pid, s->display); + sd->chansrv, sd->params.display); + sd->chansrv = -1; } - - cleanup_sockets(s->display); - g_deinit(); -} - -/******************************************************************************/ -/* - * Simple helper process to fork a child and log errors */ -static int -fork_child( - void (*runproc)(struct auth_info *, const struct session_parameters *), - struct auth_info *auth_info, - const struct session_parameters *s) -{ - int pid = g_fork(); - if (pid == 0) + else if (pid == sd->win_mgr) { - /* Child process */ - runproc(auth_info, s); - g_exit(0); - } + int wm_wait_time = g_time1() - sd->start_time; - if (pid < 0) - { - LOG(LOG_LEVEL_ERROR, "Fork failed [%s]", g_get_strerror()); - } - - return pid; -} - -/******************************************************************************/ -/** - * Sub-process to start a session - * - * @param auth_info Authentication info - * @param s Session parameters - * @param success_fd File descriptor to write to on success - * - * @return status - * - * This routine returns a status on failure. On success, a character is - * written to the file descriptor to indicate a success, and then the - * routine runs for the lifetime of the session. - */ -enum scp_screate_status -session_start_subprocess(struct auth_info *auth_info, - const struct session_parameters *s, - int success_fd) -{ - char username[256]; - int chansrv_pid; - int display_pid; - int window_manager_pid; - enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; - char text[64]; - - /* Set up wait objects so we can detect signals */ - g_pid = g_getpid(); - g_snprintf(text, sizeof(text), "xrdp_sesexec_%8.8x_main_term", - g_pid); - g_term_event = g_create_wait_obj(text); - g_signal_terminate(set_term_event); - g_snprintf(text, sizeof(text), "xrdp_sesexec_%8.8x_sigchld", - g_pid); - g_sigchld_event = g_create_wait_obj(text); - g_signal_child_stop(set_sigchld_event); - - /* Get the username for display purposes */ - username_from_uid(s->uid, username, sizeof(username)); - -#ifdef USE_BSD_SETLOGIN - /** - * Create a new session and process group since the 4.4BSD - * setlogin() affects the entire process group - */ - if (g_setsid() < 0) - { - LOG(LOG_LEVEL_WARNING, - "[session start] (display %d): setsid failed - pid %d", - s->display, g_getpid()); - } - - if (g_setlogin(username) < 0) - { - LOG(LOG_LEVEL_WARNING, - "[session start] (display %d): setlogin failed for user %s - pid %d", - s->display, username, g_getpid()); - } -#endif - - /* Set the secondary groups before starting the session to prevent - * problems on PAM-based systems (see Linux pam_setcred(3)). - * If we have *BSD setusercontext() this is not done here */ -#ifndef HAVE_SETUSERCONTEXT - if (g_initgroups(username) != 0) - { - LOG(LOG_LEVEL_ERROR, - "Failed to initialise secondary groups for %s: %s", - username, g_get_strerror()); - return E_SCP_SCREATE_GENERAL_ERROR; - } -#endif - - display_pid = fork_child(start_x_server, auth_info, s); - if (display_pid > 0) - { - enum xwait_status xws; - xws = wait_for_xserver(s->uid, - g_cfg->env_names, - g_cfg->env_values, - s->display); - - if (xws != XW_STATUS_OK) + if (e->reason == E_XR_STATUS_CODE && e->val == 0) { - switch (xws) - { - case XW_STATUS_TIMED_OUT: - LOG(LOG_LEVEL_ERROR, "Timed out waiting for X server"); - break; - case XW_STATUS_FAILED_TO_START: - LOG(LOG_LEVEL_ERROR, "X server failed to start"); - break; - default: - LOG(LOG_LEVEL_ERROR, - "An error occurred waiting for the X server"); - } - status = E_SCP_SCREATE_X_SERVER_FAIL; - /* Kill it anyway in case it did start and we just failed to - * pick up on it */ - g_sigterm(display_pid); - g_waitpid(display_pid); + LOG(LOG_LEVEL_INFO, + "Window manager (pid %d, display %d) " + "finished normally in %d secs", + sd->win_mgr, sd->params.display, wm_wait_time); } else { - LOG(LOG_LEVEL_INFO, "X server :%d is working", s->display); - LOG(LOG_LEVEL_INFO, "Starting window manager for display :%d", - s->display); - window_manager_pid = fork_child(start_window_manager, - auth_info, s); - if (window_manager_pid < 0) - { - g_sigterm(display_pid); - g_waitpid(display_pid); - } - else - { - LOG(LOG_LEVEL_INFO, - "Starting the xrdp channel server for display :%d", - s->display); + char reason[128]; + exit_status_to_str(e, reason, sizeof(reason)); - chansrv_pid = fork_child(start_chansrv, auth_info, s); - - // Tell the caller we've started - char zero = 0; - g_file_write(success_fd, &zero, 1); - status = E_SCP_SCREATE_OK; - - /* This call does not return until the session is done */ - run_xrdp_session(s, auth_info, window_manager_pid, - display_pid, chansrv_pid); - } + LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " + "exited with %s. This " + "could indicate a window manager config problem", + sd->win_mgr, sd->params.display, reason); } - } - - return status; -} - -#ifdef USE_EXTRA_SESSION_FORK -/* - * FreeBSD bug - * ports/157282: effective login name is not set by xrdp-sesman - * http://www.freebsd.org/cgi/query-pr.cgi?pr=157282 - * - * from: - * $OpenBSD: session.c,v 1.252 2010/03/07 11:57:13 dtucker Exp $ - * with some ideas about BSD process grouping to xrdp - */ -static int -run_extra_fork(void) -{ - char text[64]; - struct exit_status e; - int stat; - - pid_t bsdsespid = g_fork(); - - if (bsdsespid <= 0) - { - /* Error, or child */ - return bsdsespid; - } - /* - * intermediate sesman should return the status of its own child, and - * kill the child if we get a sigterm - */ - - /* Set up wait objects so we can detect signals */ - g_pid = g_getpid(); - g_snprintf(text, sizeof(text), "xrdp_intermediate_%8.8x_main_term", - g_pid); - g_term_event = g_create_wait_obj(text); - g_signal_terminate(set_term_event); - g_snprintf(text, sizeof(text), "xrdp_intermediate_%8.8x_sigchld", - g_pid); - g_sigchld_event = g_create_wait_obj(text); - g_signal_child_stop(set_sigchld_event); - - // Wait for a SIGCHLD event. We've only got one child so we know where - // it's come from! - while (!g_is_wait_obj_set(g_sigchld_event)) - { - int robjs_count; - intptr_t robjs[4]; - - robjs_count = 0; - robjs[robjs_count++] = g_term_event; - robjs[robjs_count++] = g_sigchld_event; - - if (g_obj_wait(robjs, robjs_count, NULL, 0, 0) != 0) + if (wm_wait_time < 10) { - /* should not get here */ - LOG(LOG_LEVEL_WARNING, "run_extra_fork: " - "Unexpected error from g_obj_wait()"); - g_sleep(100); + /* This could be a config issue. Log a significant error */ + LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " + "exited quickly (%d secs). This could indicate a window " + "manager config problem", + sd->win_mgr, sd->params.display, wm_wait_time); } - else if (g_is_wait_obj_set(g_term_event)) + + sd->win_mgr = -1; + + if (sd->x_server > 0) { - g_reset_wait_obj(g_term_event); - LOG(LOG_LEVEL_INFO, "Received SIGTERM"); - // Pass it on to the BSD session leader - g_sigterm(bsdsespid); + LOG(LOG_LEVEL_INFO, "Terminating X server (pid %d) on display :%d", + sd->x_server, sd->params.display); + g_sigterm(sd->x_server); + } + + if (sd->chansrv > 0) + { + LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) " + "on display :%d", sd->chansrv, sd->params.display); + g_sigterm(sd->chansrv); } } - e = g_waitpid_status(bsdsespid); - stat = (e.reason == E_XR_STATUS_CODE) ? e.val : E_SCP_SCREATE_GENERAL_ERROR; - g_exit(stat); - return -1; + if (!session_active(sd)) + { + cleanup_sockets(sd->params.display); + } } -#endif /******************************************************************************/ -enum scp_screate_status -session_start(struct auth_info *auth_info, - const struct session_parameters *s, - int *pid) +unsigned int +session_active(const struct session_data *sd) { - int fd[2]; - enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + return + (sd == NULL) + ? 0 + : (sd->win_mgr > 0) + (sd->x_server > 0) + (sd->chansrv > 0); +} - if (g_pipe(fd) != 0) +/******************************************************************************/ +time_t +session_get_start_time(const struct session_data *sd) +{ + return (sd == NULL) ? 0 : sd->start_time; +} + +/******************************************************************************/ +void +session_send_term(struct session_data *sd) +{ + if (sd != NULL && sd->win_mgr > 0) { - LOG(LOG_LEVEL_ERROR, "Cant create a pipe [%s]", g_get_strerror()); + g_sigterm(sd->win_mgr); + } +} + +/******************************************************************************/ +static void +start_reconnect_script(struct login_info *login_info, + const struct session_parameters *s) +{ + env_set_user(login_info->uid, 0, s->display, + g_cfg->env_names, + g_cfg->env_values); + + auth_set_env(login_info->auth_info); + + if (g_file_exist(g_cfg->reconnect_sh)) + { + LOG_DEVEL_LEAKING_FDS("reconnect script", 3, -1); + + LOG(LOG_LEVEL_INFO, + "Starting session reconnection script on display %d: %s", + s->display, g_cfg->reconnect_sh); + g_execlp3(g_cfg->reconnect_sh, g_cfg->reconnect_sh, 0); + + /* should not get here */ + LOG(LOG_LEVEL_ERROR, + "Error starting session reconnection script on display %d: %s", + s->display, g_cfg->reconnect_sh); } else { - *pid = g_fork(); - if (*pid == 0) - { - /** - * We're now forked from the main sesman process, so we - * can close file descriptors that we no longer need - * - * Set FD_CLOEXEC on the FD used to send our status back to - * sesman, as our sub-processes shouldn't be able to see it */ - g_file_close(fd[0]); - g_file_set_cloexec(fd[1], 1); - - sesman_close_all(0); - - /* Wait objects created in a parent are not valid in a child */ - sesman_delete_wait_objects(); - - LOG(LOG_LEVEL_INFO, - "calling auth_start_session for uid=%d from pid %d", - s->uid, g_getpid()); - auth_start_session(auth_info, s->display); - - /* Run the child */ -#ifdef USE_EXTRA_SESSION_FORK - if (run_extra_fork() < 0) - { - return E_SCP_SCREATE_GENERAL_ERROR; - } -#endif - status = session_start_subprocess(auth_info, s, fd[1]); - - LOG(LOG_LEVEL_INFO, - "Calling auth_stop_session from pid %d", - g_getpid()); - auth_stop_session(auth_info); - g_exit(status); - } - - g_file_close(fd[1]); - if (*pid == -1) - { - LOG(LOG_LEVEL_ERROR, "Cant fork [%s]", g_get_strerror()); - } - else - { - /* Wait for the child to signal success, or return an error */ - int err; - char buff; - do - { - err = g_file_read(fd[0], &buff, 1); - } - while (err == -1 && g_get_errno() == EINTR); - if (err < 0) - { - /* Read problem */ - LOG(LOG_LEVEL_ERROR, "Can't read pipe [%s]", g_get_strerror()); - } - else if (err > 0) - { - /* Session is up and running */ - status = E_SCP_SCREATE_OK; - } - else - { - /* Process has failed. Get the exit status of the child */ - struct exit_status e; - e = g_waitpid_status(*pid); - if (e.reason == E_XR_STATUS_CODE) - { - status = (enum scp_screate_status)e.val; - } - else - { - char reason[128]; - exit_status_to_str(&e, reason, sizeof(reason)); - LOG(LOG_LEVEL_ERROR, "Child exited with %s", reason); - } - } - } - g_file_close(fd[0]); + LOG(LOG_LEVEL_WARNING, + "Session reconnection script file does not exist: %s", + g_cfg->reconnect_sh); } - - return status; } /******************************************************************************/ -int -session_reconnect(int display, int uid, - struct auth_info *auth_info) +void +session_reconnect(struct login_info *login_info, + struct session_data *sd) { - int pid; - - pid = g_fork(); - - if (pid == -1) + if (fork_child(start_reconnect_script, + login_info, &sd->params, sd->x_server) < 0) { LOG(LOG_LEVEL_ERROR, "Failed to fork for session reconnection script"); } - else if (pid == 0) - { - env_set_user(uid, - 0, - display, - g_cfg->env_names, - g_cfg->env_values); - auth_set_env(auth_info); - - if (g_file_exist(g_cfg->reconnect_sh)) - { - LOG_DEVEL_LEAKING_FDS("reconnect script", 3, -1); - - LOG(LOG_LEVEL_INFO, - "Starting session reconnection script on display %d: %s", - display, g_cfg->reconnect_sh); - g_execlp3(g_cfg->reconnect_sh, g_cfg->reconnect_sh, 0); - - /* should not get here */ - LOG(LOG_LEVEL_ERROR, - "Error starting session reconnection script on display %d: %s", - display, g_cfg->reconnect_sh); - } - else - { - LOG(LOG_LEVEL_WARNING, - "Session reconnection script file does not exist: %s", - g_cfg->reconnect_sh); - } - - /* TODO: why is this existing with a success error code when the - reconnect script failed to be executed? */ - g_exit(0); - } - - return display; } diff --git a/sesman/sesexec/session.h b/sesman/sesexec/session.h index bd79397f..dee9fe36 100644 --- a/sesman/sesexec/session.h +++ b/sesman/sesexec/session.h @@ -34,7 +34,8 @@ #include "scp_application_types.h" #include "xrdp_constants.h" -struct auth_info; +struct login_info; +struct exit_status; /** * Information used to start a session @@ -42,35 +43,94 @@ struct auth_info; struct session_parameters { unsigned int display; - int uid; - struct guid guid; enum scp_session_type type; - unsigned short height; unsigned short width; + unsigned short height; unsigned char bpp; - char shell[INFO_CLIENT_MAX_CB_LEN]; - char directory[INFO_CLIENT_MAX_CB_LEN]; + struct guid guid; + const char *shell; // Must not be NULL + const char *directory; // Must not be NULL }; + +/** + * Data involved in running a session (opaque type) + * + * Allocate with session_start() and free with + * session_data_free() once session_active() returns zero. + */ +struct session_data; + /** * * @brief starts a session * - * @param auth_info Authentication info + * @param login_info info for logged in user * @param s Session parameters - * @param[out] pid PID of sub-process - * @return status + * @param[out] session_data Pointer to session data for the session * - * The returned PID is only valid if the status returned is - * E_SCP_SCREATE_OK + * session_data is only set if E_SCP_CREATE_OK is returned + * @return status */ enum scp_screate_status -session_start(struct auth_info *auth_info, +session_start(struct login_info *login_info, const struct session_parameters *s, - int *pid); + struct session_data **session_data); -int -session_reconnect(int display, int uid, - struct auth_info *auth_info); +/** + * Processes an exited child process + * + * The PID of the child process is removed from the session_data. + * + * @param sd session_data for this session + * @param pid PID of exited process + * @param e Exit status of the exited process + */ +void +session_process_child_exit(struct session_data *sd, + int pid, + const struct exit_status *e); + +/** + * Returns a count of active processes in the session + * + * @param sd session_data for this session + */ +unsigned int +session_active(const struct session_data *sd); + +/** + * Returns the start time for an active session + * + * @param sd session_data for this session + */ +time_t +session_get_start_time(const struct session_data *sd); + +/*** + * Ask a session to terminate by signalling the window manager + * + * @param sd session_data for this session + */ +void +session_send_term(struct session_data *sd); + +/** + * Frees a session_data object + * + * @param sd session_data for this session + * + * Do not call this until session_active() returns zero, or you + * lose the ability to track the session PIDs + */ +void +session_data_free(struct session_data *session_data); + +/** + * Runs the reconnect script for the session + */ +void +session_reconnect(struct login_info *login_info, + struct session_data *sd); #endif // SESSION_H diff --git a/sesman/sesexec/xwait.c b/sesman/sesexec/xwait.c index 659af64d..271804e6 100644 --- a/sesman/sesexec/xwait.c +++ b/sesman/sesexec/xwait.c @@ -103,8 +103,7 @@ wait_for_xserver(uid_t uid, pid_t pid = g_fork(); if (pid < 0) { - LOG(LOG_LEVEL_ERROR, "Can't create pipe : %s", - g_get_strerror()); + // Error already logged } else if (pid == 0) { From 4dcaa84fbe881b9af4de1d17dbe2d895dec3b801 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 23 Mar 2023 12:51:24 +0000 Subject: [PATCH 20/22] Changes to autotools stuff for sesexec --- configure.ac | 1 + sesman/Makefile.am | 1 + waitforx/Makefile.am | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 051f9123..3e3557db 100644 --- a/configure.ac +++ b/configure.ac @@ -588,6 +588,7 @@ AC_CONFIG_FILES([ sesman/libsesman/Makefile sesman/chansrv/Makefile sesman/Makefile + sesman/sesexec/Makefile sesman/tools/Makefile tests/Makefile tests/common/Makefile diff --git a/sesman/Makefile.am b/sesman/Makefile.am index ab097910..542186d8 100644 --- a/sesman/Makefile.am +++ b/sesman/Makefile.am @@ -62,5 +62,6 @@ dist_sesmansysconf_SCRIPTS = \ SUBDIRS = \ libsesman \ + sesexec \ tools \ chansrv diff --git a/waitforx/Makefile.am b/waitforx/Makefile.am index 4adc6ae8..5b96f661 100644 --- a/waitforx/Makefile.am +++ b/waitforx/Makefile.am @@ -4,7 +4,7 @@ pkglibexec_PROGRAMS = \ AM_LDFLAGS = -lX11 -lXrandr AM_CPPFLAGS = \ - -I$(top_srcdir)/sesman \ + -I$(top_srcdir)/sesman/sesexec \ -I$(top_srcdir)/common AM_CFLAGS = $(X_CFLAGS) From ae94891ab73b1cde2e26e1b04f780d5e7725522c Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 23 Mar 2023 20:41:31 +0000 Subject: [PATCH 21/22] Add sesexec to .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1befff83..dedde9c3 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ NEWS *.o README sesman/chansrv/xrdp-chansrv -sesman/sessvc/xrdp-sessvc +sesman/sesexec/xrdp-sesexec sesman/tools/xrdp-authtest sesman/tools/xrdp-dis sesman/tools/xrdp-sesadmin From 8853b1c4eeca0002a066ff7cb622353fcfd01957 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 23 Mar 2023 12:50:40 +0000 Subject: [PATCH 22/22] New files for sesexec --- sesman/sesexec/Makefile.am | 39 +++ sesman/sesexec/eicp_server.c | 201 ++++++++++++++ sesman/sesexec/eicp_server.h | 39 +++ sesman/sesexec/ercp_server.c | 69 +++++ sesman/sesexec/ercp_server.h | 39 +++ sesman/sesexec/login_info.c | 357 ++++++++++++++++++++++++ sesman/sesexec/login_info.h | 94 +++++++ sesman/sesexec/sesexec.c | 521 +++++++++++++++++++++++++++++++++++ sesman/sesexec/sesexec.h | 70 +++++ 9 files changed, 1429 insertions(+) create mode 100644 sesman/sesexec/Makefile.am create mode 100644 sesman/sesexec/eicp_server.c create mode 100644 sesman/sesexec/eicp_server.h create mode 100644 sesman/sesexec/ercp_server.c create mode 100644 sesman/sesexec/ercp_server.h create mode 100644 sesman/sesexec/login_info.c create mode 100644 sesman/sesexec/login_info.h create mode 100644 sesman/sesexec/sesexec.c create mode 100644 sesman/sesexec/sesexec.h diff --git a/sesman/sesexec/Makefile.am b/sesman/sesexec/Makefile.am new file mode 100644 index 00000000..f2bac24a --- /dev/null +++ b/sesman/sesexec/Makefile.am @@ -0,0 +1,39 @@ +AM_CPPFLAGS = \ + -DXRDP_CFG_PATH=\"${sysconfdir}/xrdp\" \ + -DXRDP_SBIN_PATH=\"${sbindir}\" \ + -DXRDP_LIBEXEC_PATH=\"${libexecdir}/xrdp\" \ + -DXRDP_SOCKET_PATH=\"${socketdir}\" \ + -I$(top_srcdir)/sesman/libsesman \ + -I$(top_srcdir)/libipm \ + -I$(top_srcdir)/common + +SESEXEC_EXTRA_LIBS = + +pkglibexec_PROGRAMS = \ + xrdp-sesexec + +xrdp_sesexec_SOURCES = \ + sesexec.c \ + sesexec.h \ + session.c \ + session.h \ + eicp_server.c \ + eicp_server.h \ + ercp_server.c \ + ercp_server.h \ + env.c \ + env.h \ + login_info.c \ + login_info.h \ + xauth.c \ + xauth.h \ + xwait.c \ + xwait.h + +xrdp_sesexec_LDFLAGS = + +xrdp_sesexec_LDADD = \ + $(top_builddir)/sesman/libsesman/libsesman.la \ + $(top_builddir)/libipm/libipm.la \ + $(top_builddir)/common/libcommon.la \ + $(SESEXEC_EXTRA_LIBS) diff --git a/sesman/sesexec/eicp_server.c b/sesman/sesexec/eicp_server.c new file mode 100644 index 00000000..80255fdf --- /dev/null +++ b/sesman/sesexec/eicp_server.c @@ -0,0 +1,201 @@ +/** + * 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 +#endif + +#include "trans.h" + +#include "eicp.h" +#include "eicp_server.h" +#include "login_info.h" +#include "os_calls.h" +#include "ercp.h" +#include "scp.h" +#include "sesexec.h" +#include "session.h" + +/******************************************************************************/ +static int +handle_sys_login_request(struct trans *self) +{ + const char *username; + const char *password; + const char *ip_addr; + int scp_fd; + + int rv = eicp_get_sys_login_request(self, &username, + &password, &ip_addr, &scp_fd); + if (rv == 0) + { + struct trans *scp_trans; + scp_trans = scp_init_trans_from_fd(scp_fd, TRANS_TYPE_SERVER, + sesexec_is_term); + if (scp_trans == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't create SCP trans"); + g_file_close(scp_fd); + rv = 1; + } + else + { + g_login_info = login_info_sys_login_user(scp_trans, username, + password, ip_addr); + + if (g_login_info != NULL) + { + rv = eicp_send_sys_login_response(self, 1, + g_login_info->uid, scp_fd); + } + else + { + rv = eicp_send_sys_login_response(self, 0, (uid_t) -1, 0); + } + + trans_delete(scp_trans); // Closes scp_fd as well + } + } + + return rv; +} + +/******************************************************************************/ +static int +handle_logout_request(struct trans *self) +{ + LOG(LOG_LEVEL_INFO, "xrdp-sesexec pid %d is now logging out", g_pid); + sesexec_terminate_main_loop(0); + return 0; +} + +/******************************************************************************/ +static int +handle_create_session_request(struct trans *self) +{ + int scp_fd; + struct session_parameters sp = {0}; + int rv; + + rv = eicp_get_create_session_request(self, &scp_fd, &sp.display, + &sp.type, &sp.width, &sp.height, + &sp.bpp, &sp.shell, &sp.directory); + if (rv == 0) + { + // Need to talk to the SCP client + struct trans *scp_trans; + scp_trans = scp_init_trans_from_fd(scp_fd, TRANS_TYPE_SERVER, + sesexec_is_term); + if (scp_trans == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't create SCP trans"); + g_file_close(scp_fd); + rv = 1; + } + else + { + enum scp_screate_status scp_status = E_SCP_SCREATE_OK; + + // Use the UID from the SCP connection if the user hasn't + // explicitly logged in + if (g_login_info == NULL && + (g_login_info = login_info_uds_login_user(scp_trans)) == NULL) + { + scp_status = E_SCP_SCREATE_GENERAL_ERROR; + } + + if (scp_status == E_SCP_SCREATE_OK) + { + // Try to create the session + sp.guid = guid_new(); + scp_status = session_start(g_login_info, &sp, &g_session_data); + } + + // Return the status to the SCP client + rv = scp_send_create_session_response(scp_trans, scp_status, + sp.display, &sp.guid); + trans_delete(scp_trans); + + // Further comms from sesexec is sent over the ERCP protocol + ercp_trans_from_eicp_trans(self, + sesexec_ercp_data_in, + (void *)self); + + if (scp_status == E_SCP_SCREATE_OK) + { + rv = ercp_send_session_announce_event( + self, + sp.display, + g_login_info->uid, + sp.type, + sp.width, + sp.height, + sp.bpp, + &sp.guid, + g_login_info->ip_addr, + session_get_start_time(g_session_data)); + } + else + { + rv = ercp_send_session_finished_event(self); + sesexec_terminate_main_loop(1); + } + } + + } + return rv; +} + +/******************************************************************************/ +int +eicp_server(struct trans *self) +{ + int rv = 0; + enum eicp_msg_code msgno; + + switch ((msgno = eicp_msg_in_get_msgno(self))) + { + case E_EICP_SYS_LOGIN_REQUEST: + rv = handle_sys_login_request(self); + break; + + case E_EICP_LOGOUT_REQUEST: + rv = handle_logout_request(self); + break; + + case E_EICP_CREATE_SESSION_REQUEST: + rv = handle_create_session_request(self); + break; + + default: + { + char buff[64]; + eicp_msgno_to_str(msgno, buff, sizeof(buff)); + LOG(LOG_LEVEL_ERROR, "Ignored EICP message %s", buff); + } + } + return rv; +} diff --git a/sesman/sesexec/eicp_server.h b/sesman/sesexec/eicp_server.h new file mode 100644 index 00000000..e2039f18 --- /dev/null +++ b/sesman/sesexec/eicp_server.h @@ -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 eicp_server.h + * @brief eicp (executive initialisation control protocol) server function + * @author Matt Burt + * + */ + +#ifndef EICP_SERVER_H +#define EICP_SERVER_H + +/** + * + * @brief Processes an EICP message + * @param self The EICP transport the message is coming in on + * + */ +int +eicp_server(struct trans *self); + +#endif // EICP_SERVER_H diff --git a/sesman/sesexec/ercp_server.c b/sesman/sesexec/ercp_server.c new file mode 100644 index 00000000..458ab396 --- /dev/null +++ b/sesman/sesexec/ercp_server.c @@ -0,0 +1,69 @@ +/** + * 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 ercp_server.c + * @brief ercp (executive run-time control protocol) server function + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include "arch.h" + +#include "sesexec.h" +#include "session.h" +#include "trans.h" + +#include "ercp.h" +#include "ercp_server.h" + +/******************************************************************************/ +static int +handle_session_reconnect_event(struct trans *self) +{ + session_reconnect(g_login_info, g_session_data); + return 0; +} + +/******************************************************************************/ +int +ercp_server(struct trans *self) +{ + int rv = 0; + enum ercp_msg_code msgno; + + switch ((msgno = ercp_msg_in_get_msgno(self))) + { + case E_ERCP_SESSION_RECONNECT_EVENT: + rv = handle_session_reconnect_event(self); + break; + + default: + { + char buff[64]; + ercp_msgno_to_str(msgno, buff, sizeof(buff)); + LOG(LOG_LEVEL_ERROR, "Ignored ERCP message %s", buff); + } + } + return rv; +} diff --git a/sesman/sesexec/ercp_server.h b/sesman/sesexec/ercp_server.h new file mode 100644 index 00000000..2139ef3e --- /dev/null +++ b/sesman/sesexec/ercp_server.h @@ -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 ercp_server.h + * @brief ercp (executive run-time control protocol) server function + * @author Matt Burt + * + */ + +#ifndef ERCP_SERVER_H +#define ERCP_SERVER_H + +/** + * + * @brief Processes an ERCP message + * @param self The ERCP transport the message is coming in on + * + */ +int +ercp_server(struct trans *self); + +#endif // ERCP_SERVER_H diff --git a/sesman/sesexec/login_info.c b/sesman/sesexec/login_info.c new file mode 100644 index 00000000..a8ccda81 --- /dev/null +++ b/sesman/sesexec/login_info.c @@ -0,0 +1,357 @@ +/** + * 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 login_info.c + * @brief Define functionality associated with user logins for sesexec + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include "login_info.h" + +#include "trans.h" + +#include "sesman_auth.h" +#include "sesman_access.h" +#include "sesman_config.h" +#include "login_info.h" +#include "os_calls.h" +#include "scp.h" +#include "sesexec.h" +#include "string_calls.h" + +/******************************************************************************/ +/** + * Logs an authentication failure message + * + * @param username Username + * @param ip_addr IP address, if known + * + * The message is intended for use by fail2ban. Make changes with care. + */ +static void +log_authfail_message(const char *username, const char *ip_addr) +{ + if (ip_addr == NULL || ip_addr[0] == '\0') + { + ip_addr = "unknown"; + } + LOG(LOG_LEVEL_INFO, "AUTHFAIL: user=%s ip=%s time=%d", + username, ip_addr, g_time1()); +} + +/******************************************************************************/ +/** + * Authenticate and authorize the connection + * + * @param username Name for user + * @param password Password + * @param ip_addr Remote IP address + * @param login_info Structure to fill in for a successful login + * @return Status for the operation + * + * @post If E_SCP_LOGIN_OK is returned, g_login_info is filled in + * + */ +static enum scp_login_status +authenticate_and_authorize_connection(const char *supplied_username, + const char *password, + const char *ip_addr, + struct login_info *login_info) +{ + int uid; + char *username; // From reverse-looking up the UID + enum scp_login_status status; + struct auth_info *auth_info; + + if (g_getuser_info_by_name(supplied_username, + &uid, NULL, NULL, NULL, NULL) != 0) + { + /* we can't get a UID for the user */ + LOG(LOG_LEVEL_ERROR, "Can't get UID for user %s", + supplied_username); + log_authfail_message(supplied_username, ip_addr); + status = E_SCP_LOGIN_NOT_AUTHENTICATED; + } + else if (g_getuser_info_by_uid(uid, + &username, + NULL, NULL, NULL, NULL) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't reverse lookup UID %d", uid); + status = E_SCP_LOGIN_NOT_AUTHENTICATED; + } + else + { + if (g_strcmp(username, supplied_username) != 0) + { + /* + * If using a federated naming service (e.g. AD), the username + * supplied may not match that name mapped to by the UID. We + * will generate a warning in this instance so the user can see + * what is being used + */ + LOG(LOG_LEVEL_WARNING, + "Using username %s for the session (from UID %d)", + username, uid); + } + + auth_info = auth_userpass(username, password, ip_addr, &status); + + /* Sanity check on result of call */ + if ((auth_info != NULL && status != E_SCP_LOGIN_OK) || + (auth_info == NULL && status == E_SCP_LOGIN_OK)) + { + LOG(LOG_LEVEL_ERROR, "Bugcheck; inconsistent auth result. " + "info = %p, status = %d", (void *)auth_info, (int)status); + status = E_SCP_LOGIN_GENERAL_ERROR; + auth_end(auth_info); + auth_info = NULL; + } + + /* Group access allowed? */ + if (status == E_SCP_LOGIN_OK && + !access_login_allowed(&g_cfg->sec, username)) + { + LOG(LOG_LEVEL_INFO, "Username okay but group problem for " + "user: %s", username); + status = E_SCP_LOGIN_NOT_AUTHORIZED; + auth_end(auth_info); + auth_info = NULL; + } + + switch (status) + { + case E_SCP_LOGIN_OK: + { + char *dup_username = g_strdup(username); + char *dup_ip_addr = g_strdup(ip_addr); + + if (dup_username == NULL || dup_ip_addr == NULL) + { + LOG(LOG_LEVEL_ERROR, "%s : Memory allocation failed", + __func__); + g_free(dup_username); + g_free(dup_ip_addr); + status = E_SCP_LOGIN_NO_MEMORY; + auth_end(auth_info); + auth_info = NULL; + } + else + { + LOG(LOG_LEVEL_INFO, "Access permitted for user: %s", + username); + login_info->uid = uid; + login_info->username = dup_username; + login_info->ip_addr = dup_ip_addr; + login_info->auth_info = auth_info; + } + } + break; + + case E_SCP_LOGIN_NOT_AUTHENTICATED: + log_authfail_message(username, ip_addr); + break; + + default: + break; + } + + g_free(username); + } + return status; +} + +/******************************************************************************/ +static int +get_scp_client_retry(struct trans *scp_trans, + const char **username, const char **password, + const char **ip_addr) +{ + int got_message = 0; + + // Wait for an SCP message + enum scp_msg_code msgno; + + scp_msg_in_reset(scp_trans); + + if (scp_msg_in_wait_available(scp_trans) == 0) + { + msgno = scp_msg_in_get_msgno(scp_trans); + switch (msgno) + { + case E_SCP_SYS_LOGIN_REQUEST: + if (scp_get_sys_login_request(scp_trans, username, + password, ip_addr) == 0) + { + got_message = 1; + } + break; + + case E_SCP_CLOSE_CONNECTION_REQUEST: + break; + + default: + { + char buff[64]; + scp_msgno_to_str(msgno, buff, sizeof(buff)); + LOG(LOG_LEVEL_ERROR, "unexpected message %s from SCP client", + buff); + } + break; + } + } + + return got_message; +} + +/******************************************************************************/ +struct login_info * +login_info_sys_login_user(struct trans *scp_trans, + const char *username, + const char *password, + const char *ip_addr) +{ + struct login_info *result; + enum scp_login_status status = E_SCP_LOGIN_GENERAL_ERROR; + int server_closed = 0; + + if ((result = g_new0(struct login_info, 1)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Allocation failure logging in user"); + } + else + { + int first_time = 1; + unsigned int retry_count = g_cfg->sec.login_retry; + + result->uid = (uid_t) -1; + + while (status != E_SCP_LOGIN_OK && !server_closed) + { + // First time round, we have credentials supplied by the + // caller. On subsequent trips, we have to wait for the + // SCP client to send us more. + if (first_time) + { + first_time = 0; + } + else if (!get_scp_client_retry(scp_trans, &username, + &password, &ip_addr)) + { + status = E_SCP_LOGIN_GENERAL_ERROR; + break; + } + + status = authenticate_and_authorize_connection(username, + password, + ip_addr, + result); + + if (status != E_SCP_LOGIN_OK) + { + if (retry_count > 0) + { + --retry_count; + } + else + { + server_closed = 1; + } + } + + if (scp_send_login_response(scp_trans, status, server_closed) != 0) + { + status = E_SCP_LOGIN_GENERAL_ERROR; + break; + } + } + } + + if (status != E_SCP_LOGIN_OK) + { + login_info_free(result); + result = NULL; + } + + return result; +} + +/******************************************************************************/ +struct login_info * +login_info_uds_login_user(struct trans *scp_trans) +{ + struct login_info *result; + int uid; // Needed as g_sck_get_peer_cred() doesn't use uid_t + + // Allocate a struct for the result, with the IP address set to "" + if ((result = g_new0(struct login_info, 1)) == NULL || + (result->ip_addr = g_new0(char, 1)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Allocation failure logging in user"); + } + else if (g_sck_get_peer_cred(scp_trans->sck, NULL, &uid, NULL) != 0) + { + LOG(LOG_LEVEL_ERROR, "Unable to get peer credentials for SCP socket"); + } + else if (g_getuser_info_by_uid(uid, &result->username, + NULL, NULL, NULL, NULL) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't reverse lookup UID %d", result->uid); + } + else if ((result->auth_info = auth_uds(result->username, NULL)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't authorize user %s over UDS", + result->username); + } + else if (!access_login_allowed(&g_cfg->sec, result->username)) + { + LOG(LOG_LEVEL_ERROR, "Access denied for user %s by your system admin", + result->username); + } + else + { + result->uid = (uid_t)uid; + return result; + } + + login_info_free(result); + return NULL; +} + + +/******************************************************************************/ +void +login_info_free(struct login_info *self) +{ + if (self != NULL) + { + g_free(self->username); + g_free(self->ip_addr); + if (self->auth_info != NULL) + { + auth_end(self->auth_info); + } + g_free(self); + } +} diff --git a/sesman/sesexec/login_info.h b/sesman/sesexec/login_info.h new file mode 100644 index 00000000..a6171fc4 --- /dev/null +++ b/sesman/sesexec/login_info.h @@ -0,0 +1,94 @@ +/** + * 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 login_info.h + * @brief Declare functionality associated with user logins for sesexec + * @author Matt Burt + * + */ + +#ifndef LOGIN_INFO_H +#define LOGIN_INFO_H + +#include + +struct trans; + +/** + * Information associated with the logged-in user + */ +struct login_info +{ + uid_t uid; + char *username; + char *ip_addr; + struct auth_info *auth_info; +}; + +/** + * @brief Attempt a system login using username/password + * @param scp_trans SCP transport for talking to the client + * @param username Username from xrdp + * @param password Password from xrdp + * @param ip_addr IP address for xrdp client + * + * @result Allocated login_info struct for a successful login + * + * This is a wrapper around the dialogue between sesexec and the SCP process + * which is required to start a session. + * + * While this call is in operation, only the scp_trans transport will + * be checked for messages. Incoming messages on other transports will be + * ignored. The dialog can also be terminated by a SIGTERM if the SCP + * transport is configured to allow this. + * + * The username in the returned structure may differ from the passed-in + * username if multiple names map to the same UID. This can happen with + * federated naming services (e.g. AD, LDAP) + */ +struct login_info * +login_info_sys_login_user(struct trans *scp_trans, + const char *username, + const char *password, + const char *ip_addr); + +/** + * @brief Create a login_info structure using UDS credentials + * + * This should be a formality, as by the time sesexec tries this, sesman + * should already have done it. + * + * Errors are logged. + * + * @param scp_trans SCP transport for talking to the client + * @param ip_addr IP address for xrdp client + * + * @result Allocated login_info struct for a successful login + */ +struct login_info * +login_info_uds_login_user(struct trans *scp_trans); + +/** + * Free a struct login_info + */ +void +login_info_free(struct login_info *self); + +#endif // LOGIN_INFO_H diff --git a/sesman/sesexec/sesexec.c b/sesman/sesexec/sesexec.c new file mode 100644 index 00000000..4ec4ef7c --- /dev/null +++ b/sesman/sesexec/sesexec.c @@ -0,0 +1,521 @@ +/** + * 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 +#endif + +#include +#include + +#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, 0) != 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(); + g_exit(error); +} diff --git a/sesman/sesexec/sesexec.h b/sesman/sesexec/sesexec.h new file mode 100644 index 00000000..ff6fe7e0 --- /dev/null +++ b/sesman/sesexec/sesexec.h @@ -0,0 +1,70 @@ +/** + * 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 sesexec.h + * @brief Main include file + * @author Jay Sorg + * + */ + +#ifndef SESEXEC_H +#define SESEXEC_H + +#include + +struct config_sesman; +struct trans; +struct login_info; +struct session_data; + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#define USE_BSD_SETLOGIN +#endif + +/* Globals */ +extern struct config_sesman *g_cfg; +extern unsigned char g_fixedkey[8]; +extern struct login_info *g_login_info; +extern struct session_data *g_session_data; + +extern tintptr g_term_event; +extern tintptr g_sigchld_event; +extern pid_t g_pid; + + +/** + * Callback to process incoming ERCP data + */ +int +sesexec_ercp_data_in(struct trans *self); + +/* + * Check for termination + */ +int +sesexec_is_term(void); + +/* + * Terminate the sesexec main loop + */ +void +sesexec_terminate_main_loop(int status); + +#endif // SESEXEC_H