/*	$NetBSD: inetd.c,v 1.136 2021/09/03 21:02:04 rillig 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
__COPYRIGHT("@(#) Copyright (c) 1983, 1991, 1993, 1994\
 The Regents of the University of California.  All rights reserved.");
#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 $");
#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 (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*/
}

#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;
}