]> asedeno.scripts.mit.edu Git - PuTTY_svn.git/commitdiff
Implement connection sharing between instances of PuTTY.
authorSimon Tatham <anakin@pobox.com>
Sun, 17 Nov 2013 14:05:41 +0000 (14:05 +0000)
committerSimon Tatham <anakin@pobox.com>
Sun, 17 Nov 2013 14:05:41 +0000 (14:05 +0000)
The basic strategy is described at the top of the new source file
sshshare.c. In very brief: an 'upstream' PuTTY opens a Unix-domain
socket or Windows named pipe, and listens for connections from other
PuTTYs wanting to run sessions on the same server. The protocol spoken
down that socket/pipe is essentially the bare ssh-connection protocol,
using a trivial binary packet protocol with no encryption, and the
upstream has to do some fiddly transformations that I've been
referring to as 'channel-number NAT' to avoid resource clashes between
the sessions it's managing.

This is quite different from OpenSSH's approach of using the Unix-
domain socket as a means of passing file descriptors around; the main
reason for that is that fd-passing is Unix-specific but this system
has to work on Windows too. However, there are additional advantages,
such as making it easy for each downstream PuTTY to run its own
independent set of port and X11 forwardings (though the method for
making the latter work is quite painful).

Sharing is off by default, but configuration is intended to be very
easy in the normal case - just tick one box in the SSH config panel
and everything else happens automatically.

git-svn-id: http://svn.tartarus.org/sgt/putty@10083 cda61777-01e9-0310-a592-d414129be87e

30 files changed:
Recipe
config.c
doc/config.but
logging.c
network.h
noshare.c [new file with mode: 0644]
proxy.c
pscp.c
psftp.c
putty.h
settings.c
ssh.c
ssh.h
sshdes.c
sshshare.c [new file with mode: 0644]
unix/uxnet.c
unix/uxplink.c
unix/uxputty.c
unix/uxshare.c [new file with mode: 0644]
windows/window.c
windows/winhelp.h
windows/winnet.c
windows/winnpc.c
windows/winnps.c
windows/winpgnt.c
windows/winplink.c
windows/winsecur.c
windows/winsecur.h
windows/winshare.c [new file with mode: 0644]
x11fwd.c

diff --git a/Recipe b/Recipe
index 4ef4048ec3d02d75ca61f4ad20737004c115af72..22c6b3a123ac5db133d43f5d5189f5acf1796836 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -299,9 +299,10 @@ NONSSH   = telnet raw rlogin ldisc pinger
 SSH      = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf
          + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd
          + sshaes sshsh256 sshsh512 sshbn wildcard pinger ssharcf
-         + sshgssc pgssapi
-WINSSH   = SSH winnoise winsecur winpgntc wingss winhsock errsock
-UXSSH    = SSH uxnoise uxagentc uxgss
+         + sshgssc pgssapi sshshare
+WINSSH   = SSH winnoise winsecur winpgntc wingss winshare winnps winnpc
+         + winhsock errsock
+UXSSH    = SSH uxnoise uxagentc uxgss uxshare
 
 # SFTP implementation (pscp, psftp).
 SFTP     = sftp int64 logging
index 147f0c4e6efaca69a6631d6a953ea09e50881df7..5d0193440fd31aadc58bac1d9832c2b7a88085e1 100644 (file)
--- a/config.c
+++ b/config.c
@@ -2099,6 +2099,26 @@ void setup_config_box(struct controlbox *b, int midsession,
                          I(CONF_compression));
        }
 
+       if (!midsession || protcfginfo != 1) {
+           s = ctrl_getset(b, "Connection/SSH", "sharing", "Sharing an SSH connection between PuTTY tools");
+
+           ctrl_checkbox(s, "Share SSH connections if possible", 's',
+                         HELPCTX(ssh_share),
+                         conf_checkbox_handler,
+                         I(CONF_ssh_connection_sharing));
+
+            ctrl_text(s, "Permitted roles in a shared connection:",
+                      HELPCTX(ssh_share));
+           ctrl_checkbox(s, "Upstream (connecting to the real server)", 'u',
+                         HELPCTX(ssh_share),
+                         conf_checkbox_handler,
+                         I(CONF_ssh_connection_sharing_upstream));
+           ctrl_checkbox(s, "Downstream (connecting to the upstream PuTTY)", 'd',
+                         HELPCTX(ssh_share),
+                         conf_checkbox_handler,
+                         I(CONF_ssh_connection_sharing_downstream));
+       }
+
        if (!midsession) {
            s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");
 
index 49c0ff6eb8a15ff1c4933283cc0bdf2fb57a53d7..7a7c451a0535158a247923662047f4e89aea411d 100644 (file)
@@ -2274,6 +2274,64 @@ If you select \q{1 only} or \q{2 only} here, PuTTY will only connect
 if the server you connect to offers the SSH protocol version you
 have specified.
 
+\S{config-ssh-sharing} Sharing an SSH connection between PuTTY tools
+
+The controls in this box allow you to configure PuTTY to reuse an
+existing SSH connection, where possible.
+
+The SSH-2 protocol permits you to run multiple data channels over the
+same SSH connection, so that you can log in just once (and do the
+expensive encryption setup just once) and then have more than one
+terminal window open.
+
+Each instance of PuTTY can still run at most one terminal session, but
+using the controls in this box, you can configure PuTTY to check if
+another instance of itself has already connected to the target host,
+and if so, share that instance's SSH connection instead of starting a
+separate new one.
+
+To enable this feature, just tick the box \q{Share SSH connections if
+possible}. Then, whenever you start up a PuTTY session connecting to a
+particular host, it will try to reuse an existing SSH connection if
+one is available. For example, selecting \q{Duplicate Session} from
+the system menu will launch another session on the same host, and if
+sharing is enabled then it will reuse the existing SSH connection.
+
+When this mode is in use, the first PuTTY that connected to a given
+server becomes the \q{upstream}, which means that it is the one
+managing the real SSH connection. All subsequent PuTTYs which reuse
+the connection are referred to as \q{downstreams}: they do not connect
+to the real server at all, but instead connect to the upstream PuTTY
+via local inter-process communication methods.
+
+For this system to be activated, \e{both} the upstream and downstream
+instances of PuTTY must have the sharing option enabled.
+
+The upstream PuTTY can therefore not terminate until all its
+downstreams have closed. This is similar to the effect you get with
+port forwarding or X11 forwarding, in which a PuTTY whose terminal
+session has already finished will still remain open so as to keep
+serving forwarded connections.
+
+In case you need to configure this system in more detail, there are
+two additional checkboxes which allow you to specify whether a
+particular PuTTY can act as an upstream or a downstream or both.
+(These boxes only take effect if the main \q{Share SSH connections if
+possible} box is also ticked.) By default both of these boxes are
+ticked, so that multiple PuTTYs started from the same configuration
+will designate one of themselves as the upstream and share a single
+connection; but if for some reason you need a particular PuTTY
+configuration \e{not} to be an upstream (e.g. because you definitely
+need it to close promptly) or not to be a downstream (e.g. because it
+needs to do its own authentication using a special private key) then
+you can untick one or the other of these boxes.
+
+I have referred to \q{PuTTY} throughout the above discussion, but all
+the other PuTTY tools which make SSH connections can use this
+mechanism too. For example, if PSCP or PSFTP loads a configuration
+with sharing enabled, then it can act as a downstream and use an
+existing SSH connection set up by an instance of GUI PuTTY. The one
+special case is that PSCP and PSFTP will \e{never} act as upstreams.
 
 \H{config-ssh-kex} The Kex panel
 
index fc89db73a733128bfe59d0777c34df9090f00cff..868ff9874f8a83a6ea573c6b75131ee6d510e7c5 100644 (file)
--- a/logging.c
+++ b/logging.c
@@ -235,7 +235,8 @@ void log_eventlog(void *handle, const char *event)
 void log_packet(void *handle, int direction, int type,
                char *texttype, const void *data, int len,
                int n_blanks, const struct logblank_t *blanks,
-               const unsigned long *seq)
+               const unsigned long *seq,
+                unsigned downstream_id, const char *additional_log_text)
 {
     struct LogContext *ctx = (struct LogContext *)handle;
     char dumpdata[80], smalldata[5];
@@ -248,15 +249,21 @@ void log_packet(void *handle, int direction, int type,
 
     /* Packet header. */
     if (texttype) {
-       if (seq) {
-           logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n",
-                     direction == PKT_INCOMING ? "Incoming" : "Outgoing",
-                     *seq, type, type, texttype);
-       } else {
-           logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n",
-                     direction == PKT_INCOMING ? "Incoming" : "Outgoing",
-                     type, type, texttype);
-       }
+        logprintf(ctx, "%s packet ",
+                  direction == PKT_INCOMING ? "Incoming" : "Outgoing");
+
+       if (seq)
+           logprintf(ctx, "#0x%lx, ", *seq);
+
+        logprintf(ctx, "type %d / 0x%02x (%s)", type, type, texttype);
+
+        if (downstream_id) {
+           logprintf(ctx, " on behalf of downstream #%u", downstream_id);
+            if (additional_log_text)
+                logprintf(ctx, " (%s)", additional_log_text);
+        }
+
+        logprintf(ctx, "\r\n");
     } else {
         /*
          * Raw data is logged with a timestamp, so that it's possible
index e39962847613456441a4e802f2433b05072e368b..675d24c7c5575f3e20e52b7822321f5e157af683 100644 (file)
--- a/network.h
+++ b/network.h
@@ -100,6 +100,8 @@ Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only,
                    Conf *conf, int addressfamily);
 SockAddr name_lookup(char *host, int port, char **canonicalname,
                     Conf *conf, int addressfamily);
+int proxy_for_destination (SockAddr addr, const char *hostname, int port,
+                           Conf *conf);
 
 /* platform-dependent callback from new_connection() */
 /* (same caveat about addr as new_connection()) */
@@ -116,6 +118,7 @@ void sk_cleanup(void);                     /* called just before program exit */
 SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family);
 SockAddr sk_nonamelookup(const char *host);
 void sk_getaddr(SockAddr addr, char *buf, int buflen);
+int sk_addr_needs_port(SockAddr addr);
 int sk_hostname_is_local(const char *name);
 int sk_address_is_local(SockAddr addr);
 int sk_address_is_special_local(SockAddr addr);
diff --git a/noshare.c b/noshare.c
new file mode 100644 (file)
index 0000000..7412f84
--- /dev/null
+++ b/noshare.c
@@ -0,0 +1,24 @@
+/*
+ * Stub implementation of SSH connection-sharing IPC, for any
+ * platform which can't support it at all.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "ssh.h"
+#include "network.h"
+
+int platform_ssh_share(const char *name, Conf *conf,
+                       Plug downplug, Plug upplug, Socket *sock,
+                       char **logtext)
+{
+    return SHARE_NONE;
+}
+
+void platform_ssh_share_cleanup(const char *name)
+{
+}
diff --git a/proxy.c b/proxy.c
index c22f30f4c8b95d49164e529da8d761fedf569607..e2201c643f80579e4b5dd3aa52310442ed53f469 100644 (file)
--- a/proxy.c
+++ b/proxy.c
@@ -267,8 +267,8 @@ static int plug_proxy_accepting(Plug p,
  * This function can accept a NULL pointer as `addr', in which case
  * it will only check the host name.
  */
-static int proxy_for_destination (SockAddr addr, const char *hostname,
-                                  int port, Conf *conf)
+int proxy_for_destination (SockAddr addr, const char *hostname,
+                           int port, Conf *conf)
 {
     int s = 0, e = 0;
     char hostip[64];
diff --git a/pscp.c b/pscp.c
index 32099323419ac35a7d46ecfed695f47059a0611b..484c348e5e3ee3125eace28c5cb1d2f9b2ff6e6f 100644 (file)
--- a/pscp.c
+++ b/pscp.c
@@ -2307,6 +2307,9 @@ void cmdline_error(char *p, ...)
     exit(1);
 }
 
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = FALSE;
+
 /*
  * Main program. (Called `psftp_main' because it gets called from
  * *sftp.c; bit silly, I know, but it had to be called _something_.)
diff --git a/psftp.c b/psftp.c
index 8ed2343aa14df3b8c9086fea2f74cca33434abc1..c6ed98e97edca5e06853aa4ba9be18e3a92dfa79 100644 (file)
--- a/psftp.c
+++ b/psftp.c
@@ -2885,6 +2885,9 @@ void cmdline_error(char *p, ...)
     exit(1);
 }
 
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = FALSE;
+
 /*
  * Main program. Parse arguments etc.
  */
diff --git a/putty.h b/putty.h
index 89140f80afa50b8c7dc8797abd3a45ad36f1cdee..f97c723b02d71371e891a3812c248b3a2e70ba5c 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -844,6 +844,9 @@ void cleanup_exit(int);
      * large window in SSH-2.                                         \
      */ \
     X(INT, NONE, ssh_simple) \
+    X(INT, NONE, ssh_connection_sharing) \
+    X(INT, NONE, ssh_connection_sharing_upstream) \
+    X(INT, NONE, ssh_connection_sharing_downstream) \
     /* Options for pterm. Should split out into platform-dependent part. */ \
     X(INT, NONE, stamp_utmp) \
     X(INT, NONE, login_shell) \
@@ -1016,7 +1019,8 @@ struct logblank_t {
 void log_packet(void *logctx, int direction, int type,
                char *texttype, const void *data, int len,
                int n_blanks, const struct logblank_t *blanks,
-               const unsigned long *sequence);
+               const unsigned long *sequence,
+                unsigned downstream_id, const char *additional_log_text);
 
 /*
  * Exports from testback.c
index 2aae3f1d61618196e315eaf324e166b3e50939f2..a82a388bf099d64a64332f2eb50673f5b5b0e2cf 100644 (file)
@@ -641,6 +641,9 @@ void save_open_settings(void *sesskey, Conf *conf)
     write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity));
     write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow));
     write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass));
+    write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing));
+    write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream));
+    write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream));
 }
 
 void load_settings(char *section, Conf *conf)
@@ -983,6 +986,9 @@ void load_open_settings(void *sesskey, Conf *conf)
     gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity);
     gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow);
     gpps(sesskey, "WindowClass", "", conf, CONF_winclass);
+    gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing);
+    gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream);
+    gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream);
 }
 
 void do_defaults(char *session, Conf *conf)
diff --git a/ssh.c b/ssh.c
index ee7fb44e33f36cf4c23de1dcf9beaae8ab9cf48e..25a2e24b91c6e82832d281dd3088b5b7fb6acb8e 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -329,7 +329,6 @@ enum {
 #define crWaitUntil(c) do { crReturn(0); } while (!(c))
 #define crWaitUntilV(c)        do { crReturnV; } while (!(c))
 
-typedef struct ssh_tag *Ssh;
 struct Packet;
 
 static struct Packet *ssh1_pkt_init(int pkt_type);
@@ -439,6 +438,14 @@ enum {                                    /* channel types */
     CHAN_AGENT,
     CHAN_SOCKDATA,
     CHAN_SOCKDATA_DORMANT,            /* one the remote hasn't confirmed */
+    /*
+     * CHAN_SHARING indicates a channel which is tracked here on
+     * behalf of a connection-sharing downstream. We do almost nothing
+     * with these channels ourselves: all messages relating to them
+     * get thrown straight to sshshare.c and passed on almost
+     * unmodified to downstream.
+     */
+    CHAN_SHARING,
     /*
      * CHAN_ZOMBIE is used to indicate a channel for which we've
      * already destroyed the local data source: for instance, if a
@@ -545,10 +552,14 @@ struct ssh_channel {
        } a;
        struct ssh_x11_channel {
            struct X11Connection *xconn;
+            int initial;
        } x11;
        struct ssh_pfd_channel {
             struct PortForwarding *pf;
        } pfd;
+       struct ssh_sharing_channel {
+           void *ctx;
+       } sharing;
     } u;
 };
 
@@ -585,6 +596,7 @@ struct ssh_rportfwd {
     unsigned sport, dport;
     char *shost, *dhost;
     char *sportdesc;
+    void *share_ctx;
     struct ssh_portfwd *pfrec;
 };
 
@@ -651,14 +663,25 @@ struct Packet {
      * pkt->savedpos stores the offset (again relative to pkt->data)
      * of the start of the string data field.
      */
+
+    /* Extra metadata used in SSH packet logging mode, allowing us to
+     * log in the packet header line that the packet came from a
+     * connection-sharing downstream and what if anything unusual was
+     * done to it. The additional_log_text field is expected to be a
+     * static string - it will not be freed. */
+    unsigned downstream_id;
+    const char *additional_log_text;
 };
 
 static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
                          struct Packet *pktin);
 static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
                          struct Packet *pktin);
+static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
+                                          struct Packet *pktin);
 static void ssh1_protocol_setup(Ssh ssh);
 static void ssh2_protocol_setup(Ssh ssh);
+static void ssh2_bare_connection_protocol_setup(Ssh ssh);
 static void ssh_size(void *handle, int width, int height);
 static void ssh_special(void *handle, Telnet_Special);
 static int ssh2_try_send(struct ssh_channel *c);
@@ -692,6 +715,14 @@ struct rdpkt2_state_tag {
     struct Packet *pktin;
 };
 
+struct rdpkt2_bare_state_tag {
+    char length[4];
+    long packetlen;
+    int i;
+    unsigned long incoming_sequence;
+    struct Packet *pktin;
+};
+
 struct queued_handler;
 struct queued_handler {
     int msg1, msg2;
@@ -735,6 +766,10 @@ struct ssh_tag {
     int v2_session_id_len;
     void *kex_ctx;
 
+    int bare_connection;
+    int attempting_connshare;
+    void *connshare;
+
     char *savedhost;
     int savedport;
     int send_ok;
@@ -798,6 +833,7 @@ struct ssh_tag {
 
     int ssh1_rdpkt_crstate;
     int ssh2_rdpkt_crstate;
+    int ssh2_bare_rdpkt_crstate;
     int ssh_gotdata_crstate;
     int do_ssh1_connection_crstate;
 
@@ -805,9 +841,11 @@ struct ssh_tag {
     void *do_ssh1_login_state;
     void *do_ssh2_transport_state;
     void *do_ssh2_authconn_state;
+    void *do_ssh_connection_init_state;
 
     struct rdpkt1_state_tag rdpkt1_state;
     struct rdpkt2_state_tag rdpkt2_state;
+    struct rdpkt2_bare_state_tag rdpkt2_bare_state;
 
     /* SSH-1 and SSH-2 use this for different things, but both use it */
     int protocol_initial_phase_done;
@@ -815,6 +853,7 @@ struct ssh_tag {
     void (*protocol) (Ssh ssh, void *vin, int inlen,
                      struct Packet *pkt);
     struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
+    int (*do_ssh_init)(Ssh ssh, unsigned char c);
 
     /*
      * We maintain our own copy of a Conf structure here. That way,
@@ -1151,7 +1190,8 @@ static void ssh1_log_incoming_packet(Ssh ssh, struct Packet *pkt)
     }
     log_packet(ssh->logctx, PKT_INCOMING, pkt->type,
                ssh1_pkt_type(pkt->type),
-               pkt->body, pkt->length, nblanks, blanks, NULL);
+               pkt->body, pkt->length, nblanks, blanks, NULL,
+               0, NULL);
 }
 
 static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
@@ -1224,7 +1264,7 @@ static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
     log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12],
                ssh1_pkt_type(pkt->data[12]),
                pkt->body, pkt->length,
-               nblanks, blanks, NULL);
+               nblanks, blanks, NULL, 0, NULL);
 
     /*
      * Undo the above adjustment of pkt->length, to put the packet
@@ -1373,7 +1413,8 @@ static void ssh2_log_incoming_packet(Ssh ssh, struct Packet *pkt)
 
     log_packet(ssh->logctx, PKT_INCOMING, pkt->type,
                ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->type),
-               pkt->body, pkt->length, nblanks, blanks, &pkt->sequence);
+               pkt->body, pkt->length, nblanks, blanks, &pkt->sequence,
+               0, NULL);
 }
 
 static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
@@ -1484,7 +1525,8 @@ static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
     log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5],
                ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]),
                pkt->body, pkt->length, nblanks, blanks,
-               &ssh->v2_outgoing_sequence);
+               &ssh->v2_outgoing_sequence,
+               pkt->downstream_id, pkt->additional_log_text);
 
     /*
      * Undo the above adjustment of pkt->length, to put the packet
@@ -1708,6 +1750,65 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
     crFinish(st->pktin);
 }
 
+static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, unsigned char **data,
+                                                 int *datalen)
+{
+    struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state;
+
+    crBegin(ssh->ssh2_bare_rdpkt_crstate);
+
+    /*
+     * Read the packet length field.
+     */
+    for (st->i = 0; st->i < 4; st->i++) {
+        while ((*datalen) == 0)
+            crReturn(NULL);
+        st->length[st->i] = *(*data)++;
+        (*datalen)--;
+    }
+
+    st->packetlen = toint(GET_32BIT_MSB_FIRST(st->length));
+    if (st->packetlen <= 0 || st->packetlen >= OUR_V2_PACKETLIMIT) {
+        bombout(("Invalid packet length received"));
+        crStop(NULL);
+    }
+
+    st->pktin = ssh_new_packet();
+    st->pktin->data = snewn(st->packetlen, unsigned char);
+
+    st->pktin->encrypted_len = st->packetlen;
+
+    st->pktin->sequence = st->incoming_sequence++;
+
+    /*
+     * Read the remainder of the packet.
+     */
+    for (st->i = 0; st->i < st->packetlen; st->i++) {
+        while ((*datalen) == 0)
+            crReturn(NULL);
+        st->pktin->data[st->i] = *(*data)++;
+        (*datalen)--;
+    }
+
+    /*
+     * pktin->body and pktin->length should identify the semantic
+     * content of the packet, excluding the initial type byte.
+     */
+    st->pktin->type = st->pktin->data[0];
+    st->pktin->body = st->pktin->data + 1;
+    st->pktin->length = st->packetlen - 1;
+
+    /*
+     * Log incoming packet, possibly omitting sensitive fields.
+     */
+    if (ssh->logctx)
+        ssh2_log_incoming_packet(ssh, st->pktin);
+
+    st->pktin->savedpos = 0;
+
+    crFinish(st->pktin);
+}
+
 static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p)
 {
     int pad, biglen, i, pktoffs;
@@ -1763,7 +1864,7 @@ static int s_write(Ssh ssh, void *data, int len)
 {
     if (ssh->logctx)
        log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len,
-                  0, NULL, NULL);
+                  0, NULL, NULL, 0, NULL);
     if (!ssh->s)
         return 0;
     return sk_write(ssh->s, (char *)data, len);
@@ -1995,6 +2096,8 @@ static struct Packet *ssh1_pkt_init(int pkt_type)
     ssh_pkt_addbyte(pkt, pkt_type);
     pkt->body = pkt->data + pkt->length;
     pkt->type = pkt_type;
+    pkt->downstream_id = 0;
+    pkt->additional_log_text = NULL;
     return pkt;
 }
 
@@ -2016,6 +2119,8 @@ static struct Packet *ssh2_pkt_init(int pkt_type)
     pkt->type = pkt_type;
     ssh_pkt_addbyte(pkt, (unsigned char) pkt_type);
     pkt->body = pkt->data + pkt->length; /* after packet type */
+    pkt->downstream_id = 0;
+    pkt->additional_log_text = NULL;
     return pkt;
 }
 
@@ -2031,6 +2136,17 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     if (ssh->logctx)
         ssh2_log_outgoing_packet(ssh, pkt);
 
+    if (ssh->bare_connection) {
+        /*
+         * Trivial packet construction for the bare connection
+         * protocol.
+         */
+        PUT_32BIT(pkt->data + 1, pkt->length - 5);
+        pkt->body = pkt->data + 1;
+        ssh->v2_outgoing_sequence++;   /* only for diagnostics, really */
+        return pkt->length - 1;
+    }
+
     /*
      * Compress packet payload.
      */
@@ -2080,6 +2196,7 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     pkt->encrypted_len = pkt->length + padding;
 
     /* Ready-to-send packet starts at pkt->data. We return length. */
+    pkt->body = pkt->data;
     return pkt->length + padding + maclen;
 }
 
