SSH port forwarding! How cool is that?
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 8 Aug 2001 20:44:35 +0000 (20:44 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 8 Aug 2001 20:44:35 +0000 (20:44 +0000)
Only currently works on SSH1; SSH2 should be doable but it's late
and I have other things to do tonight. The Cool Guy award for this
one goes to Nicolas Barry, for doing most of the work and actually
understanding the code he was adding to.

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

13 files changed:
Makefile
network.h
plink.c
putty.h
settings.c
ssh.c
ssh.h
winctrls.c
windlg.c
window.c
winnet.c
winstuff.h
x11fwd.c

index ff22b15..ea9b1bb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -89,7 +89,7 @@ MOBJ2 = tree234.$(OBJ)
 OBJS1 = sshcrc.$(OBJ) sshdes.$(OBJ) sshmd5.$(OBJ) sshrsa.$(OBJ) sshrand.$(OBJ)
 OBJS2 = sshsha.$(OBJ) sshblowf.$(OBJ) noise.$(OBJ) sshdh.$(OBJ) sshdss.$(OBJ)
 OBJS3 = sshbn.$(OBJ) sshpubk.$(OBJ) ssh.$(OBJ) pageantc.$(OBJ) sshzlib.$(OBJ)
-OBJS4 = x11fwd.$(OBJ) sshaes.$(OBJ)
+OBJS4 = x11fwd.$(OBJ) portforward.$(OBJ) sshaes.$(OBJ)
 ##-- objects pageant
 PAGE1 = pageant.$(OBJ) sshrsa.$(OBJ) sshpubk.$(OBJ) sshdes.$(OBJ) sshbn.$(OBJ)
 PAGE2 = sshmd5.$(OBJ) version.$(OBJ) tree234.$(OBJ) misc.$(OBJ) sshaes.$(OBJ)
@@ -298,7 +298,8 @@ windlg.$(OBJ): windlg.c network.h puttymem.h storage.h winstuff.h putty.h ssh.h
 window.$(OBJ): window.c network.h puttymem.h storage.h winstuff.h putty.h win_res.h 
 winnet.$(OBJ): winnet.c network.h puttymem.h putty.h tree234.h 
 winstore.$(OBJ): winstore.c network.h puttymem.h storage.h putty.h 
-x11fwd.$(OBJ): x11fwd.c network.h puttymem.h ssh.h putty.h 
+x11fwd.$(OBJ): x11fwd.c network.h puttymem.h ssh.h putty.h
+portforward.$(OBJ): portforward.c network.h puttymem.h ssh.h putty.h
 xlat.$(OBJ): xlat.c network.h puttymem.h putty.h 
 ##--
 
index e4bb578..e205b87 100644 (file)
--- a/network.h
+++ b/network.h
@@ -48,6 +48,10 @@ struct plug_function_table {
      *  - urgent==2. `data' points to `len' bytes of data,
      *    the first of which was the one at the Urgent mark.
      */
+    int (*accepting)(Plug p, struct sockaddr *addr, void *sock);
+    /*
+     * returns 0 if the host at address addr is a valid host for connecting or error
+     */
 };
 
 
@@ -59,6 +63,10 @@ void sk_addr_free(SockAddr addr);
 Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
              Plug p);
 
+Socket sk_newlistenner(int port, Plug plug);
+
+Socket sk_register(void *sock, Plug plug);
+
 #define sk_plug(s,p) (((*s)->plug) (s, p))
 #define sk_close(s) (((*s)->close) (s))
 #define sk_write(s,buf,len) (((*s)->write) (s, buf, len))
@@ -68,6 +76,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
 #ifdef DEFINE_PLUG_METHOD_MACROS
 #define plug_closing(p,msg,code,callback) (((*p)->closing) (p, msg, code, callback))
 #define plug_receive(p,urgent,buf,len) (((*p)->receive) (p, urgent, buf, len))
+#define plug_accepting(p, addr, sock) (((*p)->accepting)(p, addr, sock))
 #endif
 
 /*
@@ -88,6 +97,14 @@ void *sk_get_private_ptr(Socket s);
 char *sk_addr_error(SockAddr addr);
 #define sk_socket_error(s) (((*s)->socket_error) (s))
 
+/*
+ * Set the `frozen' flag on a socket. A frozen socket is one in
+ * which all sends are buffered and receives are ignored. This is
+ * so that (for example) a new port-forwarding can sit in limbo
+ * until its associated SSH channel is ready, and then pending data
+ * can be sent on.
+ */
+void sk_set_frozen(Socket sock, int is_frozen);
 
 /********** SSL stuff **********/
 
@@ -96,7 +113,6 @@ char *sk_addr_error(SockAddr addr);
  * of what it will eventually look like.
  */
 
-
 typedef struct certificate *Certificate;
 typedef struct our_certificate *Our_Certificate;
     /* to be defined somewhere else, somehow */
