]> asedeno.scripts.mit.edu Git - PuTTY_svn.git/blobdiff - ssh.c
Replace the hacky 'OSSocket' type with a closure.
[PuTTY_svn.git] / ssh.c
diff --git a/ssh.c b/ssh.c
index 3ab9725ef8c726213ab0b4d686b07dfb84bfde02..cc4f7467ba9c97992ac8e7b406d952f5dd47fadb 100644 (file)
--- a/ssh.c
+++ b/ssh.c
 #define TRUE 1
 #endif
 
-#define SSH1_MSG_DISCONNECT                       1    /* 0x1 */
-#define SSH1_SMSG_PUBLIC_KEY                      2    /* 0x2 */
-#define SSH1_CMSG_SESSION_KEY                     3    /* 0x3 */
-#define SSH1_CMSG_USER                            4    /* 0x4 */
-#define SSH1_CMSG_AUTH_RSA                        6    /* 0x6 */
-#define SSH1_SMSG_AUTH_RSA_CHALLENGE              7    /* 0x7 */
-#define SSH1_CMSG_AUTH_RSA_RESPONSE               8    /* 0x8 */
-#define SSH1_CMSG_AUTH_PASSWORD                   9    /* 0x9 */
-#define SSH1_CMSG_REQUEST_PTY                     10   /* 0xa */
-#define SSH1_CMSG_WINDOW_SIZE                     11   /* 0xb */
-#define SSH1_CMSG_EXEC_SHELL                      12   /* 0xc */
-#define SSH1_CMSG_EXEC_CMD                        13   /* 0xd */
-#define SSH1_SMSG_SUCCESS                         14   /* 0xe */
-#define SSH1_SMSG_FAILURE                         15   /* 0xf */
-#define SSH1_CMSG_STDIN_DATA                      16   /* 0x10 */
-#define SSH1_SMSG_STDOUT_DATA                     17   /* 0x11 */
-#define SSH1_SMSG_STDERR_DATA                     18   /* 0x12 */
-#define SSH1_CMSG_EOF                             19   /* 0x13 */
-#define SSH1_SMSG_EXIT_STATUS                     20   /* 0x14 */
-#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION        21   /* 0x15 */
-#define SSH1_MSG_CHANNEL_OPEN_FAILURE             22   /* 0x16 */
-#define SSH1_MSG_CHANNEL_DATA                     23   /* 0x17 */
-#define SSH1_MSG_CHANNEL_CLOSE                    24   /* 0x18 */
-#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION       25   /* 0x19 */
-#define SSH1_SMSG_X11_OPEN                        27   /* 0x1b */
-#define SSH1_CMSG_PORT_FORWARD_REQUEST            28   /* 0x1c */
-#define SSH1_MSG_PORT_OPEN                        29   /* 0x1d */
-#define SSH1_CMSG_AGENT_REQUEST_FORWARDING        30   /* 0x1e */
-#define SSH1_SMSG_AGENT_OPEN                      31   /* 0x1f */
-#define SSH1_MSG_IGNORE                           32   /* 0x20 */
-#define SSH1_CMSG_EXIT_CONFIRMATION               33   /* 0x21 */
-#define SSH1_CMSG_X11_REQUEST_FORWARDING          34   /* 0x22 */
-#define SSH1_CMSG_AUTH_RHOSTS_RSA                 35   /* 0x23 */
-#define SSH1_MSG_DEBUG                            36   /* 0x24 */
-#define SSH1_CMSG_REQUEST_COMPRESSION             37   /* 0x25 */
-#define SSH1_CMSG_AUTH_TIS                        39   /* 0x27 */
-#define SSH1_SMSG_AUTH_TIS_CHALLENGE              40   /* 0x28 */
-#define SSH1_CMSG_AUTH_TIS_RESPONSE               41   /* 0x29 */
-#define SSH1_CMSG_AUTH_CCARD                      70   /* 0x46 */
-#define SSH1_SMSG_AUTH_CCARD_CHALLENGE            71   /* 0x47 */
-#define SSH1_CMSG_AUTH_CCARD_RESPONSE             72   /* 0x48 */
-
-#define SSH1_AUTH_RHOSTS                          1    /* 0x1 */
-#define SSH1_AUTH_RSA                             2    /* 0x2 */
-#define SSH1_AUTH_PASSWORD                        3    /* 0x3 */
-#define SSH1_AUTH_RHOSTS_RSA                      4    /* 0x4 */
-#define SSH1_AUTH_TIS                             5    /* 0x5 */
-#define SSH1_AUTH_CCARD                           16   /* 0x10 */
-
-#define SSH1_PROTOFLAG_SCREEN_NUMBER              1    /* 0x1 */
-/* Mask for protoflags we will echo back to server if seen */
-#define SSH1_PROTOFLAGS_SUPPORTED                 0    /* 0x1 */
-
-#define SSH2_MSG_DISCONNECT                       1    /* 0x1 */
-#define SSH2_MSG_IGNORE                           2    /* 0x2 */
-#define SSH2_MSG_UNIMPLEMENTED                    3    /* 0x3 */
-#define SSH2_MSG_DEBUG                            4    /* 0x4 */
-#define SSH2_MSG_SERVICE_REQUEST                  5    /* 0x5 */
-#define SSH2_MSG_SERVICE_ACCEPT                   6    /* 0x6 */
-#define SSH2_MSG_KEXINIT                          20   /* 0x14 */
-#define SSH2_MSG_NEWKEYS                          21   /* 0x15 */
-#define SSH2_MSG_KEXDH_INIT                       30   /* 0x1e */
-#define SSH2_MSG_KEXDH_REPLY                      31   /* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_REQUEST               30   /* 0x1e */
-#define SSH2_MSG_KEX_DH_GEX_GROUP                 31   /* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_INIT                  32   /* 0x20 */
-#define SSH2_MSG_KEX_DH_GEX_REPLY                 33   /* 0x21 */
-#define SSH2_MSG_KEXRSA_PUBKEY                    30    /* 0x1e */
-#define SSH2_MSG_KEXRSA_SECRET                    31    /* 0x1f */
-#define SSH2_MSG_KEXRSA_DONE                      32    /* 0x20 */
-#define SSH2_MSG_USERAUTH_REQUEST                 50   /* 0x32 */
-#define SSH2_MSG_USERAUTH_FAILURE                 51   /* 0x33 */
-#define SSH2_MSG_USERAUTH_SUCCESS                 52   /* 0x34 */
-#define SSH2_MSG_USERAUTH_BANNER                  53   /* 0x35 */
-#define SSH2_MSG_USERAUTH_PK_OK                   60   /* 0x3c */
-#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ        60   /* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_REQUEST            60   /* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_RESPONSE           61   /* 0x3d */
-#define SSH2_MSG_GLOBAL_REQUEST                   80   /* 0x50 */
-#define SSH2_MSG_REQUEST_SUCCESS                  81   /* 0x51 */
-#define SSH2_MSG_REQUEST_FAILURE                  82   /* 0x52 */
-#define SSH2_MSG_CHANNEL_OPEN                     90   /* 0x5a */
-#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION        91   /* 0x5b */
-#define SSH2_MSG_CHANNEL_OPEN_FAILURE             92   /* 0x5c */
-#define SSH2_MSG_CHANNEL_WINDOW_ADJUST            93   /* 0x5d */
-#define SSH2_MSG_CHANNEL_DATA                     94   /* 0x5e */
-#define SSH2_MSG_CHANNEL_EXTENDED_DATA            95   /* 0x5f */
-#define SSH2_MSG_CHANNEL_EOF                      96   /* 0x60 */
-#define SSH2_MSG_CHANNEL_CLOSE                    97   /* 0x61 */
-#define SSH2_MSG_CHANNEL_REQUEST                  98   /* 0x62 */
-#define SSH2_MSG_CHANNEL_SUCCESS                  99   /* 0x63 */
-#define SSH2_MSG_CHANNEL_FAILURE                  100  /* 0x64 */
-#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE               60
-#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN                  61
-#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE      63
-#define SSH2_MSG_USERAUTH_GSSAPI_ERROR                  64
-#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK                 65
-#define SSH2_MSG_USERAUTH_GSSAPI_MIC                    66
-
 /*
  * Packet type contexts, so that ssh2_pkt_type can correctly decode
  * the ambiguous type numbers back into the correct type strings.
@@ -141,22 +42,6 @@ typedef enum {
     SSH2_PKTCTX_KBDINTER
 } Pkt_ACtx;
 
-#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1  /* 0x1 */
-#define SSH2_DISCONNECT_PROTOCOL_ERROR            2    /* 0x2 */
-#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED       3    /* 0x3 */
-#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4   /* 0x4 */
-#define SSH2_DISCONNECT_MAC_ERROR                 5    /* 0x5 */
-#define SSH2_DISCONNECT_COMPRESSION_ERROR         6    /* 0x6 */
-#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE     7    /* 0x7 */
-#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8       /* 0x8 */
-#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE   9    /* 0x9 */
-#define SSH2_DISCONNECT_CONNECTION_LOST           10   /* 0xa */
-#define SSH2_DISCONNECT_BY_APPLICATION            11   /* 0xb */
-#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS      12   /* 0xc */
-#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER    13   /* 0xd */
-#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14      /* 0xe */
-#define SSH2_DISCONNECT_ILLEGAL_USER_NAME         15   /* 0xf */
-
 static const char *const ssh2_disconnect_reasons[] = {
     NULL,
     "host not allowed to connect",
@@ -176,13 +61,6 @@ static const char *const ssh2_disconnect_reasons[] = {
     "illegal user name",
 };
 
-#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED     1    /* 0x1 */
-#define SSH2_OPEN_CONNECT_FAILED                  2    /* 0x2 */
-#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE            3    /* 0x3 */
-#define SSH2_OPEN_RESOURCE_SHORTAGE               4    /* 0x4 */
-
-#define SSH2_EXTENDED_DATA_STDERR                 1    /* 0x1 */
-
 /*
  * Various remote-bug flags.
  */
@@ -439,6 +317,8 @@ enum {
 #define crState(t)     crStateP(t, ssh->t)
 #define crFinish(z)    } *crLine = 0; return (z); }
 #define crFinishV      } *crLine = 0; return; }
+#define crFinishFree(z)        } sfree(s); return (z); }
+#define crFinishFreeV  } sfree(s); return; }
 #define crReturn(z)    \
        do {\
            *crLine =__LINE__; return (z); case __LINE__:;\
@@ -458,14 +338,14 @@ struct Packet;
 static struct Packet *ssh1_pkt_init(int pkt_type);
 static struct Packet *ssh2_pkt_init(int pkt_type);
 static void ssh_pkt_ensure(struct Packet *, int length);
-static void ssh_pkt_adddata(struct Packet *, void *data, int len);
+static void ssh_pkt_adddata(struct Packet *, const void *data, int len);
 static void ssh_pkt_addbyte(struct Packet *, unsigned char value);
 static void ssh2_pkt_addbool(struct Packet *, unsigned char value);
 static void ssh_pkt_adduint32(struct Packet *, unsigned long value);
 static void ssh_pkt_addstring_start(struct Packet *);
-static void ssh_pkt_addstring_str(struct Packet *, char *data);
-static void ssh_pkt_addstring_data(struct Packet *, char *data, int len);
-static void ssh_pkt_addstring(struct Packet *, char *data);
+static void ssh_pkt_addstring_str(struct Packet *, const char *data);
+static void ssh_pkt_addstring_data(struct Packet *, const char *data, int len);
+static void ssh_pkt_addstring(struct Packet *, const char *data);
 static unsigned char *ssh2_mpint_fmt(Bignum b, int *len);
 static void ssh1_pkt_addmp(struct Packet *, Bignum b);
 static void ssh2_pkt_addmp(struct Packet *, Bignum b);
@@ -523,7 +403,7 @@ static void ssh_channel_destroy(struct ssh_channel *c);
 const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
 
 const static struct ssh_mac *macs[] = {
-    &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
+    &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
 };
 const static struct ssh_mac *buggymacs[] = {
     &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
@@ -586,14 +466,6 @@ struct outstanding_channel_request {
     struct outstanding_channel_request *next;
 };
 
-/*
- * little structure to keep track of outstanding WINDOW_ADJUSTs
- */
-struct winadj {
-    struct winadj *next;
-    unsigned size;
-};
-
 /*
  * 2-3-4 tree storing channels.
  */
@@ -672,6 +544,7 @@ struct ssh_channel {
            unsigned char *message;
            unsigned char msglen[4];
            unsigned lensofar, totallen;
+            int outstanding_requests;
        } a;
        struct ssh_x11_channel {
            Socket s;
@@ -713,12 +586,20 @@ struct ssh_portfwd; /* forward declaration */
 
 struct ssh_rportfwd {
     unsigned sport, dport;
-    char dhost[256];
+    char *shost, *dhost;
     char *sportdesc;
     struct ssh_portfwd *pfrec;
 };
-#define free_rportfwd(pf) ( \
-    ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), sfree(pf) )
+
+static void free_rportfwd(struct ssh_rportfwd *pf)
+{
+    if (pf) {
+        sfree(pf->sportdesc);
+        sfree(pf->shost);
+        sfree(pf->dhost);
+        sfree(pf);
+    }
+}
 
 /*
  * Separately to the rportfwd tree (which is for looking up port
@@ -777,9 +658,9 @@ static int ssh_do_close(Ssh ssh, int notify_exit);
 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, void *vin, int inlen,
-                            struct Packet *pktin);
+static void ssh2_timer(void *ctx, unsigned long now);
+static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+                             struct Packet *pktin);
 static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin);
 
 struct rdpkt1_state_tag {
@@ -837,6 +718,7 @@ struct ssh_tag {
     void *cs_comp_ctx, *sc_comp_ctx;
     const struct ssh_kex *kex;
     const struct ssh_signkey *hostkey;
+    char *hostkey_str; /* string representation, for easy checking in rekeys */
     unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN];
     int v2_session_id_len;
     void *kex_ctx;
@@ -986,7 +868,7 @@ struct ssh_tag {
     unsigned long incoming_data_size, outgoing_data_size, deferred_data_size;
     unsigned long max_data_size;
     int kex_in_progress;
-    long next_rekey, last_rekey;
+    unsigned long next_rekey, last_rekey;
     char *deferred_rekey_reason;    /* points to STATIC string; don't free */
 
     /*
@@ -1017,14 +899,15 @@ static void logeventf(Ssh ssh, const char *fmt, ...)
     sfree(buf);
 }
 
-#define bombout(msg) \
-    do { \
-        char *text = dupprintf msg; \
-       ssh_do_close(ssh, FALSE); \
-        logevent(text); \
-        connection_fatal(ssh->frontend, "%s", text); \
-        sfree(text); \
-    } while (0)
+static void bomb_out(Ssh ssh, char *text)
+{
+    ssh_do_close(ssh, FALSE);
+    logevent(text);
+    connection_fatal(ssh->frontend, "%s", text);
+    sfree(text);
+}
+
+#define bombout(msg) bomb_out(ssh, dupprintf msg)
 
 /* Functions to leave bits out of the SSH packet log file. */
 
@@ -1060,12 +943,14 @@ static void parse_ttymodes(Ssh ssh,
         * follows it, or 'A' indicating that we should pass the
         * value through from the local environment via get_ttymode.
         */
-       if (val[0] == 'A')
+       if (val[0] == 'A') {
            val = get_ttymode(ssh->frontend, key);
-       else
-           val++;                     /* skip the 'V' */
-       if (val)
-           do_mode(data, key, val);
+           if (val) {
+               do_mode(data, key, val);
+               sfree(val);
+           }
+       } else
+           do_mode(data, key, val + 1);               /* skip the 'V' */
     }
 }
 
@@ -1108,7 +993,9 @@ static int ssh_rportcmp_ssh2(void *av, void *bv)
 {
     struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
     struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
-
+    int i;
+    if ( (i = strcmp(a->shost, b->shost)) != 0)
+       return i < 0 ? -1 : +1;
     if (a->sport > b->sport)
        return +1;
     if (a->sport < b->sport)
@@ -1451,7 +1338,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
            /* See if that gives us a valid packet. */
            if (ssh->scmac->verresult(ssh->sc_mac_ctx,
                                      st->pktin->data + st->packetlen) &&
-               (st->len = GET_32BIT(st->pktin->data)) + 4 == st->packetlen)
+               ((st->len = toint(GET_32BIT(st->pktin->data))) ==
+                 st->packetlen-4))
                    break;
            if (st->packetlen >= OUR_V2_PACKETLIMIT) {
                bombout(("No valid incoming packet found"));
@@ -1484,7 +1372,7 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
        /*
         * Now get the length figure.
         */
-       st->len = GET_32BIT(st->pktin->data);
+       st->len = toint(GET_32BIT(st->pktin->data));
 
        /*
         * _Completely_ silly lengths should be stomped on before they
@@ -1830,7 +1718,7 @@ static void ssh_pkt_ensure(struct Packet *pkt, int length)
        if (body) pkt->body = pkt->data + offset;
     }
 }
-static void ssh_pkt_adddata(struct Packet *pkt, void *data, int len)
+static void ssh_pkt_adddata(struct Packet *pkt, const void *data, int len)
 {
     if (pkt->logmode != PKTLOG_EMIT) {
        pkt->nblanks++;
@@ -1864,17 +1752,18 @@ static void ssh_pkt_addstring_start(struct Packet *pkt)
     ssh_pkt_adduint32(pkt, 0);
     pkt->savedpos = pkt->length;
 }
-static void ssh_pkt_addstring_str(struct Packet *pkt, char *data)
+static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data)
 {
     ssh_pkt_adddata(pkt, data, strlen(data));
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
-static void ssh_pkt_addstring_data(struct Packet *pkt, char *data, int len)
+static void ssh_pkt_addstring_data(struct Packet *pkt, const char *data,
+                                   int len)
 {
     ssh_pkt_adddata(pkt, data, len);
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
-static void ssh_pkt_addstring(struct Packet *pkt, char *data)
+static void ssh_pkt_addstring(struct Packet *pkt, const char *data)
 {
     ssh_pkt_addstring_start(pkt);
     ssh_pkt_addstring_str(pkt, data);
@@ -2315,7 +2204,7 @@ static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length)
     *length = 0;
     if (pkt->length - pkt->savedpos < 4)
        return;
-    len = GET_32BIT(pkt->body + pkt->savedpos);
+    len = toint(GET_32BIT(pkt->body + pkt->savedpos));
     if (len < 0)
        return;
     *length = len;
@@ -2399,7 +2288,7 @@ static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
      * See if this is in fact an ssh-rsa signature and a buggy
      * server; otherwise we can just do this the easy way.
      */
-    if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) &&
+    if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && pkblob_len > 4+7+4 &&
        (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
        int pos, len, siglen;
 
@@ -2408,8 +2297,15 @@ static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
         */
 
        pos = 4+7;                     /* skip over "ssh-rsa" */
-       pos += 4 + GET_32BIT(pkblob+pos);   /* skip over exponent */
-       len = GET_32BIT(pkblob+pos);   /* find length of modulus */
+        len = toint(GET_32BIT(pkblob+pos)); /* get length of exponent */
+        if (len < 0 || len > pkblob_len - pos - 4)
+            goto give_up;
+       pos += 4 + len;                /* skip over exponent */
+        if (pkblob_len - pos < 4)
+            goto give_up;
+       len = toint(GET_32BIT(pkblob+pos)); /* find length of modulus */
+        if (len < 0 || len > pkblob_len - pos - 4)
+            goto give_up;
        pos += 4;                      /* find modulus itself */
        while (len > 0 && pkblob[pos] == 0)
            len--, pos++;
@@ -2419,7 +2315,11 @@ static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
         * Now find the signature integer.
         */
        pos = 4+7;                     /* skip over "ssh-rsa" */
-       siglen = GET_32BIT(sigblob+pos);
+        if (sigblob_len < pos+4)
+            goto give_up;
+       siglen = toint(GET_32BIT(sigblob+pos));
+        if (siglen != sigblob_len - pos - 4)
+            goto give_up;
        /* debug(("signature length is %d\n", siglen)); */
 
        if (len != siglen) {
@@ -2441,7 +2341,10 @@ static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
            return;
        }
 
-       /* Otherwise fall through and do it the easy way. */
+       /* Otherwise fall through and do it the easy way. We also come
+         * here as a fallback if we discover above that the key blob
+         * is misformatted in some way. */
+      give_up:;
     }
 
     ssh2_pkt_addstring_start(pkt);
@@ -3204,6 +3107,7 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
     Ssh ssh = c->ssh;
     void *sentreply = reply;
 
+    c->u.a.outstanding_requests--;
     if (!sentreply) {
        /* Fake SSH_AGENT_FAILURE. */
        sentreply = "\0\0\0\1\5";
@@ -3223,6 +3127,12 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
     }
     if (reply)
        sfree(reply);
+    /*
+     * If we've already seen an incoming EOF but haven't sent an
+     * outgoing one, this may be the moment to send it.
+     */
+    if (c->u.a.outstanding_requests == 0 && (c->closes & CLOSES_RCVD_EOF))
+        sshfwd_write_eof(c);
 }
 
 /*
@@ -3266,7 +3176,6 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
 {
     int i, j, ret;
     unsigned char cookie[8], *ptr;
-    struct RSAKey servkey, hostkey;
     struct MD5Context md5c;
     struct do_ssh1_login_state {
        int crLine;
@@ -3294,6 +3203,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        int commentlen;
         int dlgret;
        Filename *keyfile;
+        struct RSAKey servkey, hostkey;
     };
     crState(do_ssh1_login_state);
 
@@ -3316,8 +3226,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     }
     memcpy(cookie, ptr, 8);
 
-    if (!ssh1_pkt_getrsakey(pktin, &servkey, &s->keystr1) ||
-       !ssh1_pkt_getrsakey(pktin, &hostkey, &s->keystr2)) {    
+    if (!ssh1_pkt_getrsakey(pktin, &s->servkey, &s->keystr1) ||
+       !ssh1_pkt_getrsakey(pktin, &s->hostkey, &s->keystr2)) { 
        bombout(("Failed to read SSH-1 public keys from public key packet"));
        crStop(0);
     }
@@ -3329,9 +3239,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        char logmsg[80];
        logevent("Host key fingerprint is:");
        strcpy(logmsg, "      ");
-       hostkey.comment = NULL;
+       s->hostkey.comment = NULL;
        rsa_fingerprint(logmsg + strlen(logmsg),
-                       sizeof(logmsg) - strlen(logmsg), &hostkey);
+                       sizeof(logmsg) - strlen(logmsg), &s->hostkey);
        logevent(logmsg);
     }
 
@@ -3346,8 +3256,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
 
     MD5Init(&md5c);
-    MD5Update(&md5c, s->keystr2, hostkey.bytes);
-    MD5Update(&md5c, s->keystr1, servkey.bytes);
+    MD5Update(&md5c, s->keystr2, s->hostkey.bytes);
+    MD5Update(&md5c, s->keystr1, s->servkey.bytes);
     MD5Update(&md5c, cookie, 8);
     MD5Final(s->session_id, &md5c);
 
@@ -3357,13 +3267,14 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     /*
      * Verify that the `bits' and `bytes' parameters match.
      */
-    if (hostkey.bits > hostkey.bytes * 8 ||
-       servkey.bits > servkey.bytes * 8) {
+    if (s->hostkey.bits > s->hostkey.bytes * 8 ||
+       s->servkey.bits > s->servkey.bytes * 8) {
        bombout(("SSH-1 public keys were badly formatted"));
        crStop(0);
     }
 
-    s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
+    s->len = (s->hostkey.bytes > s->servkey.bytes ?
+              s->hostkey.bytes : s->servkey.bytes);
 
     s->rsabuf = snewn(s->len, unsigned char);
 
@@ -3374,11 +3285,11 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        /*
         * First format the key into a string.
         */
-       int len = rsastr_len(&hostkey);
+       int len = rsastr_len(&s->hostkey);
        char fingerprint[100];
        char *keystr = snewn(len, char);
-       rsastr_fmt(keystr, &hostkey);
-       rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
+       rsastr_fmt(keystr, &s->hostkey);
+       rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey);
 
         ssh_set_frozen(ssh, 1);
        s->dlgret = verify_ssh_host_key(ssh->frontend,
@@ -3412,14 +3323,14 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            s->rsabuf[i] ^= s->session_id[i];
     }
 
-    if (hostkey.bytes > servkey.bytes) {
-       ret = rsaencrypt(s->rsabuf, 32, &servkey);
+    if (s->hostkey.bytes > s->servkey.bytes) {
+       ret = rsaencrypt(s->rsabuf, 32, &s->servkey);
        if (ret)
-           ret = rsaencrypt(s->rsabuf, servkey.bytes, &hostkey);
+           ret = rsaencrypt(s->rsabuf, s->servkey.bytes, &s->hostkey);
     } else {
-       ret = rsaencrypt(s->rsabuf, 32, &hostkey);
+       ret = rsaencrypt(s->rsabuf, 32, &s->hostkey);
        if (ret)
-           ret = rsaencrypt(s->rsabuf, hostkey.bytes, &servkey);
+           ret = rsaencrypt(s->rsabuf, s->hostkey.bytes, &s->servkey);
     }
     if (!ret) {
        bombout(("SSH-1 public key encryptions failed due to bad formatting"));
@@ -3522,21 +3433,21 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     ssh->crcda_ctx = crcda_make_context();
     logevent("Installing CRC compensation attack detector");
 
-    if (servkey.modulus) {
-       sfree(servkey.modulus);
-       servkey.modulus = NULL;
+    if (s->servkey.modulus) {
+       sfree(s->servkey.modulus);
+       s->servkey.modulus = NULL;
     }
-    if (servkey.exponent) {
-       sfree(servkey.exponent);
-       servkey.exponent = NULL;
+    if (s->servkey.exponent) {
+       sfree(s->servkey.exponent);
+       s->servkey.exponent = NULL;
     }
-    if (hostkey.modulus) {
-       sfree(hostkey.modulus);
-       hostkey.modulus = NULL;
+    if (s->hostkey.modulus) {
+       sfree(s->hostkey.modulus);
+       s->hostkey.modulus = NULL;
     }
-    if (hostkey.exponent) {
-       sfree(hostkey.exponent);
-       hostkey.exponent = NULL;
+    if (s->hostkey.exponent) {
+       sfree(s->hostkey.exponent);
+       s->hostkey.exponent = NULL;
     }
     crWaitUntil(pktin);
 
@@ -3671,7 +3582,12 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            if (s->response && s->responselen >= 5 &&
                s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
                s->p = s->response + 5;
-               s->nkeys = GET_32BIT(s->p);
+               s->nkeys = toint(GET_32BIT(s->p));
+                if (s->nkeys < 0) {
+                    logeventf(ssh, "Pageant reported negative key count %d",
+                              s->nkeys);
+                    s->nkeys = 0;
+                }
                s->p += 4;
                logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys);
                for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
@@ -3681,22 +3597,23 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                        int n, ok = FALSE;
                        do {           /* do while (0) to make breaking easy */
                            n = ssh1_read_bignum
-                               (s->p, s->responselen-(s->p-s->response),
+                               (s->p, toint(s->responselen-(s->p-s->response)),
                                 &s->key.exponent);
                            if (n < 0)
                                break;
                            s->p += n;
                            n = ssh1_read_bignum
-                               (s->p, s->responselen-(s->p-s->response),
+                               (s->p, toint(s->responselen-(s->p-s->response)),
                                 &s->key.modulus);
                            if (n < 0)
-                           break;
+                                break;
                            s->p += n;
                            if (s->responselen - (s->p-s->response) < 4)
                                break;
-                           s->commentlen = GET_32BIT(s->p);
+                           s->commentlen = toint(GET_32BIT(s->p));
                            s->p += 4;
-                           if (s->responselen - (s->p-s->response) <
+                           if (s->commentlen < 0 ||
+                                toint(s->responselen - (s->p-s->response)) <
                                s->commentlen)
                                break;
                            s->commentp = (char *)s->p;
@@ -4244,6 +4161,7 @@ static void ssh_channel_try_eof(struct ssh_channel *c)
     if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0)
         return;              /* can't send EOF: pending outgoing data */
 
+    c->pending_eof = FALSE;            /* we're about to send it */
     if (ssh->version == 1) {
         send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
                     PKT_END);
@@ -4254,17 +4172,8 @@ static void ssh_channel_try_eof(struct ssh_channel *c)
         ssh2_pkt_adduint32(pktout, c->remoteid);
         ssh2_pkt_send(ssh, pktout);
         c->closes |= CLOSES_SENT_EOF;
-        if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes)) {
-            /*
-             * Also send MSG_CLOSE.
-             */
-            pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-            ssh2_pkt_adduint32(pktout, c->remoteid);
-            ssh2_pkt_send(ssh, pktout);
-            c->closes |= CLOSES_SENT_CLOSE;
-        }
+       ssh2_channel_check_close(c);
     }
-    c->pending_eof = FALSE;            /* we've sent it now */
 }
 
 void sshfwd_write_eof(struct ssh_channel *c)
@@ -4281,31 +4190,27 @@ void sshfwd_write_eof(struct ssh_channel *c)
     ssh_channel_try_eof(c);
 }
 
-void sshfwd_unclean_close(struct ssh_channel *c)
+void sshfwd_unclean_close(struct ssh_channel *c, const char *err)
 {
     Ssh ssh = c->ssh;
-    struct Packet *pktout;
 
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    if (!(c->closes & CLOSES_SENT_CLOSE)) {
-        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-        ssh2_pkt_adduint32(pktout, c->remoteid);
-        ssh2_pkt_send(ssh, pktout);
-        c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
-    }
-
     switch (c->type) {
       case CHAN_X11:
         x11_close(c->u.x11.s);
+        logeventf(ssh, "Forwarded X11 connection terminated due to local "
+                  "error: %s", err);
         break;
       case CHAN_SOCKDATA:
       case CHAN_SOCKDATA_DORMANT:
         pfd_close(c->u.pfd.s);
+        logeventf(ssh, "Forwarded port closed due to local error: %s", err);
         break;
     }
     c->type = CHAN_ZOMBIE;
+    c->pending_eof = FALSE;   /* this will confuse a zombie channel */
 
     ssh2_channel_check_close(c);
 }
@@ -4627,9 +4532,9 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
                        /* XXX: 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");
+                       ssh2_pkt_addstring(pktout, "");
                    } else {
-                       ssh2_pkt_addstring(pktout, "127.0.0.1");
+                       ssh2_pkt_addstring(pktout, "localhost");
                    }
                    ssh2_pkt_adduint32(pktout, epf->sport);
                    ssh2_pkt_send(ssh, pktout);
@@ -4708,9 +4613,15 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
                }
 
                pf = snew(struct ssh_rportfwd);
-               strncpy(pf->dhost, epf->daddr, lenof(pf->dhost)-1);
-               pf->dhost[lenof(pf->dhost)-1] = '\0';
+                pf->dhost = dupstr(epf->daddr);
                pf->dport = epf->dport;
+                if (epf->saddr) {
+                    pf->shost = dupstr(epf->saddr);
+                } else if (conf_get_int(conf, CONF_rport_acceptall)) {
+                    pf->shost = dupstr("");
+                } else {
+                    pf->shost = dupstr("localhost");
+                }
                pf->sport = epf->sport;
                if (add234(ssh->rportfwds, pf) != pf) {
                    logeventf(ssh, "Duplicate remote port forwarding to %s:%d",
@@ -4739,14 +4650,8 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
                        pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
                        ssh2_pkt_addstring(pktout, "tcpip-forward");
                        ssh2_pkt_addbool(pktout, 1);/* want reply */
-                       if (epf->saddr) {
-                           ssh2_pkt_addstring(pktout, epf->saddr);
-                       } else if (conf_get_int(conf, CONF_rport_acceptall)) {
-                           ssh2_pkt_addstring(pktout, "0.0.0.0");
-                       } else {
-                           ssh2_pkt_addstring(pktout, "127.0.0.1");
-                       }
-                       ssh2_pkt_adduint32(pktout, epf->sport);
+                       ssh2_pkt_addstring(pktout, pf->shost);
+                       ssh2_pkt_adduint32(pktout, pf->sport);
                        ssh2_pkt_send(ssh, pktout);
 
                        ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS,
@@ -4844,6 +4749,7 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
        c->type = CHAN_AGENT;   /* identify channel type */
        c->u.a.lensofar = 0;
        c->u.a.message = NULL;
+       c->u.a.outstanding_requests = 0;
        add234(ssh->channels, c);
        send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
                    PKT_INT, c->remoteid, PKT_INT, c->localid,
@@ -4855,23 +4761,17 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
 {
     /* Remote side is trying to open a channel to talk to a
      * forwarded port. Give them back a local channel number. */
-    struct ssh_channel *c;
     struct ssh_rportfwd pf, *pfp;
     int remoteid;
     int hostsize, port;
     char *host;
     const char *e;
-    c = snew(struct ssh_channel);
-    c->ssh = ssh;
 
     remoteid = ssh_pkt_getuint32(pktin);
     ssh_pkt_getstring(pktin, &host, &hostsize);
     port = ssh_pkt_getuint32(pktin);
 
-    if (hostsize >= lenof(pf.dhost))
-       hostsize = lenof(pf.dhost)-1;
-    memcpy(pf.dhost, host, hostsize);
-    pf.dhost[hostsize] = '\0';
+    pf.dhost = dupprintf(".*s", hostsize, host);
     pf.dport = port;
     pfp = find234(ssh->rportfwds, &pf, NULL);
 
@@ -4881,6 +4781,9 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
        send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
                    PKT_INT, remoteid, PKT_END);
     } else {
+        struct ssh_channel *c = snew(struct ssh_channel);
+        c->ssh = ssh;
+
        logeventf(ssh, "Received remote port open request for %s:%d",
                  pf.dhost, port);
        e = pfd_newconnect(&c->u.pfd.s, pf.dhost, port,
@@ -4905,6 +4808,8 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
            logevent("Forwarded port opened successfully");
        }
     }
+
+    sfree(pf.dhost);
 }
 
 static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
@@ -5069,6 +4974,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
                if (c->u.a.lensofar == c->u.a.totallen) {
                    void *reply;
                    int replylen;
+                    c->u.a.outstanding_requests++;
                    if (agent_query(c->u.a.message,
                                    c->u.a.totallen,
                                    &reply, &replylen,
@@ -5485,7 +5391,7 @@ static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
 /*
  * Handle the SSH-2 transport layer.
  */
-static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                             struct Packet *pktin)
 {
     unsigned char *in = (unsigned char *)vin;
@@ -5651,12 +5557,28 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
        }
        /* List server host key algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       for (i = 0; i < lenof(hostkey_algs); i++) {
-           ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name);
-           if (i < lenof(hostkey_algs) - 1)
-               ssh2_pkt_addstring_str(s->pktout, ",");
-       }
+        if (!s->got_session_id) {
+            /*
+             * In the first key exchange, we list all the algorithms
+             * we're prepared to cope with.
+             */
+            ssh2_pkt_addstring_start(s->pktout);
+            for (i = 0; i < lenof(hostkey_algs); i++) {
+                ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name);
+                if (i < lenof(hostkey_algs) - 1)
+                    ssh2_pkt_addstring_str(s->pktout, ",");
+            }
+        } else {
+            /*
+             * In subsequent key exchanges, we list only the kex
+             * algorithm that was selected in the first key exchange,
+             * so that we keep getting the same host key and hence
+             * don't have to interrupt the user's session to ask for
+             * reverification.
+             */
+            assert(ssh->kex);
+            ssh2_pkt_addstring(s->pktout, ssh->hostkey->name);
+        }
        /* List encryption algorithms (client->server then server->client). */
        for (k = 0; k < 2; k++) {
            ssh2_pkt_addstring_start(s->pktout);
@@ -5726,7 +5648,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     ssh2_pkt_send_noqueue(ssh, s->pktout);
 
     if (!pktin)
-       crWaitUntil(pktin);
+       crWaitUntilV(pktin);
 
     /*
      * Now examine the other side's KEXINIT to see what we're up
@@ -5738,7 +5660,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
        if (pktin->type != SSH2_MSG_KEXINIT) {
            bombout(("expected key exchange packet from server"));
-           crStop(0);
+           crStopV;
        }
        ssh->kex = NULL;
        ssh->hostkey = NULL;
@@ -5773,7 +5695,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        if (!ssh->kex) {
            bombout(("Couldn't agree a key exchange algorithm (available: %s)",
                     str ? str : "(null)"));
-           crStop(0);
+           crStopV;
        }
        /*
         * Note that the server's guess is considered wrong if it doesn't match
@@ -5791,7 +5713,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        if (!ssh->hostkey) {
            bombout(("Couldn't agree a host key algorithm (available: %s)",
                     str ? str : "(null)"));
-           crStop(0);
+           crStopV;
        }
 
        s->guessok = s->guessok &&
@@ -5815,7 +5737,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        if (!s->cscipher_tobe) {
            bombout(("Couldn't agree a client-to-server cipher (available: %s)",
                     str ? str : "(null)"));
-           crStop(0);
+           crStopV;
        }
 
        ssh_pkt_getstring(pktin, &str, &len);    /* server->client cipher */
@@ -5837,7 +5759,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        if (!s->sccipher_tobe) {
            bombout(("Couldn't agree a server-to-client cipher (available: %s)",
                     str ? str : "(null)"));
-           crStop(0);
+           crStopV;
        }
 
        ssh_pkt_getstring(pktin, &str, &len);    /* client->server mac */
@@ -5894,6 +5816,16 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        ssh_pkt_getstring(pktin, &str, &len);  /* server->client language */
        s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok;
 
+       ssh->exhash = ssh->kex->hash->init();
+       hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c));
+       hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s));
+       hash_string(ssh->kex->hash, ssh->exhash,
+           s->our_kexinit, s->our_kexinitlen);
+       sfree(s->our_kexinit);
+       if (pktin->length > 5)
+           hash_string(ssh->kex->hash, ssh->exhash,
+               pktin->data + 5, pktin->length - 5);
+
        if (s->warn_kex) {
            ssh_set_frozen(ssh, 1);
            s->dlgret = askalg(ssh->frontend, "key-exchange algorithm",
@@ -5901,11 +5833,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                               ssh_dialog_callback, ssh);
            if (s->dlgret < 0) {
                do {
-                   crReturn(0);
+                   crReturnV;
                    if (pktin) {
                        bombout(("Unexpected data from server while"
                                 " waiting for user response"));
-                       crStop(0);
+                       crStopV;
                    }
                } while (pktin || inlen > 0);
                s->dlgret = ssh->user_response;
@@ -5914,7 +5846,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (s->dlgret == 0) {
                ssh_disconnect(ssh, "User aborted at kex warning", NULL,
                               0, TRUE);
-               crStop(0);
+               crStopV;
            }
        }
 
@@ -5926,11 +5858,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                               ssh_dialog_callback, ssh);
            if (s->dlgret < 0) {
                do {
-                   crReturn(0);
+                   crReturnV;
                    if (pktin) {
                        bombout(("Unexpected data from server while"
                                 " waiting for user response"));
-                       crStop(0);
+                       crStopV;
                    }
                } while (pktin || inlen > 0);
                s->dlgret = ssh->user_response;
@@ -5939,7 +5871,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (s->dlgret == 0) {
                ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
                               0, TRUE);
-               crStop(0);
+               crStopV;
            }
        }
 
