- 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/channels.c b/channels.c
index dcd7534..d67fdf4 100644
--- a/channels.c
+++ b/channels.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.335 2014/07/05 23:11:48 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.336 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
@@ -42,6 +42,7 @@
 #include "includes.h"
 
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/un.h>
 #include <sys/socket.h>
@@ -107,11 +108,15 @@
  * a corrupt remote server from accessing arbitrary TCP/IP ports on our local
  * network (which might be behind a firewall).
  */
+/* XXX: streamlocal wants a path instead of host:port */
+/*      Overload host_to_connect; we could just make this match Forward */
+/*	XXX - can we use listen_host instead of listen_path? */
 typedef struct {
 	char *host_to_connect;		/* Connect to 'host'. */
-	u_short port_to_connect;	/* Connect to 'port'. */
+	int port_to_connect;		/* Connect to 'port'. */
 	char *listen_host;		/* Remote side should listen address. */
-	u_short listen_port;		/* Remote side should listen port. */
+	char *listen_path;		/* Remote side should listen path. */
+	int listen_port;		/* Remote side should listen port. */
 } ForwardPermission;
 
 /* List of all permitted host/port pairs to connect by the user. */
@@ -474,6 +479,8 @@
 			case SSH_CHANNEL_PORT_LISTENER:
 			case SSH_CHANNEL_RPORT_LISTENER:
 			case SSH_CHANNEL_X11_LISTENER:
+			case SSH_CHANNEL_UNIX_LISTENER:
+			case SSH_CHANNEL_RUNIX_LISTENER:
 				channel_close_fd(&c->sock);
 				channel_free(c);
 				break;
@@ -536,6 +543,8 @@
 		case SSH_CHANNEL_CONNECTING:
 		case SSH_CHANNEL_ZOMBIE:
 		case SSH_CHANNEL_ABANDONED:
+		case SSH_CHANNEL_UNIX_LISTENER:
+		case SSH_CHANNEL_RUNIX_LISTENER:
 			continue;
 		case SSH_CHANNEL_LARVAL:
 			if (!compat20)
@@ -582,6 +591,8 @@
 		case SSH_CHANNEL_CONNECTING:
 		case SSH_CHANNEL_ZOMBIE:
 		case SSH_CHANNEL_ABANDONED:
+		case SSH_CHANNEL_UNIX_LISTENER:
+		case SSH_CHANNEL_RUNIX_LISTENER:
 			continue;
 		case SSH_CHANNEL_LARVAL:
 		case SSH_CHANNEL_AUTH_SOCKET:
@@ -632,6 +643,8 @@
 		case SSH_CHANNEL_ABANDONED:
 		case SSH_CHANNEL_MUX_CLIENT:
 		case SSH_CHANNEL_MUX_LISTENER:
+		case SSH_CHANNEL_UNIX_LISTENER:
+		case SSH_CHANNEL_RUNIX_LISTENER:
 			continue;
 		case SSH_CHANNEL_LARVAL:
 		case SSH_CHANNEL_OPENING:
@@ -1387,7 +1400,6 @@
 static void
 port_open_helper(Channel *c, char *rtype)
 {
-	int direct;
 	char buf[1024];
 	char *local_ipaddr = get_local_ipaddr(c->sock);
 	int local_port = c->sock == -1 ? 65536 : get_sock_port(c->sock, 1);
@@ -1401,8 +1413,6 @@
 		remote_port = 65535;
 	}
 
