Rename the various ssh2_maybe_setup_* functions to ssh2_setup_*, and
[sgt/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 7aec791..c419a89 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -439,6 +439,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__:;\
@@ -778,8 +780,8 @@ 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 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 {
@@ -1017,14 +1019,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 +1063,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' */
     }
 }
 
@@ -4244,6 +4249,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 +4260,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)
@@ -4284,25 +4281,19 @@ void sshfwd_write_eof(struct ssh_channel *c)
 void sshfwd_unclean_close(struct ssh_channel *c)
 {
     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);
+        logevent("Forwarded X11 connection terminated due to local error");
         break;
       case CHAN_SOCKDATA:
       case CHAN_SOCKDATA_DORMANT:
         pfd_close(c->u.pfd.s);
+        logevent("Forwarded port closed due to local error");
         break;
     }
     c->type = CHAN_ZOMBIE;
@@ -5485,7 +5476,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;
@@ -5726,7 +5717,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 +5729,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 +5764,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 +5782,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 +5806,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 +5828,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 */
@@ -5901,11 +5892,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 +5905,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 +5917,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 +5930,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 +5942,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,7 +5955,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;
            }
        }
 
@@ -5979,7 +5970,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                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 +6007,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 +6042,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 +6053,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 +6086,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 +6108,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 +6168,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,7 +6196,7 @@ 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;
     }
 
     /*
@@ -6222,11 +6213,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 host key response"));
-                    crStop(0);
+                    crStopV;
             }
         } while (pktin || inlen > 0);
         s->dlgret = ssh->user_response;
@@ -6235,7 +6226,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     if (s->dlgret == 0) {
        ssh_disconnect(ssh, "User aborted at host key verification", NULL,
                       0, TRUE);
-        crStop(0);
+        crStopV;
     }
     if (!s->got_session_id) {     /* don't bother logging this in rekeys */
        logevent("Host key fingerprint is:");
@@ -6325,10 +6316,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 +6421,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 +6480,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     }
     goto begin_key_exchange;
 
-    crFinish(1);
+    crFinishV;
 }
 
 /*
@@ -6599,6 +6590,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;
@@ -6610,6 +6602,33 @@ static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
 }
 
 /*
+ * 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.
  */
 static void ssh2_handle_winadj_response(struct ssh_channel *, struct Packet *,
@@ -6658,15 +6677,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 {
@@ -6893,8 +6909,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 +6943,24 @@ 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 ((!((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, 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.
+         * 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.
@@ -7447,6 +7468,193 @@ static void ssh2_send_ttymode(void *data, char *mode, char *val)
     ssh2_pkt_adduint32(pktout, arg);
 }
 
+static void ssh2_setup_x11(struct ssh_channel *c, struct Packet *pktin,
+                           void *ctx)
+{
+    struct ssh2_setup_x11_state {
+       int crLine;
+    };
+    Ssh ssh = c->ssh;
+    struct Packet *pktout;
+    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);
+    /*
+     * 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);
+
+    crWaitUntilV(pktin);
+
+    if (pktin) {
+        if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+            logevent("X11 forwarding enabled");
+            ssh->X11_fwd_enabled = TRUE;
+        } else
+            logevent("X11 forwarding refused");
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_agent(struct ssh_channel *c, struct Packet *pktin,
+                                  void *ctx)
+{
+    struct ssh2_setup_agent_state {
+       int crLine;
+    };
+    Ssh ssh = c->ssh;
+    struct Packet *pktout;
+    crStateP(ssh2_setup_agent_state, ctx);
+
+    crBeginState;
+
+    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);
+
+    crWaitUntilV(pktin);
+
+    if (pktin) {
+        if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+            logevent("Agent forwarding enabled");
+            ssh->agentfwd_enabled = TRUE;
+        } else
+            logevent("Agent forwarding refused");
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_pty(struct ssh_channel *c, struct Packet *pktin,
+                                void *ctx)
+{
+    struct ssh2_setup_pty_state {
+       int crLine;
+    };
+    Ssh ssh = c->ssh;
+    struct Packet *pktout;
+    crStateP(ssh2_setup_pty_state, ctx);
+
+    crBeginState;
+
+    /* 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;
+
+    crWaitUntilV(pktin);
+
+    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 {
+            c_write_str(ssh, "Server refused to allocate pty\r\n");
+            ssh->editing = ssh->echoing = 1;
+        }
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
+                          void *ctx)
+{
+    struct ssh2_setup_env_state {
+       int crLine;
+       int num_env, env_left, env_ok;
+    };
+    Ssh ssh = c->ssh;
+    struct Packet *pktout;
+    crStateP(ssh2_setup_env_state, ctx);
+
+    crBeginState;
+
+    /*
+     * Send environment variables.
+     * 
+     * Simplest thing here is to send all the requests at once, and
+     * then wait for a whole bunch of successes or failures.
+     */
+    s->num_env = 0;
+    {
+       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_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s);
+           ssh2_pkt_addstring(pktout, key);
+           ssh2_pkt_addstring(pktout, val);
+           ssh2_pkt_send(ssh, pktout);
+
+           s->num_env++;
+       }
+       if (s->num_env)
+           logeventf(ssh, "Sent %d environment variables", s->num_env);
+    }
+
+    if (s->num_env) {
+       s->env_ok = 0;
+       s->env_left = s->num_env;
+
+       while (s->env_left > 0) {
+           crWaitUntilV(pktin);
+           if (!pktin) goto out;
+           if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS)
+               s->env_ok++;
+           s->env_left--;
+       }
+
+       if (s->env_ok == s->num_env) {
+           logevent("All environment variables successfully set");
+       } else if (s->env_ok == 0) {
+           logevent("All environment variables refused");
+           c_write_str(ssh, "Server refused to set environment variables\r\n");
+       } else {
+           logeventf(ssh, "%d environment variables refused",
+                     s->num_env - s->env_ok);
+           c_write_str(ssh, "Server refused to set all environment variables\r\n");
+       }
+    }
+  out:;
+    crFinishFreeV;
+}
+
 /*
  * Handle the SSH-2 userauth and connection layers.
  */
@@ -7455,6 +7663,12 @@ static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin)
     do_ssh2_authconn(ssh, NULL, 0, pktin);
 }
 
