diff options
author | Bob Beck <beck@cvs.openbsd.org> | 2005-03-11 23:09:54 +0000 |
---|---|---|
committer | Bob Beck <beck@cvs.openbsd.org> | 2005-03-11 23:09:54 +0000 |
commit | 055086c57f1d2f2eae3e4ecfce8c6b2553a4c28e (patch) | |
tree | 6e802ef9dbf99eeefc4fbbe71f626683e5cce5df /libexec | |
parent | c9c43400621b4aa0b087174b82fdea6b0928a641 (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.c | 199 | ||||
-rw-r--r-- | libexec/spamd/grey.h | 5 | ||||
-rw-r--r-- | libexec/spamd/spamd.8 | 34 | ||||
-rw-r--r-- | libexec/spamd/spamd.c | 69 |
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); } |