Yet another attempt at OOB handling in the network abstraction. This
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 26b26c3..5a7dcf2 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -19,8 +19,9 @@
                       if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \
                       fprintf(stderr, "%s\n", s); }
 
-#define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, sk_close(s), \
-                       s = NULL, connection_fatal msg )
+#define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, \
+                          (s ? sk_close(s), s = NULL : (void)0), \
+                          connection_fatal msg )
 
 #define SSH1_MSG_DISCONNECT                       1    /* 0x1 */
 #define SSH1_SMSG_PUBLIC_KEY                      2    /* 0x2 */
 #define SSH1_MSG_CHANNEL_DATA                     23   /* 0x17 */
 #define SSH1_MSG_CHANNEL_CLOSE                    24   /* 0x18 */
 #define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION       25   /* 0x19 */
+#define SSH1_SMSG_X11_OPEN                        27   /* 0x1b */
+#define SSH1_CMSG_PORT_FORWARD_REQUEST            28   /* 0x1c */
+#define SSH1_MSG_PORT_OPEN                        29   /* 0x1d */
 #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_CMSG_EXIT_CONFIRMATION               33   /* 0x21 */
+#define SSH1_CMSG_X11_REQUEST_FORWARDING          34   /* 0x22 */
+#define SSH1_CMSG_AUTH_RHOSTS_RSA                 35   /* 0x23 */
 #define SSH1_MSG_DEBUG                            36   /* 0x24 */
 #define SSH1_CMSG_REQUEST_COMPRESSION             37   /* 0x25 */
 #define SSH1_CMSG_AUTH_TIS                        39   /* 0x27 */
@@ -160,6 +166,11 @@ extern const struct ssh_cipher ssh_des;
 extern const struct ssh_cipher ssh_blowfish_ssh1;
 extern const struct ssh_cipher ssh_blowfish_ssh2;
 
+extern char *x11_init (Socket *, char *, void *);
+extern void x11_close (Socket);
+extern void x11_send  (Socket , char *, int);
+extern void x11_invent_auth(char *, int, char *, int);
+
 /*
  * Ciphers for SSH2. We miss out single-DES because it isn't
  * supported; also 3DES and Blowfish are both done differently from
@@ -201,6 +212,12 @@ extern const struct ssh_compress ssh_zlib;
 const static struct ssh_compress *compressions[] = {
     &ssh_zlib, &ssh_comp_none };
 
+enum {                                 /* channel types */
+    CHAN_MAINSESSION,
+    CHAN_X11,
+    CHAN_AGENT,
+};
+
 /*
  * 2-3-4 tree storing channels.
  */
@@ -208,17 +225,20 @@ struct ssh_channel {
     unsigned remoteid, localid;
     int type;
     int closes;
+    struct ssh2_data_channel {
+        unsigned char *outbuffer;
+        unsigned outbuflen, outbufsize;
+        unsigned remwindow, remmaxpkt;
+    } v2;
     union {
         struct 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;
+        struct ssh_x11_channel {
+            Socket s;
+        } x11;
     } u;
 };
 
@@ -238,6 +258,7 @@ static Socket s = NULL;
 static unsigned char session_key[32];
 static int ssh1_compressing;
 static int ssh_agentfwd_enabled;
+static int ssh_X11_fwd_enabled;
 static const struct ssh_cipher *cipher = NULL;
 static const struct ssh_cipher *cscipher = NULL;
 static const struct ssh_cipher *sccipher = NULL;
@@ -252,6 +273,7 @@ int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL;
 static char *savedhost;
 static int savedport;
 static int ssh_send_ok;
+static int ssh_echoing, ssh_editing;
 
 static tree234 *ssh_channels;           /* indexed by local id */
 static struct ssh_channel *mainchan;   /* primary session channel */
@@ -275,6 +297,8 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt);
 static void ssh2_protocol(unsigned char *in, int inlen, int ispkt);
 static void ssh_size(void);
 static void ssh_special (Telnet_Special);
+static void ssh2_try_send(struct ssh_channel *c);
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len);
 
 static int (*s_rdpkt)(unsigned char **data, int *datalen);
 
