#!/usr/bin/env mksh
# $MirOS: contrib/hosted/tg/code/kwalletcli/kwalletcli_getpin,v 1.26 2025/12/14 07:49:52 tg Exp $
#-
# Copyright © 2009, 2010, 2011, 2016, 2025
#	mirabilos <m$(date +%Y)@mirbsd.de>
#
# Provided that these terms and disclaimer and all copyright notices
# are retained or reproduced in an accompanying document, permission
# is granted to deal in this work without restriction, including un‐
# limited rights to use, publicly perform, distribute, sell, modify,
# merge, give away, or sublicence.
#
# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
# the utmost extent permitted by applicable law, neither express nor
# implied; without malicious intent or gross negligence. In no event
# may a licensor, author or contributor be held liable for indirect,
# direct, other damage, loss, or other issues arising in any way out
# of dealing in the work, even if advised of the possibility of such
# damage or existence of a defect, except proven that it results out
# of said person’s immediate fault when using the work as intended.

nl='
'

# or e.g. en_US.UTF-8 or en_US.utf8 or the likes, depends on your OS
# choose one that is always available and uses UTF-8/CESU-8 encoding
substlocale=C.UTF-8	# sync with manpage ENVIRONMENT section

# ensure the UTF-8 locale is running and LC_CTYPE is populated
if command -v locale >/dev/null 2>&1; then
	# expand LANG/LC_*/LC_ALL to LC_*
	x=$(locale | sed -n '/^LC_/s//export &/p')
	if [[ -n $x ]]; then
		eval "$x"
		unset LANG LC_ALL
	else
		print -ru2 -- 'E: expanding locale failed; result:'
		locale 2>&1 | sed 's/^/N: /' >&2
		if [[ -n $LC_ALL ]]; then
			export LC_CTYPE=$LC_ALL
		elif [[ -z $LC_CTYPE && -n $LANG ]]; then
			export LC_CTYPE=$LANG
		fi
	fi
	if ! y=$(locale charmap); then
		print -ru2 -- "E: locale charmap command failed: result='$y'"
		y=
	fi
	if [[ $y != @(utf|UTF|cesu|CESU)?(-)8 ]]; then
		print -ru2 -- "W: no UTF-8 locale; switching to $substlocale"
		if [[ -n $x ]]; then
			export LC_CTYPE=$substlocale
		else
			export LC_CTYPE=$substlocale LC_ALL=$substlocale
			unset LANG
		fi
		[[ $(locale charmap 2>&1) = @(utf|UTF|cesu|CESU)?(-)8 ]] || \
		    print -ru2 -- 'W: still no UTF-8 locale!'
	fi
elif [[ ${LC_ALL:-${LC_CTYPE:-${LANG}}} != ?(*[!A-Za-z0-9])@(utf|UTF|cesu|CESU)?(-)8?([!A-Za-z0-9]*) ]]; then
	print -ru2 -- "W: no locale(1), no UTF-8 locale; switching to $substlocale"
	export LC_CTYPE=$substlocale LC_ALL=$substlocale
	unset LANG
else
	export LC_CTYPE=${LC_ALL:-${LC_CTYPE:-${LANG}}}
fi
set -U # regardless

function die {
	local rv=$1

	shift
	(( quiet )) || print -u2 -r -- "$@"
	exit $rv
}

function checkline {
	local res

	print -pr -- "$@"
	read -p res || die 3 pinentry died
	[[ $res = OK@(| *) ]] || die 4 "got '$res' for '$*'"
}