-	direct = (strcmp(rtype, "direct-tcpip") == 0);
-
 	snprintf(buf, sizeof buf,
 	    "%s: listening port %d for %.100s port %d, "
 	    "connect from %.200s port %d to %.100s port %d",
@@ -1418,18 +1428,29 @@
 		packet_put_int(c->self);
 		packet_put_int(c->local_window_max);
 		packet_put_int(c->local_maxpacket);
-		if (direct) {
+		if (strcmp(rtype, "direct-tcpip") == 0) {
 			/* target host, port */
 			packet_put_cstring(c->path);
 			packet_put_int(c->host_port);
+		} else if (strcmp(rtype, "direct-streamlocal@openssh.com") == 0) {
+			/* target path */
+			packet_put_cstring(c->path);
+		} else if (strcmp(rtype, "forwarded-streamlocal@openssh.com") == 0) {
+			/* listen path */
+			packet_put_cstring(c->path);
 		} else {
 			/* listen address, port */
 			packet_put_cstring(c->path);
 			packet_put_int(local_port);
 		}
-		/* originator host and port */
-		packet_put_cstring(remote_ipaddr);
-		packet_put_int((u_int)remote_port);
+		if (strcmp(rtype, "forwarded-streamlocal@openssh.com") == 0) {
+			/* reserved for future owner/mode info */
+			packet_put_cstring("");
+		} else {
+			/* originator host and port */
+			packet_put_cstring(remote_ipaddr);
+			packet_put_int((u_int)remote_port);
+		}
 		packet_send();
 	} else {
 		packet_start(SSH_MSG_PORT_OPEN);
@@ -1479,14 +1500,18 @@
 		if (c->type == SSH_CHANNEL_RPORT_LISTENER) {
 			nextstate = SSH_CHANNEL_OPENING;
 			rtype = "forwarded-tcpip";
+		} else if (c->type == SSH_CHANNEL_RUNIX_LISTENER) {
+			nextstate = SSH_CHANNEL_OPENING;
+			rtype = "forwarded-streamlocal@openssh.com";
+		} else if (c->host_port == PORT_STREAMLOCAL) {
+			nextstate = SSH_CHANNEL_OPENING;
+			rtype = "direct-streamlocal@openssh.com";
+		} else if (c->host_port == 0) {
+			nextstate = SSH_CHANNEL_DYNAMIC;
+			rtype = "dynamic-tcpip";
 		} else {
-			if (c->host_port == 0) {
-				nextstate = SSH_CHANNEL_DYNAMIC;
-				rtype = "dynamic-tcpip";
-			} else {
-				nextstate = SSH_CHANNEL_OPENING;
-				rtype = "direct-tcpip";
-			}
+			nextstate = SSH_CHANNEL_OPENING;
+			rtype = "direct-tcpip";
 		}
 
 		addrlen = sizeof(addr);
@@ -1499,7 +1524,8 @@
 				c->notbefore = monotime() + 1;
 			return;
 		}
-		set_nodelay(newsock);
+		if (c->host_port != PORT_STREAMLOCAL)
+			set_nodelay(newsock);
 		nc = channel_new(rtype, nextstate, newsock, newsock, -1,
 		    c->local_window_max, c->local_maxpacket, 0, rtype, 1);
 		nc->listening_port = c->listening_port;
@@ -1988,6 +2014,8 @@
 	channel_pre[SSH_CHANNEL_X11_OPEN] =		&channel_pre_x11_open;
 	channel_pre[SSH_CHANNEL_PORT_LISTENER] =	&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_RPORT_LISTENER] =	&channel_pre_listener;
+	channel_pre[SSH_CHANNEL_UNIX_LISTENER] =	&channel_pre_listener;
+	channel_pre[SSH_CHANNEL_RUNIX_LISTENER] =	&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_X11_LISTENER] =		&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_AUTH_SOCKET] =		&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_CONNECTING] =		&channel_pre_connecting;
@@ -1998,6 +2026,8 @@
 	channel_post[SSH_CHANNEL_OPEN] =		&channel_post_open;
 	channel_post[SSH_CHANNEL_PORT_LISTENER] =	&channel_post_port_listener;
 	channel_post[SSH_CHANNEL_RPORT_LISTENER] =	&channel_post_port_listener;
+	channel_post[SSH_CHANNEL_UNIX_LISTENER] =	&channel_post_port_listener;
+	channel_post[SSH_CHANNEL_RUNIX_LISTENER] =	&channel_post_port_listener;
 	channel_post[SSH_CHANNEL_X11_LISTENER] =	&channel_post_x11_listener;
 	channel_post[SSH_CHANNEL_AUTH_SOCKET] =		&channel_post_auth_listener;
 	channel_post[SSH_CHANNEL_CONNECTING] =		&channel_post_connecting;
@@ -2638,7 +2668,7 @@
 		originator_string = xstrdup("unknown (remote did not supply name)");
 	}
 	packet_check_eom();
