Module Name:    src
Committed By:   christos
Date:           Tue Oct 12 19:08:04 UTC 2021

Modified Files:
        src/usr.sbin/inetd: Makefile inetd.8 inetd.c inetd.h parse_v2.c
Added Files:
        src/usr.sbin/inetd: parse.c ratelimit.c

Log Message:
PR/56448: Solomon Ritzow: Various improvements.

Rate limiting code has been moved to ratelimit.c. I renamed
clear_ip_list to rl_clear_ip_list and broke the code up into more
functions. I have also made the per-IP rate limiting allocation more
efficient. IP addresses are now stored in their network format instead
of a string from getnameinfo (see inetd.h struct rl_ip_node). malloc
calls use only the space needed by the structure by using offsetof on
union members (I suppose this can be a bit dangerous if not done
correctly...). Per-IP rate limiting still supports textual comparison
using getnameinfo for address families other than AF_INET and AF_INET6, but I
don't think there are any that are actually compatible or used by inetd (I
haven't tested UNIX sockets with a remote bound to another file, but I did test
using IPv6 with the textual format by commenting out the IPv6 specific
code, and it works properly). Still potentially handy for the future.
The IP node list (se_rl_ip_list) now uses the <sys/queue.h> SLIST macros
instead of a custom list. I've broken rl_process up into helper functions
for each type of rate limiting and created a separate function for
address stringification, for use with printouts from the -d flag. I
tried to reduce stack memory use by moving printing code involving
string buffers into separate functions. I haven't tested rl_ipv6_eq on
a 32-bit system.

The code for the positional syntax has also been moved to parse.c.
Function try_biltin has been added to remove parse.c:parse_server's
dependency on the biltin structure definition.

File inetd.h has been updated with the proper function prototypes, and
the servtab structure has been update with the new IP node SLIST. I also
moved things around a bit. The way we (a peer and myself)
formatted inetd.h previously was somewhat confusing. Function and global
variable prototypes are now organized by the source file they are
defined in.

I also added a -f flag that I saw in another problem report
(https://gnats.netbsd.org/12823) that I thought could be useful. It
runs inetd in the foreground but without debug printouts or SO_DEBUG.
I'm not completely sure about the line "if (foreground) setsid()" that
I changed from "if (debug) setsid()".


To generate a diff of this commit:
cvs rdiff -u -r1.29 -r1.30 src/usr.sbin/inetd/Makefile
cvs rdiff -u -r1.64 -r1.65 src/usr.sbin/inetd/inetd.8
cvs rdiff -u -r1.136 -r1.137 src/usr.sbin/inetd/inetd.c
cvs rdiff -u -r1.3 -r1.4 src/usr.sbin/inetd/inetd.h
cvs rdiff -u -r0 -r1.1 src/usr.sbin/inetd/parse.c \
    src/usr.sbin/inetd/ratelimit.c
cvs rdiff -u -r1.5 -r1.6 src/usr.sbin/inetd/parse_v2.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.sbin/inetd/Makefile
diff -u src/usr.sbin/inetd/Makefile:1.29 src/usr.sbin/inetd/Makefile:1.30
--- src/usr.sbin/inetd/Makefile:1.29	Fri Sep  3 16:24:28 2021
+++ src/usr.sbin/inetd/Makefile	Tue Oct 12 15:08:04 2021
@@ -1,12 +1,12 @@
 #	from: @(#)Makefile	8.1 (Berkeley) 6/6/93
-#	$NetBSD: Makefile,v 1.29 2021/09/03 20:24:28 rillig Exp $
+#	$NetBSD: Makefile,v 1.30 2021/10/12 19:08:04 christos Exp $
 
 .include <bsd.own.mk>
 
 USE_FORT?= yes	# network server
 
 PROG=	inetd
-SRCS=	inetd.c parse_v2.c
+SRCS=	inetd.c parse.c parse_v2.c ratelimit.c
 MAN=	inetd.8
 MLINKS=	inetd.8 inetd.conf.5
 WARNS=	6

Index: src/usr.sbin/inetd/inetd.8
diff -u src/usr.sbin/inetd/inetd.8:1.64 src/usr.sbin/inetd/inetd.8:1.65
--- src/usr.sbin/inetd/inetd.8:1.64	Tue Aug 31 07:16:00 2021
+++ src/usr.sbin/inetd/inetd.8	Tue Oct 12 15:08:04 2021
@@ -1,4 +1,4 @@
-.\"	$NetBSD: inetd.8,v 1.64 2021/08/31 11:16:00 wiz Exp $
+.\"	$NetBSD: inetd.8,v 1.65 2021/10/12 19:08:04 christos Exp $
 .\"
 .\" Copyright (c) 1998 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -57,7 +57,7 @@
 .\"
 .\"     from: @(#)inetd.8       8.4 (Berkeley) 6/1/94
 .\"
-.Dd August 29, 2021
+.Dd October 12, 2021
 .Dt INETD 8
 .Os
 .Sh NAME
@@ -92,7 +92,9 @@ The options available for
 .Nm :
 .Bl -tag -width Ds
 .It Fl d
-Turns on debugging.
+Turns on debugging and runs inetd in the foreground.
+.It Fl f
+Runs inetd in the foreground.
 .It Fl l
 Turns on libwrap connection logging.
 .El
@@ -519,7 +521,12 @@ Defaults to 40 if not specified.
 .It Sy ip_max
 Specifies the maximum number of server instances that may be spawned from
 .Nm
-within an interval of 60 seconds for a given IP address.
+within an interval of 60 seconds for a given IP address. Other address types 
+may also work if supported by
+.Xr getnameinfo 3
+, test thoroughly using -d. For example, connections from unnamed Unix sockets
+do not work, but connections from named Unix sockets may work. However, there is
+no way to only accept named Unix sockets.
 .It Sy user
 The user to run the program as.
 Equivalent to
@@ -860,9 +867,6 @@ key), addresses with an interface specif
 are not currently supported, as addresses of that form are not parsed by 
 .Xr inet_pton 3 .
 .Pp
-.Dq tcpmux
-on IPv6 is not tested enough.
-.Pp
 If a positional service definition has an invalid parameter and extends
 across multiple lines using tab characters, the subsequent lines after the 
 error are treated as new service definitions.

Index: src/usr.sbin/inetd/inetd.c
diff -u src/usr.sbin/inetd/inetd.c:1.136 src/usr.sbin/inetd/inetd.c:1.137
--- src/usr.sbin/inetd/inetd.c:1.136	Fri Sep  3 17:02:04 2021
+++ src/usr.sbin/inetd/inetd.c	Tue Oct 12 15:08:04 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: inetd.c,v 1.136 2021/09/03 21:02:04 rillig Exp $	*/
+/*	$NetBSD: inetd.c,v 1.137 2021/10/12 19:08:04 christos Exp $	*/
 
 /*-
  * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc.
@@ -66,7 +66,7 @@ __COPYRIGHT("@(#) Copyright (c) 1983, 19
 #if 0
 static char sccsid[] = "@(#)inetd.c	8.4 (Berkeley) 4/13/94";
 #else
-__RCSID("$NetBSD: inetd.c,v 1.136 2021/09/03 21:02:04 rillig Exp $");
+__RCSID("$NetBSD: inetd.c,v 1.137 2021/10/12 19:08:04 christos Exp $");
 #endif
 #endif /* not lint */
 
@@ -191,6 +191,7 @@ __RCSID("$NetBSD: inetd.c,v 1.136 2021/0
 #include <sys/resource.h>
 #include <sys/event.h>
 #include <sys/socket.h>
+#include <sys/queue.h>
 
 
 #ifndef NO_RPC
@@ -242,9 +243,7 @@ int allow_severity = LIBWRAP_ALLOW_FACIL
 int deny_severity = LIBWRAP_DENY_FACILITY|LIBWRAP_DENY_SEVERITY;
 #endif
 
-#define	CNT_INTVL	((time_t)60)	/* servers in CNT_INTVL sec. */
-#define	RETRYTIME	(60*10)		/* retry after bind or server fail */
-
+static bool foreground;
 int	debug;
 #ifdef LIBWRAP
 int	lflag;
@@ -272,32 +271,20 @@ struct servtab *servtab;
 
 static void	chargen_dg(int, struct servtab *);
 static void	chargen_stream(int, struct servtab *);
-static void	close_sep(struct servtab *);
-static void	config(void);
 static void	daytime_dg(int, struct servtab *);
 static void	daytime_stream(int, struct servtab *);
 static void	discard_dg(int, struct servtab *);
 static void	discard_stream(int, struct servtab *);
 static void	echo_dg(int, struct servtab *);
 static void	echo_stream(int, struct servtab *);
-static void	endconfig(void);
-static struct servtab	*enter(struct servtab *);
-static struct servtab	*getconfigent(char **);
 __dead static void	goaway(void);
 static void	machtime_dg(int, struct servtab *);
 static void	machtime_stream(int, struct servtab *);
-#ifdef DEBUG_ENABLE
-static void	print_service(const char *, struct servtab *);
-#endif
 static void	reapchild(void);
 static void	retry(void);
 static void	run_service(int, struct servtab *, int);
-static void	setup(struct servtab *);
-static char	*skip(char **);
 static void	tcpmux(int, struct servtab *);
 __dead static void	usage(void);
-static void	register_rpc(struct servtab *);
-static void	unregister_rpc(struct servtab *);
 static void	bump_nofile(void);
 static void	inetd_setproctitle(char *, int);
 static void	initring(void);
@@ -308,25 +295,6 @@ static int	my_kevent(const struct kevent
 static struct kevent	*allocchange(void);
 static int	get_line(int, char *, int);
 static void	spawn(struct servtab *, int);
-static struct servtab	init_servtab(void);
-static int	rl_process(struct servtab *, int);
-static struct se_ip_list_node	*rl_add(struct servtab *, char *);
-static void	rl_reset(struct servtab *, time_t);
-static struct se_ip_list_node	*rl_try_get_ip(struct servtab *, char *);
-static void	include_configs(char *);
-static int	glob_error(const char *, int);
-static void	read_glob_configs(char *);
-static void	prepare_next_config(const char*);
-static bool	is_same_service(const struct servtab *, const struct servtab *);
-static char	*gen_file_pattern(const char *, const char *);
-static bool	check_no_reinclude(const char *);
-static void	include_matched_path(char *);
-static void	purge_unchecked(void);
-static void	config_root(void);
-static void	clear_ip_list(struct servtab *);
-static time_t	rl_time(void);
-static void	rl_get_name(struct servtab *, int, char *);
-static void	rl_drop_connection(struct servtab *, int);
 
 struct biltin {
 	const char *bi_service;		/* internally provided service name */
@@ -356,9 +324,7 @@ struct biltin {
 	{ "chargen",	SOCK_STREAM,	true, false,	chargen_stream },
 	{ "chargen",	SOCK_DGRAM,	false, false,	chargen_dg },
 
-	{ "tcpmux",	SOCK_STREAM,	true, false,	tcpmux },
-
-	{ NULL, 0, false, false, NULL }
+	{ "tcpmux",	SOCK_STREAM,	true, false,	tcpmux }
 };
 
 /* list of "bad" ports. I.e. ports that are most obviously used for
@@ -382,16 +348,20 @@ main(int argc, char *argv[])
 
 	while ((ch = getopt(argc, argv,
 #ifdef LIBWRAP
-					"dl"
+					"dfl"
 #else
-					"d"
+					"df"
 #endif
 					   )) != -1)
 		switch(ch) {
 		case 'd':
+			foreground = true;
 			debug = true;
 			options |= SO_DEBUG;
 			break;
+		case 'f':
+			foreground = true;
+			break;
 #ifdef LIBWRAP
 		case 'l':
 			lflag = true;
@@ -407,7 +377,7 @@ main(int argc, char *argv[])
 	if (argc > 0)
 		CONFIG = argv[0];
 
-	if (!debug)
+	if (!foreground)
 		daemon(0, 0);
 	openlog("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON);
 	pidfile(NULL);
@@ -537,14 +507,15 @@ spawn(struct servtab *sep, int ctrl)
 
 			for (n = 0; n < __arraycount(my_signals); n++)
 				(void) signal(my_signals[n], SIG_DFL);
-			if (debug)
+			/* Don't put services in terminal session */
+			if (foreground)
 				setsid();
 		}
 	}
 	if (pid == 0) {
 		run_service(ctrl, sep, dofork);
 		if (dofork)
-			exit(0);
+			exit(EXIT_SUCCESS);
 	}
 	if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM)
 		close(ctrl);
@@ -703,215 +674,6 @@ reapchild(void)
 	}
 }
 