if [[ $KSH_VERSION = *'KSH R'[0-5][0-9][!0-9]* ]]; then
	(( woctet_mask = 16#FF80 ))
	(( woctet_base = 16#EF80 ))
else
	(( woctet_mask = 16#FFFFFF80 ))
	(( woctet_base = 16#001BBB80 ))
fi

function toutf8 {
	local intext="$*" outtext=''
	typeset -i i=0 n=${#intext}
	typeset -i1 c
	typeset -Uui16 -Z5 x

	while (( i < n )); do
		c=1#${intext:(i++):1}
		if (( (c & woctet_mask) == woctet_base )); then
			(( c &= 0xFF ))
		fi
		if (( c < 32 || c == 1#% || c == 1#_ )); then
			let x=c
			outtext=${outtext}%${x#16#}
		elif (( c > 0x7E && c < 0xA0 )); then
			outtext=${outtext}�
		else
			outtext=${outtext}${c#1#}
		fi
	done

	print -nr -- "$outtext"
}

function tolat1 {
	local intext="$*" outtext=''
	typeset -i i=0 n=${#intext}
	typeset -i1 c
	typeset -Uui16 -Z5 x

	while (( i < n )); do
		c=1#${intext:(i++):1}
		(( c = c < 0x80 ? c : (c & woctet_mask) == woctet_base ? c :
		    (c < 0x0100 ? c : 0xFF) | woctet_base ))
		outtext=${outtext}${c#1#}
	done

	print -nr -- "$outtext"
}

function usage {
	print -ru2 'Usage:	kwalletcli_getpin [-q] [-b | -m | -p prompt]'
	print -ru2 '	[-E | -e errortext] [-N NoBtn] [-t desctext] [-Y YesBtn]'
	print -ru2 'Returns: 0=ok (pw on stdout), 1=cancel, 2+=error'
	exit 2
}

function get_tty {
	GPG_TTY=$(tty) || GPG_TTY=
	[[ -n $GPG_TTY ]] || die 5 Please set GPG_TTY yourself
}


asktext='Hast Du das Paßwort?'
askprompt='Code:'
askerror=''
askbool=0
btnyes=
btnno=
quiet=0

while getopts 'bEe:hmN:p:qt:Y:' ch; do
	case $ch {
	(b)	askbool=1 ;;
	(E)	askerror='' ;;
	(e)	askerror=$OPTARG ;;
	(m)	askbool=2 ;;
	(N)	btnno=$OPTARG ;;
	(p)	askprompt=$OPTARG ;;
	(q)	quiet=1 ;;
	(t)	asktext=$OPTARG ;;
	(Y)	btnyes=$OPTARG ;;
	(*)	usage ;;
	}
done
shift $((OPTIND - 1))
(( $# )) && usage

set -A pe_args
if [[ -n $DISPLAY ]]; then
	pe_args[${#pe_args[*]}]=--display
	pe_args[${#pe_args[*]}]=$DISPLAY
fi

[[ -n $PINENTRY ]] || PINENTRY=pinentry
if "${PINENTRY%-kwallet}" --version >/dev/null 2>&1; then
	"${PINENTRY%-kwallet}" "${pe_args[@]}" |&
	subpid=$?
	read -p res || die 3 pinentry died
	[[ $res = OK@(| *) ]] || die 4 "got '$res' for '$*'"
	got_pe=1
	trap "kill $subpid; die 6 caught SIGHUP" HUP
	trap "kill $subpid; die 6 caught SIGINT" INT
	trap "kill $subpid; die 6 caught SIGQUIT" QUIT
	trap "kill $subpid; die 6 caught SIGTRAP" TRAP
	trap "kill $subpid; die 6 caught SIGPIPE" PIPE
	trap "kill $subpid; die 6 caught SIGTERM" TERM
else
	got_pe=0
fi

[[ -z $DISPLAY && -z $GPG_TTY ]] && get_tty
[[ -z $GPG_TERM ]] && GPG_TERM=${TERM:-vt100}
export GPG_TERM GPG_TTY

if (( !got_pe )); then
	thetext=
	sep=
	(( askbool )) || if [[ -n $askerror ]]; then
		thetext="*ERROR*: $(toutf8 "$askerror")"
		sep=$nl$nl
	fi
	if [[ -n $asktext ]]; then
		thetext=$thetext$sep$(toutf8 "$asktext")
		sep=$nl$nl
	fi
	(( askbool )) || if [[ -n $askprompt ]]; then
		thetext=$thetext$sep$(toutf8 "$askprompt")
	fi

	whence -p xmessage >/dev/null || unset DISPLAY
	if (( askbool )) && [[ -n $DISPLAY ]]; then
		buttons=${btnyes:-OK},${btnno:-Cancel}
		(( askbool == 2 )) && buttons=${btnyes:-OK}
		LC_ALL=C xmessage -nearmouse -buttons "$buttons" \
		    -default "${btnyes:-OK}" "$(tolat1 "$thetext")"
		rc=$?
		(( rc == 101 )) && exit 0
		(( rc == 102 )) && exit 1
		die 3 xmessage errored out
	fi
	if (( askbool == 2 )); then
		thetext="$thetext$nl${nl}[OK${btnyes:+=$btnyes}] "
	elif (( askbool )); then
		thetext="$thetext$nl${nl}[Y${btnyes:+=$btnyes}|N${btnno:+=$btnno}] "
	elif [[ -n $askprompt ]]; then
		thetext="$thetext "
	else
		thetext=$thetext$nl
	fi
	trap 'stty echo; exit 1' HUP INT QUIT TRAP PIPE TERM
	[[ -n $GPG_TTY ]] || get_tty
	while :; do
		print -nru2 -- "$thetext"
		stty -echo
		IFS= read -r resp
		rc=$?
		print -u2
		stty echo
		if (( rc )); then
			exit 3
		elif (( askbool == 2 )); then
			exit 0
		elif (( !askbool )); then
			print -nr -- "$resp"
			exit 0
		fi
		[[ $resp = ?(1|-1|[Tt]?([Rr][Uu][Ee])|[Yy]?([Ee][Ss])) ]] && \
		    exit 0
		[[ $resp = @(0|[Ff]?([Aa][Ll][Ss][Ee])|[Nn]?([Oo])) ]] && \
		    exit 1
		print -u2 "Try again: 1/T[rue]/Y[es] vs 0/F[alse]/N[o]"
	done 0<>$GPG_TTY 2>&0
fi

[[ -z $GPG_TTY ]] || checkline "OPTION ttyname=$GPG_TTY"
[[ -z $GPG_TERM ]] || checkline "OPTION ttytype=$GPG_TERM"
[[ -z $LC_CTYPE ]] || checkline "OPTION lc-ctype=$LC_CTYPE"
[[ -z $LC_MESSAGES ]] || checkline "OPTION lc-messages=$LC_MESSAGES"

[[ -n $asktext ]] && checkline "SETDESC $(toutf8 "$asktext")"
[[ -n $askprompt ]] && checkline "SETPROMPT $(toutf8 "$askprompt")"
[[ -n $askerror ]] && checkline "SETERROR $(toutf8 "$askerror")"

[[ -n $btnyes ]] && checkline "SETOK $(toutf8 "$btnyes")"
[[ -n $btnno ]] && checkline "SETCANCEL $(toutf8 "$btnno")"

if (( askbool == 2 )); then
	print -p MESSAGE
elif (( askbool )); then
	print -p CONFIRM
else
	print -p GETPIN
fi
read -p res || die 3 pinentry died
if [[ $res = @(D )* ]]; then
	pass=${res#D }
	read -p res || die 3 pinentry died
else
	pass=
fi
if [[ $res = OK@(| *) ]]; then
	rv=0
	print -nr -- "$pass"
else
	rv=1
fi
exec 3>&p; exec 3>&-
exit $rv