@@ -5951,11 +5883,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                               ssh_dialog_callback, ssh);
            if (s->dlgret < 0) {
                do {
-                   crReturn(0);
+                   crReturnV;
                    if (pktin) {
                        bombout(("Unexpected data from server while"
                                 " waiting for user response"));
-                       crStop(0);
+                       crStopV;
                    }
                } while (pktin || inlen > 0);
                s->dlgret = ssh->user_response;
@@ -5964,22 +5896,12 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (s->dlgret == 0) {
                ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
                               0, TRUE);
-               crStop(0);
+               crStopV;
            }
        }
 
-       ssh->exhash = ssh->kex->hash->init();
-       hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c));
-       hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s));
-       hash_string(ssh->kex->hash, ssh->exhash,
-           s->our_kexinit, s->our_kexinitlen);
-       sfree(s->our_kexinit);
-       if (pktin->length > 5)
-           hash_string(ssh->kex->hash, ssh->exhash,
-               pktin->data + 5, pktin->length - 5);
-
        if (s->ignorepkt) /* first_kex_packet_follows */
-           crWaitUntil(pktin);                /* Ignore packet */
+           crWaitUntilV(pktin);                /* Ignore packet */
     }
 
     if (ssh->kex->main_type == KEXTYPE_DH) {
@@ -6016,16 +5938,16 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
             ssh2_pkt_adduint32(s->pktout, s->pbits);
             ssh2_pkt_send_noqueue(ssh, s->pktout);
 
-            crWaitUntil(pktin);
+            crWaitUntilV(pktin);
             if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
                 bombout(("expected key exchange group packet from server"));
-                crStop(0);
+                crStopV;
             }
             s->p = ssh2_pkt_getmp(pktin);
             s->g = ssh2_pkt_getmp(pktin);
             if (!s->p || !s->g) {
                 bombout(("unable to read mp-ints from incoming group packet"));
-                crStop(0);
+                crStopV;
             }
             ssh->kex_ctx = dh_setup_gex(s->p, s->g);
             s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
