> De : mxb [mailto:m...@alumni.chalmers.se], 30 juin 2014 03:26
> Could you please, post updated version to the list?

Sure!

--- /dev/null   Mon Jun 30 07:57:57 2014
+++ tarpitd.c   Fri Jun 27 14:01:35 2014
@@ -0,0 +1,525 @@
+/*
+ * 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;
+
+void     usage(void);
+void     initcon(struct con *, int, struct sockaddr *);
+void     closecon(struct con *);
+void     handler(struct con *);
+void     getcaddr(struct con *);
+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 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);
+}
+
+void
+getcaddr(struct con *cp)
+{
+       struct sockaddr_storage spamd_end;
+       struct sockaddr *sep = (struct sockaddr *) &spamd_end;
+       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;
+       error = getnameinfo(sep, sep->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:dw:")) != -1) {
+               switch (ch) {
+               case 'l':
+                       bind_address = optarg;
+                       break;
+               case 'c':
+                       i = (int) strtonum(optarg, 1, maxfiles, NULL);
+                       if (i == 0) {
+                               fprintf(stderr,
+                                   "-c %d must be between 1 and 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");
+       }
+
+       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   Mon Jun 30 08:18:00 2014
+++ tarpitd.8   Mon Jun 30 08:17:20 2014
@@ -0,0 +1,161 @@
+.\" 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 30 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 from the same host. The idea is to block
+bad hosts that scan your subnet before they get to the protected services.
+.Nm
+will not block half-open scans, because, as a DoS protection, IPs are only
+added to the
+.Aq badguys
+table once the complete 3-way handshake is complete.
+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
+webserver = "192.0.2.50"
+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
+# last match wins, protected service still available :
+pass inet proto tcp to $webserver port 80
+.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