aboutsummaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorKurt Lidl <lidl@FreeBSD.org>2016-06-01 21:48:22 +0000
committerKurt Lidl <lidl@FreeBSD.org>2016-06-01 21:48:22 +0000
commitdf0cfa3b86180bc007aafedfac6685826e8de7ea (patch)
tree87b2b18820b6cc1642b930ba61c9228f4f3bb48b /bin
Notes
Diffstat (limited to 'bin')
-rw-r--r--bin/Makefile15
-rw-r--r--bin/blacklistctl.881
-rw-r--r--bin/blacklistctl.c151
-rw-r--r--bin/blacklistd.8222
-rw-r--r--bin/blacklistd.c537
-rw-r--r--bin/blacklistd.conf.5222
-rw-r--r--bin/conf.c1142
-rw-r--r--bin/conf.h65
-rw-r--r--bin/internal.c48
-rw-r--r--bin/internal.h57
-rw-r--r--bin/run.c156
-rw-r--r--bin/run.h41
-rw-r--r--bin/state.c233
-rw-r--r--bin/state.h62
-rw-r--r--bin/support.c157
-rw-r--r--bin/support.h44
16 files changed, 3233 insertions, 0 deletions
diff --git a/bin/Makefile b/bin/Makefile
new file mode 100644
index 0000000000000..280c72fd3af16
--- /dev/null
+++ b/bin/Makefile
@@ -0,0 +1,15 @@
+# $NetBSD: Makefile,v 1.11 2015/01/27 19:40:36 christos Exp $
+
+BINDIR=/sbin
+
+PROGS=blacklistd blacklistctl
+MAN.blacklistd=blacklistd.8 blacklistd.conf.5
+MAN.blacklistctl=blacklistctl.8
+SRCS.blacklistd = blacklistd.c conf.c run.c state.c support.c internal.c
+SRCS.blacklistctl = blacklistctl.c conf.c state.c support.c internal.c
+DBG=-g
+
+LDADD+=-lutil
+DPADD+=${LIBUTIL}
+
+.include <bsd.prog.mk>
diff --git a/bin/blacklistctl.8 b/bin/blacklistctl.8
new file mode 100644
index 0000000000000..d8ed1f09cde2f
--- /dev/null
+++ b/bin/blacklistctl.8
@@ -0,0 +1,81 @@
+.\" $NetBSD: blacklistctl.8,v 1.7 2015/04/30 06:20:43 riz Exp $
+.\"
+.\" Copyright (c) 2015 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Christos Zoulas.
+.\"
+.\" 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.
+.\"
+.Dd April 29, 2015
+.Dt BLACKLISTCTL 8
+.Os
+.Sh NAME
+.Nm blacklistctl
+.Nd display and change the state of blacklistd
+.Sh SYNOPSIS
+.Nm
+.Cm dump
+.Op Fl abdnrw
+.Sh DESCRIPTION
+.Nm
+is a program used to display the state of
+.Xr blacklistd 8
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Show all database entries, by default it shows only the embryonic ones.
+.It Fl b
+Show only the blocked entries.
+.It Fl d
+Increase debugging level.
+.It Fl n
+Don't display a header.
+.It Fl r
+Show the remaining blocked time instead of the last activity time.
+.It Fl w
+Normally the width of addresses is good for IPv4, the
+.Fl w
+flag, makes the display wide enough for IPv6 addresses.
+.El
+.Sh SEE ALSO
+.Xr blacklistd 8
+.Sh NOTES
+Sometimes the reported number of failed attempts can exceed the number
+of attempts that
+.Xr blacklistd 8
+is configured to block.
+This can happen either because the rule has been removed manually, or
+because there were more attempts in flight while the rule block was being
+added.
+This condition is normal; in that case
+.Xr blacklistd 8
+will first attempt to remove the existing rule, and then it will re-add
+it to make sure that there is only one rule active.
+.Sh HISTORY
+.Nm
+appeared in
+.Nx 7 .
+.Sh AUTHORS
+.An Christos Zoulas
diff --git a/bin/blacklistctl.c b/bin/blacklistctl.c
new file mode 100644
index 0000000000000..d202fb159b897
--- /dev/null
+++ b/bin/blacklistctl.c
@@ -0,0 +1,151 @@
+/* $NetBSD: blacklistctl.c,v 1.20 2016/04/04 15:52:56 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: blacklistctl.c,v 1.20 2016/04/04 15:52:56 christos Exp $");
+
+#include <stdio.h>
+#include <time.h>
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#include <fcntl.h>
+#include <string.h>
+#include <syslog.h>
+#include <err.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include "conf.h"
+#include "state.h"
+#include "internal.h"
+#include "support.h"
+
+static __dead void
+usage(int c)
+{
+ if (c == 0)
+ warnx("Missing/unknown command");
+ else
+ warnx("Unknown option `%c'", (char)c);
+ fprintf(stderr, "Usage: %s dump [-abdnrw]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *dbname = _PATH_BLSTATE;
+ DB *db;
+ struct conf c;
+ struct dbinfo dbi;
+ unsigned int i;
+ struct timespec ts;
+ int all, blocked, remain, wide, noheader;
+ int o;
+
+ noheader = wide = blocked = all = remain = 0;
+ lfun = dlog;
+
+ if (argc == 1 || strcmp(argv[1], "dump") != 0)
+ usage(0);
+
+ argc--;
+ argv++;
+
+ while ((o = getopt(argc, argv, "abD:dnrw")) != -1)
+ switch (o) {
+ case 'a':
+ all = 1;
+ blocked = 0;
+ break;
+ case 'b':
+ blocked = 1;
+ case 'D':
+ dbname = optarg;
+ break;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'n':
+ noheader = 1;
+ break;
+ case 'r':
+ remain = 1;
+ break;
+ case 'w':
+ wide = 1;
+ break;
+ default:
+ usage(o);
+ break;
+ }
+
+ db = state_open(dbname, O_RDONLY, 0);
+ if (db == NULL)
+ err(EXIT_FAILURE, "Can't open `%s'", dbname);
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ wide = wide ? 8 * 4 + 7 : 4 * 3 + 3;
+ if (!noheader)
+ printf("%*.*s/ma:port\tid\tnfail\t%s\n", wide, wide,
+ "address", remain ? "remaining time" : "last access");
+ for (i = 1; state_iterate(db, &c, &dbi, i) != 0; i = 0) {
+ char buf[BUFSIZ];
+ if (!all) {
+ if (blocked) {
+ if (dbi.count < c.c_nfail)
+ continue;
+ } else {
+ if (dbi.count >= c.c_nfail)
+ continue;
+ }
+ }
+ sockaddr_snprintf(buf, sizeof(buf), "%a", (void *)&c.c_ss);
+ printf("%*.*s/%d:%d\t", wide, wide, buf, c.c_lmask, c.c_port);
+ if (remain)
+ fmtydhms(buf, sizeof(buf),
+ c.c_duration - (ts.tv_sec - dbi.last));
+ else
+ fmttime(buf, sizeof(buf), dbi.last);
+ printf("%s\t%d/%d\t%-s\n", dbi.id, dbi.count, c.c_nfail, buf);
+ }
+ state_close(db);
+ return EXIT_SUCCESS;
+}
diff --git a/bin/blacklistd.8 b/bin/blacklistd.8
new file mode 100644
index 0000000000000..ae684770357f0
--- /dev/null
+++ b/bin/blacklistd.8
@@ -0,0 +1,222 @@
+.\" $NetBSD: blacklistd.8,v 1.15 2016/03/11 17:16:40 christos Exp $
+.\"
+.\" Copyright (c) 2015 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Christos Zoulas.
+.\"
+.\" 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.
+.\"
+.Dd June 4, 2015
+.Dt BLACKLISTD 8
+.Os
+.Sh NAME
+.Nm blacklistd
+.Nd block and release ports on demand to avoid DoS abuse
+.Sh SYNOPSIS
+.Nm
+.Op Fl dfrv
+.Op Fl C Ar controlprog
+.Op Fl c Ar configfile
+.Op Fl D Ar dbfile
+.Op Fl P Ar sockpathsfile
+.Op Fl R Ar rulename
+.Op Fl s Ar sockpath
+.Op Fl t Ar timeout
+.Sh DESCRIPTION
+.Nm
+is a daemon similar to
+.Xr syslogd 8
+that listens to a sockets at paths specified in the
+.Ar sockpathsfile
+for notifications from other daemons about successful or failed connection
+attempts.
+If no such file is specified, then it only listens to the socket path
+specified by
+.Ar sockspath
+or if that is not specified to
+.Pa /var/run/blacklistd.sock .
+Each notification contains an (action, port, protocol, address, owner) tuple
+that identifies the remote connection and the action.
+This tuple is consulted against entries in
+.Ar configfile
+with syntax specified in
+.Xr blacklistd.conf 5 .
+If an entry is matched, a state entry is created for that tuple.
+Each entry contains a number of tries limit and a duration.
+.Pp
+If the action is
+.Dq add
+and the number of tries limit is reached, then a
+control script
+.Ar controlprog
+is invoked with arguments:
+.Bd -literal -offset indent
+control add <rulename> <proto> <address> <mask> <port>
+.Ed
+.Pp
+and should invoke a packet filter command to block the connection
+specified by the arguments.
+The
+.Ar rulename
+argument can be set from the command line (default
+.Dv blacklistd ) .
+The script could print a numerical id to stdout as a handle for
+the rule that can be used later to remove that connection, but
+that is not required as all information to remove the rule is
+kept.
+.Pp
+If the action is
+.Dq remove
+Then the same control script is invoked as:
+.Bd -literal -offset indent
+control remove <rulename> <proto> <address> <mask> <port> <id>
+.Ed
+.Pp
+where
+.Ar id
+is the number returned from the
+.Dq add
+action.
+.Pp
+.Nm
+maintains a database of known connections in
+.Ar dbfile .
+On startup it reads entries from that file, and updates its internal state.
+.Pp
+.Nm
+checks the list of active entries every
+.Ar timeout
+seconds (default
+.Dv 15 )
+and removes entries and block rules using the control program as necessary.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl C Ar controlprog
+Use
+.Ar controlprog
+to communicate with the packet filter, usually
+.Pa /libexec/blacklistd-helper .
+The following arguments are passed to the control program:
+.Bl -tag -width protocol
+.It action
+The action to perform:
+.Dv add ,
+.Dv rem ,
+or
+.Dv flush
+to add, remove or flush a firewall rule.
+.It name
+The rule name.
+.It protocol
+The optional protocol name (can be empty):
+.Dv tcp ,
+.Dv tcp6 ,
+.Dv udp ,
+.Dv udp6 .
+.It address
+The IPv4 or IPv6 numeric address to be blocked or released.
+.It mask
+The numeric mask to be applied to the blocked or released address
+.It port
+The optional numeric port to be blocked (can be empty).
+.It id
+For packet filters that support removal of rules by rule identifier, the
+identifier of the rule to be removed.
+The add command is expected to return the rule identifier string to stdout.
+.El
+.It Fl c Ar configuration
+The name of the configuration file to read, usually
+.Pa /etc/blacklistd.conf .
+.It Fl D Ar dbfile
+The Berkeley DB file where
+.Nm
+stores its state, usually
+.Pa /var/run/blacklistd.db .
+.It Fl d
+Normally,
+.Nm
+disassociates itself from the terminal unless the
+.Fl d
+flag is specified, in which case it stays in the foreground.
+.It Fl f
+Truncate the state database and flush all the rules named
+.Ar rulename
+are deleted by invoking the control script as:
+.Bd -literal -offset indent
+control flush <rulename>
+.Ed
+.It Fl P Ar sockspathsfile
+A file containing a list of pathnames, one per line that
+.Nm
+will create sockets to listen to.
+This is useful for chrooted environments.
+.It Fl R Ar rulename
+Specify the default rule name for the packet filter rules, usually
+.Dv blacklistd .
+.It Fl r
+Re-read the firewall rules from the internal database, then
+remove and re-add them.
+This helps for packet filters that don't retain state across reboots.
+.It Fl s Ar sockpath
+Add
+.Ar sockpath
+to the list of Unix sockets
+.Nm
+listens to.
+.It Fl t Ar timeout
+The interval in seconds
+.Nm
+polls the state file to update the rules.
+.It Fl v
+Cause
+.Nm
+to print
+diagnostic messages to
+.Dv stdout
+instead of
+.Xr syslogd 8 .
+.El
+.Sh FILES
+.Bl -tag -width /libexec/blacklistd-helper -compact
+.It Pa /libexec/blacklistd-helper
+Shell script invoked to interface with the packet filter.
+.It Pa /etc/blacklistd.conf
+Configuration file.
+.It Pa /var/db/blacklistd.db
+Database of current connection entries.
+.It Pa /var/run/blacklistd.sock
+Socket to receive connection notifications.
+.El
+.Sh SEE ALSO
+.Xr blacklistd.conf 5 ,
+.Xr blacklistctl 8 ,
+.Xr npfctl 8 ,
+.Xr syslogd 8
+.Sh HISTORY
+.Nm
+appeared in
+.Nx 7 .
+.Sh AUTHORS
+.An Christos Zoulas
diff --git a/bin/blacklistd.c b/bin/blacklistd.c
new file mode 100644
index 0000000000000..c16b18e193a55
--- /dev/null
+++ b/bin/blacklistd.c
@@ -0,0 +1,537 @@
+/* $NetBSD: blacklistd.c,v 1.34 2016/04/04 15:52:56 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: blacklistd.c,v 1.34 2016/04/04 15:52:56 christos Exp $");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#include <string.h>
+#include <signal.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <err.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+
+#include "bl.h"
+#include "internal.h"
+#include "conf.h"
+#include "run.h"
+#include "state.h"
+#include "support.h"
+
+static const char *configfile = _PATH_BLCONF;
+static DB *state;
+static const char *dbfile = _PATH_BLSTATE;
+static sig_atomic_t readconf;
+static sig_atomic_t done;
+static int vflag;
+
+static void
+sigusr1(int n __unused)
+{
+ debug++;
+}
+
+static void
+sigusr2(int n __unused)
+{
+ debug--;
+}
+
+static void
+sighup(int n __unused)
+{
+ readconf++;
+}
+
+static void
+sigdone(int n __unused)
+{
+ done++;
+}
+
+static __dead void
+usage(int c)
+{
+ if (c)
+ warnx("Unknown option `%c'", (char)c);
+ fprintf(stderr, "Usage: %s [-vdfr] [-c <config>] [-R <rulename>] "
+ "[-P <sockpathsfile>] [-C <controlprog>] [-D <dbfile>] "
+ "[-s <sockpath>] [-t <timeout>]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static int
+getremoteaddress(bl_info_t *bi, struct sockaddr_storage *rss, socklen_t *rsl)
+{
+ *rsl = sizeof(*rss);
+ memset(rss, 0, *rsl);
+
+ if (getpeername(bi->bi_fd, (void *)rss, rsl) != -1)
+ return 0;
+
+ if (errno != ENOTCONN) {
+ (*lfun)(LOG_ERR, "getpeername failed (%m)");
+ return -1;
+ }
+
+ if (bi->bi_slen == 0) {
+ (*lfun)(LOG_ERR, "unconnected socket with no peer in message");
+ return -1;
+ }
+
+ switch (bi->bi_ss.ss_family) {
+ case AF_INET:
+ *rsl = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ *rsl = sizeof(struct sockaddr_in6);
+ break;
+ default:
+ (*lfun)(LOG_ERR, "bad client passed socket family %u",
+ (unsigned)bi->bi_ss.ss_family);
+ return -1;
+ }
+
+ if (*rsl != bi->bi_slen) {
+ (*lfun)(LOG_ERR, "bad client passed socket length %u != %u",
+ (unsigned)*rsl, (unsigned)bi->bi_slen);
+ return -1;
+ }
+
+ memcpy(rss, &bi->bi_ss, *rsl);
+
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ if (*rsl != rss->ss_len) {
+ (*lfun)(LOG_ERR,
+ "bad client passed socket internal length %u != %u",
+ (unsigned)*rsl, (unsigned)rss->ss_len);
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+static void
+process(bl_t bl)
+{
+ struct sockaddr_storage rss;
+ socklen_t rsl;
+ char rbuf[BUFSIZ];
+ bl_info_t *bi;
+ struct conf c;
+ struct dbinfo dbi;
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
+ (*lfun)(LOG_ERR, "clock_gettime failed (%m)");
+ return;
+ }
+
+ if ((bi = bl_recv(bl)) == NULL) {
+ (*lfun)(LOG_ERR, "no message (%m)");
+ return;
+ }
+
+ if (getremoteaddress(bi, &rss, &rsl) == -1)
+ goto out;
+
+ if (debug) {
+ sockaddr_snprintf(rbuf, sizeof(rbuf), "%a:%p", (void *)&rss);
+ (*lfun)(LOG_DEBUG, "processing type=%d fd=%d remote=%s msg=%s"
+ " uid=%lu gid=%lu", bi->bi_type, bi->bi_fd, rbuf,
+ bi->bi_msg, (unsigned long)bi->bi_uid,
+ (unsigned long)bi->bi_gid);
+ }
+
+ if (conf_find(bi->bi_fd, bi->bi_uid, &rss, &c) == NULL) {
+ (*lfun)(LOG_DEBUG, "no rule matched");
+ goto out;
+ }
+
+
+ if (state_get(state, &c, &dbi) == -1)
+ goto out;
+
+ if (debug) {
+ char b1[128], b2[128];
+ (*lfun)(LOG_DEBUG, "%s: db state info for %s: count=%d/%d "
+ "last=%s now=%s", __func__, rbuf, dbi.count, c.c_nfail,
+ fmttime(b1, sizeof(b1), dbi.last),
+ fmttime(b2, sizeof(b2), ts.tv_sec));
+ }
+
+ switch (bi->bi_type) {
+ case BL_ADD:
+ dbi.count++;
+ dbi.last = ts.tv_sec;
+ if (dbi.id[0]) {
+ /*
+ * We should not be getting this since the rule
+ * should have blocked the address. A possible
+ * explanation is that someone removed that rule,
+ * and another would be that we got another attempt
+ * before we added the rule. In anycase, we remove
+ * and re-add the rule because we don't want to add
+ * it twice, because then we'd lose track of it.
+ */
+ (*lfun)(LOG_DEBUG, "rule exists %s", dbi.id);
+ (void)run_change("rem", &c, dbi.id, 0);
+ dbi.id[0] = '\0';
+ }
+ if (c.c_nfail != -1 && dbi.count >= c.c_nfail) {
+ int res = run_change("add", &c, dbi.id, sizeof(dbi.id));
+ if (res == -1)
+ goto out;
+ sockaddr_snprintf(rbuf, sizeof(rbuf), "%a",
+ (void *)&rss);
+ (*lfun)(LOG_INFO,
+ "blocked %s/%d:%d for %d seconds",
+ rbuf, c.c_lmask, c.c_port, c.c_duration);
+
+ }
+ break;
+ case BL_DELETE:
+ if (dbi.last == 0)
+ goto out;
+ dbi.last = 0;
+ break;
+ default:
+ (*lfun)(LOG_ERR, "unknown message %d", bi->bi_type);
+ }
+ if (state_put(state, &c, &dbi) == -1)
+ goto out;
+out:
+ close(bi->bi_fd);
+}
+
+static void
+update_interfaces(void)
+{
+ struct ifaddrs *oifas, *nifas;
+
+ if (getifaddrs(&nifas) == -1)
+ return;
+
+ oifas = ifas;
+ ifas = nifas;
+
+ if (oifas)
+ freeifaddrs(oifas);
+}
+
+static void
+update(void)
+{
+ struct timespec ts;
+ struct conf c;
+ struct dbinfo dbi;
+ unsigned int f, n;
+ char buf[128];
+ void *ss = &c.c_ss;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
+ (*lfun)(LOG_ERR, "clock_gettime failed (%m)");
+ return;
+ }
+
+again:
+ for (n = 0, f = 1; state_iterate(state, &c, &dbi, f) == 1;
+ f = 0, n++)
+ {
+ time_t when = c.c_duration + dbi.last;
+ if (debug > 1) {
+ char b1[64], b2[64];
+ sockaddr_snprintf(buf, sizeof(buf), "%a:%p", ss);
+ (*lfun)(LOG_DEBUG, "%s:[%u] %s count=%d duration=%d "
+ "last=%s " "now=%s", __func__, n, buf, dbi.count,
+ c.c_duration, fmttime(b1, sizeof(b1), dbi.last),
+ fmttime(b2, sizeof(b2), ts.tv_sec));
+ }
+ if (c.c_duration == -1 || when >= ts.tv_sec)
+ continue;
+ if (dbi.id[0]) {
+ run_change("rem", &c, dbi.id, 0);
+ sockaddr_snprintf(buf, sizeof(buf), "%a", ss);
+ syslog(LOG_INFO, "released %s/%d:%d after %d seconds",
+ buf, c.c_lmask, c.c_port, c.c_duration);
+ }
+ state_del(state, &c);
+ goto again;
+ }
+}
+
+static void
+addfd(struct pollfd **pfdp, bl_t **blp, size_t *nfd, size_t *maxfd,
+ const char *path)
+{
+ bl_t bl = bl_create(true, path, vflag ? vdlog : vsyslog);
+ if (bl == NULL || !bl_isconnected(bl))
+ exit(EXIT_FAILURE);
+ if (*nfd >= *maxfd) {
+ *maxfd += 10;
+ *blp = realloc(*blp, sizeof(**blp) * *maxfd);
+ if (*blp == NULL)
+ err(EXIT_FAILURE, "malloc");
+ *pfdp = realloc(*pfdp, sizeof(**pfdp) * *maxfd);
+ if (*pfdp == NULL)
+ err(EXIT_FAILURE, "malloc");
+ }
+
+ (*pfdp)[*nfd].fd = bl_getfd(bl);
+ (*pfdp)[*nfd].events = POLLIN;
+ (*blp)[*nfd] = bl;
+ *nfd += 1;
+}
+
+static void
+uniqueadd(struct conf ***listp, size_t *nlist, size_t *mlist, struct conf *c)
+{
+ struct conf **list = *listp;
+
+ if (c->c_name[0] == '\0')
+ return;
+ for (size_t i = 0; i < *nlist; i++) {
+ if (strcmp(list[i]->c_name, c->c_name) == 0)
+ return;
+ }
+ if (*nlist == *mlist) {
+ *mlist += 10;
+ void *p = realloc(*listp, *mlist * sizeof(*list));
+ if (p == NULL)
+ err(EXIT_FAILURE, "Can't allocate for rule list");
+ list = *listp = p;
+ }
+ list[(*nlist)++] = c;
+}
+
+static void
+rules_flush(void)
+{
+ struct conf **list;
+ size_t nlist, mlist;
+
+ list = NULL;
+ mlist = nlist = 0;
+ for (size_t i = 0; i < rconf.cs_n; i++)
+ uniqueadd(&list, &nlist, &mlist, &rconf.cs_c[i]);
+ for (size_t i = 0; i < lconf.cs_n; i++)
+ uniqueadd(&list, &nlist, &mlist, &lconf.cs_c[i]);
+
+ for (size_t i = 0; i < nlist; i++)
+ run_flush(list[i]);
+ free(list);
+}
+
+static void
+rules_restore(void)
+{
+ struct conf c;
+ struct dbinfo dbi;
+ unsigned int f;
+
+ for (f = 1; state_iterate(state, &c, &dbi, f) == 1; f = 0) {
+ if (dbi.id[0] == '\0')
+ continue;
+ (void)run_change("rem", &c, dbi.id, 0);
+ (void)run_change("add", &c, dbi.id, sizeof(dbi.id));
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, tout, flags, flush, restore;
+ const char *spath, *blsock;
+
+ setprogname(argv[0]);
+
+ spath = NULL;
+ blsock = _PATH_BLSOCK;
+ flush = 0;
+ restore = 0;
+ tout = 0;
+ flags = O_RDWR|O_EXCL|O_CLOEXEC;
+ while ((c = getopt(argc, argv, "C:c:D:dfP:rR:s:t:v")) != -1) {
+ switch (c) {
+ case 'C':
+ controlprog = optarg;
+ break;
+ case 'c':
+ configfile = optarg;
+ break;
+ case 'D':
+ dbfile = optarg;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'f':
+ flush++;
+ break;
+ case 'P':
+ spath = optarg;
+ break;
+ case 'R':
+ rulename = optarg;
+ break;
+ case 'r':
+ restore++;
+ break;
+ case 's':
+ blsock = optarg;
+ break;
+ case 't':
+ tout = atoi(optarg) * 1000;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage(c);
+ }
+ }
+
+ argc -= optind;
+ if (argc)
+ usage(0);
+
+ signal(SIGHUP, sighup);
+ signal(SIGINT, sigdone);
+ signal(SIGQUIT, sigdone);
+ signal(SIGTERM, sigdone);
+ signal(SIGUSR1, sigusr1);
+ signal(SIGUSR2, sigusr2);
+
+ openlog(getprogname(), LOG_PID, LOG_DAEMON);
+
+ if (debug) {
+ lfun = dlog;
+ if (tout == 0)
+ tout = 5000;
+ } else {
+ if (tout == 0)
+ tout = 15000;
+ }
+
+ update_interfaces();
+ conf_parse(configfile);
+ if (flush) {
+ rules_flush();
+ flags |= O_TRUNC;
+ }
+
+ if (restore)
+ rules_restore();
+
+ struct pollfd *pfd = NULL;
+ bl_t *bl = NULL;
+ size_t nfd = 0;
+ size_t maxfd = 0;
+
+ if (spath == NULL)
+ addfd(&pfd, &bl, &nfd, &maxfd, blsock);
+ else {
+ FILE *fp = fopen(spath, "r");
+ char *line;
+ if (fp == NULL)
+ err(EXIT_FAILURE, "Can't open `%s'", spath);
+ for (; (line = fparseln(fp, NULL, NULL, NULL, 0)) != NULL;
+ free(line))
+ addfd(&pfd, &bl, &nfd, &maxfd, line);
+ fclose(fp);
+ }
+
+ state = state_open(dbfile, flags, 0600);
+ if (state == NULL)
+ state = state_open(dbfile, flags | O_CREAT, 0600);
+ if (state == NULL)
+ return EXIT_FAILURE;
+
+ if (!debug) {
+ if (daemon(0, 0) == -1)
+ err(EXIT_FAILURE, "daemon failed");
+ if (pidfile(NULL) == -1)
+ err(EXIT_FAILURE, "Can't create pidfile");
+ }
+
+ for (size_t t = 0; !done; t++) {
+ if (readconf) {
+ readconf = 0;
+ conf_parse(configfile);
+ }
+ switch (poll(pfd, (nfds_t)nfd, tout)) {
+ case -1:
+ if (errno == EINTR)
+ continue;
+ (*lfun)(LOG_ERR, "poll (%m)");
+ return EXIT_FAILURE;
+ case 0:
+ state_sync(state);
+ break;
+ default:
+ for (size_t i = 0; i < nfd; i++)
+ if (pfd[i].revents & POLLIN)
+ process(bl[i]);
+ }
+ if (t % 100 == 0)
+ state_sync(state);
+ if (t % 10000 == 0)
+ update_interfaces();
+ update();
+ }
+ state_close(state);
+ return 0;
+}
diff --git a/bin/blacklistd.conf.5 b/bin/blacklistd.conf.5
new file mode 100644
index 0000000000000..40d507b3c08aa
--- /dev/null
+++ b/bin/blacklistd.conf.5
@@ -0,0 +1,222 @@
+.\" $NetBSD: blacklistd.conf.5,v 1.3 2015/04/30 06:20:43 riz Exp $
+.\"
+.\" Copyright (c) 2015 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Christos Zoulas.
+.\"
+.\" 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.
+.\"
+.Dd April 29, 2015
+.Dt BLACKLISTD.CONF 5
+.Os
+.Sh NAME
+.Nm blacklistd.conf
+.Nd configuration file format for blacklistd
+.Sh DESCRIPTION
+The
+.Nm
+files contains configuration lines for
+.Xr blacklistd 8 .
+It contains one entry per line, and is similar to
+.Xr inetd.conf 5 .
+There must be an entry for each field of the configuration file, with
+entries for each field separated by a tab or a space.
+Comments are denoted by a
+.Dq #
+at the beginning of a line.
+.Pp
+There are two kinds of configuration lines,
+.Va local
+and
+.Va remote .
+By default, configuration lines are
+.Va local ,
+i.e. the address specified refers to the addresses on the local machine.
+To switch to between
+.Va local
+and
+.Va remote
+configuration lines you can specify the stanzas:
+.Dq [local]
+and
+.Dq [remote] .
+.Pp
+On
+.Va local
+and
+.Va remote
+lines
+.Dq *
+means use the default, or wildcard match.
+In addition, for
+.Va remote
+lines
+.Dq =
+means use the values from the matched
+.Va local
+configuration line.
+.Pp
+The first four fields,
+.Va location ,
+.Va type ,
+.Va proto ,
+and
+.Va owner
+are used to match the
+.Va local
+or
+.Va remote
+addresses, whereas the last 3 fields
+.Va name ,
+.Va nfail ,
+and
+.Va disable
+are used to modify the filtering action.
+.Pp
+The first field denotes the
+.Va location
+as an address, mask, and port.
+The syntax for the
+.Va location
+is:
+.Bd -literal -offset indent
+ [<address>|<interface>][/<mask>][:<port>]
+.Ed
+.Pp
+The
+.Dv address
+can be an IPv4 address in numeric format, an IPv6 address
+in numeric format and enclosed by square brackets, or an interface name.
+Mask modifiers are not allowed on interfaces because interfaces
+have multiple address in different protocols where the mask has a different
+size.
+.Pp
+The
+.Dv mask
+is always numeric, but the
+.Dv port
+can be either numeric or symbolic.
+.Pp
+The second field is the socket
+.Va type :
+.Dv stream ,
+.Dv dgram ,
+or numeric.
+The third field is the
+.Va prococol :
+.Dv tcp ,
+.Dv udp ,
+.Dv tcp6 ,
+.Dv udp6 ,
+or numeric.
+The fourth file is the effective user
+.Va ( owner )
+of the daemon process reporting the event,
+either as a username or a userid.
+.Pp
+The rest of the fields are controlling the behavior of the filter.
+.Pp
+The
+.Va name
+field, is the name of the packet filter rule to be used.
+If the
+.Va name
+starts with a
+.Dq - ,
+then the default rulename is prepended to the given name.
+If the
+.Dv name
+contains a
+.Dq / ,
+the remaining portion of the name is interpreted as the mask to be
+applied to the address specified in the rule, so one can block whole
+subnets for a single rule violation.
+.Pp
+The
+.Va nfail
+field contains the number of failed attempts before access is blocked,
+defaulting to
+.Dq *
+meaning never, and the last field
+.Va disable
+specifies the amount of time since the last access that the blocking
+rule should be active, defaulting to
+.Dq *
+meaning forever.
+The default unit for
+.Va disable
+is seconds, but one can specify suffixes for different units, such as
+.Dq m
+for minutes
+.Dq h
+for hours and
+.Dq d
+for days.
+.Pp
+Matching is done first by checking the
+.Va local
+rules one by one, from the most specific to the least specific.
+If a match is found, then the
+.Va remote
+rules are applied, and if a match is found the
+.Va name ,
+.Va nfail ,
+and
+.Va disable
+fields can be altered by the
+.Va remote
+rule that matched.
+.Pp
+The
+.Va remote
+rules can be used for whitelisting specific addresses, changing the mask
+size, or the rule that the packet filter uses, the number of failed attempts,
+or the blocked duration.
+.Sh FILES
+.Bl -tag -width /etc/blacklistd.conf -compact
+.It Pa /etc/blacklistd.conf
+Configuration file.
+.El
+.Sh EXAMPLES
+.Bd -literal -offset
+# Block ssh, after 3 attempts for 6 hours on the bnx0 interface
+[local]
+# location type proto owner name nfail duration
+bnx0:ssh * * * * 3 6h
+[remote]
+# Never block 1.2.3.4
+1.2.3.4:ssh * * * * * *
+# For addresses coming from 8.8.0.0/16 block class C networks instead
+# individual hosts, but keep the rest of the blocking parameters the same.
+8.8.0.0/16:ssh * * * /24 = =
+.Ed
+.Sh SEE ALSO
+.Xr blacklistctl 8 ,
+.Xr blacklistd 8
+.Sh HISTORY
+.Nm
+appeared in
+.Nx 7 .
+.Sh AUTHORS
+.An Christos Zoulas
diff --git a/bin/conf.c b/bin/conf.c
new file mode 100644
index 0000000000000..3ec1e085c276f
--- /dev/null
+++ b/bin/conf.c
@@ -0,0 +1,1142 @@
+/* $NetBSD: conf.c,v 1.24 2016/04/04 15:52:56 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: conf.c,v 1.24 2016/04/04 15:52:56 christos Exp $");
+
+#include <stdio.h>
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#include <string.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <syslog.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <ifaddrs.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <sys/socket.h>
+
+#include "bl.h"
+#include "internal.h"
+#include "support.h"
+#include "conf.h"
+
+
+struct sockaddr_if {
+ uint8_t sif_len;
+ sa_family_t sif_family;
+ in_port_t sif_port;
+ char sif_name[16];
+};
+
+#define SIF_NAME(a) \
+ ((const struct sockaddr_if *)(const void *)(a))->sif_name
+
+static int conf_is_interface(const char *);
+
+#define FSTAR -1
+#define FEQUAL -2
+
+static void
+advance(char **p)
+{
+ char *ep = *p;
+ while (*ep && !isspace((unsigned char)*ep))
+ ep++;
+ while (*ep && isspace((unsigned char)*ep))
+ *ep++ = '\0';
+ *p = ep;
+}
+
+static int
+getnum(const char *f, size_t l, bool local, void *rp, const char *name,
+ const char *p)
+{
+ int e;
+ intmax_t im;
+ int *r = rp;
+
+ if (strcmp(p, "*") == 0) {
+ *r = FSTAR;
+ return 0;
+ }
+ if (strcmp(p, "=") == 0) {
+ if (local)
+ goto out;
+ *r = FEQUAL;
+ return 0;
+ }
+
+ im = strtoi(p, NULL, 0, 0, INT_MAX, &e);
+ if (e == 0) {
+ *r = (int)im;
+ return 0;
+ }
+
+ if (f == NULL)
+ return -1;
+ (*lfun)(LOG_ERR, "%s: %s, %zu: Bad number for %s [%s]", __func__, f, l,
+ name, p);
+ return -1;
+out:
+ (*lfun)(LOG_ERR, "%s: %s, %zu: `=' for %s not allowed in local config",
+ __func__, f, l, name);
+ return -1;
+
+}
+
+static int
+getnfail(const char *f, size_t l, bool local, struct conf *c, const char *p)
+{
+ return getnum(f, l, local, &c->c_nfail, "nfail", p);
+}
+
+static int
+getsecs(const char *f, size_t l, bool local, struct conf *c, const char *p)
+{
+ int e;
+ char *ep;
+ intmax_t tot, im;
+
+ tot = 0;
+ if (strcmp(p, "*") == 0) {
+ c->c_duration = FSTAR;
+ return 0;
+ }
+ if (strcmp(p, "=") == 0) {
+ if (local)
+ goto out;
+ c->c_duration = FEQUAL;
+ return 0;
+ }
+again:
+ im = strtoi(p, &ep, 0, 0, INT_MAX, &e);
+
+ if (e == ENOTSUP) {
+ switch (*ep) {
+ case 'd':
+ im *= 24;
+ /*FALLTHROUGH*/
+ case 'h':
+ im *= 60;
+ /*FALLTHROUGH*/
+ case 'm':
+ im *= 60;
+ /*FALLTHROUGH*/
+ case 's':
+ e = 0;
+ tot += im;
+ if (ep[1] != '\0') {
+ p = ep + 2;
+ goto again;
+ }
+ break;
+ }
+ } else
+ tot = im;
+
+ if (e == 0) {
+ c->c_duration = (int)tot;
+ return 0;
+ }
+
+ if (f == NULL)
+ return -1;
+ (*lfun)(LOG_ERR, "%s: %s, %zu: Bad number [%s]", __func__, f, l, p);
+ return -1;
+out:
+ (*lfun)(LOG_ERR, "%s: %s, %zu: `=' duration not allowed in local"
+ " config", __func__, f, l);
+ return -1;
+
+}
+
+static int
+getport(const char *f, size_t l, bool local, void *r, const char *p)
+{
+ struct servent *sv;
+
+ // XXX: Pass in the proto instead
+ if ((sv = getservbyname(p, "tcp")) != NULL) {
+ *(int *)r = ntohs(sv->s_port);
+ return 0;
+ }
+ if ((sv = getservbyname(p, "udp")) != NULL) {
+ *(int *)r = ntohs(sv->s_port);
+ return 0;
+ }
+
+ return getnum(f, l, local, r, "service", p);
+}
+
+static int
+getmask(const char *f, size_t l, bool local, const char **p, int *mask)
+{
+ char *d;
+ const char *s = *p;
+
+ if ((d = strchr(s, ':')) != NULL) {
+ *d++ = '\0';
+ *p = d;
+ }
+ if ((d = strchr(s, '/')) == NULL) {
+ *mask = FSTAR;
+ return 0;
+ }
+
+ *d++ = '\0';
+ return getnum(f, l, local, mask, "mask", d);
+}
+
+static int
+gethostport(const char *f, size_t l, bool local, struct conf *c, const char *p)
+{
+ char *d; // XXX: Ok to write to string.
+ in_port_t *port = NULL;
+ const char *pstr;
+
+ if (strcmp(p, "*") == 0) {
+ c->c_port = FSTAR;
+ c->c_lmask = FSTAR;
+ return 0;
+ }
+
+ if ((d = strchr(p, ']')) != NULL) {
+ *d++ = '\0';
+ pstr = d;
+ p++;
+ } else
+ pstr = p;
+
+ if (getmask(f, l, local, &pstr, &c->c_lmask) == -1)
+ goto out;
+
+ if (d) {
+ struct sockaddr_in6 *sin6 = (void *)&c->c_ss;
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s: host6 %s", __func__, p);
+ if (strcmp(p, "*") != 0) {
+ if (inet_pton(AF_INET6, p, &sin6->sin6_addr) == -1)
+ goto out;
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sin6->sin6_len = sizeof(*sin6);
+#endif
+ port = &sin6->sin6_port;
+ }
+ } else if (pstr != p || strchr(p, '.') || conf_is_interface(p)) {
+ if (pstr == p)
+ pstr = "*";
+ struct sockaddr_in *sin = (void *)&c->c_ss;
+ struct sockaddr_if *sif = (void *)&c->c_ss;
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s: host4 %s", __func__, p);
+ if (strcmp(p, "*") != 0) {
+ if (conf_is_interface(p)) {
+ if (!local)
+ goto out2;
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s: interface %s",
+ __func__, p);
+ if (c->c_lmask != FSTAR)
+ goto out1;
+ sif->sif_family = AF_MAX;
+ strlcpy(sif->sif_name, p,
+ sizeof(sif->sif_name));
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sif->sif_len = sizeof(*sif);
+#endif
+ port = &sif->sif_port;
+ } else if (inet_pton(AF_INET, p, &sin->sin_addr) != -1)
+ {
+ sin->sin_family = AF_INET;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sin->sin_len = sizeof(*sin);
+#endif
+ port = &sin->sin_port;
+ } else
+ goto out;
+ }
+ }
+
+ if (getport(f, l, local, &c->c_port, pstr) == -1)
+ return -1;
+
+ if (port && c->c_port != FSTAR && c->c_port != FEQUAL)
+ *port = htons((in_port_t)c->c_port);
+ return 0;
+out:
+ (*lfun)(LOG_ERR, "%s: %s, %zu: Bad address [%s]", __func__, f, l, pstr);
+ return -1;
+out1:
+ (*lfun)(LOG_ERR, "%s: %s, %zu: Can't specify mask %d with "
+ "interface [%s]", __func__, f, l, c->c_lmask, p);
+ return -1;
+out2:
+ (*lfun)(LOG_ERR, "%s: %s, %zu: Interface spec does not make sense "
+ "with remote config [%s]", __func__, f, l, p);
+ return -1;
+}
+
+static int
+getproto(const char *f, size_t l, bool local __unused, struct conf *c,
+ const char *p)
+{
+ if (strcmp(p, "stream") == 0) {
+ c->c_proto = IPPROTO_TCP;
+ return 0;
+ }
+ if (strcmp(p, "dgram") == 0) {
+ c->c_proto = IPPROTO_UDP;
+ return 0;
+ }
+ return getnum(f, l, local, &c->c_proto, "protocol", p);
+}
+
+static int
+getfamily(const char *f, size_t l, bool local __unused, struct conf *c,
+ const char *p)
+{
+ if (strncmp(p, "tcp", 3) == 0 || strncmp(p, "udp", 3) == 0) {
+ c->c_family = p[3] == '6' ? AF_INET6 : AF_INET;
+ return 0;
+ }
+ return getnum(f, l, local, &c->c_family, "family", p);
+}
+
+static int
+getuid(const char *f, size_t l, bool local __unused, struct conf *c,
+ const char *p)
+{
+ struct passwd *pw;
+
+ if ((pw = getpwnam(p)) != NULL) {
+ c->c_uid = (int)pw->pw_uid;
+ return 0;
+ }
+
+ return getnum(f, l, local, &c->c_uid, "user", p);
+}
+
+
+static int
+getname(const char *f, size_t l, bool local, struct conf *c,
+ const char *p)
+{
+ if (getmask(f, l, local, &p, &c->c_rmask) == -1)
+ return -1;
+
+ if (strcmp(p, "*") == 0) {
+ strlcpy(c->c_name, rulename, CONFNAMESZ);
+ return 0;
+ }
+ if (strcmp(p, "=") == 0) {
+ if (local)
+ goto out;
+ c->c_name[0] = '\0';
+ return 0;
+ }
+
+ snprintf(c->c_name, CONFNAMESZ, "%s%s", *p == '-' ? rulename : "", p);
+ return 0;
+out:
+ (*lfun)(LOG_ERR, "%s: %s, %zu: `=' name not allowed in local"
+ " config", __func__, f, l);
+ return -1;
+}
+
+static int
+getvalue(const char *f, size_t l, bool local, void *r, char **p,
+ int (*fun)(const char *, size_t, bool, struct conf *, const char *))
+{
+ char *ep = *p;
+
+ advance(p);
+ return (*fun)(f, l, local, r, ep);
+}
+
+
+static int
+conf_parseline(const char *f, size_t l, char *p, struct conf *c, bool local)
+{
+ int e;
+
+ while (*p && isspace((unsigned char)*p))
+ p++;
+
+ memset(c, 0, sizeof(*c));
+ e = getvalue(f, l, local, c, &p, gethostport);
+ if (e) return -1;
+ e = getvalue(f, l, local, c, &p, getproto);
+ if (e) return -1;
+ e = getvalue(f, l, local, c, &p, getfamily);
+ if (e) return -1;
+ e = getvalue(f, l, local, c, &p, getuid);
+ if (e) return -1;
+ e = getvalue(f, l, local, c, &p, getname);
+ if (e) return -1;
+ e = getvalue(f, l, local, c, &p, getnfail);
+ if (e) return -1;
+ e = getvalue(f, l, local, c, &p, getsecs);
+ if (e) return -1;
+
+ return 0;
+}
+
+static int
+conf_sort(const void *v1, const void *v2)
+{
+ const struct conf *c1 = v1;
+ const struct conf *c2 = v2;
+
+#define CMP(a, b, f) \
+ if ((a)->f > (b)->f) return -1; \
+ else if ((a)->f < (b)->f) return 1
+
+ CMP(c1, c2, c_ss.ss_family);
+ CMP(c1, c2, c_lmask);
+ CMP(c1, c2, c_port);
+ CMP(c1, c2, c_proto);
+ CMP(c1, c2, c_family);
+ CMP(c1, c2, c_rmask);
+ CMP(c1, c2, c_uid);
+#undef CMP
+ return 0;
+}
+
+static int
+conf_is_interface(const char *name)
+{
+ const struct ifaddrs *ifa;
+
+ for (ifa = ifas; ifa; ifa = ifa->ifa_next)
+ if (strcmp(ifa->ifa_name, name) == 0)
+ return 1;
+ return 0;
+}
+
+#define MASK(m) ((uint32_t)~((1 << (32 - (m))) - 1))
+
+static int
+conf_amask_eq(const void *v1, const void *v2, size_t len, int mask)
+{
+ const uint32_t *a1 = v1;
+ const uint32_t *a2 = v2;
+ uint32_t m;
+ int omask = mask;
+
+ len >>= 2;
+ switch (mask) {
+ case FSTAR:
+ if (memcmp(v1, v2, len) == 0)
+ return 1;
+ goto out;
+ case FEQUAL:
+
+ (*lfun)(LOG_CRIT, "%s: Internal error: bad mask %d", __func__,
+ mask);
+ abort();
+ default:
+ break;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ if (mask > 32) {
+ m = htonl((uint32_t)~0);
+ mask -= 32;
+ } else if (mask) {
+ m = htonl(MASK(mask));
+ mask = 0;
+ } else
+ return 1;
+ if ((a1[i] & m) != (a2[i] & m))
+ goto out;
+ }
+ return 1;
+out:
+ if (debug > 1) {
+ char b1[256], b2[256];
+ len <<= 2;
+ blhexdump(b1, sizeof(b1), "a1", v1, len);
+ blhexdump(b2, sizeof(b2), "a2", v2, len);
+ (*lfun)(LOG_DEBUG, "%s: %s != %s [0x%x]", __func__,
+ b1, b2, omask);
+ }
+ return 0;
+}
+
+/*
+ * Apply the mask to the given address
+ */
+static void
+conf_apply_mask(void *v, size_t len, int mask)
+{
+ uint32_t *a = v;
+ uint32_t m;
+
+ switch (mask) {
+ case FSTAR:
+ return;
+ case FEQUAL:
+ (*lfun)(LOG_CRIT, "%s: Internal error: bad mask %d", __func__,
+ mask);
+ abort();
+ default:
+ break;
+ }
+ len >>= 2;
+
+ for (size_t i = 0; i < len; i++) {
+ if (mask > 32) {
+ m = htonl((uint32_t)~0);
+ mask -= 32;
+ } else if (mask) {
+ m = htonl(MASK(mask));
+ mask = 0;
+ } else
+ m = 0;
+ a[i] &= m;
+ }
+}
+
+/*
+ * apply the mask and the port to the address given
+ */
+static void
+conf_addr_set(struct conf *c, const struct sockaddr_storage *ss)
+{
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ in_port_t *port;
+ void *addr;
+ size_t alen;
+
+ c->c_lmask = c->c_rmask;
+ c->c_ss = *ss;
+
+ if (c->c_ss.ss_family != c->c_family) {
+ (*lfun)(LOG_CRIT, "%s: Internal error: mismatched family "
+ "%u != %u", __func__, c->c_ss.ss_family, c->c_family);
+ abort();
+ }
+
+ switch (c->c_ss.ss_family) {
+ case AF_INET:
+ sin = (void *)&c->c_ss;
+ port = &sin->sin_port;
+ addr = &sin->sin_addr;
+ alen = sizeof(sin->sin_addr);
+ break;
+ case AF_INET6:
+ sin6 = (void *)&c->c_ss;
+ port = &sin6->sin6_port;
+ addr = &sin6->sin6_addr;
+ alen = sizeof(sin6->sin6_addr);
+ break;
+ default:
+ (*lfun)(LOG_CRIT, "%s: Internal error: bad family %u",
+ __func__, c->c_ss.ss_family);
+ abort();
+ }
+
+ *port = htons((in_port_t)c->c_port);
+ conf_apply_mask(addr, alen, c->c_lmask);
+ if (c->c_lmask == FSTAR)
+ c->c_lmask = (int)(alen * 8);
+ if (debug) {
+ char buf[128];
+ sockaddr_snprintf(buf, sizeof(buf), "%a:%p", (void *)&c->c_ss);
+ (*lfun)(LOG_DEBUG, "Applied address %s", buf);
+ }
+}
+
+/*
+ * Compared two addresses for equality applying the mask
+ */
+static int
+conf_inet_eq(const void *v1, const void *v2, int mask)
+{
+ const struct sockaddr *sa1 = v1;
+ const struct sockaddr *sa2 = v2;
+ size_t size;
+
+ if (sa1->sa_family != sa2->sa_family)
+ return 0;
+
+ switch (sa1->sa_family) {
+ case AF_INET: {
+ const struct sockaddr_in *s1 = v1;
+ const struct sockaddr_in *s2 = v2;
+ size = sizeof(s1->sin_addr);
+ v1 = &s1->sin_addr;
+ v2 = &s2->sin_addr;
+ break;
+ }
+
+ case AF_INET6: {
+ const struct sockaddr_in6 *s1 = v1;
+ const struct sockaddr_in6 *s2 = v2;
+ size = sizeof(s1->sin6_addr);
+ v1 = &s1->sin6_addr;
+ v2 = &s2->sin6_addr;
+ break;
+ }
+
+ default:
+ (*lfun)(LOG_CRIT, "%s: Internal error: bad family %u",
+ __func__, sa1->sa_family);
+ abort();
+ }
+
+ return conf_amask_eq(v1, v2, size, mask);
+}
+
+static int
+conf_addr_in_interface(const struct sockaddr_storage *s1,
+ const struct sockaddr_storage *s2, int mask)
+{
+ const char *name = SIF_NAME(s2);
+ const struct ifaddrs *ifa;
+
+ for (ifa = ifas; ifa; ifa = ifa->ifa_next) {
+ if ((ifa->ifa_flags & IFF_UP) == 0)
+ continue;
+
+ if (strcmp(ifa->ifa_name, name) != 0)
+ continue;
+
+ if (s1->ss_family != ifa->ifa_addr->sa_family)
+ continue;
+
+ bool eq;
+ switch (s1->ss_family) {
+ case AF_INET:
+ case AF_INET6:
+ eq = conf_inet_eq(ifa->ifa_addr, s1, mask);
+ break;
+ default:
+ (*lfun)(LOG_ERR, "Bad family %u", s1->ss_family);
+ continue;
+ }
+ if (eq)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+conf_addr_eq(const struct sockaddr_storage *s1,
+ const struct sockaddr_storage *s2, int mask)
+{
+ switch (s2->ss_family) {
+ case 0:
+ return 1;
+ case AF_MAX:
+ return conf_addr_in_interface(s1, s2, mask);
+ case AF_INET:
+ case AF_INET6:
+ return conf_inet_eq(s1, s2, mask);
+ default:
+ (*lfun)(LOG_CRIT, "%s: Internal error: bad family %u",
+ __func__, s1->ss_family);
+ abort();
+ }
+}
+
+static int
+conf_eq(const struct conf *c1, const struct conf *c2)
+{
+
+ if (!conf_addr_eq(&c1->c_ss, &c2->c_ss, c2->c_lmask))
+ return 0;
+
+#define CMP(a, b, f) \
+ if ((a)->f != (b)->f && (b)->f != FSTAR && (b)->f != FEQUAL) { \
+ if (debug > 1) \
+ (*lfun)(LOG_DEBUG, "%s: %s fail %d != %d", __func__, \
+ __STRING(f), (a)->f, (b)->f); \
+ return 0; \
+ }
+ CMP(c1, c2, c_port);
+ CMP(c1, c2, c_proto);
+ CMP(c1, c2, c_family);
+ CMP(c1, c2, c_uid);
+#undef CMP
+ return 1;
+}
+
+static const char *
+conf_num(char *b, size_t l, int n)
+{
+ switch (n) {
+ case FSTAR:
+ return "*";
+ case FEQUAL:
+ return "=";
+ default:
+ snprintf(b, l, "%d", n);
+ return b;
+ }
+}
+
+static const char *
+fmtname(const char *n) {
+ size_t l = strlen(rulename);
+ if (l == 0)
+ return "*";
+ if (strncmp(n, rulename, l) == 0) {
+ if (n[l] != '\0')
+ return n + l;
+ else
+ return "*";
+ } else if (!*n)
+ return "=";
+ else
+ return n;
+}
+
+static void
+fmtport(char *b, size_t l, int port)
+{
+ char buf[128];
+
+ if (port == FSTAR)
+ return;
+
+ if (b[0] == '\0' || strcmp(b, "*") == 0)
+ snprintf(b, l, "%d", port);
+ else {
+ snprintf(buf, sizeof(buf), ":%d", port);
+ strlcat(b, buf, l);
+ }
+}
+
+static const char *
+fmtmask(char *b, size_t l, int fam, int mask)
+{
+ char buf[128];
+
+ switch (mask) {
+ case FSTAR:
+ return "";
+ case FEQUAL:
+ if (strcmp(b, "=") == 0)
+ return "";
+ else {
+ strlcat(b, "/=", l);
+ return b;
+ }
+ default:
+ break;
+ }
+
+ switch (fam) {
+ case AF_INET:
+ if (mask == 32)
+ return "";
+ break;
+ case AF_INET6:
+ if (mask == 128)
+ return "";
+ break;
+ default:
+ break;
+ }
+
+ snprintf(buf, sizeof(buf), "/%d", mask);
+ strlcat(b, buf, l);
+ return b;
+}
+
+static const char *
+conf_namemask(char *b, size_t l, const struct conf *c)
+{
+ strlcpy(b, fmtname(c->c_name), l);
+ fmtmask(b, l, c->c_family, c->c_rmask);
+ return b;
+}
+
+const char *
+conf_print(char *buf, size_t len, const char *pref, const char *delim,
+ const struct conf *c)
+{
+ char ha[128], hb[32], b[5][64];
+ int sp;
+
+#define N(n, v) conf_num(b[n], sizeof(b[n]), (v))
+
+ switch (c->c_ss.ss_family) {
+ case 0:
+ snprintf(ha, sizeof(ha), "*");
+ break;
+ case AF_MAX:
+ snprintf(ha, sizeof(ha), "%s", SIF_NAME(&c->c_ss));
+ break;
+ default:
+ sockaddr_snprintf(ha, sizeof(ha), "%a", (const void *)&c->c_ss);
+ break;
+ }
+
+ fmtmask(ha, sizeof(ha), c->c_family, c->c_lmask);
+ fmtport(ha, sizeof(ha), c->c_port);
+
+ sp = *delim == '\t' ? 20 : -1;
+ hb[0] = '\0';
+ if (*delim)
+ snprintf(buf, len, "%s%*.*s%s%s%s" "%s%s%s%s"
+ "%s%s" "%s%s%s",
+ pref, sp, sp, ha, delim, N(0, c->c_proto), delim,
+ N(1, c->c_family), delim, N(2, c->c_uid), delim,
+ conf_namemask(hb, sizeof(hb), c), delim,
+ N(3, c->c_nfail), delim, N(4, c->c_duration));
+ else
+ snprintf(buf, len, "%starget:%s, proto:%s, family:%s, "
+ "uid:%s, name:%s, nfail:%s, duration:%s", pref,
+ ha, N(0, c->c_proto), N(1, c->c_family), N(2, c->c_uid),
+ conf_namemask(hb, sizeof(hb), c),
+ N(3, c->c_nfail), N(4, c->c_duration));
+ return buf;
+}
+
+/*
+ * Apply the local config match to the result
+ */
+static void
+conf_apply(struct conf *c, const struct conf *sc)
+{
+ char buf[BUFSIZ];
+
+ if (debug) {
+ (*lfun)(LOG_DEBUG, "%s: %s", __func__,
+ conf_print(buf, sizeof(buf), "merge:\t", "", sc));
+ (*lfun)(LOG_DEBUG, "%s: %s", __func__,
+ conf_print(buf, sizeof(buf), "to:\t", "", c));
+ }
+ memcpy(c->c_name, sc->c_name, CONFNAMESZ);
+ c->c_uid = sc->c_uid;
+ c->c_rmask = sc->c_rmask;
+ c->c_nfail = sc->c_nfail;
+ c->c_duration = sc->c_duration;
+
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s: %s", __func__,
+ conf_print(buf, sizeof(buf), "result:\t", "", c));
+}
+
+/*
+ * Merge a remote configuration to the result
+ */
+static void
+conf_merge(struct conf *c, const struct conf *sc)
+{
+ char buf[BUFSIZ];
+
+ if (debug) {
+ (*lfun)(LOG_DEBUG, "%s: %s", __func__,
+ conf_print(buf, sizeof(buf), "merge:\t", "", sc));
+ (*lfun)(LOG_DEBUG, "%s: %s", __func__,
+ conf_print(buf, sizeof(buf), "to:\t", "", c));
+ }
+
+ if (sc->c_name[0])
+ memcpy(c->c_name, sc->c_name, CONFNAMESZ);
+ if (sc->c_uid != FEQUAL)
+ c->c_uid = sc->c_uid;
+ if (sc->c_rmask != FEQUAL)
+ c->c_lmask = c->c_rmask = sc->c_rmask;
+ if (sc->c_nfail != FEQUAL)
+ c->c_nfail = sc->c_nfail;
+ if (sc->c_duration != FEQUAL)
+ c->c_duration = sc->c_duration;
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s: %s", __func__,
+ conf_print(buf, sizeof(buf), "result:\t", "", c));
+}
+
+static void
+confset_init(struct confset *cs)
+{
+ cs->cs_c = NULL;
+ cs->cs_n = 0;
+ cs->cs_m = 0;
+}
+
+static int
+confset_grow(struct confset *cs)
+{
+ void *tc;
+
+ cs->cs_m += 10;
+ tc = realloc(cs->cs_c, cs->cs_m * sizeof(*cs->cs_c));
+ if (tc == NULL) {
+ (*lfun)(LOG_ERR, "%s: Can't grow confset (%m)", __func__);
+ return -1;
+ }
+ cs->cs_c = tc;
+ return 0;
+}
+
+static struct conf *
+confset_get(struct confset *cs)
+{
+ return &cs->cs_c[cs->cs_n];
+}
+
+static bool
+confset_full(const struct confset *cs)
+{
+ return cs->cs_n == cs->cs_m;
+}
+
+static void
+confset_sort(struct confset *cs)
+{
+ qsort(cs->cs_c, cs->cs_n, sizeof(*cs->cs_c), conf_sort);
+}
+
+static void
+confset_add(struct confset *cs)
+{
+ cs->cs_n++;
+}
+
+static void
+confset_free(struct confset *cs)
+{
+ free(cs->cs_c);
+ confset_init(cs);
+}
+
+static void
+confset_replace(struct confset *dc, struct confset *sc)
+{
+ struct confset tc;
+ tc = *dc;
+ *dc = *sc;
+ confset_init(sc);
+ confset_free(&tc);
+}
+
+static void
+confset_list(const struct confset *cs, const char *msg, const char *where)
+{
+ char buf[BUFSIZ];
+
+ (*lfun)(LOG_DEBUG, "[%s]", msg);
+ (*lfun)(LOG_DEBUG, "%20.20s\ttype\tproto\towner\tname\tnfail\tduration",
+ where);
+ for (size_t i = 0; i < cs->cs_n; i++)
+ (*lfun)(LOG_DEBUG, "%s", conf_print(buf, sizeof(buf), "", "\t",
+ &cs->cs_c[i]));
+}
+
+/*
+ * Match a configuration against the given list and apply the function
+ * to it, returning the matched entry number.
+ */
+static size_t
+confset_match(const struct confset *cs, struct conf *c,
+ void (*fun)(struct conf *, const struct conf *))
+{
+ char buf[BUFSIZ];
+ size_t i;
+
+ for (i = 0; i < cs->cs_n; i++) {
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s", conf_print(buf, sizeof(buf),
+ "check:\t", "", &cs->cs_c[i]));
+ if (conf_eq(c, &cs->cs_c[i])) {
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s",
+ conf_print(buf, sizeof(buf),
+ "found:\t", "", &cs->cs_c[i]));
+ (*fun)(c, &cs->cs_c[i]);
+ break;
+ }
+ }
+ return i;
+}
+
+const struct conf *
+conf_find(int fd, uid_t uid, const struct sockaddr_storage *rss,
+ struct conf *cr)
+{
+ int proto;
+ socklen_t slen;
+ struct sockaddr_storage lss;
+ size_t i;
+ char buf[BUFSIZ];
+
+ memset(cr, 0, sizeof(*cr));
+ slen = sizeof(lss);
+ memset(&lss, 0, slen);
+ if (getsockname(fd, (void *)&lss, &slen) == -1) {
+ (*lfun)(LOG_ERR, "getsockname failed (%m)");
+ return NULL;
+ }
+
+ slen = sizeof(proto);
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &proto, &slen) == -1) {
+ (*lfun)(LOG_ERR, "getsockopt failed (%m)");
+ return NULL;
+ }
+
+ if (debug) {
+ sockaddr_snprintf(buf, sizeof(buf), "%a:%p", (void *)&lss);
+ (*lfun)(LOG_DEBUG, "listening socket: %s", buf);
+ }
+
+ switch (proto) {
+ case SOCK_STREAM:
+ cr->c_proto = IPPROTO_TCP;
+ break;
+ case SOCK_DGRAM:
+ cr->c_proto = IPPROTO_UDP;
+ break;
+ default:
+ (*lfun)(LOG_ERR, "unsupported protocol %d", proto);
+ return NULL;
+ }
+
+ switch (lss.ss_family) {
+ case AF_INET:
+ cr->c_port = ntohs(((struct sockaddr_in *)&lss)->sin_port);
+ break;
+ case AF_INET6:
+ cr->c_port = ntohs(((struct sockaddr_in6 *)&lss)->sin6_port);
+ break;
+ default:
+ (*lfun)(LOG_ERR, "unsupported family %d", lss.ss_family);
+ return NULL;
+ }
+
+ cr->c_ss = lss;
+ cr->c_lmask = FSTAR;
+ cr->c_uid = (int)uid;
+ cr->c_family = lss.ss_family;
+ cr->c_name[0] = '\0';
+ cr->c_rmask = FSTAR;
+ cr->c_nfail = FSTAR;
+ cr->c_duration = FSTAR;
+
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s", conf_print(buf, sizeof(buf),
+ "look:\t", "", cr));
+
+ /* match the local config */
+ i = confset_match(&lconf, cr, conf_apply);
+ if (i == lconf.cs_n) {
+ if (debug)
+ (*lfun)(LOG_DEBUG, "not found");
+ return NULL;
+ }
+
+ conf_addr_set(cr, rss);
+ /* match the remote config */
+ confset_match(&rconf, cr, conf_merge);
+ /* to apply the mask */
+ conf_addr_set(cr, &cr->c_ss);
+
+ return cr;
+}
+
+
+void
+conf_parse(const char *f)
+{
+ FILE *fp;
+ char *line;
+ size_t lineno, len;
+ struct confset lc, rc, *cs;
+
+ if ((fp = fopen(f, "r")) == NULL) {
+ (*lfun)(LOG_ERR, "%s: Cannot open `%s' (%m)", __func__, f);
+ return;
+ }
+
+ lineno = 1;
+
+ confset_init(&rc);
+ confset_init(&lc);
+ cs = &lc;
+ for (; (line = fparseln(fp, &len, &lineno, NULL, 0)) != NULL;
+ free(line))
+ {
+ if (!*line)
+ continue;
+ if (strcmp(line, "[local]") == 0) {
+ cs = &lc;
+ continue;
+ }
+ if (strcmp(line, "[remote]") == 0) {
+ cs = &rc;
+ continue;
+ }
+
+ if (confset_full(cs)) {
+ if (confset_grow(cs) == -1) {
+ confset_free(&lc);
+ confset_free(&rc);
+ fclose(fp);
+ return;
+ }
+ }
+ if (conf_parseline(f, lineno, line, confset_get(cs),
+ cs == &lc) == -1)
+ continue;
+ confset_add(cs);
+ }
+
+ fclose(fp);
+ confset_sort(&lc);
+ confset_sort(&rc);
+
+ confset_replace(&rconf, &rc);
+ confset_replace(&lconf, &lc);
+
+ if (debug) {
+ confset_list(&lconf, "local", "target");
+ confset_list(&rconf, "remote", "source");
+ }
+}
diff --git a/bin/conf.h b/bin/conf.h
new file mode 100644
index 0000000000000..03f1942e3e326
--- /dev/null
+++ b/bin/conf.h
@@ -0,0 +1,65 @@
+/* $NetBSD: conf.h,v 1.6 2015/01/27 19:40:36 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifndef _CONF_H
+#define _CONF_H
+
+#include <sys/socket.h>
+
+struct conf {
+ struct sockaddr_storage c_ss;
+ int c_lmask;
+ int c_port;
+ int c_proto;
+ int c_family;
+ int c_uid;
+ int c_nfail;
+ char c_name[128];
+ int c_rmask;
+ int c_duration;
+};
+
+struct confset {
+ struct conf *cs_c;
+ size_t cs_n;
+ size_t cs_m;
+};
+
+#define CONFNAMESZ sizeof(((struct conf *)0)->c_name)
+
+__BEGIN_DECLS
+const char *conf_print(char *, size_t, const char *, const char *,
+ const struct conf *);
+void conf_parse(const char *);
+const struct conf *conf_find(int, uid_t, const struct sockaddr_storage *,
+ struct conf *);
+__END_DECLS
+
+#endif /* _CONF_H */
diff --git a/bin/internal.c b/bin/internal.c
new file mode 100644
index 0000000000000..5c039e4dc5d26
--- /dev/null
+++ b/bin/internal.c
@@ -0,0 +1,48 @@
+/* $NetBSD: internal.c,v 1.5 2015/01/27 19:40:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: internal.c,v 1.5 2015/01/27 19:40:37 christos Exp $");
+
+#include <stdio.h>
+#include <syslog.h>
+#include "conf.h"
+#include "internal.h"
+
+int debug;
+const char *rulename = "blacklistd";
+const char *controlprog = _PATH_BLCONTROL;
+struct confset lconf, rconf;
+struct ifaddrs *ifas;
+void (*lfun)(int, const char *, ...) = syslog;
diff --git a/bin/internal.h b/bin/internal.h
new file mode 100644
index 0000000000000..5a40e49fbbd5b
--- /dev/null
+++ b/bin/internal.h
@@ -0,0 +1,57 @@
+/* $NetBSD: internal.h,v 1.14 2016/04/04 15:52:56 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifndef _INTERNAL_H
+#define _INTERNAL_H
+
+#ifndef _PATH_BLCONF
+#define _PATH_BLCONF "/etc/blacklistd.conf"
+#endif
+#ifndef _PATH_BLCONTROL
+#define _PATH_BLCONTROL "/libexec/blacklistd-helper"
+#endif
+#ifndef _PATH_BLSTATE
+#define _PATH_BLSTATE "/var/db/blacklistd.db"
+#endif
+
+extern struct confset rconf, lconf;
+extern int debug;
+extern const char *rulename;
+extern const char *controlprog;
+extern struct ifaddrs *ifas;
+
+#if !defined(__syslog_attribute__) && !defined(__syslog__)
+#define __syslog__ __printf__
+#endif
+
+extern void (*lfun)(int, const char *, ...)
+ __attribute__((__format__(__syslog__, 2, 3)));
+
+#endif /* _INTERNAL_H */
diff --git a/bin/run.c b/bin/run.c
new file mode 100644
index 0000000000000..8499edd3d9c2f
--- /dev/null
+++ b/bin/run.c
@@ -0,0 +1,156 @@
+/* $NetBSD: run.c,v 1.14 2016/04/04 15:52:56 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: run.c,v 1.14 2016/04/04 15:52:56 christos Exp $");
+
+#include <stdio.h>
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#include <stdarg.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <syslog.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <net/if.h>
+
+#include "run.h"
+#include "conf.h"
+#include "internal.h"
+#include "support.h"
+
+extern char **environ;
+
+static char *
+run(const char *cmd, const char *name, ...)
+{
+ const char *argv[20];
+ size_t i;
+ va_list ap;
+ FILE *fp;
+ char buf[10240], *res;
+
+ argv[0] = "control";
+ argv[1] = cmd;
+ argv[2] = name;
+ va_start(ap, name);
+ for (i = 3; i < __arraycount(argv) &&
+ (argv[i] = va_arg(ap, char *)) != NULL; i++)
+ continue;
+ va_end(ap);
+
+ if (debug) {
+ size_t z;
+ int r;
+
+ r = snprintf(buf, sizeof(buf), "run %s [", controlprog);
+ if (r == -1 || (z = (size_t)r) >= sizeof(buf))
+ z = sizeof(buf);
+ for (i = 0; argv[i]; i++) {
+ r = snprintf(buf + z, sizeof(buf) - z, "%s%s",
+ argv[i], argv[i + 1] ? " " : "");
+ if (r == -1 || (z += (size_t)r) >= sizeof(buf))
+ z = sizeof(buf);
+ }
+ (*lfun)(LOG_DEBUG, "%s]", buf);
+ }
+
+ fp = popenve(controlprog, __UNCONST(argv), environ, "r");
+ if (fp == NULL) {
+ (*lfun)(LOG_ERR, "popen %s failed (%m)", controlprog);
+ return NULL;
+ }
+ if (fgets(buf, sizeof(buf), fp) != NULL)
+ res = strdup(buf);
+ else
+ res = NULL;
+ pclose(fp);
+ if (debug)
+ (*lfun)(LOG_DEBUG, "%s returns %s", cmd, res);
+ return res;
+}
+
+void
+run_flush(const struct conf *c)
+{
+ free(run("flush", c->c_name, NULL));
+}
+
+int
+run_change(const char *how, const struct conf *c, char *id, size_t len)
+{
+ const char *prname;
+ char poname[64], adname[128], maskname[32], *rv;
+ size_t off;
+
+ switch (c->c_proto) {
+ case -1:
+ prname = "";
+ break;
+ case IPPROTO_TCP:
+ prname = "tcp";
+ break;
+ case IPPROTO_UDP:
+ prname = "udp";
+ break;
+ default:
+ (*lfun)(LOG_ERR, "%s: bad protocol %d", __func__, c->c_proto);
+ return -1;
+ }
+
+ if (c->c_port != -1)
+ snprintf(poname, sizeof(poname), "%d", c->c_port);
+ else
+ poname[0] = '\0';
+
+ snprintf(maskname, sizeof(maskname), "%d", c->c_lmask);
+ sockaddr_snprintf(adname, sizeof(adname), "%a", (const void *)&c->c_ss);
+
+ rv = run(how, c->c_name, prname, adname, maskname, poname, id, NULL);
+ if (rv == NULL)
+ return -1;
+ if (len != 0) {
+ rv[strcspn(rv, "\n")] = '\0';
+ off = strncmp(rv, "OK ", 3) == 0 ? 3 : 0;
+ strlcpy(id, rv + off, len);
+ }
+ free(rv);
+ return 0;
+}
diff --git a/bin/run.h b/bin/run.h
new file mode 100644
index 0000000000000..bafc3e554690b
--- /dev/null
+++ b/bin/run.h
@@ -0,0 +1,41 @@
+/* $NetBSD: run.h,v 1.5 2015/01/27 19:40:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifndef _RUN_H
+#define _RUN_H
+
+__BEGIN_DECLS
+struct conf;
+void run_flush(const struct conf *);
+struct sockaddr_storage;
+int run_change(const char *, const struct conf *, char *, size_t);
+__END_DECLS
+
+#endif /* _RUN_H */
diff --git a/bin/state.c b/bin/state.c
new file mode 100644
index 0000000000000..eb97e970fb1e0
--- /dev/null
+++ b/bin/state.c
@@ -0,0 +1,233 @@
+/* $NetBSD: state.c,v 1.18 2016/04/04 15:52:56 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: state.c,v 1.18 2016/04/04 15:52:56 christos Exp $");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <netinet/in.h>
+
+#include "bl.h"
+#include "internal.h"
+#include "conf.h"
+#include "support.h"
+#include "state.h"
+
+static HASHINFO openinfo = {
+ 4096, /* bsize */
+ 32, /* ffactor */
+ 256, /* nelem */
+ 8 * 1024 * 1024,/* cachesize */
+ NULL, /* hash() */
+ 0 /* lorder */
+};
+
+int
+state_close(DB *db)
+{
+ if (db == NULL)
+ return -1;
+ if ((*db->close)(db) == -1) {
+ (*lfun)(LOG_ERR, "%s: can't close db (%m)", __func__);
+ return -1;
+ }
+ return 0;
+}
+
+DB *
+state_open(const char *dbname, int flags, mode_t perm)
+{
+ DB *db;
+
+#ifdef __APPLE__
+ flags &= O_CREAT|O_EXCL|O_EXLOCK|O_NONBLOCK|O_RDONLY|
+ O_RDWR|O_SHLOCK|O_TRUNC;
+#endif
+ db = dbopen(dbname, flags, perm, DB_HASH, &openinfo);
+ if (db == NULL) {
+ if (errno == ENOENT && (flags & O_CREAT) == 0)
+ return NULL;
+ (*lfun)(LOG_ERR, "%s: can't open `%s' (%m)", __func__, dbname);
+ }
+ return db;
+}
+
+static int
+state_sizecheck(const DBT *t)
+{
+ if (sizeof(struct conf) == t->size)
+ return 0;
+ (*lfun)(LOG_ERR, "Key size mismatch %zu != %zu", sizeof(struct conf),
+ t->size);
+ return -1;
+}
+
+static void
+dumpkey(const struct conf *k)
+{
+ char buf[10240];
+ blhexdump(buf, sizeof(buf), __func__, k, sizeof(*k));
+ (*lfun)(LOG_DEBUG, "%s", buf);
+ (*lfun)(LOG_DEBUG, "%s: %s", __func__,
+ conf_print(buf, sizeof(buf), "", "", k));
+
+}
+
+int
+state_del(DB *db, const struct conf *c)
+{
+ int rv;
+ DBT k;
+
+ if (db == NULL)
+ return -1;
+
+ k.data = __UNCONST(c);
+ k.size = sizeof(*c);
+
+ switch (rv = (*db->del)(db, &k, 0)) {
+ case 0:
+ case 1:
+ if (debug > 1) {
+ (*lfun)(LOG_DEBUG, "%s: returns %d", __func__, rv);
+ (*db->sync)(db, 0);
+ }
+ return 0;
+ default:
+ (*lfun)(LOG_ERR, "%s: failed (%m)", __func__);
+ return -1;
+ }
+}
+
+int
+state_get(DB *db, const struct conf *c, struct dbinfo *dbi)
+{
+ int rv;
+ DBT k, v;
+
+ if (db == NULL)
+ return -1;
+
+ k.data = __UNCONST(c);
+ k.size = sizeof(*c);
+
+ switch (rv = (*db->get)(db, &k, &v, 0)) {
+ case 0:
+ case 1:
+ if (rv)
+ memset(dbi, 0, sizeof(*dbi));
+ else
+ memcpy(dbi, v.data, sizeof(*dbi));
+ if (debug > 1)
+ (*lfun)(LOG_DEBUG, "%s: returns %d", __func__, rv);
+ return 0;
+ default:
+ (*lfun)(LOG_ERR, "%s: failed (%m)", __func__);
+ return -1;
+ }
+}
+
+int
+state_put(DB *db, const struct conf *c, const struct dbinfo *dbi)
+{
+ int rv;
+ DBT k, v;
+
+ if (db == NULL)
+ return -1;
+
+ k.data = __UNCONST(c);
+ k.size = sizeof(*c);
+ v.data = __UNCONST(dbi);
+ v.size = sizeof(*dbi);
+
+ switch (rv = (*db->put)(db, &k, &v, 0)) {
+ case 0:
+ if (debug > 1) {
+ (*lfun)(LOG_DEBUG, "%s: returns %d", __func__, rv);
+ (*db->sync)(db, 0);
+ }
+ return 0;
+ case 1:
+ errno = EEXIST;
+ /*FALLTHROUGH*/
+ default:
+ (*lfun)(LOG_ERR, "%s: failed (%m)", __func__);
+ return -1;
+ }
+}
+
+int
+state_iterate(DB *db, struct conf *c, struct dbinfo *dbi, unsigned int first)
+{
+ int rv;
+ DBT k, v;
+
+ if (db == NULL)
+ return -1;
+
+ first = first ? R_FIRST : R_NEXT;
+
+ switch (rv = (*db->seq)(db, &k, &v, first)) {
+ case 0:
+ if (state_sizecheck(&k) == -1)
+ return -1;
+ memcpy(c, k.data, sizeof(*c));
+ if (debug > 2)
+ dumpkey(c);
+ memcpy(dbi, v.data, sizeof(*dbi));
+ if (debug > 1)
+ (*lfun)(LOG_DEBUG, "%s: returns %d", __func__, rv);
+ return 1;
+ case 1:
+ if (debug > 1)
+ (*lfun)(LOG_DEBUG, "%s: returns %d", __func__, rv);
+ return 0;
+ default:
+ (*lfun)(LOG_ERR, "%s: failed (%m)", __func__);
+ return -1;
+ }
+}
+
+int
+state_sync(DB *db)
+{
+ return (*db->sync)(db, 0);
+}
diff --git a/bin/state.h b/bin/state.h
new file mode 100644
index 0000000000000..2e9257006e806
--- /dev/null
+++ b/bin/state.h
@@ -0,0 +1,62 @@
+/* $NetBSD: state.h,v 1.5 2015/01/27 19:40:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifndef _STATE_H
+#define _STATE_H
+
+#ifdef HAVE_DB_185_H
+#include <db_185.h>
+#elif HAVE_DB_H
+#include <db.h>
+#else
+#error "no db.h"
+#endif
+#include <time.h>
+
+struct dbinfo {
+ int count;
+ time_t last;
+ char id[64];
+};
+
+__BEGIN_DECLS
+struct sockaddr_storage;
+struct conf;
+
+DB *state_open(const char *, int, mode_t);
+int state_close(DB *);
+int state_get(DB *, const struct conf *, struct dbinfo *);
+int state_put(DB *, const struct conf *, const struct dbinfo *);
+int state_del(DB *, const struct conf *);
+int state_iterate(DB *, struct conf *, struct dbinfo *, unsigned int);
+int state_sync(DB *);
+__END_DECLS
+
+#endif /* _STATE_H */
diff --git a/bin/support.c b/bin/support.c
new file mode 100644
index 0000000000000..0dac499aca022
--- /dev/null
+++ b/bin/support.c
@@ -0,0 +1,157 @@
+/* $NetBSD: support.c,v 1.8 2016/04/04 15:52:56 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: support.c,v 1.8 2016/04/04 15:52:56 christos Exp $");
+
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "support.h"
+
+static __attribute__((__format_arg__(3))) const char *
+expandm(char *buf, size_t len, const char *fmt)
+{
+ char *p;
+ size_t r;
+
+ if ((p = strstr(fmt, "%m")) == NULL)
+ return fmt;
+
+ r = (size_t)(p - fmt);
+ if (r >= len)
+ return fmt;
+
+ strlcpy(buf, fmt, r + 1);
+ strlcat(buf, strerror(errno), len);
+ strlcat(buf, fmt + r + 2, len);
+
+ return buf;
+}
+
+void
+vdlog(int level __unused, const char *fmt, va_list ap)
+{
+ char buf[BUFSIZ];
+
+// fprintf(stderr, "%s: ", getprogname());
+ vfprintf(stderr, expandm(buf, sizeof(buf), fmt), ap);
+ fprintf(stderr, "\n");
+}
+
+void
+dlog(int level, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdlog(level, fmt, ap);
+ va_end(ap);
+}
+
+const char *
+fmttime(char *b, size_t l, time_t t)
+{
+ struct tm tm;
+ if (localtime_r(&t, &tm) == NULL)
+ snprintf(b, l, "*%jd*", (intmax_t)t);
+ else
+ strftime(b, l, "%Y/%m/%d %H:%M:%S", &tm);
+ return b;
+}
+
+const char *
+fmtydhms(char *b, size_t l, time_t t)
+{
+ time_t s, m, h, d, y;
+ int z;
+ size_t o;
+
+ s = t % 60;
+ t /= 60;
+ m = t % 60;
+ t /= 60;
+ h = t % 60;
+ t /= 24;
+ d = t % 24;
+ t /= 356;
+ y = t;
+
+ z = 0;
+ o = 0;
+#define APPEND(a) \
+ if (a) { \
+ z = snprintf(b + o, l - o, "%jd%s", (intmax_t)a, __STRING(a)); \
+ if (z == -1) \
+ return b; \
+ o += (size_t)z; \
+ if (o >= l) \
+ return b; \
+ }
+ APPEND(y)
+ APPEND(d)
+ APPEND(h)
+ APPEND(m)
+ APPEND(s)
+ return b;
+}
+
+ssize_t
+blhexdump(char *buf, size_t len, const char *str, const void *b, size_t l)
+{
+ size_t z, cz;
+ int r;
+ const unsigned char *p = b;
+ const unsigned char *e = p + l;
+
+ r = snprintf(buf, len, "%s: ", str);
+ if (r == -1)
+ return -1;
+ if ((cz = z = (size_t)r) >= len)
+ cz = len;
+
+ while (p < e) {
+ r = snprintf(buf + cz, len - cz, "%.2x", *p++);
+ if (r == -1)
+ return -1;
+ if ((cz = (z += (size_t)r)) >= len)
+ cz = len;
+ }
+ return (ssize_t)z;
+}
diff --git a/bin/support.h b/bin/support.h
new file mode 100644
index 0000000000000..899649ce8319e
--- /dev/null
+++ b/bin/support.h
@@ -0,0 +1,44 @@
+/* $NetBSD: support.h,v 1.7 2016/04/04 15:52:56 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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.
+ */
+#ifndef _SUPPORT_H
+#define _SUPPORT_H
+
+__BEGIN_DECLS
+const char *fmttime(char *, size_t, time_t);
+const char *fmtydhms(char *, size_t, time_t);
+void vdlog(int, const char *, va_list)
+ __attribute__((__format__(__printf__, 2, 0)));
+void dlog(int, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3)));
+ssize_t blhexdump(char *, size_t, const char *, const void *, size_t);
+__END_DECLS
+
+#endif /* _SUPPORT_H */