-	c = channel_connect_to(host, host_port,
+	c = channel_connect_to_port(host, host_port,
 	    "connected socket", originator_string);
 	free(originator_string);
 	free(host);
@@ -2705,20 +2735,20 @@
  */
 static const char *
 channel_fwd_bind_addr(const char *listen_addr, int *wildcardp,
-    int is_client, int gateway_ports)
+    int is_client, struct ForwardOptions *fwd_opts)
 {
 	const char *addr = NULL;
 	int wildcard = 0;
 
 	if (listen_addr == NULL) {
 		/* No address specified: default to gateway_ports setting */
-		if (gateway_ports)
+		if (fwd_opts->gateway_ports)
 			wildcard = 1;
-	} else if (gateway_ports || is_client) {
+	} else if (fwd_opts->gateway_ports || is_client) {
 		if (((datafellows & SSH_OLD_FORWARD_ADDR) &&
 		    strcmp(listen_addr, "0.0.0.0") == 0 && is_client == 0) ||
 		    *listen_addr == '\0' || strcmp(listen_addr, "*") == 0 ||
-		    (!is_client && gateway_ports == 1)) {
+		    (!is_client && fwd_opts->gateway_ports == 1)) {
 			wildcard = 1;
 			/*
 			 * Notify client if they requested a specific listen
@@ -2752,9 +2782,8 @@
 }
 
 static int
-channel_setup_fwd_listener(int type, const char *listen_addr,
-    u_short listen_port, int *allocated_listen_port,
-    const char *host_to_connect, u_short port_to_connect, int gateway_ports)
+channel_setup_fwd_listener_tcpip(int type, struct Forward *fwd,
+    int *allocated_listen_port, struct ForwardOptions *fwd_opts)
 {
 	Channel *c;
 	int sock, r, success = 0, wildcard = 0, is_client;
@@ -2764,7 +2793,7 @@
 	in_port_t *lport_p;
 
 	host = (type == SSH_CHANNEL_RPORT_LISTENER) ?
-	    listen_addr : host_to_connect;
+	    fwd->listen_host : fwd->connect_host;
 	is_client = (type == SSH_CHANNEL_PORT_LISTENER);
 
 	if (host == NULL) {
@@ -2777,9 +2806,9 @@
 	}
 
 	/* Determine the bind address, cf. channel_fwd_bind_addr() comment */
-	addr = channel_fwd_bind_addr(listen_addr, &wildcard,
-	    is_client, gateway_ports);
-	debug3("channel_setup_fwd_listener: type %d wildcard %d addr %s",
+	addr = channel_fwd_bind_addr(fwd->listen_host, &wildcard,
+	    is_client, fwd_opts);
+	debug3("%s: type %d wildcard %d addr %s", __func__,
 	    type, wildcard, (addr == NULL) ? "NULL" : addr);
 
 	/*
@@ -2790,15 +2819,14 @@
 	hints.ai_family = IPv4or6;
 	hints.ai_flags = wildcard ? AI_PASSIVE : 0;
 	hints.ai_socktype = SOCK_STREAM;
-	snprintf(strport, sizeof strport, "%d", listen_port);
+	snprintf(strport, sizeof strport, "%d", fwd->listen_port);
 	if ((r = getaddrinfo(addr, strport, &hints, &aitop)) != 0) {
 		if (addr == NULL) {
 			/* This really shouldn't happen */
 			packet_disconnect("getaddrinfo: fatal error: %s",
 			    ssh_gai_strerror(r));
 		} else {
-			error("channel_setup_fwd_listener: "
-			    "getaddrinfo(%.64s): %s", addr,
+			error("%s: getaddrinfo(%.64s): %s", __func__, addr,
 			    ssh_gai_strerror(r));
 		}
 		return 0;
@@ -2822,13 +2850,13 @@
 		 * If allocating a port for -R forwards, then use the
 		 * same port for all address families.
 		 */
-		if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 &&
+		if (type == SSH_CHANNEL_RPORT_LISTENER && fwd->listen_port == 0 &&
 		    allocated_listen_port != NULL && *allocated_listen_port > 0)
 			*lport_p = htons(*allocated_listen_port);
 
 		if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop),
 		    strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
-			error("channel_setup_fwd_listener: getnameinfo failed");
+			error("%s: getnameinfo failed", __func__);
 			continue;
 		}
 		/* Create a port to listen for the host. */
@@ -2865,10 +2893,10 @@
 		}
 
 		/*
-		 * listen_port == 0 requests a dynamically allocated port -
+		 * fwd->listen_port == 0 requests a dynamically allocated port -
 		 * record what we got.
 		 */
-		if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 &&
+		if (type == SSH_CHANNEL_RPORT_LISTENER && fwd->listen_port == 0 &&
 		    allocated_listen_port != NULL &&
 		    *allocated_listen_port == 0) {
 			*allocated_listen_port = get_sock_port(sock, 1);
@@ -2881,24 +2909,98 @@
 		    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
 		    0, "port listener", 1);
 		c->path = xstrdup(host);
-		c->host_port = port_to_connect;
+		c->host_port = fwd->connect_port;
 		c->listening_addr = addr == NULL ? NULL : xstrdup(addr);
-		if (listen_port == 0 && allocated_listen_port != NULL &&
+		if (fwd->listen_port == 0 && allocated_listen_port != NULL &&
 		    !(datafellows & SSH_BUG_DYNAMIC_RPORT))
 			c->listening_port = *allocated_listen_port;
 		else
-			c->listening_port = listen_port;
+			c->listening_port = fwd->listen_port;
 		success = 1;
 	}
 	if (success == 0)
-		error("channel_setup_fwd_listener: cannot listen to port: %d",
-		    listen_port);
+		error("%s: cannot listen to port: %d", __func__,
+		    fwd->listen_port);
 	freeaddrinfo(aitop);
 	return success;
 }
 