+static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin,
+                                  void *ctx)
+{
+    do_ssh2_authconn(c->ssh, NULL, 0, pktin);
+}
+
 static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                             struct Packet *pktin)
 {
@@ -7498,10 +7712,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int siglen, retlen, len;
        char *q, *agentreq, *ret;
        int try_send;
-       int requested_x11;
-       int requested_agent;
-       int requested_tty;
-       int num_env, env_left, env_ok;
        struct Packet *pktout;
        Filename *keyfile;
 #ifndef NO_GSSAPI
@@ -7538,9 +7748,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_authconn;
     ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_authconn;
     ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_authconn;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_authconn;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_authconn;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_authconn;
     
     s->done_service_req = FALSE;
     s->we_are_in = s->userauth_success = FALSE;
@@ -8997,6 +9204,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_msg_channel_request;
     ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] =
        ssh2_msg_channel_open;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response;
+
 
     if (ssh->mainchan && conf_get_int(ssh->conf, CONF_ssh_simple)) {
        /*
@@ -9005,10 +9215,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);
     }
 
@@ -9018,200 +9227,41 @@ 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.  We send them all
-     * and then start looking for responses, so it's important that the
-     * sending and receiving code below it is kept in sync.
+     * Send the CHANNEL_REQUESTS for the main channel.  Each one is
+     * handled by its own little asynchronous co-routine.
      */
 
     /*
      * Potentially enable X11 forwarding.
      */
+    /*
+     * Potentially enable X11 forwarding.
+     */
     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");
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(s->pktout, "x11-req");
-       ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
-       ssh2_pkt_addbool(s->pktout, 0);        /* many connections */
-       ssh2_pkt_addstring(s->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, s->pktout, PKTLOG_BLANK);
-       ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthdatastring);
-       end_log_omission(ssh, s->pktout);
-       ssh2_pkt_adduint32(s->pktout, ssh->x11disp->screennum);
-       ssh2_pkt_send(ssh, s->pktout);
-       s->requested_x11 = TRUE;
-    } else
-       s->requested_x11 = FALSE;
+                                         conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf)))
+        ssh2_setup_x11(ssh->mainchan, NULL, NULL);
 
     /*
      * Potentially enable agent forwarding.
      */
-    if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
-       logevent("Requesting OpenSSH-style agent forwarding");
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(s->pktout, "auth-agent-req@openssh.com");
-       ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
-       ssh2_pkt_send(ssh, s->pktout);
-       s->requested_agent = TRUE;
-    } else
-       s->requested_agent = FALSE;
+    if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists())
+        ssh2_setup_agent(ssh->mainchan, NULL, NULL);
 
     /*
      * Now allocate a pty for the session.
      */
     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. */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
