Home | History | Annotate | Download | only in dropbear
      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 
     37 static void cli_closechansess(struct Channel *channel);
     38 static int cli_initchansess(struct Channel *channel);
     39 static void cli_chansessreq(struct Channel *channel);
     40 
     41 static void start_channel_request(struct Channel *channel, unsigned char *type);
     42 
     43 static void send_chansess_pty_req(struct Channel *channel);
     44 static void send_chansess_shell_req(struct Channel *channel);
     45 
     46 static void cli_tty_setup();
     47 
     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 */
     55 };
     56 
     57 static 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 
     78 out:
     79 	m_free(type);
     80 }
     81 
     82 
     83 /* If the main session goes, we close it up */
     84 static 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 
     95 static 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 "); */
    108 static 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 
    146 void 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 
    164 static 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 
    239 static 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 
    258 static void sigwinch_handler(int UNUSED(unused)) {
    259 
    260 	cli_ses.winchange = 1;
    261 
    262 }
    263 
    264 void 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 
    284 static 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 
    317 static 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 
    341 static 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 
    369 void 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