summaryrefslogtreecommitdiff
path: root/libexec
diff options
context:
space:
mode:
authorBob Beck <beck@cvs.openbsd.org>2005-03-11 23:09:54 +0000
committerBob Beck <beck@cvs.openbsd.org>2005-03-11 23:09:54 +0000
commit055086c57f1d2f2eae3e4ecfce8c6b2553a4c28e (patch)
tree6e802ef9dbf99eeefc4fbbe71f626683e5cce5df /libexec
parentc9c43400621b4aa0b087174b82fdea6b0928a641 (diff)
"Greytrapping" for spamd - allow for spamd greylisting to maintain
a list of spamtrap destination addresses in the spamd database. When a spamtrap address gets an attempted greylist delivery, blacklist the offending host for a day. Does not affect hosts already whitelisted. ok deraadt@, jmc@, dhartmei@ to get it in so it can be whacked on
Diffstat (limited to 'libexec')
-rw-r--r--libexec/spamd/grey.c199
-rw-r--r--libexec/spamd/grey.h5
-rw-r--r--libexec/spamd/spamd.834
-rw-r--r--libexec/spamd/spamd.c69
4 files changed, 255 insertions, 52 deletions
diff --git a/libexec/spamd/grey.c b/libexec/spamd/grey.c
index 713f0a9928d..3cb1fcec39a 100644
--- a/libexec/spamd/grey.c
+++ b/libexec/spamd/grey.c
@@ -1,7 +1,7 @@
-/* $OpenBSD: grey.c,v 1.19 2004/12/04 00:24:42 moritz Exp $ */
+/* $OpenBSD: grey.c,v 1.20 2005/03/11 23:09:52 beck Exp $ */
/*
- * Copyright (c) 2004 Bob Beck. All rights reserved.
+ * Copyright (c) 2004,2005 Bob Beck. 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
@@ -25,6 +25,7 @@
#include <netinet/in.h>
#include <net/pfvar.h>
#include <arpa/inet.h>
+#include <ctype.h>
#include <db.h>
#include <err.h>
#include <errno.h>
@@ -41,24 +42,32 @@
#include "grey.h"
-extern time_t passtime, greyexp, whiteexp;
+extern time_t passtime, greyexp, whiteexp, trapexp;
extern struct syslog_data sdata;
extern struct passwd *pw;
+extern u_short cfg_port;
extern pid_t jail_pid;
+extern FILE * trapcfg;
extern FILE * grey;
extern int debug;
size_t whitecount, whitealloc;
+size_t trapcount, trapalloc;
char **whitelist;
+char **traplist;
+
+char *traplist_name = "spamd-greytrap";
+char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
+
pid_t db_pid = -1;
int pfdev;
+int spamdconf;
static char *pargv[11]= {
"pfctl", "-p", "/dev/pf", "-q", "-t",
"spamd-white", "-T", "replace", "-f" "-", NULL
};
-
/* If the parent gets a signal, kill off the children and exit */
/* ARGSUSED */
static void
@@ -71,6 +80,25 @@ sig_term_chld(int sig)
_exit(1);
}
+/*
+ * Greatly simplified version from spamd_setup.c - only
+ * sends one blacklist to an already open stream. Has no need
+ * to collapse cidr ranges since these are only ever single
+ * host hits.
+ */
+int
+configure_spamd(char **addrs, int count, FILE *sdc)
+{
+ int i;
+
+ fprintf(sdc, "%s;%s;", traplist_name, traplist_msg);
+ for (i = 0; i < count; i++)
+ fprintf(sdc, "%s/32;", addrs[i]);
+ fprintf(sdc, "\n");
+ fflush(sdc);
+ return(0);
+}
+
int
configure_pf(char **addrs, int count)
{
@@ -140,7 +168,7 @@ configure_pf(char **addrs, int count)
}
void
-freewhiteaddr(void)
+freeaddrlists(void)
{
int i;
@@ -150,6 +178,13 @@ freewhiteaddr(void)
whitelist[i] = NULL;
}
whitecount = 0;
+ if (traplist != NULL) {
+ for (i = 0; i < trapcount; i++) {
+ free(traplist[i]);
+ traplist[i] = NULL;
+ }
+ }
+ trapcount = 0;
}
/* validate, then add to list of addrs to whitelist */
@@ -189,6 +224,44 @@ addwhiteaddr(char *addr)
return(0);
}
+/* validate, then add to list of addrs to traplist */
+int
+addtrapaddr(char *addr)
+{
+ struct addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET; /*for now*/
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_protocol = IPPROTO_UDP; /*dummy*/
+ hints.ai_flags = AI_NUMERICHOST;
+
+ if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
+ if (trapcount == trapalloc) {
+ char **tmp;
+
+ tmp = realloc(traplist,
+ (trapalloc + 1024) * sizeof(char *));
+ if (tmp == NULL) {
+ freeaddrinfo(res);
+ return(-1);
+ }
+ traplist = tmp;
+ trapalloc += 1024;
+ }
+ traplist[trapcount] = strdup(addr);
+ if (traplist[trapcount] == NULL) {
+ freeaddrinfo(res);
+ return(-1);
+ }
+ trapcount++;
+ freeaddrinfo(res);
+ } else
+ return(-1);
+ return(0);
+}
+
+
int
greyscan(char *dbname)
{
@@ -197,6 +270,8 @@ greyscan(char *dbname)
DB *db;
struct gdata gd;
int r;
+ char *a = NULL;
+ size_t asiz = 0;
time_t now = time(NULL);
/* walk db, expire, and whitelist */
@@ -211,30 +286,39 @@ greyscan(char *dbname)
memset(&dbd, 0, sizeof(dbd));
for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
r = db->seq(db, &dbk, &dbd, R_NEXT)) {
- char a[128];
-
if ((dbk.size < 1) || dbd.size != sizeof(struct gdata)) {
- db->close(db);
- db = NULL;
- return(-1);
+ goto bad;
+ }
+ if (asiz < dbk.size + 1) {
+ char *tmp;
+
+ tmp = realloc(a, dbk.size * 2);
+ if (tmp == NULL)
+ goto bad;
+ a = tmp;
+ asiz = dbk.size * 2;
}
+ memset(a, 0, asiz);
+ memcpy(a, dbk.data, dbk.size);
memcpy(&gd, dbd.data, sizeof(gd));
- if (gd.expire <= now) {
+ if (gd.expire <= now && gd.pcount != -2) {
/* get rid of entry */
- if (debug) {
- memset(a, 0, sizeof(a));
- memcpy(a, dbk.data, MIN(sizeof(a),
- dbk.size));
- syslog_r(LOG_DEBUG, &sdata,
- "deleting %s from %s", a, dbname);
- }
+ if (debug)
+ fprintf(stderr, "deleting %s\n", a);
if (db->del(db, &dbk, 0)) {
- db->close(db);
- db = NULL;
- return(-1);
+ goto bad;
}
db->sync(db, 0);
- } else if (gd.pass <= now) {
+ } else if (gd.pcount == -1) {
+ /* this is a greytrap hit */
+ if ((addtrapaddr(a) == -1) &&
+ db->del(db, &dbk, 0)) {
+ db->sync(db, 0);
+ goto bad;
+ }
+ if (debug)
+ fprintf(stderr, "trapped %s\n", a);
+ } else if (gd.pcount >= 0 && gd.pass <= now) {
int tuple = 0;
char *cp;
@@ -243,8 +327,6 @@ greyscan(char *dbname)
* add address to whitelist
* add an address-keyed entry to db
*/
- memset(a, 0, sizeof(a));
- memcpy(a, dbk.data, MIN(sizeof(a) - 1, dbk.size));
cp = strchr(a, '\n');
if (cp != NULL) {
tuple = 1;
@@ -281,14 +363,23 @@ greyscan(char *dbname)
}
}
configure_pf(whitelist, whitecount);
+ if (configure_spamd(traplist, trapcount, trapcfg) == -1)
+ syslog_r(LOG_DEBUG, &sdata, "configure_spamd failed");
+
db->close(db);
db = NULL;
- freewhiteaddr();
+ freeaddrlists();
+ free(a);
+ a = NULL;
+ asiz = 0;
return(0);
bad:
db->close(db);
db = NULL;
- freewhiteaddr();
+ freeaddrlists();
+ free(a);
+ a = NULL;
+ asiz = 0;
return(-1);
}
@@ -299,9 +390,11 @@ greyupdate(char *dbname, char *ip, char *from, char *to)
DBT dbk, dbd;
DB *db;
char *key = NULL;
+ char *trap = NULL;
+ char *lookup;
struct gdata gd;
- time_t now;
- int r;
+ time_t now, expire;
+ int i, r, spamtrap;
now = time(NULL);
@@ -312,9 +405,32 @@ greyupdate(char *dbname, char *ip, char *from, char *to)
return(-1);
if (asprintf(&key, "%s\n%s\n%s", ip, from, to) == -1)
goto bad;
+ if (asprintf(&trap, "%s",to) == -1)
+ goto bad;
+ for (i = 0; trap[i] != '\0'; i++)
+ if (isupper(trap[i]))
+ trap[i] = tolower(trap[i]);
+ memset(&dbk, 0, sizeof(dbk));
+ dbk.size = strlen(trap);
+ dbk.data = trap;
+ memset(&dbd, 0, sizeof(dbd));
+ r = db->get(db, &dbk, &dbd, 0);
+ if (r == -1)
+ goto bad;
+ if (r) {
+ /* didn't exist - so this doesn't match a known spamtrap */
+ spamtrap = 0;
+ lookup = key;
+ expire = greyexp;
+ } else {
+ /* To: address is a spamtrap, so add as a greytrap entry */
+ spamtrap = 1;
+ lookup = ip;
+ expire = trapexp;
+ }
memset(&dbk, 0, sizeof(dbk));
- dbk.size = strlen(key);
- dbk.data = key;
+ dbk.size = strlen(lookup);
+ dbk.data = lookup;
memset(&dbd, 0, sizeof(dbd));
r = db->get(db, &dbk, &dbd, 0);
if (r == -1)
@@ -324,11 +440,12 @@ greyupdate(char *dbname, char *ip, char *from, char *to)
memset(&gd, 0, sizeof(gd));
gd.first = now;
gd.bcount = 1;
- gd.pass = now + greyexp;
- gd.expire = now + greyexp;
+ gd.pcount = spamtrap ? -1 : 0;
+ gd.pass = now + expire;
+ gd.expire = now + expire;
memset(&dbk, 0, sizeof(dbk));
- dbk.size = strlen(key);
- dbk.data = key;
+ dbk.size = strlen(lookup);
+ dbk.data = lookup;
memset(&dbd, 0, sizeof(dbd));
dbd.size = sizeof(gd);
dbd.data = &gd;
@@ -337,7 +454,8 @@ greyupdate(char *dbname, char *ip, char *from, char *to)
if (r)
goto bad;
if (debug)
- fprintf(stderr, "added %s\n", key);
+ fprintf(stderr, "added %s %s\n",
+ spamtrap ? "greytrap entry for" : "", lookup);
} else {
/* existing entry */
if (dbd.size != sizeof(gd)) {
@@ -348,11 +466,12 @@ greyupdate(char *dbname, char *ip, char *from, char *to)
}
memcpy(&gd, dbd.data, sizeof(gd));
gd.bcount++;
+ gd.pcount = spamtrap ? -1 : 0;
if (gd.first + passtime < now)
gd.pass = now;
memset(&dbk, 0, sizeof(dbk));
- dbk.size = strlen(key);
- dbk.data = key;
+ dbk.size = strlen(lookup);
+ dbk.data = lookup;
memset(&dbd, 0, sizeof(dbd));
dbd.size = sizeof(gd);
dbd.data = &gd;
@@ -361,16 +480,20 @@ greyupdate(char *dbname, char *ip, char *from, char *to)
if (r)
goto bad;
if (debug)
- fprintf(stderr, "updated %s\n", key);
+ fprintf(stderr, "updated %s\n", lookup);
}
free(key);
key = NULL;
+ free(trap);
+ trap = NULL;
db->close(db);
db = NULL;
return(0);
bad:
free(key);
key = NULL;
+ free(trap);
+ trap = NULL;
db->close(db);
db = NULL;
return(-1);
diff --git a/libexec/spamd/grey.h b/libexec/spamd/grey.h
index 2d6ae43c92b..de5c6845c38 100644
--- a/libexec/spamd/grey.h
+++ b/libexec/spamd/grey.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: grey.h,v 1.4 2004/10/05 21:04:36 beck Exp $ */
+/* $OpenBSD: grey.h,v 1.5 2005/03/11 23:09:53 beck Exp $ */
/*
* Copyright (c) 2004 Bob Beck. All rights reserved.
@@ -20,6 +20,7 @@
#define PASSTIME (60 * 25) /* pass after first retry seen after 25 mins */
#define GREYEXP (60 * 60 * 4) /* remove grey entries after 4 hours */
#define WHITEEXP (60 * 60 * 24 * 36) /* remove white entries after 36 days */
+#define TRAPEXP (60 * 60 * 24) /* hitting a spamtrap blacklists for a day */
#define PATH_PFCTL "/sbin/pfctl"
#define DB_SCAN_INTERVAL 60
#define PATH_SPAMD_DB "/var/db/spamd"
@@ -29,7 +30,7 @@ struct gdata {
time_t pass; /* when was it whitelisted */
time_t expire; /* when will we get rid of this entry */
int bcount; /* how many times have we blocked it */
- int pcount; /* how many good connections have we seen after wl */
+ int pcount; /* how many times passed, or -1 for spamtrap */
};
extern int greywatcher(void);
diff --git a/libexec/spamd/spamd.8 b/libexec/spamd/spamd.8
index 299f519c5e6..6f4191b7ae4 100644
--- a/libexec/spamd/spamd.8
+++ b/libexec/spamd/spamd.8
@@ -1,4 +1,4 @@
-.\" $OpenBSD: spamd.8,v 1.52 2005/01/19 17:07:31 deraadt Exp $
+.\" $OpenBSD: spamd.8,v 1.53 2005/03/11 23:09:53 beck Exp $
.\"
.\" Copyright (c) 2002 Theo de Raadt. All rights reserved.
.\"
@@ -337,6 +337,38 @@ should be used to update the whitelist entries in
when connections are seen to pass to the real MTA on the
.Em smtp
port.
+.Sh GREYTRAPPING
+When greylisting with
+.Nm
+it may be useful to define
+.Em spamtrap
+destination addresses to catch spammers as they send mail from greylisted
+hosts.
+Such
+.Em spamtrap
+addresses affect only greylisted connections to
+.Nm
+and are used to temporarily blacklist a host that is obviously sending spam.
+Unused email addresses or email addresses on spammers' lists are very
+useful for this.
+When a host that is currently greylisted attempts to send mail to a
+.Em spamtrap
+address, it is blacklisted for 24 hours by adding the host to the
+.Nm
+blacklist
+.Em spamd-greytrap .
+Spamtrap addresses are added to the
+.Pa /var/db/spamd
+database with the following
+.Xr spamdb 8
+command:
+.Pp
+.Dl spamdb -T -a \&"<spamtrap@mydomain.org>\&"
+.Pp
+It should be entered exactly as the address will be used in the SMTP dialogue.
+See
+.Xr spamdb 8
+for further details.
.Sh LOGGING
.Nm
sends log messages to
diff --git a/libexec/spamd/spamd.c b/libexec/spamd/spamd.c
index 269d485c12f..52a73bac2e9 100644
--- a/libexec/spamd/spamd.c
+++ b/libexec/spamd/spamd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: spamd.c,v 1.74 2004/11/17 15:29:38 beck Exp $ */
+/* $OpenBSD: spamd.c,v 1.75 2005/03/11 23:09:53 beck Exp $ */
/*
* Copyright (c) 2002 Theo de Raadt. All rights reserved.
@@ -105,16 +105,21 @@ char *reply = NULL;
char *nreply = "450";
char *spamd = "spamd IP-based SPAM blocker";
int greypipe[2];
+int trappipe[2];
FILE *grey;
+FILE *trapcfg;
time_t passtime = PASSTIME;
time_t greyexp = GREYEXP;
time_t whiteexp = WHITEEXP;
+time_t trapexp = TRAPEXP;
struct passwd *pw;
pid_t jail_pid = -1;
+u_short cfg_port;
extern struct sdlist *blacklists;
int conffd = -1;
+int trapfd = -1;
char *cb;
size_t cbs, cbu;
@@ -171,9 +176,6 @@ parse_configline(char *line)
size_t au = 0;
int mdone = 0;
- if (debug > 0)
- printf("read config line %40s ...\n", line);
-
name = line;
for (cp = name; *cp && *cp != ';'; cp++)
@@ -227,7 +229,11 @@ parse_configline(char *line)
}
} while ((av[au++] = strsep(&cp, ";")) != NULL);
- if (au < 2)
+ /* toss empty last entry to allow for trailing ; */
+ if (av[au - 1][0] == '\0');
+ au--;
+
+ if (au < 1)
goto parse_error;
else
sdl_add(name, msg, av, au - 1);
@@ -321,6 +327,26 @@ configdone:
conffd = -1;
}
+
+int
+read_configline(FILE *config)
+{
+ char *buf;
+ size_t len;
+
+ if ((buf = fgetln(config, &len))) {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ else
+ return(-1); /* all valid lines end in \n */
+ parse_configline(buf);
+ } else {
+ syslog_r(LOG_DEBUG, &sdata, "read_configline: fgetln (%m)");
+ return(-1);
+ }
+ return(0);
+}
+
int
append_error_string(struct con *cp, size_t off, char *fmt, int af, void *ia)
{
@@ -907,7 +933,7 @@ main(int argc, char *argv[])
struct sockaddr_in lin;
int ch, s, s2, conflisten = 0, i, omax = 0, one = 1;
socklen_t sinlen;
- u_short port, cfg_port;
+ u_short port;
struct servent *ent;
struct rlimit rlp;
char *bind_address = NULL;
@@ -1074,6 +1100,11 @@ main(int argc, char *argv[])
syslog(LOG_ERR, "pipe (%m)");
exit(1);
}
+ /* open pipe to recieve spamtrap configs */
+ if (pipe(trappipe) == -1) {
+ syslog(LOG_ERR, "pipe (%m)");
+ exit(1);
+ }
jail_pid = fork();
switch(jail_pid) {
case -1:
@@ -1081,21 +1112,34 @@ main(int argc, char *argv[])
exit(1);
case 0:
/* child - continue */
- close(greypipe[0]);
grey = fdopen(greypipe[1], "w");
if (grey == NULL) {
syslog(LOG_ERR, "fdopen (%m)");
_exit(1);
}
+ close(greypipe[0]);
+ trapfd = trappipe[0];
+ trapcfg = fdopen(trappipe[0], "r");
+ if (trapcfg == NULL) {
+ syslog(LOG_ERR, "fdopen (%m)");
+ _exit(1);
+ }
+ close(trappipe[1]);
goto jail;
}
/* parent - run greylister */
- close(greypipe[1]);
grey = fdopen(greypipe[0], "r");
if (grey == NULL) {
syslog(LOG_ERR, "fdopen (%m)");
exit(1);
}
+ close(greypipe[1]);
+ trapcfg = fdopen(trappipe[1], "w");
+ if (trapcfg == NULL) {
+ syslog(LOG_ERR, "fdopen (%m)");
+ exit(1);
+ }
+ close(trappipe[0]);
return(greywatcher());
/* NOTREACHED */
}
@@ -1131,6 +1175,7 @@ jail:
max = MAX(s, conflisten);
max = MAX(max, conffd);
+ max = MAX(max, trapfd);
time(&t);
for (i = 0; i < maxcon; i++)
@@ -1184,6 +1229,8 @@ jail:
FD_SET(conflisten, fdsr);
else
FD_SET(conffd, fdsr);
+ if (trapfd != -1)
+ FD_SET(trapfd, fdsr);
if (writers == 0) {
tvp = NULL;
@@ -1242,10 +1289,10 @@ jail:
conffd = -1;
}
}
- if (conffd != -1 && FD_ISSET(conffd, fdsr)) {
+ if (conffd != -1 && FD_ISSET(conffd, fdsr))
do_config();
- }
-
+ if (trapfd != -1 && FD_ISSET(trapfd, fdsr))
+ read_configline(trapcfg);
}
exit(1);
}