Ahem. Actually _checking_ that asynchronous askalg() worked would
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 0bd89a4..58e72f5 100644 (file)
--- 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:
      * 
@@ -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,49 @@ 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)
+{
+    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 +2416,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 +2439,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 +2452,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 +2465,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 +2503,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 +2616,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 +2742,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 +2821,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 +2909,30 @@ 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);
+        }
     }
 
     for (i = 0; i < 32; i++) {
@@ -2882,9 +2994,25 @@ 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);
+           }
         }
     }
 
@@ -3045,17 +3173,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 +3384,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 +3685,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 +3939,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 +4036,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 +4085,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 +4097,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 +4153,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 +4218,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 +4247,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 +4268,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 +4285,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 +4324,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 +4362,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);
@@ -4346,11 +4471,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
@@ -4557,13 +4679,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 +4696,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. */
 }
@@ -4707,7 +4827,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;
@@ -4729,6 +4849,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);
 
@@ -4941,8 +5064,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"));
@@ -4956,25 +5079,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)",
@@ -4986,8 +5107,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)) {
@@ -4995,14 +5115,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)) {
@@ -5011,15 +5130,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)",
@@ -5028,11 +5140,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)) {
@@ -5041,15 +5152,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)",
@@ -5091,7 +5195,80 @@ 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);
+           }
+       }
+
+       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);
+           }
+       }
+
+       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);
+           }
+       }
+
+       if (s->ignorepkt) /* first_kex_packet_follows */
            crWaitUntil(pktin);                /* Ignore packet */
     }
 
@@ -5155,16 +5332,19 @@ static int do_ssh2_transport(Ssh ssh, void *vin, 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) {
@@ -5175,6 +5355,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, 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);
@@ -5208,11 +5392,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);
@@ -5623,7 +5825,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;
@@ -5699,6 +5901,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);
@@ -5730,7 +5933,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)
@@ -5742,11 +5944,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);
 
@@ -6006,6 +6205,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);
@@ -6380,19 +6580,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 &&
@@ -6910,6 +7102,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);
@@ -7247,7 +7440,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;
@@ -7276,10 +7469,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;
 
@@ -7287,12 +7480,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);
@@ -7478,6 +7669,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;
 
@@ -7604,6 +7797,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();
@@ -7921,7 +8115,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 */