Add the CRC32 compensation attack detector that all other SSH
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 67ae0d4..b8abc5b 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -15,9 +15,6 @@
 #define TRUE 1
 #endif
 
-/* uncomment this for packet level debugging */
-/* #define DUMP_PACKETS */
-
 #define logevent(s) { logevent(s); \
                       if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \
                       { fprintf(stderr, "%s\n", s); fflush(stderr); } }
 #define SSH2_MSG_CHANNEL_SUCCESS                  99   /* 0x63 */
 #define SSH2_MSG_CHANNEL_FAILURE                  100  /* 0x64 */
 
+/*
+ * Packet type contexts, so that ssh2_pkt_type can correctly decode
+ * the ambiguous type numbers back into the correct type strings.
+ */
+#define SSH2_PKTCTX_DHGROUP1         0x0001
+#define SSH2_PKTCTX_DHGEX            0x0002
+#define SSH2_PKTCTX_PUBLICKEY        0x0010
+#define SSH2_PKTCTX_PASSWORD         0x0020
+#define SSH2_PKTCTX_KBDINTER         0x0040
+#define SSH2_PKTCTX_AUTH_MASK        0x00F0
+
 #define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1  /* 0x1 */
 #define SSH2_DISCONNECT_PROTOCOL_ERROR            2    /* 0x2 */
 #define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED       3    /* 0x3 */
@@ -161,6 +169,97 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_SSH2_HMAC                             2
 #define BUG_NEEDS_SSH1_PLAIN_PASSWORD            4
 