@@ -2137,12 +2254,13 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
        return;
     }
     len = ssh2_pkt_construct(ssh, pkt);
-    backlog = s_write(ssh, pkt->data, len);
+    backlog = s_write(ssh, pkt->body, len);
     if (backlog > SSH_MAX_BACKLOG)
        ssh_throttle_all(ssh, 1, backlog);
 
     ssh->outgoing_data_size += pkt->encrypted_len;
     if (!ssh->kex_in_progress &&
+        !ssh->bare_connection &&
        ssh->max_data_size != 0 &&
        ssh->outgoing_data_size > ssh->max_data_size)
        do_ssh2_transport(ssh, "too much data sent", -1, NULL);
@@ -2174,7 +2292,7 @@ static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore)
                                          ssh->deferred_size,
                                          unsigned char);
     }
-    memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->data, len);
+    memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->body, len);
     ssh->deferred_len += len;
     ssh->deferred_data_size += pkt->encrypted_len;
     ssh_free_packet(pkt);
@@ -2244,6 +2362,7 @@ static void ssh_pkt_defersend(Ssh ssh)
 
     ssh->outgoing_data_size += ssh->deferred_data_size;
     if (!ssh->kex_in_progress &&
+        !ssh->bare_connection &&
        ssh->max_data_size != 0 &&
        ssh->outgoing_data_size > ssh->max_data_size)
        do_ssh2_transport(ssh, "too much data sent", -1, NULL);
@@ -2696,11 +2815,7 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
  */
 static void ssh_fix_verstring(char *str)
 {
-    /* Eat "SSH-<protoversion>-". */
-    assert(*str == 'S'); str++;
-    assert(*str == 'S'); str++;
-    assert(*str == 'H'); str++;
-    assert(*str == '-'); str++;
+    /* Eat "<protoversion>-". */
     while (*str && *str != '-') str++;
     assert(*str == '-'); str++;
 
@@ -2716,7 +2831,7 @@ static void ssh_fix_verstring(char *str)
 /*
  * Send an appropriate SSH version string.
  */
-static void ssh_send_verstring(Ssh ssh, char *svers)
+static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers)
 {
     char *verstring;
 
@@ -2724,18 +2839,19 @@ static void ssh_send_verstring(Ssh ssh, char *svers)
        /*
         * Construct a v2 version string.
         */
-       verstring = dupprintf("SSH-2.0-%s\015\012", sshver);
+       verstring = dupprintf("%s2.0-%s\015\012", protoname, sshver);
     } else {
        /*
         * Construct a v1 version string.
         */
+        assert(!strcmp(protoname, "SSH-")); /* no v1 bare connection protocol */
        verstring = dupprintf("SSH-%s-%s\012",
                              (ssh_versioncmp(svers, "1.5") <= 0 ?
                               svers : "1.5"),
                              sshver);
     }
 
-    ssh_fix_verstring(verstring);
+    ssh_fix_verstring(verstring + strlen(protoname));
 
     if (ssh->version == 2) {
        size_t len;
@@ -2756,6 +2872,8 @@ static void ssh_send_verstring(Ssh ssh, char *svers)
 
 static int do_ssh_init(Ssh ssh, unsigned char c)
 {
+    static const char protoname[] = "SSH-";
+
     struct do_ssh_init_state {
        int crLine;
        int vslen;
@@ -2769,15 +2887,13 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
     
     crBeginState;
 
-    /* Search for a line beginning with the string "SSH-" in the input. */
+    /* Search for a line beginning with the protocol name prefix in
+     * the input. */
     for (;;) {
-       if (c != 'S') goto no;
-       crReturn(1);
-       if (c != 'S') goto no;
-       crReturn(1);
-       if (c != 'H') goto no;
-       crReturn(1);
-       if (c != '-') goto no;
+        for (s->i = 0; protoname[s->i]; s->i++) {
+            if ((char)c != protoname[s->i]) goto no;
+            crReturn(1);
+        }
        break;
       no:
        while (c != '\012')
@@ -2785,13 +2901,12 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
        crReturn(1);
     }
 
-    s->vstrsize = 16;
+    s->vstrsize = sizeof(protoname) + 16;
     s->vstring = snewn(s->vstrsize, char);
-    strcpy(s->vstring, "SSH-");
-    s->vslen = 4;
+    strcpy(s->vstring, protoname);
+    s->vslen = strlen(protoname);
     s->i = 0;
     while (1) {
-       crReturn(1);                   /* get another char */
        if (s->vslen >= s->vstrsize - 1) {
            s->vstrsize += 16;
            s->vstring = sresize(s->vstring, s->vstrsize, char);
@@ -2805,6 +2920,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
                s->version[s->i++] = c;
        } else if (c == '\012')
            break;
+       crReturn(1);                   /* get another char */
     }
 
     ssh->agentfwd_enabled = FALSE;
@@ -2842,7 +2958,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
 
     /* Send the version string, if we haven't already */
     if (conf_get_int(ssh->conf, CONF_sshprot) != 3)
-       ssh_send_verstring(ssh, s->version);
+       ssh_send_verstring(ssh, protoname, s->version);
 
     if (ssh->version == 2) {
        size_t len;
@@ -2880,6 +2996,117 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
     crFinish(0);
 }
 
+static int do_ssh_connection_init(Ssh ssh, unsigned char c)
+{
+    /*
+     * Ordinary SSH begins with the banner "SSH-x.y-...". This is just
+     * the ssh-connection part, extracted and given a trivial binary
+     * packet protocol, so we replace 'SSH-' at the start with a new
+     * name. In proper SSH style (though of course this part of the
+     * proper SSH protocol _isn't_ subject to this kind of
+     * DNS-domain-based extension), we define the new name in our
+     * extension space.
+     */
+    static const char protoname[] =
+        "SSHCONNECTION@putty.projects.tartarus.org-";
+
+    struct do_ssh_connection_init_state {
+       int crLine;
+       int vslen;
+       char version[10];
+       char *vstring;
+       int vstrsize;
+       int i;
+    };
+    crState(do_ssh_connection_init_state);
+    
+    crBeginState;
+
+    /* Search for a line beginning with the protocol name prefix in
+     * the input. */
+    for (;;) {
+        for (s->i = 0; protoname[s->i]; s->i++) {
+            if ((char)c != protoname[s->i]) goto no;
+            crReturn(1);
+        }
+       break;
+      no:
+       while (c != '\012')
+           crReturn(1);
+       crReturn(1);
+    }
+
+    s->vstrsize = sizeof(protoname) + 16;
+    s->vstring = snewn(s->vstrsize, char);
+    strcpy(s->vstring, protoname);
+    s->vslen = strlen(protoname);
+    s->i = 0;
+    while (1) {
+       if (s->vslen >= s->vstrsize - 1) {
+           s->vstrsize += 16;
+           s->vstring = sresize(s->vstring, s->vstrsize, char);
+       }
+       s->vstring[s->vslen++] = c;
+       if (s->i >= 0) {
+           if (c == '-') {
+               s->version[s->i] = '\0';
+               s->i = -1;
+           } else if (s->i < sizeof(s->version) - 1)
+               s->version[s->i++] = c;
+       } else if (c == '\012')
+           break;
+       crReturn(1);                   /* get another char */
+    }
+
+    ssh->agentfwd_enabled = FALSE;
+    ssh->rdpkt2_bare_state.incoming_sequence = 0;
+
+    s->vstring[s->vslen] = 0;
+    s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */
+    logeventf(ssh, "Server version: %s", s->vstring);
+    ssh_detect_bugs(ssh, s->vstring);
+
+    /*
+     * Decide which SSH protocol version to support. This is easy in
+     * bare ssh-connection mode: only 2.0 is legal.
+     */
+    if (ssh_versioncmp(s->version, "2.0") < 0) {
+       bombout(("Server announces compatibility with SSH-1 in bare ssh-connection protocol"));
+        crStop(0);
+    }
+    if (conf_get_int(ssh->conf, CONF_sshprot) == 0) {
+       bombout(("Bare ssh-connection protocol cannot be run in SSH-1-only mode"));
+       crStop(0);
+    }
+
+    ssh->version = 2;
+
+    logeventf(ssh, "Using bare ssh-connection protocol");
+
+    /* Send the version string, if we haven't already */
+    ssh_send_verstring(ssh, protoname, s->version);
+
+    /*
+     * Initialise bare connection protocol.
+     */
+    ssh->protocol = ssh2_bare_connection_protocol;
+    ssh2_bare_connection_protocol_setup(ssh);
+    ssh->s_rdpkt = ssh2_bare_connection_rdpkt;
+
+    update_specials_menu(ssh->frontend);
+    ssh->state = SSH_STATE_BEFORE_SIZE;
+    ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh);
+
+    /*
+     * Get authconn (really just conn) under way.
+     */
+    do_ssh2_authconn(ssh, NULL, 0, NULL);
+
+    sfree(s->vstring);
+
+    crFinish(0);
+}
+
 static void ssh_process_incoming_data(Ssh ssh,
                                      unsigned char **data, int *datalen)
 {
@@ -2931,7 +3158,7 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
     /* Log raw data, if we're in that mode. */
     if (ssh->logctx)
        log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen,
-                  0, NULL, NULL);
+                  0, NULL, NULL, 0, NULL);
 
     crBegin(ssh->ssh_gotdata_crstate);
 
@@ -2945,7 +3172,7 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
        int ret;                       /* need not be kept across crReturn */
        if (datalen == 0)
            crReturnV;                 /* more data please */
-       ret = do_ssh_init(ssh, *data);
+       ret = ssh->do_ssh_init(ssh, *data);
        data++;
        datalen--;
        if (ret == 0)
@@ -3042,21 +3269,71 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
     return ret;
 }
 
-static void ssh_log(Plug plug, int type, SockAddr addr, int port,
-                   const char *error_msg, int error_code)
+static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port,
+                           const char *error_msg, int error_code)
 {
     Ssh ssh = (Ssh) plug;
     char addrbuf[256], *msg;
 
-    sk_getaddr(addr, addrbuf, lenof(addrbuf));
+    if (ssh->attempting_connshare) {
+        /*
+         * While we're attempting connection sharing, don't loudly log
+         * everything that happens. Real TCP connections need to be
+         * logged when we _start_ trying to connect, because it might
+         * be ages before they respond if something goes wrong; but
+         * connection sharing is local and quick to respond, and it's
+         * sufficient to simply wait and see whether it worked
+         * afterwards.
+         */
+    } else {
+        sk_getaddr(addr, addrbuf, lenof(addrbuf));
 
-    if (type == 0)
-       msg = dupprintf("Connecting to %s port %d", addrbuf, port);
-    else
-       msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
+        if (type == 0) {
+            if (sk_addr_needs_port(addr)) {
+                msg = dupprintf("Connecting to %s port %d", addrbuf, port);
+            } else {
+                msg = dupprintf("Connecting to %s", addrbuf, port);
+            }
+        } else {
+            msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
+        }
+
+        logevent(msg);
+        sfree(msg);
+    }
+}
 
