Configurable TCP_NODELAY option on network connections
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index c75c8fe..2163e21 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -159,6 +159,8 @@ static const char *const ssh2_disconnect_reasons[] = {
  */
 #define BUG_CHOKES_ON_SSH1_IGNORE                 1
 #define BUG_SSH2_HMAC                             2
+#define BUG_NEEDS_SSH1_PLAIN_PASSWORD            4
+
 
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
@@ -195,25 +197,52 @@ enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM };
 
 extern char *x11_init(Socket *, char *, void *);
 extern void x11_close(Socket);
-extern void x11_send(Socket, char *, int);
+extern int x11_send(Socket, char *, int);
 extern void x11_invent_auth(char *, int, char *, int);
+extern void x11_unthrottle(Socket s);
+extern void x11_override_throttle(Socket s, int enable);
 
 extern char *pfd_newconnect(Socket * s, char *hostname, int port, void *c);
 extern char *pfd_addforward(char *desthost, int destport, int port);
 extern void pfd_close(Socket s);
-extern void pfd_send(Socket s, char *data, int len);
+extern int pfd_send(Socket s, char *data, int len);
 extern void pfd_confirm(Socket s);
+extern void pfd_unthrottle(Socket s);
+extern void pfd_override_throttle(Socket s, int enable);
+
+/*
+ * Buffer management constants. There are several of these for
+ * various different purposes:
+ * 
+ *  - SSH1_BUFFER_LIMIT is the amount of backlog that must build up
+ *    on a local data stream before we throttle the whole SSH
+ *    connection (in SSH1 only). Throttling the whole connection is
+ *    pretty drastic so we set this high in the hope it won't
+ *    happen very often.
+ * 
+ *  - SSH_MAX_BACKLOG is the amount of backlog that must build up
+ *    on the SSH connection itself before we defensively throttle
+ *    _all_ local data streams. This is pretty drastic too (though
+ *    thankfully unlikely in SSH2 since the window mechanism should
+ *    ensure that the server never has any need to throttle its end
+ *    of the connection), so we set this high as well.
+ * 
+ *  - OUR_V2_WINSIZE is the maximum window size we present on SSH2
+ *    channels.
+ */
+
+#define SSH1_BUFFER_LIMIT 32768
+#define SSH_MAX_BACKLOG 32768
+#define OUR_V2_WINSIZE 16384
 
 /*
- * Ciphers for SSH2. We miss out single-DES because it isn't
- * supported; also 3DES and Blowfish are both done differently from
- * SSH1. (3DES uses outer chaining; Blowfish has the opposite
- * endianness and different-sized keys.)
+ * Ciphers for SSH2.
  */
 const static struct ssh2_ciphers *ciphers[] = {
     &ssh2_aes,
     &ssh2_blowfish,
     &ssh2_3des,
+    &ssh2_des,
 };
 
 const static struct ssh_kex *kex_algs[] = {
@@ -282,11 +311,16 @@ struct ssh_channel {
     unsigned remoteid, localid;
     int type;
     int closes;
-    struct ssh2_data_channel {
-       unsigned char *outbuffer;
-       unsigned outbuflen, outbufsize;
-       unsigned remwindow, remmaxpkt;
-    } v2;
+    union {
+       struct ssh1_data_channel {
+           int throttling;
+       } v1;
+       struct ssh2_data_channel {
+           bufchain outbuffer;
+           unsigned remwindow, remmaxpkt;
+           unsigned locwindow;
+       } v2;
+    } v;
     union {
        struct ssh_agent_channel {
            unsigned char *message;
@@ -394,17 +428,30 @@ static struct Packet pktout = { 0, 0, NULL, NULL, 0 };
 static unsigned char *deferred_send_data = NULL;
 static int deferred_len = 0, deferred_size = 0;
 
+/*
+ * Gross hack: pscp will try to start SFTP but fall back to scp1 if
+ * that fails. This variable is the means by which scp.c can reach
+ * into the SSH code and find out which one it got.
+ */
+int ssh_fallback_cmd = 0;
+
 static int ssh_version;
+static int ssh1_throttle_count;
+static int ssh_overall_bufsize;
+static int ssh_throttled_all;
+static int ssh1_stdout_throttling;
 static void (*ssh_protocol) (unsigned char *in, int inlen, int ispkt);
 static void ssh1_protocol(unsigned char *in, int inlen, int ispkt);
 static void ssh2_protocol(unsigned char *in, int inlen, int ispkt);
 static void ssh_size(void);
 static void ssh_special(Telnet_Special);
-static void ssh2_try_send(struct ssh_channel *c);
+static int ssh2_try_send(struct ssh_channel *c);
 static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
                                  int len);
-
+static void ssh_throttle_all(int enable, int bufsize);
+static void ssh2_set_window(struct ssh_channel *c, unsigned newwin);
 static int (*s_rdpkt) (unsigned char **data, int *datalen);
+static int ssh_sendbuffer(void);
 
 static struct rdpkt1_state_tag {
     long len, pad, biglen, to_read;
@@ -460,7 +507,7 @@ 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 (a->sport > b->sport)
        return +1;
     if (a->sport < b->sport)
@@ -560,12 +607,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        (*data)++, (*datalen)--;
     }
 
-#ifdef FWHACK
-    if (st->len == 0x52656d6f) {       /* "Remo"te server has closed ... */
-       st->len = 0x300;               /* big enough to carry to end */
-    }
-#endif
-
     st->pad = 8 - (st->len % 8);
     st->biglen = st->len + st->pad;
     pktin.length = st->len - 5;
@@ -678,6 +719,8 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        memcpy(buf + nowlen, pktin.body + 4, msglen);
        buf[nowlen + msglen] = '\0';
        logevent(buf);
+       bombout(("Server sent disconnect message:\n\"%s\"", buf+nowlen));
+       crReturn(0);
     }
 
     crFinish(0);
@@ -721,11 +764,7 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
        pktin.data[st->i] = *(*data)++;
        (*datalen)--;
     }
-#ifdef FWHACK
-    if (!memcmp(pktin.data, "Remo", 4)) {      /* "Remo"te server has closed ... */
-       /* FIXME */
-    }
-#endif
+
     if (sccipher)
        sccipher->decrypt(pktin.data, st->cipherblk);
 
@@ -851,6 +890,12 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
        memcpy(buf + nowlen, pktin.data + 14, msglen);
        buf[nowlen + msglen] = '\0';
        logevent(buf);
+       bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"",
+                reason,
+                (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+                ssh2_disconnect_reasons[reason] : "unknown",
+                buf+nowlen));
+       crReturn(0);
     }
 
     crFinish(0);
@@ -895,23 +940,18 @@ static int s_wrpkt_prepare(void)
 
     pktout.body[-1] = pktout.type;
 
-#ifdef DUMP_PACKETS
-    debug(("Packet payload pre-compression:\n"));
-    dmemdump(pktout.body - 1, pktout.length + 1);
-#endif
-
     if (ssh1_compressing) {
        unsigned char *compblk;
        int complen;
+#ifdef DUMP_PACKETS
+       debug(("Packet payload pre-compression:\n"));
+       dmemdump(pktout.body - 1, pktout.length + 1);
+#endif
        zlib_compress_block(pktout.body - 1, pktout.length + 1,
                            &compblk, &complen);
        ssh1_pktout_size(complen - 1);
        memcpy(pktout.body - 1, compblk, complen);
        sfree(compblk);
-#ifdef DUMP_PACKETS
-       debug(("Packet payload post-compression:\n"));
-       dmemdump(pktout.body - 1, pktout.length + 1);
-#endif
     }
 
     len = pktout.length + 5;          /* type and CRC */
@@ -936,9 +976,11 @@ static int s_wrpkt_prepare(void)
 
 static void s_wrpkt(void)
 {
-    int len;
+    int len, backlog;
     len = s_wrpkt_prepare();
-    sk_write(s, pktout.data, len);
+    backlog = sk_write(s, pktout.data, len);
+    if (backlog > SSH_MAX_BACKLOG)
+       ssh_throttle_all(1, backlog);
 }
 
 static void s_wrpkt_defer(void)
@@ -1192,13 +1234,15 @@ static int ssh2_pkt_construct(void)
     /*
      * Compress packet payload.
      */
