changeset 249:15bf4f68a0b2

Add dnswl support
author Carl Byington <carl@five-ten-sg.com>
date Sun, 08 Apr 2012 11:42:59 -0700
parents b0738685bf51
children 3218ab6af599
files ChangeLog NEWS configure.in dnsbl.conf dnsbl.spec.in src/context.cpp src/context.h src/dnsbl.cpp src/dnsbl.h xml/dnsbl.in
diffstat 10 files changed, 238 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Jan 06 22:07:45 2012 -0800
+++ b/ChangeLog	Sun Apr 08 11:42:59 2012 -0700
@@ -1,3 +1,6 @@
+6.29 2012-04-08
+    Add dnswl support.
+
 6.28 2011-09-30
     Add prvs decoding to envelope addresses.
 
--- a/NEWS	Fri Jan 06 22:07:45 2012 -0800
+++ b/NEWS	Sun Apr 08 11:42:59 2012 -0700
@@ -1,3 +1,4 @@
+6.29 2012-04-08 Add dnswl support.
 6.28 2011-09-30 Add prvs decoding to envelope addresses.
 6.27 2011-08-15 const correctness fixes from new gcc
 6.26 2010-11-19 64 bit fixes for libresolv.a
--- a/configure.in	Fri Jan 06 22:07:45 2012 -0800
+++ b/configure.in	Sun Apr 08 11:42:59 2012 -0700
@@ -1,6 +1,6 @@
 
 AC_PREREQ(2.59)
-AC_INIT(dnsbl,6.28,carl@five-ten-sg.com)
+AC_INIT(dnsbl,6.29,carl@five-ten-sg.com)
 AC_CONFIG_SRCDIR([config.h.in])
 AC_CONFIG_HEADER([config.h])
 
--- a/dnsbl.conf	Fri Jan 06 22:07:45 2012 -0800
+++ b/dnsbl.conf	Sun Apr 08 11:42:59 2012 -0700
@@ -40,7 +40,9 @@
     dnsbl   local   blackholes.five-ten-sg.com  "Mail from %s rejected - local; see http://www.five-ten-sg.com/blackhole.php?%s";
     dnsbl   sbl     zen.spamhaus.org            "Mail from %s rejected - sbl; see http://www.spamhaus.org/query/bl?ip=%s";
     dnsbl   xbl     xbl.spamhaus.org            "Mail from %s rejected - xbl; see http://www.spamhaus.org/query/bl?ip=%s";
+    dnswl   dnswl.org  list.dnswl.org   2;
     dnsbl_list  local sbl;
+    dnswl_list  dnswl.org;
 
     content on {
         filter    sbl-xbl.spamhaus.org        "Mail containing %s rejected - sbl; see http://www.spamhaus.org/query/bl?ip=%s";
--- a/dnsbl.spec.in	Fri Jan 06 22:07:45 2012 -0800
+++ b/dnsbl.spec.in	Sun Apr 08 11:42:59 2012 -0700
@@ -103,6 +103,9 @@
 
 
 %changelog
+* Fri Apr 08 2012 Carl Byington <carl@five-ten-sg.com> - 6.29-1
+- Add dnswl support.
+
 * Fri Sep 30 2011 Carl Byington <carl@five-ten-sg.com> - 6.28-1
 - Add prvs decoding to envelope addresses.
 
--- a/src/context.cpp	Fri Jan 06 22:07:45 2012 -0800
+++ b/src/context.cpp	Sun Apr 08 11:42:59 2012 -0700
@@ -32,6 +32,8 @@
 const char *token_default;
 const char *token_dnsbl;
 const char *token_dnsbll;
+const char *token_dnswl;
+const char *token_dnswll;
 const char *token_envfrom;
 const char *token_envto;
 const char *token_filter;
@@ -597,6 +599,20 @@
 }
 
 