-int
-channel_cancel_rport_listener(const char *host, u_short port)
+static int
+channel_setup_fwd_listener_streamlocal(int type, struct Forward *fwd,
+    struct ForwardOptions *fwd_opts)
+{
+	struct sockaddr_un sunaddr;
+	const char *path;
+	Channel *c;
+	int port, sock;
+	mode_t omask;
+
+	switch (type) {
+	case SSH_CHANNEL_UNIX_LISTENER:
+		if (fwd->connect_path != NULL) {
+			if (strlen(fwd->connect_path) > sizeof(sunaddr.sun_path)) {
+				error("Local connecting path too long: %s",
+				    fwd->connect_path);
+				return 0;
+			}
+			path = fwd->connect_path;
+			port = PORT_STREAMLOCAL;
+		} else {
+			if (fwd->connect_host == NULL) {
+				error("No forward host name.");
+				return 0;
+			}
+			if (strlen(fwd->connect_host) >= NI_MAXHOST) {
+				error("Forward host name too long.");
+				return 0;
+			}
+			path = fwd->connect_host;
+			port = fwd->connect_port;
+		}
+		break;
+	case SSH_CHANNEL_RUNIX_LISTENER:
+		path = fwd->listen_path;
+		port = PORT_STREAMLOCAL;
+		break;
+	default:
+		error("%s: unexpected channel type %d", __func__, type);
+		return 0;
+	}
+
+	if (fwd->listen_path == NULL) {
+		error("No forward path name.");
+		return 0;
+	}
+	if (strlen(fwd->listen_path) > sizeof(sunaddr.sun_path)) {
+		error("Local listening path too long: %s", fwd->listen_path);
+		return 0;
+	}
+
+	debug3("%s: type %d path %s", __func__, type, fwd->listen_path);
+
+	/* Start a Unix domain listener. */
+	omask = umask(fwd_opts->streamlocal_bind_mask);
+	sock = unix_listener(fwd->listen_path, SSH_LISTEN_BACKLOG,
+	    fwd_opts->streamlocal_bind_unlink);
+	umask(omask);
+	if (sock < 0)
+		return 0;
+
+	debug("Local forwarding listening on path %s.", fwd->listen_path);
+
+	/* Allocate a channel number for the socket. */
+	c = channel_new("unix listener", type, sock, sock, -1,
+	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
+	    0, "unix listener", 1);
+	c->path = xstrdup(path);
+	c->host_port = port;
+	c->listening_port = PORT_STREAMLOCAL;
+	c->listening_addr = xstrdup(fwd->listen_path);
+	return 1;
+}
+
+static int
+channel_cancel_rport_listener_tcpip(const char *host, u_short port)
 {
 	u_int i;
 	int found = 0;
@@ -2917,13 +3019,44 @@
 	return (found);
 }
 
-int
-channel_cancel_lport_listener(const char *lhost, u_short lport,
-    int cport, int gateway_ports)
+static int
+channel_cancel_rport_listener_streamlocal(const char *path)
 {
 	u_int i;
 	int found = 0;
-	const char *addr = channel_fwd_bind_addr(lhost, NULL, 1, gateway_ports);
+
+	for (i = 0; i < channels_alloc; i++) {
+		Channel *c = channels[i];
+		if (c == NULL || c->type != SSH_CHANNEL_RUNIX_LISTENER)
+			continue;
+		if (c->path == NULL)
+			continue;
+		if (strcmp(c->path, path) == 0) {
+			debug2("%s: close channel %d", __func__, i);
+			channel_free(c);
+			found = 1;
+		}
+	}
+
+	return (found);
+}
+
+int
+channel_cancel_rport_listener(struct Forward *fwd)
+{
+	if (fwd->listen_path != NULL)
+		return channel_cancel_rport_listener_streamlocal(fwd->listen_path);
+	else
+		return channel_cancel_rport_listener_tcpip(fwd->listen_host, fwd->listen_port);
+}
+
+static int
+channel_cancel_lport_listener_tcpip(const char *lhost, u_short lport,
+    int cport, struct ForwardOptions *fwd_opts)
+{
+	u_int i;
+	int found = 0;
+	const char *addr = channel_fwd_bind_addr(lhost, NULL, 1, fwd_opts);
 
 	for (i = 0; i < channels_alloc; i++) {
 		Channel *c = channels[i];
@@ -2952,24 +3085,68 @@
 	return (found);
 }
 
+static int
+channel_cancel_lport_listener_streamlocal(const char *path)
+{
+	u_int i;
+	int found = 0;
+
+	if (path == NULL) {
+		error("%s: no path specified.", __func__);
+		return 0;
+	}
+
+	for (i = 0; i < channels_alloc; i++) {
+		Channel *c = channels[i];
+		if (c == NULL || c->type != SSH_CHANNEL_UNIX_LISTENER)
+			continue;
+		if (c->listening_addr == NULL)
+			continue;
+		if (strcmp(c->listening_addr, path) == 0) {
+			debug2("%s: close channel %d", __func__, i);
+			channel_free(c);
+			found = 1;
+		}
+	}
+
+	return (found);
+}
+
+int
+channel_cancel_lport_listener(struct Forward *fwd, int cport, struct ForwardOptions *fwd_opts)
+{
+	if (fwd->listen_path != NULL)
+		return channel_cancel_lport_listener_streamlocal(fwd->listen_path);
+	else
+		return channel_cancel_lport_listener_tcpip(fwd->listen_host, fwd->listen_port, cport, fwd_opts);
+}
+
 /* protocol local port fwd, used by ssh (and sshd in v1) */
 int
-channel_setup_local_fwd_listener(const char *listen_host, u_short listen_port,
-    const char *host_to_connect, u_short port_to_connect, int gateway_ports)
+channel_setup_local_fwd_listener(struct Forward *fwd, struct ForwardOptions *fwd_opts)
 {
-	return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER,
-	    listen_host, listen_port, NULL, host_to_connect, port_to_connect,
-	    gateway_ports);
+	if (fwd->listen_path != NULL) {
+		return channel_setup_fwd_listener_streamlocal(
+		    SSH_CHANNEL_UNIX_LISTENER, fwd, fwd_opts);
+	} else {
+		return channel_setup_fwd_listener_tcpip(SSH_CHANNEL_PORT_LISTENER,
+		    fwd, NULL, fwd_opts);
+	}
 }
 
 /* protocol v2 remote port fwd, used by sshd */
 int
