> De : mxb [mailto:[email protected]], 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