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