@@ -794,15 +818,18 @@ static void sha_string(SHA_State *s, void *str, int len) {
 /*
  * SSH2 packet construction functions.
  */
-static void ssh2_pkt_adddata(void *data, int len) {
-    pktout.length += len;
-    if (pktout.maxlen < pktout.length) {
-        pktout.maxlen = pktout.length + 256;
+static void ssh2_pkt_ensure(int length) {
+    if (pktout.maxlen < length) {
+        pktout.maxlen = length + 256;
        pktout.data = (pktout.data == NULL ? smalloc(pktout.maxlen+APIEXTRA) :
                        srealloc(pktout.data, pktout.maxlen+APIEXTRA));
         if (!pktout.data)
             fatalbox("Out of memory");
     }
+}
+static void ssh2_pkt_adddata(void *data, int len) {
+    pktout.length += len;
+    ssh2_pkt_ensure(pktout.length);
     memcpy(pktout.data+pktout.length-len, data, len);
 }
 static void ssh2_pkt_addbyte(unsigned char byte) {
@@ -896,6 +923,8 @@ static void ssh2_pkt_send(void) {
     cipherblk = cipherblk < 8 ? 8 : cipherblk;   /* or 8 if blksize < 8 */
     padding = 4;
     padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk;
+    maclen = csmac ? csmac->len : 0;
+    ssh2_pkt_ensure(pktout.length + padding + maclen);
     pktout.data[4] = padding;
     for (i = 0; i < padding; i++)
         pktout.data[pktout.length + i] = random_byte();
@@ -914,7 +943,6 @@ static void ssh2_pkt_send(void) {
 
     if (cscipher)
         cscipher->encrypt(pktout.data, pktout.length + padding);
-    maclen = csmac ? csmac->len : 0;
 
     sk_write(s, pktout.data, pktout.length + padding + maclen);
 }
@@ -1122,7 +1150,14 @@ static void ssh_gotdata(unsigned char *data, int datalen)
 }
 
 static int ssh_receive(Socket skt, int urgent, char *data, int len) {
-    if (!len) {
+    if (urgent==3) {
+        /* A socket error has occurred. */
+        ssh_state = SSH_STATE_CLOSED;
+        sk_close(s);
+        s = NULL;
+        connection_fatal(data);
+        return 0;
+    } else if (!len) {
        /* Connection has closed. */
        ssh_state = SSH_STATE_CLOSED;
        sk_close(s);
@@ -1184,7 +1219,7 @@ static char *connect_to_host(char *host, int port, char **realhost)
     /*
      * Open socket.
      */
-    s = sk_new(addr, port, ssh_receive);
+    s = sk_new(addr, port, 0, 1, ssh_receive);
     if ( (err = sk_socket_error(s)) )
        return err;
 
@@ -1719,6 +1754,36 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
     crFinish(1);
 }
 
+void sshfwd_close(struct ssh_channel *c) {
+    if (c) {
+        if (ssh_version == 1) {
+            send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, PKT_END);
+        } else {
+            ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+            ssh2_pkt_adduint32(c->remoteid);
+            ssh2_pkt_send();
+        }
+        c->closes = 1;
+        if (c->type == CHAN_X11) {
+            c->u.x11.s = NULL;
+            logevent("X11 connection terminated");
+        }
+    }
+}
+
+void sshfwd_write(struct ssh_channel *c, char *buf, int len) {
+    if (ssh_version == 1) {
+        send_packet(SSH1_MSG_CHANNEL_DATA,
+                    PKT_INT, c->remoteid,
+                    PKT_INT, len,
+                    PKT_DATA, buf, len,
+                    PKT_END);
+    } else {
+        ssh2_add_channel_data(c, buf, len);
+        ssh2_try_send(c);
+    }
+}
+
 static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
     crBegin;
 
@@ -1745,6 +1810,26 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
        }
     }
 
+    if (cfg.x11_forward) {
+        char proto[20], data[64];
+        logevent("Requesting X11 forwarding");
+        x11_invent_auth(proto, sizeof(proto), data, sizeof(data));
+        send_packet(SSH1_CMSG_X11_REQUEST_FORWARDING, 
+                   PKT_STR, proto, PKT_STR, data,
+                   PKT_INT, 0,
+                   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("X11 forwarding refused");
+        } else {
+            logevent("X11 forwarding enabled");
+           ssh_X11_fwd_enabled = TRUE;
+       }
+    }
+
     if (!cfg.nopty) {
        send_packet(SSH1_CMSG_REQUEST_PTY,
                    PKT_STR, cfg.termtype,
@@ -1759,8 +1844,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
             crReturnV;
         } else if (pktin.type == SSH1_SMSG_FAILURE) {
             c_write("Server refused to allocate pty\r\n", 32);
+            ssh_editing = ssh_echoing = 1;
         }
        logevent("Allocated pty");
+    } else {
+        ssh_editing = ssh_echoing = 1;
     }
 
     if (cfg.compression) {
@@ -1790,9 +1878,9 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
     if (eof_needed)
         ssh_special(TS_EOF);
 
+    ldisc_send(NULL, 0);               /* cause ldisc to notice changes */
     ssh_send_ok = 1;
     ssh_channels = newtree234(ssh_channelcmp);
-    begin_session();
     while (1) {
        crReturnV;
        if (ispkt) {
@@ -1805,6 +1893,47 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
                 ssh_state = SSH_STATE_CLOSED;
                logevent("Received disconnect request");
                 crReturnV;
+            } else if (pktin.type == SSH1_SMSG_X11_OPEN) {
+                /* Remote side is trying to open a channel to talk to our
+                 * X-Server. Give them back a local channel number. */
+                unsigned i;
+                struct ssh_channel *c, *d;
+                enum234 e;
+
+               logevent("Received X11 connect request");
+               /* Refuse if X11 forwarding is disabled. */
+               if (!ssh_X11_fwd_enabled) {
+                   send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+                               PKT_INT, GET_32BIT(pktin.body),
+                               PKT_END);
+                   logevent("Rejected X11 connect request");
+               } else {
+                   c = smalloc(sizeof(struct ssh_channel));
+
+                   if ( x11_init(&c->u.x11.s, cfg.x11_display, c) != NULL ) {
+                     logevent("opening X11 forward connection failed");
+                     sfree(c);
+                     send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+                                 PKT_INT, GET_32BIT(pktin.body),
+                                 PKT_END);
+                   } else {
+                     logevent("opening X11 forward connection succeeded");
+                     for (i=1, d = first234(ssh_channels, &e); d; d = next234(&e)) {
+                          if (d->localid > i)
+                              break;     /* found a free number */
+                          i = d->localid + 1;
+                     }
+                     c->remoteid = GET_32BIT(pktin.body);
+                     c->localid = i;
+                     c->closes = 0;
+                     c->type = CHAN_X11;   /* identify channel type */
+                     add234(ssh_channels, c);
+                     send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+                                 PKT_INT, c->remoteid, PKT_INT, c->localid,
+                                 PKT_END);
+                     logevent("Opened X11 forward channel");
+                   }
+               }
             } 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. */
@@ -1828,7 +1957,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
                    c->remoteid = GET_32BIT(pktin.body);
                    c->localid = i;
                    c->closes = 0;
-                   c->type = SSH1_SMSG_AGENT_OPEN;/* identify channel type */
+                   c->type = CHAN_AGENT;   /* identify channel type */
                    c->u.a.lensofar = 0;
                    add234(ssh_channels, c);
                    send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
@@ -1845,6 +1974,12 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
                     int closetype;
                     closetype = (pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
                     send_packet(pktin.type, PKT_INT, c->remoteid, PKT_END);
+                   if ((c->closes == 0) && (c->type == CHAN_X11)) {
+                       logevent("X11 connection closed");
+                       assert(c->u.x11.s != NULL);
+                       x11_close(c->u.x11.s);
+                       c->u.x11.s = NULL;
+                   }
                     c->closes |= closetype;
                     if (c->closes == 3) {
                         del234(ssh_channels, c);
@@ -1860,7 +1995,10 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
                 c = find234(ssh_channels, &i, ssh_channelfind);
                 if (c) {
                     switch(c->type) {
-                      case SSH1_SMSG_AGENT_OPEN:
+                      case CHAN_X11:
+                       x11_send(c->u.x11.s, p, len);
+                       break;
+                      case CHAN_AGENT:
                         /* Data for an agent message. Buffer it. */
                         while (len > 0) {
                             if (c->u.a.lensofar < 4) {
@@ -1959,14 +2097,14 @@ static int in_commasep_string(char *needle, char *haystack, int haylen) {
 /*
  * SSH2 key creation method.
  */
-static void ssh2_mkkey(Bignum K, char *H, char chr, char *keyspace) {
+static void ssh2_mkkey(Bignum K, char *H, char *sessid, 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_Bytes(&s, sessid, 20);
     SHA_Final(&s, keyspace);
     /* Next 20 bytes. */
     SHA_Init(&s);
@@ -1996,6 +2134,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     static int hostkeylen, siglen;
     static void *hkey;                /* actual host key */
     static unsigned char exchange_hash[20];
+    static unsigned char first_exchange_hash[20];
     static unsigned char keyspace[40];
     static const struct ssh_cipher *preferred_cipher;
     static const struct ssh_compress *preferred_comp;
@@ -2252,8 +2391,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     fingerprint = hostkey->fingerprint(hkey);
     verify_ssh_host_key(savedhost, savedport, hostkey->keytype,
                         keystr, fingerprint);
-    logevent("Host key fingerprint is:");
-    logevent(fingerprint);
+    if (first_kex) {                  /* don't bother logging this in rekeys */
+       logevent("Host key fingerprint is:");
+       logevent(fingerprint);
+    }
     sfree(fingerprint);
     sfree(keystr);
     hostkey->freekey(hkey);
@@ -2276,14 +2417,23 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     cscomp->compress_init();
     sccomp->decompress_init();
     /*
-     * Set IVs after keys.
+     * Set IVs after keys. Here we use the exchange hash from the
+     * _first_ key exchange.
      */
-    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);
+    if (first_kex)
+       memcpy(first_exchange_hash, exchange_hash, sizeof(exchange_hash));
+    ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'C', keyspace);
+    cscipher->setcskey(keyspace);
+    ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'D', keyspace);
+    sccipher->setsckey(keyspace);
+    ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'A', keyspace);
+    cscipher->setcsiv(keyspace);
+    ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'B', keyspace);
+    sccipher->setsciv(keyspace);
+    ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'E', keyspace);
+    csmac->setcskey(keyspace);
+    ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'F', keyspace);
+    scmac->setsckey(keyspace);
 
     /*
      * If this is the first key exchange phase, we must pass the
@@ -2307,12 +2457,52 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     do {
         crReturn(1);
     } while (!(ispkt && pktin.type == SSH2_MSG_KEXINIT));
+    logevent("Server initiated key re-exchange");
     goto begin_key_exchange;
 
     crFinish(1);
 }
 
 /*
+ * Add data to an SSH2 channel output buffer.
+ */
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len) {
+    if (c->v2.outbufsize <
+        c->v2.outbuflen + len) {
+        c->v2.outbufsize =
+            c->v2.outbuflen + len + 1024;
+        c->v2.outbuffer = srealloc(c->v2.outbuffer,
+                                   c->v2.outbufsize);
+    }
+    memcpy(c->v2.outbuffer + c->v2.outbuflen,
+           buf, len);
+    c->v2.outbuflen += len;
+}
+
+/*
+ * Attempt to send data on an SSH2 channel.
+ */
+static void ssh2_try_send(struct ssh_channel *c) {
+    while (c->v2.remwindow > 0 &&
+           c->v2.outbuflen > 0) {
+        unsigned len = c->v2.remwindow;
+        if (len > c->v2.outbuflen)
+            len = c->v2.outbuflen;
+        if (len > c->v2.remmaxpkt)
+            len = c->v2.remmaxpkt;
+        ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
+        ssh2_pkt_adduint32(c->remoteid);
+        ssh2_pkt_addstring_start();
+        ssh2_pkt_addstring_data(c->v2.outbuffer, len);
+        ssh2_pkt_send();
+        c->v2.outbuflen -= len;
+        memmove(c->v2.outbuffer, c->v2.outbuffer+len,
+                c->v2.outbuflen);
+        c->v2.remwindow -= len;
+    }
+}
+
+/*
  * Handle the SSH2 userauth and connection layers.
  */
 static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
@@ -2486,13 +2676,58 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
         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;
+    mainchan->type = CHAN_MAINSESSION;
+    mainchan->closes = 0;
+    mainchan->v2.remwindow = ssh2_pkt_getuint32();
+    mainchan->v2.remmaxpkt = ssh2_pkt_getuint32();
+    mainchan->v2.outbuffer = NULL;
+    mainchan->v2.outbuflen = mainchan->v2.outbufsize = 0;
+    ssh_channels = newtree234(ssh_channelcmp);
+    add234(ssh_channels, mainchan);
     logevent("Opened channel for session");
 
     /*
+     * Potentially enable X11 forwarding.
+     */
+    if (cfg.x11_forward) {
+        char proto[20], data[64];
+        logevent("Requesting X11 forwarding");
+        x11_invent_auth(proto, sizeof(proto), data, sizeof(data));
+        ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+        ssh2_pkt_adduint32(mainchan->remoteid);
+        ssh2_pkt_addstring("x11-req");
+        ssh2_pkt_addbool(1);           /* want reply */
+        ssh2_pkt_addbool(0);           /* many connections */
+        ssh2_pkt_addstring(proto);
+        ssh2_pkt_addstring(data);
+        ssh2_pkt_adduint32(0);         /* screen number */
+        ssh2_pkt_send();
+
+        do {
+            crWaitUntilV(ispkt);
+            if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+                unsigned i = ssh2_pkt_getuint32();
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (!c)
+                    continue;          /* nonexistent channel */
+                c->v2.remwindow += ssh2_pkt_getuint32();
+            }
+        } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+
+        if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+            if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+                bombout(("Server got confused by X11 forwarding request"));
+                crReturnV;
+            }
+            logevent("X11 forwarding refused");
+        } else {
+            logevent("X11 forwarding enabled");
+           ssh_X11_fwd_enabled = TRUE;
+        }
+    }
+
+    /*
      * Now allocate a pty for the session.
      */
     if (!cfg.nopty) {
@@ -2513,10 +2748,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
         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();
+                unsigned i = ssh2_pkt_getuint32();
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (!c)
+                    continue;          /* nonexistent channel */
+                c->v2.remwindow += ssh2_pkt_getuint32();
             }
         } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
 