@@ -6051,10 +5973,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         ssh2_pkt_send_noqueue(ssh, s->pktout);
 
         set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */
-        crWaitUntil(pktin);
+        crWaitUntilV(pktin);
         if (pktin->type != s->kex_reply_value) {
             bombout(("expected key exchange reply packet from server"));
-            crStop(0);
+            crStopV;
         }
         set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */
         ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
@@ -6062,7 +5984,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         s->f = ssh2_pkt_getmp(pktin);
         if (!s->f) {
             bombout(("unable to parse key exchange reply packet"));
-            crStop(0);
+            crStopV;
         }
         ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
 
@@ -6095,10 +6017,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
          * RSA key exchange. First expect a KEXRSA_PUBKEY packet
          * from the server.
          */
-        crWaitUntil(pktin);
+        crWaitUntilV(pktin);
         if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
             bombout(("expected RSA public key packet from server"));
-            crStop(0);
+            crStopV;
         }
 
         ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
@@ -6117,7 +6039,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         if (!s->rsakey) {
             sfree(s->rsakeydata);
             bombout(("unable to parse RSA public key from server"));
-            crStop(0);
+            crStopV;
         }
 
         hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen);
@@ -6177,11 +6099,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
         ssh_rsakex_freekey(s->rsakey);
 
-        crWaitUntil(pktin);
+        crWaitUntilV(pktin);
         if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
             sfree(s->rsakeydata);
             bombout(("expected signature packet from server"));
