/** * 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 scp.c * @brief scp (sesman control protocol) handler function * @author Jay Sorg, Simone Fedele * */ #if defined(HAVE_CONFIG_H) #include #endif #include "trans.h" #include "os_calls.h" #include "eicp.h" #include "ercp.h" #include "scp.h" #include "scp_process.h" #include "sesman.h" #include "sesman_access.h" #include "sesman_auth.h" #include "sesman_config.h" #include "os_calls.h" #include "pre_session_list.h" #include "session_list.h" #include "sesexec_control.h" #include "string_calls.h" #include "xrdp_sockets.h" /******************************************************************************/ static int process_set_peername_request(struct pre_session_item *psi) { int rv; const char *peername; rv = scp_get_set_peername_request(psi->client_trans, &peername); if (rv == 0) { if (pre_session_list_set_peername(psi, peername) != 0) { LOG(LOG_LEVEL_WARNING, "Failed to set connection peername from %s to %s", psi->peername, peername); } } return rv; } /******************************************************************************/ static int process_sys_login_request(struct pre_session_item *psi) { int rv; const char *username; const char *password; const char *ip_addr; int send_client_reply = 1; rv = scp_get_sys_login_request(psi->client_trans, &username, &password, &ip_addr); if (rv == 0) { enum scp_login_status errorcode; LOG(LOG_LEVEL_INFO, "Received system login request from %s for user: %s IP: %s", psi->peername, username, ip_addr); 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", 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 { /* * Copy the IP address of the requesting user, anticipating a * successful login. We need this so we can search for a session * with a matching IP address if required. */ g_snprintf(psi->start_ip_addr, sizeof(psi->start_ip_addr), "%s", ip_addr); /* 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; } } } 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, -1); psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; } } 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) { 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; } /******************************************************************************/ static int process_uds_login_request(struct pre_session_item *psi) { enum scp_login_status errorcode; int rv; int uid; int pid; char *username = NULL; int server_closed; rv = g_sck_get_peer_cred(psi->client_trans->sck, &pid, &uid, NULL); if (rv != 0) { errorcode = E_SCP_LOGIN_GENERAL_ERROR; LOG(LOG_LEVEL_INFO, "Unable to get peer credentials for socket %d", (int)psi->client_trans->sck); } else { LOG(LOG_LEVEL_INFO, "Received UDS login request from %s for UID: %d from PID: %d", psi->peername, uid, pid); 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", psi->username); } 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 { errorcode = authenticate_and_authorize_uds_connection( psi, uid, username); g_free(username); } } if (errorcode == E_SCP_LOGIN_OK) { server_closed = 0; } else { server_closed = 1; /* Close the connection after returning from this callback */ psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; /* Never return the UID if the server is closing */ uid = -1; } return scp_send_login_response(psi->client_trans, errorcode, server_closed, uid); } /******************************************************************************/ 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 pre_session_item *psi) { if (psi->login_state != E_PS_LOGIN_NOT_LOGGED_IN) { LOG(LOG_LEVEL_INFO, "Logging out %s from sesman", psi->username); logout_pre_session(psi); } return 0; } /******************************************************************************/ /** * Create xrdp socket path for the user * * We do this here rather than in sesexec as we're single-threaded here * and so don't have to worry about race conditions * * Directory is owned by UID of session, but can be accessed by * the group specified in the config. * * Errors are logged so the caller doesn't have to */ static int create_xrdp_socket_path(uid_t uid) { // Owner all permissions, group read+execute #define RWX_PERMS 0x750 int rv = 1; const char *sockdir_group = g_cfg->sec.session_sockdir_group; int gid = 0; // Default if no group specified char sockdir[XRDP_SOCKETS_MAXPATH]; g_snprintf(sockdir, sizeof(sockdir), XRDP_SOCKET_PATH, (int)uid); // Create directory permissions RWX_PERMS, if it doesn't exist already // (our os_calls layer doesn't allow us to set the SGID bit here) int old_umask = g_umask_hex(RWX_PERMS ^ 0x777); if (!g_directory_exist(sockdir) && !g_create_dir(sockdir)) { LOG(LOG_LEVEL_ERROR, "create_xrdp_socket_path: Can't create %s [%s]", sockdir, g_get_strerror()); } else if (g_chmod_hex(sockdir, RWX_PERMS | 0x2000) != 0) { LOG(LOG_LEVEL_ERROR, "create_xrdp_socket_path: Can't set SGID bit on %s [%s]", sockdir, g_get_strerror()); } else if (sockdir_group != NULL && sockdir_group[0] != '\0' && g_getgroup_info(sockdir_group, &gid) != 0) { LOG(LOG_LEVEL_ERROR, "create_xrdp_socket_path: Can't get GID of group %s [%s]", sockdir_group, g_get_strerror()); } else if (g_chown(sockdir, uid, gid) != 0) { LOG(LOG_LEVEL_ERROR, "create_xrdp_socket_path: Can't set owner of %s to %d:%d [%s]", sockdir, uid, gid, g_get_strerror()); } else { rv = 0; } (void)g_umask_hex(old_umask); return rv; #undef RWX_PERMS } /******************************************************************************/ static int process_create_session_request(struct pre_session_item *psi) { int rv; /* 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; rv = scp_get_create_session_request(psi->client_trans, &type, &width, &height, &bpp, &shell, &directory); if (rv == 0) { 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", psi->peername, psi->username); s_item = session_list_get_bydata(psi->uid, type, width, height, bpp, psi->start_ip_addr); if (s_item != NULL) { // Found an existing session 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", 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", psi->username, display, s_item->sesexec_pid); } // 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 socket dir for this user else if (create_xrdp_socket_path(psi->uid) != 0) { status = E_SCP_SCREATE_GENERAL_ERROR; } // 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 manage session"); status = E_SCP_SCREATE_GENERAL_ERROR; } else { // 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); if (eicp_stat != 0) { LOG(LOG_LEVEL_ERROR, "Can't ask sesexec to authenticate user"); status = E_SCP_SCREATE_GENERAL_ERROR; } else { // We've handed over responsibility for the // SCP communication send_client_reply = 0; // 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 // // 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; } /******************************************************************************/ static int process_list_sessions_request(struct pre_session_item *psi) { int rv = 0; struct scp_session_info *info = NULL; unsigned int cnt = 0; unsigned int i; if (psi->login_state == E_PS_LOGIN_NOT_LOGGED_IN) { rv = scp_send_list_sessions_response(psi->client_trans, E_SCP_LS_NOT_LOGGED_IN, NULL); } else { LOG(LOG_LEVEL_INFO, "Received request from %s to list sessions for user %s", psi->peername, psi->username); info = session_list_get_byuid(psi->uid, &cnt, 0); for (i = 0; rv == 0 && i < cnt; ++i) { rv = scp_send_list_sessions_response(psi->client_trans, E_SCP_LS_SESSION_INFO, &info[i]); } free_session_info_list(info, cnt); if (rv == 0) { rv = scp_send_list_sessions_response(psi->client_trans, E_SCP_LS_END_OF_LIST, NULL); } } return rv; } /******************************************************************************/ static int process_close_connection_request(struct pre_session_item *psi) { int rv = 0; LOG(LOG_LEVEL_INFO, "Received request to close connection from %s", psi->peername); /* Expecting no more client messages. Close the connection * after returning from this callback */ psi->dispatcher_action = E_PSD_TERMINATE_PRE_SESSION; return rv; } /******************************************************************************/ int scp_process(struct pre_session_item *psi) { enum scp_msg_code msgno; int rv = 0; switch ((msgno = scp_msg_in_get_msgno(psi->client_trans))) { case E_SCP_SET_PEERNAME_REQUEST: rv = process_set_peername_request(psi); break; case E_SCP_SYS_LOGIN_REQUEST: rv = process_sys_login_request(psi); break; case E_SCP_UDS_LOGIN_REQUEST: rv = process_uds_login_request(psi); break; case E_SCP_LOGOUT_REQUEST: rv = process_logout_request(psi); break; case E_SCP_CREATE_SESSION_REQUEST: rv = process_create_session_request(psi); break; case E_SCP_LIST_SESSIONS_REQUEST: rv = process_list_sessions_request(psi); break; case E_SCP_CLOSE_CONNECTION_REQUEST: rv = process_close_connection_request(psi); break; default: { char buff[64]; scp_msgno_to_str(msgno, buff, sizeof(buff)); LOG(LOG_LEVEL_ERROR, "Ignored SCP message %s", buff); } } return rv; }