diff --git a/plink.c b/plink.c
index 3de6af2..4113a81 100644 (file)
--- a/plink.c
+++ b/plink.c
@@ -256,7 +256,7 @@ char *do_select(SOCKET skt, int startup)
 {
     int events;
     if (startup) {
-       events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
+       events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT;
     } else {
        events = 0;
     }
@@ -649,6 +649,9 @@ int main(int argc, char **argv)
                        connopen &= select_result(wp, (LPARAM) FD_OOB);
                    if (things.lNetworkEvents & FD_WRITE)
                        connopen &= select_result(wp, (LPARAM) FD_WRITE);
+                   if (things.lNetworkEvents & FD_ACCEPT)
+                       connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
+
                }
            }
        } else if (n == 1) {
diff --git a/putty.h b/putty.h
index 3fbde4e..f2c9cc6 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -313,6 +313,9 @@ typedef struct {
     /* X11 forwarding */
     int x11_forward;
     char x11_display[128];
+    /* port forwarding */
+    int lport_acceptall; /* accepts connection from hosts other than localhost */
+    char portfwd[1024]; /* [LR]localport\thost:port\000[LR]localport\thost:port\000\000 */
 } Config;
 
 /*
index b621fbf..f75bac3 100644 (file)
@@ -160,6 +160,26 @@ void save_settings(char *section, int do_host, Config * cfg)
     write_setting_i(sesskey, "BlinkText", cfg->blinktext);
     write_setting_i(sesskey, "X11Forward", cfg->x11_forward);
     write_setting_s(sesskey, "X11Display", cfg->x11_display);
+    write_setting_i(sesskey, "LocalPortAcceptAll", cfg->lport_acceptall);
+    {
+       char buf[2 * sizeof(cfg->portfwd)], *p, *q;
+       p = buf;
+       q = cfg->portfwd;
+       while (*q) {
+           while (*q) {
+               int c = *q++;
+               if (c == '=' || c == ',' || c == '\\')
+                   *p++ = '\\';
+               if (c == '\t')
+                   c = '=';
+               *p++ = c;
+           }
+           *p++ = ',';
+           q++;
+       }
+       *p = '\0';
+       write_setting_s(sesskey, "PortForwardings", buf);
+    }
 
     close_settings_w(sesskey);
 }
@@ -365,6 +385,28 @@ void load_settings(char *section, int do_host, Config * cfg)
     gpps(sesskey, "X11Display", "localhost:0", cfg->x11_display,
         sizeof(cfg->x11_display));
 
+    gppi(sesskey, "LocalPortAcceptAll", 0, &cfg->lport_acceptall);
+    {
+       char buf[2 * sizeof(cfg->portfwd)], *p, *q;
+       gpps(sesskey, "PortForwardings", "", buf, sizeof(buf));
+       p = buf;
+       q = cfg->portfwd;
+       while (*p) {
+           while (*p && *p != ',') {
+               int c = *p++;
+               if (c == '=')
+                   c = '\t';
+               if (c == '\\')
+                   c = *p++;
+               *q++ = c;
+           }
+           if (*p == ',')
+               p++;
+           *q++ = '\0';
+       }
+       *q = '\0';
+    }
+
     close_settings_r(sesskey);
 }
 
diff --git a/ssh.c b/ssh.c
index a41e3bf..cbcf439 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -196,6 +196,12 @@ extern void x11_close(Socket);
 extern void x11_send(Socket, char *, int);
 extern void x11_invent_auth(char *, int, char *, int);
 
+extern char *pfd_newconnect(Socket * s, char *hostname, int port, void *c);
+extern char *pfd_addforward(char *desthost, int destport, int port);
+extern void pfd_close(Socket s);
+extern void pfd_send(Socket s, char *data, int len);
+extern void pfd_confirm(Socket s);
+
 /*
  * Ciphers for SSH2. We miss out single-DES because it isn't
  * supported; also 3DES and Blowfish are both done differently from
@@ -263,6 +269,7 @@ enum {                                     /* channel types */
     CHAN_MAINSESSION,
     CHAN_X11,
     CHAN_AGENT,
+    CHAN_SOCKDATA
 };
 
 /*
@@ -286,9 +293,22 @@ struct ssh_channel {
        struct ssh_x11_channel {
            Socket s;
        } x11;
+       struct ssh_pfd_channel {
+           Socket s;
+       } pfd;
     } u;
 };
 
+/*
+ * 2-3-4 tree storing remote->local port forwardings (so we can
+ * reject any attempt to open a port we didn't explicitly ask to
+ * have forwarded).
+ */
+struct ssh_rportfwd {
+    unsigned port;
+    char host[256];
+};
+
 struct Packet {
     long length;
     int type;
@@ -330,6 +350,8 @@ static int ssh_echoing, ssh_editing;
 static tree234 *ssh_channels;         /* indexed by local id */
 static struct ssh_channel *mainchan;   /* primary session channel */
 
+static tree234 *ssh_rportfwds;
+
 static enum {
     SSH_STATE_PREPACKET,
     SSH_STATE_BEFORE_SIZE,
@@ -393,6 +415,18 @@ static int ssh_channelfind(void *av, void *bv)
     return 0;
 }
 
+static int ssh_rportcmp(void *av, void *bv)
+{
+    struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+    struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+    int i;
+    if ( (i = strcmp(a->host, b->host)) != 0)
+       return i < 0 ? -1 : +1;
+    if (a->port > b->port)
+       return +1;
+    return 0;
+}
+
 static int alloc_channel_id(void)
 {
     const unsigned CHANNEL_NUMBER_OFFSET = 256;
@@ -2263,7 +2297,10 @@ void sshfwd_close(struct ssh_channel *c)
        c->closes = 1;
        if (c->type == CHAN_X11) {
            c->u.x11.s = NULL;
-           logevent("X11 connection terminated");
+           logevent("Forwarded X11 connection terminated");
+       } else if (c->type == CHAN_SOCKDATA) {
+           c->u.pfd.s = NULL;
+           logevent("Forwarded port closed");
        }
     }
 }
