Index: common/network.h
===================================================================
--- common/network.h	(revision 1334)
+++ common/network.h	(working copy)
@@ -2,7 +2,7 @@
  *   Go buy a copy.
  *
  * Copyright (C) 1999 Dave Cole
- * Copyright (C) 2003, 2006 Bas Wijnen <shevek@fmf.nl>
+ * Copyright (C) 2003-2007 Bas Wijnen <shevek@fmf.nl>
  * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -41,6 +41,9 @@
 	time_t last_response;	/* used for activity detection.  */
 	guint timer_id;
 	void *user_data;
+	Session *parent;	/* NULL for non-tunneled connections */
+	GList *children;	/* NULL for tunneled connections */
+	gint id;		/* Id for tunnel, unused for parent */
 
 	gboolean connect_in_progress;
 	gboolean waiting_for_close;
@@ -67,6 +70,10 @@
 void net_finish(void);
 
 Session *net_new(NetNotifyFunc notify_func, void *user_data);
+Session *net_tunnel_accept(Session * parent, NetNotifyFunc func,
+			   void *user_data, gint id);
+Session *net_tunnel_connect(Session * parent, NetNotifyFunc func,
+			    void *user_data);
 void net_free(Session ** ses);
 
 void net_use_fd(Session * ses, int fd, gboolean do_ping);
Index: common/state.c
===================================================================
--- common/state.c	(revision 1334)
+++ common/state.c	(working copy)
@@ -2,7 +2,7 @@
  *   Go buy a copy.
  *
  * Copyright (C) 1999 Dave Cole
- * Copyright (C) 2003, 2006 Bas Wijnen <shevek@fmf.nl>
+ * Copyright (C) 2003-2007 Bas Wijnen <shevek@fmf.nl>
  * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -181,6 +181,24 @@
 	net_use_fd(sm->ses, fd, do_ping);
 }
 
