a73x

test/e2e_15_askpass.sh

Ref:   Size: 22.8 KiB   History

# shellcheck shell=sh
# e2e_15_askpass.sh — sourced by test/e2e.sh after e2e_lib.sh. Scenarios run in
# the order they stand in; see the lib's header for what this file may
# assume and what it must register.
#
# ssh's PROMPTS. A wall dial's ssh reads nothing from /dev/tty: it is spawned
# with SSH_ASKPASS_REQUIRE=force and SSH_ASKPASS pointing at this same
# binary, which carries the question over a per-client socket to the wall
# and the answer back. The two exclusions are asserted here too, because
# they are what stops the rule being a nuisance: a POLL spells BatchMode and
# is handed no socket at all, and the ENTRY dial (`mux HOST`, before any
# wall exists) keeps ssh's own tty prompts.
#
# Everything below runs under an XDG_RUNTIME_DIR of this group's own. The
# prompt socket is named from the client's pid and lives there, so a leg
# that used the developer's real runtime dir would both litter it and make
# the hygiene assertion at the bottom read somebody else's sockets.
ASKRUN="${TMPDIR:-/tmp}/mux-e2e-askrun-$$"
defer_rm "$ASKRUN"
ASKSHIM="${TMPDIR:-/tmp}/mux-e2e-askshim-$$"
defer_rm "$ASKSHIM"
# One remote "box" per leg, each a runtime dir of its own — which is the
# whole of what makes them different machines, since the shim execs the
# remote command locally and `mux d`'s default socket resolves out of that
# variable. Sharing one would let the password leg's session decide what
# the decline leg's wall is looking at.
ASKRUN_PW="${TMPDIR:-/tmp}/mux-e2e-askbox-pw-$$"
defer_rm "$ASKRUN_PW"
ASKRUN_HK="${TMPDIR:-/tmp}/mux-e2e-askbox-hk-$$"
defer_rm "$ASKRUN_HK"
ASKRUN_NO="${TMPDIR:-/tmp}/mux-e2e-askbox-no-$$"
defer_rm "$ASKRUN_NO"
# A config home with no key in it, exported by the shim into every remote
# `mux d endpoint`. Without it the first poll's endpoint ANNOUNCES a QUIC
# port and the client caches it, and every dial after that is a UDP
# handshake with no ssh in it — a box that never asks a question again,
# which is the whole subject here. This models the ordinary case anyway:
# a machine reached by password is one whose daemon has no key to offer.
ASKCFG="${TMPDIR:-/tmp}/mux-e2e-askcfg-$$"
defer_rm "$ASKCFG"
ASKSOCK_A="$ASKRUN/muxd.sock"
ASKSOCK_PW="$ASKRUN_PW/muxd.sock"
ASKSOCK_HK="$ASKRUN_HK/muxd.sock"
ASKSOCK_NO="$ASKRUN_NO/muxd.sock"
defer_sock "$ASKSOCK_A" "$ASKSOCK_PW" "$ASKSOCK_HK" "$ASKSOCK_NO"
mkdir -p "$ASKRUN" "$ASKSHIM" "$ASKRUN_PW" "$ASKRUN_HK" "$ASKRUN_NO" "$ASKCFG"
# What the shim wrote down, one line per ssh run and one per helper run.
# Both are re-pointed per phase, so a leg counts its own runs and not the
# whole group's.
ASKDIALLOG="$ASKSHIM/dials"
ASKPROMPTLOG="$ASKSHIM/prompts"
export ASKRUN_PW ASKRUN_HK ASKRUN_NO ASKDIALLOG ASKPROMPTLOG ASKCFG