-#ifdef DUMP_PACKETS
-    debug(("Pre-compression payload:\n"));
-    dmemdump(pktout.data + 5, pktout.length - 5);
-#endif
     {
        unsigned char *newpayload;
        int newlen;
+#ifdef DUMP_PACKETS
+       if (cscomp && cscomp != &ssh_comp_none) {
+           debug(("Pre-compression payload:\n"));
+           dmemdump(pktout.data + 5, pktout.length - 5);
+       }
+#endif
        if (cscomp && cscomp->compress(pktout.data + 5, pktout.length - 5,
                                       &newpayload, &newlen)) {
            pktout.length = 5;
@@ -1244,8 +1288,12 @@ static int ssh2_pkt_construct(void)
  */
 static void ssh2_pkt_send(void)
 {
-    int len = ssh2_pkt_construct();
-    sk_write(s, pktout.data, len);
+    int len;
+    int backlog;
+    len = ssh2_pkt_construct();
+    backlog = sk_write(s, pktout.data, len);
+    if (backlog > SSH_MAX_BACKLOG)
+       ssh_throttle_all(1, backlog);
 }
 
 /*
@@ -1276,10 +1324,13 @@ static void ssh2_pkt_defer(void)
  */
 static void ssh_pkt_defersend(void)
 {
-    sk_write(s, deferred_send_data, deferred_len);
+    int backlog;
+    backlog = sk_write(s, deferred_send_data, deferred_len);
     deferred_len = deferred_size = 0;
     sfree(deferred_send_data);
     deferred_send_data = NULL;
+    if (backlog > SSH_MAX_BACKLOG)
+       ssh_throttle_all(1, backlog);
 }
 
 #if 0
@@ -1364,17 +1415,15 @@ static void ssh_detect_bugs(char *vstring)
     char *imp;                        /* pointer to implementation part */
     imp = vstring;
     imp += strcspn(imp, "-");
-    if (*imp)
-       imp++;
+    if (*imp) imp++;
     imp += strcspn(imp, "-");
-    if (*imp)
-       imp++;
+    if (*imp) imp++;
 
     ssh_remote_bugs = 0;
 
     if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
        !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
-       !strcmp(imp, "1.2.22")) {
+       !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25")) {
        /*
         * These versions don't support SSH1_MSG_IGNORE, so we have
         * to use a different defence against password length
@@ -1384,6 +1433,16 @@ static void ssh_detect_bugs(char *vstring)
        logevent("We believe remote version has SSH1 ignore bug");
     }
 
+    if (!strcmp(imp, "Cisco-1.25")) {
+       /*
+        * These versions need a plain password sent; they can't
+        * handle having a null and a random length of data after
+        * the password.
+        */
+       ssh_remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
+       logevent("We believe remote version needs a plain SSH1 password");
+    }
+
     if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
        !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
        !strncmp(imp, "2.1 ", 4)) {
@@ -1425,8 +1484,8 @@ static int do_ssh_init(unsigned char c)
        crReturn(1);                   /* get another character */
     }
 
-    vstring = smalloc(16);
     vstrsize = 16;
+    vstring = smalloc(vstrsize);
     strcpy(vstring, "SSH-");
     vslen = 4;
     i = 0;
@@ -1452,10 +1511,10 @@ static int do_ssh_init(unsigned char c)
 
     vstring[vslen] = 0;
     vlog = smalloc(20 + vslen);
+    vstring[strcspn (vstring, "\r\n")] = '\0'; /* remove end-of-line chars */
     sprintf(vlog, "Server version: %s", vstring);
-    ssh_detect_bugs(vstring);
-    vlog[strcspn(vlog, "\r\n")] = '\0';
     logevent(vlog);
+    ssh_detect_bugs(vstring);
     sfree(vlog);
 
     /*
@@ -1538,6 +1597,9 @@ static void ssh_gotdata(unsigned char *data, int datalen)
     while (1) {
        while (datalen > 0) {
            if (s_rdpkt(&data, &datalen) == 0) {
+               if (ssh_state == SSH_STATE_CLOSED) {
+                   return;
+               }
                ssh_protocol(NULL, 0, 1);
                if (ssh_state == SSH_STATE_CLOSED) {
                    return;
@@ -1579,17 +1641,29 @@ static int ssh_receive(Plug plug, int urgent, char *data, int len)
     return 1;
 }
 
+static void ssh_sent(Plug plug, int bufsize)
+{
+    /*
+     * If the send backlog on the SSH socket itself clears, we
+     * should unthrottle the whole world if it was throttled.
+     */
+    if (bufsize < SSH_MAX_BACKLOG)
+       ssh_throttle_all(0, bufsize);
+}
+
 /*
  * Connect to specified host and port.
  * Returns an error message, or NULL on success.
  * Also places the canonical host name into `realhost'. It must be
  * freed by the caller.
  */
-static char *connect_to_host(char *host, int port, char **realhost)
+static char *connect_to_host(char *host, int port, char **realhost, int nodelay)
 {
     static struct plug_function_table fn_table = {
        ssh_closing,
-       ssh_receive
+       ssh_receive,
+       ssh_sent,
+       NULL
     }, *fn_table_ptr = &fn_table;
 
     SockAddr addr;
@@ -1618,6 +1692,11 @@ static char *connect_to_host(char *host, int port, char **realhost)
     /*
      * Try to find host.
      */
+    {
+       char buf[200];
+       sprintf(buf, "Looking up host \"%.170s\"", host);
+       logevent(buf);
+    }
     addr = sk_namelookup(host, realhost);
     if ((err = sk_addr_error(addr)))
        return err;
@@ -1629,7 +1708,13 @@ static char *connect_to_host(char *host, int port, char **realhost)
     /*
      * Open socket.
      */
-    s = sk_new(addr, port, 0, 1, &fn_table_ptr);
+    {
+       char buf[200], addrbuf[100];
+       sk_getaddr(addr, addrbuf, 100);
+       sprintf(buf, "Connecting to %.100s port %d", addrbuf, port);
+       logevent(buf);
+    }
+    s = sk_new(addr, port, 0, 1, nodelay, &fn_table_ptr);
     if ((err = sk_socket_error(s)))
        return err;
 
@@ -1647,6 +1732,56 @@ static char *connect_to_host(char *host, int port, char **realhost)
 }
 
 /*
+ * Throttle or unthrottle the SSH connection.
+ */
+static void ssh1_throttle(int adjust)
+{
+    int old_count = ssh1_throttle_count;
+    ssh1_throttle_count += adjust;
+    assert(ssh1_throttle_count >= 0);
+    if (ssh1_throttle_count && !old_count) {
+       sk_set_frozen(s, 1);
+    } else if (!ssh1_throttle_count && old_count) {
+       sk_set_frozen(s, 0);
+    }
+}
+
+/*
+ * Throttle or unthrottle _all_ local data streams (for when sends
+ * on the SSH connection itself back up).
+ */
+static void ssh_throttle_all(int enable, int bufsize)
+{
+    int i;
+    struct ssh_channel *c;
+
+    if (enable == ssh_throttled_all)
+       return;
+    ssh_throttled_all = enable;
+    ssh_overall_bufsize = bufsize;
+    if (!ssh_channels)
+       return;
+    for (i = 0; NULL != (c = index234(ssh_channels, i)); i++) {
+       switch (c->type) {
+         case CHAN_MAINSESSION:
+           /*
+            * This is treated separately, outside the switch.
+            */
+           break;
+         case CHAN_X11:
+           x11_override_throttle(c->u.x11.s, enable);
+           break;
+         case CHAN_AGENT:
+           /* Agent channels require no buffer management. */
+           break;
+         case CHAN_SOCKDATA:
+           pfd_override_throttle(c->u.x11.s, enable);
+           break;
+       }
+    }
+}
+
+/*
  * Handle the key exchange and user authentication phases.
  */
 static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
@@ -1659,6 +1794,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
     struct MD5Context md5c;
     static unsigned long supported_ciphers_mask, supported_auths_mask;
     static int tried_publickey;
+    static int tis_auth_refused, ccard_auth_refused;
     static unsigned char session_id[16];
     static int cipher_type;
     static char username[100];
@@ -1751,31 +1887,45 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     logevent("Encrypted session key");
 
