- millert@cvs.openbsd.org 2014/07/15 15:54:14
     [PROTOCOL auth-options.c auth-passwd.c auth-rh-rsa.c auth-rhosts.c]
     [auth-rsa.c auth.c auth1.c auth2-hostbased.c auth2-kbdint.c auth2-none.c]
     [auth2-passwd.c auth2-pubkey.c auth2.c canohost.c channels.c channels.h]
     [clientloop.c misc.c misc.h monitor.c mux.c packet.c readconf.c]
     [readconf.h servconf.c servconf.h serverloop.c session.c ssh-agent.c]
     [ssh.c ssh_config.5 sshconnect.c sshconnect1.c sshconnect2.c sshd.c]
     [sshd_config.5 sshlogin.c]
     Add support for Unix domain socket forwarding.  A remote TCP port
     may be forwarded to a local Unix domain socket and vice versa or
     both ends may be a Unix domain socket.  This is a reimplementation
     of the streamlocal patches by William Ahern from:
         http://www.25thandclement.com/~william/projects/streamlocal.html
     OK djm@ markus@
diff --git a/readconf.c b/readconf.c
index a4ecf7a..7948ce1 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.219 2014/04/23 12:42:34 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.220 2014/07/15 15:54:14 millert Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -18,6 +18,7 @@
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
+#include <sys/un.h>
 
 #include <netinet/in.h>
 #include <netinet/in_systm.h>
@@ -48,9 +49,9 @@
 #include "pathnames.h"
 #include "log.h"
 #include "key.h"
+#include "misc.h"
 #include "readconf.h"
 #include "match.h"
-#include "misc.h"
 #include "buffer.h"
 #include "kex.h"
 #include "mac.h"
@@ -149,6 +150,7 @@
 	oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass,
 	oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
 	oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
+	oStreamLocalBindMask, oStreamLocalBindUnlink,
 	oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
@@ -261,6 +263,8 @@
 	{ "canonicalizehostname", oCanonicalizeHostname },
 	{ "canonicalizemaxdots", oCanonicalizeMaxDots },
 	{ "canonicalizepermittedcnames", oCanonicalizePermittedCNAMEs },
+	{ "streamlocalbindmask", oStreamLocalBindMask },
+	{ "streamlocalbindunlink", oStreamLocalBindUnlink },
 	{ "ignoreunknown", oIgnoreUnknown },
 
 	{ NULL, oBadOption }
@@ -272,12 +276,13 @@
  */
 
 void