# The stand-in ssh. Its own option loop rather than `ssh_shim_head`'s: this
# group's whole subject is WHICH runs may prompt, and the shared head
# discards the options — a leg that cannot see `-o BatchMode=yes` cannot
# tell a poll from a dial, which is exactly the distinction being asserted.
# The arity guard below is the head's, verbatim and for its reason: `ssh
# HOST` with no command leaves nothing to exec, and a bare exec there exits
# 0 — silence shaped like success.
#
# What it models is real ssh's own contract under SSH_ASKPASS_REQUIRE=force:
# the helper is exec'd with the prompt as its ONE argument, its stdout is
# the answer, and a non-zero exit is a refused prompt that ends the login.
#
# And the other half of a real box's behaviour, which is what decides which
# tiles can exist here at all: a host that wants a password, or whose key
# is not known, REFUSES every BatchMode run. So its `SessionPoll` never
# returns a list, no tile is ever born off it, and the only dials that can
# raise a prompt are the two a user asked for — a picker birth and the
# entry tile's reconnect. A shim that let polls log in would put a tile on
# the wall by a road no real box has, and the leg driving it would be
# asserting on the fixture.
cat > "$ASKSHIM/ssh" <<'SHIM'
#!/bin/sh
ASKOPTS=
while [ $# -gt 0 ]; do
    case "$1" in
        -o) ASKOPTS="$ASKOPTS $2"; shift 2 ;;
        -*) ASKOPTS="$ASKOPTS $1"; shift ;;
        *) break ;;
    esac
done
[ $# -ge 2 ] || exit 97
case "$1" in
    askbox-pw@*) XDG_RUNTIME_DIR="${ASKRUN_PW:?}"
        ASKQ="e2e@askbox's password: "; ASKWANT=hunter2
        ASKKIND=; ASKDENY="Permission denied (publickey,password)." ;;
    askbox-hk@*) XDG_RUNTIME_DIR="${ASKRUN_HK:?}"
        ASKQ="Are you sure you want to continue connecting (yes/no/[fingerprint])? "
        ASKWANT=yes; ASKKIND=confirm; ASKDENY="Host key verification failed." ;;
    askbox-no@*) XDG_RUNTIME_DIR="${ASKRUN_NO:?}"
        ASKQ="e2e@askbox's password: "; ASKWANT=hunter2
        ASKKIND=; ASKDENY="Permission denied (publickey,password)." ;;
    *) exit 96 ;;
esac
# The remote's key lives under this, and there is none: see ASKCFG.
XDG_CONFIG_HOME="${ASKCFG:?}"
export XDG_RUNTIME_DIR XDG_CONFIG_HOME
unset MUX_KEY_FILE
# The host, the options it was given, and whether it was handed a socket to
# ask on. Three fields because the three kinds of run are told apart by
# exactly this: a poll carries BatchMode and no socket, a wall dial carries
# a socket, and the entry dial carries neither.
printf '%s|%s|%s\n' "$1" "$ASKOPTS" "${MUX_ASKPASS_SOCK:+socket}" >> "${ASKDIALLOG:?}"
shift
# Logged first, refused second: the poll legs count runs that HAPPENED, and
# a box that refuses them is still a box that was dialled.
case "$ASKOPTS" in
    *BatchMode=yes*) printf '%s\n' "$ASKDENY" >&2; exit 255 ;;
esac
if [ -n "${MUX_ASKPASS_SOCK:-}" ] && [ "${SSH_ASKPASS_REQUIRE:-}" = force ]; then
    printf '%s\n' "$ASKQ" >> "${ASKPROMPTLOG:?}"
    # SSH_ASKPASS_PROMPT is ssh's own word for what it is asking, and the
    # wall reads it to decide whether to star the answer. Empty for a
    # secret, which is ssh's own spelling: the variable is simply unset.
    if [ -n "$ASKKIND" ]; then
        ASKGOT=$(SSH_ASKPASS_PROMPT="$ASKKIND" "${SSH_ASKPASS:?}" "$ASKQ")
    else
        ASKGOT=$("${SSH_ASKPASS:?}" "$ASKQ")
    fi || {
        printf '%s\n' "$ASKDENY" >&2
        exit 255
    }
    [ "$ASKGOT" = "$ASKWANT" ] || {
        printf '%s\n' "$ASKDENY" >&2; exit 255; }
fi
exec /bin/sh -c "$*"
SHIM
chmod +x "$ASKSHIM/ssh"
ASKPATH="$ASKSHIM:$(cd "$(dirname "$MUX")" && pwd):$PATH"

