X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/80ffa58b3185ab7a88830235b2b3a4bd3ec4021a..8154b6022bd42b4aba03fc9adcbabd4f588b6575:/ssh.c diff --git a/ssh.c b/ssh.c index 825731e8..b9d906d8 100644 --- a/ssh.c +++ b/ssh.c @@ -726,10 +726,23 @@ struct ssh_tag { Config cfg; /* - * Used to transfer data back from async agent callbacks. + * Used to transfer data back from async callbacks. */ void *agent_response; int agent_response_len; + int user_response; + + /* + * The SSH connection can be set as `frozen', meaning we are + * not currently accepting incoming data from the network. This + * is slightly more serious than setting the _socket_ as + * frozen, because we may already have had data passed to us + * from the network which we need to delay processing until + * after the freeze is lifted, so we also need a bufchain to + * store that data. + */ + int frozen; + bufchain queued_incoming_data; /* * Dispatch table for packet types that we may have to deal @@ -2331,6 +2344,50 @@ static int do_ssh_init(Ssh ssh, unsigned char c) crFinish(0); } +static void ssh_process_incoming_data(Ssh ssh, + unsigned char **data, int *datalen) +{ + struct Packet *pktin = ssh->s_rdpkt(ssh, data, datalen); + if (pktin) { + ssh->protocol(ssh, NULL, 0, pktin); + ssh_free_packet(pktin); + } +} + +static void ssh_queue_incoming_data(Ssh ssh, + unsigned char **data, int *datalen) +{ + bufchain_add(&ssh->queued_incoming_data, *data, *datalen); + *data += *datalen; + *datalen = 0; +} + +static void ssh_process_queued_incoming_data(Ssh ssh) +{ + void *vdata; + unsigned char *data; + int len, origlen; + + while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) { + bufchain_prefix(&ssh->queued_incoming_data, &vdata, &len); + data = vdata; + origlen = len; + + while (!ssh->frozen && len > 0) + ssh_process_incoming_data(ssh, &data, &len); + + if (origlen > len) + bufchain_consume(&ssh->queued_incoming_data, origlen - len); + } +} + +static void ssh_set_frozen(Ssh ssh, int frozen) +{ + if (ssh->s) + sk_set_frozen(ssh->s, frozen); + ssh->frozen = frozen; +} + static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) { crBegin(ssh->ssh_gotdata_crstate); @@ -2360,13 +2417,19 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) */ if (datalen == 0) crReturnV; + + /* + * Process queued data if there is any. + */ + ssh_process_queued_incoming_data(ssh); + while (1) { while (datalen > 0) { - struct Packet *pktin = ssh->s_rdpkt(ssh, &data, &datalen); - if (pktin) { - ssh->protocol(ssh, NULL, 0, pktin); - ssh_free_packet(pktin); - } + if (ssh->frozen) + ssh_queue_incoming_data(ssh, &data, &datalen); + + ssh_process_incoming_data(ssh, &data, &datalen); + if (ssh->state == SSH_STATE_CLOSED) return; } @@ -2409,6 +2472,20 @@ static int ssh_do_close(Ssh ssh, int notify_exit) sfree(c); } } + /* + * Go through port-forwardings, and close any associated + * listening sockets. + */ + if (ssh->portfwds) { + struct ssh_portfwd *pf; + while (NULL != (pf = index234(ssh->portfwds, 0))) { + /* Dispose of any listening socket. */ + if (pf->local) + pfd_terminate(pf->local); + del234(ssh->portfwds, pf); /* moving next one to index 0 */ + free_portfwd(pf); + } + } return ret; } @@ -2540,9 +2617,9 @@ static void ssh1_throttle(Ssh ssh, int adjust) ssh->v1_throttle_count += adjust; assert(ssh->v1_throttle_count >= 0); if (ssh->v1_throttle_count && !old_count) { - sk_set_frozen(ssh->s, 1); + ssh_set_frozen(ssh, 1); } else if (!ssh->v1_throttle_count && old_count) { - sk_set_frozen(ssh->s, 0); + ssh_set_frozen(ssh, 0); } } @@ -2666,6 +2743,24 @@ static void ssh_agent_callback(void *sshv, void *reply, int replylen) do_ssh2_authconn(ssh, NULL, -1, NULL); } +static void ssh_dialog_callback(void *sshv, int ret) +{ + Ssh ssh = (Ssh) sshv; + + ssh->user_response = ret; + + if (ssh->version == 1) + do_ssh1_login(ssh, NULL, -1, NULL); + else + do_ssh2_transport(ssh, NULL, -1, NULL); + + /* + * This may have unfrozen the SSH connection, so do a + * queued-data run. + */ + ssh_process_queued_incoming_data(ssh); +} + static void ssh_agentf_callback(void *cv, void *reply, int replylen) { struct ssh_channel *c = (struct ssh_channel *)cv; @@ -2727,6 +2822,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, Bignum challenge; char *commentp; int commentlen; + int dlgret; }; crState(do_ssh1_login_state); @@ -2814,10 +2910,31 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, fatalbox("Out of memory"); rsastr_fmt(keystr, &hostkey); rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey); - verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, "rsa", keystr, - fingerprint); + + ssh_set_frozen(ssh, 1); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + "rsa", keystr, fingerprint, + ssh_dialog_callback, ssh); sfree(keystr); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + + if (s->dlgret == 0) { + ssh->close_expected = TRUE; + ssh_closing((Plug)ssh, NULL, 0, 0); + crStop(0); + } } for (i = 0; i < 32; i++) { @@ -2879,9 +2996,26 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, /* Warn about chosen cipher if necessary. */ if (warn) { - sk_set_frozen(ssh->s, 1); - askalg(ssh->frontend, "cipher", cipher_string); - sk_set_frozen(ssh->s, 0); + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, "cipher", cipher_string, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh->close_expected = TRUE; + ssh_closing((Plug)ssh, NULL, 0, 0); + crStop(0); + } } } @@ -4487,6 +4621,8 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, */ { char *cmd = ssh->cfg.remote_cmd_ptr; + + if (!cmd) cmd = ssh->cfg.remote_cmd; if (ssh->cfg.ssh_subsys && ssh->cfg.remote_cmd_ptr2) { cmd = ssh->cfg.remote_cmd_ptr2; @@ -4696,7 +4832,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, { unsigned char *in = (unsigned char *)vin; struct do_ssh2_transport_state { - int nbits, pbits, warn; + int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher; Bignum p, g, e, f, K; int kex_init_value, kex_reply_value; const struct ssh_mac **maclist; @@ -4718,6 +4854,9 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, const struct ssh_compress *preferred_comp; int got_session_id, activated_authconn; struct Packet *pktout; + int dlgret; + int guessok; + int ignorepkt; }; crState(do_ssh2_transport_state); @@ -4930,8 +5069,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, * to. */ { - char *str; - int i, j, len, guessok; + char *str, *preferred; + int i, j, len; if (pktin->type != SSH2_MSG_KEXINIT) { bombout(("expected key exchange packet from server")); @@ -4945,25 +5084,23 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, s->scmac_tobe = NULL; s->cscomp_tobe = NULL; s->sccomp_tobe = NULL; + s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE; + pktin->savedpos += 16; /* skip garbage cookie */ ssh_pkt_getstring(pktin, &str, &len); /* key exchange algorithms */ - s->warn = 0; + + preferred = NULL; for (i = 0; i < s->n_preferred_kex; i++) { const struct ssh_kex *k = s->preferred_kex[i]; if (!k) { - s->warn = 1; - } else if (in_commasep_string(k->name, str, len)) { - ssh->kex = k; + s->warn_kex = TRUE; + } else { + if (!preferred) preferred = k->name; + if (in_commasep_string(k->name, str, len)) + ssh->kex = k; } - if (ssh->kex) { - if (s->warn) { - sk_set_frozen(ssh->s, 1); - askalg(ssh->frontend, "key-exchange algorithm", - ssh->kex->name); - sk_set_frozen(ssh->s, 0); - } + if (ssh->kex) break; - } } if (!ssh->kex) { bombout(("Couldn't agree a key exchange algorithm (available: %s)", @@ -4975,8 +5112,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, * the first algorithm in our list, even if it's still the algorithm * we end up using. */ - guessok = - first_in_commasep_string(s->preferred_kex[0]->name, str, len); + s->guessok = first_in_commasep_string(preferred, str, len); ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */ for (i = 0; i < lenof(hostkey_algs); i++) { if (in_commasep_string(hostkey_algs[i]->name, str, len)) { @@ -4984,14 +5120,13 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, break; } } - guessok = guessok && + s->guessok = s->guessok && first_in_commasep_string(hostkey_algs[0]->name, str, len); ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */ - s->warn = 0; for (i = 0; i < s->n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = s->preferred_ciphers[i]; if (!c) { - s->warn = 1; + s->warn_cscipher = TRUE; } else { for (j = 0; j < c->nciphers; j++) { if (in_commasep_string(c->list[j]->name, str, len)) { @@ -5000,15 +5135,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } } } - if (s->cscipher_tobe) { - if (s->warn) { - sk_set_frozen(ssh->s, 1); - askalg(ssh->frontend, "client-to-server cipher", - s->cscipher_tobe->name); - sk_set_frozen(ssh->s, 0); - } + if (s->cscipher_tobe) break; - } } if (!s->cscipher_tobe) { bombout(("Couldn't agree a client-to-server cipher (available: %s)", @@ -5017,11 +5145,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */ - s->warn = 0; for (i = 0; i < s->n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = s->preferred_ciphers[i]; if (!c) { - s->warn = 1; + s->warn_sccipher = TRUE; } else { for (j = 0; j < c->nciphers; j++) { if (in_commasep_string(c->list[j]->name, str, len)) { @@ -5030,15 +5157,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } } } - if (s->sccipher_tobe) { - if (s->warn) { - sk_set_frozen(ssh->s, 1); - askalg(ssh->frontend, "server-to-client cipher", - s->sccipher_tobe->name); - sk_set_frozen(ssh->s, 0); - } + if (s->sccipher_tobe) break; - } } if (!s->sccipher_tobe) { bombout(("Couldn't agree a server-to-client cipher (available: %s)", @@ -5080,7 +5200,83 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } ssh_pkt_getstring(pktin, &str, &len); /* client->server language */ ssh_pkt_getstring(pktin, &str, &len); /* server->client language */ - if (ssh2_pkt_getbool(pktin) && !guessok) /* first_kex_packet_follows */ + s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok; + + if (s->warn_kex) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, "key-exchange algorithm", + ssh->kex->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh->close_expected = TRUE; + ssh_closing((Plug)ssh, NULL, 0, 0); + crStop(0); + } + } + + if (s->warn_cscipher) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, + "client-to-server cipher", + s->cscipher_tobe->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh->close_expected = TRUE; + ssh_closing((Plug)ssh, NULL, 0, 0); + crStop(0); + } + } + + if (s->warn_sccipher) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, + "server-to-client cipher", + s->sccipher_tobe->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh->close_expected = TRUE; + ssh_closing((Plug)ssh, NULL, 0, 0); + crStop(0); + } + } + + if (s->ignorepkt) /* first_kex_packet_follows */ crWaitUntil(pktin); /* Ignore packet */ } @@ -5204,11 +5400,29 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, */ s->keystr = ssh->hostkey->fmtkey(s->hkey); s->fingerprint = ssh->hostkey->fingerprint(s->hkey); - sk_set_frozen(ssh->s, 1); - verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, ssh->hostkey->keytype, - s->keystr, s->fingerprint); - sk_set_frozen(ssh->s, 0); + ssh_set_frozen(ssh, 1); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + ssh->hostkey->keytype, s->keystr, + s->fingerprint, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh->close_expected = TRUE; + ssh_closing((Plug)ssh, NULL, 0, 0); + crStop(0); + } if (!s->got_session_id) { /* don't bother logging this in rekeys */ logevent("Host key fingerprint is:"); logevent(s->fingerprint); @@ -7117,6 +7331,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } else { subsys = ssh->cfg.ssh_subsys; cmd = ssh->cfg.remote_cmd_ptr; + if (!cmd) cmd = ssh->cfg.remote_cmd; } s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); @@ -7463,6 +7678,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->queueing = FALSE; ssh->qhead = ssh->qtail = NULL; ssh->deferred_rekey_reason = NULL; + bufchain_init(&ssh->queued_incoming_data); + ssh->frozen = FALSE; *backend_handle = ssh; @@ -7589,6 +7806,7 @@ static void ssh_free(void *handle) expire_timer_context(ssh); if (ssh->pinger) pinger_free(ssh->pinger); + bufchain_clear(&ssh->queued_incoming_data); sfree(ssh); random_unref();