X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/95d2d26289607ef7e94bf3053aba99a54f83c654..8154b6022bd42b4aba03fc9adcbabd4f588b6575:/ssh.c diff --git a/ssh.c b/ssh.c index ac9d47e9..b9d906d8 100644 --- a/ssh.c +++ b/ssh.c @@ -436,6 +436,8 @@ struct ssh_channel { Ssh ssh; /* pointer back to main context */ unsigned remoteid, localid; int type; + /* True if we opened this channel but server hasn't confirmed. */ + int halfopen; /* * In SSH1, this value contains four bits: * @@ -550,9 +552,9 @@ struct Packet { struct logblank_t *blanks; }; -static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, +static void ssh1_protocol(Ssh ssh, void *vin, int inlen, struct Packet *pktin); -static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, +static void ssh2_protocol(Ssh ssh, void *vin, int inlen, struct Packet *pktin); static void ssh1_protocol_setup(Ssh ssh); static void ssh2_protocol_setup(Ssh ssh); @@ -568,7 +570,7 @@ static unsigned long ssh_pkt_getuint32(struct Packet *pkt); static int ssh2_pkt_getbool(struct Packet *pkt); static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length); static void ssh2_timer(void *ctx, long now); -static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, +static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, struct Packet *pktin); struct rdpkt1_state_tag { @@ -689,7 +691,7 @@ struct ssh_tag { int overall_bufsize; int throttled_all; int v1_stdout_throttling; - int v2_outgoing_sequence; + unsigned long v2_outgoing_sequence; int ssh1_rdpkt_crstate; int ssh2_rdpkt_crstate; @@ -711,7 +713,7 @@ struct ssh_tag { /* ssh1 and ssh2 use this for different things, but both use it */ int protocol_initial_phase_done; - void (*protocol) (Ssh ssh, unsigned char *in, int inlen, + void (*protocol) (Ssh ssh, void *vin, int inlen, struct Packet *pkt); struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen); @@ -724,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 @@ -2245,13 +2260,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c) s->vstring[s->vslen] = 0; s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */ - { - char *vlog; - vlog = snewn(20 + s->vslen, char); - sprintf(vlog, "Server version: %s", s->vstring); - logevent(vlog); - sfree(vlog); - } + logeventf(ssh, "Server version: %s", s->vstring); ssh_detect_bugs(ssh, s->vstring); /* @@ -2335,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); @@ -2364,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; } @@ -2381,7 +2440,7 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) static int ssh_do_close(Ssh ssh, int notify_exit) { - int i, ret = 0; + int ret = 0; struct ssh_channel *c; ssh->state = SSH_STATE_CLOSED; @@ -2394,11 +2453,11 @@ static int ssh_do_close(Ssh ssh, int notify_exit) ret = 1; } /* - * Now we must shut down any port and X forwardings going + * Now we must shut down any port- and X-forwarded channels going * through this connection. */ if (ssh->channels) { - for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) { + while (NULL != (c = index234(ssh->channels, 0))) { switch (c->type) { case CHAN_X11: x11_close(c->u.x11.s); @@ -2407,12 +2466,26 @@ static int ssh_do_close(Ssh ssh, int notify_exit) pfd_close(c->u.pfd.s); break; } - del234(ssh->channels, c); + del234(ssh->channels, c); /* moving next one to index 0 */ if (ssh->version == 2) bufchain_clear(&c->v.v2.outbuffer); 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; } @@ -2431,6 +2504,7 @@ static void ssh_log(Plug plug, int type, SockAddr addr, int port, msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); logevent(msg); + sfree(msg); } static int ssh_closing(Plug plug, const char *error_msg, int error_code, @@ -2543,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); } } @@ -2669,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; @@ -2730,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); @@ -2817,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++) { @@ -2882,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); + } } } @@ -3045,17 +3176,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, s->p = s->response + 5; s->nkeys = GET_32BIT(s->p); s->p += 4; - { - char buf[64]; - sprintf(buf, "Pageant has %d SSH1 keys", s->nkeys); - logevent(buf); - } + logeventf(ssh, "Pageant has %d SSH1 keys", s->nkeys); for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { - { - char buf[64]; - sprintf(buf, "Trying Pageant key #%d", s->keyi); - logevent(buf); - } + logeventf(ssh, "Trying Pageant key #%d", s->keyi); if (s->publickey_blob && !memcmp(s->p, s->publickey_blob, s->publickey_bloblen)) { @@ -3264,18 +3387,18 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) { char *comment = NULL; int type; - char msgbuf[256]; if (flags & FLAG_VERBOSE) c_write_str(ssh, "Trying public key authentication.\r\n"); logeventf(ssh, "Trying public key \"%s\"", filename_to_str(&ssh->cfg.keyfile)); type = key_type(&ssh->cfg.keyfile); if (type != SSH_KEYTYPE_SSH1) { - sprintf(msgbuf, "Key is of wrong type (%s)", - key_type_to_str(type)); - logevent(msgbuf); - c_write_str(ssh, msgbuf); + char *msg = dupprintf("Key is of wrong type (%s)", + key_type_to_str(type)); + logevent(msg); + c_write_str(ssh, msg); c_write_str(ssh, "\r\n"); + sfree(msg); s->tried_publickey = 1; continue; } @@ -3565,13 +3688,13 @@ void sshfwd_close(struct ssh_channel *c) if (c && !c->closes) { /* - * If the channel's remoteid is -1, we have sent + * If halfopen is true, we have sent * CHANNEL_OPEN for this channel, but it hasn't even been * acknowledged by the server. So we must set a close flag * on it now, and then when the server acks the channel * open, we can close it then. */ - if (((int)c->remoteid) != -1) { + if (!c->halfopen) { if (ssh->version == 1) { send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, PKT_END); @@ -3819,6 +3942,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) } } else { while (*portfwd_strptr) portfwd_strptr++; + host[0] = 0; + dports[0] = 0; dport = dserv = -1; portfwd_strptr++; /* eat the NUL and move to next one */ } @@ -3914,6 +4039,9 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) if (epf->saddr) { ssh2_pkt_addstring(pktout, epf->saddr); } else if (ssh->cfg.rport_acceptall) { + /* XXX: ssh->cfg.rport_acceptall may not represent + * what was used to open the original connection, + * since it's reconfigurable. */ ssh2_pkt_addstring(pktout, "0.0.0.0"); } else { ssh2_pkt_addstring(pktout, "127.0.0.1"); @@ -3960,7 +4088,7 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) if (epf->type == 'L') { const char *err = pfd_addforward(epf->daddr, epf->dport, epf->saddr, epf->sport, - ssh, &ssh->cfg, + ssh, cfg, &epf->local, epf->addressfamily); @@ -3972,7 +4100,7 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) } else if (epf->type == 'D') { const char *err = pfd_addforward(NULL, -1, epf->saddr, epf->sport, - ssh, &ssh->cfg, + ssh, cfg, &epf->local, epf->addressfamily); @@ -4028,7 +4156,7 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) ssh2_pkt_addbool(pktout, 1);/* want reply */ if (epf->saddr) { ssh2_pkt_addstring(pktout, epf->saddr); - } else if (ssh->cfg.rport_acceptall) { + } else if (cfg->rport_acceptall) { ssh2_pkt_addstring(pktout, "0.0.0.0"); } else { ssh2_pkt_addstring(pktout, "127.0.0.1"); @@ -4093,6 +4221,7 @@ static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin) logevent ("Opening X11 forward connection succeeded"); c->remoteid = remoteid; + c->halfopen = FALSE; c->localid = alloc_channel_id(ssh); c->closes = 0; c->v.v1.throttling = 0; @@ -4121,6 +4250,7 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin) c = snew(struct ssh_channel); c->ssh = ssh; c->remoteid = remoteid; + c->halfopen = FALSE; c->localid = alloc_channel_id(ssh); c->closes = 0; c->v.v1.throttling = 0; @@ -4141,7 +4271,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) struct ssh_rportfwd pf, *pfp; int remoteid; int hostsize, port; - char *host, buf[1024]; + char *host; const char *e; c = snew(struct ssh_channel); c->ssh = ssh; @@ -4158,26 +4288,23 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) pfp = find234(ssh->rportfwds, &pf, NULL); if (pfp == NULL) { - sprintf(buf, "Rejected remote port open request for %s:%d", - pf.dhost, port); - logevent(buf); + logeventf(ssh, "Rejected remote port open request for %s:%d", + pf.dhost, port); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, PKT_INT, remoteid, PKT_END); } else { - sprintf(buf, "Received remote port open request for %s:%d", - pf.dhost, port); - logevent(buf); + logeventf(ssh, "Received remote port open request for %s:%d", + pf.dhost, port); e = pfd_newconnect(&c->u.pfd.s, pf.dhost, port, c, &ssh->cfg, pfp->pfrec->addressfamily); if (e != NULL) { - char buf[256]; - sprintf(buf, "Port open failed: %s", e); - logevent(buf); + logeventf(ssh, "Port open failed: %s", e); sfree(c); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, PKT_INT, remoteid, PKT_END); } else { c->remoteid = remoteid; + c->halfopen = FALSE; c->localid = alloc_channel_id(ssh); c->closes = 0; c->v.v1.throttling = 0; @@ -4200,6 +4327,7 @@ static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) c = find234(ssh->channels, &remoteid, ssh_channelfind); if (c && c->type == CHAN_SOCKDATA_DORMANT) { c->remoteid = localid; + c->halfopen = FALSE; c->type = CHAN_SOCKDATA; c->v.v1.throttling = 0; pfd_confirm(c->u.pfd.s); @@ -4237,7 +4365,7 @@ static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin) unsigned i = ssh_pkt_getuint32(pktin); struct ssh_channel *c; c = find234(ssh->channels, &i, ssh_channelfind); - if (c && ((int)c->remoteid) != -1) { + if (c && !c->halfopen) { int closetype; closetype = (pktin->type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2); @@ -4279,7 +4407,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) /* Data sent down one of our channels. */ int i = ssh_pkt_getuint32(pktin); char *p; - unsigned int len; + int len; struct ssh_channel *c; ssh_pkt_getstring(pktin, &p, &len); @@ -4346,11 +4474,8 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin) { - char buf[100]; ssh->exitcode = ssh_pkt_getuint32(pktin); - sprintf(buf, "Server sent command exit status %d", - ssh->exitcode); - logevent(buf); + logeventf(ssh, "Server sent command exit status %d", ssh->exitcode); send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END); /* * In case `helpful' firewalls or proxies tack @@ -4496,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; @@ -4557,13 +4684,11 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, */ static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin) { - char *buf, *msg; + char *msg; int msglen; ssh_pkt_getstring(pktin, &msg, &msglen); - buf = dupprintf("Remote debug message: %.*s", msglen, msg); - logevent(buf); - sfree(buf); + logeventf(ssh, "Remote debug message: %.*s", msglen, msg); } static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin) @@ -4576,7 +4701,7 @@ static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin) bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg)); } -void ssh_msg_ignore(Ssh ssh, struct Packet *pktin) +static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin) { /* Do nothing, because we're ignoring it! Duhh. */ } @@ -4599,9 +4724,10 @@ static void ssh1_protocol_setup(Ssh ssh) ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug; } -static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, +static void ssh1_protocol(Ssh ssh, void *vin, int inlen, struct Packet *pktin) { + unsigned char *in=(unsigned char*)vin; if (ssh->state == SSH_STATE_CLOSED) return; @@ -4652,6 +4778,28 @@ static int in_commasep_string(char *needle, char *haystack, int haylen) } /* + * Similar routine for checking whether we have the first string in a list. + */ +static int first_in_commasep_string(char *needle, char *haystack, int haylen) +{ + int needlen; + if (!needle || !haystack) /* protect against null pointers */ + return 0; + needlen = strlen(needle); + /* + * Is it at the start of the string? + */ + if (haylen >= needlen && /* haystack is long enough */ + !memcmp(needle, haystack, needlen) && /* initial match */ + (haylen == needlen || haystack[needlen] == ',') + /* either , or EOS follows */ + ) + return 1; + return 0; +} + + +/* * SSH2 key creation method. */ static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, @@ -4679,11 +4827,12 @@ static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, /* * Handle the SSH2 transport layer. */ -static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, +static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, struct Packet *pktin) { + 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; @@ -4705,6 +4854,9 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, 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); @@ -4917,7 +5069,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, * to. */ { - char *str; + char *str, *preferred; int i, j, len; if (pktin->type != SSH2_MSG_KEXINIT) { @@ -4932,31 +5084,35 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, 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)", str ? str : "(null)")); crStop(0); } + /* + * Note that the server's guess is considered wrong if it doesn't match + * the first algorithm in our list, even if it's still the algorithm + * we end up using. + */ + 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)) { @@ -4964,12 +5120,13 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, break; } } + 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)) { @@ -4978,15 +5135,8 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, 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)", @@ -4995,11 +5145,10 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, 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)) { @@ -5008,15 +5157,8 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, 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)", @@ -5056,6 +5198,86 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, break; } } + ssh_pkt_getstring(pktin, &str, &len); /* client->server language */ + ssh_pkt_getstring(pktin, &str, &len); /* server->client language */ + 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 */ } /* @@ -5118,16 +5340,19 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, /* * Now generate and send e for Diffie-Hellman. */ + set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */ s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2); s->pktout = ssh2_pkt_init(s->kex_init_value); ssh2_pkt_addmp(s->pktout, s->e); ssh2_pkt_send_noqueue(ssh, s->pktout); + set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */ crWaitUntil(pktin); if (pktin->type != s->kex_reply_value) { bombout(("expected key exchange reply packet from server")); crStop(0); } + set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); s->f = ssh2_pkt_getmp(pktin); if (!s->f) { @@ -5138,6 +5363,10 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, s->K = dh_find_K(ssh->kex_ctx, s->f); + /* We assume everything from now on will be quick, and it might + * involve user interaction. */ + set_busy_status(ssh->frontend, BUSY_NOT); + sha_string(&ssh->exhash, s->hostkeydata, s->hostkeylen); if (ssh->kex == &ssh_diffiehellman_gex) { sha_uint32(&ssh->exhash, s->pbits); @@ -5171,11 +5400,29 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, 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); @@ -5478,7 +5725,7 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin) static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) { char *data; - unsigned int length; + int length; unsigned i = ssh_pkt_getuint32(pktin); struct ssh_channel *c; c = find234(ssh->channels, &i, ssh_channelfind); @@ -5586,7 +5833,7 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) struct Packet *pktout; c = find234(ssh->channels, &i, ssh_channelfind); - if (!c || ((int)c->remoteid) == -1) { + if (!c || c->halfopen) { bombout(("Received CHANNEL_CLOSE for %s channel %d\n", c ? "half-open" : "nonexistent", i)); return; @@ -5662,6 +5909,7 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) if (c->type != CHAN_SOCKDATA_DORMANT) return; /* dunno why they're confirming this */ c->remoteid = ssh_pkt_getuint32(pktin); + c->halfopen = FALSE; c->type = CHAN_SOCKDATA; c->v.v2.remwindow = ssh_pkt_getuint32(pktin); c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); @@ -5693,7 +5941,6 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) unsigned reason_code; char *reason_string; int reason_length; - char *message; struct ssh_channel *c; c = find234(ssh->channels, &i, ssh_channelfind); if (!c) @@ -5705,11 +5952,8 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) if (reason_code >= lenof(reasons)) reason_code = 0; /* ensure reasons[reason_code] in range */ ssh_pkt_getstring(pktin, &reason_string, &reason_length); - message = dupprintf("Forwarded connection refused by" - " server: %s [%.*s]", reasons[reason_code], - reason_length, reason_string); - logevent(message); - sfree(message); + logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]", + reasons[reason_code], reason_length, reason_string); pfd_close(c->u.pfd.s); @@ -5797,7 +6041,7 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) if (q >= 0 && q+4 <= len) { \ q = q + 4 + GET_32BIT(p+q); \ if (q >= 0 && q+4 <= len && \ - (q = q + 4 + GET_32BIT(p+q)) && q == len) \ + ((q = q + 4 + GET_32BIT(p+q))!= 0) && q == len) \ result = TRUE; \ } \ } while(0) @@ -5969,6 +6213,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) } c->remoteid = remid; + c->halfopen = FALSE; if (error) { pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE); ssh2_pkt_adduint32(pktout, c->remoteid); @@ -6343,19 +6588,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->p = s->response + 5; s->nkeys = GET_32BIT(s->p); s->p += 4; - { - char buf[64]; - sprintf(buf, "Pageant has %d SSH2 keys", s->nkeys); - logevent(buf); - } + logeventf(ssh, "Pageant has %d SSH2 keys", s->nkeys); for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { void *vret; - { - char buf[64]; - sprintf(buf, "Trying Pageant key #%d", s->keyi); - logevent(buf); - } + logeventf(ssh, "Trying Pageant key #%d", s->keyi); s->pklen = GET_32BIT(s->p); s->p += 4; if (s->publickey_blob && @@ -6789,6 +7026,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } else if (s->method == AUTH_KEYBOARD_INTERACTIVE) { if (s->curr_prompt == 0) { s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + s->pktout->forcepad = 256; ssh2_pkt_adduint32(s->pktout, s->num_prompts); } if (s->need_pw) { /* only add pw if we just got one! */ @@ -6872,6 +7110,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, crStopV; } ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin); + ssh->mainchan->halfopen = FALSE; ssh->mainchan->type = CHAN_MAINSESSION; ssh->mainchan->closes = 0; ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); @@ -7092,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); @@ -7209,7 +7449,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, /* * Handlers for SSH2 messages that might arrive at any moment. */ -void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) +static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) { /* log reason code in disconnect message */ char *buf, *msg; @@ -7238,10 +7478,10 @@ void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) sfree(buf); } -void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) +static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) { /* log the debug message */ - char *buf, *msg; + char *msg; int msglen; int always_display; @@ -7249,12 +7489,10 @@ void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) always_display = ssh2_pkt_getbool(pktin); ssh_pkt_getstring(pktin, &msg, &msglen); - buf = dupprintf("Remote debug message: %.*s", msglen, msg); - logevent(buf); - sfree(buf); + logeventf(ssh, "Remote debug message: %.*s", msglen, msg); } -void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin) +static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin) { struct Packet *pktout; pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); @@ -7335,9 +7573,10 @@ static void ssh2_timer(void *ctx, long now) } } -static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, +static void ssh2_protocol(Ssh ssh, void *vin, int inlen, struct Packet *pktin) { + unsigned char *in = (unsigned char *)vin; if (ssh->state == SSH_STATE_CLOSED) return; @@ -7439,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; @@ -7565,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(); @@ -7882,7 +8124,7 @@ void *new_sock_channel(void *handle, Socket s) c->ssh = ssh; if (c) { - c->remoteid = -1; /* to be set when open confirmed */ + c->halfopen = TRUE; c->localid = alloc_channel_id(ssh); c->closes = 0; c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */