xref: /freebsd/crypto/openssh/regress/ssh-tty.sh (revision 2574974648c68c738aec3ff96644d888d7913a37)
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