aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/plink/callback.c74
-rw-r--r--tools/plink/sshshare.c2103
-rw-r--r--tools/plink/winsecur.c215
3 files changed, 2392 insertions, 0 deletions
diff --git a/tools/plink/callback.c b/tools/plink/callback.c
new file mode 100644
index 000000000..c70dc53fb
--- /dev/null
+++ b/tools/plink/callback.c
@@ -0,0 +1,74 @@
+/*
+ * Facility for queueing callback functions to be run from the
+ * top-level event loop after the current top-level activity finishes.
+ */
+
+#include <stddef.h>
+
+#include "putty.h"
+
+struct callback {
+ struct callback *next;
+
+ toplevel_callback_fn_t fn;
+ void *ctx;
+};
+
+struct callback *cbhead = NULL, *cbtail = NULL;
+
+toplevel_callback_notify_fn_t notify_frontend = NULL;
+void *frontend = NULL;
+
+void request_callback_notifications(toplevel_callback_notify_fn_t fn,
+ void *fr)
+{
+ notify_frontend = fn;
+ frontend = fr;
+}
+
+void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx)
+{
+ struct callback *cb;
+
+ cb = snew(struct callback);
+ cb->fn = fn;
+ cb->ctx = ctx;
+
+ /* If the front end has requested notification of pending
+ * callbacks, and we didn't already have one queued, let it know
+ * we do have one now. */
+ if (notify_frontend && !cbhead)
+ notify_frontend(frontend);
+
+ if (cbtail)
+ cbtail->next = cb;
+ else
+ cbhead = cb;
+ cbtail = cb;
+ cb->next = NULL;
+}
+
+void run_toplevel_callbacks(void)
+{
+ if (cbhead) {
+ struct callback *cb = cbhead;
+ /*
+ * Careful ordering here. We call the function _before_
+ * advancing cbhead (though, of course, we must free cb
+ * _after_ advancing it). This means that if the very last
+ * callback schedules another callback, cbhead does not become
+ * NULL at any point, and so the frontend notification
+ * function won't be needlessly pestered.
+ */
+ cb->fn(cb->ctx);
+ cbhead = cb->next;
+ sfree(cb);
+ if (!cbhead)
+ cbtail = NULL;
+ }
+}
+
+int toplevel_callback_pending(void)
+{
+ return cbhead != NULL;
+}
diff --git a/tools/plink/sshshare.c b/tools/plink/sshshare.c
new file mode 100644
index 000000000..bd4602b5b
--- /dev/null
+++ b/tools/plink/sshshare.c
@@ -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;
+}
diff --git a/tools/plink/winsecur.c b/tools/plink/winsecur.c
new file mode 100644
index 000000000..d04e88a92
--- /dev/null
+++ b/tools/plink/winsecur.c
@@ -0,0 +1,215 @@
+/*
+ * winsecur.c: implementation of winsecur.h.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+#if !defined NO_SECURITY
+
+#define WINSECUR_GLOBAL
+#include "winsecur.h"
+
+int got_advapi(void)
+{
+ static int attempted = FALSE;
+ static int successful;
+ static HMODULE advapi;
+
+ if (!attempted) {
+ attempted = TRUE;
+ advapi = load_system32_dll("advapi32.dll");
+ successful = advapi &&
+ GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) &&
+ GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) &&
+ GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) &&
+ GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) &&
+ 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;
+}
+
+PSID get_user_sid(void)
+{
+ HANDLE proc = NULL, tok = NULL;
+ TOKEN_USER *user = NULL;
+ DWORD toklen, sidlen;
+ PSID sid = NULL, ret = NULL;
+
+ if (!got_advapi())
+ goto cleanup;
+
+ if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
+ GetCurrentProcessId())) == NULL)
+ goto cleanup;
+
+ if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok))
+ goto cleanup;
+
+ if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) &&
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ goto cleanup;
+
+ if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL)
+ goto cleanup;
+
+ if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen))
+ goto cleanup;
+
+ sidlen = GetLengthSid(user->User.Sid);
+
+ sid = (PSID)smalloc(sidlen);
+
+ if (!CopySid(sidlen, sid, user->User.Sid))
+ goto cleanup;
+
+ /* Success. Move sid into the return value slot, and null it out
+ * to stop the cleanup code freeing it. */
+ ret = sid;
+ sid = NULL;
+
+ cleanup:
+ if (proc != NULL)
+ CloseHandle(proc);
+ if (tok != NULL)
+ CloseHandle(tok);
+ if (user != NULL)
+ LocalFree(user);
+ if (sid != NULL)
+ sfree(sid);
+
+ return ret;
+}
+
+int make_private_security_descriptor(DWORD permissions,
+ PSECURITY_DESCRIPTOR *psd,
+ PACL *acl,
+ char **error)
+{
+ SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY;
+ SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
+ EXPLICIT_ACCESS ea[3];
+ int acl_err;
+ int ret = FALSE;
+
+ /* Initialised once, then kept around to reuse forever */
+ static PSID worldsid, networksid, usersid;
+
+ *psd = NULL;
+ *acl = NULL;
+ *error = NULL;
+
+ if (!got_advapi()) {
+ *error = dupprintf("unable to load advapi32.dll");
+ goto cleanup;
+ }
+
+ if (!usersid) {
+ if ((usersid = get_user_sid()) == NULL) {
+ *error = dupprintf("unable to construct SID for current user: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+ }
+
+ if (!worldsid) {
+ if (!AllocateAndInitializeSid(&world_auth, 1, SECURITY_WORLD_RID,
+ 0, 0, 0, 0, 0, 0, 0, &worldsid)) {
+ *error = dupprintf("unable to construct SID for world: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+ }
+
+ if (!networksid) {
+ 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_SID;
+ ea[0].Trustee.ptstrName = (LPTSTR)worldsid;
+ ea[1].grfAccessPermissions = permissions;
+ ea[1].grfAccessMode = GRANT_ACCESS;
+ ea[1].grfInheritance = NO_INHERITANCE;
+ ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[1].Trustee.ptstrName = (LPTSTR)usersid;
+ 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;
+
+ acl_err = p_SetEntriesInAclA(3, ea, NULL, acl);
+ if (acl_err != ERROR_SUCCESS || *acl == NULL) {
+ *error = dupprintf("unable to construct ACL: %s",
+ win_strerror(acl_err));
+ 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 (*acl) {
+ LocalFree(*acl);
+ *acl = NULL;
+ }
+ } else {
+ sfree(*error);
+ *error = NULL;
+ }
+ return ret;
+}
+
+#endif /* !defined NO_SECURITY */