Files
pinentry-ssh-askpass/ssh-askpass
Dustin C. Hatch f03d3a8561 Handle non-GUI sessions (SSH, tmux)
When there is a "global" SSH agent running in a GUI session, attempting
to use it from a remote SSH session may not work well.  For keys that
need a passphrase or confirmation, the prompt will appear on the
graphical display, rather than the remote client's terminal, essentially
preventing the remote client from using those keys.

To resolve this, we need to use a separate SSH agent for remote clients.
At login, the remote client should spawn its own agent, e.g. with `eval
$(ssh-agent)`.  Then, the ssh-askpass script will be able to spawn
`pinentry` attached to theh remote client's terminal.

In the specific case of running `tmux` in a remote SSH session, things
get a bit more complicated.  Since `tmux` has control of the SSH
client's terminal, attaching `pinentry` to it wreaks havoc and makes
both programs unusable.  Thus, we need to spawn `pinentry` _inside_
`tmux`.  Since we do not know which TTY the invoker is using, we need to
spawn a new window for the `pinentry` prompt.  This of course detaches
the `pinentry` process from our stdin/stdout, so we have to used named
pipes to communicate with it.
2024-04-19 11:03:02 -05:00

97 lines
1.8 KiB
Bash
Executable File

#!/bin/sh
# vim: set sw=4 ts=4 sts=4 et :
pinentry() {
if [ -n "${TMUX}" ] && [ -z "${DISPLAY}" ]; then
# We're running in a TMUX pane, launch a new window to handle
# passphrase/confirmation prompt
unset t1 t2
mkfifo "${t1:=$(mktemp -u)}"
mkfifo "${t2:=$(mktemp -u)}"
tmux new-window sh -c "exec pinentry -T \$(tty) < '$t1' > '$t2'"
cat > "$t1"
cat "$t2"
rm -f "$t1" "$t2"
return
elif [ -n "${SSH_TTY}" ]; then
# We're in an SSH session, so prompt for the passphrase on the
# SSH client terminal
set -- -T "${SSH_TTY}" "$@"
elif [ -t 0 ]; then
set -- -T $(tty) "$@"
elif [ -t 1 ]; then
set -- -T $(tty <&1) "$@"
elif [ -t 2 ]; then
set -- -T $(tty <&2) "$@"
fi
command pinentry "$@"
}
prompt_confirm() {
result=$(pinentry -g <<EOF | grep -E '^(D|ERR)'
SETTITLE ssh-askpass
SETDESC $1
SETOK Yes
SETCANCEL No
CONFIRM
EOF
)
echo "${result}" >&2
case "${result}" in
*cancelled*)
exit 255
;;
'')
exit 0
;;
*)
echo "${result}" >&2
exit 1
;;
esac
}
prompt_notify() {
pinentry -g <<EOF || :
SETTITLE ssh-askpass
SETDESC $1
SETTIMEOUT 3
MESSAGE
EOF
}
prompt_passphrase() {
result=$(pinentry -g <<EOF | grep -E '^(D|ERR)'
SETTITLE ssh-askpass
SETDESC $1
SETPROMPT Passphrase:
GETPIN
EOF
)
case "${result}" in
*cancelled*)
exit 255
;;
D*)
echo "${result#D }"
;;
*)
echo "${result}" >&2
exit 1
;;
esac
}
arg="$(echo "$1" | sed ':a;N;$!ba;s/\n/%0A/g')"
case "${SSH_ASKPASS_PROMPT}" in
confirm)
prompt_confirm "${arg}"
;;
none)
prompt_notify "${arg}"
;;
*)
prompt_passphrase "${arg}"
;;
esac