ssh: add PermitRemoteOpen for remote dynamic forwarding with SOCKS
authormarkus <markus@openbsd.org>
Mon, 15 Feb 2021 20:43:15 +0000 (20:43 +0000)
committermarkus <markus@openbsd.org>
Mon, 15 Feb 2021 20:43:15 +0000 (20:43 +0000)
ok djm@, dtucker@

usr.bin/ssh/channels.c
usr.bin/ssh/readconf.c
usr.bin/ssh/readconf.h
usr.bin/ssh/ssh.1
usr.bin/ssh/ssh.c
usr.bin/ssh/ssh_config.5

index 06a3cc0..4f55aa4 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.404 2021/01/27 09:26:53 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.405 2021/02/15 20:43:15 markus Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -4437,9 +4437,28 @@ rdynamic_connect_prepare(struct ssh *ssh, char *ctype, char *rname)
 static int
 rdynamic_connect_finish(struct ssh *ssh, Channel *c)
 {
+       struct ssh_channels *sc = ssh->chanctxt;
+       struct permission_set *pset = &sc->local_perms;
+       struct permission *perm;
        struct channel_connect cctx;
+       u_int i, permit_adm = 1;
        int sock;
 
+       if (pset->num_permitted_admin > 0) {
+               permit_adm = 0;
+               for (i = 0; i < pset->num_permitted_admin; i++) {
+                       perm = &pset->permitted_admin[i];
+                       if (open_match(perm, c->path, c->host_port)) {
+                               permit_adm = 1;
+                               break;
+                       }
+               }
+       }
+       if (!permit_adm) {
+               debug_f("requested forward not permitted");
+               return -1;
+       }
+
        memset(&cctx, 0, sizeof(cctx));
        sock = connect_to_helper(ssh, c->path, c->host_port, SOCK_STREAM, NULL,
            NULL, &cctx, NULL, NULL);
index e42b4b0..baf9414 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.350 2021/01/26 05:32:21 dtucker Exp $ */
+/* $OpenBSD: readconf.c,v 1.351 2021/02/15 20:43:15 markus Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -133,6 +133,7 @@ typedef enum {
        oPasswordAuthentication,
        oChallengeResponseAuthentication, oXAuthLocation,
        oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward,
+       oPermitRemoteOpen,
        oCertificateFile, oAddKeysToAgent, oIdentityAgent,
        oUser, oEscapeChar, oProxyCommand,
        oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts,
@@ -233,6 +234,7 @@ static struct {
        { "macs", oMacs },
        { "remoteforward", oRemoteForward },
        { "localforward", oLocalForward },
+       { "permitremoteopen", oPermitRemoteOpen },
        { "user", oUser },
        { "host", oHost },
        { "match", oMatch },
@@ -304,6 +306,7 @@ static struct {
        { NULL, oBadOption }
 };
 
+static const char *lookup_opcode_name(OpCodes code);
 
 const char *
 kex_default_pk_alg(void)
@@ -898,9 +901,9 @@ process_config_line_depth(Options *options, struct passwd *pw, const char *host,
     const char *original_host, char *line, const char *filename,
     int linenum, int *activep, int flags, int *want_final_pass, int depth)
 {
-       char *s, **charptr, *endofnumber, *keyword, *arg, *arg2;
+       char *s, **charptr, *endofnumber, *keyword, *arg, *arg2, *p, ch;
        char **cpptr, ***cppptr, fwdarg[256];
-       u_int i, *uintptr, max_entries = 0;
+       u_int i, *uintptr, uvalue, max_entries = 0;
        int r, oactive, negated, opcode, *intptr, value, value2, cmdline = 0;
        int remotefwd, dynamicfwd;
        LogLevel *log_level_ptr;
@@ -1468,6 +1471,51 @@ parse_pubkey_algos:
                }
                break;
 
+       case oPermitRemoteOpen:
+               uintptr = &options->num_permitted_remote_opens;
+               cppptr = &options->permitted_remote_opens;
+               arg = strdelim(&s);
+               if (!arg || *arg == '\0')
+                       fatal("%s line %d: missing %s specification",
+                           filename, linenum, lookup_opcode_name(opcode));
+               uvalue = *uintptr;      /* modified later */
+               if (strcmp(arg, "any") == 0 || strcmp(arg, "none") == 0) {
+                       if (*activep && uvalue == 0) {
+                               *uintptr = 1;
+                               *cppptr = xcalloc(1, sizeof(**cppptr));
+                               (*cppptr)[0] = xstrdup(arg);
+                       }
+                       break;
+               }
+               for (; arg != NULL && *arg != '\0'; arg = strdelim(&s)) {
+                       arg2 = xstrdup(arg);
+                       ch = '\0';
+                       p = hpdelim2(&arg, &ch);
+                       if (p == NULL || ch == '/') {
+                               fatal("%s line %d: missing host in %s",
+                                   filename, linenum,
+                                   lookup_opcode_name(opcode));
+                       }
+                       p = cleanhostname(p);
+                       /*
+                        * don't want to use permitopen_port to avoid
+                        * dependency on channels.[ch] here.
+                        */
+                       if (arg == NULL ||
+                           (strcmp(arg, "*") != 0 && a2port(arg) <= 0)) {
+                               fatal("%s line %d: bad port number in %s",
+                                   filename, linenum,
+                                   lookup_opcode_name(opcode));
+                       }
+                       if (*activep && uvalue == 0) {
+                               opt_array_append(filename, linenum,
+                                   lookup_opcode_name(opcode),
+                                   cppptr, uintptr, arg2);
+                       }
+                       free(arg2);
+               }
+               break;
+
        case oClearAllForwardings:
                intptr = &options->clear_forwardings;
                goto parse_flag;
@@ -2159,6 +2207,8 @@ initialize_options(Options * options)
        options->num_local_forwards = 0;
        options->remote_forwards = NULL;
        options->num_remote_forwards = 0;
+       options->permitted_remote_opens = NULL;
+       options->num_permitted_remote_opens = 0;
        options->log_facility = SYSLOG_FACILITY_NOT_SET;
        options->log_level = SYSLOG_LEVEL_NOT_SET;
        options->num_log_verbose = 0;
@@ -3105,6 +3155,13 @@ dump_client_config(Options *o, const char *host)
 
        /* Special cases */
 
+       /* PermitRemoteOpen */
+       if (o->num_permitted_remote_opens == 0)
+               printf("%s any\n", lookup_opcode_name(oPermitRemoteOpen));
+       else
+               dump_cfg_strarray_oneline(oPermitRemoteOpen,
+                   o->num_permitted_remote_opens, o->permitted_remote_opens);
+
        /* AddKeysToAgent */
        if (o->add_keys_to_agent_lifespan <= 0)
                dump_cfg_fmtint(oAddKeysToAgent, o->add_keys_to_agent);
index 6e7e95e..2fba866 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.139 2021/01/26 05:32:21 dtucker Exp $ */
+/* $OpenBSD: readconf.h,v 1.140 2021/02/15 20:43:15 markus Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -110,6 +110,10 @@ typedef struct {
        struct Forward *remote_forwards;
        int     clear_forwardings;
 
+       /* Restrict remote dynamic forwarding */
+       char  **permitted_remote_opens;
+       u_int   num_permitted_remote_opens;
+
        /* stdio forwarding (-W) host and port */
        char   *stdio_forward_host;
        int     stdio_forward_port;
index 5884b47..0a01767 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.1,v 1.418 2021/01/26 15:40:17 naddy Exp $
-.Dd $Mdocdate: January 26 2021 $
+.\" $OpenBSD: ssh.1,v 1.419 2021/02/15 20:43:15 markus Exp $
+.Dd $Mdocdate: February 15 2021 $
 .Dt SSH 1
 .Os
 .Sh NAME
@@ -531,6 +531,7 @@ For full details of the options listed below, and their possible values, see
 .It NumberOfPasswordPrompts
 .It PasswordAuthentication
 .It PermitLocalCommand
+.It PermitRemoteOpen
 .It PKCS11Provider
 .It Port
 .It PreferredAuthentications
index c5440d4..448934d 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.550 2021/02/02 22:36:59 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.551 2021/02/15 20:43:15 markus Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1853,12 +1853,53 @@ ssh_init_stdio_forwarding(struct ssh *ssh)
        channel_register_open_confirm(ssh, c->self, ssh_stdio_confirm, NULL);
 }
 
