Dynamic port forwarding by means of a local SOCKS server. Fully
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sat, 5 Apr 2003 11:45:21 +0000 (11:45 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sat, 5 Apr 2003 11:45:21 +0000 (11:45 +0000)
supports SOCKS 4, SOCKS 4A and SOCKS 5 (well, actually IPv6 in SOCKS
5 isn't supported, but it'll be no difficulty once I actually get
round to it). Thanks to Chas Honton for his `stone soup' patch: I
didn't end up actually using any of his code, but it galvanised me
into doing it properly myself :-)

git-svn-id: svn://svn.tartarus.org/sgt/putty@3055 cda61777-01e9-0310-a592-d414129be87e

cmdline.c
config.c
plink.c
portfwd.c
putty.h
ssh.c
ssh.h
unix/uxplink.c

index 0038ad1..eb98f60 100644 (file)
--- a/cmdline.c
+++ b/cmdline.c
@@ -166,44 +166,47 @@ int cmdline_process_param(char *p, char *value, int need_save, Config *cfg)
        strncpy(cfg->username, value, sizeof(cfg->username));
        cfg->username[sizeof(cfg->username) - 1] = '\0';
     }
-    if ((!strcmp(p, "-L") || !strcmp(p, "-R"))) {
+    if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) {
        char *fwd, *ptr, *q, *qq;
-       int i=0;
+       int dynamic, i=0;
        RETURN(2);
        UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
        SAVEABLE(0);
+       dynamic = !strcmp(p, "-D");
        fwd = value;
        ptr = cfg->portfwd;
        /* if multiple forwards, find end of list */
-       if (ptr[0]=='R' || ptr[0]=='L') {
+       if (ptr[0]=='R' || ptr[0]=='L' || ptr[0] == 'D') {
            for (i = 0; i < sizeof(cfg->portfwd) - 2; i++)
                if (ptr[i]=='\000' && ptr[i+1]=='\000')
                    break;
            ptr = ptr + i + 1;  /* point to next forward slot */
        }
-       ptr[0] = p[1];  /* insert a 'L' or 'R' at the start */
+       ptr[0] = p[1];  /* insert a 'L', 'R' or 'D' at the start */
        if (strlen(fwd) > sizeof(cfg->portfwd) - i - 2) {
            cmdline_error("out of space for port forwardings");
            return ret;
        }
        strncpy(ptr+1, fwd, sizeof(cfg->portfwd) - i);
-       /*
-        * 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 (!dynamic) {
+           /*
+            * 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 */
        }
-       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 7204a6c..0ce6f06 100644 (file)
--- a/config.c
+++ b/config.c
@@ -634,24 +634,31 @@ static void portfwd_handler(union control *ctrl, void *dlg,
        if (ctrl == pfd->addbutton) {
            char str[sizeof(cfg->portfwd)];
            char *p;
-           if (dlg_radiobutton_get(pfd->direction, dlg) == 0)
+           int whichbutton = dlg_radiobutton_get(pfd->direction, dlg);
+           if (whichbutton == 0)
                str[0] = 'L';
-           else
+           else if (whichbutton == 1)
                str[0] = 'R';
+           else
+               str[0] = 'D';
            dlg_editbox_get(pfd->sourcebox, dlg, str+1, sizeof(str) - 2);
            if (!str[1]) {
                dlg_error_msg(dlg, "You need to specify a source port number");
                return;
            }
            p = str + strlen(str);
-           *p++ = '\t';
-           dlg_editbox_get(pfd->destbox, dlg, p, sizeof(str)-1 - (p - str));
-           if (!*p || !strchr(p, ':')) {
-               dlg_error_msg(dlg,
-                             "You need to specify a destination address\n"
-                             "in the form \"host.name:port\"");
-               return;
-           }
+           if (str[0] != 'D') {
+               *p++ = '\t';
+               dlg_editbox_get(pfd->destbox, dlg, p,
+                               sizeof(str)-1 - (p - str));
+               if (!*p || !strchr(p, ':')) {
+                   dlg_error_msg(dlg,
+                                 "You need to specify a destination address\n"
+                                 "in the form \"host.name:port\"");
+                   return;
+               }
+           } else
+               *p = '\0';
            p = cfg->portfwd;
            while (*p) {
                while (*p)
@@ -1550,11 +1557,13 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist,
        pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67,
                                    HELPCTX(ssh_tunnels_portfwd),
                                    portfwd_handler, P(pfd), P(NULL));
-       pfd->direction = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 2,
+       pfd->direction = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3,
                                           HELPCTX(ssh_tunnels_portfwd),
                                           portfwd_handler, P(pfd),
                                           "Local", 'l', P(NULL),
-                                          "Remote", 'm', P(NULL), NULL);
+                                          "Remote", 'm', P(NULL),
+                                          "Dynamic", 'y', P(NULL),
+                                          NULL);
        ctrl_tabdelay(s, pfd->addbutton);
        ctrl_columns(s, 1, 100);
 
diff --git a/plink.c b/plink.c
index 3848214..d61bbeb 100644 (file)
--- a/plink.c
+++ b/plink.c
@@ -207,6 +207,7 @@ static void usage(void)
     printf("  -batch    disable all interactive prompts\n");
     printf("The following options only apply to SSH connections:\n");
     printf("  -pw passw login with specified password\n");
+    printf("  -D listen-port   Dynamic SOCKS-based port forwarding\n");
     printf("  -L listen-port:host:port   Forward local port to "
           "remote address\n");
     printf("  -R listen-port:host:port   Forward remote port to"
index 00c6cfc..03cbd67 100644 (file)
--- a/portfwd.c
+++ b/portfwd.c
   (cp)[0] = (value) >> 8, \
   (cp)[1] = (value) )
 
-struct pfwd_queue {
-    struct pfwd_queue *next;
-    char *buf;
-};
-
 struct PFwdPrivate {
     const struct plug_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
@@ -63,14 +58,34 @@ struct PFwdPrivate {
     void *backhandle;                 /* instance of SSH backend itself */
     /* Note that backhandle need not be filled in if c is non-NULL */
     Socket s;
-    char hostname[128];
     int throttled, throttle_override;
-    int port;
     int ready;
-    struct pfwd_queue *waiting;
+    /*
+     * `dynamic' does double duty. It's set to 0 for an ordinary
+     * forwarded port, and nonzero for SOCKS-style dynamic port
+     * forwarding; but it also represents the state of the SOCKS
+     * exchange.
+     */
+    int dynamic;
+    /*
+     * `hostname' and `port' are the real hostname and port, once
+     * we know what we're connecting to; they're unused for this
+     * purpose while conducting a local SOCKS exchange, which means
+     * we can also use them as a buffer and pointer for reading
+     * data from the SOCKS client.
+     */
+    char hostname[256];
+    int port;
+    /*
+     * When doing dynamic port forwarding, we can receive
+     * connection data before we are actually able to send it; so
+     * we may have to temporarily hold some in a dynamically
+     * allocated buffer here.
+     */
+    void *buffer;
+    int buflen;
 };
 
