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 dd8ffd5..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 (!(flags & FLAG_CONNECTION) && (flags & FLAG_VERBOSE)) \
+                      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_RSA      6
-#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7
-#define SSH1_CMSG_AUTH_RSA_RESPONSE 8
-#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_CMSG_AUTH_CCARD   70
-#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71
-#define SSH1_CMSG_AUTH_CCARD_RESPONSE  72
-
-#define SSH1_AUTH_TIS          5
-#define SSH1_AUTH_CCARD                16
-
-#define SSH_AGENTC_REQUEST_RSA_IDENTITIES    1
-#define SSH_AGENT_RSA_IDENTITIES_ANSWER      2
-#define SSH_AGENTC_RSA_CHALLENGE             3
-#define SSH_AGENT_RSA_RESPONSE               4
-#define SSH_AGENT_FAILURE                    5
-#define SSH_AGENT_SUCCESS                    6
-#define SSH_AGENTC_ADD_RSA_IDENTITY          7
-#define SSH_AGENTC_REMOVE_RSA_IDENTITY       8
-
-#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 SSH2_MSG_USERAUTH_REQUEST            50
-#define SSH2_MSG_USERAUTH_FAILURE            51
-#define SSH2_MSG_USERAUTH_SUCCESS            52
-#define SSH2_MSG_USERAUTH_BANNER             53
-#define SSH2_MSG_USERAUTH_PK_OK              60
-#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ   60
-#define SSH2_MSG_GLOBAL_REQUEST                  80
-#define SSH2_MSG_REQUEST_SUCCESS                 81
-#define SSH2_MSG_REQUEST_FAILURE                 82
-#define SSH2_MSG_CHANNEL_OPEN                    90
-#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION       91
-#define SSH2_MSG_CHANNEL_OPEN_FAILURE            92
-#define SSH2_MSG_CHANNEL_WINDOW_ADJUST           93
-#define SSH2_MSG_CHANNEL_DATA                    94
-#define SSH2_MSG_CHANNEL_EXTENDED_DATA           95
-#define SSH2_MSG_CHANNEL_EOF                     96
-#define SSH2_MSG_CHANNEL_CLOSE                   97
-#define SSH2_MSG_CHANNEL_REQUEST                 98
-#define SSH2_MSG_CHANNEL_SUCCESS                 99
-#define SSH2_MSG_CHANNEL_FAILURE                 100
-
-#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED    1
-#define SSH2_OPEN_CONNECT_FAILED                 2
-#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE           3
-#define SSH2_OPEN_RESOURCE_SHORTAGE              4
-#define SSH2_EXTENDED_DATA_STDERR                1
+#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) | \
@@ -133,10 +145,20 @@ enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM };
 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_ssh2, &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 };
@@ -146,7 +168,7 @@ struct ssh_hostkey *hostkey_algs[] = { &ssh_dss };
 
 extern struct ssh_mac ssh_sha1;
 
-SHA_State exhash;
+static SHA_State exhash;
 
 static void nullmac_key(unsigned char *key) { }
 static void nullmac_generate(unsigned char *blk, int len, unsigned long seq) { }
@@ -178,6 +200,44 @@ 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,
     SSH_STATE_INTERMED,
