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
37 #include "chansession.h"
40 static void cli_remoteclosed();
41 static void cli_sessionloop();
42 static void cli_session_init();
43 static void cli_finished();
44 static void recv_msg_service_accept(void);
45 static void cli_session_cleanup(void);
47 struct clientsession cli_ses
; /* GLOBAL */
49 /* Sorted in decreasing frequency will be more efficient - data and window
51 static const packettype cli_packettypes
[] = {
53 {SSH_MSG_CHANNEL_DATA
, recv_msg_channel_data
},
54 {SSH_MSG_CHANNEL_EXTENDED_DATA
, recv_msg_channel_extended_data
},
55 {SSH_MSG_CHANNEL_WINDOW_ADJUST
, recv_msg_channel_window_adjust
},
56 {SSH_MSG_USERAUTH_FAILURE
, recv_msg_userauth_failure
}, /* client */
57 {SSH_MSG_USERAUTH_SUCCESS
, recv_msg_userauth_success
}, /* client */
58 {SSH_MSG_KEXINIT
, recv_msg_kexinit
},
59 {SSH_MSG_KEXDH_REPLY
, recv_msg_kexdh_reply
}, /* client */
60 {SSH_MSG_NEWKEYS
, recv_msg_newkeys
},
61 {SSH_MSG_SERVICE_ACCEPT
, recv_msg_service_accept
}, /* client */
62 {SSH_MSG_CHANNEL_REQUEST
, recv_msg_channel_request
},
63 {SSH_MSG_CHANNEL_OPEN
, recv_msg_channel_open
},
64 {SSH_MSG_CHANNEL_EOF
, recv_msg_channel_eof
},
65 {SSH_MSG_CHANNEL_CLOSE
, recv_msg_channel_close
},
66 {SSH_MSG_CHANNEL_OPEN_CONFIRMATION
, recv_msg_channel_open_confirmation
},
67 {SSH_MSG_CHANNEL_OPEN_FAILURE
, recv_msg_channel_open_failure
},
68 {SSH_MSG_USERAUTH_BANNER
, recv_msg_userauth_banner
}, /* client */
69 {SSH_MSG_USERAUTH_SPECIFIC_60
, recv_msg_userauth_specific_60
}, /* client */
70 #ifdef ENABLE_CLI_REMOTETCPFWD
71 {SSH_MSG_REQUEST_SUCCESS
, cli_recv_msg_request_success
}, /* client */
72 {SSH_MSG_REQUEST_FAILURE
, cli_recv_msg_request_failure
}, /* client */
77 static const struct ChanType
*cli_chantypes
[] = {
78 #ifdef ENABLE_CLI_REMOTETCPFWD
81 #ifdef ENABLE_CLI_AGENTFWD
84 NULL
/* Null termination */
87 void cli_session(int sock_in
, int sock_out
) {
93 common_session_init(sock_in
, sock_out
);
95 chaninitialise(cli_chantypes
);
97 /* Set up cli_ses vars */
103 /* Exchange identification */
104 send_session_identification();
108 session_loop(cli_sessionloop
);
114 #ifdef USE_KEX_FIRST_FOLLOWS
115 static void cli_send_kex_first_guess() {
116 send_msg_kexdh_init();
120 static void cli_session_init() {
122 cli_ses
.state
= STATE_NOTHING
;
123 cli_ses
.kex_state
= KEX_NOTHING
;
125 cli_ses
.tty_raw_mode
= 0;
126 cli_ses
.winchange
= 0;
128 /* We store std{in,out,err}'s flags, so we can set them back on exit
129 * (otherwise busybox's ash isn't happy */
130 cli_ses
.stdincopy
= dup(STDIN_FILENO
);
131 cli_ses
.stdinflags
= fcntl(STDIN_FILENO
, F_GETFL
, 0);
132 cli_ses
.stdoutcopy
= dup(STDOUT_FILENO
);
133 cli_ses
.stdoutflags
= fcntl(STDOUT_FILENO
, F_GETFL
, 0);
134 cli_ses
.stderrcopy
= dup(STDERR_FILENO
);
135 cli_ses
.stderrflags
= fcntl(STDERR_FILENO
, F_GETFL
, 0);
137 cli_ses
.retval
= EXIT_SUCCESS
; /* Assume it's clean if we don't get a
138 specific exit status */
141 cli_ses
.lastprivkey
= NULL
;
142 cli_ses
.lastauthtype
= 0;
144 #ifdef DROPBEAR_NONE_CIPHER
145 cli_ses
.cipher_none_after_auth
= get_algo_usable(sshciphers
, "none");
146 set_algo_usable(sshciphers
, "none", 0);
148 cli_ses
.cipher_none_after_auth
= 0;
151 /* For printing "remote host closed" for the user */
152 ses
.remoteclosed
= cli_remoteclosed
;
154 ses
.extra_session_cleanup
= cli_session_cleanup
;
156 /* packet handlers */
157 ses
.packettypes
= cli_packettypes
;
161 #ifdef USE_KEX_FIRST_FOLLOWS
162 ses
.send_kex_first_guess
= cli_send_kex_first_guess
;
167 static void send_msg_service_request(char* servicename
) {
169 TRACE(("enter send_msg_service_request: servicename='%s'", servicename
))
173 buf_putbyte(ses
.writepayload
, SSH_MSG_SERVICE_REQUEST
);
174 buf_putstring(ses
.writepayload
, servicename
, strlen(servicename
));
177 TRACE(("leave send_msg_service_request"))
180 static void recv_msg_service_accept(void) {
181 // do nothing, if it failed then the server MUST have disconnected
184 /* This function drives the progress of the session - it initiates KEX,
185 * service, userauth and channel requests */
186 static void cli_sessionloop() {
188 TRACE2(("enter cli_sessionloop"))
190 if (ses
.lastpacket
== 0) {
191 TRACE2(("exit cli_sessionloop: no real packets yet"))
195 if (ses
.lastpacket
== SSH_MSG_KEXINIT
&& cli_ses
.kex_state
== KEX_NOTHING
) {
196 /* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT
197 * negotiation would have failed. */
198 if (!ses
.kexstate
.our_first_follows_matches
) {
199 send_msg_kexdh_init();
201 cli_ses
.kex_state
= KEXDH_INIT_SENT
;
202 TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD"))
206 /* A KEX has finished, so we should go back to our KEX_NOTHING state */
207 if (cli_ses
.kex_state
!= KEX_NOTHING
&& ses
.kexstate
.sentnewkeys
) {
208 cli_ses
.kex_state
= KEX_NOTHING
;
211 /* We shouldn't do anything else if a KEX is in progress */
212 if (cli_ses
.kex_state
!= KEX_NOTHING
) {
213 TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING"))
217 if (ses
.kexstate
.donefirstkex
== 0) {
218 /* We might reach here if we have partial packet reads or have
219 * received SSG_MSG_IGNORE etc. Just skip it */
220 TRACE2(("donefirstkex false\n"))
224 switch (cli_ses
.state
) {
227 /* We've got the transport layer sorted, we now need to request
229 send_msg_service_request(SSH_SERVICE_USERAUTH
);
230 cli_auth_getmethods();
231 cli_ses
.state
= USERAUTH_REQ_SENT
;
232 TRACE(("leave cli_sessionloop: sent userauth methods req"))
235 case USERAUTH_FAIL_RCVD
:
236 if (cli_auth_try() == DROPBEAR_FAILURE
) {
237 dropbear_exit("No auth methods could be used.");
239 cli_ses
.state
= USERAUTH_REQ_SENT
;
240 TRACE(("leave cli_sessionloop: cli_auth_try"))
243 case USERAUTH_SUCCESS_RCVD
:
245 #ifdef DROPBEAR_NONE_CIPHER
246 if (cli_ses
.cipher_none_after_auth
)
248 set_algo_usable(sshciphers
, "none", 1);
253 if (cli_opts
.backgrounded
) {
255 /* keeping stdin open steals input from the terminal and
256 is confusing, though stdout/stderr could be useful. */
257 devnull
= open(_PATH_DEVNULL
, O_RDONLY
);
259 dropbear_exit("Opening /dev/null: %d %s",
260 errno
, strerror(errno
));
262 dup2(devnull
, STDIN_FILENO
);
263 if (daemon(0, 1) < 0) {
264 dropbear_exit("Backgrounding failed: %d %s",
265 errno
, strerror(errno
));
269 #ifdef ENABLE_CLI_NETCAT
270 if (cli_opts
.netcat_host
) {
271 cli_send_netcat_request();
274 if (!cli_opts
.no_cmd
) {
275 cli_send_chansess_request();
278 #ifdef ENABLE_CLI_LOCALTCPFWD
281 #ifdef ENABLE_CLI_REMOTETCPFWD
285 TRACE(("leave cli_sessionloop: running"))
286 cli_ses
.state
= SESSION_RUNNING
;
289 case SESSION_RUNNING
:
290 if (ses
.chancount
< 1 && !cli_opts
.no_cmd
) {
294 if (cli_ses
.winchange
) {
295 cli_chansess_winchange();
299 /* XXX more here needed */
306 TRACE2(("leave cli_sessionloop: fell out"))
310 static void cli_session_cleanup(void) {
316 /* Set std{in,out,err} back to non-blocking - busybox ash dies nastily if
317 * we don't revert the flags */
318 fcntl(cli_ses
.stdincopy
, F_SETFL
, cli_ses
.stdinflags
);
319 fcntl(cli_ses
.stdoutcopy
, F_SETFL
, cli_ses
.stdoutflags
);
320 fcntl(cli_ses
.stderrcopy
, F_SETFL
, cli_ses
.stderrflags
);
326 static void cli_finished() {
329 fprintf(stderr
, "Connection to %s@%s:%s closed.\n", cli_opts
.username
,
330 cli_opts
.remotehost
, cli_opts
.remoteport
);
331 exit(cli_ses
.retval
);
335 /* called when the remote side closes the connection */
336 static void cli_remoteclosed() {
338 /* XXX TODO perhaps print a friendlier message if we get this but have
339 * already sent/received disconnect message(s) ??? */
340 m_close(ses
.sock_in
);
341 m_close(ses
.sock_out
);
344 dropbear_exit("Remote closed the connection");
347 /* Operates in-place turning dirty (untrusted potentially containing control
348 * characters) text into clean text.
349 * Note: this is safe only with ascii - other charsets could have problems. */
350 void cleantext(unsigned char* dirtytext
) {
356 for (i
= 0; dirtytext
[i
] != '\0'; i
++) {
359 /* We can ignore '\r's */
360 if ( (c
>= ' ' && c
<= '~') || c
== '\n' || c
== '\t') {