-
 static int pfd_closing(Plug plug, char *error_msg, int error_code,
                       int calling_back)
 {
@@ -81,7 +96,8 @@ static int pfd_closing(Plug plug, char *error_msg, int error_code,
      * so if an error occurred on the socket, we just ignore it
      * and treat it like a proper close.
      */
-    sshfwd_close(pr->c);
+    if (pr->c)
+       sshfwd_close(pr->c);
     pfd_close(pr->s);
     return 1;
 }
@@ -89,6 +105,212 @@ static int pfd_closing(Plug plug, char *error_msg, int error_code,
 static int pfd_receive(Plug plug, int urgent, char *data, int len)
 {
     struct PFwdPrivate *pr = (struct PFwdPrivate *) plug;
+    if (pr->dynamic) {
+       while (len--) {
+           if (pr->port >= lenof(pr->hostname)) {
+               if ((pr->dynamic >> 12) == 4) {
+                   /* Send back a SOCKS 4 error before closing. */
+                   char data[8];
+                   memset(data, 0, sizeof(data));
+                   data[1] = 91;      /* generic `request rejected' */
+                   sk_write(pr->s, data, 8);
+               }
+               pfd_close(pr->s);
+               return 1;
+           }
+           pr->hostname[pr->port++] = *data++;
+
+           /*
+            * Now check what's in the buffer to see if it's a
+            * valid and complete message in the SOCKS exchange.
+            */
+           if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 4) &&
+               pr->hostname[0] == 4) {
+               /*
+                * SOCKS 4.
+                */
+               if (pr->dynamic == 1)
+                   pr->dynamic = 0x4000;
+               if (pr->port < 2) continue;/* don't have command code yet */
+               if (pr->hostname[1] != 1) {
+                   /* Send back a SOCKS 4 error before closing. */
+                   char data[8];
+                   memset(data, 0, sizeof(data));
+                   data[1] = 91;      /* generic `request rejected' */
+                   sk_write(pr->s, data, 8);
+                   pfd_close(pr->s);
+                   return 1;
+               }
+               if (pr->port < 8) continue;   /* haven't started username */
+               if (pr->hostname[pr->port-1] != 0)
+                   continue;          /* haven't _finished_ username */
+               /*
+                * Now we have a full SOCKS 4 request. Check it to
+                * see if it's a SOCKS 4A request.
+                */
+               if (pr->hostname[4] == 0 && pr->hostname[5] == 0 &&
+                   pr->hostname[6] == 0 && pr->hostname[7] != 0) {
+                   /*
+                    * It's SOCKS 4A. So if we haven't yet
+                    * collected the host name, we should continue
+                    * waiting for data in order to do so; if we
+                    * have, we can go ahead.
+                    */
+                   int len;
+                   if (pr->dynamic == 0x4000) {
+                       pr->dynamic = 0x4001;
+                       continue;
+                   }
+                   pr->hostname[0] = 0;   /* reply version code */
+                   pr->hostname[1] = 90;   /* request granted */
+                   sk_write(pr->s, pr->hostname, 8);
+                   pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2);
+                   len = strlen(pr->hostname+8);
+                   memmove(pr->hostname, pr->hostname + 8 + len + 1,
+                           lenof(pr->hostname) - (8 + len + 1));
+                   goto connect;
+               } else {
+                   /*
+                    * It's SOCKS 4, which means we should format
+                    * the IP address into the hostname string and
+                    * then just go.
+                    */
+                   pr->hostname[0] = 0;   /* reply version code */
+                   pr->hostname[1] = 90;   /* request granted */
+                   sk_write(pr->s, pr->hostname, 8);
+                   pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2);
+                   sprintf(pr->hostname, "%d.%d.%d.%d",
+                           (unsigned char)pr->hostname[4],
+                           (unsigned char)pr->hostname[5],
+                           (unsigned char)pr->hostname[6],
+                           (unsigned char)pr->hostname[7]);
+                   goto connect;
+               }
+           }
+
+           if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 5) &&
+               pr->hostname[0] == 5) {
+               /*
+                * SOCKS 5.
+                */
+               if (pr->dynamic == 1)
+                   pr->dynamic = 0x5000;
+
+               if (pr->dynamic == 0x5000) {
+                   int i, method;
+                   char data[2];
+                   /*
+                    * We're receiving a set of method identifiers.
+                    */
+                   if (pr->port < 2) continue;/* no method count yet */
+                   if (pr->port < 2 + (unsigned char)pr->hostname[1])
+                       continue;      /* no methods yet */
+                   method = 0xFF;     /* invalid */
+                   for (i = 0; i < (unsigned char)pr->hostname[1]; i++)
+                       if (pr->hostname[2+i] == 0) {
+                           method = 0;/* no auth */
+                           break;
+                       }
+                   data[0] = 5;
+                   data[1] = method;
+                   sk_write(pr->s, data, 2);
+                   pr->dynamic = 0x5001;
+                   pr->port = 0;      /* re-empty the buffer */
+                   continue;
+               }
+
+               if (pr->dynamic == 0x5001) {
+                   int atype, alen;
+                   if (pr->port < 6) continue;
+                   atype = (unsigned char)pr->hostname[3];
+                   if (atype == 1)    /* IPv4 address */
+                       alen = 4;
+                   if (atype == 4)    /* IPv6 address */
+                       alen = 16;
+                   if (atype == 3)    /* domain name has leading length */
+                       alen = 1 + (unsigned char)pr->hostname[4];
+                   if (pr->port < 6 + alen) continue;
+                   if (pr->hostname[1] != 1 || pr->hostname[2] != 0) {
+                       pr->hostname[1] = 1;   /* generic failure */
+                       pr->hostname[2] = 0;   /* reserved */
+                       sk_write(pr->s, pr->hostname, pr->port);
+                       pfd_close(pr->s);
+                       return 1;
+                   }
+                   /*
+                    * Now we have a viable connect request. Switch
+                    * on atype.
+                    */
+                   pr->port = GET_16BIT_MSB_FIRST(pr->hostname+4+alen);
+                   if (atype == 1) {
+                       pr->hostname[1] = 0;   /* succeeded */
+                       sk_write(pr->s, pr->hostname, alen + 6);
+                       sprintf(pr->hostname, "%d.%d.%d.%d",
+                               (unsigned char)pr->hostname[4],
+                               (unsigned char)pr->hostname[5],
+                               (unsigned char)pr->hostname[6],
+                               (unsigned char)pr->hostname[7]);
+                       goto connect;
+                   } else if (atype == 3) {
+                       pr->hostname[1] = 0;   /* succeeded */
+                       sk_write(pr->s, pr->hostname, alen + 6);
+                       memmove(pr->hostname, pr->hostname + 5, alen-1);
+                       pr->hostname[alen-1] = '\0';
+                       goto connect;
+                   } else {
+                       /*
+                        * Unknown address type. (FIXME: support IPv6!)
+                        */
+                       pr->hostname[1] = 8;   /* atype not supported */
+                       sk_write(pr->s, pr->hostname, pr->port);
+                       pfd_close(pr->s);
+                       return 1;                       
+                   }
+               }
+           }
+           
+           /*
+            * If we get here without either having done `continue'
+            * or `goto connect', it must be because there is no
+            * sensible interpretation of what's in our buffer. So
+            * close the connection rudely.
+            */
+           pfd_close(pr->s);
+           return 1;
+       }
+       return 1;
+
+       /*
+        * We come here when we're ready to make an actual
+        * connection.
+        */
+       connect:
+
+       pr->c = new_sock_channel(pr->backhandle, pr->s);
+       if (pr->c == NULL) {
+           pfd_close(pr->s);
+           return 1;
+       } else {
+           /* asks to forward to the specified host/port for this */
+           ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding");
+       }
+       pr->dynamic = 0;
+
+       /*
+        * Now freeze the socket until the SSH server confirms the
+        * connection.
+        */
+       sk_set_frozen(pr->s, 1);
+       /*
+        * If there's any data remaining in our current buffer,
+        * save it to be sent on pfd_confirm().
+        */
+       if (len > 0) {
+           pr->buffer = snewn(len, char);
+           memcpy(pr->buffer, data, len);
+           pr->buflen = len;
+       }
+    }
     if (pr->ready) {
        if (sshfwd_write(pr->c, data, len) > 0) {
            pr->throttled = 1;
@@ -102,7 +324,8 @@ static void pfd_sent(Plug plug, int bufsize)
 {
     struct PFwdPrivate *pr = (struct PFwdPrivate *) plug;
 
-    sshfwd_unthrottle(pr->c, bufsize);
+    if (pr->c)
+       sshfwd_unthrottle(pr->c, bufsize);
 }
 
 /*
@@ -133,6 +356,7 @@ char *pfd_newconnect(Socket *s, char *hostname, int port, void *c,
      * Open socket.
      */
     pr = snew(struct PFwdPrivate);
+    pr->buffer = NULL;
     pr->fn = &fn_table;
     pr->throttled = pr->throttle_override = 0;
     pr->ready = 1;
@@ -169,6 +393,7 @@ static int pfd_accepting(Plug p, void *sock)
 
     org = (struct PFwdPrivate *)p;
     pr = snew(struct PFwdPrivate);
+    pr->buffer = NULL;
     pr->fn = &fn_table;
 
     pr->c = NULL;
@@ -180,22 +405,27 @@ static int pfd_accepting(Plug p, void *sock)
        return err != NULL;
     }
 
-    pr->c = new_sock_channel(org->backhandle, s);
+    sk_set_private_ptr(s, pr);
 
-    strcpy(pr->hostname, org->hostname);
-    pr->port = org->port;
     pr->throttled = pr->throttle_override = 0;
     pr->ready = 0;
-    pr->waiting = NULL;
 
-    sk_set_private_ptr(s, pr);
-
-    if (pr->c == NULL) {
-       sfree(pr);
-       return 1;
+    if (org->dynamic) {
+       pr->dynamic = 1;
+       pr->port = 0;                  /* hostname buffer is so far empty */
+       sk_set_frozen(s, 0);           /* we want to receive SOCKS _now_! */
     } else {
-       /* asks to forward to the specified host/port for this */
-       ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding");
+       strcpy(pr->hostname, org->hostname);
+       pr->port = org->port;   
+       pr->c = new_sock_channel(org->backhandle, s);
+
+       if (pr->c == NULL) {
+           sfree(pr);
+           return 1;
+       } else {
+           /* asks to forward to the specified host/port for this */
+           ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding");
+       }
     }
 
     return 0;
@@ -223,13 +453,17 @@ char *pfd_addforward(char *desthost, int destport, char *srcaddr, int port,
      * Open socket.
      */
     pr = snew(struct PFwdPrivate);
+    pr->buffer = NULL;
     pr->fn = &fn_table;
     pr->c = NULL;
-    strcpy(pr->hostname, desthost);
-    pr->port = destport;
+    if (desthost) {
+       strcpy(pr->hostname, desthost);
+       pr->port = destport;
+       pr->dynamic = 0;
+    } else
+       pr->dynamic = 1;
     pr->throttled = pr->throttle_override = 0;
     pr->ready = 0;
-    pr->waiting = NULL;
     pr->backhandle = backhandle;
 
     pr->s = s = new_listener(srcaddr, port, (Plug) pr,
@@ -253,6 +487,7 @@ void pfd_close(Socket s)
 
     pr = (struct PFwdPrivate *) sk_get_private_ptr(s);
 
+    sfree(pr->buffer);
     sfree(pr);
 
     sk_close(s);
@@ -302,4 +537,9 @@ void pfd_confirm(Socket s)
     pr->ready = 1;
     sk_set_frozen(s, 0);
     sk_write(s, NULL, 0);
+    if (pr->buffer) {
+       sshfwd_write(pr->c, pr->buffer, pr->buflen);
+       sfree(pr->buffer);
+       pr->buffer = NULL;
+    }
 }
diff --git a/putty.h b/putty.h
index d1c89a2..cff103b 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -442,7 +442,18 @@ struct config_tag {
     /* port forwarding */
     int lport_acceptall; /* accept conns from hosts other than localhost */
     int rport_acceptall; /* same for remote forwarded ports (SSH2 only) */
-    char portfwd[1024]; /* [LR]localport\thost:port\000[LR]localport\thost:port\000\000 */
+    /*
+     * The port forwarding string contains a number of
+     * NUL-terminated substrings, terminated in turn by an empty
+     * string (i.e. a second NUL immediately after the previous
+     * one). Each string can be of one of the following forms:
+     * 
+     *   [LR]localport\thost:port
+     *   [LR]localaddr:localport\thost:port
+     *   Dlocalport
+     *   Dlocaladdr:localport
+     */
+    char portfwd[1024];
     /* SSH bug compatibility modes */
     int sshbug_ignore1, sshbug_plainpw1, sshbug_rsa1,
        sshbug_hmac2, sshbug_derivekey2, sshbug_rsapad2,
diff --git a/ssh.c b/ssh.c
index b696e46..7472625 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -3149,30 +3149,34 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt)
                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 != ':') {
-               if (n < 255) host[n++] = *ssh->portfwd_strptr++;
-           }
-           host[n] = 0;
-           if (*ssh->portfwd_strptr == ':')
+           if (type != 'D') {
+               if (*ssh->portfwd_strptr == '\t')
+                   ssh->portfwd_strptr++;
+               n = 0;
+               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) {
+                   if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
+               }
+               dports[n] = 0;
                ssh->portfwd_strptr++;
-           n = 0;
-           while (*ssh->portfwd_strptr) {
-               if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
-           }
-           dports[n] = 0;
-           ssh->portfwd_strptr++;
-           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);
+               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);
+                   }
                }