@@ -191,8 +251,10 @@ static void s_write (char *buf, int len) {
     while (len > 0) {
        int i = send (s, buf, len, 0);
         noise_ultralight(i);
-        if (i <= 0)
-            fatalbox("Lost connection while sending");
+        if (i <= 0) {
+            bombout(("Lost connection while sending"));
+            return;
+        }
        if (i > 0)
            len -= i, buf += i;
     }
@@ -212,7 +274,7 @@ static int s_read (char *buf, int len) {
 }
 
 static void c_write (char *buf, int len) {
-    if (!(flags & FLAG_CONNECTION)) {
+    if ((flags & FLAG_STDERR)) {
         int i;
         for (i = 0; i < len; i++)
             if (buf[i] != '\r')
@@ -315,7 +377,8 @@ 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 ||
@@ -324,8 +387,10 @@ next_packet:
         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) {
@@ -415,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");
     }
@@ -446,8 +511,10 @@ next_packet:
     /*
      * Check the MAC.
      */
-    if (scmac && !scmac->verify(pktin.data, len+4, incoming_sequence))
-       fatalbox("Incorrect MAC received on packet");
+    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;
@@ -893,8 +960,10 @@ Bignum ssh2_pkt_getmp(void) {
     ssh2_pkt_getstring(&p, &length);
     if (!p)
         return NULL;
-    if (p[0] & 0x80)
-        fatalbox("internal error: Can't handle negative mpints");
+    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;
@@ -1012,8 +1081,10 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     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");
 
@@ -1116,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");
 
@@ -1133,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 ((flags & FLAG_CONNECTION) && !*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++) {
@@ -1173,7 +1247,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            char stuff[200];
            strncpy(username, cfg.username, 99);
            username[99] = '\0';
-            if (flags & FLAG_VERBOSE) {
+            if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
                sprintf(stuff, "Sent username \"%s\".\r\n", username);
                 c_write(stuff, strlen(stuff));
            }
@@ -1227,12 +1301,16 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                 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);
@@ -1273,6 +1351,10 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                                 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");
@@ -1297,7 +1379,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
         if (*cfg.keyfile && !tried_publickey)
             pwpkt_type = SSH1_CMSG_AUTH_RSA;
 
-       if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD && !FLAG_WINDOWED) {
+       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))) {
@@ -1366,6 +1449,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
             }
 
             pos = 0;
+            ssh_send_ok = 1;
             while (pos >= 0) {
                 crWaitUntil(!ispkt);
                 while (inlen--) switch (c = *in++) {
@@ -1432,8 +1516,10 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                     c_write("Server refused our public key.\r\n", 32);
                 continue;              /* go and try password */
             }
-            if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE)
-                fatalbox("Bizarre response to offer of public key");
+            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 */
@@ -1458,7 +1544,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                             45);
                 continue;              /* go and try password */
             } else if (pktin.type != SSH1_SMSG_SUCCESS) {
-                fatalbox("Bizarre response to RSA authentication response");
+                bombout(("Bizarre response to RSA authentication response"));
+                crReturn(0);
             }
 
             break;                     /* we're through! */
@@ -1477,7 +1564,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
             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);
        }
     }
 
@@ -1497,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,
@@ -1507,7 +1608,8 @@ 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);
         }
@@ -1525,6 +1627,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
        ssh_size();
 
     ssh_send_ok = 1;
