diff --git a/sesman/scp_process.c b/sesman/scp_process.c index 0f448632..819b51c6 100644 --- a/sesman/scp_process.c +++ b/sesman/scp_process.c @@ -31,6 +31,7 @@ #include "trans.h" #include "os_calls.h" #include "eicp.h" +#include "ercp.h" #include "scp.h" #include "display_utils.h" @@ -301,9 +302,12 @@ logout_scp_list_item(struct scp_list_item *sli) { if (sli->login_state != E_SLI_LOGIN_NOT_LOGGED_IN) { - (void)eicp_send_logout_request(sli->sesexec_trans); - trans_delete(sli->sesexec_trans); - sli->sesexec_trans = NULL; + if (sli->sesexec_trans != NULL) + { + (void)eicp_send_logout_request(sli->sesexec_trans); + trans_delete(sli->sesexec_trans); + sli->sesexec_trans = NULL; + } sli->uid = (uid_t) -1; g_free(sli->username); sli->username = NULL; @@ -469,6 +473,9 @@ process_create_session_request(struct scp_list_item *sli) if (s_item != NULL) { // Found an existing session + LOG(LOG_LEVEL_INFO, + "A suitable session on display :%d is already active", + s_item->display); display = s_item->display; guid = s_item->guid; } @@ -476,15 +483,22 @@ process_create_session_request(struct scp_list_item *sli) else if (g_cfg->sess.max_sessions > 0 && session_list_get_count() >= g_cfg->sess.max_sessions) { + LOG(LOG_LEVEL_ERROR, + "The maximum number of sessions has been reached"); status = E_SCP_SCREATE_MAX_REACHED; } else if ((display = get_free_display()) < 0) { + LOG(LOG_LEVEL_ERROR, + "No free display can be found for a new session"); status = E_SCP_SCREATE_NO_DISPLAY; } // Create a socket dir for this user else if (create_xrdp_socket_path(sli->uid) != 0) { + LOG(LOG_LEVEL_ERROR, + "Can't create a socket directory for UID %d", + (int)sli->uid); status = E_SCP_SCREATE_GENERAL_ERROR; } // Create a sesexec process if we don't have one (UDS login) @@ -511,6 +525,8 @@ process_create_session_request(struct scp_list_item *sli) else { // Pass the session create request to sesexec + LOG(LOG_LEVEL_INFO, + "Passing session creation request to sesexec"); int eicp_stat; eicp_stat = eicp_send_create_session_request( sli->sesexec_trans, @@ -554,6 +570,86 @@ process_create_session_request(struct scp_list_item *sli) /******************************************************************************/ +static int +process_connect_session_request(struct scp_list_item *sli) +{ + int rv; + /* Client parameters describing new session */ + struct guid guid; + unsigned int flags; + enum scp_sconnect_status status = E_SCP_SCONNECT_OK; + + rv = scp_get_connect_session_request(sli->client_trans, &guid, &flags); + + if (rv == 0) + { + if (sli->login_state == E_SLI_LOGIN_NOT_LOGGED_IN) + { + status = E_SCP_SCONNECT_NOT_LOGGED_IN; + } + else + { + struct session_item *s_item = session_list_get_byguid(&guid); + if (s_item == NULL) + { + LOG(LOG_LEVEL_ERROR, + "User %s tried to connect to non-existent session", + sli->username); + status = E_SCP_SCONNECT_NO_SUCH_GUID; + } + else if (s_item->uid != sli->uid) + { + LOG(LOG_LEVEL_ERROR, + "User %s (UID %d) denied access to session for UID %d", + sli->username, sli->uid, s_item->uid); + status = E_SCP_SCONNECT_NO_SUCH_GUID; + } + else + { + // Don't log the GUID + LOG(LOG_LEVEL_INFO, + "Forwarding request from %s to connect to a session", + sli->username); + // Pass the session create request to sesexec + int ercp_stat; + ercp_stat = ercp_send_connect_session_request( + s_item->sesexec_trans, + sli->client_trans->sck, + flags); + + if (ercp_stat != 0) + { + // The sesexec transport is broken. That's dealt with + // elsewhere. + LOG(LOG_LEVEL_ERROR, + "Can't ask sesexec to connect to a session"); + status = E_SCP_SCONNECT_GENERAL_ERROR; + } + else + { + status = E_SCP_SCONNECT_OK; + } + } + } + + // Send anything other than a successful connection request + // back to the client + if (status != E_SCP_SCONNECT_OK) + { + rv = scp_send_connect_session_response(sli->client_trans, + status, -1, -1); + } + } + + // This call is always the last thing on the SCP connection. + logout_scp_list_item(sli); // Remove any sesexec process used for auth + sli->dispatcher_action = E_SLD_TERMINATE_SCP_CONN; + + return rv; +} + +/******************************************************************************/ + static int process_list_sessions_request(struct scp_list_item *sli) { @@ -639,6 +735,12 @@ process_close_connection_request(struct scp_list_item *sli) LOG(LOG_LEVEL_INFO, "Received request to close connection from %s", sli->peername); + /* Make sure we're logged out */ + if (sli->login_state != E_SLI_LOGIN_NOT_LOGGED_IN) + { + logout_scp_list_item(sli); + } + /* Expecting no more client messages. Close the connection * after returning from this callback */ sli->dispatcher_action = E_SLD_TERMINATE_SCP_CONN; @@ -674,6 +776,10 @@ scp_process(struct scp_list_item *sli) rv = process_create_session_request(sli); break; + case E_SCP_CONNECT_SESSION_REQUEST: + rv = process_connect_session_request(sli); + break; + case E_SCP_LIST_SESSIONS_REQUEST: rv = process_list_sessions_request(sli); break; diff --git a/sesman/sesexec/ercp_server.c b/sesman/sesexec/ercp_server.c index 458ab396..6dd551c6 100644 --- a/sesman/sesexec/ercp_server.c +++ b/sesman/sesexec/ercp_server.c @@ -30,7 +30,10 @@ #include "arch.h" +#include "login_info.h" +#include "scp.h" #include "sesexec.h" +#include "os_calls.h" #include "session.h" #include "trans.h" @@ -38,11 +41,99 @@ #include "ercp_server.h" /******************************************************************************/ -static int -handle_session_reconnect_event(struct trans *self) +static enum scp_sconnect_status +get_session_fds(struct session_data *sd, unsigned int scp_flags, + int *display_fd, int *chan_fd) { - session_reconnect(g_login_info, g_session_data); - return 0; + enum scp_sconnect_status result = E_SCP_SCONNECT_OK; + + if ((*display_fd = session_get_display_server_fd(g_login_info, sd)) < 0) + { + result = E_SCP_SCONNECT_SERVER_FAIL; + } + else if ((scp_flags & E_SCP_SCONNECT_FLAG_NEED_CHANSRV) == 0) + { + // Don't need to try to connect to chansrv + *chan_fd = -1; + } + else + { + // If this fails, it's inconvenient, but not a show-stopper + *chan_fd = session_get_chansrv_fd(g_login_info, sd); + } + + return result; +} + +/******************************************************************************/ +static int +handle_connect_session_request(struct trans *self) +{ + int scp_fd = -1; + unsigned int scp_flags; + + int rv = ercp_get_connect_session_request(self, &scp_fd, &scp_flags); + if (rv == 0) + { + struct trans *scp_trans; + + if ((scp_trans = scp_init_trans_from_fd(scp_fd, + TRANS_TYPE_SERVER, + sesexec_is_term)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Can't create SCP trans"); + rv = 1; + } + else + { + scp_fd = -1; // Don't close this twice! + + // Now we've got a transport we can send data back to + // the SCP client + enum scp_sconnect_status scp_status; + int display_fd = -1; + int chan_fd = -1; + scp_status = get_session_fds(g_session_data, scp_flags, + &display_fd, &chan_fd); + rv = scp_send_connect_session_response(scp_trans, scp_status, + display_fd, chan_fd); + + if (rv == 0 && scp_status == E_SCP_SCONNECT_OK) + { + // Don't run the reconnect script on the first connect + if (session_increment_connect_count(g_session_data) == 0) + { + LOG(LOG_LEVEL_INFO, "User %s has connected to a session", + g_login_info->username); + } + else + { + LOG(LOG_LEVEL_INFO, "User %s has reconnected to a session", + g_login_info->username); + session_run_reconnect_script(g_login_info, g_session_data); + } + } + + // Regardless of the result of the send, we must close all + // our copies of file descriptors. + if (display_fd >= 0) + { + g_file_close(display_fd); + } + if (chan_fd >= 0) + { + g_file_close(chan_fd); + } + trans_delete(scp_trans); + } + } + + if (scp_fd >= 0) + { + g_file_close(scp_fd); + } + + return rv; } /******************************************************************************/ @@ -54,8 +145,8 @@ ercp_server(struct trans *self) switch ((msgno = ercp_msg_in_get_msgno(self))) { - case E_ERCP_SESSION_RECONNECT_EVENT: - rv = handle_session_reconnect_event(self); + case E_ERCP_CONNECT_SESSION_REQUEST: + rv = handle_connect_session_request(self); break; default: diff --git a/sesman/sesexec/session.c b/sesman/sesexec/session.c index cd536467..adac6cc9 100644 --- a/sesman/sesexec/session.c +++ b/sesman/sesexec/session.c @@ -33,6 +33,7 @@ #include "config_ac.h" #endif +#include #include #include "arch.h" @@ -49,6 +50,7 @@ #include "sesexec.h" #include "sessionrecord.h" #include "string_calls.h" +#include "trans.h" #include "xauth.h" #include "xwait.h" #include "xrdp_sockets.h" @@ -59,6 +61,7 @@ struct session_data pid_t win_mgr; ///< PID of window manager pid_t chansrv; //< PID of chansrv time_t start_time; + unsigned int connect_count; struct session_parameters params; // Flexible array member used to store strings in params and ip_addr; #ifdef __cplusplus @@ -95,6 +98,7 @@ session_data_new(const struct session_parameters *sp) sd->x_server = -1; sd->chansrv = -1; sd->start_time = 0; + sd->connect_count = 0; /* Copy all the non-string session parameters... */ sd->params = *sp; @@ -184,7 +188,7 @@ dumpItemsToString(struct list *self, char *outstr, int len) /******************************************************************************/ static void -start_chansrv(struct login_info *login_info, +start_chansrv(const struct login_info *login_info, const struct session_parameters *s) { struct list *chansrv_params = list_create(); @@ -222,7 +226,7 @@ start_chansrv(struct login_info *login_info, /******************************************************************************/ static void -start_window_manager(struct login_info *login_info, +start_window_manager(const struct login_info *login_info, const struct session_parameters *s) { char text[256]; @@ -471,7 +475,7 @@ prepare_xvnc_xserver_params(const struct session_parameters *s, /******************************************************************************/ /* Either execs the X server, or returns */ static void -start_x_server(struct login_info *login_info, +start_x_server(const struct login_info *login_info, const struct session_parameters *s) { char authfile[256]; /* The filename for storing xauth information */ @@ -575,8 +579,8 @@ start_x_server(struct login_info *login_info, * Simple helper process to fork a child and log errors */ static int fork_child( - void (*runproc)(struct login_info *, const struct session_parameters *), - struct login_info *login_info, + void (*runproc)(const struct login_info *, const struct session_parameters *), + const struct login_info *login_info, const struct session_parameters *s, pid_t group_pid) { @@ -1065,6 +1069,20 @@ session_get_start_time(const struct session_data *sd) return (sd == NULL) ? 0 : sd->start_time; } +/******************************************************************************/ +unsigned int +session_get_connect_count(const struct session_data *sd) +{ + return (sd == NULL) ? 0 : sd->connect_count; +} + +/******************************************************************************/ +unsigned int +session_increment_connect_count(struct session_data *sd) +{ + return (sd == NULL) ? 0 : sd->connect_count++; +} + /******************************************************************************/ const struct session_parameters * session_get_parameters(const struct session_data *sd) @@ -1110,7 +1128,7 @@ session_send_term(struct session_data *sd, int wait_for_all) /******************************************************************************/ static void -start_reconnect_script(struct login_info *login_info, +start_reconnect_script(const struct login_info *login_info, const struct session_parameters *s) { env_set_user(login_info->uid, 0, s->display, @@ -1143,8 +1161,8 @@ start_reconnect_script(struct login_info *login_info, /******************************************************************************/ void -session_reconnect(struct login_info *login_info, - struct session_data *sd) +session_run_reconnect_script(const struct login_info *login_info, + const struct session_data *sd) { if (fork_child(start_reconnect_script, login_info, &sd->params, sd->x_server) < 0) @@ -1152,3 +1170,113 @@ session_reconnect(struct login_info *login_info, LOG(LOG_LEVEL_ERROR, "Failed to fork for session reconnection script"); } } + +/******************************************************************************/ +int +session_get_display_server_fd(const struct login_info *login_info, + const struct session_data *sd) +{ + char portname[XRDP_SOCKETS_MAXPATH]; + const char *localhost = "localhost"; // Ignored for TRANS_MODE_UNIX + int socket_mode; + + int rv = -1; + + if (sd->x_server <= 0) + { + LOG(LOG_LEVEL_ERROR, + "Request to connect to display server :%u" + " which has exited", sd->params.display); + } + else + { + switch (sd->params.type) + { + case SCP_SESSION_TYPE_XVNC: + socket_mode = TRANS_MODE_TCP; + snprintf(portname, sizeof(portname), "%u", + 5900 + sd->params.display); + break; + + case SCP_SESSION_TYPE_XVNC_UDS: + case SCP_SESSION_TYPE_XORG: + socket_mode = TRANS_MODE_UNIX; + snprintf(portname, sizeof(portname), XRDP_X11RDP_STR, + login_info->uid, (int)sd->params.display); + + break; + + default: + LOG(LOG_LEVEL_ERROR, "Unsupported session type %d for connect", + sd->params.type); + portname[0] = '\0'; + } + + if (portname[0] != '\0') + { + // Use the transport library to get the fd + struct trans *t = trans_create(socket_mode, 8 * 8192, 8192); + if (t == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory creating transport"); + } + else if (trans_connect(t, localhost, portname, 3000) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't connect to display server :%u [%s]", + sd->params.display, + g_get_strerror()); + } + else + { + rv = t->sck; + t->sck = -1; + } + trans_delete(t); + } + } + + return rv; +} + +/******************************************************************************/ +int +session_get_chansrv_fd(const struct login_info *login_info, + const struct session_data *sd) +{ + char portname[XRDP_SOCKETS_MAXPATH]; + + int rv = -1; + + if (sd->chansrv <= 0) + { + LOG(LOG_LEVEL_ERROR, + "Request to connect to chansrv :%u" + " which has exited", sd->params.display); + } + else + { + snprintf(portname, sizeof(portname), + XRDP_CHANSRV_STR, login_info->uid, (int)sd->params.display); + + // Use the transport library to get the fd + struct trans *t = trans_create(TRANS_MODE_UNIX, 8192, 8192); + if (t == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory creating transport"); + } + else if (trans_connect(t, NULL, portname, 10 * 1000) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't connect to chansrv :%u [%s]", + sd->params.display, + g_get_strerror()); + } + else + { + rv = t->sck; + t->sck = -1; + } + trans_delete(t); + } + + return rv; +} diff --git a/sesman/sesexec/session.h b/sesman/sesexec/session.h index 97d3dee4..db5519d0 100644 --- a/sesman/sesexec/session.h +++ b/sesman/sesexec/session.h @@ -108,6 +108,22 @@ session_active(const struct session_data *sd); time_t session_get_start_time(const struct session_data *sd); +/** + * Returns the connect count for an active session + * @param sd session_data for this session + * @return connect count + */ +unsigned int +session_get_connect_count(const struct session_data *sd); + +/** + * Increment the connect count for an active session + * @param sd session_data for this session + * @return Pre-increment value of the connect count + */ +unsigned int +session_increment_connect_count(struct session_data *sd); + /** * Returns the parameters used to start the session * @@ -145,7 +161,21 @@ 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); +session_run_reconnect_script(const struct login_info *login_info, + const struct session_data *sd); + +/** + * Connects a file descriptor to the display server + */ +int +session_get_display_server_fd(const struct login_info *login_info, + const struct session_data *sd); + +/** + * Connects a file descriptor to chansrv + */ +int +session_get_chansrv_fd(const struct login_info *login_info, + const struct session_data *sd); #endif // SESSION_H diff --git a/sesman/session_list.c b/sesman/session_list.c index 7ce0a2df..4b42992b 100644 --- a/sesman/session_list.c +++ b/sesman/session_list.c @@ -361,6 +361,25 @@ session_list_get_byuid(const uid_t *uid, unsigned int *cnt, unsigned int flags) return sess; } +/******************************************************************************/ +struct session_item * +session_list_get_byguid(const struct guid *guid) +{ + 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) && GUID_ARE_EQUAL(guid, &si->guid)) + { + return si; + } + } + + return NULL; +} + /******************************************************************************/ void free_session_info_list(struct scp_session_info *sesslist, unsigned int cnt) diff --git a/sesman/session_list.h b/sesman/session_list.h index fa7241c5..01296170 100644 --- a/sesman/session_list.h +++ b/sesman/session_list.h @@ -153,6 +153,17 @@ session_list_get_bydata(uid_t uid, struct scp_session_info * session_list_get_byuid(const uid_t *uid, unsigned int *cnt, unsigned int flags); +/** + * @brief retrieves a session by GUID + * @param guid GUID of session + * @return Pointer to session, or NULL. + * + * The caller is responsible for checking the client has + * permissions to access the session. + */ +struct session_item * +session_list_get_byguid(const struct guid *guid); + /** * * @brief Frees the result of session_get_byuser()