- reyk@cvs.openbsd.org 2005/12/06 22:38:28
     [auth-options.c auth-options.h channels.c channels.h clientloop.c]
     [misc.c misc.h readconf.c readconf.h scp.c servconf.c servconf.h]
     [serverloop.c sftp.c ssh.1 ssh.c ssh_config ssh_config.5 sshconnect.c]
     [sshconnect.h sshd.8 sshd_config sshd_config.5]
     Add support for tun(4) forwarding over OpenSSH, based on an idea and
     initial channel code bits by markus@. This is a simple and easy way to
     use OpenSSH for ad hoc virtual private network connections, e.g.
     administrative tunnels or secure wireless access. It's based on a new
     ssh channel and works similar to the existing TCP forwarding support,
     except that it depends on the tun(4) network interface on both ends of
     the connection for layer 2 or layer 3 tunneling. This diff also adds
     support for LocalCommand in the ssh(1) client.

     ok djm@, markus@, jmc@ (manpages), tested and discussed with others
diff --git a/ChangeLog b/ChangeLog
index d187f2a..96ed9a0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,21 @@
      [ssh.1]
      avoid ambiguities in describing TZ;
      ok djm@
+   - reyk@cvs.openbsd.org 2005/12/06 22:38:28
+     [auth-options.c auth-options.h channels.c channels.h clientloop.c]
+     [misc.c misc.h readconf.c readconf.h scp.c servconf.c servconf.h]
+     [serverloop.c sftp.c ssh.1 ssh.c ssh_config ssh_config.5 sshconnect.c]
+     [sshconnect.h sshd.8 sshd_config sshd_config.5]
+     Add support for tun(4) forwarding over OpenSSH, based on an idea and
+     initial channel code bits by markus@. This is a simple and easy way to
+     use OpenSSH for ad hoc virtual private network connections, e.g.
+     administrative tunnels or secure wireless access. It's based on a new
+     ssh channel and works similar to the existing TCP forwarding support,
+     except that it depends on the tun(4) network interface on both ends of
+     the connection for layer 2 or layer 3 tunneling. This diff also adds
+     support for LocalCommand in the ssh(1) client.
+
+     ok djm@, markus@, jmc@ (manpages), tested and discussed with others
 
 20051201
  - (djm) [envpass.sh] Remove regress script that was accidentally committed 
@@ -3399,4 +3414,4 @@
    - (djm) Trim deprecated options from INSTALL. Mention UsePAM
    - (djm) Fix quote handling in sftp; Patch from admorten AT umich.edu
 
-$Id: ChangeLog,v 1.4018 2005/12/13 08:25:43 djm Exp $
+$Id: ChangeLog,v 1.4019 2005/12/13 08:29:02 djm Exp $
diff --git a/auth-options.c b/auth-options.c
index a85e408..54798d9 100644
--- a/auth-options.c
+++ b/auth-options.c
@@ -10,7 +10,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: auth-options.c,v 1.31 2005/03/10 22:40:38 deraadt Exp $");
+RCSID("$OpenBSD: auth-options.c,v 1.32 2005/12/06 22:38:27 reyk Exp $");
 
 #include "xmalloc.h"
 #include "match.h"
@@ -35,6 +35,9 @@
 /* "environment=" options. */
 struct envstring *custom_environment = NULL;
 
+/* "tunnel=" option. */
+int forced_tun_device = -1;
+
 extern ServerOptions options;
 
 void
@@ -54,6 +57,7 @@
 		xfree(forced_command);
 		forced_command = NULL;
 	}
+	forced_tun_device = -1;
 	channel_clear_permitted_opens();
 	auth_debug_reset();
 }
@@ -269,6 +273,41 @@
 			xfree(patterns);
 			goto next_option;
 		}
