[openssh-commits] [openssh] 03/03: upstream commit

git+noreply at mindrot.org git+noreply at mindrot.org
Fri Sep 22 09:15:10 AEST 2017


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 609d7a66ce578abf259da2d5f6f68795c2bda731
Author: markus at openbsd.org <markus at openbsd.org>
Date:   Thu Sep 21 19:16:53 2017 +0000

    upstream commit
    
    Add 'reverse' dynamic forwarding which combines dynamic
    forwarding (-D) with remote forwarding (-R) where the remote-forwarded port
    expects SOCKS-requests.
    
    The SSH server code is unchanged and the parsing happens at the SSH
    clients side. Thus the full SOCKS-request is sent over the forwarded
    channel and the client parses c->output. Parsing happens in
    channel_before_prepare_select(), _before_ the select bitmask is
    computed in the pre[] handlers, but after network input processing
    in the post[] handlers.
    
    help and ok djm@
    
    Upstream-ID: aa25a6a3851064f34fe719e0bf15656ad5a64b89
---
 channels.c   | 374 +++++++++++++++++++++++++++++++++++++++++++++--------------
 channels.h   |   6 +-
 readconf.c   |  42 ++++---
 ssh.1        |  21 +++-
 ssh.c        |   5 +-
 ssh_config.5 |  16 ++-
 6 files changed, 346 insertions(+), 118 deletions(-)

diff --git a/channels.c b/channels.c
index 89b7d348..8ef37c45 100644
--- a/channels.c
+++ b/channels.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.371 2017/09/19 12:10:30 millert Exp $ */
+/* $OpenBSD: channels.c,v 1.372 2017/09/21 19:16:53 markus Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -209,6 +209,8 @@ static const char *channel_rfwd_bind_host(const char *listen_host);
 /* non-blocking connect helpers */
 static int connect_next(struct channel_connect *);
 static void channel_connect_ctx_free(struct channel_connect *);
+static Channel *rdynamic_connect_prepare(struct ssh *, char *, char *);
+static int rdynamic_connect_finish(struct ssh *, Channel *);
 
 /* Setup helper */
 static void channel_handler_init(struct ssh_channels *sc);
@@ -282,6 +284,8 @@ channel_lookup(struct ssh *ssh, int id)
 	case SSH_CHANNEL_LARVAL:
 	case SSH_CHANNEL_CONNECTING:
 	case SSH_CHANNEL_DYNAMIC:
+	case SSH_CHANNEL_RDYNAMIC_OPEN:
+	case SSH_CHANNEL_RDYNAMIC_FINISH:
 	case SSH_CHANNEL_OPENING:
 	case SSH_CHANNEL_OPEN:
 	case SSH_CHANNEL_ABANDONED:
@@ -671,6 +675,7 @@ channel_still_open(struct ssh *ssh)
 		case SSH_CHANNEL_CLOSED:
 		case SSH_CHANNEL_AUTH_SOCKET:
 		case SSH_CHANNEL_DYNAMIC:
+		case SSH_CHANNEL_RDYNAMIC_OPEN:
 		case SSH_CHANNEL_CONNECTING:
 		case SSH_CHANNEL_ZOMBIE:
 		case SSH_CHANNEL_ABANDONED:
@@ -681,6 +686,7 @@ channel_still_open(struct ssh *ssh)
 			continue;
 		case SSH_CHANNEL_OPENING:
 		case SSH_CHANNEL_OPEN:
+		case SSH_CHANNEL_RDYNAMIC_FINISH:
 		case SSH_CHANNEL_X11_OPEN:
 		case SSH_CHANNEL_MUX_CLIENT:
 		case SSH_CHANNEL_MUX_PROXY:
