#!/bin/bash

set -e
set -o pipefail

source debian/tests/util

declare -r domain="EXAMPLE"
declare -r realm="EXAMPLE.FAKE"
declare -r adminpass="Passw0rd"
declare -r test_user="test_user_${RANDOM}"
declare -r test_pw="test_user_secret_${RANDOM}"
declare -A user_pass
user_pass[Administrator]="${adminpass}"
user_pass[${test_user}]="${test_pw}"
declare -A join_method_deps
# Minimum set of deps: let realmd install the extra dependencies
# as needed, depending on the join method.
join_method_deps[realmd_sssd]="realmd krb5-user smbclient"
join_method_deps[realmd_winbind]="realmd krb5-user smbclient"


cleanup() {
    rc=$?
    set +e # so we don't exit midcleanup
    if [ ${rc} -ne 0 ]; then
        echo "## Something failed, gathering logs"
        echo
        echo "## smb.conf"
        cat /etc/samba/smb.conf
        echo
        echo "## resolv.conf"
        cat /etc/resolv.conf
        echo
        echo "## resolvectl status"
        resolvectl status
        echo "## journal for samba-ad-dc.service"
        journalctl -u samba-ad-dc.service --lines 500
        echo
        for log in /var/log/samba/log.*; do
            # skip compressed logrotated files
            if [ "${log%.gz}" != "${log}" ]; then
                continue
            fi
            [ -s "${log}" ] || continue
            echo "## $(basename ${log}):"
            tail -n 500 "${log}"
            echo
        done
        echo "## syslog"
        tail -n 500 /var/log/syslog
    fi
}

trap cleanup EXIT

assert_testparm() {
    local parameter="${1}"
    local expected_value="${2}"
    local current_value=""
    local -i retval=0

    echo -n "Asserting ${parameter} is ${expected_value}: "
    current_value=$(testparm -s --parameter-name "${parameter}" 2>/dev/null) || {
        retval=$?
        echo "FAIL"
        return ${retval}
    }
    if [ "${current_value}" = "${expected_value}" ]; then
        echo "OK"
        return 0
    else
        echo "FAIL"
        return 1
    fi
}

basic_config_tests() {
    echo "## Basic config tests"
    testparm -s > /dev/null
    assert_testparm "realm" "${realm}"
    assert_testparm "workgroup" "${domain}"
    assert_testparm "server role" "active directory domain controller"
    echo
}

dns_tests() {
    echo "## DNS tests"
    echo "Obtaining administrator kerberos ticket"
    echo "${adminpass}" | timeout --verbose 30 kinit Administrator
    echo
    echo "Querying server info"
    samba-tool dns serverinfo "$(hostname)" --use-kerberos=required
    echo
    echo "Checking we got a service ticket of type host/"
    klist | grep "host/$(hostname)"
    echo
    echo "Checking specific DNS records"
    for srv in _ldap._tcp _kerberos._tcp _kerberos._udp _kpasswd._udp; do
        echo -n "${srv}.${realm,,}: "
        dig @localhost +short -t SRV ${srv}.${realm,,}
        echo
    done
    echo
    echo -n "Checking that our hostname \"$(hostname)\" is in DNS: "
    myip=$(dig @localhost +short -t A "$(hostname).${realm,,}")
    echo "${myip}"
    echo
}

user_creation_tests() {
    echo "## User creation tests"
    samba-tool domain passwordsettings set --complexity=off
    echo "Creating user \"${test_user}\" with password ${test_pw}"
    samba-tool user add "${test_user}" "${test_pw}"
    echo
    echo "Attempting to obtain kerberos ticket for user \"${test_user}\""
    # just in case it ends up waiting at a prompt, we use "timeout"
    echo "${test_pw}" | timeout --verbose 30 kinit "${test_user}"
    echo "Ticket obtained"
    klist
    echo
}