+		cp = "tunnel=\"";
+		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
+			char *tun = NULL;
+			opts += strlen(cp);
+			tun = xmalloc(strlen(opts) + 1);
+			i = 0;
+			while (*opts) {
+				if (*opts == '"')
+					break;
+				tun[i++] = *opts++;
+			}
+			if (!*opts) {
+				debug("%.100s, line %lu: missing end quote",
+				    file, linenum);
+				auth_debug_add("%.100s, line %lu: missing end quote",
+				    file, linenum);
+				xfree(tun);
+				forced_tun_device = -1;
+				goto bad_option;
+			}
+			tun[i] = 0;
+			forced_tun_device = a2tun(tun, NULL);
+			xfree(tun);
+			if (forced_tun_device < -1) {
+				debug("%.100s, line %lu: invalid tun device",
+				    file, linenum);
+				auth_debug_add("%.100s, line %lu: invalid tun device",
+				    file, linenum);
+				forced_tun_device = -1;
+				goto bad_option;
+			}
+			auth_debug_add("Forced tun device: %d", forced_tun_device);
+			opts++;
+			goto next_option;
+		}
 next_option:
 		/*
 		 * Skip the comma, and move to the next option
diff --git a/auth-options.h b/auth-options.h
index 15fb212..3cd02a7 100644
--- a/auth-options.h
+++ b/auth-options.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: auth-options.h,v 1.12 2002/07/21 18:34:43 stevesk Exp $	*/
+/*	$OpenBSD: auth-options.h,v 1.13 2005/12/06 22:38:27 reyk Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -28,6 +28,7 @@
 extern int no_pty_flag;
 extern char *forced_command;
 extern struct envstring *custom_environment;
+extern int forced_tun_device;
 
 int	auth_parse_options(struct passwd *, char *, char *, u_long);
 void	auth_clear_options(void);
diff --git a/channels.c b/channels.c
index 9607717..b4fd89f 100644
--- a/channels.c
+++ b/channels.c
@@ -39,7 +39,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: channels.c,v 1.227 2005/10/14 02:29:37 stevesk Exp $");
+RCSID("$OpenBSD: channels.c,v 1.228 2005/12/06 22:38:27 reyk Exp $");
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -1414,6 +1414,8 @@
 				debug2("channel %d: filter stops", c->self);
 				chan_read_failed(c);
 			}
+		} else if (c->datagram) {
+			buffer_put_string(&c->input, buf, len);
 		} else {
 			buffer_append(&c->input, buf, len);
 		}
@@ -1432,6 +1434,23 @@
 	if (c->wfd != -1 &&
 	    FD_ISSET(c->wfd, writeset) &&
 	    buffer_len(&c->output) > 0) {
+		if (c->datagram) {
+			data = buffer_get_string(&c->output, &dlen);
+			/* ignore truncated writes, datagrams might get lost */
+			c->local_consumed += dlen + 4;
+			len = write(c->wfd, data, dlen);
+			xfree(data);
+			if (len < 0 && (errno == EINTR || errno == EAGAIN))
+				return 1;
+			if (len <= 0) {
+				if (c->type != SSH_CHANNEL_OPEN)
+					chan_mark_dead(c);
+				else
+					chan_write_failed(c);
+				return -1;
+			}
+			return 1;
+		}
 		data = buffer_ptr(&c->output);
 		dlen = buffer_len(&c->output);
 #ifdef _AIX
@@ -1792,6 +1811,22 @@
 		if ((c->istate == CHAN_INPUT_OPEN ||
 		    c->istate == CHAN_INPUT_WAIT_DRAIN) &&
 		    (len = buffer_len(&c->input)) > 0) {
+			if (c->datagram) {
+				if (len > 0) {
+					u_char *data;
+					u_int dlen;
+
+					data = buffer_get_string(&c->input,
+					    &dlen);
+					packet_start(SSH2_MSG_CHANNEL_DATA);
+					packet_put_int(c->remote_id);
+					packet_put_string(data, dlen);
+					packet_send();
+					c->remote_window -= dlen + 4;
+					xfree(data);
+				}
+				continue;
+			}
 			/*
 			 * Send some data for the other side over the secure
 			 * connection.
@@ -1914,7 +1949,10 @@
 		c->local_window -= data_len;
 	}
 	packet_check_eom();
-	buffer_append(&c->output, data, data_len);
+	if (c->datagram)
+		buffer_put_string(&c->output, data, data_len);
+	else
+		buffer_append(&c->output, data, data_len);
 	xfree(data);
 }
 
diff --git a/channels.h b/channels.h
index 7e1cc7c..743a206 100644
--- a/channels.h
+++ b/channels.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: channels.h,v 1.80 2005/10/10 10:23:08 djm Exp $	*/
+/*	$OpenBSD: channels.h,v 1.81 2005/12/06 22:38:27 reyk Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -112,6 +112,8 @@
 
 	/* filter */
 	channel_filter_fn	*input_filter;
+
+	int     datagram;	/* keep boundaries */
 };
 
 #define CHAN_EXTENDED_IGNORE		0
diff --git a/clientloop.c b/clientloop.c
index 001c8f1..a97734c 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -59,7 +59,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: clientloop.c,v 1.145 2005/10/30 08:52:17 djm Exp $");
+RCSID("$OpenBSD: clientloop.c,v 1.146 2005/12/06 22:38:27 reyk Exp $");
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -914,6 +914,15 @@
 		logit("      -Lport:host:hostport    Request local forward");
 		logit("      -Rport:host:hostport    Request remote forward");
 		logit("      -KRhostport             Cancel remote forward");
