#!/bin/sh
# ism-1558 word-count check, invoked by pam_exec(8) from the password stack.
#
# ism-1558 constrains passwords "using a sequence of words". A single-token
# password is not a word sequence, so the rule does not apply to it -- and that
# is precisely what keeps ism-2080 (no complexity requirements imposed) intact.
# A character-class-based module such as pam_passwdqc cannot express this,
# because it can only disable whole complexity tiers.
#
# Exit 0 accepts, exit 1 rejects. Invoked as:
#   password requisite pam_exec.so expose_authtok quiet /usr/local/sbin/ism-wordcount-check

# Defence in depth against the one real exposure here: pam_exec hands the
# cleartext candidate to this script on stdin. Nothing may outlive the process.
ulimit -c 0 2>/dev/null || true	# never core-dump with a credential in memory
umask 077

[ "$PAM_TYPE" = "password" ] || exit 0

MIN_WORDS=4
[ -r /etc/security/ism-wordcount.conf ] && . /etc/security/ism-wordcount.conf

# read builtin, not $(cat): a command substitution would fork a second process
# that also holds the cleartext. read keeps it in this shell only. It returns
# non-zero at EOF without a trailing newline, which is the normal case here.
IFS= read -r pw || true

# PAM runs the password stack twice: a preliminary check where the new token is
# not yet set, then the update. Nothing to inspect in the first pass.
[ -n "$pw" ] || exit 0

set -f		# a password must never be glob-expanded
set -- $pw	# deliberate IFS word splitting

# Not a sequence of words: ism-1558 does not apply, and imposing any further
# requirement here would breach ism-2080.
[ $# -le 1 ] && exit 0

[ $# -ge "$MIN_WORDS" ] && exit 0
exit 1

# No branch above logs, echoes, or passes $pw as an argument to any command.
