/src/dropbear/src/cli-chansession.c
Line | Count | Source (jump to first uncovered line) |
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 | | #include "agentfwd.h" |
37 | | |
38 | | static void cli_closechansess(const 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(const struct Channel *channel); |
42 | | static void send_chansess_shell_req(const struct Channel *channel); |
43 | | static void cli_escape_handler(const struct Channel *channel, const unsigned char* buf, int *len); |
44 | | static int cli_init_netcat(struct Channel *channel); |
45 | | |
46 | | static void cli_tty_setup(void); |
47 | | |
48 | | const struct ChanType clichansess = { |
49 | | "session", /* name */ |
50 | | cli_initchansess, /* inithandler */ |
51 | | NULL, /* checkclosehandler */ |
52 | | cli_chansessreq, /* reqhandler */ |
53 | | cli_closechansess, /* closehandler */ |
54 | | NULL, /* cleanup */ |
55 | | }; |
56 | | |
57 | 0 | static void cli_chansessreq(struct Channel *channel) { |
58 | |
|
59 | 0 | char* type = NULL; |
60 | 0 | int wantreply; |
61 | |
|
62 | 0 | TRACE(("enter cli_chansessreq")) |
63 | |
|
64 | 0 | type = buf_getstring(ses.payload, NULL); |
65 | 0 | wantreply = buf_getbool(ses.payload); |
66 | |
|
67 | 0 | if (strcmp(type, "exit-status") == 0) { |
68 | 0 | cli_ses.retval = buf_getint(ses.payload); |
69 | 0 | TRACE(("got exit-status of '%d'", cli_ses.retval)) |
70 | 0 | } else if (strcmp(type, "exit-signal") == 0) { |
71 | 0 | TRACE(("got exit-signal, ignoring it")) |
72 | 0 | } else { |
73 | 0 | TRACE(("unknown request '%s'", type)) |
74 | 0 | if (wantreply) { |
75 | 0 | send_msg_channel_failure(channel); |
76 | 0 | } |
77 | 0 | goto out; |
78 | 0 | } |
79 | | |
80 | 0 | out: |
81 | 0 | m_free(type); |
82 | 0 | } |
83 | | |
84 | | |
85 | | /* If the main session goes, we close it up */ |
86 | 0 | static void cli_closechansess(const struct Channel *UNUSED(channel)) { |
87 | 0 | cli_tty_cleanup(); /* Restore tty modes etc */ |
88 | | |
89 | | /* This channel hasn't gone yet, so we have > 1 */ |
90 | 0 | if (ses.chancount > 1) { |
91 | 0 | dropbear_log(LOG_INFO, "Waiting for other channels to close..."); |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | /* Taken from OpenSSH's sshtty.c: |
96 | | * RCSID("OpenBSD: sshtty.c,v 1.5 2003/09/19 17:43:35 markus Exp "); */ |
97 | 0 | static void cli_tty_setup() { |
98 | |
|
99 | 0 | struct termios tio; |
100 | |
|
101 | 0 | TRACE(("enter cli_pty_setup")) |
102 | |
|
103 | 0 | if (cli_ses.tty_raw_mode == 1) { |
104 | 0 | TRACE(("leave cli_tty_setup: already in raw mode!")) |
105 | 0 | return; |
106 | 0 | } |
107 | | |
108 | 0 | if (tcgetattr(STDIN_FILENO, &tio) == -1) { |
109 | 0 | dropbear_exit("Failed to set raw TTY mode"); |
110 | 0 | } |
111 | | |
112 | | /* make a copy */ |
113 | 0 | cli_ses.saved_tio = tio; |
114 | |
|
115 | 0 | tio.c_iflag |= IGNPAR; |
116 | 0 | tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); |
117 | 0 | #ifdef IUCLC |
118 | 0 | tio.c_iflag &= ~IUCLC; |
119 | 0 | #endif |
120 | 0 | tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); |
121 | 0 | #ifdef IEXTEN |
122 | 0 | tio.c_lflag &= ~IEXTEN; |
123 | 0 | #endif |
124 | 0 | tio.c_oflag &= ~OPOST; |
125 | 0 | tio.c_cc[VMIN] = 1; |
126 | 0 | tio.c_cc[VTIME] = 0; |
127 | 0 | if (tcsetattr(STDIN_FILENO, TCSADRAIN, &tio) == -1) { |
128 | 0 | dropbear_exit("Failed to set raw TTY mode"); |
129 | 0 | } |
130 | | |
131 | 0 | cli_ses.tty_raw_mode = 1; |
132 | 0 | TRACE(("leave cli_tty_setup")) |
133 | 0 | } |
134 | | |
135 | 2.93k | void cli_tty_cleanup() { |
136 | | |
137 | 2.93k | TRACE(("enter cli_tty_cleanup")) |
138 | | |
139 | 2.93k | if (cli_ses.tty_raw_mode == 0) { |
140 | 2.93k | TRACE(("leave cli_tty_cleanup: not in raw mode")) |
141 | 2.93k | return; |
142 | 2.93k | } |
143 | | |
144 | 0 | if (tcsetattr(STDIN_FILENO, TCSADRAIN, &cli_ses.saved_tio) == -1) { |
145 | 0 | dropbear_log(LOG_WARNING, "Failed restoring TTY"); |
146 | 0 | } else { |
147 | 0 | cli_ses.tty_raw_mode = 0; |
148 | 0 | } |
149 | |
|
150 | 0 | TRACE(("leave cli_tty_cleanup")) |
151 | 0 | } |
152 | | |
153 | 0 | static void put_termcodes() { |
154 | |
|
155 | 0 | struct termios tio; |
156 | 0 | unsigned int sshcode; |
157 | 0 | const struct TermCode *termcode; |
158 | 0 | unsigned int value; |
159 | 0 | unsigned int mapcode; |
160 | |
|
161 | 0 | unsigned int bufpos1, bufpos2; |
162 | |
|
163 | 0 | TRACE(("enter put_termcodes")) |
164 | |
|
165 | 0 | if (tcgetattr(STDIN_FILENO, &tio) == -1) { |
166 | 0 | dropbear_log(LOG_WARNING, "Failed reading termmodes"); |
167 | 0 | buf_putint(ses.writepayload, 1); /* Just the terminator */ |
168 | 0 | buf_putbyte(ses.writepayload, 0); /* TTY_OP_END */ |
169 | 0 | return; |
170 | 0 | } |
171 | | |
172 | 0 | bufpos1 = ses.writepayload->pos; |
173 | 0 | buf_putint(ses.writepayload, 0); /* A placeholder for the final length */ |
174 | | |
175 | | /* As with Dropbear server, we ignore baud rates for now */ |
176 | 0 | for (sshcode = 1; sshcode < MAX_TERMCODE; sshcode++) { |
177 | |
|
178 | 0 | termcode = &termcodes[sshcode]; |
179 | 0 | mapcode = termcode->mapcode; |
180 | |
|
181 | 0 | switch (termcode->type) { |
182 | | |
183 | 0 | case TERMCODE_NONE: |
184 | 0 | continue; |
185 | | |
186 | 0 | case TERMCODE_CONTROLCHAR: |
187 | 0 | value = tio.c_cc[mapcode]; |
188 | 0 | break; |
189 | | |
190 | 0 | case TERMCODE_INPUT: |
191 | 0 | value = tio.c_iflag & mapcode; |
192 | 0 | break; |
193 | | |
194 | 0 | case TERMCODE_OUTPUT: |
195 | 0 | value = tio.c_oflag & mapcode; |
196 | 0 | break; |
197 | | |
198 | 0 | case TERMCODE_LOCAL: |
199 | 0 | value = tio.c_lflag & mapcode; |
200 | 0 | break; |
201 | | |
202 | 0 | case TERMCODE_CONTROL: |
203 | 0 | value = tio.c_cflag & mapcode; |
204 | 0 | break; |
205 | | |
206 | 0 | default: |
207 | 0 | continue; |
208 | |
|
209 | 0 | } |
210 | | |
211 | | /* If we reach here, we have something to say */ |
212 | 0 | buf_putbyte(ses.writepayload, sshcode); |
213 | 0 | buf_putint(ses.writepayload, value); |
214 | 0 | } |
215 | | |
216 | 0 | buf_putbyte(ses.writepayload, 0); /* THE END, aka TTY_OP_END */ |
217 | | |
218 | | /* Put the string length at the start of the buffer */ |
219 | 0 | bufpos2 = ses.writepayload->pos; |
220 | |
|
221 | 0 | buf_setpos(ses.writepayload, bufpos1); /* Jump back */ |
222 | 0 | buf_putint(ses.writepayload, bufpos2 - bufpos1 - 4); /* len(termcodes) */ |
223 | 0 | buf_setpos(ses.writepayload, bufpos2); /* Back where we were */ |
224 | |
|
225 | 0 | TRACE(("leave put_termcodes")) |
226 | 0 | } |
227 | | |
228 | 0 | static void put_winsize() { |
229 | |
|
230 | 0 | struct winsize ws; |
231 | |
|
232 | 0 | if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) { |
233 | | /* Some sane defaults */ |
234 | 0 | ws.ws_row = 25; |
235 | 0 | ws.ws_col = 80; |
236 | 0 | ws.ws_xpixel = 0; |
237 | 0 | ws.ws_ypixel = 0; |
238 | 0 | } |
239 | |
|
240 | 0 | buf_putint(ses.writepayload, ws.ws_col); /* Cols */ |
241 | 0 | buf_putint(ses.writepayload, ws.ws_row); /* Rows */ |
242 | 0 | buf_putint(ses.writepayload, ws.ws_xpixel); /* Width */ |
243 | 0 | buf_putint(ses.writepayload, ws.ws_ypixel); /* Height */ |
244 | |
|
245 | 0 | } |
246 | | |
247 | 0 | static void sigwinch_handler(int UNUSED(unused)) { |
248 | |
|
249 | 0 | cli_ses.winchange = 1; |
250 | |
|
251 | 0 | } |
252 | | |
253 | 0 | void cli_chansess_winchange() { |
254 | |
|
255 | 0 | unsigned int i; |
256 | 0 | struct Channel *channel = NULL; |
257 | |
|
258 | 0 | for (i = 0; i < ses.chansize; i++) { |
259 | 0 | channel = ses.channels[i]; |
260 | 0 | if (channel != NULL && channel->type == &clichansess) { |
261 | 0 | CHECKCLEARTOWRITE(); |
262 | 0 | buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST); |
263 | 0 | buf_putint(ses.writepayload, channel->remotechan); |
264 | 0 | buf_putstring(ses.writepayload, "window-change", 13); |
265 | 0 | buf_putbyte(ses.writepayload, 0); /* FALSE says the spec */ |
266 | 0 | put_winsize(); |
267 | 0 | encrypt_packet(); |
268 | 0 | } |
269 | 0 | } |
270 | 0 | cli_ses.winchange = 0; |
271 | 0 | } |
272 | | |
273 | 0 | static void send_chansess_pty_req(const struct Channel *channel) { |
274 | |
|
275 | 0 | char* term = NULL; |
276 | |
|
277 | 0 | TRACE(("enter send_chansess_pty_req")) |
278 | |
|
279 | 0 | start_send_channel_request(channel, "pty-req"); |
280 | | |
281 | | /* Don't want replies */ |
282 | 0 | buf_putbyte(ses.writepayload, 0); |
283 | | |
284 | | /* Get the terminal */ |
285 | 0 | term = getenv("TERM"); |
286 | 0 | if (term == NULL) { |
287 | 0 | term = "vt100"; /* Seems a safe default */ |
288 | 0 | } |
289 | 0 | buf_putstring(ses.writepayload, term, strlen(term)); |
290 | | |
291 | | /* Window size */ |
292 | 0 | put_winsize(); |
293 | | |
294 | | /* Terminal mode encoding */ |
295 | 0 | put_termcodes(); |
296 | |
|
297 | 0 | encrypt_packet(); |
298 | | |
299 | | /* Set up a window-change handler */ |
300 | 0 | if (signal(SIGWINCH, sigwinch_handler) == SIG_ERR) { |
301 | 0 | dropbear_exit("Signal error"); |
302 | 0 | } |
303 | 0 | TRACE(("leave send_chansess_pty_req")) |
304 | 0 | } |
305 | | |
306 | 0 | static void send_chansess_shell_req(const struct Channel *channel) { |
307 | |
|
308 | 0 | char* reqtype = NULL; |
309 | |
|
310 | 0 | TRACE(("enter send_chansess_shell_req")) |
311 | |
|
312 | 0 | if (cli_opts.cmd) { |
313 | 0 | if (cli_opts.is_subsystem) { |
314 | 0 | reqtype = "subsystem"; |
315 | 0 | } else { |
316 | 0 | reqtype = "exec"; |
317 | 0 | } |
318 | 0 | } else { |
319 | 0 | reqtype = "shell"; |
320 | 0 | } |
321 | |
|
322 | 0 | start_send_channel_request(channel, reqtype); |
323 | | |
324 | | /* XXX TODO */ |
325 | 0 | buf_putbyte(ses.writepayload, 0); /* Don't want replies */ |
326 | 0 | if (cli_opts.cmd) { |
327 | 0 | buf_putstring(ses.writepayload, cli_opts.cmd, strlen(cli_opts.cmd)); |
328 | 0 | } |
329 | |
|
330 | 0 | encrypt_packet(); |
331 | 0 | TRACE(("leave send_chansess_shell_req")) |
332 | 0 | } |
333 | | |
334 | | /* Shared for normal client channel and netcat-alike */ |
335 | 0 | static int cli_init_stdpipe_sess(struct Channel *channel) { |
336 | 0 | channel->writefd = STDOUT_FILENO; |
337 | 0 | setnonblocking(STDOUT_FILENO); |
338 | |
|
339 | 0 | channel->readfd = STDIN_FILENO; |
340 | 0 | setnonblocking(STDIN_FILENO); |
341 | |
|
342 | 0 | channel->errfd = STDERR_FILENO; |
343 | 0 | setnonblocking(STDERR_FILENO); |
344 | |
|
345 | 0 | channel->extrabuf = cbuf_new(opts.recv_window); |
346 | 0 | channel->bidir_fd = 0; |
347 | 0 | return 0; |
348 | 0 | } |
349 | | |
350 | 0 | static int cli_init_netcat(struct Channel *channel) { |
351 | 0 | return cli_init_stdpipe_sess(channel); |
352 | 0 | } |
353 | | |
354 | 0 | static int cli_initchansess(struct Channel *channel) { |
355 | |
|
356 | 0 | cli_init_stdpipe_sess(channel); |
357 | |
|
358 | 0 | #if DROPBEAR_CLI_AGENTFWD |
359 | 0 | if (cli_opts.agent_fwd) { |
360 | 0 | cli_setup_agent(channel); |
361 | 0 | } |
362 | 0 | #endif |
363 | 0 | if (cli_opts.wantpty) { |
364 | 0 | send_chansess_pty_req(channel); |
365 | 0 | channel->prio = DROPBEAR_PRIO_LOWDELAY; |
366 | 0 | } |
367 | |
|
368 | 0 | send_chansess_shell_req(channel); |
369 | |
|
370 | 0 | if (cli_opts.wantpty) { |
371 | 0 | cli_tty_setup(); |
372 | 0 | channel->read_mangler = cli_escape_handler; |
373 | 0 | cli_ses.last_char = '\r'; |
374 | 0 | } |
375 | |
|
376 | 0 | return 0; /* Success */ |
377 | 0 | } |
378 | | |
379 | | #if DROPBEAR_CLI_NETCAT |
380 | | |
381 | | static const struct ChanType cli_chan_netcat = { |
382 | | "direct-tcpip", |
383 | | cli_init_netcat, /* inithandler */ |
384 | | NULL, |
385 | | NULL, |
386 | | cli_closechansess, |
387 | | NULL, |
388 | | }; |
389 | | |
390 | 0 | void cli_send_netcat_request() { |
391 | |
|
392 | 0 | const char* source_host = "127.0.0.1"; |
393 | 0 | const int source_port = 22; |
394 | |
|
395 | 0 | TRACE(("enter cli_send_netcat_request")) |
396 | 0 | cli_opts.wantpty = 0; |
397 | |
|
398 | 0 | if (send_msg_channel_open_init(STDIN_FILENO, &cli_chan_netcat) |
399 | 0 | == DROPBEAR_FAILURE) { |
400 | 0 | dropbear_exit("Couldn't open initial channel"); |
401 | 0 | } |
402 | | |
403 | 0 | buf_putstring(ses.writepayload, cli_opts.netcat_host, |
404 | 0 | strlen(cli_opts.netcat_host)); |
405 | 0 | buf_putint(ses.writepayload, cli_opts.netcat_port); |
406 | | |
407 | | /* originator ip - localhost is accurate enough */ |
408 | 0 | buf_putstring(ses.writepayload, source_host, strlen(source_host)); |
409 | 0 | buf_putint(ses.writepayload, source_port); |
410 | |
|
411 | 0 | encrypt_packet(); |
412 | 0 | TRACE(("leave cli_send_netcat_request")) |
413 | 0 | } |
414 | | #endif |
415 | | |
416 | 0 | void cli_send_chansess_request() { |
417 | |
|
418 | 0 | TRACE(("enter cli_send_chansess_request")) |
419 | |
|
420 | 0 | if (send_msg_channel_open_init(STDIN_FILENO, &clichansess) |
421 | 0 | == DROPBEAR_FAILURE) { |
422 | 0 | dropbear_exit("Couldn't open initial channel"); |
423 | 0 | } |
424 | | |
425 | | /* No special channel request data */ |
426 | 0 | encrypt_packet(); |
427 | 0 | TRACE(("leave cli_send_chansess_request")) |
428 | |
|
429 | 0 | } |
430 | | |
431 | | /* returns 1 if the character should be consumed, 0 to pass through */ |
432 | | static int |
433 | 0 | do_escape(unsigned char c) { |
434 | 0 | switch (c) { |
435 | 0 | case '.': |
436 | 0 | dropbear_exit("Terminated"); |
437 | 0 | return 1; |
438 | 0 | case 0x1a: |
439 | | /* ctrl-z */ |
440 | 0 | cli_tty_cleanup(); |
441 | 0 | kill(getpid(), SIGTSTP); |
442 | | /* after continuation */ |
443 | 0 | cli_tty_setup(); |
444 | 0 | cli_ses.winchange = 1; |
445 | 0 | return 1; |
446 | 0 | default: |
447 | 0 | return 0; |
448 | 0 | } |
449 | 0 | } |
450 | | |
451 | | static |
452 | 0 | void cli_escape_handler(const struct Channel* UNUSED(channel), const unsigned char* buf, int *len) { |
453 | 0 | char c; |
454 | 0 | int skip_char = 0; |
455 | | |
456 | | /* only handle escape characters if they are read one at a time. simplifies |
457 | | the code and avoids nasty people putting ~. at the start of a line to paste */ |
458 | 0 | if (*len != 1) { |
459 | 0 | cli_ses.last_char = 0x0; |
460 | 0 | return; |
461 | 0 | } |
462 | | |
463 | 0 | c = buf[0]; |
464 | |
|
465 | 0 | if (cli_ses.last_char == DROPBEAR_ESCAPE_CHAR) { |
466 | 0 | skip_char = do_escape(c); |
467 | 0 | cli_ses.last_char = 0x0; |
468 | 0 | } else { |
469 | 0 | if (c == DROPBEAR_ESCAPE_CHAR) { |
470 | 0 | if (cli_ses.last_char == '\r') { |
471 | 0 | cli_ses.last_char = DROPBEAR_ESCAPE_CHAR; |
472 | 0 | skip_char = 1; |
473 | 0 | } else { |
474 | 0 | cli_ses.last_char = 0x0; |
475 | 0 | } |
476 | 0 | } else { |
477 | 0 | cli_ses.last_char = c; |
478 | 0 | } |
479 | 0 | } |
480 | |
|
481 | 0 | if (skip_char) { |
482 | 0 | *len = 0; |
483 | 0 | } |
484 | 0 | } |