+		if (!options.permit_local_command)
+			goto out;
+		logit("      !args                   Execute local command");
+		goto out;
+	}
+
+	if (*s == '!' && options.permit_local_command) {
+		s++;
+		ssh_local_cmd(s);
 		goto out;
 	}
 
diff --git a/misc.c b/misc.c
index 27b947f..9b23e2c 100644
--- a/misc.c
+++ b/misc.c
@@ -24,7 +24,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: misc.c,v 1.35 2005/09/13 23:40:07 djm Exp $");
+RCSID("$OpenBSD: misc.c,v 1.36 2005/12/06 22:38:27 reyk Exp $");
 
 #include "misc.h"
 #include "log.h"
@@ -194,6 +194,37 @@
 	return port;
 }
 
+int
+a2tun(const char *s, int *remote)
+{
+	const char *errstr = NULL;
+	char *sp, *ep;
+	int tun;
+
+	if (remote != NULL) {
+		*remote = -1;
+		sp = xstrdup(s);
+		if ((ep = strchr(sp, ':')) == NULL) {
+			xfree(sp);
+			return (a2tun(s, NULL));
+		}
+		ep[0] = '\0'; ep++;
+		*remote = a2tun(ep, NULL);
+		tun = a2tun(sp, NULL);
+		xfree(sp);
+		return (tun);
+	}
+
+	if (strcasecmp(s, "any") == 0)
+		return (-1);
+
+	tun = strtonum(s, 0, INT_MAX, &errstr);
+	if (errstr != NULL || tun < -1)
+		return (-2);
+
+	return (tun);
+}
+
 #define SECONDS		1
 #define MINUTES		(SECONDS * 60)
 #define HOURS		(MINUTES * 60)
@@ -507,6 +538,31 @@
 	return -1;
 }
 
