tid="GSSAPI Authentication"

# Skip the test if GSSAPI support is not configured
if config_defined GSSAPI ; then :
else
    skip "GSSAPI not enabled"
fi

# We test with MIT Kerberos KDC, skip if not installed
if which krb5kdc > /dev/null ; then :
else
    skip "MIT Kerberos KDC not installed"
fi

# The test needs nss_wrapper to emulate gethostname() and /etc/hosts,
# we skip if the shared library is not installed
nss_wrapper="libnss_wrapper.so"
if $SUDO ldconfig -p | grep "$nss_wrapper" >/dev/null 2>&1 ; then :
else
    skip "$nss_wrapper not installed"
fi

# Set up SSHD and KDC hostnames and resolve both to localhost
sshd_hostname="sshd.example.org"
bad_hostname="bad.example.org"
kdc_hostname="kdc.example.org"
kdc_port=2088

# Set up a directory to store Kerberos data
# (configuration, ticket cache,...)
gssdir="$OBJ/gss"
rm -fr "$gssdir"
mkdir -p "$gssdir"

export KRB5CCNAME="$gssdir/cc"
export KRB5_CONFIG="$gssdir/krb5.conf"
export KRB5_KDC_PROFILE="$gssdir/kdc.conf"
export KRB5_KTNAME="$gssdir/ssh.keytab"
export KRB5RCACHETYPE="none"

kdc_pidfile="$gssdir/pid"

nss_hosts="$gssdir/nss_hosts"
echo "127.0.0.1 $sshd_hostname $kdc_hostname" > "$nss_hosts"

# Configure Kerberos
cat > "$KRB5_KDC_PROFILE" <<EOF
[realms]
    EXAMPLE.ORG = {
        database_name = $gssdir/principal
        key_stash_file = $gssdir/stash
        kdc_listen = $kdc_hostname:$kdc_port
        kdc_tcp_listen = $kdc_hostname:$kdc_port
    }
[logging]
    kdc = FILE:$gssdir/kdc.log
    debug = true
EOF

cat > "$KRB5_CONFIG" <<EOF
[libdefaults]
    default_realm = EXAMPLE.ORG
[realms]
    EXAMPLE.ORG = {
        kdc = $kdc_hostname:$kdc_port
    }
EOF

# Back up the default sshd_config
cp "$OBJ/sshd_config" "$OBJ/sshd_config.orig"

setup_sshd() {
    mock_hostname="$1"
    strict_acceptor="$2"

    cp "$OBJ/sshd_config.orig" "$OBJ/sshd_config"

    cat >> "$OBJ/sshd_config" <<EOF
PubkeyAuthentication No
PasswordAuthentication No
GSSAPIAuthentication Yes
EOF

    if ! $strict_acceptor; then
        echo "GSSAPIStrictAcceptorCheck No" >> "$OBJ/sshd_config"
    fi

    test_ssh_sshd_env_backup="$TEST_SSH_SSHD_ENV"
    TEST_SSH_SSHD_ENV="$TEST_SSH_SSHD_ENV                  \
                       LD_PRELOAD=$nss_wrapper             \
                       NSS_WRAPPER_HOSTS=$nss_hosts        \
                       NSS_WRAPPER_HOSTNAME=$mock_hostname \
                       KRB5_CONFIG=$KRB5_CONFIG            \
                       KRB5_KDC_PROFILE=$KRB5_KDC_PROFILE  \
                       KRB5CCNAME=$KRB5CCNAME              \
                       KRB5_KTNAME=$KRB5_KTNAME            \
                       KRB5RCACHETYPE=$KRB5RCACHETYPE"
    start_sshd
}

teardown_sshd() {
    TEST_SSH_SSHD_ENV="$test_ssh_sshd_env_backup"
    stop_sshd
}

