xrdp/sesman/tools/sesrun.c
matt335672 4d80cf6d77 Move SCP synch calls from sesman tools to library
The sesman tools have some private functions to make syncronous calls
to sesman over SCP. This commit moves these calls to a new module
scp_sync in libipm so that they can be utilised by other parts of
xrdp (i.e. chansrv)
2024-10-22 12:21:25 +01:00

624 lines
16 KiB
C

/**
* 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 sesrun.c
* @brief An utility to start a session
* @author Jay Sorg, Simone Fedele
*
*/
#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif
#include <unistd.h>
#include <limits.h>
#include <ctype.h>
#include "parse.h"
#include "trans.h"
#include "os_calls.h"
#include "sesman_config.h"
#include "log.h"
#include "string_calls.h"
#include "guid.h"
#include "scp.h"
#include "scp_sync.h"
// cppcheck doesn't always set this macro to something in double-quotes
#if defined(__cppcheck__)
#undef PACKAGE_VERSION
#endif
#if !defined(PACKAGE_VERSION)
#define PACKAGE_VERSION "???"
#endif
#ifndef MAX_PASSWORD_LEN
# define MAX_PASSWORD_LEN 512
#endif
#ifndef DEFAULT_WIDTH
# define DEFAULT_WIDTH 1280
#endif
#ifndef DEFAULT_HEIGHT
# define DEFAULT_HEIGHT 1024
#endif
/* Default setting used by Windows 10 mstsc.exe */
#ifndef DEFAULT_BPP
# define DEFAULT_BPP 32
#endif
#ifndef DEFAULT_SESSION_TYPE
# define DEFAULT_SESSION_TYPE "Xorg"
#endif
/**
* Maps session type strings session type codes
*/
static struct
{
const char *name;
enum scp_session_type type;
} type_map[] =
{
{ "Xvnc", SCP_SESSION_TYPE_XVNC},
{ "Xorg", SCP_SESSION_TYPE_XORG},
{ NULL, (enum scp_session_type) - 1}
};
/**
* Parameters needed for a session
*/
struct session_params
{
int width;
int height;
int bpp;
enum scp_session_type session_type;
const char *directory;
const char *shell;
const char *ip_addr;
const char *username;
char password[MAX_PASSWORD_LEN + 1];
};
/**************************************************************************//**
* Maps a string to a session type value
*
* @param string session type string
* @param[out] value session type value
* @return 0 for success or != 0 if not found
*/
static
int string_to_session_type(const char *t, enum scp_session_type *value)
{
unsigned int i;
for (i = 0 ; type_map[i].name != NULL; ++i)
{
if (g_strcasecmp(type_map[i].name, t) == 0)
{
*value = type_map[i].type;
return 0;
}
}
return 1;
}
/**************************************************************************//**
* Returns a list of supported session types
*
* Caller supplies a buffer. Buffer handling and buffer overflow detection are
* the same as snprint()
*
* @param buff area for result
* @param bufflen Size of result
* @return number of characters for the output string
*/
static
unsigned int get_session_type_list(char *buff, unsigned int bufflen)
{
unsigned int i;
unsigned int ret = 0;
const char *sep = "";
for (i = 0 ; type_map[i].name != NULL; ++i)
{
if (ret < bufflen)
{
ret += g_snprintf(buff + ret, bufflen - ret,
"%s%s", sep, type_map[i].name);
sep = ", ";
}
}
return ret;
}
/**************************************************************************//**
* Prints a brief summary of options and defaults
*/
static void
usage(void)
{
char sesstype_list[64];
(void)get_session_type_list(sesstype_list, sizeof(sesstype_list));
g_printf("xrdp session starter v" PACKAGE_VERSION "\n");
g_printf("\nusage:\n");
g_printf("sesrun --help\n"
"\nor\n"
"sesrun [options] [username]\n\n");
g_printf("options:\n");
g_printf(" -g <geometry> Default:%dx%d\n",
DEFAULT_WIDTH, DEFAULT_HEIGHT);
g_printf(" -b <bits-per-pixel> Default:%d\n", DEFAULT_BPP);
g_printf(" -t <type> Default:%s\n", DEFAULT_SESSION_TYPE);
g_printf(" -D <directory> Default: $HOME\n"
" -S <shell> Default: Defined window manager\n"
" -p <password> TESTING ONLY - DO NOT USE IN PRODUCTION\n"
" -F <file-descriptor> Read password from this file descriptor\n"
" -c <sesman_ini> Alternative sesman.ini file\n");
g_printf("\nSupported types are %s\n",
sesstype_list);
g_printf("\nIf username is omitted, the current user is used.\n"
"If username is provided, password is needed.\n"
" Password is prompted for if -p or -F are not specified\n");
}
/**************************************************************************//**
* Parses a string <width>x<height>
*
* @param geom_str Input string
* @param sp Session parameter structure for resulting width and height
* @return !=0 for success
*/
static int
parse_geometry_string(const char *geom_str, struct session_params *sp)
{
int result = 0;
unsigned int sep_count = 0; /* Count of 'x' separators */
unsigned int other_count = 0; /* Count of non-digits and non separators */
const char *sepp = NULL; /* Pointer to the 'x' */
const char *p = geom_str;
while (*p != '\0')
{
if (!isdigit(*p))
{
if (*p == 'x' || *p == 'X')
{
++sep_count;
sepp = p;
}
else
{
++other_count;
}
}
++p;
}
if (sep_count != 1 || other_count > 0 ||
sepp == geom_str || /* Separator at start of string */
sepp == (p - 1) ) /* Separator at end of string */
{
LOG(LOG_LEVEL_ERROR, "Invalid geometry string '%s'", geom_str);
}
else
{
sp->width = atoi(geom_str);
sp->height = atoi(sepp + 1);
result = 1;
}
return result;
}
/**************************************************************************//**
* Read a password from a file descriptor
*
* @param fd_str string representing file descriptor
* @param sp Session parameter structure for resulting password
* @return !=0 for success
*/
static int
read_password_from_fd(const char *fd_str, struct session_params *sp)
{
int result = 0;
int s = g_file_read(atoi(fd_str), sp->password, sizeof (sp->password) - 1);
if (s < 0)
{
LOG(LOG_LEVEL_ERROR, "Can't read password from fd %s - %s",
fd_str, g_get_strerror());
sp->password[0] = '\0';
}
else
{
sp->password[s] = '\0';
if (s > 0 && sp->password[s - 1] == '\n')
{
sp->password[s - 1] = '\0';
}
result = 1;
}
return result;
}
/**************************************************************************//**
* Parses the program args
*
* @param argc Passed to main
* @param @argv Passed to main
* @param sp Session parameter structure for resulting values
* @param sesman_ini Pointer to an alternative config file if one is specified
* @return !=0 for success
*/
static int
parse_program_args(int argc, char *argv[], struct session_params *sp,
const char **sesman_ini)
{
int params_ok = 1;
int opt;
bool_t password_set = 0;
sp->width = DEFAULT_WIDTH;
sp->height = DEFAULT_HEIGHT;
sp->bpp = DEFAULT_BPP;
(void)string_to_session_type(DEFAULT_SESSION_TYPE, &sp->session_type);
sp->directory = "";
sp->shell = "";
sp->ip_addr = "";
sp->username = NULL;
sp->password[0] = '\0';
while ((opt = getopt(argc, argv, "g:b:s:t:D:S:p:F:c:")) != -1)
{
switch (opt)
{
case 'g':
if (!parse_geometry_string(optarg, sp))
{
params_ok = 0;
}
break;
case 'b':
sp->bpp = atoi(optarg);
break;
case 't':
if (string_to_session_type(optarg, &sp->session_type) != 0)
{
LOG(LOG_LEVEL_ERROR, "Unrecognised session type '%s'",
optarg);
params_ok = 0;
}
break;
case 'D':
sp->directory = optarg;
break;
case 'S':
sp->shell = optarg;
break;
case 'p':
if (password_set)
{
LOG(LOG_LEVEL_WARNING,
"Ignoring option '%c' - password already set ",
(char)opt);
}
else
{
g_strncpy(sp->password, optarg, sizeof(sp->password) - 1);
password_set = 1;
}
break;
case 'F':
if (password_set)
{
LOG(LOG_LEVEL_WARNING,
"Ignoring option '%c' - password already set ",
(char)opt);
}
else
{
if (read_password_from_fd(optarg, sp))
{
password_set = 1;
}
else
{
params_ok = 0;
}
}
break;
case 'c':
*sesman_ini = optarg;
break;
default:
LOG(LOG_LEVEL_ERROR, "Unrecognised switch '%c'", (char)opt);
params_ok = 0;
}
}
if (argc == optind)
{
// No username was specified
if (password_set)
{
LOG(LOG_LEVEL_WARNING, "No username - ignoring specified password");
sp->password[0] = '\0';
}
sp->username = NULL;
}
else if ((argc - optind) > 1)
{
LOG(LOG_LEVEL_ERROR, "Unexpected arguments after username");
params_ok = 0;
}
else if (params_ok)
{
// A username is specified
sp->username = argv[optind];
if (!password_set)
{
const char *p = getpass("Password: ");
if (p == NULL)
{
params_ok = 0;
}
else
{
g_snprintf(sp->password, sizeof(sp->password), "%s", p);
}
}
}
return params_ok;
}
/**************************************************************************//**
* Sends an SCP login request
*
* A sys login request (i.e. username / password) is used if a username
* is specified. Otherwise we use a uds login request for the current user.
*
* @param t SCP connection
* @param sp Data for request
*/
static int
send_login_request(struct trans *t, const struct session_params *sp)
{
int rv;
LOG(LOG_LEVEL_DEBUG, "ip_addr:\"%s\"", sp->ip_addr);
if (sp->username != NULL)
{
/* Only log the password in development builds */
LOG_DEVEL(LOG_LEVEL_DEBUG, "password:\"%s\"", sp->password);
rv = scp_send_sys_login_request(t, sp->username,
sp->password, sp->ip_addr);
}
else
{
rv = scp_send_uds_login_request(t);
}
return rv;
}
/**************************************************************************//**
* Receives an SCP login response
*
* @param t SCP transport to receive reply on
* @param[out] server_closed != 0 if server has gone away
* @return 0 for successful authentication
*/
static int
handle_login_response(struct trans *t, int *server_closed)
{
enum scp_login_status login_result;
int rv = scp_sync_wait_specific(t, E_SCP_LOGIN_RESPONSE);
if (rv != 0)
{
*server_closed = 1;
}
else
{
rv = scp_get_login_response(t, &login_result, server_closed, NULL);
if (rv == 0)
{
if (login_result != E_SCP_LOGIN_OK)
{
char msg[256];
scp_login_status_to_str(login_result, msg, sizeof(msg));
g_printf("Login failed; %s\n", msg);
rv = 1;
}
}
scp_msg_in_reset(t); // Done with this message
}
return rv;
}
/**************************************************************************//**
* Sends an SCP create session request
*
* @param t SCP connection
* @param sp Data for request
*/
static int
send_create_session_request(struct trans *t, const struct session_params *sp)
{
LOG(LOG_LEVEL_DEBUG,
"width:%d height:%d bpp:%d code:%d\n"
"directory:\"%s\" shell:\"%s\"",
sp->width, sp->height, sp->bpp, sp->session_type,
sp->directory, sp->shell);
return scp_send_create_session_request(
t, sp->session_type,
sp->width, sp->height, sp->bpp, sp->shell, sp->directory);
}
/**************************************************************************//**
* Receives an SCP create session response
*
* @param t SCP transport to receive reply on
* @return 0 for success
*/
static int
handle_create_session_response(struct trans *t)
{
enum scp_screate_status status;
int display;
struct guid guid;
int rv = scp_sync_wait_specific(t, E_SCP_CREATE_SESSION_RESPONSE);
if (rv == 0)
{
rv = scp_get_create_session_response(t, &status,
&display, &guid);
if (rv == 0)
{
if (status != E_SCP_SCREATE_OK)
{
char msg[256];
scp_screate_status_to_str(status, msg, sizeof(msg));
g_printf("Connection failed; %s\n", msg);
rv = 1;
}
else
{
char guid_str[GUID_STR_SIZE];
g_printf("ok display=:%d GUID=%s\n",
display,
guid_to_str(&guid, guid_str));
}
}
scp_msg_in_reset(t); // Done with this message
}
return rv;
}
/******************************************************************************/
int
main(int argc, char **argv)
{
const char *sesman_ini = XRDP_CFG_PATH "/sesman.ini";
struct config_sesman *cfg = NULL;
struct trans *t = NULL;
struct session_params sp;
struct log_config *logging;
int rv = 1;
logging = log_config_init_for_console(LOG_LEVEL_WARNING,
g_getenv("SESRUN_LOG_LEVEL"));
log_start_from_param(logging);
log_config_free(logging);
if (argc == 2 && g_strcmp(argv[1], "--help") == 0)
{
usage();
rv = 0;
}
else if (!parse_program_args(argc, argv, &sp, &sesman_ini))
{
usage();
}
else if ((cfg = config_read(sesman_ini)) == NULL)
{
LOG(LOG_LEVEL_ERROR, "error reading config file %s : %s",
sesman_ini, g_get_strerror());
}
else if (!(t = scp_connect(cfg->listen_port, "xrdp-sesrun", NULL)))
{
LOG(LOG_LEVEL_ERROR, "connect error - %s", g_get_strerror());
}
else
{
int server_closed = 0;
while (!server_closed)
{
rv = send_login_request(t, &sp);
if (rv != 0)
{
LOG(LOG_LEVEL_ERROR, "Error sending login request to sesman");
break;
}
rv = handle_login_response(t, &server_closed);
if (rv == 0)
{
break; /* Successful authentication */
}
if (!server_closed)
{
const char *p = getpass("Password: ");
if (p == NULL)
{
break;
}
g_snprintf(sp.password, sizeof(sp.password), "%s", p);
}
}
if (rv == 0)
{
if ((rv = send_create_session_request(t, &sp)) == 0)
{
rv = handle_create_session_response(t);
}
}
trans_delete(t);
}
g_memset(sp.password, '\0', sizeof(sp.password));
config_free(cfg);
log_end();
return rv;
}