-channel_setup_remote_fwd_listener(const char *listen_address,
-    u_short listen_port, int *allocated_listen_port, int gateway_ports)
+channel_setup_remote_fwd_listener(struct Forward *fwd,
+    int *allocated_listen_port, struct ForwardOptions *fwd_opts)
 {
-	return channel_setup_fwd_listener(SSH_CHANNEL_RPORT_LISTENER,
-	    listen_address, listen_port, allocated_listen_port,
-	    NULL, 0, gateway_ports);
+	if (fwd->listen_path != NULL) {
+		return channel_setup_fwd_listener_streamlocal(
+		    SSH_CHANNEL_RUNIX_LISTENER, fwd, fwd_opts);
+	} else {
+		return channel_setup_fwd_listener_tcpip(
+		    SSH_CHANNEL_RPORT_LISTENER, fwd, allocated_listen_port,
+		    fwd_opts);
+	}
 }
 
 /*
@@ -3000,27 +3177,32 @@
  * channel_update_permitted_opens().
  */
 int
-channel_request_remote_forwarding(const char *listen_host, u_short listen_port,
-    const char *host_to_connect, u_short port_to_connect)
+channel_request_remote_forwarding(struct Forward *fwd)
 {
 	int type, success = 0, idx = -1;
 
 	/* Send the forward request to the remote side. */
 	if (compat20) {
 		packet_start(SSH2_MSG_GLOBAL_REQUEST);
-		packet_put_cstring("tcpip-forward");
-		packet_put_char(1);		/* boolean: want reply */
-		packet_put_cstring(channel_rfwd_bind_host(listen_host));
-		packet_put_int(listen_port);
+		if (fwd->listen_path != NULL) {
+		    packet_put_cstring("streamlocal-forward@openssh.com");
+		    packet_put_char(1);		/* boolean: want reply */
+		    packet_put_cstring(fwd->listen_path);
+		} else {
+		    packet_put_cstring("tcpip-forward");
+		    packet_put_char(1);		/* boolean: want reply */
+		    packet_put_cstring(channel_rfwd_bind_host(fwd->listen_host));
+		    packet_put_int(fwd->listen_port);
+		}
 		packet_send();
 		packet_write_wait();
 		/* Assume that server accepts the request */
 		success = 1;
-	} else {
+	} else if (fwd->listen_path == NULL) {
 		packet_start(SSH_CMSG_PORT_FORWARD_REQUEST);
-		packet_put_int(listen_port);
-		packet_put_cstring(host_to_connect);
-		packet_put_int(port_to_connect);
+		packet_put_int(fwd->listen_port);
+		packet_put_cstring(fwd->connect_host);
+		packet_put_int(fwd->connect_port);
 		packet_send();
 		packet_write_wait();
 
@@ -3037,24 +3219,43 @@
 			packet_disconnect("Protocol error for port forward request:"
 			    "received packet type %d.", type);
 		}
+	} else {
+		logit("Warning: Server does not support remote stream local forwarding.");
 	}
 	if (success) {
 		/* Record that connection to this host/port is permitted. */
 		permitted_opens = xrealloc(permitted_opens,
 		    num_permitted_opens + 1, sizeof(*permitted_opens));
 		idx = num_permitted_opens++;
-		permitted_opens[idx].host_to_connect = xstrdup(host_to_connect);
-		permitted_opens[idx].port_to_connect = port_to_connect;
-		permitted_opens[idx].listen_host = listen_host ?
-		    xstrdup(listen_host) : NULL;
-		permitted_opens[idx].listen_port = listen_port;
+		if (fwd->connect_path != NULL) {
+			permitted_opens[idx].host_to_connect =
+			    xstrdup(fwd->connect_path);
+			permitted_opens[idx].port_to_connect =
+			    PORT_STREAMLOCAL;
+		} else {
+			permitted_opens[idx].host_to_connect =
+			    xstrdup(fwd->connect_host);
+			permitted_opens[idx].port_to_connect =
+			    fwd->connect_port;
+		}
+		if (fwd->listen_path != NULL) {
+			permitted_opens[idx].listen_host = NULL;
+			permitted_opens[idx].listen_path =
+			    xstrdup(fwd->listen_path);
+			permitted_opens[idx].listen_port = PORT_STREAMLOCAL;
+		} else {
+			permitted_opens[idx].listen_host =
+			    fwd->listen_host ? xstrdup(fwd->listen_host) : NULL;
+			permitted_opens[idx].listen_path = NULL;
+			permitted_opens[idx].listen_port = fwd->listen_port;
+		}
 	}
 	return (idx);
 }
 
 static int
 open_match(ForwardPermission *allowed_open, const char *requestedhost,
-    u_short requestedport)
+    int requestedport)
 {
 	if (allowed_open->host_to_connect == NULL)
 		return 0;
@@ -3067,14 +3268,14 @@
 }
 
 /*
- * Note that in he listen host/port case
+ * Note that in the listen host/port case
  * we don't support FWD_PERMIT_ANY_PORT and
  * need to translate between the configured-host (listen_host)
  * and what we've sent to the remote server (channel_rfwd_bind_host)
  */
 static int