+DNSWL::DNSWL(const char *n, const char *s, const int l) {
+    name    = n;
+    suffix  = s;
+    level   = l;
+}
+
+
+bool DNSWL::operator==(const DNSWL &rhs) {
+    return (strcmp(name,    rhs.name)    == 0) &&
+           (strcmp(suffix,  rhs.suffix)  == 0) &&
+           (level == rhs.level);
+}
+
+
 CONFIG::CONFIG() {
     reference_count    = 0;
     generation         = 0;
@@ -919,6 +935,14 @@
 }
 
 
+DNSWLP CONTEXT::find_dnswl(const char *name) {
+    dnswlp_map::iterator i = dnswl_names.find(name);
+    if (i != dnswl_names.end()) return (*i).second;
+    if (parent) return parent->find_dnswl(name);
+    return NULL;
+}
+
+
 const char* CONTEXT::get_content_suffix() {
     if (!content_suffix && parent) return parent->get_content_suffix();
     return content_suffix;
@@ -972,6 +996,12 @@
 }
 
 
+dnswlp_list& CONTEXT::get_dnswl_list() {
+    if (dnswl_list.empty() && parent) return parent->get_dnswl_list();
+    return dnswl_list;
+}
+
+
 bool CONTEXT::acceptable_content(recorder &memory, int score, int bulk, string& msg) {
     if (spamassassin_limit && (score > spamassassin_limit)) {
         char buf[maxlen];
@@ -1012,6 +1042,13 @@
         printf("%s     dnsbl %s %s \"%s\"; \n", indent, n, d.suffix, d.message);
     }
 
+    for (dnswlp_map::iterator i=dnswl_names.begin(); i!=dnswl_names.end(); i++) {
+        const char *n = (*i).first;
+        DNSWL &d = *(*i).second;
+        printf("%s     dnswl %s %s %d; \n", indent, n, d.suffix, d.level);
+    }
+
+    {
     dnsblp_list dl = get_dnsbl_list();
     if (!dl.empty()) {
         printf("%s     dnsbl_list", indent);
@@ -1020,7 +1057,18 @@
             printf(" %s", d.name);
         }
         printf("; \n");
+    }}
+
+    {
+    dnswlp_list dl = get_dnswl_list();
+    if (!dl.empty()) {
+        printf("%s     dnswl_list", indent);
+        for (dnswlp_list::iterator i=dl.begin(); i!=dl.end(); i++) {
+            DNSWL &d = *(*i);
+            printf(" %s", d.name);
     }
+        printf("; \n");
+    }}
 
     if (content_filtering) {
         printf("%s     content on { \n", indent);
@@ -1222,6 +1270,26 @@
 
 ////////////////////////////////////////////////
 //
+bool parse_dnswl(TOKEN &tok, CONFIG &dc, CONTEXT &me);
+bool parse_dnswl(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
+    const char *name = tok.next();
+    const char *suf  = tok.next();
+    const int   lev  = tok.nextint();
+    if (!tsa(tok, token_semi)) return false;
+    DNSWLP dnsnew = new DNSWL(name, suf, lev);
+    DNSWLP dnsold = me.find_dnswl(name);
+    if (dnsold && (*dnsold == *dnsnew)) {
+        // duplicate redefinition, ignore it
+        delete dnsnew;
+        return true;
+    }
+    me.add_dnswl(name, dnsnew);
+    return true;
+}
+
+
+////////////////////////////////////////////////
+//
 bool parse_dnsbll(TOKEN &tok, CONFIG &dc, CONTEXT &me);
 bool parse_dnsbll(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
     while (true) {
@@ -1243,6 +1311,27 @@
 
 ////////////////////////////////////////////////
 //
+bool parse_dnswll(TOKEN &tok, CONFIG &dc, CONTEXT &me);
+bool parse_dnswll(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
+    while (true) {
+        const char *have = tok.next();
+        if (!have) break;
+        if (have == token_semi) break;
+        DNSWLP dns = me.find_dnswl(have);
+        if (dns) {
+            me.add_dnswl(dns);
+        }
+        else {
+            tok.token_error("dnswl name", have);
+            return false;
+        }
+    }
+    return true;
+}
+
+
+////////////////////////////////////////////////
+//
 bool parse_content(TOKEN &tok, CONFIG &dc, CONTEXT &me);
 bool parse_content(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
     const char *setting = tok.next();
@@ -1650,6 +1739,12 @@
         else if (have == token_dnsbll) {
             if (!parse_dnsbll(tok, dc, *con)) return false;
         }
+        else if (have == token_dnswl) {
+            if (!parse_dnswl(tok, dc, *con)) return false;
+        }
+        else if (have == token_dnswll) {
+            if (!parse_dnswll(tok, dc, *con)) return false;
+        }
         else if (have == token_content) {
             if (!parse_content(tok, dc, *con)) return false;
         }
@@ -1736,6 +1831,8 @@
     token_default       = register_string("default");
     token_dnsbl         = register_string("dnsbl");
     token_dnsbll        = register_string("dnsbl_list");
+    token_dnswl         = register_string("dnswl");
+    token_dnswll        = register_string("dnswl_list");
     token_envfrom       = register_string("env_from");
     token_envto         = register_string("env_to");
     token_filter        = register_string("filter");
--- a/src/context.h	Fri Jan 06 22:07:45 2012 -0800
+++ b/src/context.h	Sun Apr 08 11:42:59 2012 -0700
@@ -15,6 +15,7 @@
              reject};       // rejected by a dns list
 
 class DNSBL;
+class DNSWL;
 class CONTEXT;
 class VERIFY;
 class SMTP;
@@ -27,11 +28,14 @@
 typedef set<int32_t>                            int32_t_set;
 typedef list<SMTP *>                            smtp_list;
 typedef DNSBL *                                 DNSBLP;
+typedef DNSWL *                                 DNSWLP;
 typedef VERIFY *                                VERIFYP;
 typedef WHITELISTER *                           WHITELISTERP;
 typedef DELAYWHITE *                            DELAYWHITEP;
 typedef list<DNSBLP>                            dnsblp_list;
 typedef map<const char *, DNSBLP, ltstr>        dnsblp_map;
+typedef list<DNSWLP>                            dnswlp_list;
+typedef map<const char *, DNSWLP, ltstr>        dnswlp_map;
 typedef CONTEXT *                               CONTEXTP;
 typedef list<CONTEXTP>                          context_list;
 typedef map<const char *, CONTEXTP, ltstr>      context_map;
@@ -126,6 +130,14 @@
     bool operator==(const DNSBL &rhs);
 };
 
+struct DNSWL {
+    const char    *name;    // nickname for this dns based list
+    const char    *suffix;  // whitelist suffix like list.dnswl.org
+    int            level;   // matches 127.0.x.y where y >= level
+    DNSWL(const char *n, const char *s, const int l);
+    bool operator==(const DNSWL &rhs);
+};
+
 class CONTEXT {
     CONTEXTP        parent;
     const char *    name;
@@ -163,6 +175,8 @@
     int             dcc_bulk_threshold; // off = 0, many = 1000
     dnsblp_map      dnsbl_names;        // name to dnsbl mapping for lists that are available in this context and children
     dnsblp_list     dnsbl_list;         // list of dnsbls to be used in this context
+    dnswlp_map      dnswl_names;        // name to dnswl mapping for lists that are available in this context and children
+    dnswlp_list     dnswl_list;         // list of dnswls to be used in this context
     int             default_rcpt_rate;  // if not specified per user
     rcpt_rates      rcpt_per_hour;      // per user limits on number of recipients per hour
 
@@ -221,6 +235,10 @@
     void        add_dnsbl(DNSBLP dns)                       {dnsbl_list.push_back(dns);};
     DNSBLP      find_dnsbl(const char *name);
 
+    void        add_dnswl(const char *name, DNSWLP dns)     {dnswl_names[name] = dns;  };
+    void        add_dnswl(DNSWLP dns)                       {dnswl_list.push_back(dns);};
+    DNSWLP      find_dnswl(const char *name);
+
     bool        set_white(const char *regx);
     bool        white_match(const char *from);
 
@@ -247,6 +265,7 @@
     string_set&     get_content_cctlds();
     string_set&     get_html_tags();
     dnsblp_list&    get_dnsbl_list();
+    dnswlp_list&    get_dnswl_list();
 
     bool        acceptable_content(recorder &memory, int score, int bulk, string& msg);
     bool        ignore_host(const char *host);
@@ -288,6 +307,8 @@
 extern const char *token_default;
 extern const char *token_dnsbl;
 extern const char *token_dnsbll;
+extern const char *token_dnswl;
+extern const char *token_dnswll;
 extern const char *token_envfrom;
 extern const char *token_envto;
 extern const char *token_filter;
--- a/src/dnsbl.cpp	Fri Jan 06 22:07:45 2012 -0800
+++ b/src/dnsbl.cpp	Sun Apr 08 11:42:59 2012 -0700
@@ -819,14 +819,14 @@
 ////////////////////////////////////////////////
 //  check a single dnsbl
 //
-bool check_single(mlfiPriv &priv, int32_t ip, const char *suffix);
-bool check_single(mlfiPriv &priv, int32_t ip, const char *suffix) {
+int32_t check_single(mlfiPriv &priv, int32_t ip, const char *suffix);
+int32_t check_single(mlfiPriv &priv, int32_t ip, const char *suffix) {
     // make a dns question
     const u_char *src = (const u_char *)&ip;
-    if (src[0] == 127) return false;    // don't do dns lookups on localhost
-    if (src[0] == 10)  return false;    // don't do dns lookups on rfc1918 space
-    if ((src[0] == 192) && (src[1] == 168)) return false;
-    if ((src[0] == 172) && (16 <= src[1]) && (src[1] <= 31)) return false;
+    if (src[0] == 127) return 0;    // don't do dns lookups on localhost
+    if (src[0] == 10)  return 0;    // don't do dns lookups on rfc1918 space
+    if ((src[0] == 192) && (src[1] == 168)) return 0;
+    if ((src[0] == 172) && (16 <= src[1]) && (src[1] <= 31)) return 0;
 #ifdef NS_MAXDNAME
     char question[NS_MAXDNAME];
 #else
@@ -848,6 +848,23 @@
 
 
 ////////////////////////////////////////////////
+//  check a single dnswl
+//
+bool check_single(mlfiPriv &priv, int32_t ip, DNSWL &wl);
+bool check_single(mlfiPriv &priv, int32_t ip, DNSWL &wl) {
+    int32_t r = check_single(priv, ip, wl.suffix);
+    int32_t v = (int32_t)0x7f000000;
+    int32_t m = (int32_t)0xffff0000;
+    int32_t m2 = (int32_t)0x000000ff;
+    if ((r & m) == v) {
+        int32_t l = r & m2;
+        if (l >= wl.level) return true;
+    }
+    return false;
+}
+
+
+////////////////////////////////////////////////
 //  check the dnsbls specified for this recipient
 //
 bool check_dnsbl(mlfiPriv &priv, dnsblp_list &dnsbll, DNSBLP &rejectlist);
@@ -855,12 +872,12 @@
     for (dnsblp_list::iterator i=dnsbll.begin(); i!=dnsbll.end(); i++) {
         DNSBLP dp = *i;     // non null by construction
         bool st;
-        map<DNSBLP, bool>::iterator f = priv.checked.find(dp);
-        if (f == priv.checked.end()) {
+        map<DNSBLP, bool>::iterator f = priv.checked_black.find(dp);
+        if (f == priv.checked_black.end()) {
             // have not checked this list yet
             st = check_single(priv, priv.ip, *dp);
             rejectlist = dp;
-            priv.checked[dp] = st;
+            priv.checked_black[dp] = st;
         }
         else {
             st = (*f).second;
@@ -873,6 +890,31 @@
 
 
 ////////////////////////////////////////////////
+//  check the dnswls specified for this recipient
+//
+bool check_dnswl(mlfiPriv &priv, dnswlp_list &dnswll, DNSWLP &acceptlist);
+bool check_dnswl(mlfiPriv &priv, dnswlp_list &dnswll, DNSWLP &acceptlist) {
+    for (dnswlp_list::iterator i=dnswll.begin(); i!=dnswll.end(); i++) {
+        DNSWLP dp = *i;     // non null by construction
+        bool st;
+        map<DNSWLP, bool>::iterator f = priv.checked_white.find(dp);
+        if (f == priv.checked_white.end()) {
+            // have not checked this list yet
+            st = check_single(priv, priv.ip, *dp);
+            acceptlist = dp;
+            priv.checked_white[dp] = st;
+        }
+        else {
+            st = (*f).second;
+            acceptlist = (*f).first;
+        }
+        if (st) return st;
+    }
+    return false;
+}
+
+
+////////////////////////////////////////////////
 //  check the hosts from the body against the content filter and uribl dnsbls
 //
 //
@@ -1117,8 +1159,9 @@
     const char *replyvalue = con2.find_from(loto);
     if (debug_syslog > 1) {
         char buf[maxlen];
+        char buf2[maxlen];
         char msg[maxlen];
-        snprintf(msg, sizeof(msg), "from <%s> to <%s> using context %s state %s reply state %s", priv.mailaddr, loto, con.get_full_name(buf,maxlen), fromvalue, replyvalue);
+        snprintf(msg, sizeof(msg), "from <%s> to <%s> using context %s state %s reply context %s state %s", priv.mailaddr, loto, con.get_full_name(buf,maxlen), fromvalue, con2.get_full_name(buf2,maxlen), replyvalue);
         my_syslog(&priv, msg);
     }
     free((void*)loto);
@@ -1148,8 +1191,22 @@
         st = white;
     }
     else {
-        // check the dns based lists
-        st = (check_dnsbl(priv, con.get_dnsbl_list(), rejectlist)) ? reject : oksofar;
+        // check the dns based lists, whitelist first
+        DNSWLP acceptlist = NULL;   // list that caused the whitelisting
+        if (check_dnswl(priv, con.get_dnswl_list(), acceptlist)) {
+            st = white;
+            if (debug_syslog > 1) {
+                char msg[maxlen];
+                snprintf(msg, sizeof(msg), "whitelisted by %s", acceptlist->name);
+                my_syslog(&priv, msg);
+            }
+        }
+        else if (check_dnsbl(priv, con.get_dnsbl_list(), rejectlist)) {
+            st = reject;
+        }
+        else {
+            st = oksofar;
+        }
     }
     if (st == reject) {
         // reject the recipient based on some dnsbl
--- a/src/dnsbl.h	Fri Jan 06 22:07:45 2012 -0800
+++ b/src/dnsbl.h	Sun Apr 08 11:42:59 2012 -0700
@@ -33,7 +33,8 @@
     bool    err;                            // did we get any errors on the resolver socket?
     int32_t ip;                             // ip4 address of the smtp client
     const char      *helo;                  // helo from client
-    map<DNSBLP, bool> checked;              // map of dnsblp to result of (ip listed on that dnsbl)
+    map<DNSBLP, bool> checked_black;        // map of dnsblp to result of (ip listed on that dnsbl)
+    map<DNSWLP, bool> checked_white;        // map of dnswlp to result of (ip listed on that dnswl)
     // message specific data
     const char      *mailaddr;              // envelope from value
     const char      *queueid;               // sendmail queue id
--- a/xml/dnsbl.in	Fri Jan 06 22:07:45 2012 -0800
+++ b/xml/dnsbl.in	Sun Apr 08 11:42:59 2012 -0700
@@ -298,6 +298,17 @@
                 DNSBL-LIST - a named list of DNSBLs that will be used for specific
                 recipients or recipient domains.
             </para>
+            <para>
+                DNSWL - a named DNS based white list is defined by a dns suffix (e.g.
+                list.dnswl.org) and an integer level. If the level is greater than or
+                equal to x in the 127.0.z.x return code from the white list, then the
+                ip address is considered to match, and the message will be whitelisted.
+                The names of these DNSWLs will be used to define the DNSWL-LISTs.
+            </para>
+            <para>
+                DNSWL-LIST - a named list of DNSWLs that will be used for specific
+                recipients or recipient domains.
+            </para>
         </refsect1>
 
         <refsect1 id='filtering.1'>
@@ -371,7 +382,14 @@
                     expression.
                 </para></listitem>
                 <listitem><para>
-                    If the mail has not been accepted or rejected yet, the dns lists
+                    If the mail has not been accepted or rejected yet, the dns white lists
+                    specified in the filtering context are checked and the mail is accepted
+                    if any list has an A record for the standard dns based lookup scheme
+                    (reversed octets of the client followed by the dns suffix) with a final
+                    octet greater than or equal to the level specified for that dnswl.
+                </para></listitem>
+                <listitem><para>
+                    If the mail has not been accepted or rejected yet, the dns black lists
                     specified in the filtering context are checked and the mail is rejected
                     if any list has an A record for the standard dns based lookup scheme
                     (reversed octets of the client followed by the dns suffix).
@@ -424,7 +442,9 @@
             <para>
                 If the content uribl DNSBL is defined, and any of those host names are
                 on that DNSBL, and the host name is not on the &lt;configurable&gt;
-                ignore list, the mail is rejected.
+                ignore list, the mail is rejected. Note that the Spamhaus DBL is not (yet)
+                suitable here, since we currently pass ip addresses to the uribl checker,
+                and the DBL lists all such bare ip addresses.
             </para>
             <para>
                 If any non-whitelisted recipient has a filtering context with a non-zero
@@ -564,6 +584,14 @@
                 http:// protocol header. Such references are still clickable in common
                 mail software.
             </para>
+            <para>
+                Add the ability to use the DBL for content filtering. We need to avoid
+                checking bare ip addresses against that list.
+            </para>
+            <para>
+                Add daily recipient limits based on some fixed multiple (perhaps 3?)
+                of the hourly limit.
+            </para>
         </refsect1>
 
         <refsect1 id='copyright.1'>
@@ -628,12 +656,15 @@
             <literallayout class="monospaced"><![CDATA[
 CONFIG     = {CONTEXT ";"}+
 CONTEXT    = "context" NAME "{" {STATEMENT}+ "}"
-STATEMENT  = (DNSBL    | DNSBLLIST | CONTENT | ENV-TO   | VERIFY | GENERIC
-             | W_REGEX | AUTOWHITE | CONTEXT | ENV-FROM | RATE-LIMIT) ";"
+STATEMENT  = (DNSBL    | DNSBLLIST | DNSWL   | DNSWLLIST | CONTENT | ENV-TO
+             | VERIFY  | GENERIC   | W_REGEX | AUTOWHITE | CONTEXT | ENV-FROM
+             | RATE-LIMIT) ";"
 
 DNSBL      = "dnsbl" NAME DNSPREFIX ERROR-MSG1
+DNSBLLIST  = "dnsbl_list" {NAME}+
 
-DNSBLLIST  = "dnsbl_list" {NAME}+
+DNSWL      = "dnswl" NAME DNSPREFIX INTEGER
+DNSWLLIST  = "dnswl_list" {NAME}+
 
 CONTENT    = "content" ("on" | "off") "{" {CONTENT-ST}+ "}"
 CONTENT-ST = (FILTER | URIBL | IGNORE | TLD     | CCTLD   | HTML-TAGS |
@@ -730,7 +761,9 @@
     dnsbl   local   blackholes.five-ten-sg.com  "Mail from %s rejected - local; see http://www.five-ten-sg.com/blackhole.php?%s";
     dnsbl   sbl     zen.spamhaus.org            "Mail from %s rejected - sbl; see http://www.spamhaus.org/query/bl?ip=%s";
     dnsbl   xbl     xbl.spamhaus.org            "Mail from %s rejected - xbl; see http://www.spamhaus.org/query/bl?ip=%s";
+    dnswl   dnswl.org  list.dnswl.org   2;
     dnsbl_list  local sbl;
+    dnswl_list  dnswl.org;
 
     content on {
         filter    sbl-xbl.spamhaus.org        "Mail containing %s rejected - sbl; see http://www.spamhaus.org/query/bl?ip=%s";