12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201 |
- /*
- * 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"
- 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);
- }
- if (cs->sock)
- sk_close(cs->sock);
- 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);
- 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 = 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");
- sfree(packet);
- 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) {
- #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
- ssh_sharing_logf(cs->parent->ssh, cs->id,
- "Socket error: %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];
- sfree(request_name);
- 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;
- sfree(request_name);
- 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);
- auth_proto = x11_identify_auth_proto(auth_proto_str);
- sfree(auth_proto_str);
- 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");
- sfree(auth_data);
- goto confused;
- }
- screen = GET_32BIT(pkt+pos);
- 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);
- sfree(auth_data);
- break;
- }
- chan->x11_auth_proto = auth_proto;
- chan->x11_auth_data = x11_dehexify(auth_data,
- &chan->x11_auth_datalen);
- sfree(auth_data);
- 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;
- }
- sfree(request_name);
- }
- 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);
- 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(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);
- sharestate->listensock = NULL;
- 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;
- char *peerinfo;
- /*
- * 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;
- peerinfo = sk_peer_info(cs->sock);
- ssh_sharing_downstream_connected(sharestate->ssh, cs->id, peerinfo);
- sfree(peerinfo);
- 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;
- /*
- * 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 = get_remote_username(conf);
- char *sockname;
- 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;
- }
- static void nullplug_socket_log(Plug plug, int type, SockAddr addr, int port,
- const char *error_msg, int error_code) {}
- static int nullplug_closing(Plug plug, const char *error_msg, int error_code,
- int calling_back) { return 0; }
- static int nullplug_receive(Plug plug, int urgent, char *data,
- int len) { return 0; }
- static void nullplug_sent(Plug plug, int bufsize) {}
- int ssh_share_test_for_upstream(const char *host, int port, Conf *conf)
- {
- static const struct plug_function_table fn_table = {
- nullplug_socket_log,
- nullplug_closing,
- nullplug_receive,
- nullplug_sent,
- NULL
- };
- struct nullplug {
- const struct plug_function_table *fn;
- } np;
- char *sockname, *logtext, *ds_err, *us_err;
- int result;
- Socket sock;
- np.fn = &fn_table;
- sockname = ssh_share_sockname(host, port, conf);
- sock = NULL;
- logtext = ds_err = us_err = NULL;
- result = platform_ssh_share(sockname, conf, (Plug)&np, (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;
- }
- }
- /*
- * 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;
- 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->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 = sockname;
- sharestate->nextid = 1;
- return NULL;
- }
- return NULL;
- }
|