diff src/context.cpp @ 464:428de28b34b7

cleanup code for adding extra spf data in dkim_from
author Carl Byington <carl@five-ten-sg.com>
date Sun, 10 Mar 2019 08:49:27 -0700
parents f3f1ece619ba
children 79e944269c0b
line wrap: on
line diff
--- a/src/context.cpp	Sat Mar 09 18:53:12 2019 -0800
+++ b/src/context.cpp	Sun Mar 10 08:49:27 2019 -0700
@@ -616,9 +616,10 @@
 }
 
 
-DKIM::DKIM(const char *action_, const char *signer_) {
+DKIM::DKIM(const char *action_, const char *signer_, const char*extraspf_) {
     action = action_;
     signer = signer_;
+    extraspf = extraspf_;
 }
 
 
@@ -1111,58 +1112,42 @@
 }
 
 
-const char *CONTEXT::extra_spf_data(const char *signers) {
-    const char *e = strchr(signers, ';');
-    if (e) e++;
-    return e;
-}
-
-
 bool CONTEXT::in_signing_set(const char *s, const char *signers) {
     // s is an actual signer
     // signers is the set of acceptable signers, separated by commas
     size_t n = strlen(s);
     const char *p = signers;
-    char *e = (char *)strchr(p, ';');       // only search up to ; which separates signers from extra spf data
-    if (e) *e = '\0';
-    bool rc = true;
     do {
         const char *c = strchr(p, ',');
         size_t m = (c) ? c-p : strlen(p);   // length of this element in the signing set
-        if ((m == n) && (strncasecmp(p, s, n) == 0)) break;     // exact match
+        if ((m == n) && (strncasecmp(p, s, n) == 0)) return true;   // exact match
         if ((*p == '*') && (n >= m)) {
-            if (strncasecmp(p+1, s+n-(m-1), m-1) == 0) break;   // wildcard match
+            if (strncasecmp(p+1, s+n-(m-1), m-1) == 0) return true; // wildcard match
         }
-        if (!c) {
-            rc = false;
-            break;
-        }
+        if (!c) return false;
         p = c + 1;
     } while (true);
-    if (e) *e = ';';
-    return rc;
 }
 
 
