This is a noissy "badrcptto" patch for systems that have a lot of addresses they want to reject, and want to log what happens Addresses to reject go in control/badrcptto and control/morebadrcptto, one per line, just like badmailfrom. control/morebadrcptto is compiled into control/morebadrcptto.cdb with the new program qmail-newbrt. I'd suggest putting all of your bad addresses into morebadrcptto since it's a lot faster to look something up in a CDB than in a text file. Whenever qmail-smtp rejects a message or otherwise fails, it writes a descriptive message to stderr, which if you're running under daemontools should end up in the log file. The logging code is adapted from an older anti-spam patch contributed to qmail.org. Unlike the previous badrcptto patch, this one does not reject the message at RCPT TO time. Instead, it waits until the DATA command to issue a 550 rejection. This has two effects: one is to deter dictionary attackers which are perversely encouraged by RCPT TO rejections. (If some of the addresses are bad, the rest must be good.) The other is that if a message has a mixture of bad and good addresses, the message will be rejected anyway. That's a feature, since in my experience, any message addressed to a spam-only address is spam even if it's also addressed to legit addresses. This version also does not let you list domains in the badrcptto files. There's no point in doing so, since all domains not in rcpthosts are rejected, anyway. This patch contains the entire new source file for qmail-newbrt.c as a diff against an empty file. If patch gets mad at you, just create the empty file. No warranty expressed or implied. If you find bugs, feel free to fix them and please send me the fix. John Levine, johnl@iecc.com, 4/03 diff -u qmail-1.03-dist/Makefile qmail-1.03/Makefile --- qmail-1.03-dist/Makefile Mon Jun 15 06:53:16 1998 +++ qmail-1.03/Makefile Wed Apr 9 01:30:20 2003 @@ -803,7 +803,7 @@ predate datemail mailsubj qmail-upq qmail-showctl qmail-newu \ qmail-pw2u qmail-qread qmail-qstat qmail-tcpto qmail-tcpok \ qmail-pop3d qmail-popup qmail-qmqpc qmail-qmqpd qmail-qmtpd \ -qmail-smtpd sendmail tcp-env qmail-newmrh config config-fast dnscname \ +qmail-smtpd sendmail tcp-env qmail-newmrh qmail-newbrt config config-fast dnscname \ dnsptr dnsip dnsmxip dnsfq hostname ipmeprint qreceipt qsmhook qbiff \ forward preline condredirect bouncesaying except maildirmake \ maildir2mbox maildirwatch qail elq pinq idedit install-big install \ @@ -1241,6 +1241,19 @@ uint32.h substdio.h ./compile qmail-newmrh.c +qmail-newbrt: \ +load qmail-newbrt.o cdbmss.o getln.a open.a cdbmake.a seek.a case.a \ +stralloc.a alloc.a strerr.a substdio.a error.a str.a auto_qmail.o + ./load qmail-newbrt cdbmss.o getln.a open.a cdbmake.a \ + seek.a case.a stralloc.a alloc.a strerr.a substdio.a \ + error.a str.a auto_qmail.o + +qmail-newbrt.o: \ +compile qmail-newbrt.c strerr.h stralloc.h gen_alloc.h substdio.h \ +getln.h exit.h readwrite.h open.h auto_qmail.h cdbmss.h cdbmake.h \ +uint32.h substdio.h + ./compile qmail-newbrt.c + qmail-newu: \ load qmail-newu.o cdbmss.o getln.a open.a seek.a cdbmake.a case.a \ stralloc.a alloc.a substdio.a error.a str.a auto_qmail.o diff -u qmail-1.03-dist/qmail-newbrt.c qmail-1.03/qmail-newbrt.c --- qmail-1.03-dist/qmail-newbrt.c Wed Apr 9 16:31:27 2003 +++ qmail-1.03/qmail-newbrt.c Wed Apr 9 16:18:50 2003 @@ -0,0 +1,70 @@ +#include "strerr.h" +#include "stralloc.h" +#include "substdio.h" +#include "getln.h" +#include "exit.h" +#include "readwrite.h" +#include "open.h" +#include "auto_qmail.h" +#include "cdbmss.h" + +#define FATAL "qmail-newbrt: fatal: " + +void die_read() +{ + strerr_die2sys(111,FATAL,"unable to read control/morebadrcptto: "); +} +void die_write() +{ + strerr_die2sys(111,FATAL,"unable to write to control/morebadrcptto.tmp: "); +} + +char inbuf[1024]; +substdio ssin; + +int fd; +int fdtemp; + +struct cdbmss cdbmss; +stralloc line = {0}; +int match; + +void main() +{ + umask(033); + if (chdir(auto_qmail) == -1) + strerr_die4sys(111,FATAL,"unable to chdir to ",auto_qmail,": "); + + fd = open_read("control/morebadrcptto"); + if (fd == -1) die_read(); + + substdio_fdbuf(&ssin,read,fd,inbuf,sizeof inbuf); + + fdtemp = open_trunc("control/morebadrcptto.tmp"); + if (fdtemp == -1) die_write(); + + if (cdbmss_start(&cdbmss,fdtemp) == -1) die_write(); + + for (;;) { + if (getln(&ssin,&line,&match,'\n') != 0) die_read(); + case_lowerb(line.s,line.len); + while (line.len) { + if (line.s[line.len - 1] == ' ') { --line.len; continue; } + if (line.s[line.len - 1] == '\n') { --line.len; continue; } + if (line.s[line.len - 1] == '\t') { --line.len; continue; } + if (line.s[0] != '#') + if (cdbmss_add(&cdbmss,line.s,line.len,"",0) == -1) + die_write(); + break; + } + if (!match) break; + } + + if (cdbmss_finish(&cdbmss) == -1) die_write(); + if (fsync(fdtemp) == -1) die_write(); + if (close(fdtemp) == -1) die_write(); /* NFS stupidity */ + if (rename("control/morebadrcptto.tmp","control/morebadrcptto.cdb") == -1) + strerr_die2sys(111,FATAL,"unable to move control/morebadrcpto.tmp to control/morebadrcptto.cdb"); + + _exit(0); +} diff -u qmail-1.03-dist/qmail-smtpd.c qmail-1.03/qmail-smtpd.c --- qmail-1.03-dist/qmail-smtpd.c Mon Jun 15 06:53:16 1998 +++ qmail-1.03/qmail-smtpd.c Wed Apr 9 17:32:15 2003 @@ -23,11 +23,18 @@ #include "timeoutread.h" #include "timeoutwrite.h" #include "commands.h" +#include "cdb.h" #define MAXHOPS 100 unsigned int databytes = 0; int timeout = 1200; +char *remoteip; +char *remotehost; +char *remoteinfo; +char *local; +char *relayclient; + int safewrite(fd,buf,len) int fd; char *buf; int len; { int r; @@ -39,17 +46,49 @@ char ssoutbuf[512]; substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf); +/* write errors to stderr */ +char erroutbuf[512]; +substdio errout = SUBSTDIO_FDBUF(safewrite,2,erroutbuf,sizeof erroutbuf); + void flush() { substdio_flush(&ssout); } void out(s) char *s; { substdio_puts(&ssout,s); } +void eflush() { substdio_flush(&errout); } +void eout(s) char *s; { substdio_puts(&errout,s); } +void enew() { substdio_puts(&errout,"qmail-smtpd: "); } + void die_read() { _exit(1); } -void die_alarm() { out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); } -void die_nomem() { out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); } -void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); } -void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); } -void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); } +void die_alarm() +{ + enew(); eout("Connection to "); eout(remoteip); eout(" timed out.\n"); + out("451 timeout (#4.4.2)\r\n"); flush(); eflush(); _exit(1); +} +void die_nomem() +{ + enew(); eout("Out of memory while connected to "); eout(remoteip); eout("!\n"); + out("421 out of memory (#4.3.0)\r\n"); flush(); eflush(); _exit(1); +} +void die_control() +{ + enew(); eout("Unable to read controls!\n"); + out("421 unable to read controls (#4.3.0)\r\n"); flush(); eflush(); + _exit(1); +} +void die_ipme() +{ + enew(); eout("Unable to figure out my IP addresses!\n"); + out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); + eflush(); _exit(1); +} +void straynewline() +{ + enew(); eout("Stray newline from "); eout(remoteip); eout(".\n"); + out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); + eflush(); _exit(1); +} void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } +void err_brt() { out("550 sorry, this message is not deliverable (#5.7.1)\r\n"); } void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); } void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); } void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); } @@ -76,12 +115,6 @@ smtp_greet("221 "); out("\r\n"); flush(); _exit(0); } -char *remoteip; -char *remotehost; -char *remoteinfo; -char *local; -char *relayclient; - stralloc helohost = {0}; char *fakehelo; /* pointer into helohost, or 0 */ @@ -96,6 +129,10 @@ int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; +int brtok = 0; +stralloc brt = {0}; +struct constmap mapbrt; +int fdmbrt; void setup() { @@ -116,7 +153,16 @@ if (bmfok == -1) die_control(); if (bmfok) if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem(); + + brtok = control_readfile(&brt,"control/badrcptto",0); + if (brtok == -1) die_control(); + if (brtok) + if (!constmap_init(&mapbrt,brt.s,brt.len,0)) die_nomem(); + fdmbrt = open_read("control/morebadrcptto.cdb"); + if (fdmbrt == -1) if (errno != error_noent) die_control(); + + if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&u); databytes = u; } @@ -197,6 +243,14 @@ return 1; } +static void log_deny(m,f,t) char *m,*f,*t; +{ + enew(); eout(m); eout(" check failed ("); eout(f); eout(") -> ("); + eout(t); eout(") ["); eout(remoteip); eout("] (HELO "); + eout(helohost.s); eout(")\n"); + eflush(); +} + int bmfcheck() { int j; @@ -208,6 +262,19 @@ return 0; } +int brtcheck() +{ + int j; + if (brtok) if (constmap(&mapbrt,addr.s,addr.len - 1)) return 1; + if (fdmbrt) { + uint32 dlen; + j = cdb_seek(fdmbrt, addr.s, addr.len - 1, &dlen); + if (j == -1) die_control(); + if (j) return j; + } + return 0; +} + int addrallowed() { int r; @@ -219,6 +286,7 @@ int seenmail = 0; int flagbarf; /* defined if seenmail */ +int flagbrt; /* defined if any bad rcpts */ stralloc mailfrom = {0}; stralloc rcptto = {0}; @@ -258,6 +326,10 @@ } else if (!addrallowed()) { err_nogateway(); return; } + if (!env_get("RELAYCLIENT") && brtcheck()) { + flagbrt = 1; + log_deny("BAD RCPT TO", mailfrom.s,addr.s); + } if (!stralloc_cats(&rcptto,"T")) die_nomem(); if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); @@ -372,6 +444,7 @@ if (!seenmail) { err_wantmail(); return; } if (!rcptto.len) { err_wantrcpt(); return; } + if (flagbrt) { err_brt(); return; } seenmail = 0; if (databytes) bytestooverflow = databytes + 1; if (qmail_open(&qqt) == -1) { err_qqt(); return; } END OF PATCH FILE