From 609d7a66ce578abf259da2d5f6f68795c2bda731 Mon Sep 17 00:00:00 2001 From: "markus@openbsd.org" Date: Thu, 21 Sep 2017 19:16:53 +0000 Subject: [PATCH] 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 89b7d3486..8ef37c453 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 * Copyright (c) 1995 Tatu Ylonen , 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 */ /* diff --git a/channels.h b/channels.h index d1cf5dc6a..126b04345 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 @@ -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 4f38b27cf..f63894f9c 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 * Copyright (c) 1995 Tatu Ylonen , 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; diff --git a/ssh.1 b/ssh.1 index 3aacec415..2ab1697f9 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 ecc50f37e..ae37432bd 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 * Copyright (c) 1995 Tatu Ylonen , 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 ca5a41103..eab8dd01c 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.