-open_listen_match(ForwardPermission *allowed_open, const char *requestedhost,
-    u_short requestedport, int translate)
+open_listen_match_tcpip(ForwardPermission *allowed_open,
+    const char *requestedhost, u_short requestedport, int translate)
 {
 	const char *allowed_host;
 
@@ -3094,12 +3295,26 @@
 	return 1;
 }
 
+static int
+open_listen_match_streamlocal(ForwardPermission *allowed_open,
+    const char *requestedpath)
+{
+	if (allowed_open->host_to_connect == NULL)
+		return 0;
+	if (allowed_open->listen_port != PORT_STREAMLOCAL)
+		return 0;
+	if (allowed_open->listen_path == NULL ||
+	    strcmp(allowed_open->listen_path, requestedpath) != 0)
+		return 0;
+	return 1;
+}
+
 /*
  * Request cancellation of remote forwarding of connection host:port from
  * local side.
  */
-int
-channel_request_rforward_cancel(const char *host, u_short port)
+static int
+channel_request_rforward_cancel_tcpip(const char *host, u_short port)
 {
 	int i;
 
@@ -3107,7 +3322,7 @@
 		return -1;
 
 	for (i = 0; i < num_permitted_opens; i++) {
-		if (open_listen_match(&permitted_opens[i], host, port, 0))
+		if (open_listen_match_tcpip(&permitted_opens[i], host, port, 0))
 			break;
 	}
 	if (i >= num_permitted_opens) {
@@ -3121,52 +3336,104 @@
 	packet_put_int(port);
 	packet_send();
 
-	permitted_opens[i].port_to_connect = 0;
 	permitted_opens[i].listen_port = 0;
+	permitted_opens[i].port_to_connect = 0;
 	free(permitted_opens[i].host_to_connect);
 	permitted_opens[i].host_to_connect = NULL;
 	free(permitted_opens[i].listen_host);
 	permitted_opens[i].listen_host = NULL;
+	permitted_opens[i].listen_path = NULL;
 
 	return 0;
 }
 
 /*
+ * Request cancellation of remote forwarding of Unix domain socket
+ * path from local side.
+ */
+static int
+channel_request_rforward_cancel_streamlocal(const char *path)
+{
+	int i;
+
+	if (!compat20)
+		return -1;
+
+	for (i = 0; i < num_permitted_opens; i++) {
+		if (open_listen_match_streamlocal(&permitted_opens[i], path))
+			break;
+	}
+	if (i >= num_permitted_opens) {
+		debug("%s: requested forward not found", __func__);
+		return -1;
+	}
+	packet_start(SSH2_MSG_GLOBAL_REQUEST);
+	packet_put_cstring("cancel-streamlocal-forward@openssh.com");
+	packet_put_char(0);
+	packet_put_cstring(path);
+	packet_send();
+
+	permitted_opens[i].listen_port = 0;
+	permitted_opens[i].port_to_connect = 0;
+	free(permitted_opens[i].host_to_connect);
+	permitted_opens[i].host_to_connect = NULL;
+	permitted_opens[i].listen_host = NULL;
+	free(permitted_opens[i].listen_path);
+	permitted_opens[i].listen_path = NULL;
+
+	return 0;
+}
+ 
+/*
+ * Request cancellation of remote forwarding of a connection from local side.
+ */
+int
+channel_request_rforward_cancel(struct Forward *fwd)
+{
+	if (fwd->listen_path != NULL) {
+		return (channel_request_rforward_cancel_streamlocal(
+		    fwd->listen_path));
+	} else {
+		return (channel_request_rforward_cancel_tcpip(fwd->listen_host,
+		    fwd->listen_port ? fwd->listen_port : fwd->allocated_port));
+	}
+}
+
+/*
  * This is called after receiving CHANNEL_FORWARDING_REQUEST.  This initates
  * listening for the port, and sends back a success reply (or disconnect
  * message if there was an error).
  */
 int
-channel_input_port_forward_request(int is_root, int gateway_ports)
+channel_input_port_forward_request(int is_root, struct ForwardOptions *fwd_opts)
 {
-	u_short port, host_port;
 	int success = 0;
-	char *hostname;
+	struct Forward fwd;
 
 	/* Get arguments from the packet. */
-	port = packet_get_int();
-	hostname = packet_get_string(NULL);
-	host_port = packet_get_int();
+	memset(&fwd, 0, sizeof(fwd));
+	fwd.listen_port = packet_get_int();
+	fwd.connect_host = packet_get_string(NULL);
+	fwd.connect_port = packet_get_int();
 
 #ifndef HAVE_CYGWIN
 	/*
 	 * Check that an unprivileged user is not trying to forward a
 	 * privileged port.
 	 */
-	if (port < IPPORT_RESERVED && !is_root)
+	if (fwd.listen_port < IPPORT_RESERVED && !is_root)
 		packet_disconnect(
 		    "Requested forwarding of port %d but user is not root.",
-		    port);
-	if (host_port == 0)
+		    fwd.listen_port);
+	if (fwd.connect_port == 0)
 		packet_disconnect("Dynamic forwarding denied.");
 #endif
 
 	/* Initiate forwarding */
-	success = channel_setup_local_fwd_listener(NULL, port, hostname,
-	    host_port, gateway_ports);
+	success = channel_setup_local_fwd_listener(&fwd, fwd_opts);
 
 	/* Free the argument string. */
-	free(hostname);
+	free(fwd.connect_host);
 
 	return (success ? 0 : -1);
 }