smbclient_tests() {
    echo "## smbclient tests"
    kdestroy || :
    echo
    echo "Obtaining a TGT for ${test_user}"
    echo "${test_pw}" | timeout --verbose 30 kinit "${test_user}"
    klist | grep krbtgt
    echo
    echo "Attempting password-less authentication with smbclient"
    echo
    echo "Listing shares"
    smbclient -L "$(hostname)" --use-kerberos=required
    echo
    echo "Listing the sysvol share"
    smbclient "//$(hostname)/sysvol" --use-kerberos=required -c "ls"
    echo
    echo "Listing policies"
    # lowercase the ${realm}
    smbclient "//$(hostname)/sysvol" --use-kerberos=required -c "ls ${realm,,}/Policies/*"
    echo
    echo "Checking that we have a ticket for the cifs service after all these commands"
    klist | grep cifs/
    echo
}

server_join_tests() {
    local member_server
    # the join methods are the keys of the join_method_deps dict
    local -a methods=("${!join_method_deps[@]}")
    local member_server="member-server"

    echo "## Server join tests"
    echo "## Initializing lxd"
    setup_lxd "${realm,,}"

    for method in "${methods[@]}"; do
        echo "## Setting up member server to join a domain using method ${method}"
        setup_member_server "${member_server}" "${method}"
        echo "## Joining domain with method ${method}"
        join_domain "${member_server}" "${method}"
        echo
        echo "## Verifying join with method ${method}"
        verify_join "${member_server}" "${method}"
        echo
        echo "## Leaving domain with method ${method}"
        leave_domain "${member_server}" "${method}"
        echo
        echo "## Destroying member server"
        lxc delete --force "${member_server}"
    done
}

server_motd_gpo_tests() {
    local motd_text1="Welcome1"
    local motd_text2="Welcome2"
    local output=""
    # This is the Default Domain Policy, it's a fixed value.
    # See output of "samba-tool gpo listall".
    local GPO="{31B2F340-016D-11D2-945F-00C04FB984F9}"
    local policy_manifest="/var/lib/samba/sysvol/${realm,,}/Policies/${GPO}/MACHINE/VGP/VTLA/Unix/MOTD/manifest.xml"
    local -i lines=0

    echo "## MOTD GPO tests (server only)"
    # no policy to start with
    if [ -e "${policy_manifest}" ]; then
        echo "## FAIL, GPO manifest file shouldn't exist yet"
        ls -la "${policy_manifest}"
        return 1
    fi

    echo
    echo "## Setting MOTD GPO text to \"${motd_text1}\""
    samba-tool gpo manage motd set "${GPO}" "${motd_text1}" -U "Administrator%${adminpass}" || return 1

    echo
    echo "## Verifying MOTD text \"${motd_text1}\" is in the policy manifest"
    grep "${motd_text1}" "${policy_manifest}" || return 1
    output=$(samba-tool gpo manage motd list "${GPO}")
    lines=$(echo "${output}" | wc -l)
    [ ${lines} -eq 1 ] || return 1
    [ "${output}" = "${motd_text1}" ] || return 1

    echo
    echo "## Setting MOTD GPO text to \"${motd_text2}\""
    samba-tool gpo manage motd set "${GPO}" "${motd_text2}" -U "Administrator%${adminpass}" || return 1

    echo
    echo "## Verifying MOTD text \"${motd_text2}\" is in the policy manifest"
    grep "${motd_text2}" "${policy_manifest}" || return 1
    output=$(samba-tool gpo manage motd list "${GPO}")
    lines=$(echo "${output}" | wc -l)
    [ ${lines} -eq 1 ] || return 1
    [ "${output}" = "${motd_text2}" ] || return 1

    echo
    # Previous checks should catch this, but...
    echo "## Verifying that the policy manifest DOES NOT contain previous MOTD text ${motd_text1}"
    grep "${motd_text1}" "${policy_manifest}" && return 1 || /bin/true

    echo
    echo "## Setting MOTD GPO text to empty"
    samba-tool gpo manage motd set "${GPO}" "" -U "Administrator%${adminpass}" || return 1

    echo "## Verifying that the MOTD GPO text is empty"
    output=$(samba-tool gpo manage motd list "${GPO}")
    lines=$(echo -n "${output}" | wc -l)
    [ -z "${output}" ] || return 1
    [ ${lines} -eq 0 ] || return 1
}

