diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 2002-12-21 01:41:55 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 2002-12-21 01:41:55 +0000 |
commit | b4e46ea34f21bd17d5fb0b9cb6558f71fa04670a (patch) | |
tree | fed25652610e333cd01ef187a935684965fff825 /libexec | |
parent | 2262e13d9380cf8cadcfd3ea1f8cd1f474b3d53d (diff) |
spamd: work in progress
Diffstat (limited to 'libexec')
-rw-r--r-- | libexec/Makefile | 4 | ||||
-rw-r--r-- | libexec/spamd/Makefile | 13 | ||||
-rw-r--r-- | libexec/spamd/spamd-setup.sh | 86 | ||||
-rw-r--r-- | libexec/spamd/spamd.8 | 127 | ||||
-rw-r--r-- | libexec/spamd/spamd.c | 472 |
5 files changed, 700 insertions, 2 deletions
diff --git a/libexec/Makefile b/libexec/Makefile index b177ffce537..9f106baec1c 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -1,11 +1,11 @@ # from: @(#)Makefile 5.7 (Berkeley) 4/1/91 -# $OpenBSD: Makefile,v 1.33 2002/10/04 23:16:41 deraadt Exp $ +# $OpenBSD: Makefile,v 1.34 2002/12/21 01:41:54 deraadt Exp $ .include <bsd.own.mk> SUBDIR= comsat fingerd ftpd ftp-proxy getNAME getty identd lockspool \ mail.local makewhatis \ - rpc.rquotad rpc.rstatd rpc.rusersd rpc.rwalld rpc.sprayd \ + rpc.rquotad rpc.rstatd rpc.rusersd rpc.rwalld rpc.sprayd spamd \ rshd talkd tcpd telnetd tftpd uucpd SUBDIR+=login_passwd login_skey login_krb4 login_krb4-or-pwd login_reject \ login_chpass login_lchpass login_token login_radius diff --git a/libexec/spamd/Makefile b/libexec/spamd/Makefile new file mode 100644 index 00000000000..7ec1195f654 --- /dev/null +++ b/libexec/spamd/Makefile @@ -0,0 +1,13 @@ +# $OpenBSD: Makefile,v 1.1 2002/12/21 01:41:54 deraadt Exp $ + +PROG= spamd +SRCS= spamd.c +MAN= 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> diff --git a/libexec/spamd/spamd-setup.sh b/libexec/spamd/spamd-setup.sh new file mode 100644 index 00000000000..6bda4ff902c --- /dev/null +++ b/libexec/spamd/spamd-setup.sh @@ -0,0 +1,86 @@ +#!/bin/sh + +# $OpenBSD: spamd-setup.sh,v 1.1 2002/12/21 01:41: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. + +usage() { + echo "usage: spamd-setup [-s12] [-f file] [-w whitelist]"; + exit 1 +} + +case $# in +0) usage() + ;; +esac + +filter() { + grep -v '#' | sed 's/ .*$//g' | sort +} + +pfcmd() { + awk '{ printf("rdr inet proto tcp from %s to any port 25 -> 127.0.0.1 port 8025\n", $1); } ' +} + +fetch() { + ftp -V -o - "$1" +} + +R=`mktemp /tmp/_spamdXXXXXX` || exit 1 +W=`mktemp /tmp/_spamwXXXXXX` || exit 1 +trap "rm -f $R $W; exit 0" 0 +trap "rm -f $R $W; exit 1" 1 2 3 13 15 + +while : + do case "$1" in + -x) echo 199.185.137.1 >> $R + ;; + -s|-1) + fetch http://www.spews.org/spews_list_level1.txt | filter >> $R + ;; + -2) + fetch http://www.spews.org/spews_list_level2.txt | filter >> $R + ;; + -f) + cat $2 | filter >> $R + shift + ;; + -w) + cat $2 | filter >> $W + shift + ;; + *) + break + ;; + esac + shift +done + +if [ $# != 0 ]; then + usage +fi + +# knock out whitelist here + +cat $R | uniq | pfcmd | pfctl -a spamd:spews -f - +exit 0 diff --git a/libexec/spamd/spamd.8 b/libexec/spamd/spamd.8 new file mode 100644 index 00000000000..23d6c2feefa --- /dev/null +++ b/libexec/spamd/spamd.8 @@ -0,0 +1,127 @@ +.\" $OpenBSD: spamd.8,v 1.1 2002/12/21 01:41: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. +.\" +.Dd Dec 18, 2002 +.Dt SPAMD 8 +.Os +.Sh NAME +.Nm spamd +.Nd spam deferral daemon +.Sh SYNOPSIS +.Nm spamd +.Op Fl 45d +.Op Fl r Ar reply +.Op Fl c Ar maxcon +.Op Fl p Ar port +.Sh DESCRIPTION +.Nm +is a a fake +.Xr sendmail 8 Ns -like +daemon which absorbs false mail. +If the +.Xr pf 4 +packet filter is configured to redirect port 25 (SMTP) to this daemon, +it will attempt to waste the time and resources of the spam sender. +.Pp +The options are as follows: +.Bl -tag -width Ds +.Pp +.It Fl 4 +Return error code 450 to the spammer. +.It Fl 5 +Return error code 550 to the spammer. +.It Fl d +Debug mode. +.Nm +does not +.Xr fork 2 +into the background. +.It Fl r +The SMTP error to return to the spammer, ie 450, 451, 550. This defaults +to 450. +.It Fl c +the Maximum number of concurrent connections to allow. +.It Fl p +Specify the port number that +.Nm +should operate on. +.El +.Pp +.Nm +is designed to be very efficient so that it does not slow down the +receiving machine. SPAM is never accepted, but always rejected with +either a 450 or 550 error message. +.Pp +There are currently two methods that are being used to +fight spam. +.Bl -enum -offset indent +.It +Block (or slowly block) This works well, the spammers are blocked, and cannot +access your hosts services. Unfortunately this does little to combat the +problem of spam in general, allowing the spammer to continue to spam others. +.It +Accept & drop via a +.Xr sendmail 8 +addon. This works well for many sites also. All the mail that is recieved +by your host is processed by a set of rules, and the spam is either deleted +or flagged for further action. Unfortunately this method can sonsume a +considerable amount of your systems resources. +.El +.Pp +What +.Nm +does, is to defer the spammers connections. It does this in a manner +designed to use as few local resources as possible, while consuming as much +of the spammers resources as possible. +.Nm +replies to the spammers mail with error codes which cause will cause mail +to be stored in remote mail queues, taking up disk +space, and to be resent multiple times, using network resources. +.Pp +.Nm +does not follow the pollicy of playing nice with your neighbor. Clients +which reach +.Nm +have already been flagged as being spammers. The only way to combat spam is +to remove the senders ability to deliver it. In this case, that means force +the delivery of spam to have a very high overhead for the remote relay. +.Pp +.Nm +is best started from +.Xr rc 8 +and is used in conjuction with +.Xr spamd-setup 8 +which proceses a list of spammers addresses, and applies appropriate +.Xr pfctl 8 +rdr rules. +.Pp +.Sh SEE ALSO +.Xr spamd-setup 8 , +.Xr pfctl 8 +.Sh HISTORY +The +.Nm +command +appeared in +.Tn OpenBSD 3.2. diff --git a/libexec/spamd/spamd.c b/libexec/spamd/spamd.c new file mode 100644 index 00000000000..58c320560be --- /dev/null +++ b/libexec/spamd/spamd.c @@ -0,0 +1,472 @@ +/* $OpenBSD: spamd.c,v 1.1 2002/12/21 01:41: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 <pwd.h> +#include <syslog.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> +#include <err.h> + +char hostname[MAXHOSTNAMELEN]; +struct syslog_data sdata = SYSLOG_DATA_INIT; +char *reply = NULL; +char *nreply = "450"; +char *spamd = "spamd IP-based SPAM blocker"; + +time_t t; + +#define MAXCON 200 +int maxcon = MAXCON; +int clients; + +struct con { + int fd; + int state; + char addr[32]; + char mail[64], rcpt[64]; + + /* + * 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; + time_t 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 [-45d] [-r reply] [-c maxcon] [-p port] [-n name]\n"); + exit(1); +} + +void +doreply(struct con *cp) +{ + if (reply) { + snprintf(cp->obuf, sizeof cp->obuf, + "%s %s\n", nreply, reply); + return; + } + + snprintf(cp->obuf, sizeof cp->obuf, + "%s-SPAM. www.spews.org/ask.cgi?x=%s\n" + "%s-You are trying to send mail from an address listed by one or\n" + "%s-more IP-based registries as being in a SPAM-generating netblock.\n" + "%s SPAM. www.spews.org/ask.cgi?x=%s\n", + nreply, cp->addr, nreply, nreply, nreply, cp->addr); +} + +void +setlog(char *p, int l, char *f) +{ + char *s; + + s = strsep(&f, ":"); + if (!s) + return; + s = strsep(&f, " \t"); + if (s == NULL) + return; + strlcpy(p, s, l); + s = strsep(&p, " \t\n\r"); + if (s == NULL) + return; + s = strsep(&p, " \t\n\r"); + if (s) + *s = '\0'; +} + +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 %s; %s", + hostname, spamd, ctime(&t)); + cp->op = cp->obuf; + cp->ol = strlen(cp->op); + cp->w = t + 1; + strlcpy(cp->rend, "\n\r", sizeof cp->rend); + clients++; +} + +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 */ + if (match(cp->ibuf, "HELO") || + match(cp->ibuf, "EHLO")) { + snprintf(cp->obuf, sizeof cp->obuf, + "250 Hello, spam sender. " + "Pleased to be wasting your time.\n"); + cp->op = cp->obuf; + cp->ol = strlen(cp->op); + cp->state = 2; + cp->w = t + 1; + break; + } + goto mail; + 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; + mail: + case 3: + if (match(cp->ibuf, "MAIL")) { + setlog(cp->mail, sizeof cp->mail, cp->ibuf); + snprintf(cp->obuf, sizeof cp->obuf, + "250 You are about to try to deliver spam. " + "Your time will be spent, amounting to nothing.\n"); + cp->op = cp->obuf; + cp->ol = strlen(cp->op); + cp->state = 4; + cp->w = t + 1; + break; + } + goto rcpt; + case 4: + /* sent 250 Sender ok */ + cp->ip = cp->ibuf; + cp->il = sizeof(cp->ibuf) - 1; + cp->state = 5; + cp->r = 1; + break; + rcpt: + case 5: + if (match(cp->ibuf, "RCPT")) { + setlog(cp->rcpt, sizeof(cp->rcpt), cp->ibuf); + snprintf(cp->obuf, sizeof cp->obuf, + "250 This is hurting you more than it is " + "hurting me.\n"); + cp->op = cp->obuf; + cp->ol = strlen(cp->op); + cp->state = 6; + cp->w = t + 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; + + spam: + case 50: + syslog_r(LOG_INFO, &sdata, "%s: %s -> %s", + cp->addr, cp->mail, cp->rcpt); + doreply(cp); + cp->op = cp->obuf; + cp->ol = strlen(cp->op); + cp->state = 99; + cp->w = t + 1; + break; + case 99: + close(cp->fd); + clients--; + 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); + clients--; + 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 one) +{ + int n; + + if (cp->w) { + n = write(cp->fd, cp->op, one ? 1 : cp->ol); + if (n == 0) { + close(cp->fd); + clients--; + cp->fd = -1; + } else if (n == -1) { + /* XXX */ + } else { + cp->op += n; + cp->ol -= n; + } + } + cp->w = t + 1; + if (cp->ol == 0) { + cp->w = 0; + nextstate(cp); + } +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_in sin; + struct passwd *pw; + int ch, s, s2, i; + int sinlen, one = 1; + u_short port = 8025; + int debug = 0; + + tzset(); + openlog_r("spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata); + + pw = getpwnam("_spamd"); + if (!pw) + pw = getpwnam("nobody"); + if (chroot("/var/empty") == -1) { + syslog(LOG_ERR, "cannot chdir to /var/empty."); + exit(1); + } + chdir("/"); + + if (pw) { + setgroups(1, &pw->pw_gid); + setegid(pw->pw_gid); + setgid(pw->pw_gid); + seteuid(pw->pw_uid); + setuid(pw->pw_uid); + } + + gethostname(hostname, sizeof hostname); + + for (i = 0; i < MAXCON; i++) + con[i].fd = -1; + + while ((ch = getopt(argc, argv, "45c:p:dr:n:")) != -1) { + switch (ch) { + case '4': + nreply = "450"; + break; + case '5': + nreply = "550"; + break; + case 'c': + i = atoi(optarg); + if (i > MAXCON) + usage(); + maxcon = i; + break; + case 'p': + i = atoi(optarg); + port = i; + break; + case 'd': + debug = 1; + break; + case 'r': + reply = optarg; + break; + case 'n': + spamd = optarg; + break; + default: + usage(); + break; + } + } + + signal(SIGPIPE, SIG_IGN); + + 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); + + if (debug == 0) + daemon(1, 1); + + while (1) { + fd_set *fdsr = NULL, *fdsw = NULL; + struct timeval tv, *tvp; + int max = s, i, n, omax = 0; + int writers; + + time(&t); + 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)); + } + + writers = 0; + 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) { + if (con[i].w <= t) + FD_SET(con[i].fd, fdsw); + writers = 1; + } + } + FD_SET(s, fdsr); + + if (writers == 0) { + tvp = NULL; + } else { + tv.tv_sec = 1; + tv.tv_usec = 0; + tvp = &tv; + } + + n = select(max+1, fdsr, fdsw, NULL, tvp); + if (n == -1 && errno == EINTR) + errx(1, "select"); + if (n == 0) + continue; + + 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], clients + 5 < maxcon); + } + 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); +} |