-    switch (cfg.cipher) {
-      case CIPHER_BLOWFISH:
-       cipher_type = SSH_CIPHER_BLOWFISH;
-       break;
-      case CIPHER_DES:
-       cipher_type = SSH_CIPHER_DES;
-       break;
-      case CIPHER_3DES:
-       cipher_type = SSH_CIPHER_3DES;
-       break;
-      case CIPHER_AES:
-       c_write_str("AES not supported in SSH1, falling back to 3DES\r\n");
-       cipher_type = SSH_CIPHER_3DES;
-       break;
-    }
-    if ((supported_ciphers_mask & (1 << cipher_type)) == 0) {
-       c_write_str
-           ("Selected cipher not supported, falling back to 3DES\r\n");
-       cipher_type = SSH_CIPHER_3DES;
-       if ((supported_ciphers_mask & (1 << cipher_type)) == 0) {
-           bombout(("Server violates SSH 1 protocol by "
-                    "not supporting 3DES encryption"));
+    {
+       int cipher_chosen = 0, warn = 0;
+       char *cipher_string = NULL;
+       for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
+           int next_cipher = cfg.ssh_cipherlist[i];
+           if (next_cipher == CIPHER_WARN) {
+               /* If/when we choose a cipher, warn about it */
+               warn = 1;
+           } else if (next_cipher == CIPHER_AES) {
+               /* XXX Probably don't need to mention this. */
+               logevent("AES not supported in SSH1, skipping");
+           } else {
+               switch (next_cipher) {
+                 case CIPHER_3DES:     cipher_type = SSH_CIPHER_3DES;
+                                       cipher_string = "3DES"; break;
+                 case CIPHER_BLOWFISH: cipher_type = SSH_CIPHER_BLOWFISH;
+                                       cipher_string = "Blowfish"; break;
+                 case CIPHER_DES:      cipher_type = SSH_CIPHER_DES;
+                                       cipher_string = "single-DES"; break;
+               }
+               if (supported_ciphers_mask & (1 << cipher_type))
+                   cipher_chosen = 1;
+           }
+       }
+       if (!cipher_chosen) {
+           if ((supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0)
+               bombout(("Server violates SSH 1 protocol by not "
+                        "supporting 3DES encryption"));
+           else
+               /* shouldn't happen */
+               bombout(("No supported ciphers found"));
            crReturn(0);
        }
+
+       /* Warn about chosen cipher if necessary. */
+       if (warn)
+           askcipher(cipher_string, 0);
     }
+
     switch (cipher_type) {
       case SSH_CIPHER_3DES:
        logevent("Using 3DES encryption");
@@ -1893,6 +2043,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
     crWaitUntil(ispkt);
 
     tried_publickey = 0;
+    tis_auth_refused = ccard_auth_refused = 0;
 
     while (pktin.type == SSH1_SMSG_FAILURE) {
        static char password[100];
@@ -1900,12 +2051,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        static int pos;
        static char c;
        static int pwpkt_type;
-       /*
-        * Show password prompt, having first obtained it via a TIS
-        * or CryptoCard exchange if we're doing TIS or CryptoCard
-        * authentication.
-        */
        pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
+
        if (agent_exists()) {
            /*
             * Attempt RSA authentication using Pageant.
@@ -2028,9 +2175,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        if (*cfg.keyfile && !tried_publickey)
            pwpkt_type = SSH1_CMSG_AUTH_RSA;
 
-       if (pktin.type == SSH1_SMSG_FAILURE &&
-           cfg.try_tis_auth &&
-           (supported_auths_mask & (1 << SSH1_AUTH_TIS))) {
+       if (cfg.try_tis_auth &&
+           (supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
+           !tis_auth_refused) {
            pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
            logevent("Requested TIS authentication");
            send_packet(SSH1_CMSG_AUTH_TIS, PKT_END);
@@ -2039,6 +2186,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                logevent("TIS authentication declined");
                if (flags & FLAG_INTERACTIVE)
                    c_write_str("TIS authentication refused.\r\n");
+               tis_auth_refused = 1;
+               continue;
            } else {
                int challengelen = ((pktin.body[0] << 24) |
                                    (pktin.body[1] << 16) |
@@ -2048,12 +2197,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                if (challengelen > sizeof(prompt) - 1)
                    challengelen = sizeof(prompt) - 1;  /* prevent overrun */
                memcpy(prompt, pktin.body + 4, challengelen);
-               prompt[challengelen] = '\0';
+               /* Prompt heuristic comes from OpenSSH */
+               strncpy(prompt + challengelen,
+                       memchr(prompt, '\n', challengelen) ?
+                       "": "\r\nResponse: ",
+                       (sizeof prompt) - challengelen);
+               prompt[(sizeof prompt) - 1] = '\0';
            }
        }
-       if (pktin.type == SSH1_SMSG_FAILURE &&
-           cfg.try_tis_auth &&
-           (supported_auths_mask & (1 << SSH1_AUTH_CCARD))) {
+       if (cfg.try_tis_auth &&
+           (supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
+           !ccard_auth_refused) {
            pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
            logevent("Requested CryptoCard authentication");
            send_packet(SSH1_CMSG_AUTH_CCARD, PKT_END);
@@ -2061,6 +2215,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            if (pktin.type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
                logevent("CryptoCard authentication declined");
                c_write_str("CryptoCard authentication refused.\r\n");
+               ccard_auth_refused = 1;
+               continue;
            } else {
                int challengelen = ((pktin.body[0] << 24) |
                                    (pktin.body[1] << 16) |
@@ -2070,7 +2226,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                if (challengelen > sizeof(prompt) - 1)
                    challengelen = sizeof(prompt) - 1;  /* prevent overrun */
                memcpy(prompt, pktin.body + 4, challengelen);
-               strncpy(prompt + challengelen, "\r\nResponse : ",
+               strncpy(prompt + challengelen,
+                       memchr(prompt, '\n', challengelen) ?
+                       "" : "\r\nResponse: ",
                        sizeof(prompt) - challengelen);
                prompt[sizeof(prompt) - 1] = '\0';
            }
@@ -2092,6 +2250,11 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            sfree(comment);
        }
 
+       /*
+        * Show password prompt, having first obtained it via a TIS
+        * or CryptoCard exchange if we're doing TIS or CryptoCard
+        * authentication.
+        */
        if (ssh_get_line) {
            if (!ssh_get_line(prompt, password, sizeof(password), TRUE)) {
                /*
@@ -2107,7 +2270,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                crReturn(1);
            }
        } else {
-           c_write_str(prompt);
+           /* Prompt may have come from server. We've munged it a bit, so
+            * we know it to be zero-terminated at least once. */
+           c_write_untrusted(prompt, strlen(prompt));
            pos = 0;
            ssh_send_ok = 1;
            while (pos >= 0) {
@@ -2156,7 +2321,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            tried_publickey = 1;
            i = loadrsakey(cfg.keyfile, &pubkey, password);
            if (i == 0) {
-               c_write_str("Couldn't load public key from ");
+               c_write_str("Couldn't load private key from ");
                c_write_str(cfg.keyfile);
                c_write_str(".\r\n");
                continue;              /* go and try password */
@@ -2245,26 +2410,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                 * use of the fact that the password is interpreted
                 * as a C string: so we can append a NUL, then some
                 * random data.
+                * 
+                * One server (a Cisco one) can deal with neither
+                * SSH1_MSG_IGNORE _nor_ a padded password string.
+                * For this server we are left with no defences
+                * against password length sniffing.
                 */
-               if (ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) {
-                   char string[64];
-                   char *s;
-                   int len;
-
-                   len = strlen(password);
-                   if (len < sizeof(string)) {
-                       s = string;
-                       strcpy(string, password);
-                       len++;         /* cover the zero byte */
-                       while (len < sizeof(string)) {
-                           string[len++] = (char) random_byte();
-                       }
-                   } else {
-                       s = password;
-                   }
-                   send_packet(pwpkt_type, PKT_INT, len,
-                               PKT_DATA, s, len, PKT_END);
-               } else {
+               if (!(ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+                   /*
+                    * The server can deal with SSH1_MSG_IGNORE, so
+                    * we can use the primary defence.
+                    */
                    int bottom, top, pwlen, i;
                    char *randomstr;
 
@@ -2296,7 +2452,45 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                                         PKT_STR, randomstr, PKT_END);
                        }
                    }
+                   logevent("Sending password with camouflage packets");
                    ssh_pkt_defersend();
+               } 
+               else if (!(ssh_remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+                   /*
+                    * The server can't deal with SSH1_MSG_IGNORE
+                    * but can deal with padded passwords, so we
+                    * can use the secondary defence.
+                    */
+                   char string[64];
+                   char *s;
+                   int len;
+
+                   len = strlen(password);
+                   if (len < sizeof(string)) {
+                       s = string;
+                       strcpy(string, password);
+                       len++;         /* cover the zero byte */
+                       while (len < sizeof(string)) {
+                           string[len++] = (char) random_byte();
+                       }
+                   } else {
+                       s = password;
+                   }
+                   logevent("Sending length-padded password");
+                   send_packet(pwpkt_type, PKT_INT, len,
+                               PKT_DATA, s, len, PKT_END);
+               } else {
+                   /*
+                    * The server has _both_
+                    * BUG_CHOKES_ON_SSH1_IGNORE and
+                    * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is
+                    * therefore nothing we can do.
+                    */
+                   int len;
+                   len = strlen(password);
+                   logevent("Sending unpadded password");
+                   send_packet(pwpkt_type, PKT_INT, len,
+                               PKT_DATA, password, len, PKT_END);
                }
            } else {
                send_packet(pwpkt_type, PKT_STR, password, PKT_END);
@@ -2327,34 +2521,64 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 void sshfwd_close(struct ssh_channel *c)
 {
     if (c && !c->closes) {
-       if (ssh_version == 1) {
-           send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
-                       PKT_END);
-       } else {
-           ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-           ssh2_pkt_adduint32(c->remoteid);
-           ssh2_pkt_send();
+       /*
+        * If the channel's remoteid is -1, we have sent
+        * CHANNEL_OPEN for this channel, but it hasn't even been
+        * acknowledged by the server. So we must set a close flag
+        * on it now, and then when the server acks the channel
+        * open, we can close it then.
+        */
+       if (c->remoteid != -1) {
+           if (ssh_version == 1) {
+               send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+                           PKT_END);
+           } else {
+               ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+               ssh2_pkt_adduint32(c->remoteid);
+               ssh2_pkt_send();
+           }
        }
        c->closes = 1;
        if (c->type == CHAN_X11) {
            c->u.x11.s = NULL;
            logevent("Forwarded X11 connection terminated");
-       } else if (c->type == CHAN_SOCKDATA) {
+       } else if (c->type == CHAN_SOCKDATA ||
+                  c->type == CHAN_SOCKDATA_DORMANT) {
            c->u.pfd.s = NULL;
            logevent("Forwarded port closed");
        }
     }
 }
 
-void sshfwd_write(struct ssh_channel *c, char *buf, int len)
+int sshfwd_write(struct ssh_channel *c, char *buf, int len)
 {
     if (ssh_version == 1) {
        send_packet(SSH1_MSG_CHANNEL_DATA,
                    PKT_INT, c->remoteid,
                    PKT_INT, len, PKT_DATA, buf, len, PKT_END);
+       /*
+        * In SSH1 we can return 0 here - implying that forwarded
+        * connections are never individually throttled - because
+        * the only circumstance that can cause throttling will be
+        * the whole SSH connection backing up, in which case
+        * _everything_ will be throttled as a whole.
+        */
+       return 0;
     } else {
        ssh2_add_channel_data(c, buf, len);
-       ssh2_try_send(c);
+       return ssh2_try_send(c);
+    }
+}
+
+void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
+{
+    if (ssh_version == 1) {
+       if (c->v.v1.throttling && bufsize < SSH1_BUFFER_LIMIT) {
+           c->v.v1.throttling = 0;
+           ssh1_throttle(-1);
+       }
+    } else {
+       ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
     }
 }
 
@@ -2518,12 +2742,26 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
        zlib_decompress_init();
     }
 
-    if (*cfg.remote_cmd_ptr)
-       send_packet(SSH1_CMSG_EXEC_CMD, PKT_STR, cfg.remote_cmd_ptr,
-                   PKT_END);
-    else
-       send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
-    logevent("Started session");
+    /*
+     * Start the shell or command.
+     * 
+     * Special case: if the first-choice command is an SSH2
+     * subsystem (hence not usable here) and the second choice
+     * exists, we fall straight back to that.
+     */
+    {
+       char *cmd = cfg.remote_cmd_ptr;
+       
+       if (cfg.ssh_subsys && cfg.remote_cmd_ptr2) {
+           cmd = cfg.remote_cmd_ptr2;
+           ssh_fallback_cmd = TRUE;
+       }
+       if (*cmd)
+           send_packet(SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
+       else
+           send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
+       logevent("Started session");
+    }
 
     ssh_state = SSH_STATE_SESSION;
     if (size_needed)
@@ -2531,7 +2769,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
     if (eof_needed)
        ssh_special(TS_EOF);
 
-    ldisc_send(NULL, 0);              /* cause ldisc to notice changes */
+    ldisc_send(NULL, 0, 0);           /* cause ldisc to notice changes */
     ssh_send_ok = 1;
     ssh_channels = newtree234(ssh_channelcmp);
     while (1) {
@@ -2540,8 +2778,13 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
            if (pktin.type == SSH1_SMSG_STDOUT_DATA ||
                pktin.type == SSH1_SMSG_STDERR_DATA) {
                long len = GET_32BIT(pktin.body);
-               from_backend(pktin.type == SSH1_SMSG_STDERR_DATA,
-                            pktin.body + 4, len);
+               int bufsize =
+                   from_backend(pktin.type == SSH1_SMSG_STDERR_DATA,
+                                pktin.body + 4, len);
+               if (!ssh1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
+                   ssh1_stdout_throttling = 1;
+                   ssh1_throttle(+1);
+               }
            } else if (pktin.type == SSH1_MSG_DISCONNECT) {
                ssh_state = SSH_STATE_CLOSED;
                logevent("Received disconnect request");
@@ -2572,6 +2815,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                        c->remoteid = GET_32BIT(pktin.body);
                        c->localid = alloc_channel_id();
                        c->closes = 0;
+                       c->v.v1.throttling = 0;
                        c->type = CHAN_X11;     /* identify channel type */
                        add234(ssh_channels, c);
                        send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
@@ -2594,6 +2838,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                    c->remoteid = GET_32BIT(pktin.body);
                    c->localid = alloc_channel_id();
                    c->closes = 0;
+                   c->v.v1.throttling = 0;
                    c->type = CHAN_AGENT;       /* identify channel type */
                    c->u.a.lensofar = 0;
                    add234(ssh_channels, c);
@@ -2646,6 +2891,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                        c->remoteid = GET_32BIT(pktin.body);
                        c->localid = alloc_channel_id();
                        c->closes = 0;
+                       c->v.v1.throttling = 0;
                        c->type = CHAN_SOCKDATA;        /* identify channel type */
                        add234(ssh_channels, c);
                        send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
@@ -2664,9 +2910,34 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                if (c && c->type == CHAN_SOCKDATA_DORMANT) {
                    c->remoteid = localid;
                    c->type = CHAN_SOCKDATA;
+                   c->v.v1.throttling = 0;
                    pfd_confirm(c->u.pfd.s);
                }
 
+               if (c && c->closes) {
+                   /*
+                    * We have a pending close on this channel,
+                    * which we decided on before the server acked
+                    * the channel open. So now we know the
+                    * remoteid, we can close it again.
+                    */
+                   send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+                               PKT_END);
+               }
+
+           } else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_FAILURE) {
+               unsigned int remoteid = GET_32BIT(pktin.body);
+               unsigned int localid = GET_32BIT(pktin.body+4);
+               struct ssh_channel *c;
+
+               c = find234(ssh_channels, &remoteid, ssh_channelfind);
+               if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+                   logevent("Forwarded connection refused by server");
+                   pfd_close(c->u.pfd.s);
+                   del234(ssh_channels, c);
+                   sfree(c);
+               }
+
            } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
                       pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
                /* Remote side closes a channel. */
@@ -2706,12 +2977,13 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                struct ssh_channel *c;
                c = find234(ssh_channels, &i, ssh_channelfind);
                if (c) {
+                   int bufsize;
                    switch (c->type) {
                      case CHAN_X11:
-                       x11_send(c->u.x11.s, p, len);
+                       bufsize = x11_send(c->u.x11.s, p, len);
                        break;
                      case CHAN_SOCKDATA:
-                       pfd_send(c->u.pfd.s, p, len);
+                       bufsize = pfd_send(c->u.pfd.s, p, len);
                        break;
                      case CHAN_AGENT:
                        /* Data for an agent message. Buffer it. */
@@ -2764,8 +3036,13 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                                c->u.a.lensofar = 0;
                            }
                        }
+                       bufsize = 0;   /* agent channels never back up */
                        break;
                    }
+                   if (!c->v.v1.throttling && bufsize > SSH1_BUFFER_LIMIT) {
+                       c->v.v1.throttling = 1;
+                       ssh1_throttle(+1);
+                   }
                }
            } else if (pktin.type == SSH1_SMSG_SUCCESS) {
                /* may be from EXEC_SHELL on some servers */
@@ -2774,6 +3051,15 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                 * if no pty is available or in other odd cases. Ignore */
            } else if (pktin.type == SSH1_SMSG_EXIT_STATUS) {
                send_packet(SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
+                /*
+                 * In case `helpful' firewalls or proxies tack
+                 * extra human-readable text on the end of the
+                 * session which we might mistake for another
+                 * encrypted packet, we close the session once
+                 * we've sent EXIT_CONFIRMATION.
+                 */
+                ssh_state = SSH_STATE_CLOSED;
+                crReturnV;
            } else {
                bombout(("Strange packet received: type %d", pktin.type));
                crReturnV;
@@ -2847,7 +3133,7 @@ static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr,
  */
 static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 {
-    static int i, j, len, nbits, pbits;
+    static int i, j, len, nbits, pbits, warn;
     static char *str;
     static Bignum p, g, e, f, K;
     static int kex_init_value, kex_reply_value;
@@ -2864,7 +3150,8 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     static void *hkey;                /* actual host key */
     static unsigned char exchange_hash[20];
     static unsigned char keyspace[40];
-    static const struct ssh2_ciphers *preferred_cipher;
+    static int n_preferred_ciphers;
+    static const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
     static const struct ssh_compress *preferred_comp;
     static int first_kex;
 
@@ -2873,21 +3160,41 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     first_kex = 1;
 
     /*
-     * Set up the preferred cipher and compression.
+     * Set up the preferred ciphers. (NULL => warn below here)
      */
-    if (cfg.cipher == CIPHER_BLOWFISH) {
-       preferred_cipher = &ssh2_blowfish;
-    } else if (cfg.cipher == CIPHER_DES) {
-       logevent("Single DES not supported in SSH2; using 3DES");
-       preferred_cipher = &ssh2_3des;
-    } else if (cfg.cipher == CIPHER_3DES) {
-       preferred_cipher = &ssh2_3des;
-    } else if (cfg.cipher == CIPHER_AES) {
-       preferred_cipher = &ssh2_aes;
-    } else {
-       /* Shouldn't happen, but we do want to initialise to _something_. */
-       preferred_cipher = &ssh2_3des;
+    n_preferred_ciphers = 0;
+    for (i = 0; i < CIPHER_MAX; i++) {
+       switch (cfg.ssh_cipherlist[i]) {
+         case CIPHER_BLOWFISH:
+           preferred_ciphers[n_preferred_ciphers] = &ssh2_blowfish;
+           n_preferred_ciphers++;
+           break;
+         case CIPHER_DES:
+           preferred_ciphers[n_preferred_ciphers] = &ssh2_des;
+           n_preferred_ciphers++;
+           break;
+         case CIPHER_3DES:
+           preferred_ciphers[n_preferred_ciphers] = &ssh2_3des;
+           n_preferred_ciphers++;
+           break;
+         case CIPHER_AES:
+           preferred_ciphers[n_preferred_ciphers] = &ssh2_aes;
+           n_preferred_ciphers++;
+           break;
+         case CIPHER_WARN:
+           /* Flag for later. Don't bother if it's the last in
+            * the list. */
+           if (i < CIPHER_MAX - 1) {
+               preferred_ciphers[n_preferred_ciphers] = NULL;
+               n_preferred_ciphers++;
+           }
+           break;
+       }
     }
+
+    /*
+     * Set up preferred compression.
+     */
     if (cfg.compression)
        preferred_comp = &ssh_zlib;
     else
@@ -2924,23 +3231,23 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
     /* List client->server encryption algorithms. */
     ssh2_pkt_addstring_start();
-    for (i = 0; i < lenof(ciphers) + 1; i++) {
-       const struct ssh2_ciphers *c =
-           i == 0 ? preferred_cipher : ciphers[i - 1];
+    for (i = 0; i < n_preferred_ciphers; i++) {
+       const struct ssh2_ciphers *c = preferred_ciphers[i];
+       if (!c) continue;              /* warning flag */
        for (j = 0; j < c->nciphers; j++) {
            ssh2_pkt_addstring_str(c->list[j]->name);
-           if (i < lenof(ciphers) || j < c->nciphers - 1)
+           if (i < n_preferred_ciphers || j < c->nciphers - 1)
                ssh2_pkt_addstring_str(",");
        }
     }
     /* List server->client encryption algorithms. */
     ssh2_pkt_addstring_start();
-    for (i = 0; i < lenof(ciphers) + 1; i++) {
-       const struct ssh2_ciphers *c =
-           i == 0 ? preferred_cipher : ciphers[i - 1];
+    for (i = 0; i < n_preferred_ciphers; i++) {
+       const struct ssh2_ciphers *c = preferred_ciphers[i];
+       if (!c) continue; /* warning flag */
        for (j = 0; j < c->nciphers; j++) {
            ssh2_pkt_addstring_str(c->list[j]->name);
-           if (i < lenof(ciphers) || j < c->nciphers - 1)
+           if (i < n_preferred_ciphers || j < c->nciphers - 1)
                ssh2_pkt_addstring_str(",");
        }
     }
@@ -3026,31 +3333,55 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
        }
     }
     ssh2_pkt_getstring(&str, &len);    /* client->server cipher */
-    for (i = 0; i < lenof(ciphers) + 1; i++) {
-       const struct ssh2_ciphers *c =
-           i == 0 ? preferred_cipher : ciphers[i - 1];
-       for (j = 0; j < c->nciphers; j++) {
-           if (in_commasep_string(c->list[j]->name, str, len)) {
-               cscipher_tobe = c->list[j];
-               break;
+    warn = 0;
+    for (i = 0; i < n_preferred_ciphers; i++) {
+       const struct ssh2_ciphers *c = preferred_ciphers[i];
+       if (!c) {
+           warn = 1;
+       } else {
+           for (j = 0; j < c->nciphers; j++) {
+               if (in_commasep_string(c->list[j]->name, str, len)) {
+                   cscipher_tobe = c->list[j];
+                   break;
+               }
            }
        }
-       if (cscipher_tobe)
+       if (cscipher_tobe) {
+           if (warn)
+               askcipher(cscipher_tobe->name, 1);
            break;
+       }
+    }
+    if (!cscipher_tobe) {
+       bombout(("Couldn't agree a client-to-server cipher (available: %s)", str));
+       crReturn(0);
     }
+
     ssh2_pkt_getstring(&str, &len);    /* server->client cipher */
-    for (i = 0; i < lenof(ciphers) + 1; i++) {
-       const struct ssh2_ciphers *c =
-           i == 0 ? preferred_cipher : ciphers[i - 1];
-       for (j = 0; j < c->nciphers; j++) {
-           if (in_commasep_string(c->list[j]->name, str, len)) {
-               sccipher_tobe = c->list[j];
-               break;
+    warn = 0;
+    for (i = 0; i < n_preferred_ciphers; i++) {
+       const struct ssh2_ciphers *c = preferred_ciphers[i];
+       if (!c) {
+           warn = 1;
+       } else {
+           for (j = 0; j < c->nciphers; j++) {
+               if (in_commasep_string(c->list[j]->name, str, len)) {
+                   sccipher_tobe = c->list[j];
+                   break;
+               }
            }
        }
-       if (sccipher_tobe)
+       if (sccipher_tobe) {
+           if (warn)
+               askcipher(sccipher_tobe->name, 2);
            break;
+       }
+    }
+    if (!sccipher_tobe) {
+       bombout(("Couldn't agree a server-to-client cipher (available: %s)", str));
+       crReturn(0);
     }
+
     ssh2_pkt_getstring(&str, &len);    /* client->server mac */
     for (i = 0; i < nmacs; i++) {
        if (in_commasep_string(maclist[i]->name, str, len)) {
@@ -3178,15 +3509,6 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
 
     /*
-     * Expect SSH2_MSG_NEWKEYS from server.
-     */
-    crWaitUntil(ispkt);
-    if (pktin.type != SSH2_MSG_NEWKEYS) {
-       bombout(("expected new-keys packet from server"));
-       crReturn(0);
-    }
-
-    /*
      * Authenticate remote host: verify host key. (We've already
      * checked the signature of the exchange hash.)
      */
@@ -3209,6 +3531,15 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     ssh2_pkt_send();
 
     /*
+     * Expect SSH2_MSG_NEWKEYS from server.
+     */
+    crWaitUntil(ispkt);
+    if (pktin.type != SSH2_MSG_NEWKEYS) {
+       bombout(("expected new-keys packet from server"));
+       crReturn(0);
+    }
+
+    /*
      * Create and initialise session keys.
      */
     cscipher = cscipher_tobe;
@@ -3272,33 +3603,49 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
                                  int len)
 {
-    if (c->v2.outbufsize < c->v2.outbuflen + len) {
-       c->v2.outbufsize = c->v2.outbuflen + len + 1024;
-       c->v2.outbuffer = srealloc(c->v2.outbuffer, c->v2.outbufsize);
-    }
-    memcpy(c->v2.outbuffer + c->v2.outbuflen, buf, len);
-    c->v2.outbuflen += len;
+    bufchain_add(&c->v.v2.outbuffer, buf, len);
 }
 
 /*
  * Attempt to send data on an SSH2 channel.
  */
-static void ssh2_try_send(struct ssh_channel *c)
+static int ssh2_try_send(struct ssh_channel *c)
 {
-    while (c->v2.remwindow > 0 && c->v2.outbuflen > 0) {
-       unsigned len = c->v2.remwindow;
-       if (len > c->v2.outbuflen)
-           len = c->v2.outbuflen;
-       if (len > c->v2.remmaxpkt)
-           len = c->v2.remmaxpkt;
+    while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) {
+       int len;
+       void *data;
+       bufchain_prefix(&c->v.v2.outbuffer, &data, &len);
+       if ((unsigned)len > c->v.v2.remwindow)
+           len = c->v.v2.remwindow;
+       if ((unsigned)len > c->v.v2.remmaxpkt)
+           len = c->v.v2.remmaxpkt;
        ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
        ssh2_pkt_adduint32(c->remoteid);
        ssh2_pkt_addstring_start();
-       ssh2_pkt_addstring_data(c->v2.outbuffer, len);
+       ssh2_pkt_addstring_data(data, len);
+       ssh2_pkt_send();
+       bufchain_consume(&c->v.v2.outbuffer, len);
+       c->v.v2.remwindow -= len;
+    }
+
+    /*
+     * After having sent as much data as we can, return the amount
+     * still buffered.
+     */
+    return bufchain_size(&c->v.v2.outbuffer);
+}
+
+/*
+ * Potentially enlarge the window on an SSH2 channel.
+ */
+static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
+{
+    if (newwin > c->v.v2.locwindow) {
+       ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+       ssh2_pkt_adduint32(c->remoteid);
+       ssh2_pkt_adduint32(newwin - c->v.v2.locwindow);
        ssh2_pkt_send();
-       c->v2.outbuflen -= len;
-       memmove(c->v2.outbuffer, c->v2.outbuffer + len, c->v2.outbuflen);
-       c->v2.remwindow -= len;
+       c->v.v2.locwindow = newwin;
     }
 }
 
@@ -3497,7 +3844,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                 * This is a further prompt in keyboard-interactive
                 * authentication. Do nothing.
                 */
-           } else if (!pktin.type != SSH2_MSG_USERAUTH_FAILURE) {
+           } else if (pktin.type != SSH2_MSG_USERAUTH_FAILURE) {
                bombout(("Strange packet received during authentication: type %d",
                         pktin.type));
                crReturnV;
@@ -3563,66 +3910,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    in_commasep_string("publickey", methods, methlen);
                can_passwd =
                    in_commasep_string("password", methods, methlen);
-               can_passwd =
-                   in_commasep_string("password", methods, methlen);
-               can_keyb_inter = 
+               can_keyb_inter = cfg.try_ki_auth &&
                    in_commasep_string("keyboard-interactive", methods, methlen);
            }
 
            method = 0;
 
-           if (!method && can_keyb_inter && !tried_keyb_inter) {
-               method = AUTH_KEYBOARD_INTERACTIVE;
-               type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
-               tried_keyb_inter = TRUE;
-
-               ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
-               ssh2_pkt_addstring(username);
-               ssh2_pkt_addstring("ssh-connection");   /* service requested */
-               ssh2_pkt_addstring("keyboard-interactive");     /* method */
-               ssh2_pkt_addstring(""); /* lang */
-               ssh2_pkt_addstring("");
-               ssh2_pkt_send();
-
-               crWaitUntilV(ispkt);
-               if (pktin.type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
-                   if (pktin.type == SSH2_MSG_USERAUTH_FAILURE)
-                       gotit = TRUE;
-                   logevent("Keyboard-interactive authentication refused");
-                   type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
-                   continue;
-               }
-
-               kbd_inter_running = TRUE;
-           }
-
-           if (kbd_inter_running) {
-               method = AUTH_KEYBOARD_INTERACTIVE;
-               type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
-               tried_keyb_inter = TRUE;
-
-               /* We've got packet with that "interactive" info
-                  dump banners, and set its prompt as ours */
-               {
-                   char *name, *inst, *lang, *prompt;
-                   int name_len, inst_len, lang_len, prompt_len;
-                   ssh2_pkt_getstring(&name, &name_len);
-                   ssh2_pkt_getstring(&inst, &inst_len);
-                   ssh2_pkt_getstring(&lang, &lang_len);
-                   if (name_len > 0)
-                       c_write_untrusted(name, name_len);
-                   if (inst_len > 0)
-                       c_write_untrusted(inst, inst_len);
-                   num_prompts = ssh2_pkt_getuint32();
-
-                   ssh2_pkt_getstring(&prompt, &prompt_len);
-                   strncpy(pwprompt, prompt, sizeof(pwprompt));
-                   need_pw = TRUE;
-
-                   echo = ssh2_pkt_getbool();
-               }
-           }
-
            if (!method && can_pubkey && agent_exists() && !tried_agent) {
                /*
                 * Attempt public-key authentication using Pageant.
@@ -3816,6 +4109,60 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                }
            }
 
+           if (!method && can_keyb_inter && !tried_keyb_inter) {
+               method = AUTH_KEYBOARD_INTERACTIVE;
+               type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+               tried_keyb_inter = TRUE;
+
+               ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+               ssh2_pkt_addstring(username);
+               ssh2_pkt_addstring("ssh-connection");   /* service requested */
+               ssh2_pkt_addstring("keyboard-interactive");     /* method */
+               ssh2_pkt_addstring(""); /* lang */
+               ssh2_pkt_addstring("");
+               ssh2_pkt_send();
+
+               crWaitUntilV(ispkt);
+               if (pktin.type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+                   if (pktin.type == SSH2_MSG_USERAUTH_FAILURE)
+                       gotit = TRUE;
+                   logevent("Keyboard-interactive authentication refused");
+                   type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+                   continue;
+               }
+
+               kbd_inter_running = TRUE;
+           }
+
+           if (kbd_inter_running) {
+               method = AUTH_KEYBOARD_INTERACTIVE;
+               type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+               tried_keyb_inter = TRUE;
+
+               /* We've got packet with that "interactive" info
+                  dump banners, and set its prompt as ours */
+               {
+                   char *name, *inst, *lang, *prompt;
+                   int name_len, inst_len, lang_len, prompt_len;
+                   ssh2_pkt_getstring(&name, &name_len);
+                   ssh2_pkt_getstring(&inst, &inst_len);
+                   ssh2_pkt_getstring(&lang, &lang_len);
+                   if (name_len > 0)
+                       c_write_untrusted(name, name_len);
+                   if (inst_len > 0)
+                       c_write_untrusted(inst, inst_len);
+                   num_prompts = ssh2_pkt_getuint32();
+
+                   ssh2_pkt_getstring(&prompt, &prompt_len);
+                   strncpy(pwprompt, prompt, sizeof(pwprompt));
+                   pwprompt[prompt_len < sizeof(pwprompt) ?
+                            prompt_len : sizeof(pwprompt)-1] = '\0';
+                   need_pw = TRUE;
+
+                   echo = ssh2_pkt_getbool();
+               }
+           }
+
            if (!method && can_passwd) {
                method = AUTH_PASSWORD;
                sprintf(pwprompt, "%.90s@%.90s's password: ", username,
@@ -4047,7 +4394,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
     ssh2_pkt_addstring("session");
     ssh2_pkt_adduint32(mainchan->localid);
-    ssh2_pkt_adduint32(0x8000UL);      /* our window size */
+    mainchan->v.v2.locwindow = OUR_V2_WINSIZE;
+    ssh2_pkt_adduint32(mainchan->v.v2.locwindow);      /* our window size */
     ssh2_pkt_adduint32(0x4000UL);      /* our max pkt size */
     ssh2_pkt_send();
     crWaitUntilV(ispkt);
@@ -4063,10 +4411,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     mainchan->remoteid = ssh2_pkt_getuint32();
     mainchan->type = CHAN_MAINSESSION;
     mainchan->closes = 0;
-    mainchan->v2.remwindow = ssh2_pkt_getuint32();
-    mainchan->v2.remmaxpkt = ssh2_pkt_getuint32();
-    mainchan->v2.outbuffer = NULL;
-    mainchan->v2.outbuflen = mainchan->v2.outbufsize = 0;
+    mainchan->v.v2.remwindow = ssh2_pkt_getuint32();
+    mainchan->v.v2.remmaxpkt = ssh2_pkt_getuint32();
+    bufchain_init(&mainchan->v.v2.outbuffer);
     add234(ssh_channels, mainchan);
     logevent("Opened channel for session");
 
@@ -4095,13 +4442,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                c = find234(ssh_channels, &i, ssh_channelfind);
                if (!c)
                    continue;          /* nonexistent channel */
-               c->v2.remwindow += ssh2_pkt_getuint32();
+               c->v.v2.remwindow += ssh2_pkt_getuint32();
            }
        } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
 
        if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
            if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Server got confused by X11 forwarding request"));
+               bombout(("Unexpected response to X11 forwarding request:"
+                        " packet type %d", pktin.type));
                crReturnV;
            }
            logevent("X11 forwarding refused");
@@ -4183,13 +4531,15 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                                c = find234(ssh_channels, &i, ssh_channelfind);
                                if (!c)
                                    continue;/* nonexistent channel */
-                               c->v2.remwindow += ssh2_pkt_getuint32();
+                               c->v.v2.remwindow += ssh2_pkt_getuint32();
                            }
                        } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
 
                        if (pktin.type != SSH2_MSG_REQUEST_SUCCESS) {
                            if (pktin.type != SSH2_MSG_REQUEST_FAILURE) {
-                               bombout(("Server got confused by port forwarding request"));
+                               bombout(("Unexpected response to port "
+                                        "forwarding request: packet type %d",
+                                        pktin.type));
                                crReturnV;
                            }
                            logevent("Server refused this port forwarding");
@@ -4221,14 +4571,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                c = find234(ssh_channels, &i, ssh_channelfind);
                if (!c)
                    continue;          /* nonexistent channel */
-               c->v2.remwindow += ssh2_pkt_getuint32();
+               c->v.v2.remwindow += ssh2_pkt_getuint32();
            }
        } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
 
        if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
            if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(
-                       ("Server got confused by agent forwarding request"));
+               bombout(("Unexpected response to agent forwarding request:"
+                        " packet type %d", pktin.type));
                crReturnV;
            }
            logevent("Agent forwarding refused");
@@ -4264,13 +4614,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                c = find234(ssh_channels, &i, ssh_channelfind);
                if (!c)
                    continue;          /* nonexistent channel */
-               c->v2.remwindow += ssh2_pkt_getuint32();
+               c->v.v2.remwindow += ssh2_pkt_getuint32();
            }
        } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
 
        if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
            if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Server got confused by pty request"));
+               bombout(("Unexpected response to pty request:"
+                        " packet type %d", pktin.type));
                crReturnV;
            }
            c_write_str("Server refused to allocate pty\r\n");
@@ -4283,43 +4634,71 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     }
 
     /*
-     * Start a shell or a remote command.
+     * 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.
      */
-    ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-    ssh2_pkt_adduint32(mainchan->remoteid);    /* recipient channel */
-    if (cfg.ssh_subsys) {
-       ssh2_pkt_addstring("subsystem");
-       ssh2_pkt_addbool(1);           /* want reply */
-       ssh2_pkt_addstring(cfg.remote_cmd_ptr);
-    } else if (*cfg.remote_cmd_ptr) {
-       ssh2_pkt_addstring("exec");
-       ssh2_pkt_addbool(1);           /* want reply */
-       ssh2_pkt_addstring(cfg.remote_cmd_ptr);
-    } else {
-       ssh2_pkt_addstring("shell");
-       ssh2_pkt_addbool(1);           /* want reply */
-    }
-    ssh2_pkt_send();
-    do {
-       crWaitUntilV(ispkt);
-       if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
-           unsigned i = ssh2_pkt_getuint32();
-           struct ssh_channel *c;
-           c = find234(ssh_channels, &i, ssh_channelfind);
-           if (!c)
-               continue;              /* nonexistent channel */
-           c->v2.remwindow += ssh2_pkt_getuint32();
+    while (1) {
+       int subsys;
+       char *cmd;
+
+       if (ssh_fallback_cmd) {
+           subsys = cfg.ssh_subsys2;
+           cmd = cfg.remote_cmd_ptr2;
+       } else {
+           subsys = cfg.ssh_subsys;
+           cmd = cfg.remote_cmd_ptr;
        }
-    } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-    if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
-       if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
-           bombout(("Server got confused by shell/command request"));
+
+       ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+       ssh2_pkt_adduint32(mainchan->remoteid); /* recipient channel */
+       if (subsys) {
+           ssh2_pkt_addstring("subsystem");
+           ssh2_pkt_addbool(1);               /* want reply */
+           ssh2_pkt_addstring(cmd);
+       } else if (*cmd) {
+           ssh2_pkt_addstring("exec");
+           ssh2_pkt_addbool(1);               /* want reply */
+           ssh2_pkt_addstring(cmd);
+       } else {
+           ssh2_pkt_addstring("shell");
+           ssh2_pkt_addbool(1);               /* want reply */
+       }
+       ssh2_pkt_send();
+       do {
+           crWaitUntilV(ispkt);
+           if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+               unsigned i = ssh2_pkt_getuint32();
+               struct ssh_channel *c;
+               c = find234(ssh_channels, &i, ssh_channelfind);
+               if (!c)
+                   continue;          /* nonexistent channel */
+               c->v.v2.remwindow += ssh2_pkt_getuint32();
+           }
+       } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+       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));
+               crReturnV;
+           }
+           /*
+            * 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 && cfg.remote_cmd_ptr2 != NULL) {
+               logevent("Primary command failed; attempting fallback");
+               ssh_fallback_cmd = TRUE;
+               continue;
+           }
+           bombout(("Server refused to start a shell/command"));
            crReturnV;
+       } else {
+           logevent("Started a shell/command");
        }
-       bombout(("Server refused to start a shell/command"));
-       crReturnV;
-    } else {
-       logevent("Started a shell/command");
+       break;
     }
 
     ssh_state = SSH_STATE_SESSION;
@@ -4331,7 +4710,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     /*
      * Transfer data!
      */
-    ldisc_send(NULL, 0);              /* cause ldisc to notice changes */
+    ldisc_send(NULL, 0, 0);           /* cause ldisc to notice changes */
     ssh_send_ok = 1;
     while (1) {
        static int try_send;
@@ -4352,17 +4731,20 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    continue;          /* extended but not stderr */
                ssh2_pkt_getstring(&data, &length);
                if (data) {
+                   int bufsize;
+                   c->v.v2.locwindow -= length;
                    switch (c->type) {
                      case CHAN_MAINSESSION:
-                       from_backend(pktin.type ==
-                                    SSH2_MSG_CHANNEL_EXTENDED_DATA, data,
-                                    length);
+                       bufsize =
+                           from_backend(pktin.type ==
+                                        SSH2_MSG_CHANNEL_EXTENDED_DATA,
+                                        data, length);
                        break;
                      case CHAN_X11:
-                       x11_send(c->u.x11.s, data, length);
+                       bufsize = x11_send(c->u.x11.s, data, length);
                        break;
                      case CHAN_SOCKDATA:
-                       pfd_send(c->u.pfd.s, data, length);
+                       bufsize = pfd_send(c->u.pfd.s, data, length);
                        break;
                      case CHAN_AGENT:
                        while (length > 0) {
@@ -4412,24 +4794,20 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                                c->u.a.lensofar = 0;
                            }
                        }
+                       bufsize = 0;
                        break;
                    }
                    /*
-                    * Enlarge the window again at the remote
-                    * side, just in case it ever runs down and
-                    * they fail to send us any more data.
+                    * If we are not buffering too much data,
+                    * enlarge the window again at the remote side.
                     */
-                   ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-                   ssh2_pkt_adduint32(c->remoteid);
-                   ssh2_pkt_adduint32(length);
-                   ssh2_pkt_send();
+                   if (bufsize < OUR_V2_WINSIZE)
+                       ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
                }
            } else if (pktin.type == SSH2_MSG_DISCONNECT) {
                ssh_state = SSH_STATE_CLOSED;
                logevent("Received disconnect message");
                crReturnV;
-           } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
-               continue;              /* exit status et al; ignore (FIXME?) */
            } else if (pktin.type == SSH2_MSG_CHANNEL_EOF) {
                unsigned i = ssh2_pkt_getuint32();
                struct ssh_channel *c;
@@ -4458,24 +4836,31 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                c = find234(ssh_channels, &i, ssh_channelfind);
                if (!c)
                    continue;          /* nonexistent channel */
-               if (c->closes == 0) {
-                   ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-                   ssh2_pkt_adduint32(c->remoteid);
-                   ssh2_pkt_send();
-               }
                /* Do pre-close processing on the channel. */
                switch (c->type) {
                  case CHAN_MAINSESSION:
                    break;             /* nothing to see here, move along */
                  case CHAN_X11:
+                   if (c->u.x11.s != NULL)
+                       x11_close(c->u.x11.s);
+                   sshfwd_close(c);
                    break;
                  case CHAN_AGENT:
+                   sshfwd_close(c);
                    break;
                  case CHAN_SOCKDATA:
+                   if (c->u.pfd.s != NULL)
+                       pfd_close(c->u.pfd.s);
+                   sshfwd_close(c);
                    break;
                }
+               if (c->closes == 0) {
+                   ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+                   ssh2_pkt_adduint32(c->remoteid);
+                   ssh2_pkt_send();
+               }
                del234(ssh_channels, c);
-               sfree(c->v2.outbuffer);
+               bufchain_clear(&c->v.v2.outbuffer);
                sfree(c);
 
                /*
@@ -4498,7 +4883,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                c = find234(ssh_channels, &i, ssh_channelfind);
                if (!c)
                    continue;          /* nonexistent channel */
-               c->v2.remwindow += ssh2_pkt_getuint32();
+               c->v.v2.remwindow += ssh2_pkt_getuint32();
                try_send = TRUE;
            } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
                unsigned i = ssh2_pkt_getuint32();
@@ -4510,12 +4895,77 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    continue;          /* dunno why they're confirming this */
                c->remoteid = ssh2_pkt_getuint32();
                c->type = CHAN_SOCKDATA;
-               c->closes = 0;
-               c->v2.remwindow = ssh2_pkt_getuint32();
-               c->v2.remmaxpkt = ssh2_pkt_getuint32();
-               c->v2.outbuffer = NULL;
-               c->v2.outbuflen = c->v2.outbufsize = 0;
-               pfd_confirm(c->u.pfd.s);
+               c->v.v2.remwindow = ssh2_pkt_getuint32();
+               c->v.v2.remmaxpkt = ssh2_pkt_getuint32();
+               bufchain_init(&c->v.v2.outbuffer);
+               if (c->u.pfd.s)
+                   pfd_confirm(c->u.pfd.s);
+               if (c->closes) {
+                   /*
+                    * We have a pending close on this channel,
+                    * which we decided on before the server acked
+                    * the channel open. So now we know the
+                    * remoteid, we can close it again.
+                    */
+                   ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+                   ssh2_pkt_adduint32(c->remoteid);
+                   ssh2_pkt_send();
+               }
+           } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
+               unsigned i = ssh2_pkt_getuint32();
+               struct ssh_channel *c;
+               c = find234(ssh_channels, &i, ssh_channelfind);
+               if (!c)
+                   continue;          /* nonexistent channel */
+               if (c->type != CHAN_SOCKDATA_DORMANT)
+                   continue;          /* dunno why they're failing this */
+
+               logevent("Forwarded connection refused by server");
+
+               pfd_close(c->u.pfd.s);
+
+               del234(ssh_channels, c);
+               sfree(c);
+           } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
+               unsigned localid;
+               char *type;
+               int typelen, want_reply;
+               struct ssh_channel *c;
+
+               localid = ssh2_pkt_getuint32();
+               ssh2_pkt_getstring(&type, &typelen);
+               want_reply = ssh2_pkt_getbool();
+
+               /*
+                * First, check that the channel exists. Otherwise,
+                * we can instantly disconnect with a rude message.
+                */
+               c = find234(ssh_channels, &localid, ssh_channelfind);
+               if (!c) {
+                   char buf[80];
+                   sprintf(buf, "Received channel request for nonexistent"
+                           " channel %d", localid);
+                   logevent(buf);
+                   ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+                   ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
+                   ssh2_pkt_addstring(buf);
+                   ssh2_pkt_addstring("en");   /* language tag */
+                   ssh2_pkt_send();
+                   connection_fatal(buf);
+                   ssh_state = SSH_STATE_CLOSED;
+                   crReturnV;
+               }
+
+               /*
+                * We don't recognise any form of channel request,
+                * so we now either ignore the request or respond
+                * with CHANNEL_FAILURE, depending on want_reply.
+                */
+               if (want_reply) {
+                   ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
+                   ssh2_pkt_adduint32(c->remoteid);
+                   ssh2_pkt_send();
+               }
            } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
                char *type;
                int typelen;
@@ -4588,15 +5038,15 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                } else {
                    c->localid = alloc_channel_id();
                    c->closes = 0;
-                   c->v2.remwindow = winsize;
-                   c->v2.remmaxpkt = pktsize;
-                   c->v2.outbuffer = NULL;
-                   c->v2.outbuflen = c->v2.outbufsize = 0;
+                   c->v.v2.locwindow = OUR_V2_WINSIZE;
+                   c->v.v2.remwindow = winsize;
+                   c->v.v2.remmaxpkt = pktsize;
+                   bufchain_init(&c->v.v2.outbuffer);
                    add234(ssh_channels, c);
                    ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
                    ssh2_pkt_adduint32(c->remoteid);
                    ssh2_pkt_adduint32(c->localid);
-                   ssh2_pkt_adduint32(0x8000UL);       /* our window size */
+                   ssh2_pkt_adduint32(c->v.v2.locwindow);
                    ssh2_pkt_adduint32(0x4000UL);       /* our max pkt size */
                    ssh2_pkt_send();
                }
@@ -4617,8 +5067,27 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            /*
             * Try to send data on all channels if we can.
             */
-           for (i = 0; NULL != (c = index234(ssh_channels, i)); i++)
-               ssh2_try_send(c);
+           for (i = 0; NULL != (c = index234(ssh_channels, i)); i++) {
+               int bufsize = ssh2_try_send(c);
+               if (bufsize == 0) {
+                   switch (c->type) {
+                     case CHAN_MAINSESSION:
+                       /* stdin need not receive an unthrottle
+                        * notification since it will be polled */
+                       break;
+                     case CHAN_X11:
+                       x11_unthrottle(c->u.x11.s);
+                       break;
+                     case CHAN_AGENT:
+                       /* agent sockets are request/response and need no
+                        * buffer management */
+                       break;
+                     case CHAN_SOCKDATA:
+                       pfd_unthrottle(c->u.pfd.s);
+                       break;
+                   }
+               }
+           }
        }
     }
 
