[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

spam blocking engine



This is a start at a spam blocking engine I have been working on.

Very simply, this hangs the full list of ~12,000 spam-sending IP/mask
entries listed at www.spews.org off a pf(4) rdr-anchor (which is only
entered for port 25). When connections from these spammers arrive they
are redirected to a daemon which minimally fakes the SMTP protocol
with very low overhead -- for multiple connections at the same time --
and then the message is left on the sender's queue by providing a 550
return code.

The theory here is that most spam still comes in via open relays, and
the only way we are going to convince them to clean up their act is to
waste _their_ disk space, their time, and their network bandwidth more
than they waste ours.  For those spammers who drop messages when they
received a 550, well, we have not wasted any further time or network
bandwidth, and even in that situation I think some of the might remove
an address if they receive a 550.

This will be chrooted and locked down further... and I also plan on
adding stuttering to it, to waste the spammer's time further.

If you use this, you must have very current pf code.

Index: etc/crontab
===================================================================
RCS file: /cvs/src/etc/crontab,v
retrieving revision 1.10
diff -u -r1.10 crontab
--- etc/crontab	15 Jul 2002 19:13:28 -0000	1.10
+++ etc/crontab	19 Dec 2002 01:52:21 -0000
@@ -20,3 +20,4 @@
 30	1	*	*	*	/bin/sh /etc/daily 2>&1 | tee /var/log/daily.out | mail -s "`/bin/hostname` daily output" root
 30	3	*	*	6	/bin/sh /etc/weekly 2>&1 | tee /var/log/weekly.out | mail -s "`/bin/hostname` weekly output" root
 30	5	1	*	*	/bin/sh /etc/monthly 2>&1 | tee /var/log/monthly.out | mail -s "`/bin/hostname` monthly output" root
+#0	*	*	*	*	/usr/libexec/spamd-setup spews
Index: etc/pf.conf
===================================================================
RCS file: /cvs/src/etc/pf.conf,v
retrieving revision 1.10
diff -u -r1.10 pf.conf
--- etc/pf.conf	19 Dec 2002 00:06:29 -0000	1.10
+++ etc/pf.conf	19 Dec 2002 01:25:09 -0000
@@ -32,5 +32,5 @@
 #pass  in  on $ext_if proto tcp from any to $ext_if port 22 keep state
 #pass  out on $ext_if proto { tcp, udp } all keep state
 
-# anchor to attach spews rules, which will redirect to spewsd(8)
-#rdr-anchor spews inet proto tcp from any to any port = smtp
+# anchor to attach spam-redirecting rules, which will redirect to spamd(8)
+#rdr-anchor spamd inet proto tcp from any to any port = smtp
Index: etc/rc
===================================================================
RCS file: /cvs/src/etc/rc,v
retrieving revision 1.210
diff -u -r1.210 rc
--- etc/rc	13 Dec 2002 10:20:25 -0000	1.210
+++ etc/rc	19 Dec 2002 02:00:06 -0000
@@ -487,6 +487,10 @@
 	echo -n ' sendmail';		( /usr/sbin/sendmail ${sendmail_flags} >/dev/null 2>&1 & )
 fi
 
+if [ "X${spamd} != X"NO" ]; then
+	echo -n ' spamd';		/usr/libexec/spamd
+fi
+
 if [ "X${httpd_flags}" != X"NO"  ]; then
 	# Clean up left-over httpd locks
 	rm -f /var/www/logs/{ssl_mutex,httpd.lock,accept.lock}.*
Index: etc/rc.conf
===================================================================
RCS file: /cvs/src/etc/rc.conf,v
retrieving revision 1.82
diff -u -r1.82 rc.conf
--- etc/rc.conf	3 Dec 2002 19:15:06 -0000	1.82
+++ etc/rc.conf	19 Dec 2002 02:00:47 -0000
@@ -32,6 +32,7 @@
 
 # For normal use: "-L sm-mta -bd -q30m", and note there is a cron job
 sendmail_flags="-L sm-mta -C/etc/mail/localhost.cf -bd -q30m"
+spamd_flags=NO		# for normal use: "" and see spewd-setup(8)
 
 # Set to NO if ftpd is running out of inetd
 ftpd_flags=NO		# for non-inetd use: "-D"
Index: libexec/spamd/Makefile
===================================================================
RCS file: libexec/spamd/Makefile
diff -N libexec/spamd/Makefile
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/spamd/Makefile	19 Dec 2002 00:49:36 -0000
@@ -0,0 +1,13 @@
+#	$OpenBSD: Makefile,v 1.7 2002/07/16 10:16:10 deraadt Exp $
+
+PROG=	spamd
+SRCS=	spamd.c
+NOMAN=	spamd.8
+
+CFLAGS+= -Wall -Werror 
+
+afterinstall:
+	${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \
+	    ${.CURDIR}/spamd-setup.sh ${DESTDIR}${BINDIR}/spamd-setup
+
+.include <bsd.prog.mk>
Index: libexec/spamd/spamd-setup.sh
===================================================================
RCS file: libexec/spamd/spamd-setup.sh
diff -N libexec/spamd/spamd-setup.sh
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/spamd/spamd-setup.sh	19 Dec 2002 02:02:47 -0000
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+#	$OpenBSD: comsat.c,v 1.26 2002/09/06 19:43:54 deraadt Exp $
+#
+# Copyright (c) 2002 Theo de Raadt.  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.
+
+case $# in
+0)	echo "usage: spamd-setup [spews | ...]";
+	exit 1
+	;;
+esac
+
+case $1 in
+spews)
+	ftp -V -o - http://www.spews.org/spews_list_level1.txt | \
+	    grep -v '#' | sed 's/ .*$//g' | awk \
+	    '{ printf("rdr inet proto tcp from %s to any port 25 -> 127.0.0.1 port 8025\n", $0); }' | \
+	    pfctl -a spamd:spews -f -
+	;;
+*)
+	echo "spamd-setup: invalid filter set"
+	exit 1
+	;;
+esac
+
+exit 0
Index: libexec/spamd/spamd.c
===================================================================
RCS file: libexec/spamd/spamd.c
diff -N libexec/spamd/spamd.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/spamd/spamd.c	19 Dec 2002 00:47:57 -0000
@@ -0,0 +1,372 @@
+/*	$OpenBSD: comsat.c,v 1.26 2002/09/06 19:43:54 deraadt Exp $	*/
+
+/*
+ * Copyright (c) 2002 Theo de Raadt.  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.
+ */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <err.h>
+
+char hostname[MAXHOSTNAMELEN];
+
+char *reply = "450";
+
+#define MAXCON 200
+int maxcon = MAXCON;
+
+struct con {
+	int fd;
+	int state;
+	char addr[32];
+
+	/*
+	 * we will do stuttering by changing these to time_t's of
+	 * now + n, and only advancing when the time is in the past/now
+	 */
+	int r;
+	int w;
+
+	char ibuf[8192];
+	char *ip;
+	int il;
+	char rend[5];	/* any chars in here causes input termination */
+
+	char obuf[8192];
+	char *op;
+	int ol;
+} con[MAXCON];
+
+void
+usage(void)
+{
+	fprintf(stderr, "usage: spamd [-45] [-c maxcon] [-p port]\n");
+	exit(1);
+}
+
+void
+initcon(struct con *cp, int fd, struct sockaddr_in *sin)
+{
+	time_t t;
+
+	time(&t);
+	bzero(cp, sizeof(struct con));
+	cp->fd = fd;
+	strlcpy(cp->addr, inet_ntoa(sin->sin_addr), sizeof(cp->addr));
+	snprintf(cp->obuf, sizeof(cp->obuf),
+	    "220 %s ESMTP spamd IP-based SPAM blocker; %s",
+	    hostname, ctime(&t));
+	cp->op = cp->obuf;
+	cp->ol = strlen(cp->op);
+	cp->w = 1;
+	strlcpy(cp->rend, "\n\r", sizeof cp->rend);
+}
+
+int
+match(char *s1, char *s2)
+{
+	return !strncasecmp(s1, s2, strlen(s2));
+}
+
+void
+nextstate(struct con *cp)
+{
+	switch (cp->state) {
+	case 0:
+		/* banner sent; wait for input */
+		cp->ip = cp->ibuf;
+		cp->il = sizeof(cp->ibuf) - 1;
+		cp->state = 1;
+		cp->r = 1;
+		break;
+	case 1:
+		/*
+		 * received input: parse, and select next state.
+		 * XXX or for now, just simply reply with failure.
+		 */
+		// printf("1: %s\n", cp->ibuf);
+		if (match(cp->ibuf, "HELO") ||
+		    match(cp->ibuf, "EHLO")) {
+			snprintf(cp->obuf, sizeof cp->obuf,
+			    "250 Hello, please to meet you\n");
+			cp->op = cp->obuf;
+			cp->ol = strlen(cp->op);
+			cp->state = 2;
+			cp->w = 1;
+			break;
+		}
+		goto spam;
+	case 2:
+		/* sent 250 Hello, wait for input */  
+		cp->ip = cp->ibuf;
+		cp->il = sizeof(cp->ibuf) - 1;
+		cp->state = 3;
+		cp->r = 1;
+		break;
+	case 3:
+		// printf("3: %s\n", cp->ibuf);
+		if (match(cp->ibuf, "MAIL")) {
+			snprintf(cp->obuf, sizeof cp->obuf,
+			    "250 You are about to try to deliver spam.\n");
+			cp->op = cp->obuf;
+			cp->ol = strlen(cp->op);
+			cp->state = 4;
+			cp->w = 1;
+			break;
+		}
+		goto spam;
+	case 4:
+		/* sent 250 Sender ok */
+		cp->ip = cp->ibuf;
+		cp->il = sizeof(cp->ibuf) - 1;
+		cp->state = 5;
+		cp->r = 1;
+		break;
+	case 5:
+		// printf("5: %s\n", cp->ibuf);
+		if (match(cp->ibuf, "RCPT")) {
+			snprintf(cp->obuf, sizeof cp->obuf,
+			    "250 And we will try to punish you for it.\n");
+			cp->op = cp->obuf;
+			cp->ol = strlen(cp->op);
+			cp->state = 6;
+			cp->w = 1;
+			break;
+		}
+		goto spam;
+	case 6:
+		/* sent 250 blah */
+		cp->ip = cp->ibuf;
+		cp->il = sizeof(cp->ibuf) - 1;
+		cp->state = 50;
+		cp->r = 1;
+		break;
+
+	case 50:
+		/* fallthrough */
+
+
+	spam:
+	case 98:
+		// printf("98: %s\n", cp->ibuf);
+		snprintf(cp->obuf, sizeof cp->obuf,
+		    "%s SPAM. http://www.spews.org?x=%s\n";,
+		    reply, cp->addr);
+		cp->op = cp->obuf;
+		cp->ol = strlen(cp->op);
+		cp->state = 99;
+		cp->w = 1;
+		break;
+	case 99:
+		close(cp->fd);
+		cp->fd = -1;
+		break;
+	default:
+		errx(1, "illegal state %d", cp->state);
+		break;
+	}
+}
+
+void
+handler(struct con *cp)
+{
+	int end = 0;
+	int i, n;
+
+	if (cp->r) {
+		n = read(cp->fd, cp->ip, cp->il);
+		if (n == 0) {
+			close(cp->fd);
+			cp->fd = -1;
+		} else if (n == -1) {
+			/* XXX */
+		} else {
+			if (cp->rend[0])
+				for (i = 0; i < n; i++)
+					if (strchr(cp->rend, cp->op[i]))
+						end = 1;
+			cp->ip += n;
+			cp->il -= n;
+		}
+	}
+	if (end || cp->il == 0) {
+		*cp->ip = '\0';
+		cp->r = 0;
+		nextstate(cp);
+	}
+}
+
+void
+handlew(struct con *cp)
+{
+	int n;
+
+	if (cp->w) {
+		n = write(cp->fd, cp->op, cp->ol);
+		if (n == 0) {
+			close(cp->fd);
+			cp->fd = -1;
+		} else if (n == -1) {
+			/* XXX */
+		} else {
+			cp->op += n;
+			cp->ol -= n;
+		}
+	}
+	if (cp->ol == 0) {
+		cp->w = 0;
+		nextstate(cp);
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct sockaddr_in sin;
+	int ch, s, s2, i;
+	int sinlen, one = 1;
+	u_short port = 8025;
+
+	gethostname(hostname, sizeof hostname);
+
+	for (i = 0; i < MAXCON; i++)
+		con[i].fd = -1;
+
+	while ((ch = getopt(argc, argv, "45c:p:")) != -1) {
+		switch (ch) {
+		case '4':
+			reply = "450";
+		case '5':
+			reply = "550";
+			break;
+		case 'c':
+			i = atoi(optarg);
+			if (i > MAXCON)
+				usage();
+			maxcon = i;
+			break;
+		case 'p':
+			i = atoi(optarg);
+			port = i;
+			break;
+		default:
+			usage();
+			break;
+		}
+	}
+
+	s = socket(AF_INET, SOCK_STREAM, 0);
+	if (s == -1)
+		errx(1, "socket");
+
+	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one,
+	    sizeof(one))  == -1)
+		return(-1);
+
+	memset(&sin, 0, sizeof sin);
+	sin.sin_len = sizeof(sin);
+	sin.sin_family = AF_INET;
+	sin.sin_port = htons(port);
+
+	if (bind(s, (struct sockaddr *)&sin, sizeof sin) == -1)
+		errx(1, "bind");
+
+	listen(s, 10);
+
+	while (1) {
+		fd_set *fdsr = NULL, *fdsw = NULL;
+		int max = s, i, n;
+		int omax = 0;
+
+		for (i = 0; i < maxcon; i++)
+			if (con[i].fd != -1)
+				max = MAX(max, con[i].fd);
+
+		if (max > omax) {
+			if (fdsr)
+				free(fdsr);
+			if (fdsw)
+				free(fdsw);
+			fdsr = (fd_set *)calloc(howmany(max+1, NFDBITS),
+			    sizeof(fd_mask));
+			if (fdsr == NULL)
+				errx(1, "calloc");
+			fdsw = (fd_set *)calloc(howmany(max+1, NFDBITS),
+			    sizeof(fd_mask));
+			if (fdsw == NULL)
+				errx(1, "calloc");
+			omax = max;
+		} else {
+			memset(fdsr, howmany(max+1, NFDBITS),
+			    sizeof(fd_mask));
+			memset(fdsw, howmany(max+1, NFDBITS),
+			    sizeof(fd_mask));
+		}
+
+		for (i = 0; i < maxcon; i++) {
+			if (con[i].fd != -1 && con[i].r)
+				FD_SET(con[i].fd, fdsr);
+			if (con[i].fd != -1 && con[i].w)
+				FD_SET(con[i].fd, fdsw);
+		}
+		FD_SET(s, fdsr);
+
+		n = select(max+1, fdsr, fdsw, NULL, NULL);
+		if (n == -1 && errno == EINTR)
+			errx(1, "select");
+
+		for (i = 0; i < maxcon; i++) {
+			if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsr))
+				handler(&con[i]);
+			if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsw))
+				handlew(&con[i]);
+		}
+		if (FD_ISSET(s, fdsr)) {
+			sinlen = sizeof(sin);
+			s2 = accept(s, (struct sockaddr *)&sin, &sinlen);
+			if (s2 == -1) {
+				if (errno == EINTR)
+					continue;
+				errx(1, "accept");
+			}
+			for (i = 0; i < maxcon; i++)
+				if (con[i].fd == -1)
+					break;
+			if (i == maxcon)
+				close(s2);
+			else
+				initcon(&con[i], s2, &sin);
+		}
+	}
+	exit(1);
+}



Visit your host, monkey.org