From 6a8b9d38130503917cb2d2a7f2a036f3b8e616fc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 12 Sep 2018 15:03:47 +0100 Subject: [PATCH] Replace enum+union of local channel types with a vtable. There's now an interface called 'Channel', which handles the local side of an SSH connection-layer channel, in terms of knowing where to send incoming channel data to, whether to close the channel, etc. Channel and the previous 'struct ssh_channel' mutually refer. The latter contains all the SSH-specific parts, and as much of the common logic as possible: in particular, Channel doesn't have to know anything about SSH packet formats, or which SSH protocol version is in use, or deal with all the fiddly stuff about window sizes - with the exception that x11fwd.c's implementation of it does have to be able to ask for a small fixed initial window size for the bodgy system that distinguishes upstream from downstream X forwardings. I've taken the opportunity to move the code implementing the detailed behaviour of agent forwarding out of ssh.c, now that all of it is on the far side of a uniform interface. (This also means that if I later implement agent forwarding directly to a Unix socket as an alternative, it'll be a matter of changing just the one call to agentf_new() that makes the Channel to plug into a forwarding.) --- Recipe | 2 +- agentf.c | 235 +++++++++++++++ defs.h | 2 + portfwd.c | 143 +++++---- ssh.c | 783 +++++++++++++++++++++----------------------------- ssh.h | 31 +- sshchan.h | 59 ++++ unix/uxpgnt.c | 6 +- x11fwd.c | 93 +++--- 9 files changed, 785 insertions(+), 569 deletions(-) create mode 100644 agentf.c create mode 100644 sshchan.h diff --git a/Recipe b/Recipe index 1b84885b..5af98117 100644 --- a/Recipe +++ b/Recipe @@ -254,7 +254,7 @@ SSH = ssh ssh1bpp ssh2bpp ssh2bpp-bare ssh1censor ssh2censor + sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd + sshaes sshccp sshsh256 sshsh512 sshbn wildcard pinger ssharcf - + sshgssc pgssapi sshshare sshecc aqsync marshal nullplug + + sshgssc pgssapi sshshare sshecc aqsync marshal nullplug agentf WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc + winhsock errsock UXSSH = SSH uxnoise uxagentc uxgss uxshare diff --git a/agentf.c b/agentf.c new file mode 100644 index 00000000..65a971da --- /dev/null +++ b/agentf.c @@ -0,0 +1,235 @@ +/* + * SSH agent forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "pageant.h" +#include "sshchan.h" + +typedef struct agentf { + struct ssh_channel *c; + bufchain inbuffer; + agent_pending_query *pending; + int input_wanted; + int rcvd_eof; + + Channel chan; +} agentf; + +static void agentf_got_response(agentf *af, void *reply, int replylen) +{ + af->pending = NULL; + + if (!reply) { + /* The real agent didn't send any kind of reply at all for + * some reason, so fake an SSH_AGENT_FAILURE. */ + reply = "\0\0\0\1\5"; + replylen = 5; + } + + sshfwd_write(af->c, reply, replylen); +} + +static void agentf_callback(void *vctx, void *reply, int replylen); + +static void agentf_try_forward(agentf *af) +{ + unsigned datalen, length; + strbuf *message; + unsigned char msglen[4]; + void *reply; + int replylen; + + /* + * Don't try to parallelise agent requests. Wait for each one to + * return before attempting the next. + */ + if (af->pending) + return; + + /* + * If the outgoing side of the channel connection is currently + * throttled, don't submit any new forwarded requests to the real + * agent. This causes the input side of the agent forwarding not + * to be emptied, exerting the required back-pressure on the + * remote client, and encouraging it to read our responses before + * sending too many more requests. + */ + if (!af->input_wanted) + return; + + while (1) { + /* + * Try to extract a complete message from the input buffer. + */ + datalen = bufchain_size(&af->inbuffer); + if (datalen < 4) + break; /* not even a length field available yet */ + + bufchain_fetch(&af->inbuffer, msglen, 4); + length = GET_32BIT(msglen); + + if (length > AGENT_MAX_MSGLEN-4) { + /* + * If the remote has sent a message that's just _too_ + * long, we should reject it in advance of seeing the rest + * of the incoming message, and also close the connection + * for good measure (which avoids us having to faff about + * with carefully ignoring just the right number of bytes + * from the overlong message). + */ + agentf_got_response(af, NULL, 0); + sshfwd_write_eof(af->c); + return; + } + + if (length > datalen - 4) + break; /* a whole message is not yet available */ + + bufchain_consume(&af->inbuffer, 4); + + message = strbuf_new_for_agent_query(); + bufchain_fetch_consume( + &af->inbuffer, strbuf_append(message, length), length); + af->pending = agent_query( + message, &reply, &replylen, agentf_callback, af); + strbuf_free(message); + + if (af->pending) + return; /* agent_query promised to reply in due course */ + + /* + * If the agent gave us an answer immediately, pass it + * straight on and go round this loop again. + */ + agentf_got_response(af, reply, replylen); + sfree(reply); + } + + /* + * If we get here (i.e. we left the above while loop via 'break' + * rather than 'return'), that means we've determined that the + * input buffer for the agent forwarding connection doesn't + * contain a complete request. + * + * So if there's potentially more data to come, we can return now, + * and wait for the remote client to send it. But if the remote + * has sent EOF, it would be a mistake to do that, because we'd be + * waiting a long time. So this is the moment to check for EOF, + * and respond appropriately. + */ + if (af->rcvd_eof) + sshfwd_write_eof(af->c); +} + +static void agentf_callback(void *vctx, void *reply, int replylen) +{ + agentf *af = (agentf *)vctx; + + agentf_got_response(af, reply, replylen); + sfree(reply); + + /* + * Now try to extract and send further messages from the channel's + * input-side buffer. + */ + agentf_try_forward(af); +} + +static void agentf_free(Channel *chan); +static int agentf_send(Channel *chan, int is_stderr, const void *, int); +static void agentf_send_eof(Channel *chan); +static char *agentf_log_close_msg(Channel *chan); +static void agentf_set_input_wanted(Channel *chan, int wanted); + +static const struct ChannelVtable agentf_channelvt = { + agentf_free, + chan_remotely_opened_confirmation, + chan_remotely_opened_failure, + agentf_send, + agentf_send_eof, + agentf_set_input_wanted, + agentf_log_close_msg, + chan_no_eager_close, +}; + +Channel *agentf_new(struct ssh_channel *c) +{ + agentf *af = snew(agentf); + af->c = c; + af->chan.vt = &agentf_channelvt; + af->chan.initial_fixed_window_size = 0; + af->rcvd_eof = TRUE; + bufchain_init(&af->inbuffer); + af->pending = NULL; + af->input_wanted = TRUE; + return &af->chan; +} + +static void agentf_free(Channel *chan) +{ + assert(chan->vt == &agentf_channelvt); + agentf *af = FROMFIELD(chan, agentf, chan); + + if (af->pending) + agent_cancel_query(af->pending); + bufchain_clear(&af->inbuffer); + sfree(af); +} + +static int agentf_send(Channel *chan, int is_stderr, + const void *data, int length) +{ + assert(chan->vt == &agentf_channelvt); + agentf *af = FROMFIELD(chan, agentf, chan); + bufchain_add(&af->inbuffer, data, length); + agentf_try_forward(af); + + /* + * We exert back-pressure on an agent forwarding client if and + * only if we're waiting for the response to an asynchronous agent + * request. This prevents the client running out of window while + * receiving the _first_ message, but means that if any message + * takes time to process, the client will be discouraged from + * sending an endless stream of further ones after it. + */ + return (af->pending ? bufchain_size(&af->inbuffer) : 0); +} + +static void agentf_send_eof(Channel *chan) +{ + assert(chan->vt == &agentf_channelvt); + agentf *af = FROMFIELD(chan, agentf, chan); + + af->rcvd_eof = TRUE; + + /* Call try_forward, which will respond to the EOF now if + * appropriate, or wait until the queue of outstanding requests is + * dealt with if not. */ + agentf_try_forward(af); +} + +static char *agentf_log_close_msg(Channel *chan) +{ + return dupstr("Agent-forwarding connection closed"); +} + +static void agentf_set_input_wanted(Channel *chan, int wanted) +{ + assert(chan->vt == &agentf_channelvt); + agentf *af = FROMFIELD(chan, agentf, chan); + + af->input_wanted = wanted; + + /* Agent forwarding channels are buffer-managed by not asking the + * agent questions if the SSH channel isn't accepting input. So if + * it's started again, we should ask a question if we have one + * pending.. */ + if (wanted) + agentf_try_forward(af); +} diff --git a/defs.h b/defs.h index 53ded962..2c597953 100644 --- a/defs.h +++ b/defs.h @@ -53,6 +53,8 @@ typedef struct Frontend Frontend; typedef struct ssh_tag *Ssh; +typedef struct Channel Channel; + /* Note indirection: for historical reasons (it used to be closer to * the OS socket type), the type that most code uses for a socket is * 'Socket', not 'Socket *'. So an implementation of Socket or Plug diff --git a/portfwd.c b/portfwd.c index e6b06e14..d7ad74d2 100644 --- a/portfwd.c +++ b/portfwd.c @@ -8,6 +8,7 @@ #include "putty.h" #include "ssh.h" +#include "sshchan.h" /* * Enumeration of values that live in the 'socks_state' field of @@ -21,12 +22,12 @@ typedef enum { SOCKS_5_CONNECT /* expect a SOCKS 5 connection message */ } SocksState; -struct PortForwarding { +typedef struct PortForwarding { struct ssh_channel *c; /* channel structure held by ssh.c */ Ssh ssh; /* instance of SSH backend itself */ /* Note that ssh need not be filled in if c is non-NULL */ Socket s; - int throttled, throttle_override; + int input_wanted; int ready; SocksState socks_state; /* @@ -44,7 +45,8 @@ struct PortForwarding { size_t socksbuf_consumed; const Plug_vtable *plugvt; -}; + Channel chan; +} PortForwarding; struct PortListener { Ssh ssh; /* instance of SSH backend itself */ @@ -105,6 +107,8 @@ static void pfl_log(Plug plug, int type, SockAddr addr, int port, /* we have to dump these since we have no interface to logging.c */ } +static void pfd_close(struct PortForwarding *pf); + static void pfd_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { @@ -142,10 +146,12 @@ static void pfl_closing(Plug plug, const char *error_msg, int error_code, pfl_terminate(pl); } -static void wrap_send_port_open(void *channel, const char *hostname, int port, - Socket s) +static struct ssh_channel *wrap_send_port_open( + Ssh ssh, const char *hostname, int port, Socket s, Channel *chan) { char *peerinfo, *description; + struct ssh_channel *toret; + peerinfo = sk_peer_info(s); if (peerinfo) { description = dupprintf("forwarding from %s", peerinfo); @@ -153,8 +159,11 @@ static void wrap_send_port_open(void *channel, const char *hostname, int port, } else { description = dupstr("forwarding"); } - ssh_send_port_open(channel, hostname, port, description); + + toret = ssh_send_port_open(ssh, hostname, port, description, chan); + sfree(description); + return toret; } static char *ipv4_to_string(unsigned ipv4) @@ -396,21 +405,11 @@ static void pfd_receive(Plug plug, int urgent, char *data, int len) */ sk_set_frozen(pf->s, 1); - pf->c = new_sock_channel(pf->ssh, pf); - if (pf->c == NULL) { - pfd_close(pf); - return; - } else { - /* asks to forward to the specified host/port for this */ - wrap_send_port_open(pf->c, pf->hostname, pf->port, pf->s); - } - } - if (pf->ready) { - if (sshfwd_write(pf->c, data, len) > 0) { - pf->throttled = 1; - sk_set_frozen(pf->s, 1); - } + pf->c = wrap_send_port_open(pf->ssh, pf->hostname, pf->port, pf->s, + &pf->chan); } + if (pf->ready) + sshfwd_write(pf->c, data, len); } static void pfd_sent(Plug plug, int bufsize) @@ -429,6 +428,25 @@ static const Plug_vtable PortForwarding_plugvt = { NULL }; +static void pfd_chan_free(Channel *chan); +static void pfd_open_confirmation(Channel *chan); +static void pfd_open_failure(Channel *chan, const char *errtext); +static int pfd_send(Channel *chan, int is_stderr, const void *data, int len); +static void pfd_send_eof(Channel *chan); +static void pfd_set_input_wanted(Channel *chan, int wanted); +static char *pfd_log_close_msg(Channel *chan); + +static const struct ChannelVtable PortForwarding_channelvt = { + pfd_chan_free, + pfd_open_confirmation, + pfd_open_failure, + pfd_send, + pfd_send_eof, + pfd_set_input_wanted, + pfd_log_close_msg, + chan_no_eager_close, +}; + /* * Called when receiving a PORT OPEN from the server to make a * connection to a destination host. @@ -436,8 +454,8 @@ static const Plug_vtable PortForwarding_plugvt = { * On success, returns NULL and fills in *pf_ret. On error, returns a * dynamically allocated error message string. */ -char *pfd_connect(struct PortForwarding **pf_ret, char *hostname,int port, - void *c, Conf *conf, int addressfamily) +char *pfd_connect(Channel **chan_ret, char *hostname,int port, + struct ssh_channel *c, Conf *conf, int addressfamily) { SockAddr addr; const char *err; @@ -459,9 +477,12 @@ char *pfd_connect(struct PortForwarding **pf_ret, char *hostname,int port, /* * Open socket. */ - pf = *pf_ret = new_portfwd_state(); + pf = new_portfwd_state(); + *chan_ret = &pf->chan; pf->plugvt = &PortForwarding_plugvt; - pf->throttled = pf->throttle_override = 0; + pf->chan.initial_fixed_window_size = 0; + pf->chan.vt = &PortForwarding_channelvt; + pf->input_wanted = TRUE; pf->ready = 1; pf->c = c; pf->ssh = NULL; /* we shouldn't need this */ @@ -474,7 +495,7 @@ char *pfd_connect(struct PortForwarding **pf_ret, char *hostname,int port, char *err_ret = dupstr(err); sk_close(pf->s); free_portfwd_state(pf); - *pf_ret = NULL; + *chan_ret = NULL; return err_ret; } @@ -495,6 +516,9 @@ static int pfl_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) pl = FROMFIELD(p, struct PortListener, plugvt); pf = new_portfwd_state(); pf->plugvt = &PortForwarding_plugvt; + pf->chan.initial_fixed_window_size = 0; + pf->chan.vt = &PortForwarding_channelvt; + pf->input_wanted = TRUE; pf->c = NULL; pf->ssh = pl->ssh; @@ -505,7 +529,7 @@ static int pfl_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) return err != NULL; } - pf->throttled = pf->throttle_override = 0; + pf->input_wanted = TRUE; pf->ready = 0; if (pl->is_dynamic) { @@ -518,15 +542,8 @@ static int pfl_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) pf->socks_state = SOCKS_NONE; pf->hostname = dupstr(pl->hostname); pf->port = pl->port; - pf->c = new_sock_channel(pl->ssh, pf); - - if (pf->c == NULL) { - free_portfwd_state(pf); - return 1; - } else { - /* asks to forward to the specified host/port for this */ - wrap_send_port_open(pf->c, pf->hostname, pf->port, s); - } + pf->c = wrap_send_port_open(pl->ssh, pf->hostname, pf->port, + s, &pf->chan); } return 0; @@ -580,7 +597,12 @@ char *pfl_listen(char *desthost, int destport, char *srcaddr, return NULL; } -void pfd_close(struct PortForwarding *pf) +static char *pfd_log_close_msg(Channel *chan) +{ + return dupstr("Forwarded port closed"); +} + +static void pfd_close(struct PortForwarding *pf) { if (!pf) return; @@ -601,43 +623,42 @@ void pfl_terminate(struct PortListener *pl) free_portlistener_state(pl); } -void pfd_unthrottle(struct PortForwarding *pf) +static void pfd_set_input_wanted(Channel *chan, int wanted) { - if (!pf) - return; - - pf->throttled = 0; - sk_set_frozen(pf->s, pf->throttled || pf->throttle_override); + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = FROMFIELD(chan, PortForwarding, chan); + pf->input_wanted = wanted; + sk_set_frozen(pf->s, !pf->input_wanted); } -void pfd_override_throttle(struct PortForwarding *pf, int enable) +static void pfd_chan_free(Channel *chan) { - if (!pf) - return; - - pf->throttle_override = enable; - sk_set_frozen(pf->s, pf->throttled || pf->throttle_override); + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = FROMFIELD(chan, PortForwarding, chan); + pfd_close(pf); } /* * Called to send data down the raw connection. */ -int pfd_send(struct PortForwarding *pf, const void *data, int len) +static int pfd_send(Channel *chan, int is_stderr, const void *data, int len) { - if (pf == NULL) - return 0; + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = FROMFIELD(chan, PortForwarding, chan); return sk_write(pf->s, data, len); } -void pfd_send_eof(struct PortForwarding *pf) +static void pfd_send_eof(Channel *chan) { + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = FROMFIELD(chan, PortForwarding, chan); sk_write_eof(pf->s); } -void pfd_confirm(struct PortForwarding *pf) +static void pfd_open_confirmation(Channel *chan) { - if (pf == NULL) - return; + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = FROMFIELD(chan, PortForwarding, chan); pf->ready = 1; sk_set_frozen(pf->s, 0); @@ -649,3 +670,15 @@ void pfd_confirm(struct PortForwarding *pf) pf->socksbuf = NULL; } } + +static void pfd_open_failure(Channel *chan, const char *errtext) +{ + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = FROMFIELD(chan, PortForwarding, chan); + + char *msg = dupprintf( + "Forwarded connection refused by server%s%s", + errtext ? ": " : "", errtext ? errtext : ""); + logevent(ssh_get_frontend(pf->ssh), msg); + sfree(msg); +} diff --git a/ssh.c b/ssh.c index 1009e038..bc2f7bf8 100644 --- a/ssh.c +++ b/ssh.c @@ -17,6 +17,7 @@ #include "ssh.h" #include "sshcr.h" #include "sshbpp.h" +#include "sshchan.h" #ifndef NO_GSSAPI #include "sshgssc.h" #include "sshgss.h" @@ -362,29 +363,6 @@ const static struct ssh_compress *const compressions[] = { &ssh_zlib, &ssh_comp_none }; -enum { /* channel types */ - CHAN_MAINSESSION, - CHAN_X11, - CHAN_AGENT, - CHAN_SOCKDATA, - /* - * CHAN_SHARING indicates a channel which is tracked here on - * behalf of a connection-sharing downstream. We do almost nothing - * with these channels ourselves: all messages relating to them - * get thrown straight to sshshare.c and passed on almost - * unmodified to downstream. - */ - CHAN_SHARING, - /* - * CHAN_ZOMBIE is used to indicate a channel for which we've - * already destroyed the local data source: for instance, if a - * forwarded port experiences a socket error on the local side, we - * immediately destroy its local socket and turn the SSH channel - * into CHAN_ZOMBIE. - */ - CHAN_ZOMBIE -}; - typedef void (*handler_fn_t)(Ssh ssh, PktIn *pktin); typedef void (*chandler_fn_t)(Ssh ssh, PktIn *pktin, void *ctx); typedef void (*cchandler_fn_t)(struct ssh_channel *, PktIn *, void *); @@ -452,6 +430,15 @@ struct ssh_channel { * throttled. */ int throttling_conn; + + /* + * True if we currently have backed-up data on the direction of + * this channel pointing out of the SSH connection, and therefore + * would prefer the 'Channel' implementation not to read further + * local input if possible. + */ + int throttled_by_backlog; + union { struct ssh2_data_channel { bufchain outbuffer; @@ -472,22 +459,9 @@ struct ssh_channel { enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; } v2; } v; - union { - struct ssh_agent_channel { - bufchain inbuffer; - agent_pending_query *pending; - } a; - struct ssh_x11_channel { - struct X11Connection *xconn; - int initial; - } x11; - struct ssh_pfd_channel { - struct PortForwarding *pf; - } pfd; - struct ssh_sharing_channel { - void *ctx; - } sharing; - } u; + + void *sharectx; /* sharing context, if this is a downstream channel */ + Channel *chan; /* handle the client side of this channel, if not */ }; /* @@ -945,6 +919,11 @@ static const char *ssh_pkt_type(Ssh ssh, int type) return ssh2_pkt_type(ssh->pls.kctx, ssh->pls.actx, type); } +Frontend *ssh_get_frontend(Ssh ssh) +{ + return ssh->frontend; +} + #define logevent(s) logevent(ssh->frontend, s) /* logevent, only printf-formatted. */ @@ -2107,6 +2086,88 @@ static void ssh_process_user_input(void *ctx) ssh->current_user_input_fn(ssh); } +void chan_remotely_opened_confirmation(Channel *chan) +{ + assert(0 && "this channel type should never receive OPEN_CONFIRMATION"); +} + +void chan_remotely_opened_failure(Channel *chan, const char *errtext) +{ + assert(0 && "this channel type should never receive OPEN_FAILURE"); +} + +int chan_no_eager_close(Channel *chan, int sent_local_eof, int rcvd_remote_eof) +{ + return FALSE; /* default: never proactively ask for a close */ +} + +/* + * Trivial channel vtable for handling 'zombie channels' - those whose + * local source of data has already been shut down or otherwise + * stopped existing - so that we don't have to give them a null + * 'Channel *' and special-case that all over the place. + */ + +static void zombiechan_free(Channel *chan); +static int zombiechan_send(Channel *chan, int is_stderr, const void *, int); +static void zombiechan_set_input_wanted(Channel *chan, int wanted); +static void zombiechan_do_nothing(Channel *chan); +static void zombiechan_open_failure(Channel *chan, const char *); +static int zombiechan_want_close(Channel *chan, int sent_eof, int rcvd_eof); +static char *zombiechan_log_close_msg(Channel *chan) { return NULL; } + +static const struct ChannelVtable zombiechan_channelvt = { + zombiechan_free, + zombiechan_do_nothing, /* open_confirmation */ + zombiechan_open_failure, + zombiechan_send, + zombiechan_do_nothing, /* send_eof */ + zombiechan_set_input_wanted, + zombiechan_log_close_msg, + zombiechan_want_close, +}; + +Channel *zombiechan_new(void) +{ + Channel *chan = snew(Channel); + chan->vt = &zombiechan_channelvt; + chan->initial_fixed_window_size = 0; + return chan; +} + +static void zombiechan_free(Channel *chan) +{ + assert(chan->vt == &zombiechan_channelvt); + sfree(chan); +} + +static void zombiechan_do_nothing(Channel *chan) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static void zombiechan_open_failure(Channel *chan, const char *errtext) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static int zombiechan_send(Channel *chan, int is_stderr, + const void *data, int length) +{ + assert(chan->vt == &zombiechan_channelvt); + return 0; +} + +static void zombiechan_set_input_wanted(Channel *chan, int enable) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static int zombiechan_want_close(Channel *chan, int sent_eof, int rcvd_eof) +{ + return TRUE; +} + static int ssh_do_close(Ssh ssh, int notify_exit) { int ret = 0; @@ -2428,7 +2489,21 @@ static void ssh_throttle_conn(Ssh ssh, int adjust) } } -static void ssh_agentf_try_forward(struct ssh_channel *c); +static void ssh_channel_check_throttle(struct ssh_channel *c) +{ + /* + * We don't want this channel to read further input if this + * particular channel has a backed-up SSH window, or if the + * outgoing side of the whole SSH connection is currently + * throttled, or if this channel already has an outgoing EOF + * either sent or pending. + */ + chan_set_input_wanted(c->chan, + !c->throttled_by_backlog && + !c->ssh->throttled_all && + !c->pending_eof && + !(c->closes & CLOSES_SENT_EOF)); +} /* * Throttle or unthrottle _all_ local data streams (for when sends @@ -2445,29 +2520,8 @@ static void ssh_throttle_all(Ssh ssh, int enable, int bufsize) ssh->overall_bufsize = bufsize; if (!ssh->channels) return; - for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) { - switch (c->type) { - case CHAN_MAINSESSION: - /* - * This is treated separately, outside the switch. - */ - break; - case CHAN_X11: - x11_override_throttle(c->u.x11.xconn, enable); - break; - case CHAN_AGENT: - /* Agent forwarding channels are buffer-managed by - * checking ssh->throttled_all in ssh_agentf_try_forward. - * So at the moment we _un_throttle again, we must make an - * attempt to do something. */ - if (!enable) - ssh_agentf_try_forward(c); - break; - case CHAN_SOCKDATA: - pfd_override_throttle(c->u.pfd.pf, enable); - break; - } - } + for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) + ssh_channel_check_throttle(c); } static void ssh_agent_callback(void *sshv, void *reply, int replylen) @@ -2503,140 +2557,6 @@ static void ssh_dialog_callback(void *sshv, int ret) queue_idempotent_callback(&ssh->incoming_data_consumer); } -static void ssh_agentf_got_response(struct ssh_channel *c, - void *reply, int replylen) -{ - c->u.a.pending = NULL; - - assert(!(c->closes & CLOSES_SENT_EOF)); - - if (!reply) { - /* The real agent didn't send any kind of reply at all for - * some reason, so fake an SSH_AGENT_FAILURE. */ - reply = "\0\0\0\1\5"; - replylen = 5; - } - - ssh_send_channel_data(c, reply, replylen); -} - -static void ssh_agentf_callback(void *cv, void *reply, int replylen); - -static void ssh_agentf_try_forward(struct ssh_channel *c) -{ - unsigned datalen, length; - strbuf *message; - unsigned char msglen[4]; - void *reply; - int replylen; - - /* - * Don't try to parallelise agent requests. Wait for each one to - * return before attempting the next. - */ - if (c->u.a.pending) - return; - - /* - * If the outgoing side of the channel connection is currently - * throttled (for any reason, either that channel's window size or - * the entire SSH connection being throttled), don't submit any - * new forwarded requests to the real agent. This causes the input - * side of the agent forwarding not to be emptied, exerting the - * required back-pressure on the remote client, and encouraging it - * to read our responses before sending too many more requests. - */ - if (c->ssh->throttled_all || - (c->ssh->version == 2 && c->v.v2.remwindow == 0)) - return; - - if (c->closes & CLOSES_SENT_EOF) { - /* - * If we've already sent outgoing EOF, there's nothing we can - * do with incoming data except consume it and throw it away. - */ - bufchain_clear(&c->u.a.inbuffer); - return; - } - - while (1) { - /* - * Try to extract a complete message from the input buffer. - */ - datalen = bufchain_size(&c->u.a.inbuffer); - if (datalen < 4) - break; /* not even a length field available yet */ - - bufchain_fetch(&c->u.a.inbuffer, msglen, 4); - length = GET_32BIT(msglen); - - if (length > AGENT_MAX_MSGLEN-4) { - /* - * If the remote has sent a message that's just _too_ - * long, we should reject it in advance of seeing the rest - * of the incoming message, and also close the connection - * for good measure (which avoids us having to faff about - * with carefully ignoring just the right number of bytes - * from the overlong message). - */ - ssh_agentf_got_response(c, NULL, 0); - sshfwd_write_eof(c); - return; - } - - if (length > datalen - 4) - break; /* a whole message is not yet available */ - - bufchain_consume(&c->u.a.inbuffer, 4); - - message = strbuf_new_for_agent_query(); - bufchain_fetch_consume( - &c->u.a.inbuffer, strbuf_append(message, length), length); - c->u.a.pending = agent_query( - message, &reply, &replylen, ssh_agentf_callback, c); - strbuf_free(message); - - if (c->u.a.pending) - return; /* agent_query promised to reply in due course */ - - /* - * If the agent gave us an answer immediately, pass it - * straight on and go round this loop again. - */ - ssh_agentf_got_response(c, reply, replylen); - sfree(reply); - } - - /* - * If we get here (i.e. we left the above while loop via 'break' - * rather than 'return'), that means we've determined that the - * input buffer for the agent forwarding connection doesn't - * contain a complete request. - * - * So if there's potentially more data to come, we can return now, - * and wait for the remote client to send it. But if the remote - * has sent EOF, it would be a mistake to do that, because we'd be - * waiting a long time. So this is the moment to check for EOF, - * and respond appropriately. - */ - if (c->closes & CLOSES_RCVD_EOF) - sshfwd_write_eof(c); -} - -static void ssh_agentf_callback(void *cv, void *reply, int replylen) -{ - struct ssh_channel *c = (struct ssh_channel *)cv; - - ssh_agentf_got_response(c, reply, replylen); - sfree(reply); - - /* - * Now try to extract and send further messages from the channel's - * input-side buffer. - */ - ssh_agentf_try_forward(c); -} - /* * Client-initiated disconnection. Send a DISCONNECT if `wire_reason' * non-NULL, otherwise just close the connection. `client_reason' == NULL @@ -4297,10 +4217,9 @@ static void ssh1_smsg_x11_open(Ssh ssh, PktIn *pktin) c->ssh = ssh; ssh_channel_init(c); - c->u.x11.xconn = x11_init(ssh->x11authtree, c, NULL, -1); + c->chan = x11_new_channel(ssh->x11authtree, c, NULL, -1, FALSE); c->remoteid = remoteid; c->halfopen = FALSE; - c->type = CHAN_X11; /* identify channel type */ pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); put_uint32(pkt, c->remoteid); put_uint32(pkt, c->localid); @@ -4328,9 +4247,7 @@ static void ssh1_smsg_agent_open(Ssh ssh, PktIn *pktin) ssh_channel_init(c); c->remoteid = remoteid; c->halfopen = FALSE; - c->type = CHAN_AGENT; /* identify channel type */ - c->u.a.pending = NULL; - bufchain_init(&c->u.a.inbuffer); + c->chan = agentf_new(c); pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); put_uint32(pkt, c->remoteid); put_uint32(pkt, c->localid); @@ -4369,7 +4286,7 @@ static void ssh1_msg_port_open(Ssh ssh, PktIn *pktin) logeventf(ssh, "Received remote port open request for %s:%d", pf.dhost, port); - err = pfd_connect(&c->u.pfd.pf, pf.dhost, port, + err = pfd_connect(&c->chan, pf.dhost, port, c, ssh->conf, pfp->pfrec->addressfamily); if (err != NULL) { logeventf(ssh, "Port open failed: %s", err); @@ -4382,7 +4299,6 @@ static void ssh1_msg_port_open(Ssh ssh, PktIn *pktin) ssh_channel_init(c); c->remoteid = remoteid; c->halfopen = FALSE; - c->type = CHAN_SOCKDATA; /* identify channel type */ pkt = ssh_bpp_new_pktout( ssh->bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); put_uint32(pkt, c->remoteid); @@ -4400,12 +4316,10 @@ static void ssh1_msg_channel_open_confirmation(Ssh ssh, PktIn *pktin) struct ssh_channel *c; c = ssh_channel_msg(ssh, pktin); - if (c && c->type == CHAN_SOCKDATA) { - c->remoteid = get_uint32(pktin); - c->halfopen = FALSE; - c->throttling_conn = 0; - pfd_confirm(c->u.pfd.pf); - } + chan_open_confirmation(c->chan); + c->remoteid = get_uint32(pktin); + c->halfopen = FALSE; + c->throttling_conn = 0; if (c && c->pending_eof) { /* @@ -4423,12 +4337,11 @@ static void ssh1_msg_channel_open_failure(Ssh ssh, PktIn *pktin) struct ssh_channel *c; c = ssh_channel_msg(ssh, pktin); - if (c && c->type == CHAN_SOCKDATA) { - logevent("Forwarded connection refused by server"); - pfd_close(c->u.pfd.pf); - del234(ssh->channels, c); - sfree(c); - } + chan_open_failed(c->chan, NULL); + chan_free(c->chan); + + del234(ssh->channels, c); + sfree(c); } static void ssh1_msg_channel_close(Ssh ssh, PktIn *pktin) @@ -4473,39 +4386,11 @@ static void ssh1_msg_channel_close(Ssh ssh, PktIn *pktin) } } -/* - * Handle incoming data on an SSH-1 or SSH-2 agent-forwarding channel. - */ -static int ssh_agent_channel_data(struct ssh_channel *c, const void *data, - int length) -{ - bufchain_add(&c->u.a.inbuffer, data, length); - ssh_agentf_try_forward(c); - - /* - * We exert back-pressure on an agent forwarding client if and - * only if we're waiting for the response to an asynchronous agent - * request. This prevents the client running out of window while - * receiving the _first_ message, but means that if any message - * takes time to process, the client will be discouraged from - * sending an endless stream of further ones after it. - */ - return (c->u.a.pending ? bufchain_size(&c->u.a.inbuffer) : 0); -} - static int ssh_channel_data(struct ssh_channel *c, int is_stderr, const void *data, int length) { - switch (c->type) { - case CHAN_MAINSESSION: - return from_backend(c->ssh->frontend, is_stderr, data, length); - case CHAN_X11: - return x11_send(c->u.x11.xconn, data, length); - case CHAN_SOCKDATA: - return pfd_send(c->u.pfd.pf, data, length); - case CHAN_AGENT: - return ssh_agent_channel_data(c, data, length); - } + if (c->chan) + chan_send(c->chan, is_stderr, data, length); return 0; } @@ -6933,6 +6818,8 @@ static void do_ssh2_transport(void *vctx) static int ssh_send_channel_data(struct ssh_channel *c, const char *buf, int len) { + assert(!(c->closes & CLOSES_SENT_EOF)); + if (c->ssh->version == 2) { bufchain_add(&c->v.v2.outbuffer, buf, len); return ssh2_try_send(c); @@ -7002,23 +6889,8 @@ static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) return; /* don't send on channels we've EOFed */ bufsize = ssh2_try_send(c); if (bufsize == 0) { - switch (c->type) { - case CHAN_MAINSESSION: - /* stdin need not receive an unthrottle - * notification since it will be polled */ - break; - case CHAN_X11: - x11_unthrottle(c->u.x11.xconn); - break; - case CHAN_AGENT: - /* Now that we've successfully sent all the outgoing - * replies we had, try to process more incoming data. */ - ssh_agentf_try_forward(c); - break; - case CHAN_SOCKDATA: - pfd_unthrottle(c->u.pfd.pf); - break; - } + c->throttled_by_backlog = FALSE; + ssh_channel_check_throttle(c); } } @@ -7036,7 +6908,9 @@ static int ssh_is_simple(Ssh ssh) } /* - * Set up most of a new ssh_channel. + * Set up most of a new ssh_channel. Nulls out sharectx, but leaves + * chan untouched (since it will sometimes have been filled in before + * calling this). */ static void ssh_channel_init(struct ssh_channel *c) { @@ -7045,6 +6919,7 @@ static void ssh_channel_init(struct ssh_channel *c) c->closes = 0; c->pending_eof = FALSE; c->throttling_conn = FALSE; + c->sharectx = NULL; if (ssh->version == 2) { c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; @@ -7162,11 +7037,12 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin) return; /* - * Also, never widen the window for an X11 channel when we're - * still waiting to see its initial auth and may yet hand it off - * to a downstream. + * If the client-side Channel is in an initial setup phase with a + * fixed window size, e.g. for an X11 channel when we're still + * waiting to see its initial auth and may yet hand it off to a + * downstream, don't send any WINDOW_ADJUST either. */ - if (c->type == CHAN_X11 && c->u.x11.initial) + if (c->chan->initial_fixed_window_size) return; /* @@ -7241,7 +7117,13 @@ static struct ssh_channel *ssh_channel_msg(Ssh ssh, PktIn *pktin) halfopen_ok = (pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION || pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE); c = find234(ssh->channels, &localid, ssh_channelfind); - if (!c || (c->type != CHAN_SHARING && (c->halfopen != halfopen_ok))) { + if (c && c->sharectx) { + share_got_pkt_from_server(c->sharectx, pktin->type, + BinarySource_UPCAST(pktin)->data, + BinarySource_UPCAST(pktin)->len); + return NULL; + } + if (!c || c->halfopen != halfopen_ok) { char *buf = dupprintf("Received %s for %s channel %u", ssh_pkt_type(ssh, pktin->type), !c ? "nonexistent" : @@ -7251,12 +7133,6 @@ static struct ssh_channel *ssh_channel_msg(Ssh ssh, PktIn *pktin) sfree(buf); return NULL; } - if (c->type == CHAN_SHARING) { - share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); - return NULL; - } return c; } @@ -7421,36 +7297,20 @@ void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...) /* * Close any local socket and free any local resources associated with - * a channel. This converts the channel into a CHAN_ZOMBIE. + * a channel. This converts the channel into a zombie. */ static void ssh_channel_close_local(struct ssh_channel *c, char const *reason) { Ssh ssh = c->ssh; - char const *msg = NULL; + const char *msg = NULL; + + if (c->sharectx) + return; + + msg = chan_log_close_msg(c->chan); + chan_free(c->chan); + c->chan = zombiechan_new(); - switch (c->type) { - case CHAN_MAINSESSION: - ssh->mainchan = NULL; - update_specials_menu(ssh->frontend); - break; - case CHAN_X11: - assert(c->u.x11.xconn != NULL); - x11_close(c->u.x11.xconn); - msg = "Forwarded X11 connection terminated"; - break; - case CHAN_AGENT: - if (c->u.a.pending) - agent_cancel_query(c->u.a.pending); - bufchain_clear(&c->u.a.inbuffer); - msg = "Agent-forwarding connection closed"; - break; - case CHAN_SOCKDATA: - assert(c->u.pfd.pf != NULL); - pfd_close(c->u.pfd.pf); - msg = "Forwarded port closed"; - break; - } - c->type = CHAN_ZOMBIE; if (msg != NULL) { if (reason != NULL) logeventf(ssh, "%s %s", msg, reason); @@ -7495,7 +7355,8 @@ static void ssh2_channel_check_close(struct ssh_channel *c) } if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) || - c->type == CHAN_ZOMBIE) && + chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF), + (c->closes & CLOSES_RCVD_EOF))) && !c->v.v2.chanreq_head && !(c->closes & CLOSES_SENT_CLOSE)) { /* @@ -7526,34 +7387,7 @@ static void ssh_channel_got_eof(struct ssh_channel *c) return; /* already seen EOF */ c->closes |= CLOSES_RCVD_EOF; - if (c->type == CHAN_X11) { - assert(c->u.x11.xconn != NULL); - x11_send_eof(c->u.x11.xconn); - } else if (c->type == CHAN_AGENT) { - /* Just call try_forward, which will respond to the EOF now if - * appropriate, or wait until the queue of outstanding - * requests is dealt with if not */ - ssh_agentf_try_forward(c); - } else if (c->type == CHAN_SOCKDATA) { - assert(c->u.pfd.pf != NULL); - pfd_send_eof(c->u.pfd.pf); - } else if (c->type == CHAN_MAINSESSION) { - Ssh ssh = c->ssh; - - if (!ssh->sent_console_eof && - (from_backend_eof(ssh->frontend) || ssh->got_pty)) { - /* - * Either from_backend_eof told us that the front end - * wants us to close the outgoing side of the connection - * as soon as we see EOF from the far end, or else we've - * unilaterally decided to do that because we've allocated - * a remote pty and hence EOF isn't a particularly - * meaningful concept. - */ - sshfwd_write_eof(c); - } - ssh->sent_console_eof = TRUE; - } + chan_send_eof(c->chan); } static void ssh2_msg_channel_eof(Ssh ssh, PktIn *pktin) @@ -7604,22 +7438,6 @@ static void ssh2_msg_channel_close(Ssh ssh, PktIn *pktin) * it would have just sent CHANNEL_EOF.) */ if (!(c->closes & CLOSES_SENT_EOF)) { - /* - * Make sure we don't read any more from whatever our local - * data source is for this channel. - */ - switch (c->type) { - case CHAN_MAINSESSION: - ssh->send_ok = 0; /* stop trying to read from stdin */ - break; - case CHAN_X11: - x11_override_throttle(c->u.x11.xconn, 1); - break; - case CHAN_SOCKDATA: - pfd_override_throttle(c->u.pfd.pf, 1); - break; - } - /* * Abandon any buffered data we still wanted to send to this * channel. Receiving a CHANNEL_CLOSE is an indication that @@ -7633,6 +7451,13 @@ static void ssh2_msg_channel_close(Ssh ssh, PktIn *pktin) * Send outgoing EOF. */ sshfwd_write_eof(c); + + /* + * Make sure we don't read any more from whatever our local + * data source is for this channel. (This will pick up on the + * changes made by sshfwd_write_eof.) + */ + ssh_channel_check_throttle(c); } /* @@ -7657,32 +7482,22 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, PktIn *pktin) c->v.v2.remwindow = get_uint32(pktin); c->v.v2.remmaxpkt = get_uint32(pktin); - if (c->type == CHAN_SOCKDATA) { - assert(c->u.pfd.pf != NULL); - pfd_confirm(c->u.pfd.pf); - } else if (c->type == CHAN_ZOMBIE) { - /* - * This case can occur if a local socket error occurred - * between us sending out CHANNEL_OPEN and receiving - * OPEN_CONFIRMATION. In this case, all we can do is - * immediately initiate close proceedings now that we know the - * server's id to put in the close message. - */ - ssh2_channel_check_close(c); - } else { - /* - * We never expect to receive OPEN_CONFIRMATION for any - * *other* channel type (since only local-to-remote port - * forwardings cause us to send CHANNEL_OPEN after the main - * channel is live - all other auxiliary channel types are - * initiated from the server end). It's safe to enforce this - * by assertion rather than by ssh_disconnect, because the - * real point is that we never constructed a half-open channel - * structure in the first place with any type other than the - * above. - */ - assert(!"Funny channel type in ssh2_msg_channel_open_confirmation"); - } + chan_open_confirmation(c->chan); + + /* + * Now that the channel is fully open, it's possible in principle + * to immediately close it. Check whether it wants us to! + * + * This can occur if a local socket error occurred between us + * sending out CHANNEL_OPEN and receiving OPEN_CONFIRMATION. If + * that happens, all we can do is immediately initiate close + * proceedings now that we know the server's id to put in the + * close message. We'll have handled that in this code by having + * already turned c->chan into a zombie, so its want_close method + * (which ssh2_channel_check_close will consult) will already be + * returning TRUE. + */ + ssh2_channel_check_close(c); if (c->pending_eof) ssh_channel_try_eof(c); /* in case we had a pending EOF */ @@ -7724,31 +7539,12 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, PktIn *pktin) return; assert(c->halfopen); /* ssh_channel_msg will have enforced this */ - if (c->type == CHAN_SOCKDATA) { + { char *errtext = ssh2_channel_open_failure_error_text(pktin); - logeventf(ssh, "Forwarded connection refused by server: %s", errtext); + chan_open_failed(c->chan, errtext); sfree(errtext); - pfd_close(c->u.pfd.pf); - } else if (c->type == CHAN_ZOMBIE) { - /* - * This case can occur if a local socket error occurred - * between us sending out CHANNEL_OPEN and receiving - * OPEN_FAILURE. In this case, we need do nothing except allow - * the code below to throw the half-open channel away. - */ - } else { - /* - * We never expect to receive OPEN_FAILURE for any *other* - * channel type (since only local-to-remote port forwardings - * cause us to send CHANNEL_OPEN after the main channel is - * live - all other auxiliary channel types are initiated from - * the server end). It's safe to enforce this by assertion - * rather than by ssh_disconnect, because the real point is - * that we never constructed a half-open channel structure in - * the first place with any type other than the above. - */ - assert(!"Funny channel type in ssh2_msg_channel_open_failure"); } + chan_free(c->chan); del234(ssh->channels, c); sfree(c); @@ -7970,7 +7766,6 @@ static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) const char *error = NULL; struct ssh_channel *c; unsigned remid, winsize, pktsize; - unsigned our_winsize_override = 0; PktOut *pktout; type = get_string(pktin); @@ -7991,22 +7786,8 @@ static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) if (!ssh->X11_fwd_enabled && !ssh->connshare) error = "X11 forwarding is not enabled"; else { - c->u.x11.xconn = x11_init(ssh->x11authtree, c, - addrstr, peerport); - c->type = CHAN_X11; - c->u.x11.initial = TRUE; - - /* - * If we are a connection-sharing upstream, then we should - * initially present a very small window, adequate to take - * the X11 initial authorisation packet but not much more. - * Downstream will then present us a larger window (by - * fiat of the connection-sharing protocol) and we can - * guarantee to send a positive-valued WINDOW_ADJUST. - */ - if (ssh->connshare) - our_winsize_override = 128; - + c->chan = x11_new_channel(ssh->x11authtree, c, addrstr, peerport, + ssh->connshare != NULL); logevent("Opened X11 forward channel"); } @@ -8044,7 +7825,7 @@ static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) return; } - err = pfd_connect(&c->u.pfd.pf, realpf->dhost, realpf->dport, + err = pfd_connect(&c->chan, realpf->dhost, realpf->dport, c, ssh->conf, realpf->pfrec->addressfamily); logeventf(ssh, "Attempting to forward remote port to " "%s:%d", realpf->dhost, realpf->dport); @@ -8054,17 +7835,13 @@ static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) error = "Port open failed"; } else { logevent("Forwarded port opened successfully"); - c->type = CHAN_SOCKDATA; } } } else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) { if (!ssh->agentfwd_enabled) error = "Agent forwarding is not enabled"; - else { - c->type = CHAN_AGENT; /* identify channel type */ - bufchain_init(&c->u.a.inbuffer); - c->u.a.pending = NULL; - } + else + c->chan = agentf_new(c); } else { error = "Unsupported channel type requested"; } @@ -8084,9 +7861,9 @@ static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) ssh_channel_init(c); c->v.v2.remwindow = winsize; c->v.v2.remmaxpkt = pktsize; - if (our_winsize_override) { + if (c->chan->initial_fixed_window_size) { c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = - our_winsize_override; + c->chan->initial_fixed_window_size; } pktout = ssh_bpp_new_pktout( ssh->bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); @@ -8108,30 +7885,28 @@ void sshfwd_x11_sharing_handover(struct ssh_channel *c, * This function is called when we've just discovered that an X * forwarding channel on which we'd been handling the initial auth * ourselves turns out to be destined for a connection-sharing - * downstream. So we turn the channel into a CHAN_SHARING, meaning + * downstream. So we turn the channel into a sharing one, meaning * that we completely stop tracking windows and buffering data and * just pass more or less unmodified SSH messages back and forth. */ - c->type = CHAN_SHARING; - c->u.sharing.ctx = share_cs; + c->sharectx = share_cs; share_setup_x11_channel(share_cs, share_chan, c->localid, c->remoteid, c->v.v2.remwindow, c->v.v2.remmaxpkt, c->v.v2.locwindow, peer_addr, peer_port, endian, protomajor, protominor, initial_data, initial_len); + chan_free(c->chan); + c->chan = NULL; } -void sshfwd_x11_is_local(struct ssh_channel *c) +void sshfwd_window_override_removed(struct ssh_channel *c) { /* - * This function is called when we've just discovered that an X - * forwarding channel is _not_ destined for a connection-sharing - * downstream but we're going to handle it ourselves. We stop - * presenting a cautiously small window and go into ordinary data - * exchange mode. + * This function is called when a client-side Channel has just + * stopped requiring an initial fixed-size window. */ - c->u.x11.initial = FALSE; + assert(!c->chan->initial_fixed_window_size); if (c->ssh->version == 2) ssh2_set_window( c, ssh_is_simple(c->ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); @@ -9879,6 +9654,108 @@ static void ssh2_connection_setup(Ssh ssh) ssh->channels = newtree234(ssh_channelcmp); } +typedef struct mainchan { + Ssh ssh; + struct ssh_channel *c; + + Channel chan; +} mainchan; + +static void mainchan_free(Channel *chan); +static void mainchan_open_confirmation(Channel *chan); +static void mainchan_open_failure(Channel *chan, const char *errtext); +static int mainchan_send(Channel *chan, int is_stderr, const void *, int); +static void mainchan_send_eof(Channel *chan); +static void mainchan_set_input_wanted(Channel *chan, int wanted); +static char *mainchan_log_close_msg(Channel *chan); + +static const struct ChannelVtable mainchan_channelvt = { + mainchan_free, + mainchan_open_confirmation, + mainchan_open_failure, + mainchan_send, + mainchan_send_eof, + mainchan_set_input_wanted, + mainchan_log_close_msg, + chan_no_eager_close, +}; + +static mainchan *mainchan_new(Ssh ssh) +{ + mainchan *mc = snew(mainchan); + mc->ssh = ssh; + mc->c = NULL; + mc->chan.vt = &mainchan_channelvt; + mc->chan.initial_fixed_window_size = 0; + return mc; +} + +static void mainchan_free(Channel *chan) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + mc->ssh->mainchan = NULL; + sfree(mc); +} + +static void mainchan_open_confirmation(Channel *chan) +{ + assert(FALSE && "OPEN_CONFIRMATION for main channel should be " + "handled by connection layer setup"); +} + +static void mainchan_open_failure(Channel *chan, const char *errtext) +{ + assert(FALSE && "OPEN_FAILURE for main channel should be " + "handled by connection layer setup"); +} + +static int mainchan_send(Channel *chan, int is_stderr, + const void *data, int length) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + return from_backend(mc->ssh->frontend, is_stderr, data, length); +} + +static void mainchan_send_eof(Channel *chan) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + + if (!mc->ssh->sent_console_eof && + (from_backend_eof(mc->ssh->frontend) || mc->ssh->got_pty)) { + /* + * Either from_backend_eof told us that the front end wants us + * to close the outgoing side of the connection as soon as we + * see EOF from the far end, or else we've unilaterally + * decided to do that because we've allocated a remote pty and + * hence EOF isn't a particularly meaningful concept. + */ + sshfwd_write_eof(mc->c); + } + mc->ssh->sent_console_eof = TRUE; +} + +static void mainchan_set_input_wanted(Channel *chan, int wanted) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + + /* + * This is the main channel of the SSH session, i.e. the one tied + * to the standard input (or GUI) of the primary SSH client user + * interface. So ssh->send_ok is how we control whether we're + * reading from that input. + */ + mc->ssh->send_ok = wanted; +} + +static char *mainchan_log_close_msg(Channel *chan) +{ + return dupstr("Main session channel closed"); +} + static void do_ssh2_connection(void *vctx) { Ssh ssh = (Ssh)vctx; @@ -9907,22 +9784,23 @@ static void do_ssh2_connection(void *vctx) if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) { ssh->mainchan = NULL; } else { - ssh->mainchan = snew(struct ssh_channel); - ssh->mainchan->ssh = ssh; - ssh->mainchan->type = CHAN_MAINSESSION; - ssh_channel_init(ssh->mainchan); + mainchan *mc = mainchan_new(ssh); if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) { /* * Just start a direct-tcpip channel and use it as the main * channel. */ - ssh_send_port_open(ssh->mainchan, - conf_get_str(ssh->conf, CONF_ssh_nc_host), - conf_get_int(ssh->conf, CONF_ssh_nc_port), - "main channel"); + ssh->mainchan = mc->c = ssh_send_port_open + (ssh, conf_get_str(ssh->conf, CONF_ssh_nc_host), + conf_get_int(ssh->conf, CONF_ssh_nc_port), + "main channel", &mc->chan); ssh->ncmode = TRUE; } else { + ssh->mainchan = mc->c = snew(struct ssh_channel); + ssh->mainchan->ssh = ssh; + ssh_channel_init(ssh->mainchan); + ssh->mainchan->chan = &mc->chan; s->pktout = ssh2_chanopen_init(ssh->mainchan, "session"); logevent("Opening session as main channel"); ssh2_pkt_send(ssh, s->pktout); @@ -11295,19 +11173,6 @@ static void ssh_special(Backend *be, Telnet_Special code) } } -void *new_sock_channel(Ssh ssh, struct PortForwarding *pf) -{ - struct ssh_channel *c; - c = snew(struct ssh_channel); - - c->ssh = ssh; - ssh_channel_init(c); - c->halfopen = TRUE; - c->type = CHAN_SOCKDATA;/* identify channel type */ - c->u.pfd.pf = pf; - return c; -} - unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx) { struct ssh_channel *c; @@ -11315,8 +11180,8 @@ unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx) c->ssh = ssh; ssh_channel_init(c); - c->type = CHAN_SHARING; - c->u.sharing.ctx = sharing_ctx; + c->chan = NULL; + c->sharectx = sharing_ctx; return c->localid; } @@ -11367,13 +11232,17 @@ static void ssh_unthrottle(Backend *be, int bufsize) queue_idempotent_callback(&ssh->incoming_data_consumer); } -void ssh_send_port_open(void *channel, const char *hostname, int port, - const char *org) +struct ssh_channel *ssh_send_port_open(Ssh ssh, const char *hostname, int port, + const char *org, Channel *chan) { - struct ssh_channel *c = (struct ssh_channel *)channel; - Ssh ssh = c->ssh; + struct ssh_channel *c = snew(struct ssh_channel); PktOut *pktout; + c->ssh = ssh; + ssh_channel_init(c); + c->halfopen = TRUE; + c->chan = chan; + logeventf(ssh, "Opening connection to %s:%d for %s", hostname, port, org); if (ssh->version == 1) { @@ -11405,6 +11274,8 @@ void ssh_send_port_open(void *channel, const char *hostname, int port, put_uint32(pktout, 0); ssh2_pkt_send(ssh, pktout); } + + return c; } static int ssh_connected(Backend *be) diff --git a/ssh.h b/ssh.h index 3fe8266b..82810d78 100644 --- a/ssh.h +++ b/ssh.h @@ -14,12 +14,12 @@ extern void sshfwd_write_eof(struct ssh_channel *c); extern void sshfwd_unclean_close(struct ssh_channel *c, const char *err); extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize); Conf *sshfwd_get_conf(struct ssh_channel *c); +void sshfwd_window_override_removed(struct ssh_channel *c); void sshfwd_x11_sharing_handover(struct ssh_channel *c, void *share_cs, void *share_chan, const char *peer_addr, int peer_port, int endian, int protomajor, int protominor, const void *initial_data, int initial_len); -void sshfwd_x11_is_local(struct ssh_channel *c); /* * Buffer management constants. There are several of these for @@ -185,6 +185,8 @@ void share_setup_x11_channel(void *csv, void *chanv, int protomajor, int protominor, const void *initial_data, int initial_len); +Frontend *ssh_get_frontend(Ssh ssh); + /* * Useful thing. */ @@ -665,19 +667,12 @@ void logevent(Frontend *, const char *); struct PortForwarding; /* Allocate and register a new channel for port forwarding */ -void *new_sock_channel(Ssh ssh, struct PortForwarding *pf); -void ssh_send_port_open(void *channel, const char *hostname, int port, - const char *org); +struct ssh_channel *ssh_send_port_open(Ssh ssh, const char *hostname, int port, + const char *org, Channel *chan); /* Exports from portfwd.c */ -extern char *pfd_connect(struct PortForwarding **pf, char *hostname, int port, - void *c, Conf *conf, int addressfamily); -extern void pfd_close(struct PortForwarding *); -extern int pfd_send(struct PortForwarding *, const void *data, int len); -extern void pfd_send_eof(struct PortForwarding *); -extern void pfd_confirm(struct PortForwarding *); -extern void pfd_unthrottle(struct PortForwarding *); -extern void pfd_override_throttle(struct PortForwarding *, int enable); +extern char *pfd_connect(Channel **chan_ret, char *hostname, int port, + struct ssh_channel *c, Conf *conf, int addressfamily); struct PortListener; /* desthost == NULL indicates dynamic (SOCKS) port forwarding */ extern char *pfl_listen(char *desthost, int destport, char *srcaddr, @@ -750,13 +745,9 @@ extern struct X11Display *x11_setup_display(const char *display, Conf *); void x11_free_display(struct X11Display *disp); struct X11FakeAuth *x11_invent_fake_auth(tree234 *t, int authtype); void x11_free_fake_auth(struct X11FakeAuth *auth); -struct X11Connection; /* opaque outside x11fwd.c */ -struct X11Connection *x11_init(tree234 *authtree, void *, const char *, int); -extern void x11_close(struct X11Connection *); -extern int x11_send(struct X11Connection *, const void *, int); -extern void x11_send_eof(struct X11Connection *s); -extern void x11_unthrottle(struct X11Connection *s); -extern void x11_override_throttle(struct X11Connection *s, int enable); +Channel *x11_new_channel(tree234 *authtree, struct ssh_channel *c, + const char *peeraddr, int peerport, + int connection_sharing_possible); char *x11_display(const char *display); /* Platform-dependent X11 functions */ extern void platform_get_x11_auth(struct X11Display *display, Conf *); @@ -786,6 +777,8 @@ void x11_get_auth_from_authfile(struct X11Display *display, int x11_identify_auth_proto(ptrlen protoname); void *x11_dehexify(ptrlen hex, int *outlen); +Channel *agentf_new(struct ssh_channel *c); + Bignum copybn(Bignum b); Bignum bn_power_2(int n); void bn_restore_invariant(Bignum b); diff --git a/sshchan.h b/sshchan.h new file mode 100644 index 00000000..26462bc6 --- /dev/null +++ b/sshchan.h @@ -0,0 +1,59 @@ +/* + * Abstraction of the various ways to handle the local end of an SSH + * connection-layer channel. + */ + +#ifndef PUTTY_SSHCHAN_H +#define PUTTY_SSHCHAN_H + +struct ChannelVtable { + void (*free)(Channel *); + + /* Called for channel types that were created at the same time as + * we sent an outgoing CHANNEL_OPEN, when the confirmation comes + * back from the server indicating that the channel has been + * opened, or the failure message indicating that it hasn't, + * respectively. In the latter case, this must _not_ free the + * Channel structure - the client will call the free method + * separately. But it might do logging or other local cleanup. */ + void (*open_confirmation)(Channel *); + void (*open_failed)(Channel *, const char *error_text); + + int (*send)(Channel *, int is_stderr, const void *buf, int len); + void (*send_eof)(Channel *); + void (*set_input_wanted)(Channel *, int wanted); + + char *(*log_close_msg)(Channel *); + + int (*want_close)(Channel *, int sent_local_eof, int rcvd_remote_eof); +}; + +struct Channel { + const struct ChannelVtable *vt; + unsigned initial_fixed_window_size; +}; + +#define chan_free(ch) ((ch)->vt->free(ch)) +#define chan_open_confirmation(ch) ((ch)->vt->open_confirmation(ch)) +#define chan_open_failed(ch, err) ((ch)->vt->open_failed(ch, err)) +#define chan_send(ch, err, buf, len) ((ch)->vt->send(ch, err, buf, len)) +#define chan_send_eof(ch) ((ch)->vt->send_eof(ch)) +#define chan_set_input_wanted(ch, wanted) \ + ((ch)->vt->set_input_wanted(ch, wanted)) +#define chan_log_close_msg(ch) ((ch)->vt->send_eof(ch)) +#define chan_want_close(ch, leof, reof) ((ch)->vt->want_close(ch, leof, reof)) + +/* + * Reusable methods you can put in vtables to give default handling of + * some of those functions. + */ + +/* open_confirmation / open_failed for any channel it doesn't apply to */ +void chan_remotely_opened_confirmation(Channel *chan); +void chan_remotely_opened_failure(Channel *chan, const char *errtext); + +/* want_close for any channel that wants the default behaviour of not + * closing until both directions have had an EOF */ +int chan_no_eager_close(Channel *, int, int); + +#endif /* PUTTY_SSHCHAN_H */ diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c index 6445fd57..a694bc44 100644 --- a/unix/uxpgnt.c +++ b/unix/uxpgnt.c @@ -161,13 +161,17 @@ int sshfwd_write(struct ssh_channel *c, const void *data, int len) void sshfwd_write_eof(struct ssh_channel *c) { } void sshfwd_unclean_close(struct ssh_channel *c, const char *err) { } void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) {} +void sshfwd_window_override_removed(struct ssh_channel *c) { } +void chan_remotely_opened_confirmation(Channel *chan) { } +void chan_remotely_opened_failure(Channel *chan, const char *err) { } +int chan_no_eager_close(Channel *chan, int s, int r) { return FALSE; } + Conf *sshfwd_get_conf(struct ssh_channel *c) { return NULL; } void sshfwd_x11_sharing_handover(struct ssh_channel *c, void *share_cs, void *share_chan, const char *peer_addr, int peer_port, int endian, int protomajor, int protominor, const void *initial_data, int initial_len) {} -void sshfwd_x11_is_local(struct ssh_channel *c) {} /* * These functions are part of the plug for our connection to the X diff --git a/x11fwd.c b/x11fwd.c index 190b6f68..419d4968 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -9,6 +9,7 @@ #include "putty.h" #include "ssh.h" +#include "sshchan.h" #include "tree234.h" #define GET_16BIT(endian, cp) \ @@ -26,7 +27,7 @@ struct XDMSeen { unsigned char clientid[6]; }; -struct X11Connection { +typedef struct X11Connection { unsigned char firstpkt[12]; /* first X data packet */ tree234 *authtree; struct X11Display *disp; @@ -34,7 +35,7 @@ struct X11Connection { unsigned char *auth_data; int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; int verified; - int throttled, throttle_override; + int input_wanted; int no_data_sent_to_x_client; char *peer_addr; int peer_port; @@ -42,7 +43,8 @@ struct X11Connection { Socket s; const Plug_vtable *plugvt; -}; + Channel chan; +} X11Connection; static int xdmseen_cmp(void *a, void *b) { @@ -666,11 +668,8 @@ static void x11_receive(Plug plug, int urgent, char *data, int len) struct X11Connection *xconn = FROMFIELD( plug, struct X11Connection, plugvt); - if (sshfwd_write(xconn->c, data, len) > 0) { - xconn->throttled = 1; - xconn->no_data_sent_to_x_client = FALSE; - sk_set_frozen(xconn->s, 1); - } + xconn->no_data_sent_to_x_client = FALSE; + sshfwd_write(xconn->c, data, len); } static void x11_sent(Plug plug, int bufsize) @@ -707,12 +706,30 @@ static const Plug_vtable X11Connection_plugvt = { NULL }; +static void x11_chan_free(Channel *chan); +static int x11_send(Channel *chan, int is_stderr, const void *vdata, int len); +static void x11_send_eof(Channel *chan); +static void x11_set_input_wanted(Channel *chan, int wanted); +static char *x11_log_close_msg(Channel *chan); + +static const struct ChannelVtable X11Connection_channelvt = { + x11_chan_free, + chan_remotely_opened_confirmation, + chan_remotely_opened_failure, + x11_send, + x11_send_eof, + x11_set_input_wanted, + x11_log_close_msg, + chan_no_eager_close, +}; + /* * Called to set up the X11Connection structure, though this does not * yet connect to an actual server. */ -struct X11Connection *x11_init(tree234 *authtree, void *c, - const char *peeraddr, int peerport) +Channel *x11_new_channel(tree234 *authtree, struct ssh_channel *c, + const char *peeraddr, int peerport, + int connection_sharing_possible) { struct X11Connection *xconn; @@ -721,11 +738,14 @@ struct X11Connection *x11_init(tree234 *authtree, void *c, */ xconn = snew(struct X11Connection); xconn->plugvt = &X11Connection_plugvt; + xconn->chan.vt = &X11Connection_channelvt; + xconn->chan.initial_fixed_window_size = + (connection_sharing_possible ? 128 : 0); xconn->auth_protocol = NULL; xconn->authtree = authtree; xconn->verified = 0; xconn->data_read = 0; - xconn->throttled = xconn->throttle_override = 0; + xconn->input_wanted = TRUE; xconn->no_data_sent_to_x_client = TRUE; xconn->c = c; @@ -746,13 +766,13 @@ struct X11Connection *x11_init(tree234 *authtree, void *c, xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL; xconn->peer_port = peerport; - return xconn; + return &xconn->chan; } -void x11_close(struct X11Connection *xconn) +static void x11_chan_free(Channel *chan) { - if (!xconn) - return; + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = FROMFIELD(chan, X11Connection, chan); if (xconn->auth_protocol) { sfree(xconn->auth_protocol); @@ -766,24 +786,14 @@ void x11_close(struct X11Connection *xconn) sfree(xconn); } -void x11_unthrottle(struct X11Connection *xconn) +static void x11_set_input_wanted(Channel *chan, int wanted) { - if (!xconn) - return; + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = FROMFIELD(chan, X11Connection, chan); - xconn->throttled = 0; + xconn->input_wanted = wanted; if (xconn->s) - sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override); -} - -void x11_override_throttle(struct X11Connection *xconn, int enable) -{ - if (!xconn) - return; - - xconn->throttle_override = enable; - if (xconn->s) - sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override); + sk_set_frozen(xconn->s, !xconn->input_wanted); } static void x11_send_init_error(struct X11Connection *xconn, @@ -831,13 +841,12 @@ static int x11_parse_ip(const char *addr_string, unsigned long *ip) /* * Called to send data down the raw connection. */ -int x11_send(struct X11Connection *xconn, const void *vdata, int len) +static int x11_send(Channel *chan, int is_stderr, const void *vdata, int len) { + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = FROMFIELD(chan, X11Connection, chan); const char *data = (const char *)vdata; - if (!xconn) - return 0; - /* * Read the first packet. */ @@ -914,7 +923,8 @@ int x11_send(struct X11Connection *xconn, const void *vdata, int len) /* * If this auth points to a connection-sharing downstream * rather than an X display we know how to connect to - * directly, pass it off to the sharing module now. + * directly, pass it off to the sharing module now. (This will + * have the side effect of freeing xconn.) */ if (auth_matched->share_cs) { sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs, @@ -929,7 +939,8 @@ int x11_send(struct X11Connection *xconn, const void *vdata, int len) * Now we know we're going to accept the connection, and what * X display to connect to. Actually connect to it. */ - sshfwd_x11_is_local(xconn->c); + xconn->chan.initial_fixed_window_size = 0; + sshfwd_window_override_removed(xconn->c); xconn->disp = auth_matched->disp; xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), xconn->disp->realhost, xconn->disp->port, @@ -984,8 +995,11 @@ int x11_send(struct X11Connection *xconn, const void *vdata, int len) return sk_write(xconn->s, data, len); } -void x11_send_eof(struct X11Connection *xconn) +static void x11_send_eof(Channel *chan) { + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = FROMFIELD(chan, X11Connection, chan); + if (xconn->s) { sk_write_eof(xconn->s); } else { @@ -1000,6 +1014,11 @@ void x11_send_eof(struct X11Connection *xconn) } } +static char *x11_log_close_msg(Channel *chan) +{ + return dupstr("Forwarded X11 connection terminated"); +} + /* * Utility functions used by connection sharing to convert textual * representations of an X11 auth protocol name + hex cookie into our