sshd config parser

Darren Tucker dtucker at zip.com.au
Sat Apr 1 22:19:19 EST 2006


On Thu, Mar 30, 2006 at 12:18:04AM +1100, Darren Tucker wrote:
> For various reasons, we're currently looking at extending (or even
> overhauling) the config parser used for sshd_config.
> 
> Right now the syntax I'm looking at is a cumulative "Match" keyword [...]

Here's some initial work toward this.  At the moment it only supports
a couple of existing directives (AllowTcpForwarding, GatewayPorts and
AcceptEnv).  Example syntax is below (from my test config).

It works by breaking the config parsing into two stages: once at startup
for the global directives and syntax checking of Match blocks, and once
after the connection is established to process the Match blocks.

This seems to be consistent with what you would expect although it's
not exactly first-match (it's first-match for the global block and
first-match for the Match blocks, but a Match block overrides a global
setting).

Diff is against 4.3p2.  There are surely bugs (consider it a working
prototype).  Feedback welcome.

# Example config
AllowTcpForwarding yes

Match Address 192.168.32.*,127.0.0.1
        AllowTcpForwarding no
        GatewayPorts no

Match User bar,baz
        AllowTcpForwarding yes

Match Host t*
        AllowTcpForwarding yes

-- 
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.
-------------- next part --------------
Index: auth.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh_cvs/auth.c,v
retrieving revision 1.101
diff -u -p -r1.101 auth.c
--- auth.c	31 Aug 2005 16:59:49 -0000	1.101
+++ auth.c	1 Apr 2006 10:52:03 -0000
@@ -587,3 +587,51 @@ fakepw(void)
 
 	return (&fake);
 }
+
+int
+cfg_match_line(Authctxt *ctxt, char **condition)
+{
+	int result = 1;
+	char *arg, *attrib, *cp = *condition;
+	const char *remhost;
+	size_t len;
+
+	if (ctxt == NULL)
+		debug("checking syntax for 'Match %s'", cp);
+	else
+		debug("checking match for '%s'", cp);
+
+	while ((attrib = strdelim(&cp)) && *attrib != '\0') {
+		if ((arg = strdelim(&cp)) == NULL || *arg == '\0') {
+			error("Missing Match criteria for %s", attrib);
+			return -1;
+		}
+		len = strlen(arg);
+		if (strcasecmp(attrib, "user") == 0) {
+			if (!ctxt)
+				break;
+			if (match_pattern_list(ctxt->user, arg, len, 0) != 1)
+				/* XXX what about negative match? */
+				result = 0;
+		} else if (strcasecmp(attrib, "host") == 0) {
+			if (!ctxt)
+				break;
+			remhost = get_canonical_hostname(options.use_dns);
+			debug("match remhost %s arg %s", remhost, arg);
+			if (match_hostname(remhost, arg, len) != 1)
+				result = 0;
+		} else if (strcasecmp(attrib, "address") == 0) {
+			if (!ctxt)
+				break;
+			remhost = get_remote_ipaddr();
+			if (match_hostname(remhost, arg, len) != 1)
+				result = 0;
+		} else {
+			error("Unsupported Match attribute %s", attrib);
+			return -1;
+		}
+	}
+	*condition = cp;
+	debug("cfg_check_match: returning %d", result);
+	return result;
+}
Index: auth.h
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh_cvs/auth.h,v
retrieving revision 1.68
diff -u -p -r1.68 auth.h
--- auth.h	7 Jul 2005 01:50:20 -0000	1.68
+++ auth.h	1 Apr 2006 10:41:07 -0000
@@ -185,6 +185,7 @@ void	 auth_debug_send(void);
 void	 auth_debug_reset(void);
 
 struct passwd *fakepw(void);
+int	 cfg_match_line(Authctxt *, char **);
 
 int	 sys_auth_passwd(Authctxt *, const char *);
 
