#!/bin/bash

#----------------------------------------------------------------------
# ACME client written with process isolation and minimal privileges in mind
# (test suite)
# Copyright © 2015-2021 Guilhem Moulin <guilhem@fripost.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#----------------------------------------------------------------------

set -ue
PATH="/usr/bin:/bin"
export PATH

usage() {
    local rv="${1-0}"
    echo "Usage: $0 [--deb|--dev] [TEST..]" >&2
    exit $rv
}

# Setup: for any subdomain under $DOMAINNAME,
# http://$subdomain.$DOMAINNAME/.well-known/acme-challenge/$challenge
# must be routed to this machine.
# This can be done with a wildcard DNS record and opening tcp/80 in firewall.
DOMAINNAME="lacme-test.guilhem.org"
ACMEAPI_SERVER="https://acme-staging-v02.api.letsencrypt.org/directory"

MODE="dev"
DISTRIBUTION="sid"
BUILDDIR="build/test"
while [ $# -gt 0 ]; do
    case "$1" in
        --deb) MODE="deb"; shift;;
        --dev) MODE="dev"; shift;;
        --help|-h) usage 0;;
        -*) echo "Error: Unknown option $1" >&2; usage 1;;
        --) shift; break;;
        *) break;
    esac
done

cd "$(dirname -- "$0")"
declare -a TESTS=()
if [ $# -eq 0 ]; then
    # always start with registration, the account key might be new
    TESTS+=( "register" )
    for t in tests/*; do
        if [ "$t" != "tests/register" ] && [ "${t#tests/account-encrypted-}" = "$t" ] && [ -f "$t" ]; then
            # skip registration and non-interactive tests
            TESTS+=( "${t#tests/}" )
        fi
    done
else
    for t in "$@"; do
        t="${t#tests/}"
        if [ -f "tests/$t" ]; then
            TESTS+=( "$t" )
        else
            echo "Error: '$1': no such test" >&2
            exit 1
        fi
    done
fi

if [ "$MODE" = "deb" ]; then
    DISTRIBUTION="$(dpkg-parsechangelog -S Distribution)"
    [ "$DISTRIBUTION" != "UNRELEASED" ] || DISTRIBUTION="sid"
    PKG_DESTDIR="${XDG_CACHE_HOME:-"$HOME/.cache"}/build-area"
fi

ACCOUNT_KEY="$BUILDDIR/account.key"
mkdir -pv -- "$BUILDDIR"
if [ ! -f "$ACCOUNT_KEY" ]; then
    # keep the account key (up to `make clean`) to avoid hitting
    # rate-liming -- currently 50 registrations per 3h per IP, see
    # https://letsencrypt.org/docs/staging-environment/
    echo "Generating account key $ACCOUNT_KEY..." >&2
    openssl genpkey -algorithm RSA -out "$ACCOUNT_KEY"
fi

ARCH="$(dpkg-architecture -qDEB_BUILD_ARCH)"
CHROOT=""

cleanup() {
    if [ -n "$CHROOT" ]; then
        schroot -c "$CHROOT" -e
    fi
}
trap cleanup EXIT INT TERM

run() {
    local t="tests/$1" rootdir version sub
    if [ ! -f "$t" ]; then
        echo "Error: '$1': no such test" >&2
        exit 1
    fi

    # Don't need to rebuild for each test, but editing the code at the
    # same time might cause `make install` to rebuild a wrong version
    make all -- \
        BUILDDIR="$BUILDDIR" \
        DESTDIR="" \
        exec_prefix="/usr" \
        datadir="/usr/share" \
        runstatedir="/run" \
        lacme_www_user=_lacme-www \
        lacme_www_group=nogroup \
        lacme_client_user=_lacme-client \
        lacme_client_group=nogroup \
        acmeapi_server="$ACMEAPI_SERVER"

    CHROOT="$(schroot -c "$DISTRIBUTION-$ARCH-sbuild" -b)"
    rootdir="/run/schroot/mount/$CHROOT"

    if [ "$MODE" = "deb" ]; then
        version="$(dpkg-parsechangelog -S Version)"
        echo "Installing lacme $version into $CHROOT..." >&2
        install -vt "$rootdir/dev/shm" -m0644 -- \
            "$PKG_DESTDIR/lacme_${version}_all.deb" \
            "$PKG_DESTDIR/lacme-accountd_${version}_all.deb"
        sudo schroot -d"/" -c "$CHROOT" -r -- \
            env DEBIAN_FRONTEND="noninteractive" apt install -y \
                "/dev/shm/lacme_${version}_all.deb" \
                "/dev/shm/lacme-accountd_${version}_all.deb"

    elif [ "$MODE" = "dev" ]; then
        echo "Installing lacme dev into $CHROOT..." >&2
        sudo make install -- \
            BUILDDIR="$BUILDDIR" \
            DESTDIR="$rootdir" \
            exec_prefix="$rootdir/usr" \
            datadir="$rootdir/usr/share" \
            runstatedir="$rootdir/run"
        sudo schroot -d"/" -c "$CHROOT" -r -- \
            env DEBIAN_FRONTEND="noninteractive" apt install -y \
                adduser \
                libconfig-tiny-perl \
                libcrypt-openssl-rsa-perl \
                libjson-perl \
                libnet-ssleay-perl \
                libtimedate-perl \
                libwww-perl \
                openssl
        sudo schroot -d"/" -c "$CHROOT" -r -- \
            adduser --force-badname --system \
                --home /nonexistent --no-create-home \
                --gecos "lacme www user" \
                --quiet _lacme-www
        sudo schroot -d"/" -c "$CHROOT" -r -- \
             adduser --force-badname --system \
                --home /nonexistent --no-create-home \
                --gecos "lacme client user" \
                --quiet _lacme-client
    fi

    # set up staging environment, see https://letsencrypt.org/docs/staging-environment/
    sudo install -oroot -groot -m0644 -vt "$rootdir/usr/share/lacme" certs-staging/*.pem
    sudo install -oroot -groot -m0644 -vT "$BUILDDIR/certs-staging/ca-certificates.crt" \
        "$rootdir/usr/share/lacme/ca-certificates.crt"
    sudo schroot -d"/" -c "$CHROOT" -r -- \
        sed -ri "0,/^#?server\\s*=.*/ {s||server = $ACMEAPI_SERVER|}" /etc/lacme/lacme.conf

    # install account key and configure lacme accordingly
    sudo install -oroot -groot -m0600 -vT -- "$BUILDDIR/account.key" \
        "$rootdir/etc/lacme/account.key"
    sudo schroot -d"/" -c "$CHROOT" -r -- \
        sed -ri '0,\|^#?privkey\s*=.*| {s||privkey = file:/etc/lacme/account.key|}' \
            /etc/lacme/lacme-accountd.conf

    # use lacme's internal webserver bound to INADDR_ANY port 80
    sudo schroot -d"/" -c "$CHROOT" -r -- \
        sed -ri 's|^#?listen\s*=.*|listen = 0.0.0.0|' /etc/lacme/lacme.conf

    # use a sample lacme-certs.conf, with a random subdomain so we can
    # verify that challenges are answered correctly
    sub="$(head -c10 /dev/urandom | base32 -w0)"
    sudo tee "$rootdir/etc/lacme/lacme-certs.conf.d/simpletest-rsa.conf" >/dev/null <<- EOF
		[simpletest-rsa]
		certificate-key = /etc/lacme/simpletest.rsa.key
		certificate-chain = /etc/lacme/simpletest.rsa.crt
		subject = /CN=${sub,,[A-Z]}.$DOMAINNAME
	EOF
    sudo schroot -d"/" -c "$CHROOT" -r -- \
        openssl genpkey -algorithm RSA -out /etc/lacme/simpletest.rsa.key

    # copy test wrapper and unit file
    local testdir="/dev/shm/lacme.test"
    sudo install -oroot -groot -m0700 -d -- "$rootdir$testdir"
    sudo install -oroot -groot -m0755 -T -- /dev/stdin "$rootdir$testdir/run" <<-EOF
		STDERR="$testdir/stderr"
		touch "\$STDERR"
		fail() {
		    set +x
		    local rv=\$? i
		    if [ \$rv -eq 0 ]; then rv=1; fi
		    if [ -s "\$STDERR" ]; then
		        echo "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"
		        cat <"\$STDERR" >&2
		        echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
		    fi
		    [ \$# -eq 0 ] || echo "Error: \$*" >&2
		    exit \$rv
		}
		grepstderr() {
		    grep "\$@" <"\$STDERR" || fail
		}
		ngrepstderr() {
		    ! grep "\$@" <"\$STDERR" || fail
		}
		set -x
	EOF
    sudo tee -a "$rootdir$testdir/run" >/dev/null <"$t"

    sudo schroot -d"/" -c "$CHROOT" -r -- env -i \
        USER="root" \
        HOME="/root" \
        SHELL="/bin/sh" \
        LOGNAME="root" \
        TERM="$TERM" \
        PATH="/usr/sbin:/usr/bin:/sbin:/bin" \
        DOMAINNAME="$DOMAINNAME" \
        sh -ue "$testdir/run" || return $?
}

RV=0
declare -a PASSED=() FAILED=()
for t in "${TESTS[@]}"; do
    run "$t" && rv=0 || rv=$?
    if [ -n "$CHROOT" ]; then
        # clean up
        schroot -c "$CHROOT" -e
        CHROOT=""
    fi
    if [ $rv -eq 0 ]; then
        PASSED+=( "$t" )
    else
        FAILED+=( "$t" )
        RV=$rv
        break # stop at the first failure
    fi
done

echo >&2
echo "================================================================================" >&2

echo "PASSED: ${PASSED[*]:-"(none)"}" >&2
if [ ${#FAILED[@]} -gt 0 ]; then
    echo "FAILED: ${FAILED[*]}" >&2
fi
exit $RV