-size_t	line_number;
-
-/*
- * Recursively merge loaded service definitions with any defined
- * in the current or included config files.
- */
-static void
-config(void)
-{
-	struct servtab *sep, *cp;
-	/*
-	 * Current position in line, used with key-values notation,
-	 * saves cp across getconfigent calls.
-	 */
-	char *current_pos;
-	size_t n;
-
-	/* open config file from beginning */
-	fconfig = fopen(CONFIG, "r");
-	if(fconfig == NULL) {
-		syslog(LOG_ERR, "%s: %m", CONFIG);
-		return;
-	}
-
-	/* First call to nextline will advance line_number to 1 */
-	line_number = 0;
-
-	/* Start parsing at the beginning of the first line */
-	current_pos = nextline(fconfig);
-
-	while ((cp = getconfigent(&current_pos)) != NULL) {
-		/* Find an already existing service definition */
-		for (sep = servtab; sep != NULL; sep = sep->se_next)
-			if (is_same_service(sep, cp))
-				break;
-		if (sep != NULL) {
-			int i;
-
-#define SWAP(type, a, b) {type c = a; a = b; b = c;}
-
-			/*
-			 * sep->se_wait may be holding the pid of a daemon
-			 * that we're waiting for.  If so, don't overwrite
-			 * it unless the config file explicitly says don't
-			 * wait.
-			 */
-			if (cp->se_bi == 0 &&
-			    (sep->se_wait == 1 || cp->se_wait == 0))
-				sep->se_wait = cp->se_wait;
-			SWAP(char *, sep->se_user, cp->se_user);
-			SWAP(char *, sep->se_group, cp->se_group);
-			SWAP(char *, sep->se_server, cp->se_server);
-			for (i = 0; i < MAXARGV; i++)
-				SWAP(char *, sep->se_argv[i], cp->se_argv[i]);
-#ifdef IPSEC
-			SWAP(char *, sep->se_policy, cp->se_policy);
-#endif
-			SWAP(service_type, cp->se_type, sep->se_type);
-			SWAP(size_t, cp->se_service_max, sep->se_service_max);
-			SWAP(size_t, cp->se_ip_max, sep->se_ip_max);
-#undef SWAP
-			if (isrpcservice(sep))
-				unregister_rpc(sep);
-			sep->se_rpcversl = cp->se_rpcversl;
-			sep->se_rpcversh = cp->se_rpcversh;
-			freeconfig(cp);
-#ifdef DEBUG_ENABLE
-			if (debug)
-				print_service("REDO", sep);
-#endif
-		} else {
-			sep = enter(cp);
-#ifdef DEBUG_ENABLE
-			if (debug)
-				print_service("ADD ", sep);
-#endif
-		}
-		sep->se_checked = 1;
-
-		/*
-		 * Remainder of config(void) checks validity of servtab options
-		 * and sets up the service by setting up sockets (in setup(servtab)).
-		 */
-		switch (sep->se_family) {
-		case AF_LOCAL:
-			if (sep->se_fd != -1)
-				break;
-			n = strlen(sep->se_service);
-			if (n >= sizeof(sep->se_ctrladdr_un.sun_path)) {
-				syslog(LOG_ERR, "%s/%s: address too long",
-				    sep->se_service, sep->se_proto);
-				sep->se_checked = 0;
-				continue;
-			}
-			(void)unlink(sep->se_service);
-			strlcpy(sep->se_ctrladdr_un.sun_path,
-			    sep->se_service, n + 1);
-			sep->se_ctrladdr_un.sun_family = AF_LOCAL;
-			sep->se_ctrladdr_size = (socklen_t)(n +
-			    sizeof(sep->se_ctrladdr_un) -
-			    sizeof(sep->se_ctrladdr_un.sun_path));
-			if (!ISMUX(sep))
-				setup(sep);
-			break;
-		case AF_INET:
-#ifdef INET6
-		case AF_INET6:
-#endif
-		    {
-			struct addrinfo hints, *res;
-			char *host;
-			const char *port;
-			int error;
-			int s;
-
-			/* check if the family is supported */
-			s = socket(sep->se_family, SOCK_DGRAM, 0);
-			if (s < 0) {
-				syslog(LOG_WARNING,
-				    "%s/%s: %s: the address family is not "
-				    "supported by the kernel",
-				    sep->se_service, sep->se_proto,
-				    sep->se_hostaddr);
-				sep->se_checked = false;
-				continue;
-			}
-			close(s);
-
-			memset(&hints, 0, sizeof(hints));
-			hints.ai_family = sep->se_family;
-			hints.ai_socktype = sep->se_socktype;
-			hints.ai_flags = AI_PASSIVE;
-			if (strcmp(sep->se_hostaddr, "*") == 0)
-				host = NULL;
-			else
-				host = sep->se_hostaddr;
-			if (isrpcservice(sep) || ISMUX(sep))
-				port = "0";
-			else
-				port = sep->se_service;
-			error = getaddrinfo(host, port, &hints, &res);
-			if (error != 0) {
-				if (error == EAI_SERVICE) {
-					/* gai_strerror not friendly enough */
-					syslog(LOG_WARNING, SERV_FMT ": "
-					    "unknown service",
-					    SERV_PARAMS(sep));
-				} else {
-					syslog(LOG_ERR, SERV_FMT ": %s: %s",
-					    SERV_PARAMS(sep),
-					    sep->se_hostaddr,
-					    gai_strerror(error));
-				}
-				sep->se_checked = false;
-				continue;
-			}
-			if (res->ai_next != NULL) {
-				syslog(LOG_ERR,
-					SERV_FMT ": %s: resolved to multiple addr",
-				    SERV_PARAMS(sep),
-				    sep->se_hostaddr);
-				sep->se_checked = false;
-				freeaddrinfo(res);
-				continue;
-			}
-			memcpy(&sep->se_ctrladdr, res->ai_addr,
-				res->ai_addrlen);
-			if (ISMUX(sep)) {
-				sep->se_fd = -1;
-				freeaddrinfo(res);
-				continue;
-			}
-			sep->se_ctrladdr_size = res->ai_addrlen;
-			freeaddrinfo(res);
-#ifdef RPC
-			if (isrpcservice(sep)) {
-				struct rpcent *rp;
-
-				sep->se_rpcprog = atoi(sep->se_service);
-				if (sep->se_rpcprog == 0) {
-					rp = getrpcbyname(sep->se_service);
-					if (rp == 0) {
-						syslog(LOG_ERR,
-						    SERV_FMT
-						    ": unknown service",
-						    SERV_PARAMS(sep));
-						sep->se_checked = false;
-						continue;
-					}
-					sep->se_rpcprog = rp->r_number;
-				}
-				if (sep->se_fd == -1 && !ISMUX(sep))
-					setup(sep);
-				if (sep->se_fd != -1)
-					register_rpc(sep);
-			} else
-#endif
-			{
-				if (sep->se_fd >= 0)
-					close_sep(sep);
-				if (sep->se_fd == -1 && !ISMUX(sep))
-					setup(sep);
-			}
-		    }
-		}
-	}
-	endconfig();
-}
-
 static void
 retry(void)
 {
@@ -959,10 +721,13 @@ goaway(void)
 		(void)close(sep->se_fd);
 		sep->se_fd = -1;
 	}
-	exit(0);
+
+	DPRINTF("Going away.");
+
+	exit(EXIT_SUCCESS);
 }
 
-static void
+void
 setup(struct servtab *sep)
 {
 	int		on = 1;
@@ -1065,7 +830,7 @@ setsockopt(fd, SOL_SOCKET, opt, &on, (so
 /*
  * Finish with a service and its socket.
  */
-static void
+void
 close_sep(struct servtab *sep)
 {
 
@@ -1075,11 +840,11 @@ close_sep(struct servtab *sep)
 	}
 	sep->se_count = 0;
 	if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
-		clear_ip_list(sep);
+		rl_clear_ip_list(sep);
 	}
 }
 
-static void
+void
 register_rpc(struct servtab *sep)
 {
 #ifdef RPC
@@ -1117,7 +882,7 @@ register_rpc(struct servtab *sep)
 #endif /* RPC */
 }
 
-static void
+void
 unregister_rpc(struct servtab *sep)
 {
 #ifdef RPC
@@ -1140,561 +905,6 @@ unregister_rpc(struct servtab *sep)
 #endif /* RPC */
 }
 
-
-static struct servtab *
-enter(struct servtab *cp)
-{
-	struct servtab *sep;
-
-	sep = malloc(sizeof (*sep));
-	if (sep == NULL) {
-		syslog(LOG_ERR, "Out of memory.");
-		exit(EXIT_FAILURE);
-	}
-	*sep = *cp;
-	sep->se_fd = -1;
-	sep->se_rpcprog = -1;
-	sep->se_next = servtab;
-	servtab = sep;
-	return (sep);
-}
-
-FILE	*fconfig;
-/* Temporary storage for new servtab */
-static struct	servtab serv;
-/* Current line from current config file */
-static char	line[LINE_MAX];
-char    *defhost;
-#ifdef IPSEC
-char *policy;
-#endif
-
-static void
-endconfig(void)
-{
-	if (fconfig != NULL) {
-		(void) fclose(fconfig);
-		fconfig = NULL;
-	}
-	if (defhost != NULL) {
-		free(defhost);
-		defhost = NULL;
-	}
-
-#ifdef IPSEC
-	if (policy != NULL) {
-		free(policy);
-		policy = NULL;
-	}
-#endif
-
-}
-
-#define LOG_EARLY_ENDCONF() \
-	ERR("Exiting %s early. Some services will be unavailable", CONFIG)
-
-#define LOG_TOO_FEW_ARGS() \
-	ERR("Expected more arguments")
-
-/* Parse the next service and apply any directives, and returns it as servtab */
-static struct servtab *
-getconfigent(char **current_pos)
-{
-	struct servtab *sep = &serv;
-	int argc, val;
-	char *cp, *cp0, *arg, *buf0, *buf1, *sz0, *sz1;
-	static char TCPMUX_TOKEN[] = "tcpmux/";
-#define MUX_LEN		(sizeof(TCPMUX_TOKEN)-1)
-	char *hostdelim;
-
-	/*
-	 * Pre-condition: current_pos points into line,
-	 * line contains config line. Continue where the last getconfigent left off.
-	 * Allows for multiple service definitions per line.
-	 */
-	cp = *current_pos;
-
-	if (false) {
-		/*
-		 * Go to the next line, but only after attemting to read the current
-		 * one! Keep reading until we find a valid definition or EOF.
-		 */
-more:
-		cp = nextline(fconfig);
-	}
-
-	if (cp == NULL) {
-		/* EOF or I/O error, let config() know to exit the file */
-		return NULL;
-	}
-
-	/* Comments and IPsec policies */
-	if (cp[0] == '#') {
-#ifdef IPSEC
-		/* lines starting with #@ is not a comment, but the policy */
-		if (cp[1] == '@') {
-			char *p;
-			for (p = cp + 2; isspace((unsigned char)*p); p++)
-				;
-			if (*p == '\0') {
-				if (policy)
-					free(policy);
-				policy = NULL;
-			} else {
-				if (ipsecsetup_test(p) < 0) {
-					ERR("Invalid IPsec policy \"%s\"", p);
-					LOG_EARLY_ENDCONF();
-					/*
-					* Stop reading the current config to prevent services
-					* from being run without IPsec.
-					*/
-					return NULL;
-				} else {
-					if (policy)
-						free(policy);
-					policy = newstr(p);
-				}
-			}
-		}
-#endif
-
-		goto more;
-	}
-
-	/* Parse next token: listen-addr/hostname, service-spec, .include */
-	arg = skip(&cp);
-
-	if (cp == NULL) {
-		goto more;
-	}
-
-	if(arg[0] == '.') {
-		if (strcmp(&arg[1], "include") == 0) {
-			/* include directive */
-			arg = skip(&cp);
-			if(arg == NULL) {
-				LOG_TOO_FEW_ARGS();
-				return NULL;
-			}
-			include_configs(arg);
-			goto more;
-		} else {
-			ERR("Unknown directive '%s'", &arg[1]);
-			goto more;
-		}
-	}
-
-	/* After this point, we might need to store data in a servtab */
-	*sep = init_servtab();
-
-	/* Check for a host name. */
-	hostdelim = strrchr(arg, ':');
-	if (hostdelim != NULL) {
-		*hostdelim = '\0';
-		if (arg[0] == '[' && hostdelim > arg && hostdelim[-1] == ']') {
-			hostdelim[-1] = '\0';
-			sep->se_hostaddr = newstr(arg + 1);
-		} else
-			sep->se_hostaddr = newstr(arg);
-		arg = hostdelim + 1;
-		/*
-		 * If the line is of the form `host:', then just change the
-		 * default host for the following lines.
-		 */
-		if (*arg == '\0') {
-			arg = skip(&cp);
-			if (cp == NULL) {
-				free(defhost);
-				defhost = sep->se_hostaddr;
-				goto more;
-			}
-		}
-	} else {
-		/* No host address found, set it to NULL to indicate absence */
-		sep->se_hostaddr = NULL;
-	}
-	if (strncmp(arg, TCPMUX_TOKEN, MUX_LEN) == 0) {
-		char *c = arg + MUX_LEN;
-		if (*c == '+') {
-			sep->se_type = MUXPLUS_TYPE;
-			c++;
-		} else
-			sep->se_type = MUX_TYPE;
-		sep->se_service = newstr(c);
-	} else {
-		sep->se_service = newstr(arg);
-		sep->se_type = NORM_TYPE;
-	}
-
-	DPRINTCONF("Found service definition '%s'", sep->se_service);
-
-	/* on/off/socktype */
-	arg = skip(&cp);
-	if (arg == NULL) {
-		LOG_TOO_FEW_ARGS();
-		freeconfig(sep);
-		goto more;
-	}
-
-	/* Check for new v2 syntax */
-	if (strcmp(arg, "on") == 0 || strncmp(arg, "on#", 3) == 0) {
-
-		if (arg[2] == '#') {
-			cp = nextline(fconfig);
-		}
-
-		switch(parse_syntax_v2(sep, &cp)) {
-		case V2_SUCCESS:
-			*current_pos = cp;
-			return sep;
-		case V2_SKIP:
-			/* Skip invalid definitions, freeconfig is called in parse_v2.c */
-			*current_pos = cp;
-			freeconfig(sep);
-			goto more;
-		case V2_ERROR:
-			/*
-			 * Unrecoverable error, stop reading. freeconfig is called
-			 * in parse_v2.c
-			 */
-			LOG_EARLY_ENDCONF();
-			freeconfig(sep);
-			return NULL;
-		}
-	} else if (strcmp(arg, "off") == 0 || strncmp(arg, "off#", 4) == 0) {
-
-		if (arg[3] == '#') {
-			cp = nextline(fconfig);
-		}
-
-		/* Parse syntax the same as with 'on', but ignore the result */
-		switch(parse_syntax_v2(sep, &cp)) {
-		case V2_SUCCESS:
-		case V2_SKIP:
-			*current_pos = cp;
-			freeconfig(sep);
-			goto more;
-		case V2_ERROR:
-			/* Unrecoverable error, stop reading */
-			LOG_EARLY_ENDCONF();
-			freeconfig(sep);
-			return NULL;
-		}
-	} else {
-		/* continue parsing v1 */
-		parse_socktype(arg, sep);
-		if (sep->se_socktype == SOCK_STREAM) {
-			parse_accept_filter(arg, sep);
-		}
-		if (sep->se_hostaddr == NULL) {
-			/* Set host to current default */
-			sep->se_hostaddr = newstr(defhost);
-		}
-	}
-
-	/* protocol */
-	arg = skip(&cp);
-	if (arg == NULL) {
-		LOG_TOO_FEW_ARGS();
-		freeconfig(sep);
-		goto more;
-	}
-	if (sep->se_type == NORM_TYPE &&
-	    strncmp(arg, "faith/", strlen("faith/")) == 0) {
-		arg += strlen("faith/");
-		sep->se_type = FAITH_TYPE;
-	}
-	sep->se_proto = newstr(arg);
-
-#define	MALFORMED(arg) \
-do { \
-	ERR("%s: malformed buffer size option `%s'", \
-	    sep->se_service, (arg)); \
-	freeconfig(sep); \
-	goto more; \
-} while (false)
-
-#define	GETVAL(arg) \
-do { \
-	if (!isdigit((unsigned char)*(arg))) \
-		MALFORMED(arg); \
-	val = (int)strtol((arg), &cp0, 10); \
-	if (cp0 != NULL) { \
-		if (cp0[1] != '\0') \
-			MALFORMED((arg)); \
-		if (cp0[0] == 'k') \
-			val *= 1024; \
-		if (cp0[0] == 'm') \
-			val *= 1024 * 1024; \
-	} \
-	if (val < 1) { \
-		ERR("%s: invalid buffer size `%s'", \
-		    sep->se_service, (arg)); \
-		freeconfig(sep); \
-		goto more; \
-	} \
-} while (false)
-
-#define	ASSIGN(arg) \
-do { \
-	if (strcmp((arg), "sndbuf") == 0) \
-		sep->se_sndbuf = val; \
-	else if (strcmp((arg), "rcvbuf") == 0) \
-		sep->se_rcvbuf = val; \
-	else \
-		MALFORMED((arg)); \
-} while (false)
-
-	/*
-	 * Extract the send and receive buffer sizes before parsing
-	 * the protocol.
-	 */
-	sep->se_sndbuf = sep->se_rcvbuf = 0;
-	buf0 = buf1 = sz0 = sz1 = NULL;
-	if ((buf0 = strchr(sep->se_proto, ',')) != NULL) {
-		/* Not meaningful for Tcpmux services. */
-		if (ISMUX(sep)) {
-			ERR("%s: can't specify buffer sizes for "
-			    "tcpmux services", sep->se_service);
-			goto more;
-		}
-
-		/* Skip the , */
-		*buf0++ = '\0';
-
-		/* Check to see if another socket buffer size was specified. */
-		if ((buf1 = strchr(buf0, ',')) != NULL) {
-			/* Skip the , */
-			*buf1++ = '\0';
-
-			/* Make sure a 3rd one wasn't specified. */
-			if (strchr(buf1, ',') != NULL) {
-				ERR("%s: too many buffer sizes", sep->se_service);
-				goto more;
-			}
-
-			/* Locate the size. */
-			if ((sz1 = strchr(buf1, '=')) == NULL)
-				MALFORMED(buf1);
-
-			/* Skip the = */
-			*sz1++ = '\0';
-		}
-
-		/* Locate the size. */
-		if ((sz0 = strchr(buf0, '=')) == NULL)
-			MALFORMED(buf0);
-
-		/* Skip the = */
-		*sz0++ = '\0';
-
-		GETVAL(sz0);
-		ASSIGN(buf0);
-
-		if (buf1 != NULL) {
-			GETVAL(sz1);
-			ASSIGN(buf1);
-		}
-	}
-
-#undef ASSIGN
-#undef GETVAL
-#undef MALFORMED
-
-	if (parse_protocol(sep)) {
-		freeconfig(sep);
-		goto more;
-	}
-
-	/* wait/nowait:max */
-	arg = skip(&cp);
-	if (arg == NULL) {
-		LOG_TOO_FEW_ARGS();
-		freeconfig(sep);
-		goto more;
-	}
-
-	/* Rate limiting parsing */ {
-		char *cp1;
-		if ((cp1 = strchr(arg, ':')) == NULL)
-			cp1 = strchr(arg, '.');
-		if (cp1 != NULL) {
-			int rstatus;
-			*cp1++ = '\0';
-			sep->se_service_max = (size_t)strtou(cp1, NULL, 10, 0,
-			    SERVTAB_COUNT_MAX, &rstatus);
-
-			if (rstatus != 0) {
-				if (rstatus != ERANGE) {
-					/* For compatibility with atoi parsing */
-					sep->se_service_max = 0;
-				}
-
-				WRN("Improper \"max\" value '%s', "
-				    "using '%zu' instead: %s",
-				    cp1,
-				    sep->se_service_max,
-				    strerror(rstatus));
-			}
-
-		} else
-			sep->se_service_max = TOOMANY;
-	}
-	if (parse_wait(sep, strcmp(arg, "wait") == 0)) {
-		freeconfig(sep);
-		goto more;
-	}
-
-	/* Parse user:group token */
-	arg = skip(&cp);
-	if(arg == NULL) {
-		LOG_TOO_FEW_ARGS();
-		freeconfig(sep);
-		goto more;
-	}
-	char* separator = strchr(arg, ':');
-	if (separator == NULL) {
-		/* Backwards compatibility, allow dot instead of colon */
-		separator = strchr(arg, '.');
-	}
-
-	if (separator == NULL) {
-		/* Only user was specified */
-		sep->se_group = NULL;
-	} else {
-		*separator = '\0';
-		sep->se_group = newstr(separator + 1);
-	}
-
-	sep->se_user = newstr(arg);
-
-	/* Parser server-program (path to binary or "internal") */
-	arg = skip(&cp);
-	if (arg == NULL) {
-		LOG_TOO_FEW_ARGS();
-		freeconfig(sep);
-		goto more;
-	}
-	if (parse_server(sep, arg)) {
-		freeconfig(sep);
-		goto more;
-	}
-
-	argc = 0;
-	for (arg = skip(&cp); cp != NULL; arg = skip(&cp)) {
-		if (argc < MAXARGV)
-			sep->se_argv[argc++] = newstr(arg);
-	}
-	while (argc <= MAXARGV)
-		sep->se_argv[argc++] = NULL;
-#ifdef IPSEC
-	sep->se_policy = policy != NULL ? newstr(policy) : NULL;
-#endif
-	/* getconfigent read a positional service def, move to next line */
-	*current_pos = nextline(fconfig);
-	return (sep);
-}
-
-void
-freeconfig(struct servtab *cp)
-{
-	int i;
-
-	free(cp->se_hostaddr);
-	free(cp->se_service);
-	free(cp->se_proto);
-	free(cp->se_user);
-	free(cp->se_group);
-	free(cp->se_server);
-	for (i = 0; i < MAXARGV; i++)
-		free(cp->se_argv[i]);
-#ifdef IPSEC
-	free(cp->se_policy);
-#endif
-}
-
-/*
- * Get next token *in the current service definition* from config file.
- * Allows multi-line parse if single space or single tab-indented.
- * Things in quotes are considered single token.
- * Advances cp to next token.
- */
-static char *
-skip(char **cpp)
-{
-	char *cp = *cpp;
-	char *start;
-	char quote;
-
-	if (*cpp == NULL)
-		return (NULL);
-
-again:
-	while (*cp == ' ' || *cp == '\t')
-		cp++;
-	if (*cp == '\0') {
-		int c;
-
-		c = getc(fconfig);
-		(void) ungetc(c, fconfig);
-		if (c == ' ' || c == '\t')
-			if ((cp = nextline(fconfig)) != NULL)
-				goto again;
-		*cpp = NULL;
-		return (NULL);
-	}
-	start = cp;
-	/* Parse shell-style quotes */
-	quote = '\0';
-	while (*cp != '\0' && (quote != '\0' || (*cp != ' ' && *cp != '\t'))) {
-		if (*cp == '\'' || *cp == '"') {
-			if (quote != '\0' && *cp != quote)
-				cp++;
-			else {
-				if (quote != '\0')
-					quote = '\0';
-				else
-					quote = *cp;
-				memmove(cp, cp+1, strlen(cp));
-			}
-		} else
-			cp++;
-	}
-	if (*cp != '\0')
-		*cp++ = '\0';
-	*cpp = cp;
-	return (start);
-}
-
-char *
-nextline(FILE *fd)
-{
-	char *cp;
-
-	if (fgets(line, (int)sizeof(line), fd) == NULL) {
-		if (ferror(fd) != 0) {
-			ERR("Error when reading next line: %s", strerror(errno));
-		}
-		return NULL;
-	}
-	cp = strchr(line, '\n');
-	if (cp != NULL)
-		*cp = '\0';
-	line_number++;
-	return line;
-}
-
-char *
-newstr(const char *cp)
-{
-	char *dp;
-	if ((dp = strdup((cp != NULL) ? cp : "")) != NULL)
-		return (dp);
-	syslog(LOG_ERR, "strdup: %m");
-	exit(EXIT_FAILURE);
-	/*NOTREACHED*/
-}
-
 static void
 inetd_setproctitle(char *a, int s)
 {
@@ -1759,7 +969,7 @@ echo_stream(int s, struct servtab *sep)	
 	inetd_setproctitle(sep->se_service, s);
 	while ((i = read(s, buffer, sizeof(buffer))) > 0 &&
 	    write(s, buffer, (size_t)i) > 0)
-		;
+		continue;
 }
 
 /* ARGSUSED */
@@ -1975,49 +1185,6 @@ daytime_dg(int s, struct servtab *sep)
 	(void) sendto(s, buffer, (size_t)len, 0, sa, size);
 }
 
