[PATCH] Add "permitlisten" support for -R style forwards

Philipp Heckel philipp.heckel at gmail.com
Mon May 8 05:17:31 AEST 2017


Hi there,

this patch adds support for per-key restriction of -R style forwards
via a "permitlisten"-option in the authorized_keys file -- similar to
the "permitopen"-option for -L style forwards.

This is desirable if you want to have restricted accounts/keys that
can only be used for -R style forwards on certain ports.

With this example authorized_keys file:
  restrict,permitlisten="localhost:8080" ssh-rsa AAAAB3Nza...

This is allowed:
$ ssh -R 8080:localhost:80 root at localhost -N

While this is not allowed (note port 8081):
$ ssh -R 8081:localhost:80 root at localhost -N
Error: remote port forwarding failed for listen port 8081

This is a preliminary patch (no support for a servconf option
"PermitListen" yet), because I wanted to get early feedback before
continuing. 

Do you think this approach is correct? Would this be a desirable
feature? Is "permitlisten" the correct name for this? Or would 
"permitropen", "permitremoteopen" be better suited?

My WIP branch/pull can also be found here: 
https://github.com/openssh/openssh-portable/pull/65

Best,
Philipp Heckel

---
 auth-options.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++
 channels.c     | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 channels.h     |  3 +++
 serverloop.c   |  5 +++--
 4 files changed, 128 insertions(+), 2 deletions(-)

diff --git a/auth-options.c b/auth-options.c
index 57b49f7..61034be 100644
--- a/auth-options.c
+++ b/auth-options.c
@@ -82,6 +82,7 @@ auth_clear_options(void)
 	authorized_principals = NULL;
 	forced_tun_device = -1;
 	channel_clear_permitted_opens();
+	channel_clear_permitted_listens();
 }
 
 /*
@@ -383,6 +384,61 @@ auth_parse_options(struct passwd *pw, char *opts, char *file, u_long linenum)
 			free(patterns);
 			goto next_option;
 		}
+		cp = "permitlisten=\"";
+		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
+			char *host, *p;
+			int port;
+			char *patterns = xmalloc(strlen(opts) + 1);
+
+			opts += strlen(cp);
+			i = 0;
+			while (*opts) {
+				if (*opts == '"')
+					break;
+				if (*opts == '\\' && opts[1] == '"') {
+					opts += 2;
+					patterns[i++] = '"';
+					continue;
+				}
+				patterns[i++] = *opts++;
+			}
+			if (!*opts) {
+				debug("%.100s, line %lu: missing end quote",
+				    file, linenum);
+				auth_debug_add("%.100s, line %lu: missing "
+				    "end quote", file, linenum);
+				free(patterns);
+				goto bad_option;
+			}
+			patterns[i] = '\0';
+			opts++;
+			p = patterns;
+			/* XXX - add streamlocal support */
+			host = hpdelim(&p);
+			if (host == NULL || strlen(host) >= NI_MAXHOST) {
+				debug("%.100s, line %lu: Bad permitlisten "
+				    "specification <%.100s>", file, linenum,
+				    patterns);
+				auth_debug_add("%.100s, line %lu: "
+				    "Bad permitlisten specification", file,
+				    linenum);
+				free(patterns);
+				goto bad_option;
+			}
+			host = cleanhostname(host);
+			if (p == NULL || (port = permitopen_port(p)) < 0) {
+				debug("%.100s, line %lu: Bad permitlisten port "
+				    "<%.100s>", file, linenum, p ? p : "");
+				auth_debug_add("%.100s, line %lu: "
+				    "Bad permitopen port", file, linenum);
+				free(patterns);
+				goto bad_option;
+			}
+			if ((options.allow_tcp_forwarding & FORWARD_REMOTE) != 0)
+				channel_add_permitted_listens(host, port);
+			free(patterns);
+			goto next_option;
+		}
 		cp = "tunnel=\"";
 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
 			char *tun = NULL;
diff --git a/channels.c b/channels.c
index 4092a67..551c2f0 100644
--- a/channels.c
+++ b/channels.c
@@ -129,12 +129,18 @@ static ForwardPermission *permitted_opens = NULL;
 /* List of all permitted host/port pairs to connect by the admin. */
 static ForwardPermission *permitted_adm_opens = NULL;
 
+/* List of all permitted remote host/port pairs to connect by the user. */
+static ForwardPermission *permitted_listens = NULL;
+
 /* Number of permitted host/port pairs in the array permitted by the user. */
 static int num_permitted_opens = 0;
 
 /* Number of permitted host/port pair in the array permitted by the admin. */
 static int num_adm_permitted_opens = 0;
 
+/* Number of permitted remote host/port pairs. */
+static int num_permitted_listens = 0;
+
 /* special-case port number meaning allow any port */
 #define FWD_PERMIT_ANY_PORT	0
 
@@ -148,6 +154,10 @@ static int num_adm_permitted_opens = 0;
  */
 static int all_opens_permitted = 0;
 
