]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream commit
authormarkus@openbsd.org <markus@openbsd.org>
Thu, 21 Sep 2017 19:16:53 +0000 (19:16 +0000)
committerDamien Miller <djm@mindrot.org>
Thu, 21 Sep 2017 23:14:53 +0000 (09:14 +1000)
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
channels.h
readconf.c
ssh.1
ssh.c
ssh_config.5

index 89b7d34864d9a78e1bf5c604c00ae481761331db..8ef37c45348b7d4f89ea30841f52809e6df467a8 100644 (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 ((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 (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);
                }
-               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 */
 
 /*
index d1cf5dc6a8c5852405737552a5878028641ea2bb..126b043454eb12d593726f0f487c15c5630132cf 100644 (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
 
index 4f38b27cf708b7c907a6ef1b842ba59eed3fa679..f63894f9ca158bd1db59859e89718104a051efde 100644 (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) {
-                       arg2 = strdelim(&s);
-                       if (arg2 == NULL || *arg2 == '\0')
-                               fatal("%.200s line %d: Missing target argument.",
-                                   filename, linenum);
+               remotefwd = (opcode == oRemoteForward);
+               dynamicfwd = (opcode == oDynamicForward);
 
-                       /* construct a string for parse_forward */
-                       snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2);
-               } else if (opcode == oDynamicForward) {
-                       strlcpy(fwdarg, arg, sizeof(fwdarg));
+               if (!dynamicfwd) {
+                       arg2 = strdelim(&s);
+                       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 3aacec415ef7e446fc283edefb03ea5cd9c95542..2ab1697f95deb98e8e457fb17ee6ed0a7bc93fab 100644 (file)
--- 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 ecc50f37eb0bc21f00106f7f84c17b6e852b4028..ae37432bd47e358a076e38a73a3d15786c4f8c0a 100644 (file)
--- 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 @@ 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,
index ca5a411033d6f592d72a0d3a7483b4ef55e1e97f..eab8dd01c22a7f9477b5b74fd710a217b85efedc 100644 (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.