+           } else {
+               while (*ssh->portfwd_strptr) ssh->portfwd_strptr++;
            }
            sport = atoi(sports);
            sserv = 0;
@@ -3197,6 +3201,15 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt)
                              host,
                              (int)(dserv ? strlen(dports) : 0), dports,
                              dserv, "(", dport, dserv, ")");
+               } else if (type == 'D') {
+                   pfd_addforward(NULL, -1, *saddr ? saddr : NULL,
+                                  sport, ssh, &ssh->cfg);
+                   logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
+                             " doing SOCKS dynamic forwarding",
+                             (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
+                             (int)(*saddr?1:0), ":",
+                             (int)(sserv ? strlen(sports) : 0), sports,
+                             sserv, "(", sport, sserv, ")");
                } else {
                    struct ssh_rportfwd *pf;
                    pf = snew(struct ssh_rportfwd);
@@ -5230,30 +5243,34 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
                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 != ':') {
-               if (n < 255) host[n++] = *ssh->portfwd_strptr++;
-           }
-           host[n] = 0;
-           if (*ssh->portfwd_strptr == ':')
+           if (type != 'D') {
+               if (*ssh->portfwd_strptr == '\t')
+                   ssh->portfwd_strptr++;
+               n = 0;
+               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) {
+                   if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
+               }
+               dports[n] = 0;
                ssh->portfwd_strptr++;
