From 6ee9b735013c0e636b027b77e9f6ba57a96e142f Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 18 Dec 2002 11:39:25 +0000 Subject: [PATCH] Implement `portfwd-loopback-choice'. Works on local side in Unix as well, though it's a lot less useful since you still can't bind to low-numbered ports of odd loopback IPs. Should work in principle for SSH2 remote forwardings as well as local ones, but OpenSSH seems unwilling to cooperate. git-svn-id: svn://svn.tartarus.org/sgt/putty@2344 cda61777-01e9-0310-a592-d414129be87e --- cmdline.c | 21 +++++++++++-- doc/config.but | 15 ++++++++- doc/using.but | 5 +-- network.h | 4 +-- portfwd.c | 7 +++-- proxy.c | 4 +-- ssh.c | 99 ++++++++++++++++++++++++++++++++++++++++++++-------------- unix/uxnet.c | 38 +++++++++++++++++----- winnet.c | 39 ++++++++++++++++++----- 9 files changed, 182 insertions(+), 50 deletions(-) diff --git a/cmdline.c b/cmdline.c index e3a0c340..29a27ebe 100644 --- a/cmdline.c +++ b/cmdline.c @@ -160,7 +160,7 @@ int cmdline_process_param(char *p, char *value, int need_save) cfg.username[sizeof(cfg.username) - 1] = '\0'; } if ((!strcmp(p, "-L") || !strcmp(p, "-R"))) { - char *fwd, *ptr, *q; + char *fwd, *ptr, *q, *qq; int i=0; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); @@ -180,8 +180,23 @@ int cmdline_process_param(char *p, char *value, int need_save) return ret; } strncpy(ptr+1, fwd, sizeof(cfg.portfwd) - i); - q = strchr(ptr, ':'); - if (q) *q = '\t'; /* replace first : with \t */ + /* + * We expect _at least_ two colons in this string. The + * possible formats are `sourceport:desthost:destport', or + * `sourceip:sourceport:desthost:destport' if you're + * specifying a particular loopback address. We need to + * replace the one between source and dest with a \t; this + * means we must find the second-to-last colon in the + * string. + */ + q = qq = strchr(ptr, ':'); + while (qq) { + char *qqq = strchr(qq+1, ':'); + if (qqq) + q = qq; + qq = qqq; + } + if (q) *q = '\t'; /* replace second-last colon with \t */ cfg.portfwd[sizeof(cfg.portfwd) - 1] = '\0'; cfg.portfwd[sizeof(cfg.portfwd) - 2] = '\0'; ptr[strlen(ptr)+1] = '\000'; /* append two '\000' */ diff --git a/doc/config.but b/doc/config.but index 8992638d..bbe8495f 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1,4 +1,4 @@ -\versionid $Id: config.but,v 1.44 2002/10/22 09:40:38 simon Exp $ +\versionid $Id: config.but,v 1.45 2002/12/18 11:39:25 simon Exp $ \C{config} Configuring PuTTY @@ -1881,6 +1881,19 @@ in the list box. To remove a port forwarding, simply select its details in the list box, and click the \q{Remove} button. +In the \q{Source port} box, you can also optionally enter an IP +address to listen on. Typically a Windows machine can be asked to +listen on any single IP address in the \cw{127.*.*.*} range, and all +of these are loopback addresses available only to the local machine. +So if you forward (for example) \c{127.0.0.5:79} to a remote +machine's \cw{finger} port, then you should be able to run commands +such as \c{finger fred@127.0.0.5}. This can be useful if the program +connecting to the forwarded port doesn't allow you to change the +port number it uses. This feature is available for local-to-remote +forwarded ports; SSH1 is unable to support it for remote-to-local +ports, while SSH2 can support it in theory but servers will not +necessarily cooperate. + \S{config-ssh-portfwd-localhost} Controlling the visibility of forwarded ports diff --git a/doc/using.but b/doc/using.but index ad5bab08..b7bdabc8 100644 --- a/doc/using.but +++ b/doc/using.but @@ -1,4 +1,4 @@ -\versionid $Id: using.but,v 1.8 2002/09/11 17:30:36 jacob Exp $ +\versionid $Id: using.but,v 1.9 2002/12/18 11:39:25 simon Exp $ \C{using} Using PuTTY @@ -254,7 +254,8 @@ to a port on a remote server, you need to: \b Choose a port number on your local machine where PuTTY should listen for incoming connections. There are likely to be plenty of -unused port numbers above 3000. +unused port numbers above 3000. (You can also use a local loopback +address here; see \k{config-ssh-portfwd} for more details.) \b Now, before you start your SSH connection, go to the Tunnels panel (see \k{config-ssh-portfwd}). Make sure the \q{Local} radio diff --git a/network.h b/network.h index 8d5ba1ef..744492fe 100644 --- a/network.h +++ b/network.h @@ -67,7 +67,7 @@ struct plug_function_table { Socket new_connection(SockAddr addr, char *hostname, int port, int privport, int oobinline, int nodelay, Plug plug); -Socket new_listener(int port, Plug plug, int local_host_only); +Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only); /* socket functions */ @@ -84,7 +84,7 @@ void sk_addr_free(SockAddr addr); Socket sk_new(SockAddr addr, int port, int privport, int oobinline, int nodelay, Plug p); -Socket sk_newlistener(int port, Plug plug, int local_host_only); +Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only); Socket sk_register(void *sock, Plug plug); diff --git a/portfwd.c b/portfwd.c index 16f001ab..dd8e6550 100644 --- a/portfwd.c +++ b/portfwd.c @@ -203,9 +203,10 @@ static int pfd_accepting(Plug p, void *sock) /* Add a new forwarding from port -> desthost:destport - sets up a listener on the local machine on port + sets up a listener on the local machine on (srcaddr:)port */ -char *pfd_addforward(char *desthost, int destport, int port, void *backhandle) +char *pfd_addforward(char *desthost, int destport, char *srcaddr, int port, + void *backhandle) { static struct plug_function_table fn_table = { pfd_closing, @@ -231,7 +232,7 @@ char *pfd_addforward(char *desthost, int destport, int port, void *backhandle) pr->waiting = NULL; pr->backhandle = backhandle; - pr->s = s = new_listener(port, (Plug) pr, !cfg.lport_acceptall); + pr->s = s = new_listener(srcaddr, port, (Plug) pr, !cfg.lport_acceptall); if ((err = sk_socket_error(s))) { sfree(pr); return err; diff --git a/proxy.c b/proxy.c index 475dc4a5..83bb19f8 100644 --- a/proxy.c +++ b/proxy.c @@ -404,13 +404,13 @@ Socket new_connection(SockAddr addr, char *hostname, return sk_new(addr, port, privport, oobinline, nodelay, plug); } -Socket new_listener(int port, Plug plug, int local_host_only) +Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only) { /* TODO: SOCKS (and potentially others) support inbound * TODO: connections via the proxy. support them. */ - return sk_newlistener(port, plug, local_host_only); + return sk_newlistener(srcaddr, port, plug, local_host_only); } /* ---------------------------------------------------------------------- diff --git a/ssh.c b/ssh.c index 588c8c3e..32753657 100644 --- a/ssh.c +++ b/ssh.c @@ -299,8 +299,8 @@ extern void x11_unthrottle(Socket s); extern void x11_override_throttle(Socket s, int enable); extern char *pfd_newconnect(Socket * s, char *hostname, int port, void *c); -extern char *pfd_addforward(char *desthost, int destport, int port, - void *backhandle); +extern char *pfd_addforward(char *desthost, int destport, char *srcaddr, + int port, void *backhandle); extern void pfd_close(Socket s); extern int pfd_send(Socket s, char *data, int len); extern void pfd_confirm(Socket s); @@ -3071,28 +3071,46 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt) char type; int n; int sport,dport,sserv,dserv; - char sports[256], dports[256], host[256]; + char sports[256], dports[256], saddr[256], host[256]; ssh->rportfwds = newtree234(ssh_rportcmp_ssh1); /* Add port forwardings. */ ssh->portfwd_strptr = cfg.portfwd; while (*ssh->portfwd_strptr) { type = *ssh->portfwd_strptr++; + saddr[0] = '\0'; n = 0; - while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') - sports[n++] = *ssh->portfwd_strptr++; + while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') { + if (*ssh->portfwd_strptr == ':') { + /* + * We've seen a colon in the middle of the + * source port number. This means that + * everything we've seen until now is the + * source _address_, so we'll move it into + * saddr and start sports from the beginning + * again. + */ + ssh->portfwd_strptr++; + sports[n] = '\0'; + strcpy(saddr, sports); + n = 0; + } + if (n < 255) sports[n++] = *ssh->portfwd_strptr++; + } sports[n] = 0; if (*ssh->portfwd_strptr == '\t') ssh->portfwd_strptr++; n = 0; - while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') - host[n++] = *ssh->portfwd_strptr++; + while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') { + if (n < 255) host[n++] = *ssh->portfwd_strptr++; + } host[n] = 0; if (*ssh->portfwd_strptr == ':') ssh->portfwd_strptr++; n = 0; - while (*ssh->portfwd_strptr) - dports[n++] = *ssh->portfwd_strptr++; + while (*ssh->portfwd_strptr) { + if (n < 255) dports[n++] = *ssh->portfwd_strptr++; + } dports[n] = 0; ssh->portfwd_strptr++; dport = atoi(dports); @@ -3117,9 +3135,12 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt) } if (sport && dport) { if (type == 'L') { - pfd_addforward(host, dport, sport, ssh); - logeventf(ssh, "Local port %.*s%.*s%d%.*s forwarding to" - " %s:%.*s%.*s%d%.*s", + pfd_addforward(host, dport, *saddr ? saddr : NULL, + sport, ssh); + logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s" + " forwarding to %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, ")", host, @@ -3130,6 +3151,11 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt) pf = smalloc(sizeof(*pf)); strcpy(pf->dhost, host); pf->dport = dport; + if (saddr) { + logeventf(ssh, + "SSH1 cannot handle source address spec \"%s:%d\"; ignoring", + saddr, sport); + } if (add234(ssh->rportfwds, pf) != pf) { logeventf(ssh, "Duplicate remote port forwarding to %s:%d", @@ -5102,28 +5128,46 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) char type; int n; int sport,dport,sserv,dserv; - char sports[256], dports[256], host[256]; + char sports[256], dports[256], saddr[256], host[256]; ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); /* Add port forwardings. */ ssh->portfwd_strptr = cfg.portfwd; while (*ssh->portfwd_strptr) { type = *ssh->portfwd_strptr++; + saddr[0] = '\0'; n = 0; - while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') - sports[n++] = *ssh->portfwd_strptr++; + while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') { + if (*ssh->portfwd_strptr == ':') { + /* + * We've seen a colon in the middle of the + * source port number. This means that + * everything we've seen until now is the + * source _address_, so we'll move it into + * saddr and start sports from the beginning + * again. + */ + ssh->portfwd_strptr++; + sports[n] = '\0'; + strcpy(saddr, sports); + n = 0; + } + if (n < 255) sports[n++] = *ssh->portfwd_strptr++; + } sports[n] = 0; if (*ssh->portfwd_strptr == '\t') ssh->portfwd_strptr++; n = 0; - while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') - host[n++] = *ssh->portfwd_strptr++; + while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') { + if (n < 255) host[n++] = *ssh->portfwd_strptr++; + } host[n] = 0; if (*ssh->portfwd_strptr == ':') ssh->portfwd_strptr++; n = 0; - while (*ssh->portfwd_strptr) - dports[n++] = *ssh->portfwd_strptr++; + while (*ssh->portfwd_strptr) { + if (n < 255) dports[n++] = *ssh->portfwd_strptr++; + } dports[n] = 0; ssh->portfwd_strptr++; dport = atoi(dports); @@ -5148,9 +5192,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) } if (sport && dport) { if (type == 'L') { - pfd_addforward(host, dport, sport, ssh); - logeventf(ssh, "Local port %.*s%.*s%d%.*s forwarding to" - " %s:%.*s%.*s%d%.*s", + pfd_addforward(host, dport, *saddr ? saddr : NULL, + sport, ssh); + logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s" + " forwarding to %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, ")", host, @@ -5167,8 +5214,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) " to %s:%d", host, dport); sfree(pf); } else { - logeventf(ssh, "Requesting remote port %.*s%.*s%d%.*s" + logeventf(ssh, "Requesting remote port " + "%.*s%.*s%.*s%.*s%d%.*s" " forward to %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, ")", host, @@ -5177,6 +5228,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) ssh2_pkt_init(ssh, SSH2_MSG_GLOBAL_REQUEST); ssh2_pkt_addstring(ssh, "tcpip-forward"); ssh2_pkt_addbool(ssh, 1);/* want reply */ + if (*saddr) + ssh2_pkt_addstring(ssh, saddr); if (cfg.rport_acceptall) ssh2_pkt_addstring(ssh, "0.0.0.0"); else diff --git a/unix/uxnet.c b/unix/uxnet.c index e4e07b0a..7a9630ad 100644 --- a/unix/uxnet.c +++ b/unix/uxnet.c @@ -21,6 +21,8 @@ #include "network.h" #include "tree234.h" +#define ipv4_is_loopback(addr) (inet_netof(addr) == IN_LOOPBACKNET) + struct Socket_tag { struct socket_function_table *fn; /* the above variable absolutely *must* be the first in this structure */ @@ -474,7 +476,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline, return (Socket) ret; } -Socket sk_newlistener(int port, Plug plug, int local_host_only) +Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only) { static struct socket_function_table fn_table = { sk_tcp_plug, @@ -534,6 +536,8 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only) if (addr->family == AF_INET6) { memset(&a6, 0, sizeof(a6)); a6.sin6_family = AF_INET6; + /* FIXME: srcaddr is ignored for IPv6, because I (SGT) don't + * know how to do it. :-) */ if (local_host_only) a6.sin6_addr = in6addr_loopback; else @@ -542,11 +546,32 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only) } else #endif { + int got_addr = 0; a.sin_family = AF_INET; - if (local_host_only) - a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - else - a.sin_addr.s_addr = htonl(INADDR_ANY); + + /* + * Bind to source address. First try an explicitly + * specified one... + */ + if (srcaddr) { + a.sin_addr.s_addr = inet_addr(srcaddr); + if (a.sin_addr.s_addr != INADDR_NONE) { + /* Override localhost_only with specified listen addr. */ + ret->localhost_only = ipv4_is_loopback(a.sin_addr); + got_addr = 1; + } + } + + /* + * ... and failing that, go with one of the standard ones. + */ + if (!got_addr) { + if (local_host_only) + a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + else + a.sin_addr.s_addr = htonl(INADDR_ANY); + } + a.sin_port = htons((short)port); } #ifdef IPV6 @@ -763,8 +788,7 @@ int select_result(int fd, int event) break; } - if (s->localhost_only && - ntohl(isa.sin_addr.s_addr) != INADDR_LOOPBACK) { + if (s->localhost_only && !ipv4_is_loopback(isa.sin_addr)) { close(t); /* someone let nonlocal through?! */ } else if (plug_accepting(s->plug, (void*)t)) { close(t); /* denied or error */ diff --git a/winnet.c b/winnet.c index b5ca7415..9d3f66c2 100644 --- a/winnet.c +++ b/winnet.c @@ -55,6 +55,9 @@ #include "network.h" #include "tree234.h" +#define ipv4_is_loopback(addr) \ + ((ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L) + struct Socket_tag { struct socket_function_table *fn; /* the above variable absolutely *must* be the first in this structure */ @@ -654,7 +657,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline, return (Socket) ret; } -Socket sk_newlistener(int port, Plug plug, int local_host_only) +Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only) { static struct socket_function_table fn_table = { sk_tcp_plug, @@ -716,6 +719,8 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only) if (addr->family == AF_INET6) { memset(&a6, 0, sizeof(a6)); a6.sin6_family = AF_INET6; + /* FIXME: srcaddr is ignored for IPv6, because I (SGT) don't + * know how to do it. :-) */ if (local_host_only) a6.sin6_addr = in6addr_loopback; else @@ -724,11 +729,32 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only) } else #endif { + int got_addr = 0; a.sin_family = AF_INET; - if (local_host_only) - a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - else - a.sin_addr.s_addr = htonl(INADDR_ANY); + + /* + * Bind to source address. First try an explicitly + * specified one... + */ + if (srcaddr) { + a.sin_addr.s_addr = inet_addr(srcaddr); + if (a.sin_addr.s_addr != INADDR_NONE) { + /* Override localhost_only with specified listen addr. */ + ret->localhost_only = ipv4_is_loopback(a.sin_addr); + got_addr = 1; + } + } + + /* + * ... and failing that, go with one of the standard ones. + */ + if (!got_addr) { + if (local_host_only) + a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + else + a.sin_addr.s_addr = htonl(INADDR_ANY); + } + a.sin_port = htons((short)port); } #ifdef IPV6 @@ -1047,8 +1073,7 @@ int select_result(WPARAM wParam, LPARAM lParam) break; } - if (s->localhost_only && - ntohl(isa.sin_addr.s_addr) != INADDR_LOOPBACK) { + if (s->localhost_only && !ipv4_is_loopback(isa.sin_addr)) { closesocket(t); /* dodgy WinSock let nonlocal through */ } else if (plug_accepting(s->plug, (void*)t)) { closesocket(t); /* denied or error */ -- 2.11.0