diff -ruN spamd-4.1.2_bak/spamd/Makefile spamd-4.1.2/spamd/Makefile --- spamd-4.1.2_bak/spamd/Makefile Wed Apr 4 12:20:26 2007 +++ spamd-4.1.2/spamd/Makefile Fri Sep 14 20:11:21 2007 @@ -16,7 +16,7 @@ .endif PROG= spamd -SRCS= spamd.c sdl.c grey.c sync.c +SRCS= spamd.c sdl.c grey.c sync.c crns_rbl.c .if ${OSVERSION} < 601000 SRCS+= strtonum.c @@ -24,9 +24,9 @@ MAN= spamd.8 -CFLAGS+= -Wall -Wstrict-prototypes +CFLAGS+= -Wall -Wstrict-prototypes -I/usr/local/include/ -LDADD+= -lcrypto -lmd +LDADD+= -L/usr/local/lib/ -lcrypto -lmd -ladns DPADD+= ${LIBCRYPTO} ${LIBMD} .include diff -ruN spamd-4.1.2_bak/spamd/README-CRNS.txt spamd-4.1.2/spamd/README-CRNS.txt --- spamd-4.1.2_bak/spamd/README-CRNS.txt Thu Jan 1 01:00:00 1970 +++ spamd-4.1.2/spamd/README-CRNS.txt Sat Sep 15 00:40:49 2007 @@ -0,0 +1,19 @@ + +20070914: First patchfix (p1) + +- SLIST_REMOVE_HEAD() on FreeBSD requiers a free() of the element removed + from the list. This fix should stop spamd(greywatcher) from growing. +- Fixed a race condition or better a problem with concurency on db_notin(). + On FreeBSD, I do simply open the DB for Read-Only to check if the + IP is in the DB, while other processes can access it for WRITE. + It stopped spamd from damagin the DB, and going crazy after + first round of expires... +- Added more output via syslog - you may not like it, but I don't care ;] +- Small feature can be found in crns_rbl.c, you can define (in code) + rbl list to ask while doing cleanup of the DB, every IP + which should expire due to graylisting and listed on RBL + will be added as TRAPPED + REQUIED: libadns +- Changed the way we blacklist IP when MAIL FROM: is <>, this will prevent + machines doing callouts (like exim does) being trapped. +- Added few BufferOverflows and other insecurity to your beloved spamd diff -ruN spamd-4.1.2_bak/spamd/crns_rbl.c spamd-4.1.2/spamd/crns_rbl.c --- spamd-4.1.2_bak/spamd/crns_rbl.c Thu Jan 1 01:00:00 1970 +++ spamd-4.1.2/spamd/crns_rbl.c Sat Sep 15 01:02:27 2007 @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2007 + * Rafal Lesniak, CreativeNet Service GmbH. 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 "crns_rbl.h" + +#ifdef __OpenBSD__ +extern struct syslog_data sdata; +#else +#define syslog_r(l, s, args...) syslog(l,args) +#define openlog_r(i, l, f, s) openlog(i, l, f) +extern int sdata; /* dummy */ +#endif + +int crns_debug = 0; + +char *crns_rbls[] = { + "cbl.abuseat.org:127.0.0.2", + "dnsbl.sorbs.net:127.0.0.10", + "dnsbl.sorbs.net:127.0.0.9", + "dnsbl.sorbs.net:127.0.0.7", + NULL + }; + +/** + * Reverse the IP + */ +char *crns_reverse_ip(char *ip) { + + char *new[4]; + char *ret,*src; + char *word, *brkt; + u_int cnt=0,len=0; + + src = strdup(ip); + + for(word = strtok_r(src, ".", &brkt); + word; + word = strtok_r(NULL, ".", &brkt)) + { + if (cnt == 4) + return(NULL); + + new[cnt] = strdup(word); + len += strlen(word); + cnt++; + } + + ret = calloc(len+5, sizeof(char)); + snprintf(ret, len+5, "%s.%s.%s.%s", new[3], new[2], new[1], new[0]); + + free(src); + free(new[0]); + free(new[1]); + free(new[2]); + free(new[3]); + + return(ret); +} + +/** + * Wrapper for checking all defined lists + */ +int crns_proceed_rbl(char *ip) { + int i; + for(i=0; + crns_rbls[i]; + i++) { + if(_crns_proceed_rbl(ip,crns_rbls[i])==1) + return(1); + } + return(0); +} + +/** + * Check the IP against defined RBL lists + * -1 - error + * 0 - not listed + * 1 - listed + */ +int _crns_proceed_rbl(char *ip,char *rblin) { + + char *valid, *rbl, *rev, *trbl, *typename, *datastr=NULL; + adns_state adns; + adns_query query; + adns_answer *answer; + adns_status st; + int ret=0; + size_t toalloc; + + rev = crns_reverse_ip(ip); + if (rev == NULL) + return(-1); + + trbl = strdup(rblin); + valid = strchr(trbl,':'); + if (valid != NULL) + *valid = '\0'; + + toalloc = strlen(trbl) + strlen(rev) + 12; + rbl = calloc(toalloc, sizeof(char)); + snprintf(rbl, toalloc, "%s.%s", rev, trbl); + + if (crns_debug) + { + fprintf(stderr, "[D] checking %s -> %s (%s)\n", + ip, + rbl, + (valid+1)); + } + + if(adns_init(&adns, 0, 0)) + goto bad; + + if (adns_submit(adns, rbl, adns_r_a, + adns_qf_quoteok_cname|adns_qf_cname_loose, + NULL, &query)) + goto bad; + + if (adns_wait(adns, &query, &answer, NULL)) + goto bad; + + if (answer->status != adns_s_ok) + goto end; + else + { + if (answer->status == adns_s_ok) + { + st= adns_rr_info(answer->type, &typename, 0,0, answer->rrs.untyped,&datastr); + if (strcmp(datastr,(valid+1)) == 0) + ret = 1; + else + ret = 0; + } + else + ret = 0; + } + + goto end; + + bad: + ret = -1; + goto end; + + end: + adns_finish(adns); + free(rbl); + free(rev); + free(answer); + if (datastr!=NULL) + free(datastr); + + if (crns_debug) + fprintf(stderr, "[D] %s matching %s\n", + (ret) ? "found" : "not found", + (valid+1)); + + free(trbl); + + return(ret); +} + +/* +int main(int argc, char **argv) { + return(crns_proceed_rbl(argv[1])); +} +*/ diff -ruN spamd-4.1.2_bak/spamd/crns_rbl.h spamd-4.1.2/spamd/crns_rbl.h --- spamd-4.1.2_bak/spamd/crns_rbl.h Thu Jan 1 01:00:00 1970 +++ spamd-4.1.2/spamd/crns_rbl.h Sat Sep 15 01:02:56 2007 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2007 + * Rafal Lesniak, CreativeNet Service GmbH. 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adns.h" + +char *crns_reverse_ip(char *src); +int crns_proceed_rbl(char *ip); +int _crns_proceed_rbl(char *ip, char *rblin); + diff -ruN spamd-4.1.2_bak/spamd/grey.c spamd-4.1.2/spamd/grey.c --- spamd-4.1.2_bak/spamd/grey.c Wed Jun 6 12:43:19 2007 +++ spamd-4.1.2/spamd/grey.c Sat Sep 15 12:44:51 2007 @@ -2,6 +2,7 @@ /* * Copyright (c) 2004-2006 Bob Beck. All rights reserved. + * 2007 Rafal Lesniak, CreativeNet Service GmbH * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -47,6 +48,7 @@ #include "grey.h" #include "sync.h" +#include "crns_rbl.h" extern time_t passtime, greyexp, whiteexp, trapexp; #ifdef __OpenBSD__ @@ -118,6 +120,9 @@ "spamd-white", "-T", "replace", "-f" "-", NULL }; + + + /* If the parent gets a signal, kill off the children and exit */ /* ARGSUSED */ static void @@ -538,16 +543,19 @@ struct db_change *dbc; if ((dbc = malloc(sizeof(*dbc))) == NULL) { - syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)"); + syslog_r(LOG_DEBUG, &sdata, "[%s:%s:%d] malloc failed (queue change)", + __FILE__, __FUNCTION__, __LINE__); return(-1); } if ((dbc->key = strdup(key)) == NULL) { - syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)"); + syslog_r(LOG_DEBUG, &sdata, "[%s:%s:%d] malloc failed (queue change)", + __FILE__, __FUNCTION__, __LINE__); free(dbc); return(-1); } if ((dbc->data = malloc(dsiz)) == NULL) { - syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)"); + syslog_r(LOG_DEBUG, &sdata, "[%s:%s:%d] malloc failed (queue change)", + __FILE__, __FUNCTION__, __LINE__); free(dbc->key); free(dbc); return(-1); @@ -555,9 +563,12 @@ memcpy(dbc->data, data, dsiz); dbc->dsiz = dsiz; dbc->act = act; - syslog_r(LOG_DEBUG, &sdata, - "queueing %s of %s", ((act == DBC_ADD) ? "add" : "deletion"), - dbc->key); + if (debug) + syslog_r(LOG_DEBUG, &sdata, + "[%s:%s:%d] queueing %s of %s", + __FILE__, __FUNCTION__, __LINE__, ((act == DBC_ADD) ? "add" : "deletion"), dbc->key); + + //FIXME: alloc - where is free? SLIST_INSERT_HEAD(&db_changes, dbc, entry); return(0); } @@ -569,6 +580,9 @@ struct db_change *dbc; int ret = 0; + syslog_r(LOG_DEBUG, &sdata, "[%s:%s:%d] started", + __FILE__, __FUNCTION__, __LINE__); + while (!SLIST_EMPTY(&db_changes)) { dbc = SLIST_FIRST(&db_changes); switch (dbc->act) { @@ -582,7 +596,9 @@ if (db->put(db, &dbk, &dbd, 0)) { db->sync(db, 0); syslog_r(LOG_ERR, &sdata, - "can't add %s to spamd db (%m)", dbc->key); + "[%s:%s:%d] can't add %s to spamd db (%m)", + __FILE__, __FUNCTION__, __LINE__, + dbc->key); ret = -1; } db->sync(db, 0); @@ -593,13 +609,15 @@ dbk.data = dbc->key; if (db->del(db, &dbk, 0)) { syslog_r(LOG_ERR, &sdata, - "can't delete %s from spamd db (%m)", + "[%s:%s:%d] can't delete %s from spamd db (%m)", + __FILE__, __FUNCTION__, __LINE__, dbc->key); ret = -1; } break; default: - syslog_r(LOG_ERR, &sdata, "Unrecognized db change"); + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] Unrecognized db change", + __FILE__, __FUNCTION__, __LINE__); ret = -1; } free(dbc->key); @@ -609,30 +627,59 @@ dbc->act = 0; dbc->dsiz = 0; SLIST_REMOVE_HEAD(&db_changes, entry); +#if __FreeBSD__ + //FreeBSD queue is little different ;-) + if (debug) + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] free db_change", + __FILE__, __FUNCTION__, __LINE__); + free(dbc); +#endif + } + syslog_r(LOG_DEBUG, &sdata, "[%s:%s:%d] done ret:%d", + __FILE__, __FUNCTION__, __LINE__,ret); return(ret); } int -db_notin(DB *db, char *key) +db_notin(char *key) { - int i; + int i,ret=-1; DBT dbk, dbd; + DB *db; + + db = dbopen(PATH_SPAMD_DB, O_RDONLY, 0600, DB_HASH, NULL); + if (db == NULL) { + syslog_r(LOG_INFO, &sdata, "[%s:%s:%d] dbopen failed", + __FILE__, __FUNCTION__, __LINE__); + ret = -1; + } memset(&dbk, 0, sizeof(dbk)); + if (debug) + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] checking key:%s", + __FILE__, __FUNCTION__, __LINE__, key); + dbk.size = strlen(key); dbk.data = key; memset(&dbd, 0, sizeof(dbd)); i = db->get(db, &dbk, &dbd, 0); if (i == -1) - return (-1); + ret=-1; if (i) /* not in the database */ - return (1); + ret=1; else /* it is in the database */ - return (0); + ret=0; + + if (debug) + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] done on key:%s ret:%d", + __FILE__, __FUNCTION__, __LINE__, key, ret); + + db->close(db); + return(ret); } @@ -648,6 +695,9 @@ size_t asiz = 0; time_t now = time(NULL); + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] started", + __FILE__, __FUNCTION__, __LINE__); + /* walk db, expire, and whitelist */ memset(&hashinfo, 0, sizeof(hashinfo)); db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo); @@ -659,8 +709,10 @@ memset(&dbd, 0, sizeof(dbd)); for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r; r = db->seq(db, &dbk, &dbd, R_NEXT)) { - if ((dbk.size < 1) || dbd.size != sizeof(struct gdata)) { - syslog_r(LOG_ERR, &sdata, "bogus entry in spamd database"); + + if ((dbk.size < 1) || (dbd.size != sizeof(struct gdata))) { + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] bogus entry in spamd database", + __FILE__, __FUNCTION__, __LINE__); goto bad; } if (asiz < dbk.size + 1) { @@ -675,15 +727,44 @@ memset(a, 0, asiz); memcpy(a, dbk.data, dbk.size); memcpy(&gd, dbd.data, sizeof(gd)); - if (gd.expire <= now && gd.pcount != -2) { + + if ((gd.expire <= now) && (gd.pcount != -2)) { + char *cp = NULL; + int rbl=0; + /* get rid of entry */ if (queue_change(a, NULL, 0, DBC_DEL) == -1) goto bad; - } else if (gd.pcount == -1) { + + cp = strchr(a,'\n'); + if (cp != NULL) + *cp = '\0'; + + if (crns_proceed_rbl(a)) + { + rbl = 1; + + gd.expire = now + 86400; + gd.pcount = -1; + dbd.size = sizeof(gd); + dbd.data = &gd; + if (queue_change(a, (void *) &gd, sizeof(gd), + DBC_ADD) == -1) + goto bad; + } + + if (cp != NULL) + *cp = '\n'; + + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] expire %s %s", + __FILE__, __FUNCTION__, __LINE__, (rbl) ? "(RBL)" : "", a); + + } else if (gd.pcount == -1) { /* this is a greytrap hit */ if ((addtrapaddr(a) == -1) && (queue_change(a, NULL, 0, DBC_DEL) == -1)) goto bad; + } else if (gd.pcount >= 0 && gd.pass <= now) { int tuple = 0; char *cp; @@ -699,15 +780,34 @@ } if (addwhiteaddr(a) == -1) { + if (cp != NULL) + *cp = '\n'; + + if (queue_change(a, NULL, 0, DBC_DEL) == -1) + goto bad; + } + + if (tuple && db_notin(a)) { + + if (cp != NULL) + *cp = '\0'; + + if (crns_proceed_rbl(a)) + { + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] (RBL) %s", + __FILE__, __FUNCTION__, __LINE__, a); + if (cp != NULL) - *cp = '\n'; + *cp = '\n'; + if (queue_change(a, NULL, 0, DBC_DEL) == -1) - goto bad; - } + goto bad; + + } - if (tuple && db_notin(db, a)) { if (cp != NULL) *cp = '\0'; + /* re-add entry, keyed only by ip */ gd.expire = now + whiteexp; dbd.size = sizeof(gd); @@ -715,25 +815,34 @@ if (queue_change(a, (void *) &gd, sizeof(gd), DBC_ADD) == -1) goto bad; + syslog_r(LOG_DEBUG, &sdata, - "whitelisting %s in %s", a, dbname); - } - if (debug) + "[%s:%s:%d] whitelisting %s in %s", + __FILE__, __FUNCTION__, __LINE__, a, dbname); + + if (debug) fprintf(stderr, "whitelisted %s\n", a); - } + } + } } (void) do_changes(db); db->close(db); db = NULL; - if(use_pf) configure_pf(whitelist, whitecount); - else configure_ipfw(whitelist, whitecount); + if(use_pf) + configure_pf(whitelist, whitecount); + else + configure_ipfw(whitelist, whitecount); + if (configure_spamd(traplist, trapcount, trapcfg) == -1) syslog_r(LOG_DEBUG, &sdata, "configure_spamd failed"); freeaddrlists(); free(a); a = NULL; + asiz = 0; + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] end (ok)", + __FILE__, __FUNCTION__, __LINE__); return(0); bad: (void) do_changes(db); @@ -741,8 +850,13 @@ db = NULL; freeaddrlists(); free(a); + a = NULL; asiz = 0; + + syslog_r(LOG_ERR, &sdata, "[%s:%s:%d] end (errors)", + __FILE__, __FUNCTION__, __LINE__); + return(-1); } @@ -838,7 +952,9 @@ fprintf(stderr, "added %s %s\n", spamtrap ? "trap entry for" : "", ip); syslog_r(LOG_DEBUG, &sdata, - "New %s from %s for %s, expires %s", what, source, ip, + "[%s:%s:%d] New %s from %s for %s, expires %s", + __FILE__, __FUNCTION__, __LINE__, + what, source, ip, expires); } else { /* existing entry */ @@ -897,7 +1013,15 @@ return(-1); if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1) goto bad; + r = trapcheck(db, to); + + if (r == 0 && strncmp(from,"<>",3)==0) { + syslog_r(LOG_DEBUG, &sdata, "[%s:%s:%d] Not Trapping %s for tuple %s", + __FILE__, __FUNCTION__, __LINE__, ip, key); + r = 1; + } + switch (r) { case 1: /* do not trap */ @@ -910,7 +1034,8 @@ spamtrap = 1; lookup = ip; expire = trapexp; - syslog_r(LOG_DEBUG, &sdata, "Trapping %s for tuple %s", ip, + syslog_r(LOG_DEBUG, &sdata, "[%s:%s:%d] Trapping %s for tuple %s", + __FILE__, __FUNCTION__, __LINE__, ip, key); break; default: @@ -938,7 +1063,8 @@ lookup = ip; expire = trapexp; syslog_r(LOG_DEBUG, &sdata, - "Trapping %s for trying %s first for tuple %s", + "[%s:%s:%d] Trapping %s for trying %s first for tuple %s", + __FILE__, __FUNCTION__, __LINE__, ip, low_prio_mx_ip, key); } memset(&gd, 0, sizeof(gd)); @@ -995,7 +1121,8 @@ if (syncsend && sync) { if (spamtrap) { syslog_r(LOG_DEBUG, &sdata, - "sync_trap %s", ip); + "[%s:%s:%d] sync_trap %s", + __FILE__, __FUNCTION__, __LINE__, ip); sync_trapped(now, now + expire, ip); } else @@ -1129,7 +1256,8 @@ { for (;;) { if (greyscan(PATH_SPAMD_DB) == -1) - syslog_r(LOG_NOTICE, &sdata, "scan of %s failed", + syslog_r(LOG_NOTICE, &sdata, "[%s:%s:%d] scan of %s failed", + __FILE__, __FUNCTION__, __LINE__, PATH_SPAMD_DB); sleep(DB_SCAN_INTERVAL); }