Prevent network errors from summarily closing the window when CoE is off
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index ae41a80..fddb68d 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -5,6 +5,7 @@
 #include <winsock.h>
 
 #include "putty.h"
+#include "tree234.h"
 #include "ssh.h"
 #include "scp.h"
 
 #endif
 
 #define logevent(s) { logevent(s); \
-                      if (IS_SCP && (scp_flags & SCP_VERBOSE) != 0) \
+                      if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \
                       fprintf(stderr, "%s\n", s); }
 
-#define SSH1_MSG_DISCONNECT    1
-#define SSH1_SMSG_PUBLIC_KEY   2
-#define SSH1_CMSG_SESSION_KEY  3
-#define SSH1_CMSG_USER         4
-#define SSH1_CMSG_AUTH_PASSWORD        9
-#define SSH1_CMSG_REQUEST_PTY  10
-#define SSH1_CMSG_WINDOW_SIZE  11
-#define SSH1_CMSG_EXEC_SHELL   12
-#define SSH1_CMSG_EXEC_CMD     13
-#define SSH1_SMSG_SUCCESS      14
-#define SSH1_SMSG_FAILURE      15
-#define SSH1_CMSG_STDIN_DATA   16
-#define SSH1_SMSG_STDOUT_DATA  17
-#define SSH1_SMSG_STDERR_DATA  18
-#define SSH1_CMSG_EOF          19
-#define SSH1_SMSG_EXIT_STATUS  20
-#define SSH1_CMSG_EXIT_CONFIRMATION    33
-#define SSH1_MSG_IGNORE                32
-#define SSH1_MSG_DEBUG         36
-#define SSH1_CMSG_AUTH_TIS     39
-#define SSH1_SMSG_AUTH_TIS_CHALLENGE   40
-#define SSH1_CMSG_AUTH_TIS_RESPONSE    41
-
-#define SSH1_AUTH_TIS          5
-
-#define SSH2_MSG_DISCONNECT             1
-#define SSH2_MSG_IGNORE                 2
-#define SSH2_MSG_UNIMPLEMENTED          3
-#define SSH2_MSG_DEBUG                  4
-#define SSH2_MSG_SERVICE_REQUEST        5
-#define SSH2_MSG_SERVICE_ACCEPT         6
-#define SSH2_MSG_KEXINIT                20
-#define SSH2_MSG_NEWKEYS                21
-#define SSH2_MSG_KEXDH_INIT             30
-#define SSH2_MSG_KEXDH_REPLY            31
+#define bombout(msg) ( ssh_state == SSH_STATE_CLOSED, closesocket(s), \
+                       s = INVALID_SOCKET, connection_fatal msg )
+
+#define SSH1_MSG_DISCONNECT                       1    /* 0x1 */
+#define SSH1_SMSG_PUBLIC_KEY                      2    /* 0x2 */
+#define SSH1_CMSG_SESSION_KEY                     3    /* 0x3 */
+#define SSH1_CMSG_USER                            4    /* 0x4 */
+#define SSH1_CMSG_AUTH_RSA                        6    /* 0x6 */
+#define SSH1_SMSG_AUTH_RSA_CHALLENGE              7    /* 0x7 */
+#define SSH1_CMSG_AUTH_RSA_RESPONSE               8    /* 0x8 */
+#define SSH1_CMSG_AUTH_PASSWORD                   9    /* 0x9 */
+#define SSH1_CMSG_REQUEST_PTY                     10   /* 0xa */
+#define SSH1_CMSG_WINDOW_SIZE                     11   /* 0xb */
+#define SSH1_CMSG_EXEC_SHELL                      12   /* 0xc */
+#define SSH1_CMSG_EXEC_CMD                        13   /* 0xd */
+#define SSH1_SMSG_SUCCESS                         14   /* 0xe */
+#define SSH1_SMSG_FAILURE                         15   /* 0xf */
+#define SSH1_CMSG_STDIN_DATA                      16   /* 0x10 */
+#define SSH1_SMSG_STDOUT_DATA                     17   /* 0x11 */
+#define SSH1_SMSG_STDERR_DATA                     18   /* 0x12 */
+#define SSH1_CMSG_EOF                             19   /* 0x13 */
+#define SSH1_SMSG_EXIT_STATUS                     20   /* 0x14 */
+#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION        21   /* 0x15 */
+#define SSH1_MSG_CHANNEL_OPEN_FAILURE             22   /* 0x16 */
+#define SSH1_MSG_CHANNEL_DATA                     23   /* 0x17 */
+#define SSH1_MSG_CHANNEL_CLOSE                    24   /* 0x18 */
+#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION       25   /* 0x19 */
+#define SSH1_CMSG_AGENT_REQUEST_FORWARDING        30   /* 0x1e */
+#define SSH1_SMSG_AGENT_OPEN                      31   /* 0x1f */
+#define SSH1_CMSG_EXIT_CONFIRMATION               33   /* 0x21 */
+#define SSH1_MSG_IGNORE                           32   /* 0x20 */
+#define SSH1_MSG_DEBUG                            36   /* 0x24 */
+#define SSH1_CMSG_AUTH_TIS                        39   /* 0x27 */
+#define SSH1_SMSG_AUTH_TIS_CHALLENGE              40   /* 0x28 */
+#define SSH1_CMSG_AUTH_TIS_RESPONSE               41   /* 0x29 */
+#define SSH1_CMSG_AUTH_CCARD                      70   /* 0x46 */
+#define SSH1_SMSG_AUTH_CCARD_CHALLENGE            71   /* 0x47 */
+#define SSH1_CMSG_AUTH_CCARD_RESPONSE             72   /* 0x48 */
+
+#define SSH1_AUTH_TIS                             5    /* 0x5 */
+#define SSH1_AUTH_CCARD                           16   /* 0x10 */
+
+#define SSH_AGENTC_REQUEST_RSA_IDENTITIES         1    /* 0x1 */
+#define SSH_AGENT_RSA_IDENTITIES_ANSWER           2    /* 0x2 */
+#define SSH_AGENTC_RSA_CHALLENGE                  3    /* 0x3 */
+#define SSH_AGENT_RSA_RESPONSE                    4    /* 0x4 */
+#define SSH_AGENT_FAILURE                         5    /* 0x5 */
+#define SSH_AGENT_SUCCESS                         6    /* 0x6 */
+#define SSH_AGENTC_ADD_RSA_IDENTITY               7    /* 0x7 */
+#define SSH_AGENTC_REMOVE_RSA_IDENTITY            8    /* 0x8 */
+
+#define SSH2_MSG_DISCONNECT                       1    /* 0x1 */
+#define SSH2_MSG_IGNORE                           2    /* 0x2 */
+#define SSH2_MSG_UNIMPLEMENTED                    3    /* 0x3 */
+#define SSH2_MSG_DEBUG                            4    /* 0x4 */
+#define SSH2_MSG_SERVICE_REQUEST                  5    /* 0x5 */
+#define SSH2_MSG_SERVICE_ACCEPT                   6    /* 0x6 */
+#define SSH2_MSG_KEXINIT                          20   /* 0x14 */
+#define SSH2_MSG_NEWKEYS                          21   /* 0x15 */
+#define SSH2_MSG_KEXDH_INIT                       30   /* 0x1e */
+#define SSH2_MSG_KEXDH_REPLY                      31   /* 0x1f */
+#define SSH2_MSG_USERAUTH_REQUEST                 50   /* 0x32 */
+#define SSH2_MSG_USERAUTH_FAILURE                 51   /* 0x33 */
+#define SSH2_MSG_USERAUTH_SUCCESS                 52   /* 0x34 */
+#define SSH2_MSG_USERAUTH_BANNER                  53   /* 0x35 */
+#define SSH2_MSG_USERAUTH_PK_OK                   60   /* 0x3c */
+#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ        60   /* 0x3c */
+#define SSH2_MSG_GLOBAL_REQUEST                   80   /* 0x50 */
+#define SSH2_MSG_REQUEST_SUCCESS                  81   /* 0x51 */
+#define SSH2_MSG_REQUEST_FAILURE                  82   /* 0x52 */
+#define SSH2_MSG_CHANNEL_OPEN                     90   /* 0x5a */
+#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION        91   /* 0x5b */
+#define SSH2_MSG_CHANNEL_OPEN_FAILURE             92   /* 0x5c */
+#define SSH2_MSG_CHANNEL_WINDOW_ADJUST            93   /* 0x5d */
+#define SSH2_MSG_CHANNEL_DATA                     94   /* 0x5e */
+#define SSH2_MSG_CHANNEL_EXTENDED_DATA            95   /* 0x5f */
+#define SSH2_MSG_CHANNEL_EOF                      96   /* 0x60 */
+#define SSH2_MSG_CHANNEL_CLOSE                    97   /* 0x61 */
+#define SSH2_MSG_CHANNEL_REQUEST                  98   /* 0x62 */
+#define SSH2_MSG_CHANNEL_SUCCESS                  99   /* 0x63 */
+#define SSH2_MSG_CHANNEL_FAILURE                  100  /* 0x64 */
+
+#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED     1    /* 0x1 */
+#define SSH2_OPEN_CONNECT_FAILED                  2    /* 0x2 */
+#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE            3    /* 0x3 */
+#define SSH2_OPEN_RESOURCE_SHORTAGE               4    /* 0x4 */
+
+#define SSH2_EXTENDED_DATA_STDERR                 1    /* 0x1 */
 
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
     (cp)[2] = (unsigned char)((value) >> 8); \
     (cp)[3] = (unsigned char)(value); }
 
-enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR };
+enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM };
 
 /* Coroutine mechanics for the sillier bits of the code */
 #define crBegin1       static int crLine = 0;
@@ -86,13 +140,25 @@ enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR };
 #define crStop(z)      do{ crLine = 0; return (z); }while(0)
 #define crStopV                do{ crLine = 0; return; }while(0)
 #define crWaitUntil(c) do { crReturn(0); } while (!(c))
+#define crWaitUntilV(c)        do { crReturnV; } while (!(c))
 
 extern struct ssh_cipher ssh_3des;