-    logevent(msg);
-    sfree(msg);
+void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
+                       const char *ds_err, const char *us_err)
+{
+    if (event == SHARE_NONE) {
+        /* In this case, 'logtext' is an error message indicating a
+         * reason why connection sharing couldn't be set up _at all_.
+         * Failing that, ds_err and us_err indicate why we couldn't be
+         * a downstream and an upstream respectively. */
+        if (logtext) {
+            logeventf(ssh, "Could not set up connection sharing: %s", logtext);
+        } else {
+            if (ds_err)
+                logeventf(ssh, "Could not set up connection sharing"
+                          " as downstream: %s", ds_err);
+            if (us_err)
+                logeventf(ssh, "Could not set up connection sharing"
+                          " as upstream: %s", us_err);
+        }
+    } else if (event == SHARE_DOWNSTREAM) {
+        /* In this case, 'logtext' is a local endpoint address */
+        logeventf(ssh, "Using existing shared connection at %s", logtext);
+        /* Also we should mention this in the console window to avoid
+         * confusing users as to why this window doesn't behave the
+         * usual way. */
+        if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+            c_write_str(ssh,"Reusing a shared connection to this server.\r\n");
+        }
+    } else if (event == SHARE_UPSTREAM) {
+        /* In this case, 'logtext' is a local endpoint address too */
+        logeventf(ssh, "Sharing this connection at %s", logtext);
+    }
 }
 
 static int ssh_closing(Plug plug, const char *error_msg, int error_code,
@@ -3117,7 +3394,7 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
                                   char **realhost, int nodelay, int keepalive)
 {
     static const struct plug_function_table fn_table = {
-       ssh_log,
+       ssh_socket_log,
        ssh_closing,
        ssh_receive,
        ssh_sent,
@@ -3155,30 +3432,58 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
        ssh->savedport = port;
     }
 
-    /*
-     * Try to find host.
-     */
-    addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
-    logeventf(ssh, "Looking up host \"%s\"%s", host,
-             (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
-              (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
-    addr = name_lookup(host, port, realhost, ssh->conf, addressfamily);
-    if ((err = sk_addr_error(addr)) != NULL) {
-       sk_addr_free(addr);
-       return err;
-    }
-    ssh->fullhostname = dupstr(*realhost);   /* save in case of GSSAPI */
+    ssh->fn = &fn_table;               /* make 'ssh' usable as a Plug */
 
     /*
-     * Open socket.
+     * Try connection-sharing, in case that means we don't open a
+     * socket after all. ssh_connection_sharing_init will connect to a
+     * previously established upstream if it can, and failing that,
+     * establish a listening socket for _us_ to be the upstream. In
+     * the latter case it will return NULL just as if it had done
+     * nothing, because here we only need to care if we're a
+     * downstream and need to do our connection setup differently.
      */
-    ssh->fn = &fn_table;
-    ssh->s = new_connection(addr, *realhost, port,
-                           0, 1, nodelay, keepalive, (Plug) ssh, ssh->conf);
-    if ((err = sk_socket_error(ssh->s)) != NULL) {
-       ssh->s = NULL;
-       notify_remote_exit(ssh->frontend);
-       return err;
+    ssh->connshare = NULL;
+    ssh->attempting_connshare = TRUE;  /* affects socket logging behaviour */
+    ssh->s = ssh_connection_sharing_init(ssh->savedhost, ssh->savedport,
+                                         ssh->conf, ssh, &ssh->connshare);
+    ssh->attempting_connshare = FALSE;
+    if (ssh->s != NULL) {
+        /*
+         * We are a downstream.
+         */
+        ssh->bare_connection = TRUE;
+        ssh->do_ssh_init = do_ssh_connection_init;
+        ssh->fullhostname = NULL;
+        *realhost = dupstr(host);      /* best we can do */
+    } else {
+        /*
+         * We're not a downstream, so open a normal socket.
+         */
+        ssh->do_ssh_init = do_ssh_init;
+
+        /*
+         * Try to find host.
+         */
+        addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
+        logeventf(ssh, "Looking up host \"%s\"%s", host,
+                  (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+                   (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
+        addr = name_lookup(host, port, realhost, ssh->conf, addressfamily);
+        if ((err = sk_addr_error(addr)) != NULL) {
+            sk_addr_free(addr);
+            return err;
+        }
+        ssh->fullhostname = dupstr(*realhost);   /* save in case of GSSAPI */
+
+        ssh->s = new_connection(addr, *realhost, port,
+                                0, 1, nodelay, keepalive,
+                                (Plug) ssh, ssh->conf);
+        if ((err = sk_socket_error(ssh->s)) != NULL) {
+            ssh->s = NULL;
+            notify_remote_exit(ssh->frontend);
+            return err;
+        }
     }
 
     /*
@@ -3188,9 +3493,9 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
     sshprot = conf_get_int(ssh->conf, CONF_sshprot);
     if (sshprot == 0)
        ssh->version = 1;
-    if (sshprot == 3) {
+    if (sshprot == 3 && !ssh->bare_connection) {
        ssh->version = 2;
-       ssh_send_verstring(ssh, NULL);
+       ssh_send_verstring(ssh, "SSH-", NULL);
     }
 
     /*
@@ -4533,6 +4838,41 @@ static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
     }
 }
 
+int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport,
+                               void *share_ctx)
+{
+    struct ssh_rportfwd *pf = snew(struct ssh_rportfwd);
+    pf->dhost = NULL;
+    pf->dport = 0;
+    pf->share_ctx = share_ctx;
+    pf->shost = dupstr(shost);
+    pf->sport = sport;
+    pf->sportdesc = NULL;
+    if (!ssh->rportfwds) {
+        assert(ssh->version == 2);
+        ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
+    }
+    if (add234(ssh->rportfwds, pf) != pf) {
+        sfree(pf->shost);
+        sfree(pf);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+static void ssh_sharing_global_request_response(Ssh ssh, struct Packet *pktin,
+                                                void *ctx)
+{
+    share_got_pkt_from_server(ctx, pktin->type,
+                              pktin->body, pktin->length);
+}
+
+void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx)
+{
+    ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, SSH2_MSG_REQUEST_FAILURE,
+                      ssh_sharing_global_request_response, share_ctx);
+}
+
 static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
 {
     struct ssh_portfwd *epf;
@@ -4802,6 +5142,7 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
                }
 
                pf = snew(struct ssh_rportfwd);
+                pf->share_ctx = NULL;
                 pf->dhost = dupstr(epf->daddr);
                pf->dport = epf->dport;
                 if (epf->saddr) {
@@ -5210,6 +5551,10 @@ static void ssh1_send_ttymode(void *data, char *mode, char *val)
     ssh2_pkt_addbyte(pktout, arg);
 }
 
+int ssh_agent_forwarding_permitted(Ssh ssh)
+{
+    return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists();
+}
 
 static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
                               struct Packet *pktin)
@@ -5230,7 +5575,7 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
     ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data;
     ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status;
 
-    if (conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
+    if (ssh_agent_forwarding_permitted(ssh)) {
        logevent("Requesting agent forwarding");
        send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
        do {
@@ -5611,6 +5956,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     };
     crState(do_ssh2_transport_state);
 
+    assert(!ssh->bare_connection);
+
     crBeginState;
 
     s->cscipher_tobe = s->sccipher_tobe = NULL;
@@ -6688,6 +7035,19 @@ static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
     }
 }
 
+static int ssh_is_simple(Ssh ssh)
+{
+    /*
+     * We use the 'simple' variant of the SSH protocol if we're asked
+     * to, except not if we're also doing connection-sharing (either
+     * tunnelling our packets over an upstream or expecting to be
+     * tunnelled over ourselves), since then the assumption that we
+     * have only one channel to worry about is not true after all.
+     */
+    return (conf_get_int(ssh->conf, CONF_ssh_simple) &&
+            !ssh->bare_connection && !ssh->connshare);
+}
+
 /*
  * Set up most of a new ssh_channel for SSH-2.
  */
@@ -6699,7 +7059,7 @@ static void ssh2_channel_init(struct ssh_channel *c)
     c->pending_eof = FALSE;
     c->throttling_conn = FALSE;
     c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
-       conf_get_int(ssh->conf, CONF_ssh_simple) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+       ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
     c->v.v2.chanreq_head = NULL;
     c->v.v2.throttle_state = UNTHROTTLED;
     bufchain_init(&c->v.v2.outbuffer);
@@ -6787,6 +7147,14 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
     if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
        return;
 
+    /*
+     * Also, never widen the window for an X11 channel when we're
+     * still waiting to see its initial auth and may yet hand it off
+     * to a downstream.
+     */
+    if (c->type == CHAN_X11 && c->u.x11.initial)
+        return;
+
     /*
      * If the remote end has a habit of ignoring maxpkt, limit the
      * window so that it has no choice (assuming it doesn't ignore the
@@ -6850,7 +7218,8 @@ static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
 
     c = find234(ssh->channels, &localid, ssh_channelfind);
     if (!c ||
-       (c->halfopen && pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
+       (c->type != CHAN_SHARING && c->halfopen &&
+         pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
         pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
        char *buf = dupprintf("Received %s for %s channel %u",
                              ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
@@ -6893,6 +7262,11 @@ static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin)
     struct outstanding_channel_request *ocr;
 
     if (!c) return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     ocr = c->v.v2.chanreq_head;
     if (!ocr) {
        ssh2_msg_unexpected(ssh, pktin);
@@ -6915,6 +7289,11 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     if (!(c->closes & CLOSES_SENT_EOF)) {
        c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
        ssh2_try_send_and_unthrottle(ssh, c);
@@ -6929,6 +7308,11 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
        ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR)
        return;                        /* extended but not stderr */
@@ -7017,15 +7401,59 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
         * buffering anything at all and we're in "simple" mode,
         * throttle the whole channel.
         */
-       if ((bufsize > c->v.v2.locmaxwin ||
-            (conf_get_int(ssh->conf, CONF_ssh_simple) && bufsize > 0)) &&
-           !c->throttling_conn) {
+       if ((bufsize > c->v.v2.locmaxwin || (ssh_is_simple(ssh) && bufsize>0))
+            && !c->throttling_conn) {
            c->throttling_conn = 1;
            ssh_throttle_conn(ssh, +1);
        }
     }
 }
 
+static void ssh_check_termination(Ssh ssh)
+{
+    if (ssh->version == 2 &&
+        !conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
+        count234(ssh->channels) == 0 &&
+        !(ssh->connshare && share_ndownstreams(ssh->connshare) > 0)) {
+        /*
+         * We used to send SSH_MSG_DISCONNECT here, because I'd
+         * believed that _every_ conforming SSH-2 connection had to
+         * end with a disconnect being sent by at least one side;
+         * apparently I was wrong and it's perfectly OK to
+         * unceremoniously slam the connection shut when you're done,
+         * and indeed OpenSSH feels this is more polite than sending a
+         * DISCONNECT. So now we don't.
+         */
+        ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
+    }
+}
+
+void ssh_sharing_downstream_connected(Ssh ssh, unsigned id)
+{
+    logeventf(ssh, "Connection sharing downstream #%u connected", id);
+}
+
+void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id)
+{
+    logeventf(ssh, "Connection sharing downstream #%u disconnected", id);
+    ssh_check_termination(ssh);
+}
+
+void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...)
+{
+    va_list ap;
+    char *buf;
+
+    va_start(ap, logfmt);
+    buf = dupvprintf(logfmt, ap);
+    va_end(ap);
+    if (id)
+        logeventf(ssh, "Connection sharing downstream #%u: %s", id, buf);
+    else
+        logeventf(ssh, "Connection sharing: %s", buf);
+    sfree(buf);
+}
+
 static void ssh_channel_destroy(struct ssh_channel *c)
 {
     Ssh ssh = c->ssh;
@@ -7058,26 +7486,10 @@ static void ssh_channel_destroy(struct ssh_channel *c)
     sfree(c);
 
     /*
-     * See if that was the last channel left open.
-     * (This is only our termination condition if we're
-     * not running in -N mode.)
+     * If that was the last channel left open, we might need to
+     * terminate.
      */
-    if (ssh->version == 2 &&
-        !conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
-        count234(ssh->channels) == 0) {
-        /*
-         * We used to send SSH_MSG_DISCONNECT here,
-         * because I'd believed that _every_ conforming
-         * SSH-2 connection had to end with a disconnect
-         * being sent by at least one side; apparently
-         * I was wrong and it's perfectly OK to
-         * unceremoniously slam the connection shut
-         * when you're done, and indeed OpenSSH feels
-         * this is more polite than sending a
-         * DISCONNECT. So now we don't.
-         */
-        ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
-    }
+    ssh_check_termination(ssh);
 }
 
 static void ssh2_channel_check_close(struct ssh_channel *c)
@@ -7163,6 +7575,11 @@ static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     ssh2_channel_got_eof(c);
 }
 
@@ -7173,6 +7590,11 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
 
     /*
      * When we receive CLOSE on a channel, we assume it comes with an
@@ -7235,6 +7657,11 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
     c->remoteid = ssh_pkt_getuint32(pktin);
     c->halfopen = FALSE;
@@ -7290,6 +7717,11 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
 
     if (c->type == CHAN_SOCKDATA_DORMANT) {
@@ -7337,6 +7769,11 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     ssh_pkt_getstring(pktin, &type, &typelen);
     want_reply = ssh2_pkt_getbool(pktin);
 
@@ -7529,6 +7966,30 @@ static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin)
     }
 }
 
+struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype,
+                                                void *share_cs,
+                                                void *share_chan)
+{
+    struct X11FakeAuth *auth;
+
+    /*
+     * Make up a new set of fake X11 auth data, and add it to the tree
+     * of currently valid ones with an indication of the sharing
+     * context that it's relevant to.
+     */
+    auth = x11_invent_fake_auth(ssh->x11authtree, authtype);
+    auth->share_cs = share_cs;
+    auth->share_chan = share_chan;
+
+    return auth;
+}
+
+void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth)
+{
+    del234(ssh->x11authtree, auth);
+    x11_free_fake_auth(auth);
+}
+
 static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
 {
     char *type;
@@ -7539,6 +8000,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
     char *error = NULL;
     struct ssh_channel *c;
     unsigned remid, winsize, pktsize;
+    unsigned our_winsize_override = 0;
     struct Packet *pktout;
 
     ssh_pkt_getstring(pktin, &type, &typelen);
@@ -7561,12 +8023,24 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        logeventf(ssh, "Received X11 connect request from %s:%d",
                  addrstr, peerport);
 
-       if (!ssh->X11_fwd_enabled)
+       if (!ssh->X11_fwd_enabled && !ssh->connshare)
            error = "X11 forwarding is not enabled";
        else {
             c->u.x11.xconn = x11_init(ssh->x11authtree, c,
                                       addrstr, peerport);
            c->type = CHAN_X11;
+            c->u.x11.initial = TRUE;
+
+            /*
+             * If we are a connection-sharing upstream, then we should
+             * initially present a very small window, adequate to take
+             * the X11 initial authorisation packet but not much more.
+             * Downstream will then present us a larger window (by
+             * fiat of the connection-sharing protocol) and we can
+             * guarantee to send a positive-valued WINDOW_ADJUST.
+             */
+            if (ssh->connshare)
+                our_winsize_override = 128;
 
             logevent("Opened X11 forward channel");
        }
@@ -7590,11 +8064,23 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        if (realpf == NULL) {
            error = "Remote port is not recognised";
        } else {
-           char *err = pfd_connect(&c->u.pfd.pf,
-                                    realpf->dhost,
-                                    realpf->dport, c,
-                                    ssh->conf,
-                                    realpf->pfrec->addressfamily);
+            char *err;
+
+            if (realpf->share_ctx) {
+                /*
+                 * This port forwarding is on behalf of a
+                 * connection-sharing downstream, so abandon our own
+                 * channel-open procedure and just pass the message on
+                 * to sshshare.c.
+                 */
+                share_got_pkt_from_server(realpf->share_ctx, pktin->type,
+                                          pktin->body, pktin->length);
+                sfree(c);
+                return;
+            }
+
+            err = pfd_connect(&c->u.pfd.pf, realpf->dhost, realpf->dport,
+                              c, ssh->conf, realpf->pfrec->addressfamily);
            logeventf(ssh, "Attempting to forward remote port to "
                      "%s:%d", realpf->dhost, realpf->dport);
            if (err != NULL) {
@@ -7635,6 +8121,10 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        ssh2_channel_init(c);
        c->v.v2.remwindow = winsize;
        c->v.v2.remmaxpkt = pktsize;
+        if (our_winsize_override) {
+            c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
+                our_winsize_override;
+        }
        add234(ssh->channels, c);
        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
        ssh2_pkt_adduint32(pktout, c->remoteid);
@@ -7645,6 +8135,43 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
     }
 }
 
+void sshfwd_x11_sharing_handover(struct ssh_channel *c,
+                                 void *share_cs, void *share_chan,
+                                 const char *peer_addr, int peer_port,
+                                 int endian, int protomajor, int protominor,
+                                 const void *initial_data, int initial_len)
+{
+    /*
+     * This function is called when we've just discovered that an X
+     * forwarding channel on which we'd been handling the initial auth
+     * ourselves turns out to be destined for a connection-sharing
+     * downstream. So we turn the channel into a CHAN_SHARING, meaning
+     * that we completely stop tracking windows and buffering data and
+     * just pass more or less unmodified SSH messages back and forth.
+     */
+    c->type = CHAN_SHARING;
+    c->u.sharing.ctx = share_cs;
+    share_setup_x11_channel(share_cs, share_chan,
+                            c->localid, c->remoteid, c->v.v2.remwindow,
+                            c->v.v2.remmaxpkt, c->v.v2.locwindow,
+                            peer_addr, peer_port, endian,
+                            protomajor, protominor,
+                            initial_data, initial_len);
+}
+
+void sshfwd_x11_is_local(struct ssh_channel *c)
+{
+    /*
+     * This function is called when we've just discovered that an X
+     * forwarding channel is _not_ destined for a connection-sharing
+     * downstream but we're going to handle it ourselves. We stop
+     * presenting a cautiously small window and go into ordinary data
+     * exchange mode.
+     */
+    c->u.x11.initial = FALSE;
+    ssh2_set_window(c, ssh_is_simple(c->ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE);
+}
+
 /*
  * Buffer banner messages for later display at some convenient point,
  * if we're going to display them.
@@ -7964,35 +8491,40 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     
     s->done_service_req = FALSE;
     s->we_are_in = s->userauth_success = FALSE;
+    s->agent_response = NULL;
 #ifndef NO_GSSAPI
     s->tried_gssapi = FALSE;
 #endif
 
-    if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) {
-       /*
-        * Request userauth protocol, and await a response to it.
-        */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
-       ssh2_pkt_addstring(s->pktout, "ssh-userauth");
-       ssh2_pkt_send(ssh, s->pktout);
-       crWaitUntilV(pktin);
-       if (pktin->type == SSH2_MSG_SERVICE_ACCEPT)
-           s->done_service_req = TRUE;
-    }
-    if (!s->done_service_req) {
-       /*
-        * Request connection protocol directly, without authentication.
-        */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
-       ssh2_pkt_addstring(s->pktout, "ssh-connection");
-       ssh2_pkt_send(ssh, s->pktout);
-       crWaitUntilV(pktin);
-       if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) {
-           s->we_are_in = TRUE; /* no auth required */
-       } else {
-           bombout(("Server refused service request"));
-           crStopV;
-       }
+    if (!ssh->bare_connection) {
+        if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) {
+            /*
+             * Request userauth protocol, and await a response to it.
+             */
+            s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+            ssh2_pkt_addstring(s->pktout, "ssh-userauth");
+            ssh2_pkt_send(ssh, s->pktout);
+            crWaitUntilV(pktin);
+            if (pktin->type == SSH2_MSG_SERVICE_ACCEPT)
+                s->done_service_req = TRUE;
+        }
+        if (!s->done_service_req) {
+            /*
+             * Request connection protocol directly, without authentication.
+             */
+            s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+            ssh2_pkt_addstring(s->pktout, "ssh-connection");
+            ssh2_pkt_send(ssh, s->pktout);
+            crWaitUntilV(pktin);
+            if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) {
+                s->we_are_in = TRUE; /* no auth required */
+            } else {
+                bombout(("Server refused service request"));
+                crStopV;
+            }
+        }
+    } else {
+        s->we_are_in = TRUE;
     }
 
     /* Arrange to be able to deal with any BANNERs that come in.
@@ -9325,7 +9857,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     if (s->agent_response)
        sfree(s->agent_response);
 
-    if (s->userauth_success) {
+    if (s->userauth_success && !ssh->bare_connection) {
        /*
         * We've just received USERAUTH_SUCCESS, and we haven't sent any
         * packets since. Signal the transport layer to consider enacting
@@ -9339,10 +9871,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL);
     }
 
-    /*
-     * Now the connection protocol has started, one way or another.
-     */
-
     ssh->channels = newtree234(ssh_channelcmp);
 
     /*
@@ -9420,8 +9948,15 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response;
     ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response;
 
+    /*
+     * Now the connection protocol is properly up and running, with
+     * all those dispatch table entries, so it's safe to let
+     * downstreams start trying to open extra channels through us.
+     */
+    if (ssh->connshare)
+        share_activate(ssh->connshare, ssh->v_s);
 
-    if (ssh->mainchan && conf_get_int(ssh->conf, CONF_ssh_simple)) {
+    if (ssh->mainchan && ssh_is_simple(ssh)) {
        /*
         * This message indicates to the server that we promise
         * not to try to run any other channel in parallel with
@@ -9465,7 +10000,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
         }
 
        /* Potentially enable agent forwarding. */
-       if (conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists())
+       if (ssh_agent_forwarding_permitted(ssh))
            ssh2_setup_agent(ssh->mainchan, NULL, NULL);
 
        /* Now allocate a pty for the session. */
@@ -9721,6 +10256,48 @@ static void ssh2_protocol_setup(Ssh ssh)
     ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
 }
 
+static void ssh2_bare_connection_protocol_setup(Ssh ssh)
+{
+    int i;
+
+    /*
+     * Most messages cause SSH2_MSG_UNIMPLEMENTED.
+     */
+    for (i = 0; i < 256; i++)
+       ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
+
+    /*
+     * Initially, we set all ssh-connection messages to 'unexpected';
+     * do_ssh2_authconn will fill things in properly. We also handle a
+     * couple of messages from the transport protocol which aren't
+     * related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG,
+     * DISCONNECT).
+     */
+    ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected;
+
+    ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected;
+
+    /*
+     * These messages have a special handler from the start.
+     */
+    ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect;
+    ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore;
+    ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
+}
+
 static void ssh2_timer(void *ctx, unsigned long now)
 {
     Ssh ssh = (Ssh)ctx;
@@ -9728,7 +10305,8 @@ static void ssh2_timer(void *ctx, unsigned long now)
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    if (!ssh->kex_in_progress && conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 &&
+    if (!ssh->kex_in_progress && !ssh->bare_connection &&
+        conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 &&
        now == ssh->next_rekey) {
        do_ssh2_transport(ssh, "timeout", -1, NULL);
     }
@@ -9757,6 +10335,19 @@ static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
        do_ssh2_authconn(ssh, in, inlen, pktin);
 }
 
+static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
+                                          struct Packet *pktin)
+{
+    unsigned char *in = (unsigned char *)vin;
+    if (ssh->state == SSH_STATE_CLOSED)
+       return;
+
+    if (pktin)
+       ssh->packet_dispatch[pktin->type](ssh, pktin);
+    else
+        do_ssh2_authconn(ssh, in, inlen, pktin);
+}
+
 static void ssh_cache_conf_values(Ssh ssh)
 {
     ssh->logomitdata = conf_get_int(ssh->conf, CONF_logomitdata);
@@ -9819,9 +10410,11 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->v2_outgoing_sequence = 0;
     ssh->ssh1_rdpkt_crstate = 0;
     ssh->ssh2_rdpkt_crstate = 0;
+    ssh->ssh2_bare_rdpkt_crstate = 0;
     ssh->ssh_gotdata_crstate = 0;
     ssh->do_ssh1_connection_crstate = 0;
     ssh->do_ssh_init_state = NULL;
+    ssh->do_ssh_connection_init_state = NULL;
     ssh->do_ssh1_login_state = NULL;
     ssh->do_ssh2_transport_state = NULL;
     ssh->do_ssh2_authconn_state = NULL;
@@ -9840,6 +10433,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->username = NULL;
     ssh->sent_console_eof = FALSE;
     ssh->got_pty = FALSE;
+    ssh->bare_connection = FALSE;
+    ssh->attempting_connshare = FALSE;
 
     *backend_handle = ssh;
 
@@ -9962,6 +10557,9 @@ static void ssh_free(void *handle)
        ssh->channels = NULL;
     }
 
+    if (ssh->connshare)
+        sharestate_free(ssh->connshare);
+
     if (ssh->rportfwds) {
        while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
            free_rportfwd(pf);
@@ -10062,7 +10660,7 @@ static void ssh_reconfig(void *handle, Conf *conf)
     ssh->conf = conf_copy(conf);
     ssh_cache_conf_values(ssh);
 
-    if (rekeying) {
+    if (!ssh->bare_connection && rekeying) {
        if (!ssh->kex_in_progress) {
            do_ssh2_transport(ssh, rekeying, -1, NULL);
        } else if (rekey_mandatory) {
@@ -10217,7 +10815,7 @@ static const struct telnet_special *ssh_get_specials(void *handle)
     } else if (ssh->version == 2) {
        if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE))
            ADD_SPECIALS(ssh2_ignore_special);
-       if (!(ssh->remote_bugs & BUG_SSH2_REKEY))
+       if (!(ssh->remote_bugs & BUG_SSH2_REKEY) && !ssh->bare_connection)
            ADD_SPECIALS(ssh2_rekey_special);
        if (ssh->mainchan)
            ADD_SPECIALS(ssh2_session_specials);
@@ -10273,7 +10871,8 @@ static void ssh_special(void *handle, Telnet_Special code)
            }
        }
     } else if (code == TS_REKEY) {
-       if (!ssh->kex_in_progress && ssh->version == 2) {
+       if (!ssh->kex_in_progress && !ssh->bare_connection &&
+            ssh->version == 2) {
            do_ssh2_transport(ssh, "at user request", -1, NULL);
        }
     } else if (code == TS_BRK) {
@@ -10333,6 +10932,41 @@ void *new_sock_channel(void *handle, struct PortForwarding *pf)
     return c;
 }
 
+unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx)
+{
+    struct ssh_channel *c;
+    c = snew(struct ssh_channel);
+
+    c->ssh = ssh;
+    ssh2_channel_init(c);
+    c->type = CHAN_SHARING;
+    c->u.sharing.ctx = sharing_ctx;
+    add234(ssh->channels, c);
+    return c->localid;
+}
+
+void ssh_delete_sharing_channel(Ssh ssh, unsigned localid)
+{
+    struct ssh_channel *c;
+
+    c = find234(ssh->channels, &localid, ssh_channelfind);
+    if (c)
+        ssh_channel_destroy(c);
+}
+
+void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type,
+                                     const void *data, int datalen,
+                                     const char *additional_log_text)
+{
+    struct Packet *pkt;
+
+    pkt = ssh2_pkt_init(type);
+    pkt->downstream_id = id;
+    pkt->additional_log_text = additional_log_text;
+    ssh2_pkt_adddata(pkt, data, datalen);
+    ssh2_pkt_send(ssh, pkt);
+}
+
 /*
  * This is called when stdout/stderr (the entity to which
  * from_backend sends data) manages to clear some backlog.
@@ -10352,7 +10986,7 @@ static void ssh_unthrottle(void *handle, int bufsize)
            ssh2_set_window(ssh->mainchan,
                            bufsize < ssh->mainchan->v.v2.locmaxwin ?
                            ssh->mainchan->v.v2.locmaxwin - bufsize : 0);
-           if (conf_get_int(ssh->conf, CONF_ssh_simple))
+           if (ssh_is_simple(ssh))
                buflimit = 0;
            else
                buflimit = ssh->mainchan->v.v2.locmaxwin;
diff --git a/ssh.h b/ssh.h
index 675a509ed97aff36f3a720d22aff2fc8cb9b33c3..89f772d661e34bfa1395874ff4418d4b97e91d7a 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -8,12 +8,53 @@
 #include "misc.h"
 
 struct ssh_channel;
+typedef struct ssh_tag *Ssh;
 
 extern int sshfwd_write(struct ssh_channel *c, char *, int);
 extern void sshfwd_write_eof(struct ssh_channel *c);
 extern void sshfwd_unclean_close(struct ssh_channel *c, const char *err);
 extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize);
 Conf *sshfwd_get_conf(struct ssh_channel *c);
+void sshfwd_x11_sharing_handover(struct ssh_channel *c,
+                                 void *share_cs, void *share_chan,
+                                 const char *peer_addr, int peer_port,
+                                 int endian, int protomajor, int protominor,
+                                 const void *initial_data, int initial_len);
+void sshfwd_x11_is_local(struct ssh_channel *c);
+
+extern Socket ssh_connection_sharing_init(const char *host, int port,
+                                          Conf *conf, Ssh ssh, void **state);
+void share_got_pkt_from_server(void *ctx, int type,
+                               unsigned char *pkt, int pktlen);
+void share_activate(void *state, const char *server_verstring);
+void sharestate_free(void *state);
+int share_ndownstreams(void *state);
+
+void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
+                       const char *ds_err, const char *us_err);
+unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx);
+void ssh_delete_sharing_channel(Ssh ssh, unsigned localid);
+int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport,
+                               void *share_ctx);
+void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx);
+struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype,
+                                                void *share_cs,
+                                                void *share_chan);
+void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth);
+void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type,
+                                     const void *pkt, int pktlen,
+                                     const char *additional_log_text);
+void ssh_sharing_downstream_connected(Ssh ssh, unsigned id);
+void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id);
+void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...);
+int ssh_agent_forwarding_permitted(Ssh ssh);
+void share_setup_x11_channel(void *csv, void *chanv,
+                             unsigned upstream_id, unsigned server_id,
+                             unsigned server_currwin, unsigned server_maxpkt,
+                             unsigned client_adjusted_window,
+                             const char *peer_addr, int peer_port, int endian,
+                             int protomajor, int protominor,
+                             const void *initial_data, int initial_len);
 
 /*
  * Useful thing.
@@ -400,6 +441,7 @@ struct X11FakeAuth {
      * What to do with an X connection matching this auth data.
      */
     struct X11Display *disp;
