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
This commit is contained in:
markus@openbsd.org 2017-09-21 19:16:53 +00:00 committed by Damien Miller
parent 36945fa103
commit 609d7a66ce
6 changed files with 346 additions and 118 deletions

View File

@ -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 @@ 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));
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 @@ 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 */
/*

View File

@ -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

View File

@ -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 @@ 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) {
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;

21
ssh.1
View File

@ -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

5
ssh.c
View File

@ -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 @@ 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,

View File

@ -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.