Index: qmail-smtpd.c
===================================================================
RCS file: /qmail/qmail/qmail-smtpd.c,v
retrieving revision 1.1.1.1.2.1
retrieving revision 1.1.1.1.2.23
diff -u -r1.1.1.1.2.1 -r1.1.1.1.2.23
--- qmail-smtpd.c	12 Jan 2003 08:00:19 -0000	1.1.1.1.2.1
+++ qmail-smtpd.c	25 Jun 2004 11:06:37 -0000	1.1.1.1.2.23
@@ -24,12 +24,15 @@
 #include "timeoutwrite.h"
 #include "commands.h"
 #include "dns.h"
+#include <syslog.h>
 
 #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;
@@ -52,6 +55,7 @@
 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"); }
@@ -62,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};
 
@@ -100,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()
 {
@@ -120,11 +146,25 @@
   x = env_get("MFCHECK");
   if (x) { scan_ulong(x,&u); mfchk = u; }
 
+  if (control_readint(&paranoidchk,"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; }
@@ -207,12 +247,84 @@
 
 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<addr.len; dot++) {
+        if (addr.s[dot] == '.')
+          if (slash == addr.len-dot-1)
+            if (!str_diffn(ds,addr.s+dot,slash)) {
+              ok = 1;
+              goto out;
+            }
+      }
+      ds += slash+1;
+    }
+  out:
+    if (ok) {
+      flagacceptdom = 1;
+      return 0;
+    }
+  }
+  /* check if it matches with subdomain */
   if (j < addr.len)
     if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1;
+  /* .foo.bar (wild carding) */
+  for (;j < addr.len;++j)
+    if (addr.s[j] == '.')
+      if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1;
   return 0;
 }
 
@@ -222,19 +334,45 @@
   ipalloc ia = {0};
   unsigned int random;
   int j;
+  int verp=0;
 
   if (!mfchk) return 0;
+  if (env_get("RELAYCLIENT") || env_get("RELIABLECLIENT"))
+    return 0;
   random = now() + (getpid() << 16);
-  j = byte_rchr(addr.s,addr.len,'@') + 1;
+  if (str_equal(addr.s+addr.len-5, "#@[]")) /* Should we accept?  */
+    return 0;
+  verp = str_equal(addr.s+addr.len-5, "-@[]");
+  if (verp) {
+    j = byte_chr(addr.s,addr.len,'@') + 1;
+  } else {
+    j = byte_rchr(addr.s,addr.len,'@') + 1;
+  }
   if (j < addr.len) {
     stralloc_copys(&sa, addr.s + j);
+    if (verp) {			/* if sender is VERP seed */
+      sa.len -= 4;
+      sa.s[sa.len] = 0;
+    }
     dns_init(0);
     j = dns_mxip(&ia,&sa,random);
-    if (j < 0) return j;
+    if (j < 0)
+      return j;
   }
   return 0;
 }
 
+int brtcheck()
+{
+  int j;
+  if (!brtok) return 0;
+  if (constmap(&mapbrt,addr.s,addr.len - 1)) return 1;
+  j = byte_rchr(addr.s,addr.len,'@');
+  if (j < addr.len)
+    if (constmap(&mapbrt,addr.s + j,addr.len - j - 1)) return 1;
+  return 0;
+}
+
 int addrallowed()
 {
   int r;
@@ -243,9 +381,63 @@
   return r;
 }
 
+int helocheck(arg) char *arg;
+{
+  /* Return 0:	if it seems valid,
+   * Return 1:	if it matches badhelo
+   * Return 2:	if not dots in helo string
+   * Return 3:	invalid tld
+   * Return 4:	matches with badhelo:unknown
+   */
+  int j;
+  if (env_get("RELAYCLIENT") || env_get("RELIABLECLIENT"))
+    return 0;		/* Relay Client's are allowed to use bogus HELO host */
+
+  if (!bhlok) return 0;
+  if (constmap(&mapbhl,arg,str_len(arg))) return 1;
+  j = str_chr(arg,'.');
+  /* no dots, reject it */
+  if ('\0' == arg[j])
+    return 2;
+  if (arg[j])
+    if (constmap(&mapbhl,arg + j,str_len(arg) - j)) return 1;
+  /* .foo.bar (wild carding) */
+  for (;arg[j];++j)
+    if (arg[j] == '.')
+      if (constmap(&mapbhl,arg + j,str_len(arg) - j)) return 1;
+  /* random helo with dots */
+  j = str_rchr(arg,'.');
+  if ((str_len(arg)-j) > 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};
 
@@ -253,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()
 {
@@ -268,11 +462,7 @@
 {
   if (!addrparse(arg)) { err_syntax(); return; }
   flagbarf = bmfcheck();
-  switch(mfcheck()) {
-    case DNS_HARD: err_hmf(); return;
-    case DNS_SOFT: err_smf(); return;
-    case DNS_MEM: die_nomem();
-  }
+  flagmfcheck = mfcheck();
   seenmail = 1;
   if (!stralloc_copys(&rcptto,"")) die_nomem();
   if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();
@@ -280,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 <sys/stat.h>
+#   include <fcntl.h>
+    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;
 {