+    void *share_cs, *share_chan;
 };
 void *x11_make_greeting(int endian, int protomajor, int protominor,
                         int auth_proto, const void *auth_data, int auth_len,
@@ -450,6 +492,8 @@ char *platform_get_x_display(void);
  */
 void x11_get_auth_from_authfile(struct X11Display *display,
                                const char *authfilename);
+int x11_identify_auth_proto(const char *proto);
+void *x11_dehexify(const char *hex, int *outlen);
 
 Bignum copybn(Bignum b);
 Bignum bn_power_2(int n);
@@ -590,6 +634,22 @@ int zlib_compress_block(void *, unsigned char *block, int len,
 int zlib_decompress_block(void *, unsigned char *block, int len,
                          unsigned char **outblock, int *outlen);
 
+/*
+ * Connection-sharing API provided by platforms. This function must
+ * either:
+ *  - return SHARE_NONE and do nothing
+ *  - return SHARE_DOWNSTREAM and set *sock to a Socket connected to
+ *    downplug
+ *  - return SHARE_UPSTREAM and set *sock to a Socket connected to
+ *    upplug.
+ */
+enum { SHARE_NONE, SHARE_DOWNSTREAM, SHARE_UPSTREAM };
+int platform_ssh_share(const char *name, Conf *conf,
+                       Plug downplug, Plug upplug, Socket *sock,
+                       char **logtext, char **ds_err, char **us_err,
+                       int can_upstream, int can_downstream);
+void platform_ssh_share_cleanup(const char *name);
+
 /*
  * SSH-1 message type codes.
  */
index 81aee8ba0594643a0bb5ac77d8b8102ad40e7c2d..f54990e7ceac2f359b1de1cfe90fd590ad397a95 100644 (file)
--- a/sshdes.c
+++ b/sshdes.c
@@ -1031,3 +1031,58 @@ const struct ssh_cipher ssh_des = {
     des_encrypt_blk, des_decrypt_blk,
     8, "single-DES CBC"
 };
+
+#ifdef TEST_XDM_AUTH
+
+/*
+ * Small standalone utility which allows encryption and decryption of
+ * single cipher blocks in the XDM-AUTHORIZATION-1 style. Written
+ * during the rework of X authorisation for connection sharing, to
+ * check the corner case when xa1_firstblock matches but the rest of
+ * the authorisation is bogus.
+ *
+ * Just compile this file on its own with the above ifdef symbol
+ * predefined:
+
+gcc -DTEST_XDM_AUTH -o sshdes sshdes.c
+
+ */
+
+#include <stdlib.h>
+void *safemalloc(size_t n, size_t size) { return calloc(n, size); }
+void safefree(void *p) { return free(p); }
+void smemclr(void *p, size_t size) { memset(p, 0, size); }
+int main(int argc, char **argv)
+{
+    unsigned char words[2][8];
+    unsigned char out[8];
+    int i, j;
+
+    memset(words, 0, sizeof(words));
+
+    for (i = 0; i < 2; i++) {
+        for (j = 0; j < 8 && argv[i+1][2*j]; j++) {
+            char x[3];
+            unsigned u;
+            x[0] = argv[i+1][2*j];
+            x[1] = argv[i+1][2*j+1];
+            x[2] = 0;
+            sscanf(x, "%02x", &u);
+            words[i][j] = u;
+        }
+    }
+
+    memcpy(out, words[0], 8);
+    des_decrypt_xdmauth(words[1], out, 8);
+    printf("decrypt(%s,%s) = ", argv[1], argv[2]);
+    for (i = 0; i < 8; i++) printf("%02x", out[i]);
+    printf("\n");
+
+    memcpy(out, words[0], 8);
+    des_encrypt_xdmauth(words[1], out, 8);
+    printf("encrypt(%s,%s) = ", argv[1], argv[2]);
+    for (i = 0; i < 8; i++) printf("%02x", out[i]);
+    printf("\n");
+}
+
+#endif
diff --git a/sshshare.c b/sshshare.c
new file mode 100644 (file)
index 0000000..bd4602b
--- /dev/null
@@ -0,0 +1,2103 @@
+/*
+ * Support for SSH connection sharing, i.e. permitting one PuTTY to
+ * open its own channels over the SSH session being run by another.
+ */
+
+/*
+ * Discussion and technical documentation
+ * ======================================
+ *
+ * The basic strategy for PuTTY's implementation of SSH connection
+ * sharing is to have a single 'upstream' PuTTY process, which manages
+ * the real SSH connection and all the cryptography, and then zero or
+ * more 'downstream' PuTTYs, which never talk to the real host but
+ * only talk to the upstream through local IPC (Unix-domain sockets or
+ * Windows named pipes).
+ *
+ * The downstreams communicate with the upstream using a protocol
+ * derived from SSH itself, which I'll document in detail below. In
+ * brief, though: the downstream->upstream protocol uses a trivial
+ * binary packet protocol (just length/type/data) to encapsulate
+ * unencrypted SSH messages, and downstreams talk to the upstream more
+ * or less as if it was an SSH server itself. (So downstreams can
+ * themselves open multiple SSH channels, for example, by sending
+ * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of
+ * their choice within each channel, and they handle their own
+ * WINDOW_ADJUST messages.)
+ *
+ * The upstream would ideally handle these downstreams by just putting
+ * their messages into the queue for proper SSH-2 encapsulation and
+ * encryption and sending them straight on to the server. However,
+ * that's not quite feasible as written, because client-side channel
+ * IDs could easily conflict (between multiple downstreams, or between
+ * a downstream and the upstream). To protect against that, the
+ * upstream rewrites the client-side channel IDs in messages it passes
+ * on to the server, so that it's performing what you might describe
+ * as 'channel-number NAT'. Then the upstream remembers which of its
+ * own channel IDs are channels it's managing itself, and which are
+ * placeholders associated with a particular downstream, so that when
+ * replies come in from the server they can be sent on to the relevant
+ * downstream (after un-NATting the channel number, of course).
+ *
+ * Global requests from downstreams are only accepted if the upstream
+ * knows what to do about them; currently the only such requests are
+ * the ones having to do with remote-to-local port forwarding (in
+ * which, again, the upstream remembers that some of the forwardings
+ * it's asked the server to set up were on behalf of particular
+ * downstreams, and sends the incoming CHANNEL_OPENs to those
+ * downstreams when connections come in).
+ *
+ * Other fiddly pieces of this mechanism are X forwarding and
+ * (OpenSSH-style) agent forwarding. Both of these have a fundamental
+ * problem arising from the protocol design: that the CHANNEL_OPEN
+ * from the server introducing a forwarded connection does not carry
+ * any indication of which session channel gave rise to it; so if
+ * session channels from multiple downstreams enable those forwarding
+ * methods, it's hard for the upstream to know which downstream to
+ * send the resulting connections back to.
+ *
+ * For X forwarding, we can work around this in a really painful way
+ * by using the fake X11 authorisation data sent to the server as part
+ * of the forwarding setup: upstream ensures that every X forwarding
+ * request carries distinguishable fake auth data, and then when X
+ * connections come in it waits to see the auth data in the X11 setup
+ * message before it decides which downstream to pass the connection
+ * on to.
+ *
+ * For agent forwarding, that workaround is unavailable. As a result,
+ * this system (and, as far as I can think of, any other system too)
+ * has the fundamental constraint that it can only forward one SSH
+ * agent - it can't forward two agents to different session channels.
+ * So downstreams can request agent forwarding if they like, but if
+ * they do, they'll get whatever SSH agent is known to the upstream
+ * (if any) forwarded to their sessions.
+ *
+ * Downstream-to-upstream protocol
+ * -------------------------------
+ *
+ * Here I document in detail the protocol spoken between PuTTY
+ * downstreams and upstreams over local IPC. The IPC mechanism can
+ * vary between host platforms, but the protocol is the same.
+ *
+ * The protocol commences with a version exchange which is exactly
+ * like the SSH-2 one, in that each side sends a single line of text
+ * of the form
+ *
+ *   <protocol>-<version>-<softwareversion> [comments] \r\n
+ *
+ * The only difference is that in real SSH-2, <protocol> is the string
+ * "SSH", whereas in this protocol the string is
+ * "SSHCONNECTION@putty.projects.tartarus.org".
+ *
+ * (The SSH RFCs allow many protocol-level identifier namespaces to be
+ * extended by implementors without central standardisation as long as
+ * they suffix "@" and a domain name they control to their new ids.
+ * RFC 4253 does not define this particular name to be changeable at
+ * all, but I like to think this is obviously how it would have done
+ * so if the working group had foreseen the need :-)
+ *
+ * Thereafter, all data exchanged consists of a sequence of binary
+ * packets concatenated end-to-end, each of which is of the form
+ *
+ *     uint32     length of packet, N
+ *     byte[N]    N bytes of packet data
+ *
+ * and, since these are SSH-2 messages, the first data byte is taken
+ * to be the packet type code.
+ *
+ * These messages are interpreted as those of an SSH connection, after
+ * userauth completes, and without any repeat key exchange.
+ * Specifically, any message from the SSH Connection Protocol is
+ * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG,
+ * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport
+ * Protocol.
+ *
+ * This protocol imposes a few additional requirements, over and above
+ * those of the standard SSH Connection Protocol:
+ *
+ * Message sizes are not permitted to exceed 0x4010 (16400) bytes,
+ * including their length header.
+ *
+ * When the server (i.e. really the PuTTY upstream) sends
+ * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client
+ * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that
+ * confirmation message MUST include an initial window size of at
+ * least 256. (Rationale: this is a bit of a fudge which makes it
+ * easier, by eliminating the possibility of nasty edge cases, for an
+ * upstream to arrange not to pass the CHANNEL_OPEN on to downstream
+ * until after it's seen the X11 auth data to decide which downstream
+ * it needs to go to.)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "putty.h"
+#include "tree234.h"
+#include "ssh.h"
+
+struct ssh_sharing_state {
+    const struct plug_function_table *fn;
+    /* the above variable absolutely *must* be the first in this structure */
+
+    char *sockname;                  /* the socket name, kept for cleanup */
+    Socket listensock;               /* the master listening Socket */
+    tree234 *connections;            /* holds ssh_sharing_connstates */
+    unsigned nextid;                 /* preferred id for next connstate */
+    Ssh ssh;                         /* instance of the ssh backend */
+    char *server_verstring;          /* server version string after "SSH-" */
+};
+
+struct share_globreq;
+
+struct ssh_sharing_connstate {
+    const struct plug_function_table *fn;
+    /* the above variable absolutely *must* be the first in this structure */
+
+    unsigned id;    /* used to identify this downstream in log messages */
+
+    Socket sock;                     /* the Socket for this connection */
+    struct ssh_sharing_state *parent;
+
+    int crLine;                        /* coroutine state for share_receive */
+
+    int sent_verstring, got_verstring, curr_packetlen;
+
+    unsigned char recvbuf[0x4010];
+    int recvlen;
+
+    /*
+     * Assorted state we have to remember about this downstream, so
+     * that we can clean it up appropriately when the downstream goes
+     * away.
+     */
+
+    /* Channels which don't have a downstream id, i.e. we've passed a
+     * CHANNEL_OPEN down from the server but not had an
+     * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes
+     * away, we respond to all of these with OPEN_FAILURE. */
+    tree234 *halfchannels;         /* stores 'struct share_halfchannel' */
+
+    /* Channels which do have a downstream id. We need to index these
+     * by both server id and upstream id, so we can find a channel
+     * when handling either an upward or a downward message referring
+     * to it. */
+    tree234 *channels_by_us;       /* stores 'struct share_channel' */
+    tree234 *channels_by_server;   /* stores 'struct share_channel' */
+
+    /* Another class of channel which doesn't have a downstream id.
+     * The difference between these and halfchannels is that xchannels
+     * do have an *upstream* id, because upstream has already accepted
+     * the channel request from the server. This arises in the case of
+     * X forwarding, where we have to accept the request and read the
+     * X authorisation data before we know whether the channel needs
+     * to be forwarded to a downstream. */
+    tree234 *xchannels_by_us;     /* stores 'struct share_xchannel' */
+    tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */
+
+    /* Remote port forwarding requests in force. */
+    tree234 *forwardings;          /* stores 'struct share_forwarding' */
+
+    /* Global requests we've sent on to the server, pending replies. */
+    struct share_globreq *globreq_head, *globreq_tail;
+};
+
+struct share_halfchannel {
+    unsigned server_id;
+};
+
+/* States of a share_channel. */
+enum {
+    OPEN,
+    SENT_CLOSE,
+    RCVD_CLOSE,
+    /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet.
+     * If downstream goes away when a channel is in this state, we
+     * must wait for the server's response before starting to send
+     * CLOSE. Channels in this state are also not held in
+     * channels_by_server, because their server_id field is
+     * meaningless. */
+    UNACKNOWLEDGED
+};
+
+struct share_channel {
+    unsigned downstream_id, upstream_id, server_id;
+    int downstream_maxpkt;
+    int state;
+    /*
+     * Some channels (specifically, channels on which downstream has
+     * sent "x11-req") have the additional function of storing a set
+     * of downstream X authorisation data and a handle to an upstream
+     * fake set.
+     */
+    struct X11FakeAuth *x11_auth_upstream;
+    int x11_auth_proto;
+    char *x11_auth_data;
+    int x11_auth_datalen;
+    int x11_one_shot;
+};
+
+struct share_forwarding {
+    char *host;
+    int port;
+    int active;             /* has the server sent REQUEST_SUCCESS? */
+};
+
+struct share_xchannel_message {
+    struct share_xchannel_message *next;
+    int type;
+    unsigned char *data;
+    int datalen;
+};
+
+struct share_xchannel {
+    unsigned upstream_id, server_id;
+
+    /*
+     * xchannels come in two flavours: live and dead. Live ones are
+     * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from
+     * downstream; dead ones have had an OPEN_FAILURE, so they only
+     * exist as a means of letting us conveniently respond to further
+     * channel messages from the server until such time as the server
+     * sends us CHANNEL_CLOSE.
+     */
+    int live;
+
+    /*
+     * When we receive OPEN_CONFIRMATION, we will need to send a
+     * WINDOW_ADJUST to the server to synchronise the windows. For
+     * this purpose we need to know what window we have so far offered
+     * the server. We record this as exactly the value in the
+     * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount
+     * by which the two X greetings differed in length.
+     */
+    int window;
+
+    /*
+     * Linked list of SSH messages from the server relating to this
+     * channel, which we queue up until downstream sends us an
+     * OPEN_CONFIRMATION and we can belatedly send them all on.
+     */
+    struct share_xchannel_message *msghead, *msgtail;
+};
+
+enum {
+    GLOBREQ_TCPIP_FORWARD,
+    GLOBREQ_CANCEL_TCPIP_FORWARD
+};
+
+struct share_globreq {
+    struct share_globreq *next;
+    int type;
+    int want_reply;
+    struct share_forwarding *fwd;
+};
+
+static int share_connstate_cmp(void *av, void *bv)
+{
+    const struct ssh_sharing_connstate *a =
+        (const struct ssh_sharing_connstate *)av;
+    const struct ssh_sharing_connstate *b =
+        (const struct ssh_sharing_connstate *)bv;
+
+    if (a->id < b->id)
+        return -1;
+    else if (a->id > b->id)
+        return +1;
+    else
+        return 0;
+}
+
+static unsigned share_find_unused_id
+(struct ssh_sharing_state *sharestate, unsigned first)
+{
+    int low_orig, low, mid, high, high_orig;
+    struct ssh_sharing_connstate *cs;
+    unsigned ret;
+
+    /*
+     * Find the lowest unused downstream ID greater or equal to
+     * 'first'.
+     *
+     * Begin by seeing if 'first' itself is available. If it is, we'll
+     * just return it; if it's already in the tree, we'll find the
+     * tree index where it appears and use that for the next stage.
+     */
+    {
+        struct ssh_sharing_connstate dummy;
+        dummy.id = first;
+        cs = findrelpos234(sharestate->connections, &dummy, NULL,
+                           REL234_GE, &low_orig);
+        if (!cs)
+            return first;
+    }
+
+    /*
+     * Now binary-search using the counted B-tree, to find the largest
+     * ID which is in a contiguous sequence from the beginning of that
+     * range.
+     */
+    low = low_orig;
+    high = high_orig = count234(sharestate->connections);
+    while (high - low > 1) {
+       mid = (high + low) / 2;
+       cs = index234(sharestate->connections, mid);
+       if (cs->id == first + (mid - low_orig))
+           low = mid;                 /* this one is still in the sequence */
+       else
+           high = mid;                /* this one is past the end */
+    }
+
+    /*
+     * Now low is the tree index of the largest ID in the initial
+     * sequence. So the return value is one more than low's id, and we
+     * know low's id is given by the formula in the binary search loop
+     * above.
+     *
+     * (If an SSH connection went on for _enormously_ long, we might
+     * reach a point where all ids from 'first' to UINT_MAX were in
+     * use. In that situation the formula below would wrap round by
+     * one and return zero, which is conveniently the right way to
+     * signal 'no id available' from this function.)
+     */
+    ret = first + (low - low_orig) + 1;
+    {
+        struct ssh_sharing_connstate dummy;
+        dummy.id = ret;
+       assert(NULL == find234(sharestate->connections, &dummy, NULL));
+    }
+    return ret;
+}
+
+static int share_halfchannel_cmp(void *av, void *bv)
+{
+    const struct share_halfchannel *a = (const struct share_halfchannel *)av;
+    const struct share_halfchannel *b = (const struct share_halfchannel *)bv;
+
+    if (a->server_id < b->server_id)
+        return -1;
+    else if (a->server_id > b->server_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_channel_us_cmp(void *av, void *bv)
+{
+    const struct share_channel *a = (const struct share_channel *)av;
+    const struct share_channel *b = (const struct share_channel *)bv;
+
+    if (a->upstream_id < b->upstream_id)
+        return -1;
+    else if (a->upstream_id > b->upstream_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_channel_server_cmp(void *av, void *bv)
+{
+    const struct share_channel *a = (const struct share_channel *)av;
+    const struct share_channel *b = (const struct share_channel *)bv;
+
+    if (a->server_id < b->server_id)
+        return -1;
+    else if (a->server_id > b->server_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_xchannel_us_cmp(void *av, void *bv)
+{
+    const struct share_xchannel *a = (const struct share_xchannel *)av;
+    const struct share_xchannel *b = (const struct share_xchannel *)bv;
+
+    if (a->upstream_id < b->upstream_id)
+        return -1;
+    else if (a->upstream_id > b->upstream_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_xchannel_server_cmp(void *av, void *bv)
+{
+    const struct share_xchannel *a = (const struct share_xchannel *)av;
+    const struct share_xchannel *b = (const struct share_xchannel *)bv;
+
+    if (a->server_id < b->server_id)
+        return -1;
+    else if (a->server_id > b->server_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_forwarding_cmp(void *av, void *bv)
+{
+    const struct share_forwarding *a = (const struct share_forwarding *)av;
+    const struct share_forwarding *b = (const struct share_forwarding *)bv;
+    int i;
+
+    if ((i = strcmp(a->host, b->host)) != 0)
+        return i;
+    else if (a->port < b->port)
+        return -1;
+    else if (a->port > b->port)
+        return +1;
+    else
+        return 0;
+}
+
+static void share_xchannel_free(struct share_xchannel *xc)
+{
+    while (xc->msghead) {
+        struct share_xchannel_message *tmp = xc->msghead;
+        xc->msghead = tmp->next;
+        sfree(tmp);
+    }
+    sfree(xc);
+}
+
+static void share_connstate_free(struct ssh_sharing_connstate *cs)
+{
+    struct share_halfchannel *hc;
+    struct share_xchannel *xc;
+    struct share_channel *chan;
+    struct share_forwarding *fwd;
+
+    while ((hc = (struct share_halfchannel *)
+            delpos234(cs->halfchannels, 0)) != NULL)
+        sfree(hc);
+    freetree234(cs->halfchannels);
+
+    /* All channels live in 'channels_by_us' but only some in
+     * 'channels_by_server', so we use the former to find the list of
+     * ones to free */
+    freetree234(cs->channels_by_server);
+    while ((chan = (struct share_channel *)
+            delpos234(cs->channels_by_us, 0)) != NULL)
+        sfree(chan);
+    freetree234(cs->channels_by_us);
+
+    /* But every xchannel is in both trees, so it doesn't matter which
+     * we use to free them. */
+    while ((xc = (struct share_xchannel *)
+            delpos234(cs->xchannels_by_us, 0)) != NULL)
+        share_xchannel_free(xc);
+    freetree234(cs->xchannels_by_us);
+    freetree234(cs->xchannels_by_server);
+
+    while ((fwd = (struct share_forwarding *)
+            delpos234(cs->forwardings, 0)) != NULL)
+        sfree(fwd);
+    freetree234(cs->forwardings);
+
+    while (cs->globreq_head) {
+        struct share_globreq *globreq = cs->globreq_head;
+        cs->globreq_head = cs->globreq_head->next;
+        sfree(globreq);
+    }
+
+    sfree(cs);
+}
+
+void sharestate_free(void *v)
+{
+    struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)v;
+    struct ssh_sharing_connstate *cs;
+
+    platform_ssh_share_cleanup(sharestate->sockname);
+
+    while ((cs = (struct ssh_sharing_connstate *)
+            delpos234(sharestate->connections, 0)) != NULL) {
+        share_connstate_free(cs);
+    }
+    freetree234(sharestate->connections);
+    sfree(sharestate->server_verstring);
+    sfree(sharestate->sockname);
+    sfree(sharestate);
+}
+
+static struct share_halfchannel *share_add_halfchannel
+    (struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+    struct share_halfchannel *hc = snew(struct share_halfchannel);
+    hc->server_id = server_id;
+    if (add234(cs->halfchannels, hc) != hc) {
+        /* Duplicate?! */
+        sfree(hc);
+        return NULL;
+    } else {
+        return hc;
+    }
+}
+
+static struct share_halfchannel *share_find_halfchannel
+    (struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+    struct share_halfchannel dummyhc;
+    dummyhc.server_id = server_id;
+    return find234(cs->halfchannels, &dummyhc, NULL);
+}
+
+static void share_remove_halfchannel(struct ssh_sharing_connstate *cs,
+                                     struct share_halfchannel *hc)
+{
+    del234(cs->halfchannels, hc);
+    sfree(hc);
+}
+
+static struct share_channel *share_add_channel
+    (struct ssh_sharing_connstate *cs, unsigned downstream_id,
+     unsigned upstream_id, unsigned server_id, int state, int maxpkt)
+{
+    struct share_channel *chan = snew(struct share_channel);
+    chan->downstream_id = downstream_id;
+    chan->upstream_id = upstream_id;
+    chan->server_id = server_id;
+    chan->state = state;
+    chan->downstream_maxpkt = maxpkt;
+    chan->x11_auth_upstream = NULL;
+    chan->x11_auth_data = NULL;
+    chan->x11_auth_proto = -1;
+    chan->x11_auth_datalen = 0;
+    chan->x11_one_shot = 0;
+    if (add234(cs->channels_by_us, chan) != chan) {
+        sfree(chan);
+        return NULL;
+    }
+    if (chan->state != UNACKNOWLEDGED) {
+        if (add234(cs->channels_by_server, chan) != chan) {
+            del234(cs->channels_by_us, chan);
+            sfree(chan);            
+            return NULL;
+        }
+    }
+    return chan;
+}
+
+static void share_channel_set_server_id(struct ssh_sharing_connstate *cs,
+                                        struct share_channel *chan,
+                                        unsigned server_id, int newstate)
+{
+    chan->server_id = server_id;
+    chan->state = newstate;
+    assert(newstate != UNACKNOWLEDGED);
+    add234(cs->channels_by_server, chan);
+}
+
+static struct share_channel *share_find_channel_by_upstream
+    (struct ssh_sharing_connstate *cs, unsigned upstream_id)
+{
+    struct share_channel dummychan;
+    dummychan.upstream_id = upstream_id;
+    return find234(cs->channels_by_us, &dummychan, NULL);
+}
+
+static struct share_channel *share_find_channel_by_server
+    (struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+    struct share_channel dummychan;
+    dummychan.server_id = server_id;
+    return find234(cs->channels_by_server, &dummychan, NULL);
+}
+
+static void share_remove_channel(struct ssh_sharing_connstate *cs,
+                                 struct share_channel *chan)
+{
+    del234(cs->channels_by_us, chan);
+    del234(cs->channels_by_server, chan);
+    if (chan->x11_auth_upstream)
+        ssh_sharing_remove_x11_display(cs->parent->ssh,
+                                       chan->x11_auth_upstream);
+    sfree(chan->x11_auth_data);
+    sfree(chan);
+}
+
+static struct share_xchannel *share_add_xchannel
+    (struct ssh_sharing_connstate *cs,
+     unsigned upstream_id, unsigned server_id)
+{
+    struct share_xchannel *xc = snew(struct share_xchannel);
+    xc->upstream_id = upstream_id;
+    xc->server_id = server_id;
+    xc->live = TRUE;
+    xc->msghead = xc->msgtail = NULL;
+    if (add234(cs->xchannels_by_us, xc) != xc) {
+        sfree(xc);
+        return NULL;
+    }
+    if (add234(cs->xchannels_by_server, xc) != xc) {
+        del234(cs->xchannels_by_us, xc);
+        sfree(xc);
+        return NULL;
+    }
+    return xc;
+}
+
+static struct share_xchannel *share_find_xchannel_by_upstream
+    (struct ssh_sharing_connstate *cs, unsigned upstream_id)
+{
+    struct share_xchannel dummyxc;
+    dummyxc.upstream_id = upstream_id;
+    return find234(cs->xchannels_by_us, &dummyxc, NULL);
+}
+
+static struct share_xchannel *share_find_xchannel_by_server
+    (struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+    struct share_xchannel dummyxc;
+    dummyxc.server_id = server_id;
+    return find234(cs->xchannels_by_server, &dummyxc, NULL);
+}
+
+static void share_remove_xchannel(struct ssh_sharing_connstate *cs,
+                                 struct share_xchannel *xc)
+{
+    del234(cs->xchannels_by_us, xc);
+    del234(cs->xchannels_by_server, xc);
+    share_xchannel_free(xc);
+}
+
+static struct share_forwarding *share_add_forwarding
+    (struct ssh_sharing_connstate *cs,
+     const char *host, int port)
+{
+    struct share_forwarding *fwd = snew(struct share_forwarding);
+    fwd->host = dupstr(host);
+    fwd->port = port;
+    fwd->active = FALSE;
+    if (add234(cs->forwardings, fwd) != fwd) {
+        /* Duplicate?! */
+        sfree(fwd);
+        return NULL;
+    }
+    return fwd;
+}
+
+static struct share_forwarding *share_find_forwarding
+    (struct ssh_sharing_connstate *cs, const char *host, int port)
+{
+    struct share_forwarding dummyfwd, *ret;
+    dummyfwd.host = dupstr(host);
+    dummyfwd.port = port;
+    ret = find234(cs->forwardings, &dummyfwd, NULL);
+    sfree(dummyfwd.host);
+    return ret;
+}
+
+static void share_remove_forwarding(struct ssh_sharing_connstate *cs,
+                                    struct share_forwarding *fwd)
+{
+    del234(cs->forwardings, fwd);
+    sfree(fwd);
+}
+
+static void send_packet_to_downstream(struct ssh_sharing_connstate *cs,
+                                      int type, const void *pkt, int pktlen,
+                                      struct share_channel *chan)
+{
+    if (!cs->sock) /* throw away all packets destined for a dead downstream */
+        return;
+
+    if (type == SSH2_MSG_CHANNEL_DATA) {
+        /*
+         * Special case which we take care of at a low level, so as to
+         * be sure to apply it in all cases. On rare occasions we
+         * might find that we have a channel for which the
+         * downstream's maximum packet size exceeds the max packet
+         * size we presented to the server on its behalf. (This can
+         * occur in X11 forwarding, where we have to send _our_
+         * CHANNEL_OPEN_CONFIRMATION before we discover which if any
+         * downstream the channel is destined for, so if that
+         * downstream turns out to present a smaller max packet size
+         * then we're in this situation.)
+         *
+         * If that happens, we just chop up the packet into pieces and
+         * send them as separate CHANNEL_DATA packets.
+         */
+        const char *upkt = (const char *)pkt;
+        char header[13]; /* 4 length + 1 type + 4 channel id + 4 string len */
+
+        int len = toint(GET_32BIT(upkt + 4));
+        upkt += 8;                /* skip channel id + length field */
+
+        if (len < 0 || len > pktlen - 8)
+            len = pktlen - 8;
+
+        do {
+            int this_len = (len > chan->downstream_maxpkt ?
+                            chan->downstream_maxpkt : len);
+            PUT_32BIT(header, this_len + 9);
+            header[4] = type;
+            PUT_32BIT(header + 5, chan->downstream_id);
+            PUT_32BIT(header + 9, this_len);
+            sk_write(cs->sock, header, 13);
+            sk_write(cs->sock, upkt, this_len);
+            len -= this_len;
+            upkt += this_len;
+        } while (len > 0);
+    } else {
+        /*
+         * Just do the obvious thing.
+         */
+        char header[9];
+
+        PUT_32BIT(header, pktlen + 1);
+        header[4] = type;
+        sk_write(cs->sock, header, 5);
+        sk_write(cs->sock, pkt, pktlen);
+    }
+}
+
+static void share_try_cleanup(struct ssh_sharing_connstate *cs)
+{
+    int i;
+    struct share_halfchannel *hc;
+    struct share_channel *chan;
+    struct share_forwarding *fwd;
+
+    /*
+     * Any half-open channels, i.e. those for which we'd received
+     * CHANNEL_OPEN from the server but not passed back a response
+     * from downstream, should be responded to with OPEN_FAILURE.
+     */
+    while ((hc = (struct share_halfchannel *)
+            index234(cs->halfchannels, 0)) != NULL) {
+        static const char reason[] = "PuTTY downstream no longer available";
+        static const char lang[] = "en";
+        unsigned char packet[256];
+        int pos = 0;
+
+        PUT_32BIT(packet + pos, hc->server_id); pos += 4;
+        PUT_32BIT(packet + pos, SSH2_OPEN_CONNECT_FAILED); pos += 4;
+        PUT_32BIT(packet + pos, strlen(reason)); pos += 4;
+        memcpy(packet + pos, reason, strlen(reason)); pos += strlen(reason);
+        PUT_32BIT(packet + pos, strlen(lang)); pos += 4;
+        memcpy(packet + pos, lang, strlen(lang)); pos += strlen(lang);
+        ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                        SSH2_MSG_CHANNEL_OPEN_FAILURE,
+                                        packet, pos, "cleanup after"
+                                        " downstream went away");
+
+        share_remove_halfchannel(cs, hc);
+    }
+
+    /*
+     * Any actually open channels should have a CHANNEL_CLOSE sent for
+     * them, unless we've already done so. We won't be able to
+     * actually clean them up until CHANNEL_CLOSE comes back from the
+     * server, though (unless the server happens to have sent a CLOSE
+     * already).
+     *
+     * Another annoying exception is UNACKNOWLEDGED channels, i.e.
+     * we've _sent_ a CHANNEL_OPEN to the server but not received an
+     * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply
+     * before closing the channel, because until we see that reply we
+     * won't have the server's channel id to put in the close message.
+     */
+    for (i = 0; (chan = (struct share_channel *)
+                 index234(cs->channels_by_us, i)) != NULL; i++) {
+        unsigned char packet[256];
+        int pos = 0;
+
+        if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) {
+            PUT_32BIT(packet + pos, chan->server_id); pos += 4;
+            ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                            SSH2_MSG_CHANNEL_CLOSE,
+                                            packet, pos, "cleanup after"
+                                            " downstream went away");
+            if (chan->state != RCVD_CLOSE) {
+                chan->state = SENT_CLOSE;
+            } else {
+                /* In this case, we _can_ clear up the channel now. */
+                ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id);
+                share_remove_channel(cs, chan);
+                i--;    /* don't accidentally skip one as a result */
+            }
+        }
+    }
+
+    /*
+     * Any remote port forwardings we're managing on behalf of this
+     * downstream should be cancelled. Again, we must defer those for
+     * which we haven't yet seen REQUEST_SUCCESS/FAILURE.
+     *
+     * We take a fire-and-forget approach during cleanup, not
+     * bothering to set want_reply.
+     */
+    for (i = 0; (fwd = (struct share_forwarding *)
+                 index234(cs->forwardings, i)) != NULL; i++) {
+        if (fwd->active) {
+            static const char request[] = "cancel-tcpip-forward";
+            char *packet = snewn(256 + strlen(fwd->host), char);
+            int pos = 0;
+
+            PUT_32BIT(packet + pos, strlen(request)); pos += 4;
+            memcpy(packet + pos, request, strlen(request));
+            pos += strlen(request);
+
+            packet[pos++] = 0;         /* !want_reply */
+
+            PUT_32BIT(packet + pos, strlen(fwd->host)); pos += 4;
+            memcpy(packet + pos, fwd->host, strlen(fwd->host));
+            pos += strlen(fwd->host);
+
+            PUT_32BIT(packet + pos, fwd->port); pos += 4;
+
+            ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                            SSH2_MSG_GLOBAL_REQUEST,
+                                            packet, pos, "cleanup after"
+                                            " downstream went away");
+
+            share_remove_forwarding(cs, fwd);
+            i--;    /* don't accidentally skip one as a result */
+        }
+    }
+
+    if (count234(cs->halfchannels) == 0 &&
+        count234(cs->channels_by_us) == 0 &&
+        count234(cs->forwardings) == 0) {
+        /*
+         * Now we're _really_ done, so we can get rid of cs completely.
+         */
+        del234(cs->parent->connections, cs);
+        ssh_sharing_downstream_disconnected(cs->parent->ssh, cs->id);
+        share_connstate_free(cs);
+    }
+}
+
+static void share_begin_cleanup(struct ssh_sharing_connstate *cs)
+{
+
+    sk_close(cs->sock);
+    cs->sock = NULL;
+
+    share_try_cleanup(cs);
+}
+
+static void share_disconnect(struct ssh_sharing_connstate *cs,
+                             const char *message)
+{
+    static const char lang[] = "en";
+    int msglen = strlen(message);
+    char *packet = snewn(msglen + 256, char);
+    int pos = 0;
+
+    PUT_32BIT(packet + pos, SSH2_DISCONNECT_PROTOCOL_ERROR); pos += 4;
+
+    PUT_32BIT(packet + pos, msglen); pos += 4;
+    memcpy(packet + pos, message, msglen);
+    pos += msglen;
+
+    PUT_32BIT(packet + pos, strlen(lang)); pos += 4;
+    memcpy(packet + pos, lang, strlen(lang)); pos += strlen(lang);
+
+    send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, packet, pos, NULL);
+
+    share_begin_cleanup(cs);
+}
+
+static int share_closing(Plug plug, const char *error_msg, int error_code,
+                         int calling_back)
+{
+    struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug;
+    if (error_msg)
+        ssh_sharing_logf(cs->parent->ssh, cs->id, "%s", error_msg);
+    share_begin_cleanup(cs);
+    return 1;
+}
+
+static int getstring_inner(const void *vdata, int datalen,
+                           char **out, int *outlen)
+{
+    const unsigned char *data = (const unsigned char *)vdata;
+    int len;
+
+    if (datalen < 4)
+        return FALSE;
+
+    len = toint(GET_32BIT(data));
+    if (len < 0 || len > datalen - 4)
+        return FALSE;
+
+    if (outlen)
+        *outlen = len + 4;         /* total size including length field */
+    if (out)
+        *out = dupprintf("%.*s", len, (char *)data + 4);
+    return TRUE;
+}
+
+static char *getstring(const void *data, int datalen)
+{
+    char *ret;
+    if (getstring_inner(data, datalen, &ret, NULL))
+        return ret;
+    else
+        return NULL;
+}
+
+static int getstring_size(const void *data, int datalen)
+{
+    int ret;
+    if (getstring_inner(data, datalen, NULL, &ret))
+        return ret;
+    else
+        return -1;
+}
+
+/*
+ * Append a message to the end of an xchannel's queue, with the length
+ * and type code filled in and the data block allocated but
+ * uninitialised.
+ */
+struct share_xchannel_message *share_xchannel_add_message
+(struct share_xchannel *xc, int type, int len)
+{
+    unsigned char *block;
+    struct share_xchannel_message *msg;
+
+    /*
+     * Be a little tricksy here by allocating a single memory block
+     * containing both the 'struct share_xchannel_message' and the
+     * actual data. Simplifies freeing it later.
+     */
+    block = smalloc(sizeof(struct share_xchannel_message) + len);
+    msg = (struct share_xchannel_message *)block;
+    msg->data = block + sizeof(struct share_xchannel_message);
+    msg->datalen = len;
+    msg->type = type;
+
+    /*
+     * Queue it in the xchannel.
+     */
+    if (xc->msgtail)
+        xc->msgtail->next = msg;
+    else
+        xc->msghead = msg;
+    msg->next = NULL;
+    xc->msgtail = msg;
+
+    return msg;
+}
+
+void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
+                                 struct share_xchannel *xc)
+{
+    /*
+     * Handle queued incoming messages from the server destined for an
+     * xchannel which is dead (i.e. downstream sent OPEN_FAILURE).
+     */
+    int delete = FALSE;
+    while (xc->msghead) {
+        struct share_xchannel_message *msg = xc->msghead;
+        xc->msghead = msg->next;
+
+        if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) {
+            /*
+             * A CHANNEL_REQUEST is responded to by sending
+             * CHANNEL_FAILURE, if it has want_reply set.
+             */
+            int wantreplypos = getstring_size(msg->data, msg->datalen);
+            if (wantreplypos > 0 && wantreplypos < msg->datalen &&
+                msg->data[wantreplypos] != 0) {
+                unsigned char id[4];
+                PUT_32BIT(id, xc->server_id);
+                ssh_send_packet_from_downstream
+                    (cs->parent->ssh, cs->id, SSH2_MSG_CHANNEL_FAILURE, id, 4,
+                     "downstream refused X channel open");
+            }
+        } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) {
+            /*
+             * On CHANNEL_CLOSE we can discard the channel completely.
+             */
+            delete = TRUE;
+        }
+
+        sfree(msg);
+    }
+    xc->msgtail = NULL;
+    if (delete) {
+        ssh_delete_sharing_channel(cs->parent->ssh, xc->upstream_id);
+        share_remove_xchannel(cs, xc);
+    }
+}
+
+void share_xchannel_confirmation(struct ssh_sharing_connstate *cs,
+                                 struct share_xchannel *xc,
+                                 struct share_channel *chan,
+                                 unsigned downstream_window)
+{
+    unsigned char window_adjust[8];
+
+    /*
+     * Send all the queued messages downstream.
+     */
+    while (xc->msghead) {
+        struct share_xchannel_message *msg = xc->msghead;
+        xc->msghead = msg->next;
+
+        if (msg->datalen >= 4)
+            PUT_32BIT(msg->data, chan->downstream_id);
+        send_packet_to_downstream(cs, msg->type,
+                                  msg->data, msg->datalen, chan);
+
+        sfree(msg);
+    }
+
+    /*
+     * Send a WINDOW_ADJUST back upstream, to synchronise the window
+     * size downstream thinks it's presented with the one we've
+     * actually presented.
+     */
+    PUT_32BIT(window_adjust, xc->server_id);
+    PUT_32BIT(window_adjust + 4, downstream_window - xc->window);
+    ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                    SSH2_MSG_CHANNEL_WINDOW_ADJUST,
+                                    window_adjust, 8, "window adjustment after"
+                                    " downstream accepted X channel");
+}
+
+void share_xchannel_failure(struct ssh_sharing_connstate *cs,
+                            struct share_xchannel *xc)
+{
+    /*
+     * If downstream refuses to open our X channel at all for some
+     * reason, we must respond by sending an emergency CLOSE upstream.
+     */
+    unsigned char id[4];
+    PUT_32BIT(id, xc->server_id);
+    ssh_send_packet_from_downstream
+        (cs->parent->ssh, cs->id, SSH2_MSG_CHANNEL_CLOSE, id, 4,
+         "downstream refused X channel open");
+
+    /*
+     * Now mark the xchannel as dead, and respond to anything sent on
+     * it until we see CLOSE for it in turn.
+     */
+    xc->live = FALSE;
+    share_dead_xchannel_respond(cs, xc);
+}
+
+void share_setup_x11_channel(void *csv, void *chanv,
+                             unsigned upstream_id, unsigned server_id,
+                             unsigned server_currwin, unsigned server_maxpkt,
+                             unsigned client_adjusted_window,
+                             const char *peer_addr, int peer_port, int endian,
+                             int protomajor, int protominor,
+                             const void *initial_data, int initial_len)
+{
+    struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv;
+    struct share_channel *chan = (struct share_channel *)chanv;
+    struct share_xchannel *xc;
+    struct share_xchannel_message *msg;
+    void *greeting;
+    int greeting_len;
+    unsigned char *pkt;
+    int pktlen;
+
+    /*
+     * Create an xchannel containing data we've already received from
+     * the X client, and preload it with a CHANNEL_DATA message
+     * containing our own made-up authorisation greeting and any
+     * additional data sent from the server so far.
+     */
+    xc = share_add_xchannel(cs, upstream_id, server_id);
+    greeting = x11_make_greeting(endian, protomajor, protominor,
+                                 chan->x11_auth_proto,
+                                 chan->x11_auth_data, chan->x11_auth_datalen,
+                                 peer_addr, peer_port, &greeting_len);
+    msg = share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA,
+                                     8 + greeting_len + initial_len);
+    /* leave the channel id field unfilled - we don't know the
+     * downstream id yet, of course */
+    PUT_32BIT(msg->data + 4, greeting_len + initial_len);
+    memcpy(msg->data + 8, greeting, greeting_len);
+    memcpy(msg->data + 8 + greeting_len, initial_data, initial_len);
+    sfree(greeting);
+
+    xc->window = client_adjusted_window + greeting_len;
+
+    /*
+     * Send on a CHANNEL_OPEN to downstream.
+     */
+    pktlen = 27 + strlen(peer_addr);
+    pkt = snewn(pktlen, unsigned char);
+    PUT_32BIT(pkt, 3);                 /* strlen("x11") */
+    memcpy(pkt+4, "x11", 3);
+    PUT_32BIT(pkt+7, server_id);
+    PUT_32BIT(pkt+11, server_currwin);
+    PUT_32BIT(pkt+15, server_maxpkt);
+    PUT_32BIT(pkt+19, strlen(peer_addr));
+    memcpy(pkt+23, peer_addr, strlen(peer_addr));
+    PUT_32BIT(pkt+23+strlen(peer_addr), peer_port);
+    send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, pkt, pktlen, NULL);
+    sfree(pkt);
+
+    /*
+     * If this was a once-only X forwarding, clean it up now.
+     */
+    if (chan->x11_one_shot) {
+        ssh_sharing_remove_x11_display(cs->parent->ssh,
+                                       chan->x11_auth_upstream);
+        chan->x11_auth_upstream = NULL;
+        sfree(chan->x11_auth_data);
+        chan->x11_auth_proto = -1;
+        chan->x11_auth_datalen = 0;
+        chan->x11_one_shot = 0;
+    }
+}
+
+void share_got_pkt_from_server(void *csv, int type,
+                               unsigned char *pkt, int pktlen)
+{
+    struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv;
+    struct share_globreq *globreq;
+    int id_pos;
+    unsigned upstream_id, server_id;
+    struct share_channel *chan;
+    struct share_xchannel *xc;
+
+    switch (type) {
+      case SSH2_MSG_REQUEST_SUCCESS:
+      case SSH2_MSG_REQUEST_FAILURE:
+        globreq = cs->globreq_head;
+        if (globreq->type == GLOBREQ_TCPIP_FORWARD) {
+            if (type == SSH2_MSG_REQUEST_FAILURE) {
+                share_remove_forwarding(cs, globreq->fwd);
+            } else {
+                globreq->fwd->active = TRUE;
+            }
+        } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) {
+            if (type == SSH2_MSG_REQUEST_SUCCESS) {
+                share_remove_forwarding(cs, globreq->fwd);
+            }
+        }
+        if (globreq->want_reply) {
+            send_packet_to_downstream(cs, type, pkt, pktlen, NULL);
+        }
+        cs->globreq_head = globreq->next;
+        sfree(globreq);
+        if (cs->globreq_head == NULL)
+            cs->globreq_tail = NULL;
+
+        if (!cs->sock) {
+            /* Retry cleaning up this connection, in case that reply
+             * was the last thing we were waiting for. */
+            share_try_cleanup(cs);
+        }
+
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN:
+        id_pos = getstring_size(pkt, pktlen);
+        assert(id_pos >= 0);
+        server_id = GET_32BIT(pkt + id_pos);
+        share_add_halfchannel(cs, server_id);
+
+        send_packet_to_downstream(cs, type, pkt, pktlen, NULL);
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+      case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+      case SSH2_MSG_CHANNEL_CLOSE:
+      case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+      case SSH2_MSG_CHANNEL_DATA:
+      case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+      case SSH2_MSG_CHANNEL_EOF:
+      case SSH2_MSG_CHANNEL_REQUEST:
+      case SSH2_MSG_CHANNEL_SUCCESS:
+      case SSH2_MSG_CHANNEL_FAILURE:
+        /*
+         * All these messages have the recipient channel id as the
+         * first uint32 field in the packet. Substitute the downstream
+         * channel id for our one and pass the packet downstream.
+         */
+        assert(pktlen >= 4);
+        upstream_id = GET_32BIT(pkt);
+        if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) {
+            /*
+             * The normal case: this id refers to an open channel.
+             */
+            PUT_32BIT(pkt, chan->downstream_id);
+            send_packet_to_downstream(cs, type, pkt, pktlen, chan);
+
+            /*
+             * Update the channel state, for messages that need it.
+             */
+            if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+                if (chan->state == UNACKNOWLEDGED && pktlen >= 8) {
+                    share_channel_set_server_id(cs, chan, GET_32BIT(pkt+4),
+                                                OPEN);
+                    if (!cs->sock) {
+                        /* Retry cleaning up this connection, so that we
+                         * can send an immediate CLOSE on this channel for
+                         * which we now know the server id. */
+                        share_try_cleanup(cs);
+                    }
+                }
+            } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
+                ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id);
+                share_remove_channel(cs, chan);
+            } else if (type == SSH2_MSG_CHANNEL_CLOSE) {
+                if (chan->state == SENT_CLOSE) {
+                    ssh_delete_sharing_channel(cs->parent->ssh,
+                                               chan->upstream_id);
+                    share_remove_channel(cs, chan);
+                    if (!cs->sock) {
+                        /* Retry cleaning up this connection, in case this
+                         * channel closure was the last thing we were
+                         * waiting for. */
+                        share_try_cleanup(cs);
+                    }
+                } else {
+                    chan->state = RCVD_CLOSE;
+                }
+            }
+        } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id))
+                   != NULL) {
+            /*
+             * The unusual case: this id refers to an xchannel. Add it
+             * to the xchannel's queue.
+             */
+            struct share_xchannel_message *msg;
+
+            msg = share_xchannel_add_message(xc, type, pktlen);
+            memcpy(msg->data, pkt, pktlen);
+
+            /* If the xchannel is dead, then also respond to it (which
+             * may involve deleting the channel). */
+            if (!xc->live)
+                share_dead_xchannel_respond(cs, xc);
+        }
+        break;
+
+      default:
+        assert(!"This packet type should never have come from ssh.c");
+        break;
+    }
+}
+
+static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
+                                          int type,
+                                          unsigned char *pkt, int pktlen)
+{
+    char *request_name;
+    struct share_forwarding *fwd;
+    int id_pos;
+    unsigned old_id, new_id, server_id;
+    struct share_globreq *globreq;
+    struct share_channel *chan;
+    struct share_halfchannel *hc;
+    struct share_xchannel *xc;
+    char *err = NULL;
+
+    switch (type) {
+      case SSH2_MSG_DISCONNECT:
+        /*
+         * This message stops here: if downstream is disconnecting
+         * from us, that doesn't mean we want to disconnect from the
+         * SSH server. Close the downstream connection and start
+         * cleanup.
+         */
+        share_begin_cleanup(cs);
+        break;
+
+      case SSH2_MSG_GLOBAL_REQUEST:
+        /*
+         * The only global requests we understand are "tcpip-forward"
+         * and "cancel-tcpip-forward". Since those require us to
+         * maintain state, we must assume that other global requests
+         * will probably require that too, and so we don't forward on
+         * any request we don't understand.
+         */
+        request_name = getstring(pkt, pktlen);
+        if (request_name == NULL) {
+            err = dupprintf("Truncated GLOBAL_REQUEST packet");
+            goto confused;
+        }
+
+        if (!strcmp(request_name, "tcpip-forward")) {
+            int wantreplypos, orig_wantreply, port, ret;
+            char *host;
+
+            sfree(request_name);
+
+            /*
+             * Pick the packet apart to find the want_reply field and
+             * the host/port we're going to ask to listen on.
+             */
+            wantreplypos = getstring_size(pkt, pktlen);
+            if (wantreplypos < 0 || wantreplypos >= pktlen) {
+                err = dupprintf("Truncated GLOBAL_REQUEST packet");
+                goto confused;
+            }
+            orig_wantreply = pkt[wantreplypos];
+            port = getstring_size(pkt + (wantreplypos + 1),
+                                  pktlen - (wantreplypos + 1));
+            port += (wantreplypos + 1);
+            if (port < 0 || port > pktlen - 4) {
+                err = dupprintf("Truncated GLOBAL_REQUEST packet");
+                goto confused;
+            }
+            host = getstring(pkt + (wantreplypos + 1),
+                             pktlen - (wantreplypos + 1));
+            assert(host != NULL);
+            port = GET_32BIT(pkt + port);
+
+            /*
+             * See if we can allocate space in ssh.c's tree of remote
+             * port forwardings. If we can't, it's because another
+             * client sharing this connection has already allocated
+             * the identical port forwarding, so we take it on
+             * ourselves to manufacture a failure packet and send it
+             * back to downstream.
+             */
+            ret = ssh_alloc_sharing_rportfwd(cs->parent->ssh, host, port, cs);
+            if (!ret) {
+                if (orig_wantreply) {
+                    send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+                                              "", 0, NULL);
+                }
+            } else {
+                /*
+                 * We've managed to make space for this forwarding
+                 * locally. Pass the request on to the SSH server, but
+                 * set want_reply even if it wasn't originally set, so
+                 * that we know whether this forwarding needs to be
+                 * cleaned up if downstream goes away.
+                 */
+                int old_wantreply = pkt[wantreplypos];
+                pkt[wantreplypos] = 1;
+                ssh_send_packet_from_downstream
+                    (cs->parent->ssh, cs->id, type, pkt, pktlen,
+                     old_wantreply ? NULL : "upstream added want_reply flag");
+                fwd = share_add_forwarding(cs, host, port);
+                ssh_sharing_queue_global_request(cs->parent->ssh, cs);
+
+                if (fwd) {
+                    globreq = snew(struct share_globreq);
+                    globreq->next = NULL;
+                    if (cs->globreq_tail)
+                        cs->globreq_tail->next = globreq;
+                    else
+                        cs->globreq_head = globreq;
+                    globreq->fwd = fwd;
+                    globreq->want_reply = orig_wantreply;
+                    globreq->type = GLOBREQ_TCPIP_FORWARD;
+                }
+            }
+
+            sfree(host);
+        } else if (!strcmp(request_name, "cancel-tcpip-forward")) {
+            int wantreplypos, orig_wantreply, port;
+            char *host;
+            struct share_forwarding *fwd;
+
+            sfree(request_name);
+
+            /*
+             * Pick the packet apart to find the want_reply field and
+             * the host/port we're going to ask to listen on.
+             */
+            wantreplypos = getstring_size(pkt, pktlen);
+            if (wantreplypos < 0 || wantreplypos >= pktlen) {
+                err = dupprintf("Truncated GLOBAL_REQUEST packet");
+                goto confused;
+            }
+            orig_wantreply = pkt[wantreplypos];
+            port = getstring_size(pkt + (wantreplypos + 1),
+                                  pktlen - (wantreplypos + 1));
+            port += (wantreplypos + 1);
+            if (port < 0 || port > pktlen - 4) {
+                err = dupprintf("Truncated GLOBAL_REQUEST packet");
+                goto confused;
+            }
+            host = getstring(pkt + (wantreplypos + 1),
+                             pktlen - (wantreplypos + 1));
+            assert(host != NULL);
+            port = GET_32BIT(pkt + port);
+
+            /*
+             * Look up the existing forwarding with these details.
+             */
+            fwd = share_find_forwarding(cs, host, port);
+            if (!fwd) {
+                if (orig_wantreply) {
+                    send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+                                              "", 0, NULL);
+                }
+            } else {
+                /*
+                 * Pass the cancel request on to the SSH server, but
+                 * set want_reply even if it wasn't originally set, so
+                 * that _we_ know whether the forwarding has been
+                 * deleted even if downstream doesn't want to know.
+                 */
+                int old_wantreply = pkt[wantreplypos];
+                pkt[wantreplypos] = 1;
+                ssh_send_packet_from_downstream
+                    (cs->parent->ssh, cs->id, type, pkt, pktlen,
+                     old_wantreply ? NULL : "upstream added want_reply flag");
+                ssh_sharing_queue_global_request(cs->parent->ssh, cs);
+            }
+
+            sfree(host);
+        } else {
+            /*
+             * Request we don't understand. Manufacture a failure
+             * message if an answer was required.
+             */
+            int wantreplypos;
+
+            sfree(request_name);
+
+            wantreplypos = getstring_size(pkt, pktlen);
+            if (wantreplypos < 0 || wantreplypos >= pktlen) {
+                err = dupprintf("Truncated GLOBAL_REQUEST packet");
+                goto confused;
+            }
+            if (pkt[wantreplypos])
+                send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+                                          "", 0, NULL);
+        }
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN:
+        /* Sender channel id comes after the channel type string */
+        id_pos = getstring_size(pkt, pktlen);
+        if (id_pos < 0 || id_pos > pktlen - 12) {
+            err = dupprintf("Truncated CHANNEL_OPEN packet");
+            goto confused;
+        }
+
+        old_id = GET_32BIT(pkt + id_pos);
+        new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs);
+        share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED,
+                          GET_32BIT(pkt + id_pos + 8));
+        PUT_32BIT(pkt + id_pos, new_id);
+        ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                        type, pkt, pktlen, NULL);
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+        if (pktlen < 16) {
+            err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet");
+            goto confused;
+        }
+
+        id_pos = 4;  /* sender channel id is 2nd uint32 field in packet */
+        old_id = GET_32BIT(pkt + id_pos);
+
+        server_id = GET_32BIT(pkt);
+        /* This server id may refer to either a halfchannel or an xchannel. */
+        hc = NULL, xc = NULL;          /* placate optimiser */
+        if ((hc = share_find_halfchannel(cs, server_id)) != NULL) {
+            new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs);
+        } else if ((xc = share_find_xchannel_by_server(cs, server_id))
+                   != NULL) {
+            new_id = xc->upstream_id;
+        } else {
+            err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id);
+            goto confused;
+        }
+            
+        PUT_32BIT(pkt + id_pos, new_id);
+
+        chan = share_add_channel(cs, old_id, new_id, server_id, OPEN,
+                                 GET_32BIT(pkt + 12));
+
+        if (hc) {
+            ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                            type, pkt, pktlen, NULL);
+            share_remove_halfchannel(cs, hc);
+        } else if (xc) {
+            unsigned downstream_window = GET_32BIT(pkt + 8);
+            if (downstream_window < 256) {
+                err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window);
+                goto confused;
+            }
+            share_xchannel_confirmation(cs, xc, chan, downstream_window);
+            share_remove_xchannel(cs, xc);
+        }
+
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+        if (pktlen < 4) {
+            err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet");
+            goto confused;
+        }
+
+        server_id = GET_32BIT(pkt);
+        /* This server id may refer to either a halfchannel or an xchannel. */
+        if ((hc = share_find_halfchannel(cs, server_id)) != NULL) {
+            ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                            type, pkt, pktlen, NULL);
+            share_remove_halfchannel(cs, hc);
+        } else if ((xc = share_find_xchannel_by_server(cs, server_id))
+                   != NULL) {
+            share_xchannel_failure(cs, xc);
+        } else {
+            err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id);
+            goto confused;
+        }
+
+        break;
+
+      case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+      case SSH2_MSG_CHANNEL_DATA:
+      case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+      case SSH2_MSG_CHANNEL_EOF:
+      case SSH2_MSG_CHANNEL_CLOSE:
+      case SSH2_MSG_CHANNEL_REQUEST:
+      case SSH2_MSG_CHANNEL_SUCCESS:
+      case SSH2_MSG_CHANNEL_FAILURE:
+      case SSH2_MSG_IGNORE:
+      case SSH2_MSG_DEBUG:
+        if (type == SSH2_MSG_CHANNEL_REQUEST &&
+            (request_name = getstring(pkt + 4, pktlen - 4)) != NULL) {
+            /*
+             * Agent forwarding requests from downstream are treated
+             * specially. Because OpenSSHD doesn't let us enable agent
+             * forwarding independently per session channel, and in
+             * particular because the OpenSSH-defined agent forwarding
+             * protocol does not mark agent-channel requests with the
+             * id of the session channel they originate from, the only
+             * way we can implement agent forwarding in a
+             * connection-shared PuTTY is to forward the _upstream_
+             * agent. Hence, we unilaterally deny agent forwarding
+             * requests from downstreams if we aren't prepared to
+             * forward an agent ourselves.
+             *
+             * (If we are, then we dutifully pass agent forwarding
+             * requests upstream. OpenSSHD has the curious behaviour
+             * that all but the first such request will be rejected,
+             * but all session channels opened after the first request
+             * get agent forwarding enabled whether they ask for it or
+             * not; but that's not our concern, since other SSH
+             * servers supporting the same piece of protocol might in
+             * principle at least manage to enable agent forwarding on
+             * precisely the channels that requested it, even if the
+             * subsequent CHANNEL_OPENs still can't be associated with
+             * a parent session channel.)
+             */
+            if (!strcmp(request_name, "auth-agent-req@openssh.com") &&
+                !ssh_agent_forwarding_permitted(cs->parent->ssh)) {
+                unsigned server_id = GET_32BIT(pkt);
+                unsigned char recipient_id[4];
+                chan = share_find_channel_by_server(cs, server_id);
+                if (chan) {
+                    PUT_32BIT(recipient_id, chan->downstream_id);
+                    send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_FAILURE,
+                                              recipient_id, 4, NULL);
+                } else {
+                    char *buf = dupprintf("Agent forwarding request for "
+                                          "unrecognised channel %u", server_id);
+                    share_disconnect(cs, buf);
+                    sfree(buf);
+                    return;
+                }
+                break;
+            }
+
+            /*
+             * Another thing we treat specially is X11 forwarding
+             * requests. For these, we have to make up another set of
+             * X11 auth data, and enter it into our SSH connection's
+             * list of possible X11 authorisation credentials so that
+             * when we see an X11 channel open request we can know
+             * whether it's one to handle locally or one to pass on to
+             * a downstream, and if the latter, which one.
+             */
+            if (!strcmp(request_name, "x11-req")) {
+                unsigned server_id = GET_32BIT(pkt);
+                int want_reply, single_connection, screen;
+                char *auth_proto_str, *auth_data;
+                int auth_proto, protolen, datalen;
+                int pos;
+
+                chan = share_find_channel_by_server(cs, server_id);
+                if (!chan) {
+                    char *buf = dupprintf("X11 forwarding request for "
+                                          "unrecognised channel %u", server_id);
+                    share_disconnect(cs, buf);
+                    sfree(buf);
+                    return;
+                }
+
+                /*
+                 * Pick apart the whole message to find the downstream
+                 * auth details.
+                 */
+                /* we have already seen: 4 bytes channel id, 4+7 request name */
+                if (pktlen < 17) {
+                    err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet");
+                    goto confused;
+                }
+                want_reply = pkt[15] != 0;
+                single_connection = pkt[16] != 0;
+                auth_proto_str = getstring(pkt+17, pktlen-17);
+                pos = 17 + getstring_size(pkt+17, pktlen-17);
+                auth_data = getstring(pkt+pos, pktlen-pos);
+                pos += getstring_size(pkt+pos, pktlen-pos);
+                if (pktlen < pos+4) {
+                    err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet");
+                    goto confused;
+                }
+                screen = GET_32BIT(pkt+pos);
+
+                auth_proto = x11_identify_auth_proto(auth_proto_str);
+                if (auth_proto < 0) {
+                    /* Reject due to not understanding downstream's
+                     * requested authorisation method. */
+                    unsigned char recipient_id[4];
+                    PUT_32BIT(recipient_id, chan->downstream_id);
+                    send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_FAILURE,
+                                              recipient_id, 4, NULL);
+                }
+
+                chan->x11_auth_proto = auth_proto;
+                chan->x11_auth_data = x11_dehexify(auth_data,
+                                                   &chan->x11_auth_datalen);
+                chan->x11_auth_upstream =
+                    ssh_sharing_add_x11_display(cs->parent->ssh, auth_proto,
+                                                cs, chan);
+                chan->x11_one_shot = single_connection;
+
+                /*
+                 * Now construct a replacement X forwarding request,
+                 * containing our own auth data, and send that to the
+                 * server.
+                 */
+                protolen = strlen(chan->x11_auth_upstream->protoname);
+                datalen = strlen(chan->x11_auth_upstream->datastring);
+                pktlen = 29+protolen+datalen;
+                pkt = snewn(pktlen, unsigned char);
+                PUT_32BIT(pkt, server_id);
+                PUT_32BIT(pkt+4, 7);   /* strlen("x11-req") */
+                memcpy(pkt+8, "x11-req", 7);
+                pkt[15] = want_reply;
+                pkt[16] = single_connection;
+                PUT_32BIT(pkt+17, protolen);
+                memcpy(pkt+21, chan->x11_auth_upstream->protoname, protolen);
+                PUT_32BIT(pkt+21+protolen, datalen);
+                memcpy(pkt+25+protolen, chan->x11_auth_upstream->datastring,
+                       datalen);
+                PUT_32BIT(pkt+25+protolen+datalen, screen);
+                ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                                SSH2_MSG_CHANNEL_REQUEST,
+                                                pkt, pktlen, NULL);
+                sfree(pkt);
+
+                break;
+            }
+        }
+
+        ssh_send_packet_from_downstream(cs->parent->ssh, cs->id,
+                                        type, pkt, pktlen, NULL);
+        if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) {
+            server_id = GET_32BIT(pkt);
+            chan = share_find_channel_by_server(cs, server_id);
+            if (chan) {
+                if (chan->state == RCVD_CLOSE) {
+                    ssh_delete_sharing_channel(cs->parent->ssh,
+                                               chan->upstream_id);
+                    share_remove_channel(cs, chan);
+                } else {
+                    chan->state = SENT_CLOSE;
+                }
+            }
+        }
+        break;
+
+      default:
+        err = dupprintf("Unexpected packet type %d\n", type);
+        goto confused;
+
+        /*
+         * Any other packet type is unexpected. In particular, we
+         * never pass GLOBAL_REQUESTs downstream, so we never expect
+         * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}.
+         */
+      confused:
+        assert(err != NULL);
+        share_disconnect(cs, err);
+        sfree(err);
+        break;
+    }
+}
+
+/*
+ * Coroutine macros similar to, but simplified from, those in ssh.c.
+ */
+#define crBegin(v)     { int *crLine = &v; switch(v) { case 0:;
+#define crFinish(z)    } *crLine = 0; return (z); }
+#define crGetChar(c) do                                         \
+    {                                                           \
+        while (len == 0) {                                      \
+            *crLine =__LINE__; return 1; case __LINE__:;        \
+        }                                                       \
+        len--;                                                  \
+        (c) = (unsigned char)*data++;                           \
+    } while (0)
+
+static int share_receive(Plug plug, int urgent, char *data, int len)
+{
+    struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug;
+    static const char expected_verstring_prefix[] =
+        "SSHCONNECTION@putty.projects.tartarus.org-2.0-";
+    unsigned char c;
+
+    crBegin(cs->crLine);
+
+    /*
+     * First read the version string from downstream.
+     */
+    cs->recvlen = 0;
+    while (1) {
+        crGetChar(c);
+        if (c == '\012')
+            break;
+        if (cs->recvlen > sizeof(cs->recvbuf)) {
+            char *buf = dupprintf("Version string far too long\n");
+            share_disconnect(cs, buf);
+            sfree(buf);
+            goto dead;
+        }
+        cs->recvbuf[cs->recvlen++] = c;
+    }
+
+    /*
+     * Now parse the version string to make sure it's at least vaguely
+     * sensible, and log it.
+     */
+    if (cs->recvlen < sizeof(expected_verstring_prefix)-1 ||
+        memcmp(cs->recvbuf, expected_verstring_prefix,
+               sizeof(expected_verstring_prefix) - 1)) {
+        char *buf = dupprintf("Version string did not have expected prefix\n");
+        share_disconnect(cs, buf);
+        sfree(buf);
+        goto dead;
+    }
+    if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015')
+        cs->recvlen--;                 /* trim off \r before \n */
+    ssh_sharing_logf(cs->parent->ssh, cs->id,
+                     "Downstream version string: %.*s",
+                     cs->recvlen, cs->recvbuf);
+
+    /*
+     * Loop round reading packets.
+     */
+    while (1) {
+        cs->recvlen = 0;
+        while (cs->recvlen < 4) {
+            crGetChar(c);
+            cs->recvbuf[cs->recvlen++] = c;
+        }
+        cs->curr_packetlen = toint(GET_32BIT(cs->recvbuf) + 4);
+        if (cs->curr_packetlen < 5 ||
+            cs->curr_packetlen > sizeof(cs->recvbuf)) {
+            char *buf = dupprintf("Bad packet length %u\n",
+                                  (unsigned)cs->curr_packetlen);
+            share_disconnect(cs, buf);
+            sfree(buf);
+            goto dead;
+        }
+        while (cs->recvlen < cs->curr_packetlen) {
+            crGetChar(c);
+            cs->recvbuf[cs->recvlen++] = c;
+        }
+
+        share_got_pkt_from_downstream(cs, cs->recvbuf[4],
+                                      cs->recvbuf + 5, cs->recvlen - 5);
+    }
+
+  dead:;
+    crFinish(1);
+}
+
+static void share_sent(Plug plug, int bufsize)
+{
+    /* struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; */
+
+    /*
+     * We do nothing here, because we expect that there won't be a
+     * need to throttle and unthrottle the connection to a downstream.
+     * It should automatically throttle itself: if the SSH server
+     * sends huge amounts of data on all channels then it'll run out
+     * of window until our downstream sends it back some
+     * WINDOW_ADJUSTs.
+     */
+}
+
+static int share_listen_closing(Plug plug, const char *error_msg,
+                                int error_code, int calling_back)
+{
+    struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug;
+    if (error_msg)
+        ssh_sharing_logf(sharestate->ssh, 0,
+                         "listening socket: %s", error_msg);
+    sk_close(sharestate->listensock);
+    return 1;
+}
+
+static void share_send_verstring(struct ssh_sharing_connstate *cs)
+{
+    char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-",
+                              cs->parent->server_verstring, "\015\012", NULL);
+    sk_write(cs->sock, fullstring, strlen(fullstring));
+    sfree(fullstring);
+
+    cs->sent_verstring = TRUE;
+}
+
+int share_ndownstreams(void *state)
+{
+    struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state;
+    return count234(sharestate->connections);
+}
+
+void share_activate(void *state, const char *server_verstring)
+{
+    /*
+     * Indication from ssh.c that we are now ready to begin serving
+     * any downstreams that have already connected to us.
+     */
+    struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state;
+    struct ssh_sharing_connstate *cs;
+    int i;
+
+    /*
+     * Trim the server's version string down to just the software
+     * version component, removing "SSH-2.0-" or whatever at the
+     * front.
+     */
+    for (i = 0; i < 2; i++) {
+        server_verstring += strcspn(server_verstring, "-");
+        if (*server_verstring)
+            server_verstring++;
+    }
+
+    sharestate->server_verstring = dupstr(server_verstring);
+
+    for (i = 0; (cs = (struct ssh_sharing_connstate *)
+                 index234(sharestate->connections, i)) != NULL; i++) {
+        assert(!cs->sent_verstring);
+        share_send_verstring(cs);
+    }
+}
+
+static int share_listen_accepting(Plug plug,
+                                  accept_fn_t constructor, accept_ctx_t ctx)
+{
+    static const struct plug_function_table connection_fn_table = {
+       NULL, /* no log function, because that's for outgoing connections */
+       share_closing,
+        share_receive,
+        share_sent,
+       NULL /* no accepting function, because we've already done it */
+    };
+    struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug;
+    struct ssh_sharing_connstate *cs;
+    const char *err;
+
+    /*
+     * A new downstream has connected to us.
+     */
+    cs = snew(struct ssh_sharing_connstate);
+    cs->fn = &connection_fn_table;
+    cs->parent = sharestate;
+
+    if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 &&
+        (cs->id = share_find_unused_id(sharestate, 1)) == 0) {
+        sfree(cs);
+        return 1;
+    }
+    sharestate->nextid = cs->id + 1;
+    if (sharestate->nextid == 0)
+        sharestate->nextid++; /* only happens in VERY long-running upstreams */
+
+    cs->sock = constructor(ctx, (Plug) cs);
+    if ((err = sk_socket_error(cs->sock)) != NULL) {
+        sfree(cs);
+       return err != NULL;
+    }
+
+    sk_set_frozen(cs->sock, 0);
+
+    add234(cs->parent->connections, cs);
+
+    cs->sent_verstring = FALSE;
+    if (sharestate->server_verstring)
+        share_send_verstring(cs);
+
+    cs->got_verstring = FALSE;
+    cs->recvlen = 0;
+    cs->crLine = 0;
+    cs->halfchannels = newtree234(share_halfchannel_cmp);
+    cs->channels_by_us = newtree234(share_channel_us_cmp);
+    cs->channels_by_server = newtree234(share_channel_server_cmp);
+    cs->xchannels_by_us = newtree234(share_xchannel_us_cmp);
+    cs->xchannels_by_server = newtree234(share_xchannel_server_cmp);
+    cs->forwardings = newtree234(share_forwarding_cmp);
+    cs->globreq_head = cs->globreq_tail = NULL;
+
+    ssh_sharing_downstream_connected(sharestate->ssh, cs->id);
+
+    return 0;
+}
+
+/* Per-application overrides for what roles we can take (e.g. pscp
+ * will never be an upstream) */
+extern const int share_can_be_downstream;
+extern const int share_can_be_upstream;
+
+/*
+ * Init function for connection sharing. We either open a listening
+ * socket and become an upstream, or connect to an existing one and
+ * become a downstream, or do neither. We are responsible for deciding
+ * which of these to do (including checking the Conf to see if
+ * connection sharing is even enabled in the first place). If we
+ * become a downstream, we return the Socket with which we connected
+ * to the upstream; otherwise (whether or not we have established an
+ * upstream) we return NULL.
+ */
+Socket ssh_connection_sharing_init(const char *host, int port,
+                                   Conf *conf, Ssh ssh, void **state)
+{
+    static const struct plug_function_table listen_fn_table = {
+       NULL, /* no log function, because that's for outgoing connections */
+       share_listen_closing,
+        NULL, /* no receive function on a listening socket */
+        NULL, /* no sent function on a listening socket */
+       share_listen_accepting
+    };
+
+    int result, can_upstream, can_downstream;
+    char *logtext, *ds_err, *us_err;
+    char *sockname;
+    Socket sock;
+    struct ssh_sharing_state *sharestate;
+
+    if (!conf_get_int(conf, CONF_ssh_connection_sharing))
+        return NULL;                   /* do not share anything */
+    can_upstream = share_can_be_upstream &&
+        conf_get_int(conf, CONF_ssh_connection_sharing_upstream);
+    can_downstream = share_can_be_downstream &&
+        conf_get_int(conf, CONF_ssh_connection_sharing_downstream);
+    if (!can_upstream && !can_downstream)
+        return NULL;
+
+    /*
+     * Decide on the string used to identify the connection point
+     * between upstream and downstream (be it a Windows named pipe or
+     * a Unix-domain socket or whatever else).
+     *
+     * I wondered about making this a SHA hash of all sorts of pieces
+     * of the PuTTY configuration - essentially everything PuTTY uses
+     * to know where and how to make a connection, including all the
+     * proxy details (or rather, all the _relevant_ ones - only
+     * including settings that other settings didn't prevent from
+     * having any effect), plus the username. However, I think it's
+     * better to keep it really simple: the connection point
+     * identifier is derived from the hostname and port used to index
+     * the host-key cache (not necessarily where we _physically_
+     * connected to, in cases involving proxies or CONF_loghost), plus
+     * the username if one is specified.
+     */
+    {
+        char *username = get_remote_username(conf);
+
+        if (port == 22) {
+            if (username)
+                sockname = dupprintf("%s@%s", username, host);
+            else
+                sockname = dupprintf("%s", host);
+        } else {
+            if (username)
+                sockname = dupprintf("%s@%s:%d", username, host, port);
+            else
+                sockname = dupprintf("%s:%d", host, port);
+        }
+
+        sfree(username);
+
+        /*
+         * The platform-specific code may transform this further in
+         * order to conform to local namespace conventions (e.g. not
+         * using slashes in filenames), but that's its job and not
+         * ours.
+         */
+    }
+
+    /*
+     * Create a data structure for the listening plug if we turn out
+     * to be an upstream.
+     */
+    sharestate = snew(struct ssh_sharing_state);
+    sharestate->fn = &listen_fn_table;
+    sharestate->listensock = NULL;
+
+    /*
+     * Now hand off to a per-platform routine that either connects to
+     * an existing upstream (using 'ssh' as the plug), establishes our
+     * own upstream (using 'sharestate' as the plug), or forks off a
+     * separate upstream and then connects to that. It will return a
+     * code telling us which kind of socket it put in 'sock'.
+     */
+    sock = NULL;
+    logtext = ds_err = us_err = NULL;
+    result = platform_ssh_share(sockname, conf, (Plug)ssh,
+                                (Plug)sharestate, &sock, &logtext, &ds_err,
+                                &us_err, can_upstream, can_downstream);
+    ssh_connshare_log(ssh, result, logtext, ds_err, us_err);
+    sfree(logtext);
+    sfree(ds_err);
+    sfree(us_err);
+    switch (result) {
+      case SHARE_NONE:
+        /*
+         * We aren't sharing our connection at all (e.g. something
+         * went wrong setting the socket up). Free the upstream
+         * structure and return NULL.
+         */
+        assert(sock == NULL);
+        *state = NULL;
+        sfree(sharestate);
+        sfree(sockname);
+        return NULL;
+
+      case SHARE_DOWNSTREAM:
+        /*
+         * We are downstream, so free sharestate which it turns out we
+         * don't need after all, and return the downstream socket as a
+         * replacement for an ordinary SSH connection.
+         */
+        *state = NULL;
+        sfree(sharestate);
+        sfree(sockname);
+        return sock;
+
+      case SHARE_UPSTREAM:
+        /*
+         * We are upstream. Set up sharestate properly and pass a copy
+         * to the caller; return NULL, to tell ssh.c that it has to
+         * make an ordinary connection after all.
+         */
+        *state = sharestate;
+        sharestate->listensock = sock;
+        sharestate->connections = newtree234(share_connstate_cmp);
+        sharestate->ssh = ssh;
+        sharestate->server_verstring = NULL;
+        sharestate->sockname = dupstr(sockname);
+        sharestate->nextid = 1;
+        return NULL;
+    }
+
+    return NULL;
+}
index 3ceb985854f177e940ec58ce683c64dca2675b96..868f9d0a3153f2ca1d5245ff189d61daea7bfe70 100644 (file)
@@ -337,6 +337,15 @@ void sk_getaddr(SockAddr addr, char *buf, int buflen)
     }
 }
 