+extern struct ssh_cipher ssh_3des_ssh2;
 extern struct ssh_cipher ssh_des;
-extern struct ssh_cipher ssh_blowfish;
+extern struct ssh_cipher ssh_blowfish_ssh1;
+extern struct ssh_cipher ssh_blowfish_ssh2;
 
-/* for ssh 2; we miss out single-DES because it isn't supported */
-struct ssh_cipher *ciphers[] = { &ssh_3des, &ssh_blowfish };
+/*
+ * 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.)
+ *
+ * The first entry in this array is set up to be whatever the user
+ * asks for as a cipher. Thereafter there is a fixed preference
+ * order of fallback ciphers.
+ */
+struct ssh_cipher *ciphers[] = { NULL, &ssh_blowfish_ssh2, &ssh_3des_ssh2 };
 
 extern struct ssh_kex ssh_diffiehellman;
 struct ssh_kex *kex_algs[] = { &ssh_diffiehellman };
@@ -102,13 +168,13 @@ struct ssh_hostkey *hostkey_algs[] = { &ssh_dss };
 
 extern struct ssh_mac ssh_sha1;
 
-SHA_State exhash;
+static SHA_State exhash;
 
-static void nullmac_sesskey(unsigned char *key, int len) { }
+static void nullmac_key(unsigned char *key) { }
 static void nullmac_generate(unsigned char *blk, int len, unsigned long seq) { }
 static int nullmac_verify(unsigned char *blk, int len, unsigned long seq) { return 1; }
 struct ssh_mac ssh_mac_none = {
-    nullmac_sesskey, nullmac_generate, nullmac_verify, "none", 0
+    nullmac_key, nullmac_key, nullmac_generate, nullmac_verify, "none", 0
 };
 struct ssh_mac *macs[] = { &ssh_sha1, &ssh_mac_none };
 
@@ -129,10 +195,48 @@ static struct ssh_compress *cscomp = NULL;
 static struct ssh_compress *sccomp = NULL;
 static struct ssh_kex *kex = NULL;
 static struct ssh_hostkey *hostkey = NULL;
-int scp_flags = 0;
 int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL;
 
 static char *savedhost;