@@ -2337,6 +2374,68 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
        }
     }
 
+    {
+       char type, *e;
+       int n;
+       int sport,dport;
+       char sports[256], dports[256], host[256];
+       char buf[1024];
+
+       ssh_rportfwds = newtree234(ssh_rportcmp);
+        /* Add port forwardings. */
+       e = cfg.portfwd;
+       while (*e) {
+           type = *e++;
+           n = 0;
+           while (*e && *e != '\t')
+               sports[n++] = *e++;
+           sports[n] = 0;
+           if (*e == '\t')
+               e++;
+           n = 0;
+           while (*e && *e != ':')
+               host[n++] = *e++;
+           host[n] = 0;
+           if (*e == ':')
+               e++;
+           n = 0;
+           while (*e)
+               dports[n++] = *e++;
+           dports[n] = 0;
+           e++;
+           dport = atoi(dports);
+           sport = atoi(sports);
+           if (sport && dport) {
+               if (type == 'L') {
+                   pfd_addforward(host, dport, sport);
+                   sprintf(buf, "Local port %d forwarding to %s:%d",
+                           sport, host, dport);
+                   logevent(buf);
+               } else {
+                   struct ssh_rportfwd *pf;
+                   pf = smalloc(sizeof(*pf));
+                   strcpy(pf->host, host);
+                   pf->port = dport;
+                   if (add234(ssh_rportfwds, pf) != pf) {
+                       sprintf(buf, 
+                               "Duplicate remote port forwarding to %s:%s",
+                               host, dport);
+                       logevent(buf);
+                   } else {
+                       sprintf(buf, "Requesting remote port %d forward to %s:%d",
+                               sport, host, dport);
+                       logevent(buf);
+                       send_packet(SSH1_CMSG_PORT_FORWARD_REQUEST,
+                                   PKT_INT, sport,
+                                   PKT_STR, host,
+                                   PKT_INT, dport,
+                                   PKT_END);
+                   }
+               }
+           }
+       }
+    }
+
     if (!cfg.nopty) {
        send_packet(SSH1_CMSG_REQUEST_PTY,
                    PKT_STR, cfg.termtype,
@@ -2460,6 +2559,73 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                                PKT_INT, c->remoteid, PKT_INT, c->localid,
                                PKT_END);
                }
+           } else if (pktin.type == SSH1_MSG_PORT_OPEN) {
+               /* Remote side is trying to open a channel to talk to a
+                * forwarded port. Give them back a local channel number. */
+               struct ssh_channel *c;
+               struct ssh_rportfwd pf;
+               int hostsize, port;
+               char host[256], buf[1024];
+               char *p, *h, *e;
+               c = smalloc(sizeof(struct ssh_channel));
+
+               hostsize = GET_32BIT(pktin.body+4);
+               for(h = host, p = pktin.body+8; hostsize != 0; hostsize--) {
+                   if (h+1 < host+sizeof(host))
+                       *h++ = *p;
+                   *p++;
+               }
+               *h = 0;
+               port = GET_32BIT(p);
+
+               strcpy(pf.host, host);
+               pf.port = port;
+
+               if (find234(ssh_rportfwds, &pf, NULL) == NULL) {
+                   sprintf(buf, "Rejected remote port open request for %s:%d",
+                           host, port);
+                   logevent(buf);
+                    send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+                                PKT_INT, GET_32BIT(pktin.body), PKT_END);
+               } else {
+                   sprintf(buf, "Received remote port open request for %s:%d",
+                           host, port);
+                   logevent(buf);
+                   e = pfd_newconnect(&c->u.pfd.s, host, port, c);
+                   if (e != NULL) {
+                       char buf[256];
+                       sprintf(buf, "Port open failed: %s", e);
+                       logevent(buf);
+                       sfree(c);
+                       send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+                                   PKT_INT, GET_32BIT(pktin.body),
+                                   PKT_END);
+                   } else {
+                       c->remoteid = GET_32BIT(pktin.body);
+                       c->localid = alloc_channel_id();
+                       c->closes = 0;
+                       c->type = CHAN_SOCKDATA;        /* identify channel type */
+                       add234(ssh_channels, c);
+                       send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+                                   PKT_INT, c->remoteid, PKT_INT,
+                                   c->localid, PKT_END);
+                       logevent("Forwarded port opened successfully");
+                   }
+               }
+
+           } else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION) {
+                   unsigned int remoteid = GET_32BIT(pktin.body);
+                   unsigned int localid = GET_32BIT(pktin.body+4);
+                   struct ssh_channel *c;
+                   
+                   c = find234(ssh_channels, &remoteid, ssh_channelfind);
+                   if (c) {
+                       c->remoteid = localid;
+                       pfd_confirm(c->u.pfd.s);
+                   } else {
+                       sshfwd_close(c);
+                   }
+
            } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
                       pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
                /* Remote side closes a channel. */
@@ -2474,11 +2640,17 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                        send_packet(pktin.type, PKT_INT, c->remoteid,
                                    PKT_END);
                    if ((c->closes == 0) && (c->type == CHAN_X11)) {
-                       logevent("X11 connection closed");
+                       logevent("Forwarded X11 connection terminated");
                        assert(c->u.x11.s != NULL);
                        x11_close(c->u.x11.s);
                        c->u.x11.s = NULL;
                    }