-#ifdef DEBUG_ENABLE
-/*
- * print_service:
- *	Dump relevant information to stderr
- */
-static void
-print_service(const char *action, struct servtab *sep)
-{
-
-	if (isrpcservice(sep))
-		fprintf(stderr,
-		    "%s: %s rpcprog=%d, rpcvers = %d/%d, proto=%s, wait.max=%d.%zu, user:group=%s:%s builtin=%lx server=%s"
-#ifdef IPSEC
-		    " policy=\"%s\""
-#endif
-		    "\n",
-		    action, sep->se_service,
-		    sep->se_rpcprog, sep->se_rpcversh, sep->se_rpcversl, sep->se_proto,
-		    sep->se_wait, sep->se_service_max, sep->se_user, sep->se_group,
-		    (long)sep->se_bi, sep->se_server
-#ifdef IPSEC
-		    , (sep->se_policy != NULL ? sep->se_policy : "")
-#endif
-		    );
-	else
-		fprintf(stderr,
-		    "%s: %s:%s proto=%s%s, wait.max=%d.%zu, user:group=%s:%s builtin=%lx server=%s"
-#ifdef IPSEC
-		    " policy=%s"
-#endif
-		    "\n",
-		    action, sep->se_hostaddr, sep->se_service,
-		    sep->se_type == FAITH_TYPE ? "faith/" : "",
-		    sep->se_proto,
-		    sep->se_wait, sep->se_service_max, sep->se_user, sep->se_group,
-		    (long)sep->se_bi, sep->se_server
-#ifdef IPSEC
-		    , (sep->se_policy != NULL ? sep->se_policy : "")
-#endif
-		    );
-}
-#endif
-
 static void
 usage(void)
 {
@@ -2229,747 +1396,16 @@ allocchange(void)
 	return (&changebuf[changes++]);
 }
 
