Index: Makefile =================================================================== RCS file: /qmail/qmail/Makefile,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.1 diff -u -r1.1.1.1 -r1.1.1.1.2.1 --- Makefile 12 Jan 2003 07:57:58 -0000 1.1.1.1 +++ Makefile 12 Jan 2003 08:00:19 -0000 1.1.1.1.2.1 @@ -1536,13 +1536,13 @@ 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 +fs.a auto_qmail.o socket.lib dns.o dns.lib ./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` + socket.lib` dns.o `cat dns.lib` qmail-smtpd.0: \ qmail-smtpd.8 Index: qmail-control.9 =================================================================== RCS file: /qmail/qmail/qmail-control.9,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.1 diff -u -r1.1.1.1 -r1.1.1.1.2.1 --- qmail-control.9 12 Jan 2003 07:57:58 -0000 1.1.1.1 +++ qmail-control.9 12 Jan 2003 08:00:19 -0000 1.1.1.1.2.1 @@ -55,6 +55,7 @@ .I idhost \fIme \fRqmail-inject .I localiphost \fIme \fRqmail-smtpd .I locals \fIme \fRqmail-send +.I mfcheck \fR0 \fRqmail-smtpd .I morercpthosts \fR(none) \fRqmail-smtpd .I percenthack \fR(none) \fRqmail-send .I plusdomain \fIme \fRqmail-inject Index: qmail-smtpd.8 =================================================================== RCS file: /qmail/qmail/qmail-smtpd.8,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.1 diff -u -r1.1.1.1 -r1.1.1.1.2.1 --- qmail-smtpd.8 12 Jan 2003 07:57:58 -0000 1.1.1.1 +++ qmail-smtpd.8 12 Jan 2003 08:00:19 -0000 1.1.1.1.2.1 @@ -97,6 +97,12 @@ This is done before .IR rcpthosts . .TP 5 +.I mfcheck +If set, +.B qmail-smtpd +tries to resolve the domain of the envelope from address. It can be +handy when you want to filter out spamhosts. +.TP 5 .I morercpthosts Extra allowed RCPT domains. If Index: qmail-smtpd.c =================================================================== RCS file: /qmail/qmail/qmail-smtpd.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.23 diff -u -r1.1.1.1 -r1.1.1.1.2.23 --- qmail-smtpd.c 12 Jan 2003 07:57:58 -0000 1.1.1.1 +++ qmail-smtpd.c 25 Jun 2004 11:06:37 -0000 1.1.1.1.2.23 @@ -23,11 +23,16 @@ #include "timeoutread.h" #include "timeoutwrite.h" #include "commands.h" +#include "dns.h" +#include #define MAXHOPS 100 unsigned int databytes = 0; +unsigned int mfchk = 0; int timeout = 1200; +unsigned int paranoidchk = 0; + int safewrite(fd,buf,len) int fd; char *buf; int len; { int r; @@ -50,6 +55,9 @@ void straynewline() { 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_brt() { out("553 sorry, your envelope recipient is in my badrcptto list (#5.7.1)\r\n"); } +void err_hmf() { out("553 sorry, your envelope sender domain must exist (#5.7.1)\r\n"); } +void err_smf() { 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_syntax() { out("555 syntax error (#5.5.4)\r\n"); } @@ -58,7 +66,16 @@ void err_noop() { out("250 ok\r\n"); } void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); } void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); } - +void err_rej(char *host) { + out("553 Sorry, I cannot talk with such a dubious mail server["); + out(host); + out("](#5.7.1)\r\n"); } +void err_ptr() { out("553 Your IP address's PTR record points to wrong hostname(#5.7.1)\r\n"); } +void err_reqptr() { out("553 Sorry, we request correct PTR record in your address domain(#5.7.1)\r\n"); } +void err_nofwd() { + out("553 I can accept only "); + out(env_get("ACCEPTDOMAINS")); + out("domains from your server (#5.7.1)\r\n"); } stralloc greeting = {0}; @@ -96,6 +113,19 @@ int bmfok = 0; stralloc bmf = {0}; struct constmap mapbmf; +int brtok = 0; +stralloc brt = {0}; +struct constmap mapbrt; + +int bhlok = 0; +stralloc bhl = {0}; +struct constmap mapbhl; + +/* bad HELO check */ +int flagbahl = 0; + +/* ACCEPTDOMAINS result */ +int flagacceptdom = 0; void setup() { @@ -112,11 +142,29 @@ if (rcpthosts_init() == -1) die_control(); + if (control_readint(&mfchk,"control/mfcheck") == -1) die_control(); + x = env_get("MFCHECK"); + if (x) { scan_ulong(x,&u); mfchk = u; } + + if (control_readint(¶noidchk,"control/paranoid") == -1) die_control(); + x = env_get("PARANOIDCHECK"); + if (x) { scan_ulong(x,&u); paranoidchk = u; } + bmfok = control_readfile(&bmf,"control/badmailfrom",0); 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(); + bhlok = control_readfile(&bhl,"control/badhelo",0); + if (bhlok == -1) die_control(); + if (bhlok) + if (!constmap_init(&mapbhl,bhl.s,bhl.len,0)) die_nomem(); + if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&u); databytes = u; } @@ -199,12 +247,129 @@ int bmfcheck() { + /* Return 0: if it seems valid. + * Return 1: if it matches with an entry in control/badmailfrom + * Return 2: if doesn't have domain part (without @ mark) + */ int j; + char *ad; if (!bmfok) return 0; + if (env_get("RELAYCLIENT") || env_get("RELIABLECLIENT")) + return 0; /* pass the reliable clients */ + /* Fixed string matching */ if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1; j = byte_rchr(addr.s,addr.len,'@'); + if (addr.len > 1 && j >= addr.len) + return 2; /* when sender=<>, addr.len=1 */ + /* `j' will be used below */ + /* Compare with $ACCEPTDOMAINS */ + ad = env_get("ACCEPTDOMAINS"); + /* $ACCEPTDOMAINS is a delimited list of acceptable mail domains. + Typical values are as follows; + + @yahoo.com : accepts only @yahoo.com domain + .hotmail.com : accepts any @*.hotmail.com + @hotmail.com/.hotmail.com : accepts @hotmail.com and @*.hotmail.com + + multiple domain list is delimited by `/'. + + It would be good to put @hotmail.com and any major free-mail domain + list in /var/qmail/control/badmailfrom file while setting + ACCEPTDOMAINS variable in tcpserver's rule file like this; + + =.hotmail.com:allow,ACCEPTDOMAINS="@hotmail.com" + + With these settings, qmail-smtpd prevents any fake @hotmail.com. + The 'true' hotmail.com's mail servers can send @hotmail.com messages. + */ + if (ad) { + char *ds=ad; + int len = str_len(ad); + int slash, at, dot, ok=0; + at = byte_rchr(addr.s,addr.len,'@'); + while (ds < ad+len) { + slash = byte_chr(ds,ad+len-ds,'/'); + /* compare with user@domain */ + if (slash == addr.len-1) + if (!str_diffn(ds,addr.s,slash)) { + ok = 1; + break; + } + /* compare with @domain */ + if (at < addr.len && slash == addr.len-at-1) + if (!str_diffn(ds,addr.s+at,slash)) { + ok = 1; + break; + } + /* compare with wild card matching */ + for (dot=at; dot 5) + return 3; + if (arg[0] >= '0' && arg[0] <= '9' /* in case it begins with digit */ + && arg[j+1] >= '0' && arg[j+1] <= '9') { /* && last segment begins with digit */ + struct ip_address ip; /* bogus IP address check */ + if (ip_scan(arg, &ip)) { /* arg seems to be decimal IP-address */ + if (str_diff(arg, remoteip)) + return 1; /* IP in HELO differs */ + } + } else { /* in case it begin wiht non-digit */ + if (arg[str_len(arg)-1] >= '0' && arg[str_len(arg)-1] <= '9') + return 3; /* last char of tld can't be a digit */ + } + /* unknown helo check */ + if (str_equal("unknown",remotehost)) { + static stralloc unkohelo = {0}; + if (!stralloc_copys(&unkohelo,arg)) die_nomem(); + if (!stralloc_cats(&unkohelo,":unknown")) die_nomem(); + if (constmap(&mapbhl,unkohelo.s,unkohelo.len)) return 4; + /* wild card matching */ + for (j=str_chr(unkohelo.s,'.');unkohelo.s[j];++j) + if (unkohelo.s[j] == '.') + if (constmap(&mapbhl,unkohelo.s+j,unkohelo.len-j)) return 4; + + } + return 0; +} int seenmail = 0; int flagbarf; /* defined if seenmail */ +int flagmfcheck; stralloc mailfrom = {0}; stralloc rcptto = {0}; @@ -226,11 +445,13 @@ { smtp_greet("250 "); out("\r\n"); seenmail = 0; dohelo(arg); + flagbahl = helocheck(); } void smtp_ehlo(arg) char *arg; { smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n"); seenmail = 0; dohelo(arg); + flagbahl = helocheck(); } void smtp_rset() { @@ -241,6 +462,7 @@ { if (!addrparse(arg)) { err_syntax(); return; } flagbarf = bmfcheck(); + flagmfcheck = mfcheck(); seenmail = 1; if (!stralloc_copys(&rcptto,"")) die_nomem(); if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); @@ -248,22 +470,169 @@ out("250 ok\r\n"); } void smtp_rcpt(arg) char *arg; { + int flagbadhost = 0; + int rely=0; + int somethingbad = 0; + static stralloc badreason = {0}; + if (!stralloc_copys(&badreason,"")) die_nomem(); if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } - if (flagbarf) { err_bmf(); return; } + if (flagmfcheck) { + somethingbad = flagmfcheck; + switch (flagmfcheck) { + case DNS_HARD: + err_hmf(); + stralloc_copys(&badreason,"DOMAIN"); + break; + case DNS_SOFT: err_smf(); return; + case DNS_MEM: die_nomem(); + } + } + if (flagbarf) { + char *types[] = {"", "_noDOMAIN"}; + char *type = types[(flagbarf-1)%((sizeof types)/(sizeof (char*)))]; + if (somethingbad) { + if (!stralloc_cats(&badreason,"/")) die_nomem(); + } else { + /* We produce SMTP ERROR only once. Ditto in latter err_XXX() */ + err_bmf(); + } + if (!stralloc_cats(&badreason,"MAILFROM")) die_nomem(); + if (!stralloc_cats(&badreason,type)) die_nomem(); + somethingbad = flagbarf; + } + rely = (flagacceptdom||env_get("RELAYCLIENT")||env_get("RELIABLECLIENT")); + flagbarf = brtcheck(); + if (!rely && flagbarf) { + if (somethingbad) { + if (!stralloc_cats(&badreason,"/")) die_nomem(); + } else { + err_brt(); + } + if (!stralloc_cats(&badreason,"RCPT")) die_nomem(); + somethingbad = flagbarf; + } if (relayclient) { --addr.len; if (!stralloc_cats(&addr,relayclient)) die_nomem(); if (!stralloc_0(&addr)) die_nomem(); } else - if (!addrallowed()) { err_nogateway(); return; } + if (!addrallowed()) { + if (somethingbad) { + if (!stralloc_cats(&badreason,"/")) die_nomem(); + } else { + err_nogateway(); + } + if (!stralloc_cats(&badreason,"RELAY")) die_nomem(); + somethingbad = 1; + } + /* Reject all but in $ACCEPTDOMAINS */ + if (!rely && (int)env_get("ADONLY")) { + if (somethingbad) { + if (!stralloc_cats(&badreason,"/"))die_nomem(); + } else { + err_nofwd(); + } + if (!stralloc_cats(&badreason,"ADONLY")) die_nomem(); + somethingbad = 1; + } + /* badhost check */ + flagbadhost = (int)env_get("BADHOST"); + if (!rely && (flagbahl || flagbadhost)) { + char *types[] = {"HELO", "HELO_noDot", "HELO_random", "HELO_unknownbad"}; + char *type; + if (flagbadhost) { + type = "HOST"; + } else { + type = types[(flagbahl-1)%((sizeof types)/(sizeof (char*)))]; + } + if (somethingbad) { + if (!stralloc_cats(&badreason,"/")) die_nomem(); + } else + err_rej(helohost.s); + if (!stralloc_cats(&badreason,type)) die_nomem(); + somethingbad = 1; + } + if (!rely && env_get("REQPTR") && str_equal("unknown", remotehost)) { + if (somethingbad) { + if (!stralloc_cats(&badreason,"/")) die_nomem(); + } else + err_reqptr(); + if (!stralloc_cats(&badreason,"REQPTR")) die_nomem(); + somethingbad = 1; + } + if (!rely && paranoidchk) { + if (env_get("TCPPARANOID")) { /* This requires `tcpserver -p' */ + if (somethingbad) { + if (!stralloc_cats(&badreason,"/")) die_nomem(); + } else { + err_ptr(); + } + if (!stralloc_cats(&badreason,"PTR")) die_nomem(); + somethingbad = 1; + openlog("qmail-smtpd", LOG_PID, LOG_MAIL); + syslog(LOG_INFO, "Paranoid: helo=[%s], host=%s[%s], sender=[%s], rcpt=[%s]", helohost.s, env_get("TCPPARANOID"), remoteip, mailfrom.s, addr.s); + closelog(); + } + } + if (!stralloc_0(&badreason)) die_nomem(); +#if 1 + if (!str_equal(remoteip, "127.0.0.1")) { +# include +# include + struct stat st; + static stralloc log = {0}; + char *logpipe = "control/qmlog"; + openlog("qmail-smtpd", LOG_PID, LOG_LOCAL1); + if (-1 != stat(logpipe, &st) + && st.st_mode&S_IWUSR + && st.st_mode&S_IFIFO) { + if (!fork()) { + int pipe = open(logpipe, O_WRONLY|O_NDELAY); + if (-1 != pipe) { + if (!stralloc_copys(&log,"QLV_01: ")) die_nomem(); + if (somethingbad) { + if (!stralloc_cats(&log,"Rejected_")) die_nomem(); + if (!stralloc_cats(&log,badreason.s)) die_nomem(); + } else { + if (!stralloc_cats(&log,"Accepted")) die_nomem(); + } + if (!stralloc_cats(&log,": ")) die_nomem(); + if (!stralloc_cats(&log,"helo=")) die_nomem(); + if (!stralloc_cats(&log,helohost.s)) die_nomem(); + if (!stralloc_cats(&log,", remotehost=")) die_nomem(); + if (!stralloc_cats(&log,remotehost)) die_nomem(); + if (!stralloc_cats(&log,", remoteip=")) die_nomem(); + if (!stralloc_cats(&log,remoteip)) die_nomem(); + if (!stralloc_cats(&log,", sender=")) die_nomem(); + if (!stralloc_cats(&log,mailfrom.s)) die_nomem(); + if (!stralloc_cats(&log,", rcpt=")) die_nomem(); + if (!stralloc_cats(&log,addr.s)) die_nomem(); + if (!stralloc_cats(&log,"\n")) die_nomem(); + if (!stralloc_0(&log)) die_nomem(); + write(pipe,log.s,log.len); /* Don't care its failure */ + close(pipe); + } + exit(0); + } + } + syslog(LOG_INFO, "%s: helo=%s, host=%s, remoteip=%s, sender=%s, rcpt=%s, relayclient=%s", somethingbad ? "Rejected" : "Accepted", helohost.s, remotehost, remoteip, mailfrom.s, addr.s, (env_get("RELAYCLIENT") ? "yes" : "no")); + closelog(); + } +#endif + if (somethingbad) { + openlog("qmail-smtpd", 0, LOG_MAIL); + syslog(LOG_INFO, "bad %s: host=%s, remoteip=%s, helo=%s, sender=%s, rcpt=%s", badreason.s, remotehost, remoteip, helohost.s, mailfrom.s, addr.s); + closelog(); + seenmail = 0; + return; + } if (!stralloc_cats(&rcptto,"T")) die_nomem(); if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); out("250 ok\r\n"); } - int saferead(fd,buf,len) int fd; char *buf; int len; {