+int
+tun_open(int tun)
+{
+	char name[100];
+	int i, fd;
+
+	if (tun > -1) {
+		snprintf(name, sizeof(name), "/dev/tun%d", tun);
+		if ((fd = open(name, O_RDWR)) >= 0)  {
+			debug("%s: %s: %d", __func__, name, fd);
+			return (fd);
+		}
+	} else {
+		for (i = 100; i >= 0; i--) {
+			snprintf(name, sizeof(name), "/dev/tun%d", i);
+			if ((fd = open(name, O_RDWR)) >= 0)  {
+				debug("%s: %s: %d", __func__, name, fd);
+				return (fd);
+			}
+		}
+	}
+	debug("%s: %s failed: %s", __func__, name, strerror(errno));
+	return (-1);
+}
+
 void
 sanitise_stdfd(void)
 {
diff --git a/misc.h b/misc.h
index 5154133..ff2ba1b 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: misc.h,v 1.26 2005/09/13 23:40:07 djm Exp $	*/
+/*	$OpenBSD: misc.h,v 1.27 2005/12/06 22:38:27 reyk Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -20,6 +20,7 @@
 int	 unset_nonblock(int);
 void	 set_nodelay(int);
 int	 a2port(const char *);
+int	 a2tun(const char *, int *);
 char	*hpdelim(char **);
 char	*cleanhostname(char *);
 char	*colon(char *);
@@ -49,3 +50,4 @@
 char	*read_passphrase(const char *, int);
 int	 ask_permission(const char *, ...) __attribute__((format(printf, 1, 2)));
 int	 read_keyfile_line(FILE *, const char *, char *, size_t, u_long *);
+int	 tun_open(int);
diff --git a/readconf.c b/readconf.c
index cf27a9f..b6aad9d 100644
--- a/readconf.c
+++ b/readconf.c
@@ -12,7 +12,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: readconf.c,v 1.143 2005/07/30 02:03:47 djm Exp $");
+RCSID("$OpenBSD: readconf.c,v 1.144 2005/12/06 22:38:27 reyk Exp $");
 
 #include "ssh.h"
 #include "xmalloc.h"
@@ -70,6 +70,10 @@
      Cipher none
      PasswordAuthentication no
 
+   Host vpn.fake.com
+     Tunnel yes
+     TunnelDevice 3
+
    # Defaults for various options
    Host *
      ForwardAgent no
@@ -107,6 +111,7 @@
 	oAddressFamily, oGssAuthentication, oGssDelegateCreds,
 	oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
 	oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
+	oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
 	oDeprecated, oUnsupported
 } OpCodes;
 
@@ -198,6 +203,10 @@
 	{ "controlpath", oControlPath },
 	{ "controlmaster", oControlMaster },
 	{ "hashknownhosts", oHashKnownHosts },
+	{ "tunnel", oTunnel },
+	{ "tunneldevice", oTunnelDevice },
+	{ "localcommand", oLocalCommand },
+	{ "permitlocalcommand", oPermitLocalCommand },
 	{ NULL, oBadOption }
 };
 
@@ -264,6 +273,7 @@
 		xfree(options->remote_forwards[i].connect_host);
 	}
 	options->num_remote_forwards = 0;
+	options->tun_open = 0;
 }
 
 /*
@@ -296,7 +306,7 @@
 		    int *activep)
 {
 	char *s, **charptr, *endofnumber, *keyword, *arg, *arg2, fwdarg[256];
-	int opcode, *intptr, value;
+	int opcode, *intptr, value, value2;
 	size_t len;
 	Forward fwd;
 
@@ -553,9 +563,10 @@
 		goto parse_string;
 
 	case oProxyCommand:
+		charptr = &options->proxy_command;
+parse_command:
 		if (s == NULL)
 			fatal("%.200s line %d: Missing argument.", filename, linenum);
-		charptr = &options->proxy_command;
 		len = strspn(s, WHITESPACE "=");
 		if (*activep && *charptr == NULL)
 			*charptr = xstrdup(s + len);
@@ -822,6 +833,31 @@
 		intptr = &options->hash_known_hosts;
 		goto parse_flag;
 
+	case oTunnel:
+		intptr = &options->tun_open;
+		goto parse_flag;
+
+	case oTunnelDevice:
+		arg = strdelim(&s);
+		if (!arg || *arg == '\0')
+			fatal("%.200s line %d: Missing argument.", filename, linenum);
+		value = a2tun(arg, &value2);
+		if (value < -1)
+			fatal("%.200s line %d: Bad tun device.", filename, linenum);
+		if (*activep) {
+			options->tun_local = value;
+			options->tun_remote = value2;
+		}
+		break;
+
+	case oLocalCommand:
+		charptr = &options->local_command;
+		goto parse_command;
+
+	case oPermitLocalCommand:
+		intptr = &options->permit_local_command;
+		goto parse_flag;
+
 	case oDeprecated:
 		debug("%s line %d: Deprecated option \"%s\"",
 		    filename, linenum, keyword);
@@ -966,6 +1002,11 @@
 	options->control_path = NULL;
 	options->control_master = -1;
 	options->hash_known_hosts = -1;
+	options->tun_open = -1;
+	options->tun_local = -1;
+	options->tun_remote = -1;
+	options->local_command = NULL;
+	options->permit_local_command = -1;
 }
 
 /*
@@ -1090,6 +1131,11 @@
 		options->control_master = 0;
 	if (options->hash_known_hosts == -1)
 		options->hash_known_hosts = 0;
+	if (options->tun_open == -1)
+		options->tun_open = 0;
+	if (options->permit_local_command == -1)
+		options->permit_local_command = 0;
+	/* options->local_command should not be set by default */
 	/* options->proxy_command should not be set by default */
 	/* options->user will be set in the main program if appropriate */
 	/* options->hostname will be set in the main program if appropriate */
diff --git a/readconf.h b/readconf.h
index 2b9deb9..4565b2c 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: readconf.h,v 1.67 2005/06/08 11:25:09 djm Exp $	*/
+/*	$OpenBSD: readconf.h,v 1.68 2005/12/06 22:38:27 reyk Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -114,6 +114,14 @@
 	int	control_master;
 
 	int	hash_known_hosts;
+
+	int	tun_open;	/* tun(4) */
+	int     tun_local;	/* force tun device (optional) */
+	int     tun_remote;	/* force tun device (optional) */
+
+	char	*local_command;
+	int	permit_local_command;
+
 }       Options;
 
 #define SSHCTL_MASTER_NO	0
diff --git a/scp.c b/scp.c
index a19021f..5dced6c 100644
--- a/scp.c
+++ b/scp.c
@@ -71,7 +71,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: scp.c,v 1.127 2005/11/12 18:38:15 deraadt Exp $");
+RCSID("$OpenBSD: scp.c,v 1.128 2005/12/06 22:38:27 reyk Exp $");
 
 #include "xmalloc.h"
 #include "atomicio.h"
@@ -231,6 +231,7 @@
 	addargs(&args, "ssh");		/* overwritten with ssh_program */
 	addargs(&args, "-x");
 	addargs(&args, "-oForwardAgent no");
+	addargs(&args, "-oPermitLocalCommand no");
 	addargs(&args, "-oClearAllForwardings yes");
 
 	fflag = tflag = 0;