# The local box the wall is entered on: a `--sock` target spawns no ssh, so
# the entry attach is not itself a prompt — the tile under test is the one
# the picker births on the machine across the shim.
start_daemon "$ASKSOCK_A" "$OUT.ask.a" "askpass: the local daemon never bound" --shell /bin/sh
ASKAPID=$DPID

# askpass_wall STATE HOST — a wall over two daemons: the local one and one
# fake box across the shim. No tile is EVER born off the box: it refuses
# every BatchMode run, so its poll never returns a list and its picker row
# reads `unreachable`. The prompt is raised by the one dial a user asks
# for — the picker's `c` on that row — which is the real path.
askpass_wall() {
    mkdir -p "$1/mux"
    printf -- '--sock %s\n%s\n' "$ASKSOCK_A" "$2" > "$1/mux/hosts"
    # One pane, the LOCAL daemon's own session: the wall is the layout, so
    # without a leaf these legs would open on the empty-wall line and the
    # marker they wait for before touching the popup would never paint.
    # Nothing on the box is ever a pane — it refuses every BatchMode run —
    # which is what leaves the picker as the only road to a dial.
    seed_layout "$1" stacked "--sock $ASKSOCK_A#0"
}

# ---- a password prompt is painted, masked, and answered ----------------
#
# The claim in full: a picker birth on a box that wants a password puts
# ssh's own question on the wall, takes the answer byte by byte behind
# stars, and comes up. The oracles are outside the terminal wherever they
# can be — the far daemon's own grid says the login really happened — and
# the one that must be INSIDE it is the negative: `hunter2` appears nowhere
# in the capture, because a capture is a scrollback and a scrollback is a
# file. The row the birth is typed at is asserted too: a password box says
# `unreachable: Permission denied` on the picker, in ssh's own words.
start_daemon "$ASKSOCK_PW" "$OUT.ask.pw" "askpass: the password box never bound" --shell /bin/sh
ASKPWPID=$DPID
ASKSTATE_PW="${TMPDIR:-/tmp}/mux-e2e-askstate-pw-$$"
defer_rm "$ASKSTATE_PW"
askpass_wall "$ASKSTATE_PW" "askbox-pw@127.0.0.1"

# A marker on the local daemon, printed before this wall exists. Waited for
# AFTER the prompt is answered, not before it: the box can be on the screen
# before the entry tile has painted a single cell — tiles do not paint while
# a popup is up — so a wall that waited for the marker first would be
# waiting for a paint the popup is holding back. It doubles as the claim
# that the rects come back when the box closes.
pipe_mux "$OUT.ask.a1" "$OUT.ask.a1.err" env XDG_RUNTIME_DIR="$ASKRUN" \
    XDG_STATE_HOME="$ASKSTATE_PW" timeout 40 "$MUX" --sock "$ASKSOCK_A"
pipe_send 'printf "ask-home-%%s\\n" mark\n'
await_out "$OUT.ask.a1" "ask-home-mark" "askpass: the local daemon's marker never arrived"
pipe_detach
wait_grid "$ASKSOCK_A" "ask-home-mark" "askpass: the local marker"

: > "$ASKDIALLOG"
: > "$ASKPROMPTLOG"
set +e
XDG_RUNTIME_DIR="$ASKRUN" XDG_STATE_HOME="$ASKSTATE_PW" PATH="$ASKPATH" \
    timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \
    --out "$OUT.askpw.cap" --err "$OUT.askpw.cap.err" -- \
    "$MUX" > "$OUT.askpw.pc" 2>&1 <<EOF
expect ask-home-mark 20000
settle 700 20000
send \x1cs
expect unreachable: Permission denied 15000
send 2
settle 400 15000
send c
expect askbox's password: 25000
send hunt
settle 400 15000
expect > ****_ 15000
send er2
settle 400 15000
expect > *******_ 15000
send \r
settle 900 25000
send printf 'ask-pw-%s\n' up\n
expect ask-pw-up 25000
send \x1cd
waitexit 10000
EOF
ASKRC=$?
set -e
[ "$ASKRC" -eq 0 ] || {
    echo "e2e FAIL: askpass: the password leg exited $ASKRC:"
    cat "$OUT.askpw.pc"; echo "--- client stderr ---"; cat "$OUT.askpw.cap.err"
    echo "--- dials ---"; cat "$ASKDIALLOG"
    exit 1; }
