#!/bin/sh
# Verify the ism-1558 limbs a character-level rule can enforce, on a Debian 13 host:
# monotonic runs, repeated characters and account-derived strings. The word count is
# enforced by debian-13-pam-wordcount, and word provenance by nothing.
#
# Every candidate is padded to the minimum length in force, measured rather than
# read, so a rejection is never for length when this rule is under test. Settings
# this component owns being set elsewhere is reported separately, as a conflict.
#
# Uses pwscore(1). No account is created or modified, so this is safe on a
# production host.

set -u

PASS_COUNT=0
FAIL_COUNT=0
PARTIAL_COUNT=0

report() {
	case "$1" in
		PASS) PASS_COUNT=$((PASS_COUNT + 1)) ;;
		PARTIAL) PARTIAL_COUNT=$((PARTIAL_COUNT + 1)) ;;
		*) FAIL_COUNT=$((FAIL_COUNT + 1)) ;;
	esac
	printf '%-8s %-13s %s\n' "$1" "$2" "$3"
}

accepts() { printf '%s' "$1" | pwscore >/dev/null 2>&1; }

command -v pwscore >/dev/null 2>&1 || { echo "error: pwscore not found; install libpwquality-tools" >&2; exit 1; }

MINE=/etc/security/pwquality.conf.d/50-ism-predictability.conf
[ -f "$MINE" ] || { echo "error: $MINE is not installed; run the apply script" >&2; exit 1; }

FILLER=vaultottermangoquiltravenfjordzebraplinthwidgetcarbonhelixjunipermarrow
slice() { printf '%s%s' "$FILLER" "$FILLER" | cut -c1-"$1"; }

FLOOR=
n=1
while [ "$n" -le 128 ]; do
	if accepts "$(slice "$n")"; then FLOOR=$n; break; fi
	n=$((n + 1))
done
: "${FLOOR:=6}"

echo "Minimum in force: $FLOOR characters"
echo

SEQ_OK=0
accepts "$(slice $((FLOOR - 6)))abcdefgh" || SEQ_OK=1
REP_OK=0
accepts "$(slice $((FLOOR - 5)))aaaaaa" || REP_OK=1

# Natural language satisfies every rule here while being what the control
# prohibits, so it is measured and reported rather than assumed closed.
ELIGIBLE=0
UNCAUGHT=0
for phrase in "the quick brown fox jumps over the lazy dog" \
	"we all live in a yellow submarine yellow submarine" \
	"monday tuesday wednesday thursday friday saturday"; do
	[ "${#phrase}" -ge "$FLOOR" ] || continue
	ELIGIBLE=$((ELIGIBLE + 1))
	accepts "$phrase" && UNCAUGHT=$((UNCAUGHT + 1))
done

if [ "$SEQ_OK" -eq 1 ] && [ "$REP_OK" -eq 1 ]; then
	report PARTIAL "ism-1558" \
		"monotonic sequences and repeats rejected; $UNCAUGHT/$ELIGIBLE natural-language constructions accepted; word count is not enforced here"
else
	report FAIL "ism-1558" "monotonic sequence and/or repeat rules are not enforced"
fi

OVERRIDES=''
for f in /etc/security/pwquality.conf /etc/security/pwquality.conf.d/*.conf; do
	[ -f "$f" ] && [ "$f" != "$MINE" ] || continue
	for key in maxrepeat maxsequence usercheck usersubstr gecoscheck; do
		grep -qE "^[[:space:]]*$key([[:space:]]*=|[[:space:]]*\$)" "$f" &&
			OVERRIDES="$OVERRIDES $key($(basename "$f"))"
	done
done
[ -z "$OVERRIDES" ] &&
	report PASS "settings" "the settings this component owns are set here and nowhere else" ||
	report FAIL "settings" "settings this component owns are also set elsewhere:$OVERRIDES"

echo
echo "passed: $PASS_COUNT  partial: $PARTIAL_COUNT  failed: $FAIL_COUNT"
[ "$FAIL_COUNT" -eq 0 ] || exit 1