-            crStop(0);
+            crStopV;
         }
 
         ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
@@ -6205,44 +6127,60 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                                 (char *)s->exchange_hash,
                                 ssh->kex->hash->hlen)) {
        bombout(("Server's host key did not match the signature supplied"));
-       crStop(0);
+       crStopV;
     }
 
-    /*
-     * Authenticate remote host: verify host key. (We've already
-     * checked the signature of the exchange hash.)
-     */
     s->keystr = ssh->hostkey->fmtkey(s->hkey);
-    s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
-    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_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 */
-       logevent("Host key fingerprint is:");
-       logevent(s->fingerprint);
+    if (!s->got_session_id) {
+        /*
+         * Authenticate remote host: verify host key. (We've already
+         * checked the signature of the exchange hash.)
+         */
+        s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
+        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 {
+                crReturnV;
+                if (pktin) {
+                    bombout(("Unexpected data from server while waiting"
+                             " for user host key response"));
+                    crStopV;
+                }
+            } while (pktin || inlen > 0);
+            s->dlgret = ssh->user_response;
+        }
+        ssh_set_frozen(ssh, 0);
+        if (s->dlgret == 0) {
+            ssh_disconnect(ssh, "User aborted at host key verification", NULL,
+                           0, TRUE);
+            crStopV;
+        }
+        logevent("Host key fingerprint is:");
+        logevent(s->fingerprint);
+        sfree(s->fingerprint);
+        /*
+         * Save this host key, to check against the one presented in
+         * subsequent rekeys.
+         */
+        ssh->hostkey_str = s->keystr;
+    } else {
+        /*
+         * In a rekey, we never present an interactive host key
+         * verification request to the user. Instead, we simply
+         * enforce that the key we're seeing this time is identical to
+         * the one we saw before.
+         */
+        if (strcmp(ssh->hostkey_str, s->keystr)) {
+            bombout(("Host key was different in repeat key exchange"));
+            crStopV;
+        }
+        sfree(s->keystr);
     }
