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
diff --git a/channels.c b/channels.c
index 89b7d34..8ef37c4 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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -209,6 +209,8 @@
/* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
/* 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
#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 @@
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 @@
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 @@
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 @@
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 @@
/* 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 @@
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 @@
}
}
+/* 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 @@
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 @@
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 @@
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 @@
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 @@
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;
@@ -2280,6 +2379,27 @@
}
/*
+ * 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 @@
{
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 @@
/* 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 @@
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 @@
}
/*
- * 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 @@
if (strlen(name) > sizeof(sunaddr->sun_path)) {
error("%.100s: %.100s", name, strerror(ENAMETOOLONG));
- return (NULL);
+ return -1;
}
/*
@@ -3974,18 +4095,18 @@
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 @@
*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));
+ return -1;
+ }
+
+ return sock;
+}
+
+/* Return CONNECTING channel to remote host:port or local socket path */
+static Channel *
+connect_to(struct ssh *ssh, const char *host, int port,
+ char *ctype, char *rname)
+{
+ 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;
-}
-/* Return CONNECTING channel to remote host:port or local socket path */
-static Channel *
-connect_to(struct ssh *ssh, const char *name, int port,
- char *ctype, char *rname)
-{
- return connect_to_reason(ssh, name, port, ctype, rname, NULL, NULL);
+ return c;
}
/*
@@ -4038,6 +4173,9 @@
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 @@
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 @@
*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 @@
}
}
+/* 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 d1cf5dc..126b043 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@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 4f38b27..f63894f 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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -836,6 +836,7 @@
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 @@
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) {
- strlcpy(fwdarg, arg, sizeof(fwdarg));
+ 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 3aacec4..2ab1697 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 @@
.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 ecc50f3..ae37432 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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -868,7 +868,8 @@
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 ca5a411..eab8dd0 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 @@
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.