@@ -2526,9 +2763,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                 crReturnV;
             }
             c_write("Server refused to allocate pty\r\n", 32);
+            ssh_editing = ssh_echoing = 1;
         } else {
             logevent("Allocated pty");
         }
+    } else {
+        ssh_editing = ssh_echoing = 1;
     }
 
     /*
@@ -2548,10 +2788,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     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();
+            unsigned i = ssh2_pkt_getuint32();
+            struct ssh_channel *c;
+            c = find234(ssh_channels, &i, ssh_channelfind);
+            if (!c)
+                continue;              /* nonexistent channel */
+            c->v2.remwindow += ssh2_pkt_getuint32();
         }
     } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
     if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
@@ -2574,8 +2816,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     /*
      * Transfer data!
      */
+    ldisc_send(NULL, 0);               /* cause ldisc to notice changes */
     ssh_send_ok = 1;
-    begin_session();
     while (1) {
         static int try_send;
        crReturnV;
@@ -2585,23 +2827,32 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                 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 */
+                unsigned i = ssh2_pkt_getuint32();
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (!c)
+                    continue;          /* nonexistent 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) {
-                    from_backend(pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA,
-                                data, length);
+                    switch (c->type) {
+                      case CHAN_MAINSESSION:
+                        from_backend(pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA,
+                                     data, length);
+                        break;
+                      case CHAN_X11:
+                        x11_send(c->u.x11.s, data, length);
+                        break;
+                    }
                     /*
-                     * Enlarge the window again at the remote side,
-                     * just in case it ever runs down and they fail
-                     * to send us any more data.
+                     * 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(c->remoteid);
                     ssh2_pkt_adduint32(length);
                     ssh2_pkt_send();
                 }
@@ -2612,16 +2863,50 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            } 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 */
+                unsigned i = ssh2_pkt_getuint32();
+                struct ssh_channel *c;
+
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (!c)
+                    continue;          /* nonexistent channel */
+                
+                if (c->type == CHAN_X11) {
+                    /*
+                     * Remote EOF on an X11 channel means we should
+                     * wrap up and close the channel ourselves.
+                     */
+                    x11_close(c->u.x11.s);
+                    sshfwd_close(c);
+                }
            } 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" */) {
