diff options
| author | Kurt Lidl <lidl@FreeBSD.org> | 2016-06-01 21:48:22 +0000 |
|---|---|---|
| committer | Kurt Lidl <lidl@FreeBSD.org> | 2016-06-01 21:48:22 +0000 |
| commit | df0cfa3b86180bc007aafedfac6685826e8de7ea (patch) | |
| tree | 87b2b18820b6cc1642b930ba61c9228f4f3bb48b /bin | |
Notes
Diffstat (limited to 'bin')
| -rw-r--r-- | bin/Makefile | 15 | ||||
| -rw-r--r-- | bin/blacklistctl.8 | 81 | ||||
| -rw-r--r-- | bin/blacklistctl.c | 151 | ||||
| -rw-r--r-- | bin/blacklistd.8 | 222 | ||||
| -rw-r--r-- | bin/blacklistd.c | 537 | ||||
| -rw-r--r-- | bin/blacklistd.conf.5 | 222 | ||||
| -rw-r--r-- | bin/conf.c | 1142 | ||||
| -rw-r--r-- | bin/conf.h | 65 | ||||
| -rw-r--r-- | bin/internal.c | 48 | ||||
| -rw-r--r-- | bin/internal.h | 57 | ||||
| -rw-r--r-- | bin/run.c | 156 | ||||
| -rw-r--r-- | bin/run.h | 41 | ||||
| -rw-r--r-- | bin/state.c | 233 | ||||
| -rw-r--r-- | bin/state.h | 62 | ||||
| -rw-r--r-- | bin/support.c | 157 | ||||
| -rw-r--r-- | bin/support.h | 44 |
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 */ |
