#!/bin/sh

# Quote CUR_DIR here as well as at every later use: an install path containing
# whitespace would otherwise fail at this cd, before any of the password logic
# below runs.
CUR_DIR=`dirname "$0"`
cd "$CUR_DIR" || exit 1
CUR_DIR=`pwd`


SUCC=0
cat <<EOF

Please specify the user name of administrator.
This is the user name required to login the administration Web interface.

EOF

# The user name is written verbatim into ../conf/htpasswd as "<user>:<hash>",
# so a ':' would terminate the name early and a newline would append a SECOND
# credential line. Restrict it to a charset that cannot do either.
USER_OK=0
while [ $USER_OK -eq 0 ]; do
	printf "%s" "User name [admin]: "
	read -r ADMIN_USER
	if [ "x$ADMIN_USER" = "x" ]; then
		ADMIN_USER=admin
	fi
	case "$ADMIN_USER" in
		*[!a-zA-Z0-9_-]*)
			echo ""
			echo "[ERROR] Sorry, the user name may only contain letters, digits, '_' and '-'. Try again!"
			echo ""
			;;
		*)
			USER_OK=1
			;;
	esac
done

cat <<EOF

Please specify the administrator's password.
This is the password required to login the administration Web interface.

EOF

while [ $SUCC -eq "0" ];  do
	printf "%s" "Password: "
	stty -echo
	read -r PASS_ONE
	stty echo
	echo ""
	# The "x" prefix and the threshold of 7 go together: without the guard,
	# expr misparses a password that is exactly one of its own operator
	# keywords ("substr", "length", "index", "match"), printing a syntax
	# error and wrongly rejecting a valid password -- which, inside this
	# retry loop, the admin cannot escape except by choosing a different
	# password. Do not "simplify" the x away without also restoring 6.
	if [ `expr "x$PASS_ONE" : '.*'` -ge 7 ]; then
		printf "%s" "Retype password: "
		stty -echo
		read -r PASS_TWO
		stty echo
		echo ""
		if [ "x$PASS_ONE" = "x$PASS_TWO" ]; then
			SUCC=1
		else
			echo ""
			echo "[ERROR] Sorry, passwords does not match. Try again!"
			echo ""
		fi
	else
		echo ""
		echo "[ERROR] Sorry, password must be at least 6 charactors!"
		echo ""
	fi
done


# generate password file

# admin_php5 is the only PHP binary this tree ships, so there is deliberately
# NO fallback to admin_php. A fallback would name a path the vendor never
# installs while this binary decides the WebAdmin credential: whatever it
# writes to stdout is accepted as the hash below, so anything dropped at that
# unclaimed name chooses the admin password. Aborting is strictly better --
# the existing password is left in place.
#
# The check is -x, not -f, because a present-but-non-executable admin_php5
# satisfies -f and then fails to run.
LSWS_PHP="$CUR_DIR/../fcgi-bin/admin_php5"
if [ ! -x "$LSWS_PHP" ]; then
	echo "[ERROR] $LSWS_PHP is missing or not executable; htpasswd was NOT changed."
	exit 1
fi

# -c, not -d: admin_php5 is the litespeed SAPI and does not accept -d. Given
# one it prints its usage banner to STDOUT and exits 0, which would be captured
# as the hash. Pinning the shipped ini also neutralises an inherited PHPRC.
#
# -c does NOT cover PHP_INI_SCAN_DIR, though: a conf.d file enabling output
# buffering still gzips this capture. -d would cover it and so would -n, but
# this SAPI accepts NEITHER -- -n is listed in its own usage banner yet still
# prints that banner to stdout and exits 0, with or without -c. So no flag
# closes that case here, which is why the gate below is load-bearing rather
# than defence in depth. Do not remove it, and do not "fix" this with -n.
#
# The password goes through LSWS_ADMIN_PASS rather than argv, which is
# world-readable via /proc/<pid>/cmdline while the process runs.
ENCRYPT_PASS=`LSWS_ADMIN_PASS="$PASS_ONE" "$LSWS_PHP" \
	-c "$CUR_DIR/php.ini" -q "$CUR_DIR/htpasswd.php"` || ENCRYPT_PASS=''

# Reject junk before matching the prefix: a startup warning appended AFTER the
# hash still starts with $2y$, so a prefix-only test would accept it.
#
# The cost is pinned to bcrypt's defined 04-31 range and the length to 60,
# rather than accepting any two characters as the cost. Those two together are
# what reject a capture that is a valid hash followed by trailing junk drawn
# from the bcrypt alphabet -- the negated bracket cannot catch that case,
# because every character in it is legal.
case "$ENCRYPT_PASS" in
	*[!./A-Za-z0-9\$]*)
		_pass_ok=0 ;;
	'$2y$'0[4-9]'$'*|'$2a$'0[4-9]'$'*|'$2b$'0[4-9]'$'*|\
	'$2y$'[12][0-9]'$'*|'$2a$'[12][0-9]'$'*|'$2b$'[12][0-9]'$'*|\
	'$2y$'3[01]'$'*|'$2a$'3[01]'$'*|'$2b$'3[01]'$'*)
		# bcrypt is always 60 chars: 7 prefix and cost, 22 salt, 31 digest.
		if [ ${#ENCRYPT_PASS} -eq 60 ]; then
			_pass_ok=1
		else
			_pass_ok=0
		fi ;;
	*)
		_pass_ok=0 ;;
esac

# Unlike the installer, aborting here is safe: admpass.sh is standalone with
# nothing sequenced after it, so leaving htpasswd untouched preserves the
# existing password.
if [ $_pass_ok -eq 1 ]; then
	printf '%s:%s\n' "$ADMIN_USER" "$ENCRYPT_PASS" \
		> "$CUR_DIR/../conf/htpasswd" \
		&& echo "Administrator's username/password is updated successfully!"
else
	echo "[ERROR] Failed to generate the password hash; htpasswd was NOT changed."
	echo "[ERROR] The previous password (if any) is unchanged."
	exit 1
fi

