#!/bin/sh

set -e

sasluser="user$$"
saslpass="pass$$"
ldap_admin_pw="ldapadminpw$$"
ldap_domain="example.fake"
ldap_suffix="dc=example,dc=fake"
ldap_admin_dn="cn=admin,${ldap_suffix}"
MECHS="sasldb pam ldap"
default_mux_dir="/var/run/saslauthd"

cleanup() {
    if [ $? -ne 0 ]; then
        set +e
        echo "## Something failed, gathering logs"
        echo
        echo "## saslauthd"
        journalctl -u saslauthd.service
        echo
        echo "## syslog"
        tail -n 500 /var/log/syslog
        echo
        echo "## sasldb2 users"
        sasldblistusers2
        echo
        echo "## slapd"
        journalctl -u slapd
    fi
    set +e
    if [ -n "${sasluser}" ]; then
        saslpasswd2 -d "${sasluser}" > /dev/null 2>&1 || :
        userdel -r "${sasluser}" > /dev/null 2>&1 || :
    fi
    systemctl stop saslauthd || :
    rm -f /etc/saslauthd.conf || :
    if [ -n "${custom_mux_dir}" ]; then
        rm -rf "${custom_mux_dir}"
    fi
}

die() {
    echo "FAIL"
    echo "exit status: ${result}"
    echo "output:"
    echo "${output}"
    exit 1
}


trap cleanup EXIT

_configure_saslauthd() {
    sed -r -i "s,^MECHANISMS=.*,MECHANISMS=\"${1}\"," /etc/default/saslauthd
    if [ "${1}" = "ldap" ]; then
        cat > /etc/saslauthd.conf <<EOF
ldap_servers: ldap://127.0.0.1
ldap_auth_method: bind
ldap_search_base: ${ldap_suffix}
EOF
    fi
}

_populate_ldap_tree() {
    {
        cat <<EOF
dn: ou=People,${ldap_suffix}
ou: People
objectClass: organizationalUnit

EOF
    } | ldapadd -x -D "${ldap_admin_dn}" -w "${ldap_admin_pw}"

}

_add_ldap_user() {
    {
        cat <<EOF
dn: uid=${sasluser},ou=People,${ldap_suffix}
uid: ${sasluser}
objectClass: inetOrgPerson
objectClass: posixAccount
cn: ${sasluser}
sn: ${sasluser}
givenName: ${sasluser}
mail: ${sasluser}@${ldap_domain}
userPassword: $(slappasswd -s ${saslpass})
uidNumber: 10001
gidNumber: 10001
loginShell: /bin/bash
homeDirectory: /home/${sasluser}

EOF
    } | ldapadd -x -D "${ldap_admin_dn}" -w "${ldap_admin_pw}"
}

