#!/bin/sh
# autopkgtest: functional test for pam_passwdqc.so (libpam-passwdqc)
#
# Tests the installed PAM module using pamtester, covering test for min=option
#
# The PAM config uses "non-unix" so getpwnam() is skipped; the module
# evaluates password quality only, without requiring a real system user.

set -e

FAILURES=0
PAM_SERVICE="passwdqc-test"
PAM_CONF="/etc/pam.d/$PAM_SERVICE"

# Remove the PAM service config created on exit (success or failure).
cleanup() { rm -f "$PAM_CONF"; }
trap cleanup EXIT

# --- 1. Verify the module is installed and its dependencies resolve ---

PAM_MODULE=$(dpkg -L libpam-passwdqc | grep 'pam_passwdqc\.so$' | head -1)
if [ -z "$PAM_MODULE" ]; then
    echo "FAIL: pam_passwdqc.so not found in libpam-passwdqc package files" >&2
    exit 1
fi
echo "Found: $PAM_MODULE"

if ! ldd "$PAM_MODULE" > /dev/null 2>&1; then
    echo "FAIL: unresolved dependencies in $PAM_MODULE" >&2
    ldd "$PAM_MODULE" >&2
    exit 1
fi
echo "OK: ldd"

# --- 2. A minimal PAM service config for testing ---
#
#   min=disabled,disabled,12,8,7 maps to tiers N0-N4:
#
#   N0=disabled  single-word passwords: always rejected
#   N1=disabled  two-word passphrases:  always rejected
#   N2=12        passphrases (>=3 words): minimum 12 characters
#   N3=8         3-class passwords:      minimum 8 characters
#   N4=7         4-class passwords:      minimum 7 characters
#
# Note: a leading uppercase letter and a trailing digit are NOT counted
# as separate character classes.

cat > "$PAM_CONF" << 'EOF'
# passwdqc autopkgtest service
password  required  pam_passwdqc.so  non-unix enforce=everyone min=disabled,disabled,12,8,7 retry=0
EOF
echo "Written: $PAM_CONF"
set +e

# --- 3. Test runner ---
#
# Feeds new password twice (new and confirm) to pamtester and checks the exit code
# against the expected outcome. pamtester exits 0 on PAM_SUCCESS.

run_test() {
    description="$1"
    expect="$2"   # "pass" or "fail"
    new_pw="$3"

    output=$(printf '%s\n%s\n' "$new_pw" "$new_pw" \
        | pamtester -v "$PAM_SERVICE" testuser chauthtok 2>&1)
    rc=$?
    echo "$output"

    if [ "$expect" = "pass" ] && [ "$rc" -ne 0 ]; then
        echo "FAIL [$description]: expected PAM_SUCCESS, got exit $rc"
        FAILURES=$((FAILURES + 1))
    elif [ "$expect" = "fail" ] && [ "$rc" -eq 0 ]; then
        echo "FAIL [$description]: expected rejection, got PAM_SUCCESS"
        FAILURES=$((FAILURES + 1))
    else
        echo "OK   [$description]"
    fi
    echo ""
}

# --- 4. Test cases ---

# N0=disabled: single dictionary word
run_test "N0 single word (disabled)"        fail "password"

# N1=disabled: two-word passphrase
run_test "N1 two-word phrase (disabled)"    fail "correct horse"

# N2=12: passphrase with >=3 words and >=12 chars total
run_test "N2 strong passphrase (pass)"      pass "correct horse battery staple"

# N3=8: password using 3 character classes, exactly at the 8-char minimum
run_test "N3 3-class 8 chars (pass)"        pass "qSzX8k3i"

# N3=8: 3-class password below the 8-char minimum
run_test "N3 3-class 7 chars (fail)"        fail "yvCw1lj"

# N3 edge case: uppercase-first + digit-last are excluded from class counting,
# so "Abcde1f2" counts as only 2 classes (lower + the uncounted upper/digit),
# which falls below the N3=8 threshold and is rejected.
run_test "N3 first-upper/last-digit rule"   fail "Abcde1f2"

# N4=7: password using 4 character classes, exactly at the 7-char minimum
run_test "N4 4-class 7 chars (pass)"        pass "oGr1%eg"

# N4: password using 4 classes but >=8 chars (comfortably above all thresholds)
run_test "N4 long random password (pass)"   pass "xK9#mP2@qL5v"

# Below all thresholds: too short to satisfy even N4=7
run_test "below all thresholds (fail)"      fail "abc123"

# Empty password
run_test "empty password (fail)"            fail ""

# --- 5. Report ---

if [ "$FAILURES" -gt 0 ]; then
    echo "RESULT: $FAILURES test(s) FAILED" >&2
    exit 1
fi
echo "RESULT: all tests passed"