+static int ssh_pkt_ctx = 0;
+
+#define translate(x) if (type == x) return #x
+#define translatec(x,ctx) if (type == x && (ssh_pkt_ctx & ctx)) return #x
+char *ssh1_pkt_type(int type)
+{
+    translate(SSH1_MSG_DISCONNECT);
+    translate(SSH1_SMSG_PUBLIC_KEY);
+    translate(SSH1_CMSG_SESSION_KEY);
+    translate(SSH1_CMSG_USER);
+    translate(SSH1_CMSG_AUTH_RSA);
+    translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
+    translate(SSH1_CMSG_AUTH_PASSWORD);
+    translate(SSH1_CMSG_REQUEST_PTY);
+    translate(SSH1_CMSG_WINDOW_SIZE);
+    translate(SSH1_CMSG_EXEC_SHELL);
+    translate(SSH1_CMSG_EXEC_CMD);
+    translate(SSH1_SMSG_SUCCESS);
+    translate(SSH1_SMSG_FAILURE);
+    translate(SSH1_CMSG_STDIN_DATA);
+    translate(SSH1_SMSG_STDOUT_DATA);
+    translate(SSH1_SMSG_STDERR_DATA);
+    translate(SSH1_CMSG_EOF);
+    translate(SSH1_SMSG_EXIT_STATUS);
+    translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+    translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
+    translate(SSH1_MSG_CHANNEL_DATA);
+    translate(SSH1_MSG_CHANNEL_CLOSE);
+    translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
+    translate(SSH1_SMSG_X11_OPEN);
+    translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
+    translate(SSH1_MSG_PORT_OPEN);
+    translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+    translate(SSH1_SMSG_AGENT_OPEN);
+    translate(SSH1_MSG_IGNORE);
+    translate(SSH1_CMSG_EXIT_CONFIRMATION);
+    translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
+    translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
+    translate(SSH1_MSG_DEBUG);
+    translate(SSH1_CMSG_REQUEST_COMPRESSION);
+    translate(SSH1_CMSG_AUTH_TIS);
+    translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
+    translate(SSH1_CMSG_AUTH_CCARD);
+    translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
+    return "unknown";
+}
+char *ssh2_pkt_type(int type)
+{
+    translate(SSH2_MSG_DISCONNECT);
+    translate(SSH2_MSG_IGNORE);
+    translate(SSH2_MSG_UNIMPLEMENTED);
+    translate(SSH2_MSG_DEBUG);
+    translate(SSH2_MSG_SERVICE_REQUEST);
+    translate(SSH2_MSG_SERVICE_ACCEPT);
+    translate(SSH2_MSG_KEXINIT);
+    translate(SSH2_MSG_NEWKEYS);
+    translatec(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP1);
+    translatec(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP1);
+    translatec(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
+    translate(SSH2_MSG_USERAUTH_REQUEST);
+    translate(SSH2_MSG_USERAUTH_FAILURE);
+    translate(SSH2_MSG_USERAUTH_SUCCESS);
+    translate(SSH2_MSG_USERAUTH_BANNER);
+    translatec(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
+    translatec(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
+    translatec(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
+    translatec(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
+    translate(SSH2_MSG_GLOBAL_REQUEST);
+    translate(SSH2_MSG_REQUEST_SUCCESS);
+    translate(SSH2_MSG_REQUEST_FAILURE);
+    translate(SSH2_MSG_CHANNEL_OPEN);
+    translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+    translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+    translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+    translate(SSH2_MSG_CHANNEL_DATA);
+    translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
+    translate(SSH2_MSG_CHANNEL_EOF);
+    translate(SSH2_MSG_CHANNEL_CLOSE);
+    translate(SSH2_MSG_CHANNEL_REQUEST);
+    translate(SSH2_MSG_CHANNEL_SUCCESS);
+    translate(SSH2_MSG_CHANNEL_FAILURE);
+    return "unknown";
+}
+#undef translate
+#undef translatec
 
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
@@ -235,18 +334,6 @@ extern void pfd_override_throttle(Socket s, int enable);
 #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.)
- */
-const static struct ssh2_ciphers *ciphers[] = {
-    &ssh2_aes,
-    &ssh2_blowfish,
-    &ssh2_3des,
-};
-
 const static struct ssh_kex *kex_algs[] = {
     &ssh_diffiehellman_gex,
     &ssh_diffiehellman
@@ -412,6 +499,7 @@ static int ssh_echoing, ssh_editing;
 
 static tree234 *ssh_channels;         /* indexed by local id */
 static struct ssh_channel *mainchan;   /* primary session channel */
+static int ssh_exitcode = -1;
 
 static tree234 *ssh_rportfwds;
 
@@ -636,12 +724,13 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        st->to_read -= st->chunk;
     }
 
+    if (cipher && detect_attack(pktin.data, st->biglen, NULL)) {
+        bombout(("Network attack (CRC compensation) detected!"));
+        crReturn(0);
+    }
+
     if (cipher)
        cipher->decrypt(pktin.data, st->biglen);
-#ifdef DUMP_PACKETS
-    debug(("Got packet len=%d pad=%d\n", st->len, st->pad));
-    dmemdump(pktin.data, st->biglen);
-#endif
 
     st->realcrc = crc32(pktin.data, st->biglen - 4);
     st->gotcrc = GET_32BIT(pktin.data + st->biglen - 4);
@@ -655,10 +744,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
     if (ssh1_compressing) {
        unsigned char *decompblk;
        int decomplen;
-#ifdef DUMP_PACKETS
-       debug(("Packet payload pre-decompression:\n"));
-       dmemdump(pktin.body - 1, pktin.length + 1);
-#endif
        zlib_decompress_block(pktin.body - 1, pktin.length + 1,
                              &decompblk, &decomplen);
 
@@ -673,12 +758,13 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        memcpy(pktin.body - 1, decompblk, decomplen);
        sfree(decompblk);
        pktin.length = decomplen - 1;
-#ifdef DUMP_PACKETS
-       debug(("Packet payload post-decompression:\n"));
-       dmemdump(pktin.body - 1, pktin.length + 1);
-#endif
     }
 
+    pktin.type = pktin.body[-1];
+
+    log_packet(PKT_INCOMING, pktin.type, ssh1_pkt_type(pktin.type),
+              pktin.body, pktin.length);
+
     if (pktin.type == SSH1_SMSG_STDOUT_DATA ||
        pktin.type == SSH1_SMSG_STDERR_DATA ||
        pktin.type == SSH1_MSG_DEBUG ||
@@ -691,8 +777,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        }
     }
 
-    pktin.type = pktin.body[-1];
-
     if (pktin.type == SSH1_MSG_DEBUG) {
        /* log debug message */
        char buf[80];
@@ -819,11 +903,6 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
        sccipher->decrypt(pktin.data + st->cipherblk,
                          st->packetlen - st->cipherblk);
 
-#ifdef DUMP_PACKETS
-    debug(("Got packet len=%d pad=%d\n", st->len, st->pad));
-    dmemdump(pktin.data, st->packetlen);
-#endif
-
     /*
      * Check the MAC.
      */
@@ -856,11 +935,6 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
            }
            pktin.length = 5 + newlen;
            memcpy(pktin.data + 5, newpayload, newlen);
-#ifdef DUMP_PACKETS
-           debug(("Post-decompression payload:\n"));
-           dmemdump(pktin.data + 5, newlen);
-#endif
-
            sfree(newpayload);
        }
     }
@@ -868,6 +942,9 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
     pktin.savedpos = 6;
     pktin.type = pktin.data[5];
 
+    log_packet(PKT_INCOMING, pktin.type, ssh2_pkt_type(pktin.type),
+              pktin.data+6, pktin.length-6);
+
     if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG)
        goto next_packet;              /* FIXME: print DEBUG message */
 
@@ -942,13 +1019,12 @@ static int s_wrpkt_prepare(void)
 
     pktout.body[-1] = pktout.type;
 
+    log_packet(PKT_OUTGOING, pktout.type, ssh1_pkt_type(pktout.type),
+              pktout.body, pktout.length);
+
     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);
