xrdp/sesman/scp_process.c
matt335672 252243ab6c Fix permissions on user socket directory
The user socket directory needs to be SGID so that they inherit
the group ownnership. Then xrdp can write to them.

(cherry picked from commit 200e4d84f4)
2024-03-27 09:50:27 +00:00

634 lines
20 KiB
C

/**
* 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 <config_ac.h>
#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
{
/* 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;
}