+                   if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) {
+                       logevent("Forwarded port closed");
+                       assert(c->u.pfd.s != NULL);
+                       pfd_close(c->u.pfd.s);
+                       c->u.pfd.s = NULL;
+                   }
                    c->closes |= closetype;
                    if (c->closes == 3) {
                        del234(ssh_channels, c);
@@ -2497,6 +2669,9 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                      case CHAN_X11:
                        x11_send(c->u.x11.s, p, len);
                        break;
+                     case CHAN_SOCKDATA:
+                             pfd_send(c->u.pfd.s, p, len);
+                             break;
                      case CHAN_AGENT:
                        /* Data for an agent message. Buffer it. */
                        while (len > 0) {
@@ -3976,6 +4151,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                      case CHAN_X11:
                        x11_send(c->u.x11.s, data, length);
                        break;
+                     case CHAN_SOCKDATA:
+                             pfd_send(c->u.pfd.s, data, length);
+                             break;
                      case CHAN_AGENT:
                        while (length > 0) {
                            if (c->u.a.lensofar < 4) {
@@ -4059,6 +4237,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    sshfwd_close(c);
                } else if (c->type == CHAN_AGENT) {
                    sshfwd_close(c);
+               } else if (c->type == CHAN_SOCKDATA) {
+                       pfd_close(c->u.pfd.s);
+                       sshfwd_close(c);
                }
            } else if (pktin.type == SSH2_MSG_CHANNEL_CLOSE) {
                unsigned i = ssh2_pkt_getuint32();
@@ -4080,6 +4261,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    break;
                  case CHAN_AGENT:
                    break;
+                 case CHAN_SOCKDATA:
+                         break;
                }
                del234(ssh_channels, c);
                sfree(c->v2.outbuffer);
@@ -4306,6 +4489,39 @@ static void ssh_special(Telnet_Special code)
     }
 }
 
+void *new_sock_channel(Socket s)
+{
+    struct ssh_channel *c;
+    c = smalloc(sizeof(struct ssh_channel));
+
+    if (c) {
+       c->remoteid = GET_32BIT(pktin.body);
+       c->localid = alloc_channel_id();
+       c->closes = 0;
+       c->type = CHAN_SOCKDATA;        /* identify channel type */
+       c->u.pfd.s = s;
+       add234(ssh_channels, c);
+    }
+    return c;
+}
+
+void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
+{
+    struct ssh_channel *c = (struct ssh_channel *)channel;
+    char buf[1024];
+
+    sprintf(buf, "Opening forwarded connection to %.512s:%d", hostname, port);
+    logevent(buf);
+
+    send_packet(SSH1_MSG_PORT_OPEN,
+               PKT_INT, c->localid,
+               PKT_STR, hostname,
+               PKT_INT, port,
+               //PKT_STR, org,
+               PKT_END);
+}
+
+
 static Socket ssh_socket(void)
 {
     return s;
@@ -4334,4 +4550,4 @@ Backend ssh_backend = {
     ssh_sendok,
     ssh_ldisc,
     22
-};
+};
\ No newline at end of file
diff --git a/ssh.h b/ssh.h
index 014c406..d7b5b16 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -1,6 +1,7 @@
 #include <string.h>
 
 #include "puttymem.h"
+#include "network.h"
 
 /*
  * Useful thing.
@@ -199,6 +200,8 @@ void random_add_noise(void *noise, int length);
 void random_add_heavynoise(void *noise, int length);
 
 void logevent(char *);
+void *new_sock_channel(Socket s); // allocates and register a new channel for port forwarding
+void ssh_send_port_open(void *channel, char *hostname, int port, char *org);
 
 Bignum copybn(Bignum b);
 Bignum bn_power_2(int n);
@@ -271,6 +274,7 @@ int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn,
 Bignum primegen(int bits, int modulus, int residue, int phase,
                progfn_t pfn, void *pfnparam);
 
+
 /*
  * zlib compression.
  */
index f87d265..bee184c 100644 (file)
@@ -148,35 +148,13 @@ void multiedit(struct ctlpos *cp, ...)
     cp->ypos += 8 + GAPWITHIN + 12 + GAPBETWEEN;
 }
 
-/*
- * A set of radio buttons on the same line, with a static above
- * them. `nacross' dictates how many parts the line is divided into
- * (you might want this not to equal the number of buttons if you
- * needed to line up some 2s and some 3s to look good in the same
- * panel).
- * 
- * There's a bit of a hack in here to ensure that if nacross
- * exceeds the actual number of buttons, the rightmost button
- * really does get all the space right to the edge of the line, so
- * you can do things like
- * 
- * (*) Button1  (*) Button2  (*) ButtonWithReallyLongTitle
- */
-void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
+static void radioline_common(struct ctlpos *cp, int nacross, va_list ap)
 {
     RECT r;
-    va_list ap;
     int group;
     int i;
     char *btext;
 
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
-    va_start(ap, nacross);
     group = WS_GROUP;
     i = 0;
     btext = va_arg(ap, char *);
@@ -206,11 +184,53 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
        i++;
        btext = nextbtext;
     }