setup_member_server() {
    local container_name="${1}"
    local method="${2}"
    local release

    release="$(lsb_release -cs)"
    if [ -z "${join_method_deps[${method}]}" ]; then
        echo "## INTERNAL ERROR, invalid join method: ${method}"
        return 1
    fi
    echo "## Got test dependencies: ${join_method_deps[${method}]}"
    # can't use cloud-init here to install packages, because we first need to
    # sync the apt config from the host to the container
    echo "## Launching ${release} container"
    lxc launch "ubuntu-daily:${release}" "${container_name}" -q
    wait_container_ready "${container_name}"
    send_apt_config "${container_name}"
    copy_local_apt_files "${container_name}"
    echo "## Installing dependencies in test container"
    install_packages_in_container "${container_name}" ${join_method_deps[${method}]}
}

join_domain_realmd_winbind() {
    local server="${1}"
    local discover_cmd="realm discover -v --membership-software=samba --client-software=winbind ${realm,,}"
    local join_cmd="realm join -v --membership-software=samba --client-software=winbind ${realm,,}"

    echo "## Domain information"
    lxc exec "${server}" -- ${discover_cmd}
    echo
    echo "## Running join command: ${join_cmd}"
    echo "${adminpass}" | lxc exec "${server}" -- ${join_cmd}
}

verify_join_realmd_winbind() {
    local server="${1}"
    local member_domain

    echo -n "## Verifying member server joined domain name: "
    member_domain=$(lxc exec "${server}" -- wbinfo --own-domain)
    echo "${member_domain}"
    if [ "${member_domain}" != "${domain}" ]; then
        echo "ERROR: expected member server domain to match the joined domain:"
        echo "member server domain: ${member_domain}"
        echo "AD domain: ${domain}"
        return 1
    fi
    echo
    # we just want to see the output, not parse it
    echo "## Domain status in member server"
    lxc exec "${server}" -- wbinfo --domain-info "${member_domain}"
    echo
    echo "## User status in member server"
    for u in "${!user_pass[@]}"; do
        echo "## User \"${u}@${realm}\" information:"
        lxc exec "${server}" -- wbinfo --user-info "${u}@${realm}"
        echo
        echo "## id ${u}@${realm}"
        lxc exec "${server}" -- id ${u}@${realm}
        echo
        echo "## kinit authentication check for user \"${u}@${realm}\" inside member server"
        echo "${user_pass[${u}]}" | lxc exec "${server}" -- timeout --verbose 30 kinit "${u}@${realm}"
        lxc exec "${server}" -- klist
        echo
        echo "## Listing shares with the obtained kerberos ticket"
        lxc exec "${server}" -- smbclient -L "$(hostname)" --use-kerberos=required
        lxc exec "${server}" -- kdestroy
        echo
        echo "## wbinfo authentication check for user \"${u}@${realm}\" inside member server"
        # non-interactive format for username is user%password
        lxc exec "${server}" -- wbinfo --authenticate="${u}@${realm}%${user_pass[${u}]}"
        echo
        echo "## wbinfo kerberos authentication check for user \"${u}@${realm}\" inside member server"
        lxc exec "${server}" -- wbinfo --krb5auth="${u}@${realm}%${user_pass[${u}]}"
        echo
        echo "## Listing shares with the obtained kerberos ticket"
        lxc exec "${server}" -- smbclient -L "$(hostname)" --use-kerberos=required
        lxc exec "${server}" -- kdestroy
    done
}

leave_domain_realmd_winbind() {
    local server="${1}"
    local leave_cmd="realm leave -v --remove --client-software=winbind"

    echo "## Running leave command: ${leave_cmd}"
    echo "${adminpass}" | lxc exec "${server}" -- ${leave_cmd}
}

join_domain_realmd_sssd() {
    local server="${1}"
    local discover_cmd="realm discover -v --membership-software=adcli --client-software=sssd ${realm,,}"
    local join_cmd="realm join -v --membership-software=adcli --client-software=sssd ${realm,,}"

    echo "## Domain information"
    lxc exec "${server}" -- ${discover_cmd}
    echo
    echo "## Running join command: ${join_cmd}"
    echo "${adminpass}" | lxc exec "${server}" -- ${join_cmd}
    echo
}

