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
);
42 static void send_chansess_pty_req(struct Channel
*channel
);
43 static void send_chansess_shell_req(struct Channel
*channel
);
45 static void cli_tty_setup();
47 const struct ChanType clichansess
= {
50 cli_initchansess
, /* inithandler */
51 NULL
, /* checkclosehandler */
52 cli_chansessreq
, /* reqhandler */
53 cli_closechansess
, /* closehandler */
56 static void cli_chansessreq(struct Channel
*channel
) {
58 unsigned char* type
= NULL
;
61 TRACE(("enter cli_chansessreq"))
63 type
= buf_getstring(ses
.payload
, NULL
);
64 wantreply
= buf_getbool(ses
.payload
);
66 if (strcmp(type
, "exit-status") == 0) {
67 cli_ses
.retval
= buf_getint(ses
.payload
);
68 TRACE(("got exit-status of '%d'", cli_ses
.retval
))
69 } else if (strcmp(type
, "exit-signal") == 0) {
70 TRACE(("got exit-signal, ignoring it"))
72 TRACE(("unknown request '%s'", type
))
73 send_msg_channel_failure(channel
);
82 /* If the main session goes, we close it up */
83 static void cli_closechansess(struct Channel
*UNUSED(channel
)) {
85 /* This channel hasn't gone yet, so we have > 1 */
86 if (ses
.chancount
> 1) {
87 dropbear_log(LOG_INFO
, "Waiting for other channels to close...");
90 cli_tty_cleanup(); /* Restore tty modes etc */
94 void cli_start_send_channel_request(struct Channel
*channel
,
95 unsigned char *type
) {
98 buf_putbyte(ses
.writepayload
, SSH_MSG_CHANNEL_REQUEST
);
99 buf_putint(ses
.writepayload
, channel
->remotechan
);
101 buf_putstring(ses
.writepayload
, type
, strlen(type
));
105 /* Taken from OpenSSH's sshtty.c:
106 * RCSID("OpenBSD: sshtty.c,v 1.5 2003/09/19 17:43:35 markus Exp "); */
107 static void cli_tty_setup() {
111 TRACE(("enter cli_pty_setup"))
113 if (cli_ses
.tty_raw_mode
== 1) {
114 TRACE(("leave cli_tty_setup: already in raw mode!"))
118 if (tcgetattr(STDIN_FILENO
, &tio
) == -1) {
119 dropbear_exit("Failed to set raw TTY mode");
123 cli_ses
.saved_tio
= tio
;
125 tio
.c_iflag
|= IGNPAR
;
126 tio
.c_iflag
&= ~(ISTRIP
| INLCR
| IGNCR
| ICRNL
| IXON
| IXANY
| IXOFF
);
128 tio
.c_iflag
&= ~IUCLC
;
130 tio
.c_lflag
&= ~(ISIG
| ICANON
| ECHO
| ECHOE
| ECHOK
| ECHONL
);
132 tio
.c_lflag
&= ~IEXTEN
;
134 tio
.c_oflag
&= ~OPOST
;
137 if (tcsetattr(STDIN_FILENO
, TCSADRAIN
, &tio
) == -1) {
138 dropbear_exit("Failed to set raw TTY mode");
141 cli_ses
.tty_raw_mode
= 1;
142 TRACE(("leave cli_tty_setup"))
145 void cli_tty_cleanup() {
147 TRACE(("enter cli_tty_cleanup"))
149 if (cli_ses
.tty_raw_mode
== 0) {
150 TRACE(("leave cli_tty_cleanup: not in raw mode"))
154 if (tcsetattr(STDIN_FILENO
, TCSADRAIN
, &cli_ses
.saved_tio
) == -1) {
155 dropbear_log(LOG_WARNING
, "Failed restoring TTY");
157 cli_ses
.tty_raw_mode
= 0;
160 TRACE(("leave cli_tty_cleanup"))
163 static void put_termcodes() {
166 unsigned int sshcode
;
167 const struct TermCode
*termcode
;
169 unsigned int mapcode
;
171 unsigned int bufpos1
, bufpos2
;
173 TRACE(("enter put_termcodes"))
175 if (tcgetattr(STDIN_FILENO
, &tio
) == -1) {
176 dropbear_log(LOG_WARNING
, "Failed reading termmodes");
177 buf_putint(ses
.writepayload
, 1); /* Just the terminator */
178 buf_putbyte(ses
.writepayload
, 0); /* TTY_OP_END */
182 bufpos1
= ses
.writepayload
->pos
;
183 buf_putint(ses
.writepayload
, 0); /* A placeholder for the final length */
185 /* As with Dropbear server, we ignore baud rates for now */
186 for (sshcode
= 1; sshcode
< MAX_TERMCODE
; sshcode
++) {
188 termcode
= &termcodes
[sshcode
];
189 mapcode
= termcode
->mapcode
;
191 switch (termcode
->type
) {
196 case TERMCODE_CONTROLCHAR
:
197 value
= tio
.c_cc
[mapcode
];
201 value
= tio
.c_iflag
& mapcode
;
204 case TERMCODE_OUTPUT
:
205 value
= tio
.c_oflag
& mapcode
;
209 value
= tio
.c_lflag
& mapcode
;
212 case TERMCODE_CONTROL
:
213 value
= tio
.c_cflag
& mapcode
;
221 /* If we reach here, we have something to say */
222 buf_putbyte(ses
.writepayload
, sshcode
);
223 buf_putint(ses
.writepayload
, value
);
226 buf_putbyte(ses
.writepayload
, 0); /* THE END, aka TTY_OP_END */
228 /* Put the string length at the start of the buffer */
229 bufpos2
= ses
.writepayload
->pos
;
231 buf_setpos(ses
.writepayload
, bufpos1
); /* Jump back */
232 buf_putint(ses
.writepayload
, bufpos2
- bufpos1
- 4); /* len(termcodes) */
233 buf_setpos(ses
.writepayload
, bufpos2
); /* Back where we were */
235 TRACE(("leave put_termcodes"))
238 static void put_winsize() {
242 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &ws
) < 0) {
243 /* Some sane defaults */
250 buf_putint(ses
.writepayload
, ws
.ws_col
); /* Cols */
251 buf_putint(ses
.writepayload
, ws
.ws_row
); /* Rows */
252 buf_putint(ses
.writepayload
, ws
.ws_xpixel
); /* Width */
253 buf_putint(ses
.writepayload
, ws
.ws_ypixel
); /* Height */
257 static void sigwinch_handler(int UNUSED(unused
)) {
259 cli_ses
.winchange
= 1;
263 void cli_chansess_winchange() {
266 struct Channel
*channel
= NULL
;
268 for (i
= 0; i
< ses
.chansize
; i
++) {
269 channel
= ses
.channels
[i
];
270 if (channel
!= NULL
&& channel
->type
== &clichansess
) {
272 buf_putbyte(ses
.writepayload
, SSH_MSG_CHANNEL_REQUEST
);
273 buf_putint(ses
.writepayload
, channel
->remotechan
);
274 buf_putstring(ses
.writepayload
, "window-change", 13);
275 buf_putbyte(ses
.writepayload
, 0); /* FALSE says the spec */
280 cli_ses
.winchange
= 0;
283 static void send_chansess_pty_req(struct Channel
*channel
) {
285 unsigned char* term
= NULL
;
287 TRACE(("enter send_chansess_pty_req"))
289 cli_start_send_channel_request(channel
, "pty-req");
291 /* Don't want replies */
292 buf_putbyte(ses
.writepayload
, 0);
294 /* Get the terminal */
295 term
= getenv("TERM");
297 term
= "vt100"; /* Seems a safe default */
299 buf_putstring(ses
.writepayload
, term
, strlen(term
));
304 /* Terminal mode encoding */
309 /* Set up a window-change handler */
310 if (signal(SIGWINCH
, sigwinch_handler
) == SIG_ERR
) {
311 dropbear_exit("Signal error");
313 TRACE(("leave send_chansess_pty_req"))
316 static void send_chansess_shell_req(struct Channel
*channel
) {
318 unsigned char* reqtype
= NULL
;
320 TRACE(("enter send_chansess_shell_req"))
323 if (cli_opts
.is_subsystem
) {
324 reqtype
= "subsystem";
332 cli_start_send_channel_request(channel
, reqtype
);
335 buf_putbyte(ses
.writepayload
, 0); /* Don't want replies */
337 buf_putstring(ses
.writepayload
, cli_opts
.cmd
, strlen(cli_opts
.cmd
));
341 TRACE(("leave send_chansess_shell_req"))
344 /* Shared for normal client channel and netcat-alike */
345 static int cli_init_stdpipe_sess(struct Channel
*channel
) {
346 channel
->writefd
= STDOUT_FILENO
;
347 setnonblocking(STDOUT_FILENO
);
349 channel
->readfd
= STDIN_FILENO
;
350 setnonblocking(STDIN_FILENO
);
352 channel
->errfd
= STDERR_FILENO
;
353 setnonblocking(STDERR_FILENO
);
355 channel
->extrabuf
= cbuf_new(opts
.recv_window
);
359 static int cli_initchansess(struct Channel
*channel
) {
361 cli_init_stdpipe_sess(channel
);
363 #ifdef ENABLE_CLI_AGENTFWD
364 if (cli_opts
.agent_fwd
) {
365 cli_setup_agent(channel
);
369 if (cli_opts
.wantpty
) {
370 send_chansess_pty_req(channel
);
373 send_chansess_shell_req(channel
);
375 if (cli_opts
.wantpty
) {
379 return 0; /* Success */
382 #ifdef ENABLE_CLI_NETCAT
384 static const struct ChanType cli_chan_netcat
= {
387 cli_init_stdpipe_sess
, /* inithandler */
393 void cli_send_netcat_request() {
395 const unsigned char* source_host
= "127.0.0.1";
396 const int source_port
= 22;
398 cli_opts
.wantpty
= 0;
400 if (send_msg_channel_open_init(STDIN_FILENO
, &cli_chan_netcat
)
401 == DROPBEAR_FAILURE
) {
402 dropbear_exit("Couldn't open initial channel");
405 buf_putstring(ses
.writepayload
, cli_opts
.netcat_host
,
406 strlen(cli_opts
.netcat_host
));
407 buf_putint(ses
.writepayload
, cli_opts
.netcat_port
);
409 /* originator ip - localhost is accurate enough */
410 buf_putstring(ses
.writepayload
, source_host
, strlen(source_host
));
411 buf_putint(ses
.writepayload
, source_port
);
414 TRACE(("leave cli_send_chansess_request"))
418 void cli_send_chansess_request() {
420 TRACE(("enter cli_send_chansess_request"))
422 if (send_msg_channel_open_init(STDIN_FILENO
, &clichansess
)
423 == DROPBEAR_FAILURE
) {
424 dropbear_exit("Couldn't open initial channel");
427 /* No special channel request data */
429 TRACE(("leave cli_send_chansess_request"))