@@ -966,10 +1042,6 @@ static int s_wrpkt_prepare(void)
     PUT_32BIT(pktout.data + biglen, crc);
     PUT_32BIT(pktout.data, len);
 
-#ifdef DUMP_PACKETS
-    debug(("Sending packet len=%d\n", biglen + 4));
-    dmemdump(pktout.data, biglen + 4);
-#endif
     if (cipher)
        cipher->encrypt(pktout.data + 4, biglen);
 
@@ -1233,18 +1305,15 @@ static int ssh2_pkt_construct(void)
     int cipherblk, maclen, padding, i;
     static unsigned long outgoing_sequence = 0;
 
+    log_packet(PKT_OUTGOING, pktout.data[5], ssh2_pkt_type(pktout.data[5]),
+              pktout.data + 6, pktout.length - 6);
+
     /*
      * Compress packet payload.
      */
     {
        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;
@@ -1273,11 +1342,6 @@ static int ssh2_pkt_construct(void)
                        outgoing_sequence);
     outgoing_sequence++;              /* whether or not we MACed */
 
-#ifdef DUMP_PACKETS
-    debug(("Sending packet len=%d\n", pktout.length + padding));
-    dmemdump(pktout.data, pktout.length + padding);
-#endif
-
     if (cscipher)
        cscipher->encrypt(pktout.data, pktout.length + padding);
 
@@ -1659,7 +1723,7 @@ static void ssh_sent(Plug plug, int bufsize)
  * 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,
@@ -1716,7 +1780,7 @@ static char *connect_to_host(char *host, int port, char **realhost)
        sprintf(buf, "Connecting to %.100s port %d", addrbuf, port);
        logevent(buf);
     }
-    s = sk_new(addr, port, 0, 1, &fn_table_ptr);
+    s = sk_new(addr, port, 0, 1, nodelay, &fn_table_ptr);
     if ((err = sk_socket_error(s)))
        return err;
 
@@ -1795,11 +1859,13 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
     struct RSAKey servkey, hostkey;
     struct MD5Context md5c;
     static unsigned long supported_ciphers_mask, supported_auths_mask;
