diff src/dnsbl.cpp @ 322:9f8411f3919c

add dkim white/black listing
author Carl Byington <carl@five-ten-sg.com>
date Sat, 17 Dec 2016 17:04:52 -0800
parents e172dc10fe24
children a6de27b0a1e9
line wrap: on
line diff
--- a/src/dnsbl.cpp	Sat Dec 17 13:47:28 2016 -0800
+++ b/src/dnsbl.cpp	Sat Dec 17 17:04:52 2016 -0800
@@ -98,6 +98,8 @@
 const int maxlen = 1000;    // used for snprintf buffers
 regex_t srs_pattern;        // used to detect srs coding in mail addresses
 regex_t prvs_pattern;       // used to detect prvs coding in mail addresses
+regex_t dkim_pattern;       // used to detect dkim signatures in authentication header generated by the upstream opendkim milter
+regex_t from_pattern;       // used to extract the senders mail domain from the body from: header
 
 pthread_mutex_t  config_mutex;
 pthread_mutex_t  syslog_mutex;
@@ -522,6 +524,8 @@
     mailaddr                = NULL;
     fromaddr                = NULL;
     header_count            = 0;
+    dkim_ok                 = true;
+    dkim_signer             = NULL;
     queueid                 = NULL;
     authenticated           = NULL;
     client_name             = NULL;
@@ -570,6 +574,7 @@
     }
     if (mailaddr)        free((void*)mailaddr);
     if (fromaddr)        free((void*)fromaddr);
+    if (dkim_signer)     free((void*)dkim_signer);
     if (queueid)         free((void*)queueid);
     if (authenticated)   free((void*)authenticated);
     if (client_name)     free((void*)client_name);
@@ -587,6 +592,8 @@
         mailaddr                = NULL;
         fromaddr                = NULL;
         header_count            = 0;
+        dkim_ok                 = true;
+        dkim_signer             = NULL;
         queueid                 = NULL;
         authenticated           = NULL;
         client_name             = NULL;
@@ -1450,8 +1457,8 @@
 {
     mlfiPriv &priv = *MLFIPRIV;
     priv.header_count++;
+    char msg[maxlen];
     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] = ' ';
@@ -1459,6 +1466,38 @@
         my_syslog(&priv, msg);
     }
 
+    if (priv.dkim_ok) {
+        if ((priv.header_count == 1) && (strcasecmp(headerf, "DKIM-Filter") != 0)) priv.dkim_ok = false;
+        if (priv.header_count == 2) {
+            if (strcasecmp(headerf, "Authentication-Results") != 0) priv.dkim_ok = false;
+            if (strncasecmp(headerv, token_myhostname, strlen(token_myhostname)) != 0) priv.dkim_ok = false;
+            if (priv.dkim_ok) {
+                const int nmatch = 2;
+                regmatch_t match[nmatch];
+                if (0 == regexec(&dkim_pattern, msg, nmatch, match, 0)) {
+                    int s1 = match[1].rm_so;    // domain
+                    int e1 = match[1].rm_eo;
+                    if (s1 != -1) {
+                        msg[e1] = '\0';
+                        priv.dkim_signer = strdup(msg+s1);
+                    }
+                }
+            }
+        }
+        if ((priv.header_count > 2) && (strcasecmp(headerf, "from"))) {
+            const int nmatch = 2;
+            regmatch_t match[nmatch];
+            if (0 == regexec(&from_pattern, msg, nmatch, match, 0)) {
+                int s1 = match[1].rm_so;    // domain
+                int e1 = match[1].rm_eo;
+                if (s1 != -1) {
+                    msg[e1] = '\0';
+                    priv.fromaddr = strdup(msg+s1);
+                }
+            }
+        }
+    }
+
     // headers that avoid autowhitelisting
     if (((strcasecmp(headerf, "precedence") == 0)   && (strcasecmp(headerv, "bulk") == 0)) ||
         ((strcasecmp(headerf, "content-type") == 0) && (strncasecmp(headerv, "multipart/report", 16) == 0))) {
@@ -1546,9 +1585,11 @@
         for (context_map::iterator i=priv.env_to.begin(); i!=priv.env_to.end(); i++) {
             const char *rcpt   = (*i).first;
             CONTEXT &con = *((*i).second);
-            if (!con.acceptable_content(*priv.memory, score, bulk, msg)) {
+            if (!con.acceptable_content(*priv.memory, score, bulk, priv.dkim_signer, priv.fromaddr, msg)) {
                 // bad html tags or excessive hosts or
                 // high spam assassin score or dcc bulk threshold exceedeed
+                // or signed by a dkim signer that we don't like
+                // or header from requires dkim signer that is missing
                 smfi_delrcpt(ctx, (char*)rcpt);
             }
             else {
@@ -1804,6 +1845,18 @@
         exit(3);
     }
 
+    // setup dkim signature detection
+    if (regcomp(&dkim_pattern, " dkim=pass .* header.d=([^ ]+) ", REG_ICASE | REG_EXTENDED)) {
+        printf("cannot compile regex pattern to find dkim signatures\n");
+        exit(3);
+    }
+
+    // setup from domain extraction
+    if (regcomp(&from_pattern, "@([a-zA-Z0-9.-]+)", REG_ICASE | REG_EXTENDED)) {
+        printf("cannot compile regex pattern to find dkim signatures\n");
+        exit(3);
+    }
+
     // Process command line options
     while ((c = getopt(argc, argv, args)) != -1) {
         switch (c) {