1154 lines
34 KiB
C
1154 lines
34 KiB
C
/**
|
|
* xrdp: A Remote Desktop Protocol server.
|
|
*
|
|
* Copyright (C) Jay Sorg 2004-2015
|
|
*
|
|
* BSD process grouping by:
|
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland.
|
|
* Copyright (c) 2000-2001 Markus Friedl.
|
|
* Copyright (c) 2011-2015 Koichiro Iwao, Kyushu Institute of Technology.
|
|
*
|
|
* 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 session.c
|
|
* @brief Session management code
|
|
* @author Jay Sorg, Simone Fedele
|
|
*
|
|
*/
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include "config_ac.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_PRCTL_H
|
|
#include <sys/prctl.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
|
|
#include "arch.h"
|
|
#include "session.h"
|
|
|
|
#include "sesman_auth.h"
|
|
#include "sesman_config.h"
|
|
#include "env.h"
|
|
#include "guid.h"
|
|
#include "list.h"
|
|
#include "log.h"
|
|
#include "os_calls.h"
|
|
#include "sesman.h"
|
|
#include "string_calls.h"
|
|
#include "xauth.h"
|
|
#include "xwait.h"
|
|
#include "xrdp_sockets.h"
|
|
|
|
#ifndef PR_SET_NO_NEW_PRIVS
|
|
#define PR_SET_NO_NEW_PRIVS 38
|
|
#endif
|
|
|
|
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
|
#define USE_EXTRA_SESSION_FORK
|
|
#define USE_BSD_SETLOGIN
|
|
#endif
|
|
|
|
/* Module globals */
|
|
|
|
/* Currently, these duplicate module names in sesman.c. This is fine, and
|
|
* will be fully resolved when sesman is split into two separate excutables */
|
|
static tintptr g_term_event = 0;
|
|
static tintptr g_sigchld_event = 0;
|
|
static int g_pid = 0; // PID of sesexec process (sesman sub-process)
|
|
|
|
/**
|
|
* Creates a string consisting of all parameters that is hosted in the param list
|
|
* @param self
|
|
* @param outstr, allocate this buffer before you use this function
|
|
* @param len the allocated len for outstr
|
|
* @return
|
|
*/
|
|
char *
|
|
dumpItemsToString(struct list *self, char *outstr, int len)
|
|
{
|
|
int index;
|
|
int totalLen = 0;
|
|
|
|
g_memset(outstr, 0, len);
|
|
if (self->count == 0)
|
|
{
|
|
LOG_DEVEL(LOG_LEVEL_TRACE, "List is empty");
|
|
}
|
|
|
|
for (index = 0; index < self->count; index++)
|
|
{
|
|
/* +1 = one space*/
|
|
totalLen = totalLen + g_strlen((char *)list_get_item(self, index)) + 1;
|
|
|
|
if (len > totalLen)
|
|
{
|
|
g_strcat(outstr, (char *)list_get_item(self, index));
|
|
g_strcat(outstr, " ");
|
|
}
|
|
}
|
|
|
|
return outstr ;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static void
|
|
start_chansrv(struct auth_info *auth_info,
|
|
const struct session_parameters *s)
|
|
{
|
|
struct list *chansrv_params = list_create();
|
|
const char *exe_path = XRDP_SBIN_PATH "/xrdp-chansrv";
|
|
|
|
if (chansrv_params != NULL)
|
|
{
|
|
chansrv_params->auto_free = 1;
|
|
if (!list_add_strdup(chansrv_params, exe_path))
|
|
{
|
|
list_delete(chansrv_params);
|
|
chansrv_params = NULL;
|
|
}
|
|
}
|
|
|
|
if (chansrv_params == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Out of memory starting chansrv");
|
|
}
|
|
else
|
|
{
|
|
env_set_user(s->uid, 0, s->display,
|
|
g_cfg->env_names,
|
|
g_cfg->env_values);
|
|
|
|
LOG_DEVEL_LEAKING_FDS("chansrv", 3, -1);
|
|
|
|
/* executing chansrv */
|
|
g_execvp_list(exe_path, chansrv_params);
|
|
|
|
/* should not get here */
|
|
list_delete(chansrv_params);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
cleanup_sockets(int display)
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "cleanup_sockets:");
|
|
char file[256];
|
|
int error;
|
|
|
|
error = 0;
|
|
|
|
g_snprintf(file, 255, CHANSRV_PORT_OUT_STR, display);
|
|
if (g_file_exist(file))
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file);
|
|
if (g_file_delete(file) == 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"cleanup_sockets: failed to delete %s (%s)",
|
|
file, g_get_strerror());
|
|
error++;
|
|
}
|
|
}
|
|
|
|
g_snprintf(file, 255, CHANSRV_PORT_IN_STR, display);
|
|
if (g_file_exist(file))
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file);
|
|
if (g_file_delete(file) == 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"cleanup_sockets: failed to delete %s (%s)",
|
|
file, g_get_strerror());
|
|
error++;
|
|
}
|
|
}
|
|
|
|
g_snprintf(file, 255, XRDP_CHANSRV_STR, display);
|
|
if (g_file_exist(file))
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file);
|
|
if (g_file_delete(file) == 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"cleanup_sockets: failed to delete %s (%s)",
|
|
file, g_get_strerror());
|
|
error++;
|
|
}
|
|
}
|
|
|
|
g_snprintf(file, 255, CHANSRV_API_STR, display);
|
|
if (g_file_exist(file))
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file);
|
|
if (g_file_delete(file) == 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"cleanup_sockets: failed to delete %s (%s)",
|
|
file, g_get_strerror());
|
|
error++;
|
|
}
|
|
}
|
|
|
|
/* the following files should be deleted by xorgxrdp
|
|
* but just in case the deletion failed */
|
|
|
|
g_snprintf(file, 255, XRDP_X11RDP_STR, display);
|
|
if (g_file_exist(file))
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file);
|
|
if (g_file_delete(file) == 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"cleanup_sockets: failed to delete %s (%s)",
|
|
file, g_get_strerror());
|
|
error++;
|
|
}
|
|
}
|
|
|
|
g_snprintf(file, 255, XRDP_DISCONNECT_STR, display);
|
|
if (g_file_exist(file))
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file);
|
|
if (g_file_delete(file) == 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"cleanup_sockets: failed to delete %s (%s)",
|
|
file, g_get_strerror());
|
|
error++;
|
|
}
|
|
}
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static void
|
|
start_window_manager(struct auth_info *auth_info,
|
|
const struct session_parameters *s)
|
|
{
|
|
char text[256];
|
|
|
|
env_set_user(s->uid,
|
|
0,
|
|
s->display,
|
|
g_cfg->env_names,
|
|
g_cfg->env_values);
|
|
|
|
auth_set_env(auth_info);
|
|
LOG_DEVEL_LEAKING_FDS("window manager", 3, -1);
|
|
|
|
if (s->directory[0] != '\0')
|
|
{
|
|
if (g_cfg->sec.allow_alternate_shell)
|
|
{
|
|
g_set_current_dir(s->directory);
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"Directory change to %s requested, but not "
|
|
"allowed by AllowAlternateShell config value.",
|
|
s->directory);
|
|
}
|
|
}
|
|
|
|
if (s->shell[0] != '\0')
|
|
{
|
|
if (g_cfg->sec.allow_alternate_shell)
|
|
{
|
|
if (g_strchr(s->shell, ' ') != 0 || g_strchr(s->shell, '\t') != 0)
|
|
{
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Using user requested window manager on "
|
|
"display %u with embedded arguments using a shell: %s",
|
|
s->display, s->shell);
|
|
const char *argv[] = {"sh", "-c", s->shell, NULL};
|
|
g_execvp("/bin/sh", (char **)argv);
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Using user requested window manager on "
|
|
"display %d: %s", s->display, s->shell);
|
|
g_execlp3(s->shell, s->shell, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"Shell %s requested by user, but not allowed by "
|
|
"AllowAlternateShell config value.",
|
|
s->shell);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "The user session on display %u did "
|
|
"not request a specific window manager", s->display);
|
|
}
|
|
|
|
/* try to execute user window manager if enabled */
|
|
if (g_cfg->enable_user_wm)
|
|
{
|
|
g_snprintf(text, sizeof(text), "%s/%s",
|
|
g_getenv("HOME"), g_cfg->user_wm);
|
|
if (g_file_exist(text))
|
|
{
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Using window manager on display %u"
|
|
" from user home directory: %s", s->display, text);
|
|
g_execlp3(text, g_cfg->user_wm, 0);
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG,
|
|
"The user home directory window manager configuration "
|
|
"is enabled but window manager program does not exist: %s",
|
|
text);
|
|
}
|
|
}
|
|
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Using the default window manager on display %u: %s",
|
|
s->display, g_cfg->default_wm);
|
|
g_execlp3(g_cfg->default_wm, g_cfg->default_wm, 0);
|
|
|
|
/* still a problem starting window manager just start xterm */
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"No window manager on display %u started, "
|
|
"so falling back to starting xterm for user debugging",
|
|
s->display);
|
|
g_execlp3("xterm", "xterm", 0);
|
|
|
|
/* should not get here */
|
|
LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting to start "
|
|
"the window manager on display %u, aborting connection",
|
|
s->display);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static struct list *
|
|
prepare_xorg_xserver_params(const struct session_parameters *s,
|
|
const char *authfile)
|
|
{
|
|
|
|
char screen[32]; /* display number */
|
|
char text[128];
|
|
const char *xserver;
|
|
|
|
struct list *params = list_create();
|
|
if (params != NULL)
|
|
{
|
|
params->auto_free = 1;
|
|
|
|
#ifdef HAVE_SYS_PRCTL_H
|
|
/*
|
|
* Make sure Xorg doesn't run setuid root. Root access is not
|
|
* needed. Xorg can fail when run as root and the user has no
|
|
* console permissions.
|
|
* PR_SET_NO_NEW_PRIVS requires Linux kernel 3.5 and newer.
|
|
*/
|
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"[session start] (display %u): Failed to disable "
|
|
"setuid on X server: %s",
|
|
s->display, g_get_strerror());
|
|
}
|
|
#endif
|
|
|
|
g_snprintf(screen, sizeof(screen), ":%u", s->display);
|
|
|
|
/* some args are passed via env vars */
|
|
g_snprintf(text, sizeof(text), "%d", s->width);
|
|
g_setenv("XRDP_START_WIDTH", text, 1);
|
|
|
|
g_snprintf(text, sizeof(text), "%d", s->height);
|
|
g_setenv("XRDP_START_HEIGHT", text, 1);
|
|
|
|
g_snprintf(text, sizeof(text), "%d", g_cfg->sess.max_idle_time);
|
|
g_setenv("XRDP_SESMAN_MAX_IDLE_TIME", text, 1);
|
|
|
|
g_snprintf(text, sizeof(text), "%d", g_cfg->sess.max_disc_time);
|
|
g_setenv("XRDP_SESMAN_MAX_DISC_TIME", text, 1);
|
|
|
|
g_snprintf(text, sizeof(text), "%d", g_cfg->sess.kill_disconnected);
|
|
g_setenv("XRDP_SESMAN_KILL_DISCONNECTED", text, 1);
|
|
|
|
g_setenv("XRDP_SOCKET_PATH", XRDP_SOCKET_PATH, 1);
|
|
|
|
/* get path of Xorg from config */
|
|
xserver = (const char *)list_get_item(g_cfg->xorg_params, 0);
|
|
|
|
/* these are the must have parameters */
|
|
list_add_strdup_multi(params,
|
|
xserver, screen,
|
|
"-auth", authfile,
|
|
NULL);
|
|
|
|
/* additional parameters from sesman.ini file */
|
|
list_append_list_strdup(g_cfg->xorg_params, params, 1);
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static struct list *
|
|
prepare_xvnc_xserver_params(const struct session_parameters *s,
|
|
const char *authfile,
|
|
const char *passwd_file)
|
|
{
|
|
char screen[32] = {0}; /* display number */
|
|
char geometry[32] = {0};
|
|
char depth[32] = {0};
|
|
char guid_str[GUID_STR_SIZE];
|
|
const char *xserver;
|
|
|
|
struct list *params = list_create();
|
|
if (params != NULL)
|
|
{
|
|
params->auto_free = 1;
|
|
|
|
g_snprintf(screen, sizeof(screen), ":%u", s->display);
|
|
g_snprintf(geometry, sizeof(geometry), "%dx%d", s->width, s->height);
|
|
g_snprintf(depth, sizeof(depth), "%d", s->bpp);
|
|
|
|
guid_to_str(&s->guid, guid_str);
|
|
env_check_password_file(passwd_file, guid_str);
|
|
|
|
/* get path of Xvnc from config */
|
|
xserver = (const char *)list_get_item(g_cfg->vnc_params, 0);
|
|
|
|
/* these are the must have parameters */
|
|
list_add_strdup_multi(params,
|
|
xserver, screen,
|
|
"-auth", authfile,
|
|
"-geometry", geometry,
|
|
"-depth", depth,
|
|
"-rfbauth", passwd_file,
|
|
NULL);
|
|
|
|
/* additional parameters from sesman.ini file */
|
|
//config_read_xserver_params(SCP_SESSION_TYPE_XVNC,
|
|
// xserver_params);
|
|
list_append_list_strdup(g_cfg->vnc_params, params, 1);
|
|
}
|
|
return params;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* Either execs the X server, or returns */
|
|
static void
|
|
start_x_server(struct auth_info *auth_info,
|
|
const struct session_parameters *s)
|
|
{
|
|
char authfile[256]; /* The filename for storing xauth information */
|
|
char execvpparams[2048];
|
|
char *passwd_file = NULL;
|
|
struct list *xserver_params = NULL;
|
|
int unknown_session_type = 0;
|
|
|
|
if (s->type == SCP_SESSION_TYPE_XVNC)
|
|
{
|
|
env_set_user(s->uid,
|
|
&passwd_file,
|
|
s->display,
|
|
g_cfg->env_names,
|
|
g_cfg->env_values);
|
|
}
|
|
else
|
|
{
|
|
env_set_user(s->uid,
|
|
0,
|
|
s->display,
|
|
g_cfg->env_names,
|
|
g_cfg->env_values);
|
|
}
|
|
|
|
/* prepare the Xauthority stuff */
|
|
if (g_getenv("XAUTHORITY") != NULL)
|
|
{
|
|
g_snprintf(authfile, sizeof(authfile), "%s",
|
|
g_getenv("XAUTHORITY"));
|
|
}
|
|
else
|
|
{
|
|
g_snprintf(authfile, sizeof(authfile), "%s", ".Xauthority");
|
|
}
|
|
|
|
/* Add the entry in XAUTHORITY file or exit if error */
|
|
if (add_xauth_cookie(s->display, authfile) != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"Error setting the xauth cookie for display %u in file %s",
|
|
s->display, authfile);
|
|
}
|
|
else
|
|
{
|
|
switch (s->type)
|
|
{
|
|
case SCP_SESSION_TYPE_XORG:
|
|
xserver_params = prepare_xorg_xserver_params(s, authfile);
|
|
break;
|
|
|
|
case SCP_SESSION_TYPE_XVNC:
|
|
xserver_params = prepare_xvnc_xserver_params(s, authfile,
|
|
passwd_file);
|
|
break;
|
|
|
|
default:
|
|
unknown_session_type = 1;
|
|
}
|
|
|
|
g_free(passwd_file);
|
|
passwd_file = NULL;
|
|
|
|
if (xserver_params == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Out of memory allocating X server params");
|
|
}
|
|
else if (unknown_session_type)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Unknown session type: %d",
|
|
s->type);
|
|
}
|
|
else
|
|
{
|
|
/* fire up X server */
|
|
LOG(LOG_LEVEL_INFO, "Starting X server on display %u: %s",
|
|
s->display,
|
|
dumpItemsToString(xserver_params, execvpparams, 2048));
|
|
LOG_DEVEL_LEAKING_FDS("X server", 3, -1);
|
|
g_execvp_list((const char *)xserver_params->items[0],
|
|
xserver_params);
|
|
}
|
|
}
|
|
|
|
/* should not get here */
|
|
list_delete(xserver_params);
|
|
LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting "
|
|
"to start the X server on display %u, aborting connection",
|
|
s->display);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
* Convert a UID to a username
|
|
*
|
|
* @param uid UID
|
|
* @param uname pointer to output buffer
|
|
* @param uname_len Length of output buffer
|
|
* @return 0 for success.
|
|
*/
|
|
static int
|
|
username_from_uid(int uid, char *uname, int uname_len)
|
|
{
|
|
char *ustr;
|
|
int rv = g_getuser_info_by_uid(uid, &ustr, NULL, NULL, NULL, NULL);
|
|
|
|
if (rv == 0)
|
|
{
|
|
g_snprintf(uname, uname_len, "%s", ustr);
|
|
g_free(ustr);
|
|
}
|
|
else
|
|
{
|
|
g_snprintf(uname, uname_len, "<unknown>");
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static void
|
|
exit_status_to_str(const struct exit_status *e, char buff[], int bufflen)
|
|
{
|
|
switch (e->reason)
|
|
{
|
|
case E_XR_STATUS_CODE:
|
|
if (e->val == 0)
|
|
{
|
|
g_snprintf(buff, bufflen, "exit code zero");
|
|
}
|
|
else
|
|
{
|
|
g_snprintf(buff, bufflen, "non-zero exit code %d", e->val);
|
|
}
|
|
break;
|
|
|
|
case E_XR_SIGNAL:
|
|
g_snprintf(buff, bufflen, "signal %d", e->val);
|
|
break;
|
|
|
|
default:
|
|
g_snprintf(buff, bufflen, "an unexpected error");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static void
|
|
run_xrdp_session(const struct session_parameters *s,
|
|
struct auth_info *auth_info,
|
|
int window_manager_pid,
|
|
int display_pid,
|
|
int chansrv_pid)
|
|
{
|
|
int wm_wait_time;
|
|
struct exit_status wm_exit_status = {.reason = E_XR_UNEXPECTED, .val = 0};
|
|
int wm_running = 1;
|
|
|
|
/* Monitor the amount of time we wait for the
|
|
* window manager. This is approximately how long the window
|
|
* manager was running for */
|
|
LOG(LOG_LEVEL_INFO, "Session in progress on display :%d. Waiting "
|
|
"until the window manager (pid %d) exits to end the session",
|
|
s->display, window_manager_pid);
|
|
wm_wait_time = g_time1();
|
|
|
|
/* Wait for the window manager to terminate
|
|
*
|
|
* We can't use g_waitpid() variants for this, as these aren't
|
|
* interruptible by a SIGTERM */
|
|
while (wm_running)
|
|
{
|
|
int robjs_count;
|
|
intptr_t robjs[32];
|
|
int pid;
|
|
struct exit_status e;
|
|
|
|
robjs_count = 0;
|
|
robjs[robjs_count++] = g_term_event;
|
|
robjs[robjs_count++] = g_sigchld_event;
|
|
|
|
if (g_obj_wait(robjs, robjs_count, NULL, 0, 0) != 0)
|
|
{
|
|
/* should not get here */
|
|
LOG(LOG_LEVEL_WARNING, "run_xrdp_session: "
|
|
"Unexpected error from g_obj_wait()");
|
|
g_sleep(100);
|
|
continue;
|
|
}
|
|
|
|
if (g_is_wait_obj_set(g_term_event))
|
|
{
|
|
g_reset_wait_obj(g_term_event);
|
|
LOG(LOG_LEVEL_INFO, "Received SIGTERM");
|
|
// Pass it on to the window manager
|
|
g_sigterm(window_manager_pid);
|
|
}
|
|
|
|
// Check for any finished children
|
|
g_reset_wait_obj(g_sigchld_event);
|
|
while ((pid = g_waitchild(&e)) > 0)
|
|
{
|
|
if (pid == window_manager_pid)
|
|
{
|
|
wm_running = 0;
|
|
wm_exit_status = e;
|
|
}
|
|
else if (pid == display_pid)
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "X server pid %d on display :%d finished",
|
|
display_pid, s->display);
|
|
display_pid = -1;
|
|
// No other action - window manager should be going soon
|
|
}
|
|
else if (pid == chansrv_pid)
|
|
{
|
|
LOG(LOG_LEVEL_INFO,
|
|
"xrdp channel server pid %d on display :%d finished",
|
|
chansrv_pid, s->display);
|
|
chansrv_pid = -1;
|
|
}
|
|
}
|
|
}
|
|
wm_wait_time = g_time1() - wm_wait_time;
|
|
|
|
if (wm_exit_status.reason == E_XR_STATUS_CODE && wm_exit_status.val == 0)
|
|
{
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Window manager (pid %d, display %d) finished normally in %d secs",
|
|
window_manager_pid, s->display, wm_wait_time);
|
|
}
|
|
else
|
|
{
|
|
char reason[128];
|
|
exit_status_to_str(&wm_exit_status, reason, sizeof(reason));
|
|
|
|
LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) "
|
|
"exited with %s. This "
|
|
"could indicate a window manager config problem",
|
|
window_manager_pid, s->display, reason);
|
|
}
|
|
if (wm_wait_time < 10)
|
|
{
|
|
/* This could be a config issue. Log a significant error */
|
|
LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) "
|
|
"exited quickly (%d secs). This could indicate a window "
|
|
"manager config problem",
|
|
window_manager_pid, s->display, wm_wait_time);
|
|
}
|
|
|
|
if (display_pid > 0)
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "Terminating X server (pid %d) on display :%d",
|
|
display_pid, s->display);
|
|
g_sigterm(display_pid);
|
|
}
|
|
|
|
if (chansrv_pid > 0)
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) "
|
|
"on display :%d", chansrv_pid, s->display);
|
|
g_sigterm(chansrv_pid);
|
|
}
|
|
|
|
/* make sure all children are gone before socket cleanup happens */
|
|
if (display_pid > 0)
|
|
{
|
|
g_waitpid(display_pid);
|
|
LOG(LOG_LEVEL_INFO, "X server pid %d on display :%d finished",
|
|
display_pid, s->display);
|
|
}
|
|
|
|
if (chansrv_pid > 0)
|
|
{
|
|
g_waitpid(chansrv_pid);
|
|
LOG(LOG_LEVEL_INFO,
|
|
"xrdp channel server pid %d on display :%d finished",
|
|
chansrv_pid, s->display);
|
|
}
|
|
|
|
cleanup_sockets(s->display);
|
|
g_deinit();
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
* Simple helper process to fork a child and log errors */
|
|
static int
|
|
fork_child(
|
|
void (*runproc)(struct auth_info *, const struct session_parameters *),
|
|
struct auth_info *auth_info,
|
|
const struct session_parameters *s)
|
|
{
|
|
int pid = g_fork();
|
|
if (pid == 0)
|
|
{
|
|
/* Child process */
|
|
runproc(auth_info, s);
|
|
g_exit(0);
|
|
}
|
|
|
|
if (pid < 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Fork failed [%s]", g_get_strerror());
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
* Sub-process to start a session
|
|
*
|
|
* @param auth_info Authentication info
|
|
* @param s Session parameters
|
|
* @param success_fd File descriptor to write to on success
|
|
*
|
|
* @return status
|
|
*
|
|
* This routine returns a status on failure. On success, a character is
|
|
* written to the file descriptor to indicate a success, and then the
|
|
* routine runs for the lifetime of the session.
|
|
*/
|
|
enum scp_screate_status
|
|
session_start_subprocess(struct auth_info *auth_info,
|
|
const struct session_parameters *s,
|
|
int success_fd)
|
|
{
|
|
char username[256];
|
|
int chansrv_pid;
|
|
int display_pid;
|
|
int window_manager_pid;
|
|
enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR;
|
|
char text[64];
|
|
|
|
/* Set up wait objects so we can detect signals */
|
|
g_pid = g_getpid();
|
|
g_snprintf(text, sizeof(text), "xrdp_sesexec_%8.8x_main_term",
|
|
g_pid);
|
|
g_term_event = g_create_wait_obj(text);
|
|
g_signal_terminate(set_term_event);
|
|
g_snprintf(text, sizeof(text), "xrdp_sesexec_%8.8x_sigchld",
|
|
g_pid);
|
|
g_sigchld_event = g_create_wait_obj(text);
|
|
g_signal_child_stop(set_sigchld_event);
|
|
|
|
/* Get the username for display purposes */
|
|
username_from_uid(s->uid, username, sizeof(username));
|
|
|
|
#ifdef USE_BSD_SETLOGIN
|
|
/**
|
|
* Create a new session and process group since the 4.4BSD
|
|
* setlogin() affects the entire process group
|
|
*/
|
|
if (g_setsid() < 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"[session start] (display %d): setsid failed - pid %d",
|
|
s->display, g_getpid());
|
|
}
|
|
|
|
if (g_setlogin(username) < 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"[session start] (display %d): setlogin failed for user %s - pid %d",
|
|
s->display, username, g_getpid());
|
|
}
|
|
#endif
|
|
|
|
/* Set the secondary groups before starting the session to prevent
|
|
* problems on PAM-based systems (see Linux pam_setcred(3)).
|
|
* If we have *BSD setusercontext() this is not done here */
|
|
#ifndef HAVE_SETUSERCONTEXT
|
|
if (g_initgroups(username) != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"Failed to initialise secondary groups for %s: %s",
|
|
username, g_get_strerror());
|
|
return E_SCP_SCREATE_GENERAL_ERROR;
|
|
}
|
|
#endif
|
|
|
|
display_pid = fork_child(start_x_server, auth_info, s);
|
|
if (display_pid > 0)
|
|
{
|
|
enum xwait_status xws;
|
|
xws = wait_for_xserver(s->uid,
|
|
g_cfg->env_names,
|
|
g_cfg->env_values,
|
|
s->display);
|
|
|
|
if (xws != XW_STATUS_OK)
|
|
{
|
|
switch (xws)
|
|
{
|
|
case XW_STATUS_TIMED_OUT:
|
|
LOG(LOG_LEVEL_ERROR, "Timed out waiting for X server");
|
|
break;
|
|
case XW_STATUS_FAILED_TO_START:
|
|
LOG(LOG_LEVEL_ERROR, "X server failed to start");
|
|
break;
|
|
default:
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"An error occurred waiting for the X server");
|
|
}
|
|
status = E_SCP_SCREATE_X_SERVER_FAIL;
|
|
/* Kill it anyway in case it did start and we just failed to
|
|
* pick up on it */
|
|
g_sigterm(display_pid);
|
|
g_waitpid(display_pid);
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "X server :%d is working", s->display);
|
|
LOG(LOG_LEVEL_INFO, "Starting window manager for display :%d",
|
|
s->display);
|
|
window_manager_pid = fork_child(start_window_manager,
|
|
auth_info, s);
|
|
if (window_manager_pid < 0)
|
|
{
|
|
g_sigterm(display_pid);
|
|
g_waitpid(display_pid);
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Starting the xrdp channel server for display :%d",
|
|
s->display);
|
|
|
|
chansrv_pid = fork_child(start_chansrv, auth_info, s);
|
|
|
|
// Tell the caller we've started
|
|
char zero = 0;
|
|
g_file_write(success_fd, &zero, 1);
|
|
status = E_SCP_SCREATE_OK;
|
|
|
|
/* This call does not return until the session is done */
|
|
run_xrdp_session(s, auth_info, window_manager_pid,
|
|
display_pid, chansrv_pid);
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
#ifdef USE_EXTRA_SESSION_FORK
|
|
/*
|
|
* FreeBSD bug
|
|
* ports/157282: effective login name is not set by xrdp-sesman
|
|
* http://www.freebsd.org/cgi/query-pr.cgi?pr=157282
|
|
*
|
|
* from:
|
|
* $OpenBSD: session.c,v 1.252 2010/03/07 11:57:13 dtucker Exp $
|
|
* with some ideas about BSD process grouping to xrdp
|
|
*/
|
|
static int
|
|
run_extra_fork(void)
|
|
{
|
|
char text[64];
|
|
struct exit_status e;
|
|
int stat;
|
|
|
|
pid_t bsdsespid = g_fork();
|
|
|
|
if (bsdsespid <= 0)
|
|
{
|
|
/* Error, or child */
|
|
return bsdsespid;
|
|
}
|
|
/*
|
|
* intermediate sesman should return the status of its own child, and
|
|
* kill the child if we get a sigterm
|
|
*/
|
|
|
|
/* Set up wait objects so we can detect signals */
|
|
g_pid = g_getpid();
|
|
g_snprintf(text, sizeof(text), "xrdp_intermediate_%8.8x_main_term",
|
|
g_pid);
|
|
g_term_event = g_create_wait_obj(text);
|
|
g_signal_terminate(set_term_event);
|
|
g_snprintf(text, sizeof(text), "xrdp_intermediate_%8.8x_sigchld",
|
|
g_pid);
|
|
g_sigchld_event = g_create_wait_obj(text);
|
|
g_signal_child_stop(set_sigchld_event);
|
|
|
|
// Wait for a SIGCHLD event. We've only got one child so we know where
|
|
// it's come from!
|
|
while (!g_is_wait_obj_set(g_sigchld_event))
|
|
{
|
|
int robjs_count;
|
|
intptr_t robjs[4];
|
|
|
|
robjs_count = 0;
|
|
robjs[robjs_count++] = g_term_event;
|
|
robjs[robjs_count++] = g_sigchld_event;
|
|
|
|
if (g_obj_wait(robjs, robjs_count, NULL, 0, 0) != 0)
|
|
{
|
|
/* should not get here */
|
|
LOG(LOG_LEVEL_WARNING, "run_extra_fork: "
|
|
"Unexpected error from g_obj_wait()");
|
|
g_sleep(100);
|
|
}
|
|
else if (g_is_wait_obj_set(g_term_event))
|
|
{
|
|
g_reset_wait_obj(g_term_event);
|
|
LOG(LOG_LEVEL_INFO, "Received SIGTERM");
|
|
// Pass it on to the BSD session leader
|
|
g_sigterm(bsdsespid);
|
|
}
|
|
}
|
|
|
|
e = g_waitpid_status(bsdsespid);
|
|
stat = (e.reason == E_XR_STATUS_CODE) ? e.val : E_SCP_SCREATE_GENERAL_ERROR;
|
|
g_exit(stat);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
enum scp_screate_status
|
|
session_start(struct auth_info *auth_info,
|
|
const struct session_parameters *s,
|
|
int *pid)
|
|
{
|
|
int fd[2];
|
|
enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR;
|
|
|
|
if (g_pipe(fd) != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Cant create a pipe [%s]", g_get_strerror());
|
|
}
|
|
else
|
|
{
|
|
*pid = g_fork();
|
|
if (*pid == 0)
|
|
{
|
|
/**
|
|
* We're now forked from the main sesman process, so we
|
|
* can close file descriptors that we no longer need
|
|
*
|
|
* Set FD_CLOEXEC on the FD used to send our status back to
|
|
* sesman, as our sub-processes shouldn't be able to see it */
|
|
g_file_close(fd[0]);
|
|
g_file_set_cloexec(fd[1], 1);
|
|
|
|
sesman_close_all(0);
|
|
|
|
/* Wait objects created in a parent are not valid in a child */
|
|
sesman_delete_wait_objects();
|
|
|
|
LOG(LOG_LEVEL_INFO,
|
|
"calling auth_start_session for uid=%d from pid %d",
|
|
s->uid, g_getpid());
|
|
auth_start_session(auth_info, s->display);
|
|
|
|
/* Run the child */
|
|
#ifdef USE_EXTRA_SESSION_FORK
|
|
if (run_extra_fork() < 0)
|
|
{
|
|
return E_SCP_SCREATE_GENERAL_ERROR;
|
|
}
|
|
#endif
|
|
status = session_start_subprocess(auth_info, s, fd[1]);
|
|
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Calling auth_stop_session from pid %d",
|
|
g_getpid());
|
|
auth_stop_session(auth_info);
|
|
g_exit(status);
|
|
}
|
|
|
|
g_file_close(fd[1]);
|
|
if (*pid == -1)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Cant fork [%s]", g_get_strerror());
|
|
}
|
|
else
|
|
{
|
|
/* Wait for the child to signal success, or return an error */
|
|
int err;
|
|
char buff;
|
|
do
|
|
{
|
|
err = g_file_read(fd[0], &buff, 1);
|
|
}
|
|
while (err == -1 && g_get_errno() == EINTR);
|
|
if (err < 0)
|
|
{
|
|
/* Read problem */
|
|
LOG(LOG_LEVEL_ERROR, "Can't read pipe [%s]", g_get_strerror());
|
|
}
|
|
else if (err > 0)
|
|
{
|
|
/* Session is up and running */
|
|
status = E_SCP_SCREATE_OK;
|
|
}
|
|
else
|
|
{
|
|
/* Process has failed. Get the exit status of the child */
|
|
struct exit_status e;
|
|
e = g_waitpid_status(*pid);
|
|
if (e.reason == E_XR_STATUS_CODE)
|
|
{
|
|
status = (enum scp_screate_status)e.val;
|
|
}
|
|
else
|
|
{
|
|
char reason[128];
|
|
exit_status_to_str(&e, reason, sizeof(reason));
|
|
LOG(LOG_LEVEL_ERROR, "Child exited with %s", reason);
|
|
}
|
|
}
|
|
}
|
|
g_file_close(fd[0]);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
int
|
|
session_reconnect(int display, int uid,
|
|
struct auth_info *auth_info)
|
|
{
|
|
int pid;
|
|
|
|
pid = g_fork();
|
|
|
|
if (pid == -1)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Failed to fork for session reconnection script");
|
|
}
|
|
else if (pid == 0)
|
|
{
|
|
env_set_user(uid,
|
|
0,
|
|
display,
|
|
g_cfg->env_names,
|
|
g_cfg->env_values);
|
|
auth_set_env(auth_info);
|
|
|
|
if (g_file_exist(g_cfg->reconnect_sh))
|
|
{
|
|
LOG_DEVEL_LEAKING_FDS("reconnect script", 3, -1);
|
|
|
|
LOG(LOG_LEVEL_INFO,
|
|
"Starting session reconnection script on display %d: %s",
|
|
display, g_cfg->reconnect_sh);
|
|
g_execlp3(g_cfg->reconnect_sh, g_cfg->reconnect_sh, 0);
|
|
|
|
/* should not get here */
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"Error starting session reconnection script on display %d: %s",
|
|
display, g_cfg->reconnect_sh);
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"Session reconnection script file does not exist: %s",
|
|
g_cfg->reconnect_sh);
|
|
}
|
|
|
|
/* TODO: why is this existing with a success error code when the
|
|
reconnect script failed to be executed? */
|
|
g_exit(0);
|
|
}
|
|
|
|
return display;
|
|
}
|