diff --git a/servconf.c b/servconf.c
index 9e420a5..91a0ced 100644
--- a/servconf.c
+++ b/servconf.c
@@ -10,7 +10,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: servconf.c,v 1.144 2005/08/06 10:03:12 dtucker Exp $");
+RCSID("$OpenBSD: servconf.c,v 1.145 2005/12/06 22:38:27 reyk Exp $");
 
 #include "ssh.h"
 #include "log.h"
@@ -101,6 +101,7 @@
 	options->authorized_keys_file = NULL;
 	options->authorized_keys_file2 = NULL;
 	options->num_accept_env = 0;
+	options->permit_tun = -1;
 
 	/* Needs to be accessable in many places */
 	use_privsep = -1;
@@ -229,6 +230,8 @@
 	}
 	if (options->authorized_keys_file == NULL)
 		options->authorized_keys_file = _PATH_SSH_USER_PERMITTED_KEYS;
+	if (options->permit_tun == -1)
+		options->permit_tun = 0;
 
 	/* Turn privilege separation on by default */
 	if (use_privsep == -1)
@@ -270,7 +273,7 @@
 	sBanner, sUseDNS, sHostbasedAuthentication,
 	sHostbasedUsesNameFromPacketOnly, sClientAliveInterval,
 	sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2,
-	sGssAuthentication, sGssCleanupCreds, sAcceptEnv,
+	sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel,
 	sUsePrivilegeSeparation,
 	sDeprecated, sUnsupported
 } ServerOpCodes;
@@ -373,6 +376,7 @@
 	{ "authorizedkeysfile2", sAuthorizedKeysFile2 },
 	{ "useprivilegeseparation", sUsePrivilegeSeparation},
 	{ "acceptenv", sAcceptEnv },
+	{ "permittunnel", sPermitTunnel },
 	{ NULL, sBadOption }
 };
 
@@ -962,6 +966,10 @@
 		}
 		break;
 
+	case sPermitTunnel:
+		intptr = &options->permit_tun;
+		goto parse_flag;
+
 	case sDeprecated:
 		logit("%s line %d: Deprecated option %s",
 		    filename, linenum, arg);
diff --git a/servconf.h b/servconf.h
index f7e56d5..ab82c8f 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: servconf.h,v 1.71 2004/12/23 23:11:00 djm Exp $	*/
+/*	$OpenBSD: servconf.h,v 1.72 2005/12/06 22:38:27 reyk Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -133,7 +133,10 @@
 
 	char   *authorized_keys_file;	/* File containing public keys */
 	char   *authorized_keys_file2;
+
 	int	use_pam;		/* Enable auth via PAM */
+
+	int	permit_tun;
 }       ServerOptions;
 
 void	 initialize_server_options(ServerOptions *);
diff --git a/serverloop.c b/serverloop.c
index 03376ba..199f769 100644
--- a/serverloop.c
+++ b/serverloop.c
@@ -35,7 +35,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: serverloop.c,v 1.121 2005/10/31 11:48:29 djm Exp $");
+RCSID("$OpenBSD: serverloop.c,v 1.122 2005/12/06 22:38:27 reyk Exp $");
 
 #include "xmalloc.h"
 #include "packet.h"
@@ -914,6 +914,36 @@
 }
 
 static Channel *