-    static int tried_publickey;
+    static int tried_publickey, tried_agent;
     static int tis_auth_refused, ccard_auth_refused;
     static unsigned char session_id[16];
     static int cipher_type;
     static char username[100];
+    static void *publickey_blob;
+    int publickey_bloblen;
 
     crBegin;
 
@@ -2044,8 +2110,14 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     crWaitUntil(ispkt);
 
-    tried_publickey = 0;
+    tried_publickey = tried_agent = 0;
     tis_auth_refused = ccard_auth_refused = 0;
+    /* Load the public half of cfg.keyfile so we notice if it's in Pageant */
+    if (*cfg.keyfile) {
+       if (!rsakey_pubblob(cfg.keyfile, &publickey_blob, &publickey_bloblen))
+           publickey_blob = NULL;
+    } else
+       publickey_blob = NULL;
 
     while (pktin.type == SSH1_SMSG_FAILURE) {
        static char password[100];
@@ -2055,7 +2127,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        static int pwpkt_type;
        pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
 
-       if (agent_exists()) {
+       if (agent_exists() && !tried_agent) {
            /*
             * Attempt RSA authentication using Pageant.
             */
@@ -2065,6 +2137,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            static int authed = FALSE;
            void *r;
 
+           tried_agent = 1;
            logevent("Pageant is running. Requesting keys.");
 
            /* Request the keys held by the agent. */
@@ -2093,6 +2166,11 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                        sprintf(buf, "Trying Pageant key #%d", i);
                        logevent(buf);
                    }
+                   if (publickey_blob &&
+                       !memcmp(p, publickey_blob, publickey_bloblen)) {
+                       logevent("This key matches configured key file");
+                       tried_publickey = 1;
+                   }
                    p += 4;
                    p += ssh1_read_bignum(p, &key.exponent);
                    p += ssh1_read_bignum(p, &key.modulus);
@@ -2523,19 +2601,29 @@ 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");
        }
@@ -2902,9 +2990,21 @@ 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);
@@ -3030,6 +3130,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                /* may be from EXEC_SHELL on some servers
                 * if no pty is available or in other odd cases. Ignore */
            } else if (pktin.type == SSH1_SMSG_EXIT_STATUS) {
+               char buf[100];
+               ssh_exitcode = GET_32BIT(pktin.body);
+               sprintf(buf, "Server sent command exit status %d",
+                       ssh_exitcode);
+               logevent(buf);
                send_packet(SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
                 /*
                  * In case `helpful' firewalls or proxies tack
@@ -3133,6 +3238,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     static int n_preferred_ciphers;
     static const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
     static const struct ssh_compress *preferred_comp;
+    static int cipherstr_started;
     static int first_kex;
 
     crBegin;
@@ -3150,7 +3256,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
            n_preferred_ciphers++;
            break;
          case CIPHER_DES:
-           /* Not supported in SSH2; silently drop */
+           if (cfg.ssh2_des_cbc) {
+               preferred_ciphers[n_preferred_ciphers] = &ssh2_des;
+               n_preferred_ciphers++;
+           }
            break;
          case CIPHER_3DES:
            preferred_ciphers[n_preferred_ciphers] = &ssh2_3des;
@@ -3210,24 +3319,28 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
     /* List client->server encryption algorithms. */
     ssh2_pkt_addstring_start();
+    cipherstr_started = 0;
     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 < n_preferred_ciphers || j < c->nciphers - 1)
+           if (cipherstr_started)
                ssh2_pkt_addstring_str(",");
+           ssh2_pkt_addstring_str(c->list[j]->name);
+           cipherstr_started = 1;
        }
     }
     /* List server->client encryption algorithms. */
     ssh2_pkt_addstring_start();
+    cipherstr_started = 0;
     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 < n_preferred_ciphers || j < c->nciphers - 1)
+           if (cipherstr_started)
                ssh2_pkt_addstring_str(",");
+           ssh2_pkt_addstring_str(c->list[j]->name);
+           cipherstr_started = 1;
        }
     }
     /* List client->server MAC algorithms. */