+void sm_tunnel_accept(StateMachine * sm, Session * parent, gint id)
+{
+	if (sm->ses != NULL)
+		net_free(&sm->ses);
+
+	sm->ses =
+	    net_tunnel_accept(parent, (NetNotifyFunc) net_event, sm, id);
+}
+
+void sm_tunnel_connect(StateMachine * sm, Session * parent)
+{
+	if (sm->ses != NULL)
+		net_free(&sm->ses);
+
+	sm->ses =
+	    net_tunnel_connect(parent, (NetNotifyFunc) net_event, sm);
+}
+
 static gint get_num(gchar * str, gint * num)
 {
 	gint len = 0;
Index: common/state.h
===================================================================
--- common/state.h	(revision 1334)
+++ common/state.h	(working copy)
@@ -2,7 +2,7 @@
  *   Go buy a copy.
  *
  * Copyright (C) 1999 Dave Cole
- * Copyright (C) 2003, 2006 Bas Wijnen <shevek@fmf.nl>
+ * Copyright (C) 2003-2007 Bas Wijnen <shevek@fmf.nl>
  * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -180,6 +180,8 @@
 gboolean sm_connect(StateMachine * sm, const gchar * host,
 		    const gchar * port);
 void sm_use_fd(StateMachine * sm, gint fd, gboolean do_ping);
+void sm_tunnel_accept(StateMachine * sm, Session * parent, gint id);
+void sm_tunnel_connect(StateMachine * sm, Session * parent);
 void sm_dec_use_count(StateMachine * sm);
 void sm_inc_use_count(StateMachine * sm);
 /** Dump the stack */
Index: common/network.c
===================================================================
--- common/network.c	(revision 1334)
+++ common/network.c	(working copy)
@@ -2,7 +2,7 @@
  *   Go buy a copy.
  *
  * Copyright (C) 1999 Dave Cole
- * Copyright (C) 2003, 2006 Bas Wijnen <shevek@fmf.nl>
+ * Copyright (C) 2003-2007 Bas Wijnen <shevek@fmf.nl>
  * Copyright (C) 2005-2007 Roland Clobus <rclobus@bigfoot.com>
  * Copyright (C) 2005 Keishi Suenaga
  *
@@ -21,6 +21,8 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+#define TUNNEL_CHAR '='
+
 #include "config.h"
 #ifdef HAVE_WS2TCPIP_H
 #include <ws2tcpip.h>
@@ -122,6 +124,19 @@
 static void read_ready(Session * ses);
 static void write_ready(Session * ses);
 
+static Session *tunnel_find(Session * ses, gint id)
+{
+	GList *c;
+	if (ses->parent != NULL)
+		return NULL;
+	for (c = g_list_first(ses->children); c; c = g_list_next(c)) {
+		Session *child = c->data;
+		if (child->id == id)
+			return child;
+	}
+	return NULL;
+}
+
 static void listen_read(Session * ses, gboolean monitor)
 {
 	if (monitor && ses->read_tag == 0)
@@ -335,8 +350,37 @@
 
 void net_write(Session * ses, const gchar * data)
 {
-	if (!ses || ses->fd < 0)
+	gchar *d = NULL;
+	const gchar *last = data + strlen(data) - 1;
+	/* we use a line-based protocol.  For this function to properly work
+	 * with tunneling, it is essential that full lines are always written
+	 * in one block. */
+	g_assert(last > data && *last == '\n');
+	if (ses->parent != NULL) {
+		/* This is a tunneled connection.  Prepend the tunneling
+		 * prefix to all lines in the data and send it to the
+		 * parent's session */
+		const gchar *pos = data;
+		gchar *prefix =
+		    g_strdup_printf("%c%d.", TUNNEL_CHAR, ses->id);
+		while (pos <= last) {
+			const gchar *oldpos = pos;
+			gchar *new_data;
+			pos = strchr(pos, '\n') + 1;
+			new_data = g_strdup_printf
+			    ("%s%s%*s", d, prefix, pos - oldpos, d);
+			g_free(d);
+			d = new_data;
+		}
+		g_free(prefix);
+		data = d;
+		ses = ses->parent;
+	}
+	if (!ses || ses->fd < 0) {
+		if (d)
+			g_free(d);
 		return;
+	}
 	if (ses->write_queue != NULL || !net_connected(ses)) {
 		/* reassign the pointer, because the glib docs say it may
 		 * change and because if we're in the process of connecting the
@@ -363,6 +407,8 @@
 					    ("Error writing to socket: %s\n"),
 					    net_errormsg());
 				close_and_callback(ses);
+				if (d)
+					g_free(d);
 				return;
 			}
 			num = 0;
@@ -373,6 +419,8 @@
 			listen_write(ses, TRUE);
 		}
 	}
+	if (d)
+		g_free(d);
 }
 
 void net_printf(Session * ses, const gchar * fmt, ...)
@@ -460,7 +508,27 @@
 
 		debug("(%d) <-- %s", ses->fd, line);
 
-		notify(ses, NET_READ, line);
+		if (line[0] == TUNNEL_CHAR) {
+			Session *s;
+			gint id = 0;
+			gint pos = 1;
+			while (line[pos] && line[pos] != '.'
+			       && line[pos] != '!') {
+				id *= 10;
+				id += line[pos] - '0';
+				++pos;
+			}
+			s = tunnel_find(ses, id);
+			/* ignore traffic on closed tunnels */
+			if (s) {
+				if (line[pos] == '!')
+					notify(s, NET_CLOSE, NULL);
+				else
+					notify(s, NET_READ,
+					       &line[pos + 1]);
+			}
+		} else
+			notify(ses, NET_READ, line);
 	}
 
 	if (offset < ses->read_len) {
@@ -487,10 +555,51 @@
 	ses->notify_func = notify_func;
 	ses->user_data = user_data;
 	ses->fd = -1;
+	ses->parent = NULL;
+	ses->children = NULL;
 
 	return ses;
 }
 
+Session *net_tunnel_accept(Session * parent, NetNotifyFunc func,
+			   void *user_data, gint id)
+{
+	Session *data;
+
+	/* If this session is itself a tunnel, use the parent instead.  */
+	if (parent->parent)
+		return net_tunnel_accept(parent->parent, func, user_data,
+					 id);
+
+	g_assert(tunnel_find(parent, id) == NULL);
+	data = g_malloc(sizeof(Session));
+	data->id = id;
+	data->notify_func = func;
+	data->user_data = user_data;
+	data->parent = parent;
+	data->children = NULL;
+	parent->children = g_list_prepend(parent->children, data);
+	return data;
+}
+
+Session *net_tunnel_connect(Session * parent, NetNotifyFunc func,
+			    void *user_data)
+{
+	gint id = 0;
+
+	/* If this session is itself a tunnel, use the parent instead.  */
+	if (parent->parent)
+		return net_tunnel_connect(parent->parent, func, user_data);
+
+	/* When last_id wraps, we reuse old ids.  This code makes
+	 * sure we don't use ids which are still in use.  */
+	while (tunnel_find(parent, id))
+		++id;
+
+	/* Increment last_id to avoid finding it again next time.  */
+	return net_tunnel_accept(parent, func, user_data, id);
+}
+
 void net_use_fd(Session * ses, int fd, gboolean do_ping)
 {
 	ses->fd = fd;
@@ -652,17 +761,32 @@
 		return FALSE;
 }
 
-/* Free and NULL-ity the session *ses */
+/* Free and NULL-ify the session *ses */
 void net_free(Session ** ses)
 {
-	net_close(*ses);
+	if ((*ses)->parent == NULL) {
+		/* Close active tunnels, if any.  */
+		while ((*ses)->children)
+			close_and_callback((*ses)->children->data);
 
-	if ((*ses)->host != NULL)
-		g_free((*ses)->host);
-	if ((*ses)->port != NULL)
-		g_free((*ses)->port);
-	g_free(*ses);
-	*ses = NULL;
+		net_close(*ses);
+
+		if ((*ses)->host != NULL)
+			g_free((*ses)->host);
+		if ((*ses)->port != NULL)
+			g_free((*ses)->port);
+		g_free(*ses);
+		*ses = NULL;
+	} else {
+		/* Use a new variable, because *ses may be overwritten.  */
+		Session *tunnel = *ses;
+		net_printf(tunnel->parent, "%c%d!\n", TUNNEL_CHAR,
+			   tunnel->id);
+		if (tunnel->parent)
+			tunnel->parent->children = g_list_remove
+			    (tunnel->parent->children, tunnel);
+		g_free(tunnel);
+	}
 }
 
 gchar *get_my_hostname(void)