Index: auth1.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh_cvs/auth1.c,v
retrieving revision 1.111
diff -u -p -r1.111 auth1.c
--- auth1.c	17 Jul 2005 07:26:44 -0000	1.111
+++ auth1.c	1 Apr 2006 08:40:47 -0000
@@ -385,6 +385,8 @@ do_authentication(Authctxt *authctxt)
 	authctxt->user = user;
 	authctxt->style = style;
 
+	parse_server_match_config(&options, authctxt);
+
 	/* Verify that the user is a valid user. */
 	if ((authctxt->pw = PRIVSEP(getpwnamallow(user))) != NULL)
 		authctxt->valid = 1;
Index: auth2.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh_cvs/auth2.c,v
retrieving revision 1.136
diff -u -p -r1.136 auth2.c
--- auth2.c	24 Sep 2005 02:43:51 -0000	1.136
+++ auth2.c	1 Apr 2006 08:41:11 -0000
@@ -153,6 +153,7 @@ input_userauth_request(int type, u_int32
 		/* setup auth context */
 		authctxt->pw = PRIVSEP(getpwnamallow(user));
 		authctxt->user = xstrdup(user);
+		parse_server_match_config(&options, authctxt);
 		if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
 			authctxt->valid = 1;
 			debug2("input_userauth_request: setting up authctxt for %s", user);
Index: monitor.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh_cvs/monitor.c,v
retrieving revision 1.88
diff -u -p -r1.88 monitor.c
--- monitor.c	5 Nov 2005 04:07:05 -0000	1.88
+++ monitor.c	1 Apr 2006 08:42:01 -0000
@@ -592,6 +592,8 @@ mm_answer_pwnamallow(int sock, Buffer *m
 
 	buffer_clear(m);
 
+	parse_server_match_config(&options, authctxt);
+
 	if (pwent == NULL) {
 		buffer_put_char(m, 0);
 		authctxt->pw = fakepw();
Index: servconf.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh_cvs/servconf.c,v
retrieving revision 1.136
diff -u -p -r1.136 servconf.c
--- servconf.c	13 Dec 2005 08:33:20 -0000	1.136
+++ servconf.c	1 Apr 2006 10:41:36 -0000
@@ -22,12 +22,15 @@ RCSID("$OpenBSD: servconf.c,v 1.146 2005
 #include "cipher.h"
 #include "kex.h"
 #include "mac.h"
+#include "auth.h"
+#include "match.h"
 
 static void add_listen_addr(ServerOptions *, char *, u_short);
 static void add_one_listen_addr(ServerOptions *, char *, u_short);
 
 /* Use of privilege separation or not */
 extern int use_privsep;
+extern Buffer cfg;
 
 /* Initializes the server options to their default values. */
 
@@ -102,9 +105,6 @@ initialize_server_options(ServerOptions 
 	options->authorized_keys_file2 = NULL;
 	options->num_accept_env = 0;
 	options->permit_tun = -1;
-
-	/* Needs to be accessable in many places */
-	use_privsep = -1;
 }
 
 void
@@ -274,6 +274,7 @@ typedef enum {
 	sHostbasedUsesNameFromPacketOnly, sClientAliveInterval,
 	sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2,
 	sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel,
+	sMatch,
 	sUsePrivilegeSeparation,
 	sDeprecated, sUnsupported
 } ServerOpCodes;
@@ -377,6 +378,7 @@ static struct {
 	{ "useprivilegeseparation", sUsePrivilegeSeparation},
 	{ "acceptenv", sAcceptEnv },
 	{ "permittunnel", sPermitTunnel },
+	{ "match", sMatch },
 	{ NULL, sBadOption }
 };
 
@@ -437,12 +439,41 @@ add_one_listen_addr(ServerOptions *optio
 	options->listen_addrs = aitop;
 }
 
+/*
+ * The strategy for the Match blocks is that the config file is parsed twice.
+ *
+ * The first time is at startup.  activep is initialized to 1 and the
+ * directives in the global context are processed and acted on.  Hitting a
+ * Match directive unsets activep and the directives inside the block are
+ * checked for syntax only.
+ *
+ * The second time is after a connection has been established but before
+ * authentication.  activep is initialized to 0 and global config directives
+ * are ignored since they have already been processed.  If the criteria in a
+ * Match block is met, activep is set and the subsequent directives
+ * processed and actioned until EOF or another Match block unsets it.  Any
+ * options set are copied into the main server config.
+ */
+static int
+allowed_in_match(ServerOpCodes code)
+{
+	switch (code) {
+	case sAcceptEnv:
+	case sAllowTcpForwarding:
+	case sGatewayPorts:
+	case sMatch:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
 int
 process_server_config_line(ServerOptions *options, char *line,
-    const char *filename, int linenum)
+    const char *filename, int linenum, int *activep, Authctxt *ctxt)
 {
 	char *cp, **charptr, *arg, *p;
-	int *intptr, value, n;
+	int cmdline = 0, *intptr, value, n;
 	ServerOpCodes opcode;
 	u_short port;
 	u_int i;
@@ -457,6 +488,24 @@ process_server_config_line(ServerOptions
 	intptr = NULL;
 	charptr = NULL;
 	opcode = parse_token(arg, filename, linenum);
+
+	if (activep == NULL) {
+		/* We are processing a command line directive */
+		cmdline = 1;
+		activep = &cmdline;
+	}
+	if (*activep == 0 && !allowed_in_match(opcode)) {
+		if (ctxt == 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 */
+			while (arg)
+				arg = strdelim(&cp);
+			return 0;
+		}
+	}
+
 	switch (opcode) {
 	/* Portable-specific options */
 	case sUsePAM:
@@ -494,7 +543,7 @@ parse_int:
 			fatal("%s line %d: missing integer value.",
 			    filename, linenum);
 		value = atoi(arg);
-		if (*intptr == -1)
+		if (*activep && *intptr == -1)
 			*intptr = value;
 		break;
 
@@ -625,7 +674,7 @@ parse_flag:
 		else
 			fatal("%s line %d: Bad yes/no argument: %s",
 				filename, linenum, arg);
-		if (*intptr == -1)
+		if (*activep && *intptr == -1)
 			*intptr = value;
 		break;
 
@@ -961,6 +1010,8 @@ parse_flag:
 			if (options->num_accept_env >= MAX_ACCEPT_ENV)
 				fatal("%s line %d: too many allow env.",
 				    filename, linenum);
+			if (!activep)
+				break;
 			options->accept_env[options->num_accept_env++] =
 			    xstrdup(arg);
 		}
@@ -988,6 +1039,17 @@ parse_flag:
 			*intptr = value;
 		break;
 
+	case sMatch:
+		if (cmdline)
+			fatal("Match directive not supported as a command-line "
+			   "option");
+		value = cfg_match_line(ctxt, &cp);
+		if (value < 0)
+			fatal("%s line %d: Bad Match condition", filename,
+			    linenum);
+		*activep = value;
+		break;
+
 	case sDeprecated:
 		logit("%s line %d: Deprecated option %s",
 		    filename, linenum, arg);
@@ -1044,18 +1106,45 @@ load_server_config(const char *filename,
 }
 
 void
-parse_server_config(ServerOptions *options, const char *filename, Buffer *conf)
+parse_server_match_config(ServerOptions *options, Authctxt *ctxt)
 {
-	int linenum, bad_options = 0;
+	u_int i;
+	ServerOptions mo;
+
+	initialize_server_options(&mo);
+	parse_server_config(&mo, "reprocess config", &cfg, ctxt);
+
+	/* now copy any (supported) values set */
+	if (mo.allow_tcp_forwarding != -1)
+		options->allow_tcp_forwarding = mo.allow_tcp_forwarding;
+	if (mo.gateway_ports != -1)
+		options->gateway_ports = mo.gateway_ports;
+	if (mo.num_accept_env > 0) {
+		for (i = 0; i < mo.num_accept_env; i++) {
+			if (options->num_accept_env >= MAX_ACCEPT_ENV)
+				fatal("Too many allow env in Match block.");
+			options->accept_env[options->num_accept_env++] =
+			    mo.accept_env[i];
+		}
+	}
+}
+
+void
+parse_server_config(ServerOptions *options, const char *filename, Buffer *conf,
+    Authctxt *ctxt)
+{
+	int active, linenum, bad_options = 0;
 	char *cp, *obuf, *cbuf;
 
 	debug2("%s: config %s len %d", __func__, filename, buffer_len(conf));
 
 	obuf = cbuf = xstrdup(buffer_ptr(conf));
+
+	active = ctxt ? 0 : 1;
 	linenum = 1;
 	while ((cp = strsep(&cbuf, "\n")) != NULL) {
 		if (process_server_config_line(options, cp, filename,
-		    linenum++) != 0)
+		    linenum++, &active, ctxt) != 0)
 			bad_options++;
 	}
 	xfree(obuf);
Index: servconf.h
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh_cvs/servconf.h,v
retrieving revision 1.64
diff -u -p -r1.64 servconf.h
--- servconf.h	13 Dec 2005 08:29:03 -0000	1.64
+++ servconf.h	1 Apr 2006 09:23:25 -0000
@@ -17,6 +17,7 @@
 #define SERVCONF_H
 
 #include "buffer.h"
+#include "auth.h"
 
 #define MAX_PORTS		256	/* Max # ports. */
 
@@ -141,8 +142,11 @@ typedef struct {
 
 void	 initialize_server_options(ServerOptions *);
 void	 fill_default_server_options(ServerOptions *);
-int	 process_server_config_line(ServerOptions *, char *, const char *, int);
+int	 process_server_config_line(ServerOptions *, char *, const char *, int,
+	     int *, Authctxt *);
 void	 load_server_config(const char *, Buffer *);
-void	 parse_server_config(ServerOptions *, const char *, Buffer *);
+void	 parse_server_config(ServerOptions *, const char *, Buffer *,
+	     Authctxt *);
+void	 parse_server_match_config(ServerOptions *, Authctxt *);
 
 #endif				/* SERVCONF_H */
Index: sshd.c
===================================================================
RCS file: /usr/local/src/security/openssh/cvs/openssh_cvs/sshd.c,v
retrieving revision 1.320
diff -u -p -r1.320 sshd.c
--- sshd.c	24 Dec 2005 03:59:12 -0000	1.320
+++ sshd.c	1 Apr 2006 09:45:57 -0000
@@ -201,12 +201,15 @@ int *startup_pipes = NULL;
 int startup_pipe;		/* in child */
 
 /* variables used for privilege separation */
-int use_privsep;
+int use_privsep = -1;		/* Needs to be accessable in many places */
 struct monitor *pmonitor = NULL;
 
 /* global authentication context */
 Authctxt *the_authctxt = NULL;
 
+/* sshd_config buffer */
+Buffer cfg;
+
 /* message to be displayed after login */
 Buffer loginmsg;
 
@@ -892,7 +895,6 @@ main(int ac, char **av)
 	Key *key;
 	Authctxt *authctxt;
 	int ret, key_used = 0;
-	Buffer cfg;
 
 #ifdef HAVE_SECUREWARE
 	(void)set_auth_parameters(ac, av);
@@ -1011,7 +1013,7 @@ main(int ac, char **av)
 		case 'o':
 			line = xstrdup(optarg);
 			if (process_server_config_line(&options, line,
-			    "command-line", 0) != 0)
+			    "command-line", 0, NULL, NULL) != 0)
 				exit(1);
 			xfree(line);
 			break;
@@ -1070,10 +1072,7 @@ main(int ac, char **av)
 		load_server_config(config_file_name, &cfg);
 
 	parse_server_config(&options,
-	    rexeced_flag ? "rexec" : config_file_name, &cfg);
-
-	if (!rexec_flag)
-		buffer_free(&cfg);
+	    rexeced_flag ? "rexec" : config_file_name, &cfg, NULL);
 
 	seed_rng();
 


More information about the openssh-unix-dev mailing list