-static void
-config_root(void)
-{
-	struct servtab *sep;
-	/* Uncheck services */
-	for (sep = servtab; sep != NULL; sep = sep->se_next) {
-		sep->se_checked = false;
-	}
-	defhost = newstr("*");
-#ifdef IPSEC
-	policy = NULL;
-#endif
-	fconfig = NULL;
-	config();
-	purge_unchecked();
-}
-
-static void
-purge_unchecked(void)
-{
-	struct servtab *sep, **sepp = &servtab;
-	int servtab_count = 0;
-	while ((sep = *sepp) != NULL) {
-		if (sep->se_checked) {
-			sepp = &sep->se_next;
-			servtab_count++;
-			continue;
-		}
-		*sepp = sep->se_next;
-		if (sep->se_fd >= 0)
-			close_sep(sep);
-		if (isrpcservice(sep))
-			unregister_rpc(sep);
-		if (sep->se_family == AF_LOCAL)
-			(void)unlink(sep->se_service);
-#ifdef DEBUG_ENABLE
-		if (debug)
-			print_service("FREE", sep);
-#endif
-		freeconfig(sep);
-		free(sep);
-	}
-	DPRINTF("%d service(s) loaded.", servtab_count);
-}
-
-static bool
-is_same_service(const struct servtab *sep, const struct servtab *cp)
-{
-	return
-	    strcmp(sep->se_service, cp->se_service) == 0 &&
-	    strcmp(sep->se_hostaddr, cp->se_hostaddr) == 0 &&
-	    strcmp(sep->se_proto, cp->se_proto) == 0 &&
-	    sep->se_family == cp->se_family &&
-	    ISMUX(sep) == ISMUX(cp);
-}
-
-int
-parse_protocol(struct servtab *sep)
-{
-	int val;
-
-	if (strcmp(sep->se_proto, "unix") == 0) {
-		sep->se_family = AF_LOCAL;
-	} else {
-		val = (int)strlen(sep->se_proto);
-		if (val == 0) {
-			ERR("%s: invalid protocol specified",
-			    sep->se_service);
-			return -1;
-		}
-		val = sep->se_proto[val - 1];
-		switch (val) {
-		case '4':	/*tcp4 or udp4*/
-			sep->se_family = AF_INET;
-			break;
-#ifdef INET6
-		case '6':	/*tcp6 or udp6*/
-			sep->se_family = AF_INET6;
-			break;
-#endif
-		default:
-			/* Use 'default' IP version which is IPv4, may eventually be
-			 * changed to AF_INET6 */
-			sep->se_family = AF_INET;
-			break;
-		}
-		if (strncmp(sep->se_proto, "rpc/", 4) == 0) {
-#ifdef RPC
-			char *cp1, *ccp;
-			cp1 = strchr(sep->se_service, '/');
-			if (cp1 == 0) {
-				ERR("%s: no rpc version",
-				    sep->se_service);
-				return -1;
-			}
-			*cp1++ = '\0';
-			sep->se_rpcversl = sep->se_rpcversh =
-			    (int)strtol(cp1, &ccp, 0);
-			if (ccp == cp1) {
-		badafterall:
-				ERR("%s/%s: bad rpc version",
-				    sep->se_service, cp1);
-				return -1;
-			}
-			if (*ccp == '-') {
-				cp1 = ccp + 1;
-				sep->se_rpcversh = (int)strtol(cp1, &ccp, 0);
-				if (ccp == cp1)
-					goto badafterall;
-			}
-#else
-			ERR("%s: rpc services not supported",
-			    sep->se_service);
-			return -1;
-#endif /* RPC */
-		}
-	}
-	return 0;
-}
-
-int
-parse_wait(struct servtab *sep, int wait)
-{
-	if (!ISMUX(sep)) {
-		sep->se_wait = wait;
-		return 0;
-	}
-	/*
-	 * Silently enforce "nowait" for TCPMUX services since
-	 * they don't have an assigned port to listen on.
-	 */
-	sep->se_wait = 0;
-
-	if (strncmp(sep->se_proto, "tcp", 3)) {
-		ERR("bad protocol for tcpmux service %s",
-			sep->se_service);
-		return -1;
-	}
-	if (sep->se_socktype != SOCK_STREAM) {
-		ERR("bad socket type for tcpmux service %s",
-		    sep->se_service);
-		return -1;
-	}
-	return 0;
-}
-
-int
-parse_server(struct servtab *sep, const char *arg){
-	sep->se_server = newstr(arg);
-	if (strcmp(sep->se_server, "internal") != 0) {
-		sep->se_bi = NULL;
-		return 0;
-	}
-	struct biltin *bi;
-
-	for (bi = biltins; bi->bi_service; bi++)
-		if (bi->bi_socktype == sep->se_socktype &&
-		    strcmp(bi->bi_service, sep->se_service) == 0)
-			break;
-	if (bi->bi_service == NULL) {
-		ERR("Internal service %s unknown",
-		    sep->se_service);
-		return -1;
-	}
-	sep->se_bi = bi;
-	sep->se_wait = bi->bi_wait;
-	return 0;
-}
-
-/* TODO test to make sure accept filter still works */
-void
-parse_accept_filter(char *arg, struct servtab *sep) {
-	char *accf, *accf_arg;
-	/* one and only one accept filter */
-	accf = strchr(arg, ':');
-	if (accf == NULL)
-		return;
-	if (accf != strrchr(arg, ':') || *(accf + 1) == '\0') {
-		/* more than one ||  nothing beyond */
-		sep->se_socktype = -1;
-		return;
-	}
-
-	accf++;			/* skip delimiter */
-	strlcpy(sep->se_accf.af_name, accf, sizeof(sep->se_accf.af_name));
-	accf_arg = strchr(accf, ',');
-	if (accf_arg == NULL)	/* zero or one arg, no more */
-		return;
-
-	if (strrchr(accf, ',') != accf_arg) {
-		sep->se_socktype = -1;
-	} else {
-		accf_arg++;
-		strlcpy(sep->se_accf.af_arg, accf_arg,
-		    sizeof(sep->se_accf.af_arg));
-	}
-}
-
-void
-parse_socktype(char* arg, struct servtab* sep)
-{
-	/* stream socket may have an accept filter, only check first chars */
-	if (strncmp(arg, "stream", sizeof("stream") - 1) == 0)
-		sep->se_socktype = SOCK_STREAM;
-	else if (strcmp(arg, "dgram") == 0)
-		sep->se_socktype = SOCK_DGRAM;
-	else if (strcmp(arg, "rdm") == 0)
-		sep->se_socktype = SOCK_RDM;
-	else if (strcmp(arg, "seqpacket") == 0)
-		sep->se_socktype = SOCK_SEQPACKET;
-	else if (strcmp(arg, "raw") == 0)
-		sep->se_socktype = SOCK_RAW;
-	else
-		sep->se_socktype = -1;
-}
-
-static struct servtab
-init_servtab(void)
-{
-	/* This does not set every field to default. See enter() as well */
-	return (struct servtab) {
-		/*
-		 * Set se_max to non-zero so uninitialized value is not
-	 	 * a valid value. Useful in v2 syntax parsing.
-		 */
-		.se_service_max = SERVTAB_UNSPEC_SIZE_T,
-		.se_ip_max = SERVTAB_UNSPEC_SIZE_T,
-		.se_wait = SERVTAB_UNSPEC_VAL,
-		.se_socktype = SERVTAB_UNSPEC_VAL
-		/* All other fields initialized to 0 or null */
-	};
-}
-
-/* Include directives bookkeeping structure */
-struct file_list {
-	/* Absolute path used for checking for circular references */
-	char *abs;
-	/* Pointer to the absolute path of the parent config file,
-	 * on the stack */
-	struct file_list *next;
-} *file_list_head;
-
-static void
-include_configs(char *pattern)
-{
-	/* Allocate global per-config state on the thread stack */
-	const char* save_CONFIG;
-	FILE	*save_fconfig;
-	size_t	save_line_number;
-	char    *save_defhost;
-	struct	file_list new_file;
-#ifdef IPSEC
-	char *save_policy;
-#endif
-
-	/* Store current globals on the stack */
-	save_CONFIG = CONFIG;
-	save_fconfig = fconfig;
-	save_line_number = line_number;
-	save_defhost = defhost;
-	new_file.abs = realpath(CONFIG, NULL);
-	new_file.next = file_list_head;
-#ifdef IPSEC
-	save_policy = policy;
-#endif
-	/* Put new_file at the top of the config stack */
-	file_list_head = &new_file;
-	read_glob_configs(pattern);
-	free(new_file.abs);
-	/* Pop new_file off the stack */
-	file_list_head = new_file.next;
-
-	/* Restore global per-config state */
-	CONFIG = save_CONFIG;
-	fconfig = save_fconfig;
-	line_number = save_line_number;
-	defhost = save_defhost;
-#ifdef IPSEC
-	policy = save_policy;
-#endif
-}
-
-static void
-prepare_next_config(const char *file_name)
-{
-	/* Setup new state that is normally only done in main */
-	CONFIG = file_name;
-
-	/* Inherit default host and IPsec policy */
-	defhost = newstr(defhost);
-
-#ifdef IPSEC
-	policy = (policy == NULL) ? NULL : newstr(policy);
-#endif
-}
-
-static void
-read_glob_configs(char *pattern) {
-	glob_t results;
-	char *full_pattern;
-	int glob_result;
-	full_pattern = gen_file_pattern(CONFIG, pattern);
-
-	DPRINTCONF("Found include directive '%s'", full_pattern);
-
-	glob_result = glob(full_pattern, GLOB_NOSORT, glob_error, &results);
-	switch(glob_result) {
-	case 0:
-		/* No glob errors */
-		break;
-	case GLOB_ABORTED:
-		ERR("Error while searching for include files");
-		break;
-	case GLOB_NOMATCH:
-		/* It's fine if no files were matched. */
-		DPRINTCONF("No files matched pattern '%s'", full_pattern);
-		break;
-	case GLOB_NOSPACE:
-		ERR("Error when searching for include files: %s",
-		    strerror(errno));
-		break;
-	default:
-		ERR("Unknown glob(3) error %d", errno);
-		break;
-	}
-	free(full_pattern);
-
-	for (size_t i = 0; i < results.gl_pathc; i++) {
-		include_matched_path(results.gl_pathv[i]);
-	}
-
-	globfree(&results);
-}
-
-static void
-include_matched_path(char *glob_path)
-{
-	struct stat sb;
-	char *tmp;
-
-	if (lstat(glob_path, &sb) != 0) {
-		ERR("Error calling stat on path '%s': %s", glob_path,
-		    strerror(errno));
-		return;
-	}
-
-	if (!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode)) {
-		DPRINTCONF("'%s' is not a file.", glob_path);
-		ERR("The matched path '%s' is not a regular file", glob_path);
-		return;
-	}
-
-	DPRINTCONF("Include '%s'", glob_path);
-
-	if (S_ISLNK(sb.st_mode)) {
-		tmp = glob_path;
-		glob_path = realpath(tmp, NULL);
-	}
-
-	/* Ensure the file is not being reincluded .*/
-	if (check_no_reinclude(glob_path)) {
-		prepare_next_config(glob_path);
-		config();
-	} else {
-		DPRINTCONF("File '%s' already included in current include "
-		    "chain", glob_path);
-		WRN("Including file '%s' would cause a circular "
-		    "dependency", glob_path);
-	}
-
-	if (S_ISLNK(sb.st_mode)) {
-		free(glob_path);
-		glob_path = tmp;
-	}
-}
-
-static bool
-check_no_reinclude(const char *glob_path)
-{
-	struct file_list *cur = file_list_head;
-	char *abs_path = realpath(glob_path, NULL);
-
-	if (abs_path == NULL) {
-		ERR("Error checking real path for '%s': %s",
-			glob_path, strerror(errno));
-		return false;
-	}
-
-	DPRINTCONF("Absolute path '%s'", abs_path);
-
-	for (cur = file_list_head; cur != NULL; cur = cur->next) {
-		if (strcmp(cur->abs, abs_path) == 0) {
-			/* file included more than once */
-			/* TODO relative or abs path for logging error? */
-			free(abs_path);
-			return false;
-		}
-	}
-	free(abs_path);
-	return true;
-}
-
-/* Resolve the pattern relative to the config file the pattern is from  */
-static char *
-gen_file_pattern(const char *cur_config, const char *pattern)
-{
-	if (pattern[0] == '/') {
-		/* Absolute paths don't need any normalization */
-		return newstr(pattern);
-	}
-
-	/* pattern is relative */
-	/* Find the end of the file's directory */
-	size_t i, last = 0;
-	for (i = 0; cur_config[i] != '\0'; i++) {
-		if (cur_config[i] == '/') {
-			last = i;
-		}
-	}
-
-	if (last == 0) {
-		/* cur_config is just a filename, pattern already correct */
-		return newstr(pattern);
-	}
-
-	/* Relativize pattern to cur_config file's directory */
-	char *full_pattern = malloc(last + 1 + strlen(pattern) + 1);
-	if (full_pattern == NULL) {
-		syslog(LOG_ERR, "Out of memory.");
-		exit(EXIT_FAILURE);
-	}
-	memcpy(full_pattern, cur_config, last);
-	full_pattern[last] = '/';
-	strcpy(&full_pattern[last + 1], pattern);
-	return full_pattern;
-}
-
-static int
-glob_error(const char *path, int error)
-{
-	WRN("Error while resolving path '%s': %s", path, strerror(error));
-	return 0;
-}
-
-/* Return 0 on allow, -1 if connection should be blocked */
-static int
-rl_process(struct servtab *sep, int ctrl)
-{
-	struct se_ip_list_node *node;
-	time_t now = 0; /* 0 prevents GCC from complaining */
-	bool istimevalid = false;
-	char hbuf[NI_MAXHOST];
-
-	DPRINTF(SERV_FMT ": processing rate-limiting",
-	    SERV_PARAMS(sep));
-	DPRINTF(SERV_FMT ": se_service_max "
-	    "%zu and se_count %zu", SERV_PARAMS(sep),
-	    sep->se_service_max, sep->se_count);
-
-	/* se_count is incremented if rl_process will return 0 */
-	if (sep->se_count == 0) {
-		now = rl_time();
-		sep->se_time = now;
-		istimevalid = true;
-	}
-
-	if (sep->se_count >= sep->se_service_max) {
-		if(!istimevalid) {
-			now = rl_time();
-			istimevalid = true;
-		}
-
-		if (now - sep->se_time > CNT_INTVL) {
-			rl_reset(sep, now);
-		} else {
-			syslog(LOG_ERR,
-			    SERV_FMT ": max spawn rate (%zu in %ji seconds) "
-			    "already met, closing until end of timeout in "
-			    "%ju seconds",
-			    SERV_PARAMS(sep),
-			    sep->se_service_max,
-			    (intmax_t)CNT_INTVL,
-			    (uintmax_t)RETRYTIME);
-
-			DPRINTF(SERV_FMT ": service not started",
-			    SERV_PARAMS(sep));
-
-			rl_drop_connection(sep, ctrl);
-
-			/* Close the server for 10 minutes */
-			close_sep(sep);
-			if (!timingout) {
-				timingout = true;
-				alarm(RETRYTIME);
-			}
-
-			return -1;
-		}
-	}
-
-	if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
-		rl_get_name(sep, ctrl, hbuf);
-		node = rl_try_get_ip(sep, hbuf);
-		if(node == NULL) {
-			node = rl_add(sep, hbuf);
-		}
-
-		DPRINTF(
-		    SERV_FMT ": se_ip_max %zu and ip_count %zu",
-		    SERV_PARAMS(sep), sep->se_ip_max, node->count);
-
-		if (node->count >= sep->se_ip_max) {
-			if (!istimevalid) {
-				/*
-				 * Only get the clock time if we didn't
-				 * already
-				 */
-				now = rl_time();
-				istimevalid = true;
-			}
-
-			if (now - sep->se_time > CNT_INTVL) {
-				rl_reset(sep, now);
-				node = rl_add(sep, hbuf);
-			} else {
-				if (debug && node->count == sep->se_ip_max) {
-					/*
-					 * Only log first failed request to
-					 * prevent DoS attack writing to system
-					 * log
-					 */
-					syslog(LOG_ERR, SERV_FMT
-					    ": max ip spawn rate (%zu in "
-					    "%ji seconds) for "
-					    "%." TOSTRING(NI_MAXHOST) "s "
-					    "already met; service not started",
-					    SERV_PARAMS(sep),
-					    sep->se_ip_max,
-					    (intmax_t)CNT_INTVL,
-					    node->address);
-				}
-
-				DPRINTF(SERV_FMT ": service not started",
-   				    SERV_PARAMS(sep));
-
-				rl_drop_connection(sep, ctrl);
-				/*
-				 * Increment so debug-syslog message will
-				 * trigger only once
-				 */
-				node->count++;
-				return -1;
-			}
-		}
-		node->count++;
-	}
-
-	DPRINTF(SERV_FMT ": running service ", SERV_PARAMS(sep));
-
-	sep->se_count++;
-	return 0;
-}
-
-/* Get the remote's IP address in textual form into hbuf of size NI_MAXHOST */
-static void
-rl_get_name(struct servtab *sep, int ctrl, char *hbuf)
+bool
+try_biltin(struct servtab *sep)
 {
-	struct sockaddr_storage addr;
-	socklen_t len = sizeof(struct sockaddr_storage);
-	switch (sep->se_socktype) {
-	case SOCK_STREAM:
-		if (getpeername(ctrl, (struct sockaddr *)&addr, &len) != 0) {
-			/* error, log it and skip ip rate limiting */
-			syslog(LOG_ERR,
-				SERV_FMT " failed to get peer name of the "
-				"connection", SERV_PARAMS(sep));
-			exit(EXIT_FAILURE);
-		}
-		break;
-	case SOCK_DGRAM: {
-		struct msghdr header = {
-			.msg_name = &addr,
-			.msg_namelen = sizeof(struct sockaddr_storage),
-			/* scatter/gather and control info is null */
-		};
-		ssize_t count;
-
-		/* Peek so service can still get the packet */
-		count = recvmsg(ctrl, &header, MSG_PEEK);
-		if (count == -1) {
-			syslog(LOG_ERR,
-			    "failed to get dgram source address: %s; exiting",
-			    strerror(errno));
-			exit(EXIT_FAILURE);
-		}
-		break;
-	}
-	default:
-		DPRINTF(SERV_FMT ": ip_max rate limiting not supported for "
-		    "socktype", SERV_PARAMS(sep));
-		syslog(LOG_ERR, SERV_FMT
-		    ": ip_max rate limiting not supported for socktype",
-		    SERV_PARAMS(sep));
-		exit(EXIT_FAILURE);
-	}
-
-	if (getnameinfo((struct sockaddr *)&addr,
-		addr.ss_len, hbuf,
-		NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) {
-		/* error, log it and skip ip rate limiting */
-		syslog(LOG_ERR,
-		    SERV_FMT ": failed to get name info of the incoming "
-		    "connection; exiting",
-		    SERV_PARAMS(sep));
-		exit(EXIT_FAILURE);
-	}
-}
-
-static void
-rl_drop_connection(struct servtab *sep, int ctrl)
-{
-
-	if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) {
-		/*
-		 * If the fd isn't a listen socket,
-		 * close the individual connection too.
-		 */
-		close(ctrl);
-		return;
-	}
-	if (sep->se_socktype != SOCK_DGRAM) {
-		return;
-	}
-	/*
-	 * Drop the single datagram the service would have
-	 * consumed if nowait. If this is a wait service, this
-	 * will consume 1 datagram, and further received packets
-	 * will be removed in the same way.
-	 */
-	struct msghdr header = {
-		/* All fields null, just consume one message */
-	};
-	ssize_t count;
-
-	count = recvmsg(ctrl, &header, 0);
-	if (count == -1) {
-		syslog(LOG_ERR,
-		    SERV_FMT ": failed to consume nowait dgram: %s",
-		    SERV_PARAMS(sep), strerror(errno));
-		exit(EXIT_FAILURE);
-	}
-	DPRINTF(SERV_FMT ": dropped dgram message",
-	    SERV_PARAMS(sep));
-}
-
-static time_t
-rl_time(void)
-{
-	struct timespec time;
-	if(clock_gettime(CLOCK_MONOTONIC, &time) == -1) {
-		syslog(LOG_ERR, "clock_gettime for rate limiting failed: %s; "
-		    "exiting", strerror(errno));
-		/* Exit inetd if rate limiting fails */
-		exit(EXIT_FAILURE);
-	}
-	return time.tv_sec;
-}
-
-static struct se_ip_list_node*
-rl_add(struct servtab *sep, char* ip)
-{
-	DPRINTF(
-	    SERV_FMT ": add ip %s to rate limiting tracking",
-	    SERV_PARAMS(sep), ip);
-
-	/*
-	 * TODO memory could be saved by using a variable length malloc
-	 * with only the length of ip instead of the existing address field
-	 * NI_MAXHOST in length.
-	 */
-	struct se_ip_list_node* temp = malloc(sizeof(*temp));
-	if (temp == NULL) {
-		syslog(LOG_ERR, "Out of memory.");
-		exit(EXIT_FAILURE);
-	}
-	temp->count = 0;
-	temp->next = NULL;
-	strlcpy(temp->address, ip, sizeof(temp->address));
-
-	if (sep->se_ip_list_head == NULL) {
-		/* List empty, insert as head */
-		sep->se_ip_list_head = temp;
-	} else {
-		/* List not empty, insert as head, point next to prev head */
-		temp->next = sep->se_ip_list_head;
-		sep->se_ip_list_head = temp;
-	}
-
-	return temp;
-}
-
-static void
-rl_reset(struct servtab *sep, time_t now)
-{
-	DPRINTF(SERV_FMT ": %ji seconds passed; resetting rate limiting ",
-	    SERV_PARAMS(sep), (intmax_t)(now - sep->se_time));
-
-	sep->se_count = 0;
-	sep->se_time = now;
-	if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
-		clear_ip_list(sep);
-	}
-}
-
-static void
-clear_ip_list(struct servtab *sep) {
-	struct se_ip_list_node *curr, *next;
-	curr = sep->se_ip_list_head;
-
-	while (curr != NULL) {
-		next = curr->next;
-		free(curr);
-		curr = next;
-	}
-	sep->se_ip_list_head = NULL;
-}
-
-static struct se_ip_list_node *
-rl_try_get_ip(struct servtab *sep, char *ip)
-{
-	struct se_ip_list_node *curr;
-
-	DPRINTF(
-	    SERV_FMT ": look up ip %s for ip_max rate limiting",
-	    SERV_PARAMS(sep), ip);
-
-	for (curr = sep->se_ip_list_head; curr != NULL; curr = curr->next) {
-		if (!strncmp(curr->address, ip, NI_MAXHOST)) {
-			/* IP addr match */
-			return curr;
+	for(size_t i = 0; i < __arraycount(biltins); i++) {
+		if (biltins[i].bi_socktype == sep->se_socktype &&
+		    strcmp(biltins[i].bi_service, sep->se_service) == 0) {
+			sep->se_bi = &biltins[i];
+			sep->se_wait = biltins[i].bi_wait;
+			return true;
 		}
 	}
-	return NULL;
+	return false;
 }

Index: src/usr.sbin/inetd/inetd.h
diff -u src/usr.sbin/inetd/inetd.h:1.3 src/usr.sbin/inetd/inetd.h:1.4
--- src/usr.sbin/inetd/inetd.h:1.3	Fri Sep  3 16:24:28 2021
+++ src/usr.sbin/inetd/inetd.h	Tue Oct 12 15:08:04 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: inetd.h,v 1.3 2021/09/03 20:24:28 rillig Exp $	*/
+/*	$NetBSD: inetd.h,v 1.4 2021/10/12 19:08:04 christos Exp $	*/
 
 /*-
  * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc.
@@ -66,12 +66,14 @@
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/un.h>
+#include <sys/queue.h>
 
 #include <arpa/inet.h>
 
 #include <netdb.h>
 #include <stdbool.h>
 
+
 #include "pathnames.h"
 
 #ifdef IPSEC
@@ -121,6 +123,21 @@ typedef enum service_type {
 #define STRINGIFY(x) #x
 #define TOSTRING(x) STRINGIFY(x)
 
+/* "Unspecified" indicator value for servtabs (mainly used by v2 syntax) */
+#define SERVTAB_UNSPEC_VAL -1
+
+#define SERVTAB_UNSPEC_SIZE_T SIZE_MAX
+
+#define SERVTAB_COUNT_MAX (SIZE_MAX - (size_t)1)
+
+/* Standard logging and debug print format for a servtab */
+#define SERV_FMT "%s/%s"
+#define SERV_PARAMS(sep) sep->se_service,sep->se_proto
+
+/* rate limiting macros */
+#define	CNT_INTVL	((time_t)60)	/* servers in CNT_INTVL sec. */
+#define	RETRYTIME	(60*10)		/* retry after bind or server fail */
+
 struct	servtab {
 	char	*se_hostaddr;		/* host address to listen on */
 	char	*se_service;		/* name of service */
@@ -148,30 +165,79 @@ struct	servtab {
 	int	se_fd;			/* open descriptor */
 	service_type	se_type;	/* type */
 	union {
-		struct sockaddr_storage	se_ctrladdr_storage; /* ensure correctness of C struct initializer */
+		/* ensure correctness of C struct initializer */
+		struct sockaddr_storage	se_ctrladdr_storage;
 		struct sockaddr	se_ctrladdr;
 		struct sockaddr_in	se_ctrladdr_in;
 		struct sockaddr_in6	se_ctrladdr_in6; /* in6 is used by bind()/getaddrinfo */
 		struct sockaddr_un	se_ctrladdr_un;
 	};				/* bound address */
 	socklen_t	se_ctrladdr_size;
-	size_t	se_service_max;		/* max # of instances of this service */
+	size_t	se_service_max;		/* max # of instances of this service per minute */
 	size_t	se_count;		/* number of instances of this service started since se_time */
 	size_t	se_ip_max;  		/* max # of instances of this service per ip per minute */
-	struct se_ip_list_node {
-		struct se_ip_list_node	*next;
-		size_t count;		/*
-					 * number of instances of this service started from
-					 * this ip address since se_time (includes
-					 * attempted starts if greater than se_ip_max)
-					 */
-		char address[NI_MAXHOST];
-	} *se_ip_list_head; 		/* linked list of number of requests per ip */
+	SLIST_HEAD(iplist, rl_ip_node) se_rl_ip_list; /* per-address (IP) rate limting */
 	time_t se_time;	/* start of se_count and ip_max counts, in seconds from arbitrary point */
+	
+	/* TODO convert to using SLIST */
 	struct	servtab *se_next;
 };
 
-/* From inetd.c */
+struct rl_ip_node {
+	/* Linked list entries */
+	SLIST_ENTRY(rl_ip_node) entries;
+	/*
+	 * Number of service spawns from *_addr since se_time (includes
+	 * attempted starts if greater than se_ip_max).
+	 */
+	size_t count;
+	union {
+		struct in_addr	ipv4_addr;
+#ifdef INET6
+		/* align for efficient comparison in rl_try_get, could use 8 instead */
+		struct in6_addr	ipv6_addr __attribute__((aligned(16)));
+#endif
+		/* 
+		 * other_addr is used for other address types besides the 
+		 * special cases (IPv4/IPv6), using getnameinfo.
+		 */
+		struct {
+			/* A field is required before the special array member */
+			char _placeholder;
+			/* malloc'd storage varies with length of string */
+			char other_addr[];
+		};
+	};
+	/* 
+	 * Do not declare further members after union, offsetof is used to
+	 * determine malloc size.
+	 */
+};
+
+/* 
+ * From inetd.c
+ */
+
+void	setup(struct servtab *);
+void	close_sep(struct servtab *);
+void	register_rpc(struct servtab *);
+void	unregister_rpc(struct servtab *);
+bool	try_biltin(struct servtab *);
+
+/* Global debug mode boolean, enabled with -d */
+extern int debug;
+
+/* rate limit or other error timed out flag */
+extern int	timingout;
+
+/* servtab linked list */
+extern struct servtab *servtab;
+
+/*
+ * From parse.c
+ */
+
+void	config_root(void);
 int 	parse_protocol(struct servtab *);
 int 	parse_wait(struct servtab *, int);
 int 	parse_server(struct servtab *, const char *);
@@ -179,27 +245,32 @@ void 	parse_socktype(char *, struct serv
 void 	parse_accept_filter(char *, struct servtab *);
 char 	*nextline(FILE *);
 char 	*newstr(const char *);
-void	freeconfig(struct servtab *);
 
-/* Global debug mode boolean, enabled with -d */
-extern int debug;
+/* Current line number in current config file */
+extern size_t	line_number;
 
 /* Current config file path */
-extern const char *CONFIG;
+extern const char	*CONFIG;
 
 /* Open config file */
 extern FILE	*fconfig;
 
-/* Current line number in current config file */
-extern size_t line_number;
-
 /* Default listening hostname/IP for current config file */
-extern char *defhost;
+extern char	*defhost;
 
 /* Default IPsec policy for current config file */
-extern char *policy;
+extern char	*policy;
+
+/*
+ * From ratelimit.c 
+ */
 
-/* From parse_v2.c */
+int	rl_process(struct servtab *, int);
+void	rl_clear_ip_list(struct servtab *);
+
+/*
+ * From parse_v2.c
+ */
 
 typedef enum parse_v2_result {V2_SUCCESS, V2_SKIP, V2_ERROR} parse_v2_result;
 
@@ -210,15 +281,4 @@ typedef enum parse_v2_result {V2_SUCCESS
  */
 parse_v2_result	parse_syntax_v2(struct servtab *, char **);
 
-/* "Unspecified" indicator value for servtabs (mainly used by v2 syntax) */
-#define SERVTAB_UNSPEC_VAL -1
-
-#define SERVTAB_UNSPEC_SIZE_T SIZE_MAX
-
-#define SERVTAB_COUNT_MAX (SIZE_MAX - (size_t)1)
-
-/* Standard logging and debug print format for a servtab */
-#define SERV_FMT "%s/%s"
-#define SERV_PARAMS(sep) sep->se_service,sep->se_proto
-
 #endif

Index: src/usr.sbin/inetd/parse_v2.c
diff -u src/usr.sbin/inetd/parse_v2.c:1.5 src/usr.sbin/inetd/parse_v2.c:1.6
--- src/usr.sbin/inetd/parse_v2.c:1.5	Fri Sep  3 16:24:28 2021
+++ src/usr.sbin/inetd/parse_v2.c	Tue Oct 12 15:08:04 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: parse_v2.c,v 1.5 2021/09/03 20:24:28 rillig Exp $	*/
+/*	$NetBSD: parse_v2.c,v 1.6 2021/10/12 19:08:04 christos Exp $	*/
 
 /*-
  * Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: parse_v2.c,v 1.5 2021/09/03 20:24:28 rillig Exp $");
+__RCSID("$NetBSD: parse_v2.c,v 1.6 2021/10/12 19:08:04 christos Exp $");
 
 #include <ctype.h>
 #include <errno.h>
@@ -153,11 +153,9 @@ parse_syntax_v2(struct servtab *sep, cha
 	bool is_valid_definition = true;
 	/* Line number of service for error logging. */
 	size_t line_number_start = line_number;
-	invoke_result result;
 
 	for (;;) {
-		switch(result =
-			parse_invoke_handler(&is_valid_definition, cpp, sep)) {
+		switch(parse_invoke_handler(&is_valid_definition, cpp, sep)) {
 		case INVOKE_SUCCESS:
 			/* Keep reading more options in. */
 			continue;
@@ -658,6 +656,7 @@ is_internal(struct servtab *sep)
  */
 
 static hresult
+/*ARGSUSED*/
 unknown_handler(struct servtab *sep, vlist values)
 {
 	/* Return failure for an unknown service name. */

Added files:

Index: src/usr.sbin/inetd/parse.c
diff -u /dev/null src/usr.sbin/inetd/parse.c:1.1
--- /dev/null	Tue Oct 12 15:08:04 2021
+++ src/usr.sbin/inetd/parse.c	Tue Oct 12 15:08:04 2021
@@ -0,0 +1,1376 @@
+/*	$NetBSD: parse.c,v 1.1 2021/10/12 19:08:04 christos Exp $	*/
+
+/*-
+ * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
+ * NASA Ames Research Center and by Matthias Scheler.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1983, 1991, 1993, 1994
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)inetd.c	8.4 (Berkeley) 4/13/94";
+#else
+__RCSID("$NetBSD: parse.c,v 1.1 2021/10/12 19:08:04 christos Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * This file contains code and state for loading and managing servtabs.
+ * The "positional" syntax parsing is performed in this file. See parse_v2.c
+ * for "key-values" syntax parsing.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "inetd.h"
+
+static void	config(void);
+static void	endconfig(void);
+static struct servtab	*enter(struct servtab *);
+static struct servtab	*getconfigent(char **);
+#ifdef DEBUG_ENABLE
+static void	print_service(const char *, struct servtab *);
+#endif
+static struct servtab	init_servtab(void);
+static void	include_configs(char *);
+static int	glob_error(const char *, int);
+static void	read_glob_configs(char *);
+static void	prepare_next_config(const char*);
+static bool	is_same_service(const struct servtab *, const struct servtab *);
+static char	*gen_file_pattern(const char *, const char *);
+static bool	check_no_reinclude(const char *);
+static void	include_matched_path(char *);
+static void	purge_unchecked(void);
+static void	freeconfig(struct servtab *);
+static char	*skip(char **);
+
+size_t	line_number;
+FILE	*fconfig;
+/* Temporary storage for new servtab */
+static struct	servtab serv;
+/* Current line from current config file */
+static char	line[LINE_MAX];
+char    *defhost;
+#ifdef IPSEC
+char *policy;
+#endif
+
+/*
+ * Recursively merge loaded service definitions with any defined
+ * in the current or included config files.
+ */
+static void
+config(void)
+{
+	struct servtab *sep, *cp;
+	/*
+	 * Current position in line, used with key-values notation,
+	 * saves cp across getconfigent calls.
+	 */
+	char *current_pos;
+	size_t n;
+
+	/* open config file from beginning */
+	fconfig = fopen(CONFIG, "r");
+	if (fconfig == NULL) {
+		DPRINTF("Could not open file \"%s\": %s",
+		    CONFIG, strerror(errno));
+		syslog(LOG_ERR, "%s: %m", CONFIG);
+		return;
+	}
+
+	/* First call to nextline will advance line_number to 1 */
+	line_number = 0;
+
+	/* Start parsing at the beginning of the first line */
+	current_pos = nextline(fconfig);
+
+	while ((cp = getconfigent(&current_pos)) != NULL) {
+		/* Find an already existing service definition */
+		for (sep = servtab; sep != NULL; sep = sep->se_next)
+			if (is_same_service(sep, cp))
+				break;
+		if (sep != NULL) {
+			int i;
+
+#define SWAP(type, a, b) {type c = a; a = b; b = c;}
+
+			/*
+			 * sep->se_wait may be holding the pid of a daemon
+			 * that we're waiting for.  If so, don't overwrite
+			 * it unless the config file explicitly says don't
+			 * wait.
+			 */
+			if (cp->se_bi == 0 &&
+			    (sep->se_wait == 1 || cp->se_wait == 0))
+				sep->se_wait = cp->se_wait;
+			SWAP(char *, sep->se_user, cp->se_user);
+			SWAP(char *, sep->se_group, cp->se_group);
+			SWAP(char *, sep->se_server, cp->se_server);
+			for (i = 0; i < MAXARGV; i++)
+				SWAP(char *, sep->se_argv[i], cp->se_argv[i]);
+#ifdef IPSEC
+			SWAP(char *, sep->se_policy, cp->se_policy);
+#endif
+			SWAP(service_type, cp->se_type, sep->se_type);
+			SWAP(size_t, cp->se_service_max, sep->se_service_max);
+			SWAP(size_t, cp->se_ip_max, sep->se_ip_max);
+#undef SWAP
+			if (isrpcservice(sep))
+				unregister_rpc(sep);
+			sep->se_rpcversl = cp->se_rpcversl;
+			sep->se_rpcversh = cp->se_rpcversh;
+			freeconfig(cp);
+#ifdef DEBUG_ENABLE
+			if (debug)
+				print_service("REDO", sep);
+#endif
+		} else {
+			sep = enter(cp);
+#ifdef DEBUG_ENABLE
+			if (debug)
+				print_service("ADD ", sep);
+#endif
+		}
+		sep->se_checked = 1;
+
+		/*
+		 * Remainder of config(void) checks validity of servtab options
+		 * and sets up the service by setting up sockets
+		 * (in setup(servtab)).
+		 */
+		switch (sep->se_family) {
+		case AF_LOCAL:
+			if (sep->se_fd != -1)
+				break;
+			n = strlen(sep->se_service);
+			if (n >= sizeof(sep->se_ctrladdr_un.sun_path)) {
+				syslog(LOG_ERR, "%s/%s: address too long",
+				    sep->se_service, sep->se_proto);
+				sep->se_checked = 0;
+				continue;
+			}
+			(void)unlink(sep->se_service);
+			strlcpy(sep->se_ctrladdr_un.sun_path,
+			    sep->se_service, n + 1);
+			sep->se_ctrladdr_un.sun_family = AF_LOCAL;
+			sep->se_ctrladdr_size = (socklen_t)(n +
+			    sizeof(sep->se_ctrladdr_un) -
+			    sizeof(sep->se_ctrladdr_un.sun_path));
+			if (!ISMUX(sep))
+				setup(sep);
+			break;
+		case AF_INET:
+#ifdef INET6
+		case AF_INET6:
+#endif
+		    {
+			struct addrinfo hints, *res;
+			char *host;
+			const char *port;
+			int error;
+			int s;
+
+			/* check if the family is supported */
+			s = socket(sep->se_family, SOCK_DGRAM, 0);
+			if (s < 0) {
+				syslog(LOG_WARNING,
+				    "%s/%s: %s: the address family is not "
+				    "supported by the kernel",
+				    sep->se_service, sep->se_proto,
+				    sep->se_hostaddr);
+				sep->se_checked = false;
+				continue;
+			}
+			close(s);
+
+			memset(&hints, 0, sizeof(hints));
+			hints.ai_family = sep->se_family;
+			hints.ai_socktype = sep->se_socktype;
+			hints.ai_flags = AI_PASSIVE;
+			if (strcmp(sep->se_hostaddr, "*") == 0)
+				host = NULL;
+			else
+				host = sep->se_hostaddr;
+			if (isrpcservice(sep) || ISMUX(sep))
+				port = "0";
+			else
+				port = sep->se_service;
+			error = getaddrinfo(host, port, &hints, &res);
+			if (error != 0) {
+				if (error == EAI_SERVICE) {
+					/* gai_strerror not friendly enough */
+					syslog(LOG_WARNING, SERV_FMT ": "
+					    "unknown service",
+					    SERV_PARAMS(sep));
+				} else {
+					syslog(LOG_ERR, SERV_FMT ": %s: %s",
+					    SERV_PARAMS(sep),
+					    sep->se_hostaddr,
+					    gai_strerror(error));
+				}
+				sep->se_checked = false;
+				continue;
+			}
+			if (res->ai_next != NULL) {
+				syslog(LOG_ERR, SERV_FMT
+				    ": %s: resolved to multiple addr",
+				    SERV_PARAMS(sep),
+				    sep->se_hostaddr);
+				sep->se_checked = false;
+				freeaddrinfo(res);
+				continue;
+			}
+			memcpy(&sep->se_ctrladdr, res->ai_addr,
+				res->ai_addrlen);
+			if (ISMUX(sep)) {
+				sep->se_fd = -1;
+				freeaddrinfo(res);
+				continue;
+			}
+			sep->se_ctrladdr_size = res->ai_addrlen;
+			freeaddrinfo(res);
+#ifdef RPC
+			if (isrpcservice(sep)) {
+				struct rpcent *rp;
+
+				sep->se_rpcprog = atoi(sep->se_service);
+				if (sep->se_rpcprog == 0) {
+					rp = getrpcbyname(sep->se_service);
+					if (rp == 0) {
+						syslog(LOG_ERR,
+						    SERV_FMT
+						    ": unknown service",
+						    SERV_PARAMS(sep));
+						sep->se_checked = false;
+						continue;
+					}
+					sep->se_rpcprog = rp->r_number;
+				}
+				if (sep->se_fd == -1 && !ISMUX(sep))
+					setup(sep);
+				if (sep->se_fd != -1)
+					register_rpc(sep);
+			} else
+#endif
+			{
+				if (sep->se_fd >= 0)
+					close_sep(sep);
+				if (sep->se_fd == -1 && !ISMUX(sep))
+					setup(sep);
+			}
+		    }
+		}
+	}
+	endconfig();
+}
+
+static struct servtab *
+enter(struct servtab *cp)
+{
+	struct servtab *sep;
+
+	sep = malloc(sizeof (*sep));
+	if (sep == NULL) {
+		syslog(LOG_ERR, "Out of memory.");
+		exit(EXIT_FAILURE);
+	}
+	*sep = *cp;
+	sep->se_fd = -1;
+	sep->se_rpcprog = -1;
+	sep->se_next = servtab;
+	servtab = sep;
+	return (sep);
+}
+
+static void
+endconfig(void)
+{
+	if (fconfig != NULL) {
+		(void) fclose(fconfig);
+		fconfig = NULL;
+	}
+	if (defhost != NULL) {
+		free(defhost);
+		defhost = NULL;
+	}
+
+#ifdef IPSEC
+	if (policy != NULL) {
+		free(policy);
+		policy = NULL;
+	}
+#endif
+
+}
+
+#define LOG_EARLY_ENDCONF() \
+	ERR("Exiting %s early. Some services will be unavailable", CONFIG)
+
+#define LOG_TOO_FEW_ARGS() \
+	ERR("Expected more arguments")
+
+/* Parse the next service and apply any directives, and returns it as servtab */
+static struct servtab *
+getconfigent(char **current_pos)
+{
+	struct servtab *sep = &serv;
+	int argc, val;
+	char *cp, *cp0, *arg, *buf0, *buf1, *sz0, *sz1;
+	static char TCPMUX_TOKEN[] = "tcpmux/";
+#define MUX_LEN		(sizeof(TCPMUX_TOKEN)-1)
+	char *hostdelim;
+
+	/*
+	 * Pre-condition: current_pos points into line,
+	 * line contains config line. Continue where the last getconfigent
+	 * left off. Allows for multiple service definitions per line.
+	 */
+	cp = *current_pos;
+
+	if (/*CONSTCOND*/false) {
+		/*
+		 * Go to the next line, but only after attemting to read the
+		 * current one! Keep reading until we find a valid definition
+		 * or EOF.
+		 */
+more:
+		cp = nextline(fconfig);
+	}
+
+	if (cp == NULL) {
+		/* EOF or I/O error, let config() know to exit the file */
+		return NULL;
+	}
+
+	/* Comments and IPsec policies */
+	if (cp[0] == '#') {
+#ifdef IPSEC
+		/* lines starting with #@ is not a comment, but the policy */
+		if (cp[1] == '@') {
+			char *p;
+			for (p = cp + 2; isspace((unsigned char)*p); p++)
+				;
+			if (*p == '\0') {
+				if (policy)
+					free(policy);
+				policy = NULL;
+			} else {
+				if (ipsecsetup_test(p) < 0) {
+					ERR("Invalid IPsec policy \"%s\"", p);
+					LOG_EARLY_ENDCONF();
+					/*
+					 * Stop reading the current config to
+					 * prevent services from being run
+					 * without IPsec.
+					 */
+					return NULL;
+				} else {
+					if (policy)
+						free(policy);
+					policy = newstr(p);
+				}
+			}
+		}
+#endif
+
+		goto more;
+	}
+
+	/* Parse next token: listen-addr/hostname, service-spec, .include */
+	arg = skip(&cp);
+
+	if (cp == NULL) {
+		goto more;
+	}
+
+	if(arg[0] == '.') {
+		if (strcmp(&arg[1], "include") == 0) {
+			/* include directive */
+			arg = skip(&cp);
+			if(arg == NULL) {
+				LOG_TOO_FEW_ARGS();
+				return NULL;
+			}
+			include_configs(arg);
+			goto more;
+		} else {
+			ERR("Unknown directive '%s'", &arg[1]);
+			goto more;
+		}
+	}
+
+	/* After this point, we might need to store data in a servtab */
+	*sep = init_servtab();
+
+	/* Check for a host name. */
+	hostdelim = strrchr(arg, ':');
+	if (hostdelim != NULL) {
+		*hostdelim = '\0';
+		if (arg[0] == '[' && hostdelim > arg && hostdelim[-1] == ']') {
+			hostdelim[-1] = '\0';
+			sep->se_hostaddr = newstr(arg + 1);
+		} else
+			sep->se_hostaddr = newstr(arg);
+		arg = hostdelim + 1;
+		/*
+		 * If the line is of the form `host:', then just change the
+		 * default host for the following lines.
+		 */
+		if (*arg == '\0') {
+			arg = skip(&cp);
+			if (cp == NULL) {
+				free(defhost);
+				defhost = sep->se_hostaddr;
+				goto more;
+			}
+		}
+	} else {
+		/* No host address found, set it to NULL to indicate absence */
+		sep->se_hostaddr = NULL;
+	}
+	if (strncmp(arg, TCPMUX_TOKEN, MUX_LEN) == 0) {
+		char *c = arg + MUX_LEN;
+		if (*c == '+') {
+			sep->se_type = MUXPLUS_TYPE;
+			c++;
+		} else
+			sep->se_type = MUX_TYPE;
+		sep->se_service = newstr(c);
+	} else {
+		sep->se_service = newstr(arg);
+		sep->se_type = NORM_TYPE;
+	}
+
+	DPRINTCONF("Found service definition '%s'", sep->se_service);
+
+	/* on/off/socktype */
+	arg = skip(&cp);
+	if (arg == NULL) {
+		LOG_TOO_FEW_ARGS();
+		freeconfig(sep);
+		goto more;
+	}
+
+	/* Check for new v2 syntax */
+	if (strcmp(arg, "on") == 0 || strncmp(arg, "on#", 3) == 0) {
+
+		if (arg[2] == '#') {
+			cp = nextline(fconfig);
+		}
+
+		switch(parse_syntax_v2(sep, &cp)) {
+		case V2_SUCCESS:
+			*current_pos = cp;
+			return sep;
+		case V2_SKIP:
+			/*
+			 * Skip invalid definitions, freeconfig is called in
+			 * parse_v2.c
+			 */
+			*current_pos = cp;
+			freeconfig(sep);
+			goto more;
+		case V2_ERROR:
+			/*
+			 * Unrecoverable error, stop reading. freeconfig
+			 * is called in parse_v2.c
+			 */
+			LOG_EARLY_ENDCONF();
+			freeconfig(sep);
+			return NULL;
+		}
+	} else if (strcmp(arg, "off") == 0 || strncmp(arg, "off#", 4) == 0) {
+
+		if (arg[3] == '#') {
+			cp = nextline(fconfig);
+		}
+
+		/* Parse syntax the same as with 'on', but ignore the result */
+		switch(parse_syntax_v2(sep, &cp)) {
+		case V2_SUCCESS:
+		case V2_SKIP:
+			*current_pos = cp;
+			freeconfig(sep);
+			goto more;
+		case V2_ERROR:
+			/* Unrecoverable error, stop reading */
+			LOG_EARLY_ENDCONF();
+			freeconfig(sep);
+			return NULL;
+		}
+	} else {
+		/* continue parsing v1 */
+		parse_socktype(arg, sep);
+		if (sep->se_socktype == SOCK_STREAM) {
+			parse_accept_filter(arg, sep);
+		}
+		if (sep->se_hostaddr == NULL) {
+			/* Set host to current default */
+			sep->se_hostaddr = newstr(defhost);
+		}
+	}
+
+	/* protocol */
+	arg = skip(&cp);
+	if (arg == NULL) {
+		LOG_TOO_FEW_ARGS();
+		freeconfig(sep);
+		goto more;
+	}
+	if (sep->se_type == NORM_TYPE &&
+	    strncmp(arg, "faith/", strlen("faith/")) == 0) {
+		arg += strlen("faith/");
+		sep->se_type = FAITH_TYPE;
+	}
+	sep->se_proto = newstr(arg);
+
+#define	MALFORMED(arg) \
+do { \
+	ERR("%s: malformed buffer size option `%s'", \
+	    sep->se_service, (arg)); \
+	freeconfig(sep); \
+	goto more; \
+} while (false)
+
+#define	GETVAL(arg) \
+do { \
+	if (!isdigit((unsigned char)*(arg))) \
+		MALFORMED(arg); \
+	val = (int)strtol((arg), &cp0, 10); \
+	if (cp0 != NULL) { \
+		if (cp0[1] != '\0') \
+			MALFORMED((arg)); \
+		if (cp0[0] == 'k') \
+			val *= 1024; \
+		if (cp0[0] == 'm') \
+			val *= 1024 * 1024; \
+	} \
+	if (val < 1) { \
+		ERR("%s: invalid buffer size `%s'", \
+		    sep->se_service, (arg)); \
+		freeconfig(sep); \
+		goto more; \
+	} \
+} while (false)
+
+#define	ASSIGN(arg) \
+do { \
+	if (strcmp((arg), "sndbuf") == 0) \
+		sep->se_sndbuf = val; \
+	else if (strcmp((arg), "rcvbuf") == 0) \
+		sep->se_rcvbuf = val; \
+	else \
+		MALFORMED((arg)); \
+} while (false)
+
+	/*
+	 * Extract the send and receive buffer sizes before parsing
+	 * the protocol.
+	 */
+	sep->se_sndbuf = sep->se_rcvbuf = 0;
+	buf0 = buf1 = sz0 = sz1 = NULL;
+	if ((buf0 = strchr(sep->se_proto, ',')) != NULL) {
+		/* Not meaningful for Tcpmux services. */
+		if (ISMUX(sep)) {
+			ERR("%s: can't specify buffer sizes for "
+			    "tcpmux services", sep->se_service);
+			goto more;
+		}
+
+		/* Skip the , */
+		*buf0++ = '\0';
+
+		/* Check to see if another socket buffer size was specified. */
+		if ((buf1 = strchr(buf0, ',')) != NULL) {
+			/* Skip the , */
+			*buf1++ = '\0';
+
+			/* Make sure a 3rd one wasn't specified. */
+			if (strchr(buf1, ',') != NULL) {
+				ERR("%s: too many buffer sizes",
+				    sep->se_service);
+				goto more;
+			}
+
+			/* Locate the size. */
+			if ((sz1 = strchr(buf1, '=')) == NULL)
+				MALFORMED(buf1);
+
+			/* Skip the = */
+			*sz1++ = '\0';
+		}
+
+		/* Locate the size. */
+		if ((sz0 = strchr(buf0, '=')) == NULL)
+			MALFORMED(buf0);
+
+		/* Skip the = */
+		*sz0++ = '\0';
+
+		GETVAL(sz0);
+		ASSIGN(buf0);
+
+		if (buf1 != NULL) {
+			GETVAL(sz1);
+			ASSIGN(buf1);
+		}
+	}
+
+#undef ASSIGN
+#undef GETVAL
+#undef MALFORMED
+
+	if (parse_protocol(sep)) {
+		freeconfig(sep);
+		goto more;
+	}
+
+	/* wait/nowait:max */
+	arg = skip(&cp);
+	if (arg == NULL) {
+		LOG_TOO_FEW_ARGS();
+		freeconfig(sep);
+		goto more;
+	}
+
+	/* Rate limiting parsing */ {
+		char *cp1;
+		if ((cp1 = strchr(arg, ':')) == NULL)
+			cp1 = strchr(arg, '.');
+		if (cp1 != NULL) {
+			int rstatus;
+			*cp1++ = '\0';
+			sep->se_service_max = (size_t)strtou(cp1, NULL, 10, 0,
+			    SERVTAB_COUNT_MAX, &rstatus);
+
+			if (rstatus != 0) {
+				if (rstatus != ERANGE) {
+					/* For compatibility w/ atoi parsing */
+					sep->se_service_max = 0;
+				}
+
+				WRN("Improper \"max\" value '%s', "
+				    "using '%zu' instead: %s",
+				    cp1,
+				    sep->se_service_max,
+				    strerror(rstatus));
+			}
+
+		} else
+			sep->se_service_max = TOOMANY;
+	}
+	if (parse_wait(sep, strcmp(arg, "wait") == 0)) {
+		freeconfig(sep);
+		goto more;
+	}
+
+	/* Parse user:group token */
+	arg = skip(&cp);
+	if(arg == NULL) {
+		LOG_TOO_FEW_ARGS();
+		freeconfig(sep);
+		goto more;
+	}
+	char* separator = strchr(arg, ':');
+	if (separator == NULL) {
+		/* Backwards compatibility, allow dot instead of colon */
+		separator = strchr(arg, '.');
+	}
+
+	if (separator == NULL) {
+		/* Only user was specified */
+		sep->se_group = NULL;
+	} else {
+		*separator = '\0';
+		sep->se_group = newstr(separator + 1);
+	}
+
+	sep->se_user = newstr(arg);
+
+	/* Parser server-program (path to binary or "internal") */
+	arg = skip(&cp);
+	if (arg == NULL) {
+		LOG_TOO_FEW_ARGS();
+		freeconfig(sep);
+		goto more;
+	}
+	if (parse_server(sep, arg)) {
+		freeconfig(sep);
+		goto more;
+	}
+
+	argc = 0;
+	for (arg = skip(&cp); cp != NULL; arg = skip(&cp)) {
+		if (argc < MAXARGV)
+			sep->se_argv[argc++] = newstr(arg);
+	}
+	while (argc <= MAXARGV)
+		sep->se_argv[argc++] = NULL;
+#ifdef IPSEC
+	sep->se_policy = policy != NULL ? newstr(policy) : NULL;
+#endif
+	/* getconfigent read a positional service def, move to next line */
+	*current_pos = nextline(fconfig);
+	return (sep);
+}
+
+void
+freeconfig(struct servtab *cp)
+{
+	int i;
+
+	free(cp->se_hostaddr);
+	free(cp->se_service);
+	free(cp->se_proto);
+	free(cp->se_user);
+	free(cp->se_group);
+	free(cp->se_server);
+	for (i = 0; i < MAXARGV; i++)
+		free(cp->se_argv[i]);
+#ifdef IPSEC
+	free(cp->se_policy);
+#endif
+}
+
+/*
+ * Get next token *in the current service definition* from config file.
+ * Allows multi-line parse if single space or single tab-indented.
+ * Things in quotes are considered single token.
+ * Advances cp to next token.
+ */
+static char *
+skip(char **cpp)
+{
+	char *cp = *cpp;
+	char *start;
+	char quote;
+
+	if (*cpp == NULL)
+		return (NULL);
+
+again:
+	while (*cp == ' ' || *cp == '\t')
+		cp++;
+	if (*cp == '\0') {
+		int c;
+
+		c = getc(fconfig);
+		(void) ungetc(c, fconfig);
+		if (c == ' ' || c == '\t')
+			if ((cp = nextline(fconfig)) != NULL)
+				goto again;
+		*cpp = NULL;
+		return (NULL);
+	}
+	start = cp;
+	/* Parse shell-style quotes */
+	quote = '\0';
+	while (*cp != '\0' && (quote != '\0' || (*cp != ' ' && *cp != '\t'))) {
+		if (*cp == '\'' || *cp == '"') {
+			if (quote != '\0' && *cp != quote)
+				cp++;
+			else {
+				if (quote != '\0')
+					quote = '\0';
+				else
+					quote = *cp;
+				memmove(cp, cp+1, strlen(cp));
+			}
+		} else
+			cp++;
+	}
+	if (*cp != '\0')
+		*cp++ = '\0';
+	*cpp = cp;
+	return (start);
+}
+
+char *
+nextline(FILE *fd)
+{
+	char *cp;
+
+	if (fgets(line, (int)sizeof(line), fd) == NULL) {
+		if (ferror(fd) != 0) {
+			ERR("Error when reading next line: %s",
+			    strerror(errno));
+		}
+		return NULL;
+	}
+	cp = strchr(line, '\n');
+	if (cp != NULL)
+		*cp = '\0';
+	line_number++;
+	return line;
+}
+
+char *
+newstr(const char *cp)
+{
+	char *dp;
+	if ((dp = strdup((cp != NULL) ? cp : "")) != NULL)
+		return (dp);
+	syslog(LOG_ERR, "strdup: %m");
+	exit(EXIT_FAILURE);
+	/*NOTREACHED*/
+}
+
+#ifdef DEBUG_ENABLE
+/*
+ * print_service:
+ *	Dump relevant information to stderr
+ */
+static void
+print_service(const char *action, struct servtab *sep)
+{
+
+	if (isrpcservice(sep))
+		fprintf(stderr,
+		    "%s: %s rpcprog=%d, rpcvers = %d/%d, proto=%s, "
+		    "wait.max=%d.%zu, "
+		    "user:group=%s:%s builtin=%lx server=%s"
+#ifdef IPSEC
+		    " policy=\"%s\""
+#endif
+		    "\n",
+		    action, sep->se_service,
+		    sep->se_rpcprog, sep->se_rpcversh, sep->se_rpcversl,
+		    sep->se_proto, sep->se_wait, sep->se_service_max,
+		    sep->se_user, sep->se_group,
+		    (long)sep->se_bi, sep->se_server
+#ifdef IPSEC
+		    , (sep->se_policy != NULL ? sep->se_policy : "")
+#endif
+		    );
+	else
+		fprintf(stderr,
+		    "%s: %s:%s proto=%s%s, wait.max=%d.%zu, user:group=%s:%s "
+		    "builtin=%lx "
+		    "server=%s"
+#ifdef IPSEC
+		    " policy=%s"
+#endif
+		    "\n",
+		    action, sep->se_hostaddr, sep->se_service,
+		    sep->se_type == FAITH_TYPE ? "faith/" : "",
+		    sep->se_proto,
+		    sep->se_wait, sep->se_service_max, sep->se_user,
+		    sep->se_group, (long)sep->se_bi, sep->se_server
+#ifdef IPSEC
+		    , (sep->se_policy != NULL ? sep->se_policy : "")
+#endif
+		    );
+}
+#endif
+
+void
+config_root(void)
+{
+	struct servtab *sep;
+	/* Uncheck services */
+	for (sep = servtab; sep != NULL; sep = sep->se_next) {
+		sep->se_checked = false;
+	}
+	defhost = newstr("*");
+#ifdef IPSEC
+	policy = NULL;
+#endif
+	fconfig = NULL;
+	config();
+	purge_unchecked();
+}
+
+static void
+purge_unchecked(void)
+{
+	struct servtab *sep, **sepp = &servtab;
+	int servtab_count = 0;
+	while ((sep = *sepp) != NULL) {
+		if (sep->se_checked) {
+			sepp = &sep->se_next;
+			servtab_count++;
+			continue;
+		}
+		*sepp = sep->se_next;
+		if (sep->se_fd >= 0)
+			close_sep(sep);
+		if (isrpcservice(sep))
+			unregister_rpc(sep);
+		if (sep->se_family == AF_LOCAL)
+			(void)unlink(sep->se_service);
+#ifdef DEBUG_ENABLE
+		if (debug)
+			print_service("FREE", sep);
+#endif
+		freeconfig(sep);
+		free(sep);
+	}
+	DPRINTF("%d service(s) loaded.", servtab_count);
+}
+
+static bool
+is_same_service(const struct servtab *sep, const struct servtab *cp)
+{
+	return
+	    strcmp(sep->se_service, cp->se_service) == 0 &&
+	    strcmp(sep->se_hostaddr, cp->se_hostaddr) == 0 &&
+	    strcmp(sep->se_proto, cp->se_proto) == 0 &&
+	    sep->se_family == cp->se_family &&
+	    ISMUX(sep) == ISMUX(cp);
+}
+
+int
+parse_protocol(struct servtab *sep)
+{
+	int val;
+
+	if (strcmp(sep->se_proto, "unix") == 0) {
+		sep->se_family = AF_LOCAL;
+	} else {
+		val = (int)strlen(sep->se_proto);
+		if (val == 0) {
+			ERR("%s: invalid protocol specified",
+			    sep->se_service);
+			return -1;
+		}
+		val = sep->se_proto[val - 1];
+		switch (val) {
+		case '4':	/*tcp4 or udp4*/
+			sep->se_family = AF_INET;
+			break;
+#ifdef INET6
+		case '6':	/*tcp6 or udp6*/
+			sep->se_family = AF_INET6;
+			break;
+#endif
+		default:
+			/*
+			 * Use 'default' IP version which is IPv4, may
+			 * eventually be changed to AF_INET6
+			 */
+			sep->se_family = AF_INET;
+			break;
+		}
+		if (strncmp(sep->se_proto, "rpc/", 4) == 0) {
+#ifdef RPC
+			char *cp1, *ccp;
+			cp1 = strchr(sep->se_service, '/');
+			if (cp1 == 0) {
+				ERR("%s: no rpc version",
+				    sep->se_service);
+				return -1;
+			}
+			*cp1++ = '\0';
+			sep->se_rpcversl = sep->se_rpcversh =
+			    (int)strtol(cp1, &ccp, 0);
+			if (ccp == cp1) {
+		badafterall:
+				ERR("%s/%s: bad rpc version",
+				    sep->se_service, cp1);
+				return -1;
+			}
+			if (*ccp == '-') {
+				cp1 = ccp + 1;
+				sep->se_rpcversh = (int)strtol(cp1, &ccp, 0);
+				if (ccp == cp1)
+					goto badafterall;
+			}
+#else
+			ERR("%s: rpc services not supported",
+			    sep->se_service);
+			return -1;
+#endif /* RPC */
+		}
+	}
+	return 0;
+}
+
+int
+parse_wait(struct servtab *sep, int wait)
+{
+	if (!ISMUX(sep)) {
+		sep->se_wait = wait;
+		return 0;
+	}
+	/*
+	 * Silently enforce "nowait" for TCPMUX services since
+	 * they don't have an assigned port to listen on.
+	 */
+	sep->se_wait = 0;
+
+	if (strncmp(sep->se_proto, "tcp", 3)) {
+		ERR("bad protocol for tcpmux service %s",
+			sep->se_service);
+		return -1;
+	}
+	if (sep->se_socktype != SOCK_STREAM) {
+		ERR("bad socket type for tcpmux service %s",
+		    sep->se_service);
+		return -1;
+	}
+	return 0;
+}
+
+int
+parse_server(struct servtab *sep, const char *arg)
+{
+	sep->se_server = newstr(arg);
+	if (strcmp(sep->se_server, "internal") != 0) {
+		sep->se_bi = NULL;
+		return 0;
+	}
+	
+	if (!try_biltin(sep)) {
+		ERR("Internal service %s unknown", sep->se_service);
+		return -1;
+	}
+	return 0;
+}
+
+/* TODO test to make sure accept filter still works */
+void
+parse_accept_filter(char *arg, struct servtab *sep)
+{
+	char *accf, *accf_arg;
+	/* one and only one accept filter */
+	accf = strchr(arg, ':');
+	if (accf == NULL)
+		return;
+	if (accf != strrchr(arg, ':') || *(accf + 1) == '\0') {
+		/* more than one ||  nothing beyond */
+		sep->se_socktype = -1;
+		return;
+	}
+
+	accf++;			/* skip delimiter */
+	strlcpy(sep->se_accf.af_name, accf, sizeof(sep->se_accf.af_name));
+	accf_arg = strchr(accf, ',');
+	if (accf_arg == NULL)	/* zero or one arg, no more */
+		return;
+
+	if (strrchr(accf, ',') != accf_arg) {
+		sep->se_socktype = -1;
+	} else {
+		accf_arg++;
+		strlcpy(sep->se_accf.af_arg, accf_arg,
+		    sizeof(sep->se_accf.af_arg));
+	}
+}
+
+void
+parse_socktype(char* arg, struct servtab* sep)
+{
+	/* stream socket may have an accept filter, only check first chars */
+	if (strncmp(arg, "stream", sizeof("stream") - 1) == 0)
+		sep->se_socktype = SOCK_STREAM;
+	else if (strcmp(arg, "dgram") == 0)
+		sep->se_socktype = SOCK_DGRAM;
+	else if (strcmp(arg, "rdm") == 0)
+		sep->se_socktype = SOCK_RDM;
+	else if (strcmp(arg, "seqpacket") == 0)
+		sep->se_socktype = SOCK_SEQPACKET;
+	else if (strcmp(arg, "raw") == 0)
+		sep->se_socktype = SOCK_RAW;
+	else
+		sep->se_socktype = -1;
+}
+
+static struct servtab
+init_servtab(void)
+{
+	/* This does not set every field to default. See enter() as well */
+	return (struct servtab) {
+		/*
+		 * Set se_max to non-zero so uninitialized value is not
+	 	 * a valid value. Useful in v2 syntax parsing.
+		 */
+		.se_service_max = SERVTAB_UNSPEC_SIZE_T,
+		.se_ip_max = SERVTAB_UNSPEC_SIZE_T,
+		.se_wait = SERVTAB_UNSPEC_VAL,
+		.se_socktype = SERVTAB_UNSPEC_VAL,
+		.se_rl_ip_list = SLIST_HEAD_INITIALIZER(se_ip_list_head)
+		/* All other fields initialized to 0 or null */
+	};
+}
+
+/* Include directives bookkeeping structure */
+struct file_list {
+	/* Absolute path used for checking for circular references */
+	char *abs;
+	/* Pointer to the absolute path of the parent config file,
+	 * on the stack */
+	struct file_list *next;
+} *file_list_head;
+
+static void
+include_configs(char *pattern)
+{
+	/* Allocate global per-config state on the thread stack */
+	const char* save_CONFIG;
+	FILE	*save_fconfig;
+	size_t	save_line_number;
+	char    *save_defhost;
+	struct	file_list new_file;
+#ifdef IPSEC
+	char *save_policy;
+#endif
+
+	/* Store current globals on the stack */
+	save_CONFIG = CONFIG;
+	save_fconfig = fconfig;
+	save_line_number = line_number;
+	save_defhost = defhost;
+	new_file.abs = realpath(CONFIG, NULL);
+	new_file.next = file_list_head;
+#ifdef IPSEC
+	save_policy = policy;
+#endif
+	/* Put new_file at the top of the config stack */
+	file_list_head = &new_file;
+	read_glob_configs(pattern);
+	free(new_file.abs);
+	/* Pop new_file off the stack */
+	file_list_head = new_file.next;
+
+	/* Restore global per-config state */
+	CONFIG = save_CONFIG;
+	fconfig = save_fconfig;
+	line_number = save_line_number;
+	defhost = save_defhost;
+#ifdef IPSEC
+	policy = save_policy;
+#endif
+}
+
+static void
+prepare_next_config(const char *file_name)
+{
+	/* Setup new state that is normally only done in main */
+	CONFIG = file_name;
+
+	/* Inherit default host and IPsec policy */
+	defhost = newstr(defhost);
+
+#ifdef IPSEC
+	policy = (policy == NULL) ? NULL : newstr(policy);
+#endif
+}
+
+static void
+read_glob_configs(char *pattern)
+{
+	glob_t results;
+	char *full_pattern;
+	int glob_result;
+	full_pattern = gen_file_pattern(CONFIG, pattern);
+
+	DPRINTCONF("Found include directive '%s'", full_pattern);
+
+	glob_result = glob(full_pattern, GLOB_NOSORT, glob_error, &results);
+	switch(glob_result) {
+	case 0:
+		/* No glob errors */
+		break;
+	case GLOB_ABORTED:
+		ERR("Error while searching for include files");
+		break;
+	case GLOB_NOMATCH:
+		/* It's fine if no files were matched. */
+		DPRINTCONF("No files matched pattern '%s'", full_pattern);
+		break;
+	case GLOB_NOSPACE:
+		ERR("Error when searching for include files: %s",
+		    strerror(errno));
+		break;
+	default:
+		ERR("Unknown glob(3) error %d", errno);
+		break;
+	}
+	free(full_pattern);
+
+	for (size_t i = 0; i < results.gl_pathc; i++) {
+		include_matched_path(results.gl_pathv[i]);
+	}
+
+	globfree(&results);
+}
+
+static void
+include_matched_path(char *glob_path)
+{
+	struct stat sb;
+	char *tmp;
+
+	if (lstat(glob_path, &sb) != 0) {
+		ERR("Error calling stat on path '%s': %s", glob_path,
+		    strerror(errno));
+		return;
+	}
+
+	if (!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode)) {
+		DPRINTCONF("'%s' is not a file.", glob_path);
+		ERR("The matched path '%s' is not a regular file", glob_path);
+		return;
+	}
+
+	DPRINTCONF("Include '%s'", glob_path);
+
+	if (S_ISLNK(sb.st_mode)) {
+		tmp = glob_path;
+		glob_path = realpath(tmp, NULL);
+	}
+
+	/* Ensure the file is not being reincluded .*/
+	if (check_no_reinclude(glob_path)) {
+		prepare_next_config(glob_path);
+		config();
+	} else {
+		DPRINTCONF("File '%s' already included in current include "
+		    "chain", glob_path);
+		WRN("Including file '%s' would cause a circular "
+		    "dependency", glob_path);
+	}
+
+	if (S_ISLNK(sb.st_mode)) {
+		free(glob_path);
+		glob_path = tmp;
+	}
+}
+
+static bool
+check_no_reinclude(const char *glob_path)
+{
+	struct file_list *cur = file_list_head;
+	char *abs_path = realpath(glob_path, NULL);
+
+	if (abs_path == NULL) {
+		ERR("Error checking real path for '%s': %s",
+			glob_path, strerror(errno));
+		return false;
+	}
+
+	DPRINTCONF("Absolute path '%s'", abs_path);
+
+	for (cur = file_list_head; cur != NULL; cur = cur->next) {
+		if (strcmp(cur->abs, abs_path) == 0) {
+			/* file included more than once */
+			/* TODO relative or abs path for logging error? */
+			free(abs_path);
+			return false;
+		}
+	}
+	free(abs_path);
+	return true;
+}
+
+/* Resolve the pattern relative to the config file the pattern is from  */
+static char *
+gen_file_pattern(const char *cur_config, const char *pattern)
+{
+	if (pattern[0] == '/') {
+		/* Absolute paths don't need any normalization */
+		return newstr(pattern);
+	}
+
+	/* pattern is relative */
+	/* Find the end of the file's directory */
+	size_t i, last = 0;
+	for (i = 0; cur_config[i] != '\0'; i++) {
+		if (cur_config[i] == '/') {
+			last = i;
+		}
+	}
+
+	if (last == 0) {
+		/* cur_config is just a filename, pattern already correct */
+		return newstr(pattern);
+	}
+
+	/* Relativize pattern to cur_config file's directory */
+	char *full_pattern = malloc(last + 1 + strlen(pattern) + 1);
+	if (full_pattern == NULL) {
+		syslog(LOG_ERR, "Out of memory.");
+		exit(EXIT_FAILURE);
+	}
+	memcpy(full_pattern, cur_config, last);
+	full_pattern[last] = '/';
+	strcpy(&full_pattern[last + 1], pattern);
+	return full_pattern;
+}
+
+static int
+glob_error(const char *path, int error)
+{
+	WRN("Error while resolving path '%s': %s", path, strerror(error));
+	return 0;
+}
Index: src/usr.sbin/inetd/ratelimit.c
diff -u /dev/null src/usr.sbin/inetd/ratelimit.c:1.1
--- /dev/null	Tue Oct 12 15:08:04 2021
+++ src/usr.sbin/inetd/ratelimit.c	Tue Oct 12 15:08:04 2021
@@ -0,0 +1,555 @@
+/*	$NetBSD: ratelimit.c,v 1.1 2021/10/12 19:08:04 christos Exp $	*/
+
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by James Browning, Gabe Coffland, Alex Gavin, and Solomon Ritzow.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: ratelimit.c,v 1.1 2021/10/12 19:08:04 christos Exp $");
+
+#include <sys/queue.h>
+
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stddef.h>
+
+#include "inetd.h"
+
+union addr {
+	struct in_addr	ipv4_addr;
+	/* ensure aligned for comparison in rl_ipv6_eq (already is on 64-bit) */
+#ifdef INET6
+	struct in6_addr	ipv6_addr __attribute__((aligned(16)));
+#endif
+	char		other_addr[NI_MAXHOST];
+};
+
+static void	rl_reset(struct servtab *, time_t);
+static time_t	rl_time(void);
+static void	rl_get_name(struct servtab *, int, union addr *);
+static void	rl_drop_connection(struct servtab *, int);
+static struct rl_ip_node	*rl_add(struct servtab *, union addr *);
+static struct rl_ip_node	*rl_try_get_ip(struct servtab *, union addr *);
+static bool	rl_ip_eq(struct servtab *, union addr *, struct rl_ip_node *);
+#ifdef INET6
+static bool	rl_ipv6_eq(struct in6_addr *, struct in6_addr *);
+#endif
+#ifdef DEBUG_ENABLE
+static void	rl_print_found_node(struct servtab *, struct rl_ip_node *);
+#endif
+static void	rl_log_address_exceed(struct servtab *, struct rl_ip_node *);
+static const char	*rl_node_tostring(struct servtab *, struct rl_ip_node *, char[NI_MAXHOST]);
+static bool	rl_process_service_max(struct servtab *, int, time_t *);
+static bool	rl_process_ip_max(struct servtab *, int, time_t *);
+
+/* Return 0 on allow, -1 if connection should be blocked */
+int
+rl_process(struct servtab *sep, int ctrl)
+{
+	time_t now = -1;
+
+	DPRINTF(SERV_FMT ": processing rate-limiting",
+	    SERV_PARAMS(sep));
+	DPRINTF(SERV_FMT ": se_service_max "
+	    "%zu and se_count %zu", SERV_PARAMS(sep),
+	    sep->se_service_max, sep->se_count);
+
+	if (sep->se_count == 0) {
+		now = rl_time();
+		sep->se_time = now;
+	}
+
+	if(!rl_process_service_max(sep, ctrl, &now) 
+	    || !rl_process_ip_max(sep, ctrl, &now)) {
+		return -1;
+	}
+
+	DPRINTF(SERV_FMT ": running service ", SERV_PARAMS(sep));
+
+	/* se_count is only incremented if rl_process will return 0 */
+	sep->se_count++;
+	return 0;
+}
+
+/*
+ * Get the identifier for the remote peer based on sep->se_socktype and
+ * sep->se_family
+ */
+static void
+rl_get_name(struct servtab *sep, int ctrl, union addr *out)
+{
+	union {
+		struct sockaddr_storage ss;
+		struct sockaddr sa;
+		struct sockaddr_in sin;
+		struct sockaddr_in6 sin6;
+	} addr;
+
+	/* Get the sockaddr of socket ctrl */
+	switch (sep->se_socktype) {
+	case SOCK_STREAM: {
+		socklen_t len = sizeof(struct sockaddr_storage);
+		if (getpeername(ctrl, &addr.sa, &len) == -1) {
+			/* error, log it and skip ip rate limiting */
+			syslog(LOG_ERR,
+			    SERV_FMT " failed to get peer name of the "
+			    "connection", SERV_PARAMS(sep));
+			exit(EXIT_FAILURE);
+		}
+		break;
+	}
+	case SOCK_DGRAM: {
+		struct msghdr header = {
+			.msg_name = &addr.sa,
+			.msg_namelen = sizeof(struct sockaddr_storage),
+			/* scatter/gather and control info is null */
+		};
+		ssize_t count;
+
+		/* Peek so service can still get the packet */
+		count = recvmsg(ctrl, &header, MSG_PEEK);
+		if (count == -1) {
+			syslog(LOG_ERR,
+			    "failed to get dgram source address: %s; exiting",
+			    strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+		break;
+	}
+	default:
+		DPRINTF(SERV_FMT ": ip_max rate limiting not supported for "
+		    "socktype", SERV_PARAMS(sep));
+		syslog(LOG_ERR, SERV_FMT
+		    ": ip_max rate limiting not supported for socktype",
+		    SERV_PARAMS(sep));
+		exit(EXIT_FAILURE);
+	}
+
+	/* Convert addr to to rate limiting address */
+	switch (sep->se_family) {
+		case AF_INET:
+			out->ipv4_addr = addr.sin.sin_addr;
+			break;
+#ifdef INET6
+		case AF_INET6:
+			out->ipv6_addr = addr.sin6.sin6_addr;
+			break;
+#endif
+		default: {
+			int res = getnameinfo(&addr.sa,
+			    (socklen_t)addr.sa.sa_len,
+			    out->other_addr, NI_MAXHOST,
+			    NULL, 0,
+			    NI_NUMERICHOST
+			);
+			if (res != 0) {
+				syslog(LOG_ERR,
+				    SERV_FMT ": failed to get name info of "
+				    "the incoming connection: %s; exiting",
+				    SERV_PARAMS(sep), gai_strerror(res));
+				exit(EXIT_FAILURE);
+			}
+			break;
+		}
+	}
+}
+
+static void
+rl_drop_connection(struct servtab *sep, int ctrl)
+{
+
+	if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) {
+		/*
+		 * If the fd isn't a listen socket,
+		 * close the individual connection too.
+		 */
+		close(ctrl);
+		return;
+	}
+	if (sep->se_socktype != SOCK_DGRAM) {
+		return;
+	}
+	/*
+	 * Drop the single datagram the service would have
+	 * consumed if nowait. If this is a wait service, this
+	 * will consume 1 datagram, and further received packets
+	 * will be removed in the same way.
+	 */
+	struct msghdr header = {
+		/* All fields null, just consume one message */
+	};
+	ssize_t count;
+
+	count = recvmsg(ctrl, &header, 0);
+	if (count == -1) {
+		syslog(LOG_ERR,
+		    SERV_FMT ": failed to consume nowait dgram: %s",
+		    SERV_PARAMS(sep), strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	DPRINTF(SERV_FMT ": dropped dgram message",
+	    SERV_PARAMS(sep));
+}
+
+static time_t
+rl_time(void)
+{
+	struct timespec ts;
+	if(clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
+		syslog(LOG_ERR, "clock_gettime for rate limiting failed: %s; "
+		    "exiting", strerror(errno));
+		/* Exit inetd if rate limiting fails */
+		exit(EXIT_FAILURE);
+	}
+	return ts.tv_sec;
+}
+
+/* Add addr to IP tracking or return NULL if malloc fails */
+static struct rl_ip_node *
+rl_add(struct servtab *sep, union addr *addr)
+{
+
+	struct rl_ip_node *node;
+	size_t node_size, bufsize;
+#ifdef DEBUG_ENABLE
+	char buffer[NI_MAXHOST];
+#endif
+
+	switch(sep->se_family) {
+	case AF_INET:
+		/* ip_node to end of IPv4 address */
+		node_size = offsetof(struct rl_ip_node, ipv4_addr)
+		    + sizeof(struct in_addr);
+		break;
+	case AF_INET6:
+		/* ip_node to end of IPv6 address */
+		node_size = offsetof(struct rl_ip_node, ipv6_addr)
+		    + sizeof(struct in6_addr);
+		break;
+	default:
+		/* ip_node to other_addr plus size of string + NULL */
+		bufsize = strlen(addr->other_addr) + sizeof(char);
+		node_size = offsetof(struct rl_ip_node, other_addr) + bufsize;
+		break;
+	}
+
+	node = malloc(node_size);
+	if (node == NULL) {
+		if(errno == ENOMEM) {
+			return NULL;
+		} else {
+			syslog(LOG_ERR, "malloc failed unexpectedly: %s",
+			    strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	node->count = 0;
+
+	/* copy the data into the new allocation */
+	switch(sep->se_family) {
+	case AF_INET:
+		node->ipv4_addr = addr->ipv4_addr;
+		break;
+	case AF_INET6:
+		/* Hopefully this is inlined, means the same thing as memcpy */
+		__builtin_memcpy(&node->ipv6_addr, &addr->ipv6_addr,
+		    sizeof(struct in6_addr));
+		break;
+	default:
+		strlcpy(node->other_addr, addr->other_addr, bufsize);
+		break;
+	}
+
+	/* initializes 'entries' member to NULL automatically */
+	SLIST_INSERT_HEAD(&sep->se_rl_ip_list, node, entries);
+
+	DPRINTF(SERV_FMT ": add '%s' to rate limit tracking (%zu byte record)",
+ 	    SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer), node_size);
+
+	return node;
+}
+
+static void
+rl_reset(struct servtab *sep, time_t now)
+{
+	DPRINTF(SERV_FMT ": %ji seconds passed; resetting rate limiting ",
+	    SERV_PARAMS(sep), (intmax_t)(now - sep->se_time));
+
+	sep->se_count = 0;
+	sep->se_time = now;
+	if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
+		rl_clear_ip_list(sep);
+	}
+}
+
+void
+rl_clear_ip_list(struct servtab *sep)
+{
+	while (!SLIST_EMPTY(&sep->se_rl_ip_list)) {
+		struct rl_ip_node *node = SLIST_FIRST(&sep->se_rl_ip_list);
+		SLIST_REMOVE_HEAD(&sep->se_rl_ip_list, entries);
+		free(node);
+	}
+}
+
+/* Get the node associated with addr, or NULL */
+static struct rl_ip_node *
+rl_try_get_ip(struct servtab *sep, union addr *addr)
+{
+
+	struct rl_ip_node *cur;
+	SLIST_FOREACH(cur, &sep->se_rl_ip_list, entries) {
+		if (rl_ip_eq(sep, addr, cur)) {
+			return cur;
+		}
+	}
+
+	return NULL;
+}
+
+/* Return true if passed service rate limiting checks, false if blocked */
+static bool
+rl_process_service_max(struct servtab *sep, int ctrl, time_t *now)
+{
+	if (sep->se_count >= sep->se_service_max) {
+		if(*now == -1) {
+			/* Only get the clock time if we didn't already */
+			*now = rl_time();
+		}
+
+		if (*now - sep->se_time > CNT_INTVL) {
+			rl_reset(sep, *now);
+		} else { 
+			syslog(LOG_ERR, SERV_FMT
+			    ": max spawn rate (%zu in %ji seconds) "
+			    "already met; closing for %ju seconds",
+			    SERV_PARAMS(sep),
+			    sep->se_service_max,
+			    (intmax_t)CNT_INTVL,
+			    (uintmax_t)RETRYTIME);
+			DPRINTF(SERV_FMT
+			    ": max spawn rate (%zu in %ji seconds) "
+			    "already met; closing for %ju seconds",
+			    SERV_PARAMS(sep),
+			    sep->se_service_max,
+			    (intmax_t)CNT_INTVL,
+			    (uintmax_t)RETRYTIME);
+
+			rl_drop_connection(sep, ctrl);
+
+			/* Close the server for 10 minutes */
+			close_sep(sep);
+			if (!timingout) {
+				timingout = true;
+				alarm(RETRYTIME);
+			}
+
+			return false;
+		}
+	}
+	return true;
+}
+
+/* Return true if passed IP rate limiting checks, false if blocked */
+static bool
+rl_process_ip_max(struct servtab *sep, int ctrl, time_t *now) {
+	if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
+		struct rl_ip_node *node;
+		union addr addr;
+
+		rl_get_name(sep, ctrl, &addr);
+		node = rl_try_get_ip(sep, &addr);
+		if (node == NULL) {
+			node = rl_add(sep, &addr);
+			if (node == NULL) {
+				/* If rl_add can't allocate, reject request */
+				DPRINTF("Cannot allocate rl_ip_node");
+				return false;
+			}
+		} 
+#ifdef DEBUG_ENABLE		
+		else {
+			/*
+			 * in a separate function to prevent large stack
+			 * frame
+			 */
+			rl_print_found_node(sep, node);
+		}
+#endif
+
+		DPRINTF(
+		    SERV_FMT ": se_ip_max %zu and ip_count %zu",
+		    SERV_PARAMS(sep), sep->se_ip_max, node->count);
+
+		if (node->count >= sep->se_ip_max) {
+			if (*now == -1) {
+				*now = rl_time();
+			}
+
+			if (*now - sep->se_time > CNT_INTVL) {
+				rl_reset(sep, *now);
+				node = rl_add(sep, &addr);
+				if (node == NULL) {
+					DPRINTF("Cannot allocate rl_ip_node");
+					return false;
+				}
+			} else {
+				if (debug && node->count == sep->se_ip_max) {
+					/*
+					 * Only log first failed request to
+					 * prevent DoS attack writing to system
+					 * log
+					 */
+					rl_log_address_exceed(sep, node);
+				} else {
+					DPRINTF(SERV_FMT
+					    ": service not started",
+					    SERV_PARAMS(sep));
+				}
+
+				rl_drop_connection(sep, ctrl);
+				/*
+				 * Increment so debug-syslog message will
+				 * trigger only once
+				 */
+				if (node->count < SIZE_MAX) {
+					node->count++;
+				}
+				return false;
+			}
+		}
+		node->count++;
+	}
+	return true;
+}
+
+static bool
+rl_ip_eq(struct servtab *sep, union addr *addr, struct rl_ip_node *cur) {
+	switch(sep->se_family) {
+	case AF_INET:
+		if (addr->ipv4_addr.s_addr == cur->ipv4_addr.s_addr) {
+			return true;
+		}
+		break;
+#ifdef INET6
+	case AF_INET6:
+		if(rl_ipv6_eq(&addr->ipv6_addr, &cur->ipv6_addr)) {
+			return true;
+		}
+		break;
+#endif
+	default:
+		if (strncmp(cur->other_addr, addr->other_addr, NI_MAXHOST)
+		    == 0) {
+			return true;
+		}
+		break;
+	}
+	return false;
+}
+
+#ifdef INET6
+static bool
+rl_ipv6_eq(struct in6_addr *a, struct in6_addr *b)
+{
+#if UINTMAX_MAX >= UINT64_MAX
+	{ /* requires 8 byte aligned structs */
+		uint64_t *ap = (uint64_t *)a->s6_addr;
+		uint64_t *bp = (uint64_t *)b->s6_addr;
+		return (ap[0] == bp[0]) & (ap[1] == bp[1]);
+	}
+#else
+	{ /* requires 4 byte aligned structs */
+		uint32_t *ap = (uint32_t *)a->s6_addr;
+		uint32_t *bp = (uint32_t *)b->s6_addr;
+		return ap[0] == bp[0] && ap[1] == bp[1] &&
+			ap[2] == bp[2] && ap[3] == bp[3];
+	}
+#endif
+}
+#endif
+
+static const char *
+rl_node_tostring(struct servtab *sep, struct rl_ip_node *node,
+    char buffer[NI_MAXHOST])
+{
+	switch (sep->se_family) {
+	case AF_INET:
+#ifdef INET6
+	case AF_INET6:
+#endif
+		/* ipv4_addr/ipv6_addr share same address */
+		return inet_ntop(sep->se_family, (void*)&node->ipv4_addr,
+		    (char*)buffer, NI_MAXHOST);
+	default:
+		return (char *)&node->other_addr;
+	}
+}
+
+#ifdef DEBUG_ENABLE
+/* Separate function due to large buffer size */
+static void
+rl_print_found_node(struct servtab *sep, struct rl_ip_node *node)
+{
+	char buffer[NI_MAXHOST];
+	DPRINTF(SERV_FMT ": found record for address '%s'",
+	    SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer));
+}
+#endif
+
+/* Separate function due to large buffer sie */
+static void
+rl_log_address_exceed(struct servtab *sep, struct rl_ip_node *node)
+{
+	char buffer[NI_MAXHOST];
+	const char * name = rl_node_tostring(sep, node, buffer);
+	syslog(LOG_ERR, SERV_FMT
+	    ": max ip spawn rate (%zu in "
+	    "%ji seconds) for "
+	    "'%." TOSTRING(NI_MAXHOST) "s' "
+	    "already met; service not started",
+	    SERV_PARAMS(sep),
+	    sep->se_ip_max,
+	    (intmax_t)CNT_INTVL,
+	    name);
+	DPRINTF(SERV_FMT
+	    ": max ip spawn rate (%zu in "
+	    "%ji seconds) for "
+	    "'%." TOSTRING(NI_MAXHOST) "s' "
+	    "already met; service not started",
+	    SERV_PARAMS(sep),
+	    sep->se_ip_max,
+	    (intmax_t)CNT_INTVL,
+	    name);
+}

Reply via email to