changeset 321:e172dc10fe24

add dkim white/black listing
author Carl Byington <carl@five-ten-sg.com>
date Sat, 17 Dec 2016 13:47:28 -0800
parents e27c24c1974a
children 9f8411f3919c
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(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Dec 17 09:46:40 2016 -0800
+++ b/ChangeLog	Sat Dec 17 13:47:28 2016 -0800
@@ -1,3 +1,6 @@
+6.48 2016-12-17
+    Add dkim white/black listing
+
 6.47 2016-09-21
     Better smtp verify logging
 
--- a/NEWS	Sat Dec 17 09:46:40 2016 -0800
+++ b/NEWS	Sat Dec 17 13:47:28 2016 -0800
@@ -1,3 +1,4 @@
+6.48 2016-12-17 Add dkim white/black listing
 6.47 2016-09-21 Better smtp verify logging
 6.46 2016-09-19 Enable smtp verify logging
 6.45 2015-04-09 Add bitcoin donation address
--- a/configure.in	Sat Dec 17 09:46:40 2016 -0800
+++ b/configure.in	Sat Dec 17 13:47:28 2016 -0800
@@ -1,6 +1,6 @@
 
 AC_PREREQ(2.59)
-AC_INIT(dnsbl,6.47,carl@five-ten-sg.com)
+AC_INIT(dnsbl,6.48,carl@five-ten-sg.com)
 AC_CONFIG_SRCDIR([config.h.in])
 AC_CONFIG_HEADER([config.h])
 AC_CONFIG_MACRO_DIR([m4])
--- a/dnsbl.conf	Sat Dec 17 09:46:40 2016 -0800
+++ b/dnsbl.conf	Sat Dec 17 13:47:28 2016 -0800
@@ -80,6 +80,17 @@
         include "/etc/mail/local-host-names";
     };
 