@@ -3331,6 +3444,11 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
            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 */
     warn = 0;
     for (i = 0; i < n_preferred_ciphers; i++) {
@@ -3351,6 +3469,11 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
            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)) {
@@ -3407,6 +3530,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
      */
     if (kex == &ssh_diffiehellman_gex) {
        logevent("Doing Diffie-Hellman group exchange");
+       ssh_pkt_ctx |= SSH2_PKTCTX_DHGEX;
        /*
         * Work out how big a DH group we will need to allow that
         * much data.
@@ -3427,6 +3551,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
        kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
        kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
     } else {
+       ssh_pkt_ctx |= SSH2_PKTCTX_DHGROUP1;
        dh_setup_group1();
        kex_init_value = SSH2_MSG_KEXDH_INIT;
        kex_reply_value = SSH2_MSG_KEXDH_REPLY;
@@ -3478,15 +3603,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.)
      */
@@ -3509,6 +3625,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;
@@ -3645,6 +3770,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     static char username[100];
     static char pwprompt[200];
     static char password[100];
+    static void *publickey_blob;
+    static int publickey_bloblen;
 
     crBegin;
 
@@ -3684,6 +3811,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
      *    the username they will want to be able to get back and
      *    retype it!
      */
+    username[0] = '\0';
     do {
        static int pos;
        static char c;
@@ -3692,7 +3820,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
         * Get a username.
         */
        pos = 0;
-       if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
+       if (*username && !cfg.change_username) {
+           /*
+            * We got a username last time round this loop, and
+            * with change_username turned off we don't try to get
+            * it again.
+            */
+       } else if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
            if (ssh_get_line) {
                if (!ssh_get_line("login as: ",
                                  username, sizeof(username), FALSE)) {
@@ -3763,6 +3897,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
         * just in case it succeeds, and (b) so that we know what
         * authentication methods we can usefully try next.
         */
+       ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+
        ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
        ssh2_pkt_addstring(username);
        ssh2_pkt_addstring("ssh-connection");   /* service requested */
@@ -3776,6 +3912,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
        tried_agent = FALSE;
        tried_keyb_inter = FALSE;
        kbd_inter_running = FALSE;
+       /* Load the pub half of cfg.keyfile so we notice if it's in Pageant */
+       if (*cfg.keyfile) {
+           publickey_blob = ssh2_userkey_loadpub(cfg.keyfile, NULL,
+                                                 &publickey_bloblen);
+       } else
+           publickey_blob = NULL;
 
        while (1) {
            /*
@@ -3879,11 +4021,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_keyb_inter = 
+               can_keyb_inter = cfg.try_ki_auth &&
                    in_commasep_string("keyboard-interactive", methods, methlen);
            }
 
            method = 0;
+           ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
 
            if (!method && can_pubkey && agent_exists() && !tried_agent) {
                /*
@@ -3895,6 +4038,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                static int authed = FALSE;
                void *r;
 
+               ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
                tried_agent = TRUE;
 
                logevent("Pageant is running. Requesting keys.");
@@ -3928,6 +4073,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        }
                        pklen = GET_32BIT(p);
                        p += 4;
+                       if (publickey_blob &&
+                           pklen == publickey_bloblen &&
+                           !memcmp(p, publickey_blob, publickey_bloblen)) {
+                           logevent("This key matches configured key file");
+                           tried_pubkey_config = 1;
+                       }
                        pkblob = p;
                        p += pklen;
                        alglen = GET_32BIT(pkblob);
@@ -4031,6 +4182,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
                tried_pubkey_config = TRUE;
 
+               ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
                /*
                 * Try the public key supplied in the configuration.
                 *
@@ -4083,6 +4236,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
                tried_keyb_inter = TRUE;
 
+               ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
                ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
                ssh2_pkt_addstring(username);
                ssh2_pkt_addstring("ssh-connection");   /* service requested */
@@ -4108,6 +4263,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
                tried_keyb_inter = TRUE;
 
+               ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
                /* We've got packet with that "interactive" info
                   dump banners, and set its prompt as ours */
                {
@@ -4124,6 +4281,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
                    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();
@@ -4132,6 +4291,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
            if (!method && can_passwd) {
                method = AUTH_PASSWORD;
+               ssh_pkt_ctx |= SSH2_PKTCTX_PASSWORD;
                sprintf(pwprompt, "%.90s@%.90s's password: ", username,
                        savedhost);
                need_pw = TRUE;
@@ -4486,7 +4646,10 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
                        ssh2_pkt_addstring("tcpip-forward");
                        ssh2_pkt_addbool(1);/* want reply */
-                       ssh2_pkt_addstring("127.0.0.1");
+                       if (cfg.rport_acceptall)
+                           ssh2_pkt_addstring("0.0.0.0");
+                       else
+                           ssh2_pkt_addstring("127.0.0.1");
                        ssh2_pkt_adduint32(sport);
                        ssh2_pkt_send();
 
@@ -4775,8 +4938,6 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                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;
@@ -4836,12 +4997,25 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                 * See if that was the last channel left open.
                 */
                if (count234(ssh_channels) == 0) {
+#if 0
+                    /*
+                     * We used to send SSH_MSG_DISCONNECT here,
+                     * because I'd believed that _every_ conforming
+                     * SSH2 connection had to end with a disconnect
+                     * being sent by at least one side; apparently
+                     * I was wrong and it's perfectly OK to
+                     * unceremoniously slam the connection shut
+                     * when you're done, and indeed OpenSSH feels
+                     * this is more polite than sending a
+                     * DISCONNECT. So now we don't.
+                     */
                    logevent("All channels closed. Disconnecting");
                    ssh2_pkt_init(SSH2_MSG_DISCONNECT);
                    ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
                    ssh2_pkt_addstring("All open channels closed");
                    ssh2_pkt_addstring("en");   /* language tag */
                    ssh2_pkt_send();
+#endif
                    ssh_state = SSH_STATE_CLOSED;
                    crReturnV;
                }
@@ -4864,11 +5038,22 @@ 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->v.v2.remwindow = ssh2_pkt_getuint32();
                c->v.v2.remmaxpkt = ssh2_pkt_getuint32();
                bufchain_init(&c->v.v2.outbuffer);
-               pfd_confirm(c->u.pfd.s);
+               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;
@@ -4884,6 +5069,67 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
                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;
+               }
+
+               /*
+                * Having got the channel number, we now look at
+                * the request type string to see if it's something
+                * we recognise.
+                */
+               if (typelen == 11 && !memcmp(type, "exit-status", 11) &&
+                   c == mainchan) {
+                   /* We recognise "exit-status" on the primary channel. */
+                   char buf[100];
+                   ssh_exitcode = ssh2_pkt_getuint32();
+                   sprintf(buf, "Server sent command exit status %d",
+                           ssh_exitcode);
+                   logevent(buf);
+                   if (want_reply) {
+                       ssh2_pkt_init(SSH2_MSG_CHANNEL_SUCCESS);
+                       ssh2_pkt_adduint32(c->remoteid);
+                       ssh2_pkt_send();
+                   }
+               } else {
+                   /*
+                    * This is a channel request we don't know
+                    * about, 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;
@@ -5027,7 +5273,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;
 
@@ -5043,7 +5289,7 @@ static char *ssh_init(char *host, int port, char **realhost)
     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;
 
@@ -5257,6 +5503,11 @@ static int ssh_ldisc(int option)
     return FALSE;
 }
 
+static int ssh_return_exitcode(void)
+{
+    return ssh_exitcode;
+}
+
 Backend ssh_backend = {
     ssh_init,
     ssh_send,
@@ -5264,6 +5515,7 @@ Backend ssh_backend = {
     ssh_size,
     ssh_special,
     ssh_socket,
+    ssh_return_exitcode,
     ssh_sendok,
     ssh_ldisc,
     ssh_unthrottle,