-    sfree(s->fingerprint);
-    sfree(s->keystr);
     ssh->hostkey->freekey(s->hkey);
 
     /*
@@ -6325,10 +6263,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     /*
      * Expect SSH2_MSG_NEWKEYS from server.
      */
-    crWaitUntil(pktin);
+    crWaitUntilV(pktin);
     if (pktin->type != SSH2_MSG_NEWKEYS) {
        bombout(("expected new-keys packet from server"));
-       crStop(0);
+       crStopV;
     }
     ssh->incoming_data_size = 0;       /* start counting from here */
 
@@ -6430,7 +6368,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
             */
            do_ssh2_authconn(ssh, NULL, 0, NULL);
        }
-       crReturn(1);
+       crReturnV;
     }
     if (pktin) {
        logevent("Server initiated key re-exchange");
@@ -6489,7 +6427,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     }
     goto begin_key_exchange;
 
-    crFinish(1);
+    crFinishV;
 }
 
 /*
@@ -6588,6 +6526,21 @@ static void ssh2_channel_init(struct ssh_channel *c)
     bufchain_init(&c->v.v2.outbuffer);
 }
 
+/*
+ * Construct the common parts of a CHANNEL_OPEN.
+ */
+static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, char *type)
+{
+    struct Packet *pktout;
+
+    pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
+    ssh2_pkt_addstring(pktout, type);
+    ssh2_pkt_adduint32(pktout, c->localid);
+    ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
+    ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT);      /* our max pkt size */
+    return pktout;
+}
+
 /*
  * CHANNEL_FAILURE doesn't come with any indication of what message
  * caused it, so we have to keep track of the outstanding
@@ -6599,6 +6552,7 @@ static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
     struct outstanding_channel_request *ocr =
        snew(struct outstanding_channel_request);
 
+    assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
     ocr->handler = handler;
     ocr->ctx = ctx;
     ocr->next = NULL;
@@ -6609,6 +6563,33 @@ static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
     c->v.v2.chanreq_tail = ocr;
 }
 
+/*
+ * Construct the common parts of a CHANNEL_REQUEST.  If handler is not
+ * NULL then a reply will be requested and the handler will be called
+ * when it arrives.  The returned packet is ready to have any
+ * request-specific data added and be sent.  Note that if a handler is
+ * provided, it's essential that the request actually be sent.
+ *
+ * The handler will usually be passed the response packet in pktin.
+ * If pktin is NULL, this means that no reply will ever be forthcoming
+ * (e.g. because the entire connection is being destroyed) and the
+ * handler should free any storage it's holding.
+ */
+static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type,
+                                       cchandler_fn_t handler, void *ctx)
+{
+    struct Packet *pktout;
+
+    assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+    pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+    ssh2_pkt_adduint32(pktout, c->remoteid);
+    ssh2_pkt_addstring(pktout, type);
+    ssh2_pkt_addbool(pktout, handler != NULL);
+    if (handler != NULL)
+       ssh2_queue_chanreq_handler(c, handler, ctx);
+    return pktout;
+}
+
 /*
  * Potentially enlarge the window on an SSH-2 channel.
  */
@@ -6658,15 +6639,12 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
         */
        if (newwin == c->v.v2.locmaxwin &&
             !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) {
-           pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(pktout, c->remoteid);
-           ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org");
-           ssh2_pkt_addbool(pktout, TRUE);
-           ssh2_pkt_send(ssh, pktout);
-
            up = snew(unsigned);
            *up = newwin - c->v.v2.locwindow;
-           ssh2_queue_chanreq_handler(c, ssh2_handle_winadj_response, up);
+           pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org",
+                                      ssh2_handle_winadj_response, up);
+           ssh2_pkt_send(ssh, pktout);
+
            if (c->v.v2.throttle_state != UNTHROTTLED)
                c->v.v2.throttle_state = UNTHROTTLING;
        } else {
@@ -6824,6 +6802,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
                if (c->u.a.lensofar == c->u.a.totallen) {
                    void *reply;
                    int replylen;
+                    c->u.a.outstanding_requests++;
                    if (agent_query(c->u.a.message,
                                    c->u.a.totallen,
                                    &reply, &replylen,
@@ -6893,8 +6872,10 @@ static void ssh_channel_destroy(struct ssh_channel *c)
     }
 
     del234(ssh->channels, c);
-    if (ssh->version == 2)
+    if (ssh->version == 2) {
         bufchain_clear(&c->v.v2.outbuffer);
+       assert(c->v.v2.chanreq_head == NULL);
+    }
     sfree(c);
 
     /*
@@ -6925,21 +6906,33 @@ static void ssh2_channel_check_close(struct ssh_channel *c)
     Ssh ssh = c->ssh;
     struct Packet *pktout;
 
-    if ((c->closes & (CLOSES_SENT_EOF | CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
-        == (CLOSES_SENT_EOF | CLOSES_RCVD_EOF) && !c->v.v2.chanreq_head) {
+    if (c->halfopen) {
         /*
-         * We have both sent and received EOF, and we have no
-         * outstanding winadj channel requests, which means the
-         * channel is in final wind-up. But we haven't sent CLOSE, so
-         * let's do so now.
+         * If we've sent out our own CHANNEL_OPEN but not yet seen
+         * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then
+         * it's too early to be sending close messages of any kind.
+         */
+        return;
+    }
+
+    if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) ||
+        c->type == CHAN_ZOMBIE) &&
+       !c->v.v2.chanreq_head &&
+       !(c->closes & CLOSES_SENT_CLOSE)) {
+        /*
+         * We have both sent and received EOF (or the channel is a
+         * zombie), and we have no outstanding channel requests, which
+         * means the channel is in final wind-up. But we haven't sent
+         * CLOSE, so let's do so now.
          */
        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
        ssh2_pkt_adduint32(pktout, c->remoteid);
        ssh2_pkt_send(ssh, pktout);
-        c->closes |= CLOSES_SENT_CLOSE;
+        c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
     }
 
     if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+       assert(c->v.v2.chanreq_head == NULL);
         /*
          * We have both sent and received CLOSE, which means we're
          * completely done with the channel.
@@ -6957,8 +6950,10 @@ static void ssh2_channel_got_eof(struct ssh_channel *c)
     if (c->type == CHAN_X11) {
        x11_send_eof(c->u.x11.s);
     } else if (c->type == CHAN_AGENT) {
-        /* Manufacture an outgoing EOF in response to the incoming one. */
-        sshfwd_write_eof(c);
+        if (c->u.a.outstanding_requests == 0) {
+            /* Manufacture an outgoing EOF in response to the incoming one. */
+            sshfwd_write_eof(c);
+        }
     } else if (c->type == CHAN_SOCKDATA) {
        pfd_send_eof(c->u.pfd.s);
     } else if (c->type == CHAN_MAINSESSION) {
@@ -7030,6 +7025,15 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
            break;
         }
 
+        /*
+         * Abandon any buffered data we still wanted to send to this
+         * channel. Receiving a CHANNEL_CLOSE is an indication that
+         * the server really wants to get on and _destroy_ this
+         * channel, and it isn't going to send us any further
+         * WINDOW_ADJUSTs to permit us to send pending stuff.
+         */
+        bufchain_clear(&c->v.v2.outbuffer);
+
         /*
          * Send outgoing EOF.
          */
@@ -7052,17 +7056,42 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
-    if (c->type != CHAN_SOCKDATA_DORMANT)
-       return;                        /* dunno why they're confirming this */
+    assert(c->halfopen); /* ssh2_channel_msg will have enforced 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);
-    if (c->u.pfd.s)
-       pfd_confirm(c->u.pfd.s);
+
+    if (c->type == CHAN_SOCKDATA_DORMANT) {
+        c->type = CHAN_SOCKDATA;
+        if (c->u.pfd.s)
+            pfd_confirm(c->u.pfd.s);
+    } else if (c->type == CHAN_ZOMBIE) {
+        /*
+         * This case can occur if a local socket error occurred
+         * between us sending out CHANNEL_OPEN and receiving
+         * OPEN_CONFIRMATION. In this case, all we can do is
+         * immediately initiate close proceedings now that we know the
+         * server's id to put in the close message.
+         */
+        ssh2_channel_check_close(c);
+    } else {
+        /*
+         * We never expect to receive OPEN_CONFIRMATION for any
+         * *other* channel type (since only local-to-remote port
+         * forwardings cause us to send CHANNEL_OPEN after the main
+         * channel is live - all other auxiliary channel types are
+         * initiated from the server end). It's safe to enforce this
+         * by assertion rather than by ssh_disconnect, because the
+         * real point is that we never constructed a half-open channel
+         * structure in the first place with any type other than the
+         * above.
+         */
+        assert(!"Funny channel type in ssh2_msg_channel_open_confirmation");
+    }
+
     if (c->pending_eof)
-        ssh_channel_try_eof(c);
+        ssh_channel_try_eof(c);        /* in case we had a pending EOF */
 }
 
 static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
@@ -7078,20 +7107,41 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
     char *reason_string;
     int reason_length;
     struct ssh_channel *c;
+
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
-    if (c->type != CHAN_SOCKDATA_DORMANT)
-       return;                        /* dunno why they're failing this */
+    assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
 
-    reason_code = ssh_pkt_getuint32(pktin);
-    if (reason_code >= lenof(reasons))
-       reason_code = 0; /* ensure reasons[reason_code] in range */
-    ssh_pkt_getstring(pktin, &reason_string, &reason_length);
-    logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
-             reasons[reason_code], reason_length, reason_string);
+    if (c->type == CHAN_SOCKDATA_DORMANT) {
+        reason_code = ssh_pkt_getuint32(pktin);
+        if (reason_code >= lenof(reasons))
+            reason_code = 0; /* ensure reasons[reason_code] in range */
+        ssh_pkt_getstring(pktin, &reason_string, &reason_length);
+        logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
+                  reasons[reason_code], reason_length, reason_string);
 