+server_request_tun(void)
+{
+	Channel *c = NULL;
+	int sock, tun;
+
+	if (!options.permit_tun) {
+		packet_send_debug("Server has disabled tunnel device forwarding.");
+		return NULL;
+	}
+
+	tun = packet_get_int();
+	if (forced_tun_device != -1) {
+	 	if (tun != -1 && forced_tun_device != tun)
+			goto done;
+		tun = forced_tun_device;
+	}
+	sock = tun_open(tun);
+	if (sock < 0)
+		goto done;
+	c = channel_new("tun", SSH_CHANNEL_OPEN, sock, sock, -1,
+	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1);
+	c->datagram = 1;
+
+ done:
+	if (c == NULL)
+		packet_send_debug("Failed to open the tunnel device.");
+	return c;
+}
+
+static Channel *
 server_request_session(void)
 {
 	Channel *c;
@@ -958,6 +988,8 @@
 		c = server_request_session();
 	} else if (strcmp(ctype, "direct-tcpip") == 0) {
 		c = server_request_direct_tcpip();
+	} else if (strcmp(ctype, "tun@openssh.com") == 0) {
+		c = server_request_tun();
 	}
 	if (c != NULL) {
 		debug("server_input_channel_open: confirm %s", ctype);
diff --git a/sftp.c b/sftp.c
index ff3223a..24f6dc5 100644
--- a/sftp.c
+++ b/sftp.c
@@ -16,7 +16,7 @@
 
 #include "includes.h"
 
-RCSID("$OpenBSD: sftp.c,v 1.68 2005/10/31 06:15:04 dtucker Exp $");
+RCSID("$OpenBSD: sftp.c,v 1.69 2005/12/06 22:38:27 reyk Exp $");
 
 #ifdef USE_LIBEDIT
 #include <histedit.h>
@@ -1457,6 +1457,7 @@
 	addargs(&args, "ssh");		/* overwritten with ssh_program */
 	addargs(&args, "-oForwardX11 no");
 	addargs(&args, "-oForwardAgent no");
+	addargs(&args, "-oPermitLocalCommand no");
 	addargs(&args, "-oClearAllForwardings yes");
 
 	ll = SYSLOG_LEVEL_INFO;
diff --git a/ssh.1 b/ssh.1
index dd97a89..8a55c2f 100644
--- a/ssh.1
+++ b/ssh.1
@@ -34,7 +34,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh.1,v 1.214 2005/11/30 11:45:20 jmc Exp $
+.\" $OpenBSD: ssh.1,v 1.215 2005/12/06 22:38:27 reyk Exp $
 .Dd September 25, 1999
 .Dt SSH 1
 .Os
@@ -77,6 +77,7 @@
 .Sm on
 .Oc
 .Op Fl S Ar ctl_path
+.Op Fl w Ar tunnel : tunnel
 .Oo Ar user Ns @ Oc Ns Ar hostname
 .Op Ar command
 .Sh DESCRIPTION
@@ -301,6 +302,12 @@
 It also allows the cancellation of existing remote port-forwardings
 using
 .Fl KR Ar hostport .
+The 
+.Ic ! Ar command
+allows the user to execute a local command if the
+.Ic PermitLocalCommand
+option is enabled in
+.Xr ssh_config 5 .
 Basic help is available, using the
 .Fl h
 option.
@@ -747,12 +754,14 @@
 .It IdentityFile
 .It IdentitiesOnly
 .It KbdInteractiveDevices
+.It LocalCommand
 .It LocalForward
 .It LogLevel
 .It MACs
 .It NoHostAuthenticationForLocalhost
 .It NumberOfPasswordPrompts
 .It PasswordAuthentication
+.It PermitLocalCommand
 .It Port
 .It PreferredAuthentications
 .It Protocol
@@ -767,6 +776,8 @@
 .It SmartcardDevice
 .It StrictHostKeyChecking
 .It TCPKeepAlive
+.It Tunnel
+.It TunnelDevice
 .It UsePrivilegedPort
 .It User
 .It UserKnownHostsFile
@@ -866,6 +877,13 @@
 .Fl v
 options increase the verbosity.
 The maximum is 3.
+.It Fl w
+Requests a
+.Xr tun 4
+device on the client and server like the
+.Cm Tunnel
+directive in
+.Xr ssh_config 5 .
 .It Fl X
 Enables X11 forwarding.
 This can also be specified on a per-host basis in a configuration file.
diff --git a/ssh.c b/ssh.c
index 2227755..8a4a0e4 100644
--- a/ssh.c
+++ b/ssh.c
@@ -40,7 +40,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: ssh.c,v 1.254 2005/10/30 08:52:18 djm Exp $");
+RCSID("$OpenBSD: ssh.c,v 1.255 2005/12/06 22:38:27 reyk Exp $");
 
 #include <openssl/evp.h>
 #include <openssl/err.h>
@@ -162,7 +162,7 @@
 "           [-i identity_file] [-L [bind_address:]port:host:hostport]\n"
 "           [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n"
 "           [-R [bind_address:]port:host:hostport] [-S ctl_path]\n"
-"           [user@]hostname [command]\n"
+"           [-w tunnel:tunnel] [user@]hostname [command]\n"
 	);
 	exit(1);
 }