+int sk_addr_needs_port(SockAddr addr)
+{
+    if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) {
+        return FALSE;
+    } else {
+        return TRUE;
+    }
+}
+
 int sk_hostname_is_local(const char *name)
 {
     return !strcmp(name, "localhost") ||
index 87efe78a55fec59d0fe2d15571f01a28a727b063..81356d363fd07ff4a30f1717f203759b6cdfaf8d 100644 (file)
@@ -591,6 +591,9 @@ static void version(void)
 
 void frontend_net_error_pending(void) {}
 
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = TRUE;
+
 int main(int argc, char **argv)
 {
     int sending;
index fbaf029d3b769478903356b18283d205f588a14d..1e3604bae67b1f118fbc933b02b56decf809f9e5 100644 (file)
@@ -122,6 +122,9 @@ char *platform_get_x_display(void) {
     return dupstr(display);
 }
 
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = TRUE;
+
 int main(int argc, char **argv)
 {
     extern int pt_main(int argc, char **argv);
diff --git a/unix/uxshare.c b/unix/uxshare.c
new file mode 100644 (file)
index 0000000..8960372
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * Unix implementation of SSH connection-sharing IPC setup.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/file.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "ssh.h"
+
+#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
+
+/*
+ * Functions provided by uxnet.c to help connection sharing.
+ */
+SockAddr unix_sock_addr(const char *path);
+Socket new_unix_listener(SockAddr listenaddr, Plug plug);
+
+static char *make_dirname(const char *name, char **parent_out)
+{
+    char *username, *dirname, *parent;
+
+    username = get_username();
+    parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
+    sfree(username);
+    assert(*parent == '/');
+
+    dirname = dupprintf("%s/%s", parent, name);
+
+    if (parent_out)
+        *parent_out = parent;
+    else
+        sfree(parent);
+
+    return dirname;
+}
+
+static char *make_dir_and_check_ours(const char *dirname)
+{
+    struct stat st;
+
+    /*
+     * Create the directory. We might have created it before, so
+     * EEXIST is an OK error; but anything else is doom.
+     */
+    if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
+        return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
+
+    /*
+     * Now check that that directory is _owned by us_ and not writable
+     * by anybody else. This protects us against somebody else
+     * previously having created the directory in a way that's
+     * writable to us, and thus manipulating us into creating the
+     * actual socket in a directory they can see so that they can
+     * connect to it and use our authenticated SSH sessions.
+     */
+    if (stat(dirname, &st) < 0)
+        return dupprintf("%s: stat: %s", dirname, strerror(errno));
+    if (st.st_uid != getuid())
+        return dupprintf("%s: directory owned by uid %d, not by us",
+                         dirname, st.st_uid);
+    if ((st.st_mode & 077) != 0)
+        return dupprintf("%s: directory has overgenerous permissions %03o"
+                         " (expected 700)", dirname, st.st_mode & 0777);
+
+    return NULL;
+}
+
+int platform_ssh_share(const char *pi_name, Conf *conf,
+                       Plug downplug, Plug upplug, Socket *sock,
+                       char **logtext, char **ds_err, char **us_err,
+                       int can_upstream, int can_downstream)
+{
+    char *name, *parentdirname, *dirname, *lockname, *sockname, *err;
+    int lockfd;
+    Socket retsock;
+
+    /*
+     * Transform the platform-independent version of the connection
+     * identifier into something valid for a Unix socket, by escaping
+     * slashes (and, while we're here, any control characters).
+     */
+    {
+        const char *p;
+        char *q;
+
+        name = snewn(1+3*strlen(pi_name), char);
+
+        for (p = pi_name, q = name; *p; p++) {
+            if (*p == '/' || *p == '%' ||
+                (unsigned char)*p < 0x20 || *p == 0x7f) {
+                q += sprintf(q, "%%%02x", (unsigned char)*p);
+            } else {
+                *q++ = *p;
+            }
+        }
+        *q = '\0';
+    }
+
+    /*
+     * First, make sure our subdirectory exists. We must create two
+     * levels of directory - the one for this particular connection,
+     * and the containing one for our username.
+     */
+    dirname = make_dirname(name, &parentdirname);
+    if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
+        *logtext = err;
+        sfree(dirname);
+        sfree(parentdirname);
+        sfree(name);
+        return SHARE_NONE;
+    }
+    sfree(parentdirname);
+    if ((err = make_dir_and_check_ours(dirname)) != NULL) {
+        *logtext = err;
+        sfree(dirname);
+        sfree(name);
+        return SHARE_NONE;
+    }
+
+    /*
+     * Acquire a lock on a file in that directory.
+     */
+    lockname = dupcat(dirname, "/lock", (char *)NULL);
+    lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
+    if (lockfd < 0) {
+        *logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
+        sfree(dirname);
+        sfree(lockname);
+        sfree(name);
+        return SHARE_NONE;
+    }
+    if (flock(lockfd, LOCK_EX) < 0) {
+        *logtext = dupprintf("%s: flock(LOCK_EX): %s",
+                             lockname, strerror(errno));
+        sfree(dirname);
+        sfree(lockname);
+        close(lockfd);
+        sfree(name);
+        return SHARE_NONE;
+    }
+
+    sockname = dupprintf("%s/socket", dirname);
+
+    *logtext = NULL;
+
+    if (can_downstream) {
+        retsock = new_connection(unix_sock_addr(sockname),
+                                 "", 0, 0, 1, 0, 0, downplug, conf);
+        if (sk_socket_error(retsock) == NULL) {
+            sfree(*logtext);
+            *logtext = sockname;
+            *sock = retsock;
+            sfree(dirname);
+            sfree(lockname);
+            close(lockfd);
+            sfree(name);
+            return SHARE_DOWNSTREAM;
+        }
+        sfree(*ds_err);
+        *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
+        sk_close(retsock);
+    }
+
+    if (can_upstream) {
+        retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
+        if (sk_socket_error(retsock) == NULL) {
+            sfree(*logtext);
+            *logtext = sockname;
+            *sock = retsock;
+            sfree(dirname);
+            sfree(lockname);
+            close(lockfd);
+            sfree(name);
+            return SHARE_UPSTREAM;
+        }
+        sfree(*us_err);
+        *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
+        sk_close(retsock);
+    }
+
+    /* One of the above clauses ought to have happened. */
+    assert(*logtext || *ds_err || *us_err);
+
+    sfree(dirname);
+    sfree(lockname);
+    sfree(sockname);
+    close(lockfd);
+    sfree(name);
+    return SHARE_NONE;
+}
+
+void platform_ssh_share_cleanup(const char *name)
+{
+    char *dirname, *filename;
+
+    dirname = make_dirname(name, NULL);
+
+    filename = dupcat(dirname, "/socket", (char *)NULL);
+    remove(filename);
+    sfree(filename);
+
+    filename = dupcat(dirname, "/lock", (char *)NULL);
+    remove(filename);
+    sfree(filename);
+
+    rmdir(dirname);
+
+    /*
+     * We deliberately _don't_ clean up the parent directory
+     * /tmp/putty-connshare.<username>, because if we leave it around
+     * then it reduces the ability for other users to be a nuisance by
+     * putting their own directory in the way of it.
+     */
+
+    sfree(dirname);
+}
index bcf1ab811560e584fb8158b720f15aeb78853e86..bf212b9ab267df665327b82dc11ce8c58b398b65 100644 (file)
@@ -215,6 +215,9 @@ static UINT wm_mousewheel = WM_MOUSEWHEEL;
     (((wch) >= 0x180B && (wch) <= 0x180D) || /* MONGOLIAN FREE VARIATION SELECTOR */ \
      ((wch) >= 0xFE00 && (wch) <= 0xFE0F)) /* VARIATION SELECTOR 1-16 */
 
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = TRUE;
+
 /* Dummy routine, only required in plink. */
 void ldisc_update(void *frontend, int echo, int edit)
 {
index ca9c721ae282f77a8db37fa980014c6b09d26711..d81af0bc279eba3cd5c3f41f06aa4ac2b7fd8ad3 100644 (file)
@@ -99,6 +99,7 @@
 #define WINHELP_CTX_ssh_protocol "ssh.protocol:config-ssh-prot"
 #define WINHELP_CTX_ssh_command "ssh.command:config-command"
 #define WINHELP_CTX_ssh_compress "ssh.compress:config-ssh-comp"
+#define WINHELP_CTX_ssh_share "ssh.auth.gssapi:config-ssh-share"
 #define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order"
 #define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey"
 #define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth"
index aca37af753a027bead6edc77c0a890723b2fb520..f4507240ff5497248117a929b90936c6d65596f7 100644 (file)
@@ -81,6 +81,8 @@ struct SockAddr_tag {
     int refcount;
     char *error;
     int resolved;
+    int namedpipe; /* indicates that this SockAddr is phony, holding a Windows
+                    * named pipe pathname instead of a network address */
 #ifndef NO_IPV6
     struct addrinfo *ais;             /* Addresses IPv6 style. */
 #endif
@@ -504,6 +506,7 @@ SockAddr sk_namelookup(const char *host, char **canonicalname,
 #ifndef NO_IPV6
     ret->ais = NULL;
 #endif
+    ret->namedpipe = FALSE;
     ret->addresses = NULL;
     ret->resolved = FALSE;
     ret->refcount = 1;
@@ -610,6 +613,7 @@ SockAddr sk_nonamelookup(const char *host)
 #ifndef NO_IPV6
     ret->ais = NULL;
 #endif
+    ret->namedpipe = FALSE;
     ret->addresses = NULL;
     ret->naddresses = 0;
     ret->refcount = 1;
@@ -618,6 +622,23 @@ SockAddr sk_nonamelookup(const char *host)
     return ret;
 }
 
+SockAddr sk_namedpipe_addr(const char *pipename)
+{
+    SockAddr ret = snew(struct SockAddr_tag);
+    ret->error = NULL;
+    ret->resolved = FALSE;
+#ifndef NO_IPV6
+    ret->ais = NULL;
+#endif
+    ret->namedpipe = TRUE;
+    ret->addresses = NULL;
+    ret->naddresses = 0;
+    ret->refcount = 1;
+    strncpy(ret->hostname, pipename, lenof(ret->hostname));
+    ret->hostname[lenof(ret->hostname)-1] = '\0';
+    return ret;
+}
+
 int sk_nextaddr(SockAddr addr, SockAddrStep *step)
 {
 #ifndef NO_IPV6
@@ -671,6 +692,11 @@ void sk_getaddr(SockAddr addr, char *buf, int buflen)
     }
 }
 
+int sk_addr_needs_port(SockAddr addr)
+{
+    return addr->namedpipe ? FALSE : TRUE;
+}
+
 int sk_hostname_is_local(const char *name)
 {
     return !strcmp(name, "localhost") ||
index 90e2cbda66b48b710f669f7075f70da03b002b26..0e8ac6994dc5baa45e21fff76d84451a480f1a32 100644 (file)
@@ -30,15 +30,36 @@ Socket new_named_pipe_client(const char *pipename, Plug plug)
     assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0);
     assert(strchr(pipename + 9, '\\') == NULL);
 
-    pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
-                            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+    while (1) {
+        pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE,
+                                0, NULL, OPEN_EXISTING,
+                                FILE_FLAG_OVERLAPPED, NULL);
 
-    if (pipehandle == INVALID_HANDLE_VALUE) {
-        err = dupprintf("Unable to open named pipe '%s': %s",
-                        pipename, win_strerror(GetLastError()));
-        ret = new_error_socket(err, plug);
-        sfree(err);
-        return ret;
+        if (pipehandle != INVALID_HANDLE_VALUE)
+            break;
+
+        if (GetLastError() != ERROR_PIPE_BUSY) {
+            err = dupprintf("Unable to open named pipe '%s': %s",
+                            pipename, win_strerror(GetLastError()));
+            ret = new_error_socket(err, plug);
+            sfree(err);
+            return ret;
+        }
+
+        /*
+         * If we got ERROR_PIPE_BUSY, wait for the server to
+         * create a new pipe instance. (Since the server is
+         * expected to be winnps.c, which will do that immediately
+         * after a previous connection is accepted, that shouldn't
+         * take excessively long.)
+         */
+        if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) {
+            err = dupprintf("Error waiting for named pipe '%s': %s",
+                            pipename, win_strerror(GetLastError()));
+            ret = new_error_socket(err, plug);
+            sfree(err);
+            return ret;
+        }
     }
 
     if ((usersid = get_user_sid()) == NULL) {
@@ -49,10 +70,10 @@ Socket new_named_pipe_client(const char *pipename, Plug plug)
         return ret;
     }
 
-    if (GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT,
-                        OWNER_SECURITY_INFORMATION,
-                        &pipeowner, NULL, NULL, NULL,
-                        &psd) != ERROR_SUCCESS) {
+    if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT,
+                          OWNER_SECURITY_INFORMATION,
+                          &pipeowner, NULL, NULL, NULL,
+                          &psd) != ERROR_SUCCESS) {
         err = dupprintf("Unable to get named pipe security information: %s",
                         win_strerror(GetLastError()));
         ret = new_error_socket(err, plug);
index a013736726b3bba20c2004fbb352cf157f793c20..9cc8176fdefdc63091add8123af4886d3836127e 100644 (file)
@@ -14,7 +14,7 @@
 
 #if !defined NO_SECURITY
 
-#include <aclapi.h>
+#include "winsecur.h"
 
 Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug,
                           int overlapped);