-    va_end(ap);
     cp->ypos += r.bottom + GAPBETWEEN;
 }
 
 /*
+ * A set of radio buttons on the same line, with a static above
+ * them. `nacross' dictates how many parts the line is divided into
+ * (you might want this not to equal the number of buttons if you
+ * needed to line up some 2s and some 3s to look good in the same
+ * panel).
+ * 
+ * There's a bit of a hack in here to ensure that if nacross
+ * exceeds the actual number of buttons, the rightmost button
+ * really does get all the space right to the edge of the line, so
+ * you can do things like
+ * 
+ * (*) Button1  (*) Button2  (*) ButtonWithReallyLongTitle
+ */
+void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
+{
+    RECT r;
+    va_list ap;
+
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = STATICHEIGHT;
+    cp->ypos += r.bottom + GAPWITHIN;
+    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
+    va_start(ap, nacross);
+    radioline_common(cp, nacross, ap);
+    va_end(ap);
+}
+
+/*
+ * A set of radio buttons on the same line, without a static above
+ * them. Otherwise just like radioline.
+ */
+void bareradioline(struct ctlpos *cp, int nacross, ...)
+{
+    va_list ap;
+
+    va_start(ap, nacross);
+    radioline_common(cp, nacross, ap);
+    va_end(ap);
+}
+
+/*
  * A set of radio buttons on multiple lines, with a static above
  * them.
  */
@@ -763,3 +783,72 @@ void progressbar(struct ctlpos *cp, int id)
 #endif
          , WS_EX_CLIENTEDGE, "", id);
 }
+
+/*
+ * Another special control: the forwarding options setter. First a
+ * list box; next a static header line, introducing a pair of edit
+ * boxes with associated statics, another button, and a radio
+ * button pair.
+ */
+void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
+              char *e1stext, int e1sid, int e1id,
+              char *e2stext, int e2sid, int e2id,
+              char *btext, int bid)
+{
+    RECT r;
+    const int height = (STATICHEIGHT > EDITHEIGHT
+                       && STATICHEIGHT >
+                       PUSHBTNHEIGHT ? STATICHEIGHT : EDITHEIGHT >
+                       PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT);
+    const static int percents[] = { 25, 35, 15, 25 };
+    int i, j, xpos, percent;
+    const int LISTHEIGHT = 42;
+
+    /* The list box. */
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = LISTHEIGHT;
+    cp->ypos += r.bottom + GAPBETWEEN;
+    doctl(cp, r, "LISTBOX",
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
+         | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid);
+
+    /* The static control. */
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = STATICHEIGHT;
+    cp->ypos += r.bottom + GAPWITHIN;
+    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+    /* The statics+edits+buttons. */
+    for (j = 0; j < 2; j++) {
+       percent = 0;
+       for (i = 0; i < (j ? 2 : 4); i++) {
+           xpos = (cp->width + GAPBETWEEN) * percent / 100;
+           r.left = xpos + GAPBETWEEN;
+           percent += percents[i];
+           if (j==1 && i==1) percent = 100;
+           xpos = (cp->width + GAPBETWEEN) * percent / 100;
+           r.right = xpos - r.left;
+           r.top = cp->ypos;
+           r.bottom = (i == 0 ? STATICHEIGHT :
+                       i == 1 ? EDITHEIGHT : PUSHBTNHEIGHT);
+           r.top += (height - r.bottom) / 2;
+           if (i == 0) {
+               doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
+                     j == 0 ? e1stext : e2stext, j == 0 ? e1sid : e2sid);
+           } else if (i == 1) {
+               doctl(cp, r, "EDIT",
+                     WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
+                     WS_EX_CLIENTEDGE, "", j == 0 ? e1id : e2id);
+           } else if (i == 3) {
+               doctl(cp, r, "BUTTON",
+                     WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+                     0, btext, bid);
+           }
+       }
+       cp->ypos += height + GAPWITHIN;
+    }
+}
index 34d35af..550e479 100644 (file)
--- a/windlg.c
+++ b/windlg.c
@@ -503,10 +503,24 @@ enum { IDCX_ABOUT =
 
     tunnelspanelstart,
     IDC_TITLE_TUNNELS,
-    IDC_BOX_TUNNELS,
+    IDC_BOX_TUNNELS1,
+    IDC_BOX_TUNNELS2,
     IDC_X11_FORWARD,
     IDC_X11_DISPSTATIC,
     IDC_X11_DISPLAY,
+    IDC_LPORT_ALL,
+    IDC_PFWDSTATIC,
+    IDC_PFWDSTATIC2,
+    IDC_PFWDREMOVE,
+    IDC_PFWDLIST,
+    IDC_PFWDADD,
+    IDC_SPORTSTATIC,
+    IDC_SPORTEDIT,
+    IDC_DPORTSTATIC,
+    IDC_DPORTEDIT,
+    IDC_PFWDLOCAL,
+    IDC_PFWDREMOTE,
+
     tunnelspanelend,
 
     controlendvalue
@@ -669,6 +683,12 @@ static void init_dlg_ctrls(HWND hwnd, int keepsess)
                               (LPARAM) p);
            p += strlen(p) + 1;
        }
+       p = cfg.portfwd;
+       while (*p) {
+           SendDlgItemMessage(hwnd, IDC_PFWDLIST, LB_ADDSTRING, 0,
+                              (LPARAM) p);
+           p += strlen(p) + 1;
+       }
     }
     CheckRadioButton(hwnd, IDC_EMBSD, IDC_EMRFC,
                     cfg.rfc_environ ? IDC_EMRFC : IDC_EMBSD);
