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 "random.h" 35 #include "service.h" 36 #include "runopts.h" 37 #include "chansession.h" 38 39 static void cli_remoteclosed(); 40 static void cli_sessionloop(); 41 static void cli_session_init(); 42 static void cli_finished(); 43 44 struct clientsession cli_ses; /* GLOBAL */ 45 46 /* Sorted in decreasing frequency will be more efficient - data and window 47 * should be first */ 48 static const packettype cli_packettypes[] = { 49 /* TYPE, FUNCTION */ 50 {SSH_MSG_CHANNEL_DATA, recv_msg_channel_data}, 51 {SSH_MSG_CHANNEL_EXTENDED_DATA, recv_msg_channel_extended_data}, 52 {SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust}, 53 {SSH_MSG_USERAUTH_FAILURE, recv_msg_userauth_failure}, /* client */ 54 {SSH_MSG_USERAUTH_SUCCESS, recv_msg_userauth_success}, /* client */ 55 {SSH_MSG_KEXINIT, recv_msg_kexinit}, 56 {SSH_MSG_KEXDH_REPLY, recv_msg_kexdh_reply}, /* client */ 57 {SSH_MSG_NEWKEYS, recv_msg_newkeys}, 58 {SSH_MSG_SERVICE_ACCEPT, recv_msg_service_accept}, /* client */ 59 {SSH_MSG_CHANNEL_REQUEST, recv_msg_channel_request}, 60 {SSH_MSG_CHANNEL_OPEN, recv_msg_channel_open}, 61 {SSH_MSG_CHANNEL_EOF, recv_msg_channel_eof}, 62 {SSH_MSG_CHANNEL_CLOSE, recv_msg_channel_close}, 63 {SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation}, 64 {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure}, 65 {SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */ 66 {SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */ 67 {0, 0} /* End */ 68 }; 69 70 static const struct ChanType *cli_chantypes[] = { 71 #ifdef ENABLE_CLI_REMOTETCPFWD 72 &cli_chan_tcpremote, 73 #endif 74 NULL /* Null termination */ 75 }; 76 77 void cli_session(int sock, char* remotehost) { 78 79 seedrandom(); 80 81 crypto_init(); 82 83 common_session_init(sock, remotehost); 84 85 chaninitialise(cli_chantypes); 86 87 /* Set up cli_ses vars */ 88 cli_session_init(); 89 90 /* Ready to go */ 91 sessinitdone = 1; 92 93 /* Exchange identification */ 94 session_identification(); 95 96 send_msg_kexinit(); 97 98 session_loop(cli_sessionloop); 99 100 /* Not reached */ 101 102 } 103 104 static void cli_session_init() { 105 106 cli_ses.state = STATE_NOTHING; 107 cli_ses.kex_state = KEX_NOTHING; 108 109 cli_ses.tty_raw_mode = 0; 110 cli_ses.winchange = 0; 111 112 /* We store std{in,out,err}'s flags, so we can set them back on exit 113 * (otherwise busybox's ash isn't happy */ 114 cli_ses.stdincopy = dup(STDIN_FILENO); 115 cli_ses.stdinflags = fcntl(STDIN_FILENO, F_GETFL, 0); 116 cli_ses.stdoutcopy = dup(STDOUT_FILENO); 117 cli_ses.stdoutflags = fcntl(STDOUT_FILENO, F_GETFL, 0); 118 cli_ses.stderrcopy = dup(STDERR_FILENO); 119 cli_ses.stderrflags = fcntl(STDERR_FILENO, F_GETFL, 0); 120 121 cli_ses.retval = EXIT_SUCCESS; /* Assume it's clean if we don't get a 122 specific exit status */ 123 124 /* Auth */ 125 cli_ses.lastprivkey = NULL; 126 cli_ses.lastauthtype = 0; 127 128 /* For printing "remote host closed" for the user */ 129 ses.remoteclosed = cli_remoteclosed; 130 ses.buf_match_algo = cli_buf_match_algo; 131 132 /* packet handlers */ 133 ses.packettypes = cli_packettypes; 134 135 ses.isserver = 0; 136 } 137 138 /* This function drives the progress of the session - it initiates KEX, 139 * service, userauth and channel requests */ 140 static void cli_sessionloop() { 141 142 TRACE(("enter cli_sessionloop")) 143 144 if (ses.lastpacket == SSH_MSG_KEXINIT && cli_ses.kex_state == KEX_NOTHING) { 145 cli_ses.kex_state = KEXINIT_RCVD; 146 } 147 148 if (cli_ses.kex_state == KEXINIT_RCVD) { 149 150 /* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT 151 * negotiation would have failed. */ 152 send_msg_kexdh_init(); 153 cli_ses.kex_state = KEXDH_INIT_SENT; 154 TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD")) 155 return; 156 } 157 158 /* A KEX has finished, so we should go back to our KEX_NOTHING state */ 159 if (cli_ses.kex_state != KEX_NOTHING && ses.kexstate.recvkexinit == 0 160 && ses.kexstate.sentkexinit == 0) { 161 cli_ses.kex_state = KEX_NOTHING; 162 } 163 164 /* We shouldn't do anything else if a KEX is in progress */ 165 if (cli_ses.kex_state != KEX_NOTHING) { 166 TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING")) 167 return; 168 } 169 170 /* We should exit if we haven't donefirstkex: we shouldn't reach here 171 * in normal operation */ 172 if (ses.kexstate.donefirstkex == 0) { 173 TRACE(("XXX XXX might be bad! leave cli_sessionloop: haven't donefirstkex")) 174 return; 175 } 176 177 switch (cli_ses.state) { 178 179 case STATE_NOTHING: 180 /* We've got the transport layer sorted, we now need to request 181 * userauth */ 182 send_msg_service_request(SSH_SERVICE_USERAUTH); 183 cli_ses.state = SERVICE_AUTH_REQ_SENT; 184 TRACE(("leave cli_sessionloop: sent userauth service req")) 185 return; 186 187 /* userauth code */ 188 case SERVICE_AUTH_ACCEPT_RCVD: 189 cli_auth_getmethods(); 190 cli_ses.state = USERAUTH_REQ_SENT; 191 TRACE(("leave cli_sessionloop: sent userauth methods req")) 192 return; 193 194 case USERAUTH_FAIL_RCVD: 195 cli_auth_try(); 196 cli_ses.state = USERAUTH_REQ_SENT; 197 TRACE(("leave cli_sessionloop: cli_auth_try")) 198 return; 199 200 /* 201 case USERAUTH_SUCCESS_RCVD: 202 send_msg_service_request(SSH_SERVICE_CONNECTION); 203 cli_ses.state = SERVICE_CONN_REQ_SENT; 204 TRACE(("leave cli_sessionloop: sent ssh-connection service req")) 205 return; 206 207 case SERVICE_CONN_ACCEPT_RCVD: 208 cli_send_chansess_request(); 209 TRACE(("leave cli_sessionloop: cli_send_chansess_request")) 210 cli_ses.state = SESSION_RUNNING; 211 return; 212 */ 213 214 case USERAUTH_SUCCESS_RCVD: 215 216 if (cli_opts.backgrounded) { 217 int devnull; 218 /* keeping stdin open steals input from the terminal and 219 is confusing, though stdout/stderr could be useful. */ 220 devnull = open(_PATH_DEVNULL, O_RDONLY); 221 if (devnull < 0) { 222 dropbear_exit("opening /dev/null: %d %s", 223 errno, strerror(errno)); 224 } 225 dup2(devnull, STDIN_FILENO); 226 if (daemon(0, 1) < 0) { 227 dropbear_exit("Backgrounding failed: %d %s", 228 errno, strerror(errno)); 229 } 230 } 231 232 #ifdef ENABLE_CLI_LOCALTCPFWD 233 setup_localtcp(); 234 #endif 235 #ifdef ENABLE_CLI_REMOTETCPFWD 236 setup_remotetcp(); 237 #endif 238 if (!cli_opts.no_cmd) { 239 cli_send_chansess_request(); 240 } 241 TRACE(("leave cli_sessionloop: running")) 242 cli_ses.state = SESSION_RUNNING; 243 return; 244 245 case SESSION_RUNNING: 246 if (ses.chancount < 1 && !cli_opts.no_cmd) { 247 cli_finished(); 248 } 249 250 if (cli_ses.winchange) { 251 cli_chansess_winchange(); 252 } 253 return; 254 255 /* XXX more here needed */ 256 257 258 default: 259 break; 260 } 261 262 TRACE(("leave cli_sessionloop: fell out")) 263 264 } 265 266 void cli_session_cleanup() { 267 268 if (!sessinitdone) { 269 return; 270 } 271 272 /* Set std{in,out,err} back to non-blocking - busybox ash dies nastily if 273 * we don't revert the flags */ 274 fcntl(cli_ses.stdincopy, F_SETFL, cli_ses.stdinflags); 275 fcntl(cli_ses.stdoutcopy, F_SETFL, cli_ses.stdoutflags); 276 fcntl(cli_ses.stderrcopy, F_SETFL, cli_ses.stderrflags); 277 278 cli_tty_cleanup(); 279 280 } 281 282 static void cli_finished() { 283 284 cli_session_cleanup(); 285 common_session_cleanup(); 286 fprintf(stderr, "Connection to %s@%s:%s closed.\n", cli_opts.username, 287 cli_opts.remotehost, cli_opts.remoteport); 288 exit(cli_ses.retval); 289 } 290 291 292 /* called when the remote side closes the connection */ 293 static void cli_remoteclosed() { 294 295 /* XXX TODO perhaps print a friendlier message if we get this but have 296 * already sent/received disconnect message(s) ??? */ 297 close(ses.sock); 298 ses.sock = -1; 299 dropbear_exit("remote closed the connection"); 300 } 301 302 /* Operates in-place turning dirty (untrusted potentially containing control 303 * characters) text into clean text. 304 * Note: this is safe only with ascii - other charsets could have problems. */ 305 void cleantext(unsigned char* dirtytext) { 306 307 unsigned int i, j; 308 unsigned char c; 309 310 j = 0; 311 for (i = 0; dirtytext[i] != '\0'; i++) { 312 313 c = dirtytext[i]; 314 /* We can ignore '\r's */ 315 if ( (c >= ' ' && c <= '~') || c == '\n' || c == '\t') { 316 dirtytext[j] = c; 317 j++; 318 } 319 } 320 /* Null terminate */ 321 dirtytext[j] = '\0'; 322 } 323