Hi,

Would this piece of code be useful to someone else than me?

It works with pf's divert-to to block some scanners. It's basically a
stripped-down spamd(8), that listens to every TCP connection that is
diverted to it, and sends the received data to the great bitbucket in
the sky, one byte per second. It also adds the client's IP to a pf table,
which can be used to block future connections from the same host.

I'm a sysadmin, not a programmer, so comments are welcome!

Sebastien Leclerc

--- /dev/null   Fri Jun 27 09:37:50 2014
+++ tarpitd.c   Wed Jun 18 15:29:48 2014
@@ -0,0 +1,622 @@
+/*
+ * Copyright (c) 2014 Sebastien Leclerc. All rights reserved.
+ * Copyright (c) 2002-2007 Bob Beck.     All rights reserved.
+ * Copyright (c) 2002 Theo de Raadt.     All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <sys/resource.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <netdb.h>
+
+struct con {
+       int fd;
+       int af;
+       struct sockaddr_storage ss;
+       void *ia;
+       char addr[32];
+       char caddr[32];
+       char cport[6];
+       time_t r;
+       time_t s;
+       char ibuf[8192];
+       char *ip;
+       int il;
+} *con;
+
+/* From netinet/in.h, but only _KERNEL_ gets them. */
+#define satosin(sa)    ((struct sockaddr_in *)(sa))
+#define satosin6(sa)   ((struct sockaddr_in6 *)(sa))
+int server_lookup4(struct sockaddr_in *, struct sockaddr_in *,
+    struct sockaddr_in *);
+int server_lookup6(struct sockaddr_in6 *, struct sockaddr_in6 *,
+    struct sockaddr_in6 *);
+
+void     usage(void);
+void     initcon(struct con *, int, struct sockaddr *);
+void     closecon(struct con *);
+void     handler(struct con *);
+void     getcaddr(struct con *);
+int      server_lookup(struct sockaddr *, struct sockaddr *,
+    struct sockaddr *);
+int      blockhost(char *);
+int      blocklistener(void);
+
+struct syslog_data sdata = SYSLOG_DATA_INIT;
+struct passwd *pw;
+
+time_t t;
+
+#define MAXCON 800
+int maxfiles;
+int maxcon = MAXCON;
+int clients;
+int debug;
+int pfdev;
+int window = 0;
+int autoblock = 1;
+int pipel[2] = { -1, -1 };
+pid_t pidl = -1;
+#define MAXIDLETIME 30
+#define MAXTIME 120
+#define PATH_PFCTL "/sbin/pfctl"
+
+void
+usage(void)
+{
+       extern char *__progname;
+
+       fprintf(stderr,
+           "usage: %s [-d] [-c maxcon] [-l address] "
+           "[-p port] [-w window]\n",
+           __progname);
+
+       exit(1);
+}
+
+int
+blockhost(char *ip)
+{
+       switch(fork()) {
+       case -1:
+               syslog_r(LOG_WARNING, &sdata, "child cannot fork (%m)");
+               return (-1);
+       case 0:
+               /* child */
+               if (-1 == execl(PATH_PFCTL, "pfctl", "-q", "-t", "badguys", 
"-T", "add", ip, NULL)) {
+                       syslog_r(LOG_WARNING, &sdata, "cannot exec pfctl (%m)");
+                       return (-2);
+               }
+       }
+
+       /* parent */
+       return (0);
+}
+
+int blocklistener(void)
+{
+       int ret = 0;
+       ssize_t len;
+       size_t lsize = 0;
+       char *buf = NULL;
+       FILE *pf;
+
+       fcntl(pipel[0], F_SETFD, FD_CLOEXEC);
+
+       pf = fdopen(pipel[0], "r");
+       if (pf == NULL) {
+               syslog_r(LOG_WARNING, &sdata, "cannot open pipe (%m)");
+               close(pipel[0]);
+               return(-1);
+       }
+
+       while (-1 != (len = getline(&buf, &lsize, pf))) {
+               buf[len - 1] = '\0';
+               blockhost(buf);
+               memset(buf, 0, sizeof buf);
+       }
+
+       if (ferror(pf)) {
+               syslog_r(LOG_ERR, &sdata, "child listener aborted (%m)");
+               ret = 2;
+       }
+       else if (feof(pf)) {
+               syslog_r(LOG_INFO, &sdata, "child listener terminated 
normally.");
+       }
+
+       fclose(pf);
+       return(ret);
+}
+
+/* Stolen from ftp-proxy */
+int
+server_lookup(struct sockaddr *client, struct sockaddr *proxy,
+    struct sockaddr *server)
+{
+       if (client->sa_family == AF_INET)
+               return (server_lookup4(satosin(client), satosin(proxy),
+                   satosin(server)));
+
+       if (client->sa_family == AF_INET6)
+               return (server_lookup6(satosin6(client), satosin6(proxy),
+                   satosin6(server)));
+
+       errno = EPROTONOSUPPORT;
+       return (-1);
+}
+
+int
+server_lookup4(struct sockaddr_in *client, struct sockaddr_in *proxy,
+    struct sockaddr_in *server)
+{
+       struct pfioc_natlook pnl;
+
+       memset(&pnl, 0, sizeof pnl);
+       pnl.direction = PF_OUT;
+       pnl.af = AF_INET;
+       pnl.proto = IPPROTO_TCP;
+       memcpy(&pnl.saddr.v4, &client->sin_addr.s_addr, sizeof pnl.saddr.v4);
+       memcpy(&pnl.daddr.v4, &proxy->sin_addr.s_addr, sizeof pnl.daddr.v4);
+       pnl.sport = client->sin_port;
+       pnl.dport = proxy->sin_port;
+
+       if (ioctl(pfdev, DIOCNATLOOK, &pnl) == -1)
+               return (-1);
+
+       memset(server, 0, sizeof(struct sockaddr_in));
+       server->sin_len = sizeof(struct sockaddr_in);
+       server->sin_family = AF_INET;
+       memcpy(&server->sin_addr.s_addr, &pnl.rdaddr.v4,
+           sizeof server->sin_addr.s_addr);
+       server->sin_port = pnl.rdport;
+
+       return (0);
+}
+
+int
+server_lookup6(struct sockaddr_in6 *client, struct sockaddr_in6 *proxy,
+    struct sockaddr_in6 *server)
+{
+       struct pfioc_natlook pnl;
+
+       memset(&pnl, 0, sizeof pnl);
+       pnl.direction = PF_OUT;
+       pnl.af = AF_INET6;
+       pnl.proto = IPPROTO_TCP;
+       memcpy(&pnl.saddr.v6, &client->sin6_addr.s6_addr, sizeof pnl.saddr.v6);
+       memcpy(&pnl.daddr.v6, &proxy->sin6_addr.s6_addr, sizeof pnl.daddr.v6);
+       pnl.sport = client->sin6_port;
+       pnl.dport = proxy->sin6_port;
+
+       if (ioctl(pfdev, DIOCNATLOOK, &pnl) == -1)
+               return (-1);
+
+       memset(server, 0, sizeof(struct sockaddr_in6));
+       server->sin6_len = sizeof(struct sockaddr_in6);
+       server->sin6_family = AF_INET6;
+       memcpy(&server->sin6_addr.s6_addr, &pnl.rdaddr.v6,
+           sizeof server->sin6_addr);
+       server->sin6_port = pnl.rdport;
+
+       return (0);
+}
+
+/*
+ * Get address client connected to, by doing a DIOCNATLOOK call.
+ * Uses server_lookup code from ftp-proxy.
+ */
+void
+getcaddr(struct con *cp)
+{
+       struct sockaddr_storage spamd_end;
+       struct sockaddr *sep = (struct sockaddr *) &spamd_end;
+       struct sockaddr_storage original_destination;
+       struct sockaddr *odp = (struct sockaddr *) &original_destination;
+       socklen_t len = sizeof(struct sockaddr_storage);
+       int error;
+
+       cp->caddr[0] = '\0';
+       cp->cport[0] = '\0';
+       if (getsockname(cp->fd, sep, &len) == -1)
+               return;
+       if (server_lookup((struct sockaddr *)&cp->ss, sep, odp) != 0)
+               return;
+       error = getnameinfo(odp, odp->sa_len, cp->caddr, sizeof(cp->caddr),
+           cp->cport, sizeof(cp->cport), NI_NUMERICHOST | NI_NUMERICSERV);
+       if (error) {
+               syslog_r(LOG_WARNING, &sdata, "cannot get original destination 
address.");
+               cp->caddr[0] = '\0';
+               cp->cport[0] = '\0';
+       }
+}
+
+void
+initcon(struct con *cp, int fd, struct sockaddr *sa)
+{
+       socklen_t len = sa->sa_len;
+       time_t tt;
+       int error;
+
+       time(&tt);
+       bzero(cp, sizeof(struct con));
+       cp->fd = fd;
+       if (len > sizeof(cp->ss))
+               errx(1, "sockaddr size");
+       if (sa->sa_family != AF_INET)
+               errx(1, "not supported yet");
+       memcpy(&cp->ss, sa, sa->sa_len);
+       cp->af = sa->sa_family;
+       cp->ia = &((struct sockaddr_in *)&cp->ss)->sin_addr;
+       error = getnameinfo(sa, sa->sa_len, cp->addr, sizeof(cp->addr), NULL, 0,
+           NI_NUMERICHOST);
+#ifdef useless
+       if (error)
+               errx(1, "%s", gai_strerror(error));
+#endif
+       getcaddr(cp);
+       cp->s = tt;
+       cp->r = tt;
+       clients++;
+
+       if (autoblock)
+               dprintf(pipel[1], "%s/32\n", cp->addr);
+}
+
+void
+closecon(struct con *cp)
+{
+       time_t tt;
+
+       close(cp->fd);
+
+       time(&tt);
+       syslog_r(LOG_INFO, &sdata, "%s: disconnected after %lld seconds.",
+           cp->addr, (long long)(tt - cp->s));
+       if (debug > 0)
+               printf("%s connected for %lld seconds.\n", cp->addr,
+                   (long long)(tt - cp->s));
+       clients--;
+       cp->fd = -1;
+}
+
+void
+handler(struct con *cp)
+{
+       int n;
+
+       if (cp->r) {
+               cp->ip = cp->ibuf;
+               cp->il = sizeof(cp->ibuf) - 1;
+               n = read(cp->fd, cp->ip, cp->il);
+               if (n == 0)
+                       closecon(cp);
+               else if (n == -1) {
+                       if (debug > 0)
+                               warn("read");
+                       closecon(cp);
+               } else {
+                       cp->r = time(NULL);
+               }
+       }
+}
+
+static void
+sighandler(int sig)
+{
+       if (sig == SIGPIPE && pidl != -1) {
+               autoblock = 0;
+               syslog_r(LOG_ERR, &sdata, "pipe widowed, autoblock disabled.");
+       }
+       if (sig == SIGCHLD && pidl != -1) {
+               if (0 > waitpid(pidl, NULL, 0))
+                       syslog_r(LOG_ERR, &sdata, "sighdlr error on waitpid");
+       }
+}
+
+static int
+get_maxfiles(void)
+{
+       int mib[2], maxfiles;
+       size_t len;
+
+       mib[0] = CTL_KERN;
+       mib[1] = KERN_MAXFILES;
+       len = sizeof(maxfiles);
+       if (sysctl(mib, 2, &maxfiles, &len, NULL, 0) == -1)
+               return(MAXCON);
+       if ((maxfiles - 200) < 10)
+               errx(1, "kern.maxfiles is only %d, can not continue\n",
+                   maxfiles);
+       else
+               return(maxfiles - 200);
+}
+
+int
+main(int argc, char *argv[])
+{
+       fd_set *fdsr = NULL;
+       struct sockaddr_in sin;
+       int ch, s, i, omax = 0, one = 1;
+       u_short port;
+       struct servent *ent;
+       struct rlimit rlp;
+       char *bind_address = NULL;
+
+       tzset();
+       openlog_r("tarpitd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
+
+       if ((ent = getservbyname("tarpitd", "tcp")) == NULL)
+               errx(1, "Can't find service \"tarpitd\" in /etc/services");
+       port = ntohs(ent->s_port);
+
+       maxfiles = get_maxfiles();
+       if (maxcon > maxfiles)
+               maxcon = maxfiles;
+       while ((ch =
+           getopt(argc, argv, "l:c:p:dvw:")) != -1) {
+               switch (ch) {
+               case 'l':
+                       bind_address = optarg;
+                       break;
+               case 'c':
+                       i = atoi(optarg);
+                       if (i > maxfiles) {
+                               fprintf(stderr,
+                                   "%d > system max of %d connections\n",
+                                   i, maxfiles);
+                               usage();
+                       }
+                       maxcon = i;
+                       break;
+               case 'p':
+                       i = atoi(optarg);
+                       port = i;
+                       break;
+               case 'd':
+                       debug = 1;
+                       break;
+               case 'w':
+                       window = atoi(optarg);
+                       if (window <= 0)
+                               usage();
+                       break;
+               default:
+                       usage();
+                       break;
+               }
+       }
+
+       setproctitle(NULL);
+
+       rlp.rlim_cur = rlp.rlim_max = maxcon + 15;
+       if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
+               err(1, "setrlimit");
+
+       signal(SIGPIPE, &sighandler);
+       signal(SIGCHLD, &sighandler);
+
+       if (0 == pipe(pipel)) {
+               switch (pidl = fork()) {
+               case -1:
+                       syslog_r(LOG_ERR, &sdata, "cannot fork - auto blocking 
disabled.");
+                       close(pipel[0]);
+                       close(pipel[1]);
+                       autoblock = 0;
+               case 0:
+                       /* child */
+                       close(pipel[1]);
+                       setproctitle("(blocker)");
+                       blocklistener();
+                       _exit(1);
+               }
+               /* parent */
+               if (autoblock)
+                       close(pipel[0]);
+
+       } else {
+               syslog_r(LOG_ERR, &sdata, "cannot open pipe - auto blocking 
disabled.");
+               autoblock = 0;
+       }
+
+       con = calloc(maxcon, sizeof(*con));
+       if (con == NULL)
+               err(1, "calloc");
+
+       for (i = 0; i < maxcon; i++)
+               con[i].fd = -1;
+
+       s = socket(AF_INET, SOCK_STREAM, 0);
+       if (s == -1)
+               err(1, "socket");
+
+       if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one,
+           sizeof(one)) == -1)
+               return (-1);
+
+       if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &window,
+           sizeof(window)) == -1)
+               return (-1);
+
+       memset(&sin, 0, sizeof sin);
+       sin.sin_len = sizeof(sin);
+       if (bind_address) {
+               if (inet_pton(AF_INET, bind_address, &sin.sin_addr) != 1)
+                       err(1, "inet_pton");
+       } else
+               sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(port);
+
+       if (bind(s, (struct sockaddr *)&sin, sizeof sin) == -1)
+               err(1, "bind");
+
+       if ((pw = getpwnam("_tarpitd")) == NULL)
+               errx(1, "no such user _tarpitd");
+
+       if (debug == 0) {
+               if (daemon(1, 1) == -1)
+                       err(1, "daemon");
+       }
+
+       pfdev = open("/dev/pf", O_RDWR);
+       if (pfdev == -1) {
+               syslog_r(LOG_ERR, &sdata, "open /dev/pf: %m");
+               exit(1);
+       }
+
+       if (chroot("/var/empty") == -1 || chdir("/") == -1) {
+               syslog(LOG_ERR, "cannot chdir to /var/empty.");
+               exit(1);
+       }
+
+       if (pw)
+               if (setgroups(1, &pw->pw_gid) ||
+                   setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+                   setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+                       err(1, "failed to drop privs");
+
+       if (listen(s, 10) == -1)
+               err(1, "listen");
+
+       if (debug != 0)
+               printf("listening for incoming connections.\n");
+       syslog_r(LOG_WARNING, &sdata, "listening for incoming connections.");
+
+       while (1) {
+               struct timeval tv, *tvp;
+               int max, n;
+
+               max = s;
+
+               time(&t);
+
+               if (autoblock) {
+                       switch (waitpid(pidl, NULL, WNOHANG)) {
+                       case -1:
+                               if (errno != ECHILD) {
+                                       /* XXX : ECHILD only happens when 
daemonized? */
+                                       syslog_r(LOG_ERR, &sdata, "error 
waiting for child (%m)");
+                                       autoblock = 0;
+                               }
+                       case 0:
+                               break;
+                       default:
+                               syslog_r(LOG_WARNING, &sdata, "child 
terminated, autoblock disabled");
+                               autoblock = 0;
+                       }
+               }
+
+               for (i = 0; i < maxcon; i++)
+                       if (con[i].fd != -1)
+                               max = MAX(max, con[i].fd);
+
+               if (max > omax) {
+                       free(fdsr);
+                       fdsr = NULL;
+                       fdsr = (fd_set *)calloc(howmany(max+1, NFDBITS),
+                           sizeof(fd_mask));
+                       if (fdsr == NULL)
+                               err(1, "calloc");
+                       omax = max;
+               } else {
+                       memset(fdsr, 0, howmany(max+1, NFDBITS) *
+                           sizeof(fd_mask));
+               }
+
+               for (i = 0; i < maxcon; i++) {
+                       if (con[i].fd != -1 && con[i].r) {
+                               if ((con[i].r + MAXIDLETIME <= t) ||
+                                   (con[i].s + MAXTIME <= t)) {
+                                       closecon(&con[i]);
+                                       continue;
+                               }
+                               FD_SET(con[i].fd, fdsr);
+                       }
+               }
+               FD_SET(s, fdsr);
+
+               /* Wake up at least once a second */
+               tv.tv_sec = 1;
+               tv.tv_usec = 0;
+               tvp = &tv;
+
+               n = select(max+1, fdsr, NULL, NULL, tvp);
+               if (n == -1) {
+                       if (errno != EINTR)
+                               err(1, "select");
+                       continue;
+               }
+
+               for (i = 0; i < maxcon; i++) {
+                       if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsr))
+                               handler(&con[i]);
+               }
+               if (FD_ISSET(s, fdsr)) {
+                       socklen_t sinlen;
+                       int s2;
+
+                       sinlen = sizeof(sin);
+                       s2 = accept(s, (struct sockaddr *)&sin, &sinlen);
+                       if (s2 == -1) {
+                               switch (errno) {
+                               case EINTR:
+                               case ECONNABORTED:
+                               case EMFILE:
+                               case ENFILE:
+                                       break;
+                               default:
+                                       errx(1, "accept");
+                               }
+                       } else {
+                               /* Check if we hit the chosen fd limit */
+                               for (i = 0; i < maxcon; i++)
+                                       if (con[i].fd == -1)
+                                               break;
+                               if (i == maxcon) {
+                                       close(s2);
+                               } else {
+                                       initcon(&con[i], s2,
+                                           (struct sockaddr *)&sin);
+                                       syslog_r(LOG_INFO, &sdata,
+                                           "%s: connected to %s:%s (%d)",
+                                           con[i].addr, con[i].caddr, 
con[i].cport, clients);
+                               }
+                       }
+               }
+       }
+       exit(1);
+}
+
+
--- /dev/null   Fri Jun 27 09:37:57 2014
+++ tarpitd.8   Thu Jun 19 10:55:12 2014
@@ -0,0 +1,152 @@
+.\" Copyright (c) Sebastien Leclerc.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 $Mdocdate: June 19 2014 $
+.Dt TARPITD 8
+.Os
+.Sh NAME
+.Nm tarpitd
+.Nd tcp tarpit and IP autoblocking daemon
+.Sh SYNOPSIS
+.Nm tarpitd
+.Bk -words
+.Op Fl c Ar maxcon
+.Op Fl d
+.Op Fl l Ar address
+.Op Fl p Ar port
+.Op Fl w Ar window
+.Ek
+.Sh DESCRIPTION
+.Nm
+is a TCP daemon that discards bytes received from the connecting client,
+using a default window of only 1 byte. It listens by default on 127.0.0.1, on
+port
+.Em tarpitd
+(normally listed in
+.Xr services 5
+as 8024/tcp) and works in concert with
+.Xr pf 4 ,
+which divert unwanted connections to
+.Nm .
+.Nm
+automatically adds source IP addresses to the
+.Aq badguys
+table, which can be
+used to block further connections. It is assumed that all connections directed
+to
+.Nm
+are using the TCP protocol (to avoid a DoS using spoofed UDP connections), and
+are undesirable.
+.Nm
+forks a child that runs as root, and is used to update the
+.Aq badguys
+table. The parent process then drops to the unprivileged
+.Dq _tarpitd
+user and accepts the connections. Connections are closed after 30 seconds of
+inactivity, i.e. no data received from the client, or after 120 seconds,
+whichever comes first.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl c Ar maxcon
+Override the default value of 800 for the maximum number of connections allowed
+.It Fl d
+Don't run in background
+.It Fl l Ar address
+Bind to this address instead of 127.0.0.1
+.It Fl p Ar port
+Bind to this port instead of 8024
+.It Fl w Ar window
+Set the socket receive buffer to this many bytes, adjusting the window size,
+instead of 1 byte
+.El
+.Pp
+.Nm
+sends log messages to
+.Xr syslogd 8
+using
+.Em facility
+daemon and
+.Em level
+err, warn, and info.
+When a host connects, an entry shows the time of the connection, the IP address
+of the connecting host, the original destination IP address and port, and the
+total number of active connections.
+When a host disconnects, the amount of time spent talking to
+.Nm
+is shown.
+.Pp
+If, for any reason, the privileged child process terminates, the parent will
+log an entry, and will disable the autoblocking feature. Clients will still
+be tarpitted, but their IP address won't be added to the
+.Aq badguys
+table.
+.Sh EXAMPLES
+Supplying no arguments to
+.Nm ,
+one can use these
+.Xr pf.conf 5
+rules to divert inbound connections on egress interface(s) to
+.Nm
+and to block IP addresses which previously made a connection to
+.Nm :
+.Bd -literal -offset 4n
+table \*(Ltbadguys\*(Gt persist
+block drop log quick inet from \*(Ltbadguys\*(Gt
+pass in on egress inet proto tcp set prio 0 \\
+    divert-to 127.0.0.1 port 8024
+.Ed
+.Pp
+Such rules would divert all inbound TCP connections on egress interface(s),
+except if there are more specific rules later in the ruleset to allow
+desirable connections.
+.Pp
+It is recommended to use
+.Xr pfctl 8
+to remove old entries periodically, like in this
+.Xr crontab 1
+entry:
+.Bd -literal -offset 4n
+1 * * * * /sbin/pfctl -t badguys -T expire 86400
+.Ed
+.Pp
+This would run each hour, and remove entries that are 24 hours old.
+.Sh SEE ALSO
+.Xr pf.conf 5 ,
+.Xr services 5 ,
+.Xr pfctl 8 ,
+.Xr syslogd 8
+.Sh HISTORY
+The
+.Nm
+command is still a work in progress. It was created in great part from the
+.Xr spamd 8
+source code.
+.Sh BUGS
+.Bl -tag -width Ds
+.It Autoblocking
+If the privileged child process dies,
+.Nm
+does not respawn a new privileged process, and disables autoblocking.
+.It IPv6
+IPv6 support is untested.
+.El

Reply via email to