verify_join_realmd_sssd() {
    local server="${1}"
    local samba_domain

    echo -n "## Verifying member server joined domain name: "
    samba_domain=$(lxc exec "${server}" -- sssctl domain-list)
    echo "${samba_domain}"
    if [ "${samba_domain}" != "${realm,,}" ]; then
        echo "ERROR: expected member server domain to match the joined domain:"
        echo "member server domain: ${samba_domain}"
        echo "AD domain: ${realm,,}"
        return 1
    fi
    echo
    # we just want to see the output, not parse it
    echo "## Domain status in member server"
    lxc exec "${server}" -- sssctl domain-status "${realm}"
    echo
    echo "## User status in member server"
    for u in "${!user_pass[@]}"; do
        echo "## User \"${u}@${realm}\" information:"
        lxc exec "${server}" -- sssctl user-checks "${u}@${realm}"
        echo
        echo "## id ${u}@${realm}"
        lxc exec "${server}" -- id "${u}@${realm}"
        echo
        echo "## kinit authentication check for user \"${u}@${realm}\" inside member server"
        echo "${user_pass[${u}]}" | lxc exec "${server}" -- timeout --verbose 30 kinit "${u}@${realm}"
        lxc exec "${server}" -- klist
        echo
        echo "## Listing shares with the obtained kerberos ticket"
        lxc exec "${server}" -- smbclient -L "$(hostname)" --use-kerberos=required
        lxc exec "${server}" -- kdestroy
    done
}

leave_domain_realmd_sssd() {
    local server="${1}"
    local leave_cmd="realm leave -v --remove --client-software=sssd"

    echo "## Running leave command: ${leave_cmd}"
    echo "${adminpass}" | lxc exec "${server}" -- ${leave_cmd}
}

join_domain() {
    local server="${1}"
    local m="${2}"

    join_domain_${m} "${server}"
}

verify_join() {
    local server="${1}"
    local m="${2}"

    verify_join_${m} "${server}"
}

leave_domain() {
    local server="${1}"
    local m="${2}"

    leave_domain_${m} "${server}"
}

systemctl stop smbd nmbd winbind
systemctl disable smbd nmbd winbind
systemctl mask smbd nmbd winbind

systemctl unmask samba-ad-dc
systemctl enable samba-ad-dc

if [ -f /etc/samba/smb.conf ]; then
    mv /etc/samba/smb.conf{,.orig}
fi

# make sure we are starting fresh, as previous tests might left things around

rm -rf /var/lib/samba/* /var/cache/samba/* /run/samba/*
kdestroy || :

samba-tool domain provision \
    --domain="${domain}" \
    --realm="${realm}" \
    --adminpass="${adminpass}" \
    --server-role=dc \
    --use-rfc2307 \
    --dns-backend=SAMBA_INTERNAL

current_dns=$(resolvectl status | grep "^Current DNS Server:" | awk '{print $4}')

if [ -n "${current_dns}" ]; then
    echo "## Setting dns forwarder to ${current_dns} in smb.conf"
    sed -r -i "s,dns forwarder = .*,dns forwarder = ${current_dns}," \
        /etc/samba/smb.conf
    unlink /etc/resolv.conf
    echo "nameserver 127.0.0.1" > /etc/resolv.conf
    # lowercase substitution
    echo "search ${realm,,}" >> /etc/resolv.conf
    systemctl stop systemd-resolved
    systemctl disable systemd-resolved
else
    echo "## Warning, couldn't detect the current DNS server to use as forwarder in smb.conf"
    echo "## resolvectl status:"
    resolvectl status
    echo "## Continuing, and hoping for the best"
fi

cp -f /var/lib/samba/private/krb5.conf /etc/krb5.conf

systemctl start samba-ad-dc

# give it some time, it's a lot of services to start
sleep 5s

basic_config_tests
dns_tests
user_creation_tests
smbclient_tests
server_join_tests

declare -i result=0
server_motd_gpo_tests || result=$?
if [ ${result} -ne 0 ]; then
    # if on s390x, ignore a failure for now due to LP: #2110127
    arch=$(dpkg --print-architecture)
    if [ "${arch}" = "s390x" ]; then
        echo "WARNING: ignoring test failure on s390x due to LP: #2110127"
        result=0
    fi
fi

exit ${result}