-           n = 0;
-           while (*ssh->portfwd_strptr) {
-               if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
-           }
-           dports[n] = 0;
-           ssh->portfwd_strptr++;
-           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);
+               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);
+                   }
                }
+           } else {
+               while (*ssh->portfwd_strptr) ssh->portfwd_strptr++;
            }
            sport = atoi(sports);
            sserv = 0;
@@ -5278,6 +5295,15 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
                              host,
                              (int)(dserv ? strlen(dports) : 0), dports,
                              dserv, "(", dport, dserv, ")");
+               } else if (type == 'D') {
+                   pfd_addforward(NULL, -1, *saddr ? saddr : NULL,
+                                  sport, ssh, &ssh->cfg);
+                   logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
+                             " doing SOCKS dynamic forwarding",
+                             (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
+                             (int)(*saddr?1:0), ":",
+                             (int)(sserv ? strlen(sports) : 0), sports,
+                             sserv, "(", sport, sserv, ")");
                } else {
                    struct ssh_rportfwd *pf;
                    pf = snew(struct ssh_rportfwd);
diff --git a/ssh.h b/ssh.h
index 766e2e3..e0f82f3 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -259,6 +259,7 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org);
 /* Exports from portfwd.c */
 extern char *pfd_newconnect(Socket * s, char *hostname, int port, void *c,
                            const Config *cfg);
+/* desthost == NULL indicates dynamic (SOCKS) port forwarding */
 extern char *pfd_addforward(char *desthost, int destport, char *srcaddr,
                            int port, void *backhandle, const Config *cfg);
 extern void pfd_close(Socket s);
index 451d112..9fce056 100644 (file)
@@ -263,6 +263,7 @@ static void usage(void)
     printf("  -batch    disable all interactive prompts\n");
     printf("The following options only apply to SSH connections:\n");
     printf("  -pw passw login with specified password\n");
+    printf("  -D listen-port   Dynamic SOCKS-based port forwarding\n");
     printf("  -L listen-port:host:port   Forward local port to "
           "remote address\n");
     printf("  -R listen-port:host:port   Forward remote port to"