X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/f68353be68bbe6499b4c36efcd6c8302c26ff5cb..985b644054f5e0d777184c48bfd8a97e824705fd:/ssh.c diff --git a/ssh.c b/ssh.c index b5fa3f21..e0556a62 100644 --- a/ssh.c +++ b/ssh.c @@ -721,6 +721,7 @@ struct ssh_tag { struct ssh_channel *mainchan; /* primary session channel */ int exitcode; int close_expected; + int clean_exit; tree234 *rportfwds, *portfwds; @@ -748,6 +749,7 @@ struct ssh_tag { */ int fallback_cmd; + bufchain banner; /* accumulates banners during do_ssh2_authconn */ /* * Used for username and password input. */ @@ -1265,7 +1267,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) * _Completely_ silly lengths should be stomped on before they * do us any more damage. */ - if (st->len < 0 || st->pad < 0 || st->len + st->pad < 0) { + if (st->len < 0 || st->len > 35000 || st->pad < 4 || + st->len - st->pad < 1 || (st->len + 4) % st->cipherblk != 0) { bombout(("Incoming packet was garbled on decryption")); ssh_free_packet(st->pktin); crStop(NULL); @@ -1650,8 +1653,6 @@ static unsigned char *ssh2_mpint_fmt(Bignum b, int *len) unsigned char *p; int i, n = (bignum_bitcount(b) + 7) / 8; p = snewn(n + 1, unsigned char); - if (!p) - fatalbox("out of memory"); p[0] = 0; for (i = 1; i <= n; i++) p[i] = bignum_byte(b, n - i); @@ -2244,10 +2245,12 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring) if (ssh->cfg.sshbug_rekey2 == FORCE_ON || (ssh->cfg.sshbug_rekey2 == AUTO && - (wc_match("OpenSSH_2.[0-4]*", imp) || + (wc_match("DigiSSH_2.0", imp) || + wc_match("OpenSSH_2.[0-4]*", imp) || wc_match("OpenSSH_2.5.[0-3]*", imp) || wc_match("Sun_SSH_1.0", imp) || - wc_match("Sun_SSH_1.0.1", imp)))) { + wc_match("Sun_SSH_1.0.1", imp) || + wc_match("WeOnlyDo-1.2.6", imp)))) { /* * These versions have the SSH-2 rekey bug. */ @@ -2293,23 +2296,20 @@ static int do_ssh_init(Ssh ssh, unsigned char c) crBegin(ssh->do_ssh_init_crstate); - /* Search for the string "SSH-" in the input. */ - s->i = 0; - while (1) { - static const int transS[] = { 1, 2, 2, 1 }; - static const int transH[] = { 0, 0, 3, 0 }; - static const int transminus[] = { 0, 0, 0, -1 }; - if (c == 'S') - s->i = transS[s->i]; - else if (c == 'H') - s->i = transH[s->i]; - else if (c == '-') - s->i = transminus[s->i]; - else - s->i = 0; - if (s->i < 0) - break; - crReturn(1); /* get another character */ + /* Search for a line beginning with the string "SSH-" in the input. */ + for (;;) { + if (c != 'S') goto no; + crReturn(1); + if (c != 'S') goto no; + crReturn(1); + if (c != 'H') goto no; + crReturn(1); + if (c != '-') goto no; + break; + no: + while (c != '\012') + crReturn(1); + crReturn(1); } s->vstrsize = 16; @@ -2595,20 +2595,20 @@ static int ssh_closing(Plug plug, const char *error_msg, int error_code, Ssh ssh = (Ssh) plug; int need_notify = ssh_do_close(ssh, FALSE); - if (!error_msg && !ssh->close_expected) { - error_msg = "Server unexpectedly closed network connection"; + if (!error_msg) { + if (!ssh->close_expected) + error_msg = "Server unexpectedly closed network connection"; + else + error_msg = "Server closed network connection"; } if (need_notify) notify_remote_exit(ssh->frontend); - if (error_msg) { - /* A socket error has occurred. */ + if (error_msg) logevent(error_msg); + if (!ssh->close_expected || !ssh->clean_exit) connection_fatal(ssh->frontend, "%s", error_msg); - } else { - logevent("Server closed network connection"); - } return 0; } @@ -2655,8 +2655,6 @@ static const char *connect_to_host(Ssh ssh, char *host, int port, const char *err; ssh->savedhost = snewn(1 + strlen(host), char); - if (!ssh->savedhost) - fatalbox("Out of memory"); strcpy(ssh->savedhost, host); if (port < 0) @@ -2872,6 +2870,39 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen) } /* + * Client-initiated disconnection. Send a DISCONNECT if `wire_reason' + * non-NULL, otherwise just close the connection. `client_reason' == NULL + * => log `wire_reason'. + */ +static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason, + int code, int clean_exit) +{ + char *error; + if (!client_reason) + client_reason = wire_reason; + if (client_reason) + error = dupprintf("Disconnected: %s", client_reason); + else + error = dupstr("Disconnected"); + if (wire_reason) { + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason, + PKT_END); + } else if (ssh->version == 2) { + struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); + ssh2_pkt_adduint32(pktout, code); + ssh2_pkt_addstring(pktout, wire_reason); + ssh2_pkt_addstring(pktout, "en"); /* language tag */ + ssh2_pkt_send_noqueue(ssh, pktout); + } + } + ssh->close_expected = TRUE; + ssh->clean_exit = clean_exit; + ssh_closing((Plug)ssh, error, 0, 0); + sfree(error); +} + +/* * Handle the key exchange and user authentication phases. */ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, @@ -2976,8 +3007,6 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes); s->rsabuf = snewn(s->len, unsigned char); - if (!s->rsabuf) - fatalbox("Out of memory"); /* * Verify the host key. @@ -2989,8 +3018,6 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, int len = rsastr_len(&hostkey); char fingerprint[100]; char *keystr = snewn(len, char); - if (!keystr) - fatalbox("Out of memory"); rsastr_fmt(keystr, &hostkey); rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey); @@ -3014,8 +3041,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "User aborted at host key verification", + NULL, 0, TRUE); crStop(0); } } @@ -3095,8 +3122,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, } ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); crStop(0); } } @@ -3170,9 +3197,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, * get_line failed to get a username. * Terminate. */ - logevent("No username provided. Abandoning session."); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); crStop(1); } } else { @@ -3509,13 +3534,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, * because one was supplied on the command line * which has already failed to work). Terminate. */ - send_packet(ssh, SSH1_MSG_DISCONNECT, - PKT_STR, "No more passwords available to try", - PKT_END); - logevent("Unable to authenticate"); - connection_fatal(ssh->frontend, "Unable to authenticate"); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, FALSE); crStop(1); } } else { @@ -4563,8 +4582,7 @@ static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin) * encrypted packet, we close the session once * we've sent EXIT_CONFIRMATION. */ - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, NULL, NULL, 0, TRUE); } /* Helper function to deal with sending tty modes for REQUEST_PTY */ @@ -5339,8 +5357,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "User aborted at kex warning", NULL, + 0, TRUE); crStop(0); } } @@ -5364,8 +5382,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); crStop(0); } } @@ -5389,8 +5407,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); crStop(0); } } @@ -5538,8 +5556,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "User aborted at host key verification", NULL, + 0, TRUE); crStop(0); } if (!s->got_session_id) { /* don't bother logging this in rekeys */ @@ -5705,7 +5723,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, * it would only confuse the layer above. */ if (s->activated_authconn) { - crReturn(1); + crReturn(0); } s->activated_authconn = TRUE; @@ -6020,8 +6038,6 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) * not running in -N mode.) */ if (!ssh->cfg.ssh_no_shell && count234(ssh->channels) == 0) { - logevent("All channels closed. Disconnecting"); -#if 0 /* * We used to send SSH_MSG_DISCONNECT here, * because I'd believed that _every_ conforming @@ -6033,14 +6049,7 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) * this is more polite than sending a * DISCONNECT. So now we don't. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); - ssh2_pkt_adduint32(s->pktout, SSH2_DISCONNECT_BY_APPLICATION); - ssh2_pkt_addstring(s->pktout, "All open channels closed"); - ssh2_pkt_addstring(s->pktout, "en"); /* language tag */ - ssh2_pkt_send_noqueue(ssh, s->pktout); -#endif - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); } } @@ -6127,18 +6136,10 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) */ c = find234(ssh->channels, &localid, ssh_channelfind); if (!c) { - char buf[80]; - sprintf(buf, "Received channel request for nonexistent" - " channel %d", localid); - logevent(buf); - pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); - ssh2_pkt_adduint32(pktout, SSH2_DISCONNECT_BY_APPLICATION); - ssh2_pkt_addstring(pktout, buf); - ssh2_pkt_addstring(pktout, "en"); /* language tag */ - ssh2_pkt_send_noqueue(ssh, pktout); - connection_fatal(ssh->frontend, "%s", buf); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + char *buf = dupprintf("Received channel request for nonexistent" + " channel %d", localid); + ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); + sfree(buf); return; } @@ -6348,7 +6349,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) } } } else if (typelen == 22 && - !memcmp(type, "auth-agent@openssh.com", 3)) { + !memcmp(type, "auth-agent@openssh.com", 22)) { if (!ssh->agentfwd_enabled) error = "Agent forwarding is not enabled"; else { @@ -6387,6 +6388,21 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) } } +/* + * Buffer banner messages for later display at some convenient point. + */ +static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin) +{ + /* Arbitrary limit to prevent unbounded inflation of buffer */ + if (bufchain_size(&ssh->banner) <= 131072) { + char *banner = NULL; + int size = 0; + ssh_pkt_getstring(pktin, &banner, &size); + if (banner) + bufchain_add(&ssh->banner, banner, size); + } +} + /* Helper function to deal with sending tty modes for "pty-req" */ static void ssh2_send_ttymode(void *data, char *mode, char *val) { @@ -6428,6 +6444,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, AUTH_TYPE_KEYBOARD_INTERACTIVE, AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET } type; + int done_service_req; int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; int tried_pubkey_config, tried_agent; int kbd_inter_running, kbd_inter_refused; @@ -6455,16 +6472,33 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, crBegin(ssh->do_ssh2_authconn_crstate); - /* - * Request userauth protocol, and await a response to it. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); - ssh2_pkt_addstring(s->pktout, "ssh-userauth"); - ssh2_pkt_send(ssh, s->pktout); - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) { - bombout(("Server refused user authentication protocol")); - crStopV; + s->done_service_req = FALSE; + s->we_are_in = FALSE; + if (!ssh->cfg.ssh_no_userauth) { + /* + * Request userauth protocol, and await a response to it. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring(s->pktout, "ssh-userauth"); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) + s->done_service_req = TRUE; + } + if (!s->done_service_req) { + /* + * Request connection protocol directly, without authentication. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) { + s->we_are_in = TRUE; /* no auth required */ + } else { + bombout(("Server refused service request")); + crStopV; + } } /* @@ -6493,7 +6527,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, */ s->username[0] = '\0'; s->got_username = FALSE; - do { + bufchain_init(&ssh->banner); + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = + ssh2_msg_userauth_banner; + while (!s->we_are_in) { /* * Get a username. */ @@ -6511,9 +6548,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * get_line failed to get a username. * Terminate. */ - logevent("No username provided. Abandoning session."); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); crStopV; } } else { @@ -6593,9 +6628,14 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, */ if (!s->gotit) crWaitUntilV(pktin); - while (pktin->type == SSH2_MSG_USERAUTH_BANNER) { - char *banner; - int size; + /* + * Now is a convenient point to spew any banner material + * that we've accumulated. (This should ensure that when + * we exit the auth loop, we haven't any left to deal + * with.) + */ + { + int size = bufchain_size(&ssh->banner); /* * Don't show the banner if we're operating in * non-verbose non-interactive mode. (It's probably @@ -6604,12 +6644,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * the banner will screw up processing on the * output of (say) plink.) */ - if (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE)) { - ssh_pkt_getstring(pktin, &banner, &size); - if (banner) - c_write_untrusted(ssh, banner, size); + if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) { + char *banner = snewn(size, char); + bufchain_fetch(&ssh->banner, banner, size); + c_write_untrusted(ssh, banner, size); + sfree(banner); } - crWaitUntilV(pktin); + bufchain_clear(&ssh->banner); } if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { logevent("Access granted"); @@ -7058,17 +7099,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * command line which has already failed to * work). Terminate. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); - ssh2_pkt_adduint32(s->pktout,SSH2_DISCONNECT_BY_APPLICATION); - ssh2_pkt_addstring(s->pktout, "No more passwords available" - " to try"); - ssh2_pkt_addstring(s->pktout, "en"); /* language tag */ - ssh2_pkt_send_noqueue(ssh, s->pktout); - logevent("Unable to authenticate"); - connection_fatal(ssh->frontend, - "Unable to authenticate"); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + FALSE); crStopV; } } else { @@ -7219,27 +7252,18 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; } else { - c_write_str(ssh, "No supported authentication methods" - " left to try!\r\n"); - logevent("No supported authentications offered." - " Disconnecting"); - s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); - ssh2_pkt_adduint32(s->pktout, SSH2_DISCONNECT_BY_APPLICATION); - ssh2_pkt_addstring(s->pktout, "No supported authentication" - " methods available"); - ssh2_pkt_addstring(s->pktout, "en"); /* language tag */ - ssh2_pkt_send_noqueue(ssh, s->pktout); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); + ssh_disconnect(ssh, NULL, + "No supported authentication methods available", + SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + FALSE); crStopV; } } - } while (!s->we_are_in); + } + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; /* - * Now we're authenticated for the connection protocol. The - * connection protocol will automatically have started at this - * point; there's no need to send SERVICE_REQUEST. + * Now the connection protocol has started, one way or another. */ ssh->channels = newtree234(ssh_channelcmp); @@ -7805,6 +7829,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->hostkey = NULL; ssh->exitcode = -1; ssh->close_expected = FALSE; + ssh->clean_exit = FALSE; ssh->state = SSH_STATE_PREPACKET; ssh->size_needed = FALSE; ssh->eof_needed = FALSE; @@ -7982,7 +8007,8 @@ static void ssh_reconfig(void *handle, Config *cfg) unsigned long old_max_data_size; pinger_reconfig(ssh->pinger, &ssh->cfg, cfg); - ssh_setup_portfwd(ssh, cfg); + if (ssh->portfwds) + ssh_setup_portfwd(ssh, cfg); if (ssh->cfg.ssh_rekey_time != cfg->ssh_rekey_time && cfg->ssh_rekey_time != 0) { @@ -8029,7 +8055,7 @@ static void ssh_reconfig(void *handle, Config *cfg) } /* - * Called to send data down the Telnet connection. + * Called to send data down the SSH connection. */ static int ssh_send(void *handle, char *buf, int len) { @@ -8186,7 +8212,7 @@ static const struct telnet_special *ssh_get_specials(void *handle) } /* - * Send Telnet special codes. TS_EOF is useful for `plink', so you + * Send special codes. TS_EOF is useful for `plink', so you * can send an EOF and collect resulting output (e.g. `plink * hostname sort'). */