-       ssh2_pkt_addstring(s->pktout, "pty-req");
-       ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
-       ssh2_pkt_addstring(s->pktout, conf_get_str(ssh->conf, CONF_termtype));
-       ssh2_pkt_adduint32(s->pktout, ssh->term_width);
-       ssh2_pkt_adduint32(s->pktout, ssh->term_height);
-       ssh2_pkt_adduint32(s->pktout, 0);              /* pixel width */
-       ssh2_pkt_adduint32(s->pktout, 0);              /* pixel height */
-       ssh2_pkt_addstring_start(s->pktout);
-       parse_ttymodes(ssh, ssh2_send_ttymode, (void *)s->pktout);
-       ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_ISPEED);
-       ssh2_pkt_adduint32(s->pktout, ssh->ispeed);
-       ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_OSPEED);
-       ssh2_pkt_adduint32(s->pktout, ssh->ospeed);
-       ssh2_pkt_addstring_data(s->pktout, "\0", 1); /* TTY_OP_END */
-       ssh2_pkt_send(ssh, s->pktout);
-       ssh->state = SSH_STATE_INTERMED;
-       s->requested_tty = TRUE;
-    } else
-       s->requested_tty = FALSE;
-
-    /*
-     * Send environment variables.
-     * 
-     * Simplest thing here is to send all the requests at once, and
-     * 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)) {
-           s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
-           ssh2_pkt_addstring(s->pktout, "env");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
-           ssh2_pkt_addstring(s->pktout, key);
-           ssh2_pkt_addstring(s->pktout, val);
-           ssh2_pkt_send(ssh, s->pktout);
-
-           s->num_env++;
-       }
-       if (s->num_env)
-           logeventf(ssh, "Sent %d environment variables", s->num_env);
-    }
-
-    /*
-     * All CHANNEL_REQUESTs sent.  Now collect up the replies.  These
-     * must be in precisely the same order as the requests.
-     */
-
-    if (s->requested_x11) {
-       crWaitUntilV(pktin);
-
-       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));
-               crStopV;
-           }
-           logevent("X11 forwarding refused");
-       } else {
-           logevent("X11 forwarding enabled");
-           ssh->X11_fwd_enabled = TRUE;
-       }
-    }
-
-    if (s->requested_agent) {
-       crWaitUntilV(pktin);
-
-       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 (s->requested_tty) {
-       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);
-            ssh->got_pty = TRUE;
-       }
+        ssh2_setup_pty(ssh->mainchan, NULL, NULL);
     } else {
        ssh->editing = ssh->echoing = 1;
     }
 
-    if (s->num_env) {
-       s->env_ok = 0;
-       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 {
-               s->env_ok++;
-           }
-
-           s->env_left--;
-       }
-
-       if (s->env_ok == s->num_env) {
-           logevent("All environment variables successfully set");
-       } else if (s->env_ok == 0) {
-           logevent("All environment variables refused");
-           c_write_str(ssh, "Server refused to set environment variables\r\n");
-       } else {
-           logeventf(ssh, "%d environment variables refused",
-                     s->num_env - s->env_ok);
-           c_write_str(ssh, "Server refused to set all environment variables\r\n");
-       }
-    }
+    /*
+     * Send environment variables.
+     */
+    if (ssh->mainchan && !ssh->ncmode)
+        ssh2_setup_env(ssh->mainchan, NULL, NULL);
 
     /*
      * Start a shell or a remote command. We may have to attempt
@@ -9230,19 +9280,17 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
            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 */
+           s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem",
+                                         ssh2_response_authconn, NULL);
            ssh2_pkt_addstring(s->pktout, cmd);
        } else if (*cmd) {
-           ssh2_pkt_addstring(s->pktout, "exec");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
+           s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec",
+                                         ssh2_response_authconn, NULL);
            ssh2_pkt_addstring(s->pktout, cmd);
        } else {
-           ssh2_pkt_addstring(s->pktout, "shell");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
+           s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell",
+                                         ssh2_response_authconn, NULL);
        }
        ssh2_pkt_send(ssh, s->pktout);
 
@@ -9685,6 +9733,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);
@@ -9870,10 +9929,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);
@@ -10009,10 +10066,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);
        }
@@ -10037,10 +10091,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);