From 895b09a4c65243265d4f155bd1b32070bbda4ec4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 14 Sep 2018 17:04:39 +0100 Subject: [PATCH] Move port-forwarding setup out of ssh.c. The tree234 storing currently active port forwardings - both local and remote - now lives in portfwd.c, as does the complicated function that updates it based on a Conf listing the new set of desired forwardings. Local port forwardings are passed to ssh.c via the same route as before - once the listening port receives a connection and portfwd.c knows where it should be directed to (in particular, after the SOCKS exchange, if any), it calls ssh_send_port_open. Remote forwardings are now initiated by calling ssh_rportfwd_alloc, which adds an entry to the rportfwds tree (which _is_ still in ssh.c, and still confusingly sorted by a different criterion depending on SSH protocol version) and sends out the appropriate protocol request. ssh_rportfwd_remove cancels one again, sending a protocol request too. Those functions look enough like ssh_{alloc,remove}_sharing_rportfwd that I've merged those into the new pair as well - now allocating an rportfwd allows you to specify either a destination host/port or a sharing context, and returns a handy pointer you can use to cancel the forwarding later. --- defs.h | 3 + misc.c | 11 ++ misc.h | 6 + portfwd.c | 511 ++++++++++++++++++++++++++++++++++++++++++------ ssh.c | 556 ++++++++++------------------------------------------- ssh.h | 33 ++-- sshshare.c | 16 +- 7 files changed, 597 insertions(+), 539 deletions(-) diff --git a/defs.h b/defs.h index e6efe0a9..822ee71d 100644 --- a/defs.h +++ b/defs.h @@ -60,6 +60,9 @@ typedef struct ssh_sharing_state ssh_sharing_state; typedef struct ssh_sharing_connstate ssh_sharing_connstate; typedef struct share_channel share_channel; +typedef struct PortFwdManager PortFwdManager; +typedef struct PortFwdRecord PortFwdRecord; + typedef struct dlgparam dlgparam; typedef struct settings_w settings_w; diff --git a/misc.c b/misc.c index fdd5635c..f4334a04 100644 --- a/misc.c +++ b/misc.c @@ -1181,6 +1181,17 @@ int smemeq(const void *av, const void *bv, size_t len) return (0x100 - val) >> 8; } +int nullstrcmp(const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 0; + if (a == NULL) + return -1; + if (b == NULL) + return +1; + return strcmp(a, b); +} + ptrlen make_ptrlen(const void *ptr, size_t len) { ptrlen pl; diff --git a/misc.h b/misc.h index 35d9de6c..242d1a94 100644 --- a/misc.h +++ b/misc.h @@ -87,6 +87,12 @@ int validate_manual_hostkey(char *key); struct tm ltime(void); +/* + * Special form of strcmp which can cope with NULL inputs. NULL is + * defined to sort before even the empty string. + */ +int nullstrcmp(const char *a, const char *b); + ptrlen make_ptrlen(const void *ptr, size_t len); int ptrlen_eq_string(ptrlen pl, const char *str); char *mkstr(ptrlen pl); diff --git a/portfwd.c b/portfwd.c index 53d5234a..d200c78b 100644 --- a/portfwd.c +++ b/portfwd.c @@ -10,6 +10,18 @@ #include "ssh.h" #include "sshchan.h" +static void logeventf(Frontend *frontend, const char *fmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, fmt); + buf = dupvprintf(fmt, ap); + va_end(ap); + logevent(frontend, buf); + sfree(buf); +} + /* * Enumeration of values that live in the 'socks_state' field of * struct PortForwarding. @@ -139,6 +151,8 @@ static void pfd_closing(Plug plug, const char *error_msg, int error_code, } } +static void pfl_terminate(struct PortListener *pl); + static void pfl_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { @@ -447,61 +461,6 @@ static const struct ChannelVtable PortForwarding_channelvt = { chan_no_eager_close, }; -/* - * Called when receiving a PORT OPEN from the server to make a - * connection to a destination host. - * - * On success, returns NULL and fills in *pf_ret. On error, returns a - * dynamically allocated error message string. - */ -char *pfd_connect(Channel **chan_ret, char *hostname,int port, - SshChannel *c, Conf *conf, int addressfamily) -{ - SockAddr addr; - const char *err; - char *dummy_realhost = NULL; - struct PortForwarding *pf; - - /* - * Try to find host. - */ - addr = name_lookup(hostname, port, &dummy_realhost, conf, addressfamily, - NULL, NULL); - if ((err = sk_addr_error(addr)) != NULL) { - char *err_ret = dupstr(err); - sk_addr_free(addr); - sfree(dummy_realhost); - return err_ret; - } - - /* - * Open socket. - */ - pf = new_portfwd_state(); - *chan_ret = &pf->chan; - pf->plugvt = &PortForwarding_plugvt; - 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 */ - pf->socks_state = SOCKS_NONE; - - pf->s = new_connection(addr, dummy_realhost, port, - 0, 1, 0, 0, &pf->plugvt, conf); - sfree(dummy_realhost); - if ((err = sk_socket_error(pf->s)) != NULL) { - char *err_ret = dupstr(err); - sk_close(pf->s); - free_portfwd_state(pf); - *chan_ret = NULL; - return err_ret; - } - - return NULL; -} - /* called when someone connects to the local port */ @@ -560,12 +519,14 @@ static const Plug_vtable PortListener_plugvt = { /* * Add a new port-forwarding listener from srcaddr:port -> desthost:destport. * + * desthost == NULL indicates dynamic SOCKS port forwarding. + * * On success, returns NULL and fills in *pl_ret. On error, returns a * dynamically allocated error message string. */ -char *pfl_listen(char *desthost, int destport, char *srcaddr, - int port, Ssh ssh, Conf *conf, - struct PortListener **pl_ret, int address_family) +static char *pfl_listen(char *desthost, int destport, char *srcaddr, + int port, Ssh ssh, Conf *conf, + struct PortListener **pl_ret, int address_family) { const char *err; struct PortListener *pl; @@ -614,7 +575,7 @@ static void pfd_close(struct PortForwarding *pf) /* * Terminate a listener. */ -void pfl_terminate(struct PortListener *pl) +static void pfl_terminate(struct PortListener *pl) { if (!pl) return; @@ -676,9 +637,431 @@ 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); + logeventf(ssh_get_frontend(pf->ssh), + "Forwarded connection refused by server%s%s", + errtext ? ": " : "", errtext ? errtext : ""); +} + +/* ---------------------------------------------------------------------- + * Code to manage the complete set of currently active port + * forwardings, and update it from Conf. + */ + +struct PortFwdRecord { + enum { DESTROY, KEEP, CREATE } status; + int type; + unsigned sport, dport; + char *saddr, *daddr; + char *sserv, *dserv; + struct ssh_rportfwd *remote; + int addressfamily; + struct PortListener *local; +}; + +static int pfr_cmp(void *av, void *bv) +{ + PortFwdRecord *a = (PortFwdRecord *) av; + PortFwdRecord *b = (PortFwdRecord *) bv; + int i; + if (a->type > b->type) + return +1; + if (a->type < b->type) + return -1; + if (a->addressfamily > b->addressfamily) + return +1; + if (a->addressfamily < b->addressfamily) + return -1; + if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0) + return i < 0 ? -1 : +1; + if (a->sport > b->sport) + return +1; + if (a->sport < b->sport) + return -1; + if (a->type != 'D') { + if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0) + return i < 0 ? -1 : +1; + if (a->dport > b->dport) + return +1; + if (a->dport < b->dport) + return -1; + } + return 0; +} + +void pfr_free(PortFwdRecord *pfr) +{ + /* Dispose of any listening socket. */ + if (pfr->local) + pfl_terminate(pfr->local); + + sfree(pfr->saddr); + sfree(pfr->daddr); + sfree(pfr->sserv); + sfree(pfr->dserv); + sfree(pfr); +} + +struct PortFwdManager { + Ssh ssh; + Frontend *frontend; + Conf *conf; + tree234 *forwardings; +}; + +PortFwdManager *portfwdmgr_new(Ssh ssh) +{ + PortFwdManager *mgr = snew(PortFwdManager); + + mgr->ssh = ssh; + mgr->frontend = ssh_get_frontend(ssh); + mgr->conf = NULL; + mgr->forwardings = newtree234(pfr_cmp); + + return mgr; +} + +void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr) +{ + PortFwdRecord *realpfr = del234(mgr->forwardings, pfr); + if (realpfr == pfr) + pfr_free(pfr); +} + +void portfwdmgr_close_all(PortFwdManager *mgr) +{ + PortFwdRecord *pfr; + + while ((pfr = delpos234(mgr->forwardings, 0)) != NULL) + pfr_free(pfr); +} + +void portfwdmgr_free(PortFwdManager *mgr) +{ + portfwdmgr_close_all(mgr); + freetree234(mgr->forwardings); + if (mgr->conf) + conf_free(mgr->conf); + sfree(mgr); +} + +void portfwdmgr_config(PortFwdManager *mgr, Conf *conf) +{ + PortFwdRecord *pfr; + int i; + char *key, *val; + + if (mgr->conf) + conf_free(mgr->conf); + mgr->conf = conf_copy(conf); + + /* + * Go through the existing port forwardings and tag them + * with status==DESTROY. Any that we want to keep will be + * re-enabled (status==KEEP) as we go through the + * configuration and find out which bits are the same as + * they were before. + */ + for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) + pfr->status = DESTROY; + + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + char *kp, *kp2, *vp, *vp2; + char address_family, type; + int sport, dport, sserv, dserv; + char *sports, *dports, *saddr, *host; + + kp = key; + + address_family = 'A'; + type = 'L'; + if (*kp == 'A' || *kp == '4' || *kp == '6') + address_family = *kp++; + if (*kp == 'L' || *kp == 'R') + type = *kp++; + + if ((kp2 = host_strchr(kp, ':')) != NULL) { + /* + * There's a colon in the middle of the source port + * string, which means that the part before it is + * actually a source address. + */ + char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp); + saddr = host_strduptrim(saddr_tmp); + sfree(saddr_tmp); + sports = kp2+1; + } else { + saddr = NULL; + sports = kp; + } + sport = atoi(sports); + sserv = 0; + if (sport == 0) { + sserv = 1; + sport = net_service_lookup(sports); + if (!sport) { + logeventf(mgr->frontend, "Service lookup failed for source" + " port \"%s\"", sports); + } + } + + if (type == 'L' && !strcmp(val, "D")) { + /* dynamic forwarding */ + host = NULL; + dports = NULL; + dport = -1; + dserv = 0; + type = 'D'; + } else { + /* ordinary forwarding */ + vp = val; + vp2 = vp + host_strcspn(vp, ":"); + host = dupprintf("%.*s", (int)(vp2 - vp), vp); + if (*vp2) + vp2++; + dports = vp2; + dport = atoi(dports); + dserv = 0; + if (dport == 0) { + dserv = 1; + dport = net_service_lookup(dports); + if (!dport) { + logeventf(mgr->frontend, + "Service lookup failed for destination" + " port \"%s\"", dports); + } + } + } + + if (sport && dport) { + /* Set up a description of the source port. */ + pfr = snew(PortFwdRecord); + pfr->type = type; + pfr->saddr = saddr; + pfr->sserv = sserv ? dupstr(sports) : NULL; + pfr->sport = sport; + pfr->daddr = host; + pfr->dserv = dserv ? dupstr(dports) : NULL; + pfr->dport = dport; + pfr->local = NULL; + pfr->remote = NULL; + pfr->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : + address_family == '6' ? ADDRTYPE_IPV6 : + ADDRTYPE_UNSPEC); + + PortFwdRecord *existing = add234(mgr->forwardings, pfr); + if (existing != pfr) { + if (existing->status == DESTROY) { + /* + * We already have a port forwarding up and running + * with precisely these parameters. Hence, no need + * to do anything; simply re-tag the existing one + * as KEEP. + */ + existing->status = KEEP; + } + /* + * Anything else indicates that there was a duplicate + * in our input, which we'll silently ignore. + */ + pfr_free(pfr); + } else { + pfr->status = CREATE; + } + } else { + sfree(saddr); + sfree(host); + } + } + + /* + * Now go through and destroy any port forwardings which were + * not re-enabled. + */ + for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) { + if (pfr->status == DESTROY) { + char *message; + + message = dupprintf("%s port forwarding from %s%s%d", + pfr->type == 'L' ? "local" : + pfr->type == 'R' ? "remote" : "dynamic", + pfr->saddr ? pfr->saddr : "", + pfr->saddr ? ":" : "", + pfr->sport); + + if (pfr->type != 'D') { + char *msg2 = dupprintf("%s to %s:%d", message, + pfr->daddr, pfr->dport); + sfree(message); + message = msg2; + } + + logeventf(mgr->frontend, "Cancelling %s", message); + sfree(message); + + /* pfr->remote or pfr->local may be NULL if setting up a + * forwarding failed. */ + if (pfr->remote) { + /* + * Cancel the port forwarding at the server + * end. + * + * Actually closing the listening port on the server + * side may fail - because in SSH-1 there's no message + * in the protocol to request it! + * + * Instead, we simply remove the record of the + * forwarding from our local end, so that any + * connections the server tries to make on it are + * rejected. + */ + ssh_rportfwd_remove(mgr->ssh, pfr->remote); + } else if (pfr->local) { + pfl_terminate(pfr->local); + } + + delpos234(mgr->forwardings, i); + pfr_free(pfr); + i--; /* so we don't skip one in the list */ + } + } + + /* + * And finally, set up any new port forwardings (status==CREATE). + */ + for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) { + if (pfr->status == CREATE) { + char *sportdesc, *dportdesc; + sportdesc = dupprintf("%s%s%s%s%d%s", + pfr->saddr ? pfr->saddr : "", + pfr->saddr ? ":" : "", + pfr->sserv ? pfr->sserv : "", + pfr->sserv ? "(" : "", + pfr->sport, + pfr->sserv ? ")" : ""); + if (pfr->type == 'D') { + dportdesc = NULL; + } else { + dportdesc = dupprintf("%s:%s%s%d%s", + pfr->daddr, + pfr->dserv ? pfr->dserv : "", + pfr->dserv ? "(" : "", + pfr->dport, + pfr->dserv ? ")" : ""); + } + + if (pfr->type == 'L') { + char *err = pfl_listen(pfr->daddr, pfr->dport, + pfr->saddr, pfr->sport, + mgr->ssh, conf, &pfr->local, + pfr->addressfamily); + + logeventf(mgr->frontend, + "Local %sport %s forwarding to %s%s%s", + pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : + pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", + sportdesc, dportdesc, + err ? " failed: " : "", err ? err : ""); + if (err) + sfree(err); + } else if (pfr->type == 'D') { + char *err = pfl_listen(NULL, -1, pfr->saddr, pfr->sport, + mgr->ssh, conf, &pfr->local, + pfr->addressfamily); + + logeventf(mgr->frontend, + "Local %sport %s SOCKS dynamic forwarding%s%s", + pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : + pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", + sportdesc, + err ? " failed: " : "", err ? err : ""); + + if (err) + sfree(err); + } else { + const char *shost; + + if (pfr->saddr) { + shost = pfr->saddr; + } else if (conf_get_int(conf, CONF_rport_acceptall)) { + shost = ""; + } else { + shost = "localhost"; + } + + pfr->remote = ssh_rportfwd_alloc( + mgr->ssh, shost, pfr->sport, pfr->daddr, pfr->dport, + pfr->addressfamily, sportdesc, pfr, NULL); + + if (!pfr->remote) { + logeventf(mgr->frontend, + "Duplicate remote port forwarding to %s:%d", + pfr->daddr, pfr->dport); + pfr_free(pfr); + } else { + logeventf(mgr->frontend, "Requesting remote port %s" + " forward to %s", sportdesc, dportdesc); + } + } + sfree(sportdesc); + sfree(dportdesc); + } + } +} + +/* + * Called when receiving a PORT OPEN from the server to make a + * connection to a destination host. + * + * On success, returns NULL and fills in *pf_ret. On error, returns a + * dynamically allocated error message string. + */ +char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, + char *hostname, int port, SshChannel *c, + int addressfamily) +{ + SockAddr addr; + const char *err; + char *dummy_realhost = NULL; + struct PortForwarding *pf; + + /* + * Try to find host. + */ + addr = name_lookup(hostname, port, &dummy_realhost, mgr->conf, + addressfamily, NULL, NULL); + if ((err = sk_addr_error(addr)) != NULL) { + char *err_ret = dupstr(err); + sk_addr_free(addr); + sfree(dummy_realhost); + return err_ret; + } + + /* + * Open socket. + */ + pf = new_portfwd_state(); + *chan_ret = &pf->chan; + pf->plugvt = &PortForwarding_plugvt; + pf->chan.initial_fixed_window_size = 0; + pf->chan.vt = &PortForwarding_channelvt; + pf->input_wanted = TRUE; + pf->ready = 1; + pf->c = c; + pf->ssh = mgr->ssh; + pf->socks_state = SOCKS_NONE; + + pf->s = new_connection(addr, dummy_realhost, port, + 0, 1, 0, 0, &pf->plugvt, mgr->conf); + sfree(dummy_realhost); + if ((err = sk_socket_error(pf->s)) != NULL) { + char *err_ret = dupstr(err); + sk_close(pf->s); + free_portfwd_state(pf); + *chan_ret = NULL; + return err_ret; + } + + return NULL; } diff --git a/ssh.c b/ssh.c index eaf1f386..bedfe6af 100644 --- a/ssh.c +++ b/ssh.c @@ -527,42 +527,22 @@ struct ssh_portfwd; /* forward declaration */ struct ssh_rportfwd { unsigned sport, dport; char *shost, *dhost; - char *sportdesc; + int addressfamily; + char *log_description; /* name of remote listening port, for logging */ ssh_sharing_connstate *share_ctx; - struct ssh_portfwd *pfrec; + PortFwdRecord *pfr; }; static void free_rportfwd(struct ssh_rportfwd *pf) { if (pf) { - sfree(pf->sportdesc); + sfree(pf->log_description); sfree(pf->shost); sfree(pf->dhost); sfree(pf); } } -/* - * Separately to the rportfwd tree (which is for looking up port - * open requests from the server), a tree of _these_ structures is - * used to keep track of all the currently open port forwardings, - * so that we can reconfigure in mid-session if the user requests - * it. - */ -struct ssh_portfwd { - enum { DESTROY, KEEP, CREATE } status; - int type; - unsigned sport, dport; - char *saddr, *daddr; - char *sserv, *dserv; - struct ssh_rportfwd *remote; - int addressfamily; - struct PortListener *local; -}; -#define free_portfwd(pf) ( \ - ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \ - sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) ) - static void ssh1_protocol_setup(Ssh ssh); static void ssh2_protocol_setup(Ssh ssh); static void ssh2_bare_connection_protocol_setup(Ssh ssh); @@ -746,7 +726,8 @@ struct ssh_tag { int clean_exit; int disconnect_message_seen; - tree234 *rportfwds, *portfwds; + tree234 *rportfwds; + PortFwdManager *portfwdmgr; enum { SSH_STATE_PREPACKET, @@ -1063,51 +1044,6 @@ static int ssh_rportcmp_ssh2(void *av, void *bv) return 0; } -/* - * Special form of strcmp which can cope with NULL inputs. NULL is - * defined to sort before even the empty string. - */ -static int nullstrcmp(const char *a, const char *b) -{ - if (a == NULL && b == NULL) - return 0; - if (a == NULL) - return -1; - if (b == NULL) - return +1; - return strcmp(a, b); -} - -static int ssh_portcmp(void *av, void *bv) -{ - struct ssh_portfwd *a = (struct ssh_portfwd *) av; - struct ssh_portfwd *b = (struct ssh_portfwd *) bv; - int i; - if (a->type > b->type) - return +1; - if (a->type < b->type) - return -1; - if (a->addressfamily > b->addressfamily) - return +1; - if (a->addressfamily < b->addressfamily) - return -1; - if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0) - return i < 0 ? -1 : +1; - if (a->sport > b->sport) - return +1; - if (a->sport < b->sport) - return -1; - if (a->type != 'D') { - if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0) - return i < 0 ? -1 : +1; - if (a->dport > b->dport) - return +1; - if (a->dport < b->dport) - return -1; - } - return 0; -} - static int alloc_channel_id(Ssh ssh) { const unsigned CHANNEL_NUMBER_OFFSET = 256; @@ -2230,18 +2166,7 @@ static int ssh_do_close(Ssh ssh, int notify_exit) * Go through port-forwardings, and close any associated * listening sockets. */ - if (ssh->portfwds) { - struct ssh_portfwd *pf; - while (NULL != (pf = index234(ssh->portfwds, 0))) { - /* Dispose of any listening socket. */ - if (pf->local) - pfl_terminate(pf->local); - del234(ssh->portfwds, pf); /* moving next one to index 0 */ - free_portfwd(pf); - } - freetree234(ssh->portfwds); - ssh->portfwds = NULL; - } + portfwdmgr_close_all(ssh->portfwdmgr); /* * Also stop attempting to connection-share. @@ -3816,58 +3741,110 @@ static void ssh_queue_handler(Ssh ssh, int msg1, int msg2, static void ssh_rportfwd_succfail(Ssh ssh, PktIn *pktin, void *ctx) { - struct ssh_rportfwd *rpf, *pf = (struct ssh_rportfwd *)ctx; + struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS : SSH2_MSG_REQUEST_SUCCESS)) { logeventf(ssh, "Remote port forwarding from %s enabled", - pf->sportdesc); + rpf->log_description); } else { logeventf(ssh, "Remote port forwarding from %s refused", - pf->sportdesc); + rpf->log_description); - rpf = del234(ssh->rportfwds, pf); - assert(rpf == pf); - pf->pfrec->remote = NULL; - free_rportfwd(pf); + struct ssh_rportfwd *realpf = del234(ssh->rportfwds, rpf); + assert(realpf == rpf); + portfwdmgr_close(ssh->portfwdmgr, rpf->pfr); + free_rportfwd(rpf); } } -int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport, - ssh_sharing_connstate *share_ctx) +struct ssh_rportfwd *ssh_rportfwd_alloc( + Ssh ssh, const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx) { - struct ssh_rportfwd *pf = snew(struct ssh_rportfwd); - pf->dhost = NULL; - pf->dport = 0; - pf->share_ctx = share_ctx; - pf->shost = dupstr(shost); - pf->sport = sport; - pf->sportdesc = NULL; + /* + * Ensure the remote port forwardings tree exists. + */ if (!ssh->rportfwds) { - assert(ssh->version == 2); - ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); + if (ssh->version == 1) + ssh->rportfwds = newtree234(ssh_rportcmp_ssh1); + else + ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); } - if (add234(ssh->rportfwds, pf) != pf) { - sfree(pf->shost); - sfree(pf); - return FALSE; + + struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); + + rpf->shost = dupstr(shost); + rpf->sport = sport; + rpf->dhost = dupstr(dhost); + rpf->dport = dport; + rpf->addressfamily = addressfamily; + rpf->log_description = dupstr(log_description); + rpf->pfr = pfr; + rpf->share_ctx = share_ctx; + + if (add234(ssh->rportfwds, rpf) != rpf) { + free_rportfwd(rpf); + return NULL; } - return TRUE; + + if (!rpf->share_ctx) { + PktOut *pktout; + + if (ssh->version == 1) { + pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH1_CMSG_PORT_FORWARD_REQUEST); + put_uint32(pktout, rpf->sport); + put_stringz(pktout, rpf->dhost); + put_uint32(pktout, rpf->dport); + ssh_pkt_write(ssh, pktout); + ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS, + SSH1_SMSG_FAILURE, + ssh_rportfwd_succfail, rpf); + } else { + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_GLOBAL_REQUEST); + put_stringz(pktout, "tcpip-forward"); + put_bool(pktout, 1); /* want reply */ + put_stringz(pktout, rpf->shost); + put_uint32(pktout, rpf->sport); + ssh2_pkt_send(ssh, pktout); + + ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, + SSH2_MSG_REQUEST_FAILURE, + ssh_rportfwd_succfail, rpf); + } + } + + return rpf; } -void ssh_remove_sharing_rportfwd(Ssh ssh, const char *shost, int sport, - ssh_sharing_connstate *share_ctx) +void ssh_rportfwd_remove(Ssh ssh, struct ssh_rportfwd *rpf) { - struct ssh_rportfwd pf, *realpf; + if (ssh->version == 1) { + /* + * We cannot cancel listening ports on the server side in + * SSH-1! There's no message to support it. + */ + } else if (rpf->share_ctx) { + /* + * We don't manufacture a cancel-tcpip-forward message for + * remote port forwardings being removed on behalf of a + * downstream; we just pass through the one the downstream + * sent to us. + */ + } else { + PktOut *pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_GLOBAL_REQUEST); + put_stringz(pktout, "cancel-tcpip-forward"); + put_bool(pktout, 0); /* _don't_ want reply */ + put_stringz(pktout, rpf->shost); + put_uint32(pktout, rpf->sport); + ssh2_pkt_send(ssh, pktout); + } - assert(ssh->rportfwds); - pf.shost = dupstr(shost); - pf.sport = sport; - realpf = del234(ssh->rportfwds, &pf); - assert(realpf); - assert(realpf->share_ctx == share_ctx); - sfree(realpf->shost); - sfree(realpf); + struct ssh_rportfwd *realpf = del234(ssh->rportfwds, rpf); + assert(realpf == rpf); + free_rportfwd(rpf); } static void ssh_sharing_global_request_response(Ssh ssh, PktIn *pktin, @@ -3885,334 +3862,6 @@ void ssh_sharing_queue_global_request(Ssh ssh, ssh_sharing_global_request_response, share_ctx); } -static void ssh_setup_portfwd(Ssh ssh, Conf *conf) -{ - struct ssh_portfwd *epf; - int i; - char *key, *val; - - if (!ssh->portfwds) { - ssh->portfwds = newtree234(ssh_portcmp); - } else { - /* - * Go through the existing port forwardings and tag them - * with status==DESTROY. Any that we want to keep will be - * re-enabled (status==KEEP) as we go through the - * configuration and find out which bits are the same as - * they were before. - */ - struct ssh_portfwd *epf; - int i; - for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) - epf->status = DESTROY; - } - - for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); - val != NULL; - val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { - char *kp, *kp2, *vp, *vp2; - char address_family, type; - int sport,dport,sserv,dserv; - char *sports, *dports, *saddr, *host; - - kp = key; - - address_family = 'A'; - type = 'L'; - if (*kp == 'A' || *kp == '4' || *kp == '6') - address_family = *kp++; - if (*kp == 'L' || *kp == 'R') - type = *kp++; - - if ((kp2 = host_strchr(kp, ':')) != NULL) { - /* - * There's a colon in the middle of the source port - * string, which means that the part before it is - * actually a source address. - */ - char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp); - saddr = host_strduptrim(saddr_tmp); - sfree(saddr_tmp); - sports = kp2+1; - } else { - saddr = NULL; - sports = kp; - } - sport = atoi(sports); - sserv = 0; - if (sport == 0) { - sserv = 1; - sport = net_service_lookup(sports); - if (!sport) { - logeventf(ssh, "Service lookup failed for source" - " port \"%s\"", sports); - } - } - - if (type == 'L' && !strcmp(val, "D")) { - /* dynamic forwarding */ - host = NULL; - dports = NULL; - dport = -1; - dserv = 0; - type = 'D'; - } else { - /* ordinary forwarding */ - vp = val; - vp2 = vp + host_strcspn(vp, ":"); - host = dupprintf("%.*s", (int)(vp2 - vp), vp); - if (*vp2) - vp2++; - dports = vp2; - dport = atoi(dports); - dserv = 0; - if (dport == 0) { - dserv = 1; - dport = net_service_lookup(dports); - if (!dport) { - logeventf(ssh, "Service lookup failed for destination" - " port \"%s\"", dports); - } - } - } - - if (sport && dport) { - /* Set up a description of the source port. */ - struct ssh_portfwd *pfrec, *epfrec; - - pfrec = snew(struct ssh_portfwd); - pfrec->type = type; - pfrec->saddr = saddr; - pfrec->sserv = sserv ? dupstr(sports) : NULL; - pfrec->sport = sport; - pfrec->daddr = host; - pfrec->dserv = dserv ? dupstr(dports) : NULL; - pfrec->dport = dport; - pfrec->local = NULL; - pfrec->remote = NULL; - pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : - address_family == '6' ? ADDRTYPE_IPV6 : - ADDRTYPE_UNSPEC); - - epfrec = add234(ssh->portfwds, pfrec); - if (epfrec != pfrec) { - if (epfrec->status == DESTROY) { - /* - * We already have a port forwarding up and running - * with precisely these parameters. Hence, no need - * to do anything; simply re-tag the existing one - * as KEEP. - */ - epfrec->status = KEEP; - } - /* - * Anything else indicates that there was a duplicate - * in our input, which we'll silently ignore. - */ - free_portfwd(pfrec); - } else { - pfrec->status = CREATE; - } - } else { - sfree(saddr); - sfree(host); - } - } - - /* - * Now go through and destroy any port forwardings which were - * not re-enabled. - */ - for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) - if (epf->status == DESTROY) { - char *message; - - message = dupprintf("%s port forwarding from %s%s%d", - epf->type == 'L' ? "local" : - epf->type == 'R' ? "remote" : "dynamic", - epf->saddr ? epf->saddr : "", - epf->saddr ? ":" : "", - epf->sport); - - if (epf->type != 'D') { - char *msg2 = dupprintf("%s to %s:%d", message, - epf->daddr, epf->dport); - sfree(message); - message = msg2; - } - - logeventf(ssh, "Cancelling %s", message); - sfree(message); - - /* epf->remote or epf->local may be NULL if setting up a - * forwarding failed. */ - if (epf->remote) { - struct ssh_rportfwd *rpf = epf->remote; - PktOut *pktout; - - /* - * Cancel the port forwarding at the server - * end. - */ - if (ssh->version == 1) { - /* - * We cannot cancel listening ports on the - * server side in SSH-1! There's no message - * to support it. Instead, we simply remove - * the rportfwd record from the local end - * so that any connections the server tries - * to make on it are rejected. - */ - } else { - pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_GLOBAL_REQUEST); - put_stringz(pktout, "cancel-tcpip-forward"); - put_bool(pktout, 0);/* _don't_ want reply */ - if (epf->saddr) { - put_stringz(pktout, epf->saddr); - } else if (conf_get_int(conf, CONF_rport_acceptall)) { - /* XXX: rport_acceptall may not represent - * what was used to open the original connection, - * since it's reconfigurable. */ - put_stringz(pktout, ""); - } else { - put_stringz(pktout, "localhost"); - } - put_uint32(pktout, epf->sport); - ssh2_pkt_send(ssh, pktout); - } - - del234(ssh->rportfwds, rpf); - free_rportfwd(rpf); - } else if (epf->local) { - pfl_terminate(epf->local); - } - - delpos234(ssh->portfwds, i); - free_portfwd(epf); - i--; /* so we don't skip one in the list */ - } - - /* - * And finally, set up any new port forwardings (status==CREATE). - */ - for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) - if (epf->status == CREATE) { - char *sportdesc, *dportdesc; - sportdesc = dupprintf("%s%s%s%s%d%s", - epf->saddr ? epf->saddr : "", - epf->saddr ? ":" : "", - epf->sserv ? epf->sserv : "", - epf->sserv ? "(" : "", - epf->sport, - epf->sserv ? ")" : ""); - if (epf->type == 'D') { - dportdesc = NULL; - } else { - dportdesc = dupprintf("%s:%s%s%d%s", - epf->daddr, - epf->dserv ? epf->dserv : "", - epf->dserv ? "(" : "", - epf->dport, - epf->dserv ? ")" : ""); - } - - if (epf->type == 'L') { - char *err = pfl_listen(epf->daddr, epf->dport, - epf->saddr, epf->sport, - ssh, conf, &epf->local, - epf->addressfamily); - - logeventf(ssh, "Local %sport %s forwarding to %s%s%s", - epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : - epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", - sportdesc, dportdesc, - err ? " failed: " : "", err ? err : ""); - if (err) - sfree(err); - } else if (epf->type == 'D') { - char *err = pfl_listen(NULL, -1, epf->saddr, epf->sport, - ssh, conf, &epf->local, - epf->addressfamily); - - logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s", - epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : - epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", - sportdesc, - err ? " failed: " : "", err ? err : ""); - - if (err) - sfree(err); - } else { - struct ssh_rportfwd *pf; - - /* - * Ensure the remote port forwardings tree exists. - */ - if (!ssh->rportfwds) { - if (ssh->version == 1) - ssh->rportfwds = newtree234(ssh_rportcmp_ssh1); - else - ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); - } - - pf = snew(struct ssh_rportfwd); - pf->share_ctx = NULL; - pf->dhost = dupstr(epf->daddr); - pf->dport = epf->dport; - if (epf->saddr) { - pf->shost = dupstr(epf->saddr); - } else if (conf_get_int(conf, CONF_rport_acceptall)) { - pf->shost = dupstr(""); - } else { - pf->shost = dupstr("localhost"); - } - pf->sport = epf->sport; - if (add234(ssh->rportfwds, pf) != pf) { - logeventf(ssh, "Duplicate remote port forwarding to %s:%d", - epf->daddr, epf->dport); - sfree(pf); - } else { - PktOut *pktout; - - logeventf(ssh, "Requesting remote port %s" - " forward to %s", sportdesc, dportdesc); - - pf->sportdesc = sportdesc; - sportdesc = NULL; - epf->remote = pf; - pf->pfrec = epf; - - if (ssh->version == 1) { - pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH1_CMSG_PORT_FORWARD_REQUEST); - put_uint32(pktout, epf->sport); - put_stringz(pktout, epf->daddr); - put_uint32(pktout, epf->dport); - ssh_pkt_write(ssh, pktout); - ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS, - SSH1_SMSG_FAILURE, - ssh_rportfwd_succfail, pf); - } else { - pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_GLOBAL_REQUEST); - put_stringz(pktout, "tcpip-forward"); - put_bool(pktout, 1);/* want reply */ - put_stringz(pktout, pf->shost); - put_uint32(pktout, pf->sport); - ssh2_pkt_send(ssh, pktout); - - ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, - SSH2_MSG_REQUEST_FAILURE, - ssh_rportfwd_succfail, pf); - } - } - } - sfree(sportdesc); - sfree(dportdesc); - } -} - static void ssh1_smsg_stdout_stderr_data(Ssh ssh, PktIn *pktin) { ptrlen string; @@ -4321,8 +3970,8 @@ 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->chan, pf.dhost, port, - &c->sc, ssh->conf, pfp->pfrec->addressfamily); + err = portfwdmgr_connect(ssh->portfwdmgr, &c->chan, pf.dhost, port, + &c->sc, pfp->addressfamily); if (err != NULL) { logeventf(ssh, "Port open failed: %s", err); sfree(err); @@ -4562,7 +4211,7 @@ static void do_ssh1_connection(void *vctx) } } - ssh_setup_portfwd(ssh, ssh->conf); + portfwdmgr_config(ssh->portfwdmgr, ssh->conf); ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open; if (!conf_get_int(ssh->conf, CONF_nopty)) { @@ -7858,8 +7507,9 @@ static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) return; } - err = pfd_connect(&c->chan, realpf->dhost, realpf->dport, - &c->sc, ssh->conf, realpf->pfrec->addressfamily); + err = portfwdmgr_connect( + ssh->portfwdmgr, &c->chan, realpf->dhost, realpf->dport, + &c->sc, realpf->addressfamily); logeventf(ssh, "Attempting to forward remote port to " "%s:%d", realpf->dhost, realpf->dport); if (err != NULL) { @@ -9925,7 +9575,7 @@ static void do_ssh2_connection(void *vctx) /* * Enable port forwardings. */ - ssh_setup_portfwd(ssh, ssh->conf); + portfwdmgr_config(ssh->portfwdmgr, ssh->conf); if (ssh->mainchan && !ssh->ncmode) { /* @@ -10703,7 +10353,7 @@ static const char *ssh_init(Frontend *frontend, Backend **backend_handle, ssh->channels = NULL; ssh->rportfwds = NULL; - ssh->portfwds = NULL; + ssh->portfwdmgr = portfwdmgr_new(ssh); ssh->send_ok = 0; ssh->editing = 0; @@ -10798,6 +10448,7 @@ static void ssh_free(Backend *be) freetree234(ssh->rportfwds); ssh->rportfwds = NULL; } + portfwdmgr_free(ssh->portfwdmgr); if (ssh->x11disp) x11_free_display(ssh->x11disp); while ((auth = delpos234(ssh->x11authtree, 0)) != NULL) @@ -10864,8 +10515,7 @@ static void ssh_reconfig(Backend *be, Conf *conf) int i, rekey_time; pinger_reconfig(ssh->pinger, ssh->conf, conf); - if (ssh->portfwds) - ssh_setup_portfwd(ssh, conf); + portfwdmgr_config(ssh->portfwdmgr, conf); rekey_time = conf_get_int(conf, CONF_ssh_rekey_time); if (ssh2_timer_update(ssh, rekey_mins(rekey_time, 60))) diff --git a/ssh.h b/ssh.h index a4aea86e..4f5fc204 100644 --- a/ssh.h +++ b/ssh.h @@ -149,10 +149,6 @@ void ssh_connshare_log(Ssh ssh, int event, const char *logtext, const char *ds_err, const char *us_err); unsigned ssh_alloc_sharing_channel(Ssh ssh, ssh_sharing_connstate *connstate); void ssh_delete_sharing_channel(Ssh ssh, unsigned localid); -int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport, - ssh_sharing_connstate *connstate); -void ssh_remove_sharing_rportfwd(Ssh ssh, const char *shost, int sport, - ssh_sharing_connstate *connstate); void ssh_sharing_queue_global_request( Ssh ssh, ssh_sharing_connstate *connstate); struct X11FakeAuth *ssh_sharing_add_x11_display( @@ -175,6 +171,23 @@ void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, int protomajor, int protominor, const void *initial_data, int initial_len); +struct ssh_rportfwd; +struct ssh_rportfwd *ssh_rportfwd_alloc( + Ssh ssh, const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx); +void ssh_rportfwd_remove(Ssh ssh, struct ssh_rportfwd *rpf); + +/* Exports from portfwd.c */ +PortFwdManager *portfwdmgr_new(Ssh ssh); +void portfwdmgr_free(PortFwdManager *mgr); +void portfwdmgr_config(PortFwdManager *mgr, Conf *conf); +void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr); +void portfwdmgr_close_all(PortFwdManager *mgr); +char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, + char *hostname, int port, SshChannel *c, + int addressfamily); + Frontend *ssh_get_frontend(Ssh ssh); #define SSH_CIPHER_IDEA 1 @@ -718,22 +731,10 @@ void random_add_heavynoise(void *noise, int length); void logevent(Frontend *, const char *); -struct PortForwarding; - /* Allocate and register a new channel for port forwarding */ SshChannel *ssh_send_port_open(Ssh ssh, const char *hostname, int port, const char *org, Channel *chan); -/* Exports from portfwd.c */ -extern char *pfd_connect(Channel **chan_ret, char *hostname, int port, - SshChannel *c, Conf *conf, int addressfamily); -struct PortListener; -/* desthost == NULL indicates dynamic (SOCKS) port forwarding */ -extern char *pfl_listen(char *desthost, int destport, char *srcaddr, - int port, Ssh ssh, Conf *conf, - struct PortListener **pl, int address_family); -extern void pfl_terminate(struct PortListener *); - /* Exports from x11fwd.c */ enum { X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256 diff --git a/sshshare.c b/sshshare.c index 22611e7d..a43ab9c2 100644 --- a/sshshare.c +++ b/sshshare.c @@ -242,6 +242,7 @@ struct share_forwarding { char *host; int port; int active; /* has the server sent REQUEST_SUCCESS? */ + struct ssh_rportfwd *rpf; }; struct share_xchannel_message { @@ -856,8 +857,7 @@ static void share_try_cleanup(struct ssh_sharing_connstate *cs) "cleanup after downstream went away"); strbuf_free(packet); - ssh_remove_sharing_rportfwd(cs->parent->ssh, - fwd->host, fwd->port, cs); + ssh_rportfwd_remove(cs->parent->ssh, fwd->rpf); share_remove_forwarding(cs, fwd); i--; /* don't accidentally skip one as a result */ } @@ -1306,7 +1306,8 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, if (ptrlen_eq_string(request_name, "tcpip-forward")) { ptrlen hostpl; char *host; - int port, ret; + int port; + struct ssh_rportfwd *rpf; /* * Pick the packet apart to find the want_reply field and @@ -1328,8 +1329,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * 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) { + rpf = ssh_rportfwd_alloc( + cs->parent->ssh, host, port, NULL, 0, 0, NULL, NULL, cs); + if (!rpf) { if (orig_wantreply) { send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, "", 0, NULL); @@ -1359,6 +1361,8 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, globreq->fwd = fwd; globreq->want_reply = orig_wantreply; globreq->type = GLOBREQ_TCPIP_FORWARD; + + fwd->rpf = rpf; } } @@ -1395,7 +1399,7 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * Tell ssh.c to stop sending us channel-opens for * this forwarding. */ - ssh_remove_sharing_rportfwd(cs->parent->ssh, host, port, cs); + ssh_rportfwd_remove(cs->parent->ssh, fwd->rpf); /* * Pass the cancel request on to the SSH server, but