@@ -736,6 +756,9 @@ static void init_dlg_ctrls(HWND hwnd, int keepsess)
 
     CheckDlgButton(hwnd, IDC_X11_FORWARD, cfg.x11_forward);
     SetDlgItemText(hwnd, IDC_X11_DISPLAY, cfg.x11_display);
+
+    CheckDlgButton(hwnd, IDC_LPORT_ALL, cfg.lport_acceptall);
+    CheckRadioButton(hwnd, IDC_PFWDLOCAL, IDC_PFWDREMOTE, IDC_PFWDLOCAL);
 }
 
 struct treeview_faff {
@@ -1195,17 +1218,30 @@ static void create_controls(HWND hwnd, int dlgtype, int panel)
     }
 
     if (panel == tunnelspanelstart) {
-       /* The Tunnels panel. Accelerators used: [acgo] ex */
+       /* The Tunnels panel. Accelerators used: [acgo] deilmrstx */
        struct ctlpos cp;
        ctlposinit(&cp, hwnd, 80, 3, 13);
        if (dlgtype == 0) {
            bartitle(&cp, "Options controlling SSH tunnelling",
                     IDC_TITLE_TUNNELS);
-           beginbox(&cp, "X11 forwarding options", IDC_BOX_TUNNELS);
+           beginbox(&cp, "X11 forwarding", IDC_BOX_TUNNELS1);
            checkbox(&cp, "&Enable X11 forwarding", IDC_X11_FORWARD);
            multiedit(&cp, "&X display location", IDC_X11_DISPSTATIC,
                      IDC_X11_DISPLAY, 50, NULL);
            endbox(&cp);
+           beginbox(&cp, "Port forwarding", IDC_BOX_TUNNELS2);
+           checkbox(&cp, "Local ports accept connections from o&ther hosts", IDC_LPORT_ALL);
+           staticbtn(&cp, "Forwarded ports:", IDC_PFWDSTATIC,
+                     "&Remove", IDC_PFWDREMOVE);
+           fwdsetter(&cp, IDC_PFWDLIST,
+                     "Add new forwarded port:", IDC_PFWDSTATIC2,
+                     "&Source port", IDC_SPORTSTATIC, IDC_SPORTEDIT,
+                     "Dest&ination", IDC_DPORTSTATIC, IDC_DPORTEDIT,
+                     "A&dd", IDC_PFWDADD);
+           bareradioline(&cp, 2,
+                         "&Local", IDC_PFWDLOCAL, "Re&mote", IDC_PFWDREMOTE, NULL);
+           endbox(&cp);
+
        }
     }
 }
@@ -2400,11 +2436,101 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg,
                        cfg.x11_forward =
                        IsDlgButtonChecked(hwnd, IDC_X11_FORWARD);
                break;
+             case IDC_LPORT_ALL:
+               if (HIWORD(wParam) == BN_CLICKED ||
+                   HIWORD(wParam) == BN_DOUBLECLICKED)
+                       cfg.lport_acceptall =
+                       IsDlgButtonChecked(hwnd, IDC_LPORT_ALL);
+               break;
              case IDC_X11_DISPLAY:
                if (HIWORD(wParam) == EN_CHANGE)
                    GetDlgItemText(hwnd, IDC_X11_DISPLAY, cfg.x11_display,
                                   sizeof(cfg.x11_display) - 1);
                break;
+             case IDC_PFWDADD:
+               if (HIWORD(wParam) == BN_CLICKED ||
+                   HIWORD(wParam) == BN_DOUBLECLICKED) {
+                   char str[sizeof(cfg.portfwd)];
+                   char *p;
+                   if (IsDlgButtonChecked(hwnd, IDC_PFWDLOCAL))
+                       str[0] = 'L';
+                   else
+                       str[0] = 'R';
+                   GetDlgItemText(hwnd, IDC_SPORTEDIT, str+1,
+                                  sizeof(str) - 2);
+                   if (!str[1]) {
+                       MessageBox(hwnd,
+                                  "You need to specify a source port number",
+                                  "PuTTY Error", MB_OK | MB_ICONERROR);
+                       break;
+                   }
+                   p = str + strlen(str);
+                   *p++ = '\t';
+                   GetDlgItemText(hwnd, IDC_DPORTEDIT, p,
+                                  sizeof(str) - 1 - (p - str));
+                   if (!*p || !strchr(p, ':')) {
+                       MessageBox(hwnd,
+                                  "You need to specify a destination address\n"
+                                  "in the form \"host.name:port\"",
+                                  "PuTTY Error", MB_OK | MB_ICONERROR);
+                       break;
+                   }
+                   p = cfg.portfwd;
+                   while (*p) {
+                       while (*p)
+                           p++;
+                       p++;
+                   }
+                   if ((p - cfg.portfwd) + strlen(str) + 2 <
+                       sizeof(cfg.portfwd)) {
+                       strcpy(p, str);
+                       p[strlen(str) + 1] = '\0';
+                       SendDlgItemMessage(hwnd, IDC_PFWDLIST, LB_ADDSTRING,
+                                          0, (LPARAM) str);
+                       SetDlgItemText(hwnd, IDC_SPORTEDIT, "");
+                       SetDlgItemText(hwnd, IDC_DPORTEDIT, "");
+                   } else {
+                       MessageBox(hwnd, "Too many forwardings",
+                                  "PuTTY Error", MB_OK | MB_ICONERROR);
+                   }
+               }
+               break;
+             case IDC_PFWDREMOVE:
+               if (HIWORD(wParam) != BN_CLICKED &&
+                   HIWORD(wParam) != BN_DOUBLECLICKED) break;
+               i = SendDlgItemMessage(hwnd, IDC_PFWDLIST,
+                                      LB_GETCURSEL, 0, 0);
+               if (i == LB_ERR)
+                   MessageBeep(0);
+               else {
+                   char *p, *q;
+
+                   SendDlgItemMessage(hwnd, IDC_PFWDLIST, LB_DELETESTRING,
+                                      i, 0);
+                   p = cfg.portfwd;
+                   while (i > 0) {
+                       if (!*p)
+                           goto disaster2;
+                       while (*p)
+                           p++;
+                       p++;
+                       i--;
+                   }
+                   q = p;
+                   if (!*p)
+                       goto disaster2;
+                   while (*p)
+                       p++;
+                   p++;
+                   while (*p) {
+                       while (*p)
+                           *q++ = *p++;
+                       *q++ = *p++;
+                   }
+                   *q = '\0';
+                 disaster2:;
+               }
+               break;
            }
        return 0;
       case WM_CLOSE:
index ff7ae2a..15ad7b3 100644 (file)
--- a/window.c
+++ b/window.c
@@ -706,7 +706,7 @@ char *do_select(SOCKET skt, int startup)
     int msg, events;
     if (startup) {
        msg = WM_NETEVENT;
-       events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
+       events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT;
     } else {
        msg = events = 0;
     }
index 621c3ca..bcb9348 100644 (file)
--- a/winnet.c
+++ b/winnet.c
@@ -65,6 +65,7 @@ struct Socket_tag {
     void *private_ptr;
     struct buffer *head, *tail;
     int writable;
+    int frozen; /* this tells the write stuff not to even bother trying to send at this point */
     int sending_oob;
     int oobinline;
 };
@@ -368,6 +369,58 @@ static void sk_tcp_write(Socket s, char *data, int len);
 static void sk_tcp_write_oob(Socket s, char *data, int len);
 static char *sk_tcp_socket_error(Socket s);
 
+extern char *do_select(SOCKET skt, int startup);
+
+Socket sk_register(void *sock, Plug plug)
+{
+    static struct socket_function_table fn_table = {
+       sk_tcp_plug,
+       sk_tcp_close,
+       sk_tcp_write,
+       sk_tcp_write_oob,
+       sk_tcp_flush,
+       sk_tcp_socket_error
+    };
+
+    DWORD err;
+    char *errstr;
+    Actual_Socket ret;
+
+    /*
+     * Create Socket structure.
+     */
+    ret = smalloc(sizeof(struct Socket_tag));
+    ret->fn = &fn_table;
+    ret->error = NULL;
+    ret->plug = plug;
+    ret->head = ret->tail = NULL;
+    ret->writable = 1;                /* to start with */
+    ret->sending_oob = 0;
+    ret->frozen = 1;
+
+    ret->s = (SOCKET)sock;
+
+    if (ret->s == INVALID_SOCKET) {
+       err = WSAGetLastError();
+       ret->error = winsock_error_string(err);
+       return (Socket) ret;
+    }
+
+    ret->oobinline = 0;
+
+    /* Set up a select mechanism. This could be an AsyncSelect on a
+     * window, or an EventSelect on an event object. */
+    errstr = do_select(ret->s, 1);
+    if (errstr) {
+       ret->error = errstr;
+       return (Socket) ret;
+    }
+
+    add234(sktree, ret);
+
+    return (Socket) ret;
+}
+
 Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
              Plug plug)
 {
@@ -388,7 +441,6 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     DWORD err;
     char *errstr;
     Actual_Socket ret;
-    extern char *do_select(SOCKET skt, int startup);
     short localport;
 
     /*
@@ -401,6 +453,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     ret->head = ret->tail = NULL;
     ret->writable = 1;                /* to start with */
     ret->sending_oob = 0;
+    ret->frozen = 0;
 
     /*
      * Open socket.
@@ -519,6 +572,111 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     return (Socket) ret;
 }
 
+Socket sk_newlistenner(int port, Plug plug)
+{
+    static struct socket_function_table fn_table = {
+       sk_tcp_plug,
+       sk_tcp_close,
+       sk_tcp_write,
+       sk_tcp_write_oob,
+       sk_tcp_flush,
+       sk_tcp_socket_error
+    };
+
+    SOCKET s;
+#ifdef IPV6
+    SOCKADDR_IN6 a6;
+#endif
+    SOCKADDR_IN a;
+    DWORD err;
+    char *errstr;
+    Actual_Socket ret;
+    int retcode;
+    int on = 1;
+
+    /*
+     * Create Socket structure.
+     */
+    ret = smalloc(sizeof(struct Socket_tag));
+    ret->fn = &fn_table;
+    ret->error = NULL;
+    ret->plug = plug;
+    ret->head = ret->tail = NULL;
+    ret->writable = 0;                /* to start with */
+    ret->sending_oob = 0;
+    ret->frozen = 0;
+
+    /*
+     * Open socket.
+     */
+    s = socket(AF_INET, SOCK_STREAM, 0);
+    ret->s = s;
+
+    if (s == INVALID_SOCKET) {
+       err = WSAGetLastError();
+       ret->error = winsock_error_string(err);
+       return (Socket) ret;
+    }
+
+    ret->oobinline = 0;
+
+
+    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
+
+
+#ifdef IPV6
+       if (addr->family == AF_INET6) {
+           memset(&a6, 0, sizeof(a6));
+           a6.sin6_family = AF_INET6;
+/*a6.sin6_addr      = in6addr_any; *//* == 0 */
+           a6.sin6_port = htons(port);
+       } else
+#endif
+       {
+           a.sin_family = AF_INET;
+           a.sin_addr.s_addr = htonl(INADDR_ANY);
+           a.sin_port = htons((short)port);
+       }
+#ifdef IPV6
+       retcode = bind(s, (addr->family == AF_INET6 ?
+                          (struct sockaddr *) &a6 :
+                          (struct sockaddr *) &a),
+                      (addr->family ==
+                       AF_INET6 ? sizeof(a6) : sizeof(a)));
+#else
+       retcode = bind(s, (struct sockaddr *) &a, sizeof(a));
+#endif
+       if (retcode != SOCKET_ERROR) {
+           err = 0;
+       } else {
+           err = WSAGetLastError();
+       }
+
+    if (err) {
+       ret->error = winsock_error_string(err);
+       return (Socket) ret;
+    }
+
+
+    if (listen(s, SOMAXCONN) == SOCKET_ERROR) {
+        closesocket(s);
+       ret->error = winsock_error_string(err);
+       return (Socket) ret;
+    }
+
+    /* Set up a select mechanism. This could be an AsyncSelect on a
+     * window, or an EventSelect on an event object. */
+    errstr = do_select(s, 1);
+    if (errstr) {
+       ret->error = errstr;
+       return (Socket) ret;
+    }
+
+    add234(sktree, ret);
+
+    return (Socket) ret;
+}
+
 static void sk_tcp_close(Socket sock)
 {
     extern char *do_select(SOCKET skt, int startup);
@@ -536,6 +694,7 @@ static void sk_tcp_close(Socket sock)
  */
 void try_send(Actual_Socket s)
 {
+    if (s->frozen) return;
     while (s->head) {
        int nsent;
        DWORD err;
@@ -694,6 +853,11 @@ int select_result(WPARAM wParam, LPARAM lParam)
 
     switch (WSAGETSELECTEVENT(lParam)) {
       case FD_READ:
+
+       /* In the case the socket is still frozen, we don't even bother */
+       if (s->frozen)
+           break;
+
        /*
         * We have received data on the socket. For an oobinline
         * socket, this might be data _before_ an urgent pointer,
@@ -769,6 +933,26 @@ int select_result(WPARAM wParam, LPARAM lParam)
            }
        } while (ret > 0);
        return open;
+       case FD_ACCEPT:
+       {
+               struct sockaddr isa;
+               int addrlen = sizeof(struct sockaddr);
+               SOCKET t;  /* socket of connection */
+
+               memset(&isa, 0, sizeof(struct sockaddr));
+               err = 0;
+               t = accept(s->s,&isa,&addrlen);
+               if (t == INVALID_SOCKET)
+                               {
+                                       err = WSAGetLastError();
+                                       if (err == WSATRY_AGAIN)
+                                               break;
+                               }
+
+               if (plug_accepting(s->plug, &isa, (void*)t)) {
+                       closesocket(t); // denied or error
+               }
+       }
     }
 
     return 1;
@@ -805,6 +989,16 @@ static char *sk_tcp_socket_error(Socket sock)
     return s->error;
 }
 
+void sk_set_frozen(Socket sock, int is_frozen)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    s->frozen = is_frozen;
+    if (!is_frozen) {
+       char c;
+       recv(s->s, &c, 1, MSG_PEEK);
+    }
+}
+
 /*
  * For Plink: enumerate all sockets currently active.
  */