@@ -244,7 +244,7 @@
 
 again:
 	while ((opt = getopt(ac, av,
-	    "1246ab:c:e:fgi:kl:m:no:p:qstvxACD:F:I:L:MNO:PR:S:TVXY")) != -1) {
+	    "1246ab:c:e:fgi:kl:m:no:p:qstvxACD:F:I:L:MNO:PR:S:TVw:XY")) != -1) {
 		switch (opt) {
 		case '1':
 			options.protocol = SSH_PROTO_1;
@@ -340,6 +340,14 @@
 			if (opt == 'V')
 				exit(0);
 			break;
+		case 'w':
+			options.tun_open = 1;
+			options.tun_local = a2tun(optarg, &options.tun_remote);
+			if (options.tun_local < -1) {
+				fprintf(stderr, "Bad tun device '%s'\n", optarg);
+				exit(1);
+			}
+			break;
 		case 'q':
 			options.log_level = SYSLOG_LEVEL_QUIET;
 			break;
@@ -1059,6 +1067,26 @@
 		packet_send();
 	}
 
+	if (options.tun_open) {
+		Channel *c;
+		int fd;
+
+		debug("Requesting tun.");
+		if ((fd = tun_open(options.tun_local)) >= 0) {
+			c = channel_new("tun", SSH_CHANNEL_OPENING, fd, fd, -1,
+			    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
+			    0, "tun", 1);
+			c->datagram = 1;
+			packet_start(SSH2_MSG_CHANNEL_OPEN);
+			packet_put_cstring("tun@openssh.com");
+			packet_put_int(c->self);
+			packet_put_int(c->local_window_max);
+			packet_put_int(c->local_maxpacket);
+			packet_put_int(options.tun_remote);
+			packet_send();
+		}
+	}
+
 	client_session2_setup(id, tty_flag, subsystem_flag, getenv("TERM"),
 	    NULL, fileno(stdin), &command, environ, &ssh_subsystem_reply);
 
@@ -1123,6 +1151,11 @@
 	if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN))
 		id = ssh_session2_open();
 
+	/* Execute a local command */
+	if (options.local_command != NULL &&
+	    options.permit_local_command)
+		ssh_local_cmd(options.local_command);
+
 	/* If requested, let ssh continue in the background. */
 	if (fork_after_authentication_flag)
 		if (daemon(1, 1) < 0)
diff --git a/ssh_config b/ssh_config
index f41bee0..7bc8762 100644
--- a/ssh_config
+++ b/ssh_config
@@ -1,4 +1,4 @@
-#	$OpenBSD: ssh_config,v 1.20 2005/01/28 09:45:53 dtucker Exp $
+#	$OpenBSD: ssh_config,v 1.21 2005/12/06 22:38:27 reyk Exp $
 
 # This is the ssh client system-wide configuration file.  See
 # ssh_config(5) for more information.  This file provides defaults for
@@ -37,3 +37,6 @@
 #   Cipher 3des
 #   Ciphers aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,arcfour,aes192-cbc,aes256-cbc
 #   EscapeChar ~
+#   Tunnel no
+#   TunnelDevice any:any
+#   PermitLocalCommand no
diff --git a/ssh_config.5 b/ssh_config.5
index 13cdee8..d1930ba 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -34,7 +34,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.64 2005/10/30 08:43:47 jmc Exp $
+.\" $OpenBSD: ssh_config.5,v 1.65 2005/12/06 22:38:27 reyk Exp $
 .Dd September 25, 1999
 .Dt SSH_CONFIG 5
 .Os
@@ -556,6 +556,14 @@
 Specifies the list of methods to use in keyboard-interactive authentication.
 Multiple method names must be comma-separated.
 The default is to use the server specified list.
+.It Cm LocalCommand
+Specifies a command to execute on the local machine after successfully
+connecting to the server.
+The command string extends to the end of the line, and is executed with
+.Pa /bin/sh .
+This directive is ignored unless
+.Cm PermitLocalCommand
+has been enabled.
 .It Cm LocalForward
 Specifies that a TCP/IP port on the local machine be forwarded over
 the secure channel to the specified host and port from the remote machine.
@@ -628,6 +636,19 @@
 .It Cm Port
 Specifies the port number to connect on the remote host.
 Default is 22.
+.It Cm PermitLocalCommand
+Allow local command execution via the
+.Ic LocalCommand
+option or using the
+.Ic ! Ar command
+escape sequence in
+.Xr ssh 1 .
+The argument must be
+.Dq yes
+or
+.Dq no .
+The default is
+.Dq no .
 .It Cm PreferredAuthentications
 Specifies the order in which the client should try protocol 2
 authentication methods.
@@ -887,6 +908,21 @@
 for
 .Cm RhostsRSAAuthentication
 with older servers.
+.It Cm Tunnel
+Request starting
+.Xr tun 4
+device forwarding between the client and the server.
+The argument must be
+.Dq yes
+or
+.Dq no .
+The default is
+.Dq no .
+.It Cm TunnelDevice
+Force a specified
+.Xr tun 4
+device on the client.
+Without this option, the next available device will be used.
 .It Cm User
 Specifies the user to log in as.
 This can be useful when a different user name is used on different machines.
diff --git a/sshconnect.c b/sshconnect.c
index 2245a8a..64ffec2 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -13,7 +13,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: sshconnect.c,v 1.170 2005/10/30 08:52:18 djm Exp $");
+RCSID("$OpenBSD: sshconnect.c,v 1.171 2005/12/06 22:38:27 reyk Exp $");
 
 #include <openssl/bn.h>
 
