diff options
Diffstat (limited to 'sshshare.c')
-rw-r--r-- | sshshare.c | 2180 |
1 files changed, 0 insertions, 2180 deletions
diff --git a/sshshare.c b/sshshare.c deleted file mode 100644 index b5da8657..00000000 --- a/sshshare.c +++ /dev/null @@ -1,2180 +0,0 @@ -/* - * 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 <errno.h> - -#include "putty.h" -#include "tree234.h" -#include "ssh.h" -#include "sshcr.h" - -struct ssh_sharing_state { - 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 */ - ConnectionLayer *cl; /* instance of the ssh connection layer */ - char *server_verstring; /* server version string after "SSH-" */ - - Plug plug; -}; - -struct share_globreq; - -struct ssh_sharing_connstate { - 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 */ - - bool sent_verstring, got_verstring; - int curr_packetlen; - - unsigned char recvbuf[0x4010]; - size_t 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; - - Plug plug; -}; - -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; - bool x11_one_shot; -}; - -struct share_forwarding { - char *host; - int port; - bool active; /* has the server sent REQUEST_SUCCESS? */ - struct ssh_rportfwd *rpf; -}; - -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. - */ - bool 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; - bool 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); - } - - if (cs->sock) - sk_close(cs->sock); - - sfree(cs); -} - -void sharestate_free(ssh_sharing_state *sharestate) -{ - 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); - if (sharestate->listensock) { - sk_close(sharestate->listensock); - sharestate->listensock = NULL; - } - 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 = false; - 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_remove_sharing_x11_display(cs->parent->cl, - 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 PRINTF_LIKE(2, 3) void log_downstream(struct ssh_sharing_connstate *cs, - const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - logeventf(cs->parent->cl->logctx, - "Connection sharing downstream #%u: %s", cs->id, buf); - sfree(buf); -} - -static PRINTF_LIKE(2, 3) void log_general(struct ssh_sharing_state *sharestate, - const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - logeventf(sharestate->cl->logctx, "Connection sharing: %s", buf); - sfree(buf); -} - -static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, - int type, const void *pkt, int pktlen, - struct share_channel *chan) -{ - strbuf *packet; - - 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. - */ - BinarySource src[1]; - unsigned channel; - ptrlen data; - - BinarySource_BARE_INIT(src, pkt, pktlen); - channel = get_uint32(src); - data = get_string(src); - - do { - int this_len = (data.len > chan->downstream_maxpkt ? - chan->downstream_maxpkt : data.len); - - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* placeholder for length field */ - put_byte(packet, type); - put_uint32(packet, channel); - put_uint32(packet, this_len); - put_data(packet, data.ptr, this_len); - data.ptr = (const char *)data.ptr + this_len; - data.len -= this_len; - PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); - sk_write(cs->sock, packet->s, packet->len); - strbuf_free(packet); - } while (data.len > 0); - } else { - /* - * Just do the obvious thing. - */ - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* placeholder for length field */ - put_byte(packet, type); - put_data(packet, pkt, pktlen); - PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); - sk_write(cs->sock, packet->s, packet->len); - strbuf_free(packet); - } -} - -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"; - strbuf *packet; - - packet = strbuf_new(); - put_uint32(packet, hc->server_id); - put_uint32(packet, SSH2_OPEN_CONNECT_FAILED); - put_stringz(packet, reason); - put_stringz(packet, lang); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_OPEN_FAILURE, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - 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++) { - strbuf *packet; - - if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { - packet = strbuf_new(); - put_uint32(packet, chan->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - 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->cl, 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) { - strbuf *packet = strbuf_new(); - put_stringz(packet, "cancel-tcpip-forward"); - put_bool(packet, false); /* !want_reply */ - put_stringz(packet, fwd->host); - put_uint32(packet, fwd->port); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_GLOBAL_REQUEST, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); - 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) { - struct ssh_sharing_state *sharestate = cs->parent; - - /* - * Now we're _really_ done, so we can get rid of cs completely. - */ - del234(sharestate->connections, cs); - log_downstream(cs, "disconnected"); - share_connstate_free(cs); - - /* - * And if this was the last downstream, notify the connection - * layer, because it might now be time to wind up the whole - * SSH connection. - */ - if (count234(sharestate->connections) == 0 && sharestate->cl) - ssh_sharing_no_more_downstreams(sharestate->cl); - } -} - -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) -{ - strbuf *packet = strbuf_new(); - put_uint32(packet, SSH2_DISCONNECT_PROTOCOL_ERROR); - put_stringz(packet, message); - put_stringz(packet, "en"); /* language */ - send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, - packet->s, packet->len, NULL); - strbuf_free(packet); - - share_begin_cleanup(cs); -} - -static void share_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - struct ssh_sharing_connstate *cs = container_of( - plug, struct ssh_sharing_connstate, plug); - - if (error_msg) { -#ifdef BROKEN_PIPE_ERROR_CODE - /* - * Most of the time, we log what went wrong when a downstream - * disappears with a socket error. One exception, though, is - * receiving EPIPE when we haven't received a protocol version - * string from the downstream, because that can happen as a result - * of plink -shareexists (opening the connection and instantly - * closing it again without bothering to read our version string). - * So that one case is not treated as a log-worthy error. - */ - if (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring) - /* do nothing */; - else -#endif - log_downstream(cs, "Socket error: %s", error_msg); - } - share_begin_cleanup(cs); -} - -/* - * Append a message to the end of an xchannel's queue. - */ -static void share_xchannel_add_message( - struct share_xchannel *xc, int type, const void *data, int len) -{ - struct share_xchannel_message *msg; - - /* - * Allocate the 'struct share_xchannel_message' and the actual - * data in one unit. - */ - msg = snew_plus(struct share_xchannel_message, len); - msg->data = snew_plus_get_aux(msg); - msg->datalen = len; - msg->type = type; - memcpy(msg->data, data, len); - - /* - * Queue it in the xchannel. - */ - if (xc->msgtail) - xc->msgtail->next = msg; - else - xc->msghead = msg; - msg->next = NULL; - xc->msgtail = 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). - */ - bool 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. - */ - BinarySource src[1]; - BinarySource_BARE_INIT(src, msg->data, msg->datalen); - get_uint32(src); /* skip channel id */ - get_string(src); /* skip request type */ - if (get_bool(src)) { - strbuf *packet = strbuf_new(); - put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, - "downstream refused X channel open"); - strbuf_free(packet); - } - } 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->cl, 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) -{ - strbuf *packet; - - /* - * 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_MSB_FIRST(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. - */ - packet = strbuf_new(); - put_uint32(packet, xc->server_id); - put_uint32(packet, downstream_window - xc->window); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_WINDOW_ADJUST, - packet->s, packet->len, - "window adjustment after downstream accepted X channel"); - strbuf_free(packet); -} - -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. - */ - strbuf *packet = strbuf_new(); - put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, - packet->s, packet->len, - "downstream refused X channel open"); - strbuf_free(packet); - - /* - * 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(ssh_sharing_connstate *cs, share_channel *chan, - 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 share_xchannel *xc; - void *greeting; - int greeting_len; - strbuf *packet; - - /* - * 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); - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* leave the channel id field unfilled - we - * don't know the downstream id yet */ - put_uint32(packet, greeting_len + initial_len); - put_data(packet, greeting, greeting_len); - put_data(packet, initial_data, initial_len); - sfree(greeting); - share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, - packet->s, packet->len); - strbuf_free(packet); - - xc->window = client_adjusted_window + greeting_len; - - /* - * Send on a CHANNEL_OPEN to downstream. - */ - packet = strbuf_new(); - put_stringz(packet, "x11"); - put_uint32(packet, server_id); - put_uint32(packet, server_currwin); - put_uint32(packet, server_maxpkt); - put_stringz(packet, peer_addr); - put_uint32(packet, peer_port); - send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, - packet->s, packet->len, NULL); - strbuf_free(packet); - - /* - * If this was a once-only X forwarding, clean it up now. - */ - if (chan->x11_one_shot) { - ssh_remove_sharing_x11_display(cs->parent->cl, - 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 = false; - } -} - -void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, - const void *vpkt, int pktlen) -{ - const unsigned char *pkt = (const unsigned char *)vpkt; - struct share_globreq *globreq; - size_t id_pos; - unsigned upstream_id, server_id; - struct share_channel *chan; - struct share_xchannel *xc; - BinarySource src[1]; - - BinarySource_BARE_INIT(src, pkt, pktlen); - - switch (type) { - case SSH2_MSG_REQUEST_SUCCESS: - case SSH2_MSG_REQUEST_FAILURE: - globreq = cs->globreq_head; - assert(globreq); /* should match the queue in ssh.c */ - 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: - get_string(src); - server_id = get_uint32(src); - assert(!get_err(src)); - 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. - */ - id_pos = src->pos; - upstream_id = get_uint32(src); - if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) { - /* - * The normal case: this id refers to an open channel. - */ - unsigned char *rewritten = snewn(pktlen, unsigned char); - memcpy(rewritten, pkt, pktlen); - PUT_32BIT_MSB_FIRST(rewritten + id_pos, chan->downstream_id); - send_packet_to_downstream(cs, type, rewritten, pktlen, chan); - sfree(rewritten); - - /* - * 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_MSB_FIRST(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->cl, 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->cl, - 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. - */ - share_xchannel_add_message(xc, type, 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: - unreachable("This packet type should never have come from ssh.c"); - } -} - -static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, - int type, - unsigned char *pkt, int pktlen) -{ - ptrlen request_name; - struct share_forwarding *fwd; - size_t id_pos; - unsigned maxpkt; - unsigned old_id, new_id, server_id; - struct share_globreq *globreq; - struct share_channel *chan; - struct share_halfchannel *hc; - struct share_xchannel *xc; - strbuf *packet; - char *err = NULL; - BinarySource src[1]; - size_t wantreplypos; - bool orig_wantreply; - - BinarySource_BARE_INIT(src, pkt, pktlen); - - 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 = get_string(src); - wantreplypos = src->pos; - orig_wantreply = get_bool(src); - - if (ptrlen_eq_string(request_name, "tcpip-forward")) { - ptrlen hostpl; - char *host; - int port; - struct ssh_rportfwd *rpf; - - /* - * Pick the packet apart to find the want_reply field and - * the host/port we're going to ask to listen on. - */ - hostpl = get_string(src); - port = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = mkstr(hostpl); - - /* - * 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. - */ - rpf = ssh_rportfwd_alloc( - cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs); - if (!rpf) { - 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. - */ - pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); - fwd = share_add_forwarding(cs, host, port); - ssh_sharing_queue_global_request(cs->parent->cl, 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; - - fwd->rpf = rpf; - } - } - - sfree(host); - } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) { - ptrlen hostpl; - char *host; - int port; - struct share_forwarding *fwd; - - /* - * Pick the packet apart to find the want_reply field and - * the host/port we're going to ask to listen on. - */ - hostpl = get_string(src); - port = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = mkstr(hostpl); - - /* - * 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 { - /* - * Tell ssh.c to stop sending us channel-opens for - * this forwarding. - */ - ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); - - /* - * 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. - */ - pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); - ssh_sharing_queue_global_request(cs->parent->cl, cs); - - /* - * And queue a globreq so that when the reply comes - * back we know to cancel it. - */ - 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_CANCEL_TCPIP_FORWARD; - } - - sfree(host); - } else { - /* - * Request we don't understand. Manufacture a failure - * message if an answer was required. - */ - if (orig_wantreply) - 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 */ - get_string(src); - id_pos = src->pos; - old_id = get_uint32(src); - new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); - get_uint32(src); /* skip initial window size */ - maxpkt = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN packet"); - goto confused; - } - share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt); - PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); - ssh_send_packet_from_downstream(cs->parent->cl, 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; - } - - server_id = get_uint32(src); - id_pos = src->pos; - old_id = get_uint32(src); - get_uint32(src); /* skip initial window size */ - maxpkt = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); - goto confused; - } - - /* 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->cl, 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_MSB_FIRST(pkt + id_pos, new_id); - - chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt); - - if (hc) { - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - share_remove_halfchannel(cs, hc); - } else if (xc) { - unsigned downstream_window = GET_32BIT_MSB_FIRST(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: - server_id = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); - goto confused; - } - - /* 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->cl, 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: - server_id = get_uint32(src); - - if (type == SSH2_MSG_CHANNEL_REQUEST) { - request_name = get_string(src); - - /* - * 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 (ptrlen_eq_string(request_name, "auth-agent-req@openssh.com") && - !ssh_agent_forwarding_permitted(cs->parent->cl)) { - - chan = share_find_channel_by_server(cs, server_id); - if (chan) { - packet = strbuf_new(); - put_uint32(packet, chan->downstream_id); - send_packet_to_downstream( - cs, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, NULL); - strbuf_free(packet); - } 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 (ptrlen_eq_string(request_name, "x11-req")) { - bool want_reply, single_connection; - int screen; - ptrlen auth_data; - int auth_proto; - - 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. - */ - want_reply = get_bool(src); - single_connection = get_bool(src); - auth_proto = x11_identify_auth_proto(get_string(src)); - auth_data = get_string(src); - screen = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_REQUEST(\"x11-req\")" - " packet"); - goto confused; - } - - if (auth_proto < 0) { - /* Reject due to not understanding downstream's - * requested authorisation method. */ - packet = strbuf_new(); - put_uint32(packet, chan->downstream_id); - send_packet_to_downstream( - cs, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, NULL); - strbuf_free(packet); - break; - } - - chan->x11_auth_proto = auth_proto; - chan->x11_auth_data = x11_dehexify(auth_data, - &chan->x11_auth_datalen); - chan->x11_auth_upstream = - ssh_add_sharing_x11_display(cs->parent->cl, 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. - */ - packet = strbuf_new_nm(); - put_uint32(packet, server_id); - put_stringz(packet, "x11-req"); - put_bool(packet, want_reply); - put_bool(packet, single_connection); - put_stringz(packet, chan->x11_auth_upstream->protoname); - put_stringz(packet, chan->x11_auth_upstream->datastring); - put_uint32(packet, screen); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_REQUEST, - packet->s, packet->len, NULL); - strbuf_free(packet); - - break; - } - } - - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { - chan = share_find_channel_by_server(cs, server_id); - if (chan) { - if (chan->state == RCVD_CLOSE) { - ssh_delete_sharing_channel(cs->parent->cl, - 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; - } -} - -/* - * An extra coroutine macro, specific to this code which is consuming - * 'const char *data'. - */ -#define crGetChar(c) do \ - { \ - while (len == 0) { \ - *crLine =__LINE__; return; case __LINE__:; \ - } \ - len--; \ - (c) = (unsigned char)*data++; \ - } while (0) - -static void share_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - ssh_sharing_connstate *cs = container_of( - plug, 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 */ - ptrlen verstring = make_ptrlen(cs->recvbuf, cs->recvlen); - log_downstream(cs, "Downstream version string: %.*s", - PTRLEN_PRINTF(verstring)); - cs->got_verstring = true; - - /* - * 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_MSB_FIRST(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:; - crFinishV; -} - -static void share_sent(Plug *plug, size_t bufsize) -{ - /* ssh_sharing_connstate *cs = container_of( - plug, 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 void share_listen_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) -{ - ssh_sharing_state *sharestate = - container_of(plug, ssh_sharing_state, plug); - if (error_msg) - log_general(sharestate, "listening socket: %s", error_msg); - sk_close(sharestate->listensock); - sharestate->listensock = NULL; -} - -static void share_send_verstring(ssh_sharing_connstate *cs) -{ - char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-", - cs->parent->server_verstring, "\015\012"); - sk_write(cs->sock, fullstring, strlen(fullstring)); - sfree(fullstring); - - cs->sent_verstring = true; -} - -int share_ndownstreams(ssh_sharing_state *sharestate) -{ - return count234(sharestate->connections); -} - -void share_activate(ssh_sharing_state *sharestate, - 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_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 const PlugVtable ssh_sharing_conn_plugvt = { - .closing = share_closing, - .receive = share_receive, - .sent = share_sent, -}; - -static int share_listen_accepting(Plug *plug, - accept_fn_t constructor, accept_ctx_t ctx) -{ - struct ssh_sharing_state *sharestate = container_of( - plug, struct ssh_sharing_state, plug); - struct ssh_sharing_connstate *cs; - const char *err; - SocketPeerInfo *peerinfo; - - /* - * A new downstream has connected to us. - */ - cs = snew(struct ssh_sharing_connstate); - cs->plug.vt = &ssh_sharing_conn_plugvt; - 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, &cs->plug); - if ((err = sk_socket_error(cs->sock)) != NULL) { - sfree(cs); - return err != NULL; - } - - sk_set_frozen(cs->sock, false); - - 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; - - peerinfo = sk_peer_info(cs->sock); - log_downstream(cs, "connected%s%s", - (peerinfo && peerinfo->log_text ? " from " : ""), - (peerinfo && peerinfo->log_text ? peerinfo->log_text : "")); - sk_free_peer_info(peerinfo); - - return 0; -} - -/* - * 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. - * - * The per-platform code will quite likely hash or obfuscate this name - * in turn, for privacy from other users; failing that, it might - * transform it to avoid dangerous filename characters and so on. But - * that doesn't matter to us: for us, the point is that two session - * configurations which return the same string from this function will - * be treated as potentially shareable with each other. - */ -char *ssh_share_sockname(const char *host, int port, Conf *conf) -{ - char *username = NULL; - char *sockname; - - /* Include the username we're logging in as in the hash, unless - * we're using a protocol for which it's completely irrelevant. */ - if (conf_get_int(conf, CONF_protocol) != PROT_SSHCONN) - 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); - return sockname; -} - -bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf) -{ - char *sockname, *logtext, *ds_err, *us_err; - int result; - Socket *sock; - - sockname = ssh_share_sockname(host, port, conf); - - sock = NULL; - logtext = ds_err = us_err = NULL; - result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock, - &logtext, &ds_err, &us_err, false, true); - - sfree(logtext); - sfree(ds_err); - sfree(us_err); - sfree(sockname); - - if (result == SHARE_NONE) { - assert(sock == NULL); - return false; - } else { - assert(result == SHARE_DOWNSTREAM); - sk_close(sock); - return true; - } -} - -static const PlugVtable ssh_sharing_listen_plugvt = { - .closing = share_listen_closing, - .accepting = share_listen_accepting, -}; - -void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, - ConnectionLayer *cl) -{ - sharestate->cl = cl; -} - -/* - * 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, LogContext *logctx, - Plug *sshplug, ssh_sharing_state **state) -{ - int result; - bool can_upstream, can_downstream; - char *logtext, *ds_err, *us_err; - char *sockname; - Socket *sock, *toret = NULL; - struct ssh_sharing_state *sharestate; - - if (!conf_get_bool(conf, CONF_ssh_connection_sharing)) - return NULL; /* do not share anything */ - can_upstream = share_can_be_upstream && - conf_get_bool(conf, CONF_ssh_connection_sharing_upstream); - can_downstream = share_can_be_downstream && - conf_get_bool(conf, CONF_ssh_connection_sharing_downstream); - if (!can_upstream && !can_downstream) - return NULL; - - sockname = ssh_share_sockname(host, port, conf); - - /* - * Create a data structure for the listening plug if we turn out - * to be an upstream. - */ - sharestate = snew(struct ssh_sharing_state); - sharestate->plug.vt = &ssh_sharing_listen_plugvt; - sharestate->listensock = NULL; - sharestate->cl = 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, sshplug, &sharestate->plug, &sock, &logtext, - &ds_err, &us_err, can_upstream, can_downstream); - 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. - */ - - if (logtext) { - /* For this result, if 'logtext' is not NULL then it is an - * error message indicating a reason why connection sharing - * couldn't be set up _at all_ */ - logeventf(logctx, - "Could not set up connection sharing: %s", logtext); - } else { - /* Failing that, ds_err and us_err indicate why we - * couldn't be a downstream and an upstream respectively */ - if (ds_err) - logeventf(logctx, "Could not set up connection sharing" - " as downstream: %s", ds_err); - if (us_err) - logeventf(logctx, "Could not set up connection sharing" - " as upstream: %s", us_err); - } - - assert(sock == NULL); - *state = NULL; - sfree(sharestate); - sfree(sockname); - break; - - 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. - */ - - /* 'logtext' is a local endpoint address */ - logeventf(logctx, "Using existing shared connection at %s", logtext); - - *state = NULL; - sfree(sharestate); - sfree(sockname); - toret = sock; - break; - - 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. - */ - - /* 'logtext' is a local endpoint address */ - logeventf(logctx, "Sharing this connection at %s", logtext); - - *state = sharestate; - sharestate->listensock = sock; - sharestate->connections = newtree234(share_connstate_cmp); - sharestate->server_verstring = NULL; - sharestate->sockname = sockname; - sharestate->nextid = 1; - break; - } - - sfree(logtext); - sfree(ds_err); - sfree(us_err); - return toret; -} |