X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/06fadff539456d105c682f5272703e66500c7e86..31fb1866040c36038f24c1e2e79e36bf35993279:/ssh.c diff --git a/ssh.c b/ssh.c index dc58727a..3a7f6953 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, @@ -723,7 +746,7 @@ struct ssh_tag { unsigned long incoming_data_size, outgoing_data_size, deferred_data_size; unsigned long max_data_size; int kex_in_progress; - long next_rekey; + long next_rekey, last_rekey; }; #define logevent(s) logevent(ssh->frontend, s) @@ -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) @@ -4011,7 +4185,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) /* Data sent down one of our channels. */ int i = ssh_pkt_getuint32(pktin); char *p; - int len; + unsigned int len; struct ssh_channel *c; ssh_pkt_getstring(pktin, &p, &len); @@ -4030,7 +4204,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) /* Data for an agent message. Buffer it. */ while (len > 0) { if (c->u.a.lensofar < 4) { - int l = min(4 - c->u.a.lensofar, len); + unsigned int l = min(4 - c->u.a.lensofar, len); memcpy(c->u.a.msglen + c->u.a.lensofar, p, l); p += l; @@ -4045,7 +4219,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) memcpy(c->u.a.message, c->u.a.msglen, 4); } if (c->u.a.lensofar >= 4 && len > 0) { - int l = + unsigned int l = min(c->u.a.totallen - c->u.a.lensofar, len); memcpy(c->u.a.message + c->u.a.lensofar, p, @@ -5037,6 +5211,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, * Key exchange is over. Schedule a timer for our next rekey. */ ssh->kex_in_progress = FALSE; + ssh->last_rekey = GETTICKCOUNT(); if (ssh->cfg.ssh_rekey_time != 0) ssh->next_rekey = schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC, ssh2_timer, ssh); @@ -5160,7 +5335,7 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin) static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) { char *data; - int length; + unsigned int length; unsigned i = ssh_pkt_getuint32(pktin); struct ssh_channel *c; c = find234(ssh->channels, &i, ssh_channelfind); @@ -5189,7 +5364,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) case CHAN_AGENT: while (length > 0) { if (c->u.a.lensofar < 4) { - int l = min(4 - c->u.a.lensofar, length); + unsigned int l = min(4 - c->u.a.lensofar, length); memcpy(c->u.a.msglen + c->u.a.lensofar, data, l); data += l; @@ -5204,7 +5379,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) memcpy(c->u.a.message, c->u.a.msglen, 4); } if (c->u.a.lensofar >= 4 && length > 0) { - int l = + unsigned int l = min(c->u.a.totallen - c->u.a.lensofar, length); memcpy(c->u.a.message + c->u.a.lensofar, @@ -7039,7 +7214,7 @@ static void ssh2_timer(void *ctx, long now) { Ssh ssh = (Ssh)ctx; - if (!ssh->kex_in_progress && + if (!ssh->kex_in_progress && ssh->cfg.ssh_rekey_time != 0 && now - ssh->next_rekey >= 0) { do_ssh2_transport(ssh, "Initiating key re-exchange (timeout)", -1, NULL); @@ -7163,6 +7338,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->channels = NULL; ssh->rportfwds = NULL; + ssh->portfwds = NULL; ssh->send_ok = 0; ssh->editing = 0; @@ -7281,17 +7457,41 @@ 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; + char *rekeying = NULL; + unsigned long old_max_data_size; + pinger_reconfig(ssh->pinger, &ssh->cfg, cfg); + ssh_setup_portfwd(ssh, cfg); + + if (ssh->cfg.ssh_rekey_time != cfg->ssh_rekey_time && + cfg->ssh_rekey_time != 0) { + long new_next = ssh->last_rekey + cfg->ssh_rekey_time*60*TICKSPERSEC; + long now = GETTICKCOUNT(); + + if (new_next - now < 0) { + rekeying = "Initiating key re-exchange (timeout shortened)"; + } else { + ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh); + } + } + + old_max_data_size = ssh->max_data_size; + ssh->max_data_size = parse_blocksize(cfg->ssh_rekey_data); + if (old_max_data_size != ssh->max_data_size && + ssh->max_data_size != 0) { + if (ssh->outgoing_data_size > ssh->max_data_size || + ssh->incoming_data_size > ssh->max_data_size) + rekeying = "Initiating key re-exchange (data limit lowered)"; + } + + if (rekeying && !ssh->kex_in_progress) { + do_ssh2_transport(ssh, rekeying, -1, NULL); + } + ssh->cfg = *cfg; /* STRUCTURE COPY */ } @@ -7594,7 +7794,7 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org) PKT_INT, c->localid, PKT_STR, hostname, PKT_INT, port, - //PKT_STR, , + /* PKT_STR, , */ PKT_END); } else { pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); @@ -7661,6 +7861,16 @@ static int ssh_return_exitcode(void *handle) } /* + * cfg_info for SSH is the currently running version of the + * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.) + */ +static int ssh_cfg_info(void *handle) +{ + Ssh ssh = (Ssh) handle; + return ssh->version; +} + +/* * Gross hack: pscp will try to start SFTP but fall back to scp1 if * that fails. This variable is the means by which scp.c can reach * into the SSH code and find out which one it got. @@ -7687,5 +7897,6 @@ Backend ssh_backend = { ssh_provide_ldisc, ssh_provide_logctx, ssh_unthrottle, + ssh_cfg_info, 22 };