# The login really happened, asked of the far DAEMON's own grid and not of
# the screen: a popup that painted and a session nobody reached would
# satisfy every assertion above.
dump_session "$ASKSOCK_PW" 0 | grep -q 'ask-pw-up' || {
    echo "e2e FAIL: askpass: the answered login's keystrokes never reached the box:"
    dump_session "$ASKSOCK_PW" 0; exit 1; }
# The one assertion that is about the capture BECAUSE it is a file: a pty
# capture is a scrollback, and a password in one is a password on disk.
if grep -aqF 'hunter2' "$OUT.askpw.cap"; then
    echo "e2e FAIL: askpass: the password is in the capture in the clear"; exit 1
fi
# ...and the helper really was ssh's, run once per dial rather than by the
# wall calling itself.
[ "$(grep -c . "$ASKPROMPTLOG")" -ge 1 ] || {
    echo "e2e FAIL: askpass: ssh never ran the helper:"; cat "$ASKDIALLOG"; exit 1; }
ok "a wall dial's password prompt is painted, masked, and answered"

# ---- a poll carries BatchMode and is handed no socket -------------------
#
# The same wall's own log, read after it has gone. The rule is not "polls
# happen not to prompt": a poller that could prompt would raise a question
# every second per host, on a wall nobody is looking at. It spells
# BatchMode, which cannot ask, and it is handed no socket to ask on — and
# the run that CAN ask is told apart from it here by both fields at once.
# The rule first, so a break in it is named by the message that describes
# it rather than by the count below going to zero.
if grep -q 'BatchMode=yes.*|socket$' "$ASKDIALLOG"; then
    echo "e2e FAIL: askpass: a poll was handed a prompt socket:"
    cat "$ASKDIALLOG"; exit 1
fi
# At least one, not several: a host whose poll FAILED is re-asked a tenth
# as often on purpose (`client.pollDelayMs` reads the link that answered,
# and a refused ssh is a pipe), and this leg is not long enough to buy two.
ASKPOLLS=$(grep -c 'BatchMode=yes.*|$' "$ASKDIALLOG" || true)
[ "$ASKPOLLS" -ge 1 ] || {
    echo "e2e FAIL: askpass: the wall never polled the box:"
    cat "$ASKDIALLOG"; exit 1; }
# The dial that DID carry one, so the absence above is a rule and not a leg
# in which nothing was ever offered a socket.
grep -q '||socket$' "$ASKDIALLOG" || {
    echo "e2e FAIL: askpass: no dial carried a prompt socket at all:"
    cat "$ASKDIALLOG"; exit 1; }
ok "a poll spells BatchMode and is handed no prompt socket"

# ---- the wall takes its prompt socket with it ---------------------------
#
# The socket is named from the client's pid, and a pid comes round again. A
# file left behind is one the next client of that pid finds sitting on its
# own name.
ASKLEFT=$(find "$ASKRUN" -maxdepth 1 -name 'mux-ask-*' | wc -l)
[ "$ASKLEFT" -eq 0 ] || {
    echo "e2e FAIL: askpass: the wall left $ASKLEFT prompt socket(s) behind:"
    find "$ASKRUN" -maxdepth 1 -name 'mux-ask-*'; exit 1; }
ok "the wall takes its prompt socket with it"

# ---- the host-key question is answered in the CLEAR ---------------------
#
# The one prompt whose answer the user has to be able to read: they are
# being asked to compare a fingerprint, and stars would make the question
# unanswerable. Its own box and its own wall — one shim run's question is
# fixed by the host word, which is what two machines buy here.
start_daemon "$ASKSOCK_HK" "$OUT.ask.hk" "askpass: the host-key box never bound" --shell /bin/sh
ASKHKPID=$DPID
ASKSTATE_HK="${TMPDIR:-/tmp}/mux-e2e-askstate-hk-$$"
defer_rm "$ASKSTATE_HK"
askpass_wall "$ASKSTATE_HK" "askbox-hk@127.0.0.1"
pipe_mux "$OUT.ask.a2" "$OUT.ask.a2.err" env XDG_RUNTIME_DIR="$ASKRUN" \
    XDG_STATE_HOME="$ASKSTATE_HK" timeout 40 "$MUX" --sock "$ASKSOCK_A"