-add_local_forward(Options *options, const Forward *newfwd)
+add_local_forward(Options *options, const struct Forward *newfwd)
 {
-	Forward *fwd;
+	struct Forward *fwd;
 #ifndef NO_IPPORT_RESERVED_CONCEPT
 	extern uid_t original_real_uid;
-	if (newfwd->listen_port < IPPORT_RESERVED && original_real_uid != 0)
+	if (newfwd->listen_port < IPPORT_RESERVED && original_real_uid != 0 &&
+	    newfwd->listen_path == NULL)
 		fatal("Privileged ports can only be forwarded by root.");
 #endif
 	options->local_forwards = xrealloc(options->local_forwards,
@@ -287,8 +292,10 @@
 
 	fwd->listen_host = newfwd->listen_host;
 	fwd->listen_port = newfwd->listen_port;
+	fwd->listen_path = newfwd->listen_path;
 	fwd->connect_host = newfwd->connect_host;
 	fwd->connect_port = newfwd->connect_port;
+	fwd->connect_path = newfwd->connect_path;
 }
 
 /*
@@ -297,9 +304,9 @@
  */
 
 void
-add_remote_forward(Options *options, const Forward *newfwd)
+add_remote_forward(Options *options, const struct Forward *newfwd)
 {
-	Forward *fwd;
+	struct Forward *fwd;
 
 	options->remote_forwards = xrealloc(options->remote_forwards,
 	    options->num_remote_forwards + 1,
@@ -308,8 +315,10 @@
 
 	fwd->listen_host = newfwd->listen_host;
 	fwd->listen_port = newfwd->listen_port;
+	fwd->listen_path = newfwd->listen_path;
 	fwd->connect_host = newfwd->connect_host;
 	fwd->connect_port = newfwd->connect_port;
+	fwd->connect_path = newfwd->connect_path;
 	fwd->handle = newfwd->handle;
 	fwd->allocated_port = 0;
 }
@@ -321,7 +330,9 @@
 
 	for (i = 0; i < options->num_local_forwards; i++) {
 		free(options->local_forwards[i].listen_host);
+		free(options->local_forwards[i].listen_path);
 		free(options->local_forwards[i].connect_host);
+		free(options->local_forwards[i].connect_path);
 	}
 	if (options->num_local_forwards > 0) {
 		free(options->local_forwards);
@@ -330,7 +341,9 @@
 	options->num_local_forwards = 0;
 	for (i = 0; i < options->num_remote_forwards; i++) {
 		free(options->remote_forwards[i].listen_host);
+		free(options->remote_forwards[i].listen_path);
 		free(options->remote_forwards[i].connect_host);
+		free(options->remote_forwards[i].connect_path);
 	}
 	if (options->num_remote_forwards > 0) {
 		free(options->remote_forwards);
@@ -715,7 +728,7 @@
 	LogLevel *log_level_ptr;
 	long long val64;
 	size_t len;
-	Forward fwd;
+	struct Forward fwd;
 	const struct multistate *multistate_ptr;
 	struct allowed_cname *cname;
 
@@ -805,7 +818,7 @@
 		goto parse_time;
 
 	case oGatewayPorts:
-		intptr = &options->gateway_ports;
+		intptr = &options->fwd_opts.gateway_ports;
 		goto parse_flag;
 
 	case oExitOnForwardFailure:
@@ -1405,6 +1418,21 @@
 		intptr = &options->canonicalize_fallback_local;
 		goto parse_flag;
 
+	case oStreamLocalBindMask:
+		arg = strdelim(&s);
+		if (!arg || *arg == '\0')
+			fatal("%.200s line %d: Missing StreamLocalBindMask argument.", filename, linenum);
+		/* Parse mode in octal format */
+		value = strtol(arg, &endofnumber, 8);
+		if (arg == endofnumber || value < 0 || value > 0777)
+			fatal("%.200s line %d: Bad mask.", filename, linenum);
+		options->fwd_opts.streamlocal_bind_mask = (mode_t)value;
+		break;
+
+	case oStreamLocalBindUnlink:
+		intptr = &options->fwd_opts.streamlocal_bind_unlink;
+		goto parse_flag;
+
 	case oDeprecated:
 		debug("%s line %d: Deprecated option \"%s\"",
 		    filename, linenum, keyword);
@@ -1502,7 +1530,9 @@
 	options->forward_x11_timeout = -1;
 	options->exit_on_forward_failure = -1;
 	options->xauth_location = NULL;
-	options->gateway_ports = -1;
+	options->fwd_opts.gateway_ports = -1;
+	options->fwd_opts.streamlocal_bind_mask = (mode_t)-1;
+	options->fwd_opts.streamlocal_bind_unlink = -1;
 	options->use_privileged_port = -1;
 	options->rsa_authentication = -1;
 	options->pubkey_authentication = -1;
@@ -1615,8 +1645,12 @@
 		options->exit_on_forward_failure = 0;
 	if (options->xauth_location == NULL)
 		options->xauth_location = _PATH_XAUTH;
-	if (options->gateway_ports == -1)
-		options->gateway_ports = 0;
+	if (options->fwd_opts.gateway_ports == -1)
+		options->fwd_opts.gateway_ports = 0;
+	if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1)
+		options->fwd_opts.streamlocal_bind_mask = 0177;
+	if (options->fwd_opts.streamlocal_bind_unlink == -1)
+		options->fwd_opts.streamlocal_bind_unlink = 0;
 	if (options->use_privileged_port == -1)
 		options->use_privileged_port = 0;
 	if (options->rsa_authentication == -1)
@@ -1768,22 +1802,92 @@
 	/* options->preferred_authentications will be set in ssh */
 }
 
+struct fwdarg {
+	char *arg;
+	int ispath;
+};
+
+/*
+ * parse_fwd_field
+ * parses the next field in a port forwarding specification.
+ * sets fwd to the parsed field and advances p past the colon
+ * or sets it to NULL at end of string.
+ * returns 0 on success, else non-zero.
+ */
+static int
+parse_fwd_field(char **p, struct fwdarg *fwd)
+{
+	char *ep, *cp = *p;
+	int ispath = 0;
+
+	if (*cp == '\0') {
+		*p = NULL;
+		return -1;	/* end of string */
+	}
+
+	/*
+	 * A field escaped with square brackets is used literally.
+	 * XXX - allow ']' to be escaped via backslash?
+	 */
+	if (*cp == '[') {
+		/* find matching ']' */
+		for (ep = cp + 1; *ep != ']' && *ep != '\0'; ep++) {
+			if (*ep == '/')
+				ispath = 1;
+		}
+		/* no matching ']' or not at end of field. */
+		if (ep[0] != ']' || (ep[1] != ':' && ep[1] != '\0'))
+			return -1;
+		/* NUL terminate the field and advance p past the colon */
+		*ep++ = '\0';
+		if (*ep != '\0')
+			*ep++ = '\0';
+		fwd->arg = cp + 1;
+		fwd->ispath = ispath;
+		*p = ep;
+		return 0;
+	}
+
+	for (cp = *p; *cp != '\0'; cp++) {
+		switch (*cp) {
+		case '\\':
+			memmove(cp, cp + 1, strlen(cp + 1) + 1);
+			cp++;
+			break;
+		case '/':
+			ispath = 1;
+			break;
+		case ':':
+			*cp++ = '\0';
+			goto done;
+		}
+	}
+done:
+	fwd->arg = *p;
+	fwd->ispath = ispath;
+	*p = cp;
+	return 0;
+}
+
 /*
  * parse_forward
  * parses a string containing a port forwarding specification of the form:
  *   dynamicfwd == 0
- *	[listenhost:]listenport:connecthost:connectport
+ *	[listenhost:]listenport|listenpath:connecthost:connectport|connectpath
+ *	listenpath:connectpath
  *   dynamicfwd == 1
  *	[listenhost:]listenport
  * returns number of arguments parsed or zero on error
  */
 int
-parse_forward(Forward *fwd, const char *fwdspec, int dynamicfwd, int remotefwd)
+parse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remotefwd)
 {
+	struct fwdarg fwdargs[4];
+	char *p, *cp;
 	int i;
-	char *p, *cp, *fwdarg[4];
 
-	memset(fwd, '\0', sizeof(*fwd));
+	memset(fwd, 0, sizeof(*fwd));
+	memset(fwdargs, 0, sizeof(fwdargs));
 
 	cp = p = xstrdup(fwdspec);
 
@@ -1791,39 +1895,70 @@
 	while (isspace((u_char)*cp))
 		cp++;
 
-	for (i = 0; i < 4; ++i)
-		if ((fwdarg[i] = hpdelim(&cp)) == NULL)
+	for (i = 0; i < 4; ++i) {
+		if (parse_fwd_field(&cp, &fwdargs[i]) != 0)
 			break;
+	}
 
 	/* Check for trailing garbage */
-	if (cp != NULL)
+	if (cp != NULL && *cp != '\0') {
 		i = 0;	/* failure */
+	}
 
 	switch (i) {
 	case 1:
-		fwd->listen_host = NULL;
-		fwd->listen_port = a2port(fwdarg[0]);
+		if (fwdargs[0].ispath) {
+			fwd->listen_path = xstrdup(fwdargs[0].arg);
+			fwd->listen_port = PORT_STREAMLOCAL;
+		} else {
+			fwd->listen_host = NULL;
+			fwd->listen_port = a2port(fwdargs[0].arg);
+		}
 		fwd->connect_host = xstrdup("socks");
 		break;
 
 	case 2:
-		fwd->listen_host = xstrdup(cleanhostname(fwdarg[0]));
-		fwd->listen_port = a2port(fwdarg[1]);
-		fwd->connect_host = xstrdup("socks");
+		if (fwdargs[0].ispath && fwdargs[1].ispath) {
+			fwd->listen_path = xstrdup(fwdargs[0].arg);
+			fwd->listen_port = PORT_STREAMLOCAL;
+			fwd->connect_path = xstrdup(fwdargs[1].arg);
+			fwd->connect_port = PORT_STREAMLOCAL;
+		} else if (fwdargs[1].ispath) {
+			fwd->listen_host = NULL;
+			fwd->listen_port = a2port(fwdargs[0].arg);
+			fwd->connect_path = xstrdup(fwdargs[1].arg);
+			fwd->connect_port = PORT_STREAMLOCAL;
+		} else {
+			fwd->listen_host = xstrdup(fwdargs[0].arg);
+			fwd->listen_port = a2port(fwdargs[1].arg);
+			fwd->connect_host = xstrdup("socks");
+		}
 		break;
 
 	case 3:
-		fwd->listen_host = NULL;
-		fwd->listen_port = a2port(fwdarg[0]);
-		fwd->connect_host = xstrdup(cleanhostname(fwdarg[1]));
-		fwd->connect_port = a2port(fwdarg[2]);
+		if (fwdargs[0].ispath) {
+			fwd->listen_path = xstrdup(fwdargs[0].arg);
+			fwd->listen_port = PORT_STREAMLOCAL;
+			fwd->connect_host = xstrdup(fwdargs[1].arg);
+			fwd->connect_port = a2port(fwdargs[2].arg);
+		} else if (fwdargs[2].ispath) {
+			fwd->listen_host = xstrdup(fwdargs[0].arg);
+			fwd->listen_port = a2port(fwdargs[1].arg);
+			fwd->connect_path = xstrdup(fwdargs[2].arg);
+			fwd->connect_port = PORT_STREAMLOCAL;
+		} else {
+			fwd->listen_host = NULL;
+			fwd->listen_port = a2port(fwdargs[0].arg);
+			fwd->connect_host = xstrdup(fwdargs[1].arg);
+			fwd->connect_port = a2port(fwdargs[2].arg);
+		}
 		break;
 
 	case 4:
-		fwd->listen_host = xstrdup(cleanhostname(fwdarg[0]));
-		fwd->listen_port = a2port(fwdarg[1]);
-		fwd->connect_host = xstrdup(cleanhostname(fwdarg[2]));
-		fwd->connect_port = a2port(fwdarg[3]);
+		fwd->listen_host = xstrdup(fwdargs[0].arg);
+		fwd->listen_port = a2port(fwdargs[1].arg);
+		fwd->connect_host = xstrdup(fwdargs[2].arg);
+		fwd->connect_port = a2port(fwdargs[3].arg);
 		break;
 	default:
 		i = 0; /* failure */
@@ -1835,29 +1970,42 @@
 		if (!(i == 1 || i == 2))
 			goto fail_free;
 	} else {
-		if (!(i == 3 || i == 4))
-			goto fail_free;
-		if (fwd->connect_port <= 0)
+		if (!(i == 3 || i == 4)) {
+			if (fwd->connect_path == NULL &&
+			    fwd->listen_path == NULL)
+				goto fail_free;
+		}
+		if (fwd->connect_port <= 0 && fwd->connect_path == NULL)
 			goto fail_free;
 	}
 
-	if (fwd->listen_port < 0 || (!remotefwd && fwd->listen_port == 0))
+	if ((fwd->listen_port < 0 && fwd->listen_path == NULL) ||
+	    (!remotefwd && fwd->listen_port == 0))
 		goto fail_free;
-
 	if (fwd->connect_host != NULL &&
 	    strlen(fwd->connect_host) >= NI_MAXHOST)
 		goto fail_free;
+	/* XXX - if connecting to a remote socket, max sun len may not match this host */
+	if (fwd->connect_path != NULL &&
+	    strlen(fwd->connect_path) >= PATH_MAX_SUN)
+		goto fail_free;
 	if (fwd->listen_host != NULL &&
 	    strlen(fwd->listen_host) >= NI_MAXHOST)
 		goto fail_free;
-
+	if (fwd->listen_path != NULL &&
+	    strlen(fwd->listen_path) >= PATH_MAX_SUN)
+		goto fail_free;
 
 	return (i);
 
  fail_free:
 	free(fwd->connect_host);
 	fwd->connect_host = NULL;
+	free(fwd->connect_path);
+	fwd->connect_path = NULL;
 	free(fwd->listen_host);
 	fwd->listen_host = NULL;
+	free(fwd->listen_path);
+	fwd->listen_path = NULL;
 	return (0);
 }