+    ssh_channels = newtree234(ssh_channelcmp);
     while (1) {
        crReturnV;
        if (ispkt) {
@@ -1535,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) {
@@ -1543,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,
@@ -1623,6 +1816,21 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     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.
@@ -1705,7 +1913,8 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
      * to.
      */
     if (pktin.type != SSH2_MSG_KEXINIT) {
-        fatalbox("expected key exchange packet from server");
+        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;
@@ -1771,8 +1980,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
      * 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.
@@ -1784,7 +1995,8 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 
     crWaitUntil(ispkt);
     if (pktin.type != SSH2_MSG_KEXDH_REPLY) {
-        fatalbox("expected key exchange packet from server");
+        bombout(("expected key exchange packet from server"));
+        crReturn(0);
     }
     ssh2_pkt_getstring(&hostkeydata, &hostkeylen);
     f = ssh2_pkt_getmp();
@@ -1806,15 +2018,19 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 #endif
 
     hostkey->setkey(hostkeydata, hostkeylen);
-    if (!hostkey->verifysig(sigdata, siglen, exchange_hash, 20))
-        fatalbox("Server failed host key check");
+    if (!hostkey->verifysig(sigdata, siglen, exchange_hash, 20)) {
+        bombout(("Server failed host key check"));
+        crReturn(0);
+    }
 
     /*
      * Expect SSH2_MSG_NEWKEYS from server.
      */
     crWaitUntil(ispkt);
-    if (pktin.type != SSH2_MSG_NEWKEYS)
-        fatalbox("expected new-keys packet from server");
+    if (pktin.type != SSH2_MSG_NEWKEYS) {
+        bombout(("expected new-keys packet from server"));
+        crReturn(0);
+    }
 
     /*
      * Authenticate remote host: verify host key. (We've already
@@ -1864,11 +2080,6 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 }
 
 /*
- * SSH2: remote identifier for the main session channel.
- */
-static unsigned long ssh_remote_channel;
-
-/*
  * Handle the SSH2 userauth and connection layers.
  */
 static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
@@ -1885,8 +2096,10 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     ssh2_pkt_addstring("ssh-userauth");
     ssh2_pkt_send();
     crWaitUntilV(ispkt);
-    if (pktin.type != SSH2_MSG_SERVICE_ACCEPT)
-        fatalbox("Server refused user authentication protocol");
+    if (pktin.type != SSH2_MSG_SERVICE_ACCEPT) {
+        bombout(("Server refused user authentication protocol"));
+        crReturnV;
+    }
 
     /*
      * FIXME: currently we support only password authentication.
@@ -1902,8 +2115,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
        static int pos = 0;
        static char c;
 
-       if ((flags & FLAG_CONNECTION) && !*cfg.username) {
+       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++) {
@@ -1942,13 +2156,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            char stuff[200];
            strncpy(username, cfg.username, 99);
            username[99] = '\0';
-           if (flags & FLAG_VERBOSE) {
+            if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
                sprintf(stuff, "Using username \"%s\".\r\n", username);
                c_write(stuff, strlen(stuff));
            }
        }
 
-       if (!(flags & FLAG_WINDOWED)) {
+       if (!(flags & FLAG_INTERACTIVE)) {
            char prompt[200];
            sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost);
            if (!ssh_get_password(prompt, password, sizeof(password))) {
@@ -1964,6 +2178,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
             }
        } else {
             c_write("password: ", 10);
+            ssh_send_ok = 1;
 
             pos = 0;
             while (pos >= 0) {
@@ -2019,72 +2234,101 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     /*
      * 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(100);           /* as good as any */
-    ssh2_pkt_adduint32(0xFFFFFFFFUL);  /* very big window which we ignore */
-    ssh2_pkt_adduint32(0xFFFFFFFFUL);  /* very big max pkt size */
+    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) {
-        fatalbox("Server refused to open a session");
+        bombout(("Server refused to open a session"));
+        crReturnV;
         /* FIXME: error data comes back in FAILURE packet */
     }
-    if (ssh2_pkt_getuint32() != 100) {
-        fatalbox("Server's channel confirmation cited wrong channel");
+    if (ssh2_pkt_getuint32() != mainchan->localid) {
+        bombout(("Server's channel confirmation cited wrong channel"));
+        crReturnV;
     }
-    ssh_remote_channel = ssh2_pkt_getuint32();
-    remote_winsize = ssh2_pkt_getuint32();
-    remote_maxpkt = ssh2_pkt_getuint32();
+    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.
      */
-    ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-    ssh2_pkt_adduint32(ssh_remote_channel); /* 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();
+    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 {                               /* FIXME: pay attention to these */
-        crWaitUntilV(ispkt);
-    } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+        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) {
-            fatalbox("Server got confused by pty request");
+        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");
         }
-        c_write("Server refused to allocate pty\r\n", 32);
-    } else {
-        logevent("Allocated pty");
     }
 
     /*
-     * Start a shell.
+     * Start a shell or a remote command.
      */
     ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-    ssh2_pkt_adduint32(ssh_remote_channel); /* recipient channel */
-    ssh2_pkt_addstring("shell");
-    ssh2_pkt_addbool(1);               /* want reply */
+    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 {                               /* FIXME: pay attention to these */
+    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) {
-            fatalbox("Server got confused by shell request");
+            bombout(("Server got confused by shell/command request"));
+            crReturnV;
         }
-        fatalbox("Server refused to start a shell");
+        bombout(("Server refused to start a shell/command"));
+        crReturnV;
     } else {
-        logevent("Started a shell");
+        logevent("Started a shell/command");
     }
 
     /*
@@ -2092,38 +2336,103 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
      */
     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;
-                if (ssh2_pkt_getuint32() != 100)
+                /* 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)
+                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 request");
+               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) {
-                continue;              /* ignore for now (FIXME!) */
+                /* 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 {
-               fatalbox("Strange packet received: type %d", pktin.type);
+               bombout(("Strange packet received: type %d", pktin.type));
+                crReturnV;
            }
        } else {
-            /* FIXME: for now, ignore window size */
-            ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
-            ssh2_pkt_adduint32(ssh_remote_channel);
-            ssh2_pkt_addstring_start();
-            ssh2_pkt_addstring_data(in, inlen);
-            ssh2_pkt_send();
+            /*
+             * 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;
@@ -2188,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:
@@ -2197,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;
@@ -2251,13 +2566,14 @@ static void ssh_size(void) {
  */
 static void ssh_special (Telnet_Special code) {
     if (code == TS_EOF) {
-        if (ssh_version = 1) {
+        if (ssh_version == 1) {
             send_packet(SSH1_CMSG_EOF, PKT_END);
         } else {
             ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF);
-            ssh2_pkt_adduint32(ssh_remote_channel);
+            ssh2_pkt_adduint32(mainchan->remoteid);
             ssh2_pkt_send();
         }
+        logevent("Sent EOF message");
     } else {
         /* do nothing */
     }
@@ -2351,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;
        }
     }