diff --git a/configure.ac b/configure.ac index 8b11da51..75296f23 100644 --- a/configure.ac +++ b/configure.ac @@ -646,6 +646,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/xrdp.ini.5.in b/docs/man/xrdp.ini.5.in index c7a22841..039852ad 100644 --- a/docs/man/xrdp.ini.5.in +++ b/docs/man/xrdp.ini.5.in @@ -127,9 +127,21 @@ 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/tools/Makefile.am b/tools/Makefile.am index c4f7c462..ef5c72fe 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 00000000..c6b1d04b --- /dev/null +++ b/tools/chkpriv/Makefile.am @@ -0,0 +1,31 @@ +xrdppkgdatadir=$(datadir)/xrdp + +pkglibexec_PROGRAMS = \ + xrdp-droppriv + +dist_xrdppkgdata_SCRIPTS = \ + xrdp-chkpriv + +AM_LDFLAGS = + +AM_CPPFLAGS = \ + -I$(top_srcdir)/common + +xrdp_droppriv_SOURCES = xrdp-droppriv.c + +xrdp_droppriv_LDADD = \ + $(top_builddir)/common/libcommon.la + +SUBST_VARS = sed \ + -e 's|@pkglibexecdir[@]|$(pkglibexecdir)|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 = xrdp-chkpriv + diff --git a/tools/chkpriv/xrdp-chkpriv.in b/tools/chkpriv/xrdp-chkpriv.in new file mode 100755 index 00000000..f78bb722 --- /dev/null +++ b/tools/chkpriv/xrdp-chkpriv.in @@ -0,0 +1,205 @@ +#!/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=/etc/xrdp +XRDP_INI="$CONF_DIR"/xrdp.ini +SESMAN_INI="$CONF_DIR"/sesman.ini +RSAKEYS_INI="$CONF_DIR"/rsakeys.ini +DROPPRIV=@pkglibexecdir@/xrdp-droppriv + +# ----------------------------------------------------------------------------- +# 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 + 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 + echo "-Info- This system is not configured to run xrdp without privilege" + exit 0 +fi + +if [ -z "$runtime_user" ] || [ -z "$runtime_group" ]; then + echo "-Error- Both 'runtime_user' and 'runtime_group' must be set" + errors=$(( errors + 1 )) + exit 1 +fi + +if getent passwd "$runtime_user" >/dev/null ; then + echo "-Info- runtime_user '$runtime_user' appears to exist" +else + echo "-Error- runtime_user '$runtime_user' does not exist" + errors=$(( errors + 1 )) +fi + +GID= +if getent group "$runtime_group" >/dev/null ; then + echo "-Info- runtime_group '$runtime_group' appears to exist" + GID=$(getent group xrdp | cut -d: -f3) +else + echo "-Error- runtime_group '$runtime_group' does not exist" + errors=$(( errors + 1 )) +fi + +# Groups agree between sesman and xrdp? +if [ "$runtime_user" = "$SessionSockdirGroup" ]; then + echo "-Info- xrdp.ini and sesman.ini agree on group ownbership" +else + echo "-Error- xrdp.ini and sesman.ini do not agree on group ownbership" + 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 + echo "-Info- $RSAKEYS_INI has correct permissions" + else + if [ "$1" != 640 ]; then + echo "-Error- $RSAKEYS_INI should have permissions -rw-r-----" + errors=$(( errors + 1 )) + fi + if [ "$2" != 0 ]; then + echo "-Error- $RSAKEYS_INI should be owned by root" + errors=$(( errors + 1 )) + fi + if [ "$3" != "$GID" ]; then + echo "-Error- $RSAKEYS_INI should be in the $runtime_group group" + errors=$(( errors + 1 )) + fi + fi + fi +else + echo "-Error- $RSAKEYS_INI does not exist" + errors=$(( errors + 1 )) +fi + +# Are cert and key readable by the user? +# +# These aren't necessarily our files, so we can't be 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 +if ! [ -e $certificate ]; then + echo "-Error- $certificate does not exist" + errors=$(( errors + 1 )) +elif $DROPPRIV "$runtime_user" "$runtime_group" sh -c '[ -r '"$certificate"' ]' +then + echo "-Info- $certificate is readable by $runtime_user:$runtime_group" +else + echo "-Error- $certificate is not readable by $runtime_user:$runtime_group" + errors=$(( errors + 1 )) +fi + +if ! [ -e $key_file ]; then + echo "-Error- $key_file does not exist" + errors=$(( errors + 1 )) +elif $DROPPRIV "$runtime_user" "$runtime_group" sh -c '[ -r '"$key_file"' ]' + sh -c '[ -r '"$key_file"' ]' +then + echo "-Info- $key_file is readable by $runtime_user:$runtime_group" +else + echo "-Error- $key_file is not readable by $runtime_user:$runtime_group" + errors=$(( errors + 1 )) +fi + +echo +if [ $errors -eq 0 ]; then + echo "-Summary- Permissions appear to be correct to run xrdp unprivileged" + status=0 +else + 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 100755 index 00000000..185f575b --- /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.ini.in b/xrdp/xrdp.ini.in index d2998a3c..efa25fc6 100644 --- a/xrdp/xrdp.ini.in +++ b/xrdp/xrdp.ini.in @@ -28,11 +28,8 @@ port=3389 use_vsock=false ; Unprivileged User name and group to run the xrdp daemon. -; It is HIGHLY RECOMMENDED you set these values. -; A suitable user and group can be added with a command like this (Linux):- -; useradd xrdp -d / -c 'xrdp daemon' -s /usr/sbin/nologin -; Be aware that runtime_group here, and SessionSockdirGroup in sesman.ini -; MUST be the same if you want to run sessions. +; 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