@@ -4640,7 +5109,7 @@ static void ssh2_protocol(unsigned char *in, int inlen, int ispkt)
  *
  * Returns an error message, or NULL on success.
  */
-static char *ssh_init(char *host, int port, char **realhost)
+static char *ssh_init(char *host, int port, char **realhost, int nodelay)
 {
     char *p;
 
@@ -4652,8 +5121,11 @@ static char *ssh_init(char *host, int port, char **realhost)
     ssh_send_ok = 0;
     ssh_editing = 0;
     ssh_echoing = 0;
+    ssh1_throttle_count = 0;
+    ssh_overall_bufsize = 0;
+    ssh_fallback_cmd = 0;
 
-    p = connect_to_host(host, port, realhost);
+    p = connect_to_host(host, port, realhost, nodelay);
     if (p != NULL)
        return p;
 
@@ -4663,12 +5135,44 @@ static char *ssh_init(char *host, int port, char **realhost)
 /*
  * Called to send data down the Telnet connection.
  */
-static void ssh_send(char *buf, int len)
+static int ssh_send(char *buf, int len)
 {
     if (s == NULL || ssh_protocol == NULL)
-       return;
+       return 0;
 
     ssh_protocol(buf, len, 0);
+
+    return ssh_sendbuffer();
+}
+
+/*
+ * Called to query the current amount of buffered stdin data.
+ */
+static int ssh_sendbuffer(void)
+{
+    int override_value;
+
+    if (s == NULL || ssh_protocol == NULL)
+       return 0;
+
+    /*
+     * If the SSH socket itself has backed up, add the total backup
+     * size on that to any individual buffer on the stdin channel.
+     */
+    override_value = 0;
+    if (ssh_throttled_all)
+       override_value = ssh_overall_bufsize;
+
+    if (ssh_version == 1) {
+       return override_value;
+    } else if (ssh_version == 2) {
+       if (!mainchan || mainchan->closes > 0)
+           return override_value;
+       else
+           return override_value + bufchain_size(&mainchan->v.v2.outbuffer);
+    }
+
+    return 0;
 }
 
 /*
@@ -4762,6 +5266,23 @@ void *new_sock_channel(Socket s)
     return c;
 }
 
+/*
+ * This is called when stdout/stderr (the entity to which
+ * from_backend sends data) manages to clear some backlog.
+ */
+void ssh_unthrottle(int bufsize)
+{
+    if (ssh_version == 1) {
+       if (ssh1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
+           ssh1_stdout_throttling = 0;
+           ssh1_throttle(-1);
+       }
+    } else {
+       if (mainchan && mainchan->closes == 0)
+           ssh2_set_window(mainchan, OUR_V2_WINSIZE - bufsize);
+    }
+}
+
 void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
 {
     struct ssh_channel *c = (struct ssh_channel *)channel;
@@ -4781,7 +5302,8 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
        ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
        ssh2_pkt_addstring("direct-tcpip");
        ssh2_pkt_adduint32(c->localid);
-       ssh2_pkt_adduint32(0x8000UL);      /* our window size */
+       c->v.v2.locwindow = OUR_V2_WINSIZE;
+       ssh2_pkt_adduint32(c->v.v2.locwindow);/* our window size */
        ssh2_pkt_adduint32(0x4000UL);      /* our max pkt size */
        ssh2_pkt_addstring(hostname);
        ssh2_pkt_adduint32(port);
@@ -4820,10 +5342,12 @@ static int ssh_ldisc(int option)
 Backend ssh_backend = {
     ssh_init,
     ssh_send,
+    ssh_sendbuffer,
     ssh_size,
     ssh_special,
     ssh_socket,
     ssh_sendok,
     ssh_ldisc,
+    ssh_unthrottle,
     22
 };