@@ -3193,6 +3460,7 @@
 	permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host);
 	permitted_opens[num_permitted_opens].port_to_connect = port;
 	permitted_opens[num_permitted_opens].listen_host = NULL;
+	permitted_opens[num_permitted_opens].listen_path = NULL;
 	permitted_opens[num_permitted_opens].listen_port = 0;
 	num_permitted_opens++;
 
@@ -3227,6 +3495,8 @@
 		permitted_opens[idx].host_to_connect = NULL;
 		free(permitted_opens[idx].listen_host);
 		permitted_opens[idx].listen_host = NULL;
+		free(permitted_opens[idx].listen_path);
+		permitted_opens[idx].listen_path = NULL;
 	}
 }
 
@@ -3241,6 +3511,7 @@
 	     = xstrdup(host);
 	permitted_adm_opens[num_adm_permitted_opens].port_to_connect = port;
 	permitted_adm_opens[num_adm_permitted_opens].listen_host = NULL;
+	permitted_adm_opens[num_adm_permitted_opens].listen_path = NULL;
 	permitted_adm_opens[num_adm_permitted_opens].listen_port = 0;
 	return ++num_adm_permitted_opens;
 }
@@ -3262,6 +3533,7 @@
 	for (i = 0; i < num_permitted_opens; i++) {
 		free(permitted_opens[i].host_to_connect);
 		free(permitted_opens[i].listen_host);
+		free(permitted_opens[i].listen_path);
 	}
 	free(permitted_opens);
 	permitted_opens = NULL;
@@ -3276,6 +3548,7 @@
 	for (i = 0; i < num_adm_permitted_opens; i++) {
 		free(permitted_adm_opens[i].host_to_connect);
 		free(permitted_adm_opens[i].listen_host);
+		free(permitted_adm_opens[i].listen_path);
 	}
 	free(permitted_adm_opens);
 	permitted_adm_opens = NULL;
