/*      $NetBSD: parse.c,v 1.5 2022/08/10 08:37:53 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.5 2022/08/10 08:37:53 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 /* RPC */
                       {
                               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 attempting 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;
}