Commits

Anonymous committed c919cc6

SSH port forwarding! How cool is that?
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.

Comments (0)

Files changed (13)

 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)
 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 
 ##--
 
      *  - 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
+     */
 };
 
 
 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))
 #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
 
 /*
 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 **********/
 
  * of what it will eventually look like.
  */
 
-
 typedef struct certificate *Certificate;
 typedef struct our_certificate *Our_Certificate;
     /* to be defined somewhere else, somehow */
 {
     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;
     }
 			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) {
     /* 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;
 
 /*
     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);
 }
     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);
 }
 
 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
     CHAN_MAINSESSION,
     CHAN_X11,
     CHAN_AGENT,
+    CHAN_SOCKDATA
 };
 
 /*
 	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;
 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,
     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;
 	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");
 	}
     }
 }
 	}
     }
 
+    {
+	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,
 				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. */
 			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);
 		      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) {
 		      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) {
 		    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();
 		    break;
 		  case CHAN_AGENT:
 		    break;
+		  case CHAN_SOCKDATA:
+			  break;
 		}
 		del234(ssh_channels, c);
 		sfree(c->v2.outbuffer);
     }
 }
 
+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;
     ssh_sendok,
     ssh_ldisc,
     22
-};
+};
 #include <string.h>
 
 #include "puttymem.h"
+#include "network.h"
 
 /*
  * Useful thing.
 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);
 Bignum primegen(int bits, int modulus, int residue, int phase,
 		progfn_t pfn, void *pfnparam);
 
+
 /*
  * zlib compression.
  */
     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 *);
 	i++;
 	btext = nextbtext;
     }
+    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);
-    cp->ypos += r.bottom + GAPBETWEEN;
+}
+
+/*
+ * 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);
 }
 
 /*
 #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;
+    }
+}
 
     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
 			       (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);
 
     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 {
     }
 
     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);
+
 	}
     }
 }
 			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:
     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;
     }
     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;
 };
 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)
 {
     DWORD err;
     char *errstr;
     Actual_Socket ret;
-    extern char *do_select(SOCKET skt, int startup);
     short localport;
 
     /*
     ret->head = ret->tail = NULL;
     ret->writable = 1;		       /* to start with */
     ret->sending_oob = 0;
+    ret->frozen = 0;
 
     /*
      * Open socket.
     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);
  */
 void try_send(Actual_Socket s)
 {
+    if (s->frozen) return;
     while (s->head) {
 	int nsent;
 	DWORD err;
 
     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,
 	    }
 	} 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;
     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.
  */
 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);
 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);
 void x11_close(Socket s)
 {
     struct X11Private *pr;
-    
-	
-    
-
+    if (!s)
+	return;
+    pr = (struct X11Private *) sk_get_private_ptr(s);
     if (pr->auth_protocol) {
 	sfree(pr->auth_protocol);
 	sfree(pr->auth_data);