dropbear: update to 2015.67
[tomato.git] / release / src-rt-6.x.4708 / router / dropbear / cli-chansession.c
blob57457d23621f962bbe9bca41bdd29ffa4bbb5da9
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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. */
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 #include "agentfwd.h"
38 static void cli_closechansess(struct Channel *channel);
39 static int cli_initchansess(struct Channel *channel);
40 static void cli_chansessreq(struct Channel *channel);
41 static void send_chansess_pty_req(struct Channel *channel);
42 static void send_chansess_shell_req(struct Channel *channel);
43 static void cli_escape_handler(struct Channel *channel, unsigned char* buf, int *len);
44 static int cli_init_netcat(struct Channel *channel);
46 static void cli_tty_setup();
48 const struct ChanType clichansess = {
49 0, /* sepfds */
50 "session", /* name */
51 cli_initchansess, /* inithandler */
52 NULL, /* checkclosehandler */
53 cli_chansessreq, /* reqhandler */
54 cli_closechansess, /* closehandler */
57 static void cli_chansessreq(struct Channel *channel) {
59 unsigned char* type = NULL;
60 int wantreply;
62 TRACE(("enter cli_chansessreq"))
64 type = buf_getstring(ses.payload, NULL);
65 wantreply = buf_getbool(ses.payload);
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 if (wantreply) {
75 send_msg_channel_failure(channel);
77 goto out;
80 out:
81 m_free(type);
85 /* If the main session goes, we close it up */
86 static void cli_closechansess(struct Channel *UNUSED(channel)) {
87 cli_tty_cleanup(); /* Restore tty modes etc */
89 /* This channel hasn't gone yet, so we have > 1 */
90 if (ses.chancount > 1) {
91 dropbear_log(LOG_INFO, "Waiting for other channels to close...");
95 /* Taken from OpenSSH's sshtty.c:
96 * RCSID("OpenBSD: sshtty.c,v 1.5 2003/09/19 17:43:35 markus Exp "); */
97 static void cli_tty_setup() {
99 struct termios tio;
101 TRACE(("enter cli_pty_setup"))
103 if (cli_ses.tty_raw_mode == 1) {
104 TRACE(("leave cli_tty_setup: already in raw mode!"))
105 return;
108 if (tcgetattr(STDIN_FILENO, &tio) == -1) {
109 dropbear_exit("Failed to set raw TTY mode");
112 /* make a copy */
113 cli_ses.saved_tio = tio;
115 tio.c_iflag |= IGNPAR;
116 tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
117 #ifdef IUCLC
118 tio.c_iflag &= ~IUCLC;
119 #endif
120 tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
121 #ifdef IEXTEN
122 tio.c_lflag &= ~IEXTEN;
123 #endif
124 tio.c_oflag &= ~OPOST;
125 tio.c_cc[VMIN] = 1;
126 tio.c_cc[VTIME] = 0;
127 if (tcsetattr(STDIN_FILENO, TCSADRAIN, &tio) == -1) {
128 dropbear_exit("Failed to set raw TTY mode");
131 cli_ses.tty_raw_mode = 1;
132 TRACE(("leave cli_tty_setup"))
135 void cli_tty_cleanup() {
137 TRACE(("enter cli_tty_cleanup"))
139 if (cli_ses.tty_raw_mode == 0) {
140 TRACE(("leave cli_tty_cleanup: not in raw mode"))
141 return;
144 if (tcsetattr(STDIN_FILENO, TCSADRAIN, &cli_ses.saved_tio) == -1) {
145 dropbear_log(LOG_WARNING, "Failed restoring TTY");
146 } else {
147 cli_ses.tty_raw_mode = 0;
150 TRACE(("leave cli_tty_cleanup"))
153 static void put_termcodes() {
155 struct termios tio;
156 unsigned int sshcode;
157 const struct TermCode *termcode;
158 unsigned int value;
159 unsigned int mapcode;
161 unsigned int bufpos1, bufpos2;
163 TRACE(("enter put_termcodes"))
165 if (tcgetattr(STDIN_FILENO, &tio) == -1) {
166 dropbear_log(LOG_WARNING, "Failed reading termmodes");
167 buf_putint(ses.writepayload, 1); /* Just the terminator */
168 buf_putbyte(ses.writepayload, 0); /* TTY_OP_END */
169 return;
172 bufpos1 = ses.writepayload->pos;
173 buf_putint(ses.writepayload, 0); /* A placeholder for the final length */
175 /* As with Dropbear server, we ignore baud rates for now */
176 for (sshcode = 1; sshcode < MAX_TERMCODE; sshcode++) {
178 termcode = &termcodes[sshcode];
179 mapcode = termcode->mapcode;
181 switch (termcode->type) {
183 case TERMCODE_NONE:
184 continue;
186 case TERMCODE_CONTROLCHAR:
187 value = tio.c_cc[mapcode];
188 break;
190 case TERMCODE_INPUT:
191 value = tio.c_iflag & mapcode;
192 break;
194 case TERMCODE_OUTPUT:
195 value = tio.c_oflag & mapcode;
196 break;
198 case TERMCODE_LOCAL:
199 value = tio.c_lflag & mapcode;
200 break;
202 case TERMCODE_CONTROL:
203 value = tio.c_cflag & mapcode;
204 break;
206 default:
207 continue;
211 /* If we reach here, we have something to say */
212 buf_putbyte(ses.writepayload, sshcode);
213 buf_putint(ses.writepayload, value);
216 buf_putbyte(ses.writepayload, 0); /* THE END, aka TTY_OP_END */
218 /* Put the string length at the start of the buffer */
219 bufpos2 = ses.writepayload->pos;
221 buf_setpos(ses.writepayload, bufpos1); /* Jump back */
222 buf_putint(ses.writepayload, bufpos2 - bufpos1 - 4); /* len(termcodes) */
223 buf_setpos(ses.writepayload, bufpos2); /* Back where we were */
225 TRACE(("leave put_termcodes"))
228 static void put_winsize() {
230 struct winsize ws;
232 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
233 /* Some sane defaults */
234 ws.ws_row = 25;
235 ws.ws_col = 80;
236 ws.ws_xpixel = 0;
237 ws.ws_ypixel = 0;
240 buf_putint(ses.writepayload, ws.ws_col); /* Cols */
241 buf_putint(ses.writepayload, ws.ws_row); /* Rows */
242 buf_putint(ses.writepayload, ws.ws_xpixel); /* Width */
243 buf_putint(ses.writepayload, ws.ws_ypixel); /* Height */
247 static void sigwinch_handler(int UNUSED(unused)) {
249 cli_ses.winchange = 1;
253 void cli_chansess_winchange() {
255 unsigned int i;
256 struct Channel *channel = NULL;
258 for (i = 0; i < ses.chansize; i++) {
259 channel = ses.channels[i];
260 if (channel != NULL && channel->type == &clichansess) {
261 CHECKCLEARTOWRITE();
262 buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
263 buf_putint(ses.writepayload, channel->remotechan);
264 buf_putstring(ses.writepayload, "window-change", 13);
265 buf_putbyte(ses.writepayload, 0); /* FALSE says the spec */
266 put_winsize();
267 encrypt_packet();
270 cli_ses.winchange = 0;
273 static void send_chansess_pty_req(struct Channel *channel) {
275 unsigned char* term = NULL;
277 TRACE(("enter send_chansess_pty_req"))
279 start_send_channel_request(channel, "pty-req");
281 /* Don't want replies */
282 buf_putbyte(ses.writepayload, 0);
284 /* Get the terminal */
285 term = getenv("TERM");
286 if (term == NULL) {
287 term = "vt100"; /* Seems a safe default */
289 buf_putstring(ses.writepayload, term, strlen(term));
291 /* Window size */
292 put_winsize();
294 /* Terminal mode encoding */
295 put_termcodes();
297 encrypt_packet();
299 /* Set up a window-change handler */
300 if (signal(SIGWINCH, sigwinch_handler) == SIG_ERR) {
301 dropbear_exit("Signal error");
303 TRACE(("leave send_chansess_pty_req"))
306 static void send_chansess_shell_req(struct Channel *channel) {
308 unsigned char* reqtype = NULL;
310 TRACE(("enter send_chansess_shell_req"))
312 if (cli_opts.cmd) {
313 if (cli_opts.is_subsystem) {
314 reqtype = "subsystem";
315 } else {
316 reqtype = "exec";
318 } else {
319 reqtype = "shell";
322 start_send_channel_request(channel, reqtype);
324 /* XXX TODO */
325 buf_putbyte(ses.writepayload, 0); /* Don't want replies */
326 if (cli_opts.cmd) {
327 buf_putstring(ses.writepayload, cli_opts.cmd, strlen(cli_opts.cmd));
330 encrypt_packet();
331 TRACE(("leave send_chansess_shell_req"))
334 /* Shared for normal client channel and netcat-alike */
335 static int cli_init_stdpipe_sess(struct Channel *channel) {
336 channel->writefd = STDOUT_FILENO;
337 setnonblocking(STDOUT_FILENO);
339 channel->readfd = STDIN_FILENO;
340 setnonblocking(STDIN_FILENO);
342 channel->errfd = STDERR_FILENO;
343 setnonblocking(STDERR_FILENO);
345 channel->extrabuf = cbuf_new(opts.recv_window);
346 return 0;
349 static int cli_init_netcat(struct Channel *channel) {
350 channel->prio = DROPBEAR_CHANNEL_PRIO_UNKNOWABLE;
351 return cli_init_stdpipe_sess(channel);
354 static int cli_initchansess(struct Channel *channel) {
356 cli_init_stdpipe_sess(channel);
358 #ifdef ENABLE_CLI_AGENTFWD
359 if (cli_opts.agent_fwd) {
360 cli_setup_agent(channel);
362 #endif
364 if (cli_opts.wantpty) {
365 send_chansess_pty_req(channel);
366 channel->prio = DROPBEAR_CHANNEL_PRIO_INTERACTIVE;
367 } else {
368 channel->prio = DROPBEAR_CHANNEL_PRIO_BULK;
371 send_chansess_shell_req(channel);
373 if (cli_opts.wantpty) {
374 cli_tty_setup();
375 channel->read_mangler = cli_escape_handler;
376 cli_ses.last_char = '\r';
379 return 0; /* Success */
382 #ifdef ENABLE_CLI_NETCAT
384 static const struct ChanType cli_chan_netcat = {
385 0, /* sepfds */
386 "direct-tcpip",
387 cli_init_netcat, /* inithandler */
388 NULL,
389 NULL,
390 cli_closechansess
393 void cli_send_netcat_request() {
395 const unsigned char* source_host = "127.0.0.1";
396 const int source_port = 22;
398 TRACE(("enter cli_send_netcat_request"))
399 cli_opts.wantpty = 0;
401 if (send_msg_channel_open_init(STDIN_FILENO, &cli_chan_netcat)
402 == DROPBEAR_FAILURE) {
403 dropbear_exit("Couldn't open initial channel");
406 buf_putstring(ses.writepayload, cli_opts.netcat_host,
407 strlen(cli_opts.netcat_host));
408 buf_putint(ses.writepayload, cli_opts.netcat_port);
410 /* originator ip - localhost is accurate enough */
411 buf_putstring(ses.writepayload, source_host, strlen(source_host));
412 buf_putint(ses.writepayload, source_port);
414 encrypt_packet();
415 TRACE(("leave cli_send_netcat_request"))
417 #endif
419 void cli_send_chansess_request() {
421 TRACE(("enter cli_send_chansess_request"))
423 if (send_msg_channel_open_init(STDIN_FILENO, &clichansess)
424 == DROPBEAR_FAILURE) {
425 dropbear_exit("Couldn't open initial channel");
428 /* No special channel request data */
429 encrypt_packet();
430 TRACE(("leave cli_send_chansess_request"))
434 /* returns 1 if the character should be consumed, 0 to pass through */
435 static int
436 do_escape(unsigned char c) {
437 switch (c) {
438 case '.':
439 dropbear_exit("Terminated");
440 return 1;
441 break;
442 case 0x1a:
443 /* ctrl-z */
444 cli_tty_cleanup();
445 kill(getpid(), SIGTSTP);
446 /* after continuation */
447 cli_tty_setup();
448 cli_ses.winchange = 1;
449 return 1;
450 break;
452 return 0;
455 static
456 void cli_escape_handler(struct Channel* UNUSED(channel), unsigned char* buf, int *len) {
457 char c;
458 int skip_char = 0;
460 /* only handle escape characters if they are read one at a time. simplifies
461 the code and avoids nasty people putting ~. at the start of a line to paste */
462 if (*len != 1) {
463 cli_ses.last_char = 0x0;
464 return;
467 c = buf[0];
469 if (cli_ses.last_char == DROPBEAR_ESCAPE_CHAR) {
470 skip_char = do_escape(c);
471 cli_ses.last_char = 0x0;
472 } else {
473 if (c == DROPBEAR_ESCAPE_CHAR) {
474 if (cli_ses.last_char == '\r') {
475 cli_ses.last_char = DROPBEAR_ESCAPE_CHAR;
476 skip_char = 1;
477 } else {
478 cli_ses.last_char = 0x0;
480 } else {
481 cli_ses.last_char = c;
485 if (skip_char) {
486 *len = 0;