@@ -3319,16 +3592,27 @@
 connect_next(struct channel_connect *cctx)
 {
 	int sock, saved_errno;
-	char ntop[NI_MAXHOST], strport[NI_MAXSERV];
+	struct sockaddr_un *sunaddr;
+	char ntop[NI_MAXHOST], strport[MAX(NI_MAXSERV,sizeof(sunaddr->sun_path))];
 
 	for (; cctx->ai; cctx->ai = cctx->ai->ai_next) {
-		if (cctx->ai->ai_family != AF_INET &&
-		    cctx->ai->ai_family != AF_INET6)
-			continue;
-		if (getnameinfo(cctx->ai->ai_addr, cctx->ai->ai_addrlen,
-		    ntop, sizeof(ntop), strport, sizeof(strport),
-		    NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
-			error("connect_next: getnameinfo failed");
+		switch (cctx->ai->ai_family) {
+		case AF_UNIX:
+			/* unix:pathname instead of host:port */
+			sunaddr = (struct sockaddr_un *)cctx->ai->ai_addr;
+			strlcpy(ntop, "unix", sizeof(ntop));
+			strlcpy(strport, sunaddr->sun_path, sizeof(strport));
+			break;
+		case AF_INET:
+		case AF_INET6:
+			if (getnameinfo(cctx->ai->ai_addr, cctx->ai->ai_addrlen,
+			    ntop, sizeof(ntop), strport, sizeof(strport),
+			    NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
+				error("connect_next: getnameinfo failed");
+				continue;
+			}
+			break;
+		default:
 			continue;
 		}
 		if ((sock = socket(cctx->ai->ai_family, cctx->ai->ai_socktype,
@@ -3351,10 +3635,11 @@
 			errno = saved_errno;
 			continue;	/* fail -- try next */
 		}
+		if (cctx->ai->ai_family != AF_UNIX)
+			set_nodelay(sock);
 		debug("connect_next: host %.100s ([%.100s]:%s) "
 		    "in progress, fd=%d", cctx->host, ntop, strport, sock);
 		cctx->ai = cctx->ai->ai_next;
-		set_nodelay(sock);
 		return sock;
 	}
 	return -1;
@@ -3364,14 +3649,18 @@
 channel_connect_ctx_free(struct channel_connect *cctx)
 {
 	free(cctx->host);
-	if (cctx->aitop)
-		freeaddrinfo(cctx->aitop);
+	if (cctx->aitop) {
+		if (cctx->aitop->ai_family == AF_UNIX)
+			free(cctx->aitop);
+		else
+			freeaddrinfo(cctx->aitop);
+	}
 	memset(cctx, 0, sizeof(*cctx));
 }
 
-/* Return CONNECTING channel to remote host, port */
+/* Return CONNECTING channel to remote host:port or local socket path */
 static Channel *
-connect_to(const char *host, u_short port, char *ctype, char *rname)
+connect_to(const char *name, int port, char *ctype, char *rname)
 {
 	struct addrinfo hints;
 	int gaierr;
@@ -3381,23 +3670,51 @@
 	Channel *c;
 
 	memset(&cctx, 0, sizeof(cctx));
-	memset(&hints, 0, sizeof(hints));
-	hints.ai_family = IPv4or6;
-	hints.ai_socktype = SOCK_STREAM;
-	snprintf(strport, sizeof strport, "%d", port);
-	if ((gaierr = getaddrinfo(host, strport, &hints, &cctx.aitop)) != 0) {
-		error("connect_to %.100s: unknown host (%s)", host,
-		    ssh_gai_strerror(gaierr));
-		return NULL;
+
+	if (port == PORT_STREAMLOCAL) {
+		struct sockaddr_un *sunaddr;
+		struct addrinfo *ai;
+
+		if (strlen(name) > sizeof(sunaddr->sun_path)) {
+			error("%.100s: %.100s", name, strerror(ENAMETOOLONG));
+			return (NULL);
+		}
+
+		/*
+		 * Fake up a struct addrinfo for AF_UNIX connections.
+		 * channel_connect_ctx_free() must check ai_family
+		 * and use free() not freeaddirinfo() for AF_UNIX.
+		 */
+		ai = xmalloc(sizeof(*ai) + sizeof(*sunaddr));
+		memset(ai, 0, sizeof(*ai) + sizeof(*sunaddr));
+		ai->ai_addr = (struct sockaddr *)(ai + 1);
+		ai->ai_addrlen = sizeof(*sunaddr);
+		ai->ai_family = AF_UNIX;
+		ai->ai_socktype = SOCK_STREAM;
+		ai->ai_protocol = PF_UNSPEC;
+		sunaddr = (struct sockaddr_un *)ai->ai_addr;
+		sunaddr->sun_family = AF_UNIX;
+		strlcpy(sunaddr->sun_path, name, sizeof(sunaddr->sun_path));
+		cctx.aitop = ai;
+	} else {
+		memset(&hints, 0, sizeof(hints));
+		hints.ai_family = IPv4or6;
+		hints.ai_socktype = SOCK_STREAM;
+		snprintf(strport, sizeof strport, "%d", port);
+		if ((gaierr = getaddrinfo(name, strport, &hints, &cctx.aitop)) != 0) {
+			error("connect_to %.100s: unknown host (%s)", name,
+			    ssh_gai_strerror(gaierr));
+			return NULL;
+		}
 	}
 
-	cctx.host = xstrdup(host);
+	cctx.host = xstrdup(name);
 	cctx.port = port;
 	cctx.ai = cctx.aitop;
 
 	if ((sock = connect_next(&cctx)) == -1) {
 		error("connect to %.100s port %d failed: %s",
-		    host, port, strerror(errno));
+		    name, port, strerror(errno));
 		channel_connect_ctx_free(&cctx);
 		return NULL;
 	}
@@ -3414,7 +3731,7 @@
 	int i;
 
 	for (i = 0; i < num_permitted_opens; i++) {
-		if (open_listen_match(&permitted_opens[i], listen_host,
+		if (open_listen_match_tcpip(&permitted_opens[i], listen_host,
 		    listen_port, 1)) {
 			return connect_to(
 			    permitted_opens[i].host_to_connect,
@@ -3426,9 +3743,26 @@
 	return NULL;
 }
 
+Channel *
+channel_connect_by_listen_path(const char *path, char *ctype, char *rname)
+{
+	int i;
+
+	for (i = 0; i < num_permitted_opens; i++) {
+		if (open_listen_match_streamlocal(&permitted_opens[i], path)) {
+			return connect_to(
+			    permitted_opens[i].host_to_connect,
+			    permitted_opens[i].port_to_connect, ctype, rname);
+		}
+	}
+	error("WARNING: Server requests forwarding for unknown path %.100s",
+	    path);
+	return NULL;
+}
+
 /* Check if connecting to that port is permitted and connect. */
 Channel *
-channel_connect_to(const char *host, u_short port, char *ctype, char *rname)
+channel_connect_to_port(const char *host, u_short port, char *ctype, char *rname)
 {
 	int i, permit, permit_adm = 1;
 
@@ -3458,6 +3792,38 @@
 	return connect_to(host, port, ctype, rname);
 }
 
+/* Check if connecting to that path is permitted and connect. */
+Channel *
+channel_connect_to_path(const char *path, char *ctype, char *rname)
+{
+	int i, permit, permit_adm = 1;
+
+	permit = all_opens_permitted;
+	if (!permit) {
+		for (i = 0; i < num_permitted_opens; i++)
+			if (open_match(&permitted_opens[i], path, PORT_STREAMLOCAL)) {
+				permit = 1;
+				break;
+			}
+	}
+
+	if (num_adm_permitted_opens > 0) {
+		permit_adm = 0;
+		for (i = 0; i < num_adm_permitted_opens; i++)
+			if (open_match(&permitted_adm_opens[i], path, PORT_STREAMLOCAL)) {
+				permit_adm = 1;
+				break;
+			}
+	}
+
+	if (!permit || !permit_adm) {
+		logit("Received request to connect to path %.100s, "
+		    "but the request was denied.", path);
+		return NULL;
+	}
+	return connect_to(path, PORT_STREAMLOCAL, ctype, rname);
+}
+
 void
 channel_send_window_changes(void)
 {