> 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