1*25749746SEd Maste# $OpenBSD: ssh-tty.sh,v 1.8 2025/10/23 06:15:26 dtucker Exp $ 2*25749746SEd Maste# Placed in the Public Domain. 3*25749746SEd Maste 4*25749746SEd Maste# Basic TTY smoke test 5*25749746SEd Maste 6*25749746SEd Mastetid="ssh-tty" 7*25749746SEd Maste 8*25749746SEd Maste# Fake home directory to avoid user shell configuration. 9*25749746SEd MasteFAKEHOME="$OBJ/.fakehome" 10*25749746SEd Masterm -rf "$FAKEHOME" 11*25749746SEd Mastemkdir -m 0700 -p "$FAKEHOME" 12*25749746SEd Maste 13*25749746SEd Mastecase "${PATH}${HOME}" in 14*25749746SEd Maste*\ *|*\t*) skip "\$PATH or \$HOME has whitespace, not supported in this test";; 15*25749746SEd Masteesac 16*25749746SEd Maste 17*25749746SEd Maste# tmux stuff 18*25749746SEd MasteTMUX=${TMUX:-tmux} 19*25749746SEd Mastetype $TMUX >/dev/null || skip "tmux not found" 20*25749746SEd Maste 21*25749746SEd Masteif $TMUX -V >/dev/null 2>&1; then 22*25749746SEd Maste tver="`$TMUX -V 2>&1`" 23*25749746SEd Maste echo "tmux version $tver" 24*25749746SEd Masteelse 25*25749746SEd Maste skip "tmux version not reported" 26*25749746SEd Mastefi 27*25749746SEd Maste 28*25749746SEd MasteCLEANENV="env -i HOME=$HOME LOGNAME=$USER USER=$USER PATH=$PATH SHELL=$SHELL" 29*25749746SEd MasteTMUX_TEST="$CLEANENV $TMUX -f/dev/null -Lopenssh-regress-ssh-tty" 30*25749746SEd Mastesess="regress-ssh-tty$$" 31*25749746SEd Maste 32*25749746SEd Maste# Multiplexing control socket. 33*25749746SEd MasteCTL=$OBJ/ctl-sock 34*25749746SEd Maste 35*25749746SEd Maste# Some randomish strings used for signalling back and forth. 36*25749746SEd Maste# We use the octal variants via printf(1). 37*25749746SEd MasteMAGIC1="XY23zzY" 38*25749746SEd MasteMAGIC1_OCTAL="\130\131\062\063\172\172\131" 39*25749746SEd MasteMAGIC2="99sMarT86" 40*25749746SEd MasteMAGIC2_OCTAL="\071\071\163\115\141\162\124\070\066" 41*25749746SEd MasteMAGIC3="woLF1701d" 42*25749746SEd MasteMAGIC3_OCTAL="\167\157\114\106\061\067\060\061\144" 43*25749746SEd MasteMAGIC4="lUh4thX4evR" 44*25749746SEd MasteMAGIC4_OCTAL="\154\125\150\064\164\150\130\064\145\166\122" 45*25749746SEd MasteMAGIC5="AllMo1000x" 46*25749746SEd MasteMAGIC5_OCTAL="\101\154\154\115\157\061\060\060\060\170" 47*25749746SEd Maste 48*25749746SEd Maste# Wait for a mux process to become ready. 49*25749746SEd Mastewait_for_mux_ready() 50*25749746SEd Maste{ 51*25749746SEd Maste for i in 1 2 3 4 5 6 7 8 9; do 52*25749746SEd Maste ${SSH} -F $OBJ/ssh_config -S $CTL -Ocheck otherhost \ 53*25749746SEd Maste >/dev/null 2>&1 && return 0 54*25749746SEd Maste sleep $i 55*25749746SEd Maste done 56*25749746SEd Maste fatal "mux never becomes ready" 57*25749746SEd Maste} 58*25749746SEd Maste 59*25749746SEd Maste# Wait for a mux process to have finished. 60*25749746SEd Mastewait_for_mux_done() 61*25749746SEd Maste{ 62*25749746SEd Maste for i in 1 2 3 4 5 6 7 8 9; do 63*25749746SEd Maste test -S $CTL || return 0 64*25749746SEd Maste sleep $i 65*25749746SEd Maste done 66*25749746SEd Maste fatal "mux socket never removed" 67*25749746SEd Maste} 68*25749746SEd Maste 69*25749746SEd Maste# Wait for a regex to appear in terminal output. 70*25749746SEd Mastewait_for_regex() { 71*25749746SEd Maste string="$1" 72*25749746SEd Maste errors_are_fatal="$2" 73*25749746SEd Maste for x in 1 2 3 4 5 6 7 8 9 10 ; do 74*25749746SEd Maste $TMUX_TEST capture-pane -pt $sess | grep "$string" >/dev/null 75*25749746SEd Maste [ $? -eq 0 ] && return 76*25749746SEd Maste sleep 1 77*25749746SEd Maste done 78*25749746SEd Maste if test -z "$errors_are_fatal"; then 79*25749746SEd Maste fail "failed to match \"$string\" in terminal output" 80*25749746SEd Maste return 81*25749746SEd Maste fi 82*25749746SEd Maste fatal "failed to match \"$string\" in terminal output" 83*25749746SEd Maste} 84*25749746SEd Maste 85*25749746SEd Maste# Check that a regex does *not* appear in terminal output 86*25749746SEd Mastenot_in_term() { 87*25749746SEd Maste string="$1" 88*25749746SEd Maste error="$2" 89*25749746SEd Maste errors_are_fatal="$3" 90*25749746SEd Maste $TMUX_TEST capture-pane -pt $sess | grep "$string" > /dev/null 91*25749746SEd Maste [ $? -ne 0 ] && return 92*25749746SEd Maste if test -z "$errors_are_fatal"; then 93*25749746SEd Maste fail "$error" 94*25749746SEd Maste return 95*25749746SEd Maste fi 96*25749746SEd Maste fatal "$error" 97*25749746SEd Maste} 98*25749746SEd Maste 99*25749746SEd Maste# Shut down tmux session and Wait for it to terminate. 100*25749746SEd Mastekill_tmux() { 101*25749746SEd Maste $TMUX_TEST kill-session -t $sess 2>/dev/null 102*25749746SEd Maste for x in 1 2 3 4 5 6 7 8 9 10; do 103*25749746SEd Maste $TMUX_TEST has-session -t $sess >/dev/null 2>&1 || return 104*25749746SEd Maste sleep 1 105*25749746SEd Maste done 106*25749746SEd Maste fatal "tmux session didn't terminate" 107*25749746SEd Maste} 108*25749746SEd Maste 109*25749746SEd Mastetrap "$TMUX_TEST kill-session -t $sess 2>/dev/null" EXIT 110*25749746SEd Maste 111*25749746SEd Masterun_test() { 112*25749746SEd Maste tag="$1" 113*25749746SEd Maste ssh_args="$2" 114*25749746SEd Maste # Prepare a tmux session. 115*25749746SEd Maste kill_tmux 116*25749746SEd Maste $TMUX_TEST new-session -d -s $sess 117*25749746SEd Maste # echo XXXXXXXXXX $TMUX_TEST attach -t $sess; sleep 10 118*25749746SEd Maste 119*25749746SEd Maste # Command to start SSH; sent as keystrokes to tmux session. 120*25749746SEd Maste RCMD="$CLEANENV $SHELL" 121*25749746SEd Maste CMD="$SSH -F $OBJ/ssh_proxy $ssh_args -S $CTL x -tt $RCMD" 122*25749746SEd Maste 123*25749746SEd Maste verbose "${tag}: start connection" 124*25749746SEd Maste # arrange for the shell to print something after ssh completes. 125*25749746SEd Maste $TMUX_TEST send-keys -t $sess "$CMD && printf '$MAGIC1_OCTAL\n'" ENTER 126*25749746SEd Maste wait_for_mux_ready 127*25749746SEd Maste 128*25749746SEd Maste verbose "${tag}: send string" 129*25749746SEd Maste $TMUX_TEST send-keys -t $sess "printf '$MAGIC2_OCTAL\n'" ENTER 130*25749746SEd Maste wait_for_regex "$MAGIC2" 131*25749746SEd Maste 132*25749746SEd Maste verbose "${tag}: ^c interrupts process" 133*25749746SEd Maste # ^c should interrupt the sleep and prevent the magic string 134*25749746SEd Maste # from appearing. 135*25749746SEd Maste $TMUX_TEST send-keys -t $sess \ 136*25749746SEd Maste "printf '$MAGIC3_OCTAL' ; sleep 30 || printf '$MAGIC4_OCTAL\n'" 137*25749746SEd Maste $TMUX_TEST send-keys -t $sess ENTER 138*25749746SEd Maste wait_for_regex "$MAGIC3" # Command has executed. 139*25749746SEd Maste $TMUX_TEST send-keys -t $sess "C-c" 140*25749746SEd Maste # send another string to let us know that the sleep has finished. 141*25749746SEd Maste $TMUX_TEST send-keys -t $sess "printf '$MAGIC5_OCTAL\n'" ENTER 142*25749746SEd Maste wait_for_regex "$MAGIC5" 143*25749746SEd Maste not_in_term "$MAGIC4" "^c did not interrupt" 144*25749746SEd Maste 145*25749746SEd Maste verbose "${tag}: ~? produces help" 146*25749746SEd Maste $TMUX_TEST send-keys -t $sess ENTER "~?" 147*25749746SEd Maste wait_for_regex "^Supported escape sequences:$" 148*25749746SEd Maste 149*25749746SEd Maste verbose "${tag}: ~. terminates session" 150*25749746SEd Maste $TMUX_TEST send-keys -t $sess ENTER "~." 151*25749746SEd Maste wait_for_mux_done 152*25749746SEd Maste not_in_term "$MAGIC1" "ssh unexpectedly exited successfully after ~." 153*25749746SEd Maste 154*25749746SEd Maste verbose "${tag}: restart session" 155*25749746SEd Maste $TMUX_TEST send-keys -t $sess "$CMD && printf '$MAGIC1_OCTAL\n'" ENTER 156*25749746SEd Maste wait_for_mux_ready 157*25749746SEd Maste 158*25749746SEd Maste verbose "${tag}: eof terminates session successfully" 159*25749746SEd Maste $TMUX_TEST send-keys -t $sess ENTER "C-d" 160*25749746SEd Maste wait_for_regex "$MAGIC1" 161*25749746SEd Maste} 162*25749746SEd Maste 163*25749746SEd Maste# Make sure tmux is working as expected before we start. 164*25749746SEd Mastekill_tmux 165*25749746SEd Maste$TMUX_TEST new-session -d -s $sess 166*25749746SEd Maste# Make sure the session doesn't contain the magic strings we will use 167*25749746SEd Maste# for signalling or any #? output. 168*25749746SEd Mastenot_in_term "$MAGIC1" "terminal already contains magic1 string" fatal 169*25749746SEd Mastenot_in_term "$MAGIC2" "terminal already contains magic2 string" fatal 170*25749746SEd Mastenot_in_term "$MAGIC3" "terminal already contains magic3 string" fatal 171*25749746SEd Mastenot_in_term "$MAGIC4" "terminal already contains magic4 string" fatal 172*25749746SEd Mastenot_in_term "$MAGIC5" "terminal already contains magic5 string" fatal 173*25749746SEd Mastenot_in_term "^Supported escape" "terminal already contains escape help" fatal 174*25749746SEd Maste$TMUX_TEST send-keys -t $sess "printf '$MAGIC1_OCTAL\n'" ENTER 175*25749746SEd Mastewait_for_regex "$MAGIC1" fatal 176*25749746SEd Mastekill_tmux 177*25749746SEd Maste 178*25749746SEd Masterun_test "basic" "-oControlMaster=yes" 179*25749746SEd Masterun_test "ControlPersist" "-oControlMaster=auto -oControlPersist=1s" 180