xref: /freebsd/crypto/openssh/clientloop.c (revision 511b41d2a18c00a5f0db1a67a87ef8d831b19f2a)
1511b41d2SMark Murray /*
2511b41d2SMark Murray  *
3511b41d2SMark Murray  * clientloop.c
4511b41d2SMark Murray  *
5511b41d2SMark Murray  * Author: Tatu Ylonen <ylo@cs.hut.fi>
6511b41d2SMark Murray  *
7511b41d2SMark Murray  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
8511b41d2SMark Murray  *                    All rights reserved
9511b41d2SMark Murray  *
10511b41d2SMark Murray  *
11511b41d2SMark Murray  * Created: Sat Sep 23 12:23:57 1995 ylo
12511b41d2SMark Murray  *
13511b41d2SMark Murray  * The main loop for the interactive session (client side).
14511b41d2SMark Murray  *
15511b41d2SMark Murray  */
16511b41d2SMark Murray 
17511b41d2SMark Murray #include "includes.h"
18511b41d2SMark Murray RCSID("$Id: clientloop.c,v 1.14 1999/12/06 20:15:26 deraadt Exp $");
19511b41d2SMark Murray 
20511b41d2SMark Murray #include "xmalloc.h"
21511b41d2SMark Murray #include "ssh.h"
22511b41d2SMark Murray #include "packet.h"
23511b41d2SMark Murray #include "buffer.h"
24511b41d2SMark Murray #include "authfd.h"
25511b41d2SMark Murray #include "readconf.h"
26511b41d2SMark Murray 
27511b41d2SMark Murray /* Flag indicating that stdin should be redirected from /dev/null. */
28511b41d2SMark Murray extern int stdin_null_flag;
29511b41d2SMark Murray 
30511b41d2SMark Murray /*
31511b41d2SMark Murray  * Name of the host we are connecting to.  This is the name given on the
32511b41d2SMark Murray  * command line, or the HostName specified for the user-supplied name in a
33511b41d2SMark Murray  * configuration file.
34511b41d2SMark Murray  */
35511b41d2SMark Murray extern char *host;
36511b41d2SMark Murray 
37511b41d2SMark Murray /*
38511b41d2SMark Murray  * Flag to indicate that we have received a window change signal which has
39511b41d2SMark Murray  * not yet been processed.  This will cause a message indicating the new
40511b41d2SMark Murray  * window size to be sent to the server a little later.  This is volatile
41511b41d2SMark Murray  * because this is updated in a signal handler.
42511b41d2SMark Murray  */
43511b41d2SMark Murray static volatile int received_window_change_signal = 0;
44511b41d2SMark Murray 
45511b41d2SMark Murray /* Terminal modes, as saved by enter_raw_mode. */
46511b41d2SMark Murray static struct termios saved_tio;
47511b41d2SMark Murray 
48511b41d2SMark Murray /*
49511b41d2SMark Murray  * Flag indicating whether we are in raw mode.  This is used by
50511b41d2SMark Murray  * enter_raw_mode and leave_raw_mode.
51511b41d2SMark Murray  */
52511b41d2SMark Murray static int in_raw_mode = 0;
53511b41d2SMark Murray 
54511b41d2SMark Murray /* Flag indicating whether the user\'s terminal is in non-blocking mode. */
55511b41d2SMark Murray static int in_non_blocking_mode = 0;
56511b41d2SMark Murray 
57511b41d2SMark Murray /* Common data for the client loop code. */
58511b41d2SMark Murray static int escape_pending;	/* Last character was the escape character */
59511b41d2SMark Murray static int last_was_cr;		/* Last character was a newline. */
60511b41d2SMark Murray static int exit_status;		/* Used to store the exit status of the command. */
61511b41d2SMark Murray static int stdin_eof;		/* EOF has been encountered on standard error. */
62511b41d2SMark Murray static Buffer stdin_buffer;	/* Buffer for stdin data. */
63511b41d2SMark Murray static Buffer stdout_buffer;	/* Buffer for stdout data. */
64511b41d2SMark Murray static Buffer stderr_buffer;	/* Buffer for stderr data. */
65511b41d2SMark Murray static unsigned int buffer_high;/* Soft max buffer size. */
66511b41d2SMark Murray static int max_fd;		/* Maximum file descriptor number in select(). */
67511b41d2SMark Murray static int connection_in;	/* Connection to server (input). */
68511b41d2SMark Murray static int connection_out;	/* Connection to server (output). */
69511b41d2SMark Murray static unsigned long stdin_bytes, stdout_bytes, stderr_bytes;
70511b41d2SMark Murray static int quit_pending;	/* Set to non-zero to quit the client loop. */
71511b41d2SMark Murray static int escape_char;		/* Escape character. */
72511b41d2SMark Murray 
73511b41d2SMark Murray /* Returns the user\'s terminal to normal mode if it had been put in raw mode. */
74511b41d2SMark Murray 
75511b41d2SMark Murray void
76511b41d2SMark Murray leave_raw_mode()
77511b41d2SMark Murray {
78511b41d2SMark Murray 	if (!in_raw_mode)
79511b41d2SMark Murray 		return;
80511b41d2SMark Murray 	in_raw_mode = 0;
81511b41d2SMark Murray 	if (tcsetattr(fileno(stdin), TCSADRAIN, &saved_tio) < 0)
82511b41d2SMark Murray 		perror("tcsetattr");
83511b41d2SMark Murray 
84511b41d2SMark Murray 	fatal_remove_cleanup((void (*) (void *)) leave_raw_mode, NULL);
85511b41d2SMark Murray }
86511b41d2SMark Murray 
87511b41d2SMark Murray /* Puts the user\'s terminal in raw mode. */
88511b41d2SMark Murray 
89511b41d2SMark Murray void
90511b41d2SMark Murray enter_raw_mode()
91511b41d2SMark Murray {
92511b41d2SMark Murray 	struct termios tio;
93511b41d2SMark Murray 
94511b41d2SMark Murray 	if (tcgetattr(fileno(stdin), &tio) < 0)
95511b41d2SMark Murray 		perror("tcgetattr");
96511b41d2SMark Murray 	saved_tio = tio;
97511b41d2SMark Murray 	tio.c_iflag |= IGNPAR;
98511b41d2SMark Murray 	tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
99511b41d2SMark Murray 	tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
100511b41d2SMark Murray #ifdef IEXTEN
101511b41d2SMark Murray 	tio.c_lflag &= ~IEXTEN;
102511b41d2SMark Murray #endif				/* IEXTEN */
103511b41d2SMark Murray 	tio.c_oflag &= ~OPOST;
104511b41d2SMark Murray 	tio.c_cc[VMIN] = 1;
105511b41d2SMark Murray 	tio.c_cc[VTIME] = 0;
106511b41d2SMark Murray 	if (tcsetattr(fileno(stdin), TCSADRAIN, &tio) < 0)
107511b41d2SMark Murray 		perror("tcsetattr");
108511b41d2SMark Murray 	in_raw_mode = 1;
109511b41d2SMark Murray 
110511b41d2SMark Murray 	fatal_add_cleanup((void (*) (void *)) leave_raw_mode, NULL);
111511b41d2SMark Murray }
112511b41d2SMark Murray 
113511b41d2SMark Murray /* Restores stdin to blocking mode. */
114511b41d2SMark Murray 
115511b41d2SMark Murray void
116511b41d2SMark Murray leave_non_blocking()
117511b41d2SMark Murray {
118511b41d2SMark Murray 	if (in_non_blocking_mode) {
119511b41d2SMark Murray 		(void) fcntl(fileno(stdin), F_SETFL, 0);
120511b41d2SMark Murray 		in_non_blocking_mode = 0;
121511b41d2SMark Murray 		fatal_remove_cleanup((void (*) (void *)) leave_non_blocking, NULL);
122511b41d2SMark Murray 	}
123511b41d2SMark Murray }
124511b41d2SMark Murray 
125511b41d2SMark Murray /* Puts stdin terminal in non-blocking mode. */
126511b41d2SMark Murray 
127511b41d2SMark Murray void
128511b41d2SMark Murray enter_non_blocking()
129511b41d2SMark Murray {
130511b41d2SMark Murray 	in_non_blocking_mode = 1;
131511b41d2SMark Murray 	(void) fcntl(fileno(stdin), F_SETFL, O_NONBLOCK);
132511b41d2SMark Murray 	fatal_add_cleanup((void (*) (void *)) leave_non_blocking, NULL);
133511b41d2SMark Murray }
134511b41d2SMark Murray 
135511b41d2SMark Murray /*
136511b41d2SMark Murray  * Signal handler for the window change signal (SIGWINCH).  This just sets a
137511b41d2SMark Murray  * flag indicating that the window has changed.
138511b41d2SMark Murray  */
139511b41d2SMark Murray 
140511b41d2SMark Murray void
141511b41d2SMark Murray window_change_handler(int sig)
142511b41d2SMark Murray {
143511b41d2SMark Murray 	received_window_change_signal = 1;
144511b41d2SMark Murray 	signal(SIGWINCH, window_change_handler);
145511b41d2SMark Murray }
146511b41d2SMark Murray 
147511b41d2SMark Murray /*
148511b41d2SMark Murray  * Signal handler for signals that cause the program to terminate.  These
149511b41d2SMark Murray  * signals must be trapped to restore terminal modes.
150511b41d2SMark Murray  */
151511b41d2SMark Murray 
152511b41d2SMark Murray void
153511b41d2SMark Murray signal_handler(int sig)
154511b41d2SMark Murray {
155511b41d2SMark Murray 	if (in_raw_mode)
156511b41d2SMark Murray 		leave_raw_mode();
157511b41d2SMark Murray 	if (in_non_blocking_mode)
158511b41d2SMark Murray 		leave_non_blocking();
159511b41d2SMark Murray 	channel_stop_listening();
160511b41d2SMark Murray 	packet_close();
161511b41d2SMark Murray 	fatal("Killed by signal %d.", sig);
162511b41d2SMark Murray }
163511b41d2SMark Murray 
164511b41d2SMark Murray /*
165511b41d2SMark Murray  * Returns current time in seconds from Jan 1, 1970 with the maximum
166511b41d2SMark Murray  * available resolution.
167511b41d2SMark Murray  */
168511b41d2SMark Murray 
169511b41d2SMark Murray double
170511b41d2SMark Murray get_current_time()
171511b41d2SMark Murray {
172511b41d2SMark Murray 	struct timeval tv;
173511b41d2SMark Murray 	gettimeofday(&tv, NULL);
174511b41d2SMark Murray 	return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0;
175511b41d2SMark Murray }
176511b41d2SMark Murray 
177511b41d2SMark Murray /*
178511b41d2SMark Murray  * This is called when the interactive is entered.  This checks if there is
179511b41d2SMark Murray  * an EOF coming on stdin.  We must check this explicitly, as select() does
180511b41d2SMark Murray  * not appear to wake up when redirecting from /dev/null.
181511b41d2SMark Murray  */
182511b41d2SMark Murray 
183511b41d2SMark Murray void
184511b41d2SMark Murray client_check_initial_eof_on_stdin()
185511b41d2SMark Murray {
186511b41d2SMark Murray 	int len;
187511b41d2SMark Murray 	char buf[1];
188511b41d2SMark Murray 
189511b41d2SMark Murray 	/*
190511b41d2SMark Murray 	 * If standard input is to be "redirected from /dev/null", we simply
191511b41d2SMark Murray 	 * mark that we have seen an EOF and send an EOF message to the
192511b41d2SMark Murray 	 * server. Otherwise, we try to read a single character; it appears
193511b41d2SMark Murray 	 * that for some files, such /dev/null, select() never wakes up for
194511b41d2SMark Murray 	 * read for this descriptor, which means that we never get EOF.  This
195511b41d2SMark Murray 	 * way we will get the EOF if stdin comes from /dev/null or similar.
196511b41d2SMark Murray 	 */
197511b41d2SMark Murray 	if (stdin_null_flag) {
198511b41d2SMark Murray 		/* Fake EOF on stdin. */
199511b41d2SMark Murray 		debug("Sending eof.");
200511b41d2SMark Murray 		stdin_eof = 1;
201511b41d2SMark Murray 		packet_start(SSH_CMSG_EOF);
202511b41d2SMark Murray 		packet_send();
203511b41d2SMark Murray 	} else {
204511b41d2SMark Murray 		enter_non_blocking();
205511b41d2SMark Murray 
206511b41d2SMark Murray 		/* Check for immediate EOF on stdin. */
207511b41d2SMark Murray 		len = read(fileno(stdin), buf, 1);
208511b41d2SMark Murray 		if (len == 0) {
209511b41d2SMark Murray 			/* EOF.  Record that we have seen it and send EOF to server. */
210511b41d2SMark Murray 			debug("Sending eof.");
211511b41d2SMark Murray 			stdin_eof = 1;
212511b41d2SMark Murray 			packet_start(SSH_CMSG_EOF);
213511b41d2SMark Murray 			packet_send();
214511b41d2SMark Murray 		} else if (len > 0) {
215511b41d2SMark Murray 			/*
216511b41d2SMark Murray 			 * Got data.  We must store the data in the buffer,
217511b41d2SMark Murray 			 * and also process it as an escape character if
218511b41d2SMark Murray 			 * appropriate.
219511b41d2SMark Murray 			 */
220511b41d2SMark Murray 			if ((unsigned char) buf[0] == escape_char)
221511b41d2SMark Murray 				escape_pending = 1;
222511b41d2SMark Murray 			else {
223511b41d2SMark Murray 				buffer_append(&stdin_buffer, buf, 1);
224511b41d2SMark Murray 				stdin_bytes += 1;
225511b41d2SMark Murray 			}
226511b41d2SMark Murray 		}
227511b41d2SMark Murray 		leave_non_blocking();
228511b41d2SMark Murray 	}
229511b41d2SMark Murray }
230511b41d2SMark Murray 
231511b41d2SMark Murray /*
232511b41d2SMark Murray  * Get packets from the connection input buffer, and process them as long as
233511b41d2SMark Murray  * there are packets available.
234511b41d2SMark Murray  */
235511b41d2SMark Murray 
236511b41d2SMark Murray void
237511b41d2SMark Murray client_process_buffered_input_packets()
238511b41d2SMark Murray {
239511b41d2SMark Murray 	int type;
240511b41d2SMark Murray 	char *data;
241511b41d2SMark Murray 	unsigned int data_len;
242511b41d2SMark Murray 	int payload_len;
243511b41d2SMark Murray 
244511b41d2SMark Murray 	/* Process any buffered packets from the server. */
245511b41d2SMark Murray 	while (!quit_pending &&
246511b41d2SMark Murray 	       (type = packet_read_poll(&payload_len)) != SSH_MSG_NONE) {
247511b41d2SMark Murray 		switch (type) {
248511b41d2SMark Murray 
249511b41d2SMark Murray 		case SSH_SMSG_STDOUT_DATA:
250511b41d2SMark Murray 			data = packet_get_string(&data_len);
251511b41d2SMark Murray 			packet_integrity_check(payload_len, 4 + data_len, type);
252511b41d2SMark Murray 			buffer_append(&stdout_buffer, data, data_len);
253511b41d2SMark Murray 			stdout_bytes += data_len;
254511b41d2SMark Murray 			memset(data, 0, data_len);
255511b41d2SMark Murray 			xfree(data);
256511b41d2SMark Murray 			break;
257511b41d2SMark Murray 
258511b41d2SMark Murray 		case SSH_SMSG_STDERR_DATA:
259511b41d2SMark Murray 			data = packet_get_string(&data_len);
260511b41d2SMark Murray 			packet_integrity_check(payload_len, 4 + data_len, type);
261511b41d2SMark Murray 			buffer_append(&stderr_buffer, data, data_len);
262511b41d2SMark Murray 			stdout_bytes += data_len;
263511b41d2SMark Murray 			memset(data, 0, data_len);
264511b41d2SMark Murray 			xfree(data);
265511b41d2SMark Murray 			break;
266511b41d2SMark Murray 
267511b41d2SMark Murray 		case SSH_SMSG_EXITSTATUS:
268511b41d2SMark Murray 			packet_integrity_check(payload_len, 4, type);
269511b41d2SMark Murray 			exit_status = packet_get_int();
270511b41d2SMark Murray 			/* Acknowledge the exit. */
271511b41d2SMark Murray 			packet_start(SSH_CMSG_EXIT_CONFIRMATION);
272511b41d2SMark Murray 			packet_send();
273511b41d2SMark Murray 			/*
274511b41d2SMark Murray 			 * Must wait for packet to be sent since we are
275511b41d2SMark Murray 			 * exiting the loop.
276511b41d2SMark Murray 			 */
277511b41d2SMark Murray 			packet_write_wait();
278511b41d2SMark Murray 			/* Flag that we want to exit. */
279511b41d2SMark Murray 			quit_pending = 1;
280511b41d2SMark Murray 			break;
281511b41d2SMark Murray 
282511b41d2SMark Murray 		case SSH_SMSG_X11_OPEN:
283511b41d2SMark Murray 			x11_input_open(payload_len);
284511b41d2SMark Murray 			break;
285511b41d2SMark Murray 
286511b41d2SMark Murray 		case SSH_MSG_PORT_OPEN:
287511b41d2SMark Murray 			channel_input_port_open(payload_len);
288511b41d2SMark Murray 			break;
289511b41d2SMark Murray 
290511b41d2SMark Murray 		case SSH_SMSG_AGENT_OPEN:
291511b41d2SMark Murray 			packet_integrity_check(payload_len, 4, type);
292511b41d2SMark Murray 			auth_input_open_request();
293511b41d2SMark Murray 			break;
294511b41d2SMark Murray 
295511b41d2SMark Murray 		case SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
296511b41d2SMark Murray 			packet_integrity_check(payload_len, 4 + 4, type);
297511b41d2SMark Murray 			channel_input_open_confirmation();
298511b41d2SMark Murray 			break;
299511b41d2SMark Murray 
300511b41d2SMark Murray 		case SSH_MSG_CHANNEL_OPEN_FAILURE:
301511b41d2SMark Murray 			packet_integrity_check(payload_len, 4, type);
302511b41d2SMark Murray 			channel_input_open_failure();
303511b41d2SMark Murray 			break;
304511b41d2SMark Murray 
305511b41d2SMark Murray 		case SSH_MSG_CHANNEL_DATA:
306511b41d2SMark Murray 			channel_input_data(payload_len);
307511b41d2SMark Murray 			break;
308511b41d2SMark Murray 
309511b41d2SMark Murray 		case SSH_MSG_CHANNEL_CLOSE:
310511b41d2SMark Murray 			packet_integrity_check(payload_len, 4, type);
311511b41d2SMark Murray 			channel_input_close();
312511b41d2SMark Murray 			break;
313511b41d2SMark Murray 
314511b41d2SMark Murray 		case SSH_MSG_CHANNEL_CLOSE_CONFIRMATION:
315511b41d2SMark Murray 			packet_integrity_check(payload_len, 4, type);
316511b41d2SMark Murray 			channel_input_close_confirmation();
317511b41d2SMark Murray 			break;
318511b41d2SMark Murray 
319511b41d2SMark Murray 		default:
320511b41d2SMark Murray 			/*
321511b41d2SMark Murray 			 * Any unknown packets received during the actual
322511b41d2SMark Murray 			 * session cause the session to terminate.  This is
323511b41d2SMark Murray 			 * intended to make debugging easier since no
324511b41d2SMark Murray 			 * confirmations are sent.  Any compatible protocol
325511b41d2SMark Murray 			 * extensions must be negotiated during the
326511b41d2SMark Murray 			 * preparatory phase.
327511b41d2SMark Murray 			 */
328511b41d2SMark Murray 			packet_disconnect("Protocol error during session: type %d",
329511b41d2SMark Murray 					  type);
330511b41d2SMark Murray 		}
331511b41d2SMark Murray 	}
332511b41d2SMark Murray }
333511b41d2SMark Murray 
334511b41d2SMark Murray /*
335511b41d2SMark Murray  * Make packets from buffered stdin data, and buffer them for sending to the
336511b41d2SMark Murray  * connection.
337511b41d2SMark Murray  */
338511b41d2SMark Murray 
339511b41d2SMark Murray void
340511b41d2SMark Murray client_make_packets_from_stdin_data()
341511b41d2SMark Murray {
342511b41d2SMark Murray 	unsigned int len;
343511b41d2SMark Murray 
344511b41d2SMark Murray 	/* Send buffered stdin data to the server. */
345511b41d2SMark Murray 	while (buffer_len(&stdin_buffer) > 0 &&
346511b41d2SMark Murray 	       packet_not_very_much_data_to_write()) {
347511b41d2SMark Murray 		len = buffer_len(&stdin_buffer);
348511b41d2SMark Murray 		/* Keep the packets at reasonable size. */
349511b41d2SMark Murray 		if (len > packet_get_maxsize())
350511b41d2SMark Murray 			len = packet_get_maxsize();
351511b41d2SMark Murray 		packet_start(SSH_CMSG_STDIN_DATA);
352511b41d2SMark Murray 		packet_put_string(buffer_ptr(&stdin_buffer), len);
353511b41d2SMark Murray 		packet_send();
354511b41d2SMark Murray 		buffer_consume(&stdin_buffer, len);
355511b41d2SMark Murray 		/* If we have a pending EOF, send it now. */
356511b41d2SMark Murray 		if (stdin_eof && buffer_len(&stdin_buffer) == 0) {
357511b41d2SMark Murray 			packet_start(SSH_CMSG_EOF);
358511b41d2SMark Murray 			packet_send();
359511b41d2SMark Murray 		}
360511b41d2SMark Murray 	}
361511b41d2SMark Murray }
362511b41d2SMark Murray 
363511b41d2SMark Murray /*
364511b41d2SMark Murray  * Checks if the client window has changed, and sends a packet about it to
365511b41d2SMark Murray  * the server if so.  The actual change is detected elsewhere (by a software
366511b41d2SMark Murray  * interrupt on Unix); this just checks the flag and sends a message if
367511b41d2SMark Murray  * appropriate.
368511b41d2SMark Murray  */
369511b41d2SMark Murray 
370511b41d2SMark Murray void
371511b41d2SMark Murray client_check_window_change()
372511b41d2SMark Murray {
373511b41d2SMark Murray 	/* Send possible window change message to the server. */
374511b41d2SMark Murray 	if (received_window_change_signal) {
375511b41d2SMark Murray 		struct winsize ws;
376511b41d2SMark Murray 
377511b41d2SMark Murray 		/* Clear the window change indicator. */
378511b41d2SMark Murray 		received_window_change_signal = 0;
379511b41d2SMark Murray 
380511b41d2SMark Murray 		/* Read new window size. */
381511b41d2SMark Murray 		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) >= 0) {
382511b41d2SMark Murray 			/* Successful, send the packet now. */
383511b41d2SMark Murray 			packet_start(SSH_CMSG_WINDOW_SIZE);
384511b41d2SMark Murray 			packet_put_int(ws.ws_row);
385511b41d2SMark Murray 			packet_put_int(ws.ws_col);
386511b41d2SMark Murray 			packet_put_int(ws.ws_xpixel);
387511b41d2SMark Murray 			packet_put_int(ws.ws_ypixel);
388511b41d2SMark Murray 			packet_send();
389511b41d2SMark Murray 		}
390511b41d2SMark Murray 	}
391511b41d2SMark Murray }
392511b41d2SMark Murray 
393511b41d2SMark Murray /*
394511b41d2SMark Murray  * Waits until the client can do something (some data becomes available on
395511b41d2SMark Murray  * one of the file descriptors).
396511b41d2SMark Murray  */
397511b41d2SMark Murray 
398511b41d2SMark Murray void
399511b41d2SMark Murray client_wait_until_can_do_something(fd_set * readset, fd_set * writeset)
400511b41d2SMark Murray {
401511b41d2SMark Murray 	/* Initialize select masks. */
402511b41d2SMark Murray 	FD_ZERO(readset);
403511b41d2SMark Murray 
404511b41d2SMark Murray 	/* Read from the connection, unless our buffers are full. */
405511b41d2SMark Murray 	if (buffer_len(&stdout_buffer) < buffer_high &&
406511b41d2SMark Murray 	    buffer_len(&stderr_buffer) < buffer_high &&
407511b41d2SMark Murray 	    channel_not_very_much_buffered_data())
408511b41d2SMark Murray 		FD_SET(connection_in, readset);
409511b41d2SMark Murray 
410511b41d2SMark Murray 	/*
411511b41d2SMark Murray 	 * Read from stdin, unless we have seen EOF or have very much
412511b41d2SMark Murray 	 * buffered data to send to the server.
413511b41d2SMark Murray 	 */
414511b41d2SMark Murray 	if (!stdin_eof && packet_not_very_much_data_to_write())
415511b41d2SMark Murray 		FD_SET(fileno(stdin), readset);
416511b41d2SMark Murray 
417511b41d2SMark Murray 	FD_ZERO(writeset);
418511b41d2SMark Murray 
419511b41d2SMark Murray 	/* Add any selections by the channel mechanism. */
420511b41d2SMark Murray 	channel_prepare_select(readset, writeset);
421511b41d2SMark Murray 
422511b41d2SMark Murray 	/* Select server connection if have data to write to the server. */
423511b41d2SMark Murray 	if (packet_have_data_to_write())
424511b41d2SMark Murray 		FD_SET(connection_out, writeset);
425511b41d2SMark Murray 
426511b41d2SMark Murray 	/* Select stdout if have data in buffer. */
427511b41d2SMark Murray 	if (buffer_len(&stdout_buffer) > 0)
428511b41d2SMark Murray 		FD_SET(fileno(stdout), writeset);
429511b41d2SMark Murray 
430511b41d2SMark Murray 	/* Select stderr if have data in buffer. */
431511b41d2SMark Murray 	if (buffer_len(&stderr_buffer) > 0)
432511b41d2SMark Murray 		FD_SET(fileno(stderr), writeset);
433511b41d2SMark Murray 
434511b41d2SMark Murray 	/* Update maximum file descriptor number, if appropriate. */
435511b41d2SMark Murray 	if (channel_max_fd() > max_fd)
436511b41d2SMark Murray 		max_fd = channel_max_fd();
437511b41d2SMark Murray 
438511b41d2SMark Murray 	/*
439511b41d2SMark Murray 	 * Wait for something to happen.  This will suspend the process until
440511b41d2SMark Murray 	 * some selected descriptor can be read, written, or has some other
441511b41d2SMark Murray 	 * event pending. Note: if you want to implement SSH_MSG_IGNORE
442511b41d2SMark Murray 	 * messages to fool traffic analysis, this might be the place to do
443511b41d2SMark Murray 	 * it: just have a random timeout for the select, and send a random
444511b41d2SMark Murray 	 * SSH_MSG_IGNORE packet when the timeout expires.
445511b41d2SMark Murray 	 */
446511b41d2SMark Murray 
447511b41d2SMark Murray 	if (select(max_fd + 1, readset, writeset, NULL, NULL) < 0) {
448511b41d2SMark Murray 		char buf[100];
449511b41d2SMark Murray 		/* Some systems fail to clear these automatically. */
450511b41d2SMark Murray 		FD_ZERO(readset);
451511b41d2SMark Murray 		FD_ZERO(writeset);
452511b41d2SMark Murray 		if (errno == EINTR)
453511b41d2SMark Murray 			return;
454511b41d2SMark Murray 		/* Note: we might still have data in the buffers. */
455511b41d2SMark Murray 		snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno));
456511b41d2SMark Murray 		buffer_append(&stderr_buffer, buf, strlen(buf));
457511b41d2SMark Murray 		stderr_bytes += strlen(buf);
458511b41d2SMark Murray 		quit_pending = 1;
459511b41d2SMark Murray 	}
460511b41d2SMark Murray }
461511b41d2SMark Murray 
462511b41d2SMark Murray void
463511b41d2SMark Murray client_suspend_self()
464511b41d2SMark Murray {
465511b41d2SMark Murray 	struct winsize oldws, newws;
466511b41d2SMark Murray 
467511b41d2SMark Murray 	/* Flush stdout and stderr buffers. */
468511b41d2SMark Murray 	if (buffer_len(&stdout_buffer) > 0)
469511b41d2SMark Murray 		atomicio(write, fileno(stdout), buffer_ptr(&stdout_buffer),
470511b41d2SMark Murray 		    buffer_len(&stdout_buffer));
471511b41d2SMark Murray 	if (buffer_len(&stderr_buffer) > 0)
472511b41d2SMark Murray 		atomicio(write, fileno(stderr), buffer_ptr(&stderr_buffer),
473511b41d2SMark Murray 		    buffer_len(&stderr_buffer));
474511b41d2SMark Murray 
475511b41d2SMark Murray 	leave_raw_mode();
476511b41d2SMark Murray 
477511b41d2SMark Murray 	/*
478511b41d2SMark Murray 	 * Free (and clear) the buffer to reduce the amount of data that gets
479511b41d2SMark Murray 	 * written to swap.
480511b41d2SMark Murray 	 */
481511b41d2SMark Murray 	buffer_free(&stdin_buffer);
482511b41d2SMark Murray 	buffer_free(&stdout_buffer);
483511b41d2SMark Murray 	buffer_free(&stderr_buffer);
484511b41d2SMark Murray 
485511b41d2SMark Murray 	/* Save old window size. */
486511b41d2SMark Murray 	ioctl(fileno(stdin), TIOCGWINSZ, &oldws);
487511b41d2SMark Murray 
488511b41d2SMark Murray 	/* Send the suspend signal to the program itself. */
489511b41d2SMark Murray 	kill(getpid(), SIGTSTP);
490511b41d2SMark Murray 
491511b41d2SMark Murray 	/* Check if the window size has changed. */
492511b41d2SMark Murray 	if (ioctl(fileno(stdin), TIOCGWINSZ, &newws) >= 0 &&
493511b41d2SMark Murray 	    (oldws.ws_row != newws.ws_row ||
494511b41d2SMark Murray 	     oldws.ws_col != newws.ws_col ||
495511b41d2SMark Murray 	     oldws.ws_xpixel != newws.ws_xpixel ||
496511b41d2SMark Murray 	     oldws.ws_ypixel != newws.ws_ypixel))
497511b41d2SMark Murray 		received_window_change_signal = 1;
498511b41d2SMark Murray 
499511b41d2SMark Murray 	/* OK, we have been continued by the user. Reinitialize buffers. */
500511b41d2SMark Murray 	buffer_init(&stdin_buffer);
501511b41d2SMark Murray 	buffer_init(&stdout_buffer);
502511b41d2SMark Murray 	buffer_init(&stderr_buffer);
503511b41d2SMark Murray 
504511b41d2SMark Murray 	enter_raw_mode();
505511b41d2SMark Murray }
506511b41d2SMark Murray 
507511b41d2SMark Murray void
508511b41d2SMark Murray client_process_input(fd_set * readset)
509511b41d2SMark Murray {
510511b41d2SMark Murray 	int len, pid;
511511b41d2SMark Murray 	char buf[8192], *s;
512511b41d2SMark Murray 
513511b41d2SMark Murray 	/*
514511b41d2SMark Murray 	 * Read input from the server, and add any such data to the buffer of
515511b41d2SMark Murray 	 * the packet subsystem.
516511b41d2SMark Murray 	 */
517511b41d2SMark Murray 	if (FD_ISSET(connection_in, readset)) {
518511b41d2SMark Murray 		/* Read as much as possible. */
519511b41d2SMark Murray 		len = read(connection_in, buf, sizeof(buf));
520511b41d2SMark Murray 		if (len == 0) {
521511b41d2SMark Murray 			/* Received EOF.  The remote host has closed the connection. */
522511b41d2SMark Murray 			snprintf(buf, sizeof buf, "Connection to %.300s closed by remote host.\r\n",
523511b41d2SMark Murray 				 host);
524511b41d2SMark Murray 			buffer_append(&stderr_buffer, buf, strlen(buf));
525511b41d2SMark Murray 			stderr_bytes += strlen(buf);
526511b41d2SMark Murray 			quit_pending = 1;
527511b41d2SMark Murray 			return;
528511b41d2SMark Murray 		}
529511b41d2SMark Murray 		/*
530511b41d2SMark Murray 		 * There is a kernel bug on Solaris that causes select to
531511b41d2SMark Murray 		 * sometimes wake up even though there is no data available.
532511b41d2SMark Murray 		 */
533511b41d2SMark Murray 		if (len < 0 && errno == EAGAIN)
534511b41d2SMark Murray 			len = 0;
535511b41d2SMark Murray 
536511b41d2SMark Murray 		if (len < 0) {
537511b41d2SMark Murray 			/* An error has encountered.  Perhaps there is a network problem. */
538511b41d2SMark Murray 			snprintf(buf, sizeof buf, "Read from remote host %.300s: %.100s\r\n",
539511b41d2SMark Murray 				 host, strerror(errno));
540511b41d2SMark Murray 			buffer_append(&stderr_buffer, buf, strlen(buf));
541511b41d2SMark Murray 			stderr_bytes += strlen(buf);
542511b41d2SMark Murray 			quit_pending = 1;
543511b41d2SMark Murray 			return;
544511b41d2SMark Murray 		}
545511b41d2SMark Murray 		packet_process_incoming(buf, len);
546511b41d2SMark Murray 	}
547511b41d2SMark Murray 	/* Read input from stdin. */
548511b41d2SMark Murray 	if (FD_ISSET(fileno(stdin), readset)) {
549511b41d2SMark Murray 		/* Read as much as possible. */
550511b41d2SMark Murray 		len = read(fileno(stdin), buf, sizeof(buf));
551511b41d2SMark Murray 		if (len <= 0) {
552511b41d2SMark Murray 			/*
553511b41d2SMark Murray 			 * Received EOF or error.  They are treated
554511b41d2SMark Murray 			 * similarly, except that an error message is printed
555511b41d2SMark Murray 			 * if it was an error condition.
556511b41d2SMark Murray 			 */
557511b41d2SMark Murray 			if (len < 0) {
558511b41d2SMark Murray 				snprintf(buf, sizeof buf, "read: %.100s\r\n", strerror(errno));
559511b41d2SMark Murray 				buffer_append(&stderr_buffer, buf, strlen(buf));
560511b41d2SMark Murray 				stderr_bytes += strlen(buf);
561511b41d2SMark Murray 			}
562511b41d2SMark Murray 			/* Mark that we have seen EOF. */
563511b41d2SMark Murray 			stdin_eof = 1;
564511b41d2SMark Murray 			/*
565511b41d2SMark Murray 			 * Send an EOF message to the server unless there is
566511b41d2SMark Murray 			 * data in the buffer.  If there is data in the
567511b41d2SMark Murray 			 * buffer, no message will be sent now.  Code
568511b41d2SMark Murray 			 * elsewhere will send the EOF when the buffer
569511b41d2SMark Murray 			 * becomes empty if stdin_eof is set.
570511b41d2SMark Murray 			 */
571511b41d2SMark Murray 			if (buffer_len(&stdin_buffer) == 0) {
572511b41d2SMark Murray 				packet_start(SSH_CMSG_EOF);
573511b41d2SMark Murray 				packet_send();
574511b41d2SMark Murray 			}
575511b41d2SMark Murray 		} else if (escape_char == -1) {
576511b41d2SMark Murray 			/*
577511b41d2SMark Murray 			 * Normal successful read, and no escape character.
578511b41d2SMark Murray 			 * Just append the data to buffer.
579511b41d2SMark Murray 			 */
580511b41d2SMark Murray 			buffer_append(&stdin_buffer, buf, len);
581511b41d2SMark Murray 			stdin_bytes += len;
582511b41d2SMark Murray 		} else {
583511b41d2SMark Murray 			/*
584511b41d2SMark Murray 			 * Normal, successful read.  But we have an escape character
585511b41d2SMark Murray 			 * and have to process the characters one by one.
586511b41d2SMark Murray 			 */
587511b41d2SMark Murray 			unsigned int i;
588511b41d2SMark Murray 			for (i = 0; i < len; i++) {
589511b41d2SMark Murray 				unsigned char ch;
590511b41d2SMark Murray 				/* Get one character at a time. */
591511b41d2SMark Murray 				ch = buf[i];
592511b41d2SMark Murray 
593511b41d2SMark Murray 				if (escape_pending) {
594511b41d2SMark Murray 					/* We have previously seen an escape character. */
595511b41d2SMark Murray 					/* Clear the flag now. */
596511b41d2SMark Murray 					escape_pending = 0;
597511b41d2SMark Murray 					/* Process the escaped character. */
598511b41d2SMark Murray 					switch (ch) {
599511b41d2SMark Murray 					case '.':
600511b41d2SMark Murray 						/* Terminate the connection. */
601511b41d2SMark Murray 						snprintf(buf, sizeof buf, "%c.\r\n", escape_char);
602511b41d2SMark Murray 						buffer_append(&stderr_buffer, buf, strlen(buf));
603511b41d2SMark Murray 						stderr_bytes += strlen(buf);
604511b41d2SMark Murray 						quit_pending = 1;
605511b41d2SMark Murray 						return;
606511b41d2SMark Murray 
607511b41d2SMark Murray 					case 'Z' - 64:
608511b41d2SMark Murray 						/* Suspend the program. */
609511b41d2SMark Murray 						/* Print a message to that effect to the user. */
610511b41d2SMark Murray 						snprintf(buf, sizeof buf, "%c^Z\r\n", escape_char);
611511b41d2SMark Murray 						buffer_append(&stderr_buffer, buf, strlen(buf));
612511b41d2SMark Murray 						stderr_bytes += strlen(buf);
613511b41d2SMark Murray 
614511b41d2SMark Murray 						/* Restore terminal modes and suspend. */
615511b41d2SMark Murray 						client_suspend_self();
616511b41d2SMark Murray 
617511b41d2SMark Murray 						/* We have been continued. */
618511b41d2SMark Murray 						continue;
619511b41d2SMark Murray 
620511b41d2SMark Murray 					case '&':
621511b41d2SMark Murray 						/*
622511b41d2SMark Murray 						 * Detach the program (continue to serve connections,
623511b41d2SMark Murray 						 * but put in background and no more new connections).
624511b41d2SMark Murray 						 */
625511b41d2SMark Murray 						if (!stdin_eof) {
626511b41d2SMark Murray 							/*
627511b41d2SMark Murray 							 * Sending SSH_CMSG_EOF alone does not always appear
628511b41d2SMark Murray 							 * to be enough.  So we try to send an EOF character
629511b41d2SMark Murray 							 * first.
630511b41d2SMark Murray 							 */
631511b41d2SMark Murray 							packet_start(SSH_CMSG_STDIN_DATA);
632511b41d2SMark Murray 							packet_put_string("\004", 1);
633511b41d2SMark Murray 							packet_send();
634511b41d2SMark Murray 							/* Close stdin. */
635511b41d2SMark Murray 							stdin_eof = 1;
636511b41d2SMark Murray 							if (buffer_len(&stdin_buffer) == 0) {
637511b41d2SMark Murray 								packet_start(SSH_CMSG_EOF);
638511b41d2SMark Murray 								packet_send();
639511b41d2SMark Murray 							}
640511b41d2SMark Murray 						}
641511b41d2SMark Murray 						/* Restore tty modes. */
642511b41d2SMark Murray 						leave_raw_mode();
643511b41d2SMark Murray 
644511b41d2SMark Murray 						/* Stop listening for new connections. */
645511b41d2SMark Murray 						channel_stop_listening();
646511b41d2SMark Murray 
647511b41d2SMark Murray 						printf("%c& [backgrounded]\n", escape_char);
648511b41d2SMark Murray 
649511b41d2SMark Murray 						/* Fork into background. */
650511b41d2SMark Murray 						pid = fork();
651511b41d2SMark Murray 						if (pid < 0) {
652511b41d2SMark Murray 							error("fork: %.100s", strerror(errno));
653511b41d2SMark Murray 							continue;
654511b41d2SMark Murray 						}
655511b41d2SMark Murray 						if (pid != 0) {	/* This is the parent. */
656511b41d2SMark Murray 							/* The parent just exits. */
657511b41d2SMark Murray 							exit(0);
658511b41d2SMark Murray 						}
659511b41d2SMark Murray 						/* The child continues serving connections. */
660511b41d2SMark Murray 						continue;
661511b41d2SMark Murray 
662511b41d2SMark Murray 					case '?':
663511b41d2SMark Murray 						snprintf(buf, sizeof buf,
664511b41d2SMark Murray "%c?\r\n\
665511b41d2SMark Murray Supported escape sequences:\r\n\
666511b41d2SMark Murray ~.  - terminate connection\r\n\
667511b41d2SMark Murray ~^Z - suspend ssh\r\n\
668511b41d2SMark Murray ~#  - list forwarded connections\r\n\
669511b41d2SMark Murray ~&  - background ssh (when waiting for connections to terminate)\r\n\
670511b41d2SMark Murray ~?  - this message\r\n\
671511b41d2SMark Murray ~~  - send the escape character by typing it twice\r\n\
672511b41d2SMark Murray (Note that escapes are only recognized immediately after newline.)\r\n",
673511b41d2SMark Murray 							 escape_char);
674511b41d2SMark Murray 						buffer_append(&stderr_buffer, buf, strlen(buf));
675511b41d2SMark Murray 						continue;
676511b41d2SMark Murray 
677511b41d2SMark Murray 					case '#':
678511b41d2SMark Murray 						snprintf(buf, sizeof buf, "%c#\r\n", escape_char);
679511b41d2SMark Murray 						buffer_append(&stderr_buffer, buf, strlen(buf));
680511b41d2SMark Murray 						s = channel_open_message();
681511b41d2SMark Murray 						buffer_append(&stderr_buffer, s, strlen(s));
682511b41d2SMark Murray 						xfree(s);
683511b41d2SMark Murray 						continue;
684511b41d2SMark Murray 
685511b41d2SMark Murray 					default:
686511b41d2SMark Murray 						if (ch != escape_char) {
687511b41d2SMark Murray 							/*
688511b41d2SMark Murray 							 * Escape character followed by non-special character.
689511b41d2SMark Murray 							 * Append both to the input buffer.
690511b41d2SMark Murray 							 */
691511b41d2SMark Murray 							buf[0] = escape_char;
692511b41d2SMark Murray 							buf[1] = ch;
693511b41d2SMark Murray 							buffer_append(&stdin_buffer, buf, 2);
694511b41d2SMark Murray 							stdin_bytes += 2;
695511b41d2SMark Murray 							continue;
696511b41d2SMark Murray 						}
697511b41d2SMark Murray 						/*
698511b41d2SMark Murray 						 * Note that escape character typed twice
699511b41d2SMark Murray 						 * falls through here; the latter gets processed
700511b41d2SMark Murray 						 * as a normal character below.
701511b41d2SMark Murray 						 */
702511b41d2SMark Murray 						break;
703511b41d2SMark Murray 					}
704511b41d2SMark Murray 				} else {
705511b41d2SMark Murray 					/*
706511b41d2SMark Murray 					 * The previous character was not an escape char. Check if this
707511b41d2SMark Murray 					 * is an escape.
708511b41d2SMark Murray 					 */
709511b41d2SMark Murray 					if (last_was_cr && ch == escape_char) {
710511b41d2SMark Murray 						/* It is. Set the flag and continue to next character. */
711511b41d2SMark Murray 						escape_pending = 1;
712511b41d2SMark Murray 						continue;
713511b41d2SMark Murray 					}
714511b41d2SMark Murray 				}
715511b41d2SMark Murray 
716511b41d2SMark Murray 				/*
717511b41d2SMark Murray 				 * Normal character.  Record whether it was a newline,
718511b41d2SMark Murray 				 * and append it to the buffer.
719511b41d2SMark Murray 				 */
720511b41d2SMark Murray 				last_was_cr = (ch == '\r' || ch == '\n');
721511b41d2SMark Murray 				buf[0] = ch;
722511b41d2SMark Murray 				buffer_append(&stdin_buffer, buf, 1);
723511b41d2SMark Murray 				stdin_bytes += 1;
724511b41d2SMark Murray 				continue;
725511b41d2SMark Murray 			}
726511b41d2SMark Murray 		}
727511b41d2SMark Murray 	}
728511b41d2SMark Murray }
729511b41d2SMark Murray 
730511b41d2SMark Murray void
731511b41d2SMark Murray client_process_output(fd_set * writeset)
732511b41d2SMark Murray {
733511b41d2SMark Murray 	int len;
734511b41d2SMark Murray 	char buf[100];
735511b41d2SMark Murray 
736511b41d2SMark Murray 	/* Write buffered output to stdout. */
737511b41d2SMark Murray 	if (FD_ISSET(fileno(stdout), writeset)) {
738511b41d2SMark Murray 		/* Write as much data as possible. */
739511b41d2SMark Murray 		len = write(fileno(stdout), buffer_ptr(&stdout_buffer),
740511b41d2SMark Murray 		    buffer_len(&stdout_buffer));
741511b41d2SMark Murray 		if (len <= 0) {
742511b41d2SMark Murray 			if (errno == EAGAIN)
743511b41d2SMark Murray 				len = 0;
744511b41d2SMark Murray 			else {
745511b41d2SMark Murray 				/*
746511b41d2SMark Murray 				 * An error or EOF was encountered.  Put an
747511b41d2SMark Murray 				 * error message to stderr buffer.
748511b41d2SMark Murray 				 */
749511b41d2SMark Murray 				snprintf(buf, sizeof buf, "write stdout: %.50s\r\n", strerror(errno));
750511b41d2SMark Murray 				buffer_append(&stderr_buffer, buf, strlen(buf));
751511b41d2SMark Murray 				stderr_bytes += strlen(buf);
752511b41d2SMark Murray 				quit_pending = 1;
753511b41d2SMark Murray 				return;
754511b41d2SMark Murray 			}
755511b41d2SMark Murray 		}
756511b41d2SMark Murray 		/* Consume printed data from the buffer. */
757511b41d2SMark Murray 		buffer_consume(&stdout_buffer, len);
758511b41d2SMark Murray 	}
759511b41d2SMark Murray 	/* Write buffered output to stderr. */
760511b41d2SMark Murray 	if (FD_ISSET(fileno(stderr), writeset)) {
761511b41d2SMark Murray 		/* Write as much data as possible. */
762511b41d2SMark Murray 		len = write(fileno(stderr), buffer_ptr(&stderr_buffer),
763511b41d2SMark Murray 		    buffer_len(&stderr_buffer));
764511b41d2SMark Murray 		if (len <= 0) {
765511b41d2SMark Murray 			if (errno == EAGAIN)
766511b41d2SMark Murray 				len = 0;
767511b41d2SMark Murray 			else {
768511b41d2SMark Murray 				/* EOF or error, but can't even print error message. */
769511b41d2SMark Murray 				quit_pending = 1;
770511b41d2SMark Murray 				return;
771511b41d2SMark Murray 			}
772511b41d2SMark Murray 		}
773511b41d2SMark Murray 		/* Consume printed characters from the buffer. */
774511b41d2SMark Murray 		buffer_consume(&stderr_buffer, len);
775511b41d2SMark Murray 	}
776511b41d2SMark Murray }
777511b41d2SMark Murray 
778511b41d2SMark Murray /*
779511b41d2SMark Murray  * Implements the interactive session with the server.  This is called after
780511b41d2SMark Murray  * the user has been authenticated, and a command has been started on the
781511b41d2SMark Murray  * remote host.  If escape_char != -1, it is the character used as an escape
782511b41d2SMark Murray  * character for terminating or suspending the session.
783511b41d2SMark Murray  */
784511b41d2SMark Murray 
785511b41d2SMark Murray int
786511b41d2SMark Murray client_loop(int have_pty, int escape_char_arg)
787511b41d2SMark Murray {
788511b41d2SMark Murray 	extern Options options;
789511b41d2SMark Murray 	double start_time, total_time;
790511b41d2SMark Murray 	int len;
791511b41d2SMark Murray 	char buf[100];
792511b41d2SMark Murray 
793511b41d2SMark Murray 	debug("Entering interactive session.");
794511b41d2SMark Murray 
795511b41d2SMark Murray 	start_time = get_current_time();
796511b41d2SMark Murray 
797511b41d2SMark Murray 	/* Initialize variables. */
798511b41d2SMark Murray 	escape_pending = 0;
799511b41d2SMark Murray 	last_was_cr = 1;
800511b41d2SMark Murray 	exit_status = -1;
801511b41d2SMark Murray 	stdin_eof = 0;
802511b41d2SMark Murray 	buffer_high = 64 * 1024;
803511b41d2SMark Murray 	connection_in = packet_get_connection_in();
804511b41d2SMark Murray 	connection_out = packet_get_connection_out();
805511b41d2SMark Murray 	max_fd = connection_in;
806511b41d2SMark Murray 	if (connection_out > max_fd)
807511b41d2SMark Murray 		max_fd = connection_out;
808511b41d2SMark Murray 	stdin_bytes = 0;
809511b41d2SMark Murray 	stdout_bytes = 0;
810511b41d2SMark Murray 	stderr_bytes = 0;
811511b41d2SMark Murray 	quit_pending = 0;
812511b41d2SMark Murray 	escape_char = escape_char_arg;
813511b41d2SMark Murray 
814511b41d2SMark Murray 	/* Initialize buffers. */
815511b41d2SMark Murray 	buffer_init(&stdin_buffer);
816511b41d2SMark Murray 	buffer_init(&stdout_buffer);
817511b41d2SMark Murray 	buffer_init(&stderr_buffer);
818511b41d2SMark Murray 
819511b41d2SMark Murray 	/* Set signal handlers to restore non-blocking mode.  */
820511b41d2SMark Murray 	signal(SIGINT, signal_handler);
821511b41d2SMark Murray 	signal(SIGQUIT, signal_handler);
822511b41d2SMark Murray 	signal(SIGTERM, signal_handler);
823511b41d2SMark Murray 	signal(SIGPIPE, SIG_IGN);
824511b41d2SMark Murray 	if (have_pty)
825511b41d2SMark Murray 		signal(SIGWINCH, window_change_handler);
826511b41d2SMark Murray 
827511b41d2SMark Murray 	if (have_pty)
828511b41d2SMark Murray 		enter_raw_mode();
829511b41d2SMark Murray 
830511b41d2SMark Murray 	/* Check if we should immediately send of on stdin. */
831511b41d2SMark Murray 	client_check_initial_eof_on_stdin();
832511b41d2SMark Murray 
833511b41d2SMark Murray 	/* Main loop of the client for the interactive session mode. */
834511b41d2SMark Murray 	while (!quit_pending) {
835511b41d2SMark Murray 		fd_set readset, writeset;
836511b41d2SMark Murray 
837511b41d2SMark Murray 		/* Process buffered packets sent by the server. */
838511b41d2SMark Murray 		client_process_buffered_input_packets();
839511b41d2SMark Murray 
840511b41d2SMark Murray 		/*
841511b41d2SMark Murray 		 * Make packets of buffered stdin data, and buffer them for
842511b41d2SMark Murray 		 * sending to the server.
843511b41d2SMark Murray 		 */
844511b41d2SMark Murray 		client_make_packets_from_stdin_data();
845511b41d2SMark Murray 
846511b41d2SMark Murray 		/*
847511b41d2SMark Murray 		 * Make packets from buffered channel data, and buffer them
848511b41d2SMark Murray 		 * for sending to the server.
849511b41d2SMark Murray 		 */
850511b41d2SMark Murray 		if (packet_not_very_much_data_to_write())
851511b41d2SMark Murray 			channel_output_poll();
852511b41d2SMark Murray 
853511b41d2SMark Murray 		/*
854511b41d2SMark Murray 		 * Check if the window size has changed, and buffer a message
855511b41d2SMark Murray 		 * about it to the server if so.
856511b41d2SMark Murray 		 */
857511b41d2SMark Murray 		client_check_window_change();
858511b41d2SMark Murray 
859511b41d2SMark Murray 		if (quit_pending)
860511b41d2SMark Murray 			break;
861511b41d2SMark Murray 
862511b41d2SMark Murray 		/*
863511b41d2SMark Murray 		 * Wait until we have something to do (something becomes
864511b41d2SMark Murray 		 * available on one of the descriptors).
865511b41d2SMark Murray 		 */
866511b41d2SMark Murray 		client_wait_until_can_do_something(&readset, &writeset);
867511b41d2SMark Murray 
868511b41d2SMark Murray 		if (quit_pending)
869511b41d2SMark Murray 			break;
870511b41d2SMark Murray 
871511b41d2SMark Murray 		/* Do channel operations. */
872511b41d2SMark Murray 		channel_after_select(&readset, &writeset);
873511b41d2SMark Murray 
874511b41d2SMark Murray 		/*
875511b41d2SMark Murray 		 * Process input from the connection and from stdin. Buffer
876511b41d2SMark Murray 		 * any data that is available.
877511b41d2SMark Murray 		 */
878511b41d2SMark Murray 		client_process_input(&readset);
879511b41d2SMark Murray 
880511b41d2SMark Murray 		/*
881511b41d2SMark Murray 		 * Process output to stdout and stderr.   Output to the
882511b41d2SMark Murray 		 * connection is processed elsewhere (above).
883511b41d2SMark Murray 		 */
884511b41d2SMark Murray 		client_process_output(&writeset);
885511b41d2SMark Murray 
886511b41d2SMark Murray 		/* Send as much buffered packet data as possible to the sender. */
887511b41d2SMark Murray 		if (FD_ISSET(connection_out, &writeset))
888511b41d2SMark Murray 			packet_write_poll();
889511b41d2SMark Murray 	}
890511b41d2SMark Murray 
891511b41d2SMark Murray 	/* Terminate the session. */
892511b41d2SMark Murray 
893511b41d2SMark Murray 	/* Stop watching for window change. */
894511b41d2SMark Murray 	if (have_pty)
895511b41d2SMark Murray 		signal(SIGWINCH, SIG_DFL);
896511b41d2SMark Murray 
897511b41d2SMark Murray 	/* Stop listening for connections. */
898511b41d2SMark Murray 	channel_stop_listening();
899511b41d2SMark Murray 
900511b41d2SMark Murray 	/*
901511b41d2SMark Murray 	 * In interactive mode (with pseudo tty) display a message indicating
902511b41d2SMark Murray 	 * that the connection has been closed.
903511b41d2SMark Murray 	 */
904511b41d2SMark Murray 	if (have_pty && options.log_level != SYSLOG_LEVEL_QUIET) {
905511b41d2SMark Murray 		snprintf(buf, sizeof buf, "Connection to %.64s closed.\r\n", host);
906511b41d2SMark Murray 		buffer_append(&stderr_buffer, buf, strlen(buf));
907511b41d2SMark Murray 		stderr_bytes += strlen(buf);
908511b41d2SMark Murray 	}
909511b41d2SMark Murray 	/* Output any buffered data for stdout. */
910511b41d2SMark Murray 	while (buffer_len(&stdout_buffer) > 0) {
911511b41d2SMark Murray 		len = write(fileno(stdout), buffer_ptr(&stdout_buffer),
912511b41d2SMark Murray 		    buffer_len(&stdout_buffer));
913511b41d2SMark Murray 		if (len <= 0) {
914511b41d2SMark Murray 			error("Write failed flushing stdout buffer.");
915511b41d2SMark Murray 			break;
916511b41d2SMark Murray 		}
917511b41d2SMark Murray 		buffer_consume(&stdout_buffer, len);
918511b41d2SMark Murray 	}
919511b41d2SMark Murray 
920511b41d2SMark Murray 	/* Output any buffered data for stderr. */
921511b41d2SMark Murray 	while (buffer_len(&stderr_buffer) > 0) {
922511b41d2SMark Murray 		len = write(fileno(stderr), buffer_ptr(&stderr_buffer),
923511b41d2SMark Murray 		    buffer_len(&stderr_buffer));
924511b41d2SMark Murray 		if (len <= 0) {
925511b41d2SMark Murray 			error("Write failed flushing stderr buffer.");
926511b41d2SMark Murray 			break;
927511b41d2SMark Murray 		}
928511b41d2SMark Murray 		buffer_consume(&stderr_buffer, len);
929511b41d2SMark Murray 	}
930511b41d2SMark Murray 
931511b41d2SMark Murray 	if (have_pty)
932511b41d2SMark Murray 		leave_raw_mode();
933511b41d2SMark Murray 
934511b41d2SMark Murray 	/* Clear and free any buffers. */
935511b41d2SMark Murray 	memset(buf, 0, sizeof(buf));
936511b41d2SMark Murray 	buffer_free(&stdin_buffer);
937511b41d2SMark Murray 	buffer_free(&stdout_buffer);
938511b41d2SMark Murray 	buffer_free(&stderr_buffer);
939511b41d2SMark Murray 
940511b41d2SMark Murray 	/* Report bytes transferred, and transfer rates. */
941511b41d2SMark Murray 	total_time = get_current_time() - start_time;
942511b41d2SMark Murray 	debug("Transferred: stdin %lu, stdout %lu, stderr %lu bytes in %.1f seconds",
943511b41d2SMark Murray 	      stdin_bytes, stdout_bytes, stderr_bytes, total_time);
944511b41d2SMark Murray 	if (total_time > 0)
945511b41d2SMark Murray 		debug("Bytes per second: stdin %.1f, stdout %.1f, stderr %.1f",
946511b41d2SMark Murray 		      stdin_bytes / total_time, stdout_bytes / total_time,
947511b41d2SMark Murray 		      stderr_bytes / total_time);
948511b41d2SMark Murray 
949511b41d2SMark Murray 	/* Return the exit status of the program. */
950511b41d2SMark Murray 	debug("Exit status %d", exit_status);
951511b41d2SMark Murray 	return exit_status;
952511b41d2SMark Murray }
953