setup_kdc() {
    kdb5_util create -P "foo" -s >> $TEST_REGRESS_LOGFILE
    krb5kdc -w 1 -P "$kdc_pidfile"
    i=0;
    while [ ! -f "$kdc_pidfile" -a $i -lt 10 ]; do
        i=`expr $i + 1`
        sleep 1
    done
    test -f "$kdc_pidfile" || fatal "KDC failed to start"
}

teardown_kdc() {
    kill "`cat "$kdc_pidfile"`"
    kdestroy >> $TEST_REGRESS_LOGFILE 2>&1
    rm -f "$KRB5_KTNAME" "$kdc_pidfile"
    kdb5_util destroy -f >> $TEST_REGRESS_LOGFILE
}

setup_nss_emulation() {
    LD_PRELOAD="$nss_wrapper"
    NSS_WRAPPER_HOSTS="$nss_hosts"
    export LD_PRELOAD NSS_WRAPPER_HOSTS
}

teardown_nss_emulation() {
    unset LD_PRELOAD || :
    unset NSS_WRAPPER_HOSTS || :
}

setup_krb_principal_with_key() {
    name="$1"
    add_to_keytab="$2"
    kadmin.local add_principal -randkey "$name"
    if $add_to_keytab; then
        kadmin.local ktadd "$name" >> $TEST_REGRESS_LOGFILE
    fi
}

setup_krb_principal_with_pw() {
    name="$1"
    password="$2"
    authenticate="$3"
    kadmin.local add_principal -pw "$password" "$name"
    if $authenticate; then
        echo "$password" | kinit "$name" >> $TEST_REGRESS_LOGFILE
    fi
}

test_gss_auth() {
    sshd_mock_hostname="$1" # the name that gethostname() will return within sshd
    sshd_principal="$2"     # the hostname for which a Kerberos principal will be created
    auth_sshd="$3"          # whether sshd will be authenticated via a keytab
    auth_client="$4"        # whether the client will be authenticated via kinit
    strict_acceptor="$5"    # whether to be strict about the identity of the sshd server
    expect="$6"             # the expected return value of the sshd command

    setup_sshd "$sshd_mock_hostname" "$strict_acceptor"
    setup_nss_emulation
    setup_kdc

    setup_krb_principal_with_key "host/$sshd_principal" "$auth_sshd"
    setup_krb_principal_with_pw "$USER" "foo" "$auth_client"

    $SSH -F "$OBJ/ssh_config" -o "GSSAPIAuthentication Yes" "$USER@$sshd_hostname" true
    status=$?

    teardown_kdc
    teardown_nss_emulation
    teardown_sshd

    test $status -eq $expect
}

#              sshd_mock_hostname  sshd_principal  auth_sshd  auth_client  strict_acceptor  expect
verbose "valid authentication"
test_gss_auth  $sshd_hostname      $sshd_hostname  true       true         true             0      \
               || fail "valid authentication attempt failed"
verbose "authentication without a keytab entry"
test_gss_auth  $sshd_hostname      $sshd_hostname  false      true         true             255    \
               || fail "authentication succeeded without a keytab entry for the host"
verbose "authentication without ticket-granting"
test_gss_auth  $sshd_hostname      $sshd_hostname  true       false        true             255    \
               || fail "authentication succeeded without a ticket-granting ticket"
verbose "authentication with server hostname/principal mismatch"
test_gss_auth  $bad_hostname       $sshd_hostname  true       true         true             255    \
               || fail "authentication succeeded with a hostname/principal mismatch on server side"
verbose "authentication with strict acceptor check"
test_gss_auth  $bad_hostname       $sshd_hostname  true       true         false            0      \
               || fail "valid authentication without strict acceptor check failed"
verbose "authentication with client hostname/principal mismatch"
test_gss_auth  $bad_hostname       $bad_hostname   true       true         true             255    \
               || fail "authentication succeeded with a hostname/principal mismatch on client side"

unset KRB5CCNAME || :
unset KRB5_CONFIG || :
unset KRB5_KDC_PROFILE || :
unset KRB5_KTNAME || :
unset KRB5RCACHETYPE || :
rm -r "$gssdir"