@@ -1034,3 +1034,39 @@
 
 	xfree(fp);
 }
+
+/*
+ * Execute a local command
+ */
+int
+ssh_local_cmd(const char *args)
+{
+	char *shell;
+	pid_t pid;
+	int status;
+
+	if (!options.permit_local_command ||
+	    args == NULL || !*args)
+		return (1);
+
+	if ((shell = getenv("SHELL")) == NULL)
+		shell = _PATH_BSHELL;
+
+	pid = fork();
+	if (pid == 0) {
+		debug3("Executing %s -c \"%s\"", shell, args);
+		execl(shell, shell, "-c", args, (char *)NULL);
+		error("Couldn't execute %s -c \"%s\": %s",
+		    shell, args, strerror(errno));
+		_exit(1);
+	} else if (pid == -1)
+		fatal("fork failed: %.100s", strerror(errno));
+	while (waitpid(pid, &status, 0) == -1)
+		if (errno != EINTR)
+			fatal("Couldn't wait for child: %s", strerror(errno));
+
+	if (!WIFEXITED(status))
+		return (1);
+
+	return (WEXITSTATUS(status));
+}
diff --git a/sshconnect.h b/sshconnect.h
index 0be30fe..e7c7a2b 100644
--- a/sshconnect.h
+++ b/sshconnect.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: sshconnect.h,v 1.17 2002/06/19 00:27:55 deraadt Exp $	*/
+/*	$OpenBSD: sshconnect.h,v 1.18 2005/12/06 22:38:28 reyk Exp $	*/
 
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
@@ -49,7 +49,7 @@
 void	 ssh_userauth2(const char *, const char *, char *, Sensitive *);
 
 void	 ssh_put_password(char *);
-
+int	 ssh_local_cmd(const char *);
 
 /*
  * Macros to raise/lower permissions.
diff --git a/sshd.8 b/sshd.8
index c610f47..53eddcd 100644
--- a/sshd.8
+++ b/sshd.8
@@ -34,7 +34,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: sshd.8,v 1.208 2005/06/08 03:50:00 djm Exp $
+.\" $OpenBSD: sshd.8,v 1.209 2005/12/06 22:38:28 reyk Exp $
 .Dd September 25, 1999
 .Dt SSHD 8
 .Os
@@ -518,6 +518,12 @@
 options may be applied separated by commas.
 No pattern matching is performed on the specified hostnames,
 they must be literal domains or addresses.
+.It Cm tunnel="n"
+Force a
+.Xr tun 4
+device on the server.
+Without this option, the next available device will be used if
+the client requests a tunnel.
 .El
 .Ss Examples
 1024 33 12121...312314325 ylo@foo.bar
@@ -527,6 +533,8 @@
 command="dump /home",no-pty,no-port-forwarding 1024 33 23...2323 backup.hut.fi
 .Pp
 permitopen="10.2.1.55:80",permitopen="10.2.1.56:25" 1024 33 23...2323
+.Pp
+tunnel="0",command="sh /etc/netstart tun0" ssh-rsa AAAA...== reyk@openbsd.org
 .Sh SSH_KNOWN_HOSTS FILE FORMAT
 The
 .Pa /etc/ssh/ssh_known_hosts
diff --git a/sshd_config b/sshd_config
index 1440c05..4957dd1 100644
--- a/sshd_config
+++ b/sshd_config
@@ -1,4 +1,4 @@
-#	$OpenBSD: sshd_config,v 1.72 2005/07/25 11:59:40 markus Exp $
+#	$OpenBSD: sshd_config,v 1.73 2005/12/06 22:38:28 reyk Exp $
 
 # This is the sshd server system-wide configuration file.  See
 # sshd_config(5) for more information.
@@ -96,6 +96,7 @@
 #UseDNS yes
 #PidFile /var/run/sshd.pid
 #MaxStartups 10
+#PermitTunnel no
 
 # no default banner path
 #Banner /some/path
diff --git a/sshd_config.5 b/sshd_config.5
index 45c1c01..3835fcd 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -34,7 +34,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: sshd_config.5,v 1.45 2005/09/21 23:36:54 djm Exp $
+.\" $OpenBSD: sshd_config.5,v 1.46 2005/12/06 22:38:28 reyk Exp $
 .Dd September 25, 1999
 .Dt SSHD_CONFIG 5
 .Os
@@ -502,6 +502,12 @@
 If this option is set to
 .Dq no
 root is not allowed to log in.
+.It Cm PermitTunnel
+Specifies whether
+.Xr tun 4
+device forwarding is allowed.
+The default is
+.Dq no .
 .It Cm PermitUserEnvironment
 Specifies whether
 .Pa ~/.ssh/environment