cli-chansession.c revision f7fc46c63fdc8f39234fea409b8dbe116d73ebf8
1/*
2 * Dropbear SSH
3 *
4 * Copyright (c) 2002,2003 Matt Johnston
5 * Copyright (c) 2004 by Mihnea Stoenescu
6 * All rights reserved.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE. */
25
26#include "includes.h"
27#include "packet.h"
28#include "buffer.h"
29#include "session.h"
30#include "dbutil.h"
31#include "channel.h"
32#include "ssh.h"
33#include "runopts.h"
34#include "termcodes.h"
35#include "chansession.h"
36
37static void cli_closechansess(struct Channel *channel);
38static int cli_initchansess(struct Channel *channel);
39static void cli_chansessreq(struct Channel *channel);
40
41static void start_channel_request(struct Channel *channel, unsigned char *type);
42
43static void send_chansess_pty_req(struct Channel *channel);
44static void send_chansess_shell_req(struct Channel *channel);
45
46static void cli_tty_setup();
47
48const struct ChanType clichansess = {
49	0, /* sepfds */
50	"session", /* name */
51	cli_initchansess, /* inithandler */
52	NULL, /* checkclosehandler */
53	cli_chansessreq, /* reqhandler */
54	cli_closechansess, /* closehandler */
55};
56
57static void cli_chansessreq(struct Channel *channel) {
58
59	unsigned char* type = NULL;
60	int wantreply;
61
62	TRACE(("enter cli_chansessreq"))
63
64	type = buf_getstring(ses.payload, NULL);
65	wantreply = buf_getbool(ses.payload);
66
67	if (strcmp(type, "exit-status") == 0) {
68		cli_ses.retval = buf_getint(ses.payload);
69		TRACE(("got exit-status of '%d'", cli_ses.retval))
70	} else if (strcmp(type, "exit-signal") == 0) {
71		TRACE(("got exit-signal, ignoring it"))
72	} else {
73		TRACE(("unknown request '%s'", type))
74		send_msg_channel_failure(channel);
75		goto out;
76	}
77
78out:
79	m_free(type);
80}
81
82
83/* If the main session goes, we close it up */
84static void cli_closechansess(struct Channel *UNUSED(channel)) {
85
86	/* This channel hasn't gone yet, so we have > 1 */
87	if (ses.chancount > 1) {
88		dropbear_log(LOG_INFO, "Waiting for other channels to close...");
89	}
90
91	cli_tty_cleanup(); /* Restore tty modes etc */
92
93}
94
95static void start_channel_request(struct Channel *channel,
96		unsigned char *type) {
97
98	CHECKCLEARTOWRITE();
99	buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
100	buf_putint(ses.writepayload, channel->remotechan);
101
102	buf_putstring(ses.writepayload, type, strlen(type));
103
104}
105
106/* Taken from OpenSSH's sshtty.c:
107 * RCSID("OpenBSD: sshtty.c,v 1.5 2003/09/19 17:43:35 markus Exp "); */
108static void cli_tty_setup() {
109
110	struct termios tio;
111
112	TRACE(("enter cli_pty_setup"))
113
114	if (cli_ses.tty_raw_mode == 1) {
115		TRACE(("leave cli_tty_setup: already in raw mode!"))
116		return;
117	}
118
119	if (tcgetattr(STDIN_FILENO, &tio) == -1) {
120		dropbear_exit("Failed to set raw TTY mode");
121	}
122
123	/* make a copy */
124	cli_ses.saved_tio = tio;
125
126	tio.c_iflag |= IGNPAR;
127	tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
128#ifdef IUCLC
129	tio.c_iflag &= ~IUCLC;
130#endif
131	tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
132#ifdef IEXTEN
133	tio.c_lflag &= ~IEXTEN;
134#endif
135	tio.c_oflag &= ~OPOST;
136	tio.c_cc[VMIN] = 1;
137	tio.c_cc[VTIME] = 0;
138	if (tcsetattr(STDIN_FILENO, TCSADRAIN, &tio) == -1) {
139		dropbear_exit("Failed to set raw TTY mode");
140	}
141
142	cli_ses.tty_raw_mode = 1;
143	TRACE(("leave cli_tty_setup"))
144}
145
146void cli_tty_cleanup() {
147
148	TRACE(("enter cli_tty_cleanup"))
149
150	if (cli_ses.tty_raw_mode == 0) {
151		TRACE(("leave cli_tty_cleanup: not in raw mode"))
152		return;
153	}
154
155	if (tcsetattr(STDIN_FILENO, TCSADRAIN, &cli_ses.saved_tio) == -1) {
156		dropbear_log(LOG_WARNING, "Failed restoring TTY");
157	} else {
158		cli_ses.tty_raw_mode = 0;
159	}
160
161	TRACE(("leave cli_tty_cleanup"))
162}
163
164static void put_termcodes() {
165
166	struct termios tio;
167	unsigned int sshcode;
168	const struct TermCode *termcode;
169	unsigned int value;
170	unsigned int mapcode;
171
172	unsigned int bufpos1, bufpos2;
173
174	TRACE(("enter put_termcodes"))
175
176	if (tcgetattr(STDIN_FILENO, &tio) == -1) {
177		dropbear_log(LOG_WARNING, "Failed reading termmodes");
178		buf_putint(ses.writepayload, 1); /* Just the terminator */
179		buf_putbyte(ses.writepayload, 0); /* TTY_OP_END */
180		return;
181	}
182
183	bufpos1 = ses.writepayload->pos;
184	buf_putint(ses.writepayload, 0); /* A placeholder for the final length */
185
186	/* As with Dropbear server, we ignore baud rates for now */
187	for (sshcode = 1; sshcode < MAX_TERMCODE; sshcode++) {
188
189		termcode = &termcodes[sshcode];
190		mapcode = termcode->mapcode;
191
192		switch (termcode->type) {
193
194			case TERMCODE_NONE:
195				continue;
196
197			case TERMCODE_CONTROLCHAR:
198				value = tio.c_cc[mapcode];
199				break;
200
201			case TERMCODE_INPUT:
202				value = tio.c_iflag & mapcode;
203				break;
204
205			case TERMCODE_OUTPUT:
206				value = tio.c_oflag & mapcode;
207				break;
208
209			case TERMCODE_LOCAL:
210				value = tio.c_lflag & mapcode;
211				break;
212
213			case TERMCODE_CONTROL:
214				value = tio.c_cflag & mapcode;
215				break;
216
217			default:
218				continue;
219
220		}
221
222		/* If we reach here, we have something to say */
223		buf_putbyte(ses.writepayload, sshcode);
224		buf_putint(ses.writepayload, value);
225	}
226
227	buf_putbyte(ses.writepayload, 0); /* THE END, aka TTY_OP_END */
228
229	/* Put the string length at the start of the buffer */
230	bufpos2 = ses.writepayload->pos;
231
232	buf_setpos(ses.writepayload, bufpos1); /* Jump back */
233	buf_putint(ses.writepayload, bufpos2 - bufpos1 - 4); /* len(termcodes) */
234	buf_setpos(ses.writepayload, bufpos2); /* Back where we were */
235
236	TRACE(("leave put_termcodes"))
237}
238
239static void put_winsize() {
240
241	struct winsize ws;
242
243	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
244		/* Some sane defaults */
245		ws.ws_row = 25;
246		ws.ws_col = 80;
247		ws.ws_xpixel = 0;
248		ws.ws_ypixel = 0;
249	}
250
251	buf_putint(ses.writepayload, ws.ws_col); /* Cols */
252	buf_putint(ses.writepayload, ws.ws_row); /* Rows */
253	buf_putint(ses.writepayload, ws.ws_xpixel); /* Width */
254	buf_putint(ses.writepayload, ws.ws_ypixel); /* Height */
255
256}
257
258static void sigwinch_handler(int UNUSED(unused)) {
259
260	cli_ses.winchange = 1;
261
262}
263
264void cli_chansess_winchange() {
265
266	unsigned int i;
267	struct Channel *channel = NULL;
268
269	for (i = 0; i < ses.chansize; i++) {
270		channel = ses.channels[i];
271		if (channel != NULL && channel->type == &clichansess) {
272			CHECKCLEARTOWRITE();
273			buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
274			buf_putint(ses.writepayload, channel->remotechan);
275			buf_putstring(ses.writepayload, "window-change", 13);
276			buf_putbyte(ses.writepayload, 0); /* FALSE says the spec */
277			put_winsize();
278			encrypt_packet();
279		}
280	}
281	cli_ses.winchange = 0;
282}
283
284static void send_chansess_pty_req(struct Channel *channel) {
285
286	unsigned char* term = NULL;
287
288	TRACE(("enter send_chansess_pty_req"))
289
290	start_channel_request(channel, "pty-req");
291
292	/* Don't want replies */
293	buf_putbyte(ses.writepayload, 0);
294
295	/* Get the terminal */
296	term = getenv("TERM");
297	if (term == NULL) {
298		term = "vt100"; /* Seems a safe default */
299	}
300	buf_putstring(ses.writepayload, term, strlen(term));
301
302	/* Window size */
303	put_winsize();
304
305	/* Terminal mode encoding */
306	put_termcodes();
307
308	encrypt_packet();
309
310	/* Set up a window-change handler */
311	if (signal(SIGWINCH, sigwinch_handler) == SIG_ERR) {
312		dropbear_exit("signal error");
313	}
314	TRACE(("leave send_chansess_pty_req"))
315}
316
317static void send_chansess_shell_req(struct Channel *channel) {
318
319	unsigned char* reqtype = NULL;
320
321	TRACE(("enter send_chansess_shell_req"))
322
323	if (cli_opts.cmd) {
324		reqtype = "exec";
325	} else {
326		reqtype = "shell";
327	}
328
329	start_channel_request(channel, reqtype);
330
331	/* XXX TODO */
332	buf_putbyte(ses.writepayload, 0); /* Don't want replies */
333	if (cli_opts.cmd) {
334		buf_putstring(ses.writepayload, cli_opts.cmd, strlen(cli_opts.cmd));
335	}
336
337	encrypt_packet();
338	TRACE(("leave send_chansess_shell_req"))
339}
340
341static int cli_initchansess(struct Channel *channel) {
342
343
344	channel->writefd = STDOUT_FILENO;
345	setnonblocking(STDOUT_FILENO);
346
347	channel->readfd = STDIN_FILENO;
348	setnonblocking(STDIN_FILENO);
349
350	channel->errfd = STDERR_FILENO;
351	setnonblocking(STDERR_FILENO);
352
353	channel->extrabuf = cbuf_new(RECV_MAXWINDOW);
354
355	if (cli_opts.wantpty) {
356		send_chansess_pty_req(channel);
357	}
358
359	send_chansess_shell_req(channel);
360
361	if (cli_opts.wantpty) {
362		cli_tty_setup();
363	}
364
365	return 0; /* Success */
366
367}
368
369void cli_send_chansess_request() {
370
371	TRACE(("enter cli_send_chansess_request"))
372	if (send_msg_channel_open_init(STDIN_FILENO, &clichansess)
373			== DROPBEAR_FAILURE) {
374		dropbear_exit("Couldn't open initial channel");
375	}
376
377	/* No special channel request data */
378	encrypt_packet();
379	TRACE(("leave cli_send_chansess_request"))
380
381}
382