4 * Copyright (c) 2002,2003 Matt Johnston
5 * Copyright (c) 2004 by Mihnea Stoenescu
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
34 #include "termcodes.h"
35 #include "chansession.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
= {
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
;
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"))
73 TRACE(("unknown request '%s'", type
))
75 send_msg_channel_failure(channel
);
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() {
101 TRACE(("enter cli_pty_setup"))
103 if (cli_ses
.tty_raw_mode
== 1) {
104 TRACE(("leave cli_tty_setup: already in raw mode!"))
108 if (tcgetattr(STDIN_FILENO
, &tio
) == -1) {
109 dropbear_exit("Failed to set raw TTY mode");
113 cli_ses
.saved_tio
= tio
;
115 tio
.c_iflag
|= IGNPAR
;
116 tio
.c_iflag
&= ~(ISTRIP
| INLCR
| IGNCR
| ICRNL
| IXON
| IXANY
| IXOFF
);
118 tio
.c_iflag
&= ~IUCLC
;
120 tio
.c_lflag
&= ~(ISIG
| ICANON
| ECHO
| ECHOE
| ECHOK
| ECHONL
);
122 tio
.c_lflag
&= ~IEXTEN
;
124 tio
.c_oflag
&= ~OPOST
;
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"))
144 if (tcsetattr(STDIN_FILENO
, TCSADRAIN
, &cli_ses
.saved_tio
) == -1) {
145 dropbear_log(LOG_WARNING
, "Failed restoring TTY");
147 cli_ses
.tty_raw_mode
= 0;
150 TRACE(("leave cli_tty_cleanup"))
153 static void put_termcodes() {
156 unsigned int sshcode
;
157 const struct TermCode
*termcode
;
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 */
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
) {
186 case TERMCODE_CONTROLCHAR
:
187 value
= tio
.c_cc
[mapcode
];
191 value
= tio
.c_iflag
& mapcode
;
194 case TERMCODE_OUTPUT
:
195 value
= tio
.c_oflag
& mapcode
;
199 value
= tio
.c_lflag
& mapcode
;
202 case TERMCODE_CONTROL
:
203 value
= tio
.c_cflag
& mapcode
;
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() {
232 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &ws
) < 0) {
233 /* Some sane defaults */
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() {
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
) {
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 */
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");
287 term
= "vt100"; /* Seems a safe default */
289 buf_putstring(ses
.writepayload
, term
, strlen(term
));
294 /* Terminal mode encoding */
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"))
313 if (cli_opts
.is_subsystem
) {
314 reqtype
= "subsystem";
322 start_send_channel_request(channel
, reqtype
);
325 buf_putbyte(ses
.writepayload
, 0); /* Don't want replies */
327 buf_putstring(ses
.writepayload
, cli_opts
.cmd
, strlen(cli_opts
.cmd
));
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
);
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
);
364 if (cli_opts
.wantpty
) {
365 send_chansess_pty_req(channel
);
366 channel
->prio
= DROPBEAR_CHANNEL_PRIO_INTERACTIVE
;
368 channel
->prio
= DROPBEAR_CHANNEL_PRIO_BULK
;
371 send_chansess_shell_req(channel
);
373 if (cli_opts
.wantpty
) {
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
= {
387 cli_init_netcat
, /* inithandler */
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
);
415 TRACE(("leave cli_send_netcat_request"))
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 */
430 TRACE(("leave cli_send_chansess_request"))
434 /* returns 1 if the character should be consumed, 0 to pass through */
436 do_escape(unsigned char c
) {
439 dropbear_exit("Terminated");
445 kill(getpid(), SIGTSTP
);
446 /* after continuation */
448 cli_ses
.winchange
= 1;
456 void cli_escape_handler(struct Channel
* UNUSED(channel
), unsigned char* buf
, int *len
) {
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 */
463 cli_ses
.last_char
= 0x0;
469 if (cli_ses
.last_char
== DROPBEAR_ESCAPE_CHAR
) {
470 skip_char
= do_escape(c
);
471 cli_ses
.last_char
= 0x0;
473 if (c
== DROPBEAR_ESCAPE_CHAR
) {
474 if (cli_ses
.last_char
== '\r') {
475 cli_ses
.last_char
= DROPBEAR_ESCAPE_CHAR
;
478 cli_ses
.last_char
= 0x0;
481 cli_ses
.last_char
= c
;