pipe_send 'printf "ask-hk-home-%%s\\n" mark\n'
await_out "$OUT.ask.a2" "ask-hk-home-mark" "askpass: the host-key leg's marker never arrived"
pipe_detach
wait_grid "$ASKSOCK_A" "ask-hk-home-mark" "askpass: the host-key leg's marker"

set +e
XDG_RUNTIME_DIR="$ASKRUN" XDG_STATE_HOME="$ASKSTATE_HK" PATH="$ASKPATH" \
    timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \
    --out "$OUT.askhk.cap" --err "$OUT.askhk.cap.err" -- \
    "$MUX" > "$OUT.askhk.pc" 2>&1 <<EOF
expect ask-hk-home-mark 20000
settle 700 20000
send \x1cs
expect unreachable: Host key verification 15000
send 2
settle 400 15000
send c
expect continue connecting 25000
send yes
settle 400 15000
expect > yes_ 15000
send \r
settle 900 25000
send printf 'ask-hk-%s\n' up\n
expect ask-hk-up 25000
send \x1cd
waitexit 10000
EOF
ASKRC=$?
set -e
[ "$ASKRC" -eq 0 ] || {
    echo "e2e FAIL: askpass: the host-key leg exited $ASKRC:"
    cat "$OUT.askhk.pc"; echo "--- client stderr ---"; cat "$OUT.askhk.cap.err"
    echo "--- dials ---"; cat "$ASKDIALLOG"; exit 1; }
dump_session "$ASKSOCK_HK" 0 | grep -q 'ask-hk-up' || {
    echo "e2e FAIL: askpass: the host-key answer did not open the session:"
    dump_session "$ASKSOCK_HK" 0; exit 1; }
ok "the host-key question is answered in the clear, not behind stars"

# ---- Esc on a prompt ends the tile, and the redialling stops ------------
#
# Without this the user who declined is asked the same question every two
# seconds for as long as the wall is up — the redial loop arguing with an
# answer it already has. The oracle is the shim's own log: after the Esc,
# five seconds buys the box NO further dial that carried a socket. The
# polls go on, and are counted separately, because a poll is not the thing
# that was refused.
start_daemon "$ASKSOCK_NO" "$OUT.ask.no" "askpass: the decline box never bound" --shell /bin/sh
ASKNOPID=$DPID
ASKSTATE_NO="${TMPDIR:-/tmp}/mux-e2e-askstate-no-$$"
defer_rm "$ASKSTATE_NO"
askpass_wall "$ASKSTATE_NO" "askbox-no@127.0.0.1"
pipe_mux "$OUT.ask.a3" "$OUT.ask.a3.err" env XDG_RUNTIME_DIR="$ASKRUN" \
    XDG_STATE_HOME="$ASKSTATE_NO" timeout 40 "$MUX" --sock "$ASKSOCK_A"
pipe_send 'printf "ask-no-home-%%s\\n" mark\n'
await_out "$OUT.ask.a3" "ask-no-home-mark" "askpass: the decline leg's marker never arrived"
pipe_detach
wait_grid "$ASKSOCK_A" "ask-no-home-mark" "askpass: the decline leg's marker"

: > "$ASKDIALLOG"
: > "$ASKPROMPTLOG"
set +e
XDG_RUNTIME_DIR="$ASKRUN" XDG_STATE_HOME="$ASKSTATE_NO" PATH="$ASKPATH" \
    timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \
    --out "$OUT.askno.cap" --err "$OUT.askno.cap.err" -- \
    "$MUX" > "$OUT.askno.pc" 2>&1 <<EOF
