--- FILES 2001/02/19 20:16:31 1.1.1.1 +++ FILES 2001/12/13 21:39:00 1.1.1.1.2.1 @@ -431,3 +431,14 @@ tcp-environ.5 constmap.h constmap.c +README.spamthrottle +time.c +time.h +control_time.c +lock_exfcntl.c +lock_unfcntl.c +spam.c +tryltai.c +trytai.c +open_rw.c +qmail-spamthrottle.9 --- Makefile 2001/02/19 20:16:31 1.1.1.1 +++ Makefile 2001/12/13 21:48:39 1.1.1.1.2.8 @@ -358,9 +358,14 @@ control.o: \ compile control.c readwrite.h open.h getln.h stralloc.h gen_alloc.h \ -substdio.h error.h control.h alloc.h scan.h +substdio.h error.h control.h alloc.h scan.h fmt.h ./compile control.c +control_time.o: \ +compile control_time.c readwrite.h open.h stralloc.h gen_alloc.h \ +substdio.h error.h control.h alloc.h time.h hastai.h + ./compile control_time.c + date822fmt.o: \ compile date822fmt.c datetime.h fmt.h date822fmt.h ./compile date822fmt.c @@ -685,6 +690,13 @@ hasshsgr.h rm -f tryshsgr.o tryshsgr +hastai.h: \ +trytai.c compile load + ( ( ./compile trytai.c && ./load trytai -ltai ) >/dev/null \ + 2>&1 \ + && echo \#define HASTAI 1 || exit 0 ) > hastai.h + rm -f trytai.o trytai + haswaitp.h: \ trywaitp.c compile load ( ( ./compile trywaitp.c && ./load trywaitp ) >/dev/null \ @@ -816,8 +828,8 @@ chmod 755 load lock.a: \ -makelib lock_ex.o lock_exnb.o lock_un.o - ./makelib lock.a lock_ex.o lock_exnb.o lock_un.o +makelib lock_ex.o lock_exnb.o lock_un.o lock_exfcntl.o lock_unfcntl.o + ./makelib lock.a lock_ex.o lock_exnb.o lock_un.o lock_exfcntl.o lock_unfcntl.o lock_ex.o: \ compile lock_ex.c hasflock.h lock.h @@ -831,6 +843,14 @@ compile lock_un.c hasflock.h lock.h ./compile lock_un.c +lock_exfcntl.o: \ +compile lock_exfcntl.c lock.h + ./compile lock_exfcntl.c + +lock_unfcntl.o: \ +compile lock_unfcntl.c lock.h + ./compile lock_unfcntl.c + maildir.0: \ maildir.5 nroff -man maildir.5 > maildir.0 @@ -935,7 +955,7 @@ maildir2mbox.0 maildirwatch.0 qmail.0 qmail-limits.0 qmail-log.0 \ qmail-control.0 qmail-header.0 qmail-users.0 dot-qmail.0 \ qmail-command.0 tcp-environ.0 maildir.0 mbox.0 addresses.0 \ -envelopes.0 forgeries.0 +envelopes.0 forgeries.0 qmail-spamthrottle.0 mbox.0: \ mbox.5 @@ -968,9 +988,9 @@ open.a: \ makelib open_append.o open_excl.o open_read.o open_trunc.o \ -open_write.o +open_write.o open_rw.o ./makelib open.a open_append.o open_excl.o open_read.o \ - open_trunc.o open_write.o + open_trunc.o open_write.o open_rw.o open_append.o: \ compile open_append.c open.h @@ -984,6 +1004,10 @@ compile open_read.c open.h ./compile open_read.c +open_rw.o: \ +compile open_rw.c open.h + ./compile open_rw.c + open_trunc.o: \ compile open_trunc.c open.h ./compile open_trunc.c @@ -1144,7 +1168,7 @@ quote.o now.o control.o date822fmt.o constmap.o qmail.o \ case.a fd.a wait.a open.a getln.a sig.a getopt.a datetime.a \ token822.o env.a stralloc.a alloc.a substdio.a error.a \ - str.a fs.a auto_qmail.o + str.a fs.a auto_qmail.o qmail-inject.0: \ qmail-inject.8 @@ -1533,16 +1557,16 @@ qmail-smtpd: \ load qmail-smtpd.o rcpthosts.o commands.o timeoutread.o \ -timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \ -date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \ -open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \ -fs.a auto_qmail.o socket.lib +timeoutwrite.o ip.o ipme.o ipalloc.o control.o control_time.o time.o constmap.o \ +received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \ +spam.o open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \ +fs.a auto_qmail.o auto_uids.o socket.lib tai.lib lock.a ./load qmail-smtpd rcpthosts.o commands.o timeoutread.o \ - timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \ - received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \ - datetime.a getln.a open.a sig.a case.a env.a stralloc.a \ - alloc.a substdio.a error.a str.a fs.a auto_qmail.o `cat \ - socket.lib` + timeoutwrite.o ip.o ipme.o ipalloc.o control.o control_time.o time.o \ + constmap.o received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \ + datetime.a getln.a spam.o strerr.a open.a sig.a case.a env.a stralloc.a \ + alloc.a substdio.a error.a str.a fs.a auto_qmail.o auto_uids.o \ + `cat socket.lib tai.lib` lock.a qmail-smtpd.0: \ qmail-smtpd.8 @@ -1556,6 +1580,18 @@ exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h ./compile qmail-smtpd.c +qmail-spamthrottle.0: \ +qmail-spamthrottle.5 + nroff -man qmail-spamthrottle.5 > qmail-spamthrottle.0 + +qmail-spamthrottle.5: \ +qmail-spamthrottle.9 conf-break conf-spawn + cat qmail-spamthrottle.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-spamthrottle.5 + qmail-start: \ load qmail-start.o prot.o fd.a auto_uids.o ./load qmail-start prot.o fd.a auto_uids.o @@ -1815,7 +1851,8 @@ trysgact.c trysgprm.c env.3 env.h env.c envread.c byte.h byte_chr.c \ byte_copy.c byte_cr.c byte_diff.c byte_rchr.c byte_zero.c str.h \ str_chr.c str_cpy.c str_diff.c str_diffn.c str_len.c str_rchr.c \ -str_start.c lock.h lock_ex.c lock_exnb.c lock_un.c tryflock.c getln.3 \ +str_start.c lock.h lock_ex.c lock_exnb.c lock_un.c lock_exfcntl.c \ +lock_unfcntl.c tryflock.c getln.3 \ getln.h getln.c getln2.3 getln2.c sgetopt.3 sgetopt.h sgetopt.c \ subgetopt.3 subgetopt.h subgetopt.c error.3 error_str.3 error_temp.3 \ error.h error.c error_str.c error_temp.c fmt.h fmt_str.c fmt_strn.c \ @@ -1827,7 +1864,9 @@ ipalloc.h ipalloc.c select.h1 select.h2 trysysel.c ndelay.h ndelay.c \ ndelay_off.c direntry.3 direntry.h1 direntry.h2 trydrent.c prot.h \ prot.c chkshsgr.c warn-shsgr tryshsgr.c ipme.h ipme.c trysalen.c \ -maildir.5 maildir.h maildir.c tcp-environ.5 constmap.h constmap.c +maildir.5 maildir.h maildir.c tcp-environ.5 constmap.h constmap.c \ +README.spamthrottle time.c time.h control_time.c lock_exfcntl.c \ +lock_unfcntl.c spam.c tryltai.c trytai.c open_rw.c qmail-spamthrottle.9 shar -m `cat FILES` > shar chmod 400 shar @@ -1890,6 +1929,10 @@ && echo -lsocket -lnsl || exit 0 ) > socket.lib rm -f trylsock.o trylsock +spam.o: \ +compile spam.c auto_uids.h control.h stralloc.h lock.h + ./compile spam.c + spawn.o: \ compile chkspawn spawn.c sig.h wait.h substdio.h byte.h str.h \ stralloc.h gen_alloc.h select.h exit.h coe.h open.h error.h \ @@ -2064,6 +2107,13 @@ find-systype trycpp.c ./find-systype > systype +tai.lib: \ +tryltai.c compile load + ( ( ./compile tryltai.c && \ + ./load tryltai -ltai ) >/dev/null 2>&1 \ + && echo -ltai || exit 0 ) > tai.lib + rm -f tryltai.o tryltai + tcp-env: \ load tcp-env.o dns.o remoteinfo.o timeoutread.o timeoutwrite.o \ timeoutconn.o ip.o ipalloc.o case.a ndelay.a sig.a env.a getopt.a \ @@ -2094,6 +2144,10 @@ tcpto_clean.o: \ compile tcpto_clean.c tcpto.h open.h substdio.h readwrite.h ./compile tcpto_clean.c + +time.o: \ +compile time.c time.h stralloc.h fmt.h scan.h hastai.h + ./compile time.c timeoutconn.o: \ compile timeoutconn.c ndelay.h select.h error.h readwrite.h ip.h \ --- README.spamthrottle Tue May 5 16:32:27 1998 +++ README.spamthrottle Wed Jan 2 18:37:46 2002 @@ -0,0 +1,17 @@ +qmail-spamthrottle 1.00 +20011213 +Copyright 2001 +D. Woolridge, dale-qmail-spamthrottle@woolridge.ca +J. Law, jtlaw@arctic.org +M. Kawasaki, kawasaki@kawasaki3.org + +Man pages have been updated with spamthrottle documentation: + qmail-smtpd(8) (file qmail-smtpd.8) + qmail-control(5) (file qmail-control.9) + qmail-spamthrottle(5) (file qmail-spamthrottle.9) ** new ** + +This patch uses subsecond precision calculations. Use of libtai +(http://cr.yp.to/libtai.html) is recommended, but not required. +If you choose to use libtai, we suggest version 0.60 since this patch +has been tested with it. Use of libtai is automatic when it is +available (for inclusion and linking). --- TARGETS 2001/02/19 20:16:31 1.1.1.1 +++ TARGETS 2001/12/19 17:31:40 1.1.1.1.2.2 @@ -45,6 +45,7 @@ open_read.o open_trunc.o open_write.o +open_rw.o open.a seek_cur.o seek_end.o @@ -55,6 +56,8 @@ lock_ex.o lock_exnb.o lock_un.o +lock_exfcntl.o +lock_unfcntl.o lock.a fd_copy.o fd_move.o @@ -165,6 +168,10 @@ qmail-getpw qmail-remote.o control.o +control_time.o +hastai.h +time.o +tai.lib constmap.o timeoutread.o timeoutwrite.o @@ -252,6 +259,7 @@ qmail-qmtpd qmail-smtpd.o qmail-smtpd +spam.o sendmail.o sendmail tcp-env.o @@ -382,6 +390,8 @@ addresses.0 envelopes.0 forgeries.0 +qmail-spamthrottle.5 +qmail-spamthrottle.0 man setup check --- control.c 2001/02/19 20:16:33 1.1.1.1 +++ control.c 2001/03/09 03:54:13 1.1.1.1.2.2 @@ -7,8 +7,10 @@ #include "control.h" #include "alloc.h" #include "scan.h" +#include "fmt.h" static char inbuf[64]; +static char outbuf[64]; static stralloc line = {0}; static stralloc me = {0}; static int meok = 0; @@ -127,4 +129,26 @@ } close(fd); return -1; +} + + +int control_writeint(i,fn) +int *i; +char *fn; +{ + substdio ss; + int fd; + char num[FMT_ULONG]; + + fd = open_write(fn); + if (fd == -1) { if (errno == error_noent) return 0; return -1; } + + substdio_fdbuf(&ss,write,fd,outbuf,sizeof(outbuf)); + + substdio_putflush(&ss,num,fmt_ulong(num,*i)); + substdio_putsflush(&ss,"\n"); + + close(fd); + + return 1; } --- control.h 2001/02/19 20:16:33 1.1.1.1 +++ control.h 2001/03/13 08:59:40 1.1.1.1.2.2 @@ -6,5 +6,9 @@ extern int control_rldef(); extern int control_readint(); extern int control_readfile(); +extern int control_writeint(); + +extern int control_readtime(); +extern int control_writetime(); #endif --- control_time.c Tue May 5 16:32:27 1998 +++ control_time.c Wed Jan 2 18:37:46 2002 @@ -0,0 +1,63 @@ +#include "readwrite.h" +#include "open.h" +#include "stralloc.h" +#include "substdio.h" +#include "error.h" +#include "control.h" +#include "alloc.h" +#include "time.h" + +static char inbuf[64]; +static char outbuf[64]; + + +int control_readtime(t,fn) +struct q_time_t *t; +char *fn; +{ + stralloc sa = { 0 }; + substdio ss; + int fd; + char tb; + + fd = open_read(fn); + if (fd == -1) { if (errno == error_noent) return 0; return -1; } + + substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); + + for (;;) + { + if (substdio_get(&ss,&tb,1) <= 0) break; + if (!stralloc_append(&sa,&tb)) return -1; + } + + if ((!Q_TIME_PACK && sa.len) || sa.len == Q_TIME_PACK) + { + time_unpack(sa.s,t); + } + + close(fd); + if (Q_TIME_PACK && sa.len != Q_TIME_PACK) return -1; + return 1; +} + + +int control_writetime(t,fn) +struct q_time_t *t; +char *fn; +{ + stralloc sa = { 0 }; + substdio ss; + int fd; + + fd = open_write(fn); + if (fd == -1) { if (errno == error_noent) return 0; return -1; } + + substdio_fdbuf(&ss,write,fd,outbuf,sizeof(outbuf)); + + time_pack(&sa,t); + substdio_putflush(&ss,sa.s,sa.len); + + close(fd); + return 1; +} --- hier.c 2001/02/19 20:16:32 1.1.1.1 +++ hier.c 2001/02/27 09:28:19 1.1.1.1.2.3 @@ -47,6 +47,7 @@ d(auto_qmail,"man/man8",auto_uido,auto_gidq,0755); d(auto_qmail,"alias",auto_uida,auto_gidq,02755); + d(auto_qmail,"spam",auto_uidd,auto_gidn,02700); d(auto_qmail,"queue",auto_uidq,auto_gidq,0750); d(auto_qmail,"queue/pid",auto_uidq,auto_gidq,0700); @@ -164,6 +165,8 @@ c(auto_qmail,"man/cat5","qmail-users.0",auto_uido,auto_gidq,0644); c(auto_qmail,"man/man5","tcp-environ.5",auto_uido,auto_gidq,0644); c(auto_qmail,"man/cat5","tcp-environ.0",auto_uido,auto_gidq,0644); + c(auto_qmail,"man/man5","qmail-spamthrottle.5",auto_uido,auto_gidq,0644); + c(auto_qmail,"man/cat5","qmail-spamthrottle.0",auto_uido,auto_gidq,0644); c(auto_qmail,"man/man7","forgeries.7",auto_uido,auto_gidq,0644); c(auto_qmail,"man/cat7","forgeries.0",auto_uido,auto_gidq,0644); --- lock.h 2001/02/19 20:16:33 1.1.1.1 +++ lock.h 2001/03/09 03:54:13 1.1.1.1.2.1 @@ -5,4 +5,7 @@ extern int lock_un(); extern int lock_exnb(); +extern int lock_exfcntl(); +extern int lock_unfcntl(); + #endif --- lock_exfcntl.c Tue May 5 16:32:27 1998 +++ lock_exfcntl.c Wed Jan 2 18:37:46 2002 @@ -0,0 +1,16 @@ +#include +#include +#include "lock.h" + +int lock_exfcntl(fd) +int fd; +{ + struct flock fl; + + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + return fcntl(fd,F_SETLKW,&fl); +} --- lock_unfcntl.c Tue May 5 16:32:27 1998 +++ lock_unfcntl.c Wed Jan 2 18:37:46 2002 @@ -0,0 +1,16 @@ +#include +#include +#include "lock.h" + +int lock_unfcntl(fd) +int fd; +{ + struct flock fl; + + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + return fcntl(fd,F_SETLKW,&fl); +} --- open.h 2001/02/19 20:16:32 1.1.1.1 +++ open.h 2001/02/20 06:00:02 1.1.1.1.2.1 @@ -6,5 +6,6 @@ extern int open_append(); extern int open_trunc(); extern int open_write(); +extern int open_rw(); #endif --- open_rw.c Tue May 5 16:32:27 1998 +++ open_rw.c Wed Jan 2 18:37:46 2002 @@ -0,0 +1,6 @@ +#include +#include +#include "open.h" + +int open_rw(fn) char *fn; +{ return open(fn,O_RDWR | O_CREAT); } --- qmail-control.9 2001/02/19 20:16:32 1.1.1.1 +++ qmail-control.9 2001/09/23 18:11:36 1.1.1.1.2.2 @@ -63,6 +63,10 @@ .I rcpthosts \fR(none) \fRqmail-smtpd .I smtpgreeting \fIme \fRqmail-smtpd .I smtproutes \fR(none) \fRqmail-remote +.I spamthrottle \fR0 \fRqmail-smtpd +.I spamthrottlemax \fR0 \fRqmail-smtpd +.I spamthrottlercpt \fR0 \fRqmail-smtpd +.I spamthrottleflush \fR0 \fRqmail-smtpd .I timeoutconnect \fR60 \fRqmail-remote .I timeoutremote \fR1200 \fRqmail-remote .I timeoutsmtpd \fR1200 \fRqmail-smtpd --- qmail-showctl.c 2001/02/19 20:16:32 1.1.1.1 +++ qmail-showctl.c 2001/09/23 18:11:36 1.1.1.1.2.3 @@ -257,6 +257,10 @@ do_str("smtpgreeting",1,"smtpgreeting","SMTP greeting: 220 "); do_lst("smtproutes","No artificial SMTP routes.","SMTP route: ",""); + do_int("spamthrottle","0","Spam throttle delay is "," milliseconds"); + do_int("spamthrottlemax","0","Spam throttle maximum delay is "," milliseconds"); + do_int("spamthrottlercpt","0","Spam throttle reasonable recipient count is ",""); + do_int("spamthrottleflush","0","Spam throttle flush before DATA is ",""); do_int("timeoutconnect","60","SMTP client connection timeout is "," seconds"); do_int("timeoutremote","1200","SMTP client data timeout is "," seconds"); do_int("timeoutsmtpd","1200","SMTP server data timeout is "," seconds"); @@ -292,6 +296,10 @@ if (str_equal(d->d_name,"rcpthosts")) continue; if (str_equal(d->d_name,"smtpgreeting")) continue; if (str_equal(d->d_name,"smtproutes")) continue; + if (str_equal(d->d_name,"spamthrottle")) continue; + if (str_equal(d->d_name,"spamthrottlemax")) continue; + if (str_equal(d->d_name,"spamthrottlercpt")) continue; + if (str_equal(d->d_name,"spamthrottleflush")) continue; if (str_equal(d->d_name,"timeoutconnect")) continue; if (str_equal(d->d_name,"timeoutremote")) continue; if (str_equal(d->d_name,"timeoutsmtpd")) continue; --- qmail-smtpd.8 2001/02/19 20:16:32 1.1.1.1 +++ qmail-smtpd.8 2001/09/23 18:11:36 1.1.1.1.2.2 @@ -169,10 +169,65 @@ .B qmail-smtpd will wait for each new buffer of data from the remote SMTP client. Default: 1200. +.TP 5 +.I spamthrottle +Approximately, the number of milliseconds allowed between delivery +of messages from the same IP address. Default: 0. A typical value +would be 2000. See +.BR qmail-spamthrottle(5) +for details. + +If the environment variable +.B SPAMTHROTTLE +is set, it overrides +.IR spamthrottle . + +.TP 5 +.I spamthrottlemax +The maximum delay, in milliseconds, between delivery of messages from +the same IP address. Default: 0. A recommended value would be 240000. +See +.BR qmail-spamthrottle(5) +for details. + +If the environment variable +.B SPAMTHROTTLEMAX +is set, it overrides +.IR spamthrottlemax . + +.TP 5 +.I spamthrottlercpt +The number of recipients allowed per SMTP connection before additional +delay penalties are applied. Default: 0. A recommended value would be 10. +See +.BR qmail-spamthrottle(5) +for details. + +If the environment variable +.B SPAMTHROTTLERCPT +is set, it overrides +.IR spamthrottlercpt . + +.TP 5 +.I spamthrottleflush +If non-zero, then the input buffer is flushed after receiving the DATA +command and prior to sending a response to the DATA command. This is +in direct violation of RFC 2920 (STD 60) when PIPELINING is supported. +Thus, PIPELINING is not supported when this flush mechanism is enabled. +See +.BR qmail-spamthrottle(5) +for details. + +If the environment variable +.B SPAMTHROTTLEFLUSH +is set, it overrides +.IR spamthrottleflush . + .SH "SEE ALSO" tcp-env(1), tcp-environ(5), qmail-control(5), +qmail-spamthrottle(5), qmail-inject(8), qmail-newmrh(8), qmail-queue(8), --- qmail-smtpd.c 2001/02/19 20:16:32 1.1.1.1 +++ qmail-smtpd.c 2001/09/23 18:11:36 1.1.1.1.2.7 @@ -23,6 +23,7 @@ #include "timeoutread.h" #include "timeoutwrite.h" #include "commands.h" +#include "spam.h" #define MAXHOPS 100 unsigned int databytes = 0; @@ -85,7 +86,9 @@ stralloc helohost = {0}; char *fakehelo; /* pointer into helohost, or 0 */ -void dohelo(arg) char *arg; { +void dohelo(arg) +char *arg; +{ if (!stralloc_copys(&helohost,arg)) die_nomem(); if (!stralloc_0(&helohost)) die_nomem(); fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0; @@ -96,6 +99,7 @@ int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; +struct spam_t spamt = spam_t_init(); void setup() { @@ -110,6 +114,24 @@ if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control(); if (timeout <= 0) timeout = 1; + if (control_readint(&spamt.throttle,"control/spamthrottle") == -1) die_control(); + x = env_get("SPAMTHROTTLE"); + if (x) { scan_ulong(x,&u); spamt.throttle = u; } + + if (spamt.throttle) { + if (control_readint(&spamt.max,"control/spamthrottlemax") == -1) die_control(); + x = env_get("SPAMTHROTTLEMAX"); + if (x) { scan_ulong(x,&u); spamt.max = u; } + + if (control_readint(&spamt.flush,"control/spamthrottleflush") == -1) die_control(); + x = env_get("SPAMTHROTTLEFLUSH"); + if (x) { scan_ulong(x,&u); spamt.flush = u; } + + if (control_readint(&spamt.reasonablercpt,"control/spamthrottlercpt") == -1) die_control(); + x = env_get("SPAMTHROTTLERCPT"); + if (x) { scan_ulong(x,&u); spamt.reasonablercpt = u; } + } + if (rcpthosts_init() == -1) die_control(); bmfok = control_readfile(&bmf,"control/badmailfrom",0); @@ -131,6 +153,13 @@ if (!remotehost) remotehost = "unknown"; remoteinfo = env_get("TCPREMOTEINFO"); relayclient = env_get("RELAYCLIENT"); + spamt.dir = env_get("SPAMTHROTTLEDIR"); + if (spamt.dir) { + if (*spamt.dir == '\0') spamt.dir = 0; + } + else { + spamt.dir = remoteip; + } dohelo(remotehost); } @@ -219,6 +248,7 @@ int seenmail = 0; int flagbarf; /* defined if seenmail */ +int rcptcount; stralloc mailfrom = {0}; stralloc rcptto = {0}; @@ -229,7 +259,11 @@ } void smtp_ehlo(arg) char *arg; { - smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n"); + smtp_greet("250-"); + if (!spamt.flush || !spamt.throttle) { + out("\r\n250-PIPELINING"); + } + out("\r\n250 8BITMIME\r\n"); seenmail = 0; dohelo(arg); } void smtp_rset() @@ -245,6 +279,7 @@ if (!stralloc_copys(&rcptto,"")) die_nomem(); if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); if (!stralloc_0(&mailfrom)) die_nomem(); + rcptcount = 0; out("250 ok\r\n"); } void smtp_rcpt(arg) char *arg; { @@ -261,6 +296,7 @@ if (!stralloc_cats(&rcptto,"T")) die_nomem(); if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); + ++rcptcount; out("250 ok\r\n"); } @@ -365,7 +401,8 @@ out("\r\n"); } -void smtp_data() { +void smtp_data() +{ int hops; unsigned long qp; char *qqx; @@ -376,6 +413,11 @@ if (databytes) bytestooverflow = databytes + 1; if (qmail_open(&qqt) == -1) { err_qqt(); return; } qp = qmail_qp(&qqt); + spamt.rcptcount = rcptcount; + if (spam_wait(&spamt) && spamt.flush) + { + spam_flush(ssin.fd); + } out("354 go ahead\r\n"); received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo); @@ -384,7 +426,7 @@ if (hops) qmail_fail(&qqt); qmail_from(&qqt,mailfrom.s); qmail_put(&qqt,rcptto.s,rcptto.len); - + qqx = qmail_close(&qqt); if (!*qqx) { acceptmessage(qp); return; } if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; } --- qmail-spamthrottle.9 Tue May 5 16:32:27 1998 +++ qmail-spamthrottle.9 Wed Jan 2 18:37:46 2002 @@ -0,0 +1,201 @@ +.TH qmail-spamthrottle 5 + +.SH NAME +qmail-spamthrottle \- the qmail spam throttle mechanism + +.SH INTRODUCTION +The use of spam throttling was motivated by the ease with which +tarpitting is countered by would-be spammers. A reasonable +tarpitcount is one which does not adversely affect acceptable +mail usage, but any would-be spammer can simply create separate +SMTP connections with a recipient count lower than tarpitcount. +Spam throttling addresses these issues by parameterizing SMTP +usage and imposing a wait (via +.BR sleep (3)) +following the +.B DATA +command. SMTP usage parameters include: remote IP address; +previous SMTP connection timestamp; and previous wait time. + +.SH FILES +Two files, +.I wait +and +.IR time , +store the previous wait time and SMTP connection timestamp, +respectively. Both files are found in +.B QMAILHOME/spam/\fIdir\fB/\fR. +Where +.I dir +is +.BR SPAMTHROTTLEDIR , +if that is set, and +.B TCPREMOTEIP +otherwise. + +Note: Every dot (\fB.\fR) is interpreted as a slash (\fB/\fR) +for the purposes of constructing \fIdir\fR. For example, if +.B TCPREMOTEIP +is +.I a\fR.\fIb\fR.\fIc\fR.\fId +then the two spam throttle state files are stored in +.B QMAILHOME/spam/\fIa\fB/\fIb\fB/\fIc\fB/\fId\fB/\fR. + +Message throughput is controlled using the +.B QMAILHOME/control/spamthrottle +file. The delays imposed (by calling +.BR sleep (3)) +depend on: the contents of this file (\fIS\fR); number of recipients +for the current SMTP session (\fIR\fR); the number of reasonable +recipients per connection (\fIRM\fR); how much time has +passed (\fIT\fR) since the last SMTP request from +.B TCPREMOTEIP +(or in +.BR SPAMTHROTTLEDIR ); +and the last imposed delay (\fIW\fR). The latter two correspond +to the state files mentioned above. The new delay is approximately +.EX + + (1 + \fIR\fR - \fIR\fR / 2^(\fIR\fR/\fIRM\fR)) * ((\fIW\fR * \fIS\fR * \fIR\fR) / \fIT\fR) + +.EE +when \fIRM\fR is greater than 0, and +.EX + + (1 + \fIR\fR) * ((\fIW\fR * \fIS\fR * \fIR\fR) / \fIT\fR) + +.EE +otherwise. The unit of time is milliseconds. + +If +.B QMAILHOME/control/spamthrottlemax +exists, then it is used as a maximum (in milliseconds) for the +delay calculated above. The environment variable, +.BR SPAMTHROTTLEMAX , +may also contain the maximum delay. The environment variable +always takes precendence. + +.SH ENVIRONMENT +The environment variable, +.BR SPAMTHROTTLE , +will be used, if present, instead of the contents of +.BR QMAILHOME/control/spamthrottle . +This is particularly useful when one wishes to disable the spamthrottle +mechanism in select cases and is easily done in conjunction with a program +like +.BR tcpserver . +An ISP may wish to enable this mechanism for its customers to prevent +them from using the mail servers as a convenient location from which +to send spam. However, in some or all other cases (other originating +IP addresses) this mechanism might be disabled to allow for legitimate +high-volume mail traffic such as mailing lists. + +As indicated above, +.B SPAMTHROTTLEDIR +will be used in the construction of the state files' storage path. +Otherwise, +.B TCPREMOTEIP +is used in its place. +Typically, this environment variable would be used to collect groups +of connections (IP addresses) logically to avoid the proliferation of +files/directories (for each IP address). This is easily done in +conjunction with a program like +.BR tcpserver . +As a home user, operating a small mail server, one might use the +directory 'external', 'all', 'public', or 'default' (as examples) +to use a shared state file by default, but support exceptions for +specific IP addresses such as a local subnet or an external set of +trusted mail servers. +See +.B EXAMPLES +below for specific examples of the spamthrottle mechanism used in +conjunction with +.BR tcpserver . + +Despite efforts to impose a waiting period on would-be +spammers, it is still possible for the client to circumvent +the call to +.BR sleep (3). +That is, they may not wait for the response from +the DATA command, continuing to write their message, assuming +success, then closing the socket, again without waiting for a +response from the server; the message will be delivered at no +(time) cost to them. Adherence to standards should not be +assumed for clients acting as agents for unsolicited bulk email. +As such, an additional variable, +.BR SPAMTHROTTLEFLUSH , +can be set to indicate that all input will be flushed after +calling +.BR sleep (3) +and prior to sending a response to the DATA command. The contents of +.B QMAILHOME/control/spamthrottleflush +may also be used for the same purpose, but the environment +variable still takes precendence. RFC 2920 (STD 60) prohibits +flushing of the input buffer if PIPELINING is supported. +As such, EHLO responses will not advertise PIPELINING while +SPAMTHROTTLEFLUSH is set. + +.SH EXAMPLES +These examples assume that +.B QMAILHOME/control/spamthrottle +contains a non-zero value. + +Here is a sample rule file for a home user: +.EX + + # private (trusted) network does not adhere to + # spamthrottle mechanism + 192.168.0.:allow,RELAYCLIENT="",SPAMTHROTTLE="" + # + # some external network which we would like to + # throttle collectively + 10.0.0.:allow,SPAMTHROTTLEDIR="collected" + # + # an external network which is throttled based on + # individual IP address + # - we don't specify SPAMTHROTTLEDIR and the default + # behaviour of storing state files in directories + # based on IP address is used) + # - we also allow relaying from this semi-trusted + # network + 10.1.:allow,RELAYCLIENT="" + # + # allow other connections and treat them as one + # large throttle group + :allow,SPAMTHROTTLEDIR="public" + +.EE + +Here is a sample rule file for a high-volume mail server (or servers) +for some arbitrary ISP (with customer network 10.0.0.0/16 and internal/ +employee network 10.1.0.0/24): +.EX + + # customer network uses default behaviour + # (IP-based throttle files) + 10.0.:allow,RELAYCLIENT="" + # + # employee network doesn't adhere to throttling + 10.1.0.:allow,RELAYCLIENT="",SPAMTHROTTLE="" + # + # external trusted network which legitimately + # provides high volume mail traffic + 10.1.1.:allow,SPAMTHROTTLE="" + # + # a collection of addresses/networks which we + # might have gathered from past abuse experience + # - we allow the mail, but we're aggressive + # about throttling it + 10.1.2.1:allow,SPAMTHROTTLE="5000",SPAMTHROTTLEDIR="abuse" + 10.1.2.2:allow,SPAMTHROTTLE="5000",SPAMTHROTTLEDIR="abuse" + 10.1.2.3:allow,SPAMTHROTTLE="5000",SPAMTHROTTLEDIR="abuse" + 10.1.3.:allow,SPAMTHROTTLE="5000",SPAMTHROTTLEDIR="abuse" + # + # allow other connections and assume throttling + # isn't necessary + :allow,SPAMTHROTTLE="" + +.EE + +.SH "SEE ALSO" +qmail-smtpd(8) --- spam.c Tue May 5 16:32:27 1998 +++ spam.c Wed Jan 2 18:37:47 2002 @@ -0,0 +1,333 @@ +#include "auto_uids.h" +#include "lock.h" +#include "stralloc.h" +#include "control.h" +#include "open.h" +#include "strerr.h" +#include "fmt.h" +#include "timeoutread.h" +#include "error.h" +#include "time.h" +#include "spam.h" + + +int spam_create_directory(dn) +char *dn; +{ + if (mkdir(dn,0700) == -1 && errno != error_exist) + { + strerr_warn2("qmail-smtpd: Throttle spam cannot create directory ",dn,0); + return 0; + } + if (chown(dn,auto_uidd,auto_gidn) == -1) + { + strerr_warn2("qmail-smtpd: Throttle spam cannot chown directory ",dn,0); + return 0; + } + return 1; +} + + +int spam_open_rw_warn(fn) +char *fn; +{ + int fd; + + fd = open_rw(fn); + + if (fd == -1) + { + strerr_warn2("qmail-smtpd: Throttle spam cannot open (rw) file ",fn,0); + close(fn); + return -1; + } + + if (chmod(fn,0600) == -1) + { + strerr_warn2("qmail-smtpd: Throttle spam cannot chmod (0600) file ",fn,0); + close(fd); + return -1; + } + return fd; +} + + +int spam_lock_exfcntl_warn(fd,fn) +int fd; +char *fn; +{ + int rc = 0; + + if (lock_exfcntl(fd) == -1) + { + strerr_warn2("qmail-smtpd: Throttle spam cannot lock file ",fn,0); + rc = -1; + } + return rc; +} + + +int spam_lock_unfcntl_warn(fd,fn) +int fd; +char *fn; +{ + int rc = 0; + + if (lock_unfcntl(fd) == -1) + { + strerr_warn2("qmail-smtpd: Throttle spam cannot unlock file ",fn,0); + rc = -1; + } + return rc; +} + + +int spam_stralloc_path_warn(to,path,pa1,pa2) +stralloc *to; +char *path; +char *pa1; +char *pa2; +{ + int rc = 0; + + if (!stralloc_copys(to,path) || !stralloc_cats(to,pa1) || (pa2 && !stralloc_cats(to,pa2)) || !stralloc_0(to)) + { + strerr_warn1("qmail-smtpd: Throttle spam cannot allocate memory",0); + rc = -1; + } + return rc; +} + + +static unsigned int normalize_recipients( r, r_m ) +unsigned int r; +unsigned int r_m; +{ + static const unsigned int r_min = 1; + unsigned int r_new = r_min; + + if( r_m ) + { + r_new = r - (r >> (r/r_m)); + if( r_new < r_min ) r_new = r_min; + } + + return r_new; +} + + +static unsigned int normalize_wait( w, dt, s_t, s_m, r, r_m ) +unsigned int w; +unsigned int dt; +unsigned int s_t; +unsigned int s_m; +unsigned int r; +unsigned int r_m; +{ + static const unsigned int min_wait = 100; + unsigned int w_new = 0; + + /* + * the maximum throttle times the "reasonable" # recipients + * is approximately the longest wait we want/expect so if + * we've waited that long, then we'll return the minimum + */ + if( r_m && s_m && dt > (s_m * r_m) ) return min_wait; + + /* + * then apply a normalizing factor to the previous wait (w) of + * (s_t * r) / dt, where s_t is our throttle constant (time per msg), + * r is actual # recipients, and dt is time since last imposed wait + */ + w_new = (w * (s_t * r)) / (dt ? dt : 1); + if( w_new < min_wait ) w_new = min_wait; + + return w_new; +} + + +int spam_wait( spamt ) +struct spam_t * spamt; +{ + stralloc sd = {0}; + stralloc sw = {0}; + stralloc st = {0}; + stralloc sl = {0}; + unsigned int pd = 0; + int fdw = 0; + int fdt = 0; + int fdl = 0; + struct q_time_t tn; + struct q_time_t ts; + unsigned int lw = 0; + unsigned long it = 0; + unsigned long sr = 0; + char lwbuf[FMT_ULONG]; + int rc = 1; + + if (!spamt) return 0; + if (!spamt->throttle) return 0; + + if (spam_stralloc_path_warn(&sd,"spam/",spamt->dir,".") == -1) return 0; + + while (1) + { + pd += str_chr(&sd.s[pd],'.'); + if (!sd.s[pd]) break; + + sd.s[pd] = '\0'; + if (!spam_create_directory(sd.s)) return 0; + sd.s[pd] = '/'; + } + + if (spam_stralloc_path_warn(&sl,sd.s,"lock",0) == -1) return 0; + if (spam_stralloc_path_warn(&st,sd.s,"time",0) == -1) return 0; + if (spam_stralloc_path_warn(&sw,sd.s,"wait",0) == -1) return 0; + + if ((fdl = spam_open_rw_warn(sl.s)) == -1) return 0; + + if (spam_lock_exfcntl_warn(fdl,sl.s) == -1) + { + close(fdl); + return 0; + } + + if ((fdt = spam_open_rw_warn(st.s)) == -1) + { + spam_lock_unfcntl_warn(fdl,sl.s); + close(fdl); + return 0; + } + + if ((fdw = spam_open_rw_warn(sw.s)) == -1) + { + spam_lock_unfcntl_warn(fdl,sl.s); + close(fdl); + close(fdt); + return 0; + } + + close(fdw); + close(fdt); + + time_now(&tn); + ts = tn; + switch (control_readtime(&ts,st.s)) { + case -1: + break; + case 0: + strerr_warn3("qmail-smtpd: Throttle spam time file (",st.s,") was opened then not found",0); + rc = 0; + break; + case 1: + { + switch (control_readint(&lw,sw.s)) { + case -1: + strerr_warn4("qmail-smtpd: Throttle spam (in ",sd.s,") has timestamp but is missing wait time in ",sw.s,0); + rc = 0; + break; + case 0: + strerr_warn4("qmail-smtpd: Throttle spam (in ",sd.s,") has timestamp but is missing wait file ",sw.s,0); + rc = 0; + break; + default: + rc = 1; + break; + } + } + default: + rc = 1; + } + + if (!rc) + { + spam_lock_unfcntl_warn(fdl,sl.s); + close(fdl); + return 0; + } + + time_sub(&ts,&tn,&ts); + + it = time_ms(&ts); + lw = normalize_recipients( spamt->rcptcount, spamt->reasonablercpt ) + * normalize_wait( lw, it, spamt->throttle, spamt->max, spamt->rcptcount, spamt->reasonablercpt ); + + switch (control_writetime(&tn,st.s)) { + case -1: + strerr_warn4("qmail-smtpd: Throttle spam (in ",sd.s,") cannot write timestamp to file ",st.s,0); + rc = 0; + break; + case 0: + strerr_warn4("qmail-smtpd: Throttle spam (in ",sd.s,") cannot write timestamp to missing file ",st.s,0); + rc = 0; + break; + default: + rc = 1; + break; + } + + if (!rc) + { + spam_lock_unfcntl_warn(fdl,sl.s); + close(fdl); + return 0; + } + + switch (control_writeint(&lw,sw.s)) { + case -1: + strerr_warn4("qmail-smtpd: Throttle spam (in ",sw.s,") cannot write wait time to file ",sw.s,0); + rc = 0; + break; + case 0: + strerr_warn4("qmail-smtpd: Throttle spam (in ",sw.s,") cannot write wait time to missing file ",sw.s,0); + rc = 0; + break; + default: + rc = 1; + break; + } + + if (spam_lock_unfcntl_warn(fdl) == -1) + { + close(fdl); + return 0; + } + + close(fdl); + + if (!rc) return 0; + + if (lw < 1000) return 0; + + if (spamt->max && lw > spamt->max) lw = spamt->max; + lw /= 1000; + lwbuf[fmt_ulong(lwbuf,lw)] = 0; + strerr_warn4("qmail-smtpd: Throttle spam (in ",sd.s,") sleeping ",lwbuf,0); + + sleep(lw); + + return 1; +} + + +void spam_flush(fd) +int fd; +{ + int flushdone = 0; + char ch; + + /* + * since this is a potential spammer, we'll see if we should + * flush all input (we want to force them to wait for the + * response from their DATA command before we accept input + * from them) + */ + while (!flushdone) + { + if (timeoutread(1,fd,&ch,1) < 1) flushdone = 1; /* timeout/error...doesn't matter */ + } + if (flushdone) + { + strerr_warn1("qmail-smtpd: Throttle spam flushed input",0); + } +} --- spam.h Tue May 5 16:32:27 1998 +++ spam.h Wed Jan 2 18:37:47 2002 @@ -0,0 +1,18 @@ +#ifndef SPAM_H +#define SPAM_H + +extern int spam_wait(); +extern void spam_flush(); + +typedef struct spam_t { + unsigned int throttle; + unsigned int max; + unsigned int reasonablercpt; + unsigned int flush; + char * dir; + unsigned int rcptcount; +} spam_t; + +#define spam_t_init() { 0, 0, 1, 0, 0, 0 } + +#endif --- time.c Tue May 5 16:32:27 1998 +++ time.c Wed Jan 2 18:37:47 2002 @@ -0,0 +1,81 @@ +#include "time.h" +#include "stralloc.h" +#include "fmt.h" +#include "scan.h" + +void time_unpack(s,t) +char *s; +struct q_time_t *t; +{ +#ifdef HASTAI + taia_unpack(s,t); +#else + int i; + + i = scan_ulong(s,&(t->tv_sec)); + if (s[i] == ':') + scan_ulong(s+i+1,&(t->tv_usec)); + else + t->tv_usec = 0; +#endif +} + + +void time_pack(sa,t) +stralloc *sa; +struct q_time_t *t; +{ +#ifdef HASTAI + stralloc_ready(sa,TAIA_PACK); + taia_pack(sa->s,t); +#else + char strnum[FMT_ULONG]; + + stralloc_catb(sa,strnum,fmt_ulong(strnum,(unsigned long)t->tv_sec)); + stralloc_cats(sa,":"); + stralloc_catb(sa,strnum,fmt_ulong(strnum,(unsigned long)t->tv_usec)); + stralloc_0(sa); +#endif +} + +void time_now(t) +struct q_time_t *t; +{ +#ifdef HASTAI + taia_now(t); +#else + gettimeofday(t,0); +#endif +} + + +void time_sub(td,t1,t2) +struct q_time_t *td; +struct q_time_t *t1; +struct q_time_t *t2; +{ +#ifdef HASTAI + taia_sub(td,t1,t2); +#else + unsigned long usec = t2->tv_usec; + + td->tv_sec = t2->tv_sec - t1->tv_sec; + td->tv_usec = usec - t1->tv_sec; + if (td->tv_usec > usec) + { + td->tv_usec += 1000000UL; + --td->tv_sec; + } +#endif +} + + +unsigned long time_ms(t) +struct q_time_t *t; +{ +#ifdef HASTAI + return (unsigned long)((taia_approx(t) + taia_frac(t)) * (double)1000.0); +#else + return (unsigned long)((t->tv_sec + t->tv_usec * (double)1000.0) * (double)1000.0); +#endif +} --- time.h Tue May 5 16:32:27 1998 +++ time.h Wed Jan 2 18:37:47 2002 @@ -0,0 +1,22 @@ +#ifndef TIME_H +#define TIME_H + +#include "hastai.h" + +#ifdef HASTAI +# include +# define q_time_t taia +# define Q_TIME_PACK TAIA_PACK +#else +# include +# define q_time_t timeval +# define Q_TIME_PACK 0 +#endif + +void time_pack(); +void time_unpack(); +void time_now(); +void time_sub(); +unsigned long time_ms(); + +#endif --- tryltai.c Tue May 5 16:32:27 1998 +++ tryltai.c Wed Jan 2 18:37:47 2002 @@ -0,0 +1,4 @@ +main() +{ + ; +} --- trytai.c Tue May 5 16:32:27 1998 +++ trytai.c Wed Jan 2 18:37:47 2002 @@ -0,0 +1,7 @@ +#include + +void main() +{ + struct taia t; + taia_now(&t); +}