@@ -707,6 +713,8 @@ channel_find_open(struct ssh *ssh)
 		switch (c->type) {
 		case SSH_CHANNEL_CLOSED:
 		case SSH_CHANNEL_DYNAMIC:
+		case SSH_CHANNEL_RDYNAMIC_OPEN:
+		case SSH_CHANNEL_RDYNAMIC_FINISH:
 		case SSH_CHANNEL_X11_LISTENER:
 		case SSH_CHANNEL_PORT_LISTENER:
 		case SSH_CHANNEL_RPORT_LISTENER:
@@ -772,6 +780,8 @@ channel_open_message(struct ssh *ssh)
 		case SSH_CHANNEL_OPENING:
 		case SSH_CHANNEL_CONNECTING:
 		case SSH_CHANNEL_DYNAMIC:
+		case SSH_CHANNEL_RDYNAMIC_OPEN:
+		case SSH_CHANNEL_RDYNAMIC_FINISH:
 		case SSH_CHANNEL_OPEN:
 		case SSH_CHANNEL_X11_OPEN:
 		case SSH_CHANNEL_MUX_PROXY:
@@ -1124,8 +1134,7 @@ channel_pre_mux_client(struct ssh *ssh,
 
 /* try to decode a socks4 header */
 static int
-channel_decode_socks4(struct ssh *ssh, Channel *c,
-    fd_set *readset, fd_set *writeset)
+channel_decode_socks4(Channel *c, struct sshbuf *input, struct sshbuf *output)
 {
 	const u_char *p;
 	char *host;
@@ -1141,11 +1150,11 @@ channel_decode_socks4(struct ssh *ssh, Channel *c,
 
 	debug2("channel %d: decode socks4", c->self);
 
-	have = sshbuf_len(c->input);
+	have = sshbuf_len(input);
 	len = sizeof(s4_req);
 	if (have < len)
 		return 0;
-	p = sshbuf_ptr(c->input);
+	p = sshbuf_ptr(input);
 
 	need = 1;
 	/* SOCKS4A uses an invalid IP address 0.0.0.x */
@@ -1170,15 +1179,15 @@ channel_decode_socks4(struct ssh *ssh, Channel *c,
 	}
 	if (found < need)
 		return 0;
-	if ((r = sshbuf_get(c->input, &s4_req.version, 1)) != 0 ||
-	    (r = sshbuf_get(c->input, &s4_req.command, 1)) != 0 ||
-	    (r = sshbuf_get(c->input, &s4_req.dest_port, 2)) != 0 ||
-	    (r = sshbuf_get(c->input, &s4_req.dest_addr, 4)) != 0) {
+	if ((r = sshbuf_get(input, &s4_req.version, 1)) != 0 ||
+	    (r = sshbuf_get(input, &s4_req.command, 1)) != 0 ||
+	    (r = sshbuf_get(input, &s4_req.dest_port, 2)) != 0 ||
+	    (r = sshbuf_get(input, &s4_req.dest_addr, 4)) != 0) {
 		debug("channels %d: decode socks4: %s", c->self, ssh_err(r));
 		return -1;
 	}
-	have = sshbuf_len(c->input);
-	p = sshbuf_ptr(c->input);
+	have = sshbuf_len(input);
+	p = sshbuf_ptr(input);
 	if (memchr(p, '\0', have) == NULL) {
 		error("channel %d: decode socks4: user not nul terminated",
 		    c->self);
@@ -1188,7 +1197,7 @@ channel_decode_socks4(struct ssh *ssh, Channel *c,
 	debug2("channel %d: decode socks4: user %s/%d", c->self, p, len);
 	len++; /* trailing '\0' */
 	strlcpy(username, p, sizeof(username));
-	if ((r = sshbuf_consume(c->input, len)) != 0) {
+	if ((r = sshbuf_consume(input, len)) != 0) {
 		fatal("%s: channel %d: consume: %s", __func__,
 		    c->self, ssh_err(r));
 	}
@@ -1198,8 +1207,8 @@ channel_decode_socks4(struct ssh *ssh, Channel *c,
 		host = inet_ntoa(s4_req.dest_addr);
 		c->path = xstrdup(host);
 	} else {				/* SOCKS4A: two strings */
-		have = sshbuf_len(c->input);
-		p = sshbuf_ptr(c->input);
+		have = sshbuf_len(input);
+		p = sshbuf_ptr(input);
 		if (memchr(p, '\0', have) == NULL) {
 			error("channel %d: decode socks4a: host not nul "
 			    "terminated", c->self);
@@ -1215,7 +1224,7 @@ channel_decode_socks4(struct ssh *ssh, Channel *c,
 			return -1;
 		}
 		c->path = xstrdup(p);
-		if ((r = sshbuf_consume(c->input, len)) != 0) {
+		if ((r = sshbuf_consume(input, len)) != 0) {
 			fatal("%s: channel %d: consume: %s", __func__,
 			    c->self, ssh_err(r));
 		}
@@ -1234,7 +1243,7 @@ channel_decode_socks4(struct ssh *ssh, Channel *c,
 	s4_rsp.command = 90;			/* cd: req granted */
 	s4_rsp.dest_port = 0;			/* ignored */
 	s4_rsp.dest_addr.s_addr = INADDR_ANY;	/* ignored */
-	if ((r = sshbuf_put(c->output, &s4_rsp, sizeof(s4_rsp))) != 0) {
+	if ((r = sshbuf_put(output, &s4_rsp, sizeof(s4_rsp))) != 0) {
 		fatal("%s: channel %d: append reply: %s", __func__,
 		    c->self, ssh_err(r));
 	}
@@ -1251,8 +1260,7 @@ channel_decode_socks4(struct ssh *ssh, Channel *c,
 #define SSH_SOCKS5_SUCCESS	0x00
 
 static int
-channel_decode_socks5(struct ssh *ssh, Channel *c,
-    fd_set *readset, fd_set *writeset)
+channel_decode_socks5(Channel *c, struct sshbuf *input, struct sshbuf *output)
 {
 	/* XXX use get/put_u8 instead of trusting struct padding */
 	struct {
@@ -1268,10 +1276,10 @@ channel_decode_socks5(struct ssh *ssh, Channel *c,
 	int r;
 
 	debug2("channel %d: decode socks5", c->self);
-	p = sshbuf_ptr(c->input);
+	p = sshbuf_ptr(input);
 	if (p[0] != 0x05)
 		return -1;
-	have = sshbuf_len(c->input);
+	have = sshbuf_len(input);
 	if (!(c->flags & SSH_SOCKS5_AUTHDONE)) {
 		/* format: ver | nmethods | methods */
 		if (have < 2)
@@ -1291,17 +1299,16 @@ channel_decode_socks5(struct ssh *ssh, Channel *c,
 			    c->self);
 			return -1;
 		}
-		if ((r = sshbuf_consume(c->input, nmethods + 2)) != 0) {
+		if ((r = sshbuf_consume(input, nmethods + 2)) != 0) {
 			fatal("%s: channel %d: consume: %s", __func__,
 			    c->self, ssh_err(r));
 		}
 		/* version, method */
-		if ((r = sshbuf_put_u8(c->output, 0x05)) != 0 ||
-		    (r = sshbuf_put_u8(c->output, SSH_SOCKS5_NOAUTH)) != 0) {
+		if ((r = sshbuf_put_u8(output, 0x05)) != 0 ||
+		    (r = sshbuf_put_u8(output, SSH_SOCKS5_NOAUTH)) != 0) {
 			fatal("%s: channel %d: append reply: %s", __func__,
 			    c->self, ssh_err(r));
 		}
-		FD_SET(c->sock, writeset);
 		c->flags |= SSH_SOCKS5_AUTHDONE;
 		debug2("channel %d: socks5 auth done", c->self);
 		return 0;				/* need more */
@@ -1338,19 +1345,19 @@ channel_decode_socks5(struct ssh *ssh, Channel *c,
 		need++;
 	if (have < need)
 		return 0;
-	if ((r = sshbuf_consume(c->input, sizeof(s5_req))) != 0) {
+	if ((r = sshbuf_consume(input, sizeof(s5_req))) != 0) {
 		fatal("%s: channel %d: consume: %s", __func__,
 		    c->self, ssh_err(r));
 	}
 	if (s5_req.atyp == SSH_SOCKS5_DOMAIN) {
 		/* host string length */
-		if ((r = sshbuf_consume(c->input, 1)) != 0) {
+		if ((r = sshbuf_consume(input, 1)) != 0) {
 			fatal("%s: channel %d: consume: %s", __func__,
 			    c->self, ssh_err(r));
 		}
 	}
-	if ((r = sshbuf_get(c->input, &dest_addr, addrlen)) != 0 ||
-	    (r = sshbuf_get(c->input, &dest_port, 2)) != 0) {
+	if ((r = sshbuf_get(input, &dest_addr, addrlen)) != 0 ||
+	    (r = sshbuf_get(input, &dest_port, 2)) != 0) {
 		debug("channel %d: parse addr/port: %s", c->self, ssh_err(r));
 		return -1;
 	}
@@ -1380,9 +1387,9 @@ channel_decode_socks5(struct ssh *ssh, Channel *c,
 	s5_rsp.atyp = SSH_SOCKS5_IPV4;
 	dest_port = 0;				/* ignored */
 
-	if ((r = sshbuf_put(c->output, &s5_rsp, sizeof(s5_rsp))) != 0 ||
-	    (r = sshbuf_put_u32(c->output, ntohl(INADDR_ANY))) != 0 ||
-	    (r = sshbuf_put(c->output, &dest_port, sizeof(dest_port))) != 0)
+	if ((r = sshbuf_put(output, &s5_rsp, sizeof(s5_rsp))) != 0 ||
+	    (r = sshbuf_put_u32(output, ntohl(INADDR_ANY))) != 0 ||
+	    (r = sshbuf_put(output, &dest_port, sizeof(dest_port))) != 0)
 		fatal("%s: channel %d: append reply: %s", __func__,
 		    c->self, ssh_err(r));
 	return 1;
@@ -1434,10 +1441,10 @@ channel_pre_dynamic(struct ssh *ssh, Channel *c,
 	/* XXX sshbuf_peek_u8? */
 	switch (p[0]) {
 	case 0x04:
-		ret = channel_decode_socks4(ssh, c, readset, writeset);
+		ret = channel_decode_socks4(c, c->input, c->output);
 		break;
 	case 0x05:
-		ret = channel_decode_socks5(ssh, c, readset, writeset);
+		ret = channel_decode_socks5(c, c->input, c->output);
 		break;
 	default:
 		ret = -1;
@@ -1449,6 +1456,8 @@ channel_pre_dynamic(struct ssh *ssh, Channel *c,
 		debug2("channel %d: pre_dynamic: need more", c->self);
 		/* need more */
 		FD_SET(c->sock, readset);
+		if (sshbuf_len(c->output))
+			FD_SET(c->sock, writeset);
 	} else {
 		/* switch to the next state */
 		c->type = SSH_CHANNEL_OPENING;
@@ -1456,6 +1465,81 @@ channel_pre_dynamic(struct ssh *ssh, Channel *c,
 	}
 }
 
+/* simulate read-error */
+static void
+rdynamic_close(struct ssh *ssh, Channel *c)
+{
+	c->type = SSH_CHANNEL_OPEN;
+	chan_read_failed(ssh, c);
+	sshbuf_reset(c->input);
+	chan_ibuf_empty(ssh, c);
+	sshbuf_reset(c->output);
+	chan_write_failed(ssh, c);
+}
+
+/* reverse dynamic port forwarding */
+static void
+channel_before_prepare_select_rdynamic(struct ssh *ssh, Channel *c)
+{
+	const u_char *p;
+	u_int have, len;
+	int r, ret;
+
+	have = sshbuf_len(c->output);
+	debug2("channel %d: pre_rdynamic: have %d", c->self, have);
+	/* sshbuf_dump(c->output, stderr); */
+	/* EOF received */
+	if (c->flags & CHAN_EOF_RCVD) {
+		if ((r = sshbuf_consume(c->output, have)) != 0) {
+			fatal("%s: channel %d: consume: %s",
+			    __func__, c->self, ssh_err(r));
+		}
+		rdynamic_close(ssh, c);
+		return;
+	}
+	/* check if the fixed size part of the packet is in buffer. */
+	if (have < 3)
+		return;
+	/* try to guess the protocol */
+	p = sshbuf_ptr(c->output);
+	switch (p[0]) {
+	case 0x04:
+		/* switch input/output for reverse forwarding */
+		ret = channel_decode_socks4(c, c->output, c->input);
+		break;
+	case 0x05:
+		ret = channel_decode_socks5(c, c->output, c->input);
+		break;
+	default:
+		ret = -1;
+		break;
+	}
+	if (ret < 0) {
+		rdynamic_close(ssh, c);
+	} else if (ret == 0) {
+		debug2("channel %d: pre_rdynamic: need more", c->self);
+		/* send socks request to peer */
+		len = sshbuf_len(c->input);
+		if (len > 0 && len < c->remote_window) {
+			if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 ||
+			    (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 ||
+			    (r = sshpkt_put_stringb(ssh, c->input)) != 0 ||
+			    (r = sshpkt_send(ssh)) != 0) {
+				fatal("%s: channel %i: rdynamic: %s", __func__,
+				    c->self, ssh_err(r));
+			}
+			if ((r = sshbuf_consume(c->input, len)) != 0) {
+				fatal("%s: channel %d: consume: %s",
+				    __func__, c->self, ssh_err(r));
+			}
+			c->remote_window -= len;
+		}
+	} else if (rdynamic_connect_finish(ssh, c) < 0) {
+		/* the connect failed */
+		rdynamic_close(ssh, c);
+	}
+}
+
 /* This is our fake X11 server socket. */
 static void
 channel_post_x11_listener(struct ssh *ssh, Channel *c,
@@ -1699,14 +1783,15 @@ static void
 channel_post_connecting(struct ssh *ssh, Channel *c,
     fd_set *readset, fd_set *writeset)
 {
-	int err = 0, sock, r;
+	int err = 0, sock, isopen, r;
 	socklen_t sz = sizeof(err);
 
 	if (!FD_ISSET(c->sock, writeset))
 		return;
 	if (!c->have_remote_id)
 		fatal(":%s: channel %d: no remote id", __func__, c->self);
-
+	/* for rdynamic the OPEN_CONFIRMATION has been sent already */
+	isopen = (c->type == SSH_CHANNEL_RDYNAMIC_FINISH);
 	if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) < 0) {
 		err = errno;
 		error("getsockopt SO_ERROR failed");
@@ -1716,14 +1801,21 @@ channel_post_connecting(struct ssh *ssh, Channel *c,
 		    c->self, c->connect_ctx.host, c->connect_ctx.port);
 		channel_connect_ctx_free(&c->connect_ctx);
 		c->type = SSH_CHANNEL_OPEN;
-		if ((r = sshpkt_start(ssh,
-		    SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 ||
-		    (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 ||
-		    (r = sshpkt_put_u32(ssh, c->self)) != 0 ||
-		    (r = sshpkt_put_u32(ssh, c->local_window)) != 0 ||
-		    (r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0) {
-			fatal("%s: channel %i: confirm: %s", __func__,
-			    c->self, ssh_err(r));
+		if (isopen) {
+			/* no message necessary */
+		} else {
+			if ((r = sshpkt_start(ssh,
+			    SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 ||
+			    (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 ||
+			    (r = sshpkt_put_u32(ssh, c->self)) != 0 ||
+			    (r = sshpkt_put_u32(ssh, c->local_window)) != 0 ||
+			    (r = sshpkt_put_u32(ssh, c->local_maxpacket))
+			    != 0)
+				fatal("%s: channel %i: confirm: %s", __func__,
+				    c->self, ssh_err(r));
+			if ((r = sshpkt_send(ssh)) != 0)
+				fatal("%s: channel %i: %s", __func__, c->self,
+				    ssh_err(r));
 		}
 	} else {
 		debug("channel %d: connection failed: %s",
@@ -1739,22 +1831,27 @@ channel_post_connecting(struct ssh *ssh, Channel *c,
 		error("connect_to %.100s port %d: failed.",
 		    c->connect_ctx.host, c->connect_ctx.port);
 		channel_connect_ctx_free(&c->connect_ctx);
-		if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE)) != 0 ||
-		    (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 ||
-		    (r = sshpkt_put_u32(ssh, SSH2_OPEN_CONNECT_FAILED)) != 0) {
-			fatal("%s: channel %i: failure: %s", __func__,
-			    c->self, ssh_err(r));
+		if (isopen) {
+			rdynamic_close(ssh, c);
+		} else {
+			if ((r = sshpkt_start(ssh,
+			    SSH2_MSG_CHANNEL_OPEN_FAILURE)) != 0 ||
+			    (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 ||
+			    (r = sshpkt_put_u32(ssh, SSH2_OPEN_CONNECT_FAILED))
+			    != 0)
+				fatal("%s: channel %i: failure: %s", __func__,
+				    c->self, ssh_err(r));
+			if ((datafellows & SSH_BUG_OPENFAILURE) == 0 &&
+			    ((r = sshpkt_put_cstring(ssh, strerror(err))) != 0 ||
+			    (r = sshpkt_put_cstring(ssh, "")) != 0))
+				fatal("%s: channel %i: failure: %s", __func__,
+				    c->self, ssh_err(r));
+			if ((r = sshpkt_send(ssh)) != 0)
+				fatal("%s: channel %i: %s", __func__, c->self,
+				    ssh_err(r));
+			chan_mark_dead(ssh, c);
 		}
-		if ((datafellows & SSH_BUG_OPENFAILURE) == 0 &&
-		    ((r = sshpkt_put_cstring(ssh, strerror(err))) != 0 ||
-		    (r = sshpkt_put_cstring(ssh, "")) != 0)) {
-			fatal("%s: channel %i: failure: %s", __func__,
-			    c->self, ssh_err(r));
-		}
-		chan_mark_dead(ssh, c);
 	}
-	if ((r = sshpkt_send(ssh)) != 0)
-		fatal("%s: channel %i: %s", __func__, c->self, ssh_err(r));
 }
 
 static int
@@ -2187,6 +2284,7 @@ channel_handler_init(struct ssh_channels *sc)
 	pre[SSH_CHANNEL_AUTH_SOCKET] =		&channel_pre_listener;
 	pre[SSH_CHANNEL_CONNECTING] =		&channel_pre_connecting;
 	pre[SSH_CHANNEL_DYNAMIC] =		&channel_pre_dynamic;
+	pre[SSH_CHANNEL_RDYNAMIC_FINISH] =	&channel_pre_connecting;
 	pre[SSH_CHANNEL_MUX_LISTENER] =		&channel_pre_listener;
 	pre[SSH_CHANNEL_MUX_CLIENT] =		&channel_pre_mux_client;
 
@@ -2199,6 +2297,7 @@ channel_handler_init(struct ssh_channels *sc)
 	post[SSH_CHANNEL_AUTH_SOCKET] =		&channel_post_auth_listener;
 	post[SSH_CHANNEL_CONNECTING] =		&channel_post_connecting;
 	post[SSH_CHANNEL_DYNAMIC] =		&channel_post_open;
+	post[SSH_CHANNEL_RDYNAMIC_FINISH] =	&channel_post_connecting;
 	post[SSH_CHANNEL_MUX_LISTENER] =	&channel_post_mux_listener;
 	post[SSH_CHANNEL_MUX_CLIENT] =		&channel_post_mux_client;
 
@@ -2279,6 +2378,27 @@ channel_handler(struct ssh *ssh, int table,
 		    __func__, (int)*unpause_secs);
 }
 
+/*
+ * Create sockets before allocating the select bitmasks.
+ * This is necessary for things that need to happen after reading
+ * the network-input but before channel_prepare_select().
+ */
+static void
+channel_before_prepare_select(struct ssh *ssh)
+{
+	struct ssh_channels *sc = ssh->chanctxt;
+	Channel *c;
+	u_int i, oalloc;
+
+	for (i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) {
+		c = sc->channels[i];
+		if (c == NULL)
+			continue;
+		if (c->type == SSH_CHANNEL_RDYNAMIC_OPEN)
+			channel_before_prepare_select_rdynamic(ssh, c);
+	}
+}
+
 /*
  * Allocate/update select bitmasks and add any bits relevant to channels in
  * select bitmasks.
@@ -2289,6 +2409,8 @@ channel_prepare_select(struct ssh *ssh, fd_set **readsetp, fd_set **writesetp,
 {
 	u_int n, sz, nfdset;
 
+	channel_before_prepare_select(ssh); /* might update channel_max_fd */
+
 	n = MAXIMUM(*maxfdp, ssh->chanctxt->channel_max_fd);
 
 	nfdset = howmany(n+1, NFDBITS);
@@ -2794,6 +2916,8 @@ channel_input_data(int type, u_int32_t seq, struct ssh *ssh)
 
 	/* Ignore any data for non-open channels (might happen on close) */
 	if (c->type != SSH_CHANNEL_OPEN &&
+	    c->type != SSH_CHANNEL_RDYNAMIC_OPEN &&
+	    c->type != SSH_CHANNEL_RDYNAMIC_FINISH &&
 	    c->type != SSH_CHANNEL_X11_OPEN)
 		return 0;
 
@@ -3032,7 +3156,7 @@ channel_input_window_adjust(int type, u_int32_t seq, struct ssh *ssh)
 	if ((c = channel_lookup(ssh, id)) == NULL) {
 		logit("Received window adjust for non-open channel %d.", id);
 		return 0;
-        }
+	}
 
 	if (channel_proxy_upstream(c, type, seq, ssh))
 		return 0;
@@ -3939,21 +4063,18 @@ channel_connect_ctx_free(struct channel_connect *cctx)
 }
 
 /*
- * Return CONNECTING channel to remote host:port or local socket path,
+ * Return connecting socket to remote host:port or local socket path,
  * passing back the failure reason if appropriate.
  */
-static Channel *
-connect_to_reason(struct ssh *ssh, const char *name, int port,
-    char *ctype, char *rname, int *reason, const char **errmsg)
+static int
+connect_to_helper(struct ssh *ssh, const char *name, int port, int socktype,
+    char *ctype, char *rname, struct channel_connect *cctx,
+    int *reason, const char **errmsg)
 {
 	struct addrinfo hints;
 	int gaierr;
 	int sock = -1;
 	char strport[NI_MAXSERV];
-	struct channel_connect cctx;
-	Channel *c;
-
-	memset(&cctx, 0, sizeof(cctx));
 
 	if (port == PORT_STREAMLOCAL) {
 		struct sockaddr_un *sunaddr;
@@ -3961,7 +4082,7 @@ connect_to_reason(struct ssh *ssh, const char *name, int port,
 
 		if (strlen(name) > sizeof(sunaddr->sun_path)) {
 			error("%.100s: %.100s", name, strerror(ENAMETOOLONG));
-			return (NULL);
+			return -1;
 		}
 
 		/*
@@ -3974,18 +4095,18 @@ connect_to_reason(struct ssh *ssh, const char *name, int port,
 		ai->ai_addr = (struct sockaddr *)(ai + 1);
 		ai->ai_addrlen = sizeof(*sunaddr);
 		ai->ai_family = AF_UNIX;
-		ai->ai_socktype = SOCK_STREAM;
+		ai->ai_socktype = socktype;
 		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;
+		cctx->aitop = ai;
 	} else {
 		memset(&hints, 0, sizeof(hints));
 		hints.ai_family = ssh->chanctxt->IPv4or6;
-		hints.ai_socktype = SOCK_STREAM;
+		hints.ai_socktype = socktype;
 		snprintf(strport, sizeof strport, "%d", port);
-		if ((gaierr = getaddrinfo(name, strport, &hints, &cctx.aitop))
+		if ((gaierr = getaddrinfo(name, strport, &hints, &cctx->aitop))
 		    != 0) {
 			if (errmsg != NULL)
 				*errmsg = ssh_gai_strerror(gaierr);
@@ -3993,32 +4114,46 @@ connect_to_reason(struct ssh *ssh, const char *name, int port,
 				*reason = SSH2_OPEN_CONNECT_FAILED;
 			error("connect_to %.100s: unknown host (%s)", name,
 			    ssh_gai_strerror(gaierr));
-			return NULL;
+			return -1;
 		}
 	}
 
-	cctx.host = xstrdup(name);
-	cctx.port = port;
-	cctx.ai = cctx.aitop;
+	cctx->host = xstrdup(name);
+	cctx->port = port;
+	cctx->ai = cctx->aitop;
 
-	if ((sock = connect_next(&cctx)) == -1) {
+	if ((sock = connect_next(cctx)) == -1) {
 		error("connect to %.100s port %d failed: %s",
 		    name, port, strerror(errno));
-		channel_connect_ctx_free(&cctx);
-		return NULL;
+		return -1;
 	}
-	c = channel_new(ssh, ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1,
-	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1);
-	c->connect_ctx = cctx;
-	return c;
+
+	return sock;
 }
 
 /* Return CONNECTING channel to remote host:port or local socket path */
 static Channel *
-connect_to(struct ssh *ssh, const char *name, int port,
+connect_to(struct ssh *ssh, const char *host, int port,
     char *ctype, char *rname)
 {
-	return connect_to_reason(ssh, name, port, ctype, rname, NULL, NULL);
+	struct channel_connect cctx;
+	Channel *c;
+	int sock;
+
+	memset(&cctx, 0, sizeof(cctx));
+	sock = connect_to_helper(ssh, host, port, SOCK_STREAM, ctype, rname,
+	    &cctx, NULL, NULL);
+	if (sock == -1) {
+		channel_connect_ctx_free(&cctx);
+		return NULL;
+	}
+	c = channel_new(ssh, ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1,
+	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1);
+	c->host_port = port;
+	c->path = xstrdup(host);
+	c->connect_ctx = cctx;
+
+	return c;
 }
 
 /*
@@ -4038,6 +4173,9 @@ channel_connect_by_listen_address(struct ssh *ssh, const char *listen_host,
 		if (open_listen_match_tcpip(fp, listen_host, listen_port, 1)) {
 			if (fp->downstream)
 				return fp->downstream;
+			if (fp->port_to_connect == 0)
+				return rdynamic_connect_prepare(ssh,
+				    ctype, rname);
 			return connect_to(ssh,
 			    fp->host_to_connect, fp->port_to_connect,
 			    ctype, rname);
@@ -4075,7 +4213,10 @@ channel_connect_to_port(struct ssh *ssh, const char *host, u_short port,
     char *ctype, char *rname, int *reason, const char **errmsg)
 {
 	struct ssh_channels *sc = ssh->chanctxt;
+	struct channel_connect cctx;
+	Channel *c;
 	u_int i, permit, permit_adm = 1;
+	int sock;
 	ForwardPermission *fp;
 
 	permit = sc->all_opens_permitted;
@@ -4107,7 +4248,22 @@ channel_connect_to_port(struct ssh *ssh, const char *host, u_short port,
 			*reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED;
 		return NULL;
 	}
-	return connect_to_reason(ssh, host, port, ctype, rname, reason, errmsg);
+
+	memset(&cctx, 0, sizeof(cctx));
+	sock = connect_to_helper(ssh, host, port, SOCK_STREAM, ctype, rname,
+	    &cctx, reason, errmsg);
+	if (sock == -1) {
+		channel_connect_ctx_free(&cctx);
+		return NULL;
+	}
+
+	c = channel_new(ssh, ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1,
+	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1);
+	c->host_port = port;
+	c->path = xstrdup(host);
+	c->connect_ctx = cctx;
+
+	return c;
 }
 
 /* Check if connecting to that path is permitted and connect. */
@@ -4174,6 +4330,54 @@ channel_send_window_changes(struct ssh *ssh)
 	}
 }
 
+/* Return RDYNAMIC_OPEN channel: channel allows SOCKS, but is not connected */
+static Channel *
+rdynamic_connect_prepare(struct ssh *ssh, char *ctype, char *rname)
+{
+	Channel *c;
+	int r;
+
+	c = channel_new(ssh, ctype, SSH_CHANNEL_RDYNAMIC_OPEN, -1, -1, -1,
+	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1);
+	c->host_port = 0;
+	c->path = NULL;
+
+	/*
+	 * We need to open the channel before we have a FD,
+	 * so that we can get SOCKS header from peer.
+	 */
+	if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 ||
+	    (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 ||
+	    (r = sshpkt_put_u32(ssh, c->self)) != 0 ||
+	    (r = sshpkt_put_u32(ssh, c->local_window)) != 0 ||
+	    (r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0) {
+		fatal("%s: channel %i: confirm: %s", __func__,
+		    c->self, ssh_err(r));
+	}
+	return c;
+}
+
+/* Return CONNECTING socket to remote host:port or local socket path */
+static int
+rdynamic_connect_finish(struct ssh *ssh, Channel *c)
+{
+	struct channel_connect cctx;
+	int sock;
+
+	memset(&cctx, 0, sizeof(cctx));
+	sock = connect_to_helper(ssh, c->path, c->host_port, SOCK_STREAM, NULL,
+	    NULL, &cctx, NULL, NULL);
+	if (sock == -1)
+		channel_connect_ctx_free(&cctx);
+	else {
+		/* similar to SSH_CHANNEL_CONNECTING but we've already sent the open */
+		c->type = SSH_CHANNEL_RDYNAMIC_FINISH;
+		c->connect_ctx = cctx;
+		channel_register_fds(ssh, c, sock, sock, -1, 0, 1, 0);
+	}
+	return sock;
+}
+
 /* -- X11 forwarding */
 
 /*
diff --git a/channels.h b/channels.h
index d1cf5dc6..126b0434 100644
--- a/channels.h
+++ b/channels.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.h,v 1.129 2017/09/12 06:35:32 djm Exp $ */
+/* $OpenBSD: channels.h,v 1.130 2017/09/21 19:16:53 markus Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
@@ -57,7 +57,9 @@
 #define SSH_CHANNEL_UNIX_LISTENER	18	/* Listening on a domain socket. */
 #define SSH_CHANNEL_RUNIX_LISTENER	19	/* Listening to a R-style domain socket. */
 #define SSH_CHANNEL_MUX_PROXY		20	/* proxy channel for mux-slave */
-#define SSH_CHANNEL_MAX_TYPE		21
+#define SSH_CHANNEL_RDYNAMIC_OPEN	21	/* reverse SOCKS, parsing request */
+#define SSH_CHANNEL_RDYNAMIC_FINISH	22	/* reverse SOCKS, finishing connect */
+#define SSH_CHANNEL_MAX_TYPE		23
 
 #define CHANNEL_CANCEL_PORT_STATIC	-1
 
diff --git a/readconf.c b/readconf.c
index 4f38b27c..f63894f9 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.278 2017/09/03 23:33:13 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.279 2017/09/21 19:16:53 markus Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -836,6 +836,7 @@ process_config_line_depth(Options *options, struct passwd *pw, const char *host,
 	char **cpptr, fwdarg[256];
 	u_int i, *uintptr, max_entries = 0;
 	int r, oactive, negated, opcode, *intptr, value, value2, cmdline = 0;
+	int remotefwd, dynamicfwd;
 	LogLevel *log_level_ptr;
 	SyslogFacility *log_facility_ptr;
 	long long val64;
@@ -1255,31 +1256,36 @@ parse_keytypes:
 			fatal("%.200s line %d: Missing port argument.",
 			    filename, linenum);
 
-		if (opcode == oLocalForward ||
-		    opcode == oRemoteForward) {
+		remotefwd = (opcode == oRemoteForward);
+		dynamicfwd = (opcode == oDynamicForward);
+
+		if (!dynamicfwd) {
 			arg2 = strdelim(&s);
-			if (arg2 == NULL || *arg2 == '\0')
-				fatal("%.200s line %d: Missing target argument.",
-				    filename, linenum);
-
-			/* construct a string for parse_forward */
-			snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2);
-		} else if (opcode == oDynamicForward) {
+			if (arg2 == NULL || *arg2 == '\0') {
+				if (remotefwd)
+					dynamicfwd = 1;
+				else
+					fatal("%.200s line %d: Missing target "
+					    "argument.", filename, linenum);
+			} else {
+				/* construct a string for parse_forward */
+				snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg,
+				    arg2);
+			}
+		}
+		if (dynamicfwd)
 			strlcpy(fwdarg, arg, sizeof(fwdarg));
-		}
 
-		if (parse_forward(&fwd, fwdarg,
-		    opcode == oDynamicForward ? 1 : 0,
-		    opcode == oRemoteForward ? 1 : 0) == 0)
+		if (parse_forward(&fwd, fwdarg, dynamicfwd, remotefwd) == 0)
 			fatal("%.200s line %d: Bad forwarding specification.",
 			    filename, linenum);
 
 		if (*activep) {
-			if (opcode == oLocalForward ||
-			    opcode == oDynamicForward)
-				add_local_forward(options, &fwd);
-			else if (opcode == oRemoteForward)
+			if (remotefwd) {
 				add_remote_forward(options, &fwd);
+			} else {
+				add_local_forward(options, &fwd);
+			}
 		}
 		break;
 
diff --git a/ssh.1 b/ssh.1
index 3aacec41..2ab1697f 100644
--- a/ssh.1
+++ b/ssh.1
@@ -33,8 +33,8 @@
 .\" (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.383 2017/06/09 06:43:01 djm Exp $
-.Dd $Mdocdate: June 9 2017 $
+.\" $OpenBSD: ssh.1,v 1.384 2017/09/21 19:16:53 markus Exp $
+.Dd $Mdocdate: September 21 2017 $
 .Dt SSH 1
 .Os
 .Sh NAME
@@ -592,21 +592,30 @@ Causes most warning and diagnostic messages to be suppressed.
 .Ar remote_socket : local_socket
 .Sm on
 .Xc
+.It Fl R Xo
+.Sm off
+.Oo Ar bind_address : Oc
+.Ar port
+.Sm on
+.Xc
 Specifies that connections to the given TCP port or Unix socket on the remote
-(server) host are to be forwarded to the given host and port, or Unix socket,
-on the local side.
+(server) host are to be forwarded to the local side.
+.Pp
 This works by allocating a socket to listen to either a TCP
 .Ar port
 or to a Unix socket on the remote side.
 Whenever a connection is made to this port or Unix socket, the
 connection is forwarded over the secure channel, and a connection
-is made to either
+is made from the local machine to either an explicit destination specified by
 .Ar host
 port
 .Ar hostport ,
 or
 .Ar local_socket ,
-from the local machine.
+or, if no explicit destination was specified,
+.Nm
+will act as a SOCKS 4/5 proxy and forward connections to the destinations
+requested by the remote SOCKS client.
 .Pp
 Port forwardings can also be specified in the configuration file.
 Privileged ports can be forwarded only when
diff --git a/ssh.c b/ssh.c
index ecc50f37..ae37432b 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.463 2017/09/12 06:32:07 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.464 2017/09/21 19:16:53 markus Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -868,7 +868,8 @@ main(int ac, char **av)
 			break;
 
 		case 'R':
-			if (parse_forward(&fwd, optarg, 0, 1)) {
+			if (parse_forward(&fwd, optarg, 0, 1) ||
+			    parse_forward(&fwd, optarg, 1, 1)) {
 				add_remote_forward(&options, &fwd);
 			} else {
 				fprintf(stderr,
diff --git a/ssh_config.5 b/ssh_config.5
index ca5a4110..eab8dd01 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,8 +33,8 @@
 .\" (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.255 2017/09/04 06:34:43 jmc Exp $
-.Dd $Mdocdate: September 4 2017 $
+.\" $OpenBSD: ssh_config.5,v 1.256 2017/09/21 19:16:53 markus Exp $
+.Dd $Mdocdate: September 21 2017 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -1298,13 +1298,19 @@ accept the tokens described in the
 section.
 .It Cm RemoteForward
 Specifies that a TCP port on the remote machine be forwarded over
-the secure channel to the specified host and port from the local machine.
+the secure channel.
+The remote port may either be fowarded to a specified host and port
+from the local machine, or may act as a SOCKS 4/5 proxy that allows a remote
+client to connect to arbitrary destinations from the local machine.
 The first argument must be
 .Sm off
 .Oo Ar bind_address : Oc Ar port
 .Sm on
-and the second argument must be
-.Ar host : Ns Ar hostport .
+If forwarding to a specific destination then the second argument must be
+.Ar host : Ns Ar hostport ,
+otherwise if no destination argument is specified then the remote forwarding
+will be established as a SOCKS proxy.
+.Pp
 IPv6 addresses can be specified by enclosing addresses in square brackets.
 Multiple forwardings may be specified, and additional
 forwardings can be given on the command line.

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list