@@ -118,6 +118,12 @@ static Socket named_pipe_accept(accept_ctx_t ctx, Plug plug)
     return make_handle_socket(conn, conn, plug, TRUE);
 }
 
+/*
+ * Dummy SockAddr type which just holds a named pipe address. Only
+ * used for calling plug_log from named_pipe_accept_loop() here.
+ */
+SockAddr sk_namedpipe_addr(const char *pipename);
+
 static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps,
                                    int got_one_already)
 {
@@ -178,7 +184,7 @@ static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps,
 
         errmsg = dupprintf("Error while listening to named pipe: %s",
                            win_strerror(error));
-        plug_log(ps->plug, 1, NULL /* FIXME: appropriate kind of sockaddr */, 0,
+        plug_log(ps->plug, 1, sk_namedpipe_addr(ps->pipename), 0,
                  errmsg, error);
         sfree(errmsg);
         break;
@@ -209,8 +215,6 @@ Socket new_named_pipe_listener(const char *pipename, Plug plug)
     };
 
     Named_Pipe_Server_Socket ret;
-    SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
-    EXPLICIT_ACCESS ea[2];
 
     ret = snew(struct Socket_named_pipe_server_tag);
     ret->fn = &socket_fn_table;
@@ -224,49 +228,9 @@ Socket new_named_pipe_listener(const char *pipename, Plug plug)
     assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0);
     assert(strchr(pipename + 9, '\\') == NULL);
 