+static int ssh_send_ok;
+
+/*
+ * 2-3-4 tree storing channels.
+ */
+struct ssh_channel {
+    unsigned remoteid, localid;
+    int type;
+    int closes;
+    union {
+        struct ssh_agent_channel {
+            unsigned char *message;
+            unsigned char msglen[4];
+            int lensofar, totallen;
+        } a;
+        struct ssh2_data_channel {
+            unsigned char *outbuffer;
+            unsigned outbuflen, outbufsize;
+            unsigned remwindow, remmaxpkt;
+        } v2;
+    } u;
+};
+static tree234 *ssh_channels;           /* indexed by local id */
+static int ssh_channelcmp(void *av, void *bv) {
+    struct ssh_channel *a = (struct ssh_channel *)av;
+    struct ssh_channel *b = (struct ssh_channel *)bv;
+    if (a->localid < b->localid) return -1;
+    if (a->localid > b->localid) return +1;
+    return 0;
+}
+static int ssh_channelfind(void *av, void *bv) {
+    unsigned *a = (unsigned *)av;
+    struct ssh_channel *b = (struct ssh_channel *)bv;
+    if (*a < b->localid) return -1;
+    if (*a > b->localid) return +1;
+    return 0;
+}
+
+static struct ssh_channel *mainchan;   /* primary session channel */
 
 static enum {
     SSH_STATE_BEFORE_SIZE,
@@ -146,11 +250,11 @@ static int size_needed = FALSE;
 static void s_write (char *buf, int len) {
     while (len > 0) {
        int i = send (s, buf, len, 0);
-       if (IS_SCP) {
-           noise_ultralight(i);
-           if (i <= 0)
-               fatalbox("Lost connection while sending");
-       }
+        noise_ultralight(i);
+        if (i <= 0) {
+            bombout(("Lost connection while sending"));
+            return;
+        }
        if (i > 0)
            len -= i, buf += i;
     }
@@ -160,8 +264,7 @@ static int s_read (char *buf, int len) {
     int ret = 0;
     while (len > 0) {
        int i = recv (s, buf, len, 0);
-       if (IS_SCP)
-           noise_ultralight(i);
+        noise_ultralight(i);
        if (i > 0)
            len -= i, buf += i, ret += i;
        else
@@ -171,10 +274,11 @@ static int s_read (char *buf, int len) {
 }
 
 static void c_write (char *buf, int len) {
-    if (IS_SCP) {
-       if (len > 0 && buf[len-1] == '\n') len--;
-       if (len > 0 && buf[len-1] == '\r') len--;
-       if (len > 0) { fwrite(buf, len, 1, stderr); fputc('\n', stderr); }
+    if ((flags & FLAG_STDERR)) {
+        int i;
+        for (i = 0; i < len; i++)
+            if (buf[i] != '\r')
+                fputc(buf[i], stderr);
        return;
     }
     while (len--) 
@@ -193,6 +297,7 @@ struct Packet {
 static struct Packet pktin = { 0, 0, NULL, NULL, 0 };
 static struct Packet pktout = { 0, 0, NULL, NULL, 0 };
 
+static int ssh_version;
 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);
@@ -272,16 +377,20 @@ next_packet:
     realcrc = crc32(pktin.data, biglen-4);
     gotcrc = GET_32BIT(pktin.data+biglen-4);
     if (gotcrc != realcrc) {
-       fatalbox("Incorrect CRC received on packet");
+       bombout(("Incorrect CRC received on packet"));
+        crReturn(0);
     }
 
     if (pktin.type == SSH1_SMSG_STDOUT_DATA ||
         pktin.type == SSH1_SMSG_STDERR_DATA ||
         pktin.type == SSH1_MSG_DEBUG ||
-        pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+        pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE ||
+        pktin.type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
        long strlen = GET_32BIT(pktin.body);
-       if (strlen + 4 != pktin.length)
-           fatalbox("Received data packet with bogus string length");
+       if (strlen + 4 != pktin.length) {
+           bombout(("Received data packet with bogus string length"));
+            crReturn(0);
+        }
     }
 
     if (pktin.type == SSH1_MSG_DEBUG) {
@@ -371,10 +480,10 @@ next_packet:
     /*
      * Adjust memory allocation if packet is too big.
      */
-    if (pktin.maxlen < packetlen) {
-       pktin.maxlen = packetlen;
-       pktin.data = (pktin.data == NULL ? malloc(packetlen+APIEXTRA) :
-                     realloc(pktin.data, packetlen+APIEXTRA));
+    if (pktin.maxlen < packetlen+maclen) {
+       pktin.maxlen = packetlen+maclen;
+       pktin.data = (pktin.data == NULL ? malloc(pktin.maxlen+APIEXTRA) :
+                     realloc(pktin.data, pktin.maxlen+APIEXTRA));
        if (!pktin.data)
            fatalbox("Out of memory");
     }
@@ -392,24 +501,27 @@ next_packet:
     if (sccipher)
         sccipher->decrypt(pktin.data + cipherblk, packetlen - cipherblk);
 
-    /*
-     * Check the MAC.
-     */
-    if (scmac && !scmac->verify(pktin.data, incoming_sequence++, len+4))
-       fatalbox("Incorrect MAC received on packet");
-
-    pktin.savedpos = 6;
-    pktin.type = pktin.data[5];
 #if 0
     debug(("Got packet len=%d pad=%d\r\n", len, pad));
-    for (i = 0; i < payload; i++)
+    for (i = 0; i < packetlen; i++)
         debug(("  %02x", (unsigned char)pktin.data[i]));
     debug(("\r\n"));
 #endif
 
     /*
-     * FIXME: handle IGNORE and DEBUG messages.
+     * Check the MAC.
      */
+    if (scmac && !scmac->verify(pktin.data, len+4, incoming_sequence)) {
+       bombout(("Incorrect MAC received on packet"));
+        crReturn(0);
+    }
+    incoming_sequence++;               /* whether or not we MACed */
+
+    pktin.savedpos = 6;
+    pktin.type = pktin.data[5];
+
+    if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG)
+        goto next_packet;              /* FIXME: print DEBUG message */
 
     crFinish(0);
 }
@@ -485,6 +597,7 @@ static void send_packet(int pkttype, ...)
     unsigned char *p, *argp, argchar;
     unsigned long argint;
     int pktlen, argtype, arglen;
+    Bignum bn;
 
     pktlen = 0;
     va_start(args, pkttype);
@@ -508,6 +621,10 @@ static void send_packet(int pkttype, ...)
            arglen = strlen(argp);
            pktlen += 4 + arglen;
            break;
+         case PKT_BIGNUM:
+           bn = va_arg(args, Bignum);
+            pktlen += ssh1_bignum_length(bn);
+           break;
          default:
            assert(0);
        }
@@ -543,6 +660,10 @@ static void send_packet(int pkttype, ...)
            memcpy(p + 4, argp, arglen);
            p += 4 + arglen;
            break;
+         case PKT_BIGNUM:
+           bn = va_arg(args, Bignum);
+            p += ssh1_write_bignum(p, bn);
+           break;
        }
     }
     va_end(args);
@@ -675,16 +796,185 @@ static int ssh_versioncmp(char *a, char *b) {
 #include <stdio.h>
 void sha_string(SHA_State *s, void *str, int len) {
     unsigned char lenblk[4];
-static FILE *fp;
     PUT_32BIT(lenblk, len);
-if (!fp) fp = fopen("h:\\statham\\windows\\putty\\data","wb");
-fwrite(lenblk, 4, 1, fp);
     SHA_Bytes(s, lenblk, 4);
-fwrite(str, len, 1, fp);
-fflush(fp);
     SHA_Bytes(s, str, len);
 }
 
+/*
+ * SSH2 packet construction functions.
+ */
+void ssh2_pkt_adddata(void *data, int len) {
+    pktout.length += len;
+    if (pktout.maxlen < pktout.length) {
+        pktout.maxlen = pktout.length + 256;
+       pktout.data = (pktout.data == NULL ? malloc(pktout.maxlen+APIEXTRA) :
+                       realloc(pktout.data, pktout.maxlen+APIEXTRA));
+        if (!pktout.data)
+            fatalbox("Out of memory");
+    }
+    memcpy(pktout.data+pktout.length-len, data, len);
+}
+void ssh2_pkt_addbyte(unsigned char byte) {
+    ssh2_pkt_adddata(&byte, 1);
+}
+void ssh2_pkt_init(int pkt_type) {
+    pktout.length = 5;
+    ssh2_pkt_addbyte((unsigned char)pkt_type);
+}
+void ssh2_pkt_addbool(unsigned char value) {
+    ssh2_pkt_adddata(&value, 1);
+}
+void ssh2_pkt_adduint32(unsigned long value) {
+    unsigned char x[4];
+    PUT_32BIT(x, value);
+    ssh2_pkt_adddata(x, 4);
+}
+void ssh2_pkt_addstring_start(void) {
+    ssh2_pkt_adduint32(0);
+    pktout.savedpos = pktout.length;
+}
+void ssh2_pkt_addstring_str(char *data) {
+    ssh2_pkt_adddata(data, strlen(data));
+    PUT_32BIT(pktout.data + pktout.savedpos - 4,
+              pktout.length - pktout.savedpos);
+}
+void ssh2_pkt_addstring_data(char *data, int len) {
+    ssh2_pkt_adddata(data, len);
+    PUT_32BIT(pktout.data + pktout.savedpos - 4,
+              pktout.length - pktout.savedpos);
+}
+void ssh2_pkt_addstring(char *data) {
+    ssh2_pkt_addstring_start();
+    ssh2_pkt_addstring_str(data);
+}
+char *ssh2_mpint_fmt(Bignum b, int *len) {
+    unsigned char *p;
+    int i, n = b[0];
+    p = malloc(n * 2 + 1);
+    if (!p)
+        fatalbox("out of memory");
+    p[0] = 0;
+    for (i = 0; i < n; i++) {
+        p[i*2+1] = (b[n-i] >> 8) & 0xFF;
+        p[i*2+2] = (b[n-i]     ) & 0xFF;
+    }
+    i = 0;
+    while (p[i] == 0 && (p[i+1] & 0x80) == 0)
+        i++;
+    memmove(p, p+i, n*2+1-i);
+    *len = n*2+1-i;
+    return p;
+}
+void ssh2_pkt_addmp(Bignum b) {
+    unsigned char *p;
+    int len;
+    p = ssh2_mpint_fmt(b, &len);
+    ssh2_pkt_addstring_start();
+    ssh2_pkt_addstring_data(p, len);
+    free(p);
+}
+void ssh2_pkt_send(void) {
+    int cipherblk, maclen, padding, i;
+    static unsigned long outgoing_sequence = 0;
+
+    /*
+     * Add padding. At least four bytes, and must also bring total
+     * length (minus MAC) up to a multiple of the block size.
+     */
+    cipherblk = cipher ? cipher->blksize : 8;   /* block size */
+    cipherblk = cipherblk < 8 ? 8 : cipherblk;   /* or 8 if blksize < 8 */
+    padding = 4;
+    padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk;
+    pktout.data[4] = padding;
+    for (i = 0; i < padding; i++)
+        pktout.data[pktout.length + i] = random_byte();
+    PUT_32BIT(pktout.data, pktout.length + padding - 4);
+    if (csmac)
+        csmac->generate(pktout.data, pktout.length + padding,
+                        outgoing_sequence);
+    outgoing_sequence++;               /* whether or not we MACed */
+
+#if 0
+    debug(("Sending packet len=%d\r\n", pktout.length+padding));
+    for (i = 0; i < pktout.length+padding; i++)
+        debug(("  %02x", (unsigned char)pktout.data[i]));
+    debug(("\r\n"));
+#endif
+
+    if (cscipher)
+        cscipher->encrypt(pktout.data, pktout.length + padding);
+    maclen = csmac ? csmac->len : 0;
+
+    s_write(pktout.data, pktout.length + padding + maclen);
+}
+
+#if 0
+void bndebug(char *string, Bignum b) {
+    unsigned char *p;
+    int i, len;
+    p = ssh2_mpint_fmt(b, &len);
+    debug(("%s", string));
+    for (i = 0; i < len; i++)
+        debug((" %02x", p[i]));
+    debug(("\r\n"));
+    free(p);
+}
+#endif
+
+void sha_mpint(SHA_State *s, Bignum b) {
+    unsigned char *p;
+    int len;
+    p = ssh2_mpint_fmt(b, &len);
+    sha_string(s, p, len);
+    free(p);
+}
+
+/*
+ * SSH2 packet decode functions.
+ */
+unsigned long ssh2_pkt_getuint32(void) {
+    unsigned long value;
+    if (pktin.length - pktin.savedpos < 4)
+        return 0;                      /* arrgh, no way to decline (FIXME?) */
+    value = GET_32BIT(pktin.data+pktin.savedpos);
+    pktin.savedpos += 4;
+    return value;
+}
+void ssh2_pkt_getstring(char **p, int *length) {
+    *p = NULL;
+    if (pktin.length - pktin.savedpos < 4)
+        return;
+    *length = GET_32BIT(pktin.data+pktin.savedpos);
+    pktin.savedpos += 4;
+    if (pktin.length - pktin.savedpos < *length)
+        return;
+    *p = pktin.data+pktin.savedpos;
+    pktin.savedpos += *length;
+}
+Bignum ssh2_pkt_getmp(void) {
+    char *p;
+    int i, j, length;
+    Bignum b;
+
+    ssh2_pkt_getstring(&p, &length);
+    if (!p)
+        return NULL;
+    if (p[0] & 0x80) {
+        bombout(("internal error: Can't handle negative mpints"));
+        return NULL;
+    }
+    b = newbn((length+1)/2);
+    for (i = 0; i < length; i++) {
+        j = length - 1 - i;
+        if (j & 1)
+            b[j/2+1] |= ((unsigned char)p[i]) << 8;
+        else
+            b[j/2+1] |= ((unsigned char)p[i]);
+    }
+    return b;
+}
+
 static int do_ssh_init(void) {
     char c, *vsp;
     char version[10];
@@ -730,7 +1020,11 @@ static int do_ssh_init(void) {
     vlog[strcspn(vlog, "\r\n")] = '\0';
     logevent(vlog);
 
-    if (ssh_versioncmp(version, "1.99") >= 0) {
+    /*
+     * Server version "1.99" means we can choose whether we use v1
+     * or v2 protocol. Choice is based on cfg.sshprot.
+     */
+    if (ssh_versioncmp(version, cfg.sshprot == 1 ? "2.0" : "1.99") >= 0) {
         /*
          * This is a v2 server. Begin v2 protocol.
          */
@@ -747,6 +1041,7 @@ static int do_ssh_init(void) {
         logevent("Using SSH protocol version 2");
         s_write(vstring, strlen(vstring));
         ssh_protocol = ssh2_protocol;
+        ssh_version = 2;
         s_rdpkt = ssh2_rdpkt;
     } else {
         /*
@@ -760,8 +1055,10 @@ static int do_ssh_init(void) {
         logevent("Using SSH protocol version 1");
         s_write(vstring, strlen(vstring));
         ssh_protocol = ssh1_protocol;
+        ssh_version = 1;
         s_rdpkt = ssh1_rdpkt;
     }
+    ssh_send_ok = 0;
     return 1;
 }
 
@@ -771,27 +1068,30 @@ static int do_ssh_init(void) {
 static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 {
     int i, j, len;
-    unsigned char session_id[16];
     unsigned char *rsabuf, *keystr1, *keystr2;
     unsigned char cookie[8];
     struct RSAKey servkey, hostkey;
     struct MD5Context md5c;
     static unsigned long supported_ciphers_mask, supported_auths_mask;
+    static int tried_publickey;
+    static unsigned char session_id[16];
     int cipher_type;
 
     crBegin;
 
     if (!ispkt) crWaitUntil(ispkt);
 
-    if (pktin.type != SSH1_SMSG_PUBLIC_KEY)
-       fatalbox("Public key packet not received");
+    if (pktin.type != SSH1_SMSG_PUBLIC_KEY) {
+       bombout(("Public key packet not received"));
+        crReturn(0);
+    }
 
     logevent("Received public keys");
 
     memcpy(cookie, pktin.body, 8);
 
-    i = makekey(pktin.body+8, &servkey, &keystr1);
-    j = makekey(pktin.body+8+i, &hostkey, &keystr2);
+    i = makekey(pktin.body+8, &servkey, &keystr1, 0);
+    j = makekey(pktin.body+8+i, &hostkey, &keystr2, 0);
 
     /*
      * Hash the host key and print the hash in the log box. Just as
@@ -887,15 +1187,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     free(rsabuf);
 
-    cipher = cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish :
+    cipher = cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
              cipher_type == SSH_CIPHER_DES ? &ssh_des :
              &ssh_3des;
     cipher->sesskey(session_key);
 
     crWaitUntil(ispkt);
 
-    if (pktin.type != SSH1_SMSG_SUCCESS)
-       fatalbox("Encryption not successfully enabled");
+    if (pktin.type != SSH1_SMSG_SUCCESS) {
+       bombout(("Encryption not successfully enabled"));
+        crReturn(0);
+    }
 
     logevent("Successfully started encryption");
 
@@ -904,8 +1206,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        static char username[100];
        static int pos = 0;
        static char c;
-       if (!IS_SCP && !*cfg.username) {
+       if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
            c_write("login as: ", 10);
+            ssh_send_ok = 1;
            while (pos >= 0) {
                crWaitUntil(!ispkt);
                while (inlen--) switch (c = *in++) {
@@ -944,9 +1247,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            char stuff[200];
            strncpy(username, cfg.username, 99);
            username[99] = '\0';
-           if (!IS_SCP) {
+            if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
                sprintf(stuff, "Sent username \"%s\".\r\n", username);
-               c_write(stuff, strlen(stuff));
+                c_write(stuff, strlen(stuff));
            }
        }
 
@@ -960,19 +1263,124 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     crWaitUntil(ispkt);
 
+    tried_publickey = 0;
+
     while (pktin.type == SSH1_SMSG_FAILURE) {
        static char password[100];
        static int pos;
        static char c;
         static int pwpkt_type;
-
         /*
          * Show password prompt, having first obtained it via a TIS
-         * exchange if we're doing TIS authentication.
+         * 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.
+             */
+            static unsigned char request[5], *response, *p;
+            static int responselen;
+            static int i, nkeys;
+            static int authed = FALSE;
+            void *r;
+
+            logevent("Pageant is running. Requesting keys.");
+
+            /* Request the keys held by the agent. */
+            PUT_32BIT(request, 1);
+            request[4] = SSH_AGENTC_REQUEST_RSA_IDENTITIES;
+            agent_query(request, 5, &r, &responselen);
+            response = (unsigned char *)r;
+            if (response) {
+                p = response + 5;
+                nkeys = GET_32BIT(p); p += 4;
+                { char buf[64]; sprintf(buf, "Pageant has %d keys", nkeys);
+                    logevent(buf); }
+                for (i = 0; i < nkeys; i++) {
+                    static struct RSAKey key;
+                    static Bignum challenge;
+                    static char *commentp;
+                    static int commentlen;
+
+                    { char buf[64]; sprintf(buf, "Trying Pageant key #%d", i);
+                        logevent(buf); }
+                    p += 4;
+                    p += ssh1_read_bignum(p, &key.exponent);
+                    p += ssh1_read_bignum(p, &key.modulus);
+                    commentlen = GET_32BIT(p); p += 4;
+                    commentp = p; p += commentlen;
+                    send_packet(SSH1_CMSG_AUTH_RSA,
+                                PKT_BIGNUM, key.modulus, PKT_END);
+                    crWaitUntil(ispkt);
+                    if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+                        logevent("Key refused");
+                        continue;
+                    }
+                    logevent("Received RSA challenge");
+                    ssh1_read_bignum(pktin.body, &challenge);
+                    {
+                        char *agentreq, *q, *ret;
+                        int len, retlen;
+                        len = 1 + 4;   /* message type, bit count */
+                        len += ssh1_bignum_length(key.exponent);
+                        len += ssh1_bignum_length(key.modulus);
+                        len += ssh1_bignum_length(challenge);
+                        len += 16;     /* session id */
+                        len += 4;      /* response format */
+                        agentreq = malloc(4 + len);
+                        PUT_32BIT(agentreq, len);
+                        q = agentreq + 4;
+                        *q++ = SSH_AGENTC_RSA_CHALLENGE;
+                        PUT_32BIT(q, ssh1_bignum_bitcount(key.modulus));
+                        q += 4;
+                        q += ssh1_write_bignum(q, key.exponent);
+                        q += ssh1_write_bignum(q, key.modulus);
+                        q += ssh1_write_bignum(q, challenge);
+                        memcpy(q, session_id, 16); q += 16;
+                        PUT_32BIT(q, 1);   /* response format */
+                        agent_query(agentreq, len+4, &ret, &retlen);
+                        free(agentreq);
+                        if (ret) {
+                            if (ret[4] == SSH_AGENT_RSA_RESPONSE) {
+                                logevent("Sending Pageant's response");
+                                send_packet(SSH1_CMSG_AUTH_RSA_RESPONSE,
+                                            PKT_DATA, ret+5, 16, PKT_END);
+                                free(ret);
+                                crWaitUntil(ispkt);
+                                if (pktin.type == SSH1_SMSG_SUCCESS) {
+                                    logevent("Pageant's response accepted");
+                                    c_write("Authenticated using RSA key \"",
+                                            29);
+                                    c_write(commentp, commentlen);
+                                    c_write("\" from agent\r\n", 14);
+                                    authed = TRUE;
+                                } else
+                                    logevent("Pageant's response not accepted");
+                            } else {
+                                logevent("Pageant failed to answer challenge");
+                                free(ret);
+                            }
+                        } else {
+                            logevent("No reply received from Pageant");
+                        }
+                    }
+                    freebn(key.exponent);
+                    freebn(key.modulus);
+                    freebn(challenge);
+                    if (authed)
+                        break;
+                }
+            }
+            if (authed)
+                break;
+        }
+        if (*cfg.keyfile && !tried_publickey)
+            pwpkt_type = SSH1_CMSG_AUTH_RSA;
 
-       if (IS_SCP) {
+       if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD &&
+            !(flags & FLAG_INTERACTIVE)) {
            char prompt[200];
            sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost);
            if (!ssh_get_password(prompt, password, sizeof(password))) {
@@ -988,71 +1396,176 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
             }
        } else {
 
-        if (pktin.type == SSH1_SMSG_FAILURE &&
-            cfg.try_tis_auth &&
-            (supported_auths_mask & (1<<SSH1_AUTH_TIS))) {
-            pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
-           logevent("Requested TIS authentication");
-           send_packet(SSH1_CMSG_AUTH_TIS, PKT_END);
-            crWaitUntil(ispkt);
-            if (pktin.type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
-                logevent("TIS authentication declined");
-                c_write("TIS authentication refused.\r\n", 29);
-            } else {
-                int challengelen = ((pktin.body[0] << 24) |
-                                    (pktin.body[1] << 16) |
-                                    (pktin.body[2] << 8) |
-                                    (pktin.body[3]));
-                logevent("Received TIS challenge");
-                c_write(pktin.body+4, challengelen);
+            if (pktin.type == SSH1_SMSG_FAILURE &&
+                cfg.try_tis_auth &&
+                (supported_auths_mask & (1<<SSH1_AUTH_TIS))) {
+                pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
+                logevent("Requested TIS authentication");
+                send_packet(SSH1_CMSG_AUTH_TIS, PKT_END);
+                crWaitUntil(ispkt);
+                if (pktin.type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+                    logevent("TIS authentication declined");
+                    c_write("TIS authentication refused.\r\n", 29);
+                } else {
+                    int challengelen = ((pktin.body[0] << 24) |
+                                        (pktin.body[1] << 16) |
+                                        (pktin.body[2] << 8) |
+                                        (pktin.body[3]));
+                    logevent("Received TIS challenge");
+                    c_write(pktin.body+4, challengelen);
+                }
+            }
+            if (pktin.type == SSH1_SMSG_FAILURE &&
+                cfg.try_tis_auth &&
+                (supported_auths_mask & (1<<SSH1_AUTH_CCARD))) {
+                pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
+                logevent("Requested CryptoCard authentication");
+                send_packet(SSH1_CMSG_AUTH_CCARD, PKT_END);
+                crWaitUntil(ispkt);
+                if (pktin.type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
+                    logevent("CryptoCard authentication declined");
+                    c_write("CryptoCard authentication refused.\r\n", 29);
+                } else {
+                    int challengelen = ((pktin.body[0] << 24) |
+                                        (pktin.body[1] << 16) |
+                                        (pktin.body[2] << 8) |
+                                        (pktin.body[3]));
+                    logevent("Received CryptoCard challenge");
+                    c_write(pktin.body+4, challengelen);
+                    c_write("\r\nResponse : ", 13);
+                }
+            }
+            if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD)
+                c_write("password: ", 10);
+            if (pwpkt_type == SSH1_CMSG_AUTH_RSA) {
+                if (flags & FLAG_VERBOSE)
+                    c_write("Trying public key authentication.\r\n", 35);
+                if (!rsakey_encrypted(cfg.keyfile)) {
+                    if (flags & FLAG_VERBOSE)
+                        c_write("No passphrase required.\r\n", 25);
+                    goto tryauth;
+                }
+                c_write("passphrase: ", 12);
             }
-        }
-        if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD)
-            c_write("password: ", 10);
 
-       pos = 0;
-       while (pos >= 0) {
-           crWaitUntil(!ispkt);
-           while (inlen--) switch (c = *in++) {
-             case 10: case 13:
-               password[pos] = 0;
-               pos = -1;
-               break;
-             case 8: case 127:
-               if (pos > 0)
-                   pos--;
-               break;
-             case 21: case 27:
-               pos = 0;
-               break;
-             case 3: case 4:
-               random_save_seed();
-               exit(0);
-               break;
-             default:
-               if (((c >= ' ' && c <= '~') ||
-                     ((unsigned char)c >= 160)) && pos < 40)
-                   password[pos++] = c;
-               break;
-           }
-       }
-       c_write("\r\n", 2);
+            pos = 0;
+            ssh_send_ok = 1;
+            while (pos >= 0) {
+                crWaitUntil(!ispkt);
+                while (inlen--) switch (c = *in++) {
+                  case 10: case 13:
+                    password[pos] = 0;
+                    pos = -1;
+                    break;
+                  case 8: case 127:
+                    if (pos > 0)
+                        pos--;
+                    break;
+                  case 21: case 27:
+                    pos = 0;
+                    break;
+                  case 3: case 4:
+                    random_save_seed();
+                    exit(0);
+                    break;
+                  default:
+                    if (((c >= ' ' && c <= '~') ||
+                         ((unsigned char)c >= 160)) && pos < sizeof(password))
+                        password[pos++] = c;
+                    break;
+                }
+            }
+            c_write("\r\n", 2);
 
        }
 
-       send_packet(pwpkt_type, PKT_STR, password, PKT_END);
+        tryauth:
+       if (pwpkt_type == SSH1_CMSG_AUTH_RSA) {
+            /*
+             * Try public key authentication with the specified
+             * key file.
+             */
+            static struct RSAKey pubkey;
+            static Bignum challenge, response;
+            static int i;
+            static unsigned char buffer[32];
+
+            tried_publickey = 1;
+            i = loadrsakey(cfg.keyfile, &pubkey, password);
+            if (i == 0) {
+                c_write("Couldn't load public key from ", 30);
+                c_write(cfg.keyfile, strlen(cfg.keyfile));
+                c_write(".\r\n", 3);
+                continue;              /* go and try password */
+            }
+            if (i == -1) {
+                c_write("Wrong passphrase.\r\n", 19);
+                tried_publickey = 0;
+                continue;              /* try again */
+            }
+
+            /*
+             * Send a public key attempt.
+             */
+            send_packet(SSH1_CMSG_AUTH_RSA,
+                        PKT_BIGNUM, pubkey.modulus, PKT_END);
+
+            crWaitUntil(ispkt);
+            if (pktin.type == SSH1_SMSG_FAILURE) {
+                if (flags & FLAG_VERBOSE)
+                    c_write("Server refused our public key.\r\n", 32);
+                continue;              /* go and try password */
+            }
+            if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+                bombout(("Bizarre response to offer of public key"));
+                crReturn(0);
+            }
+            ssh1_read_bignum(pktin.body, &challenge);
+            response = rsadecrypt(challenge, &pubkey);
+            freebn(pubkey.private_exponent);   /* burn the evidence */
+
+            for (i = 0; i < 32; i += 2) {
+                buffer[i] = response[16-i/2] >> 8;
+                buffer[i+1] = response[16-i/2] & 0xFF;
+            }
+
+            MD5Init(&md5c);
+            MD5Update(&md5c, buffer, 32);
+            MD5Update(&md5c, session_id, 16);
+            MD5Final(buffer, &md5c);
+
+            send_packet(SSH1_CMSG_AUTH_RSA_RESPONSE,
+                        PKT_DATA, buffer, 16, PKT_END);
+
+            crWaitUntil(ispkt);
+            if (pktin.type == SSH1_SMSG_FAILURE) {
+                if (flags & FLAG_VERBOSE)
+                    c_write("Failed to authenticate with our public key.\r\n",
+                            45);
+                continue;              /* go and try password */
+            } else if (pktin.type != SSH1_SMSG_SUCCESS) {
+                bombout(("Bizarre response to RSA authentication response"));
+                crReturn(0);
+            }
+
+            break;                     /* we're through! */
+        } else {
+            send_packet(pwpkt_type, PKT_STR, password, PKT_END);
+        }
        logevent("Sent password");
        memset(password, 0, strlen(password));
        crWaitUntil(ispkt);
        if (pktin.type == SSH1_SMSG_FAILURE) {
-           c_write("Access denied\r\n", 15);
+            if (flags & FLAG_VERBOSE)
+                c_write("Access denied\r\n", 15);
            logevent("Authentication refused");
        } else if (pktin.type == SSH1_MSG_DISCONNECT) {
            logevent("Received disconnect request");
             ssh_state = SSH_STATE_CLOSED;
            crReturn(1);
        } else if (pktin.type != SSH1_SMSG_SUCCESS) {
-           fatalbox("Strange packet received, type %d", pktin.type);
+           bombout(("Strange packet received, type %d", pktin.type));
+            crReturn(0);
        }
     }
 
@@ -1072,6 +1585,19 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
     if (ssh_state == SSH_STATE_CLOSED)
         crReturnV;
 
+    if (cfg.agentfwd && agent_exists()) {
+        logevent("Requesting agent forwarding");
+        send_packet(SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
+        do { crReturnV; } while (!ispkt);
+        if (pktin.type != SSH1_SMSG_SUCCESS && pktin.type != SSH1_SMSG_FAILURE) {
+            bombout(("Protocol confusion"));
+            crReturnV;
+        } else if (pktin.type == SSH1_SMSG_FAILURE) {
+            logevent("Agent forwarding refused");
+        } else
+            logevent("Agent forwarding enabled");
+    }
+
     if (!cfg.nopty) {
        send_packet(SSH1_CMSG_REQUEST_PTY,
                    PKT_STR, cfg.termtype,
@@ -1082,20 +1608,26 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
         ssh_state = SSH_STATE_INTERMED;
         do { crReturnV; } while (!ispkt);
         if (pktin.type != SSH1_SMSG_SUCCESS && pktin.type != SSH1_SMSG_FAILURE) {
-            fatalbox("Protocol confusion");
+            bombout(("Protocol confusion"));
+            crReturnV;
         } else if (pktin.type == SSH1_SMSG_FAILURE) {
             c_write("Server refused to allocate pty\r\n", 32);
         }
        logevent("Allocated pty");
     }
 
-    send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
+    if (*cfg.remote_cmd)
+        send_packet(SSH1_CMSG_EXEC_CMD, PKT_STR, cfg.remote_cmd, PKT_END);
+    else
+        send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
     logevent("Started session");
 
     ssh_state = SSH_STATE_SESSION;
     if (size_needed)
        ssh_size();
 
+    ssh_send_ok = 1;
+    ssh_channels = newtree234(ssh_channelcmp);
     while (1) {
        crReturnV;
        if (ispkt) {
@@ -1106,6 +1638,95 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
            } else if (pktin.type == SSH1_MSG_DISCONNECT) {
                 ssh_state = SSH_STATE_CLOSED;
                logevent("Received disconnect request");
+            } else if (pktin.type == SSH1_SMSG_AGENT_OPEN) {
+                /* Remote side is trying to open a channel to talk to our
+                 * agent. Give them back a local channel number. */
+                unsigned i = 1;
+                struct ssh_channel *c;
+                enum234 e;
+                for (c = first234(ssh_channels, &e); c; c = next234(&e)) {
+                    if (c->localid > i)
+                        break;         /* found a free number */
+                    i = c->localid + 1;
+                }
+                c = malloc(sizeof(struct ssh_channel));
+                c->remoteid = GET_32BIT(pktin.body);
+                c->localid = i;
+                c->closes = 0;
+                c->type = SSH1_SMSG_AGENT_OPEN;   /* identify channel type */
+                add234(ssh_channels, c);
+                send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+                            PKT_INT, c->remoteid, PKT_INT, c->localid,
+                            PKT_END);
+            } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
+                       pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
+                /* Remote side closes a channel. */
+                unsigned i = GET_32BIT(pktin.body);
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (c) {
+                    int closetype;
+                    closetype = (pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
+                    send_packet(pktin.type, PKT_INT, c->remoteid, PKT_END);
+                    c->closes |= closetype;
+                    if (c->closes == 3) {
+                        del234(ssh_channels, c);
+                        free(c);
+                    }
+                }
+            } else if (pktin.type == SSH1_MSG_CHANNEL_DATA) {
+                /* Data sent down one of our channels. */
+                int i = GET_32BIT(pktin.body);
+                int len = GET_32BIT(pktin.body+4);
+                unsigned char *p = pktin.body+8;
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (c) {
+                    switch(c->type) {
+                      case SSH1_SMSG_AGENT_OPEN:
+                        /* Data for an agent message. Buffer it. */
+                        while (len > 0) {
+                            if (c->u.a.lensofar < 4) {
+                                int l = min(4 - c->u.a.lensofar, len);
+                                memcpy(c->u.a.msglen + c->u.a.lensofar, p, l);
+                                p += l; len -= l; c->u.a.lensofar += l;
+                            }
+                            if (c->u.a.lensofar == 4) {
+                                c->u.a.totallen = 4 + GET_32BIT(c->u.a.msglen);
+                                c->u.a.message = malloc(c->u.a.totallen);
+                                memcpy(c->u.a.message, c->u.a.msglen, 4);
+                            }
+                            if (c->u.a.lensofar >= 4 && len > 0) {
+                                int l = min(c->u.a.totallen - c->u.a.lensofar, len);
+                                memcpy(c->u.a.message + c->u.a.lensofar, p, l);
+                                p += l; len -= l; c->u.a.lensofar += l;
+                            }
+                            if (c->u.a.lensofar == c->u.a.totallen) {
+                                void *reply, *sentreply;
+                                int replylen;
+                                agent_query(c->u.a.message, c->u.a.totallen,
+                                            &reply, &replylen);
+                                if (reply)
+                                    sentreply = reply;
+                                else {
+                                    /* Fake SSH_AGENT_FAILURE. */
+                                    sentreply = "\0\0\0\1\5";
+                                    replylen = 5;
+                                }
+                                send_packet(SSH1_MSG_CHANNEL_DATA,
+                                            PKT_INT, c->remoteid,
+                                            PKT_INT, replylen,
+                                            PKT_DATA, sentreply, replylen,
+                                            PKT_END);
+                                if (reply)
+                                    free(reply);
+                                free(c->u.a.message);
+                                c->u.a.lensofar = 0;
+                            }
+                        }
+                        break;
+                    }
+                }                
            } else if (pktin.type == SSH1_SMSG_SUCCESS) {
                /* may be from EXEC_SHELL on some servers */
            } else if (pktin.type == SSH1_SMSG_FAILURE) {
@@ -1114,7 +1735,8 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
            } else if (pktin.type == SSH1_SMSG_EXIT_STATUS) {
                send_packet(SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
            } else {
-               fatalbox("Strange packet received: type %d", pktin.type);
+               bombout(("Strange packet received: type %d", pktin.type));
+                crReturnV;
            }
        } else {
            send_packet(SSH1_CMSG_STDIN_DATA,
@@ -1126,165 +1748,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
 }
 
 /*
- * SSH2 packet construction functions.
- */
-void ssh2_pkt_adddata(void *data, int len) {
-    pktout.length += len;
-    if (pktout.maxlen < pktout.length) {
-        pktout.maxlen = pktout.length + 256;
-       pktout.data = (pktout.data == NULL ? malloc(pktout.maxlen+APIEXTRA) :
-                       realloc(pktout.data, pktout.maxlen+APIEXTRA));
-        if (!pktout.data)
-            fatalbox("Out of memory");
-    }
-    memcpy(pktout.data+pktout.length-len, data, len);
-}
-void ssh2_pkt_addbyte(unsigned char byte) {
-    ssh2_pkt_adddata(&byte, 1);
-}
-void ssh2_pkt_init(int pkt_type) {
-    pktout.length = 5;
-    ssh2_pkt_addbyte((unsigned char)pkt_type);
-}
-void ssh2_pkt_addbool(unsigned char value) {
-    ssh2_pkt_adddata(&value, 1);
-}
-void ssh2_pkt_adduint32(unsigned long value) {
-    unsigned char x[4];
-    PUT_32BIT(x, value);
-    ssh2_pkt_adddata(x, 4);
-}
-void ssh2_pkt_addstring_start(void) {
-    ssh2_pkt_adduint32(0);
-    pktout.savedpos = pktout.length;
-}
-void ssh2_pkt_addstring_str(char *data) {
-    ssh2_pkt_adddata(data, strlen(data));
-    PUT_32BIT(pktout.data + pktout.savedpos - 4,
-              pktout.length - pktout.savedpos);
-}
-void ssh2_pkt_addstring_data(char *data, int len) {
-    ssh2_pkt_adddata(data, len);
-    PUT_32BIT(pktout.data + pktout.savedpos - 4,
-              pktout.length - pktout.savedpos);
-}
-void ssh2_pkt_addstring(char *data) {
-    ssh2_pkt_addstring_start();
-    ssh2_pkt_addstring_str(data);
-}
-char *ssh2_mpint_fmt(Bignum b, int *len) {
-    unsigned char *p;
-    int i, n = b[0];
-    p = malloc(n * 2 + 1);
-    if (!p)
-        fatalbox("out of memory");
-    p[0] = 0;
-    for (i = 0; i < n; i++) {
-        p[i*2+1] = (b[n-i] >> 8) & 0xFF;
-        p[i*2+2] = (b[n-i]     ) & 0xFF;
-    }
-    i = 0;
-    while (p[i] == 0 && (p[i+1] & 0x80) == 0)
-        i++;
-    memmove(p, p+i, n*2+1-i);
-    *len = n*2+1-i;
-    return p;
-}
-void ssh2_pkt_addmp(Bignum b) {
-    unsigned char *p;
-    int len;
-    p = ssh2_mpint_fmt(b, &len);
-    ssh2_pkt_addstring_start();
-    ssh2_pkt_addstring_data(p, len);
-    free(p);
-}
-void ssh2_pkt_send(void) {
-    int cipherblk, maclen, padding, i;
-    unsigned long outgoing_sequence = 0;
-
-    /*
-     * Add padding. At least four bytes, and must also bring total
-     * length (minus MAC) up to a multiple of the block size.
-     */
-    cipherblk = cipher ? cipher->blksize : 8;   /* block size */
-    cipherblk = cipherblk < 8 ? 8 : cipherblk;   /* or 8 if blksize < 8 */
-    padding = 4;
-    padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk;
-    pktout.data[4] = padding;
-    for (i = 0; i < padding; i++)
-        pktout.data[pktout.length + i] = random_byte();
-    PUT_32BIT(pktout.data, pktout.length + padding - 4);
-    if (csmac)
-        csmac->generate(pktout.data, outgoing_sequence++,
-                      pktout.length + padding);
-    if (cscipher)
-        cscipher->encrypt(pktout.data, pktout.length + padding);
-    maclen = csmac ? csmac->len : 0;
-#if 0
-    debug(("Sending packet len=%d\r\n", pktout.length+padding+maclen));
-    for (i = 0; i < pktout.length+padding+maclen; i++)
-        debug(("  %02x", (unsigned char)pktout.data[i]));
-    debug(("\r\n"));
-#endif
-    s_write(pktout.data, pktout.length + padding + maclen);
-}
-
-void sha_mpint(SHA_State *s, Bignum b) {
-    unsigned char *p;
-    int len;
-    p = ssh2_mpint_fmt(b, &len);
-    sha_string(s, p, len);
-    free(p);
-}
-
-/*
- * SSH2 packet decode functions.
- */
-void ssh2_pkt_getstring(char **p, int *length) {
-    *p = NULL;
-    if (pktin.length - pktin.savedpos < 4)
-        return;
-    *length = GET_32BIT(pktin.data+pktin.savedpos);
-    pktin.savedpos += 4;
-    if (pktin.length - pktin.savedpos < *length)
-        return;
-    *p = pktin.data+pktin.savedpos;
-    pktin.savedpos += *length;
-}
-Bignum ssh2_pkt_getmp(void) {
-    char *p;
-    int i, j, length;
-    Bignum b;
-
-    ssh2_pkt_getstring(&p, &length);
-    if (!p)
-        return NULL;
-    if (p[0] & 0x80)
-        fatalbox("internal error: Can't handle negative mpints");
-    b = newbn((length+1)/2);
-    for (i = 0; i < length; i++) {
-        j = length - 1 - i;
-        if (j & 1)
-            b[j/2+1] |= ((unsigned char)p[i]) << 8;
-        else
-            b[j/2+1] |= ((unsigned char)p[i]);
-    }
-    return b;
-}
-
-void bndebug(char *string, Bignum b) {
-    unsigned char *p;
-    int i, len;
-    p = ssh2_mpint_fmt(b, &len);
-    debug(("%s", string));
-    for (i = 0; i < len; i++)
-        debug(("  %02x", p[i]));
-    debug(("\r\n"));
-    free(p);
-}
-
-/*
- * Utility routine for decoding comma-separated strings in KEXINIT.
+ * Utility routine for decoding comma-separated strings in KEXINIT.
  */
 int in_commasep_string(char *needle, char *haystack, int haylen) {
     int needlen = strlen(needle);
@@ -1311,9 +1775,29 @@ int in_commasep_string(char *needle, char *haystack, int haylen) {
 }
 
 /*
- * Handle the SSH2 key exchange phase.
+ * SSH2 key creation method.
+ */
+void ssh2_mkkey(Bignum K, char *H, char chr, char *keyspace) {
+    SHA_State s;
+    /* First 20 bytes. */
+    SHA_Init(&s);
+    sha_mpint(&s, K);
+    SHA_Bytes(&s, H, 20);
+    SHA_Bytes(&s, &chr, 1);
+    SHA_Bytes(&s, H, 20);
+    SHA_Final(&s, keyspace);
+    /* Next 20 bytes. */
+    SHA_Init(&s);
+    sha_mpint(&s, K);
+    SHA_Bytes(&s, H, 20);
+    SHA_Bytes(&s, keyspace, 20);
+    SHA_Final(&s, keyspace+20);
+}
+
+/*
+ * Handle the SSH2 transport layer.
  */
-static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
+static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 {
     static int i, len;
     static char *str;
@@ -1324,12 +1808,30 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
     static struct ssh_mac *scmac_tobe = NULL;
     static struct ssh_compress *cscomp_tobe = NULL;
     static struct ssh_compress *sccomp_tobe = NULL;
-    static char *hostkeydata, *sigdata;
+    static char *hostkeydata, *sigdata, *keystr;
     static int hostkeylen, siglen;
     static unsigned char exchange_hash[20];
+    static unsigned char keyspace[40];
 
     crBegin;
+    random_init();
+
+    /*
+     * Set up the preferred cipher.
+     */
+    if (cfg.cipher == CIPHER_BLOWFISH) {
+        ciphers[0] = &ssh_blowfish_ssh2;
+    } else if (cfg.cipher == CIPHER_DES) {
+        logevent("Single DES not supported in SSH2; using 3DES");
+        ciphers[0] = &ssh_3des_ssh2;
+    } else if (cfg.cipher == CIPHER_3DES) {
+        ciphers[0] = &ssh_3des_ssh2;
+    } else {
+        /* Shouldn't happen, but we do want to initialise to _something_. */
+        ciphers[0] = &ssh_3des_ssh2;
+    }
 
+    begin_key_exchange:
     /*
      * Construct and send our key exchange packet.
      */
@@ -1410,8 +1912,10 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
      * Now examine the other side's KEXINIT to see what we're up
      * to.
      */
-    if (pktin.type != SSH2_MSG_KEXINIT)
-        fatalbox("expected key exchange packet from server");
+    if (pktin.type != SSH2_MSG_KEXINIT) {
+        bombout(("expected key exchange packet from server"));
+        crReturn(0);
+    }
     kex = NULL; hostkey = NULL; cscipher_tobe = NULL; sccipher_tobe = NULL;
     csmac_tobe = NULL; scmac_tobe = NULL; cscomp_tobe = NULL; sccomp_tobe = NULL;
     pktin.savedpos += 16;              /* skip garbage cookie */
@@ -1471,41 +1975,34 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
             break;
         }
     }
-    debug(("key exchange is %s\r\n", kex ? kex->name : NULL));
-    debug(("host key alg is %s\r\n", hostkey ? hostkey->name : NULL));
-    debug(("cscipher alg is %s\r\n", cscipher_tobe ? cscipher_tobe->name : NULL));
-    debug(("sccipher alg is %s\r\n", sccipher_tobe ? sccipher_tobe->name : NULL));
-    debug(("csmac alg is %s\r\n", csmac_tobe ? csmac_tobe->name : NULL));
-    debug(("scmac alg is %s\r\n", scmac_tobe ? scmac_tobe->name : NULL));
-    debug(("cscomp alg is %s\r\n", cscomp_tobe ? cscomp_tobe->name : NULL));
-    debug(("sccomp alg is %s\r\n", sccomp_tobe ? sccomp_tobe->name : NULL));
 
     /*
      * Currently we only support Diffie-Hellman and DSS, so let's
      * bomb out if those aren't selected.
      */
-    if (kex != &ssh_diffiehellman || hostkey != &ssh_dss)
-        fatalbox("internal fault: chaos in SSH 2 transport layer");
+    if (kex != &ssh_diffiehellman || hostkey != &ssh_dss) {
+        bombout(("internal fault: chaos in SSH 2 transport layer"));
+        crReturn(0);
+    }
 
     /*
      * Now we begin the fun. Generate and send e for Diffie-Hellman.
      */
     e = dh_create_e();
-    bndebug("e=", e);
     ssh2_pkt_init(SSH2_MSG_KEXDH_INIT);
     ssh2_pkt_addmp(e);
     ssh2_pkt_send();
 
     crWaitUntil(ispkt);
-    if (pktin.type != SSH2_MSG_KEXDH_REPLY)
-        fatalbox("expected key exchange packet from server");
+    if (pktin.type != SSH2_MSG_KEXDH_REPLY) {
+        bombout(("expected key exchange packet from server"));
+        crReturn(0);
+    }
     ssh2_pkt_getstring(&hostkeydata, &hostkeylen);
     f = ssh2_pkt_getmp();
-    bndebug("f=", f);
     ssh2_pkt_getstring(&sigdata, &siglen);
 
     K = dh_find_K(f);
-    bndebug("K=", K);
 
     sha_string(&exhash, hostkeydata, hostkeylen);
     sha_mpint(&exhash, e);
@@ -1513,33 +2010,445 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt)
     sha_mpint(&exhash, K);
     SHA_Final(&exhash, exchange_hash);
 
+#if 0
     debug(("Exchange hash is:\r\n"));
     for (i = 0; i < 20; i++)
         debug((" %02x", exchange_hash[i]));
     debug(("\r\n"));
+#endif
+
+    hostkey->setkey(hostkeydata, hostkeylen);
+    if (!hostkey->verifysig(sigdata, siglen, exchange_hash, 20)) {
+        bombout(("Server failed host key check"));
+        crReturn(0);
+    }
 
     /*
-     * FIXME: verify hostkeydata and sigdata.
+     * Expect SSH2_MSG_NEWKEYS from server.
      */
-    
-    crWaitUntil(0);
+    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.)
+     */
+    keystr = hostkey->fmtkey();
+    verify_ssh_host_key(savedhost, keystr);
+    free(keystr);
+
+    /*
+     * Send SSH2_MSG_NEWKEYS.
+     */
+    ssh2_pkt_init(SSH2_MSG_NEWKEYS);
+    ssh2_pkt_send();
+
+    /*
+     * Create and initialise session keys.
+     */
+    cscipher = cscipher_tobe;
+    sccipher = sccipher_tobe;
+    csmac = csmac_tobe;
+    scmac = scmac_tobe;
+    cscomp = cscomp_tobe;
+    sccomp = sccomp_tobe;
+    /*
+     * Set IVs after keys.
+     */
+    ssh2_mkkey(K, exchange_hash, 'C', keyspace); cscipher->setcskey(keyspace);
+    ssh2_mkkey(K, exchange_hash, 'D', keyspace); cscipher->setsckey(keyspace);
+    ssh2_mkkey(K, exchange_hash, 'A', keyspace); cscipher->setcsiv(keyspace);
+    ssh2_mkkey(K, exchange_hash, 'B', keyspace); sccipher->setsciv(keyspace);
+    ssh2_mkkey(K, exchange_hash, 'E', keyspace); csmac->setcskey(keyspace);
+    ssh2_mkkey(K, exchange_hash, 'F', keyspace); scmac->setsckey(keyspace);
+
+    /*
+     * Now we're encrypting. Begin returning 1 to the protocol main
+     * function so that other things can run on top of the
+     * transport. If we ever see a KEXINIT, we must go back to the
+     * start.
+     */
+    do {
+        crReturn(1);
+    } while (!(ispkt && pktin.type == SSH2_MSG_KEXINIT));
+    goto begin_key_exchange;
 
     crFinish(1);
 }
 
-static void ssh2_protocol(unsigned char *in, int inlen, int ispkt) {
+/*
+ * Handle the SSH2 userauth and connection layers.
+ */
+static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
+{
+    static unsigned long remote_winsize;
+    static unsigned long remote_maxpkt;
+
     crBegin;
 
-    random_init();
+    /*
+     * Request userauth protocol, and await a response to it.
+     */
+    ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+    ssh2_pkt_addstring("ssh-userauth");
+    ssh2_pkt_send();
+    crWaitUntilV(ispkt);
+    if (pktin.type != SSH2_MSG_SERVICE_ACCEPT) {
+        bombout(("Server refused user authentication protocol"));
+        crReturnV;
+    }
+
+    /*
+     * FIXME: currently we support only password authentication.
+     * (This places us technically in violation of the SSH2 spec.
+     * We must fix this.)
+     */
+    while (1) {
+        /*
+         * Get a username and a password.
+         */
+       static char username[100];
+       static char password[100];
+       static int pos = 0;
+       static char c;
+
+       if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
+           c_write("login as: ", 10);
+            ssh_send_ok = 1;
+           while (pos >= 0) {
+               crWaitUntilV(!ispkt);
+               while (inlen--) switch (c = *in++) {
+                 case 10: case 13:
+                   username[pos] = 0;
+                   pos = -1;
+                   break;
+                 case 8: case 127:
+                   if (pos > 0) {
+                       c_write("\b \b", 3);
+                       pos--;
+                   }
+                   break;
+                 case 21: case 27:
+                   while (pos > 0) {
+                       c_write("\b \b", 3);
+                       pos--;
+                   }
+                   break;
+                 case 3: case 4:
+                   random_save_seed();
+                   exit(0);
+                   break;
+                 default:
+                   if (((c >= ' ' && c <= '~') ||
+                         ((unsigned char)c >= 160)) && pos < 40) {
+                       username[pos++] = c;
+                       c_write(&c, 1);
+                   }
+                   break;
+               }
+           }
+           c_write("\r\n", 2);
+           username[strcspn(username, "\n\r")] = '\0';
+       } else {
+           char stuff[200];
+           strncpy(username, cfg.username, 99);
+           username[99] = '\0';
+            if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+               sprintf(stuff, "Using username \"%s\".\r\n", username);
+               c_write(stuff, strlen(stuff));
+           }
+       }
+
+       if (!(flags & FLAG_INTERACTIVE)) {
+           char prompt[200];
+           sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost);
+           if (!ssh_get_password(prompt, password, sizeof(password))) {
+                /*
+                 * get_password failed to get a password (for
+                 * example because one was supplied on the command
+                 * line which has already failed to work).
+                 * Terminate.
+                 */
+                logevent("No more passwords to try");
+                ssh_state = SSH_STATE_CLOSED;
+                crReturnV;
+            }
+       } else {
+            c_write("password: ", 10);
+            ssh_send_ok = 1;
+
+            pos = 0;
+            while (pos >= 0) {
+                crWaitUntilV(!ispkt);
+                while (inlen--) switch (c = *in++) {
+                  case 10: case 13:
+                    password[pos] = 0;
+                    pos = -1;
+                    break;
+                  case 8: case 127:
+                    if (pos > 0)
+                        pos--;
+                    break;
+                  case 21: case 27:
+                    pos = 0;
+                    break;
+                  case 3: case 4:
+                    random_save_seed();
+                    exit(0);
+                    break;
+                  default:
+                    if (((c >= ' ' && c <= '~') ||
+                         ((unsigned char)c >= 160)) && pos < 40)
+                        password[pos++] = c;
+                    break;
+                }
+            }
+            c_write("\r\n", 2);
+       }
+
+        ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+        ssh2_pkt_addstring(username);
+        ssh2_pkt_addstring("ssh-connection");   /* service requested */
+        ssh2_pkt_addstring("password");
+        ssh2_pkt_addbool(FALSE);
+        ssh2_pkt_addstring(password);
+        ssh2_pkt_send();
+
+        crWaitUntilV(ispkt);
+        if (pktin.type != SSH2_MSG_USERAUTH_SUCCESS) {
+           c_write("Access denied\r\n", 15);
+           logevent("Authentication refused");
+        } else
+            break;
+    }
+
+    /*
+     * Now we're authenticated for the connection protocol. The
+     * connection protocol will automatically have started at this
+     * point; there's no need to send SERVICE_REQUEST.
+     */
+
+    /*
+     * So now create a channel with a session in it.
+     */
+    mainchan = malloc(sizeof(struct ssh_channel));
+    mainchan->localid = 100;           /* as good as any */
+    ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
+    ssh2_pkt_addstring("session");
+    ssh2_pkt_adduint32(mainchan->localid);
+    ssh2_pkt_adduint32(0x7FFFFFFFUL);  /* our window size */
+    ssh2_pkt_adduint32(0x4000UL);  /* our max pkt size */
+    ssh2_pkt_send();
+    crWaitUntilV(ispkt);
+    if (pktin.type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+        bombout(("Server refused to open a session"));
+        crReturnV;
+        /* FIXME: error data comes back in FAILURE packet */
+    }
+    if (ssh2_pkt_getuint32() != mainchan->localid) {
+        bombout(("Server's channel confirmation cited wrong channel"));
+        crReturnV;
+    }
+    mainchan->remoteid = ssh2_pkt_getuint32();
+    mainchan->u.v2.remwindow = ssh2_pkt_getuint32();
+    mainchan->u.v2.remmaxpkt = ssh2_pkt_getuint32();
+    mainchan->u.v2.outbuffer = NULL;
+    mainchan->u.v2.outbuflen = mainchan->u.v2.outbufsize = 0;
+    logevent("Opened channel for session");
+
+    /*
+     * Now allocate a pty for the session.
+     */
+    if (!cfg.nopty) {
+        ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+        ssh2_pkt_adduint32(mainchan->remoteid); /* recipient channel */
+        ssh2_pkt_addstring("pty-req");
+        ssh2_pkt_addbool(1);           /* want reply */
+        ssh2_pkt_addstring(cfg.termtype);
+        ssh2_pkt_adduint32(cols);
+        ssh2_pkt_adduint32(rows);
+        ssh2_pkt_adduint32(0);         /* pixel width */
+        ssh2_pkt_adduint32(0);         /* pixel height */
+        ssh2_pkt_addstring_start();
+        ssh2_pkt_addstring_data("\0", 1);/* TTY_OP_END, no special options */
+        ssh2_pkt_send();
+
+        do {
+            crWaitUntilV(ispkt);
+            if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+                /* FIXME: be able to handle other channels here */
+                if (ssh2_pkt_getuint32() != mainchan->localid)
+                    continue;          /* wrong channel */
+                mainchan->u.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"));
+                crReturnV;
+            }
+            c_write("Server refused to allocate pty\r\n", 32);
+        } else {
+            logevent("Allocated pty");
+        }
+    }
+
+    /*
+     * Start a shell or a remote command.
+     */
+    ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+    ssh2_pkt_adduint32(mainchan->remoteid); /* recipient channel */
+    if (*cfg.remote_cmd) {
+        ssh2_pkt_addstring("exec");
+        ssh2_pkt_addbool(1);           /* want reply */
+        ssh2_pkt_addstring(cfg.remote_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) {
+            /* FIXME: be able to handle other channels here */
+            if (ssh2_pkt_getuint32() != mainchan->localid)
+                continue;          /* wrong channel */
+            mainchan->u.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 shell/command request"));
+            crReturnV;
+        }
+        bombout(("Server refused to start a shell/command"));
+        crReturnV;
+    } else {
+        logevent("Started a shell/command");
+    }
 
-    while (!do_ssh2_kex(in, inlen, ispkt)) {
+    /*
+     * Transfer data!
+     */
+    ssh_send_ok = 1;
+    while (1) {
+        static int try_send;
        crReturnV;
+        try_send = FALSE;
+       if (ispkt) {
+           if (pktin.type == SSH2_MSG_CHANNEL_DATA ||
+                pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
+                char *data;
+                int length;
+                /* FIXME: be able to handle other channels here */
+                if (ssh2_pkt_getuint32() != mainchan->localid)
+                    continue;          /* wrong channel */
+                if (pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
+                    ssh2_pkt_getuint32() != SSH2_EXTENDED_DATA_STDERR)
+                    continue;          /* extended but not stderr */
+                ssh2_pkt_getstring(&data, &length);
+                if (data) {
+                    c_write(data, length);
+                    /*
+                     * Enlarge the window again at the remote side,
+                     * just in case it ever runs down and they fail
+                     * to send us any more data.
+                     */
+                    ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+                    ssh2_pkt_adduint32(mainchan->remoteid);
+                    ssh2_pkt_adduint32(length);
+                    ssh2_pkt_send();
+                }
+           } else if (pktin.type == SSH2_MSG_DISCONNECT) {
+                ssh_state = SSH_STATE_CLOSED;
+               logevent("Received disconnect message");
+           } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
+                continue;              /* exit status et al; ignore (FIXME?) */
+           } else if (pktin.type == SSH2_MSG_CHANNEL_EOF) {
+                continue;              /* remote sends EOF; ignore */
+           } else if (pktin.type == SSH2_MSG_CHANNEL_CLOSE) {
+                /* FIXME: be able to handle other channels here */
+                if (ssh2_pkt_getuint32() != mainchan->localid)
+                    continue;          /* wrong channel */
+                ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+                ssh2_pkt_adduint32(mainchan->remoteid);
+                ssh2_pkt_send();
+                /* FIXME: mark the channel as closed */
+                if (1 /* FIXME: "all channels are closed" */) {
+                    logevent("All channels closed. Disconnecting");
+                    ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+                    ssh2_pkt_send();
+                }
+                continue;              /* remote sends close; ignore (FIXME) */
+           } else if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+                /* FIXME: be able to handle other channels here */
+                if (ssh2_pkt_getuint32() != mainchan->localid)
+                    continue;          /* wrong channel */
+                mainchan->u.v2.remwindow += ssh2_pkt_getuint32();
+                try_send = TRUE;
+           } else {
+               bombout(("Strange packet received: type %d", pktin.type));
+                crReturnV;
+           }
+       } else {
+            /*
+             * We have spare data. Add it to the channel buffer.
+             */
+            if (mainchan->u.v2.outbufsize <
+                mainchan->u.v2.outbuflen + inlen) {
+                mainchan->u.v2.outbufsize =
+                    mainchan->u.v2.outbuflen + inlen + 1024;
+                mainchan->u.v2.outbuffer = srealloc(mainchan->u.v2.outbuffer,
+                                                    mainchan->u.v2.outbufsize);
+            }
+            memcpy(mainchan->u.v2.outbuffer + mainchan->u.v2.outbuflen,
+                   in, inlen);
+            mainchan->u.v2.outbuflen += inlen;
+            try_send = TRUE;
+       }
+        if (try_send) {
+            /*
+             * Try to send data on the channel if we can. (FIXME:
+             * on _all_ channels.)
+             */
+            while (mainchan->u.v2.remwindow > 0 &&
+                   mainchan->u.v2.outbuflen > 0) {
+                unsigned len = mainchan->u.v2.remwindow;
+                if (len > mainchan->u.v2.outbuflen)
+                    len = mainchan->u.v2.outbuflen;
+                if (len > mainchan->u.v2.remmaxpkt)
+                    len = mainchan->u.v2.remmaxpkt;
+                ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
+                ssh2_pkt_adduint32(mainchan->remoteid);
+                ssh2_pkt_addstring_start();
+                ssh2_pkt_addstring_data(mainchan->u.v2.outbuffer, len);
+                ssh2_pkt_send();
+                mainchan->u.v2.outbuflen -= len;
+                memmove(mainchan->u.v2.outbuffer, mainchan->u.v2.outbuffer+len,
+                        mainchan->u.v2.outbuflen);
+                mainchan->u.v2.remwindow -= len;
+            }
+        }
     }
 
     crFinishV;
 }
 
 /*
+ * Handle the top-level SSH2 protocol.
+ */
+static void ssh2_protocol(unsigned char *in, int inlen, int ispkt)
+{
+    if (do_ssh2_transport(in, inlen, ispkt) == 0)
+        return;
+    do_ssh2_authconn(in, inlen, ispkt);
+}
+
+/*
  * Called to set up the connection. Will arrange for WM_NETEVENT
  * messages to be passed to the specified window, whose window
  * procedure should then call telnet_msg().
@@ -1561,7 +2470,7 @@ static char *ssh_init (HWND hwnd, char *host, int port, char **realhost) {
     if (!do_ssh_init())
        return "Protocol initialisation error";
 
-    if (WSAAsyncSelect (s, hwnd, WM_NETEVENT, FD_READ | FD_CLOSE) == SOCKET_ERROR)
+    if (hwnd && WSAAsyncSelect (s, hwnd, WM_NETEVENT, FD_READ | FD_CLOSE) == SOCKET_ERROR)
        switch (WSAGetLastError()) {
          case WSAENETDOWN: return "Network is down";
          default: return "WSAAsyncSelect(): unknown error";
@@ -1588,8 +2497,11 @@ static int ssh_msg (WPARAM wParam, LPARAM lParam) {
     if (s == INVALID_SOCKET)
        return 1;
 
-    if (WSAGETSELECTERROR(lParam) != 0)
+    if (WSAGETSELECTERROR(lParam) != 0) {
+        closesocket(s);
+        s = INVALID_SOCKET;
        return -WSAGETSELECTERROR(lParam);
+    }
 
     switch (WSAGETSELECTEVENT(lParam)) {
       case FD_READ:
@@ -1597,8 +2509,11 @@ static int ssh_msg (WPARAM wParam, LPARAM lParam) {
        ret = recv(s, buf, sizeof(buf), 0);
        if (ret < 0 && WSAGetLastError() == WSAEWOULDBLOCK)
            return 1;
-       if (ret < 0)                   /* any _other_ error */
+       if (ret < 0) {                 /* any _other_ error */
+            closesocket(s);
+            s = INVALID_SOCKET;
            return -10000-WSAGetLastError();
+        }
        if (ret == 0) {
            s = INVALID_SOCKET;
            return 0;
@@ -1645,10 +2560,23 @@ static void ssh_size(void) {
 }
 
 /*
- * (Send Telnet special codes)
+ * Send Telnet special codes. TS_EOF is useful for `plink', so you
+ * can send an EOF and collect resulting output (e.g. `plink
+ * hostname sort').
  */
 static void ssh_special (Telnet_Special code) {
-    /* do nothing */
+    if (code == TS_EOF) {
+        if (ssh_version == 1) {
+            send_packet(SSH1_CMSG_EOF, PKT_END);
+        } else {
+            ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF);
+            ssh2_pkt_adduint32(mainchan->remoteid);
+            ssh2_pkt_send();
+        }
+        logevent("Sent EOF message");
+    } else {
+        /* do nothing */
+    }
 }
 
 
