/* $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 #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(¤t_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; }