-    if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID,
-                                  0, 0, 0, 0, 0, 0, 0, &ret->networksid)) {
-        ret->error = dupprintf("unable to construct SID for rejecting "
-                               "remote pipe connections: %s",
-                               win_strerror(GetLastError()));
-        goto cleanup;
-    }
-
-    memset(ea, 0, sizeof(ea));
-    ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
-    ea[0].grfAccessMode = GRANT_ACCESS;
-    ea[0].grfInheritance = NO_INHERITANCE;
-    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_NAME;
-    ea[0].Trustee.ptstrName = "CURRENT_USER";
-    ea[1].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
-    ea[1].grfAccessMode = REVOKE_ACCESS;
-    ea[1].grfInheritance = NO_INHERITANCE;
-    ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
-    ea[1].Trustee.ptstrName = (LPTSTR)ret->networksid;
-
-    if (SetEntriesInAcl(2, ea, NULL, &ret->acl) != ERROR_SUCCESS) {
-        ret->error = dupprintf("unable to construct ACL: %s",
-                               win_strerror(GetLastError()));
-        goto cleanup;
-    }
-
-    ret->psd = (PSECURITY_DESCRIPTOR)
-        LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
-    if (!ret->psd) {
-        ret->error = dupprintf("unable to allocate security descriptor: %s",
-                               win_strerror(GetLastError()));
-        goto cleanup;
-    }
-
-    if (!InitializeSecurityDescriptor(ret->psd,SECURITY_DESCRIPTOR_REVISION)) {
-        ret->error = dupprintf("unable to initialise security descriptor: %s",
-                               win_strerror(GetLastError()));
-        goto cleanup;
-    }
-
-    if (!SetSecurityDescriptorDacl(ret->psd, TRUE, ret->acl, FALSE)) {
-        ret->error = dupprintf("unable to set DACL in security descriptor: %s",
-                               win_strerror(GetLastError()));
+    if (!make_private_security_descriptor(GENERIC_READ | GENERIC_WRITE,
+                                          &ret->psd, &ret->networksid,
+                                          &ret->acl, &ret->error)) {
         goto cleanup;
     }
 
index 7e6a12fa2fddfd21513372ac8bb0447d91cd9c02..22b607881572d9a39e810e5d3ce6245b980b7bf9 100644 (file)
@@ -117,12 +117,6 @@ static void unmungestr(char *in, char *out, int outlen)
 static tree234 *rsakeys, *ssh2keys;
 
 static int has_security;
-#ifndef NO_SECURITY
-DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo,
-                     (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
-                      PSID *, PSID *, PACL *, PACL *,
-                      PSECURITY_DESCRIPTOR *));
-#endif
 
 /*
  * Forward references
index 5849e0d4a1b8e0caa20cd287949b2af92aafdf9c..ac7069a2e3fd966e5b8c376e7a06d9d5e411f685 100644 (file)
@@ -288,6 +288,9 @@ void stdouterr_sent(struct handle *h, int new_backlog)
     }
 }
 
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = TRUE;
+
 int main(int argc, char **argv)
 {
     int sending;
index 9271af5f91ada069b5afcf17e56056a749b27d78..05feafd273713bed44e0bf0f177a63ece2faa8cf 100644 (file)
@@ -26,7 +26,23 @@ int got_advapi(void)
             GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) &&
             GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) &&
             GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) &&
-            GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner);
+            GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) &&
+            GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA);
+    }
+    return successful;
+}
+
+int got_crypt(void)
+{
+    static int attempted = FALSE;
+    static int successful;
+    static HMODULE crypt;
+
+    if (!attempted) {
+        attempted = TRUE;
+        crypt = load_system32_dll("crypt32.dll");
+        successful = crypt &&
+            GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory);
     }
     return successful;
 }
@@ -83,4 +99,98 @@ PSID get_user_sid(void)
     return ret;
 }
 
+int make_private_security_descriptor(DWORD permissions,
+                                     PSECURITY_DESCRIPTOR *psd,
+                                     PSID *networksid,
+                                     PACL *acl,
+                                     char **error)
+{
+    SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
+    EXPLICIT_ACCESS ea[3];
+    int ret = FALSE;
+
+    *psd = NULL;
+    *networksid = NULL;
+    *acl = NULL;
+    *error = NULL;
+
+    if (!got_advapi()) {
+        *error = dupprintf("unable to load advapi32.dll");
+        goto cleanup;
+    }
+
+    if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID,
+                                  0, 0, 0, 0, 0, 0, 0, networksid)) {
+        *error = dupprintf("unable to construct SID for "
+                           "local same-user access only: %s",
+                           win_strerror(GetLastError()));
+        goto cleanup;
+    }
+
+    memset(ea, 0, sizeof(ea));
+    ea[0].grfAccessPermissions = permissions;
+    ea[0].grfAccessMode = REVOKE_ACCESS;
+    ea[0].grfInheritance = NO_INHERITANCE;
+    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_NAME;
+    ea[0].Trustee.ptstrName = "EVERYONE";
+    ea[1].grfAccessPermissions = permissions;
+    ea[1].grfAccessMode = GRANT_ACCESS;
+    ea[1].grfInheritance = NO_INHERITANCE;
+    ea[1].Trustee.TrusteeForm = TRUSTEE_IS_NAME;
+    ea[1].Trustee.ptstrName = "CURRENT_USER";
+    ea[2].grfAccessPermissions = permissions;
+    ea[2].grfAccessMode = REVOKE_ACCESS;
+    ea[2].grfInheritance = NO_INHERITANCE;
+    ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+    ea[2].Trustee.ptstrName = (LPTSTR)*networksid;
+
+    if (p_SetEntriesInAclA(2, ea, NULL, acl) != ERROR_SUCCESS || *acl == NULL) {
+        *error = dupprintf("unable to construct ACL: %s",
+                           win_strerror(GetLastError()));
+        goto cleanup;
+    }
+
+    *psd = (PSECURITY_DESCRIPTOR)
+        LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+    if (!*psd) {
+        *error = dupprintf("unable to allocate security descriptor: %s",
+                           win_strerror(GetLastError()));
+        goto cleanup;
+    }
+
+    if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) {
+        *error = dupprintf("unable to initialise security descriptor: %s",
+                           win_strerror(GetLastError()));
+        goto cleanup;
+    }
+
+    if (!SetSecurityDescriptorDacl(*psd, TRUE, *acl, FALSE)) {
+        *error = dupprintf("unable to set DACL in security descriptor: %s",
+                           win_strerror(GetLastError()));
+        goto cleanup;
+    }
+
+    ret = TRUE;
+
+  cleanup:
+    if (!ret) {
+        if (*psd) {
+            LocalFree(*psd);
+            *psd = NULL;
+        }
+        if (*networksid) {
+            LocalFree(*networksid);
+            *networksid = NULL;
+        }
+        if (*acl) {
+            LocalFree(*acl);
+            *acl = NULL;
+        }
+    } else {
+        sfree(*error);
+        *error = NULL;
+    }
+    return ret;
+}
+
 #endif /* !defined NO_SECURITY */
