# Check privilige drop: UID/GID changes, chdir, environment, and file
# descriptors

# ensure failure to drop privileges doesn't retain root privileges
sed -ri 's/^#(user|group)\s*=\s*$/\1 = nonexistent-\1/' /etc/lacme/lacme.conf
! lacme account 2>"$STDERR" || fail
grepstderr -Fxq "getgrnam(nonexistent-group)"
grepstderr -Fxq "Error: Invalid client version"

sed -ri 's/^group\s*=\s*nonexistent.*/#&/' /etc/lacme/lacme.conf
! lacme account 2>"$STDERR" || fail
grepstderr -Fxq "getpwnam(nonexistent-user)"
grepstderr -Fxq "Error: Invalid client version"

# create wrapper to inspect processes
STATUSDIR="/dev/shm/lacme-wrap"
install -oroot -groot -m0755 /dev/stdin /run/lacme-wrap <<-EOF
	#!/bin/sh
	set -ue
	PATH="/usr/bin:/bin"
	export PATH

	prefix="$STATUSDIR/\${1##*[/-]}"
	cat </proc/\$\$/status >"\$prefix/status"
	pwd >"\$prefix/cwd"
	stat -c "%U:%G %#a" . >"\$prefix/cwd-mod"
	sort -z </proc/\$\$/environ | tr "\\0" "\\n" >"\$prefix/environ"
	( find -P /proc/\$\$/fd -mindepth 1 \! -lname "\$0" -printf "%P %#m %U:%G %l\\n" >"\$prefix/fd" )

	exec "\$@"
EOF

# also check privilege drop for the spawned accountd
adduser --system --group \
       --home /nonexistent --no-create-home \
       --gecos "lacme account user" \
       --quiet lacme-account
sed -ri 's/^#?(user|group)\s*=\s*nonexistent.*/\1 = lacme-account/' /etc/lacme/lacme.conf
chown lacme-account: /etc/lacme/account.key

install -oroot -groot -dm0755 -- "$STATUSDIR"
install -olacme-account -groot -dm0700 -- "$STATUSDIR/accountd"
install -o_lacme-client -groot -dm0700 -- "$STATUSDIR/client"
install -o_lacme-www    -groot -dm0700 -- "$STATUSDIR/webserver"

# test with a group that's not the primary group (nogroup) of _lacme-www etc
addgroup --system nogroup2
sed -ri 's|^#?group\s*=\s*nogroup$|group = nogroup2|' /etc/lacme/lacme.conf
sed -ri 's|^#?command\s*=.*/lacme-accountd$|command = /run/lacme-wrap /usr/bin/lacme-accountd|' /etc/lacme/lacme.conf
sed -ri 's|^#?command\s*=.*/lacme/client$|command = /run/lacme-wrap /usr/libexec/lacme/client|' /etc/lacme/lacme.conf
sed -ri 's|^#?command\s*=.*/lacme/webserver$|command = /run/lacme-wrap /usr/libexec/lacme/webserver|' /etc/lacme/lacme.conf
sed -ri 's|^#?config\s*=\s*$|config = /etc/lacme/lacme-accountd.conf|' /etc/lacme/lacme.conf

check_status() {
    local path="$STATUSDIR/$1/status" user="$2" group="$3"
    UID="$(getent passwd "$user"  | cut -sd: -f3)"
    GID="$(getent group  "$group" | cut -sd: -f3)"
    [ -n "$UID" -a -n "$GID" ] || return 1
    grep -Ex "Uid:\\s+$UID\\s+$UID\\s+$UID\\s+$UID" "$path" || return 1
    grep -Ex "Gid:\\s+$GID\\s+$GID\\s+$GID\\s+$GID" "$path" || return 1
    grep -Ex "Groups:\s+$GID\s*" "$path" || return 1
}
check_cwd() {
    local path="$STATUSDIR/$1/cwd" dir="$2" cwd
    cwd="$(cat <"$path")" || return 1
    [ "$cwd" = "$dir" ] || return 1
}

