This is a goodrcptto patch for qmail-smtpd-viruscan-1.3 patched qmail-1.03 or netqmail-1.05: http://netdevice.com/qmail/patch/goodrcptto-ms-12.patch See http://qmail.org/qmail-smtpd-viruscan-1.3.patch and http://cr.yp.to/qmail.html or http://qmail.org/netqmail/. A qmail server will normally accept email for any recipient address at a domain. This patch causes the server to reject single recipient email to an invalid recipient, and filter out the invalid recipients from multiple recipient email, while accepting the message for the valid recipients. This occurs during the initial SMTP conversation for a reduction in disk I/O. The server rejects attempts to queue messages to non existent recipients, and joe job bounces to forged recipients, preventing them from becoming double bounces. To prevent dictionary attacks, the transmission channel is closed after the number of bad recipients set in control/brtlimit or BRTLIMIT, two by default. Repeated attempts from the same IPs may be handled by a cron that looks at the logs and updates tcprules accordingly. A goodrcptto list and or moregoodrcptto database is maintained. Relay and accept clients are not held to the address check, control/brtlimit or BRTLIMIT. If you need to wildcard domains, list them one per line like @example.net in control/goodrcptto only. Recipient addresses like name@example.com may be included in control/goodrcptto, but the check will run fastest if you put these into control/moregoodrcptto, then into control/moregoodrcptto.cdb using qmail-newmgrt. A check against a 50,000 address moregoodrcptto.cdb is virtually instantaneous on a 300Mhz machine. A user may want to participate in mailing list discussions, but doesn't want spam or off list replies to her now public address. Set ACCEPTCLIENT="" for the IPs of the mailing list servers with tcprules, and put the recipient address in control/protectedgood instead. For an example of how to automate this process, see the parent directory for an interactive user run script where one can remotely add, remove or list their disposable alias addresses, and the mail server cron that keeps the moregoodrcptto.cdb up to date. The patch assumes a Dave Sill type of installation with regards to extra control files concurrencyincoming and defaultdelivery, see http://lifewithqmail.org. Use http@ to get the patch onto your box, tab characters must be preserved. Here are examples of how to patch. Solaris: # gzip -cd qmail-1.03.tar.gz |tar -xf - ;cd qmail-1.03 # gpatch collate.sh" # ./collate.sh ;cd netqmail-1.05 # gpatch qmail-newmgrt.0 + +qmail-newmgrt.8: \ +qmail-newmgrt.9 conf-break conf-spawn + cat qmail-newmgrt.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-newmgrt.8 + +qmail-newmgrt.o: \ +compile qmail-newmgrt.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-newmgrt.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 @@ -1771,7 +1796,7 @@ maildirwatch.1 mailsubj.1 mbox.5 preline.1 qbiff.1 qmail-clean.8 \ qmail-command.8 qmail-control.9 qmail-getpw.9 qmail-header.5 \ qmail-inject.8 qmail-limits.9 qmail-local.8 qmail-log.5 \ -qmail-lspawn.8 qmail-newmrh.9 qmail-newu.9 qmail-pop3d.8 \ +qmail-lspawn.8 qmail-newmrh.9 qmail-newmgrt.9 qmail-newu.9 qmail-pop3d.8 \ qmail-popup.8 qmail-pw2u.9 qmail-qmqpc.8 qmail-qmqpd.8 qmail-qmtpd.8 \ qmail-qread.8 qmail-qstat.8 qmail-queue.8 qmail-remote.8 \ qmail-rspawn.8 qmail-send.9 qmail-showctl.8 qmail-smtpd.8 \ @@ -1778,7 +1803,7 @@ qmail-start.9 qmail-tcpok.8 qmail-tcpto.8 qmail-users.9 qmail.7 \ qreceipt.1 splogger.8 tcp-env.1 config.sh config-fast.sh \ qmail-clean.c qmail-getpw.c qmail-inject.c qmail-local.c \ -qmail-lspawn.c qmail-newmrh.c qmail-newu.c qmail-pop3d.c \ +qmail-lspawn.c qmail-newmrh.c qmail-newmgrt.c qmail-newu.c qmail-pop3d.c \ qmail-popup.c qmail-pw2u.c qmail-qmqpc.c qmail-qmqpd.c qmail-qmtpd.c \ qmail-qread.c qmail-qstat.sh qmail-queue.c qmail-remote.c \ qmail-rspawn.c qmail-send.c qmail-showctl.c qmail-smtpd.c \ diff -ur qmail-1.03.orig/TARGETS qmail-1.03/TARGETS --- qmail-1.03.orig/TARGETS Mon Jun 15 06:53:16 1998 +++ qmail-1.03/TARGETS Thu Mar 4 17:17:42 2004 @@ -257,6 +257,8 @@ tcp-env.o remoteinfo.o tcp-env +qmail-newmgrt.o +qmail-newmgrt qmail-newmrh.o qmail-newmrh config @@ -352,6 +354,8 @@ qmail-qmtpd.0 qmail-smtpd.0 tcp-env.0 +qmail-newmgrt.8 +qmail-newmgrt.0 qmail-newmrh.8 qmail-newmrh.0 qreceipt.0 Only in qmail-1.03: case_startb.o diff -ur qmail-1.03.orig/conf-spawn qmail-1.03/conf-spawn --- qmail-1.03.orig/conf-spawn Mon Jun 15 06:53:16 1998 +++ qmail-1.03/conf-spawn Thu Mar 4 17:17:42 2004 @@ -1,4 +1,4 @@ -120 +255 This is a silent concurrency limit. You can't set it above 255. On some systems you can't set it above 125. qmail will refuse to compile if the diff -ur qmail-1.03.orig/hier.c qmail-1.03/hier.c --- qmail-1.03.orig/hier.c Mon Jun 15 06:53:16 1998 +++ qmail-1.03/hier.c Thu Mar 4 17:17:42 2004 @@ -109,6 +109,7 @@ c(auto_qmail,"bin","qmail-clean",auto_uido,auto_gidq,0711); c(auto_qmail,"bin","qmail-send",auto_uido,auto_gidq,0711); c(auto_qmail,"bin","splogger",auto_uido,auto_gidq,0711); + c(auto_qmail,"bin","qmail-newmgrt",auto_uido,auto_gidq,0700); c(auto_qmail,"bin","qmail-newu",auto_uido,auto_gidq,0700); c(auto_qmail,"bin","qmail-newmrh",auto_uido,auto_gidq,0700); c(auto_qmail,"bin","qmail-pw2u",auto_uido,auto_gidq,0711); @@ -221,6 +222,8 @@ c(auto_qmail,"man/cat8","qmail-inject.0",auto_uido,auto_gidq,0644); c(auto_qmail,"man/man8","qmail-showctl.8",auto_uido,auto_gidq,0644); c(auto_qmail,"man/cat8","qmail-showctl.0",auto_uido,auto_gidq,0644); + c(auto_qmail,"man/man8","qmail-newmgrt.8",auto_uido,auto_gidq,0644); + c(auto_qmail,"man/cat8","qmail-newmgrt.0",auto_uido,auto_gidq,0644); c(auto_qmail,"man/man8","qmail-newmrh.8",auto_uido,auto_gidq,0644); c(auto_qmail,"man/cat8","qmail-newmrh.0",auto_uido,auto_gidq,0644); c(auto_qmail,"man/man8","qmail-newu.8",auto_uido,auto_gidq,0644); diff -ur qmail-1.03.orig/install-big.c qmail-1.03/install-big.c --- qmail-1.03.orig/install-big.c Mon Jun 15 06:53:16 1998 +++ qmail-1.03/install-big.c Thu Mar 4 17:17:42 2004 @@ -109,6 +109,7 @@ c(auto_qmail,"bin","qmail-clean",auto_uido,auto_gidq,0711); c(auto_qmail,"bin","qmail-send",auto_uido,auto_gidq,0711); c(auto_qmail,"bin","splogger",auto_uido,auto_gidq,0711); + c(auto_qmail,"bin","qmail-newmgrt",auto_uido,auto_gidq,0700); c(auto_qmail,"bin","qmail-newu",auto_uido,auto_gidq,0700); c(auto_qmail,"bin","qmail-newmrh",auto_uido,auto_gidq,0700); c(auto_qmail,"bin","qmail-pw2u",auto_uido,auto_gidq,0711); @@ -221,6 +222,8 @@ c(auto_qmail,"man/cat8","qmail-inject.0",auto_uido,auto_gidq,0644); c(auto_qmail,"man/man8","qmail-showctl.8",auto_uido,auto_gidq,0644); c(auto_qmail,"man/cat8","qmail-showctl.0",auto_uido,auto_gidq,0644); + c(auto_qmail,"man/man8","qmail-newmgrt.8",auto_uido,auto_gidq,0644); + c(auto_qmail,"man/cat8","qmail-newmgrt.0",auto_uido,auto_gidq,0644); c(auto_qmail,"man/man8","qmail-newmrh.8",auto_uido,auto_gidq,0644); c(auto_qmail,"man/cat8","qmail-newmrh.0",auto_uido,auto_gidq,0644); c(auto_qmail,"man/man8","qmail-newu.8",auto_uido,auto_gidq,0644); diff -ur qmail-1.03.orig/qmail-control.9 qmail-1.03/qmail-control.9 --- qmail-1.03.orig/qmail-control.9 Mon Jun 15 06:53:16 1998 +++ qmail-1.03/qmail-control.9 Thu Mar 4 17:17:42 2004 @@ -21,6 +21,7 @@ Comments are allowed in .IR badmailfrom , +.IR goodrcptto , .IR locals , .IR percenthack , .IR qmqpservers , @@ -43,8 +44,11 @@ .I badmailfrom \fR(none) \fRqmail-smtpd .I bouncefrom \fRMAILER-DAEMON \fRqmail-send .I bouncehost \fIme \fRqmail-send +.I brtlimit \fR2 \fRqmail-smtpd +.I concurrencyincoming \fR40 \fRtcpserver .I concurrencylocal \fR10 \fRqmail-send .I concurrencyremote \fR20 \fRqmail-send +.I defaultdelivery \fR(none) \fRqmail-start .I defaultdomain \fIme \fRqmail-inject .I defaulthost \fIme \fRqmail-inject .I databytes \fR0 \fRqmail-smtpd @@ -51,13 +55,16 @@ .I doublebouncehost \fIme \fRqmail-send .I doublebounceto \fRpostmaster \fRqmail-send .I envnoathost \fIme \fRqmail-send +.I goodrcptto \fR(none) \fRqmail-smtpd .I helohost \fIme \fRqmail-remote .I idhost \fIme \fRqmail-inject .I localiphost \fIme \fRqmail-smtpd .I locals \fIme \fRqmail-send +.I moregoodrcptto \fR(none) \fRqmail-smtpd .I morercpthosts \fR(none) \fRqmail-smtpd .I percenthack \fR(none) \fRqmail-send .I plusdomain \fIme \fRqmail-inject +.I protectedgood \fR(none) \fRqmail-showctl .I qmqpservers \fR(none) \fRqmail-qmqpc .I queuelifetime \fR604800 \fRqmail-send .I rcpthosts \fR(none) \fRqmail-smtpd diff -ur qmail-1.03.orig/qmail-newmgrt.9 qmail-1.03/qmail-newmgrt.9 --- qmail-1.03.orig/qmail-newmgrt.9 Thu Jan 1 00:00:00 1970 +++ qmail-1.03/qmail-newmgrt.9 Thu Mar 4 17:17:42 2004 @@ -0,0 +1,41 @@ +.TH qmail-newmgrt 8 +.SH NAME +qmail-newmgrt \- prepare moregoodrcptto for qmail-smtpd +.SH SYNOPSIS +.B qmail-newmgrt +.SH DESCRIPTION +.B qmail-newmgrt +reads the instructions in +.B QMAILHOME/control/moregoodrcptto +and writes them into +.B QMAILHOME/control/moregoodrcptto.cdb +in a binary format suited +for quick access by +.BR qmail-smtpd . + +If there is a problem with +.BR control/moregoodrcptto , +.B qmail-newmgrt +complains and leaves +.B control/moregoodrcptto.cdb +alone. + +.B qmail-newmgrt +ensures that +.B control/moregoodrcptto.cdb +is updated atomically, +so +.B qmail-smtpd +never has to wait for +.B qmail-newmgrt +to finish. +However, +.B qmail-newmgrt +makes no attempt to protect against two simultaneous updates of +.BR control/moregoodrcptto.cdb . + +The binary +.B control/moregoodrcptto.cdb +format is portable across machines. +.SH "SEE ALSO" +qmail-smtpd(8) diff -ur qmail-1.03.orig/qmail-newmgrt.c qmail-1.03/qmail-newmgrt.c --- qmail-1.03.orig/qmail-newmgrt.c Thu Jan 1 00:00:00 1970 +++ qmail-1.03/qmail-newmgrt.c Thu Mar 4 17:17:42 2004 @@ -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-newmgrt: fatal: " + +void die_read() +{ + strerr_die2sys(111,FATAL,"unable to read control/moregoodrcptto: "); +} +void die_write() +{ + strerr_die2sys(111,FATAL,"unable to write to control/moregoodrcptto.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/moregoodrcptto"); + if (fd == -1) die_read(); + + substdio_fdbuf(&ssin,read,fd,inbuf,sizeof inbuf); + + fdtemp = open_trunc("control/moregoodrcptto.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/moregoodrcptto.tmp","control/moregoodrcptto.cdb") == -1) + strerr_die2sys(111,FATAL,"unable to move control/moregoodrcpto.tmp to control/moregoodrcptto.cdb"); + + _exit(0); +} diff -ur qmail-1.03.orig/qmail-showctl.c qmail-1.03/qmail-showctl.c --- qmail-1.03.orig/qmail-showctl.c Mon Jun 15 06:53:16 1998 +++ qmail-1.03/qmail-showctl.c Thu Mar 4 17:17:42 2004 @@ -142,6 +142,8 @@ direntry *d; struct stat stmrh; struct stat stmrhcdb; + struct stat stmgrt; + struct stat stmgrtcdb; substdio_puts(subfdout,"qmail home directory: "); substdio_puts(subfdout,auto_qmail); @@ -217,9 +219,12 @@ do_lst("badmailfrom","Any MAIL FROM is allowed.",""," not accepted in MAIL FROM."); do_str("bouncefrom",0,"MAILER-DAEMON","Bounce user name is "); do_str("bouncehost",1,"bouncehost","Bounce host name is "); + do_int("brtlimit","2","Transmission channel close after "," bad recipients"); + do_int("concurrencyincoming","1","Incoming concurrency is ",""); do_int("concurrencylocal","10","Local concurrency is ",""); do_int("concurrencyremote","20","Remote concurrency is ",""); do_int("databytes","0","SMTP DATA limit is "," bytes"); + do_str("defaultdelivery",1,"defaultdelivery","Default mailbox is "); do_str("defaultdomain",1,"defaultdomain","Default domain name is "); do_str("defaulthost",1,"defaulthost","Default host name is "); do_str("doublebouncehost",1,"doublebouncehost","2B recipient host: "); @@ -235,8 +240,8 @@ do_lst("qmqpservers","No QMQP servers.","QMQP server: ","."); do_int("queuelifetime","604800","Message lifetime in the queue is "," seconds"); - if (do_lst("rcpthosts","SMTP clients may send messages to any recipient.","SMTP clients may send messages to recipients at ",".")) - do_lst("morercpthosts","No effect.","SMTP clients may send messages to recipients at ","."); + if (do_lst("rcpthosts","SMTP relay clients may send to any recipient.","SMTP relay clients may send to recipients at ",".")) + do_lst("morercpthosts","No effect.","SMTP relay clients may send to recipients at ","."); else do_lst("morercpthosts","No rcpthosts; morercpthosts is irrelevant.","No rcpthosts; doesn't matter that morercpthosts has ","."); /* XXX: check morercpthosts.cdb contents */ @@ -255,6 +260,27 @@ else substdio_puts(subfdout,"Modified recently enough; hopefully up to date.\n"); + if (do_lst("goodrcptto","Oops? moregoodrcptto must exist if this doesn't.","SMTP clients may send to ",".")) + do_lst("moregoodrcptto","No effect.","SMTP clients may send to ","."); + else + do_lst("moregoodrcptto","Oops? goodrcptto must exist if this doesn't.","SMTP clients may send to ","."); + /* XXX: check moregoodrcptto.cdb contents */ + substdio_puts(subfdout,"\nmoregoodrcptto.cdb: "); + if (stat("moregoodrcptto",&stmgrt) == -1) + if (stat("moregoodrcptto.cdb",&stmgrtcdb) == -1) + substdio_puts(subfdout,"(Default.) No effect.\n"); + else + substdio_puts(subfdout,"Oops! moregoodrcptto.cdb exists but moregoodrcptto doesn't.\n"); + else + if (stat("moregoodrcptto.cdb",&stmgrtcdb) == -1) + substdio_puts(subfdout,"Oops! moregoodrcptto exists but moregoodrcptto.cdb doesn't.\n"); + else + if (stmgrt.st_mtime > stmgrtcdb.st_mtime) + substdio_puts(subfdout,"Oops! moregoodrcptto.cdb is older than moregoodrcptto.\n"); + else + substdio_puts(subfdout,"Modified recently enough; hopefully up to date.\n"); + + do_lst("protectedgood","No accept client addresses.","SMTP accept clients may send to ","."); do_str("smtpgreeting",1,"smtpgreeting","SMTP greeting: 220 "); do_lst("smtproutes","No artificial SMTP routes.","SMTP route: ",""); do_int("timeoutconnect","60","SMTP client connection timeout is "," seconds"); @@ -265,19 +291,21 @@ while (d = readdir(dir)) { if (str_equal(d->d_name,".")) continue; if (str_equal(d->d_name,"..")) continue; - if (str_equal(d->d_name,"bouncefrom")) continue; - if (str_equal(d->d_name,"bouncehost")) continue; if (str_equal(d->d_name,"badmailfrom")) continue; if (str_equal(d->d_name,"bouncefrom")) continue; if (str_equal(d->d_name,"bouncehost")) continue; + if (str_equal(d->d_name,"brtlimit")) continue; + if (str_equal(d->d_name,"concurrencyincoming")) continue; if (str_equal(d->d_name,"concurrencylocal")) continue; if (str_equal(d->d_name,"concurrencyremote")) continue; if (str_equal(d->d_name,"databytes")) continue; + if (str_equal(d->d_name,"defaultdelivery")) continue; if (str_equal(d->d_name,"defaultdomain")) continue; if (str_equal(d->d_name,"defaulthost")) continue; if (str_equal(d->d_name,"doublebouncehost")) continue; if (str_equal(d->d_name,"doublebounceto")) continue; if (str_equal(d->d_name,"envnoathost")) continue; + if (str_equal(d->d_name,"goodrcptto")) continue; if (str_equal(d->d_name,"helohost")) continue; if (str_equal(d->d_name,"idhost")) continue; if (str_equal(d->d_name,"localiphost")) continue; @@ -285,8 +313,11 @@ if (str_equal(d->d_name,"me")) continue; if (str_equal(d->d_name,"morercpthosts")) continue; if (str_equal(d->d_name,"morercpthosts.cdb")) continue; + if (str_equal(d->d_name,"moregoodrcptto")) continue; + if (str_equal(d->d_name,"moregoodrcptto.cdb")) continue; if (str_equal(d->d_name,"percenthack")) continue; if (str_equal(d->d_name,"plusdomain")) continue; + if (str_equal(d->d_name,"protectedgood")) continue; if (str_equal(d->d_name,"qmqpservers")) continue; if (str_equal(d->d_name,"queuelifetime")) continue; if (str_equal(d->d_name,"rcpthosts")) continue; diff -ur qmail-1.03.orig/qmail-smtpd.8 qmail-1.03/qmail-smtpd.8 --- qmail-1.03.orig/qmail-smtpd.8 Mon Jun 15 06:53:16 1998 +++ qmail-1.03/qmail-smtpd.8 Thu Mar 4 17:17:43 2004 @@ -50,6 +50,20 @@ meaning every address at .IR host . .TP 5 +.I brtlimit +Number of bad recipients before closing the transmission channel. +.B qmail-smtpd +will close the transmission channel after +reaching the number of bad recipients in +.IR brtlimit . + +If the environment variable +.B BRTLIMIT +is set, it overrides +.IR brtlimit . + +Default and minimum: 2. +.TP 5 .I databytes Maximum number of bytes allowed in a message, or 0 for no limit. @@ -77,6 +91,50 @@ is set, it overrides .IR databytes . .TP 5 +.I goodrcptto +Allowed RCPT addresses. +.B qmail-smtpd +will reject +any envelope recipient address not listed in +.I goodrcptto +or +.IR moregoodrcptto . +A line in +.I goodrcptto +may be of the form +.BR @\fIhost , +meaning every address at +.IR host . + +.I goodrcptto +format: + +.EX + @heaven.af.mil + box@heaven.af.mil +.EE + +Exceptions: +If the environment variable +.B RELAYCLIENT +is set, +.B qmail-smtpd +will ignore +.I goodrcptto +and +.IR moregoodrcptto , +and will append the value of +.B RELAYCLIENT +to each incoming recipient address. +If the environment variable +.B ACCEPTCLIENT +is set, +.B qmail-smtpd +will ignore +.I goodrcptto +and +.IR moregoodrcptto . +.TP 5 .I localiphost Replacement host name for local IP addresses. Default: @@ -97,6 +155,38 @@ This is done before .IR rcpthosts . .TP 5 +.I moregoodrcptto +Extra allowed RCPT addresses. +If +.I goodrcptto +and +.I moregoodrcptto +both exist, +.I moregoodrcptto +is effectively appended to +.IR goodrcptto . + +.I moregoodrcptto +format: + +.EX + box@heaven.af.mil +.EE + +You must run +.B qmail-newmgrt +whenever +.I moregoodrcptto +changes. + +Rule of thumb: +Put your +.BR @\fIhost +wildcarded domains into +.IR goodrcptto , +and the rest into +.IR moregoodrcptto . +.TP 5 .I morercpthosts Extra allowed RCPT domains. If @@ -150,7 +240,7 @@ .EE Envelope recipient addresses without @ signs are -always allowed through. +allowed through if added to goodrcptto or moregoodrcptto. .TP 5 .I smtpgreeting SMTP greeting message. @@ -174,6 +264,7 @@ tcp-environ(5), qmail-control(5), qmail-inject(8), +qmail-newmgrt(8), qmail-newmrh(8), qmail-queue(8), qmail-remote(8) diff -ur qmail-1.03.orig/qmail-smtpd.c qmail-1.03/qmail-smtpd.c --- qmail-1.03.orig/qmail-smtpd.c Thu Mar 4 17:17:09 2004 +++ qmail-1.03/qmail-smtpd.c Fri Mar 5 19:35:49 2004 @@ -23,11 +23,19 @@ #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; +char *acceptclient; + int safewrite(fd,buf,len) int fd; char *buf; int len; { int r; @@ -42,12 +50,39 @@ void flush() { substdio_flush(&ssout); } void out(s) char *s; { substdio_puts(&ssout,s); } +char sserrbuf[512]; +substdio sserr = SUBSTDIO_FDBUF(safewrite,2,sserrbuf,sizeof sserrbuf); + +char strnum[FMT_ULONG]; +void log(s) char *s; { substdio_putsflush(&sserr,s); } +void logs(s1,s2,s3) char *s1; char *s2; char *s3; { + substdio_putsflush(&sserr,s1); + substdio_putsflush(&sserr,s2); + substdio_putsflush(&sserr,s3); +} +void pid() { log("qmail-smtpd: !ok "); strnum[fmt_ulong(strnum,getpid())] = 0; log(strnum); } + 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() { + pid(); logs(" Connection to ",remoteip," timed out.\n"); + out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); +} +void die_nomem() { + pid(); logs(" Out of memory while connected to ",remoteip,"!\n"); + out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); +} +void die_control() { + pid(); log(" Unable to read controls!\n"); + out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); +} +void die_ipme() { + pid(); log(" Unable to figure out my IP addresses!\n"); + out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); +} +void straynewline() { + pid(); logs(" Stray newline from ",remoteip,".\n"); + out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); +} void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#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"); } @@ -76,12 +111,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 +125,11 @@ int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; +int grtok = 0; +stralloc grt = {0}; +struct constmap mapgrt; +int fdmgrt; +int brtlimit = 0; int sigsok = 0; stralloc sigs = {0}; @@ -119,6 +153,19 @@ if (bmfok) if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem(); + grtok = control_readfile(&grt,"control/goodrcptto",0); + if (grtok == -1) die_control(); + if (grtok) + if (!constmap_init(&mapgrt,grt.s,grt.len,0)) die_nomem(); + + fdmgrt = open_read("control/moregoodrcptto.cdb"); + if (fdmgrt == -1) if (errno != error_noent) die_control(); + + if (control_readint(&brtlimit,"control/brtlimit") == -1) die_control(); + x = env_get("BRTLIMIT"); + if (x) { scan_ulong(x,&u); brtlimit = u; }; + if (brtlimit <= 1) brtlimit = 2; + sigsok = control_readfile(&sigs,"control/signatures",0); if (sigsok == -1) die_control(); @@ -136,6 +183,7 @@ if (!remotehost) remotehost = "unknown"; remoteinfo = env_get("TCPREMOTEINFO"); relayclient = env_get("RELAYCLIENT"); + acceptclient = env_get("ACCEPTCLIENT"); dohelo(remotehost); } @@ -202,6 +250,16 @@ return 1; } +void err_brt(s1,s2,s3,s4) char *s1; char *s2; char *s3; char *s4; { + pid(); log(s1); log(s2); log(s3); log(s4); log(" by "); + log(remoteip); log(" (HELO "); log(helohost.s); log(").\n"); +} + +void die_attack() { + pid(); logs(" Too many bad recipients from ",remoteip,", closing connection.\n"); + out("421 service shutting down and closing transmission channel (#4.3.0)\r\n"); flush(); _exit(1); +} + int bmfcheck() { int j; @@ -226,6 +284,24 @@ return 0; } +int grtcheck() +{ + int g; + case_lowerb(addr.s,addr.len); + if (grtok) { + if (constmap(&mapgrt,addr.s,addr.len - 1)) return 1; + g = byte_rchr(addr.s,addr.len,'@'); + if (g < addr.len) + if (constmap(&mapgrt,addr.s + g,addr.len - g - 1)) return 1; + } + if (fdmgrt != -1) { + uint32 dlen; + g = cdb_seek(fdmgrt, addr.s, addr.len - 1, &dlen); + if (g) return g; + } + return 0; +} + int addrallowed() { int r; @@ -239,6 +315,8 @@ int flagbarf; /* defined if seenmail */ stralloc mailfrom = {0}; stralloc rcptto = {0}; +stralloc rcpttos = {0}; +int brtcount; void smtp_helo(arg) char *arg; { @@ -268,7 +346,10 @@ void smtp_rcpt(arg) char *arg; { if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } - if (flagbarf) { err_bmf(); return; } + if (flagbarf) { + err_brt(" Bad envelope sender ",mailfrom.s," to ",addr.s); + err_bmf(); return; + } if (relayclient) { --addr.len; if (!stralloc_cats(&addr,relayclient)) die_nomem(); @@ -275,9 +356,25 @@ if (!stralloc_0(&addr)) die_nomem(); } else - if (!addrallowed()) { err_nogateway(); return; } - if (!stralloc_cats(&rcptto,"T")) die_nomem(); - if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); + if (!addrallowed()) { + err_brt(" Bad recipient host ",addr.s," from ",mailfrom.s); + if (++brtcount == brtlimit) die_attack(); + err_nogateway(); return; + } + else + if (!acceptclient) { + if (!grtcheck()) { + if (str_equal(mailfrom.s,"")) { + err_brt(" Forged recipient user ",addr.s," from ","null"); + } + else + err_brt(" Bad recipient user ",addr.s," from ",mailfrom.s); + if (++brtcount == brtlimit) die_attack(); + out("550 sorry, no mailbox here by that name (#5.1.1)\r\n"); return; + } + } + if ((!stralloc_cats(&rcptto,"T")) || (!stralloc_cats(&rcpttos," "))) die_nomem(); + if ((!stralloc_cats(&rcptto,addr.s)) || (!stralloc_cats(&rcpttos,addr.s))) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); out("250 ok\r\n"); } @@ -552,7 +649,15 @@ if (!*qqx) { acceptmessage(qp); return; } if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; } if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); return; } - if (flagexecutable) { out("552 we don't accept email with such content (#5.3.4)\r\n"); return; } + if (flagexecutable) { + if (!stralloc_append(&rcpttos,"")) die_nomem(); + if (str_equal(mailfrom.s,"")) { + err_brt(" Rejected unacceptable content to",rcpttos.s," from ","null"); + } + else + err_brt(" Rejected unacceptable content to",rcpttos.s," from ",mailfrom.s); + out("552 we don't accept email with such content (#5.3.4)\r\n"); return; + } if (*qqx == 'D') out("554 "); else out("451 "); out(qqx + 1); out("\r\n");