+static void
+ssh_init_forward_permissions(struct ssh *ssh, const char *what, char **opens,
+    u_int num_opens)
+{
+       u_int i;
+       int port;
+       char *addr, *arg, *oarg, ch;
+       int where = FORWARD_LOCAL;
+
+       channel_clear_permission(ssh, FORWARD_ADM, where);
+       if (num_opens == 0)
+               return; /* permit any */
+
+       /* handle keywords: "any" / "none" */
+       if (num_opens == 1 && strcmp(opens[0], "any") == 0)
+               return;
+       if (num_opens == 1 && strcmp(opens[0], "none") == 0) {
+               channel_disable_admin(ssh, where);
+               return;
+       }
+       /* Otherwise treat it as a list of permitted host:port */
+       for (i = 0; i < num_opens; i++) {
+               oarg = arg = xstrdup(opens[i]);
+               ch = '\0';
+               addr = hpdelim2(&arg, &ch);
+               if (addr == NULL || ch == '/')
+                       fatal_f("missing host in %s", what);
+               addr = cleanhostname(addr);
+               if (arg == NULL || ((port = permitopen_port(arg)) < 0))
+                       fatal_f("bad port number in %s", what);
+               /* Send it to channels layer */
+               channel_add_permission(ssh, FORWARD_ADM,
+                   where, addr, port);
+               free(oarg);
+       }
+}
+
 static void
 ssh_init_forwarding(struct ssh *ssh, char **ifname)
 {
        int success = 0;
        int i;
 
+       ssh_init_forward_permissions(ssh, "permitremoteopen",
+           options.permitted_remote_opens,
+           options.num_permitted_remote_opens);
+
        if (options.exit_on_forward_failure)
                forward_confirms_pending = 0; /* track pending requests */
        /* Initiate local TCP/IP port forwardings. */
index 29a199c..99e7033 100644 (file)
@@ -33,7 +33,7 @@
 .\" (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.346 2021/02/15 11:09:22 dlg Exp $
+.\" $OpenBSD: ssh_config.5,v 1.347 2021/02/15 20:43:15 markus Exp $
 .Dd $Mdocdate: February 15 2021 $
 .Dt SSH_CONFIG 5
 .Os
@@ -1291,6 +1291,42 @@ The argument must be
 or
 .Cm no
 (the default).
+.It Cm PermitRemoteOpen
+Specifies the destinations to which remote TCP port forwarding is permitted when
+.Cm RemoteForward
+is used as a SOCKS proxy.
+The forwarding specification must be one of the following forms:
+.Pp
+.Bl -item -offset indent -compact
+.It
+.Cm PermitRemoteOpen
+.Sm off
+.Ar host : port
+.Sm on
+.It
+.Cm PermitRemoteOpen
+.Sm off
+.Ar IPv4_addr : port
+.Sm on
+.It
+.Cm PermitRemoteOpen
+.Sm off
+.Ar \&[ IPv6_addr \&] : port
+.Sm on
+.El
+.Pp
+Multiple forwards may be specified by separating them with whitespace.
+An argument of
+.Cm any
+can be used to remove all restrictions and permit any forwarding requests.
+An argument of
+.Cm none
+can be used to prohibit all forwarding requests.
+The wildcard
+.Sq *
+can be used for host or port to allow all hosts or ports respectively.
+Otherwise, no pattern matching or address lookups are performed on supplied
+names.
 .It Cm PKCS11Provider
 Specifies which PKCS#11 provider to use or
 .Cm none
@@ -1485,6 +1521,9 @@ If forwarding to a specific destination then the second argument must be
 or a Unix domain socket path,
 otherwise if no destination argument is specified then the remote forwarding
 will be established as a SOCKS proxy.
+When acting as a SOCKS proxy the destination of the connection can be
+restricted by
+.Cm PermitRemoteOpen .
 .Pp
 IPv6 addresses can be specified by enclosing addresses in square brackets.
 Multiple forwardings may be specified, and additional