-    pfd_close(c->u.pfd.s);
+        pfd_close(c->u.pfd.s);
+    } else if (c->type == CHAN_ZOMBIE) {
+        /*
+         * This case can occur if a local socket error occurred
+         * between us sending out CHANNEL_OPEN and receiving
+         * OPEN_FAILURE. In this case, we need do nothing except allow
+         * the code below to throw the half-open channel away.
+         */
+    } else {
+        /*
+         * We never expect to receive OPEN_FAILURE for any *other*
+         * channel type (since only local-to-remote port forwardings
+         * cause us to send CHANNEL_OPEN after the main channel is
+         * live - all other auxiliary channel types are initiated from
+         * the server end). It's safe to enforce this by assertion
+         * rather than by ssh_disconnect, because the real point is
+         * that we never constructed a half-open channel structure in
+         * the first place with any type other than the above.
+         */
+        assert(!"Funny channel type in ssh2_msg_channel_open_failure");
+    }
 
     del234(ssh->channels, c);
     sfree(c);
@@ -7151,16 +7201,18 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
                    is_int = FALSE;
                } else {
                    int maybe_int = FALSE, maybe_str = FALSE;
-#define CHECK_HYPOTHESIS(offset, result) \
-    do { \
-       long q = offset; \
-       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))!= 0) && q == len) \
-               result = TRUE; \
-       } \
-    } while(0)
+#define CHECK_HYPOTHESIS(offset, result)                                \
+                    do                                                  \
+                    {                                                   \
+                        int q = toint(offset);                          \
+                        if (q >= 0 && q+4 <= len) {                     \
+                            q = toint(q + 4 + GET_32BIT(p+q));          \
+                            if (q >= 0 && q+4 <= len &&                 \
+                                ((q = toint(q + 4 + GET_32BIT(p+q))) != 0) && \
+                                q == len)                               \
+                                result = TRUE;                          \
+                        }                                               \
+                    } while(0)
                    CHECK_HYPOTHESIS(4+1, maybe_int);
                    CHECK_HYPOTHESIS(4+num+1, maybe_str);
 #undef CHECK_HYPOTHESIS
@@ -7346,15 +7398,18 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
     } else if (typelen == 15 &&
               !memcmp(type, "forwarded-tcpip", 15)) {
        struct ssh_rportfwd pf, *realpf;
-       char *dummy;
-       int dummylen;
-       ssh_pkt_getstring(pktin, &dummy, &dummylen);/* skip address */
+       char *shost;
+       int shostlen;
+       ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */
+        pf.shost = dupprintf("%.*s", shostlen, shost);
        pf.sport = ssh_pkt_getuint32(pktin);
        ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
        peerport = ssh_pkt_getuint32(pktin);
        realpf = find234(ssh->rportfwds, &pf, NULL);
-       logeventf(ssh, "Received remote port %d open request "
-                 "from %s:%d", pf.sport, peeraddr, peerport);
+       logeventf(ssh, "Received remote port %s:%d open request "
+                 "from %s:%d", pf.shost, pf.sport, peeraddr, peerport);
+        sfree(pf.shost);
+
        if (realpf == NULL) {
            error = "Remote port is not recognised";
        } else {
@@ -7380,6 +7435,8 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        else {
            c->type = CHAN_AGENT;       /* identify channel type */
            c->u.a.lensofar = 0;
+            c->u.a.message = NULL;
+            c->u.a.outstanding_requests = 0;
        }
     } else {
        error = "Unsupported channel type requested";
@@ -7447,165 +7504,133 @@ static void ssh2_send_ttymode(void *data, char *mode, char *val)
     ssh2_pkt_adduint32(pktout, arg);
 }
 
-static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin);
-static void ssh2_maybe_setup_x11(struct ssh_channel *c, struct Packet *pktin,
-                                void *ctx)
+static void ssh2_setup_x11(struct ssh_channel *c, struct Packet *pktin,
+                           void *ctx)
 {
-    struct ssh2_maybe_setup_x11_state {
+    struct ssh2_setup_x11_state {
        int crLine;
     };
     Ssh ssh = c->ssh;
     struct Packet *pktout;
-    crStateP(ssh2_maybe_setup_x11_state, ctx);
+    crStateP(ssh2_setup_x11_state, ctx);
 
     crBeginState;
 
+    logevent("Requesting X11 forwarding");
+    pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req",
+                               ssh2_setup_x11, s);
+    ssh2_pkt_addbool(pktout, 0);              /* many connections */
+    ssh2_pkt_addstring(pktout, ssh->x11disp->remoteauthprotoname);
     /*
-     * Potentially enable X11 forwarding.
+     * Note that while we blank the X authentication data here, we don't
+     * take any special action to blank the start of an X11 channel,
+     * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
+     * without having session blanking enabled is likely to leak your
+     * cookie into the log.
      */
-    if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_x11_forward) &&
-       (ssh->x11disp = x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
-                                         conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf))) {
-       logevent("Requesting X11 forwarding");
-       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(pktout, "x11-req");
-       ssh2_pkt_addbool(pktout, 1);           /* want reply */
-       ssh2_pkt_addbool(pktout, 0);           /* many connections */
-       ssh2_pkt_addstring(pktout, ssh->x11disp->remoteauthprotoname);
-       /*
-        * Note that while we blank the X authentication data here, we don't
-        * take any special action to blank the start of an X11 channel,
-        * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
-        * without having session blanking enabled is likely to leak your
-        * cookie into the log.
-        */
-       dont_log_password(ssh, pktout, PKTLOG_BLANK);
-       ssh2_pkt_addstring(pktout, ssh->x11disp->remoteauthdatastring);
-       end_log_omission(ssh, pktout);
-       ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum);
-       ssh2_pkt_send(ssh, pktout);
-
-       ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_maybe_setup_x11, s);
+    dont_log_password(ssh, pktout, PKTLOG_BLANK);
+    ssh2_pkt_addstring(pktout, ssh->x11disp->remoteauthdatastring);
+    end_log_omission(ssh, pktout);
+    ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum);
+    ssh2_pkt_send(ssh, pktout);
 
-       crWaitUntilV(pktin);
+    /* Wait to be called back with either a response packet, or NULL
+     * meaning clean up and free our data */
+    crReturnV;
 
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to X11 forwarding request:"
-                        " packet type %d", pktin->type));
-               sfree(s);
-               crStopV;
-           }
-           logevent("X11 forwarding refused");
-       } else {
-           logevent("X11 forwarding enabled");
-           ssh->X11_fwd_enabled = TRUE;
-       }
+    if (pktin) {
+        if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+            logevent("X11 forwarding enabled");
+            ssh->X11_fwd_enabled = TRUE;
+        } else
+            logevent("X11 forwarding refused");
     }
-    sfree(s);
-    crFinishV;
+
+    crFinishFreeV;
 }
 
-static void ssh2_maybe_setup_agent(struct ssh_channel *c, struct Packet *pktin,
+static void ssh2_setup_agent(struct ssh_channel *c, struct Packet *pktin,
                                   void *ctx)
 {
-    struct ssh2_maybe_setup_agent_state {
+    struct ssh2_setup_agent_state {
        int crLine;
     };
     Ssh ssh = c->ssh;
     struct Packet *pktout;
-    crStateP(ssh2_maybe_setup_agent_state, ctx);
+    crStateP(ssh2_setup_agent_state, ctx);
 
     crBeginState;
 
-    if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
-       logevent("Requesting OpenSSH-style agent forwarding");
-       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(pktout, "auth-agent-req@openssh.com");
-       ssh2_pkt_addbool(pktout, 1);           /* want reply */
-       ssh2_pkt_send(ssh, pktout);
+    logevent("Requesting OpenSSH-style agent forwarding");
+    pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com",
+                               ssh2_setup_agent, s);
+    ssh2_pkt_send(ssh, pktout);
 
-       ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_maybe_setup_agent, s);
-
-       crWaitUntilV(pktin);
+    /* Wait to be called back with either a response packet, or NULL
+     * meaning clean up and free our data */
+    crReturnV;
 
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to agent forwarding request:"
-                        " packet type %d", pktin->type));
-               crStopV;
-           }
-           logevent("Agent forwarding refused");
-       } else {
-           logevent("Agent forwarding enabled");
-           ssh->agentfwd_enabled = TRUE;
-       }
+    if (pktin) {
+        if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+            logevent("Agent forwarding enabled");
+            ssh->agentfwd_enabled = TRUE;
+        } else
+            logevent("Agent forwarding refused");
     }
-    sfree(s);
-    crFinishV;
+
+    crFinishFreeV;
 }
 
-static void ssh2_maybe_setup_pty(struct ssh_channel *c, struct Packet *pktin,
+static void ssh2_setup_pty(struct ssh_channel *c, struct Packet *pktin,
                                 void *ctx)
 {
-    struct ssh2_maybe_setup_pty_state {
+    struct ssh2_setup_pty_state {
        int crLine;
     };
     Ssh ssh = c->ssh;
     struct Packet *pktout;
-    crStateP(ssh2_maybe_setup_pty_state, ctx);
+    crStateP(ssh2_setup_pty_state, ctx);
 
     crBeginState;
 
-    if (ssh->mainchan && !ssh->ncmode && !conf_get_int(ssh->conf, CONF_nopty)) {
-       /* Unpick the terminal-speed string. */
-       /* XXX perhaps we should allow no speeds to be sent. */
-        ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
-       sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
-       /* Build the pty request. */
-       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);    /* recipient channel */
-       ssh2_pkt_addstring(pktout, "pty-req");
-       ssh2_pkt_addbool(pktout, 1);           /* want reply */
-       ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype));
-       ssh2_pkt_adduint32(pktout, ssh->term_width);
-       ssh2_pkt_adduint32(pktout, ssh->term_height);
-       ssh2_pkt_adduint32(pktout, 0);         /* pixel width */
-       ssh2_pkt_adduint32(pktout, 0);         /* pixel height */
-       ssh2_pkt_addstring_start(pktout);
-       parse_ttymodes(ssh, ssh2_send_ttymode, (void *)pktout);
-       ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_ISPEED);
-       ssh2_pkt_adduint32(pktout, ssh->ispeed);
-       ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_OSPEED);
-       ssh2_pkt_adduint32(pktout, ssh->ospeed);
-       ssh2_pkt_addstring_data(pktout, "\0", 1); /* TTY_OP_END */
-       ssh2_pkt_send(ssh, pktout);
-       ssh->state = SSH_STATE_INTERMED;
-
-       ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_maybe_setup_pty, s);
+    /* Unpick the terminal-speed string. */
+    /* XXX perhaps we should allow no speeds to be sent. */
+    ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
+    sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
+    /* Build the pty request. */
+    pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req",
+                               ssh2_setup_pty, s);
+    ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype));
+    ssh2_pkt_adduint32(pktout, ssh->term_width);
+    ssh2_pkt_adduint32(pktout, ssh->term_height);
+    ssh2_pkt_adduint32(pktout, 0);            /* pixel width */
+    ssh2_pkt_adduint32(pktout, 0);            /* pixel height */
+    ssh2_pkt_addstring_start(pktout);
+    parse_ttymodes(ssh, ssh2_send_ttymode, (void *)pktout);
+    ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_ISPEED);
+    ssh2_pkt_adduint32(pktout, ssh->ispeed);
+    ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_OSPEED);
+    ssh2_pkt_adduint32(pktout, ssh->ospeed);
+    ssh2_pkt_addstring_data(pktout, "\0", 1); /* TTY_OP_END */
+    ssh2_pkt_send(ssh, pktout);
+    ssh->state = SSH_STATE_INTERMED;
+
+    /* Wait to be called back with either a response packet, or NULL
+     * meaning clean up and free our data */
+    crReturnV;
 
