Feature request: virtual servers

Darren Tucker dtucker at zip.com.au
Tue May 1 20:48:52 EST 2012


On Thu, Apr 26, 2012 at 08:34:32AM +0200, Philipp Marek wrote:
> Could you put that in OpenSSH, so that -portable and the distributions can 
> pick that up sometime?

We're looking at it.

In the mean time, here's an updated patch that:
 - fixes a problem with the "Match Port" code
 - fixes the regress tests and adds a couple more
 - some minor cleanups
 - applies to openssh-6.0p1

Index: auth.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh/auth.c,v
retrieving revision 1.149
diff -u -p -r1.149 auth.c
--- auth.c	29 May 2011 11:40:42 -0000	1.149
+++ auth.c	1 May 2012 10:36:24 -0000
@@ -544,9 +544,14 @@ getpwnamallow(const char *user)
 #endif
 #endif
 	struct passwd *pw;
+	ConnectionInfo connection_info;
 
-	parse_server_match_config(&options, user,
-	    get_canonical_hostname(options.use_dns), get_remote_ipaddr());
+	connection_info.user = user;
+	connection_info.host = get_canonical_hostname(options.use_dns);
+	connection_info.address = get_remote_ipaddr();
+	connection_info.laddress = get_local_ipaddr(packet_get_connection_in());
+	connection_info.lport = get_local_port();
+	parse_server_match_config(&options, &connection_info);
 
 #if defined(_AIX) && defined(HAVE_SETAUTHDB)
 	aix_setauthdb(user);
Index: servconf.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh/servconf.c,v
retrieving revision 1.220
diff -u -p -r1.220 servconf.c
--- servconf.c	2 Oct 2011 07:57:38 -0000	1.220
+++ servconf.c	1 May 2012 10:36:24 -0000
@@ -598,19 +598,20 @@ out:
 }
 
 static int
