X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/edd4e662224034b394d7d33a6689c4aae2ff97d8..5fe290992c445306bfc0f6757959ed4ff285be26:/ssh.c diff --git a/ssh.c b/ssh.c index 605e0e6f..2a97bb52 100644 --- a/ssh.c +++ b/ssh.c @@ -679,7 +679,8 @@ struct ssh_tag { const struct plug_function_table *fn; /* the above field _must_ be first in the structure */ - SHA_State exhash, exhashbase; + char *v_c, *v_s; + SHA_State exhash; Socket s; @@ -721,6 +722,7 @@ struct ssh_tag { struct ssh_channel *mainchan; /* primary session channel */ int exitcode; int close_expected; + int clean_exit; tree234 *rportfwds, *portfwds; @@ -1266,7 +1268,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); @@ -1651,8 +1654,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); @@ -2245,10 +2246,13 @@ 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) || + /* All versions <= 1.2.6 (they changed their format in 1.2.7) */ + wc_match("WeOnlyDo-*", imp)))) { /* * These versions have the SSH-2 rekey bug. */ @@ -2294,23 +2298,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; @@ -2384,15 +2385,19 @@ static int do_ssh_init(Ssh ssh, unsigned char c) ssh_fix_verstring(verstring); if (ssh->version == 2) { + size_t len; /* * Hash our version string and their version string. */ - SHA_Init(&ssh->exhashbase); - sha_string(&ssh->exhashbase, verstring, - strcspn(verstring, "\015\012")); - sha_string(&ssh->exhashbase, s->vstring, - strcspn(s->vstring, "\015\012")); - + len = strcspn(verstring, "\015\012"); + ssh->v_c = snewn(len + 1, char); + memcpy(ssh->v_c, verstring, len); + ssh->v_c[len] = 0; + len = strcspn(s->vstring, "\015\012"); + ssh->v_s = snewn(len + 1, char); + memcpy(ssh->v_s, s->vstring, len); + ssh->v_s[len] = 0; + /* * Initialise SSH-2 protocol. */ @@ -2497,24 +2502,29 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) * everything to s_rdpkt, and then pass the resulting packets * to the proper protocol handler. */ - if (datalen == 0) - crReturnV; - - /* - * Process queued data if there is any. - */ - ssh_process_queued_incoming_data(ssh); while (1) { - while (datalen > 0) { - if (ssh->frozen) + while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) { + if (ssh->frozen) { ssh_queue_incoming_data(ssh, &data, &datalen); - - ssh_process_incoming_data(ssh, &data, &datalen); - + /* This uses up all data and cannot cause anything interesting + * to happen; indeed, for anything to happen at all, we must + * return, so break out. */ + break; + } else if (bufchain_size(&ssh->queued_incoming_data) > 0) { + /* This uses up some or all data, and may freeze the + * session. */ + ssh_process_queued_incoming_data(ssh); + } else { + /* This uses up some or all data, and may freeze the + * session. */ + ssh_process_incoming_data(ssh, &data, &datalen); + } + /* FIXME this is probably EBW. */ if (ssh->state == SSH_STATE_CLOSED) return; } + /* We're out of data. Go and get some more. */ crReturnV; } crFinishV; @@ -2596,20 +2606,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; } @@ -2656,8 +2666,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) @@ -2873,6 +2881,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, @@ -2977,8 +3018,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. @@ -2990,8 +3029,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); @@ -3015,8 +3052,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); } } @@ -3096,8 +3133,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); } } @@ -3171,9 +3208,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 { @@ -3510,13 +3545,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"); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); - connection_fatal(ssh->frontend, "Unable to authenticate"); + ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, FALSE); crStop(1); } } else { @@ -4564,8 +4593,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 */ @@ -4951,6 +4979,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, struct do_ssh2_transport_state { int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher; Bignum p, g, e, f, K; + void *our_kexinit; + int our_kexinitlen; int kex_init_value, kex_reply_value; const struct ssh_mac **maclist; int nmacs; @@ -5174,15 +5204,14 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh2_pkt_adduint32(s->pktout, 0); } - ssh->exhash = ssh->exhashbase; - sha_string(&ssh->exhash, s->pktout->data + 5, s->pktout->length - 5); + s->our_kexinitlen = s->pktout->length - 5; + s->our_kexinit = snewn(s->our_kexinitlen, unsigned char); + memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen); ssh2_pkt_send_noqueue(ssh, s->pktout); if (!pktin) crWaitUntil(pktin); - if (pktin->length > 5) - sha_string(&ssh->exhash, pktin->data + 5, pktin->length - 5); /* * Now examine the other side's KEXINIT to see what we're up @@ -5340,8 +5369,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); } } @@ -5365,8 +5394,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); } } @@ -5390,12 +5419,21 @@ 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); } } + SHA_Init(&ssh->exhash); + sha_string(&ssh->exhash, ssh->v_c, strlen(ssh->v_c)); + sha_string(&ssh->exhash, ssh->v_s, strlen(ssh->v_s)); + sha_string(&ssh->exhash, s->our_kexinit, s->our_kexinitlen); + sfree(s->our_kexinit); + if (pktin->length > 5) + sha_string(&ssh->exhash, pktin->data + 5, pktin->length - 5); + + if (s->ignorepkt) /* first_kex_packet_follows */ crWaitUntil(pktin); /* Ignore packet */ } @@ -5539,8 +5577,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 */ @@ -5706,7 +5744,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; @@ -6021,8 +6059,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 @@ -6034,14 +6070,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); } } @@ -6128,18 +6157,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); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); - connection_fatal(ssh->frontend, "%s", buf); + char *buf = dupprintf("Received channel request for nonexistent" + " channel %d", localid); + ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); + sfree(buf); return; } @@ -6349,7 +6370,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 { @@ -6548,9 +6569,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 { @@ -7101,17 +7120,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"); - ssh->close_expected = TRUE; - ssh_closing((Plug)ssh, NULL, 0, 0); - connection_fatal(ssh->frontend, - "Unable to authenticate"); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + FALSE); crStopV; } } else { @@ -7262,18 +7273,10 @@ 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; } } @@ -7847,6 +7850,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; @@ -7872,6 +7876,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->do_ssh1_login_state = NULL; ssh->do_ssh2_transport_state = NULL; ssh->do_ssh2_authconn_state = NULL; + ssh->v_c = NULL; + ssh->v_s = NULL; ssh->mainchan = NULL; ssh->throttled_all = 0; ssh->v1_stdout_throttling = 0; @@ -7999,6 +8005,8 @@ static void ssh_free(void *handle) sfree(ssh->do_ssh1_login_state); sfree(ssh->do_ssh2_transport_state); sfree(ssh->do_ssh2_authconn_state); + sfree(ssh->v_c); + sfree(ssh->v_s); if (ssh->crcda_ctx) { crcda_free_context(ssh->crcda_ctx); ssh->crcda_ctx = NULL; @@ -8072,7 +8080,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) { @@ -8229,7 +8237,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'). */