+/**
+ * If this is true, all remote opens are permitted.
+ */
+static int all_listens_permitted = 0;
 
 /* -- X11 forwarding */
 
@@ -3503,6 +3513,23 @@ channel_add_permitted_opens(char *host, int port)
 	all_opens_permitted = 0;
 }
 
+void
+channel_add_permitted_listens(char *host, int port)
+{
+	debug("allow remote port forwarding to host %s port %d", host, port);
+
+	permitted_listens = xreallocarray(permitted_listens,
+        num_permitted_listens + 1, sizeof(*permitted_listens));
+	permitted_listens[num_permitted_listens].host_to_connect = xstrdup(host);
+	permitted_listens[num_permitted_listens].port_to_connect = port;
+	permitted_listens[num_permitted_listens].listen_host = NULL;
+	permitted_listens[num_permitted_listens].listen_path = NULL;
+	permitted_listens[num_permitted_listens].listen_port = 0;
+	num_permitted_listens++;
+
+	all_listens_permitted = 0;
+}
+
 /*
  * Update the listen port for a dynamic remote forward, after
  * the actual 'newport' has been allocated. If 'newport' < 0 is
@@ -3592,6 +3619,21 @@ channel_clear_adm_permitted_opens(void)
 }
 
 void
+channel_clear_permitted_listens(void)
+{
+    int i;
+
+    for (i = 0; i < num_permitted_listens; i++) {
+        free(permitted_listens[i].host_to_connect);
+        free(permitted_listens[i].listen_host);
+        free(permitted_listens[i].listen_path);
+    }
+    free(permitted_listens);
+    permitted_listens = NULL;
+    num_permitted_listens = 0;
+}
+
+void
 channel_print_adm_permitted_opens(void)
 {
 	int i;
@@ -3885,6 +3927,30 @@ channel_connect_to_path(const char *path, char *ctype, char *rname)
 	return connect_to(path, PORT_STREAMLOCAL, ctype, rname);
 }
 
+/* Check if connecting to that port is permitted and connect. */
+int
+channel_connect_check_permitted_listens(const char *host, u_short port)
+{
+    int i, permit = 1;
+
+    permit = all_listens_permitted;
+    if (!permit) {
+        for (i = 0; i < num_permitted_listens; i++)
+            if (open_match(&permitted_listens[i], host, port)) {
+                permit = 1;
+                break;
+            }
+    }
+
+    if (!permit) {
+        logit("Received request for remote forward to host %.100s port %d, "
+                      "but the request was denied.", host, port);
+        return -1;
+    }
+
+    return 0;
+}
+
 void
 channel_send_window_changes(void)
 {
diff --git a/channels.h b/channels.h
index 4e9b77d..7d55055 100644
--- a/channels.h
+++ b/channels.h
@@ -267,15 +267,18 @@ struct ForwardOptions;
 void	 channel_set_af(int af);
 void     channel_permit_all_opens(void);
 void	 channel_add_permitted_opens(char *, int);
+void	 channel_add_permitted_listens(char *, int);
 int	 channel_add_adm_permitted_opens(char *, int);
 void	 channel_disable_adm_local_opens(void);
 void	 channel_update_permitted_opens(int, int);
 void	 channel_clear_permitted_opens(void);
 void	 channel_clear_adm_permitted_opens(void);
+void	 channel_clear_permitted_listens(void);
 void 	 channel_print_adm_permitted_opens(void);
 Channel	*channel_connect_to_port(const char *, u_short, char *, char *, int *,
 	     const char **);
 Channel *channel_connect_to_path(const char *, char *, char *);
+int channel_connect_check_permitted_listens(const char *host, u_short port);
 Channel	*channel_connect_stdio_fwd(const char*, u_short, int, int);
 Channel	*channel_connect_by_listen_address(const char *, u_short,
 	     char *, char *);
diff --git a/serverloop.c b/serverloop.c
index 2976f55..50d8feb 100644
--- a/serverloop.c
+++ b/serverloop.c
@@ -729,11 +729,12 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
 		    fwd.listen_host, fwd.listen_port);
 
 		/* check permissions */
-		if ((options.allow_tcp_forwarding & FORWARD_REMOTE) == 0 ||
+		if (channel_connect_check_permitted_listens(fwd.listen_host, fwd.listen_port) < 0 &&
+		    ((options.allow_tcp_forwarding & FORWARD_REMOTE) == 0 ||
 		    no_port_forwarding_flag || options.disable_forwarding ||
 		    (!want_reply && fwd.listen_port == 0) ||
 		    (fwd.listen_port != 0 &&
-		     !bind_permitted(fwd.listen_port, pw->pw_uid))) {
+		     !bind_permitted(fwd.listen_port, pw->pw_uid)))) {
 			success = 0;
 			packet_send_debug("Server has disabled port forwarding.");
 		} else {
-- 
2.7.4



More information about the openssh-unix-dev mailing list