Implement `portfwd-loopback-choice'. Works on local side in Unix as
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 18 Dec 2002 11:39:25 +0000 (11:39 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 18 Dec 2002 11:39:25 +0000 (11:39 +0000)
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
doc/config.but
doc/using.but
network.h
portfwd.c
proxy.c
ssh.c
unix/uxnet.c
winnet.c

index e3a0c34..29a27eb 100644 (file)
--- 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' */
index 8992638..bbe8495 100644 (file)
@@ -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
 
index ad5bab0..b7bdabc 100644 (file)
@@ -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
index 8d5ba1e..744492f 100644 (file)
--- 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);
 
index 16f001a..dd8e655 100644 (file)
--- 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 475dc4a..83bb19f 100644 (file)
--- 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 588c8c3..3275365 100644 (file)
--- 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
index e4e07b0..7a9630a 100644 (file)
@@ -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 */
index b5ca741..9d3f66c 100644 (file)
--- 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 */