index 17eaca3..eb610e0 100644 (file)
@@ -38,6 +38,7 @@ void beginbox(struct ctlpos *cp, char *name, int idbox);
 void endbox(struct ctlpos *cp);
 void multiedit(struct ctlpos *cp, ...);
 void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...);
+void bareradioline(struct ctlpos *cp, int nacross, ...);
 void radiobig(struct ctlpos *cp, char *text, int id, ...);
 void checkbox(struct ctlpos *cp, char *text, int id);
 void statictext(struct ctlpos *cp, char *text, int id);
@@ -63,3 +64,7 @@ void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
 void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
                char *btext, int bid, ...);
 void progressbar(struct ctlpos *cp, int id);
+void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
+              char *e1stext, int e1sid, int e1id,
+              char *e2stext, int e2sid, int e2id,
+              char *btext, int bid);
index 5be69b8..b13f695 100644 (file)
--- a/x11fwd.c
+++ b/x11fwd.c
@@ -197,10 +197,9 @@ char *x11_init(Socket * s, char *display, void *c)
 void x11_close(Socket s)
 {
     struct X11Private *pr;
-    \rif (!s)
-       \rreturn;
-    \rpr = (struct X11Private *) sk_get_private_ptr(s);
-
+    if (!s)
+       return;
+    pr = (struct X11Private *) sk_get_private_ptr(s);
     if (pr->auth_protocol) {
        sfree(pr->auth_protocol);
        sfree(pr->auth_data);