cli-session.c 13 KB

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