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] 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