diff --git a/.gitignore b/.gitignore index dedde9c33c..2d390d047d 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,8 @@ tests/libxrdp/test_libxrdp tests/memtest/memtest tests/xrdp/test_xrdp tools/devel/tcp_proxy/tcp_proxy +tools/chkpriv/xrdp-chkpriv +tools/chkpriv/xrdp-droppriv *.trs waitforx/waitforx xrdp/xrdp diff --git a/common/os_calls.c b/common/os_calls.c index 5a788b5d76..719c069c3e 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -3167,6 +3167,48 @@ g_setgid(int pid) #endif } +/*****************************************************************************/ +/* Used by daemonizing code */ +/* returns error, zero is success, non zero is error */ +int +g_drop_privileges(const char *user, const char *group) +{ + int rv = 1; + int uid; + int gid; + if (g_getuser_info_by_name(user, &uid, NULL, NULL, NULL, NULL) != 0) + { + LOG(LOG_LEVEL_ERROR, "Unable to get UID for user '%s' [%s]", user, + g_get_strerror()); + } + else if (g_getgroup_info(group, &gid) != 0) + { + LOG(LOG_LEVEL_ERROR, "Unable to get GID for group '%s' [%s]", group, + g_get_strerror()); + } + else if (initgroups(user, gid) != 0) + { + LOG(LOG_LEVEL_ERROR, "Unable to init groups for '%s' [%s]", user, + g_get_strerror()); + } + else if (g_setgid(gid) != 0) + { + LOG(LOG_LEVEL_ERROR, "Unable to set group to '%s' [%s]", group, + g_get_strerror()); + } + else if (g_setuid(uid) != 0) + { + LOG(LOG_LEVEL_ERROR, "Unable to set user to '%s' [%s]", user, + g_get_strerror()); + } + else + { + rv = 0; + } + + return rv; +} + /*****************************************************************************/ /* returns error, zero is success, non zero is error */ /* does not work in win32 */ @@ -3492,6 +3534,12 @@ g_sigterm(int pid) #endif } +/*****************************************************************************/ +int g_pid_is_active(int pid) +{ + return (kill(pid, 0) == 0); +} + /*****************************************************************************/ /* does not work in win32 */ int diff --git a/common/os_calls.h b/common/os_calls.h index aa64b41ff6..cf4dc17d5b 100644 --- a/common/os_calls.h +++ b/common/os_calls.h @@ -338,6 +338,7 @@ void g_signal_pipe(void (*func)(int)); void g_signal_usr1(void (*func)(int)); int g_fork(void); int g_setgid(int pid); +int g_drop_privileges(const char *user, const char *group); int g_initgroups(const char *user); int g_getuid(void); int g_getgid(void); @@ -371,6 +372,12 @@ int g_exit(int exit_code); int g_getpid(void); int g_sigterm(int pid); int g_sighup(int pid); +/* + * Is a particular PID active? + * @param pid PID to check + * Returns boolean + */ +int g_pid_is_active(int pid); int g_getuser_info_by_name(const char *username, int *uid, int *gid, char **shell, char **dir, char **gecos); int g_getuser_info_by_uid(int uid, char **username, int *gid, diff --git a/common/thread_calls.c b/common/thread_calls.c index 0b821b8fd3..b575d4eade 100644 --- a/common/thread_calls.c +++ b/common/thread_calls.c @@ -122,8 +122,11 @@ tc_mutex_delete(tbus mutex) pthread_mutex_t *lmutex; lmutex = (pthread_mutex_t *)mutex; - pthread_mutex_destroy(lmutex); - g_free(lmutex); + if (lmutex != NULL) + { + pthread_mutex_destroy(lmutex); + g_free(lmutex); + } #endif } diff --git a/configure.ac b/configure.ac index a53fb7f32a..6f7e102809 100644 --- a/configure.ac +++ b/configure.ac @@ -614,6 +614,7 @@ AC_CONFIG_FILES([ tools/Makefile tools/devel/Makefile tools/devel/tcp_proxy/Makefile + tools/chkpriv/Makefile vnc/Makefile xrdpapi/Makefile xrdp/Makefile diff --git a/docs/man/sesman.ini.5.in b/docs/man/sesman.ini.5.in index 5e7ed2029d..6b45b7120a 100644 --- a/docs/man/sesman.ini.5.in +++ b/docs/man/sesman.ini.5.in @@ -315,7 +315,8 @@ transitions between confinement domains. .TP \fBSessionSockdirGroup\fR=\fIgroup\fR Sets the group owner of the directories containing session sockets. This -is normally the GID of the xrdp process so xrdp can connect to user sessions. +MUST be the same as runtime_group in xrdp.ini, or xrdp will not +be able to connect to any sessions. .SH "X11 SERVER" Following parameters can be used in the \fB[Xvnc]\fR and diff --git a/docs/man/xrdp.ini.5.in b/docs/man/xrdp.ini.5.in index 1118cc568b..a9a608a362 100644 --- a/docs/man/xrdp.ini.5.in +++ b/docs/man/xrdp.ini.5.in @@ -119,6 +119,29 @@ The default port for RDP is \fB3389\fP. Multiple address:port instances must be separated by spaces or commas. Check the .ini file for examples. Specifying interfaces requires said interfaces to be UP before xrdp starts. +.TP +\fBruntime_user\fP=\fIusername\fP +.TP +\fBruntime_group\fP=\fIgroupname\fP +User name and group to run the xrdp daemon under. + +After xrdp starts, it sets its UID and GID to values derived from these +settings, so that it's running without system privilege. + +The \fBruntime_group\fP MUST be set to the same value as +\fBSessionSockdirGroup\fP in \fBsesman.ini\fP if you want to run sessions. + +A suitable user and group can be added with a command like this (Linux):- + +useradd xrdp -d / -c 'xrdp daemon' -s /usr/sbin/nologin + +In order to establish secure connections, the xrdp daemon needs permission +to access sensitive cryptographic files. After changing either or both +of these values, check that xrdp has access to required files by running +this script:- + +@xrdpdatadir@/xrdp-chkpriv + .TP \fBenable_token_login\fP=\fI[true|false]\fP If set to \fB1\fP, \fBtrue\fP or \fByes\fP, \fBxrdp\fP will scan the user name provided by the diff --git a/instfiles/Makefile.am b/instfiles/Makefile.am index a318ed8f92..fafa295da6 100644 --- a/instfiles/Makefile.am +++ b/instfiles/Makefile.am @@ -98,6 +98,9 @@ endif if FREEBSD # must be tab below install-data-hook: - sed -i '' 's|%%PREFIX%%|$(prefix)|g' $(DESTDIR)$(sysconfdir)/rc.d/xrdp \ - $(DESTDIR)$(sysconfdir)/rc.d/xrdp-sesman + sed -e 's|%%PREFIX%%|$(prefix)|g' \ + -e 's|%%LOCALSTATEDIR%%|$(localstatedir)|g' \ + -i '' \ + $(DESTDIR)$(sysconfdir)/rc.d/xrdp \ + $(DESTDIR)$(sysconfdir)/rc.d/xrdp-sesman endif diff --git a/instfiles/init.d/xrdp b/instfiles/init.d/xrdp index e3607b08a7..39f25cdde3 100644 --- a/instfiles/init.d/xrdp +++ b/instfiles/init.d/xrdp @@ -116,7 +116,7 @@ case "$1" in log_progress_msg $NAME if pidofproc -p $PIDDIR/$NAME.pid $DAEMON > /dev/null; then start-stop-daemon --stop --quiet --oknodo --pidfile $PIDDIR/$NAME.pid \ - --exec $DAEMON + --remove-pidfile --exec $DAEMON value=$? [ $value -gt 0 ] && exitval=$value else diff --git a/instfiles/rc.d/xrdp b/instfiles/rc.d/xrdp index b5dbcd2518..39f35d657b 100644 --- a/instfiles/rc.d/xrdp +++ b/instfiles/rc.d/xrdp @@ -48,6 +48,7 @@ command="%%PREFIX%%/sbin/xrdp" allstart_cmd="xrdp_allstart" allstop_cmd="xrdp_allstop" allrestart_cmd="xrdp_allrestart" +stop_postcmd="xrdp_poststop" xrdp_allstart() { @@ -79,4 +80,10 @@ xrdp_allrestart() run_rc_command "restart" } +xrdp_poststop() +{ + # If running with dropped privileges, xrdp can't delete its own + # PID file + rm -f %%LOCALSTATEDIR%%/run/xrdp.pid +} run_rc_command "$1" diff --git a/sesman/sesman.ini.in b/sesman/sesman.ini.in index a290ea3ebe..96089a3f1e 100644 --- a/sesman/sesman.ini.in +++ b/sesman/sesman.ini.in @@ -45,10 +45,10 @@ RestrictInboundClipboard=none ; Leave this unset unless you need to disable it. #XorgNoNewPrivileges=true ; Specify the group which is to have read access to the directory where -; local sockets for the session are created. This is normally the GID -; which the xrdp process runs as. -; Default is 'root' -#SessionSockdirGroup=root +; local sockets for the session are created. +; This MUST be the same as runtime_group in xrdp.ini, or xrdp will not +; be able to connect to your sessions. +#SessionSockdirGroup=xrdp [Sessions] diff --git a/tools/Makefile.am b/tools/Makefile.am index c4f7c462ca..ef5c72fe44 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,3 +1,4 @@ SUBDIRS = \ + chkpriv \ devel diff --git a/tools/chkpriv/Makefile.am b/tools/chkpriv/Makefile.am new file mode 100644 index 0000000000..f12344a6f9 --- /dev/null +++ b/tools/chkpriv/Makefile.am @@ -0,0 +1,34 @@ +xrdppkgdatadir=$(datadir)/xrdp + +pkglibexec_PROGRAMS = \ + xrdp-droppriv + +nodist_xrdppkgdata_SCRIPTS = \ + xrdp-chkpriv + +AM_LDFLAGS = + +AM_CPPFLAGS = \ + -I$(top_srcdir)/common + +xrdp_droppriv_SOURCES = \ + xrdp-chkpriv.in \ + xrdp-droppriv.c + +xrdp_droppriv_LDADD = \ + $(top_builddir)/common/libcommon.la + +SUBST_VARS = sed \ + -e 's|@pkglibexecdir[@]|$(pkglibexecdir)|g' \ + -e 's|@sysconfdir[@]|$(sysconfdir)|g' + +subst_verbose = $(subst_verbose_@AM_V@) +subst_verbose_ = $(subst_verbose_@AM_DEFAULT_V@) +subst_verbose_0 = @echo " SUBST $@"; + +SUFFIXES = .in +.in: + $(subst_verbose)$(SUBST_VARS) $< > $@ + +CLEANFILES = $(nodist_xrdppkgdata_SCRIPTS) + diff --git a/tools/chkpriv/xrdp-chkpriv.in b/tools/chkpriv/xrdp-chkpriv.in new file mode 100644 index 0000000000..512e90efe2 --- /dev/null +++ b/tools/chkpriv/xrdp-chkpriv.in @@ -0,0 +1,248 @@ +#!/bin/sh +# +# xrdp: A Remote Desktop Protocol server. +# +# Copyright (C) Jay Sorg and contributors 2004-2024 +# +# 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. +# +# Program to check permissions for xrdp when running in a non-privileged +# mode + +# Change these if they do not match your installation +CONF_DIR=@sysconfdir@/xrdp +XRDP_INI="$CONF_DIR"/xrdp.ini +SESMAN_INI="$CONF_DIR"/sesman.ini +RSAKEYS_INI="$CONF_DIR"/rsakeys.ini +DROPPRIV=@pkglibexecdir@/xrdp-droppriv + +# Helper functions to print colored tag like "[ OK ]" + +print_ok() +{ + if [ -t 1 ]; then + printf "\033[1m[ \033[1;32mOK\033[0m ]\033[0m " + else + printf "[ OK ] " + fi +} + +print_warn() +{ + if [ -t 1 ]; then + printf "\033[1m[ \033[1;33mWARN\033[0m ]\033[0m " + else + printf "[ WARN ] " + fi +} + +print_ng() +{ + if [ -t 1 ]; then + printf "\033[1m[ \033[1;31mNG\033[0m ]\033[0m " + else + printf "[ NG ] " + fi +} + +# ----------------------------------------------------------------------------- +# G E T I N I V A L U E +# +# Gets a value from an ini file. +# +# Params [ini_file] [key] +# ----------------------------------------------------------------------------- +GetIniValue() +{ + # Look for a line matching 'key=' with optional whitespace + # either side of the key. When we find one, strip everything + # up to and including the first '=', print it, and quit + # + # This doesn't take sections into account + sed -n -e '/^ *'"$2"' *=/{ + s/^[^=]*=//p + q + }' "$1" +} + +# ----------------------------------------------------------------------------- +# M A I N +# ----------------------------------------------------------------------------- + +if [ "$(id -u)" != 0 ]; then + print_ng + echo "** Must run this script as root" >&2 + exit 1 +fi + +OS=$(uname) +case "$OS" in + FreeBSD | Linux) ;; + *) echo "Unsupported operating system $OS" >&2 + exit 1 +esac + +errors=0 + +runtime_user=$(GetIniValue "$XRDP_INI" runtime_user) +runtime_group=$(GetIniValue "$XRDP_INI" runtime_group) +certificate=$(GetIniValue "$XRDP_INI" certificate) +key_file=$(GetIniValue "$XRDP_INI" key_file) +SessionSockdirGroup=$(GetIniValue "$SESMAN_INI" SessionSockdirGroup) + +case "$certificate" in + '') certificate="$CONF_DIR"/cert.pem ;; + /*) ;; + *) certificate="$CONF_DIR"/"$certificate" +esac + +case "$key_file" in + '') key_file="$CONF_DIR"/key.pem ;; + /*) ;; + *) key_file="$CONF_DIR"/"$key_file" +esac + +echo "Settings" +echo " - [xrdp.ini] runtime_user : $runtime_user" +echo " - [xrdp.ini] runtime_group : $runtime_group" +echo " - [xrdp.ini] certificate : $certificate" +echo " - [xrdp.ini] key_file : $key_file" +echo " - [sesman.ini] SessionSockdirGroup : $SessionSockdirGroup" +echo + +# Basic checks on runtime user/group +if [ -z "$runtime_user" ] && [ -z "$runtime_group" ]; then + print_warn + echo "This system is not configured to run xrdp without privilege" + exit 0 +fi + +if [ -z "$runtime_user" ] || [ -z "$runtime_group" ]; then + print_ng + echo "Both 'runtime_user' and 'runtime_group' must be set" + errors=$(( errors + 1 )) + exit 1 +fi + +if getent passwd "$runtime_user" >/dev/null ; then + print_ok + echo "runtime_user '$runtime_user' appears to exist" +else + print_ng + echo "runtime_user '$runtime_user' does not exist" + errors=$(( errors + 1 )) +fi + +GID= +if getent group "$runtime_group" >/dev/null ; then + print_ok + echo "runtime_group '$runtime_group' appears to exist" + GID=$(getent group xrdp | cut -d: -f3) +else + print_ng + echo "runtime_group '$runtime_group' does not exist" + errors=$(( errors + 1 )) +fi + +# Groups agree between sesman and xrdp? +if [ "$runtime_user" = "$SessionSockdirGroup" ]; then + print_ok + echo "xrdp.ini and sesman.ini agree on group ownership" +else + print_ng + echo "xrdp.ini and sesman.ini do not agree on group ownership" + errors=$(( errors + 1 )) +fi + +# Check we can access rsakeys.ini +# +# This is our file, so we can be completely prescriptive about +# the permissions +if [ -e $RSAKEYS_INI ]; then + # Only check if we have a GID + if [ -n "$GID" ]; then + # Get the permissions, UID and GID in $1..$3 + case "$OS" in + FreeBSD) + # shellcheck disable=SC2046 + set -- $(stat -f "%Lp %u %g" $RSAKEYS_INI) + ;; + *) + # shellcheck disable=SC2046 + set -- $(stat -c "%a %u %g" $RSAKEYS_INI) + esac + if [ "$1/$2/$3" = "640/0/$GID" ]; then + print_ok + echo "$RSAKEYS_INI has correct permissions" + else + if [ "$1" != 640 ]; then + print_ng + echo "$RSAKEYS_INI should have permissions -rw-r-----" + errors=$(( errors + 1 )) + fi + if [ "$2" != 0 ]; then + print_ng + echo "$RSAKEYS_INI should be owned by root" + errors=$(( errors + 1 )) + fi + if [ "$3" != "$GID" ]; then + print_ng + echo "$RSAKEYS_INI should be in the $runtime_group group" + errors=$(( errors + 1 )) + fi + fi + fi +else + print_ng + echo "$RSAKEYS_INI does not exist" + errors=$(( errors + 1 )) +fi + +# Are cert and key readable (but NOT writeable) by the user? +# +# These aren't necessarily our files, so we can't be too prescriptive about +# privileges. On Debian for example, we might be using the 'ssl-cert' +# group to obtain access to /etc/ssl/private/ssl-cert-snakeoil.key +for file in "$certificate" "$key_file"; do + if ! [ -e $file ]; then + print_ng + echo "$file does not exist" + errors=$(( errors + 1 )) + elif ! $DROPPRIV "$runtime_user" "$runtime_group" sh -c '[ -r '"$file"' ]' + then + print_ng + echo "$file is not readable by $runtime_user:$runtime_group" + errors=$(( errors + 1 )) + elif $DROPPRIV "$runtime_user" "$runtime_group" sh -c '[ -w '"$file"' ]' + then + print_ng + echo "$file is writeable by $runtime_user:$runtime_group" + errors=$(( errors + 1 )) + else + print_ok + echo "$file is read-only for $runtime_user:$runtime_group" + fi +done + +echo +if [ $errors -eq 0 ]; then + print_ok + echo "-Summary- Permissions appear to be correct to run xrdp unprivileged" + status=0 +else + print_ng + echo "-Summary- $errors error(s) found. Please correct these and try again" + status=1 +fi + +exit $status diff --git a/tools/chkpriv/xrdp-droppriv.c b/tools/chkpriv/xrdp-droppriv.c new file mode 100644 index 0000000000..185f575b10 --- /dev/null +++ b/tools/chkpriv/xrdp-droppriv.c @@ -0,0 +1,49 @@ +/* + * + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg and contributors 2004-2024 + * + * 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. + * + * Shell around the g_drop_privileges() call + */ + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include "os_calls.c" +#include "log.h" + +int main(int argc, char *argv[]) +{ + struct log_config *logging; + int status = 1; + logging = log_config_init_for_console(LOG_LEVEL_WARNING, + g_getenv("DROPPRIV_LOG_LEVEL")); + log_start_from_param(logging); + log_config_free(logging); + + if (argc < 4) + { + LOG(LOG_LEVEL_ERROR, "Usage : %s [user] [group] [cmd...]\n", argv[0]); + } + else if (g_drop_privileges(argv[1], argv[2]) == 0) + { + status = g_execvp(argv[3], &argv[3]); + } + + log_end(); + return status; +} diff --git a/xrdp/xrdp.c b/xrdp/xrdp.c index b2f9492002..0b7a453d7e 100644 --- a/xrdp/xrdp.c +++ b/xrdp/xrdp.c @@ -240,6 +240,127 @@ xrdp_process_params(int argc, char **argv, return 0; } +/*****************************************************************************/ +/** + * + * @brief Read additional startup parameters from xrdp.ini + * + * @param [in,out] startup parameters from the command line + * @return 0 on success + * + */ +static int +read_xrdp_ini_startup_params(struct xrdp_startup_params *startup_params) +{ + int rv = 0; + int fd; + int index; + int port_override; + int fork_override; + const char *name; + const char *val; + struct list *names; + struct list *values; + + port_override = startup_params->port[0] != 0; + fork_override = startup_params->fork; + names = list_create(); + names->auto_free = 1; + values = list_create(); + values->auto_free = 1; + + fd = g_file_open_ro(startup_params->xrdp_ini); + if (fd < 0) + { + LOG(LOG_LEVEL_ERROR, "Can't open %s [%s]", startup_params->xrdp_ini, + g_get_strerror()); + rv = 1; + } + else if (file_read_section(fd, "globals", names, values) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't read [Globals] from %s", + startup_params->xrdp_ini); + rv = 1; + } + else + { + for (index = 0; index < names->count; index++) + { + name = (const char *)list_get_item(names, index); + val = (const char *)list_get_item(values, index); + if (name == 0 || val == 0) + { + continue; + } + + if (g_strcasecmp(name, "port") == 0) + { + if (port_override == 0) + { + g_strncpy(startup_params->port, val, + sizeof(startup_params->port) - 1); + } + } + + else if (g_strcasecmp(name, "fork") == 0) + { + if (fork_override == 0) + { + startup_params->fork = g_text2bool(val); + } + } + + else if (g_strcasecmp(name, "tcp_nodelay") == 0) + { + startup_params->tcp_nodelay = g_text2bool(val); + } + + else if (g_strcasecmp(name, "tcp_keepalive") == 0) + { + startup_params->tcp_keepalive = g_text2bool(val); + } + + else if (g_strcasecmp(name, "tcp_send_buffer_bytes") == 0) + { + startup_params->tcp_send_buffer_bytes = g_atoi(val); + } + + else if (g_strcasecmp(name, "tcp_recv_buffer_bytes") == 0) + { + startup_params->tcp_recv_buffer_bytes = g_atoi(val); + } + + else if (g_strcasecmp(name, "use_vsock") == 0) + { + startup_params->use_vsock = g_text2bool(val); + } + + else if (g_strcasecmp(name, "runtime_user") == 0) + { + g_snprintf(startup_params->runtime_user, + sizeof(startup_params->runtime_user), + "%s", val); + } + + else if (g_strcasecmp(name, "runtime_group") == 0) + { + g_snprintf(startup_params->runtime_group, + sizeof(startup_params->runtime_group), + "%s", val); + } + } + } + + list_delete(names); + list_delete(values); + if (fd >= 0) + { + g_file_close(fd); + } + return rv; +} + + /*****************************************************************************/ /* Basic sanity checks before any forking */ static int @@ -295,15 +416,123 @@ xrdp_sanity_check(void) return 0; } +/*****************************************************************************/ +static int +check_drop_privileges(struct xrdp_startup_params *startup_params) +{ + int rv = 1; + const char *user = startup_params->runtime_user; + const char *group = startup_params->runtime_group; + + if (user[0] == '\0' && group[0] == '\0') + { + // Allow this for now + LOG(LOG_LEVEL_ALWAYS, + "You are running xrdp as root. This is not safe."); + rv = 0; + } + else if (user[0] == '\0' || group[0] == '\0') + { + LOG(LOG_LEVEL_ERROR, + "Both a runtime_user and a runtime_group MUST be specified"); + } + else + { + rv = g_drop_privileges(user, group); + if (rv == 0) + { + LOG(LOG_LEVEL_INFO, "Switched user:group to %s:%s", user, group); + } + } + + return rv; +} + +/*****************************************************************************/ +static int +read_pid_file(const char *pid_file) +{ + int pid = -1; + int fd = g_file_open_ro(pid_file); /* xrdp.pid */ + if (fd >= 0) + { + char text[32]; + g_memset(text, 0, sizeof(text)); + g_file_read(fd, text, sizeof(text) - 1); + pid = g_atoi(text); + g_file_close(fd); + } + + return pid; +} + +/*****************************************************************************/ +/** + * Kills an active xrdp daemon + * + * It is assumed that logging is not active + * + * @param pid_file PID file + * @return 0 for success + */ +static int +kill_daemon(const char *pid_file) +{ + int status = 1; + int pid; + if (g_getuid() != 0) + { + g_writeln("Must be root"); + } + else if ((pid = read_pid_file(pid_file)) > 0) + { + if (!g_pid_is_active(pid)) + { + g_writeln("Daemon not active"); + status = 0; + } + else + { + g_writeln("stopping process id %d", pid); + int i; + g_sigterm(pid); + g_sleep(100); + i = 5 * 1000 / 500; + while (i > 0 && g_pid_is_active(pid)) + { + g_sleep(500); + --i; + } + + if (g_pid_is_active(pid)) + { + g_writeln("Can't stop process"); + } + else + { + status = 0; + } + } + + if (status == 0) + { + /* delete the xrdp.pid file, as xrdp can't do this + * if it's running without privilege */ + g_file_delete(pid_file); + } + } + + return status; +} + /*****************************************************************************/ int main(int argc, char **argv) { - int exit_status = 0; + int exit_status = 1; enum logReturns error; struct xrdp_startup_params startup_params = {0}; int pid; - int fd; int daemon; char text[256]; const char *pid_file = XRDP_PID_PATH "/xrdp.pid"; @@ -368,36 +597,9 @@ main(int argc, char **argv) if (startup_params.kill) { - g_writeln("stopping xrdp"); - /* read the xrdp.pid file */ - fd = -1; - - if (g_file_exist(pid_file)) /* xrdp.pid */ - { - fd = g_file_open_ro(pid_file); /* xrdp.pid */ - } - - if (fd == -1) - { - g_writeln("cannot open %s, maybe xrdp is not running", pid_file); - } - else - { - g_memset(text, 0, 32); - g_file_read(fd, text, 31); - pid = g_atoi(text); - g_writeln("stopping process id %d", pid); - - if (pid > 0) - { - g_sigterm(pid); - } - - g_file_close(fd); - } - + int status = kill_daemon(pid_file); g_deinit(); - g_exit(0); + g_exit(status); } /* starting logging subsystem */ @@ -428,11 +630,17 @@ main(int argc, char **argv) g_exit(1); } + if (read_xrdp_ini_startup_params(&startup_params) != 0) + { + log_end(); + g_deinit(); + g_exit(1); + } - - if (g_file_exist(pid_file)) /* xrdp.pid */ + if ((pid = read_pid_file(pid_file)) > 0 && g_pid_is_active(pid)) { - LOG(LOG_LEVEL_ALWAYS, "It looks like xrdp is already running."); + LOG(LOG_LEVEL_ALWAYS, + "It looks like xrdp (pid=%d) is already running.", pid); LOG(LOG_LEVEL_ALWAYS, "If not, delete %s and try again.", pid_file); log_end(); g_deinit(); @@ -444,23 +652,13 @@ main(int argc, char **argv) if (daemon) { - /* make sure containing directory exists */ g_create_path(pid_file); /* make sure we can write to pid file */ - fd = g_file_open_rw(pid_file); /* xrdp.pid */ - - if (fd == -1) - { - LOG(LOG_LEVEL_ALWAYS, - "running in daemon mode with no access to pid files, quitting"); - log_end(); - g_deinit(); - g_exit(1); - } + int pid_fd = g_file_open_rw(pid_file); /* xrdp.pid */ - if (g_file_write(fd, "0", 1) == -1) + if (pid_fd == -1) { LOG(LOG_LEVEL_ALWAYS, "running in daemon mode with no access to pid files, quitting"); @@ -469,14 +667,14 @@ main(int argc, char **argv) g_exit(1); } - g_file_close(fd); - g_file_delete(pid_file); - } + /* Before daemonising, check we can listen. + * If we can't listen, exit with failure status */ + struct xrdp_listen *xrdp_listen; + xrdp_listen = xrdp_listen_create(&startup_params); + int status = xrdp_listen_init(xrdp_listen); + xrdp_listen_delete(xrdp_listen); - if (daemon) - { - /* if can't listen, exit with failure status */ - if (xrdp_listen_test(&startup_params) != 0) + if (status != 0) { LOG(LOG_LEVEL_ALWAYS, "Failed to start xrdp daemon, " "possibly address already in use."); @@ -486,6 +684,7 @@ main(int argc, char **argv) or systemd cannot detect xrdp daemon couldn't start properly */ g_exit(1); } + /* start of daemonizing code */ pid = g_fork(); @@ -506,21 +705,10 @@ main(int argc, char **argv) } g_sleep(1000); - /* write the pid to file */ - pid = g_getpid(); - fd = g_file_open_rw(pid_file); /* xrdp.pid */ - - if (fd == -1) - { - LOG(LOG_LEVEL_WARNING, "Can't open %s for writing [%s]", - pid_file, g_get_strerror()); - } - else - { - g_sprintf(text, "%d", pid); - g_file_write(fd, text, g_strlen(text)); - g_file_close(fd); - } + /* write our pid to file */ + g_sprintf(text, "%d", g_getpid()); + g_file_write(pid_fd, text, g_strlen(text)); + g_file_close(pid_fd); g_sleep(1000); g_file_close(0); @@ -542,43 +730,51 @@ main(int argc, char **argv) /* end of daemonizing code */ } - g_set_threadid(tc_get_threadid()); - g_listen = xrdp_listen_create(); - g_signal_user_interrupt(xrdp_shutdown); /* SIGINT */ - g_signal_pipe(xrdp_sig_no_op); /* SIGPIPE */ - g_signal_terminate(xrdp_shutdown); /* SIGTERM */ - g_signal_child_stop(xrdp_child); /* SIGCHLD */ - g_signal_hang_up(xrdp_sig_no_op); /* SIGHUP */ - g_set_sync_mutex(tc_mutex_create()); - g_set_sync1_mutex(tc_mutex_create()); - pid = g_getpid(); - LOG(LOG_LEVEL_INFO, "starting xrdp with pid %d", pid); - g_snprintf(text, 255, "xrdp_%8.8x_main_term", pid); - g_set_term_event(g_create_wait_obj(text)); - - if (g_get_term() == 0) + g_listen = xrdp_listen_create(&startup_params); + if (xrdp_listen_init(g_listen) != 0) { - LOG(LOG_LEVEL_WARNING, "error creating g_term_event"); + LOG(LOG_LEVEL_ALWAYS, "Failed to start xrdp daemon, " + "possibly address already in use."); } + else if (check_drop_privileges(&startup_params) == 0) + { + g_set_threadid(tc_get_threadid()); + g_signal_user_interrupt(xrdp_shutdown); /* SIGINT */ + g_signal_pipe(xrdp_sig_no_op); /* SIGPIPE */ + g_signal_terminate(xrdp_shutdown); /* SIGTERM */ + g_signal_child_stop(xrdp_child); /* SIGCHLD */ + g_signal_hang_up(xrdp_sig_no_op); /* SIGHUP */ + g_set_sync_mutex(tc_mutex_create()); + g_set_sync1_mutex(tc_mutex_create()); + pid = g_getpid(); + LOG(LOG_LEVEL_INFO, "starting xrdp with pid %d", pid); + g_snprintf(text, 255, "xrdp_%8.8x_main_term", pid); + g_set_term_event(g_create_wait_obj(text)); - g_snprintf(text, 255, "xrdp_%8.8x_main_sigchld", pid); - g_set_sigchld_event(g_create_wait_obj(text)); + if (g_get_term() == 0) + { + LOG(LOG_LEVEL_WARNING, "error creating g_term_event"); + } - if (g_get_sigchld() == 0) - { - LOG(LOG_LEVEL_WARNING, "error creating g_sigchld_event"); - } + g_snprintf(text, 255, "xrdp_%8.8x_main_sigchld", pid); + g_set_sigchld_event(g_create_wait_obj(text)); - g_snprintf(text, 255, "xrdp_%8.8x_main_sync", pid); - g_set_sync_event(g_create_wait_obj(text)); + if (g_get_sigchld() == 0) + { + LOG(LOG_LEVEL_WARNING, "error creating g_sigchld_event"); + } - if (g_get_sync_event() == 0) - { - LOG(LOG_LEVEL_WARNING, "error creating g_sync_event"); + g_snprintf(text, 255, "xrdp_%8.8x_main_sync", pid); + g_set_sync_event(g_create_wait_obj(text)); + + if (g_get_sync_event() == 0) + { + LOG(LOG_LEVEL_WARNING, "error creating g_sync_event"); + } + + exit_status = xrdp_listen_main_loop(g_listen); } - g_listen->startup_params = &startup_params; - exit_status = xrdp_listen_main_loop(g_listen); xrdp_listen_delete(g_listen); tc_mutex_delete(g_get_sync_mutex()); @@ -596,10 +792,10 @@ main(int argc, char **argv) g_delete_wait_obj(g_get_sync_event()); g_set_sync_event(0); - /* only main process should delete pid file */ - if (daemon && (pid == g_getpid())) + if (daemon) { - /* delete the xrdp.pid file */ + /* Try to delete the PID file, although if we've dropped + * privileges this won't be successful */ g_file_delete(pid_file); } diff --git a/xrdp/xrdp.h b/xrdp/xrdp.h index 0e34b4e068..0cf26cccb1 100644 --- a/xrdp/xrdp.h +++ b/xrdp/xrdp.h @@ -202,13 +202,14 @@ xrdp_process_main_loop(struct xrdp_process *self); /* xrdp_listen.c */ struct xrdp_listen * -xrdp_listen_create(void); +xrdp_listen_create(struct xrdp_startup_params *startup_params); +int +xrdp_listen_init(struct xrdp_listen *self); void xrdp_listen_delete(struct xrdp_listen *self); int xrdp_listen_main_loop(struct xrdp_listen *self); -int -xrdp_listen_test(struct xrdp_startup_params *startup_params); + /* xrdp_region.c */ struct xrdp_region * diff --git a/xrdp/xrdp.ini.in b/xrdp/xrdp.ini.in index 905266524c..5fa0e0c3bc 100644 --- a/xrdp/xrdp.ini.in +++ b/xrdp/xrdp.ini.in @@ -27,6 +27,12 @@ port=3389 ; prefer use vsock://: above use_vsock=false +; Unprivileged User name and group to run the xrdp daemon. +; It is HIGHLY RECOMMENDED you set these values. See the xrdp.ini(5) +; manpage for more information on setting and checking these. +#runtime_user=xrdp +#runtime_group=xrdp + ; regulate if the listening socket use socket option tcp_nodelay ; no buffering will be performed in the TCP stack tcp_nodelay=true diff --git a/xrdp/xrdp_listen.c b/xrdp/xrdp_listen.c index 099b7735ac..e06b8ec3a4 100644 --- a/xrdp/xrdp_listen.c +++ b/xrdp/xrdp_listen.c @@ -55,7 +55,7 @@ xrdp_listen_create_pro_done(struct xrdp_listen *self) /*****************************************************************************/ struct xrdp_listen * -xrdp_listen_create(void) +xrdp_listen_create(struct xrdp_startup_params *startup_params) { struct xrdp_listen *self; @@ -64,6 +64,7 @@ xrdp_listen_create(void) self->trans_list = list_create(); self->process_list = list_create(); self->fork_list = list_create(); + self->startup_params = startup_params; if (g_process_sem == 0) { @@ -153,95 +154,6 @@ xrdp_process_run(void *in_val) LOG_DEVEL(LOG_LEVEL_TRACE, "process done"); return 0; } - -/*****************************************************************************/ -static int -xrdp_listen_get_startup_params(struct xrdp_listen *self) -{ - int fd; - int index; - int port_override; - int fork_override; - char *val; - struct list *names; - struct list *values; - struct xrdp_startup_params *startup_params; - - startup_params = self->startup_params; - port_override = startup_params->port[0] != 0; - fork_override = startup_params->fork; - fd = g_file_open_ro(startup_params->xrdp_ini); - if (fd != -1) - { - names = list_create(); - names->auto_free = 1; - values = list_create(); - values->auto_free = 1; - if (file_read_section(fd, "globals", names, values) == 0) - { - for (index = 0; index < names->count; index++) - { - val = (char *)list_get_item(names, index); - if (val != 0) - { - if (g_strcasecmp(val, "port") == 0) - { - if (port_override == 0) - { - val = (char *) list_get_item(values, index); - g_strncpy(startup_params->port, val, - sizeof(startup_params->port) - 1); - } - } - if (g_strcasecmp(val, "fork") == 0) - { - if (fork_override == 0) - { - val = (char *) list_get_item(values, index); - startup_params->fork = g_text2bool(val); - } - } - - if (g_strcasecmp(val, "tcp_nodelay") == 0) - { - val = (char *)list_get_item(values, index); - startup_params->tcp_nodelay = g_text2bool(val); - } - - if (g_strcasecmp(val, "tcp_keepalive") == 0) - { - val = (char *)list_get_item(values, index); - startup_params->tcp_keepalive = g_text2bool(val); - } - - if (g_strcasecmp(val, "tcp_send_buffer_bytes") == 0) - { - val = (char *)list_get_item(values, index); - startup_params->tcp_send_buffer_bytes = g_atoi(val); - } - - if (g_strcasecmp(val, "tcp_recv_buffer_bytes") == 0) - { - val = (char *)list_get_item(values, index); - startup_params->tcp_recv_buffer_bytes = g_atoi(val); - } - - if (g_strcasecmp(val, "use_vsock") == 0) - { - val = (char *)list_get_item(values, index); - startup_params->use_vsock = g_text2bool(val); - } - } - } - } - - list_delete(names); - list_delete(values); - g_file_close(fd); - } - return 0; -} - /*****************************************************************************/ static int xrdp_listen_stop_all_listen(struct xrdp_listen *self) @@ -651,8 +563,10 @@ xrdp_listen_pp(struct xrdp_listen *self, int *index, } /*****************************************************************************/ -static int -xrdp_listen_process_startup_params(struct xrdp_listen *self) +/* returns 0 if xrdp is listening correctly + returns 1 if xrdp is not listening correctly */ +int +xrdp_listen_init(struct xrdp_listen *self) { int mode; /* TRANS_MODE_TCP*, TRANS_MODE_UNIX, TRANS_MODE_VSOCK */ int error; @@ -886,18 +800,7 @@ xrdp_listen_main_loop(struct xrdp_listen *self) struct trans *ltrans; self->status = 1; - if (xrdp_listen_get_startup_params(self) != 0) - { - LOG(LOG_LEVEL_ERROR, "xrdp_listen_main_loop: xrdp_listen_get_port failed"); - self->status = -1; - return 1; - } - if (xrdp_listen_process_startup_params(self) != 0) - { - LOG(LOG_LEVEL_ERROR, "xrdp_listen_main_loop: xrdp_listen_get_port failed"); - self->status = -1; - return 1; - } + term_obj = g_get_term(); /*Global termination event */ sigchld_obj = g_get_sigchld(); sync_obj = g_get_sync_event(); @@ -1037,27 +940,3 @@ xrdp_listen_main_loop(struct xrdp_listen *self) self->status = -1; return 0; } - -/*****************************************************************************/ -/* returns 0 if xrdp can listen - returns 1 if xrdp cannot listen */ -int -xrdp_listen_test(struct xrdp_startup_params *startup_params) -{ - struct xrdp_listen *xrdp_listen; - - xrdp_listen = xrdp_listen_create(); - xrdp_listen->startup_params = startup_params; - if (xrdp_listen_get_startup_params(xrdp_listen) != 0) - { - xrdp_listen_delete(xrdp_listen); - return 1; - } - if (xrdp_listen_process_startup_params(xrdp_listen) != 0) - { - xrdp_listen_delete(xrdp_listen); - return 1; - } - xrdp_listen_delete(xrdp_listen); - return 0; -} diff --git a/xrdp/xrdp_types.h b/xrdp/xrdp_types.h index 9aa744ba72..97c2b920d1 100644 --- a/xrdp/xrdp_types.h +++ b/xrdp/xrdp_types.h @@ -738,6 +738,10 @@ struct xrdp_startup_params int tcp_nodelay; int tcp_keepalive; int use_vsock; + // These should be local users/groups, and so we shouldn't need + // a lot of storage for them. + char runtime_user[64]; + char runtime_group[64]; }; /*