check_accountd() {
    local socket_ino stderr prefix="$STATUSDIR/accountd"
    check_status accountd lacme-account lacme-account || return 1
    check_cwd    accountd / || return 1

    diff --label="a/accountd/environ" --label="b/accountd/environ" \
            --color=auto --unified "$prefix/environ" - <<-EOF
		HOME=/nonexistent
		LOGNAME=lacme-account
		PATH=/usr/bin:/bin
		SHELL=/usr/sbin/nologin
		TERM=$TERM
		USER=lacme-account
	EOF

    stderr="$(readlink -e "/proc/$$/fd/2")"
    socket_ino="$(sed -rn '/^0 .* socket:\[([0-9]+)\]$/ {s//\1/p;q}' "$prefix/fd")"
    [ -n "$socket_ino" ] || return 1
    grep -Fxq "0 0700 $UID:$GID socket:[$socket_ino]" "$prefix/fd" || return 1
    grep -Fxq "1 0700 $UID:$GID socket:[$socket_ino]" "$prefix/fd" || return 1
    grep -Fxq "2 0700 $UID:$GID $stderr" "$prefix/fd" || return 1
    sed -ri '\#^[012] #d' "$prefix/fd"
    ! test -s "$prefix/fd" || return 1
}
check_client() {
    local command="$1" cwd="$2" UID GID stdout stderr prefix="$STATUSDIR/client"
    check_status client _lacme-client nogroup2
    check_cwd    client "$cwd"

    diff --label="a/client/environ" --label="b/client/environ" \
            --color=auto --unified "$prefix/environ" - <<-EOF
		DEBUG=0
		HOME=/nonexistent
		LOGNAME=_lacme-client
		PATH=/usr/bin:/bin
		SHELL=/usr/sbin/nologin
		TERM=$TERM
		USER=_lacme-client
	EOF

    stdout="$(readlink -e "/proc/$$/fd/1")"
    stderr="$(readlink -e "/proc/$$/fd/2")"
    if [ "$command" = "account" ]; then # no pipe
        grep -Fxq "0 0500 $UID:$GID /dev/null" "$prefix/fd" || return 1
        grep -Fxq "1 0700 $UID:$GID $stdout"   "$prefix/fd" || return 1
    elif [ "$command" = "order" ]; then
        grep -Exq "0 0500 $UID:$GID pipe:\[[0-9]+\]" "$prefix/fd" || return 1
        grep -Exq "1 0300 $UID:$GID pipe:\[[0-9]+\]" "$prefix/fd" || return 1
    else
        exit 1
    fi
    grep -Fxq "2 0700 $UID:$GID $stderr" "$prefix/fd" || return 1
    sed -ri '\#^[012] #d' "$prefix/fd"

    grep -Exq "[0-9]+ 0700 $UID:$GID socket:\[[0-9]+\]" "$prefix/fd" || return 1
    sed -ri '0,\#^[0-9]+ .* socket:\[[0-9]+\]$# {//d}' "$prefix/fd"

    grep -Exq "[0-9]+ 0500 $UID:$GID /etc/lacme/lacme\.conf" "$prefix/fd" || return 1
    sed -ri '0,\#^[0-9]+ .* /etc/lacme/lacme\.conf$# {//d}' "$prefix/fd"
    ! test -s "$prefix/fd" || return 1
}
check_webserver() {
    local cwd="$1" UID GID stdout stderr prefix="$STATUSDIR/webserver"
    check_status webserver _lacme-www nogroup2
    check_cwd    webserver "$cwd"

    diff --label="a/webserver/environ" --label="b/webserver/environ" \
            --color=auto --unified "$prefix/environ" - <<-EOF
		DEBUG=0
		HOME=/nonexistent
		LOGNAME=_lacme-www
		PATH=/usr/bin:/bin
		SHELL=/usr/sbin/nologin
		TERM=$TERM
		USER=_lacme-www
	EOF

    stdout="$(readlink -e "/proc/$$/fd/1")"
    stderr="$(readlink -e "/proc/$$/fd/2")"
    grep -Fxq "0 0500 $UID:$GID /dev/null"  "$prefix/fd" || return 1
    grep -Fxq "1 0700 $UID:$GID $stdout" "$prefix/fd" || return 1
    grep -Fxq "2 0700 $UID:$GID $stderr" "$prefix/fd" || return 1
    sed -ri '\#^[012] #d' "$prefix/fd"

    grep -Exq "[0-9]+ 0700 $UID:$GID socket:\[[0-9]+\]" "$prefix/fd" || return 1
    sed -ri '0,\#^[0-9]+ .* socket:\[[0-9]+\]$# {//d}' "$prefix/fd"
    ! test -s "$prefix/fd" || return 1
}

lacme account
check_accountd
check_client account /
! test -e "$STATUSDIR/webserver/status" # account 'command' doesn't start the webserver

lacme newOrder
check_accountd
challenge_dir="$(cat "$STATUSDIR/webserver/cwd")"
[ "${challenge_dir#"/tmp/acme-challenge."}" != "$challenge_dir" ] || exit 1
check_client order "$challenge_dir"
check_webserver    "$challenge_dir"

# the temporary challenge directory is created with permissive mode
diff --label="a/webserver/cwd" --label="b/webserver/cwd" \
        --color=auto --unified "$STATUSDIR/webserver/cwd-mod" - <<-EOF
	_lacme-client:root 0755
EOF

# vim: set filetype=sh :