_setup_slapd() {
    # MUST use REAL TABS as delimiters below!
    debconf-set-selections << EOF
slapd	slapd/domain	string	${ldap_domain}
slapd	shared/organization	string	${ldap_domain}
slapd	slapd/password1	password	${ldap_admin_pw}
slapd	slapd/password2	password	${ldap_admin_pw}
EOF
    rm -rf /var/backups/*slapd* /var/backups/unknown*ldapdb
    dpkg-reconfigure -fnoninteractive -pcritical slapd 2>&1
    systemctl restart slapd # http://bugs.debian.org/1010678
    ldapmodify -Y EXTERNAL -H ldapi:/// 2>&1 <<EOF
dn: cn=config
changetype: modify
replace: olcLogLevel
olcLogLevel: stats

EOF
    _populate_ldap_tree
}

_setup_saslauthd_sasldb() {
    _configure_saslauthd "${1}"
    echo "${saslpass}" | saslpasswd2 -p -c "${sasluser}"
}

_setup_saslauthd_pam() {
    _configure_saslauthd "${1}"
    # this user will exist already on the second run, so let's ignore the error
    useradd "${sasluser}" 2>/dev/null || :
    echo "${sasluser}:${saslpass}" | chpasswd
}

_set_mux_dir() {
    local mux_dir="${1}"
    sed -r -i "s,(^OPTIONS=.*)-m[[:blank:]]*[^ \"]+(.*),\1-m ${mux_dir}\2," /etc/default/saslauthd
}

_setup_saslauthd_ldap() {
    _configure_saslauthd "${1}"
    _setup_slapd
    _add_ldap_user
}

setup_saslauthd() {
    if echo "${MECHS}" | grep -qw "${1}"; then
        "_setup_saslauthd_${1}" "${1}"
    else
        echo "setup(): invalid mechanism \"${1}\""
        exit 1
    fi
    _restart_saslauthd
}

_restart_saslauthd() {
    local tries=5
    local mux_dir=$(_get_current_mux_dir)

    systemctl restart saslauthd
    if ! systemctl is-active --quiet saslauthd; then
        echo "ERROR: saslauthd service is not active after restart"
        return 1
    fi
    echo -n "Waiting for saslauthd restart "
    while [ ${tries} -ne 0 ]; do
        echo -n "."
        if [ -S "${mux_dir}/mux" ]; then
            echo
            break
        fi
        tries=$((tries-1))
        sleep 1s
    done
    if [ ${tries} -eq 0 ]; then
        echo "ERROR: saslauthd socket not available after start"
        return 1
    fi
}

_get_current_mux_dir() {
    local mux_dir=""

    mux_dir=$(grep -E "^OPTIONS" /etc/default/saslauthd | sed -r 's,.*-m[[:blank:]]*([^ \"]+).*,\1,' | head -n 1)
    if [ -z "${mux_dir}" ]; then
        echo "ERROR: couldn't parse mux directory from /etc/default/saslauthd"
        return 1
    fi
    if [ ! -d ${mux_dir} ]; then
        echo "ERROR: mux directory ${mux_dir} does not exist"
        return 1
    fi
    echo "${mux_dir}"
}

override_systemd_throttling() {
    local svc="${1}"

    mkdir -p "/run/systemd/system/${svc}.service.d"
    cat > "/run/systemd/system/${svc}.service.d/override.conf" <<EOF
[Unit]
StartLimitIntervalSec=0
EOF
    systemctl daemon-reload
}

run_mech_test() {
    local mech="${1}"
    local mux_path="$(_get_current_mux_dir)/mux"

    echo "Setting up saslauthd with mecanism ${mech}"
    setup_saslauthd ${mech}

    # test correct credentials
    echo -n "Authentication of user ${sasluser} with correct password should succeed... "
    result=0
    output=$(testsaslauthd -u "${sasluser}" -p "${saslpass}" -f "${mux_path}" 2>&1) || result=$?
    [ ${result} -ne 0 ] && die
    [ "${output}" != '0: OK "Success."' ] && die
    echo "OK"

    # test incorrect credentials
    echo -n "Authentication of unknown user ${sasluser}notexist should fail... "
    result=0
    output=$(testsaslauthd -u "${sasluser}"notexist -p "${saslpass}" -f "${mux_path}" 2>&1) || result=$?
    [ ${result} = 0 ] && die
    [ "${output}" != '0: NO "authentication failed"' ] && die
    echo "OK"

    echo -n "Authentication of user ${sasluser} with incorrect password should fail... "
    result=0
    output=$(testsaslauthd -u "${sasluser}" -p "${saslpass}"incorrect -f "${mux_path}" 2>&1)  || result=$?
    [ ${result} = 0 ] && die
    [ "${output}" != '0: NO "authentication failed"' ] && die
    echo "OK"
    echo
}

run_mech_tests() {
    local mechanisms="$@"
    for mech in ${mechanisms}; do
        run_mech_test ${mech}
    done
}

override_systemd_throttling saslauthd

echo "## Running saslauthd mechanism tests with default mux directory ${default_mux_dir}: ${MECHS}"
run_mech_tests ${MECHS}

custom_mux_dir=$(mktemp -d)
chown root:sasl "${custom_mux_dir}"
chmod 710 "${custom_mux_dir}"
echo "## Changing saslauthd mux directory to custom path ${custom_mux_dir}"
_set_mux_dir "${custom_mux_dir}"
_restart_saslauthd

echo -n "### Verifying saslauthd was started with the custom mux path... "
saslauthd_pid=$(cat "${custom_mux_dir}/saslauthd.pid")
if ! tr '\0' '\n' < /proc/${saslauthd_pid}/cmdline | grep -qxF "${custom_mux_dir}"; then
    echo "FAIL: saslauthd cmdline does not contain ${custom_mux_dir}"
    exit 1
fi
echo "OK"

echo -n "### Verifying the saslauthd socket is at custom path ${custom_mux_dir}/mux... "
if [ -S "${custom_mux_dir}/mux" ]; then
    echo "OK"
else
    echo "FAIL: socket not present at ${custom_mux_dir}/mux"
    exit 1
fi

echo
echo "## Re-running saslauthd mechanism tests with custom mux directory ${custom_mux_dir}: ${MECHS}"

run_mech_tests ${MECHS}
