1377 lines
32 KiB
C
1377 lines
32 KiB
C
/* $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(¤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;
|
|
}
|