From: Lionel Widdifield To: Russell Nelson Subject: Re: Qmail SMTPD SPAM diff Date: Sat, 5 Apr 1997 00:36:55 -0800 Below, hopefully, will be attached a diff for qmail-smtpd.c for better logging and SPAM deferal for inclusion to the www.qmail.org site. The SPAM blocking uses an new env variable DENYMAIL. It currently has t settings 1) "SPAM" ==> qmail-smtpd will politely deny all mail. 2) "NOBOUNCE" ==> qmail-smtpd will politely drop all mail with a null sender. Usefull for the stealth bulkemailer operating out of worldnet.att.net It also logs HELO <> remote host, badmailfrom checking Date: Mon, 28 Apr 1997 16:29:35 -0700 Attached is the latest qmail-smtpd SPAM patch. It patches qmail-smtpd.c and Makefile from qmail-1.00 cleanly, while qmail-1.01 requires several lines to be hand added to the start of qmail-smtpd.c . The latest adds DNS verificating of the mailfrom for the targeted ip hosts. -- Lionel Widdifield [] Spydernet Communications Systems Inc. [] P.O. Box 8406 lwiddif@spydernet.com [] Victoria BC, Canada V8W 3S1 [] Phone (250)383-2999 Fax 480-7262 --- orig/qmail-smtpd.c Mon Mar 17 16:53:32 1997 +++ qmail-smtpd.c Mon Apr 28 16:15:38 1997 @@ -1,8 +1,17 @@ +#define BADRCPT 1 +#define XLOGGING 1 +#define DENYSPAM 1 +#define MAILFROMCHECK 1 +#define MAILFROMDNS 1 +/*zzz Version 0.7*/ #include "signal.h" #include "readwrite.h" #include "getline.h" #include "stralloc.h" #include "substdio.h" +#ifdef XLOGGING +#include "subfd.h" +#endif #include "alloc.h" #include "conf-home.h" #include "control.h" @@ -19,6 +28,9 @@ #include "env.h" #include "now.h" #include "exit.h" +#ifdef MAILFROMDNS +#include "dns.h" +#endif #define MAXHOPS 100 int timeout = 1200; @@ -58,6 +70,11 @@ stralloc bmf = {0}; struct constmap mapbmf; int flagbarf; /* defined if seenmail */ +#ifdef BADRCPT +int brtok = 0; +stralloc brt = {0}; +struct constmap mapbadrcptto; +#endif stralloc helohost = {0}; stralloc mailfrom = {0}; @@ -71,6 +88,9 @@ char *remoteinfo; char *local; char *relayclient; +#ifdef DENYSPAM +char *denymail; +#endif void dohelo(arg) char *arg; { @@ -90,6 +110,9 @@ remoteinfo = env_get("TCPREMOTEINFO"); relayclient = env_get("RELAYCLIENT"); dohelo(remotehost); +#ifdef DENYSPAM + denymail = env_get("DENYMAIL"); +#endif } void straynewline() @@ -230,9 +253,106 @@ return 1; } +#ifdef XLOGGING +static int issafe(ch) char ch; +{ + if (ch == '.') return 1; + if (ch == '@') return 1; + if (ch == '%') return 1; + if (ch == '+') return 1; + if (ch == '/') return 1; + if (ch == '=') return 1; + if (ch == ':') return 1; + if (ch == '-') return 1; + if ((ch >= 'a') && (ch <= 'z')) return 1; + if ((ch >= 'A') && (ch <= 'Z')) return 1; + if ((ch >= '0') && (ch <= '9')) return 1; + return 0; +} + +static void safeErr(s) char *s; +{ + char ch; + while (ch = *s++) { + if (!issafe(ch)) ch = '?'; + substdio_BPUTC(subfderr, ch); + /*substdio_bput(subfderr, &ch, 1);*/ + } +} +#ifdef BADRCPT +static void log_brt(s) char *s; +{ + substdio_bputs(subfderr, "BRT: check failed ("); + safeErr(s); + substdio_bputs(subfderr, ")\n"); + substdio_flush(subfderr); +} +#endif +static void log_bmf() +{ + substdio_bputs(subfderr, "BMF: check failed ("); + safeErr(mailfrom.s); + substdio_bputs(subfderr, ")\n"); + substdio_flush(subfderr); +} +#ifdef DENYSPAM +static void log_deny(m,f,t) char *m,*f,*t; +{ + substdio_bputs(subfderr, "DENYMAIL: "); safeErr(m); + substdio_bputs(subfderr, " check failed ("); safeErr(f); + substdio_bputs(subfderr, ") -> ("); safeErr(t); + substdio_bputs(subfderr, ")\n"); + substdio_flush(subfderr); +} +#endif +static void log_helo() +{ + substdio_bputs(subfderr, "Received: from "); + safeErr(remotehost); + substdio_bputs(subfderr, " (HELO "); + safeErr(helohost.s); + substdio_bputs(subfderr, ")\n"); + substdio_flush(subfderr); +} + +#ifdef MAILFROMDNS +int badmxcheck(dom) char *dom; +{ + ipalloc checkip = {0}; + int ret=0; + stralloc checkhost = {0}; + + if (!*dom) return (DNS_HARD); + if (!stralloc_copys(&checkhost,dom)) return (DNS_SOFT); + + switch (dns_mxip(&checkip,&checkhost,1)) + { + case DNS_MEM: + case DNS_SOFT: + ret=DNS_SOFT; + break; + + case DNS_HARD: + ret=DNS_HARD; + break; + case 1: + if (checkip.len <= 0) ret=DNS_HARD; + break; + } + + return (ret); +} +#endif +#endif + int addrallowed() { int j; +#ifdef BADRCPT + if (brtok) + if (constmap(&mapbadrcptto, addr.s, addr.len - 1)) + {log_brt(addr.s); return 0;} +#endif if (!rhok) return 1; j = byte_rchr(addr.s,addr.len,'@'); if (j >= addr.len) return 1; /* can be taken care of by envnoathost */ @@ -251,7 +371,15 @@ if (constmap(&mapbmf,addr.s,addr.len - 1)) { flagbarf = 1; return; } j = byte_rchr(addr.s,addr.len,'@'); if (j < addr.len) - if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) flagbarf = 1; +#ifdef DENYSPAM + { +#endif + if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) { flagbarf = 1; return; } +#ifdef DENYSPAM + /* allow for bulkemailer@ checking */ + if (constmap(&mapbmf,addr.s, j + 1)) { flagbarf = 1; return; } + } +#endif } void smtp_greet(code) char *code; { @@ -260,7 +388,11 @@ void smtp_quit() { smtp_greet("221 "); out("\r\n"); die(); } void smtp_help() { out("214-qmail home page: http://pobox.com/~djb/qmail.html\r\n214 send comments to qmail@pobox.com\r\n"); } void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); } -void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } +/* zzz + void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } +*/ +void err_bmf() { out("553 syntax error, please forward to your postmaster (#5.7.1)\r\n"); } +void err_dns() { out("451 DNS temporary failure (#4.3.0)\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_seenmail() { out("503 one MAIL per message (#5.5.1)\r\n"); } @@ -286,10 +418,11 @@ if (!stralloc_copys(&mailfrom,addr.s)) outofmem(); if (!stralloc_0(&mailfrom)) outofmem(); } void smtp_rcpt(arg) char *arg; { +int i,j; char *why; if (!seenmail) { err_wantmail(); return; } if (!arg) { err_syntax(); return; } if (!addrparse(arg)) { err_syntax(); return; } - if (flagbarf) { err_bmf(); return; } + if (flagbarf) { err_bmf(); log_bmf(); return; } if (relayclient) { --addr.len; @@ -298,10 +431,80 @@ } else if (!addrallowed()) { err_nogateway(); return; } +#ifdef DENYSPAM + /* DENYMAIL is set for this session from this client, + so heavy checking of mailfrom */ + /* SPAM -> refuse all mail */ + /* NOBOUNCE -> refuse null mailfrom */ + /* DNSCHECK -> validate Mailfrom domain */ + if (denymail) + { + why = denymail; + + if (!str_diff("SPAM", denymail)) + flagbarf=1; + else + if (!mailfrom.s[0] || !str_diff("#@[]", mailfrom.s)) /*mjr*/ + /* if (!mailfrom.s[0]) */ + { + if (!str_diff("NOBOUNCE", denymail)) + flagbarf=1; + } +#ifdef MAILFROMCHECK + else + { + /*why = "Invalid.Mailfrom";*/ + why = "Mailfrom.Syntax"; + if ((i=byte_chr(mailfrom.s,mailfrom.len,'@')) >= mailfrom.len) + flagbarf=1; /* no '@' in from */ + else + { + /* check syntax, visual */ + if ((j = byte_rchr(mailfrom.s+i, mailfrom.len-i, '.')) >= mailfrom.len-i) + flagbarf=1; /* curious no '.' in domain.TLD */ + + j = mailfrom.len-(i+1+j+1); + if (j < 2 || j > 3) + flagbarf=1; /* root domain, not a country (2), nor TLD (3)*/ + +#ifdef MAILFROMDNS + if (!flagbarf) + if (!str_diff("DNSCHECK", denymail)) + { + /* check syntax, via DNS */ + why = "Mailfrom.DNS"; + switch (badmxcheck(&mailfrom.s[i+1])) + { + case 0: break; /*valid*/ + case DNS_SOFT: flagbarf=2; /*fail tmp*/ + why = "Mailfrom.DNS.temp"; + break; + case DNS_HARD: flagbarf=1; + break; + } + } +#endif + } + } +#endif + +logit: + if (flagbarf) + { + log_deny(why, mailfrom.s, addr.s); + if (2==flagbarf) + err_dns(); + else + err_bmf(); + return; + } + }/* denymail */ +#endif out("250 ok\r\n"); if (!stralloc_cats(&rcptto,"T")) outofmem(); if (!stralloc_cats(&rcptto,addr.s)) outofmem(); - if (!stralloc_0(&rcptto)) outofmem(); } + if (!stralloc_0(&rcptto)) outofmem(); +} char accept_buf[FMT_ULONG]; void acceptmessage(qp) unsigned long qp; @@ -326,7 +529,8 @@ qp = qqtalk_qp(&qqt); out("354 go ahead\r\n"); - received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,case_diffs(remotehost,helohost.s) ? helohost.s : 0); + received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,(r=case_diffs(remotehost,helohost.s)) ? helohost.s : 0); + if (r) log_helo(); blast(&ssin,&hops); hops = (hops >= MAXHOPS); if (hops) qqtalk_fail(&qqt); @@ -416,6 +620,15 @@ bmfok = 1; if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die(); } +#ifdef BADRCPT + switch(control_readfile(&brt,"control/badrcptto",0)) + { + case -1: die(); + case 1: + brtok = 1; + if (!constmap_init(&mapbadrcptto,brt.s,brt.len,0)) die(); + } +#endif } void main() --- orig/Makefile Wed Apr 23 12:39:59 1997 +++ Makefile Wed Apr 23 12:23:03 1997 @@ -1318,7 +1318,7 @@ date822fmt.o signal.o now.o libopen.a libcase.a qqtalk.o libwait.a \ constmap.o libgetline.a libstralloc.a libsubstdio.a libenv.a \ liballoc.a liberror.a libstr.a libfs.a libfd.a load - ./load qmail-smtpd ipme.o ip.o ipalloc.o control.o received.o \ + ./loaddns qmail-smtpd dns.o ipme.o ip.o ipalloc.o control.o received.o \ datetime.o date822fmt.o signal.o now.o qqtalk.o \ constmap.o libgetline.a libstralloc.a libsubstdio.a \ libfd.a \ .