@@ -1662,8 +2590,6 @@ static void get_packet(void)
     long to_read;
     int len;
 
-    assert(IS_SCP);
-
     p = NULL;
     len = 0;
 
@@ -1692,8 +2618,6 @@ int ssh_scp_recv(unsigned char *buf, int len)
     static unsigned char *pending_input_ptr;
     int to_read = len;
 
-    assert(IS_SCP);
-
     if (pending_input_len >= to_read) {
        memcpy(buf, pending_input_ptr, to_read);
        pending_input_ptr += to_read;
@@ -1743,7 +2667,8 @@ int ssh_scp_recv(unsigned char *buf, int len)
            closesocket(s);
            s = INVALID_SOCKET;
        } else {
-           fatalbox("Strange packet received: type %d", pktin.type);
+           bombout(("Strange packet received: type %d", pktin.type));
+            return 0;
        }
     }
 
@@ -1757,7 +2682,6 @@ int ssh_scp_recv(unsigned char *buf, int len)
  */
 void ssh_scp_send(unsigned char *buf, int len)
 {
-    assert(IS_SCP);
     if (s == INVALID_SOCKET)
        return;
     send_packet(SSH1_CMSG_STDIN_DATA,
@@ -1770,7 +2694,6 @@ void ssh_scp_send(unsigned char *buf, int len)
  */
 void ssh_scp_send_eof(void)
 {
-    assert(IS_SCP);
     if (s == INVALID_SOCKET)
        return;
     send_packet(SSH1_CMSG_EOF, PKT_END);
@@ -1786,8 +2709,6 @@ char *ssh_scp_init(char *host, int port, char *cmd, char **realhost)
 {
     char buf[160], *p;
 
-    assert(IS_SCP);
-
 #ifdef MSCRYPTOAPI
     if (crypto_startup() == 0)
        return "Microsoft high encryption pack not installed!";
@@ -1823,12 +2744,16 @@ char *ssh_scp_init(char *host, int port, char *cmd, char **realhost)
     return NULL;
 }
 
+static SOCKET ssh_socket(void) { return s; }
+
+static int ssh_sendok(void) { return ssh_send_ok; }
 
 Backend ssh_backend = {
     ssh_init,
     ssh_msg,
     ssh_send,
     ssh_size,
-    ssh_special
+    ssh_special,
+    ssh_socket,
+    ssh_sendok
 };
-