#!/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=/usr/lib/xrdp/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