expect ask-no-home-mark 20000
settle 700 20000
send \x1cs
expect unreachable: Permission denied 15000
send 2
settle 400 15000
send c
expect askbox's password: 25000
send \x1b
expect prompt declined 25000
settle 5000 20000
send printf 'ask-no-%s\n' back\n
expect ask-no-back 20000
send \x1cd
waitexit 10000
EOF
ASKRC=$?
set -e
[ "$ASKRC" -eq 0 ] || {
    echo "e2e FAIL: askpass: the decline leg exited $ASKRC:"
    cat "$OUT.askno.pc"; echo "--- client stderr ---"; cat "$OUT.askno.cap.err"
    echo "--- dials ---"; cat "$ASKDIALLOG"; exit 1; }
# ONE prompt, and one only. A second says the tile redialled: five seconds
# at the 2s backoff cap is two more goes at least.
ASKNOPROMPTS=$(grep -c . "$ASKPROMPTLOG" || true)
[ "$ASKNOPROMPTS" -eq 1 ] || {
    echo "e2e FAIL: askpass: the declined box was asked $ASKNOPROMPTS times, want 1:"
    cat "$ASKDIALLOG"; exit 1; }
# ...and the dial that carried a socket happened once, which is what makes
# the count above a stopped loop rather than a helper that stopped running.
ASKNODIALS=$(grep -c '||socket$' "$ASKDIALLOG" || true)
[ "$ASKNODIALS" -eq 1 ] || {
    echo "e2e FAIL: askpass: the declined box was dialled $ASKNODIALS times, want 1:"
    cat "$ASKDIALLOG"; exit 1; }
# The tile LEFT, and the wall said why. A picker-born tile is focused and
# keeps the wall, so its ending goes through `endAction`'s vanish arm: the
# focus falls back to the tile the birth was typed from, and the sentence
# is printed on the way out. Both are asserted where the terminal cannot
# flatter them — the keys after the decline are looked for in the LOCAL
# daemon's own grid, and the sentence in the client's stderr file.
dump_session "$ASKSOCK_A" 0 | grep -q 'ask-no-back' || {
    echo "e2e FAIL: askpass: the focus did not fall back to the tile the birth came from:"
    dump_session "$ASKSOCK_A" 0; exit 1; }
grep -qF 'mux: prompt declined' "$OUT.askno.cap.err" || {
    echo "e2e FAIL: askpass: the wall did not say why the tile left:"
    cat "$OUT.askno.cap.err"; exit 1; }
ok "an Esc on a prompt ends the tile, says why, and stops the redialling"

# ---- the entry dial keeps ssh's own prompts -----------------------------
#
# `mux HOST` is a person at a bare prompt with a /dev/tty right there, and
# no wall exists yet to paint a popup on. It must therefore be handed no
# socket at all — asserted on the shim's env dump, because "it did not
# prompt" is also what a leg that never dialled looks like.
#
# On a PTY, deliberately, though the assertion would be easier on a pipe: a
# piped client starts no listener at all, so a pipe would satisfy this leg
# with the mechanism switched off. Here the wall behind the entry tile has
# a live listener the whole time, and the claim is that the FIRST attach
# still never reaches it.
: > "$ASKDIALLOG"
: > "$ASKPROMPTLOG"
set +e
XDG_RUNTIME_DIR="$ASKRUN" XDG_STATE_HOME="$ASKSTATE_PW" PATH="$ASKPATH" \
    timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \
    --out "$OUT.askentry.cap" --err "$OUT.askentry.cap.err" -- \
    "$MUX" askbox-pw@127.0.0.1 > "$OUT.askentry.pc" 2>&1 <<EOF
settle 1200 25000
send printf 'ask-entry-%s\n' mark\n
expect ask-entry-mark 20000
send \x1cd
waitexit 10000
EOF
ASKRC=$?
set -e
[ "$ASKRC" -eq 0 ] || {
    echo "e2e FAIL: askpass: the entry-dial leg exited $ASKRC:"
    cat "$OUT.askentry.pc"; echo "--- client stderr ---"; cat "$OUT.askentry.cap.err"
    echo "--- dials ---"; cat "$ASKDIALLOG"; exit 1; }
# The entry dial's own line: no options (it spells the asking word, which
# carries no `-o`) and no socket. The wall's dial is the same shape but for
# the third field, which is the whole distinction.
grep -q '^askbox-pw@127.0.0.1||$' "$ASKDIALLOG" || {
    echo "e2e FAIL: askpass: the entry dial's ssh did not run without a prompt socket:"
    cat "$ASKDIALLOG"; exit 1; }
