X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/06fadff539456d105c682f5272703e66500c7e86..fda2feb1b6e51994fef214de385874f9f6715c3b:/ssh.c diff --git a/ssh.c b/ssh.c index dc58727a..e619b599 100644 --- a/ssh.c +++ b/ssh.c @@ -497,11 +497,34 @@ struct ssh_channel { * Hence, in SSH 1 this structure is indexed by destination * host:port pair, whereas in SSH 2 it is indexed by source port. */ +struct ssh_portfwd; /* forward declaration */ + struct ssh_rportfwd { unsigned sport, dport; char dhost[256]; char *sportdesc; + struct ssh_portfwd *pfrec; }; +#define free_rportfwd(pf) ( \ + ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), 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 { + int keep; + int type; + unsigned sport, dport; + char *saddr, *daddr; + struct ssh_rportfwd *remote; + void *local; +}; +#define free_portfwd(pf) ( \ + ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr)) : (void)0 ), sfree(pf) ) struct Packet { long length; @@ -616,7 +639,7 @@ struct ssh_tag { struct ssh_channel *mainchan; /* primary session channel */ int exitcode; - tree234 *rportfwds; + tree234 *rportfwds, *portfwds; enum { SSH_STATE_PREPACKET, @@ -816,6 +839,47 @@ 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 ( (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; @@ -3596,8 +3660,7 @@ static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx) rpf = del234(ssh->rportfwds, pf); assert(rpf == pf); - sfree(pf->sportdesc); - sfree(pf); + free_rportfwd(pf); } } @@ -3611,6 +3674,21 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) portfwd_strptr = cfg->portfwd; + if (!ssh->portfwds) { + ssh->portfwds = newtree234(ssh_portcmp); + } else { + /* + * Go through the existing port forwardings and tag them + * with keep==FALSE. Any that we want to keep will be + * re-enabled 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->keep = FALSE; + } + while (*portfwd_strptr) { type = *portfwd_strptr++; saddr[0] = '\0'; @@ -3680,13 +3758,33 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) } if (sport && dport) { /* Set up a description of the source port. */ - static char *sportdesc; + struct ssh_portfwd *pfrec, *epfrec; + char *sportdesc; sportdesc = dupprintf("%.*s%.*s%.*s%.*s%d%.*s", (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL, (int)(*saddr?1:0), ":", (int)(sserv ? strlen(sports) : 0), sports, sserv, "(", sport, sserv, ")"); - if (type == 'L') { + + pfrec = snew(struct ssh_portfwd); + pfrec->type = type; + pfrec->saddr = *saddr ? dupstr(saddr) : NULL; + pfrec->sport = sport; + pfrec->daddr = dupstr(host); + pfrec->dport = dport; + pfrec->local = NULL; + pfrec->remote = NULL; + + epfrec = add234(ssh->portfwds, pfrec); + if (epfrec != pfrec) { + /* + * We already have a port forwarding with precisely + * these parameters. Hence, no need to do anything; + * simply tag the existing one as `keep'. + */ + epfrec->keep = TRUE; + free_portfwd(pfrec); + } else if (type == 'L') { /* Verbose description of the destination port */ char *dportdesc = dupprintf("%s:%.*s%.*s%d%.*s", host, @@ -3694,7 +3792,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) dserv, "(", dport, dserv, ")"); const char *err = pfd_addforward(host, dport, *saddr ? saddr : NULL, - sport, ssh, &ssh->cfg); + sport, ssh, &ssh->cfg, + &pfrec->local); if (err) { logeventf(ssh, "Local port %s forward to %s" " failed: %s", sportdesc, dportdesc, err); @@ -3706,7 +3805,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) } else if (type == 'D') { const char *err = pfd_addforward(NULL, -1, *saddr ? saddr : NULL, - sport, ssh, &ssh->cfg); + sport, ssh, &ssh->cfg, + &pfrec->local); if (err) { logeventf(ssh, "Local port %s SOCKS dynamic forward" " setup failed: %s", sportdesc, err); @@ -3744,6 +3844,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) pf->sportdesc = sportdesc; sportdesc = NULL; + pfrec->remote = pf; + pf->pfrec = pfrec; if (ssh->version == 1) { send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST, @@ -3778,6 +3880,78 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) sfree(sportdesc); } } + + /* + * Now go through and destroy any port forwardings which were + * not re-enabled. + */ + { + struct ssh_portfwd *epf; + int i; + for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) + if (!epf->keep) { + 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); + + if (epf->remote) { + struct ssh_rportfwd *rpf = epf->remote; + struct Packet *pktout; + + /* + * Cancel the port forwarding at the server + * end. + */ + if (ssh->version == 1) { + /* + * We cannot cancel listening ports on the + * server side in SSH1! 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 = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); + ssh2_pkt_addstring(pktout, "cancel-tcpip-forward"); + ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */ + if (epf->saddr) { + ssh2_pkt_addstring(pktout, epf->saddr); + } else if (ssh->cfg.rport_acceptall) { + ssh2_pkt_addstring(pktout, "0.0.0.0"); + } else { + ssh2_pkt_addstring(pktout, "127.0.0.1"); + } + ssh2_pkt_adduint32(pktout, epf->sport); + ssh2_pkt_send(ssh, pktout); + } + + del234(ssh->rportfwds, rpf); + free_rportfwd(rpf); + } else if (epf->local) { + pfd_terminate(epf->local); + } + + delpos234(ssh->portfwds, i); + free_portfwd(epf); + i--; /* so we don't skip one in the list */ + } + } } static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin) @@ -7281,17 +7455,12 @@ static void ssh_free(void *handle) /* * Reconfigure the SSH backend. - * - * Currently, this function does nothing very useful. In future, - * however, we could do some handy things with it. For example, we - * could make the port forwarding configurer active in the Change - * Settings box, and this routine could close down existing - * forwardings and open up new ones in response to changes. */ static void ssh_reconfig(void *handle, Config *cfg) { Ssh ssh = (Ssh) handle; pinger_reconfig(ssh->pinger, &ssh->cfg, cfg); + ssh_setup_portfwd(ssh, cfg); ssh->cfg = *cfg; /* STRUCTURE COPY */ }