-       crWaitUntilV(pktin);
-
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to pty request:"
-                        " packet type %d", pktin->type));
-               crStopV;
-           }
-           c_write_str(ssh, "Server refused to allocate pty\r\n");
-           ssh->editing = ssh->echoing = 1;
-       } else {
-           logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
-                     ssh->ospeed, ssh->ispeed);
+    if (pktin) {
+        if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+            logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
+                      ssh->ospeed, ssh->ispeed);
             ssh->got_pty = TRUE;
-       }
-    } else {
-       ssh->editing = ssh->echoing = 1;
+        } else {
+            c_write_str(ssh, "Server refused to allocate pty\r\n");
+            ssh->editing = ssh->echoing = 1;
+        }
     }
-    sfree(s);
-    crFinishV;
+
+    crFinishFreeV;
 }
 
 static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
@@ -7628,20 +7653,16 @@ static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
      * then wait for a whole bunch of successes or failures.
      */
     s->num_env = 0;
-    if (ssh->mainchan && !ssh->ncmode) {
+    {
        char *key, *val;
 
        for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key);
             val != NULL;
             val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) {
-           pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-           ssh2_pkt_addstring(pktout, "env");
-           ssh2_pkt_addbool(pktout, 1);               /* want reply */
+           pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s);
            ssh2_pkt_addstring(pktout, key);
            ssh2_pkt_addstring(pktout, val);
            ssh2_pkt_send(ssh, pktout);
-           ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_setup_env, s);
 
            s->num_env++;
        }
@@ -7654,18 +7675,12 @@ static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
        s->env_left = s->num_env;
 
        while (s->env_left > 0) {
-           crWaitUntilV(pktin);
-
-           if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-               if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-                   bombout(("Unexpected response to environment request:"
-                            " packet type %d", pktin->type));
-                   crStopV;
-               }
-           } else {
+            /* Wait to be called back with either a response packet,
+             * or NULL meaning clean up and free our data */
+            crReturnV;
+           if (!pktin) goto out;
+           if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS)
                s->env_ok++;
-           }
-
            s->env_left--;
        }
 
@@ -7680,8 +7695,8 @@ static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
            c_write_str(ssh, "Server refused to set all environment variables\r\n");
        }
     }
-    sfree(s);
-    crFinishV;
+  out:;
+    crFinishFreeV;
 }
 
 /*
@@ -7903,13 +7918,53 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                int keyi;
                unsigned char *p;
                p = s->agent_response + 5;
-               s->nkeys = GET_32BIT(p);
+               s->nkeys = toint(GET_32BIT(p));
+
+                /*
+                 * Vet the Pageant response to ensure that the key
+                 * count and blob lengths make sense.
+                 */
+                if (s->nkeys < 0) {
+                    logeventf(ssh, "Pageant response contained a negative"
+                              " key count %d", s->nkeys);
+                    s->nkeys = 0;
+                    goto done_agent_query;
+                } else {
+                    unsigned char *q = p + 4;
+                    int lenleft = s->agent_responselen - 5 - 4;
+
+                    for (keyi = 0; keyi < s->nkeys; keyi++) {
+                        int bloblen, commentlen;
+                        if (lenleft < 4) {
+                            logeventf(ssh, "Pageant response was truncated");
+                            s->nkeys = 0;
+                            goto done_agent_query;
+                        }
+                        bloblen = toint(GET_32BIT(q));
+                        if (bloblen < 0 || bloblen > lenleft) {
+                            logeventf(ssh, "Pageant response was truncated");
+                            s->nkeys = 0;
+                            goto done_agent_query;
+                        }
+                        lenleft -= 4 + bloblen;
+                        q += 4 + bloblen;
+                        commentlen = toint(GET_32BIT(q));
+                        if (commentlen < 0 || commentlen > lenleft) {
+                            logeventf(ssh, "Pageant response was truncated");
+                            s->nkeys = 0;
+                            goto done_agent_query;
+                        }
+                        lenleft -= 4 + commentlen;
+                        q += 4 + commentlen;
+                    }
+                }
+
                p += 4;
                logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys);
                if (s->publickey_blob) {
                    /* See if configured key is in agent. */
                    for (keyi = 0; keyi < s->nkeys; keyi++) {
-                       s->pklen = GET_32BIT(p);
+                       s->pklen = toint(GET_32BIT(p));
                        if (s->pklen == s->publickey_bloblen &&
                            !memcmp(p+4, s->publickey_blob,
                                    s->publickey_bloblen)) {
@@ -7920,7 +7975,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                            break;
                        }
                        p += 4 + s->pklen;
-                       p += GET_32BIT(p) + 4; /* comment */
+                       p += toint(GET_32BIT(p)) + 4; /* comment */
                    }
                    if (!s->pkblob_in_agent) {
                        logevent("Configured key file not in Pageant");
@@ -7930,6 +7985,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
            } else {
                 logevent("Failed to get reply from Pageant");
            }
+          done_agent_query:;
        }
 
     }
@@ -8181,13 +8237,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                logeventf(ssh, "Trying Pageant key #%d", s->keyi);
 
                /* Unpack key from agent response */
-               s->pklen = GET_32BIT(s->agentp);
+               s->pklen = toint(GET_32BIT(s->agentp));
                s->agentp += 4;
                s->pkblob = (char *)s->agentp;
                s->agentp += s->pklen;
-               s->alglen = GET_32BIT(s->pkblob);
+               s->alglen = toint(GET_32BIT(s->pkblob));
                s->alg = s->pkblob + 4;
-               s->commentlen = GET_32BIT(s->agentp);
+               s->commentlen = toint(GET_32BIT(s->agentp));
                s->agentp += 4;
                s->commentp = (char *)s->agentp;
                s->agentp += s->commentlen;
@@ -8291,7 +8347,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    s->ret = vret;
                    sfree(s->agentreq);
                    if (s->ret) {
-                       if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
+                       if (s->retlen >= 9 &&
+                            s->ret[4] == SSH2_AGENT_SIGN_RESPONSE &&
+                            GET_32BIT(s->ret + 5) <= (unsigned)(s->retlen-9)) {
                            logevent("Sending Pageant's response");
                            ssh2_add_sigblob(ssh, s->pktout,
                                             s->pkblob, s->pklen,
@@ -9138,66 +9196,30 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      */
     if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) {
        ssh->mainchan = NULL;
-    } else if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) {
-       /*
-        * Just start a direct-tcpip channel and use it as the main
-        * channel.
-        */
+    } else {
        ssh->mainchan = snew(struct ssh_channel);
        ssh->mainchan->ssh = ssh;
        ssh2_channel_init(ssh->mainchan);
-       logeventf(ssh,
-                 "Opening direct-tcpip channel to %s:%d in place of session",
-                 conf_get_str(ssh->conf, CONF_ssh_nc_host),
-                 conf_get_int(ssh->conf, CONF_ssh_nc_port));
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
-       ssh2_pkt_addstring(s->pktout, "direct-tcpip");
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
-       ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT);      /* our max pkt size */
-       ssh2_pkt_addstring(s->pktout, conf_get_str(ssh->conf, CONF_ssh_nc_host));
-       ssh2_pkt_adduint32(s->pktout, conf_get_int(ssh->conf, CONF_ssh_nc_port));
-       /*
-        * There's nothing meaningful to put in the originator
-        * fields, but some servers insist on syntactically correct
-        * information.
-        */
-       ssh2_pkt_addstring(s->pktout, "0.0.0.0");
-       ssh2_pkt_adduint32(s->pktout, 0);
-       ssh2_pkt_send(ssh, s->pktout);
 
-       crWaitUntilV(pktin);
-       if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
-           bombout(("Server refused to open a direct-tcpip channel"));
-           crStopV;
-           /* FIXME: error data comes back in FAILURE packet */
-       }
-       if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) {
-           bombout(("Server's channel confirmation cited wrong channel"));
-           crStopV;
+       if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) {
+           /*
+            * Just start a direct-tcpip channel and use it as the main
+            * channel.
+            */
+           ssh_send_port_open(ssh->mainchan,
+                              conf_get_str(ssh->conf, CONF_ssh_nc_host),
+                              conf_get_int(ssh->conf, CONF_ssh_nc_port),
+                              "main channel");
+           ssh->ncmode = TRUE;
+       } else {
+           s->pktout = ssh2_chanopen_init(ssh->mainchan, "session");
+           logevent("Opening session as main channel");
+           ssh2_pkt_send(ssh, s->pktout);
+           ssh->ncmode = FALSE;
        }
-       ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin);
-       ssh->mainchan->halfopen = FALSE;
-       ssh->mainchan->type = CHAN_MAINSESSION;
-       ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
-       ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
-       add234(ssh->channels, ssh->mainchan);
-       update_specials_menu(ssh->frontend);
-       logevent("Opened direct-tcpip channel");
-       ssh->ncmode = TRUE;
-    } else {
-       ssh->mainchan = snew(struct ssh_channel);
-       ssh->mainchan->ssh = ssh;
-       ssh2_channel_init(ssh->mainchan);
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
-       ssh2_pkt_addstring(s->pktout, "session");
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
-       ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT);    /* our max pkt size */
-       ssh2_pkt_send(ssh, s->pktout);
        crWaitUntilV(pktin);
        if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
-           bombout(("Server refused to open a session"));
+           bombout(("Server refused to open channel"));
            crStopV;
            /* FIXME: error data comes back in FAILURE packet */
        }
@@ -9212,8 +9234,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
        add234(ssh->channels, ssh->mainchan);
        update_specials_menu(ssh->frontend);
-       logevent("Opened channel for session");
-       ssh->ncmode = FALSE;
+       logevent("Opened main channel");
     }
 
     /*
@@ -9244,10 +9265,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
         * this one, so it's safe for it to advertise a very large
         * window and leave the flow control to TCP.
         */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(s->pktout, "simple@putty.projects.tartarus.org");
-       ssh2_pkt_addbool(s->pktout, 0); /* no reply */
+       s->pktout = ssh2_chanreq_init(ssh->mainchan,
+                                     "simple@putty.projects.tartarus.org",
+                                     NULL, NULL);
        ssh2_pkt_send(ssh, s->pktout);
     }
 
@@ -9256,91 +9276,92 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      */
     ssh_setup_portfwd(ssh, ssh->conf);
 
