Index: stable/11/contrib/blacklist/README =================================================================== --- stable/11/contrib/blacklist/README (revision 318238) +++ stable/11/contrib/blacklist/README (revision 318239) @@ -1,103 +1,113 @@ -# $NetBSD: README,v 1.7 2015/01/26 00:34:50 christos Exp $ +# $NetBSD: README,v 1.8 2017/04/13 17:59:34 christos Exp $ This package contains library that can be used by network daemons to communicate with a packet filter via a daemon to enforce opening and closing ports dynamically based on policy. The interface to the packet filter is in libexec/blacklistd-helper (this is currently designed for npf) and the configuration file (inspired from inetd.conf) is in etc/blacklistd.conf. On NetBSD you can find an example npf.conf and blacklistd.conf in /usr/share/examples/blacklistd; you need to adjust the interface in npf.conf and copy both files to /etc; then you just enable blacklistd=YES in /etc/rc.conf, start it up, and you are all set. There is also a startup file in etc/rc.d/blacklistd Patches to various daemons to add blacklisting capabilitiers are in the "diff" directory: - OpenSSH: diff/ssh.diff [tcp socket example] - Bind: diff/named.diff [both tcp and udp] - ftpd: diff/ftpd.diff [tcp] These patches have been applied to NetBSD-current. The network daemon (for example sshd) communicates to blacklistd, via a unix socket like syslog. The library calls are simple and everything is handled by the library. In the simplest form the only thing the daemon needs to do is to call: blacklist(action, acceptedfd, message); Where: action = 0 -> successful login clear blacklist state 1 -> failed login, add to the failed count acceptedfd -> the file descriptor where the server is connected to the remote client. It is used to determine the listening socket, and the remote address. This allows any program to contact the blacklist daemon, since the verification if the program has access to the listening socket is done by virtue that the port number is retrieved from the kernel. message -> an optional string that is used in debugging logs. Unfortunately there is no way to get information about the "peer" from a udp socket, because there is no connection and that information is kept with the server. In that case the daemon can provide the peer information to blacklistd via: blacklist_sa(action, acceptedfd, sockaddr, sockaddr_len, message); The configuration file contains entries of the form: # Blacklist rule # host/Port type protocol owner name nfail disable 192.168.1.1:ssh stream tcp * -int 10 1m 8.8.8.8:ssh stream tcp * -ext 6 60m ssh stream tcp6 * * 6 60m http stream tcp * * 6 60m Here note that owner is * because the connection is done from the child ssh socket which runs with user privs. We treat ipv4 connections differently by maintaining two different rules one for the external interface and one from the internal We also register for both tcp and tcp6 since those are different listening sockets and addresses; we don't bother with ipv6 and separate rules. We use nfail = 6, because ssh allows 3 password attempts per connection, and this will let us have 2 connections before blocking. Finally we block for an hour; we could block forever too by specifying * in the duration column. blacklistd and the library use syslog(3) to report errors. The blacklist filter state is persisted automatically in /var/db/blacklistd.db so that if the daemon is restarted, it remembers what connections is currently handling. To start from a fresh state (if you restart npf too for example), you can use -f. To watch the daemon at work, you can use -d. The current control file is designed for npf, and it uses the dynamic rule feature. You need to create a dynamic rule in your /etc/npf.conf on the group referring to the interface you want to block called blacklistd as follows: ext_if=bge0 int_if=sk0 group "external" on $ext_if { ... ruleset "blacklistd-ext" ruleset "blacklistd" ... } group "internal" on $int_if { ... ruleset "blacklistd-int" ... } + +You can use 'blacklistctl dump -a' to list all the current entries +in the database; the ones that have nfail / where urrent +>= otal, should have an id assosiated with them; this means that +there is a packet filter rule added for that entry. For npf, you +can examine the packet filter dynamic rule entries using 'npfctl +rule list'. The number of current entries can exceed +the total. This happens because entering packet filter rules is +asynchronous; there could be other connection before the rule +becomes activated. Enjoy, christos Index: stable/11/contrib/blacklist/bin/blacklistctl.8 =================================================================== --- stable/11/contrib/blacklist/bin/blacklistctl.8 (revision 318238) +++ stable/11/contrib/blacklist/bin/blacklistctl.8 (revision 318239) @@ -1,85 +1,86 @@ -.\" $NetBSD: blacklistctl.8,v 1.7 2015/04/30 06:20:43 riz Exp $ +.\" $NetBSD: blacklistctl.8,v 1.9 2016/06/08 12:48:37 wiz 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 7, 2016 .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 first appeared in .Nx 7 . -.Fx support for +.Fx +support for .Nm was implemented in .Fx 11 . .Sh AUTHORS .An Christos Zoulas Index: stable/11/contrib/blacklist/bin/blacklistctl.c =================================================================== --- stable/11/contrib/blacklist/bin/blacklistctl.c (revision 318238) +++ stable/11/contrib/blacklist/bin/blacklistctl.c (revision 318239) @@ -1,151 +1,151 @@ -/* $NetBSD: blacklistctl.c,v 1.20 2016/04/04 15:52:56 christos Exp $ */ +/* $NetBSD: blacklistctl.c,v 1.21 2016/11/02 03:15:07 jnemeth 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 -__RCSID("$NetBSD: blacklistctl.c,v 1.20 2016/04/04 15:52:56 christos Exp $"); +__RCSID("$NetBSD: blacklistctl.c,v 1.21 2016/11/02 03:15:07 jnemeth Exp $"); #include #include #ifdef HAVE_LIBUTIL_H #include #endif #ifdef HAVE_UTIL_H #include #endif #include #include #include #include #include #include #include #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; break; case 'D': dbname = optarg; 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; } Index: stable/11/contrib/blacklist/bin/blacklistd.c =================================================================== --- stable/11/contrib/blacklist/bin/blacklistd.c (revision 318238) +++ stable/11/contrib/blacklist/bin/blacklistd.c (revision 318239) @@ -1,549 +1,565 @@ -/* $NetBSD: blacklistd.c,v 1.35 2016/09/26 19:43:43 christos Exp $ */ +/* $NetBSD: blacklistd.c,v 1.37 2017/02/18 00:26:16 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 -__RCSID("$NetBSD: blacklistd.c,v 1.35 2016/09/26 19:43:43 christos Exp $"); +__RCSID("$NetBSD: blacklistd.c,v 1.37 2017/02/18 00:26:16 christos Exp $"); #include #include #include #ifdef HAVE_LIBUTIL_H #include #endif #ifdef HAVE_UTIL_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ] [-R ] " "[-P ] [-C ] [-D ] " "[-s ] [-t ]\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: initial db state 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.count = 0; dbi.last = 0; break; default: (*lfun)(LOG_ERR, "unknown message %d", bi->bi_type); } state_put(state, &c, &dbi); out: close(bi->bi_fd); if (debug) { char b1[128], b2[128]; (*lfun)(LOG_DEBUG, "%s: final db state 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)); } } 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, ret; - const char *spath, *blsock; + const char *spath, **blsock; + size_t nblsock, maxblsock; setprogname(argv[0]); spath = NULL; - blsock = _PATH_BLSOCK; + blsock = NULL; + maxblsock = nblsock = 0; 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; + if (nblsock >= maxblsock) { + maxblsock += 10; + void *p = realloc(blsock, + sizeof(*blsock) * maxblsock); + if (p == NULL) + err(EXIT_FAILURE, + "Can't allocate memory for %zu sockets", + maxblsock); + blsock = p; + } + blsock[nblsock++] = 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; } 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 { + for (size_t i = 0; i < nblsock; i++) + addfd(&pfd, &bl, &nfd, &maxfd, blsock[i]); + free(blsock); + + if (spath) { 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); } + if (nfd == 0) + addfd(&pfd, &bl, &nfd, &maxfd, _PATH_BLSOCK); state = state_open(dbfile, flags, 0600); if (state == NULL) state = state_open(dbfile, flags | O_CREAT, 0600); if (state == NULL) return EXIT_FAILURE; if (restore) rules_restore(); 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); } ret = poll(pfd, (nfds_t)nfd, tout); if (debug) (*lfun)(LOG_DEBUG, "received %d from poll()", ret); switch (ret) { 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; } Index: stable/11/contrib/blacklist/bin/blacklistd.conf.5 =================================================================== --- stable/11/contrib/blacklist/bin/blacklistd.conf.5 (revision 318238) +++ stable/11/contrib/blacklist/bin/blacklistd.conf.5 (revision 318239) @@ -1,226 +1,227 @@ -.\" $NetBSD: blacklistd.conf.5,v 1.3 2015/04/30 06:20:43 riz Exp $ +.\" $NetBSD: blacklistd.conf.5,v 1.5 2016/06/08 12:48:37 wiz 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 7, 2016 .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 [
|][/][:] .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 first appeared in .Nx 7 . -.Fx support for +.Fx +support for .Nm was implemented in .Fx 11 . .Sh AUTHORS .An Christos Zoulas Index: stable/11/contrib/blacklist/etc/rc.d/blacklistd =================================================================== --- stable/11/contrib/blacklist/etc/rc.d/blacklistd (revision 318238) +++ stable/11/contrib/blacklist/etc/rc.d/blacklistd (revision 318239) @@ -1,57 +1,57 @@ #!/bin/sh # -# $NetBSD: blacklistd,v 1.1 2015/01/22 17:49:41 christos Exp $ +# $NetBSD: blacklistd,v 1.2 2016/10/17 22:47:16 christos Exp $ # # PROVIDE: blacklistd # REQUIRE: npf # BEFORE: SERVERS $_rc_subr_loaded . /etc/rc.subr name="blacklistd" rcvar=$name command="/sbin/${name}" pidfile="/var/run/${name}.pid" required_files="/etc/${name}.conf" start_precmd="${name}_precmd" extra_commands="reload" _sockfile="/var/run/${name}.sockets" -_sockname="blsock" +_sockname="blacklistd.sock" blacklistd_precmd() { # Create default list of blacklistd sockets to watch # ( umask 022 ; > $_sockfile ) # Find /etc/rc.d scripts with "chrootdir" rcorder(8) keyword, # and if $${app}_chrootdir is a directory, add appropriate # blacklistd socket to list of sockets to watch. # for _lr in $(rcorder -k chrootdir /etc/rc.d/*); do ( _l=${_lr##*/} load_rc_config ${_l} eval _ldir=\$${_l}_chrootdir if checkyesno $_l && [ -n "$_ldir" ]; then echo "${_ldir}/var/run/${_sockname}" >> $_sockfile fi ) done # If other sockets have been provided, change run_rc_command()'s # internal copy of $blacklistd_flags to force use of specific # blacklistd sockets. # if [ -s $_sockfile ]; then echo "/var/run/${_sockname}" >> $_sockfile rc_flags="-P $_sockfile $rc_flags" fi return 0 } load_rc_config $name run_rc_command "$1" Index: stable/11/contrib/blacklist/lib/bl.c =================================================================== --- stable/11/contrib/blacklist/lib/bl.c (revision 318238) +++ stable/11/contrib/blacklist/lib/bl.c (revision 318239) @@ -1,527 +1,527 @@ -/* $NetBSD: bl.c,v 1.27 2015/12/30 16:42:48 christos Exp $ */ +/* $NetBSD: bl.c,v 1.28 2016/07/29 17:13:09 christos Exp $ */ /*- * Copyright (c) 2014 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 -__RCSID("$NetBSD: bl.c,v 1.27 2015/12/30 16:42:48 christos Exp $"); +__RCSID("$NetBSD: bl.c,v 1.28 2016/07/29 17:13:09 christos Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _REENTRANT #include #endif #include "bl.h" typedef struct { uint32_t bl_len; uint32_t bl_version; uint32_t bl_type; uint32_t bl_salen; struct sockaddr_storage bl_ss; char bl_data[]; } bl_message_t; struct blacklist { #ifdef _REENTRANT pthread_mutex_t b_mutex; # define BL_INIT(b) pthread_mutex_init(&b->b_mutex, NULL) # define BL_LOCK(b) pthread_mutex_lock(&b->b_mutex) # define BL_UNLOCK(b) pthread_mutex_unlock(&b->b_mutex) #else # define BL_INIT(b) do {} while(/*CONSTCOND*/0) # define BL_LOCK(b) BL_INIT(b) # define BL_UNLOCK(b) BL_INIT(b) #endif int b_fd; int b_connected; struct sockaddr_un b_sun; void (*b_fun)(int, const char *, va_list); bl_info_t b_info; }; #define BL_VERSION 1 bool bl_isconnected(bl_t b) { return b->b_connected == 0; } int bl_getfd(bl_t b) { return b->b_fd; } static void bl_reset(bl_t b, bool locked) { int serrno = errno; if (!locked) BL_LOCK(b); close(b->b_fd); errno = serrno; b->b_fd = -1; b->b_connected = -1; if (!locked) BL_UNLOCK(b); } static void bl_log(void (*fun)(int, const char *, va_list), int level, const char *fmt, ...) { va_list ap; int serrno = errno; va_start(ap, fmt); (*fun)(level, fmt, ap); va_end(ap); errno = serrno; } static int bl_init(bl_t b, bool srv) { static int one = 1; /* AF_UNIX address of local logger */ mode_t om; int rv, serrno; struct sockaddr_un *sun = &b->b_sun; #ifndef SOCK_NONBLOCK #define SOCK_NONBLOCK 0 #endif #ifndef SOCK_CLOEXEC #define SOCK_CLOEXEC 0 #endif #ifndef SOCK_NOSIGPIPE #define SOCK_NOSIGPIPE 0 #endif BL_LOCK(b); if (b->b_fd == -1) { b->b_fd = socket(PF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK|SOCK_NOSIGPIPE, 0); if (b->b_fd == -1) { bl_log(b->b_fun, LOG_ERR, "%s: socket failed (%s)", __func__, strerror(errno)); BL_UNLOCK(b); return -1; } #if SOCK_CLOEXEC == 0 fcntl(b->b_fd, F_SETFD, FD_CLOEXEC); #endif #if SOCK_NONBLOCK == 0 fcntl(b->b_fd, F_SETFL, fcntl(b->b_fd, F_GETFL) | O_NONBLOCK); #endif #if SOCK_NOSIGPIPE == 0 #ifdef SO_NOSIGPIPE int o = 1; setsockopt(b->b_fd, SOL_SOCKET, SO_NOSIGPIPE, &o, sizeof(o)); #else signal(SIGPIPE, SIG_IGN); #endif #endif } if (bl_isconnected(b)) { BL_UNLOCK(b); return 0; } /* * We try to connect anyway even when we are a server to verify * that no other server is listening to the socket. If we succeed * to connect and we are a server, someone else owns it. */ rv = connect(b->b_fd, (const void *)sun, (socklen_t)sizeof(*sun)); if (rv == 0) { if (srv) { bl_log(b->b_fun, LOG_ERR, "%s: another daemon is handling `%s'", __func__, sun->sun_path); goto out; } } else { if (!srv) { /* * If the daemon is not running, we just try a * connect, so leave the socket alone until it does * and only log once. */ if (b->b_connected != 1) { bl_log(b->b_fun, LOG_DEBUG, "%s: connect failed for `%s' (%s)", __func__, sun->sun_path, strerror(errno)); b->b_connected = 1; } BL_UNLOCK(b); return -1; } bl_log(b->b_fun, LOG_DEBUG, "Connected to blacklist server", __func__); } if (srv) { (void)unlink(sun->sun_path); om = umask(0); rv = bind(b->b_fd, (const void *)sun, (socklen_t)sizeof(*sun)); serrno = errno; (void)umask(om); errno = serrno; if (rv == -1) { bl_log(b->b_fun, LOG_ERR, "%s: bind failed for `%s' (%s)", __func__, sun->sun_path, strerror(errno)); goto out; } } b->b_connected = 0; #define GOT_FD 1 #if defined(LOCAL_CREDS) #define CRED_LEVEL 0 #define CRED_NAME LOCAL_CREDS #define CRED_SC_UID sc_euid #define CRED_SC_GID sc_egid #define CRED_MESSAGE SCM_CREDS #define CRED_SIZE SOCKCREDSIZE(NGROUPS_MAX) #define CRED_TYPE struct sockcred #define GOT_CRED 2 #elif defined(SO_PASSCRED) #define CRED_LEVEL SOL_SOCKET #define CRED_NAME SO_PASSCRED #define CRED_SC_UID uid #define CRED_SC_GID gid #define CRED_MESSAGE SCM_CREDENTIALS #define CRED_SIZE sizeof(struct ucred) #define CRED_TYPE struct ucred #define GOT_CRED 2 #else #define GOT_CRED 0 /* * getpeereid() and LOCAL_PEERCRED don't help here * because we are not a stream socket! */ #define CRED_SIZE 0 #define CRED_TYPE void * __unused #endif #ifdef CRED_LEVEL if (setsockopt(b->b_fd, CRED_LEVEL, CRED_NAME, &one, (socklen_t)sizeof(one)) == -1) { bl_log(b->b_fun, LOG_ERR, "%s: setsockopt %s " "failed (%s)", __func__, __STRING(CRED_NAME), strerror(errno)); goto out; } #endif BL_UNLOCK(b); return 0; out: bl_reset(b, true); BL_UNLOCK(b); return -1; } bl_t bl_create(bool srv, const char *path, void (*fun)(int, const char *, va_list)) { bl_t b = calloc(1, sizeof(*b)); if (b == NULL) goto out; b->b_fun = fun == NULL ? vsyslog : fun; b->b_fd = -1; b->b_connected = -1; BL_INIT(b); memset(&b->b_sun, 0, sizeof(b->b_sun)); b->b_sun.sun_family = AF_LOCAL; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN b->b_sun.sun_len = sizeof(b->b_sun); #endif strlcpy(b->b_sun.sun_path, path ? path : _PATH_BLSOCK, sizeof(b->b_sun.sun_path)); bl_init(b, srv); return b; out: free(b); bl_log(fun, LOG_ERR, "%s: malloc failed (%s)", __func__, strerror(errno)); return NULL; } void bl_destroy(bl_t b) { bl_reset(b, false); free(b); } static int bl_getsock(bl_t b, struct sockaddr_storage *ss, const struct sockaddr *sa, socklen_t slen, const char *ctx) { uint8_t family; memset(ss, 0, sizeof(*ss)); switch (slen) { case 0: return 0; case sizeof(struct sockaddr_in): family = AF_INET; break; case sizeof(struct sockaddr_in6): family = AF_INET6; break; default: bl_log(b->b_fun, LOG_ERR, "%s: invalid socket len %u (%s)", __func__, (unsigned)slen, ctx); errno = EINVAL; return -1; } memcpy(ss, sa, slen); if (ss->ss_family != family) { bl_log(b->b_fun, LOG_INFO, "%s: correcting socket family %d to %d (%s)", __func__, ss->ss_family, family, ctx); ss->ss_family = family; } #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN if (ss->ss_len != slen) { bl_log(b->b_fun, LOG_INFO, "%s: correcting socket len %u to %u (%s)", __func__, ss->ss_len, (unsigned)slen, ctx); ss->ss_len = (uint8_t)slen; } #endif return 0; } int bl_send(bl_t b, bl_type_t e, int pfd, const struct sockaddr *sa, socklen_t slen, const char *ctx) { struct msghdr msg; struct iovec iov; union { char ctrl[CMSG_SPACE(sizeof(int))]; uint32_t fd; } ua; struct cmsghdr *cmsg; union { bl_message_t bl; char buf[512]; } ub; size_t ctxlen, tried; #define NTRIES 5 ctxlen = strlen(ctx); if (ctxlen > 128) ctxlen = 128; iov.iov_base = ub.buf; iov.iov_len = sizeof(bl_message_t) + ctxlen; ub.bl.bl_len = (uint32_t)iov.iov_len; ub.bl.bl_version = BL_VERSION; ub.bl.bl_type = (uint32_t)e; if (bl_getsock(b, &ub.bl.bl_ss, sa, slen, ctx) == -1) return -1; ub.bl.bl_salen = slen; memcpy(ub.bl.bl_data, ctx, ctxlen); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = ua.ctrl; msg.msg_controllen = sizeof(ua.ctrl); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(cmsg), &pfd, sizeof(pfd)); tried = 0; again: if (bl_init(b, false) == -1) return -1; if ((sendmsg(b->b_fd, &msg, 0) == -1) && tried++ < NTRIES) { bl_reset(b, false); goto again; } return tried >= NTRIES ? -1 : 0; } bl_info_t * bl_recv(bl_t b) { struct msghdr msg; struct iovec iov; union { char ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(CRED_SIZE)]; uint32_t fd; CRED_TYPE sc; } ua; struct cmsghdr *cmsg; CRED_TYPE *sc; union { bl_message_t bl; char buf[512]; } ub; int got; ssize_t rlen; bl_info_t *bi = &b->b_info; got = 0; memset(bi, 0, sizeof(*bi)); iov.iov_base = ub.buf; iov.iov_len = sizeof(ub); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = ua.ctrl; msg.msg_controllen = sizeof(ua.ctrl) + 100; rlen = recvmsg(b->b_fd, &msg, 0); if (rlen == -1) { bl_log(b->b_fun, LOG_ERR, "%s: recvmsg failed (%s)", __func__, strerror(errno)); return NULL; } for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET) { bl_log(b->b_fun, LOG_ERR, "%s: unexpected cmsg_level %d", __func__, cmsg->cmsg_level); continue; } switch (cmsg->cmsg_type) { case SCM_RIGHTS: if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) { bl_log(b->b_fun, LOG_ERR, "%s: unexpected cmsg_len %d != %zu", __func__, cmsg->cmsg_len, CMSG_LEN(2 * sizeof(int))); continue; } memcpy(&bi->bi_fd, CMSG_DATA(cmsg), sizeof(bi->bi_fd)); got |= GOT_FD; break; #ifdef CRED_MESSAGE case CRED_MESSAGE: sc = (void *)CMSG_DATA(cmsg); bi->bi_uid = sc->CRED_SC_UID; bi->bi_gid = sc->CRED_SC_GID; got |= GOT_CRED; break; #endif default: bl_log(b->b_fun, LOG_ERR, "%s: unexpected cmsg_type %d", __func__, cmsg->cmsg_type); continue; } } if (got != (GOT_CRED|GOT_FD)) { bl_log(b->b_fun, LOG_ERR, "message missing %s %s", #if GOT_CRED != 0 (got & GOT_CRED) == 0 ? "cred" : #endif "", (got & GOT_FD) == 0 ? "fd" : ""); return NULL; } if ((size_t)rlen <= sizeof(ub.bl)) { bl_log(b->b_fun, LOG_ERR, "message too short %zd", rlen); return NULL; } if (ub.bl.bl_version != BL_VERSION) { bl_log(b->b_fun, LOG_ERR, "bad version %d", ub.bl.bl_version); return NULL; } bi->bi_type = ub.bl.bl_type; bi->bi_slen = ub.bl.bl_salen; bi->bi_ss = ub.bl.bl_ss; #ifndef CRED_MESSAGE bi->bi_uid = -1; bi->bi_gid = -1; #endif strlcpy(bi->bi_msg, ub.bl.bl_data, MIN(sizeof(bi->bi_msg), ((size_t)rlen - sizeof(ub.bl) + 1))); return bi; } Index: stable/11/contrib/blacklist/lib/libblacklist.3 =================================================================== --- stable/11/contrib/blacklist/lib/libblacklist.3 (revision 318238) +++ stable/11/contrib/blacklist/lib/libblacklist.3 (revision 318239) @@ -1,125 +1,132 @@ -.\" $NetBSD: libblacklist.3,v 1.3 2015/01/25 23:09:28 wiz Exp $ +.\" $NetBSD: libblacklist.3,v 1.7 2017/02/04 23:33:56 wiz 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 January 22, 2015 .Dt LIBBLACKLIST 3 .Os .Sh NAME .Nm blacklist_open , .Nm blacklist_close , .Nm blacklist_r , .Nm blacklist , .Nm blacklist_sa -.Nm blacklist_sa_r , +.Nm blacklist_sa_r .Nd Blacklistd notification library .Sh LIBRARY .Lb libblacklist .Sh SYNOPSIS .In blacklist.h .Ft struct blacklist * .Fn blacklist_open "void" .Ft void .Fn blacklist_close "struct blacklist *cookie" .Ft int .Fn blacklist "int action" "int fd" "const char *msg" .Ft int .Fn blacklist_r "struct blacklist *cookie" "int action" "int fd" "const char *msg" .Ft int .Fn blacklist_sa "int action" "int fd" "const struct sockaddr *sa" "socklen_t salen" "const char *msg" .Ft int .Fn blacklist_sa_r "struct blacklist *cookie" "int action" "int fd" "const struct sockaddr *sa" "socklen_t salen" "const char *msg" .Sh DESCRIPTION These functions can be used by daemons to notify .Xr blacklistd 8 about successful and failed remote connections so that blacklistd can block or release port access to prevent Denial of Service attacks. .Pp The function .Fn blacklist_open -creates a the necessary state to communicate with +creates the necessary state to communicate with .Xr blacklistd 8 and returns a pointer to it, or .Dv NULL on failure. .Pp The .Fn blacklist_close function frees all memory and resources used. .Pp The .Fn blacklist function sends a message to .Xr blacklistd 8 , with an .Ar action argument specifying .Dv 1 for a failed connection or .Dv 0 for a successful connection, a file descriptor .Ar fd specifying the accepted file descriptor connected to the client, and an optional message in the .Ar msg argument. .Pp The .Fn blacklist_r function is more efficient because it keeps the blacklist state around. .Pp The .Fn blacklist_sa and .Fn blacklist_sa_r functions can be used with unconnected sockets, where .Xr getpeername 2 will not work, the server will pass the peer name in the message. .Pp All functions log errors to .Xr syslogd 8 . .Sh RETURN VALUES The function -.Fn bl_open +.Fn blacklist_open returns a cookie on success and .Dv NULL -on failure setting errno to an appropriate value. +on failure setting +.Dv errno +to an appropriate value. .Pp -The -.Fn bl_send -function returns +The functions +.Fn blacklist , +.Fn blacklist_sa , +and +.Fn blacklist_sa_r +return .Dv 0 on success and -.Dv -1 -on failure setting errno to an appropriate value. +.Dv \-1 +on failure setting +.Dv errno +to an appropriate value. .Sh SEE ALSO .Xr blacklistd.conf 5 , .Xr blacklistd 8 .Sh AUTHORS .An Christos Zoulas Index: stable/11/contrib/blacklist/libexec/blacklistd-helper =================================================================== --- stable/11/contrib/blacklist/libexec/blacklistd-helper (revision 318238) +++ stable/11/contrib/blacklist/libexec/blacklistd-helper (revision 318239) @@ -1,129 +1,129 @@ #!/bin/sh #echo "run $@" 1>&2 #set -x # $1 command # $2 rulename # $3 protocol # $4 address # $5 mask # $6 port # $7 id pf= if [ -f "/etc/ipfw-blacklist.rc" ]; then pf="ipfw" . /etc/ipfw-blacklist.rc ipfw_offset=${ipfw_offset:-2000} fi if [ -z "$pf" ]; then for f in npf pf ipf; do if [ -f "/etc/$f.conf" ]; then - pf="$f" - break + pf="$f" + break fi done fi if [ -z "$pf" ]; then echo "$0: Unsupported packet filter" 1>&2 exit 1 fi if [ -n "$3" ]; then proto="proto $3" fi if [ -n "$6" ]; then port="port $6" fi addr="$4" mask="$5" case "$4" in ::ffff:*.*.*.*) if [ "$5" = 128 ]; then mask=32 addr=${4#::ffff:} fi;; esac case "$1" in add) case "$pf" in ipf) /sbin/ipfstat -io | /sbin/ipf -I -f - >/dev/null 2>&1 echo block in quick $proto from $addr/$mask to \ any port=$6 head port$6 | \ /sbin/ipf -I -f - -s >/dev/null 2>&1 && echo OK ;; ipfw) # use $ipfw_offset+$port for rule number rule=$(($ipfw_offset + $6)) tname="port$6" /sbin/ipfw table $tname create type addr 2>/dev/null /sbin/ipfw -q table $tname add "$addr/$mask" # if rule number $rule does not already exist, create it /sbin/ipfw show $rule >/dev/null 2>&1 || \ /sbin/ipfw add $rule drop $3 from \ table"("$tname")" to any dst-port $6 >/dev/null && \ echo OK ;; npf) /sbin/npfctl rule "$2" add block in final $proto from \ "$addr/$mask" to any $port ;; pf) # if the filtering rule does not exist, create it /sbin/pfctl -a "$2/$6" -sr 2>/dev/null | \ grep -q "" || \ echo "block in quick $proto from to any $port" | \ /sbin/pfctl -a "$2/$6" -f - # insert $ip/$mask into per-protocol/port anchored table /sbin/pfctl -a "$2/$6" -t "port$6" -T add "$addr/$mask" && \ echo OK ;; esac ;; rem) case "$pf" in ipf) /sbin/ipfstat -io | /sbin/ipf -I -f - >/dev/null 2>&1 echo block in quick $proto from $addr/$mask to \ any port=$6 head port$6 | \ /sbin/ipf -I -r -f - -s >/dev/null 2>&1 && echo OK ;; ipfw) /sbin/ipfw table "port$6" delete "$addr/$mask" 2>/dev/null && \ echo OK ;; npf) /sbin/npfctl rule "$2" rem-id "$7" ;; pf) /sbin/pfctl -a "$2/$6" -t "port$6" -T delete "$addr/$mask" && \ echo OK ;; esac ;; flush) case "$pf" in ipf) /sbin/ipf -Z -I -Fi -s > /dev/null && echo OK ;; ipfw) /sbin/ipfw table "port$6" flush 2>/dev/null && echo OK ;; npf) /sbin/npfctl rule "$2" flush ;; pf) /sbin/pfctl -a "$2/$6" -t "port$6" -T flush && echo OK ;; esac ;; *) echo "$0: Unknown command '$1'" 1>&2 exit 1 ;; esac Index: stable/11/contrib/blacklist/port/Makefile.am =================================================================== --- stable/11/contrib/blacklist/port/Makefile.am (revision 318238) +++ stable/11/contrib/blacklist/port/Makefile.am (revision 318239) @@ -1,25 +1,25 @@ # ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libblacklist.la -include_HEADERS = blacklist.h +include_HEADERS = ../include/blacklist.h bin_PROGRAMS = blacklistd blacklistctl srvtest cltest -VPATH = ../bin:../lib:../test +VPATH = ../bin:../lib:../test:../include AM_CPPFLAGS = -I../include -DDOT="." AM_CFLAGS = @WARNINGS@ libblacklist_la_SOURCES = bl.c blacklist.c libblacklist_la_LDFLAGS = -no-undefined -version-info 0:0:0 libblacklist_la_LIBADD = $(LTLIBOBJS) SRCS = internal.c support.c run.c conf.c state.c blacklistd_SOURCES = blacklistd.c ${SRCS} blacklistd_LDADD = libblacklist.la blacklistctl_SOURCES = blacklistctl.c ${SRCS} blacklistctl_LDADD = libblacklist.la srvtest_SOURCES = srvtest.c ${SRCS} srvtest_LDADD = libblacklist.la cltest_SOURCES = cltest.c ${SRCS} cltest_LDADD = libblacklist.la Index: stable/11/contrib/blacklist/port/sockaddr_snprintf.c =================================================================== --- stable/11/contrib/blacklist/port/sockaddr_snprintf.c (revision 318238) +++ stable/11/contrib/blacklist/port/sockaddr_snprintf.c (revision 318239) @@ -1,383 +1,383 @@ -/* $NetBSD: sockaddr_snprintf.c,v 1.10 2016/04/05 12:28:57 christos Exp $ */ +/* $NetBSD: sockaddr_snprintf.c,v 1.11 2016/06/01 22:57:51 christos Exp $ */ /*- * Copyright (c) 2004 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 #if defined(LIBC_SCCS) && !defined(lint) -__RCSID("$NetBSD: sockaddr_snprintf.c,v 1.10 2016/04/05 12:28:57 christos Exp $"); +__RCSID("$NetBSD: sockaddr_snprintf.c,v 1.11 2016/06/01 22:57:51 christos Exp $"); #endif /* LIBC_SCCS and not lint */ #include #include #include #include #include #ifdef __linux__ #undef HAVE_NETATALK_AT_H #endif #ifdef HAVE_NETATALK_AT_H #include #endif #ifdef HAVE_NET_IF_DL_H #include #endif #include #include #include #include #ifdef HAVE_LIBUTIL_H #include #endif #ifdef HAVE_UTIL_H #include #endif #include #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN #define SLEN(a) (a)->a ## _len #else static socklen_t socklen(u_int af) { switch (af) { case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: return sizeof(struct sockaddr_in6); case AF_LOCAL: return sizeof(struct sockaddr_un); #ifdef HAVE_NET_IF_DL_H case AF_LINK: return sizeof(struct sockaddr_dl); #endif #ifdef HAVE_NETATALK_AT_H case AF_APPLETALK: return sizeof(struct sockaddr_at); #endif default: return sizeof(struct sockaddr_storage); } } #define SLEN(a) socklen((a)->a ## _family) #endif #ifdef HAVE_NETATALK_AT_H static int debug_at(char *str, size_t len, const struct sockaddr_at *sat) { return snprintf(str, len, "sat_len=%u, sat_family=%u, sat_port=%u, " "sat_addr.s_net=%u, sat_addr.s_node=%u, " "sat_range.r_netrange.nr_phase=%u, " "sat_range.r_netrange.nr_firstnet=%u, " "sat_range.r_netrange.nr_lastnet=%u", SLEN(sat), sat->sat_family, sat->sat_port, sat->sat_addr.s_net, sat->sat_addr.s_node, sat->sat_range.r_netrange.nr_phase, sat->sat_range.r_netrange.nr_firstnet, sat->sat_range.r_netrange.nr_lastnet); } #endif static int debug_in(char *str, size_t len, const struct sockaddr_in *sin) { return snprintf(str, len, "sin_len=%u, sin_family=%u, sin_port=%u, " "sin_addr.s_addr=%08x", SLEN(sin), sin->sin_family, sin->sin_port, sin->sin_addr.s_addr); } static int debug_in6(char *str, size_t len, const struct sockaddr_in6 *sin6) { const uint8_t *s = sin6->sin6_addr.s6_addr; return snprintf(str, len, "sin6_len=%u, sin6_family=%u, sin6_port=%u, " "sin6_flowinfo=%u, " "sin6_addr=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:" "%02x:%02x:%02x:%02x:%02x:%02x, sin6_scope_id=%u", SLEN(sin6), sin6->sin6_family, sin6->sin6_port, sin6->sin6_flowinfo, s[0x0], s[0x1], s[0x2], s[0x3], s[0x4], s[0x5], s[0x6], s[0x7], s[0x8], s[0x9], s[0xa], s[0xb], s[0xc], s[0xd], s[0xe], s[0xf], sin6->sin6_scope_id); } static int debug_un(char *str, size_t len, const struct sockaddr_un *sun) { return snprintf(str, len, "sun_len=%u, sun_family=%u, sun_path=%*s", SLEN(sun), sun->sun_family, (int)sizeof(sun->sun_path), sun->sun_path); } #ifdef HAVE_NET_IF_DL_H static int debug_dl(char *str, size_t len, const struct sockaddr_dl *sdl) { const uint8_t *s = (const void *)sdl->sdl_data; return snprintf(str, len, "sdl_len=%u, sdl_family=%u, sdl_index=%u, " "sdl_type=%u, sdl_nlen=%u, sdl_alen=%u, sdl_slen=%u, sdl_data=" "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", SLEN(sdl), sdl->sdl_family, sdl->sdl_index, sdl->sdl_type, sdl->sdl_nlen, sdl->sdl_alen, sdl->sdl_slen, s[0x0], s[0x1], s[0x2], s[0x3], s[0x4], s[0x5], s[0x6], s[0x7], s[0x8], s[0x9], s[0xa], s[0xb]); } #endif int sockaddr_snprintf(char * const sbuf, const size_t len, const char * const fmt, const struct sockaddr * const sa) { const void *a = NULL; char abuf[1024], nbuf[1024], *addr = NULL; char Abuf[1024], pbuf[32], *name = NULL, *port = NULL; char *ebuf = &sbuf[len - 1], *buf = sbuf; const char *ptr, *s; int p = -1; #ifdef HAVE_NETATALK_AT_H const struct sockaddr_at *sat = NULL; #endif const struct sockaddr_in *sin4 = NULL; const struct sockaddr_in6 *sin6 = NULL; const struct sockaddr_un *sun = NULL; #ifdef HAVE_NET_IF_DL_H const struct sockaddr_dl *sdl = NULL; char *w = NULL; #endif int na = 1; #define ADDC(c) do { if (buf < ebuf) *buf++ = c; else buf++; } \ while (/*CONSTCOND*/0) #define ADDS(p) do { for (s = p; *s; s++) ADDC(*s); } \ while (/*CONSTCOND*/0) #define ADDNA() do { if (na) ADDS("N/A"); } \ while (/*CONSTCOND*/0) switch (sa->sa_family) { case AF_UNSPEC: goto done; #ifdef HAVE_NETATALK_AT_H case AF_APPLETALK: sat = ((const struct sockaddr_at *)(const void *)sa); p = ntohs(sat->sat_port); (void)snprintf(addr = abuf, sizeof(abuf), "%u.%u", ntohs(sat->sat_addr.s_net), sat->sat_addr.s_node); (void)snprintf(port = pbuf, sizeof(pbuf), "%d", p); break; #endif case AF_LOCAL: sun = ((const struct sockaddr_un *)(const void *)sa); (void)strlcpy(addr = abuf, sun->sun_path, sizeof(abuf)); break; case AF_INET: sin4 = ((const struct sockaddr_in *)(const void *)sa); p = ntohs(sin4->sin_port); a = &sin4->sin_addr; break; case AF_INET6: sin6 = ((const struct sockaddr_in6 *)(const void *)sa); p = ntohs(sin6->sin6_port); a = &sin6->sin6_addr; break; #ifdef HAVE_NET_IF_DL_H case AF_LINK: sdl = ((const struct sockaddr_dl *)(const void *)sa); (void)strlcpy(addr = abuf, link_ntoa(sdl), sizeof(abuf)); - if ((w = strchr(addr, ':')) != 0) { + if ((w = strchr(addr, ':')) != NULL) { *w++ = '\0'; addr = w; } break; #endif default: errno = EAFNOSUPPORT; return -1; } if (addr == abuf) name = addr; if (a && getnameinfo(sa, (socklen_t)SLEN(sa), addr = abuf, (unsigned int)sizeof(abuf), NULL, 0, NI_NUMERICHOST|NI_NUMERICSERV) != 0) return -1; for (ptr = fmt; *ptr; ptr++) { if (*ptr != '%') { ADDC(*ptr); continue; } next_char: switch (*++ptr) { case '?': na = 0; goto next_char; case 'a': ADDS(addr); break; case 'p': if (p != -1) { (void)snprintf(nbuf, sizeof(nbuf), "%d", p); ADDS(nbuf); } else ADDNA(); break; case 'f': (void)snprintf(nbuf, sizeof(nbuf), "%d", sa->sa_family); ADDS(nbuf); break; case 'l': (void)snprintf(nbuf, sizeof(nbuf), "%d", SLEN(sa)); ADDS(nbuf); break; case 'A': if (name) ADDS(name); else if (!a) ADDNA(); else { getnameinfo(sa, (socklen_t)SLEN(sa), name = Abuf, (unsigned int)sizeof(nbuf), NULL, 0, 0); ADDS(name); } break; case 'P': if (port) ADDS(port); else if (p == -1) ADDNA(); else { getnameinfo(sa, (socklen_t)SLEN(sa), NULL, 0, port = pbuf, (unsigned int)sizeof(pbuf), 0); ADDS(port); } break; case 'I': #ifdef HAVE_NET_IF_DL_H if (sdl && addr != abuf) { ADDS(abuf); } else #endif { ADDNA(); } break; case 'F': if (sin6) { (void)snprintf(nbuf, sizeof(nbuf), "%d", sin6->sin6_flowinfo); ADDS(nbuf); break; } else { ADDNA(); } break; case 'S': if (sin6) { (void)snprintf(nbuf, sizeof(nbuf), "%d", sin6->sin6_scope_id); ADDS(nbuf); break; } else { ADDNA(); } break; case 'R': #ifdef HAVE_NETATALK_AT_H if (sat) { const struct netrange *n = &sat->sat_range.r_netrange; (void)snprintf(nbuf, sizeof(nbuf), "%d:[%d,%d]", n->nr_phase , n->nr_firstnet, n->nr_lastnet); ADDS(nbuf); } else #endif { ADDNA(); } break; case 'D': switch (sa->sa_family) { #ifdef HAVE_NETATALK_AT_H case AF_APPLETALK: debug_at(nbuf, sizeof(nbuf), sat); break; #endif case AF_LOCAL: debug_un(nbuf, sizeof(nbuf), sun); break; case AF_INET: debug_in(nbuf, sizeof(nbuf), sin4); break; case AF_INET6: debug_in6(nbuf, sizeof(nbuf), sin6); break; #ifdef HAVE_NET_IF_DL_H case AF_LINK: debug_dl(nbuf, sizeof(nbuf), sdl); break; #endif default: abort(); } ADDS(nbuf); break; default: ADDC('%'); if (na == 0) ADDC('?'); if (*ptr == '\0') goto done; /*FALLTHROUGH*/ case '%': ADDC(*ptr); break; } na = 1; } done: if (buf < ebuf) *buf = '\0'; else if (len != 0) sbuf[len - 1] = '\0'; return (int)(buf - sbuf); } Index: stable/11 =================================================================== --- stable/11 (revision 318238) +++ stable/11 (revision 318239) Property changes on: stable/11 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r317802