index 9844afc8a3a57a13c4550ddb64993b003532f42b..57de5d1de8c5f7c7c44aee94b67363d6b9097156 100644 (file)
@@ -12,6 +12,9 @@
 #define WINSECUR_GLOBAL extern
 #endif
 
+/*
+ * Functions loaded from advapi32.dll.
+ */
 DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, OpenProcessToken,
                      (HANDLE, DWORD, PHANDLE));
 DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, GetTokenInformation,
@@ -25,8 +28,38 @@ DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, GetSecurityInfo,
                      (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
                       PSID *, PSID *, PACL *, PACL *,
                       PSECURITY_DESCRIPTOR *));
-
+DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetEntriesInAclA,
+                     (ULONG, PEXPLICIT_ACCESS, PACL, PACL *));
 int got_advapi(void);
+
+/*
+ * Functions loaded from crypt32.dll.
+ */
+DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, CryptProtectMemory,
+                     (LPVOID, DWORD, DWORD));
+int got_crypt(void);
+
+/*
+ * Find the SID describing the current user. The return value (if not
+ * NULL for some error-related reason) is smalloced.
+ */
 PSID get_user_sid(void);
 
+/*
+ * Construct a PSECURITY_DESCRIPTOR of the type used for named pipe
+ * servers, i.e. allowing access only to the current user id and also
+ * only local (i.e. not over SMB) connections.
+ *
+ * If this function returns TRUE, then 'psd', 'networksid' and 'acl'
+ * will all have been filled in with memory allocated using LocalAlloc
+ * (and hence must be freed later using LocalFree). If it returns
+ * FALSE, then instead 'error' has been filled with a dynamically
+ * allocated error message.
+ */
+int make_private_security_descriptor(DWORD permissions,
+                                     PSECURITY_DESCRIPTOR *psd,
+                                     PSID *networksid,
+                                     PACL *acl,
+                                     char **error);
+
 #endif
diff --git a/windows/winshare.c b/windows/winshare.c
new file mode 100644 (file)
index 0000000..ad1cea4
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Windows implementation of SSH connection-sharing IPC setup.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "ssh.h"
+
+#if !defined NO_SECURITY
+
+#include "winsecur.h"
+
+#define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare"
+#define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex"
+
+static char *obfuscate_name(const char *realname)
+{
+    /*
+     * Windows's named pipes all live in the same namespace, so one
+     * user can see what pipes another user has open. This is an
+     * undesirable privacy leak and in particular permits one user to
+     * know what username@host another user is SSHing to, so we
+     * protect that information by using CryptProtectMemory (which
+     * uses a key built in to each user's account).
+     */
+    char *cryptdata;
+    int cryptlen;
+    SHA256_State sha;
+    unsigned char lenbuf[4];
+    unsigned char digest[32];
+    char retbuf[65];
+    int i;
+
+    cryptlen = strlen(realname) + 1;
+    cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1;
+    cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE;
+    cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE;
+
+    cryptdata = snewn(cryptlen, char);
+    memset(cryptdata, 0, cryptlen);
+    strcpy(cryptdata, realname);
+
+    /*
+     * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to
+     * use the same key in all processes with this user id, meaning
+     * that the next PuTTY process calling this function with the same
+     * input will get the same data.
+     *
+     * (Contrast with CryptProtectData, which invents a new session
+     * key every time since its API permits returning more data than
+     * was input, so calling _that_ and hashing the output would not
+     * be stable.)
+     */
+    if (!p_CryptProtectMemory(cryptdata, cryptlen,
+                              CRYPTPROTECTMEMORY_CROSS_PROCESS)) {
+        return NULL;
+    }
+
+    /*
+     * We don't want to give away the length of the hostname either,
+     * so having got it back out of CryptProtectMemory we now hash it.
+     */
+    SHA256_Init(&sha);
+    PUT_32BIT_MSB_FIRST(lenbuf, cryptlen);
+    SHA256_Bytes(&sha, lenbuf, 4);
+    SHA256_Bytes(&sha, cryptdata, cryptlen);
+    SHA256_Final(&sha, digest);
+
+    sfree(cryptdata);
+
+    /*
+     * Finally, make printable.
+     */
+    for (i = 0; i < 32; i++) {
+        sprintf(retbuf + 2*i, "%02x", digest[i]);
+        /* the last of those will also write the trailing NUL */
+    }
+
+    return dupstr(retbuf);
+}
+
+static char *make_name(const char *prefix, const char *name)
+{
+    char *username, *retname;
+
+    username = get_username();
+    retname = dupprintf("%s.%s.%s", prefix, username, name);
+    sfree(username);
+
+    return retname;
+}
+
+Socket new_named_pipe_client(const char *pipename, Plug plug);
+Socket new_named_pipe_listener(const char *pipename, Plug plug);
+
+int platform_ssh_share(const char *pi_name, Conf *conf,
+                       Plug downplug, Plug upplug, Socket *sock,
+                       char **logtext, char **ds_err, char **us_err,
+                       int can_upstream, int can_downstream)
+{
+    char *name, *mutexname, *pipename;
+    HANDLE mutex;
+    Socket retsock;
+    PSECURITY_DESCRIPTOR psd;
+    PACL acl;
+    PSID networksid;
+
+    if (!got_crypt()) {
+        *logtext = dupprintf("Unable to load crypt32.dll");
+        return SHARE_NONE;
+    }
+
+    /*
+     * Transform the platform-independent version of the connection
+     * identifier into the obfuscated version we'll use for our
+     * Windows named pipe and mutex. A side effect of doing this is
+     * that it also eliminates any characters illegal in Windows pipe
+     * names.
+     */
+    name = obfuscate_name(pi_name);
+    if (!name) {
+        *logtext = dupprintf("Unable to call CryptProtectMemory: %s",
+                             win_strerror(GetLastError()));
+        return SHARE_NONE;
+    }
+
+    /*
+     * Make a mutex name out of the connection identifier, and lock it
+     * while we decide whether to be upstream or downstream.
+     */
+    {
+        SECURITY_ATTRIBUTES sa;
+
+        mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name);
+        if (!make_private_security_descriptor(MUTEX_ALL_ACCESS,
+                                              &psd, &networksid,
+                                              &acl, logtext)) {
+            sfree(mutexname);
+            return SHARE_NONE;
+        }
+
+        memset(&sa, 0, sizeof(sa));
+        sa.nLength = sizeof(sa);
+        sa.lpSecurityDescriptor = psd;
+        sa.bInheritHandle = FALSE;
+
+        mutex = CreateMutex(&sa, FALSE, mutexname);
+
+        if (!mutex) {
+            *logtext = dupprintf("CreateMutex(\"%s\") failed: %s",
+                                 mutexname, win_strerror(GetLastError()));
+            sfree(mutexname);
+            LocalFree(psd);
+            LocalFree(networksid);
+            LocalFree(acl);
+            return SHARE_NONE;
+        }
+
+        sfree(mutexname);
+        LocalFree(psd);
+        LocalFree(networksid);
+        LocalFree(acl);
+
+        WaitForSingleObject(mutex, INFINITE);
+    }
+
+    pipename = make_name(CONNSHARE_PIPE_PREFIX, name);
+
+    *logtext = NULL;
+
+    if (can_downstream) {
+        retsock = new_named_pipe_client(pipename, downplug);
+        if (sk_socket_error(retsock) == NULL) {
+            sfree(*logtext);
+            *logtext = pipename;
+            *sock = retsock;
+            sfree(name);
+            ReleaseMutex(mutex);
+            CloseHandle(mutex);
+            return SHARE_DOWNSTREAM;
+        }
+        sfree(*ds_err);
+        *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
+        sk_close(retsock);
+    }
+
+    if (can_upstream) {
+        retsock = new_named_pipe_listener(pipename, upplug);
+        if (sk_socket_error(retsock) == NULL) {
+            sfree(*logtext);
+            *logtext = pipename;
+            *sock = retsock;
+            sfree(name);
+            ReleaseMutex(mutex);
+            CloseHandle(mutex);
+            return SHARE_UPSTREAM;
+        }
+        sfree(*us_err);
+        *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
+        sk_close(retsock);
+    }
+
+    /* One of the above clauses ought to have happened. */
+    assert(*logtext || *ds_err || *us_err);
+
+    sfree(pipename);
+    sfree(name);
+    ReleaseMutex(mutex);
+    CloseHandle(mutex);
+    return SHARE_NONE;
+}
+
+void platform_ssh_share_cleanup(const char *name)
+{
+}
+
+#else /* !defined NO_SECURITY */
+
+#include "noshare.c"
+
+#endif /* !defined NO_SECURITY */
index 54656f3a457e4091d3a66298b1e8e654de414717..ce572f69a274e3ecd0b5df88d6b91f157ea1a9c3 100644 (file)
--- a/x11fwd.c
+++ b/x11fwd.c
@@ -75,6 +75,31 @@ struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype)
     struct X11FakeAuth *auth = snew(struct X11FakeAuth);
     int i;
 
+    /*
+     * This function has the job of inventing a set of X11 fake auth
+     * data, and adding it to 'authtree'. We must preserve the
+     * property that for any given actual authorisation attempt, _at
+     * most one_ thing in the tree can possibly match it.
+     *
+     * For MIT-MAGIC-COOKIE-1, that's not too difficult: the match
+     * criterion is simply that the entire cookie is correct, so we
+     * just have to make sure we don't make up two cookies the same.
+     * (Vanishingly unlikely, but we check anyway to be sure, and go
+     * round again inventing a new cookie if add234 tells us the one
+     * we thought of is already in use.)
+     *
+     * For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup
+     * with XA1 is that half the cookie is used as a DES key with
+     * which to CBC-encrypt an assortment of stuff. Happily, the stuff
+     * encrypted _begins_ with the other half of the cookie, and the
+     * IV is always zero, which means that any valid XA1 authorisation
+     * attempt for a given cookie must begin with the same cipher
+     * block, consisting of the DES ECB encryption of the first half
+     * of the cookie using the second half as a key. So we compute
+     * that cipher block here and now, and use it as the sorting key
+     * for distinguishing XA1 entries in the tree.
+     */
+
     if (authtype == X11_MIT) {
        auth->proto = X11_MIT;
 
@@ -118,6 +143,9 @@ struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype)
        sprintf(auth->datastring + i*2, "%02x",
                auth->data[i]);
 
+    auth->disp = NULL;
+    auth->share_cs = auth->share_chan = NULL;
+
     return auth;
 }
 
@@ -342,10 +370,20 @@ static char *x11_verify(unsigned long peer_ip, int peer_port,
      * record that _might_ match.
      */
     if (!strcmp(proto, x11_authnames[X11_MIT])) {
+        /*
+         * Just look up the whole cookie that was presented to us,
+         * which x11_authcmp will compare against the cookies we
+         * currently believe in.
+         */
         match_dummy.proto = X11_MIT;
         match_dummy.datalen = dlen;
         match_dummy.data = data;
     } else if (!strcmp(proto, x11_authnames[X11_XDM])) {
+        /*
+         * Look up the first cipher block, against the stored first
+         * cipher blocks for the XDM-AUTHORIZATION-1 cookies we
+         * currently know. (See comment in x11_invent_fake_auth.)
+         */
         match_dummy.proto = X11_XDM;
         match_dummy.xa1_firstblock = data;
     } else {
@@ -843,6 +881,7 @@ int x11_send(struct X11Connection *xconn, char *data, int len)
 
        xconn->auth_protocol[xconn->auth_plen] = '\0';  /* ASCIZ */
 
+        peer_ip = 0;                   /* placate optimiser */
         if (x11_parse_ip(xconn->peer_addr, &peer_ip))
             peer_port = xconn->peer_port;
         else
@@ -857,10 +896,25 @@ int x11_send(struct X11Connection *xconn, char *data, int len)
         }
         assert(auth_matched);
 
+        /*
+         * If this auth points to a connection-sharing downstream
+         * rather than an X display we know how to connect to
+         * directly, pass it off to the sharing module now.
+         */
+        if (auth_matched->share_cs) {
+            sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs,
+                                        auth_matched->share_chan,
+                                        xconn->peer_addr, xconn->peer_port,
+                                        xconn->firstpkt[0],
+                                        protomajor, protominor, data, len);
+            return 0;
+        }
+
         /*
          * Now we know we're going to accept the connection, and what
          * X display to connect to. Actually connect to it.
          */
+        sshfwd_x11_is_local(xconn->c);
         xconn->disp = auth_matched->disp;
         xconn->s = new_connection(sk_addr_dup(xconn->disp->addr),
                                   xconn->disp->realhost, xconn->disp->port, 
@@ -930,6 +984,44 @@ void x11_send_eof(struct X11Connection *xconn)
             sshfwd_write_eof(xconn->c);
     }
 }
+
+/*
+ * Utility functions used by connection sharing to convert textual
+ * representations of an X11 auth protocol name + hex cookie into our
+ * usual integer protocol id and binary auth data.
+ */
+int x11_identify_auth_proto(const char *protoname)
+{
+    int protocol;
+
+    for (protocol = 1; protocol < lenof(x11_authnames); protocol++)
+        if (!strcmp(protoname, x11_authnames[protocol]))
+            return protocol;
+    return -1;
+}
+
+void *x11_dehexify(const char *hex, int *outlen)
+{
+    int len, i;
+    unsigned char *ret;
+
+    len = strlen(hex) / 2;
+    ret = snewn(len, unsigned char);
+
+    for (i = 0; i < len; i++) {
+        char bytestr[3];
+        unsigned val = 0;
+        bytestr[0] = hex[2*i];
+        bytestr[1] = hex[2*i+1];
+        bytestr[2] = '\0';
+        sscanf(bytestr, "%x", &val);
+        ret[i] = val;
+    }
+
+    *outlen = len;
+    return ret;
+}
+
 /*
  * Construct an X11 greeting packet, including making up the right
  * authorisation data.
@@ -969,7 +1061,8 @@ void *x11_make_greeting(int endian, int protomajor, int protominor,
         t = time(NULL);
         PUT_32BIT_MSB_FIRST(realauthdata+14, t);
 
-        des_encrypt_xdmauth(auth_data + 9, realauthdata, authdatalen);
+        des_encrypt_xdmauth((const unsigned char *)auth_data + 9,
+                            realauthdata, authdatalen);
     } else {
         authdata = realauthdata;
         authdatalen = 0;