-void CONTEXT::replace(char *buf, char *p, const char *what)
+void CONTEXT::replace(char *buf, char *p, int nn, const char *what)
 {
-    // replace 4 chars in buf starting at p with what
+    // replace nn chars in buf starting at p with what
     char repl[maxlen];
     size_t bn = strlen(buf);
     size_t wn = strlen(what);
-    if ((bn - 4 + wn) < (size_t)maxlen) {
+    if ((bn - nn + wn) < (size_t)maxlen) {
         size_t n = p - buf;         // leading part length
         strncpy(repl, buf, n);      // leading part
         strcpy(repl+n, what);       // replacement
-        strcpy(repl+n+wn, buf+n+4); // trailing part
+        strcpy(repl+n+wn, buf+n+nn); // trailing part
         strcpy(buf, repl);
     }
 }
 
 
-bool CONTEXT::resolve_spf(const char *from, uint32_t ip, mlfiPriv *priv, const char *signers)
+bool CONTEXT::resolve_spf(const char *from, uint32_t ip, mlfiPriv *priv, const char *extraspf)
 {
-    const char *extraspf = extra_spf_data(signers);
     // ip is in host order
     if (priv->mailaddr) {
         const char *f = strchr(priv->mailaddr, '@');
@@ -1189,12 +1174,18 @@
     char buf[maxdnslength];
     log(priv->queueid, "looking for %s txt record", from);
     dns_interface(*priv, from, ns_t_txt, false, NULL, buf, maxdnslength);
-    if ((level == 0) &&
-        extraspf &&
-        ((strlen(buf) + strlen(extraspf) + 1) < sizeof(buf))) {
-        strcat(buf, " ");
+    if ((level == 0) && extraspf && ((strlen(buf) + strlen(extraspf) + 1) < sizeof(buf))) {
+        if (strlen(buf) >= 7) {
+            // modify existing spf record
+            replace(buf, buf+7, 0, extraspf);
+            replace(buf, buf+7+strlen(extraspf), 0, " ");
+        }
+        else {
+            // synthesize full spf record
+            strcat(buf, "v=spf1 ");
         strcat(buf, extraspf);
     }
+    }
     if (*buf) {
         log(priv->queueid, "found txt record %s", buf);
         // expand some macros here - a very restricted subset of all possible spf macros
@@ -1204,17 +1195,17 @@
             char adr[sizeof "255.255.255.255   "];
             adr[0] = '\0';
             inet_ntop(AF_INET, (const u_char *)&priv->ip, adr, sizeof(adr));
-            replace(buf, p, adr);
+            replace(buf, p, 4, adr);
             log(priv->queueid, "have txt record %s", buf);
         }
         p = strstr(buf, "%{h}");
         if (p) {
-            replace(buf, p, priv->helo);
+            replace(buf, p, 4, priv->helo);
             log(priv->queueid, "have txt record %s", buf);
         }
         p = strstr(buf, "%{d}");
         if (p) {
-            replace(buf, p, from);
+            replace(buf, p, 4, from);
             log(priv->queueid, "have txt record %s", buf);
         }
         //
@@ -1375,7 +1366,7 @@
             if (st == token_unsigned_black) {
                 // enforce dmarc
                 if (!dmarc) {
-                    dmarc = resolve_spf(from, ntohl(priv->ip), priv, dk->signer);
+                    dmarc = resolve_spf(from, ntohl(priv->ip), priv, dk->extraspf);
                 }
                 if (!dmarc) {
                     // not signed and does not pass spf, reject it
@@ -1387,7 +1378,7 @@
             }
             if (st == token_signed_white) {
                 // not signed by a white listed signer, but maybe passes strong spf check
-                if (resolve_spf(from, ntohl(priv->ip), priv, dk->signer)) {
+                if (resolve_spf(from, ntohl(priv->ip), priv, dk->extraspf)) {
                     log(queueid, "spf pass for %s rather than whitelisted dkim signer", from);
                     return token_white;
                 }
@@ -1395,7 +1386,7 @@
             if (st == token_require_signed) {
                 // not signed by a required signer, but maybe passes strong spf check
                 // only check spf if the list of required signers is not a single dot.
-                if (strcmp(dk->signer, ".") && resolve_spf(from, ntohl(priv->ip), priv, dk->signer)) {
+                if (strcmp(dk->signer, ".") && resolve_spf(from, ntohl(priv->ip), priv, dk->extraspf)) {
                     log(queueid, "spf pass for %s rather than required dkim signer", from);
                     return token_white;
                 }
@@ -1499,8 +1490,13 @@
         for (dkimp_map::iterator i=dkim_from_names.begin(); i!=dkim_from_names.end(); i++) {
             const char *n = (*i).first;
             DKIM &d = *(*i).second;
+            if (d.extraspf) {
+                printf("%s             %s %s \"%s;%s\"; \n", indent, n, d.action, d.signer, d.extraspf);
+            }
+            else {
             printf("%s             %s %s \"%s\"; \n", indent, n, d.action, d.signer);
         }
+        }
         printf("%s         }; \n", indent);
         if (content_suffix) {
             printf("%s         filter %s \"%s\"; \n", indent, content_suffix, content_message);
@@ -1823,7 +1819,18 @@
             if ((action == token_signed_white) || (action == token_signed_black) || (action == token_unsigned_black) || (action == token_require_signed)) {
                 const char *signer = tok.next();
                 if (!signer) break;
-                else         me.add_dkim_from(from, action, signer);
+                else {
+                    const char *extraspf = NULL;
+                    if (strchr(signer, ';')) {
+                        char *x = strdup(signer);
+                        char *e = strchr(x, ';');
+                        *e = '\0';
+                        signer = register_string(x);
+                        extraspf = register_string(e+1);
+                        free(x);
+                    }
+                    me.add_dkim_from(from, action, signer, extraspf);
+                }
             }
             else {
                 tok.token_error("signed_white/signed_black/unsigned_black/require_signed", action);