# ...and NOTHING on this wall asked a question, which is the half that
# catches an entry tile re-dialling through the armed path instead of
# adopting the link its attach already opened.
[ "$(grep -c . "$ASKPROMPTLOG")" -eq 0 ] || {
    echo "e2e FAIL: askpass: the entry dial raised a prompt:"; cat "$ASKPROMPTLOG"; exit 1; }
ok "the entry dial is handed no prompt socket and keeps ssh's own"

# ---- no runtime dir, no popup: ssh keeps its own prompts ----------------
#
# The refusal that has no other witness. `$XDG_RUNTIME_DIR` is where the
# prompt socket goes and mux takes that directory as found; with none
# there is nowhere private to put a socket, and a guess would land in a
# shared /tmp — where a password prompt is one any local user could answer.
# So the wall starts no listener at all and ssh keeps its own prompts,
# which is the OLD behaviour and a visible one.
#
# A comment cannot fail. Without this leg a future fallback to /tmp would
# pass every other check in this file green.

# The wall this leg opens is written fresh: the password leg above left a
# layout with the pane its birth made, and a second pane dialling the box
# would put a dial in the log before the one under test.
askpass_wall "$ASKSTATE_PW" "askbox-pw@127.0.0.1"
: > "$ASKDIALLOG"
: > "$ASKPROMPTLOG"
set +e
env -u XDG_RUNTIME_DIR XDG_STATE_HOME="$ASKSTATE_PW" PATH="$ASKPATH" \
    timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \
    --out "$OUT.asknort.cap" --err "$OUT.asknort.cap.err" -- \
    "$MUX" --sock "$ASKSOCK_A" > "$OUT.asknort.pc" 2>&1 <<EOF
expect ask-home-mark 20000
settle 700 20000
send \x1cs
expect unreachable: 15000
send 2
settle 400 15000
send c
settle 2500 20000
send \x1cd
waitexit 10000
EOF
ASKRC=$?
set -e
[ "$ASKRC" -eq 0 ] || {
    echo "e2e FAIL: askpass: the no-runtime-dir leg exited $ASKRC:"
    cat "$OUT.asknort.pc"; echo "--- client stderr ---"; cat "$OUT.asknort.cap.err"
    echo "--- dials ---"; cat "$ASKDIALLOG"; exit 1; }
# The birth DID dial — otherwise the absence below is a leg that never ran.
grep -q '^askbox-pw@127.0.0.1||' "$ASKDIALLOG" || {
    echo "e2e FAIL: askpass: the picker birth never dialled without a runtime dir:"
    cat "$ASKDIALLOG"; exit 1; }
# ...and no run of it carried a socket, which is what says no listener was
# ever started.
if grep -q 'socket$' "$ASKDIALLOG"; then
    echo "e2e FAIL: askpass: a wall with no runtime dir armed a prompt socket:"
    cat "$ASKDIALLOG"; exit 1
fi
[ "$(grep -c . "$ASKPROMPTLOG")" -eq 0 ] || {
    echo "e2e FAIL: askpass: a wall with no runtime dir raised a prompt:"
    cat "$ASKPROMPTLOG"; exit 1; }
ok "a wall with no runtime dir starts no listener and ssh keeps its own prompts"

assert_stopped "$ASKSOCK_A" "$ASKAPID" "askpass local" "$OUT.ask.a.stop"
ASKAPID=""
assert_stopped "$ASKSOCK_PW" "$ASKPWPID" "askpass password box" "$OUT.ask.pw.stop"
ASKPWPID=""
assert_stopped "$ASKSOCK_HK" "$ASKHKPID" "askpass host-key box" "$OUT.ask.hk.stop"
ASKHKPID=""
assert_stopped "$ASKSOCK_NO" "$ASKNOPID" "askpass decline box" "$OUT.ask.no.stop"
ASKNOPID=""
rm -rf "$ASKSHIM" "$ASKRUN" "$ASKRUN_PW" "$ASKRUN_HK" "$ASKRUN_NO"