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 "session.h"
28 #include "dbutil.h"
29 #include "kex.h"
30 #include "ssh.h"
31 #include "packet.h"
32 #include "tcpfwd.h"
33 #include "channel.h"
34 #include "dbrandom.h"
35 #include "service.h"
36 #include "runopts.h"
37 #include "chansession.h"
38 #include "agentfwd.h"
39 #include "crypto_desc.h"
40 #include "netio.h"
41
42 static void cli_remoteclosed(void) ATTRIB_NORETURN;
43 static void cli_sessionloop(void);
44 static void cli_session_init(pid_t proxy_cmd_pid);
45 static void cli_finished(void) ATTRIB_NORETURN;
46 static void recv_msg_service_accept(void);
47 static void cli_session_cleanup(void);
48 static void recv_msg_global_request_cli(void);
49
50 struct clientsession cli_ses; /* GLOBAL */
51
52 /* Sorted in decreasing frequency will be more efficient - data and window
53 * should be first */
54 static const packettype cli_packettypes[] = {
55 /* TYPE, FUNCTION */
56 {SSH_MSG_CHANNEL_DATA, recv_msg_channel_data},
57 {SSH_MSG_CHANNEL_EXTENDED_DATA, recv_msg_channel_extended_data},
58 {SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust},
59 {SSH_MSG_USERAUTH_FAILURE, recv_msg_userauth_failure}, /* client */
60 {SSH_MSG_USERAUTH_SUCCESS, recv_msg_userauth_success}, /* client */
61 {SSH_MSG_KEXINIT, recv_msg_kexinit},
62 {SSH_MSG_KEXDH_REPLY, recv_msg_kexdh_reply}, /* client */
63 {SSH_MSG_NEWKEYS, recv_msg_newkeys},
64 {SSH_MSG_SERVICE_ACCEPT, recv_msg_service_accept}, /* client */
65 {SSH_MSG_CHANNEL_REQUEST, recv_msg_channel_request},
66 {SSH_MSG_CHANNEL_OPEN, recv_msg_channel_open},
67 {SSH_MSG_CHANNEL_EOF, recv_msg_channel_eof},
68 {SSH_MSG_CHANNEL_CLOSE, recv_msg_channel_close},
69 {SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation},
70 {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure},
71 {SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */
72 {SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */
73 {SSH_MSG_GLOBAL_REQUEST, recv_msg_global_request_cli},
74 {SSH_MSG_CHANNEL_SUCCESS, ignore_recv_response},
75 {SSH_MSG_CHANNEL_FAILURE, ignore_recv_response},
76 #if DROPBEAR_CLI_REMOTETCPFWD
77 {SSH_MSG_REQUEST_SUCCESS, cli_recv_msg_request_success}, /* client */
78 {SSH_MSG_REQUEST_FAILURE, cli_recv_msg_request_failure}, /* client */
79 #else
80 /* For keepalive */
81 {SSH_MSG_REQUEST_SUCCESS, ignore_recv_response},
82 {SSH_MSG_REQUEST_FAILURE, ignore_recv_response},
83 #endif
84 {SSH_MSG_EXT_INFO, recv_msg_ext_info},
85 {0, NULL} /* End */
86 };
87
88 static const struct ChanType *cli_chantypes[] = {
89 #if DROPBEAR_CLI_REMOTETCPFWD
90 &cli_chan_tcpremote,
91 #endif
92 #if DROPBEAR_CLI_AGENTFWD
93 &cli_chan_agent,
94 #endif
95 NULL /* Null termination */
96 };
97
cli_connected(int result,int sock,void * userdata,const char * errstring)98 void cli_connected(int result, int sock, void* userdata, const char *errstring)
99 {
100 struct sshsession *myses = userdata;
101 if (result == DROPBEAR_FAILURE) {
102 dropbear_exit("Connect failed: %s", errstring);
103 }
104 myses->sock_in = myses->sock_out = sock;
105 update_channel_prio();
106 }
107
cli_session(int sock_in,int sock_out,struct dropbear_progress_connection * progress,pid_t proxy_cmd_pid)108 void cli_session(int sock_in, int sock_out, struct dropbear_progress_connection *progress, pid_t proxy_cmd_pid) {
109
110 common_session_init(sock_in, sock_out);
111
112 if (progress) {
113 connect_set_writequeue(progress, &ses.writequeue);
114 }
115
116 chaninitialise(cli_chantypes);
117
118 /* Set up cli_ses vars */
119 cli_session_init(proxy_cmd_pid);
120
121 /* Ready to go */
122 ses.init_done = 1;
123
124 /* Exchange identification */
125 send_session_identification();
126
127 kexfirstinitialise(); /* initialise the kex state */
128
129 send_msg_kexinit();
130
131 session_loop(cli_sessionloop);
132
133 /* Not reached */
134
135 }
136
137 #if DROPBEAR_KEX_FIRST_FOLLOWS
cli_send_kex_first_guess()138 static void cli_send_kex_first_guess() {
139 send_msg_kexdh_init();
140 }
141 #endif
142
cli_session_init(pid_t proxy_cmd_pid)143 static void cli_session_init(pid_t proxy_cmd_pid) {
144
145 cli_ses.state = STATE_NOTHING;
146 cli_ses.kex_state = KEX_NOTHING;
147
148 cli_ses.tty_raw_mode = 0;
149 cli_ses.winchange = 0;
150
151 /* We store std{in,out,err}'s flags, so we can set them back on exit
152 * (otherwise busybox's ash isn't happy */
153 cli_ses.stdincopy = dup(STDIN_FILENO);
154 cli_ses.stdinflags = fcntl(STDIN_FILENO, F_GETFL, 0);
155 cli_ses.stdoutcopy = dup(STDOUT_FILENO);
156 cli_ses.stdoutflags = fcntl(STDOUT_FILENO, F_GETFL, 0);
157 cli_ses.stderrcopy = dup(STDERR_FILENO);
158 cli_ses.stderrflags = fcntl(STDERR_FILENO, F_GETFL, 0);
159
160 cli_ses.retval = EXIT_SUCCESS; /* Assume it's clean if we don't get a
161 specific exit status */
162 cli_ses.proxy_cmd_pid = proxy_cmd_pid;
163 TRACE(("proxy command PID='%d'", proxy_cmd_pid));
164
165 /* Auth */
166 cli_ses.lastprivkey = NULL;
167 cli_ses.lastauthtype = 0;
168
169 /* For printing "remote host closed" for the user */
170 ses.remoteclosed = cli_remoteclosed;
171
172 ses.extra_session_cleanup = cli_session_cleanup;
173
174 /* packet handlers */
175 ses.packettypes = cli_packettypes;
176
177 ses.isserver = 0;
178
179 #if DROPBEAR_KEX_FIRST_FOLLOWS
180 ses.send_kex_first_guess = cli_send_kex_first_guess;
181 #endif
182
183 }
184
send_msg_service_request(const char * servicename)185 static void send_msg_service_request(const char* servicename) {
186
187 TRACE(("enter send_msg_service_request: servicename='%s'", servicename))
188
189 CHECKCLEARTOWRITE();
190
191 buf_putbyte(ses.writepayload, SSH_MSG_SERVICE_REQUEST);
192 buf_putstring(ses.writepayload, servicename, strlen(servicename));
193
194 encrypt_packet();
195 TRACE(("leave send_msg_service_request"))
196 }
197
recv_msg_service_accept(void)198 static void recv_msg_service_accept(void) {
199 /* do nothing, if it failed then the server MUST have disconnected */
200 }
201
202 /* This function drives the progress of the session - it initiates KEX,
203 * service, userauth and channel requests */
cli_sessionloop()204 static void cli_sessionloop() {
205
206 TRACE2(("enter cli_sessionloop"))
207
208 if (ses.lastpacket == 0) {
209 TRACE2(("exit cli_sessionloop: no real packets yet"))
210 return;
211 }
212
213 if (ses.lastpacket == SSH_MSG_KEXINIT && cli_ses.kex_state == KEX_NOTHING) {
214 /* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT
215 * negotiation would have failed. */
216 if (!ses.kexstate.our_first_follows_matches) {
217 send_msg_kexdh_init();
218 }
219 cli_ses.kex_state = KEXDH_INIT_SENT;
220 TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD"))
221 return;
222 }
223
224 /* A KEX has finished, so we should go back to our KEX_NOTHING state */
225 if (cli_ses.kex_state != KEX_NOTHING && ses.kexstate.sentnewkeys) {
226 cli_ses.kex_state = KEX_NOTHING;
227 }
228
229 /* We shouldn't do anything else if a KEX is in progress */
230 if (cli_ses.kex_state != KEX_NOTHING) {
231 TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING"))
232 return;
233 }
234
235 if (ses.kexstate.donefirstkex == 0) {
236 /* We might reach here if we have partial packet reads or have
237 * received SSG_MSG_IGNORE etc. Just skip it */
238 TRACE2(("donefirstkex false\n"))
239 return;
240 }
241
242 switch (cli_ses.state) {
243
244 case STATE_NOTHING:
245 /* We've got the transport layer sorted, we now need to request
246 * userauth */
247 send_msg_service_request(SSH_SERVICE_USERAUTH);
248 cli_auth_getmethods();
249 cli_ses.state = USERAUTH_REQ_SENT;
250 TRACE(("leave cli_sessionloop: sent userauth methods req"))
251 return;
252
253 case USERAUTH_REQ_SENT:
254 TRACE(("leave cli_sessionloop: waiting, req_sent"))
255 return;
256
257 case USERAUTH_FAIL_RCVD:
258 if (cli_auth_try() == DROPBEAR_FAILURE) {
259 dropbear_exit("No auth methods could be used.");
260 }
261 cli_ses.state = USERAUTH_REQ_SENT;
262 TRACE(("leave cli_sessionloop: cli_auth_try"))
263 return;
264
265 case USERAUTH_SUCCESS_RCVD:
266 #ifndef DISABLE_SYSLOG
267 if (opts.usingsyslog) {
268 dropbear_log(LOG_INFO, "Authentication succeeded.");
269 }
270 #endif
271
272 if (cli_opts.backgrounded) {
273 int devnull;
274 /* keeping stdin open steals input from the terminal and
275 is confusing, though stdout/stderr could be useful. */
276 devnull = open(DROPBEAR_PATH_DEVNULL, O_RDONLY);
277 if (devnull < 0) {
278 dropbear_exit("Opening /dev/null: %d %s",
279 errno, strerror(errno));
280 }
281 dup2(devnull, STDIN_FILENO);
282 if (daemon(0, 1) < 0) {
283 dropbear_exit("Backgrounding failed: %d %s",
284 errno, strerror(errno));
285 }
286 }
287
288 #if DROPBEAR_CLI_NETCAT
289 if (cli_opts.netcat_host) {
290 cli_send_netcat_request();
291 } else
292 #endif
293 if (!cli_opts.no_cmd) {
294 cli_send_chansess_request();
295 }
296
297 #if DROPBEAR_CLI_LOCALTCPFWD
298 setup_localtcp();
299 #endif
300 #if DROPBEAR_CLI_REMOTETCPFWD
301 setup_remotetcp();
302 #endif
303
304 TRACE(("leave cli_sessionloop: running"))
305 cli_ses.state = SESSION_RUNNING;
306 return;
307
308 case SESSION_RUNNING:
309 if (ses.chancount < 1 && !cli_opts.no_cmd) {
310 cli_finished();
311 }
312
313 if (cli_ses.winchange) {
314 cli_chansess_winchange();
315 }
316 return;
317
318 /* XXX more here needed */
319
320
321 default:
322 break;
323 }
324
325 TRACE2(("leave cli_sessionloop: fell out"))
326
327 }
328
kill_proxy_command(void)329 void kill_proxy_command(void) {
330 /*
331 * Send SIGHUP to proxy command if used. We don't wait() in
332 * case it hangs and instead rely on init to reap the child
333 */
334 if (cli_ses.proxy_cmd_pid > 1) {
335 TRACE(("killing proxy command with PID='%d'", cli_ses.proxy_cmd_pid));
336 kill(cli_ses.proxy_cmd_pid, SIGHUP);
337 }
338 }
339
cli_session_cleanup(void)340 static void cli_session_cleanup(void) {
341
342 if (!ses.init_done) {
343 return;
344 }
345
346 kill_proxy_command();
347
348 /* Set std{in,out,err} back to non-blocking - busybox ash dies nastily if
349 * we don't revert the flags */
350 /* Ignore return value since there's nothing we can do */
351 (void)fcntl(cli_ses.stdincopy, F_SETFL, cli_ses.stdinflags);
352 (void)fcntl(cli_ses.stdoutcopy, F_SETFL, cli_ses.stdoutflags);
353 (void)fcntl(cli_ses.stderrcopy, F_SETFL, cli_ses.stderrflags);
354
355 /* Don't leak */
356 m_close(cli_ses.stdincopy);
357 m_close(cli_ses.stdoutcopy);
358 m_close(cli_ses.stderrcopy);
359
360 cli_tty_cleanup();
361 if (cli_ses.server_sig_algs) {
362 buf_free(cli_ses.server_sig_algs);
363 }
364 }
365
cli_finished()366 static void cli_finished() {
367 TRACE(("cli_finished()"))
368
369 session_cleanup();
370 fprintf(stderr, "Connection to %s@%s:%s closed.\n", cli_opts.username,
371 cli_opts.remotehost, cli_opts.remoteport);
372 exit(cli_ses.retval);
373 }
374
375
376 /* called when the remote side closes the connection */
cli_remoteclosed()377 static void cli_remoteclosed() {
378
379 /* XXX TODO perhaps print a friendlier message if we get this but have
380 * already sent/received disconnect message(s) ??? */
381 m_close(ses.sock_in);
382 m_close(ses.sock_out);
383 ses.sock_in = -1;
384 ses.sock_out = -1;
385 dropbear_exit("Remote closed the connection");
386 }
387
388 /* Operates in-place turning dirty (untrusted potentially containing control
389 * characters) text into clean text.
390 * Note: this is safe only with ascii - other charsets could have problems. */
cleantext(char * dirtytext)391 void cleantext(char* dirtytext) {
392
393 unsigned int i, j;
394 char c;
395
396 j = 0;
397 for (i = 0; dirtytext[i] != '\0'; i++) {
398
399 c = dirtytext[i];
400 /* We can ignore '\r's */
401 if ( (c >= ' ' && c <= '~') || c == '\n' || c == '\t') {
402 dirtytext[j] = c;
403 j++;
404 }
405 }
406 /* Null terminate */
407 dirtytext[j] = '\0';
408 }
409
recv_msg_global_request_cli(void)410 static void recv_msg_global_request_cli(void) {
411 TRACE(("recv_msg_global_request_cli"))
412 /* Send a proper rejection */
413 send_msg_request_failure();
414 }
415
cli_dropbear_exit(int exitcode,const char * format,va_list param)416 void cli_dropbear_exit(int exitcode, const char* format, va_list param) {
417 char exitmsg[150];
418 char fullmsg[300];
419
420 /* Note that exit message must be rendered before session cleanup */
421
422 /* Render the formatted exit message */
423 vsnprintf(exitmsg, sizeof(exitmsg), format, param);
424 TRACE(("Exited, cleaning up: %s", exitmsg))
425
426 /* Add the prefix depending on session/auth state */
427 if (!ses.init_done) {
428 snprintf(fullmsg, sizeof(fullmsg), "Exited: %s", exitmsg);
429 } else {
430 snprintf(fullmsg, sizeof(fullmsg),
431 "Connection to %s@%s:%s exited: %s",
432 cli_opts.username, cli_opts.remotehost,
433 cli_opts.remoteport, exitmsg);
434 }
435
436 /* Do the cleanup first, since then the terminal will be reset */
437 session_cleanup();
438
439 #if DROPBEAR_FUZZ
440 if (fuzz.do_jmp) {
441 longjmp(fuzz.jmp, 1);
442 }
443 #endif
444
445 /* Avoid printing onwards from terminal cruft */
446 fprintf(stderr, "\n");
447
448 dropbear_log(LOG_INFO, "%s", fullmsg);
449
450 exit(exitcode);
451 }
452
cli_dropbear_log(int priority,const char * format,va_list param)453 void cli_dropbear_log(int priority, const char* format, va_list param) {
454
455 char printbuf[1024];
456 const char *name;
457
458 name = cli_opts.progname;
459 if (!name) {
460 name = "dbclient";
461 }
462
463 vsnprintf(printbuf, sizeof(printbuf), format, param);
464
465 #ifndef DISABLE_SYSLOG
466 if (opts.usingsyslog) {
467 syslog(priority, "%s", printbuf);
468 }
469 #endif
470
471 fprintf(stderr, "%s: %s\n", name, printbuf);
472 fflush(stderr);
473 }
474
475