+                unsigned i = ssh2_pkt_getuint32();
+                struct ssh_channel *c;
+                enum234 e;
+
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (!c)
+                    continue;          /* nonexistent channel */
+                if (c->closes == 0) {
+                    ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+                    ssh2_pkt_adduint32(c->remoteid);
+                    ssh2_pkt_send();
+                }
+                /* Do pre-close processing on the channel. */
+                switch (c->type) {
+                  case CHAN_MAINSESSION:
+                    break;             /* nothing to see here, move along */
+                  case CHAN_X11:
+                    break;
+                }
+                del234(ssh_channels, c);
+                sfree(c->v2.outbuffer);
+                sfree(c);
+
+                /*
+                 * See if that was the last channel left open.
+                 */
+                c = first234(ssh_channels, &e);
+                if (!c) {
                     logevent("All channels closed. Disconnecting");
                     ssh2_pkt_init(SSH2_MSG_DISCONNECT);
                     ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
@@ -2633,11 +2918,67 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                 }
                 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();
+                unsigned i = ssh2_pkt_getuint32();
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (!c)
+                    continue;          /* nonexistent channel */
+                mainchan->v2.remwindow += ssh2_pkt_getuint32();
                 try_send = TRUE;
+           } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
+                char *type;
+                int typelen;
+                char *error = NULL;
+                struct ssh_channel *c;
+                ssh2_pkt_getstring(&type, &typelen);
+                c = smalloc(sizeof(struct ssh_channel));
+
+                if (typelen == 3 && !memcmp(type, "x11", 3)) {
+                    if (!ssh_X11_fwd_enabled)
+                        error = "X11 forwarding is not enabled";
+                    else if ( x11_init(&c->u.x11.s, cfg.x11_display, c) != NULL ) {
+                        error = "Unable to open an X11 connection";
+                    } else {
+                        c->type = CHAN_X11;
+                    }
+                } else {
+                    error = "Unsupported channel type requested";
+                }
+
+                c->remoteid = ssh2_pkt_getuint32();
+                if (error) {
+                    ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+                    ssh2_pkt_adduint32(c->remoteid);
+                    ssh2_pkt_adduint32(SSH2_OPEN_CONNECT_FAILED);
+                    ssh2_pkt_addstring(error);
+                    ssh2_pkt_addstring("en");   /* language tag */
+                    ssh2_pkt_send();
+                    sfree(c);
+                } else {
+                    struct ssh_channel *d;
+                    unsigned i;
+                    enum234 e;
+
+                    for (i=1, d = first234(ssh_channels, &e); d;
+                         d = next234(&e)) {
+                       if (d->localid > i)
+                            break;     /* found a free number */
+                       i = d->localid + 1;
+                    }
+                    c->localid = i;
+                    c->closes = 0;
+                    c->v2.remwindow = ssh2_pkt_getuint32();
+                    c->v2.remmaxpkt = ssh2_pkt_getuint32();
+                    c->v2.outbuffer = NULL;
+                    c->v2.outbuflen = c->v2.outbufsize = 0;
+                    add234(ssh_channels, c);
+                    ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+                    ssh2_pkt_adduint32(c->remoteid);
+                    ssh2_pkt_adduint32(c->localid);
+                    ssh2_pkt_adduint32(0x8000UL);  /* our window size */
+                    ssh2_pkt_adduint32(0x4000UL);  /* our max pkt size */
+                    ssh2_pkt_send();
+                }
            } else {
                bombout(("Strange packet received: type %d", pktin.type));
                 crReturnV;
@@ -2646,40 +2987,17 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
             /*
              * 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;
+            ssh2_add_channel_data(mainchan, in, inlen);
             try_send = TRUE;
        }
         if (try_send) {
+            enum234 e;
+            struct ssh_channel *c;
             /*
-             * Try to send data on the channel if we can. (FIXME:
-             * on _all_ channels.)
+             * Try to send data on all channels if we can.
              */
-            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;
-            }
+            for (c = first234(ssh_channels, &e); c; c = next234(&e))
+                ssh2_try_send(c);
         }
     }
 
@@ -2710,6 +3028,8 @@ static char *ssh_init (char *host, int port, char **realhost) {
 #endif
 
     ssh_send_ok = 0;
+    ssh_editing = 0;
+    ssh_echoing = 0;
 
     p = connect_to_host(host, port, realhost);
     if (p != NULL)
@@ -2805,6 +3125,12 @@ static Socket ssh_socket(void) { return s; }
 
 static int ssh_sendok(void) { return ssh_send_ok; }
 
+static int ssh_ldisc(int option) {
+    if (option == LD_ECHO) return ssh_echoing;
+    if (option == LD_EDIT) return ssh_editing;
+    return FALSE;
+}
+
 Backend ssh_backend = {
     ssh_init,
     ssh_send,
@@ -2812,5 +3138,6 @@ Backend ssh_backend = {
     ssh_special,
     ssh_socket,
     ssh_sendok,
+    ssh_ldisc,
     22
 };