+    dkim_signer {
+        sendgrid.me     black;
+        weather.com     white;
+    };
+
+    dkim_from {
+        yahoo.com        require_signed   yahoo.com;
+        gmail.com        signed_white     gmail.com;
+        girlscoutsla.org signed_white     girlscoutsla.ccsend.com;
+    };
+
     context whitelist {
         content off {};
         env_to {
--- a/dnsbl.spec.in	Sat Dec 17 09:46:40 2016 -0800
+++ b/dnsbl.spec.in	Sat Dec 17 13:47:28 2016 -0800
@@ -103,6 +103,9 @@
 
 
 %changelog
+* Sat Dec 17 2016 Carl Byington <carl@five-ten-sg.com> - 6.48-1
+- allow dkim whitelisting.
+
 * Wed Sep 21 2016 Carl Byington <carl@five-ten-sg.com> - 6.47-1
 - Better smtp verify logging
 
--- a/src/context.cpp	Sat Dec 17 09:46:40 2016 -0800
+++ b/src/context.cpp	Sat Dec 17 13:47:28 2016 -0800
@@ -69,6 +69,11 @@
 const char *token_white;
 const char *token_white_regex;
 const char *token_yes;
+const char *token_dkim_signer;
+const char *token_dkim_from;
+const char *token_signed_white;
+const char *token_signed_black;
+const char *token_require_signed;
 
 const char *token_myhostname;
 #ifndef HOST_NAME_MAX
@@ -610,6 +615,12 @@
 }
 
 
+DKIM::DKIM(const char *action_, const char *signer_) {
+    action = action_;
+    signer = signer_;
+}
+
+
 DNSBL::DNSBL(const char *n, const char *s, const char *m) {
     name    = n;
     suffix  = s;
@@ -763,6 +774,11 @@
 
 
 CONTEXT::~CONTEXT() {
+    for (dkimp_map::iterator i=dkim_from_names.begin(); i!=dkim_from_names.end(); i++) {
+        DKIMP d = (*i).second;
+        // delete the underlying DKIM objects.
+        delete d;
+    }
     for (dnsblp_map::iterator i=dnsbl_names.begin(); i!=dnsbl_names.end(); i++) {
         DNSBLP d = (*i).second;
         // delete the underlying DNSBL objects.
@@ -985,6 +1001,22 @@
 }
 
 
+const char *CONTEXT::find_dkim_signer(const char *name) {
+    string_map::iterator i = dkim_signer_names.find(name);
+    if (i != dkim_signer_names.end()) return (*i).second;
+    if (parent) return parent->find_dkim_signer(name);
+    return NULL;
+}
+
+
+DKIMP CONTEXT::find_dkim_from(const char *name) {
+    dkimp_map::iterator i = dkim_from_names.find(name);
+    if (i != dkim_from_names.end()) return (*i).second;
+    if (parent) return parent->find_dkim_from(name);
+    return NULL;
+}
+
+
 DNSBLP CONTEXT::find_dnsbl(const char *name) {
     dnsblp_map::iterator i = dnsbl_names.find(name);
     if (i != dnsbl_names.end()) return (*i).second;
@@ -1199,6 +1231,23 @@
         printf("%s     content off {}; \n", indent);
     }
 
+    printf("%s     dkim_signer { \n", indent);
+    for (string_map::iterator i=dkim_signer_names.begin(); i!=dkim_signer_names.end(); i++) {
+        const char *n = (*i).first;
+        const char *a = (*i).second;
+        printf("%s         %s %s; \n", indent, n, a);
+    }
+    printf("%s     } \n", indent);
+
+    printf("%s     dkim_from { \n", indent);
+    for (dkimp_map::iterator i=dkim_from_names.begin(); i!=dkim_from_names.end(); i++) {
+        const char *n = (*i).first;
+        DKIM &d = *(*i).second;
+        printf("%s         %s %s %s; \n", indent, n, d.action, d.signer);
+    }
+
+    printf("%s     } \n", indent);
+
     printf("%s     env_to { \t// %s\n", indent, fullname);
     for (string_set::iterator i=env_to.begin(); i!=env_to.end(); i++) {
         printf("%s         %s; \n", indent, *i);
@@ -1810,6 +1859,62 @@
 
 ////////////////////////////////////////////////
 //
+bool parse_dkim_signer(TOKEN &tok, CONFIG &dc, CONTEXT &me);
+bool parse_dkim_signer(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
+    if (!tsa(tok, token_lbrace)) return false;
+    while (true) {
+        const char *have = tok.next();
+        if (!have) break;
+        if (have == token_rbrace) break;
+        if (have == token_semi) {
+            // optional separators
+        }
+        else {
+            const char *signer = have;
+            const char *action = tok.next();
+            if ((action == token_white) || (action == token_black)) {
+                me.add_dkim_signer(signer, action);
+            }
+            else {
+                tok.token_error("white/black", action);
+            }
+        }
+    }
+    return tsa(tok, token_semi);
+}
+
+
+////////////////////////////////////////////////
+//
+bool parse_dkim_from(TOKEN &tok, CONFIG &dc, CONTEXT &me);
+bool parse_dkim_from(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
+    if (!tsa(tok, token_lbrace)) return false;
+    while (true) {
+        const char *have = tok.next();
+        if (!have) break;
+        if (have == token_rbrace) break;
+        if (have == token_semi) {
+            // optional separators
+        }
+        else {
+            const char *from   = have;
+            const char *action = tok.next();
+            if ((action == token_signed_white) || (action == token_signed_black) || (action == token_require_signed)) {
+                const char *signer = tok.next();
+                if (!signer) break;
+                else         me.add_dkim_from(from, action, signer);
+            }
+            else {
+                tok.token_error("signed_white/signed_black/require_signed", action);
+            }
+        }
+    }
+    return tsa(tok, token_semi);
+}
+
+
+////////////////////////////////////////////////
+//
 bool parse_context(TOKEN &tok, CONFIG &dc, CONTEXTP parent);
 bool parse_context(TOKEN &tok, CONFIG &dc, CONTEXTP parent) {
     const char *name = tok.next();
@@ -1832,9 +1937,6 @@
         else if (have == token_dnswll) {
             if (!parse_dnswll(tok, dc, *con)) return false;
         }
-        else if (have == token_requirerdns) {
-            if (!parse_requirerdns(tok, dc, *con)) return false;
-        }
         else if (have == token_content) {
             if (!parse_content(tok, dc, *con)) return false;
         }
@@ -1856,10 +1958,19 @@
         else if (have == token_envfrom) {
             if (!parse_envfrom(tok, dc, *con)) return false;
         }
+        else if (have == token_dkim_signer) {
+            if (!parse_dkim_signer(tok, dc, *con)) return false;
+        }
+        else if (have == token_dkim_from) {
+            if (!parse_dkim_from(tok, dc, *con)) return false;
+        }
         else if (have == token_rate) {
             if (parent || dc.default_context) tok.token_error("rate limit ignored in non default context");
             if (!parse_rate(tok, dc, *con)) return false;
         }
+        else if (have == token_requirerdns) {
+            if (!parse_requirerdns(tok, dc, *con)) return false;
+        }
         else if (have == token_context) {
             if (!parse_context(tok, dc, con)) return false;
         }
@@ -1958,6 +2069,11 @@
     token_white         = register_string("white");
     token_white_regex   = register_string("white_regex");
     token_yes           = register_string("yes");
+    token_dkim_signer       = register_string("dkim_signer");
+    token_dkim_from         = register_string("dkim_from");
+    token_signed_white      = register_string("signed_white");
+    token_signed_black      = register_string("signed_black");
+    token_require_signed    = register_string("require_signed");
 
     if (gethostname(myhostname, HOST_NAME_MAX+1) != 0) {
         strncpy(myhostname, "localhost", HOST_NAME_MAX+1);
--- a/src/context.h	Sat Dec 17 09:46:40 2016 -0800
+++ b/src/context.h	Sat Dec 17 13:47:28 2016 -0800
@@ -14,6 +14,7 @@
              black,         // blacklisted
              reject};       // rejected by a dns list
 
+class DKIM;
 class DNSBL;
 class DNSWL;
 class CONTEXT;
@@ -28,11 +29,13 @@
 typedef set<int32_t>                            int32_t_set;
 typedef int32_t_set *                           int32_t_set_p;
 typedef list<SMTP *>                            smtp_list;
+typedef DKIM *                                  DKIMP;
 typedef DNSBL *                                 DNSBLP;
 typedef DNSWL *                                 DNSWLP;
 typedef VERIFY *                                VERIFYP;
 typedef WHITELISTER *                           WHITELISTERP;
 typedef DELAYWHITE *                            DELAYWHITEP;
+typedef map<const char *, DKIMP, ltstr>         dkimp_map;
 typedef list<DNSBLP>                            dnsblp_list;
 typedef map<const char *, DNSBLP, ltstr>        dnsblp_map;
 typedef list<DNSWLP>                            dnswlp_list;
@@ -126,6 +129,13 @@
     CONTEXTP        get_con()  {return con;};
 };
 
+struct DKIM {
+    const char *action;
+    const char *signer;
+public:
+    DKIM(const char *action_, const char *signer_);
+};
+
 struct DNSBL {
     const char    *name;    // nickname for this dns based list
     const char    *suffix;  // blacklist suffix like blackholes.five-ten-sg.com
@@ -179,6 +189,8 @@
     bool            require_rdns;           // require proper rdns on client ip
     bool            dcc_greylist;           // should we do dcc greylisting?
     int             dcc_bulk_threshold;     // off = 0, many = 1000
+    dkimp_map       dkim_from_names;        // map header from domains to dkim constraints
+    string_map      dkim_signer_names;      // map dkim signers to actions
     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
     bool            dnsbl_list_parsed;      // true iff we have actually parsed a dnsbl_list
@@ -252,6 +264,13 @@
     void        set_tag_message(const char *message)        {tag_limit_message  = message;};
     void        add_tag(const char *tag)                    {html_tags.insert(tag);       };
 
+    const char *find_dkim_signer(const char *name);
+    void        add_dkim_signer(const char *signer, const char *action)
+                                                            {dkim_signer_names[signer] = action;};
+    DKIMP       find_dkim_from(const char *name);
+    void        add_dkim_from(const char *from, const char *action, const char *signer)
+                                                            {dkim_from_names[from] = new DKIM(action,signer);};
+
     void        add_dnsbl(const char *name, DNSBLP dns)     {dnsbl_names[name] = dns;  };
     void        add_dnsbl(DNSBLP dns)                       {dnsbl_list.push_back(dns);};
     DNSBLP      find_dnsbl(const char *name);
@@ -370,6 +389,10 @@
 extern const char *token_white;
 extern const char *token_white_regex;
 extern const char *token_yes;
+extern const char *token_dkim;
+extern const char *token_signed_white;
+extern const char *token_signed_black;
+extern const char *token_require_signed;
 
 extern pthread_mutex_t verifier_mutex;     // protect the verifier map
 extern pthread_mutex_t whitelister_mutex;  // protect the
--- a/src/dnsbl.cpp	Sat Dec 17 09:46:40 2016 -0800
+++ b/src/dnsbl.cpp	Sat Dec 17 13:47:28 2016 -0800
@@ -520,6 +520,8 @@
     ip                      = 0;
     helo                    = NULL;
     mailaddr                = NULL;
+    fromaddr                = NULL;
+    header_count            = 0;
     queueid                 = NULL;
     authenticated           = NULL;
     client_name             = NULL;
@@ -567,6 +569,7 @@
         delayer.pop_front();
     }
     if (mailaddr)        free((void*)mailaddr);
+    if (fromaddr)        free((void*)fromaddr);
     if (queueid)         free((void*)queueid);
     if (authenticated)   free((void*)authenticated);
     if (client_name)     free((void*)client_name);
@@ -582,6 +585,8 @@
         ctx                     = NULL;
         eom                     = false;
         mailaddr                = NULL;
+        fromaddr                = NULL;
+        header_count            = 0;
         queueid                 = NULL;
         authenticated           = NULL;
         client_name             = NULL;
@@ -1444,6 +1449,16 @@
 sfsistat mlfi_header(SMFICTX* ctx, char* headerf, char* headerv)
 {
     mlfiPriv &priv = *MLFIPRIV;
+    priv.header_count++;
+    if ((priv.header_count < 4) || (strcasecmp(headerf, "from") == 0)) {
+        char msg[maxlen];
+        snprintf(msg, sizeof(msg), "header %s: %s", headerf, headerv);
+        for (int i=0; i<strlen(msg); i++) {
+            if (msg[i] < 0x20) msg[i] = ' ';
+        }
+        my_syslog(&priv, msg);
+    }
+
     // headers that avoid autowhitelisting
     if (((strcasecmp(headerf, "precedence") == 0)   && (strcasecmp(headerv, "bulk") == 0)) ||
         ((strcasecmp(headerf, "content-type") == 0) && (strncasecmp(headerv, "multipart/report", 16) == 0))) {
--- a/src/dnsbl.h	Sat Dec 17 09:46:40 2016 -0800
+++ b/src/dnsbl.h	Sat Dec 17 13:47:28 2016 -0800
@@ -38,6 +38,8 @@
     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      *fromaddr;              // header from value, set by mlfi_header()
+    int             header_count;           // count of headers already seen
     const char      *queueid;               // sendmail queue id
     const char      *authenticated;         // client authenticated? if so, suppress all dnsbl checks, but check rate limits
     const char      *client_name;           // fully qualified host name of the smtp client xxx [ip.ad.dr.es] (may be forged)
--- a/xml/dnsbl.in	Sat Dec 17 09:46:40 2016 -0800
+++ b/xml/dnsbl.in	Sat Dec 17 13:47:28 2016 -0800
@@ -25,7 +25,7 @@
 
     <refentry id="@PACKAGE@.1">
         <refentryinfo>
-            <date>2013-05-22</date>
+            <date>2016-12-17</date>
             <author>
                 <firstname>Carl</firstname>
                 <surname>Byington</surname>
@@ -666,7 +666,7 @@
 
     <refentry id="@PACKAGE@.conf.5">
         <refentryinfo>
-            <date>2013-05-22</date>
+            <date>2016-12-17</date>
             <author>
                 <firstname>Carl</firstname>
                 <surname>Byington</surname>
@@ -706,7 +706,7 @@
 CONTEXT    = "context" NAME "{" {STATEMENT}+ "}"
 STATEMENT  = (DNSBL    | DNSBLLIST | DNSWL   | DNSWLLIST | CONTENT | ENV-TO
              | VERIFY  | GENERIC   | W_REGEX | AUTOWHITE | CONTEXT | ENV-FROM
-             | RATE-LIMIT | REQUIRERDNS) ";"
+             | DKIM_SIGNER | DKIM_FROM | RATE-LIMIT | REQUIRERDNS) ";"
 
 DNSBL      = "dnsbl" NAME DNSPREFIX ERROR-MSG1
 DNSBLLIST  = "dnsbl_list" {NAME}*
@@ -742,6 +742,10 @@
 DCCGREY    = "dcc_greylist"         ("yes" | "no")
 DCCBULK    = "dcc_bulk_threshold"   (INTEGER | "many" | "off")
 
+DKIMSIGNER = "dkim_signer" "{" {SIGNING_DOMAIN DEF [";"]}+ "}"
+DKIMFROM   = "dkim_from"   "{" {HEADER_FROM_DOMAIN DKIMVALUE SIGNING_DOMAIN [";"]}+ "}"
+DKIMVALUE  = "signed_white" | "signed_black" | "require_signed"
+
 ENV-TO     = "env_to"     "{" {(TO-ADDR | DCC-TO)}+ "}"
 TO-ADDR    = ADDRESS [";"]
 DCC-TO     = "dcc_to" ("ok" | "many") "{" DCCINCLUDEFILE "}" ";"
@@ -766,7 +770,8 @@
 DEFAULT_IP_LIMIT    = INTEGER
 DAILY_MULTIPLE_IP   = INTEGER
 
-DEFAULT    = ("white" | "black" | "unknown" | "inherit" | "")
+DEF        = ("white" | "black")
+DEFAULT    = (DEF | "unknown" | "inherit" | "")
 ADDRESS    = (USER@ | DOMAIN | USER@DOMAIN)
 VALUE      = ("white" | "black" | "unknown" | "inherit" | CHILD-CONTEXT-NAME)]]></literallayout>
         </refsect1>