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