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"
39 #include "crypto_desc.h"
41 static void cli_remoteclosed();
42 static void cli_sessionloop();
43 static void cli_session_init();
44 static void cli_finished();
45 static void recv_msg_service_accept(void);
46 static void cli_session_cleanup(void);
47 static void recv_msg_global_request_cli(void);
49 struct clientsession cli_ses
; /* GLOBAL */
51 /* Sorted in decreasing frequency will be more efficient - data and window
53 static const packettype cli_packettypes
[] = {
55 {SSH_MSG_CHANNEL_DATA
, recv_msg_channel_data
},
56 {SSH_MSG_CHANNEL_EXTENDED_DATA
, recv_msg_channel_extended_data
},
57 {SSH_MSG_CHANNEL_WINDOW_ADJUST
, recv_msg_channel_window_adjust
},
58 {SSH_MSG_USERAUTH_FAILURE
, recv_msg_userauth_failure
}, /* client */
59 {SSH_MSG_USERAUTH_SUCCESS
, recv_msg_userauth_success
}, /* client */
60 {SSH_MSG_KEXINIT
, recv_msg_kexinit
},
61 {SSH_MSG_KEXDH_REPLY
, recv_msg_kexdh_reply
}, /* client */
62 {SSH_MSG_NEWKEYS
, recv_msg_newkeys
},
63 {SSH_MSG_SERVICE_ACCEPT
, recv_msg_service_accept
}, /* client */
64 {SSH_MSG_CHANNEL_REQUEST
, recv_msg_channel_request
},
65 {SSH_MSG_CHANNEL_OPEN
, recv_msg_channel_open
},
66 {SSH_MSG_CHANNEL_EOF
, recv_msg_channel_eof
},
67 {SSH_MSG_CHANNEL_CLOSE
, recv_msg_channel_close
},
68 {SSH_MSG_CHANNEL_OPEN_CONFIRMATION
, recv_msg_channel_open_confirmation
},
69 {SSH_MSG_CHANNEL_OPEN_FAILURE
, recv_msg_channel_open_failure
},
70 {SSH_MSG_USERAUTH_BANNER
, recv_msg_userauth_banner
}, /* client */
71 {SSH_MSG_USERAUTH_SPECIFIC_60
, recv_msg_userauth_specific_60
}, /* client */
72 {SSH_MSG_GLOBAL_REQUEST
, recv_msg_global_request_cli
},
73 {SSH_MSG_CHANNEL_SUCCESS
, ignore_recv_response
},
74 {SSH_MSG_CHANNEL_FAILURE
, ignore_recv_response
},
75 #ifdef ENABLE_CLI_REMOTETCPFWD
76 {SSH_MSG_REQUEST_SUCCESS
, cli_recv_msg_request_success
}, /* client */
77 {SSH_MSG_REQUEST_FAILURE
, cli_recv_msg_request_failure
}, /* client */
80 {SSH_MSG_REQUEST_SUCCESS
, ignore_recv_response
},
81 {SSH_MSG_REQUEST_FAILURE
, ignore_recv_response
},
86 static const struct ChanType
*cli_chantypes
[] = {
87 #ifdef ENABLE_CLI_REMOTETCPFWD
90 #ifdef ENABLE_CLI_AGENTFWD
93 NULL
/* Null termination */
96 void cli_session(int sock_in
, int sock_out
) {
98 common_session_init(sock_in
, sock_out
);
100 chaninitialise(cli_chantypes
);
102 /* Set up cli_ses vars */
108 /* Exchange identification */
109 send_session_identification();
113 session_loop(cli_sessionloop
);
119 #ifdef USE_KEX_FIRST_FOLLOWS
120 static void cli_send_kex_first_guess() {
121 send_msg_kexdh_init();
125 static void cli_session_init() {
127 cli_ses
.state
= STATE_NOTHING
;
128 cli_ses
.kex_state
= KEX_NOTHING
;
130 cli_ses
.tty_raw_mode
= 0;
131 cli_ses
.winchange
= 0;
133 /* We store std{in,out,err}'s flags, so we can set them back on exit
134 * (otherwise busybox's ash isn't happy */
135 cli_ses
.stdincopy
= dup(STDIN_FILENO
);
136 cli_ses
.stdinflags
= fcntl(STDIN_FILENO
, F_GETFL
, 0);
137 cli_ses
.stdoutcopy
= dup(STDOUT_FILENO
);
138 cli_ses
.stdoutflags
= fcntl(STDOUT_FILENO
, F_GETFL
, 0);
139 cli_ses
.stderrcopy
= dup(STDERR_FILENO
);
140 cli_ses
.stderrflags
= fcntl(STDERR_FILENO
, F_GETFL
, 0);
142 cli_ses
.retval
= EXIT_SUCCESS
; /* Assume it's clean if we don't get a
143 specific exit status */
146 cli_ses
.lastprivkey
= NULL
;
147 cli_ses
.lastauthtype
= 0;
149 #ifdef DROPBEAR_NONE_CIPHER
150 cli_ses
.cipher_none_after_auth
= get_algo_usable(sshciphers
, "none");
151 set_algo_usable(sshciphers
, "none", 0);
153 cli_ses
.cipher_none_after_auth
= 0;
156 /* For printing "remote host closed" for the user */
157 ses
.remoteclosed
= cli_remoteclosed
;
159 ses
.extra_session_cleanup
= cli_session_cleanup
;
161 /* packet handlers */
162 ses
.packettypes
= cli_packettypes
;
166 #ifdef USE_KEX_FIRST_FOLLOWS
167 ses
.send_kex_first_guess
= cli_send_kex_first_guess
;
172 static void send_msg_service_request(char* servicename
) {
174 TRACE(("enter send_msg_service_request: servicename='%s'", servicename
))
178 buf_putbyte(ses
.writepayload
, SSH_MSG_SERVICE_REQUEST
);
179 buf_putstring(ses
.writepayload
, servicename
, strlen(servicename
));
182 TRACE(("leave send_msg_service_request"))
185 static void recv_msg_service_accept(void) {
186 /* do nothing, if it failed then the server MUST have disconnected */
189 /* This function drives the progress of the session - it initiates KEX,
190 * service, userauth and channel requests */
191 static void cli_sessionloop() {
193 TRACE2(("enter cli_sessionloop"))
195 if (ses
.lastpacket
== 0) {
196 TRACE2(("exit cli_sessionloop: no real packets yet"))
200 if (ses
.lastpacket
== SSH_MSG_KEXINIT
&& cli_ses
.kex_state
== KEX_NOTHING
) {
201 /* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT
202 * negotiation would have failed. */
203 if (!ses
.kexstate
.our_first_follows_matches
) {
204 send_msg_kexdh_init();
206 cli_ses
.kex_state
= KEXDH_INIT_SENT
;
207 TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD"))
211 /* A KEX has finished, so we should go back to our KEX_NOTHING state */
212 if (cli_ses
.kex_state
!= KEX_NOTHING
&& ses
.kexstate
.sentnewkeys
) {
213 cli_ses
.kex_state
= KEX_NOTHING
;
216 /* We shouldn't do anything else if a KEX is in progress */
217 if (cli_ses
.kex_state
!= KEX_NOTHING
) {
218 TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING"))
222 if (ses
.kexstate
.donefirstkex
== 0) {
223 /* We might reach here if we have partial packet reads or have
224 * received SSG_MSG_IGNORE etc. Just skip it */
225 TRACE2(("donefirstkex false\n"))
229 switch (cli_ses
.state
) {
232 /* We've got the transport layer sorted, we now need to request
234 send_msg_service_request(SSH_SERVICE_USERAUTH
);
235 cli_auth_getmethods();
236 cli_ses
.state
= USERAUTH_REQ_SENT
;
237 TRACE(("leave cli_sessionloop: sent userauth methods req"))
240 case USERAUTH_REQ_SENT
:
241 TRACE(("leave cli_sessionloop: waiting, req_sent"))
244 case USERAUTH_FAIL_RCVD
:
245 if (cli_auth_try() == DROPBEAR_FAILURE
) {
246 dropbear_exit("No auth methods could be used.");
248 cli_ses
.state
= USERAUTH_REQ_SENT
;
249 TRACE(("leave cli_sessionloop: cli_auth_try"))
252 case USERAUTH_SUCCESS_RCVD
:
254 #ifdef DROPBEAR_NONE_CIPHER
255 if (cli_ses
.cipher_none_after_auth
)
257 set_algo_usable(sshciphers
, "none", 1);
262 if (cli_opts
.backgrounded
) {
264 /* keeping stdin open steals input from the terminal and
265 is confusing, though stdout/stderr could be useful. */
266 devnull
= open(_PATH_DEVNULL
, O_RDONLY
);
268 dropbear_exit("Opening /dev/null: %d %s",
269 errno
, strerror(errno
));
271 dup2(devnull
, STDIN_FILENO
);
272 if (daemon(0, 1) < 0) {
273 dropbear_exit("Backgrounding failed: %d %s",
274 errno
, strerror(errno
));
278 #ifdef ENABLE_CLI_NETCAT
279 if (cli_opts
.netcat_host
) {
280 cli_send_netcat_request();
283 if (!cli_opts
.no_cmd
) {
284 cli_send_chansess_request();
287 #ifdef ENABLE_CLI_LOCALTCPFWD
290 #ifdef ENABLE_CLI_REMOTETCPFWD
294 TRACE(("leave cli_sessionloop: running"))
295 cli_ses
.state
= SESSION_RUNNING
;
298 case SESSION_RUNNING
:
299 if (ses
.chancount
< 1 && !cli_opts
.no_cmd
) {
303 if (cli_ses
.winchange
) {
304 cli_chansess_winchange();
308 /* XXX more here needed */
315 TRACE2(("leave cli_sessionloop: fell out"))
319 static void cli_session_cleanup(void) {
325 /* Set std{in,out,err} back to non-blocking - busybox ash dies nastily if
326 * we don't revert the flags */
327 fcntl(cli_ses
.stdincopy
, F_SETFL
, cli_ses
.stdinflags
);
328 fcntl(cli_ses
.stdoutcopy
, F_SETFL
, cli_ses
.stdoutflags
);
329 fcntl(cli_ses
.stderrcopy
, F_SETFL
, cli_ses
.stderrflags
);
335 static void cli_finished() {
338 fprintf(stderr
, "Connection to %s@%s:%s closed.\n", cli_opts
.username
,
339 cli_opts
.remotehost
, cli_opts
.remoteport
);
340 exit(cli_ses
.retval
);
344 /* called when the remote side closes the connection */
345 static void cli_remoteclosed() {
347 /* XXX TODO perhaps print a friendlier message if we get this but have
348 * already sent/received disconnect message(s) ??? */
349 m_close(ses
.sock_in
);
350 m_close(ses
.sock_out
);
353 dropbear_exit("Remote closed the connection");
356 /* Operates in-place turning dirty (untrusted potentially containing control
357 * characters) text into clean text.
358 * Note: this is safe only with ascii - other charsets could have problems. */
359 void cleantext(unsigned char* dirtytext
) {
365 for (i
= 0; dirtytext
[i
] != '\0'; i
++) {
368 /* We can ignore '\r's */
369 if ( (c
>= ' ' && c
<= '~') || c
== '\n' || c
== '\t') {
378 static void recv_msg_global_request_cli(void) {
379 TRACE(("recv_msg_global_request_cli"))
380 /* Send a proper rejection */
381 send_msg_request_failure();