-match_cfg_line(char **condition, int line, const char *user, const char *host,
-    const char *address)
+match_cfg_line(char **condition, int line, ConnectionInfo *ci)
 {
-	int result = 1;
+	int result = 1, port;
 	char *arg, *attrib, *cp = *condition;
 	size_t len;
 
-	if (user == NULL)
+	if (ci == NULL)
 		debug3("checking syntax for 'Match %s'", cp);
 	else
-		debug3("checking match for '%s' user %s host %s addr %s", cp,
-		    user ? user : "(null)", host ? host : "(null)",
-		    address ? address : "(null)");
+		debug3("checking match for '%s' user %s host %s addr %s "
+		    "laddr %s lport %d", cp, ci->user ? ci->user : "(null)",
+		    ci->host ? ci->host : "(null)",
+		    ci->address ? ci->address : "(null)",
+		    ci->laddress ? ci->laddress : "(null)", ci->lport);
 
 	while ((attrib = strdelim(&cp)) && *attrib != '\0') {
 		if ((arg = strdelim(&cp)) == NULL || *arg == '\0') {
@@ -619,37 +620,63 @@ match_cfg_line(char **condition, int lin
 		}
 		len = strlen(arg);
 		if (strcasecmp(attrib, "user") == 0) {
-			if (!user) {
+			if (ci == NULL || ci->user == NULL) {
 				result = 0;
 				continue;
 			}
-			if (match_pattern_list(user, arg, len, 0) != 1)
+			if (match_pattern_list(ci->user, arg, len, 0) != 1)
 				result = 0;
 			else
 				debug("user %.100s matched 'User %.100s' at "
-				    "line %d", user, arg, line);
+				    "line %d", ci->user, arg, line);
 		} else if (strcasecmp(attrib, "group") == 0) {
-			switch (match_cfg_line_group(arg, line, user)) {
+			if (ci == NULL || ci->user == NULL) {
+				result = 0;
+				continue;
+			}
+			switch (match_cfg_line_group(arg, line, ci->user)) {
 			case -1:
 				return -1;
 			case 0:
 				result = 0;
 			}
 		} else if (strcasecmp(attrib, "host") == 0) {
-			if (!host) {
+			if (ci == NULL || ci->host == NULL) {
 				result = 0;
 				continue;
 			}
-			if (match_hostname(host, arg, len) != 1)
+			if (match_hostname(ci->host, arg, len) != 1)
 				result = 0;
 			else
 				debug("connection from %.100s matched 'Host "
-				    "%.100s' at line %d", host, arg, line);
+				    "%.100s' at line %d", ci->host, arg, line);
 		} else if (strcasecmp(attrib, "address") == 0) {
-			switch (addr_match_list(address, arg)) {
+			if (ci == NULL || ci->address == NULL) {
+				result = 0;
+				continue;
+			}
+			switch (addr_match_list(ci->address, arg)) {
 			case 1:
 				debug("connection from %.100s matched 'Address "
-				    "%.100s' at line %d", address, arg, line);
+				    "%.100s' at line %d", ci->address, arg, line);
+				break;
+			case 0:
+			case -1:
+				result = 0;
+				break;
+			case -2:
+				return -1;
+			}
+		} else if (strcasecmp(attrib, "localaddress") == 0){
+			if (ci == NULL || ci->laddress == NULL) {
+				result = 0;
+				continue;
+			}
+			switch (addr_match_list(ci->laddress, arg)) {
+			case 1:
+				debug("connection from %.100s matched "
+				    "'LocalAddress %.100s' at line %d",
+				    ci->laddress, arg, line);
 				break;
 			case 0:
 			case -1:
@@ -658,12 +685,25 @@ match_cfg_line(char **condition, int lin
 			case -2:
 				return -1;
 			}
+		} else if (strcasecmp(attrib, "localport") == 0) {
+			if ((port = a2port(arg)) == -1) {
+				error("Invalid LocalPort '%s' on Match line",
+				    arg);
+				return -1;
+			}
+			if (ci == NULL) {
+				result = 0;
+				continue;
+			}
+			/* TODO support port lists */
+			if (port != ci->lport)
+				result = 0;
 		} else {
 			error("Unsupported Match attribute %s", attrib);
 			return -1;
 		}
 	}
-	if (user != NULL)
+	if (ci != NULL)
 		debug3("match %sfound", result ? "" : "not ");
 	*condition = cp;
 	return result;
@@ -710,8 +750,8 @@ static const struct multistate multistat
 
 int
 process_server_config_line(ServerOptions *options, char *line,
-    const char *filename, int linenum, int *activep, const char *user,
-    const char *host, const char *address)
+    const char *filename, int linenum, int *activep,
+    ConnectionInfo *connectinfo)
 {
 	char *cp, **charptr, *arg, *p;
 	int cmdline = 0, *intptr, value, value2, n;
@@ -742,7 +782,7 @@ process_server_config_line(ServerOptions
 	if (*activep && opcode != sMatch)
 		debug3("%s:%d setting %s %s", filename, linenum, arg, cp);
 	if (*activep == 0 && !(flags & SSHCFG_MATCH)) {
-		if (user == NULL) {
+		if (connectinfo == NULL) {
 			fatal("%s line %d: Directive '%s' is not allowed "
 			    "within a Match block", filename, linenum, arg);
 		} else { /* this is a directive we have already processed */
@@ -1313,7 +1353,7 @@ process_server_config_line(ServerOptions
 		if (cmdline)
 			fatal("Match directive not supported as a command-line "
 			   "option");
-		value = match_cfg_line(&cp, linenum, user, host, address);
+		value = match_cfg_line(&cp, linenum, connectinfo);
 		if (value < 0)
 			fatal("%s line %d: Bad Match condition", filename,
 			    linenum);
@@ -1451,16 +1491,57 @@ load_server_config(const char *filename,
 }
 
 void
-parse_server_match_config(ServerOptions *options, const char *user,
-    const char *host, const char *address)
+parse_server_match_config(ServerOptions *options, ConnectionInfo *connectinfo)
 {
 	ServerOptions mo;
 
 	initialize_server_options(&mo);
-	parse_server_config(&mo, "reprocess config", &cfg, user, host, address);
+	parse_server_config(&mo, "reprocess config", &cfg, connectinfo);
 	copy_set_server_options(options, &mo, 0);
 }
 
+int parse_server_match_testspec(ConnectionInfo *ci, char *spec)
+{
+	char *p;
+
+	while ((p = strsep(&spec, ",")) && *p != '\0') {
+		if (strncmp(p, "addr=", 5) == 0) {
+			ci->address = xstrdup(p + 5);
+		} else if (strncmp(p, "host=", 5) == 0) {
+			ci->host = xstrdup(p + 5);
+		} else if (strncmp(p, "user=", 5) == 0) {
+			ci->user = xstrdup(p + 5);
+		} else if (strncmp(p, "laddr=", 6) == 0) {
+			ci->laddress = xstrdup(p + 6);
+		} else if (strncmp(p, "lport=", 6) == 0) {
+			ci->lport = a2port(p + 6);
+			if (ci->lport == -1) {
+				fprintf(stderr, "Invalid port '%s' in test mode"
+				   " specification %s\n", p+6, p);
+				return -1;
+			}
+		} else {
+			fprintf(stderr, "Invalid test mode specification %s\n",
+			   p);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * returns 1 for a complete spec, 0 for partial spec and -1 for an
+ * empty spec.
+ */
+int server_match_spec_complete(ConnectionInfo *ci)
+{
+	if (ci->user && ci->host && ci->address)
+		return 1;	/* complete */
+	if (!ci->user && !ci->host && !ci->address)
+		return -1;	/* empty */
+	return 0;	/* partial */
+}
+
 /* Helper macros */
 #define M_CP_INTOPT(n) do {\
 	if (src->n != -1) \
@@ -1534,7 +1615,7 @@ copy_set_server_options(ServerOptions *d
 
 void
 parse_server_config(ServerOptions *options, const char *filename, Buffer *conf,
-    const char *user, const char *host, const char *address)
+    ConnectionInfo *connectinfo)
 {
 	int active, linenum, bad_options = 0;
 	char *cp, *obuf, *cbuf;
@@ -1542,11 +1623,11 @@ parse_server_config(ServerOptions *optio
 	debug2("%s: config %s len %d", __func__, filename, buffer_len(conf));
 
 	obuf = cbuf = xstrdup(buffer_ptr(conf));
-	active = user ? 0 : 1;
+	active = connectinfo ? 0 : 1;
 	linenum = 1;
 	while ((cp = strsep(&cbuf, "\n")) != NULL) {
 		if (process_server_config_line(options, cp, filename,
-		    linenum++, &active, user, host, address) != 0)
+		    linenum++, &active, connectinfo) != 0)
 			bad_options++;
 	}
 	xfree(obuf);
Index: servconf.h
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh/servconf.h,v
retrieving revision 1.91
diff -u -p -r1.91 servconf.h
--- servconf.h	22 Jun 2011 22:30:03 -0000	1.91
+++ servconf.h	1 May 2012 10:36:24 -0000
@@ -168,6 +168,17 @@ typedef struct {
 	char   *authorized_principals_file;
 }       ServerOptions;
 
+
+/* Information about the incoming connection as used by Match */
+typedef struct {
+	const char *user;
+	const char *host;	/* possibly resolved hostname */
+	const char *address; 	/* remote address */
+	const char *laddress;	/* local address */
+	int lport;		/* local port */
+} ConnectionInfo;
+
+
 /*
  * These are string config options that must be copied between the
  * Match sub-config and the main config, and must be sent from the
@@ -185,12 +196,13 @@ typedef struct {
 void	 initialize_server_options(ServerOptions *);
 void	 fill_default_server_options(ServerOptions *);
 int	 process_server_config_line(ServerOptions *, char *, const char *, int,
-	     int *, const char *, const char *, const char *);
+	     int *, ConnectionInfo *);
 void	 load_server_config(const char *, Buffer *);
 void	 parse_server_config(ServerOptions *, const char *, Buffer *,
-	     const char *, const char *, const char *);
-void	 parse_server_match_config(ServerOptions *, const char *, const char *,
-	     const char *);
+	     ConnectionInfo *);
+void	 parse_server_match_config(ServerOptions *, ConnectionInfo *);
+int	 parse_server_match_testspec(ConnectionInfo *, char *);
+int	 server_match_spec_complete(ConnectionInfo *);
 void	 copy_set_server_options(ServerOptions *, ServerOptions *, int);
 void	 dump_config(ServerOptions *);
 char	*derelativise_path(const char *);
Index: sshd.8
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh/sshd.8,v
retrieving revision 1.225
diff -u -p -r1.225 sshd.8
--- sshd.8	2 Oct 2011 07:57:38 -0000	1.225
+++ sshd.8	10 Oct 2011 06:14:47 -0000
@@ -114,6 +114,8 @@ The connection parameters are supplied a
 The keywords are
 .Dq user ,
 .Dq host ,
+.Dq laddr ,
+.Dq lport ,
 and
 .Dq addr .
 All are required and may be supplied in any order, either with multiple
Index: sshd.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh/sshd.c,v
retrieving revision 1.411
diff -u -p -r1.411 sshd.c
--- sshd.c	14 Feb 2012 18:03:31 -0000	1.411
+++ sshd.c	1 May 2012 10:36:24 -0000
@@ -1320,14 +1320,14 @@ main(int ac, char **av)
 	int opt, i, j, on = 1;
 	int sock_in = -1, sock_out = -1, newsock = -1;
 	const char *remote_ip;
-	char *test_user = NULL, *test_host = NULL, *test_addr = NULL;
 	int remote_port;
-	char *line, *p, *cp;
+	char *line;
 	int config_s[2] = { -1 , -1 };
 	u_int64_t ibytes, obytes;
 	mode_t new_umask;
 	Key *key;
 	Authctxt *authctxt;
+	ConnectionInfo connection_info;
 
 #ifdef HAVE_SECUREWARE
 	(void)set_auth_parameters(ac, av);
@@ -1357,6 +1357,10 @@ main(int ac, char **av)
 	/* Initialize configuration options to their default values. */
 	initialize_server_options(&options);
 
+	connection_info.user = connection_info.host = connection_info.address
+	    = connection_info.laddress = NULL;
+	connection_info.lport = 0;
+
 	/* Parse command-line arguments. */
 	while ((opt = getopt(ac, av, "f:p:b:k:h:g:u:o:C:dDeiqrtQRT46")) != -1) {
 		switch (opt) {
@@ -1449,20 +1453,9 @@ main(int ac, char **av)
 			test_flag = 2;
 			break;
 		case 'C':
-			cp = optarg;
-			while ((p = strsep(&cp, ",")) && *p != '\0') {
-				if (strncmp(p, "addr=", 5) == 0)
-					test_addr = xstrdup(p + 5);
-				else if (strncmp(p, "host=", 5) == 0)
-					test_host = xstrdup(p + 5);
-				else if (strncmp(p, "user=", 5) == 0)
-					test_user = xstrdup(p + 5);
-				else {
-					fprintf(stderr, "Invalid test "
-					    "mode specification %s\n", p);
-					exit(1);
-				}
-			}
+			if (parse_server_match_testspec(&connection_info,
+			    optarg) == -1)
+				exit(1);
 			break;
 		case 'u':
 			utmp_len = (u_int)strtonum(optarg, 0, MAXHOSTNAMELEN+1, NULL);
@@ -1474,7 +1467,7 @@ main(int ac, char **av)
 		case 'o':
 			line = xstrdup(optarg);
 			if (process_server_config_line(&options, line,
-			    "command-line", 0, NULL, NULL, NULL, NULL) != 0)
+			    "command-line", 0, NULL, NULL) != 0)
 				exit(1);
 			xfree(line);
 			break;
@@ -1530,13 +1523,10 @@ main(int ac, char **av)
 	 * the parameters we need.  If we're not doing an extended test,
 	 * do not silently ignore connection test params.
 	 */
-	if (test_flag >= 2 &&
-	   (test_user != NULL || test_host != NULL || test_addr != NULL)
-	    && (test_user == NULL || test_host == NULL || test_addr == NULL))
+	if (test_flag >= 2 && server_match_spec_complete(&connection_info) == 0)
 		fatal("user, host and addr are all required when testing "
 		   "Match configs");
-	if (test_flag < 2 && (test_user != NULL || test_host != NULL ||
-	    test_addr != NULL))
+	if (test_flag < 2 && server_match_spec_complete(&connection_info) >= 0)
 		fatal("Config test connection parameter (-C) provided without "
 		   "test mode (-T)");
 
@@ -1548,7 +1538,7 @@ main(int ac, char **av)
 		load_server_config(config_file_name, &cfg);
 
 	parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name,
-	    &cfg, NULL, NULL, NULL);
+	    &cfg, NULL);
 
 	seed_rng();
 
@@ -1710,9 +1700,8 @@ main(int ac, char **av)
 	}
 
 	if (test_flag > 1) {
-		if (test_user != NULL && test_addr != NULL && test_host != NULL)
-			parse_server_match_config(&options, test_user,
-			    test_host, test_addr);
+		if (server_match_spec_complete(&connection_info) == 1)
+			parse_server_match_config(&options, &connection_info);
 		dump_config(&options);
 	}
 
Index: sshd_config.5
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh/sshd_config.5,v
retrieving revision 1.143
diff -u -p -r1.143 sshd_config.5
--- sshd_config.5	22 Sep 2011 11:37:13 -0000	1.143
+++ sshd_config.5	1 May 2012 10:36:24 -0000
@@ -675,6 +675,8 @@ The available criteria are
 .Cm User ,
 .Cm Group ,
 .Cm Host ,
+.Cm LocalAddress ,
+.Cm LocalPort ,
 and
 .Cm Address .
 The match patterns may consist of single entries or comma-separated
Index: regress/addrmatch.sh
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh/regress/addrmatch.sh,v
retrieving revision 1.5
diff -u -p -r1.5 addrmatch.sh
--- regress/addrmatch.sh	24 Feb 2010 06:26:39 -0000	1.5
+++ regress/addrmatch.sh	1 May 2012 10:36:46 -0000
@@ -7,39 +7,47 @@ mv $OBJ/sshd_proxy $OBJ/sshd_proxy_bak
 
 run_trial()
 {
-	user="$1"; addr="$2"; host="$3"; expected="$4"; descr="$5"
+	user="$1"; addr="$2"; host="$3"; laddr="$4"; lport="$5"
+	expected="$6"; descr="$7"
 
 	verbose "test $descr for $user $addr $host"
 	result=`${SSHD} -f $OBJ/sshd_proxy -T \
-	    -C user=${user},addr=${addr},host=${host} | \
-	    awk '/^passwordauthentication/ {print $2}'`
+	    -C user=${user},addr=${addr},host=${host},laddr=${laddr},lport=${lport} | \
+	    awk '/^forcecommand/ {print $2}'`
 	if [ "$result" != "$expected" ]; then
-		fail "failed for $user $addr $host: expected $expected, got $result"
+		fail "failed '$descr' expected $expected got $result"
 	fi
 }
 
 cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
 cat >>$OBJ/sshd_proxy <<EOD
-PasswordAuthentication no
+ForceCommand nomatch
 Match Address 192.168.0.0/16,!192.168.30.0/24,10.0.0.0/8,host.example.com
-	PasswordAuthentication yes
+	ForceCommand match1
 Match Address 1.1.1.1,::1,!::3,2000::/16
-	PasswordAuthentication yes
+	ForceCommand match2
+Match LocalAddress 127.0.0.1,::1
+	ForceCommand match3
+Match LocalPort 5678
+	ForceCommand match4
 EOD
 
-run_trial user 192.168.0.1 somehost yes		"permit, first entry"
-run_trial user 192.168.30.1 somehost no		"deny, negative match"
-run_trial user 19.0.0.1 somehost no		"deny, no match"
-run_trial user 10.255.255.254 somehost yes	"permit, list middle"
-run_trial user 192.168.30.1 192.168.0.1 no	"deny, faked IP in hostname"
-run_trial user 1.1.1.1 somehost.example.com yes	"permit, bare IP4 address"
-test "$TEST_SSH_IPV6" = "no" && exit
-run_trial user ::1 somehost.example.com	 yes	"permit, bare IP6 address"
-run_trial user ::2 somehost.exaple.com no	"deny IPv6"
-run_trial user ::3 somehost no			"deny IP6 negated"
-run_trial user ::4 somehost no			"deny, IP6 no match"
-run_trial user 2000::1 somehost yes		"permit, IP6 network"
-run_trial user 2001::1 somehost no		"deny, IP6 network"
+run_trial user 192.168.0.1 somehost 1.2.3.4 1234 match1 "first entry"
+run_trial user 192.168.30.1 somehost 1.2.3.4 1234 nomatch "negative match"
+run_trial user 19.0.0.1 somehost 1.2.3.4 1234 nomatch "no match"
+run_trial user 10.255.255.254 somehost 1.2.3.4 1234 match1 "list middle"
+run_trial user 192.168.30.1 192.168.0.1 1.2.3.4 1234 nomatch "faked IP in hostname"
+run_trial user 1.1.1.1 somehost.example.com 1.2.3.4 1234 match2 "bare IP4 address"
+run_trial user 19.0.0.1 somehost 127.0.0.1 1234 match3 "localaddress"
+run_trial user 19.0.0.1 somehost 1.2.3.4 5678 match4 "localport"
+run_trial user ::1 somehost.example.com ::2 1234 match2 "bare IP6 address"
+run_trial user ::2 somehost.exaple.com ::2 1234 nomatch "deny IPv6"
+run_trial user ::3 somehost ::2 1234 nomatch "IP6 negated"
+run_trial user ::4 somehost ::2 1234 nomatch "IP6 no match"
+run_trial user 2000::1 somehost ::2 1234 match2 "IP6 network"
+run_trial user 2001::1 somehost ::2 1234 nomatch "IP6 network"
+run_trial user ::5 somehost ::1 1234 match3 "IP6 localaddress"
+run_trial user ::5 somehost ::2 5678 match4 "IP6 localport"
 
 cp $OBJ/sshd_proxy_bak $OBJ/sshd_proxy
 rm $OBJ/sshd_proxy_bak
-- 
Darren Tucker (dtucker at zip.com.au)
GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4  37C9 C982 80C7 8FF4 FA69
    Good judgement comes with experience. Unfortunately, the experience
usually comes from bad judgement.


More information about the openssh-unix-dev mailing list