-    /*
-     * Send the CHANNEL_REQUESTS for the main channel.  Each one is
-     * handled by its own little asynchronous co-routine.
-     */
+    if (ssh->mainchan && !ssh->ncmode) {
+       /*
+        * Send the CHANNEL_REQUESTS for the main session channel.
+        * Each one is handled by its own little asynchronous
+        * co-routine.
+        */
 
-    /*
-     * Potentially enable X11 forwarding.
-     */
-    ssh2_maybe_setup_x11(ssh->mainchan, NULL, NULL);
+       /* Potentially enable X11 forwarding. */
+       if (conf_get_int(ssh->conf, CONF_x11_forward) &&
+           (ssh->x11disp =
+            x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
+                              conf_get_int(ssh->conf, CONF_x11_auth),
+                              ssh->conf)))
+           ssh2_setup_x11(ssh->mainchan, NULL, NULL);
 
-    /*
-     * Potentially enable agent forwarding.
-     */
-    ssh2_maybe_setup_agent(ssh->mainchan, NULL, NULL);
+       /* Potentially enable agent forwarding. */
+       if (conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists())
+           ssh2_setup_agent(ssh->mainchan, NULL, NULL);
 
-    /*
-     * Now allocate a pty for the session.
-     */
-    ssh2_maybe_setup_pty(ssh->mainchan, NULL, NULL);
+       /* Now allocate a pty for the session. */
+       if (!conf_get_int(ssh->conf, CONF_nopty))
+           ssh2_setup_pty(ssh->mainchan, NULL, NULL);
 
-    /*
-     * Send environment variables.
-     */
-    ssh2_setup_env(ssh->mainchan, NULL, NULL);
+       /* Send environment variables. */
+       ssh2_setup_env(ssh->mainchan, NULL, NULL);
 
-    /*
-     * Start a shell or a remote command. We may have to attempt
-     * this twice if the config data has provided a second choice
-     * of command.
-     */
-    if (ssh->mainchan && !ssh->ncmode) while (1) {
-       int subsys;
-       char *cmd;
+       /*
+        * Start a shell or a remote command. We may have to attempt
+        * this twice if the config data has provided a second choice
+        * of command.
+        */
+       while (1) {
+           int subsys;
+           char *cmd;
 
-       if (ssh->fallback_cmd) {
-           subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2);
-           cmd = conf_get_str(ssh->conf, CONF_remote_cmd2);
-       } else {
-           subsys = conf_get_int(ssh->conf, CONF_ssh_subsys);
-           cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
-       }
+           if (ssh->fallback_cmd) {
+               subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2);
+               cmd = conf_get_str(ssh->conf, CONF_remote_cmd2);
+           } else {
+               subsys = conf_get_int(ssh->conf, CONF_ssh_subsys);
+               cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
+           }
 
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
-       if (subsys) {
-           ssh2_pkt_addstring(s->pktout, "subsystem");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
-           ssh2_pkt_addstring(s->pktout, cmd);
-       } else if (*cmd) {
-           ssh2_pkt_addstring(s->pktout, "exec");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
-           ssh2_pkt_addstring(s->pktout, cmd);
-       } else {
-           ssh2_pkt_addstring(s->pktout, "shell");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
-       }
-       ssh2_pkt_send(ssh, s->pktout);
-       ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_response_authconn, NULL);
+           if (subsys) {
+               s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem",
+                                             ssh2_response_authconn, NULL);
+               ssh2_pkt_addstring(s->pktout, cmd);
+           } else if (*cmd) {
+               s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec",
+                                             ssh2_response_authconn, NULL);
+               ssh2_pkt_addstring(s->pktout, cmd);
+           } else {
+               s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell",
+                                             ssh2_response_authconn, NULL);
+           }
+           ssh2_pkt_send(ssh, s->pktout);
 
-       crWaitUntilV(pktin);
+           crWaitUntilV(pktin);
 
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to shell/command request:"
-                        " packet type %d", pktin->type));
+           if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
+               if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
+                   bombout(("Unexpected response to shell/command request:"
+                            " packet type %d", pktin->type));
+                   crStopV;
+               }
+               /*
+                * We failed to start the command. If this is the
+                * fallback command, we really are finished; if it's
+                * not, and if the fallback command exists, try falling
+                * back to it before complaining.
+                */
+               if (!ssh->fallback_cmd &&
+                   *conf_get_str(ssh->conf, CONF_remote_cmd2)) {
+                   logevent("Primary command failed; attempting fallback");
+                   ssh->fallback_cmd = TRUE;
+                   continue;
+               }
+               bombout(("Server refused to start a shell/command"));
                crStopV;
+           } else {
+               logevent("Started a shell/command");
            }
-           /*
-            * We failed to start the command. If this is the
-            * fallback command, we really are finished; if it's
-            * not, and if the fallback command exists, try falling
-            * back to it before complaining.
-            */
-           if (!ssh->fallback_cmd &&
-               *conf_get_str(ssh->conf, CONF_remote_cmd2)) {
-               logevent("Primary command failed; attempting fallback");
-               ssh->fallback_cmd = TRUE;
-               continue;
-           }
-           bombout(("Server refused to start a shell/command"));
-           crStopV;
-       } else {
-           logevent("Started a shell/command");
+           break;
        }
-       break;
+    } else {
+       ssh->editing = ssh->echoing = TRUE;
     }
 
     ssh->state = SSH_STATE_SESSION;
@@ -9349,13 +9370,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     if (ssh->eof_needed)
        ssh_special(ssh, TS_EOF);
 
-    /*
-     * All the initial channel requests are done, so install the default
-     * response handler.
-     */
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response;
-
     /*
      * Transfer data!
      */
@@ -9534,7 +9548,7 @@ static void ssh2_protocol_setup(Ssh ssh)
     ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
 }
 
-static void ssh2_timer(void *ctx, long now)
+static void ssh2_timer(void *ctx, unsigned long now)
 {
     Ssh ssh = (Ssh)ctx;
 
@@ -9542,7 +9556,7 @@ static void ssh2_timer(void *ctx, long now)
        return;
 
     if (!ssh->kex_in_progress && conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 &&
-       now - ssh->next_rekey >= 0) {
+       now == ssh->next_rekey) {
        do_ssh2_transport(ssh, "timeout", -1, NULL);
     }
 }
@@ -9610,6 +9624,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->kex = NULL;
     ssh->kex_ctx = NULL;
     ssh->hostkey = NULL;
+    ssh->hostkey_str = NULL;
     ssh->exitcode = -1;
     ssh->close_expected = FALSE;
     ssh->clean_exit = FALSE;
@@ -9737,7 +9752,7 @@ static void ssh_free(void *handle)
     while (ssh->qhead) {
        struct queued_handler *qh = ssh->qhead;
        ssh->qhead = qh->next;
-       sfree(ssh->qhead);
+       sfree(qh);
     }
     ssh->qhead = ssh->qtail = NULL;
 
@@ -9754,6 +9769,17 @@ static void ssh_free(void *handle)
                    pfd_close(c->u.pfd.s);
                break;
            }
+           if (ssh->version == 2) {
+               struct outstanding_channel_request *ocr, *nocr;
+               ocr = c->v.v2.chanreq_head;
+               while (ocr) {
+                   ocr->handler(c, NULL, ocr->ctx);
+                   nocr = ocr->next;
+                   sfree(ocr);
+                   ocr = nocr;
+               }
+               bufchain_clear(&c->v.v2.outbuffer);
+           }
            sfree(c);
        }
        freetree234(ssh->channels);
@@ -9776,6 +9802,7 @@ static void ssh_free(void *handle)
     sfree(ssh->v_c);
     sfree(ssh->v_s);
     sfree(ssh->fullhostname);
+    sfree(ssh->hostkey_str);
     if (ssh->crcda_ctx) {
        crcda_free_context(ssh->crcda_ctx);
        ssh->crcda_ctx = NULL;
@@ -9814,10 +9841,10 @@ static void ssh_reconfig(void *handle, Conf *conf)
     rekey_time = conf_get_int(conf, CONF_ssh_rekey_time);
     if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != rekey_time &&
        rekey_time != 0) {
-       long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC;
-       long now = GETTICKCOUNT();
+       unsigned long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC;
+       unsigned long now = GETTICKCOUNT();
 
-       if (new_next - now < 0) {
+       if (now - ssh->last_rekey > rekey_time*60*TICKSPERSEC) {
            rekeying = "timeout shortened";
        } else {
            ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh);
@@ -9939,10 +9966,8 @@ static void ssh_size(void *handle, int width, int height)
                            PKT_INT, ssh->term_width,
                            PKT_INT, 0, PKT_INT, 0, PKT_END);
            } else if (ssh->mainchan) {
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-               ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-               ssh2_pkt_addstring(pktout, "window-change");
-               ssh2_pkt_addbool(pktout, 0);
+               pktout = ssh2_chanreq_init(ssh->mainchan, "window-change",
+                                          NULL, NULL);
                ssh2_pkt_adduint32(pktout, ssh->term_width);
                ssh2_pkt_adduint32(pktout, ssh->term_height);
                ssh2_pkt_adduint32(pktout, 0);
@@ -10078,10 +10103,7 @@ static void ssh_special(void *handle, Telnet_Special code)
        if (ssh->version == 1) {
            logevent("Unable to send BREAK signal in SSH-1");
        } else if (ssh->mainchan) {
-           pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-           ssh2_pkt_addstring(pktout, "break");
-           ssh2_pkt_addbool(pktout, 0);
+           pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL);
            ssh2_pkt_adduint32(pktout, 0);   /* default break length */
            ssh2_pkt_send(ssh, pktout);
        }
@@ -10106,10 +10128,7 @@ static void ssh_special(void *handle, Telnet_Special code)
        if (signame) {
            /* It's a signal. */
            if (ssh->version == 2 && ssh->mainchan) {
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-               ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-               ssh2_pkt_addstring(pktout, "signal");
-               ssh2_pkt_addbool(pktout, 0);
+               pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL);
                ssh2_pkt_addstring(pktout, signame);
                ssh2_pkt_send(ssh, pktout);
                logeventf(ssh, "Sent signal SIG%s", signame);
@@ -10178,7 +10197,7 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
     Ssh ssh = c->ssh;
     struct Packet *pktout;
 
-    logeventf(ssh, "Opening forwarded connection to %s:%d", hostname, port);
+    logeventf(ssh, "Opening connection to %s:%d for %s", hostname, port, org);
 
     if (ssh->version == 1) {
        send_packet(ssh, SSH1_MSG_PORT_OPEN,
@@ -10188,11 +10207,7 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
                    /* PKT_STR, <org:orgport>, */
                    PKT_END);
     } else {
-       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
-       ssh2_pkt_addstring(pktout, "direct-tcpip");
-       ssh2_pkt_adduint32(pktout, c->localid);
-       ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
-       ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT);      /* our max pkt size */
+       pktout = ssh2_chanopen_init(c, "direct-tcpip");
        ssh2_pkt_addstring(pktout, hostname);
        ssh2_pkt_adduint32(pktout, port);
        /*