Module Name: src
Committed By: christos
Date: Tue Oct 12 19:08:04 UTC 2021
Modified Files:
src/usr.sbin/inetd: Makefile inetd.8 inetd.c inetd.h parse_v2.c
Added Files:
src/usr.sbin/inetd: parse.c ratelimit.c
Log Message:
PR/56448: Solomon Ritzow: Various improvements.
Rate limiting code has been moved to ratelimit.c. I renamed
clear_ip_list to rl_clear_ip_list and broke the code up into more
functions. I have also made the per-IP rate limiting allocation more
efficient. IP addresses are now stored in their network format instead
of a string from getnameinfo (see inetd.h struct rl_ip_node). malloc
calls use only the space needed by the structure by using offsetof on
union members (I suppose this can be a bit dangerous if not done
correctly...). Per-IP rate limiting still supports textual comparison
using getnameinfo for address families other than AF_INET and AF_INET6, but I
don't think there are any that are actually compatible or used by inetd (I
haven't tested UNIX sockets with a remote bound to another file, but I did test
using IPv6 with the textual format by commenting out the IPv6 specific
code, and it works properly). Still potentially handy for the future.
The IP node list (se_rl_ip_list) now uses the <sys/queue.h> SLIST macros
instead of a custom list. I've broken rl_process up into helper functions
for each type of rate limiting and created a separate function for
address stringification, for use with printouts from the -d flag. I
tried to reduce stack memory use by moving printing code involving
string buffers into separate functions. I haven't tested rl_ipv6_eq on
a 32-bit system.
The code for the positional syntax has also been moved to parse.c.
Function try_biltin has been added to remove parse.c:parse_server's
dependency on the biltin structure definition.
File inetd.h has been updated with the proper function prototypes, and
the servtab structure has been update with the new IP node SLIST. I also
moved things around a bit. The way we (a peer and myself)
formatted inetd.h previously was somewhat confusing. Function and global
variable prototypes are now organized by the source file they are
defined in.
I also added a -f flag that I saw in another problem report
(https://gnats.netbsd.org/12823) that I thought could be useful. It
runs inetd in the foreground but without debug printouts or SO_DEBUG.
I'm not completely sure about the line "if (foreground) setsid()" that
I changed from "if (debug) setsid()".
To generate a diff of this commit:
cvs rdiff -u -r1.29 -r1.30 src/usr.sbin/inetd/Makefile
cvs rdiff -u -r1.64 -r1.65 src/usr.sbin/inetd/inetd.8
cvs rdiff -u -r1.136 -r1.137 src/usr.sbin/inetd/inetd.c
cvs rdiff -u -r1.3 -r1.4 src/usr.sbin/inetd/inetd.h
cvs rdiff -u -r0 -r1.1 src/usr.sbin/inetd/parse.c \
src/usr.sbin/inetd/ratelimit.c
cvs rdiff -u -r1.5 -r1.6 src/usr.sbin/inetd/parse_v2.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/usr.sbin/inetd/Makefile
diff -u src/usr.sbin/inetd/Makefile:1.29 src/usr.sbin/inetd/Makefile:1.30
--- src/usr.sbin/inetd/Makefile:1.29 Fri Sep 3 16:24:28 2021
+++ src/usr.sbin/inetd/Makefile Tue Oct 12 15:08:04 2021
@@ -1,12 +1,12 @@
# from: @(#)Makefile 8.1 (Berkeley) 6/6/93
-# $NetBSD: Makefile,v 1.29 2021/09/03 20:24:28 rillig Exp $
+# $NetBSD: Makefile,v 1.30 2021/10/12 19:08:04 christos Exp $
.include <bsd.own.mk>
USE_FORT?= yes # network server
PROG= inetd
-SRCS= inetd.c parse_v2.c
+SRCS= inetd.c parse.c parse_v2.c ratelimit.c
MAN= inetd.8
MLINKS= inetd.8 inetd.conf.5
WARNS= 6
Index: src/usr.sbin/inetd/inetd.8
diff -u src/usr.sbin/inetd/inetd.8:1.64 src/usr.sbin/inetd/inetd.8:1.65
--- src/usr.sbin/inetd/inetd.8:1.64 Tue Aug 31 07:16:00 2021
+++ src/usr.sbin/inetd/inetd.8 Tue Oct 12 15:08:04 2021
@@ -1,4 +1,4 @@
-.\" $NetBSD: inetd.8,v 1.64 2021/08/31 11:16:00 wiz Exp $
+.\" $NetBSD: inetd.8,v 1.65 2021/10/12 19:08:04 christos Exp $
.\"
.\" Copyright (c) 1998 The NetBSD Foundation, Inc.
.\" All rights reserved.
@@ -57,7 +57,7 @@
.\"
.\" from: @(#)inetd.8 8.4 (Berkeley) 6/1/94
.\"
-.Dd August 29, 2021
+.Dd October 12, 2021
.Dt INETD 8
.Os
.Sh NAME
@@ -92,7 +92,9 @@ The options available for
.Nm :
.Bl -tag -width Ds
.It Fl d
-Turns on debugging.
+Turns on debugging and runs inetd in the foreground.
+.It Fl f
+Runs inetd in the foreground.
.It Fl l
Turns on libwrap connection logging.
.El
@@ -519,7 +521,12 @@ Defaults to 40 if not specified.
.It Sy ip_max
Specifies the maximum number of server instances that may be spawned from
.Nm
-within an interval of 60 seconds for a given IP address.
+within an interval of 60 seconds for a given IP address. Other address types
+may also work if supported by
+.Xr getnameinfo 3
+, test thoroughly using -d. For example, connections from unnamed Unix sockets
+do not work, but connections from named Unix sockets may work. However, there is
+no way to only accept named Unix sockets.
.It Sy user
The user to run the program as.
Equivalent to
@@ -860,9 +867,6 @@ key), addresses with an interface specif
are not currently supported, as addresses of that form are not parsed by
.Xr inet_pton 3 .
.Pp
-.Dq tcpmux
-on IPv6 is not tested enough.
-.Pp
If a positional service definition has an invalid parameter and extends
across multiple lines using tab characters, the subsequent lines after the
error are treated as new service definitions.
Index: src/usr.sbin/inetd/inetd.c
diff -u src/usr.sbin/inetd/inetd.c:1.136 src/usr.sbin/inetd/inetd.c:1.137
--- src/usr.sbin/inetd/inetd.c:1.136 Fri Sep 3 17:02:04 2021
+++ src/usr.sbin/inetd/inetd.c Tue Oct 12 15:08:04 2021
@@ -1,4 +1,4 @@
-/* $NetBSD: inetd.c,v 1.136 2021/09/03 21:02:04 rillig Exp $ */
+/* $NetBSD: inetd.c,v 1.137 2021/10/12 19:08:04 christos Exp $ */
/*-
* Copyright (c) 1998, 2003 The NetBSD Foundation, Inc.
@@ -66,7 +66,7 @@ __COPYRIGHT("@(#) Copyright (c) 1983, 19
#if 0
static char sccsid[] = "@(#)inetd.c 8.4 (Berkeley) 4/13/94";
#else
-__RCSID("$NetBSD: inetd.c,v 1.136 2021/09/03 21:02:04 rillig Exp $");
+__RCSID("$NetBSD: inetd.c,v 1.137 2021/10/12 19:08:04 christos Exp $");
#endif
#endif /* not lint */
@@ -191,6 +191,7 @@ __RCSID("$NetBSD: inetd.c,v 1.136 2021/0
#include <sys/resource.h>
#include <sys/event.h>
#include <sys/socket.h>
+#include <sys/queue.h>
#ifndef NO_RPC
@@ -242,9 +243,7 @@ int allow_severity = LIBWRAP_ALLOW_FACIL
int deny_severity = LIBWRAP_DENY_FACILITY|LIBWRAP_DENY_SEVERITY;
#endif
-#define CNT_INTVL ((time_t)60) /* servers in CNT_INTVL sec. */
-#define RETRYTIME (60*10) /* retry after bind or server fail */
-
+static bool foreground;
int debug;
#ifdef LIBWRAP
int lflag;
@@ -272,32 +271,20 @@ struct servtab *servtab;
static void chargen_dg(int, struct servtab *);
static void chargen_stream(int, struct servtab *);
-static void close_sep(struct servtab *);
-static void config(void);
static void daytime_dg(int, struct servtab *);
static void daytime_stream(int, struct servtab *);
static void discard_dg(int, struct servtab *);
static void discard_stream(int, struct servtab *);
static void echo_dg(int, struct servtab *);
static void echo_stream(int, struct servtab *);
-static void endconfig(void);
-static struct servtab *enter(struct servtab *);
-static struct servtab *getconfigent(char **);
__dead static void goaway(void);
static void machtime_dg(int, struct servtab *);
static void machtime_stream(int, struct servtab *);
-#ifdef DEBUG_ENABLE
-static void print_service(const char *, struct servtab *);
-#endif
static void reapchild(void);
static void retry(void);
static void run_service(int, struct servtab *, int);
-static void setup(struct servtab *);
-static char *skip(char **);
static void tcpmux(int, struct servtab *);
__dead static void usage(void);
-static void register_rpc(struct servtab *);
-static void unregister_rpc(struct servtab *);
static void bump_nofile(void);
static void inetd_setproctitle(char *, int);
static void initring(void);
@@ -308,25 +295,6 @@ static int my_kevent(const struct kevent
static struct kevent *allocchange(void);
static int get_line(int, char *, int);
static void spawn(struct servtab *, int);
-static struct servtab init_servtab(void);
-static int rl_process(struct servtab *, int);
-static struct se_ip_list_node *rl_add(struct servtab *, char *);
-static void rl_reset(struct servtab *, time_t);
-static struct se_ip_list_node *rl_try_get_ip(struct servtab *, char *);
-static void include_configs(char *);
-static int glob_error(const char *, int);
-static void read_glob_configs(char *);
-static void prepare_next_config(const char*);
-static bool is_same_service(const struct servtab *, const struct servtab *);
-static char *gen_file_pattern(const char *, const char *);
-static bool check_no_reinclude(const char *);
-static void include_matched_path(char *);
-static void purge_unchecked(void);
-static void config_root(void);
-static void clear_ip_list(struct servtab *);
-static time_t rl_time(void);
-static void rl_get_name(struct servtab *, int, char *);
-static void rl_drop_connection(struct servtab *, int);
struct biltin {
const char *bi_service; /* internally provided service name */
@@ -356,9 +324,7 @@ struct biltin {
{ "chargen", SOCK_STREAM, true, false, chargen_stream },
{ "chargen", SOCK_DGRAM, false, false, chargen_dg },
- { "tcpmux", SOCK_STREAM, true, false, tcpmux },
-
- { NULL, 0, false, false, NULL }
+ { "tcpmux", SOCK_STREAM, true, false, tcpmux }
};
/* list of "bad" ports. I.e. ports that are most obviously used for
@@ -382,16 +348,20 @@ main(int argc, char *argv[])
while ((ch = getopt(argc, argv,
#ifdef LIBWRAP
- "dl"
+ "dfl"
#else
- "d"
+ "df"
#endif
)) != -1)
switch(ch) {
case 'd':
+ foreground = true;
debug = true;
options |= SO_DEBUG;
break;
+ case 'f':
+ foreground = true;
+ break;
#ifdef LIBWRAP
case 'l':
lflag = true;
@@ -407,7 +377,7 @@ main(int argc, char *argv[])
if (argc > 0)
CONFIG = argv[0];
- if (!debug)
+ if (!foreground)
daemon(0, 0);
openlog("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON);
pidfile(NULL);
@@ -537,14 +507,15 @@ spawn(struct servtab *sep, int ctrl)
for (n = 0; n < __arraycount(my_signals); n++)
(void) signal(my_signals[n], SIG_DFL);
- if (debug)
+ /* Don't put services in terminal session */
+ if (foreground)
setsid();
}
}
if (pid == 0) {
run_service(ctrl, sep, dofork);
if (dofork)
- exit(0);
+ exit(EXIT_SUCCESS);
}
if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM)
close(ctrl);
@@ -703,215 +674,6 @@ reapchild(void)
}
}
-size_t line_number;
-
-/*
- * Recursively merge loaded service definitions with any defined
- * in the current or included config files.
- */
-static void
-config(void)
-{
- struct servtab *sep, *cp;
- /*
- * Current position in line, used with key-values notation,
- * saves cp across getconfigent calls.
- */
- char *current_pos;
- size_t n;
-
- /* open config file from beginning */
- fconfig = fopen(CONFIG, "r");
- if(fconfig == NULL) {
- syslog(LOG_ERR, "%s: %m", CONFIG);
- return;
- }
-
- /* First call to nextline will advance line_number to 1 */
- line_number = 0;
-
- /* Start parsing at the beginning of the first line */
- current_pos = nextline(fconfig);
-
- while ((cp = getconfigent(¤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
- {
- if (sep->se_fd >= 0)
- close_sep(sep);
- if (sep->se_fd == -1 && !ISMUX(sep))
- setup(sep);
- }
- }
- }
- }
- endconfig();
-}
-
static void
retry(void)
{
@@ -959,10 +721,13 @@ goaway(void)
(void)close(sep->se_fd);
sep->se_fd = -1;
}
- exit(0);
+
+ DPRINTF("Going away.");
+
+ exit(EXIT_SUCCESS);
}
-static void
+void
setup(struct servtab *sep)
{
int on = 1;
@@ -1065,7 +830,7 @@ setsockopt(fd, SOL_SOCKET, opt, &on, (so
/*
* Finish with a service and its socket.
*/
-static void
+void
close_sep(struct servtab *sep)
{
@@ -1075,11 +840,11 @@ close_sep(struct servtab *sep)
}
sep->se_count = 0;
if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
- clear_ip_list(sep);
+ rl_clear_ip_list(sep);
}
}
-static void
+void
register_rpc(struct servtab *sep)
{
#ifdef RPC
@@ -1117,7 +882,7 @@ register_rpc(struct servtab *sep)
#endif /* RPC */
}
-static void
+void
unregister_rpc(struct servtab *sep)
{
#ifdef RPC
@@ -1140,561 +905,6 @@ unregister_rpc(struct servtab *sep)
#endif /* RPC */
}
-
-static struct servtab *
-enter(struct servtab *cp)
-{
- struct servtab *sep;
-
- sep = malloc(sizeof (*sep));
- if (sep == NULL) {
- syslog(LOG_ERR, "Out of memory.");
- exit(EXIT_FAILURE);
- }
- *sep = *cp;
- sep->se_fd = -1;
- sep->se_rpcprog = -1;
- sep->se_next = servtab;
- servtab = sep;
- return (sep);
-}
-
-FILE *fconfig;
-/* Temporary storage for new servtab */
-static struct servtab serv;
-/* Current line from current config file */
-static char line[LINE_MAX];
-char *defhost;
-#ifdef IPSEC
-char *policy;
-#endif
-
-static void
-endconfig(void)
-{
- if (fconfig != NULL) {
- (void) fclose(fconfig);
- fconfig = NULL;
- }
- if (defhost != NULL) {
- free(defhost);
- defhost = NULL;
- }
-
-#ifdef IPSEC
- if (policy != NULL) {
- free(policy);
- policy = NULL;
- }
-#endif
-
-}
-
-#define LOG_EARLY_ENDCONF() \
- ERR("Exiting %s early. Some services will be unavailable", CONFIG)
-
-#define LOG_TOO_FEW_ARGS() \
- ERR("Expected more arguments")
-
-/* Parse the next service and apply any directives, and returns it as servtab */
-static struct servtab *
-getconfigent(char **current_pos)
-{
- struct servtab *sep = &serv;
- int argc, val;
- char *cp, *cp0, *arg, *buf0, *buf1, *sz0, *sz1;
- static char TCPMUX_TOKEN[] = "tcpmux/";
-#define MUX_LEN (sizeof(TCPMUX_TOKEN)-1)
- char *hostdelim;
-
- /*
- * Pre-condition: current_pos points into line,
- * line contains config line. Continue where the last getconfigent left off.
- * Allows for multiple service definitions per line.
- */
- cp = *current_pos;
-
- if (false) {
- /*
- * Go to the next line, but only after attemting to read the current
- * one! Keep reading until we find a valid definition or EOF.
- */
-more:
- cp = nextline(fconfig);
- }
-
- if (cp == NULL) {
- /* EOF or I/O error, let config() know to exit the file */
- return NULL;
- }
-
- /* Comments and IPsec policies */
- if (cp[0] == '#') {
-#ifdef IPSEC
- /* lines starting with #@ is not a comment, but the policy */
- if (cp[1] == '@') {
- char *p;
- for (p = cp + 2; isspace((unsigned char)*p); p++)
- ;
- if (*p == '\0') {
- if (policy)
- free(policy);
- policy = NULL;
- } else {
- if (ipsecsetup_test(p) < 0) {
- ERR("Invalid IPsec policy \"%s\"", p);
- LOG_EARLY_ENDCONF();
- /*
- * Stop reading the current config to prevent services
- * from being run without IPsec.
- */
- return NULL;
- } else {
- if (policy)
- free(policy);
- policy = newstr(p);
- }
- }
- }
-#endif
-
- goto more;
- }
-
- /* Parse next token: listen-addr/hostname, service-spec, .include */
- arg = skip(&cp);
-
- if (cp == NULL) {
- goto more;
- }
-
- if(arg[0] == '.') {
- if (strcmp(&arg[1], "include") == 0) {
- /* include directive */
- arg = skip(&cp);
- if(arg == NULL) {
- LOG_TOO_FEW_ARGS();
- return NULL;
- }
- include_configs(arg);
- goto more;
- } else {
- ERR("Unknown directive '%s'", &arg[1]);
- goto more;
- }
- }
-
- /* After this point, we might need to store data in a servtab */
- *sep = init_servtab();
-
- /* Check for a host name. */
- hostdelim = strrchr(arg, ':');
- if (hostdelim != NULL) {
- *hostdelim = '\0';
- if (arg[0] == '[' && hostdelim > arg && hostdelim[-1] == ']') {
- hostdelim[-1] = '\0';
- sep->se_hostaddr = newstr(arg + 1);
- } else
- sep->se_hostaddr = newstr(arg);
- arg = hostdelim + 1;
- /*
- * If the line is of the form `host:', then just change the
- * default host for the following lines.
- */
- if (*arg == '\0') {
- arg = skip(&cp);
- if (cp == NULL) {
- free(defhost);
- defhost = sep->se_hostaddr;
- goto more;
- }
- }
- } else {
- /* No host address found, set it to NULL to indicate absence */
- sep->se_hostaddr = NULL;
- }
- if (strncmp(arg, TCPMUX_TOKEN, MUX_LEN) == 0) {
- char *c = arg + MUX_LEN;
- if (*c == '+') {
- sep->se_type = MUXPLUS_TYPE;
- c++;
- } else
- sep->se_type = MUX_TYPE;
- sep->se_service = newstr(c);
- } else {
- sep->se_service = newstr(arg);
- sep->se_type = NORM_TYPE;
- }
-
- DPRINTCONF("Found service definition '%s'", sep->se_service);
-
- /* on/off/socktype */
- arg = skip(&cp);
- if (arg == NULL) {
- LOG_TOO_FEW_ARGS();
- freeconfig(sep);
- goto more;
- }
-
- /* Check for new v2 syntax */
- if (strcmp(arg, "on") == 0 || strncmp(arg, "on#", 3) == 0) {
-
- if (arg[2] == '#') {
- cp = nextline(fconfig);
- }
-
- switch(parse_syntax_v2(sep, &cp)) {
- case V2_SUCCESS:
- *current_pos = cp;
- return sep;
- case V2_SKIP:
- /* Skip invalid definitions, freeconfig is called in parse_v2.c */
- *current_pos = cp;
- freeconfig(sep);
- goto more;
- case V2_ERROR:
- /*
- * Unrecoverable error, stop reading. freeconfig is called
- * in parse_v2.c
- */
- LOG_EARLY_ENDCONF();
- freeconfig(sep);
- return NULL;
- }
- } else if (strcmp(arg, "off") == 0 || strncmp(arg, "off#", 4) == 0) {
-
- if (arg[3] == '#') {
- cp = nextline(fconfig);
- }
-
- /* Parse syntax the same as with 'on', but ignore the result */
- switch(parse_syntax_v2(sep, &cp)) {
- case V2_SUCCESS:
- case V2_SKIP:
- *current_pos = cp;
- freeconfig(sep);
- goto more;
- case V2_ERROR:
- /* Unrecoverable error, stop reading */
- LOG_EARLY_ENDCONF();
- freeconfig(sep);
- return NULL;
- }
- } else {
- /* continue parsing v1 */
- parse_socktype(arg, sep);
- if (sep->se_socktype == SOCK_STREAM) {
- parse_accept_filter(arg, sep);
- }
- if (sep->se_hostaddr == NULL) {
- /* Set host to current default */
- sep->se_hostaddr = newstr(defhost);
- }
- }
-
- /* protocol */
- arg = skip(&cp);
- if (arg == NULL) {
- LOG_TOO_FEW_ARGS();
- freeconfig(sep);
- goto more;
- }
- if (sep->se_type == NORM_TYPE &&
- strncmp(arg, "faith/", strlen("faith/")) == 0) {
- arg += strlen("faith/");
- sep->se_type = FAITH_TYPE;
- }
- sep->se_proto = newstr(arg);
-
-#define MALFORMED(arg) \
-do { \
- ERR("%s: malformed buffer size option `%s'", \
- sep->se_service, (arg)); \
- freeconfig(sep); \
- goto more; \
-} while (false)
-
-#define GETVAL(arg) \
-do { \
- if (!isdigit((unsigned char)*(arg))) \
- MALFORMED(arg); \
- val = (int)strtol((arg), &cp0, 10); \
- if (cp0 != NULL) { \
- if (cp0[1] != '\0') \
- MALFORMED((arg)); \
- if (cp0[0] == 'k') \
- val *= 1024; \
- if (cp0[0] == 'm') \
- val *= 1024 * 1024; \
- } \
- if (val < 1) { \
- ERR("%s: invalid buffer size `%s'", \
- sep->se_service, (arg)); \
- freeconfig(sep); \
- goto more; \
- } \
-} while (false)
-
-#define ASSIGN(arg) \
-do { \
- if (strcmp((arg), "sndbuf") == 0) \
- sep->se_sndbuf = val; \
- else if (strcmp((arg), "rcvbuf") == 0) \
- sep->se_rcvbuf = val; \
- else \
- MALFORMED((arg)); \
-} while (false)
-
- /*
- * Extract the send and receive buffer sizes before parsing
- * the protocol.
- */
- sep->se_sndbuf = sep->se_rcvbuf = 0;
- buf0 = buf1 = sz0 = sz1 = NULL;
- if ((buf0 = strchr(sep->se_proto, ',')) != NULL) {
- /* Not meaningful for Tcpmux services. */
- if (ISMUX(sep)) {
- ERR("%s: can't specify buffer sizes for "
- "tcpmux services", sep->se_service);
- goto more;
- }
-
- /* Skip the , */
- *buf0++ = '\0';
-
- /* Check to see if another socket buffer size was specified. */
- if ((buf1 = strchr(buf0, ',')) != NULL) {
- /* Skip the , */
- *buf1++ = '\0';
-
- /* Make sure a 3rd one wasn't specified. */
- if (strchr(buf1, ',') != NULL) {
- ERR("%s: too many buffer sizes", sep->se_service);
- goto more;
- }
-
- /* Locate the size. */
- if ((sz1 = strchr(buf1, '=')) == NULL)
- MALFORMED(buf1);
-
- /* Skip the = */
- *sz1++ = '\0';
- }
-
- /* Locate the size. */
- if ((sz0 = strchr(buf0, '=')) == NULL)
- MALFORMED(buf0);
-
- /* Skip the = */
- *sz0++ = '\0';
-
- GETVAL(sz0);
- ASSIGN(buf0);
-
- if (buf1 != NULL) {
- GETVAL(sz1);
- ASSIGN(buf1);
- }
- }
-
-#undef ASSIGN
-#undef GETVAL
-#undef MALFORMED
-
- if (parse_protocol(sep)) {
- freeconfig(sep);
- goto more;
- }
-
- /* wait/nowait:max */
- arg = skip(&cp);
- if (arg == NULL) {
- LOG_TOO_FEW_ARGS();
- freeconfig(sep);
- goto more;
- }
-
- /* Rate limiting parsing */ {
- char *cp1;
- if ((cp1 = strchr(arg, ':')) == NULL)
- cp1 = strchr(arg, '.');
- if (cp1 != NULL) {
- int rstatus;
- *cp1++ = '\0';
- sep->se_service_max = (size_t)strtou(cp1, NULL, 10, 0,
- SERVTAB_COUNT_MAX, &rstatus);
-
- if (rstatus != 0) {
- if (rstatus != ERANGE) {
- /* For compatibility with atoi parsing */
- sep->se_service_max = 0;
- }
-
- WRN("Improper \"max\" value '%s', "
- "using '%zu' instead: %s",
- cp1,
- sep->se_service_max,
- strerror(rstatus));
- }
-
- } else
- sep->se_service_max = TOOMANY;
- }
- if (parse_wait(sep, strcmp(arg, "wait") == 0)) {
- freeconfig(sep);
- goto more;
- }
-
- /* Parse user:group token */
- arg = skip(&cp);
- if(arg == NULL) {
- LOG_TOO_FEW_ARGS();
- freeconfig(sep);
- goto more;
- }
- char* separator = strchr(arg, ':');
- if (separator == NULL) {
- /* Backwards compatibility, allow dot instead of colon */
- separator = strchr(arg, '.');
- }
-
- if (separator == NULL) {
- /* Only user was specified */
- sep->se_group = NULL;
- } else {
- *separator = '\0';
- sep->se_group = newstr(separator + 1);
- }
-
- sep->se_user = newstr(arg);
-
- /* Parser server-program (path to binary or "internal") */
- arg = skip(&cp);
- if (arg == NULL) {
- LOG_TOO_FEW_ARGS();
- freeconfig(sep);
- goto more;
- }
- if (parse_server(sep, arg)) {
- freeconfig(sep);
- goto more;
- }
-
- argc = 0;
- for (arg = skip(&cp); cp != NULL; arg = skip(&cp)) {
- if (argc < MAXARGV)
- sep->se_argv[argc++] = newstr(arg);
- }
- while (argc <= MAXARGV)
- sep->se_argv[argc++] = NULL;
-#ifdef IPSEC
- sep->se_policy = policy != NULL ? newstr(policy) : NULL;
-#endif
- /* getconfigent read a positional service def, move to next line */
- *current_pos = nextline(fconfig);
- return (sep);
-}
-
-void
-freeconfig(struct servtab *cp)
-{
- int i;
-
- free(cp->se_hostaddr);
- free(cp->se_service);
- free(cp->se_proto);
- free(cp->se_user);
- free(cp->se_group);
- free(cp->se_server);
- for (i = 0; i < MAXARGV; i++)
- free(cp->se_argv[i]);
-#ifdef IPSEC
- free(cp->se_policy);
-#endif
-}
-
-/*
- * Get next token *in the current service definition* from config file.
- * Allows multi-line parse if single space or single tab-indented.
- * Things in quotes are considered single token.
- * Advances cp to next token.
- */
-static char *
-skip(char **cpp)
-{
- char *cp = *cpp;
- char *start;
- char quote;
-
- if (*cpp == NULL)
- return (NULL);
-
-again:
- while (*cp == ' ' || *cp == '\t')
- cp++;
- if (*cp == '\0') {
- int c;
-
- c = getc(fconfig);
- (void) ungetc(c, fconfig);
- if (c == ' ' || c == '\t')
- if ((cp = nextline(fconfig)) != NULL)
- goto again;
- *cpp = NULL;
- return (NULL);
- }
- start = cp;
- /* Parse shell-style quotes */
- quote = '\0';
- while (*cp != '\0' && (quote != '\0' || (*cp != ' ' && *cp != '\t'))) {
- if (*cp == '\'' || *cp == '"') {
- if (quote != '\0' && *cp != quote)
- cp++;
- else {
- if (quote != '\0')
- quote = '\0';
- else
- quote = *cp;
- memmove(cp, cp+1, strlen(cp));
- }
- } else
- cp++;
- }
- if (*cp != '\0')
- *cp++ = '\0';
- *cpp = cp;
- return (start);
-}
-
-char *
-nextline(FILE *fd)
-{
- char *cp;
-
- if (fgets(line, (int)sizeof(line), fd) == NULL) {
- if (ferror(fd) != 0) {
- ERR("Error when reading next line: %s", strerror(errno));
- }
- return NULL;
- }
- cp = strchr(line, '\n');
- if (cp != NULL)
- *cp = '\0';
- line_number++;
- return line;
-}
-
-char *
-newstr(const char *cp)
-{
- char *dp;
- if ((dp = strdup((cp != NULL) ? cp : "")) != NULL)
- return (dp);
- syslog(LOG_ERR, "strdup: %m");
- exit(EXIT_FAILURE);
- /*NOTREACHED*/
-}
-
static void
inetd_setproctitle(char *a, int s)
{
@@ -1759,7 +969,7 @@ echo_stream(int s, struct servtab *sep)
inetd_setproctitle(sep->se_service, s);
while ((i = read(s, buffer, sizeof(buffer))) > 0 &&
write(s, buffer, (size_t)i) > 0)
- ;
+ continue;
}
/* ARGSUSED */
@@ -1975,49 +1185,6 @@ daytime_dg(int s, struct servtab *sep)
(void) sendto(s, buffer, (size_t)len, 0, sa, size);
}
-#ifdef DEBUG_ENABLE
-/*
- * print_service:
- * Dump relevant information to stderr
- */
-static void
-print_service(const char *action, struct servtab *sep)
-{
-
- if (isrpcservice(sep))
- fprintf(stderr,
- "%s: %s rpcprog=%d, rpcvers = %d/%d, proto=%s, wait.max=%d.%zu, user:group=%s:%s builtin=%lx server=%s"
-#ifdef IPSEC
- " policy=\"%s\""
-#endif
- "\n",
- action, sep->se_service,
- sep->se_rpcprog, sep->se_rpcversh, sep->se_rpcversl, sep->se_proto,
- sep->se_wait, sep->se_service_max, sep->se_user, sep->se_group,
- (long)sep->se_bi, sep->se_server
-#ifdef IPSEC
- , (sep->se_policy != NULL ? sep->se_policy : "")
-#endif
- );
- else
- fprintf(stderr,
- "%s: %s:%s proto=%s%s, wait.max=%d.%zu, user:group=%s:%s builtin=%lx server=%s"
-#ifdef IPSEC
- " policy=%s"
-#endif
- "\n",
- action, sep->se_hostaddr, sep->se_service,
- sep->se_type == FAITH_TYPE ? "faith/" : "",
- sep->se_proto,
- sep->se_wait, sep->se_service_max, sep->se_user, sep->se_group,
- (long)sep->se_bi, sep->se_server
-#ifdef IPSEC
- , (sep->se_policy != NULL ? sep->se_policy : "")
-#endif
- );
-}
-#endif
-
static void
usage(void)
{
@@ -2229,747 +1396,16 @@ allocchange(void)
return (&changebuf[changes++]);
}
-static void
-config_root(void)
-{
- struct servtab *sep;
- /* Uncheck services */
- for (sep = servtab; sep != NULL; sep = sep->se_next) {
- sep->se_checked = false;
- }
- defhost = newstr("*");
-#ifdef IPSEC
- policy = NULL;
-#endif
- fconfig = NULL;
- config();
- purge_unchecked();
-}
-
-static void
-purge_unchecked(void)
-{
- struct servtab *sep, **sepp = &servtab;
- int servtab_count = 0;
- while ((sep = *sepp) != NULL) {
- if (sep->se_checked) {
- sepp = &sep->se_next;
- servtab_count++;
- continue;
- }
- *sepp = sep->se_next;
- if (sep->se_fd >= 0)
- close_sep(sep);
- if (isrpcservice(sep))
- unregister_rpc(sep);
- if (sep->se_family == AF_LOCAL)
- (void)unlink(sep->se_service);
-#ifdef DEBUG_ENABLE
- if (debug)
- print_service("FREE", sep);
-#endif
- freeconfig(sep);
- free(sep);
- }
- DPRINTF("%d service(s) loaded.", servtab_count);
-}
-
-static bool
-is_same_service(const struct servtab *sep, const struct servtab *cp)
-{
- return
- strcmp(sep->se_service, cp->se_service) == 0 &&
- strcmp(sep->se_hostaddr, cp->se_hostaddr) == 0 &&
- strcmp(sep->se_proto, cp->se_proto) == 0 &&
- sep->se_family == cp->se_family &&
- ISMUX(sep) == ISMUX(cp);
-}
-
-int
-parse_protocol(struct servtab *sep)
-{
- int val;
-
- if (strcmp(sep->se_proto, "unix") == 0) {
- sep->se_family = AF_LOCAL;
- } else {
- val = (int)strlen(sep->se_proto);
- if (val == 0) {
- ERR("%s: invalid protocol specified",
- sep->se_service);
- return -1;
- }
- val = sep->se_proto[val - 1];
- switch (val) {
- case '4': /*tcp4 or udp4*/
- sep->se_family = AF_INET;
- break;
-#ifdef INET6
- case '6': /*tcp6 or udp6*/
- sep->se_family = AF_INET6;
- break;
-#endif
- default:
- /* Use 'default' IP version which is IPv4, may eventually be
- * changed to AF_INET6 */
- sep->se_family = AF_INET;
- break;
- }
- if (strncmp(sep->se_proto, "rpc/", 4) == 0) {
-#ifdef RPC
- char *cp1, *ccp;
- cp1 = strchr(sep->se_service, '/');
- if (cp1 == 0) {
- ERR("%s: no rpc version",
- sep->se_service);
- return -1;
- }
- *cp1++ = '\0';
- sep->se_rpcversl = sep->se_rpcversh =
- (int)strtol(cp1, &ccp, 0);
- if (ccp == cp1) {
- badafterall:
- ERR("%s/%s: bad rpc version",
- sep->se_service, cp1);
- return -1;
- }
- if (*ccp == '-') {
- cp1 = ccp + 1;
- sep->se_rpcversh = (int)strtol(cp1, &ccp, 0);
- if (ccp == cp1)
- goto badafterall;
- }
-#else
- ERR("%s: rpc services not supported",
- sep->se_service);
- return -1;
-#endif /* RPC */
- }
- }
- return 0;
-}
-
-int
-parse_wait(struct servtab *sep, int wait)
-{
- if (!ISMUX(sep)) {
- sep->se_wait = wait;
- return 0;
- }
- /*
- * Silently enforce "nowait" for TCPMUX services since
- * they don't have an assigned port to listen on.
- */
- sep->se_wait = 0;
-
- if (strncmp(sep->se_proto, "tcp", 3)) {
- ERR("bad protocol for tcpmux service %s",
- sep->se_service);
- return -1;
- }
- if (sep->se_socktype != SOCK_STREAM) {
- ERR("bad socket type for tcpmux service %s",
- sep->se_service);
- return -1;
- }
- return 0;
-}
-
-int
-parse_server(struct servtab *sep, const char *arg){
- sep->se_server = newstr(arg);
- if (strcmp(sep->se_server, "internal") != 0) {
- sep->se_bi = NULL;
- return 0;
- }
- struct biltin *bi;
-
- for (bi = biltins; bi->bi_service; bi++)
- if (bi->bi_socktype == sep->se_socktype &&
- strcmp(bi->bi_service, sep->se_service) == 0)
- break;
- if (bi->bi_service == NULL) {
- ERR("Internal service %s unknown",
- sep->se_service);
- return -1;
- }
- sep->se_bi = bi;
- sep->se_wait = bi->bi_wait;
- return 0;
-}
-
-/* TODO test to make sure accept filter still works */
-void
-parse_accept_filter(char *arg, struct servtab *sep) {
- char *accf, *accf_arg;
- /* one and only one accept filter */
- accf = strchr(arg, ':');
- if (accf == NULL)
- return;
- if (accf != strrchr(arg, ':') || *(accf + 1) == '\0') {
- /* more than one || nothing beyond */
- sep->se_socktype = -1;
- return;
- }
-
- accf++; /* skip delimiter */
- strlcpy(sep->se_accf.af_name, accf, sizeof(sep->se_accf.af_name));
- accf_arg = strchr(accf, ',');
- if (accf_arg == NULL) /* zero or one arg, no more */
- return;
-
- if (strrchr(accf, ',') != accf_arg) {
- sep->se_socktype = -1;
- } else {
- accf_arg++;
- strlcpy(sep->se_accf.af_arg, accf_arg,
- sizeof(sep->se_accf.af_arg));
- }
-}
-
-void
-parse_socktype(char* arg, struct servtab* sep)
-{
- /* stream socket may have an accept filter, only check first chars */
- if (strncmp(arg, "stream", sizeof("stream") - 1) == 0)
- sep->se_socktype = SOCK_STREAM;
- else if (strcmp(arg, "dgram") == 0)
- sep->se_socktype = SOCK_DGRAM;
- else if (strcmp(arg, "rdm") == 0)
- sep->se_socktype = SOCK_RDM;
- else if (strcmp(arg, "seqpacket") == 0)
- sep->se_socktype = SOCK_SEQPACKET;
- else if (strcmp(arg, "raw") == 0)
- sep->se_socktype = SOCK_RAW;
- else
- sep->se_socktype = -1;
-}
-
-static struct servtab
-init_servtab(void)
-{
- /* This does not set every field to default. See enter() as well */
- return (struct servtab) {
- /*
- * Set se_max to non-zero so uninitialized value is not
- * a valid value. Useful in v2 syntax parsing.
- */
- .se_service_max = SERVTAB_UNSPEC_SIZE_T,
- .se_ip_max = SERVTAB_UNSPEC_SIZE_T,
- .se_wait = SERVTAB_UNSPEC_VAL,
- .se_socktype = SERVTAB_UNSPEC_VAL
- /* All other fields initialized to 0 or null */
- };
-}
-
-/* Include directives bookkeeping structure */
-struct file_list {
- /* Absolute path used for checking for circular references */
- char *abs;
- /* Pointer to the absolute path of the parent config file,
- * on the stack */
- struct file_list *next;
-} *file_list_head;
-
-static void
-include_configs(char *pattern)
-{
- /* Allocate global per-config state on the thread stack */
- const char* save_CONFIG;
- FILE *save_fconfig;
- size_t save_line_number;
- char *save_defhost;
- struct file_list new_file;
-#ifdef IPSEC
- char *save_policy;
-#endif
-
- /* Store current globals on the stack */
- save_CONFIG = CONFIG;
- save_fconfig = fconfig;
- save_line_number = line_number;
- save_defhost = defhost;
- new_file.abs = realpath(CONFIG, NULL);
- new_file.next = file_list_head;
-#ifdef IPSEC
- save_policy = policy;
-#endif
- /* Put new_file at the top of the config stack */
- file_list_head = &new_file;
- read_glob_configs(pattern);
- free(new_file.abs);
- /* Pop new_file off the stack */
- file_list_head = new_file.next;
-
- /* Restore global per-config state */
- CONFIG = save_CONFIG;
- fconfig = save_fconfig;
- line_number = save_line_number;
- defhost = save_defhost;
-#ifdef IPSEC
- policy = save_policy;
-#endif
-}
-
-static void
-prepare_next_config(const char *file_name)
-{
- /* Setup new state that is normally only done in main */
- CONFIG = file_name;
-
- /* Inherit default host and IPsec policy */
- defhost = newstr(defhost);
-
-#ifdef IPSEC
- policy = (policy == NULL) ? NULL : newstr(policy);
-#endif
-}
-
-static void
-read_glob_configs(char *pattern) {
- glob_t results;
- char *full_pattern;
- int glob_result;
- full_pattern = gen_file_pattern(CONFIG, pattern);
-
- DPRINTCONF("Found include directive '%s'", full_pattern);
-
- glob_result = glob(full_pattern, GLOB_NOSORT, glob_error, &results);
- switch(glob_result) {
- case 0:
- /* No glob errors */
- break;
- case GLOB_ABORTED:
- ERR("Error while searching for include files");
- break;
- case GLOB_NOMATCH:
- /* It's fine if no files were matched. */
- DPRINTCONF("No files matched pattern '%s'", full_pattern);
- break;
- case GLOB_NOSPACE:
- ERR("Error when searching for include files: %s",
- strerror(errno));
- break;
- default:
- ERR("Unknown glob(3) error %d", errno);
- break;
- }
- free(full_pattern);
-
- for (size_t i = 0; i < results.gl_pathc; i++) {
- include_matched_path(results.gl_pathv[i]);
- }
-
- globfree(&results);
-}
-
-static void
-include_matched_path(char *glob_path)
-{
- struct stat sb;
- char *tmp;
-
- if (lstat(glob_path, &sb) != 0) {
- ERR("Error calling stat on path '%s': %s", glob_path,
- strerror(errno));
- return;
- }
-
- if (!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode)) {
- DPRINTCONF("'%s' is not a file.", glob_path);
- ERR("The matched path '%s' is not a regular file", glob_path);
- return;
- }
-
- DPRINTCONF("Include '%s'", glob_path);
-
- if (S_ISLNK(sb.st_mode)) {
- tmp = glob_path;
- glob_path = realpath(tmp, NULL);
- }
-
- /* Ensure the file is not being reincluded .*/
- if (check_no_reinclude(glob_path)) {
- prepare_next_config(glob_path);
- config();
- } else {
- DPRINTCONF("File '%s' already included in current include "
- "chain", glob_path);
- WRN("Including file '%s' would cause a circular "
- "dependency", glob_path);
- }
-
- if (S_ISLNK(sb.st_mode)) {
- free(glob_path);
- glob_path = tmp;
- }
-}
-
-static bool
-check_no_reinclude(const char *glob_path)
-{
- struct file_list *cur = file_list_head;
- char *abs_path = realpath(glob_path, NULL);
-
- if (abs_path == NULL) {
- ERR("Error checking real path for '%s': %s",
- glob_path, strerror(errno));
- return false;
- }
-
- DPRINTCONF("Absolute path '%s'", abs_path);
-
- for (cur = file_list_head; cur != NULL; cur = cur->next) {
- if (strcmp(cur->abs, abs_path) == 0) {
- /* file included more than once */
- /* TODO relative or abs path for logging error? */
- free(abs_path);
- return false;
- }
- }
- free(abs_path);
- return true;
-}
-
-/* Resolve the pattern relative to the config file the pattern is from */
-static char *
-gen_file_pattern(const char *cur_config, const char *pattern)
-{
- if (pattern[0] == '/') {
- /* Absolute paths don't need any normalization */
- return newstr(pattern);
- }
-
- /* pattern is relative */
- /* Find the end of the file's directory */
- size_t i, last = 0;
- for (i = 0; cur_config[i] != '\0'; i++) {
- if (cur_config[i] == '/') {
- last = i;
- }
- }
-
- if (last == 0) {
- /* cur_config is just a filename, pattern already correct */
- return newstr(pattern);
- }
-
- /* Relativize pattern to cur_config file's directory */
- char *full_pattern = malloc(last + 1 + strlen(pattern) + 1);
- if (full_pattern == NULL) {
- syslog(LOG_ERR, "Out of memory.");
- exit(EXIT_FAILURE);
- }
- memcpy(full_pattern, cur_config, last);
- full_pattern[last] = '/';
- strcpy(&full_pattern[last + 1], pattern);
- return full_pattern;
-}
-
-static int
-glob_error(const char *path, int error)
-{
- WRN("Error while resolving path '%s': %s", path, strerror(error));
- return 0;
-}
-
-/* Return 0 on allow, -1 if connection should be blocked */
-static int
-rl_process(struct servtab *sep, int ctrl)
-{
- struct se_ip_list_node *node;
- time_t now = 0; /* 0 prevents GCC from complaining */
- bool istimevalid = false;
- char hbuf[NI_MAXHOST];
-
- DPRINTF(SERV_FMT ": processing rate-limiting",
- SERV_PARAMS(sep));
- DPRINTF(SERV_FMT ": se_service_max "
- "%zu and se_count %zu", SERV_PARAMS(sep),
- sep->se_service_max, sep->se_count);
-
- /* se_count is incremented if rl_process will return 0 */
- if (sep->se_count == 0) {
- now = rl_time();
- sep->se_time = now;
- istimevalid = true;
- }
-
- if (sep->se_count >= sep->se_service_max) {
- if(!istimevalid) {
- now = rl_time();
- istimevalid = true;
- }
-
- if (now - sep->se_time > CNT_INTVL) {
- rl_reset(sep, now);
- } else {
- syslog(LOG_ERR,
- SERV_FMT ": max spawn rate (%zu in %ji seconds) "
- "already met, closing until end of timeout in "
- "%ju seconds",
- SERV_PARAMS(sep),
- sep->se_service_max,
- (intmax_t)CNT_INTVL,
- (uintmax_t)RETRYTIME);
-
- DPRINTF(SERV_FMT ": service not started",
- SERV_PARAMS(sep));
-
- rl_drop_connection(sep, ctrl);
-
- /* Close the server for 10 minutes */
- close_sep(sep);
- if (!timingout) {
- timingout = true;
- alarm(RETRYTIME);
- }
-
- return -1;
- }
- }
-
- if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
- rl_get_name(sep, ctrl, hbuf);
- node = rl_try_get_ip(sep, hbuf);
- if(node == NULL) {
- node = rl_add(sep, hbuf);
- }
-
- DPRINTF(
- SERV_FMT ": se_ip_max %zu and ip_count %zu",
- SERV_PARAMS(sep), sep->se_ip_max, node->count);
-
- if (node->count >= sep->se_ip_max) {
- if (!istimevalid) {
- /*
- * Only get the clock time if we didn't
- * already
- */
- now = rl_time();
- istimevalid = true;
- }
-
- if (now - sep->se_time > CNT_INTVL) {
- rl_reset(sep, now);
- node = rl_add(sep, hbuf);
- } else {
- if (debug && node->count == sep->se_ip_max) {
- /*
- * Only log first failed request to
- * prevent DoS attack writing to system
- * log
- */
- syslog(LOG_ERR, SERV_FMT
- ": max ip spawn rate (%zu in "
- "%ji seconds) for "
- "%." TOSTRING(NI_MAXHOST) "s "
- "already met; service not started",
- SERV_PARAMS(sep),
- sep->se_ip_max,
- (intmax_t)CNT_INTVL,
- node->address);
- }
-
- DPRINTF(SERV_FMT ": service not started",
- SERV_PARAMS(sep));
-
- rl_drop_connection(sep, ctrl);
- /*
- * Increment so debug-syslog message will
- * trigger only once
- */
- node->count++;
- return -1;
- }
- }
- node->count++;
- }
-
- DPRINTF(SERV_FMT ": running service ", SERV_PARAMS(sep));
-
- sep->se_count++;
- return 0;
-}
-
-/* Get the remote's IP address in textual form into hbuf of size NI_MAXHOST */
-static void
-rl_get_name(struct servtab *sep, int ctrl, char *hbuf)
+bool
+try_biltin(struct servtab *sep)
{
- struct sockaddr_storage addr;
- socklen_t len = sizeof(struct sockaddr_storage);
- switch (sep->se_socktype) {
- case SOCK_STREAM:
- if (getpeername(ctrl, (struct sockaddr *)&addr, &len) != 0) {
- /* error, log it and skip ip rate limiting */
- syslog(LOG_ERR,
- SERV_FMT " failed to get peer name of the "
- "connection", SERV_PARAMS(sep));
- exit(EXIT_FAILURE);
- }
- break;
- case SOCK_DGRAM: {
- struct msghdr header = {
- .msg_name = &addr,
- .msg_namelen = sizeof(struct sockaddr_storage),
- /* scatter/gather and control info is null */
- };
- ssize_t count;
-
- /* Peek so service can still get the packet */
- count = recvmsg(ctrl, &header, MSG_PEEK);
- if (count == -1) {
- syslog(LOG_ERR,
- "failed to get dgram source address: %s; exiting",
- strerror(errno));
- exit(EXIT_FAILURE);
- }
- break;
- }
- default:
- DPRINTF(SERV_FMT ": ip_max rate limiting not supported for "
- "socktype", SERV_PARAMS(sep));
- syslog(LOG_ERR, SERV_FMT
- ": ip_max rate limiting not supported for socktype",
- SERV_PARAMS(sep));
- exit(EXIT_FAILURE);
- }
-
- if (getnameinfo((struct sockaddr *)&addr,
- addr.ss_len, hbuf,
- NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) {
- /* error, log it and skip ip rate limiting */
- syslog(LOG_ERR,
- SERV_FMT ": failed to get name info of the incoming "
- "connection; exiting",
- SERV_PARAMS(sep));
- exit(EXIT_FAILURE);
- }
-}
-
-static void
-rl_drop_connection(struct servtab *sep, int ctrl)
-{
-
- if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) {
- /*
- * If the fd isn't a listen socket,
- * close the individual connection too.
- */
- close(ctrl);
- return;
- }
- if (sep->se_socktype != SOCK_DGRAM) {
- return;
- }
- /*
- * Drop the single datagram the service would have
- * consumed if nowait. If this is a wait service, this
- * will consume 1 datagram, and further received packets
- * will be removed in the same way.
- */
- struct msghdr header = {
- /* All fields null, just consume one message */
- };
- ssize_t count;
-
- count = recvmsg(ctrl, &header, 0);
- if (count == -1) {
- syslog(LOG_ERR,
- SERV_FMT ": failed to consume nowait dgram: %s",
- SERV_PARAMS(sep), strerror(errno));
- exit(EXIT_FAILURE);
- }
- DPRINTF(SERV_FMT ": dropped dgram message",
- SERV_PARAMS(sep));
-}
-
-static time_t
-rl_time(void)
-{
- struct timespec time;
- if(clock_gettime(CLOCK_MONOTONIC, &time) == -1) {
- syslog(LOG_ERR, "clock_gettime for rate limiting failed: %s; "
- "exiting", strerror(errno));
- /* Exit inetd if rate limiting fails */
- exit(EXIT_FAILURE);
- }
- return time.tv_sec;
-}
-
-static struct se_ip_list_node*
-rl_add(struct servtab *sep, char* ip)
-{
- DPRINTF(
- SERV_FMT ": add ip %s to rate limiting tracking",
- SERV_PARAMS(sep), ip);
-
- /*
- * TODO memory could be saved by using a variable length malloc
- * with only the length of ip instead of the existing address field
- * NI_MAXHOST in length.
- */
- struct se_ip_list_node* temp = malloc(sizeof(*temp));
- if (temp == NULL) {
- syslog(LOG_ERR, "Out of memory.");
- exit(EXIT_FAILURE);
- }
- temp->count = 0;
- temp->next = NULL;
- strlcpy(temp->address, ip, sizeof(temp->address));
-
- if (sep->se_ip_list_head == NULL) {
- /* List empty, insert as head */
- sep->se_ip_list_head = temp;
- } else {
- /* List not empty, insert as head, point next to prev head */
- temp->next = sep->se_ip_list_head;
- sep->se_ip_list_head = temp;
- }
-
- return temp;
-}
-
-static void
-rl_reset(struct servtab *sep, time_t now)
-{
- DPRINTF(SERV_FMT ": %ji seconds passed; resetting rate limiting ",
- SERV_PARAMS(sep), (intmax_t)(now - sep->se_time));
-
- sep->se_count = 0;
- sep->se_time = now;
- if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
- clear_ip_list(sep);
- }
-}
-
-static void
-clear_ip_list(struct servtab *sep) {
- struct se_ip_list_node *curr, *next;
- curr = sep->se_ip_list_head;
-
- while (curr != NULL) {
- next = curr->next;
- free(curr);
- curr = next;
- }
- sep->se_ip_list_head = NULL;
-}
-
-static struct se_ip_list_node *
-rl_try_get_ip(struct servtab *sep, char *ip)
-{
- struct se_ip_list_node *curr;
-
- DPRINTF(
- SERV_FMT ": look up ip %s for ip_max rate limiting",
- SERV_PARAMS(sep), ip);
-
- for (curr = sep->se_ip_list_head; curr != NULL; curr = curr->next) {
- if (!strncmp(curr->address, ip, NI_MAXHOST)) {
- /* IP addr match */
- return curr;
+ for(size_t i = 0; i < __arraycount(biltins); i++) {
+ if (biltins[i].bi_socktype == sep->se_socktype &&
+ strcmp(biltins[i].bi_service, sep->se_service) == 0) {
+ sep->se_bi = &biltins[i];
+ sep->se_wait = biltins[i].bi_wait;
+ return true;
}
}
- return NULL;
+ return false;
}
Index: src/usr.sbin/inetd/inetd.h
diff -u src/usr.sbin/inetd/inetd.h:1.3 src/usr.sbin/inetd/inetd.h:1.4
--- src/usr.sbin/inetd/inetd.h:1.3 Fri Sep 3 16:24:28 2021
+++ src/usr.sbin/inetd/inetd.h Tue Oct 12 15:08:04 2021
@@ -1,4 +1,4 @@
-/* $NetBSD: inetd.h,v 1.3 2021/09/03 20:24:28 rillig Exp $ */
+/* $NetBSD: inetd.h,v 1.4 2021/10/12 19:08:04 christos Exp $ */
/*-
* Copyright (c) 1998, 2003 The NetBSD Foundation, Inc.
@@ -66,12 +66,14 @@
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
+#include <sys/queue.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdbool.h>
+
#include "pathnames.h"
#ifdef IPSEC
@@ -121,6 +123,21 @@ typedef enum service_type {
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
+/* "Unspecified" indicator value for servtabs (mainly used by v2 syntax) */
+#define SERVTAB_UNSPEC_VAL -1
+
+#define SERVTAB_UNSPEC_SIZE_T SIZE_MAX
+
+#define SERVTAB_COUNT_MAX (SIZE_MAX - (size_t)1)
+
+/* Standard logging and debug print format for a servtab */
+#define SERV_FMT "%s/%s"
+#define SERV_PARAMS(sep) sep->se_service,sep->se_proto
+
+/* rate limiting macros */
+#define CNT_INTVL ((time_t)60) /* servers in CNT_INTVL sec. */
+#define RETRYTIME (60*10) /* retry after bind or server fail */
+
struct servtab {
char *se_hostaddr; /* host address to listen on */
char *se_service; /* name of service */
@@ -148,30 +165,79 @@ struct servtab {
int se_fd; /* open descriptor */
service_type se_type; /* type */
union {
- struct sockaddr_storage se_ctrladdr_storage; /* ensure correctness of C struct initializer */
+ /* ensure correctness of C struct initializer */
+ struct sockaddr_storage se_ctrladdr_storage;
struct sockaddr se_ctrladdr;
struct sockaddr_in se_ctrladdr_in;
struct sockaddr_in6 se_ctrladdr_in6; /* in6 is used by bind()/getaddrinfo */
struct sockaddr_un se_ctrladdr_un;
}; /* bound address */
socklen_t se_ctrladdr_size;
- size_t se_service_max; /* max # of instances of this service */
+ size_t se_service_max; /* max # of instances of this service per minute */
size_t se_count; /* number of instances of this service started since se_time */
size_t se_ip_max; /* max # of instances of this service per ip per minute */
- struct se_ip_list_node {
- struct se_ip_list_node *next;
- size_t count; /*
- * number of instances of this service started from
- * this ip address since se_time (includes
- * attempted starts if greater than se_ip_max)
- */
- char address[NI_MAXHOST];
- } *se_ip_list_head; /* linked list of number of requests per ip */
+ SLIST_HEAD(iplist, rl_ip_node) se_rl_ip_list; /* per-address (IP) rate limting */
time_t se_time; /* start of se_count and ip_max counts, in seconds from arbitrary point */
+
+ /* TODO convert to using SLIST */
struct servtab *se_next;
};
-/* From inetd.c */
+struct rl_ip_node {
+ /* Linked list entries */
+ SLIST_ENTRY(rl_ip_node) entries;
+ /*
+ * Number of service spawns from *_addr since se_time (includes
+ * attempted starts if greater than se_ip_max).
+ */
+ size_t count;
+ union {
+ struct in_addr ipv4_addr;
+#ifdef INET6
+ /* align for efficient comparison in rl_try_get, could use 8 instead */
+ struct in6_addr ipv6_addr __attribute__((aligned(16)));
+#endif
+ /*
+ * other_addr is used for other address types besides the
+ * special cases (IPv4/IPv6), using getnameinfo.
+ */
+ struct {
+ /* A field is required before the special array member */
+ char _placeholder;
+ /* malloc'd storage varies with length of string */
+ char other_addr[];
+ };
+ };
+ /*
+ * Do not declare further members after union, offsetof is used to
+ * determine malloc size.
+ */
+};
+
+/*
+ * From inetd.c
+ */
+
+void setup(struct servtab *);
+void close_sep(struct servtab *);
+void register_rpc(struct servtab *);
+void unregister_rpc(struct servtab *);
+bool try_biltin(struct servtab *);
+
+/* Global debug mode boolean, enabled with -d */
+extern int debug;
+
+/* rate limit or other error timed out flag */
+extern int timingout;
+
+/* servtab linked list */
+extern struct servtab *servtab;
+
+/*
+ * From parse.c
+ */
+
+void config_root(void);
int parse_protocol(struct servtab *);
int parse_wait(struct servtab *, int);
int parse_server(struct servtab *, const char *);
@@ -179,27 +245,32 @@ void parse_socktype(char *, struct serv
void parse_accept_filter(char *, struct servtab *);
char *nextline(FILE *);
char *newstr(const char *);
-void freeconfig(struct servtab *);
-/* Global debug mode boolean, enabled with -d */
-extern int debug;
+/* Current line number in current config file */
+extern size_t line_number;
/* Current config file path */
-extern const char *CONFIG;
+extern const char *CONFIG;
/* Open config file */
extern FILE *fconfig;
-/* Current line number in current config file */
-extern size_t line_number;
-
/* Default listening hostname/IP for current config file */
-extern char *defhost;
+extern char *defhost;
/* Default IPsec policy for current config file */
-extern char *policy;
+extern char *policy;
+
+/*
+ * From ratelimit.c
+ */
-/* From parse_v2.c */
+int rl_process(struct servtab *, int);
+void rl_clear_ip_list(struct servtab *);
+
+/*
+ * From parse_v2.c
+ */
typedef enum parse_v2_result {V2_SUCCESS, V2_SKIP, V2_ERROR} parse_v2_result;
@@ -210,15 +281,4 @@ typedef enum parse_v2_result {V2_SUCCESS
*/
parse_v2_result parse_syntax_v2(struct servtab *, char **);
-/* "Unspecified" indicator value for servtabs (mainly used by v2 syntax) */
-#define SERVTAB_UNSPEC_VAL -1
-
-#define SERVTAB_UNSPEC_SIZE_T SIZE_MAX
-
-#define SERVTAB_COUNT_MAX (SIZE_MAX - (size_t)1)
-
-/* Standard logging and debug print format for a servtab */
-#define SERV_FMT "%s/%s"
-#define SERV_PARAMS(sep) sep->se_service,sep->se_proto
-
#endif
Index: src/usr.sbin/inetd/parse_v2.c
diff -u src/usr.sbin/inetd/parse_v2.c:1.5 src/usr.sbin/inetd/parse_v2.c:1.6
--- src/usr.sbin/inetd/parse_v2.c:1.5 Fri Sep 3 16:24:28 2021
+++ src/usr.sbin/inetd/parse_v2.c Tue Oct 12 15:08:04 2021
@@ -1,4 +1,4 @@
-/* $NetBSD: parse_v2.c,v 1.5 2021/09/03 20:24:28 rillig Exp $ */
+/* $NetBSD: parse_v2.c,v 1.6 2021/10/12 19:08:04 christos Exp $ */
/*-
* Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
*/
#include <sys/cdefs.h>
-__RCSID("$NetBSD: parse_v2.c,v 1.5 2021/09/03 20:24:28 rillig Exp $");
+__RCSID("$NetBSD: parse_v2.c,v 1.6 2021/10/12 19:08:04 christos Exp $");
#include <ctype.h>
#include <errno.h>
@@ -153,11 +153,9 @@ parse_syntax_v2(struct servtab *sep, cha
bool is_valid_definition = true;
/* Line number of service for error logging. */
size_t line_number_start = line_number;
- invoke_result result;
for (;;) {
- switch(result =
- parse_invoke_handler(&is_valid_definition, cpp, sep)) {
+ switch(parse_invoke_handler(&is_valid_definition, cpp, sep)) {
case INVOKE_SUCCESS:
/* Keep reading more options in. */
continue;
@@ -658,6 +656,7 @@ is_internal(struct servtab *sep)
*/
static hresult
+/*ARGSUSED*/
unknown_handler(struct servtab *sep, vlist values)
{
/* Return failure for an unknown service name. */
Added files:
Index: src/usr.sbin/inetd/parse.c
diff -u /dev/null src/usr.sbin/inetd/parse.c:1.1
--- /dev/null Tue Oct 12 15:08:04 2021
+++ src/usr.sbin/inetd/parse.c Tue Oct 12 15:08:04 2021
@@ -0,0 +1,1376 @@
+/* $NetBSD: parse.c,v 1.1 2021/10/12 19:08:04 christos Exp $ */
+
+/*-
+ * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
+ * NASA Ames Research Center and by Matthias Scheler.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1983, 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)inetd.c 8.4 (Berkeley) 4/13/94";
+#else
+__RCSID("$NetBSD: parse.c,v 1.1 2021/10/12 19:08:04 christos Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * This file contains code and state for loading and managing servtabs.
+ * The "positional" syntax parsing is performed in this file. See parse_v2.c
+ * for "key-values" syntax parsing.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "inetd.h"
+
+static void config(void);
+static void endconfig(void);
+static struct servtab *enter(struct servtab *);
+static struct servtab *getconfigent(char **);
+#ifdef DEBUG_ENABLE
+static void print_service(const char *, struct servtab *);
+#endif
+static struct servtab init_servtab(void);
+static void include_configs(char *);
+static int glob_error(const char *, int);
+static void read_glob_configs(char *);
+static void prepare_next_config(const char*);
+static bool is_same_service(const struct servtab *, const struct servtab *);
+static char *gen_file_pattern(const char *, const char *);
+static bool check_no_reinclude(const char *);
+static void include_matched_path(char *);
+static void purge_unchecked(void);
+static void freeconfig(struct servtab *);
+static char *skip(char **);
+
+size_t line_number;
+FILE *fconfig;
+/* Temporary storage for new servtab */
+static struct servtab serv;
+/* Current line from current config file */
+static char line[LINE_MAX];
+char *defhost;
+#ifdef IPSEC
+char *policy;
+#endif
+
+/*
+ * Recursively merge loaded service definitions with any defined
+ * in the current or included config files.
+ */
+static void
+config(void)
+{
+ struct servtab *sep, *cp;
+ /*
+ * Current position in line, used with key-values notation,
+ * saves cp across getconfigent calls.
+ */
+ char *current_pos;
+ size_t n;
+
+ /* open config file from beginning */
+ fconfig = fopen(CONFIG, "r");
+ if (fconfig == NULL) {
+ DPRINTF("Could not open file \"%s\": %s",
+ CONFIG, strerror(errno));
+ syslog(LOG_ERR, "%s: %m", CONFIG);
+ return;
+ }
+
+ /* First call to nextline will advance line_number to 1 */
+ line_number = 0;
+
+ /* Start parsing at the beginning of the first line */
+ current_pos = nextline(fconfig);
+
+ while ((cp = getconfigent(¤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
+ {
+ if (sep->se_fd >= 0)
+ close_sep(sep);
+ if (sep->se_fd == -1 && !ISMUX(sep))
+ setup(sep);
+ }
+ }
+ }
+ }
+ endconfig();
+}
+
+static struct servtab *
+enter(struct servtab *cp)
+{
+ struct servtab *sep;
+
+ sep = malloc(sizeof (*sep));
+ if (sep == NULL) {
+ syslog(LOG_ERR, "Out of memory.");
+ exit(EXIT_FAILURE);
+ }
+ *sep = *cp;
+ sep->se_fd = -1;
+ sep->se_rpcprog = -1;
+ sep->se_next = servtab;
+ servtab = sep;
+ return (sep);
+}
+
+static void
+endconfig(void)
+{
+ if (fconfig != NULL) {
+ (void) fclose(fconfig);
+ fconfig = NULL;
+ }
+ if (defhost != NULL) {
+ free(defhost);
+ defhost = NULL;
+ }
+
+#ifdef IPSEC
+ if (policy != NULL) {
+ free(policy);
+ policy = NULL;
+ }
+#endif
+
+}
+
+#define LOG_EARLY_ENDCONF() \
+ ERR("Exiting %s early. Some services will be unavailable", CONFIG)
+
+#define LOG_TOO_FEW_ARGS() \
+ ERR("Expected more arguments")
+
+/* Parse the next service and apply any directives, and returns it as servtab */
+static struct servtab *
+getconfigent(char **current_pos)
+{
+ struct servtab *sep = &serv;
+ int argc, val;
+ char *cp, *cp0, *arg, *buf0, *buf1, *sz0, *sz1;
+ static char TCPMUX_TOKEN[] = "tcpmux/";
+#define MUX_LEN (sizeof(TCPMUX_TOKEN)-1)
+ char *hostdelim;
+
+ /*
+ * Pre-condition: current_pos points into line,
+ * line contains config line. Continue where the last getconfigent
+ * left off. Allows for multiple service definitions per line.
+ */
+ cp = *current_pos;
+
+ if (/*CONSTCOND*/false) {
+ /*
+ * Go to the next line, but only after attemting to read the
+ * current one! Keep reading until we find a valid definition
+ * or EOF.
+ */
+more:
+ cp = nextline(fconfig);
+ }
+
+ if (cp == NULL) {
+ /* EOF or I/O error, let config() know to exit the file */
+ return NULL;
+ }
+
+ /* Comments and IPsec policies */
+ if (cp[0] == '#') {
+#ifdef IPSEC
+ /* lines starting with #@ is not a comment, but the policy */
+ if (cp[1] == '@') {
+ char *p;
+ for (p = cp + 2; isspace((unsigned char)*p); p++)
+ ;
+ if (*p == '\0') {
+ if (policy)
+ free(policy);
+ policy = NULL;
+ } else {
+ if (ipsecsetup_test(p) < 0) {
+ ERR("Invalid IPsec policy \"%s\"", p);
+ LOG_EARLY_ENDCONF();
+ /*
+ * Stop reading the current config to
+ * prevent services from being run
+ * without IPsec.
+ */
+ return NULL;
+ } else {
+ if (policy)
+ free(policy);
+ policy = newstr(p);
+ }
+ }
+ }
+#endif
+
+ goto more;
+ }
+
+ /* Parse next token: listen-addr/hostname, service-spec, .include */
+ arg = skip(&cp);
+
+ if (cp == NULL) {
+ goto more;
+ }
+
+ if(arg[0] == '.') {
+ if (strcmp(&arg[1], "include") == 0) {
+ /* include directive */
+ arg = skip(&cp);
+ if(arg == NULL) {
+ LOG_TOO_FEW_ARGS();
+ return NULL;
+ }
+ include_configs(arg);
+ goto more;
+ } else {
+ ERR("Unknown directive '%s'", &arg[1]);
+ goto more;
+ }
+ }
+
+ /* After this point, we might need to store data in a servtab */
+ *sep = init_servtab();
+
+ /* Check for a host name. */
+ hostdelim = strrchr(arg, ':');
+ if (hostdelim != NULL) {
+ *hostdelim = '\0';
+ if (arg[0] == '[' && hostdelim > arg && hostdelim[-1] == ']') {
+ hostdelim[-1] = '\0';
+ sep->se_hostaddr = newstr(arg + 1);
+ } else
+ sep->se_hostaddr = newstr(arg);
+ arg = hostdelim + 1;
+ /*
+ * If the line is of the form `host:', then just change the
+ * default host for the following lines.
+ */
+ if (*arg == '\0') {
+ arg = skip(&cp);
+ if (cp == NULL) {
+ free(defhost);
+ defhost = sep->se_hostaddr;
+ goto more;
+ }
+ }
+ } else {
+ /* No host address found, set it to NULL to indicate absence */
+ sep->se_hostaddr = NULL;
+ }
+ if (strncmp(arg, TCPMUX_TOKEN, MUX_LEN) == 0) {
+ char *c = arg + MUX_LEN;
+ if (*c == '+') {
+ sep->se_type = MUXPLUS_TYPE;
+ c++;
+ } else
+ sep->se_type = MUX_TYPE;
+ sep->se_service = newstr(c);
+ } else {
+ sep->se_service = newstr(arg);
+ sep->se_type = NORM_TYPE;
+ }
+
+ DPRINTCONF("Found service definition '%s'", sep->se_service);
+
+ /* on/off/socktype */
+ arg = skip(&cp);
+ if (arg == NULL) {
+ LOG_TOO_FEW_ARGS();
+ freeconfig(sep);
+ goto more;
+ }
+
+ /* Check for new v2 syntax */
+ if (strcmp(arg, "on") == 0 || strncmp(arg, "on#", 3) == 0) {
+
+ if (arg[2] == '#') {
+ cp = nextline(fconfig);
+ }
+
+ switch(parse_syntax_v2(sep, &cp)) {
+ case V2_SUCCESS:
+ *current_pos = cp;
+ return sep;
+ case V2_SKIP:
+ /*
+ * Skip invalid definitions, freeconfig is called in
+ * parse_v2.c
+ */
+ *current_pos = cp;
+ freeconfig(sep);
+ goto more;
+ case V2_ERROR:
+ /*
+ * Unrecoverable error, stop reading. freeconfig
+ * is called in parse_v2.c
+ */
+ LOG_EARLY_ENDCONF();
+ freeconfig(sep);
+ return NULL;
+ }
+ } else if (strcmp(arg, "off") == 0 || strncmp(arg, "off#", 4) == 0) {
+
+ if (arg[3] == '#') {
+ cp = nextline(fconfig);
+ }
+
+ /* Parse syntax the same as with 'on', but ignore the result */
+ switch(parse_syntax_v2(sep, &cp)) {
+ case V2_SUCCESS:
+ case V2_SKIP:
+ *current_pos = cp;
+ freeconfig(sep);
+ goto more;
+ case V2_ERROR:
+ /* Unrecoverable error, stop reading */
+ LOG_EARLY_ENDCONF();
+ freeconfig(sep);
+ return NULL;
+ }
+ } else {
+ /* continue parsing v1 */
+ parse_socktype(arg, sep);
+ if (sep->se_socktype == SOCK_STREAM) {
+ parse_accept_filter(arg, sep);
+ }
+ if (sep->se_hostaddr == NULL) {
+ /* Set host to current default */
+ sep->se_hostaddr = newstr(defhost);
+ }
+ }
+
+ /* protocol */
+ arg = skip(&cp);
+ if (arg == NULL) {
+ LOG_TOO_FEW_ARGS();
+ freeconfig(sep);
+ goto more;
+ }
+ if (sep->se_type == NORM_TYPE &&
+ strncmp(arg, "faith/", strlen("faith/")) == 0) {
+ arg += strlen("faith/");
+ sep->se_type = FAITH_TYPE;
+ }
+ sep->se_proto = newstr(arg);
+
+#define MALFORMED(arg) \
+do { \
+ ERR("%s: malformed buffer size option `%s'", \
+ sep->se_service, (arg)); \
+ freeconfig(sep); \
+ goto more; \
+} while (false)
+
+#define GETVAL(arg) \
+do { \
+ if (!isdigit((unsigned char)*(arg))) \
+ MALFORMED(arg); \
+ val = (int)strtol((arg), &cp0, 10); \
+ if (cp0 != NULL) { \
+ if (cp0[1] != '\0') \
+ MALFORMED((arg)); \
+ if (cp0[0] == 'k') \
+ val *= 1024; \
+ if (cp0[0] == 'm') \
+ val *= 1024 * 1024; \
+ } \
+ if (val < 1) { \
+ ERR("%s: invalid buffer size `%s'", \
+ sep->se_service, (arg)); \
+ freeconfig(sep); \
+ goto more; \
+ } \
+} while (false)
+
+#define ASSIGN(arg) \
+do { \
+ if (strcmp((arg), "sndbuf") == 0) \
+ sep->se_sndbuf = val; \
+ else if (strcmp((arg), "rcvbuf") == 0) \
+ sep->se_rcvbuf = val; \
+ else \
+ MALFORMED((arg)); \
+} while (false)
+
+ /*
+ * Extract the send and receive buffer sizes before parsing
+ * the protocol.
+ */
+ sep->se_sndbuf = sep->se_rcvbuf = 0;
+ buf0 = buf1 = sz0 = sz1 = NULL;
+ if ((buf0 = strchr(sep->se_proto, ',')) != NULL) {
+ /* Not meaningful for Tcpmux services. */
+ if (ISMUX(sep)) {
+ ERR("%s: can't specify buffer sizes for "
+ "tcpmux services", sep->se_service);
+ goto more;
+ }
+
+ /* Skip the , */
+ *buf0++ = '\0';
+
+ /* Check to see if another socket buffer size was specified. */
+ if ((buf1 = strchr(buf0, ',')) != NULL) {
+ /* Skip the , */
+ *buf1++ = '\0';
+
+ /* Make sure a 3rd one wasn't specified. */
+ if (strchr(buf1, ',') != NULL) {
+ ERR("%s: too many buffer sizes",
+ sep->se_service);
+ goto more;
+ }
+
+ /* Locate the size. */
+ if ((sz1 = strchr(buf1, '=')) == NULL)
+ MALFORMED(buf1);
+
+ /* Skip the = */
+ *sz1++ = '\0';
+ }
+
+ /* Locate the size. */
+ if ((sz0 = strchr(buf0, '=')) == NULL)
+ MALFORMED(buf0);
+
+ /* Skip the = */
+ *sz0++ = '\0';
+
+ GETVAL(sz0);
+ ASSIGN(buf0);
+
+ if (buf1 != NULL) {
+ GETVAL(sz1);
+ ASSIGN(buf1);
+ }
+ }
+
+#undef ASSIGN
+#undef GETVAL
+#undef MALFORMED
+
+ if (parse_protocol(sep)) {
+ freeconfig(sep);
+ goto more;
+ }
+
+ /* wait/nowait:max */
+ arg = skip(&cp);
+ if (arg == NULL) {
+ LOG_TOO_FEW_ARGS();
+ freeconfig(sep);
+ goto more;
+ }
+
+ /* Rate limiting parsing */ {
+ char *cp1;
+ if ((cp1 = strchr(arg, ':')) == NULL)
+ cp1 = strchr(arg, '.');
+ if (cp1 != NULL) {
+ int rstatus;
+ *cp1++ = '\0';
+ sep->se_service_max = (size_t)strtou(cp1, NULL, 10, 0,
+ SERVTAB_COUNT_MAX, &rstatus);
+
+ if (rstatus != 0) {
+ if (rstatus != ERANGE) {
+ /* For compatibility w/ atoi parsing */
+ sep->se_service_max = 0;
+ }
+
+ WRN("Improper \"max\" value '%s', "
+ "using '%zu' instead: %s",
+ cp1,
+ sep->se_service_max,
+ strerror(rstatus));
+ }
+
+ } else
+ sep->se_service_max = TOOMANY;
+ }
+ if (parse_wait(sep, strcmp(arg, "wait") == 0)) {
+ freeconfig(sep);
+ goto more;
+ }
+
+ /* Parse user:group token */
+ arg = skip(&cp);
+ if(arg == NULL) {
+ LOG_TOO_FEW_ARGS();
+ freeconfig(sep);
+ goto more;
+ }
+ char* separator = strchr(arg, ':');
+ if (separator == NULL) {
+ /* Backwards compatibility, allow dot instead of colon */
+ separator = strchr(arg, '.');
+ }
+
+ if (separator == NULL) {
+ /* Only user was specified */
+ sep->se_group = NULL;
+ } else {
+ *separator = '\0';
+ sep->se_group = newstr(separator + 1);
+ }
+
+ sep->se_user = newstr(arg);
+
+ /* Parser server-program (path to binary or "internal") */
+ arg = skip(&cp);
+ if (arg == NULL) {
+ LOG_TOO_FEW_ARGS();
+ freeconfig(sep);
+ goto more;
+ }
+ if (parse_server(sep, arg)) {
+ freeconfig(sep);
+ goto more;
+ }
+
+ argc = 0;
+ for (arg = skip(&cp); cp != NULL; arg = skip(&cp)) {
+ if (argc < MAXARGV)
+ sep->se_argv[argc++] = newstr(arg);
+ }
+ while (argc <= MAXARGV)
+ sep->se_argv[argc++] = NULL;
+#ifdef IPSEC
+ sep->se_policy = policy != NULL ? newstr(policy) : NULL;
+#endif
+ /* getconfigent read a positional service def, move to next line */
+ *current_pos = nextline(fconfig);
+ return (sep);
+}
+
+void
+freeconfig(struct servtab *cp)
+{
+ int i;
+
+ free(cp->se_hostaddr);
+ free(cp->se_service);
+ free(cp->se_proto);
+ free(cp->se_user);
+ free(cp->se_group);
+ free(cp->se_server);
+ for (i = 0; i < MAXARGV; i++)
+ free(cp->se_argv[i]);
+#ifdef IPSEC
+ free(cp->se_policy);
+#endif
+}
+
+/*
+ * Get next token *in the current service definition* from config file.
+ * Allows multi-line parse if single space or single tab-indented.
+ * Things in quotes are considered single token.
+ * Advances cp to next token.
+ */
+static char *
+skip(char **cpp)
+{
+ char *cp = *cpp;
+ char *start;
+ char quote;
+
+ if (*cpp == NULL)
+ return (NULL);
+
+again:
+ while (*cp == ' ' || *cp == '\t')
+ cp++;
+ if (*cp == '\0') {
+ int c;
+
+ c = getc(fconfig);
+ (void) ungetc(c, fconfig);
+ if (c == ' ' || c == '\t')
+ if ((cp = nextline(fconfig)) != NULL)
+ goto again;
+ *cpp = NULL;
+ return (NULL);
+ }
+ start = cp;
+ /* Parse shell-style quotes */
+ quote = '\0';
+ while (*cp != '\0' && (quote != '\0' || (*cp != ' ' && *cp != '\t'))) {
+ if (*cp == '\'' || *cp == '"') {
+ if (quote != '\0' && *cp != quote)
+ cp++;
+ else {
+ if (quote != '\0')
+ quote = '\0';
+ else
+ quote = *cp;
+ memmove(cp, cp+1, strlen(cp));
+ }
+ } else
+ cp++;
+ }
+ if (*cp != '\0')
+ *cp++ = '\0';
+ *cpp = cp;
+ return (start);
+}
+
+char *
+nextline(FILE *fd)
+{
+ char *cp;
+
+ if (fgets(line, (int)sizeof(line), fd) == NULL) {
+ if (ferror(fd) != 0) {
+ ERR("Error when reading next line: %s",
+ strerror(errno));
+ }
+ return NULL;
+ }
+ cp = strchr(line, '\n');
+ if (cp != NULL)
+ *cp = '\0';
+ line_number++;
+ return line;
+}
+
+char *
+newstr(const char *cp)
+{
+ char *dp;
+ if ((dp = strdup((cp != NULL) ? cp : "")) != NULL)
+ return (dp);
+ syslog(LOG_ERR, "strdup: %m");
+ exit(EXIT_FAILURE);
+ /*NOTREACHED*/
+}
+
+#ifdef DEBUG_ENABLE
+/*
+ * print_service:
+ * Dump relevant information to stderr
+ */
+static void
+print_service(const char *action, struct servtab *sep)
+{
+
+ if (isrpcservice(sep))
+ fprintf(stderr,
+ "%s: %s rpcprog=%d, rpcvers = %d/%d, proto=%s, "
+ "wait.max=%d.%zu, "
+ "user:group=%s:%s builtin=%lx server=%s"
+#ifdef IPSEC
+ " policy=\"%s\""
+#endif
+ "\n",
+ action, sep->se_service,
+ sep->se_rpcprog, sep->se_rpcversh, sep->se_rpcversl,
+ sep->se_proto, sep->se_wait, sep->se_service_max,
+ sep->se_user, sep->se_group,
+ (long)sep->se_bi, sep->se_server
+#ifdef IPSEC
+ , (sep->se_policy != NULL ? sep->se_policy : "")
+#endif
+ );
+ else
+ fprintf(stderr,
+ "%s: %s:%s proto=%s%s, wait.max=%d.%zu, user:group=%s:%s "
+ "builtin=%lx "
+ "server=%s"
+#ifdef IPSEC
+ " policy=%s"
+#endif
+ "\n",
+ action, sep->se_hostaddr, sep->se_service,
+ sep->se_type == FAITH_TYPE ? "faith/" : "",
+ sep->se_proto,
+ sep->se_wait, sep->se_service_max, sep->se_user,
+ sep->se_group, (long)sep->se_bi, sep->se_server
+#ifdef IPSEC
+ , (sep->se_policy != NULL ? sep->se_policy : "")
+#endif
+ );
+}
+#endif
+
+void
+config_root(void)
+{
+ struct servtab *sep;
+ /* Uncheck services */
+ for (sep = servtab; sep != NULL; sep = sep->se_next) {
+ sep->se_checked = false;
+ }
+ defhost = newstr("*");
+#ifdef IPSEC
+ policy = NULL;
+#endif
+ fconfig = NULL;
+ config();
+ purge_unchecked();
+}
+
+static void
+purge_unchecked(void)
+{
+ struct servtab *sep, **sepp = &servtab;
+ int servtab_count = 0;
+ while ((sep = *sepp) != NULL) {
+ if (sep->se_checked) {
+ sepp = &sep->se_next;
+ servtab_count++;
+ continue;
+ }
+ *sepp = sep->se_next;
+ if (sep->se_fd >= 0)
+ close_sep(sep);
+ if (isrpcservice(sep))
+ unregister_rpc(sep);
+ if (sep->se_family == AF_LOCAL)
+ (void)unlink(sep->se_service);
+#ifdef DEBUG_ENABLE
+ if (debug)
+ print_service("FREE", sep);
+#endif
+ freeconfig(sep);
+ free(sep);
+ }
+ DPRINTF("%d service(s) loaded.", servtab_count);
+}
+
+static bool
+is_same_service(const struct servtab *sep, const struct servtab *cp)
+{
+ return
+ strcmp(sep->se_service, cp->se_service) == 0 &&
+ strcmp(sep->se_hostaddr, cp->se_hostaddr) == 0 &&
+ strcmp(sep->se_proto, cp->se_proto) == 0 &&
+ sep->se_family == cp->se_family &&
+ ISMUX(sep) == ISMUX(cp);
+}
+
+int
+parse_protocol(struct servtab *sep)
+{
+ int val;
+
+ if (strcmp(sep->se_proto, "unix") == 0) {
+ sep->se_family = AF_LOCAL;
+ } else {
+ val = (int)strlen(sep->se_proto);
+ if (val == 0) {
+ ERR("%s: invalid protocol specified",
+ sep->se_service);
+ return -1;
+ }
+ val = sep->se_proto[val - 1];
+ switch (val) {
+ case '4': /*tcp4 or udp4*/
+ sep->se_family = AF_INET;
+ break;
+#ifdef INET6
+ case '6': /*tcp6 or udp6*/
+ sep->se_family = AF_INET6;
+ break;
+#endif
+ default:
+ /*
+ * Use 'default' IP version which is IPv4, may
+ * eventually be changed to AF_INET6
+ */
+ sep->se_family = AF_INET;
+ break;
+ }
+ if (strncmp(sep->se_proto, "rpc/", 4) == 0) {
+#ifdef RPC
+ char *cp1, *ccp;
+ cp1 = strchr(sep->se_service, '/');
+ if (cp1 == 0) {
+ ERR("%s: no rpc version",
+ sep->se_service);
+ return -1;
+ }
+ *cp1++ = '\0';
+ sep->se_rpcversl = sep->se_rpcversh =
+ (int)strtol(cp1, &ccp, 0);
+ if (ccp == cp1) {
+ badafterall:
+ ERR("%s/%s: bad rpc version",
+ sep->se_service, cp1);
+ return -1;
+ }
+ if (*ccp == '-') {
+ cp1 = ccp + 1;
+ sep->se_rpcversh = (int)strtol(cp1, &ccp, 0);
+ if (ccp == cp1)
+ goto badafterall;
+ }
+#else
+ ERR("%s: rpc services not supported",
+ sep->se_service);
+ return -1;
+#endif /* RPC */
+ }
+ }
+ return 0;
+}
+
+int
+parse_wait(struct servtab *sep, int wait)
+{
+ if (!ISMUX(sep)) {
+ sep->se_wait = wait;
+ return 0;
+ }
+ /*
+ * Silently enforce "nowait" for TCPMUX services since
+ * they don't have an assigned port to listen on.
+ */
+ sep->se_wait = 0;
+
+ if (strncmp(sep->se_proto, "tcp", 3)) {
+ ERR("bad protocol for tcpmux service %s",
+ sep->se_service);
+ return -1;
+ }
+ if (sep->se_socktype != SOCK_STREAM) {
+ ERR("bad socket type for tcpmux service %s",
+ sep->se_service);
+ return -1;
+ }
+ return 0;
+}
+
+int
+parse_server(struct servtab *sep, const char *arg)
+{
+ sep->se_server = newstr(arg);
+ if (strcmp(sep->se_server, "internal") != 0) {
+ sep->se_bi = NULL;
+ return 0;
+ }
+
+ if (!try_biltin(sep)) {
+ ERR("Internal service %s unknown", sep->se_service);
+ return -1;
+ }
+ return 0;
+}
+
+/* TODO test to make sure accept filter still works */
+void
+parse_accept_filter(char *arg, struct servtab *sep)
+{
+ char *accf, *accf_arg;
+ /* one and only one accept filter */
+ accf = strchr(arg, ':');
+ if (accf == NULL)
+ return;
+ if (accf != strrchr(arg, ':') || *(accf + 1) == '\0') {
+ /* more than one || nothing beyond */
+ sep->se_socktype = -1;
+ return;
+ }
+
+ accf++; /* skip delimiter */
+ strlcpy(sep->se_accf.af_name, accf, sizeof(sep->se_accf.af_name));
+ accf_arg = strchr(accf, ',');
+ if (accf_arg == NULL) /* zero or one arg, no more */
+ return;
+
+ if (strrchr(accf, ',') != accf_arg) {
+ sep->se_socktype = -1;
+ } else {
+ accf_arg++;
+ strlcpy(sep->se_accf.af_arg, accf_arg,
+ sizeof(sep->se_accf.af_arg));
+ }
+}
+
+void
+parse_socktype(char* arg, struct servtab* sep)
+{
+ /* stream socket may have an accept filter, only check first chars */
+ if (strncmp(arg, "stream", sizeof("stream") - 1) == 0)
+ sep->se_socktype = SOCK_STREAM;
+ else if (strcmp(arg, "dgram") == 0)
+ sep->se_socktype = SOCK_DGRAM;
+ else if (strcmp(arg, "rdm") == 0)
+ sep->se_socktype = SOCK_RDM;
+ else if (strcmp(arg, "seqpacket") == 0)
+ sep->se_socktype = SOCK_SEQPACKET;
+ else if (strcmp(arg, "raw") == 0)
+ sep->se_socktype = SOCK_RAW;
+ else
+ sep->se_socktype = -1;
+}
+
+static struct servtab
+init_servtab(void)
+{
+ /* This does not set every field to default. See enter() as well */
+ return (struct servtab) {
+ /*
+ * Set se_max to non-zero so uninitialized value is not
+ * a valid value. Useful in v2 syntax parsing.
+ */
+ .se_service_max = SERVTAB_UNSPEC_SIZE_T,
+ .se_ip_max = SERVTAB_UNSPEC_SIZE_T,
+ .se_wait = SERVTAB_UNSPEC_VAL,
+ .se_socktype = SERVTAB_UNSPEC_VAL,
+ .se_rl_ip_list = SLIST_HEAD_INITIALIZER(se_ip_list_head)
+ /* All other fields initialized to 0 or null */
+ };
+}
+
+/* Include directives bookkeeping structure */
+struct file_list {
+ /* Absolute path used for checking for circular references */
+ char *abs;
+ /* Pointer to the absolute path of the parent config file,
+ * on the stack */
+ struct file_list *next;
+} *file_list_head;
+
+static void
+include_configs(char *pattern)
+{
+ /* Allocate global per-config state on the thread stack */
+ const char* save_CONFIG;
+ FILE *save_fconfig;
+ size_t save_line_number;
+ char *save_defhost;
+ struct file_list new_file;
+#ifdef IPSEC
+ char *save_policy;
+#endif
+
+ /* Store current globals on the stack */
+ save_CONFIG = CONFIG;
+ save_fconfig = fconfig;
+ save_line_number = line_number;
+ save_defhost = defhost;
+ new_file.abs = realpath(CONFIG, NULL);
+ new_file.next = file_list_head;
+#ifdef IPSEC
+ save_policy = policy;
+#endif
+ /* Put new_file at the top of the config stack */
+ file_list_head = &new_file;
+ read_glob_configs(pattern);
+ free(new_file.abs);
+ /* Pop new_file off the stack */
+ file_list_head = new_file.next;
+
+ /* Restore global per-config state */
+ CONFIG = save_CONFIG;
+ fconfig = save_fconfig;
+ line_number = save_line_number;
+ defhost = save_defhost;
+#ifdef IPSEC
+ policy = save_policy;
+#endif
+}
+
+static void
+prepare_next_config(const char *file_name)
+{
+ /* Setup new state that is normally only done in main */
+ CONFIG = file_name;
+
+ /* Inherit default host and IPsec policy */
+ defhost = newstr(defhost);
+
+#ifdef IPSEC
+ policy = (policy == NULL) ? NULL : newstr(policy);
+#endif
+}
+
+static void
+read_glob_configs(char *pattern)
+{
+ glob_t results;
+ char *full_pattern;
+ int glob_result;
+ full_pattern = gen_file_pattern(CONFIG, pattern);
+
+ DPRINTCONF("Found include directive '%s'", full_pattern);
+
+ glob_result = glob(full_pattern, GLOB_NOSORT, glob_error, &results);
+ switch(glob_result) {
+ case 0:
+ /* No glob errors */
+ break;
+ case GLOB_ABORTED:
+ ERR("Error while searching for include files");
+ break;
+ case GLOB_NOMATCH:
+ /* It's fine if no files were matched. */
+ DPRINTCONF("No files matched pattern '%s'", full_pattern);
+ break;
+ case GLOB_NOSPACE:
+ ERR("Error when searching for include files: %s",
+ strerror(errno));
+ break;
+ default:
+ ERR("Unknown glob(3) error %d", errno);
+ break;
+ }
+ free(full_pattern);
+
+ for (size_t i = 0; i < results.gl_pathc; i++) {
+ include_matched_path(results.gl_pathv[i]);
+ }
+
+ globfree(&results);
+}
+
+static void
+include_matched_path(char *glob_path)
+{
+ struct stat sb;
+ char *tmp;
+
+ if (lstat(glob_path, &sb) != 0) {
+ ERR("Error calling stat on path '%s': %s", glob_path,
+ strerror(errno));
+ return;
+ }
+
+ if (!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode)) {
+ DPRINTCONF("'%s' is not a file.", glob_path);
+ ERR("The matched path '%s' is not a regular file", glob_path);
+ return;
+ }
+
+ DPRINTCONF("Include '%s'", glob_path);
+
+ if (S_ISLNK(sb.st_mode)) {
+ tmp = glob_path;
+ glob_path = realpath(tmp, NULL);
+ }
+
+ /* Ensure the file is not being reincluded .*/
+ if (check_no_reinclude(glob_path)) {
+ prepare_next_config(glob_path);
+ config();
+ } else {
+ DPRINTCONF("File '%s' already included in current include "
+ "chain", glob_path);
+ WRN("Including file '%s' would cause a circular "
+ "dependency", glob_path);
+ }
+
+ if (S_ISLNK(sb.st_mode)) {
+ free(glob_path);
+ glob_path = tmp;
+ }
+}
+
+static bool
+check_no_reinclude(const char *glob_path)
+{
+ struct file_list *cur = file_list_head;
+ char *abs_path = realpath(glob_path, NULL);
+
+ if (abs_path == NULL) {
+ ERR("Error checking real path for '%s': %s",
+ glob_path, strerror(errno));
+ return false;
+ }
+
+ DPRINTCONF("Absolute path '%s'", abs_path);
+
+ for (cur = file_list_head; cur != NULL; cur = cur->next) {
+ if (strcmp(cur->abs, abs_path) == 0) {
+ /* file included more than once */
+ /* TODO relative or abs path for logging error? */
+ free(abs_path);
+ return false;
+ }
+ }
+ free(abs_path);
+ return true;
+}
+
+/* Resolve the pattern relative to the config file the pattern is from */
+static char *
+gen_file_pattern(const char *cur_config, const char *pattern)
+{
+ if (pattern[0] == '/') {
+ /* Absolute paths don't need any normalization */
+ return newstr(pattern);
+ }
+
+ /* pattern is relative */
+ /* Find the end of the file's directory */
+ size_t i, last = 0;
+ for (i = 0; cur_config[i] != '\0'; i++) {
+ if (cur_config[i] == '/') {
+ last = i;
+ }
+ }
+
+ if (last == 0) {
+ /* cur_config is just a filename, pattern already correct */
+ return newstr(pattern);
+ }
+
+ /* Relativize pattern to cur_config file's directory */
+ char *full_pattern = malloc(last + 1 + strlen(pattern) + 1);
+ if (full_pattern == NULL) {
+ syslog(LOG_ERR, "Out of memory.");
+ exit(EXIT_FAILURE);
+ }
+ memcpy(full_pattern, cur_config, last);
+ full_pattern[last] = '/';
+ strcpy(&full_pattern[last + 1], pattern);
+ return full_pattern;
+}
+
+static int
+glob_error(const char *path, int error)
+{
+ WRN("Error while resolving path '%s': %s", path, strerror(error));
+ return 0;
+}
Index: src/usr.sbin/inetd/ratelimit.c
diff -u /dev/null src/usr.sbin/inetd/ratelimit.c:1.1
--- /dev/null Tue Oct 12 15:08:04 2021
+++ src/usr.sbin/inetd/ratelimit.c Tue Oct 12 15:08:04 2021
@@ -0,0 +1,555 @@
+/* $NetBSD: ratelimit.c,v 1.1 2021/10/12 19:08:04 christos Exp $ */
+
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by James Browning, Gabe Coffland, Alex Gavin, and Solomon Ritzow.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: ratelimit.c,v 1.1 2021/10/12 19:08:04 christos Exp $");
+
+#include <sys/queue.h>
+
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stddef.h>
+
+#include "inetd.h"
+
+union addr {
+ struct in_addr ipv4_addr;
+ /* ensure aligned for comparison in rl_ipv6_eq (already is on 64-bit) */
+#ifdef INET6
+ struct in6_addr ipv6_addr __attribute__((aligned(16)));
+#endif
+ char other_addr[NI_MAXHOST];
+};
+
+static void rl_reset(struct servtab *, time_t);
+static time_t rl_time(void);
+static void rl_get_name(struct servtab *, int, union addr *);
+static void rl_drop_connection(struct servtab *, int);
+static struct rl_ip_node *rl_add(struct servtab *, union addr *);
+static struct rl_ip_node *rl_try_get_ip(struct servtab *, union addr *);
+static bool rl_ip_eq(struct servtab *, union addr *, struct rl_ip_node *);
+#ifdef INET6
+static bool rl_ipv6_eq(struct in6_addr *, struct in6_addr *);
+#endif
+#ifdef DEBUG_ENABLE
+static void rl_print_found_node(struct servtab *, struct rl_ip_node *);
+#endif
+static void rl_log_address_exceed(struct servtab *, struct rl_ip_node *);
+static const char *rl_node_tostring(struct servtab *, struct rl_ip_node *, char[NI_MAXHOST]);
+static bool rl_process_service_max(struct servtab *, int, time_t *);
+static bool rl_process_ip_max(struct servtab *, int, time_t *);
+
+/* Return 0 on allow, -1 if connection should be blocked */
+int
+rl_process(struct servtab *sep, int ctrl)
+{
+ time_t now = -1;
+
+ DPRINTF(SERV_FMT ": processing rate-limiting",
+ SERV_PARAMS(sep));
+ DPRINTF(SERV_FMT ": se_service_max "
+ "%zu and se_count %zu", SERV_PARAMS(sep),
+ sep->se_service_max, sep->se_count);
+
+ if (sep->se_count == 0) {
+ now = rl_time();
+ sep->se_time = now;
+ }
+
+ if(!rl_process_service_max(sep, ctrl, &now)
+ || !rl_process_ip_max(sep, ctrl, &now)) {
+ return -1;
+ }
+
+ DPRINTF(SERV_FMT ": running service ", SERV_PARAMS(sep));
+
+ /* se_count is only incremented if rl_process will return 0 */
+ sep->se_count++;
+ return 0;
+}
+
+/*
+ * Get the identifier for the remote peer based on sep->se_socktype and
+ * sep->se_family
+ */
+static void
+rl_get_name(struct servtab *sep, int ctrl, union addr *out)
+{
+ union {
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ } addr;
+
+ /* Get the sockaddr of socket ctrl */
+ switch (sep->se_socktype) {
+ case SOCK_STREAM: {
+ socklen_t len = sizeof(struct sockaddr_storage);
+ if (getpeername(ctrl, &addr.sa, &len) == -1) {
+ /* error, log it and skip ip rate limiting */
+ syslog(LOG_ERR,
+ SERV_FMT " failed to get peer name of the "
+ "connection", SERV_PARAMS(sep));
+ exit(EXIT_FAILURE);
+ }
+ break;
+ }
+ case SOCK_DGRAM: {
+ struct msghdr header = {
+ .msg_name = &addr.sa,
+ .msg_namelen = sizeof(struct sockaddr_storage),
+ /* scatter/gather and control info is null */
+ };
+ ssize_t count;
+
+ /* Peek so service can still get the packet */
+ count = recvmsg(ctrl, &header, MSG_PEEK);
+ if (count == -1) {
+ syslog(LOG_ERR,
+ "failed to get dgram source address: %s; exiting",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ break;
+ }
+ default:
+ DPRINTF(SERV_FMT ": ip_max rate limiting not supported for "
+ "socktype", SERV_PARAMS(sep));
+ syslog(LOG_ERR, SERV_FMT
+ ": ip_max rate limiting not supported for socktype",
+ SERV_PARAMS(sep));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Convert addr to to rate limiting address */
+ switch (sep->se_family) {
+ case AF_INET:
+ out->ipv4_addr = addr.sin.sin_addr;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ out->ipv6_addr = addr.sin6.sin6_addr;
+ break;
+#endif
+ default: {
+ int res = getnameinfo(&addr.sa,
+ (socklen_t)addr.sa.sa_len,
+ out->other_addr, NI_MAXHOST,
+ NULL, 0,
+ NI_NUMERICHOST
+ );
+ if (res != 0) {
+ syslog(LOG_ERR,
+ SERV_FMT ": failed to get name info of "
+ "the incoming connection: %s; exiting",
+ SERV_PARAMS(sep), gai_strerror(res));
+ exit(EXIT_FAILURE);
+ }
+ break;
+ }
+ }
+}
+
+static void
+rl_drop_connection(struct servtab *sep, int ctrl)
+{
+
+ if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) {
+ /*
+ * If the fd isn't a listen socket,
+ * close the individual connection too.
+ */
+ close(ctrl);
+ return;
+ }
+ if (sep->se_socktype != SOCK_DGRAM) {
+ return;
+ }
+ /*
+ * Drop the single datagram the service would have
+ * consumed if nowait. If this is a wait service, this
+ * will consume 1 datagram, and further received packets
+ * will be removed in the same way.
+ */
+ struct msghdr header = {
+ /* All fields null, just consume one message */
+ };
+ ssize_t count;
+
+ count = recvmsg(ctrl, &header, 0);
+ if (count == -1) {
+ syslog(LOG_ERR,
+ SERV_FMT ": failed to consume nowait dgram: %s",
+ SERV_PARAMS(sep), strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ DPRINTF(SERV_FMT ": dropped dgram message",
+ SERV_PARAMS(sep));
+}
+
+static time_t
+rl_time(void)
+{
+ struct timespec ts;
+ if(clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
+ syslog(LOG_ERR, "clock_gettime for rate limiting failed: %s; "
+ "exiting", strerror(errno));
+ /* Exit inetd if rate limiting fails */
+ exit(EXIT_FAILURE);
+ }
+ return ts.tv_sec;
+}
+
+/* Add addr to IP tracking or return NULL if malloc fails */
+static struct rl_ip_node *
+rl_add(struct servtab *sep, union addr *addr)
+{
+
+ struct rl_ip_node *node;
+ size_t node_size, bufsize;
+#ifdef DEBUG_ENABLE
+ char buffer[NI_MAXHOST];
+#endif
+
+ switch(sep->se_family) {
+ case AF_INET:
+ /* ip_node to end of IPv4 address */
+ node_size = offsetof(struct rl_ip_node, ipv4_addr)
+ + sizeof(struct in_addr);
+ break;
+ case AF_INET6:
+ /* ip_node to end of IPv6 address */
+ node_size = offsetof(struct rl_ip_node, ipv6_addr)
+ + sizeof(struct in6_addr);
+ break;
+ default:
+ /* ip_node to other_addr plus size of string + NULL */
+ bufsize = strlen(addr->other_addr) + sizeof(char);
+ node_size = offsetof(struct rl_ip_node, other_addr) + bufsize;
+ break;
+ }
+
+ node = malloc(node_size);
+ if (node == NULL) {
+ if(errno == ENOMEM) {
+ return NULL;
+ } else {
+ syslog(LOG_ERR, "malloc failed unexpectedly: %s",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ node->count = 0;
+
+ /* copy the data into the new allocation */
+ switch(sep->se_family) {
+ case AF_INET:
+ node->ipv4_addr = addr->ipv4_addr;
+ break;
+ case AF_INET6:
+ /* Hopefully this is inlined, means the same thing as memcpy */
+ __builtin_memcpy(&node->ipv6_addr, &addr->ipv6_addr,
+ sizeof(struct in6_addr));
+ break;
+ default:
+ strlcpy(node->other_addr, addr->other_addr, bufsize);
+ break;
+ }
+
+ /* initializes 'entries' member to NULL automatically */
+ SLIST_INSERT_HEAD(&sep->se_rl_ip_list, node, entries);
+
+ DPRINTF(SERV_FMT ": add '%s' to rate limit tracking (%zu byte record)",
+ SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer), node_size);
+
+ return node;
+}
+
+static void
+rl_reset(struct servtab *sep, time_t now)
+{
+ DPRINTF(SERV_FMT ": %ji seconds passed; resetting rate limiting ",
+ SERV_PARAMS(sep), (intmax_t)(now - sep->se_time));
+
+ sep->se_count = 0;
+ sep->se_time = now;
+ if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
+ rl_clear_ip_list(sep);
+ }
+}
+
+void
+rl_clear_ip_list(struct servtab *sep)
+{
+ while (!SLIST_EMPTY(&sep->se_rl_ip_list)) {
+ struct rl_ip_node *node = SLIST_FIRST(&sep->se_rl_ip_list);
+ SLIST_REMOVE_HEAD(&sep->se_rl_ip_list, entries);
+ free(node);
+ }
+}
+
+/* Get the node associated with addr, or NULL */
+static struct rl_ip_node *
+rl_try_get_ip(struct servtab *sep, union addr *addr)
+{
+
+ struct rl_ip_node *cur;
+ SLIST_FOREACH(cur, &sep->se_rl_ip_list, entries) {
+ if (rl_ip_eq(sep, addr, cur)) {
+ return cur;
+ }
+ }
+
+ return NULL;
+}
+
+/* Return true if passed service rate limiting checks, false if blocked */
+static bool
+rl_process_service_max(struct servtab *sep, int ctrl, time_t *now)
+{
+ if (sep->se_count >= sep->se_service_max) {
+ if(*now == -1) {
+ /* Only get the clock time if we didn't already */
+ *now = rl_time();
+ }
+
+ if (*now - sep->se_time > CNT_INTVL) {
+ rl_reset(sep, *now);
+ } else {
+ syslog(LOG_ERR, SERV_FMT
+ ": max spawn rate (%zu in %ji seconds) "
+ "already met; closing for %ju seconds",
+ SERV_PARAMS(sep),
+ sep->se_service_max,
+ (intmax_t)CNT_INTVL,
+ (uintmax_t)RETRYTIME);
+ DPRINTF(SERV_FMT
+ ": max spawn rate (%zu in %ji seconds) "
+ "already met; closing for %ju seconds",
+ SERV_PARAMS(sep),
+ sep->se_service_max,
+ (intmax_t)CNT_INTVL,
+ (uintmax_t)RETRYTIME);
+
+ rl_drop_connection(sep, ctrl);
+
+ /* Close the server for 10 minutes */
+ close_sep(sep);
+ if (!timingout) {
+ timingout = true;
+ alarm(RETRYTIME);
+ }
+
+ return false;
+ }
+ }
+ return true;
+}
+
+/* Return true if passed IP rate limiting checks, false if blocked */
+static bool
+rl_process_ip_max(struct servtab *sep, int ctrl, time_t *now) {
+ if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
+ struct rl_ip_node *node;
+ union addr addr;
+
+ rl_get_name(sep, ctrl, &addr);
+ node = rl_try_get_ip(sep, &addr);
+ if (node == NULL) {
+ node = rl_add(sep, &addr);
+ if (node == NULL) {
+ /* If rl_add can't allocate, reject request */
+ DPRINTF("Cannot allocate rl_ip_node");
+ return false;
+ }
+ }
+#ifdef DEBUG_ENABLE
+ else {
+ /*
+ * in a separate function to prevent large stack
+ * frame
+ */
+ rl_print_found_node(sep, node);
+ }
+#endif
+
+ DPRINTF(
+ SERV_FMT ": se_ip_max %zu and ip_count %zu",
+ SERV_PARAMS(sep), sep->se_ip_max, node->count);
+
+ if (node->count >= sep->se_ip_max) {
+ if (*now == -1) {
+ *now = rl_time();
+ }
+
+ if (*now - sep->se_time > CNT_INTVL) {
+ rl_reset(sep, *now);
+ node = rl_add(sep, &addr);
+ if (node == NULL) {
+ DPRINTF("Cannot allocate rl_ip_node");
+ return false;
+ }
+ } else {
+ if (debug && node->count == sep->se_ip_max) {
+ /*
+ * Only log first failed request to
+ * prevent DoS attack writing to system
+ * log
+ */
+ rl_log_address_exceed(sep, node);
+ } else {
+ DPRINTF(SERV_FMT
+ ": service not started",
+ SERV_PARAMS(sep));
+ }
+
+ rl_drop_connection(sep, ctrl);
+ /*
+ * Increment so debug-syslog message will
+ * trigger only once
+ */
+ if (node->count < SIZE_MAX) {
+ node->count++;
+ }
+ return false;
+ }
+ }
+ node->count++;
+ }
+ return true;
+}
+
+static bool
+rl_ip_eq(struct servtab *sep, union addr *addr, struct rl_ip_node *cur) {
+ switch(sep->se_family) {
+ case AF_INET:
+ if (addr->ipv4_addr.s_addr == cur->ipv4_addr.s_addr) {
+ return true;
+ }
+ break;
+#ifdef INET6
+ case AF_INET6:
+ if(rl_ipv6_eq(&addr->ipv6_addr, &cur->ipv6_addr)) {
+ return true;
+ }
+ break;
+#endif
+ default:
+ if (strncmp(cur->other_addr, addr->other_addr, NI_MAXHOST)
+ == 0) {
+ return true;
+ }
+ break;
+ }
+ return false;
+}
+
+#ifdef INET6
+static bool
+rl_ipv6_eq(struct in6_addr *a, struct in6_addr *b)
+{
+#if UINTMAX_MAX >= UINT64_MAX
+ { /* requires 8 byte aligned structs */
+ uint64_t *ap = (uint64_t *)a->s6_addr;
+ uint64_t *bp = (uint64_t *)b->s6_addr;
+ return (ap[0] == bp[0]) & (ap[1] == bp[1]);
+ }
+#else
+ { /* requires 4 byte aligned structs */
+ uint32_t *ap = (uint32_t *)a->s6_addr;
+ uint32_t *bp = (uint32_t *)b->s6_addr;
+ return ap[0] == bp[0] && ap[1] == bp[1] &&
+ ap[2] == bp[2] && ap[3] == bp[3];
+ }
+#endif
+}
+#endif
+
+static const char *
+rl_node_tostring(struct servtab *sep, struct rl_ip_node *node,
+ char buffer[NI_MAXHOST])
+{
+ switch (sep->se_family) {
+ case AF_INET:
+#ifdef INET6
+ case AF_INET6:
+#endif
+ /* ipv4_addr/ipv6_addr share same address */
+ return inet_ntop(sep->se_family, (void*)&node->ipv4_addr,
+ (char*)buffer, NI_MAXHOST);
+ default:
+ return (char *)&node->other_addr;
+ }
+}
+
+#ifdef DEBUG_ENABLE
+/* Separate function due to large buffer size */
+static void
+rl_print_found_node(struct servtab *sep, struct rl_ip_node *node)
+{
+ char buffer[NI_MAXHOST];
+ DPRINTF(SERV_FMT ": found record for address '%s'",
+ SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer));
+}
+#endif
+
+/* Separate function due to large buffer sie */
+static void
+rl_log_address_exceed(struct servtab *sep, struct rl_ip_node *node)
+{
+ char buffer[NI_MAXHOST];
+ const char * name = rl_node_tostring(sep, node, buffer);
+ syslog(LOG_ERR, SERV_FMT
+ ": max ip spawn rate (%zu in "
+ "%ji seconds) for "
+ "'%." TOSTRING(NI_MAXHOST) "s' "
+ "already met; service not started",
+ SERV_PARAMS(sep),
+ sep->se_ip_max,
+ (intmax_t)CNT_INTVL,
+ name);
+ DPRINTF(SERV_FMT
+ ": max ip spawn rate (%zu in "
+ "%ji seconds) for "
+ "'%." TOSTRING(NI_MAXHOST) "s' "
+ "already met; service not started",
+ SERV_PARAMS(sep),
+ sep->se_ip_max,
+ (intmax_t)CNT_INTVL,
+ name);
+}