dmarc dynamic module
authorJeremy Harris <jgh146exb@wizmail.org>
Wed, 28 Aug 2024 21:01:24 +0000 (22:01 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Thu, 29 Aug 2024 08:33:26 +0000 (09:33 +0100)
29 files changed:
doc/doc-docbook/spec.xfpt
src/OS/Makefile-Base
src/scripts/Configure-Makefile
src/scripts/MakeLinks
src/src/EDITME
src/src/acl.c
src/src/arc.c
src/src/daemon.c
src/src/dmarc.c [deleted file]
src/src/dmarc.h [deleted file]
src/src/drtables.c
src/src/exim.c
src/src/exim.h
src/src/expand.c
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/lookups/spf.c
src/src/miscmods/Makefile
src/src/miscmods/dmarc.c [new file with mode: 0644]
src/src/miscmods/dmarc.h [new file with mode: 0644]
src/src/miscmods/spf.c
src/src/moan.c
src/src/readconf.c
src/src/receive.c
src/src/smtp_in.c
src/src/structs.h
test/runtest
test/stderr/0437

index 428cbc07967074676d787edc324cb3be8625b163..90fb6dd47840b85936c9c1c8b18504a5b5af35be 100644 (file)
@@ -32055,12 +32055,20 @@ This control turns off DKIM verification processing entirely.  For details on
 the operation and configuration of DKIM, see section &<<SECDKIM>>&.
 
 
-.vitem &*control&~=&~dmarc_disable_verify*&
+.vitem &*control&~=&~enforce_sync*& &&&
+       &*control&~=&~no_enforce_sync*&
+
+.vitem &*control&~=&~dmarc_disable_verify*& &&&
+       &*control&~=&~dmarc_enable_forensic*&
 .cindex "disable DMARC verify"
-.cindex "DMARC" "disable verify"
-This control turns off DMARC verification processing entirely.  For details on
+.cindex DMARC "disable verify"
+.cindex DMARC controls
+.cindex DMARC "forensic mails"
+These control affect DMARC processing.  For details on
 the operation and configuration of DMARC, see section &<<SECDMARC>>&.
 
+The &"disable"& turns off DMARC verification processing entirely.
+
 
 .vitem &*control&~=&~dscp/*&<&'value'&>
 .cindex "&ACL;" "setting DSCP value"
index caae1e536bf841ea334a9b0965741827eaa04250..43cec361cdcb74cacf0606d196a827f342641ee6 100644 (file)
@@ -499,7 +499,6 @@ OBJ_EXPERIMENTAL =  arc.o \
                        bmi_spam.o \
                        dane.o \
                        dcc.o \
-                       dmarc.o \
                        imap_utf7.o \
                        utf8.o \
                        xclient.o
@@ -901,7 +900,6 @@ arc.o:              $(HDRS) pdkim/pdkim.h arc.c
 bmi_spam.o:    $(HDRS) bmi_spam.c
 dane.o:                $(HDRS) dane.c dane-openssl.c
 dcc.o:         $(HDRS) dcc.h dcc.c
-dmarc.o:       $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c
 imap_utf7.o:   $(HDRS) imap_utf7.c
 utf8.o:                $(HDRS) utf8.c
 xclient.o:     $(HDRS) xclient.c
index af0de26e470d044b667bed7b555a99f84a5958db..2b8a9bcb5a25c461ed272ecf3b0798edad7097bd 100755 (executable)
@@ -29,6 +29,16 @@ fi
 
 archtype=`../scripts/arch-type` || exit 1
 
+# Linux now whines about egrep, saying "use grep -E".
+# Solarix doesn't support -E on grep.  Thanks so much for
+# going non-back-compatible, Linux.
+if echo 1 | grep -E 1 >/dev/null; then
+  egrep="grep -E"
+else
+  egrep="egrep"
+fi
+
+
 # Now test for either the non-existence of Makefile, or for any of its
 # components being newer. Note that the "newer" script gives the right
 # answer (for our purposes) when the first file is non-existent.
@@ -311,7 +321,7 @@ done <<-END
  routers    ROUTER     ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT
  transports TRANSPORT  APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP
  auths     AUTH        CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS
- miscmods   SUPPORT    SPF
+ miscmods   SUPPORT    SPF DMARC
 END
 
 # See if there is a definition of EXIM_PERL in what we have built so far.
index 09d18b63cf89a5263e6effa8cb9f5b9ec244010c..e45097243be93382064e37a3e71eb516031741ff 100755 (executable)
@@ -94,7 +94,7 @@ d="miscmods"
 mkdir $d
 cd $d
 # Makefile is generated
-for f in spf.c spf.h
+for f in dmarc.c dmarc.h spf.c spf.h
 do
   ln -s ../../src/$d/$f $f
 done
@@ -140,7 +140,7 @@ for f in blob.h dbfunctions.h exim.h functions.h globals.h \
   string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-cipher-stdname.c \
   tls-gnu.c tls-openssl.c \
   tod.c transport.c tree.c verify.c version.c xtextencode.c \
-  dkim.c dkim.h dkim_transport.c dmarc.c dmarc.h \
+  dkim.c dkim.h dkim_transport.c \
   valgrind.h memcheck.h \
   macro_predef.c macro_predef.h
 do
index e507ab3cd53a5eccb1c5826c58f6dc817cab2ce1..35c497697a75447764c5152c8693d196993ca4e5 100644 (file)
@@ -642,9 +642,16 @@ DISABLE_MAL_MKS=yes
 
 # Uncomment the following line to add DMARC checking capability, implemented
 # using libopendmarc libraries. You must have SPF and DKIM support enabled also.
+#
+# If set to "2" instead of "yes" then the support will be
+# built as a module and must be installed into LOOKUP_MODULE_DIR (the name
+# is historic).  The same rules as for other module builds apply; use
+# SUPPORT_DMARC_{INCLUDE,LIBS}.
+#
 # SUPPORT_DMARC=yes
 # CFLAGS += -I/usr/local/include
 # LDFLAGS += -lopendmarc
+#
 # Uncomment the following if you need to change the default. You can
 # override it at runtime (main config option dmarc_tld_file)
 # DMARC_TLD_FILE=/etc/exim/opendmarc.tlds
index e285da65ca430719fcc04524783735b39f81ab18..30fc09174177514eb48af43c6142cc5d4f6028a9 100644 (file)
@@ -218,7 +218,11 @@ static condition_def conditions[] = {
   },
 #endif
 #ifdef SUPPORT_DMARC
-  [ACLC_DMARC_STATUS] =                { US"dmarc_status",     ACD_EXP,
+  [ACLC_DMARC_STATUS] =                { US"dmarc_status",
+# if SUPPORT_DMARC==2
+                                 ACD_LOAD |
+# endif
+                                 ACD_EXP,
                                  PERMITTED(ACL_BIT_DATA) },
 #endif
 
@@ -393,6 +397,9 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
 
 #ifndef MACRO_PREDEF
 
+/* These tables support loading of dynamic modules triggered by an ACL
+condition use, spotted during readconf. See acl_read(). */
+
 # ifdef LOOKUP_MODULE_DIR
 typedef struct condition_module {
   const uschar *       mod_name;       /* module for the givien conditions */
@@ -403,11 +410,17 @@ typedef struct condition_module {
 #  if SUPPORT_SPF==2
 static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 };
 #  endif
+#  if SUPPORT_DMARC==2
+static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 };
+#  endif
 
 static condition_module condition_modules[] = {
 #  if SUPPORT_SPF==2
   {.mod_name = US"spf", .conditions = spf_condx},
 #  endif
+#  if SUPPORT_SPF==2
+  {.mod_name = US"dmarc", .conditions = dmarc_condx},
+#  endif
 };
 
 # endif
@@ -3942,14 +3955,36 @@ for (; cb; cb = cb->next)
 
 #ifdef SUPPORT_DMARC
     case ACLC_DMARC_STATUS:
+      /* See comment on ACLC_SPF wrt. coding issues */
+      {
+      misc_module_info * mi = misc_mod_find(US"dmarc", &log_message);
+      typedef uschar * (*efn_t)(int);
+      uschar * expanded_query;
+
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
+      if (!mi)
+       { rc = DEFER; break; }                  /* shouldn't happen */
+
+debug_printf("%s %d: mi %p\n", __FUNCTION__, __LINE__, mi);
       if (!f.dmarc_has_been_checked)
-       dmarc_process();
-      f.dmarc_has_been_checked = TRUE;
+       {
+       typedef int (*pfn_t)(void);
+       (void) (((pfn_t *) mi->functions)[0]) ();       /* dmarc_process */
+       f.dmarc_has_been_checked = TRUE;
+       }
 
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
       /* used long way of dmarc_exim_expand_query() in case we need more
       view into the process in the future. */
-      rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
+
+      /*XXX is this call used with any other arg? */
+      expanded_query = (((efn_t *) mi->functions)[1]) (DMARC_VERIFY_STATUS);
+
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
+      rc = match_isinlist(expanded_query,
                          &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
+      }
       break;
 #endif
 
@@ -4185,8 +4220,10 @@ for (; cb; cb = cb->next)
 #ifdef SUPPORT_SPF
     case ACLC_SPF:
     case ACLC_SPF_GUESS:
-      /* Hardwire the offset of the function in the module functions table
-      for now.  Work out a more general mech later. */
+      /* We have hardwired function-call numbers, and also prototypes for the
+      functions.  We could do a function name table search for the number
+      but I can't see how to deal with prototypes.  Is a K&R non-prototyped
+      function still usable with today's compilers? */
       {
       misc_module_info * mi = misc_mod_find(US"spf", &log_message);
       typedef int (*fn_t)(const uschar **, const uschar *, int);
@@ -4195,7 +4232,7 @@ for (; cb; cb = cb->next)
       if (!mi)
        { rc = DEFER; break; }                  /* shouldn't happen */
 
-      fn = ((fn_t *) mi->functions)[1];
+      fn = ((fn_t *) mi->functions)[0];                /* spf_process() */
 
       rc = fn(&arg, sender_address,
              cb->type == ACLC_SPF ? SPF_PROCESS_NORMAL : SPF_PROCESS_GUESS);
index 48f69a8cf8bce6432ad44268831d916ffaab1604..2dcbf2efbd9b2c390d5f752ced1a89d563d5b3d6 100644 (file)
@@ -19,7 +19,7 @@
 #  include "pdkim/signing.h"
 
 #  ifdef SUPPORT_DMARC
-#   include "dmarc.h"
+#   include "miscmods/dmarc.h"
 #  endif
 
 extern pdkim_ctx * dkim_verify_ctx;
index 49bf74a11d3e7c43795826d05ce503f2849bb85f..456c586da1632b2b2b16ed54929c40c9fa800ecf 100644 (file)
@@ -2578,9 +2578,6 @@ smtp_deliver_init();      /* Used for callouts */
 #ifdef WITH_CONTENT_SCAN
 malware_init();
 #endif
-#ifdef SUPPORT_DMARC
-dmarc_init();
-#endif
 #ifndef DISABLE_TLS
 tls_daemon_init();
 #endif
diff --git a/src/src/dmarc.c b/src/src/dmarc.c
deleted file mode 100644 (file)
index 664daa7..0000000
+++ /dev/null
@@ -1,717 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-/* DMARC support.
-   Copyright (c) The Exim Maintainers 2019 - 2024
-   Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014
-   License: GPL */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
-   All rights reserved, licensed for use per LICENSE.opendmarc. */
-
-/* Code for calling dmarc checks via libopendmarc. Called from acl.c. */
-
-#include "exim.h"
-#ifdef SUPPORT_DMARC
-# if !defined SUPPORT_SPF
-#  error SPF must also be enabled for DMARC
-# elif defined DISABLE_DKIM
-#  error DKIM must also be enabled for DMARC
-# else
-
-#  include "functions.h"
-#  include "dmarc.h"
-#  include "pdkim/pdkim.h"
-
-OPENDMARC_LIB_T     dmarc_ctx;
-DMARC_POLICY_T     *dmarc_pctx = NULL;
-OPENDMARC_STATUS_T  libdm_status, action, dmarc_policy;
-OPENDMARC_STATUS_T  da, sa, action;
-BOOL dmarc_abort  = FALSE;
-uschar *dmarc_pass_fail = US"skipped";
-header_line *from_header   = NULL;
-
-misc_module_info * spf_mod_info;
-SPF_response_t   *spf_response_p;
-int dmarc_spf_ares_result  = 0;
-uschar *spf_sender_domain  = NULL;
-uschar *spf_human_readable = NULL;
-u_char *header_from_sender = NULL;
-int history_file_status    = DMARC_HIST_OK;
-
-typedef struct dmarc_exim_p {
-  uschar *name;
-  int    value;
-} dmarc_exim_p;
-
-static dmarc_exim_p dmarc_policy_description[] = {
-  /* name              value */
-  { US"",           DMARC_RECORD_P_UNSPECIFIED },
-  { US"none",       DMARC_RECORD_P_NONE },
-  { US"quarantine", DMARC_RECORD_P_QUARANTINE },
-  { US"reject",     DMARC_RECORD_P_REJECT },
-  { NULL,           0 }
-};
-
-
-int
-dmarc_init(void)
-{
-uschar * errstr;
-if (!(spf_mod_info = misc_mod_find(US"spf", &errstr)))
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-           "dmarc: failed to find SPF module: %s", errstr);
-return TRUE;
-}
-
-gstring *
-dmarc_version_report(gstring * g)
-{
-return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
-    (OPENDMARC_LIB_VERSION & 0xff000000) >> 24, (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
-    (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8, OPENDMARC_LIB_VERSION & 0x000000ff);
-}
-
-
-/* Accept an error_block struct, initialize if empty, parse to the
-end, and append the two strings passed to it.  Used for adding
-variable amounts of value:pair data to the forensic emails. */
-
-static error_block *
-add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
-{
-error_block *eb = store_malloc(sizeof(error_block));
-if (!eblock)
-  eblock = eb;
-else
-  {
-  /* Find the end of the eblock struct and point it at eb */
-  error_block *tmp = eblock;
-  while(tmp->next)
-    tmp = tmp->next;
-  tmp->next = eb;
-  }
-eb->text1 = t1;
-eb->text2 = t2;
-eb->next  = NULL;
-return eblock;
-}
-
-/* dmarc_conn_init sets up a context that can be re-used for several
-messages on the same SMTP connection (that come from the
-same host with the same HELO string) */
-
-int
-dmarc_conn_init(void)
-{
-int *netmask   = NULL;   /* Ignored */
-int is_ipv6    = 0;
-
-/* Set some sane defaults.  Also clears previous results when
-multiple messages in one connection. */
-
-dmarc_pctx         = NULL;
-dmarc_status       = US"none";
-dmarc_abort        = FALSE;
-dmarc_pass_fail    = US"skipped";
-dmarc_used_domain  = US"";
-f.dmarc_has_been_checked = FALSE;
-header_from_sender = NULL;
-spf_response_p    = NULL;
-spf_sender_domain  = NULL;
-spf_human_readable = NULL;
-
-/* ACLs have "control=dmarc_disable_verify" */
-if (f.dmarc_disable_verify)
-  return OK;
-
-(void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
-dmarc_ctx.nscount = 0;
-libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
-if (libdm_status != DMARC_PARSE_OKAY)
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
-                      opendmarc_policy_status_to_str(libdm_status));
-  dmarc_abort = TRUE;
-  }
-if (!dmarc_tld_file || !*dmarc_tld_file)
-  {
-  DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
-  dmarc_abort = TRUE;
-  }
-else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
-                      dmarc_tld_file, strerror(errno));
-  dmarc_abort = TRUE;
-  }
-if (!sender_host_address)
-  {
-  DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
-  dmarc_abort = TRUE;
-  }
-/* This catches locally originated email and startup errors above. */
-if (!dmarc_abort)
-  {
-  is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
-  if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "DMARC failure creating policy context: ip=%s", sender_host_address);
-    dmarc_abort = TRUE;
-    }
-  }
-
-return OK;
-}
-
-
-/* dmarc_store_data stores the header data so that subsequent dmarc_process can
-access the data.
-Called after the entire message has been received, with the From: header. */
-
-int
-dmarc_store_data(header_line * hdr)
-{
-/* No debug output because would change every test debug output */
-if (!f.dmarc_disable_verify)
-  from_header = hdr;
-return OK;
-}
-
-
-static void
-dmarc_send_forensic_report(u_char ** ruf)
-{
-uschar *recipient, *save_sender;
-BOOL  send_status = FALSE;
-error_block *eblock = NULL;
-FILE *message_file = NULL;
-
-/* Earlier ACL does not have *required* control=dmarc_enable_forensic */
-if (!f.dmarc_enable_forensic)
-  return;
-
-if (  dmarc_policy == DMARC_POLICY_REJECT     && action == DMARC_RESULT_REJECT
-   || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE
-   || dmarc_policy == DMARC_POLICY_NONE       && action == DMARC_RESULT_REJECT
-   || dmarc_policy == DMARC_POLICY_NONE       && action == DMARC_RESULT_QUARANTINE
-   )
-  if (ruf)
-    {
-    eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain);
-    eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
-    eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
-    eblock = add_to_eblock(eblock, US"SPF Alignment",
-                    sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no");
-    eblock = add_to_eblock(eblock, US"DKIM Alignment",
-                    da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no");
-    eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
-
-    for (int c = 0; ruf[c]; c++)
-      {
-      recipient = string_copylc(ruf[c]);
-      if (Ustrncmp(recipient, "mailto:",7))
-       continue;
-      /* Move to first character past the colon */
-      recipient += 7;
-      DEBUG(D_receive)
-       debug_printf_indent("DMARC forensic report to %s%s\n", recipient,
-            (host_checking || f.running_in_test_harness) ? " (not really)" : "");
-      if (host_checking || f.running_in_test_harness)
-       continue;
-
-      if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
-                           header_list, message_file, NULL))
-       log_write(0, LOG_MAIN|LOG_PANIC,
-         "failure to send DMARC forensic report to %s", recipient);
-      }
-    }
-}
-
-
-/* Look up a DNS dmarc record for the given domain.  Return it or NULL */
-
-static uschar *
-dmarc_dns_lookup(uschar * dom)
-{
-dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
-int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
-
-if (rc == DNS_SUCCEED)
-  for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
-       rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-    if (rr->type == T_TXT && rr->size > 3)
-      {
-      uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
-      store_free_dns_answer(dnsa);
-      return record;
-      }
-store_free_dns_answer(dnsa);
-return NULL;
-}
-
-
-static int
-dmarc_write_history_file(const gstring * dkim_history_buffer)
-{
-int history_file_fd = 0;
-ssize_t written_len;
-int tmp_ans;
-u_char ** rua; /* aggregate report addressees */
-gstring * g;
-
-if (!dmarc_history_file)
-  {
-  DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
-  return DMARC_HIST_DISABLED;
-  }
-if (!host_checking)
-  {
-  uschar * s = string_copy(dmarc_history_file);                /* need a writeable copy */
-  if ((history_file_fd = log_open_as_exim(s)) < 0)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-             "failure to create DMARC history file: %s: %s",
-             s, strerror(errno));
-    return DMARC_HIST_FILE_ERR;
-    }
-  }
-
-/* Generate the contents of the history file entry */
-
-g = string_fmt_append(NULL,
-  "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
-  message_id, primary_hostname, time(NULL), sender_host_address,
-  header_from_sender, expand_string(US"$sender_address_domain"));
-
-if (spf_response_p)
-  g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
-
-if (dkim_history_buffer)
-  g = string_fmt_append(g, "%Y", dkim_history_buffer);
-
-g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
-  dmarc_used_domain, dmarc_policy);
-
-if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
-  for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
-    g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]);
-else
-  g = string_catn(g, US"rua -\n", 6);
-
-opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "pct %d\n", tmp_ans);
-
-opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "adkim %d\n", tmp_ans);
-
-opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "aspf %d\n", tmp_ans);
-
-opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "p %d\n", tmp_ans);
-
-opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "sp %d\n", tmp_ans);
-
-g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
-  da, sa, action);
-
-#if DMARC_API >= 100400
-# ifdef EXPERIMENTAL_ARC
-g = arc_dmarc_hist_append(g);
-# else
-g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
-                     ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
-# endif
-#endif
-
-/* Write the contents to the history file */
-DEBUG(D_receive)
-  {
-  debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
-            host_checking ? " (not really)" : "");
-  debug_printf_indent("DMARC history data for debugging:\n");
-  expand_level++;
-    debug_printf_indent("%Y", g);
-  expand_level--;
-  }
-
-if (!host_checking)
-  {
-  written_len = write_to_fd_buf(history_file_fd,
-                               g->s,
-                               gstring_length(g));
-  if (written_len == 0)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
-                          dmarc_history_file);
-    return DMARC_HIST_WRITE_ERR;
-    }
-  (void)close(history_file_fd);
-  }
-return DMARC_HIST_OK;
-}
-
-
-/* dmarc_process adds the envelope sender address to the existing
-context (if any), retrieves the result, sets up expansion
-strings and evaluates the condition outcome.
-Called for the first ACL dmarc= condition. */
-
-int
-dmarc_process(void)
-{
-int sr, origin;             /* used in SPF section */
-int dmarc_spf_result  = 0;  /* stores spf into dmarc conn ctx */
-int tmp_ans, c;
-pdkim_signature * sig = dkim_signatures;
-uschar * rr;
-BOOL has_dmarc_record = TRUE;
-u_char ** ruf; /* forensic report addressees, if called for */
-
-/* ACLs have "control=dmarc_disable_verify" */
-if (f.dmarc_disable_verify)
-  return OK;
-
-/* Store the header From: sender domain for this part of DMARC.
-If there is no from_header struct, then it's likely this message
-is locally generated and relying on fixups to add it.  Just skip
-the entire DMARC system if we can't find a From: header....or if
-there was a previous error.  */
-
-if (!from_header)
-  {
-  DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
-  dmarc_abort = TRUE;
-  }
-else if (!dmarc_abort)
-  {
-  uschar * errormsg;
-  int dummy, domain;
-  uschar * p;
-  uschar saveend;
-
-  f.parse_allow_group = TRUE;
-  p = parse_find_address_end(from_header->text, FALSE);
-  saveend = *p; *p = '\0';
-  if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
-                             &dummy, &dummy, &domain, FALSE)))
-    header_from_sender += domain;
-  *p = saveend;
-
-  /* The opendmarc library extracts the domain from the email address, but
-  only try to store it if it's not empty.  Otherwise, skip out of DMARC. */
-
-  if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
-    dmarc_abort = TRUE;
-  libdm_status = dmarc_abort
-    ? DMARC_PARSE_OKAY
-    : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
-  if (libdm_status != DMARC_PARSE_OKAY)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-             "failure to store header From: in DMARC: %s, header was '%s'",
-             opendmarc_policy_status_to_str(libdm_status), from_header->text);
-    dmarc_abort = TRUE;
-    }
-  }
-
-/* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
-instead do this in the ACLs.  */
-
-if (!dmarc_abort && !sender_host_authenticated)
-  {
-  uschar * dmarc_domain;
-  gstring * dkim_history_buffer = NULL;
-
-  /* Use the envelope sender domain for this part of DMARC */
-
-  spf_sender_domain = expand_string(US"$sender_address_domain");
-
-    {
-    misc_module_info * mi = misc_mod_findonly(US"spf");
-    typedef SPF_response_t * (*fn_t)(void);
-    if (mi)
-      spf_response_p = ((fn_t *) mi->functions)[3]();  /* spf_get_response */
-    }
-
-  if (!spf_response_p)
-    {
-    /* No spf data means null envelope sender so generate a domain name
-    from the sender_helo_name  */
-
-    if (!spf_sender_domain)
-      {
-      spf_sender_domain = sender_helo_name;
-      log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
-                            spf_sender_domain);
-      DEBUG(D_receive)
-       debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
-         spf_sender_domain);
-      }
-    dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
-    dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
-    origin = DMARC_POLICY_SPF_ORIGIN_HELO;
-    spf_human_readable = US"";
-    }
-  else
-    {
-    sr = spf_response_p->result;
-    dmarc_spf_result = sr == SPF_RESULT_NEUTRAL  ? DMARC_POLICY_SPF_OUTCOME_NONE :
-                      sr == SPF_RESULT_PASS     ? DMARC_POLICY_SPF_OUTCOME_PASS :
-                      sr == SPF_RESULT_FAIL     ? DMARC_POLICY_SPF_OUTCOME_FAIL :
-                      sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
-                      DMARC_POLICY_SPF_OUTCOME_NONE;
-    dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL   ? ARES_RESULT_NEUTRAL :
-                           sr == SPF_RESULT_PASS      ? ARES_RESULT_PASS :
-                           sr == SPF_RESULT_FAIL      ? ARES_RESULT_FAIL :
-                           sr == SPF_RESULT_SOFTFAIL  ? ARES_RESULT_SOFTFAIL :
-                           sr == SPF_RESULT_NONE      ? ARES_RESULT_NONE :
-                           sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
-                           sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
-                           ARES_RESULT_UNKNOWN;
-    origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
-    spf_human_readable = US spf_response_p->header_comment;
-    DEBUG(D_receive)
-      debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
-    }
-  if (strcmp( CCS spf_sender_domain, "") == 0)
-    dmarc_abort = TRUE;
-  if (!dmarc_abort)
-    {
-    libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
-                               dmarc_spf_result, origin, spf_human_readable);
-    if (libdm_status != DMARC_PARSE_OKAY)
-      log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
-                          opendmarc_policy_status_to_str(libdm_status));
-    }
-
-  /* Now we cycle through the dkim signature results and put into
-  the opendmarc context, further building the DMARC reply. */
-
-  for(pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
-    {
-    int dkim_result, dkim_ares_result, vs, ves;
-
-    vs  = sig->verify_status & ~PDKIM_VERIFY_POLICY;
-    ves = sig->verify_ext_status;
-    dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
-                 vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
-                 vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
-                 DMARC_POLICY_DKIM_OUTCOME_NONE;
-    libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
-
-/* The opendmarc project broke its API in a way we can't detect easily.
-The EDITME provides a DMARC_API variable */
-#if DMARC_API >= 100400
-                                               sig->selector,
-#endif
-                                               dkim_result, US"");
-    DEBUG(D_receive)
-      debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
-    if (libdm_status != DMARC_PARSE_OKAY)
-      log_write(0, LOG_MAIN|LOG_PANIC,
-               "failure to store dkim (%s) for DMARC: %s",
-               sig->domain, opendmarc_policy_status_to_str(libdm_status));
-
-    dkim_ares_result =
-      vs == PDKIM_VERIFY_PASS    ? ARES_RESULT_PASS :
-      vs == PDKIM_VERIFY_FAIL    ? ARES_RESULT_FAIL :
-      vs == PDKIM_VERIFY_NONE    ? ARES_RESULT_NONE :
-      vs == PDKIM_VERIFY_INVALID ?
-       ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
-       ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE        ? ARES_RESULT_PERMERROR :
-       ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD   ? ARES_RESULT_PERMERROR :
-       ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT      ? ARES_RESULT_PERMERROR :
-       ARES_RESULT_UNKNOWN :
-      ARES_RESULT_UNKNOWN;
-#if DMARC_API >= 100400
-    dkim_history_buffer = string_fmt_append(dkim_history_buffer,
-      "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
-#else
-    dkim_history_buffer = string_fmt_append(dkim_history_buffer,
-      "dkim %s %d\n", sig->domain, dkim_ares_result);
-#endif
-    }
-
-  /* Look up DMARC policy record in DNS.  We do this explicitly, rather than
-  letting the dmarc library do it with opendmarc_policy_query_dmarc(), so that
-  our dns access path is used for debug tracing and for the testsuite
-  diversion. */
-
-  libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
-    ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
-    : DMARC_DNS_ERROR_NO_RECORD;
-  switch (libdm_status)
-    {
-    case DMARC_DNS_ERROR_NXDOMAIN:
-    case DMARC_DNS_ERROR_NO_RECORD:
-      DEBUG(D_receive)
-       debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
-      has_dmarc_record = FALSE;
-      break;
-    case DMARC_PARSE_OKAY:
-      DEBUG(D_receive)
-       debug_printf_indent("DMARC record found for %s\n", header_from_sender);
-      break;
-    case DMARC_PARSE_ERROR_BAD_VALUE:
-      DEBUG(D_receive)
-       debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
-      has_dmarc_record = FALSE;
-      break;
-    default:
-      /* everything else, skip dmarc */
-      DEBUG(D_receive)
-       debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
-                     opendmarc_policy_status_to_str(libdm_status),
-                     from_header->text);
-      has_dmarc_record = FALSE;
-      break;
-    }
-
-  /* Store the policy string in an expandable variable. */
-
-  libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
-  for (c = 0; dmarc_policy_description[c].name; c++)
-    if (tmp_ans == dmarc_policy_description[c].value)
-      {
-      dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
-      break;
-      }
-
-  /* Can't use exim's string manipulation functions so allocate memory
-  for libopendmarc using its max hostname length definition. */
-
-  dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
-  libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
-    dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
-  store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
-  dmarc_used_domain = dmarc_domain;
-
-  if (libdm_status != DMARC_PARSE_OKAY)
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "failure to read domainname used for DMARC lookup: %s",
-      opendmarc_policy_status_to_str(libdm_status));
-
-  dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
-  switch(libdm_status)
-    {
-    case DMARC_POLICY_ABSENT:     /* No DMARC record found */
-      dmarc_status = US"norecord";
-      dmarc_pass_fail = US"none";
-      dmarc_status_text = US"No DMARC record";
-      action = DMARC_RESULT_ACCEPT;
-      break;
-    case DMARC_FROM_DOMAIN_ABSENT:    /* No From: domain */
-      dmarc_status = US"nofrom";
-      dmarc_pass_fail = US"temperror";
-      dmarc_status_text = US"No From: domain found";
-      action = DMARC_RESULT_ACCEPT;
-      break;
-    case DMARC_POLICY_NONE:       /* Accept and report */
-      dmarc_status = US"none";
-      dmarc_pass_fail = US"none";
-      dmarc_status_text = US"None, Accept";
-      action = DMARC_RESULT_ACCEPT;
-      break;
-    case DMARC_POLICY_PASS:       /* Explicit accept */
-      dmarc_status = US"accept";
-      dmarc_pass_fail = US"pass";
-      dmarc_status_text = US"Accept";
-      action = DMARC_RESULT_ACCEPT;
-      break;
-    case DMARC_POLICY_REJECT:       /* Explicit reject */
-      dmarc_status = US"reject";
-      dmarc_pass_fail = US"fail";
-      dmarc_status_text = US"Reject";
-      action = DMARC_RESULT_REJECT;
-      break;
-    case DMARC_POLICY_QUARANTINE:       /* Explicit quarantine */
-      dmarc_status = US"quarantine";
-      dmarc_pass_fail = US"fail";
-      dmarc_status_text = US"Quarantine";
-      action = DMARC_RESULT_QUARANTINE;
-      break;
-    default:
-      dmarc_status = US"temperror";
-      dmarc_pass_fail = US"temperror";
-      dmarc_status_text = US"Internal Policy Error";
-      action = DMARC_RESULT_TEMPFAIL;
-      break;
-    }
-
-  libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
-  if (libdm_status != DMARC_PARSE_OKAY)
-    log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
-                            opendmarc_policy_status_to_str(libdm_status));
-
-  if (has_dmarc_record)
-    {
-    log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
-                          "spf_align=%s dkim_align=%s enforcement='%s'",
-                          spf_sender_domain, dmarc_used_domain,
-                          sa==DMARC_POLICY_SPF_ALIGNMENT_PASS  ?"yes":"no",
-                          da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
-                          dmarc_status_text);
-    history_file_status = dmarc_write_history_file(dkim_history_buffer);
-    /* Now get the forensic reporting addresses, if any */
-    ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
-    dmarc_send_forensic_report(ruf);
-    }
-  }
-
-/* shut down libopendmarc */
-if (dmarc_pctx)
-  (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
-if (!f.dmarc_disable_verify)
-  (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
-
-return OK;
-}
-
-uschar *
-dmarc_exim_expand_query(int what)
-{
-if (f.dmarc_disable_verify || !dmarc_pctx)
-  return dmarc_exim_expand_defaults(what);
-
-if (what == DMARC_VERIFY_STATUS)
-  return dmarc_status;
-return US"";
-}
-
-uschar *
-dmarc_exim_expand_defaults(int what)
-{
-if (what == DMARC_VERIFY_STATUS)
-  return f.dmarc_disable_verify ?  US"off" : US"none";
-return US"";
-}
-
-
-gstring *
-authres_dmarc(gstring * g)
-{
-if (f.dmarc_has_been_checked)
-  {
-  int start = 0;               /* Compiler quietening */
-  DEBUG(D_acl) start = gstring_length(g);
-  g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
-  if (header_from_sender)
-    g = string_append(g, 2, US" header.from=", header_from_sender);
-  DEBUG(D_acl) debug_printf("DMARC:\tauthres '%.*s'\n",
-                 gstring_length(g) - start - 3, g->s + start + 3);
-  }
-else
-  DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
-return g;
-}
-
-# endif /* SUPPORT_SPF */
-#endif /* SUPPORT_DMARC */
-/* vi: aw ai sw=2
- */
diff --git a/src/src/dmarc.h b/src/src/dmarc.h
deleted file mode 100644 (file)
index dcf289f..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Experimental DMARC support.
-   Copyright (c) The Exim Maintainers 2021 - 2023
-   Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014
-   License: GPL */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
-   All rights reserved, licensed for use per LICENSE.opendmarc. */
-
-#ifdef SUPPORT_DMARC
-
-# include "opendmarc/dmarc.h"
-# ifdef SUPPORT_SPF
-#  include "spf2/spf.h"
-# endif /* SUPPORT_SPF */
-
-/* prototypes */
-gstring * dmarc_version_report(gstring *);
-int dmarc_init(void);
-int dmarc_conn_init(void);
-int dmarc_store_data(header_line *);
-int dmarc_process(void);
-uschar *dmarc_exim_expand_query(int);
-uschar *dmarc_exim_expand_defaults(int);
-
-#define DMARC_VERIFY_STATUS    1
-
-#define DMARC_HIST_OK          1
-#define DMARC_HIST_DISABLED    2
-#define DMARC_HIST_EMPTY       3
-#define DMARC_HIST_FILE_ERR    4
-#define DMARC_HIST_WRITE_ERR   5
-
-/* From opendmarc.c */
-#define DMARC_RESULT_REJECT     0
-#define DMARC_RESULT_DISCARD    1
-#define DMARC_RESULT_ACCEPT     2
-#define DMARC_RESULT_TEMPFAIL   3
-#define DMARC_RESULT_QUARANTINE 4
-
-/* From opendmarc-ar.h */
-/* ARES_RESULT_T -- type for specifying an authentication result */
-#define ARES_RESULT_UNDEFINED   (-1)
-#define ARES_RESULT_PASS    0
-#define ARES_RESULT_UNUSED  1
-#define ARES_RESULT_SOFTFAIL    2
-#define ARES_RESULT_NEUTRAL 3
-#define ARES_RESULT_TEMPERROR   4
-#define ARES_RESULT_PERMERROR   5
-#define ARES_RESULT_NONE    6
-#define ARES_RESULT_FAIL    7
-#define ARES_RESULT_POLICY  8
-#define ARES_RESULT_NXDOMAIN    9
-#define ARES_RESULT_SIGNED  10
-#define ARES_RESULT_UNKNOWN 11
-#define ARES_RESULT_DISCARD 12
-
-#define        DMARC_ARC_POLICY_RESULT_PASS    0
-#define        DMARC_ARC_POLICY_RESULT_UNUSED  1
-#define        DMARC_ARC_POLICY_RESULT_FAIL    2
-
-#endif /* SUPPORT_DMARC */
index 35a376dd1aeb92ef8e51a9070f3f2dac2b5da427..9ed55e29a02c9bc64dad53d6f2801865eae81e3a 100644 (file)
@@ -432,8 +432,8 @@ static void
 misc_mod_add(misc_module_info * mi)
 {
 if (mi->init) mi->init(mi);
-DEBUG(D_lookup) if (mi->lib_vers_report)
-  debug_printf_indent("%Y\n", mi->lib_vers_report(NULL));
+DEBUG(D_any) if (mi->lib_vers_report)
+  debug_printf_indent("%Y", mi->lib_vers_report(NULL));
 
 mi->next = misc_module_list;
 misc_module_list = mi;
@@ -459,8 +459,10 @@ mi = (struct misc_module_info *) dlsym(dl,
                                    CS string_sprintf("%s_module_info", name));
 if ((errormsg = dlerror()))
   {
-  fprintf(stderr, "%s does not appear to be an spf module (%s)\n", name, errormsg);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be an spf module (%s)", name, errormsg);
+  fprintf(stderr, "%s does not appear to be a '%s' module (%s)\n",
+         name, name, errormsg);
+  log_write(0, LOG_MAIN|LOG_PANIC,
+    "%s does not contain the expected module info symbol (%s)", name, errormsg);
   dlclose(dl);
   return NULL;
   }
@@ -491,6 +493,7 @@ misc_mod_findonly(const uschar * name)
 for (misc_module_info * mi = misc_module_list; mi; mi = mi->next)
   if (Ustrcmp(name, mi->name) == 0)
     return mi;
+return NULL;
 }
 
 /* Find a "misc" module, possibly already loaded, by name. */
@@ -508,6 +511,42 @@ return NULL;
 }
 
 
+/* For any "misc" module having a connection-init routine, call it. */
+
+int
+misc_mod_conn_init(const uschar * sender_helo_name,
+  const uschar * sender_host_address)
+{
+for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+  if (mi->conn_init)
+    if ((mi->conn_init) (sender_helo_name, sender_host_address) != OK)
+      return FAIL;
+return OK;
+}
+
+/* Ditto, smtp-reset */
+
+void
+misc_mod_smtp_reset(void)
+{
+for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+  if (mi->smtp_reset)
+    (mi->smtp_reset)();
+}
+
+/* Ditto, msg-init */
+
+int
+misc_mod_msg_init(void)
+{
+for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+  if (mi->msg_init)
+    if ((mi->msg_init)() != OK)
+      return FAIL;
+return OK;
+}
+
+
 
 
 
@@ -658,6 +697,9 @@ DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
 }
 
 
+#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
+extern misc_module_info dmarc_module_info;
+#endif
 #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
 extern misc_module_info spf_module_info;
 #endif
@@ -667,9 +709,15 @@ init_misc_mod_list(void)
 {
 static BOOL onetime = FALSE;
 if (onetime) return;
+
 #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
+/* dmarc depends on spf so this add must go first, for the dmarc-static case */
 misc_mod_add(&spf_module_info);
 #endif
+#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
+misc_mod_add(&dmarc_module_info);
+#endif
+
 onetime = TRUE;
 }
 
index ecc25d6bc5920474681428bc1de2d1e5931757bd..5ad54ffc102a943885087ed059c4f9ebeea4e059 100644 (file)
@@ -1395,9 +1395,9 @@ DEBUG(D_any)
 #ifdef SUPPORT_I18N
   g = utf8_version_report(g);
 #endif
-#ifdef SUPPORT_DMARC
-  g = dmarc_version_report(g);
-#endif
+
+/*XXX do we need a "show misc-mods version-report" ?
+Currently they are output in misc_mod_add() */
 
   show_string(is_stdout, g);
   g = NULL;
index 470adb351321547d022dd099d37e6d83b704aedf..284748c5d79087ac7aef8debcabde120687a8d50 100644 (file)
@@ -549,7 +549,7 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 # include "dkim.h"
 #endif
 #ifdef SUPPORT_DMARC
-# include "dmarc.h"
+# include "miscmods/dmarc.h"
 # include <opendmarc/dmarc.h>
 #endif
 
index b3a1575a7e0e5d9ba346f0efdb11de069481042f..a6b05bd879b66a3c39db72c02f477542c8b19ae9 100644 (file)
@@ -513,10 +513,10 @@ static var_entry var_table[] = {
   { "dkim_verify_status",  vtype_stringptr,   &dkim_verify_status },
 #endif
 #ifdef SUPPORT_DMARC
-  { "dmarc_domain_policy", vtype_stringptr,   &dmarc_domain_policy },
-  { "dmarc_status",        vtype_stringptr,   &dmarc_status },
-  { "dmarc_status_text",   vtype_stringptr,   &dmarc_status_text },
-  { "dmarc_used_domain",   vtype_stringptr,   &dmarc_used_domain },
+  { "dmarc_domain_policy", vtype_module,       US"dmarc" },
+  { "dmarc_status",        vtype_module,       US"dmarc" },
+  { "dmarc_status_text",   vtype_module,       US"dmarc" },
+  { "dmarc_used_domain",   vtype_module,       US"dmarc" },
 #endif
   { "dnslist_domain",      vtype_stringptr,   &dnslist_domain },
   { "dnslist_matched",     vtype_stringptr,   &dnslist_matched },
@@ -4888,7 +4888,7 @@ while (*s)
        if (mi)
          {
          typedef gstring * (*fn_t)(gstring *);
-         fn_t fn = ((fn_t *) mi->functions)[2];        /* authres_spf */
+         fn_t fn = ((fn_t *) mi->functions)[1];        /* authres_spf */
          yield = fn(yield);
          }
        }
@@ -4897,7 +4897,16 @@ while (*s)
       yield = authres_dkim(yield);
 #endif
 #ifdef SUPPORT_DMARC
-      yield = authres_dmarc(yield);
+       {
+       misc_module_info * mi = misc_mod_findonly(US"dmarc");
+       if (mi)
+         {
+         /*XXX is authres common enough to be generic? */
+         typedef gstring * (*fn_t)(gstring *);
+         fn_t fn = ((fn_t *) mi->functions)[2];        /* authres_dmarc*/
+         yield = fn(yield);
+         }
+       }
 #endif
 #ifdef EXPERIMENTAL_ARC
       yield = authres_arc(yield);
index aaec6461f61fd683d8a472b868ca283fea8ccaf7..3a980318f37534e05b2d358c8d2e453b5c3a781d 100644 (file)
@@ -147,9 +147,6 @@ extern gstring *authres_arc(gstring *);
 #ifndef DISABLE_DKIM
 extern gstring *authres_dkim(gstring *);
 #endif
-#ifdef SUPPORT_DMARC
-extern gstring *authres_dmarc(gstring *);
-#endif
 extern gstring *authres_smtpauth(gstring *);
 
 extern uschar *b64encode(const uschar *, int);
@@ -377,8 +374,13 @@ extern ssize_t mime_decode_base64(FILE *, FILE *, uschar *);
 extern int     mime_regex(const uschar **, BOOL);
 extern void    mime_set_anomaly(int);
 #endif
+
+extern int     misc_mod_conn_init(const uschar *, const uschar *);
 extern misc_module_info * misc_mod_find(const uschar * modname, uschar **);
 extern misc_module_info * misc_mod_findonly(const uschar * modname);
+extern int     misc_mod_msg_init(void);
+extern void    misc_mod_smtp_reset(void);
+
 extern uschar *moan_check_errorcopy(const uschar *);
 extern BOOL    moan_skipped_syntax_errors(uschar *, error_block *, uschar *,
                  BOOL, uschar *);
index f2287d41cf5599dfeb7df28052de55062d3092b5..cfa75f1d7bac98e198239ef98d874e279bf5514c 100644 (file)
@@ -882,15 +882,6 @@ uschar *dkim_verify_signers      = US"$dkim_signers";
 uschar *dkim_verify_status      = NULL;
 uschar *dkim_verify_reason      = NULL;
 #endif
-#ifdef SUPPORT_DMARC
-uschar *dmarc_domain_policy     = NULL;
-uschar *dmarc_forensic_sender   = NULL;
-uschar *dmarc_history_file      = NULL;
-uschar *dmarc_status            = NULL;
-uschar *dmarc_status_text       = NULL;
-uschar *dmarc_tld_file          = NULL;
-uschar *dmarc_used_domain       = NULL;
-#endif
 
 uschar *dns_again_means_nonexist = NULL;
 int     dns_csa_search_limit   = 5;
index 9b30e502c1ff5f48814914c2415d7373015c64d7..8173d771e9fba0675074612462ec1cb6c39951ba 100644 (file)
@@ -563,15 +563,6 @@ extern uschar *dkim_verify_signers;    /* Colon-separated list of domains for ea
 extern uschar *dkim_verify_status;     /* result for this signature */
 extern uschar *dkim_verify_reason;     /* result for this signature */
 #endif
-#ifdef SUPPORT_DMARC
-extern uschar *dmarc_domain_policy;    /* Expansion for declared policy of used domain */
-extern uschar *dmarc_forensic_sender;  /* Set sender address for forensic reports */
-extern uschar *dmarc_history_file;     /* Expansion variable, file to store dmarc results */
-extern uschar *dmarc_status;           /* Expansion variable, one word value */
-extern uschar *dmarc_status_text;      /* Expansion variable, human readable value */
-extern uschar *dmarc_tld_file;         /* Mozilla TLDs text file */
-extern uschar *dmarc_used_domain;      /* Expansion variable, domain libopendmarc chose for DMARC policy lookup */
-#endif
 
 extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */
 extern int     dns_csa_search_limit;   /* How deep to search for CSA SRV records */
index 4e0824911d96b798c250a4f6466b76309297c074..a06f9117d83a7aa630563a7534cd08b6ba185b47 100644 (file)
@@ -39,7 +39,7 @@ misc_module_info * mi = misc_mod_find(US"spf", errmsg);
 if (mi)
   {
   typedef void * (*fn_t)(const uschar *, uschar **);
-  return (((fn_t *) mi->functions)[5]) (filename, errmsg);
+  return (((fn_t *) mi->functions)[3]) (filename, errmsg);
   }
 return NULL;
 }
@@ -52,7 +52,7 @@ misc_module_info * mi = misc_mod_find(US"spf", NULL);
 if (mi)
   {
   typedef void (*fn_t)(void *);
-  return (((fn_t *) mi->functions)[6]) (handle);
+  return (((fn_t *) mi->functions)[4]) (handle);
   }
 }
 
@@ -67,7 +67,7 @@ if (mi)
   {
   typedef int (*fn_t) (void *, const uschar *, const uschar *,
                      int, uschar **, uschar **, uint *, const uschar *);
-  return (((fn_t *) mi->functions)[7]) (handle, filename, keystring, key_len,
+  return (((fn_t *) mi->functions)[5]) (handle, filename, keystring, key_len,
                                      result, errmsg, do_cache, opts);
   }
 return FAIL;
index 59bf29836ab8df815137aba3c1a5194630854f51..d20b7a9f10cd1d578554e6bd4bada0d42386adf3 100644 (file)
@@ -26,6 +26,7 @@ miscmods.a:   $(OBJ)
 .c.so:;         @echo "$(CC) -shared $*.c"
                $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@
 
-spf.o spf.so:  $(HDRS) spf.h spf.c
+spf.o spf.so:          $(HDRS) spf.h spf.c
+dmarc.o dmarc.so:      $(HDRS) ../pdkim/pdkim.h dmarc.h dmarc.c
 
 # End
diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c
new file mode 100644 (file)
index 0000000..0a97bf6
--- /dev/null
@@ -0,0 +1,791 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+/* DMARC support.
+   Copyright (c) The Exim Maintainers 2019 - 2024
+   Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014
+   License: GPL */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
+   All rights reserved, licensed for use per LICENSE.opendmarc. */
+
+/* Code for calling dmarc checks via libopendmarc. Called from acl.c. */
+
+#include "../exim.h"
+#ifdef SUPPORT_DMARC
+# if !defined SUPPORT_SPF
+#  error SPF must also be enabled for DMARC
+# elif defined DISABLE_DKIM
+#  error DKIM must also be enabled for DMARC
+# else
+
+#  include "../functions.h"
+#  include "dmarc.h"
+#  include "../pdkim/pdkim.h"
+
+OPENDMARC_LIB_T     dmarc_ctx;
+DMARC_POLICY_T     *dmarc_pctx = NULL;
+OPENDMARC_STATUS_T  libdm_status, action, dmarc_policy;
+OPENDMARC_STATUS_T  da, sa, action;
+BOOL dmarc_abort  = FALSE;
+uschar *dmarc_pass_fail = US"skipped";
+header_line *from_header   = NULL;
+
+misc_module_info * spf_mod_info;
+SPF_response_t   *spf_response_p;
+int dmarc_spf_ares_result  = 0;
+uschar *spf_sender_domain  = NULL;
+uschar *spf_human_readable = NULL;
+u_char *header_from_sender = NULL;
+int history_file_status    = DMARC_HIST_OK;
+
+typedef struct dmarc_exim_p {
+  uschar *name;
+  int    value;
+} dmarc_exim_p;
+
+static dmarc_exim_p dmarc_policy_description[] = {
+  /* name              value */
+  { US"",           DMARC_RECORD_P_UNSPECIFIED },
+  { US"none",       DMARC_RECORD_P_NONE },
+  { US"quarantine", DMARC_RECORD_P_QUARANTINE },
+  { US"reject",     DMARC_RECORD_P_REJECT },
+  { NULL,           0 }
+};
+
+
+/* $variables */
+uschar * dmarc_domain_policy     = NULL; /* Declared policy of used domain */
+uschar * dmarc_status            = NULL; /* One word value */
+uschar * dmarc_status_text       = NULL; /* Human readable value */
+uschar * dmarc_used_domain       = NULL; /* Domain libopendmarc chose for DMARC policy lookup */
+
+/* options */
+uschar * dmarc_forensic_sender   = NULL; /* Set sender address for forensic reports */
+uschar * dmarc_history_file      = NULL; /* File to store dmarc results */
+uschar * dmarc_tld_file          = NULL; /* Mozilla TLDs text file */
+
+
+/* One-time initialisation for dmarc.  Ensure the spf module is available. */
+
+static BOOL
+dmarc_init(void *)
+{
+uschar * errstr;
+if (!(spf_mod_info = misc_mod_find(US"spf", &errstr)))
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+           "dmarc: failed to find SPF module: %s", errstr);
+return TRUE;
+}
+
+static gstring *
+dmarc_version_report(gstring * g)
+{
+return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
+    (OPENDMARC_LIB_VERSION & 0xff000000) >> 24,
+    (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
+    (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8,
+    (OPENDMARC_LIB_VERSION & 0x000000ff));
+}
+
+
+/* Accept an error_block struct, initialize if empty, parse to the
+end, and append the two strings passed to it.  Used for adding
+variable amounts of value:pair data to the forensic emails. */
+
+static error_block *
+add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
+{
+error_block *eb = store_malloc(sizeof(error_block));
+if (!eblock)
+  eblock = eb;
+else
+  {
+  /* Find the end of the eblock struct and point it at eb */
+  error_block *tmp = eblock;
+  while(tmp->next)
+    tmp = tmp->next;
+  tmp->next = eb;
+  }
+eb->text1 = t1;
+eb->text2 = t2;
+eb->next  = NULL;
+return eblock;
+}
+
+/* dmarc_msg_init sets up a context that can be re-used for several
+messages on the same SMTP connection (that come from the
+same host with the same HELO string).
+However, we seem to only use it for one; we destroy some sort of context
+at the tail end of dmarc_process(). */
+
+static int
+dmarc_msg_init()
+{
+int *netmask   = NULL;   /* Ignored */
+int is_ipv6    = 0;
+
+/* Set some sane defaults.  Also clears previous results when
+multiple messages in one connection. */
+
+dmarc_pctx         = NULL;
+dmarc_status       = US"none";
+dmarc_abort        = FALSE;
+dmarc_pass_fail    = US"skipped";
+dmarc_used_domain  = US"";
+f.dmarc_has_been_checked = FALSE;
+header_from_sender = NULL;
+spf_response_p    = NULL;
+spf_sender_domain  = NULL;
+spf_human_readable = NULL;
+
+/* ACLs have "control=dmarc_disable_verify" */
+if (f.dmarc_disable_verify)
+  return OK;
+
+(void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
+dmarc_ctx.nscount = 0;
+libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
+if (libdm_status != DMARC_PARSE_OKAY)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
+                      opendmarc_policy_status_to_str(libdm_status));
+  dmarc_abort = TRUE;
+  }
+if (!dmarc_tld_file || !*dmarc_tld_file)
+  {
+  DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
+  dmarc_abort = TRUE;
+  }
+else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
+                      dmarc_tld_file, strerror(errno));
+  dmarc_abort = TRUE;
+  }
+if (!sender_host_address)
+  {
+  DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
+  dmarc_abort = TRUE;
+  }
+/* This catches locally originated email and startup errors above. */
+if (!dmarc_abort)
+  {
+  is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
+  if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "DMARC failure creating policy context: ip=%s", sender_host_address);
+    dmarc_abort = TRUE;
+    }
+  }
+
+return OK;
+}
+
+
+static void
+dmarc_smtp_reset(void)
+{
+dmarc_domain_policy = dmarc_status = dmarc_status_text =
+dmarc_used_domain = NULL;
+}
+
+
+/* dmarc_store_data stores the header data so that subsequent dmarc_process can
+access the data.
+Called after the entire message has been received, with the From: header. */
+
+static int
+dmarc_store_data(header_line * hdr)
+{
+/* No debug output because would change every test debug output */
+if (!f.dmarc_disable_verify)
+  from_header = hdr;
+return OK;
+}
+
+
+static void
+dmarc_send_forensic_report(u_char ** ruf)
+{
+uschar *recipient, *save_sender;
+BOOL  send_status = FALSE;
+error_block *eblock = NULL;
+FILE *message_file = NULL;
+
+/* Earlier ACL does not have *required* control=dmarc_enable_forensic */
+if (!f.dmarc_enable_forensic)
+  return;
+
+if (  dmarc_policy == DMARC_POLICY_REJECT     && action == DMARC_RESULT_REJECT
+   || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE
+   || dmarc_policy == DMARC_POLICY_NONE       && action == DMARC_RESULT_REJECT
+   || dmarc_policy == DMARC_POLICY_NONE       && action == DMARC_RESULT_QUARANTINE
+   )
+  if (ruf)
+    {
+    eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain);
+    eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
+    eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
+    eblock = add_to_eblock(eblock, US"SPF Alignment",
+                    sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no");
+    eblock = add_to_eblock(eblock, US"DKIM Alignment",
+                    da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no");
+    eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
+
+    for (int c = 0; ruf[c]; c++)
+      {
+      recipient = string_copylc(ruf[c]);
+      if (Ustrncmp(recipient, "mailto:",7))
+       continue;
+      /* Move to first character past the colon */
+      recipient += 7;
+      DEBUG(D_receive)
+       debug_printf_indent("DMARC forensic report to %s%s\n", recipient,
+            (host_checking || f.running_in_test_harness) ? " (not really)" : "");
+      if (host_checking || f.running_in_test_harness)
+       continue;
+
+      if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
+                           header_list, message_file, NULL))
+       log_write(0, LOG_MAIN|LOG_PANIC,
+         "failure to send DMARC forensic report to %s", recipient);
+      }
+    }
+}
+
+
+/* Look up a DNS dmarc record for the given domain.  Return it or NULL */
+
+static uschar *
+dmarc_dns_lookup(uschar * dom)
+{
+dns_answer * dnsa = store_get_dns_answer();
+dns_scan dnss;
+int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
+
+if (rc == DNS_SUCCEED)
+  for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+       rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+    if (rr->type == T_TXT && rr->size > 3)
+      {
+      uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
+      store_free_dns_answer(dnsa);
+      return record;
+      }
+store_free_dns_answer(dnsa);
+return NULL;
+}
+
+
+static int
+dmarc_write_history_file(const gstring * dkim_history_buffer)
+{
+int history_file_fd = 0;
+ssize_t written_len;
+int tmp_ans;
+u_char ** rua; /* aggregate report addressees */
+gstring * g;
+
+if (!dmarc_history_file)
+  {
+  DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
+  return DMARC_HIST_DISABLED;
+  }
+if (!host_checking)
+  {
+  uschar * s = string_copy(dmarc_history_file);                /* need a writeable copy */
+  if ((history_file_fd = log_open_as_exim(s)) < 0)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+             "failure to create DMARC history file: %s: %s",
+             s, strerror(errno));
+    return DMARC_HIST_FILE_ERR;
+    }
+  }
+
+/* Generate the contents of the history file entry */
+
+g = string_fmt_append(NULL,
+  "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
+  message_id, primary_hostname, time(NULL), sender_host_address,
+  header_from_sender, expand_string(US"$sender_address_domain"));
+
+if (spf_response_p)
+  g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
+
+if (dkim_history_buffer)
+  g = string_fmt_append(g, "%Y", dkim_history_buffer);
+
+g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
+  dmarc_used_domain, dmarc_policy);
+
+if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
+  for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
+    g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]);
+else
+  g = string_catn(g, US"rua -\n", 6);
+
+opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
+g = string_fmt_append(g, "pct %d\n", tmp_ans);
+
+opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
+g = string_fmt_append(g, "adkim %d\n", tmp_ans);
+
+opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
+g = string_fmt_append(g, "aspf %d\n", tmp_ans);
+
+opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
+g = string_fmt_append(g, "p %d\n", tmp_ans);
+
+opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
+g = string_fmt_append(g, "sp %d\n", tmp_ans);
+
+g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
+  da, sa, action);
+
+#if DMARC_API >= 100400
+# ifdef EXPERIMENTAL_ARC
+g = arc_dmarc_hist_append(g);
+# else
+g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
+                     ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
+# endif
+#endif
+
+/* Write the contents to the history file */
+DEBUG(D_receive)
+  {
+  debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
+            host_checking ? " (not really)" : "");
+  debug_printf_indent("DMARC history data for debugging:\n");
+  expand_level++;
+    debug_printf_indent("%Y", g);
+  expand_level--;
+  }
+
+if (!host_checking)
+  {
+  written_len = write_to_fd_buf(history_file_fd,
+                               g->s,
+                               gstring_length(g));
+  if (written_len == 0)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
+                          dmarc_history_file);
+    return DMARC_HIST_WRITE_ERR;
+    }
+  (void)close(history_file_fd);
+  }
+return DMARC_HIST_OK;
+}
+
+
+/* dmarc_process adds the envelope sender address to the existing
+context (if any), retrieves the result, sets up expansion
+strings and evaluates the condition outcome.
+Called for the first ACL dmarc= condition. */
+
+static int
+dmarc_process(void)
+{
+int sr, origin;             /* used in SPF section */
+int dmarc_spf_result  = 0;  /* stores spf into dmarc conn ctx */
+int tmp_ans, c;
+pdkim_signature * sig = dkim_signatures;
+uschar * rr;
+BOOL has_dmarc_record = TRUE;
+u_char ** ruf; /* forensic report addressees, if called for */
+
+/* ACLs have "control=dmarc_disable_verify" */
+if (f.dmarc_disable_verify)
+  return OK;
+
+/* Store the header From: sender domain for this part of DMARC.
+If there is no from_header struct, then it's likely this message
+is locally generated and relying on fixups to add it.  Just skip
+the entire DMARC system if we can't find a From: header....or if
+there was a previous error.  */
+
+if (!from_header)
+  {
+  DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
+  dmarc_abort = TRUE;
+  }
+else if (!dmarc_abort)
+  {
+  uschar * errormsg;
+  int dummy, domain;
+  uschar * p;
+  uschar saveend;
+
+  f.parse_allow_group = TRUE;
+  p = parse_find_address_end(from_header->text, FALSE);
+  saveend = *p; *p = '\0';
+  if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
+                             &dummy, &dummy, &domain, FALSE)))
+    header_from_sender += domain;
+  *p = saveend;
+
+  /* The opendmarc library extracts the domain from the email address, but
+  only try to store it if it's not empty.  Otherwise, skip out of DMARC. */
+
+  if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
+    dmarc_abort = TRUE;
+  libdm_status = dmarc_abort
+    ? DMARC_PARSE_OKAY
+    : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
+  if (libdm_status != DMARC_PARSE_OKAY)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+             "failure to store header From: in DMARC: %s, header was '%s'",
+             opendmarc_policy_status_to_str(libdm_status), from_header->text);
+    dmarc_abort = TRUE;
+    }
+  }
+
+/* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
+instead do this in the ACLs.  */
+
+if (!dmarc_abort && !sender_host_authenticated)
+  {
+  uschar * dmarc_domain;
+  gstring * dkim_history_buffer = NULL;
+
+  /* Use the envelope sender domain for this part of DMARC */
+
+  spf_sender_domain = expand_string(US"$sender_address_domain");
+
+    {
+    typedef SPF_response_t * (*fn_t)(void);
+    if (spf_mod_info)
+      spf_response_p = ((fn_t *) spf_mod_info->functions)[2]();        /* spf_get_response */
+    }
+
+  if (!spf_response_p)
+    {
+    /* No spf data means null envelope sender so generate a domain name
+    from the sender_helo_name  */
+
+    if (!spf_sender_domain)
+      {
+      spf_sender_domain = sender_helo_name;
+      log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
+                            spf_sender_domain);
+      DEBUG(D_receive)
+       debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
+         spf_sender_domain);
+      }
+    dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
+    dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
+    origin = DMARC_POLICY_SPF_ORIGIN_HELO;
+    spf_human_readable = US"";
+    }
+  else
+    {
+    sr = spf_response_p->result;
+    dmarc_spf_result = sr == SPF_RESULT_NEUTRAL  ? DMARC_POLICY_SPF_OUTCOME_NONE :
+                      sr == SPF_RESULT_PASS     ? DMARC_POLICY_SPF_OUTCOME_PASS :
+                      sr == SPF_RESULT_FAIL     ? DMARC_POLICY_SPF_OUTCOME_FAIL :
+                      sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
+                      DMARC_POLICY_SPF_OUTCOME_NONE;
+    dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL   ? ARES_RESULT_NEUTRAL :
+                           sr == SPF_RESULT_PASS      ? ARES_RESULT_PASS :
+                           sr == SPF_RESULT_FAIL      ? ARES_RESULT_FAIL :
+                           sr == SPF_RESULT_SOFTFAIL  ? ARES_RESULT_SOFTFAIL :
+                           sr == SPF_RESULT_NONE      ? ARES_RESULT_NONE :
+                           sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
+                           sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
+                           ARES_RESULT_UNKNOWN;
+    origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
+    spf_human_readable = US spf_response_p->header_comment;
+    DEBUG(D_receive)
+      debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
+    }
+  if (strcmp( CCS spf_sender_domain, "") == 0)
+    dmarc_abort = TRUE;
+  if (!dmarc_abort)
+    {
+    libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
+                               dmarc_spf_result, origin, spf_human_readable);
+    if (libdm_status != DMARC_PARSE_OKAY)
+      log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
+                          opendmarc_policy_status_to_str(libdm_status));
+    }
+
+  /* Now we cycle through the dkim signature results and put into
+  the opendmarc context, further building the DMARC reply. */
+
+  for(pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
+    {
+    int dkim_result, dkim_ares_result, vs, ves;
+
+    vs  = sig->verify_status & ~PDKIM_VERIFY_POLICY;
+    ves = sig->verify_ext_status;
+    dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
+                 vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
+                 vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
+                 DMARC_POLICY_DKIM_OUTCOME_NONE;
+    libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
+
+/* The opendmarc project broke its API in a way we can't detect easily.
+The EDITME provides a DMARC_API variable */
+#if DMARC_API >= 100400
+                                               sig->selector,
+#endif
+                                               dkim_result, US"");
+    DEBUG(D_receive)
+      debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
+    if (libdm_status != DMARC_PARSE_OKAY)
+      log_write(0, LOG_MAIN|LOG_PANIC,
+               "failure to store dkim (%s) for DMARC: %s",
+               sig->domain, opendmarc_policy_status_to_str(libdm_status));
+
+    dkim_ares_result =
+      vs == PDKIM_VERIFY_PASS    ? ARES_RESULT_PASS :
+      vs == PDKIM_VERIFY_FAIL    ? ARES_RESULT_FAIL :
+      vs == PDKIM_VERIFY_NONE    ? ARES_RESULT_NONE :
+      vs == PDKIM_VERIFY_INVALID ?
+       ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
+       ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE        ? ARES_RESULT_PERMERROR :
+       ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD   ? ARES_RESULT_PERMERROR :
+       ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT      ? ARES_RESULT_PERMERROR :
+       ARES_RESULT_UNKNOWN :
+      ARES_RESULT_UNKNOWN;
+#if DMARC_API >= 100400
+    dkim_history_buffer = string_fmt_append(dkim_history_buffer,
+      "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
+#else
+    dkim_history_buffer = string_fmt_append(dkim_history_buffer,
+      "dkim %s %d\n", sig->domain, dkim_ares_result);
+#endif
+    }
+
+  /* Look up DMARC policy record in DNS.  We do this explicitly, rather than
+  letting the dmarc library do it with opendmarc_policy_query_dmarc(), so that
+  our dns access path is used for debug tracing and for the testsuite
+  diversion. */
+
+  libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
+    ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
+    : DMARC_DNS_ERROR_NO_RECORD;
+  switch (libdm_status)
+    {
+    case DMARC_DNS_ERROR_NXDOMAIN:
+    case DMARC_DNS_ERROR_NO_RECORD:
+      DEBUG(D_receive)
+       debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
+      has_dmarc_record = FALSE;
+      break;
+    case DMARC_PARSE_OKAY:
+      DEBUG(D_receive)
+       debug_printf_indent("DMARC record found for %s\n", header_from_sender);
+      break;
+    case DMARC_PARSE_ERROR_BAD_VALUE:
+      DEBUG(D_receive)
+       debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
+      has_dmarc_record = FALSE;
+      break;
+    default:
+      /* everything else, skip dmarc */
+      DEBUG(D_receive)
+       debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
+                     opendmarc_policy_status_to_str(libdm_status),
+                     from_header->text);
+      has_dmarc_record = FALSE;
+      break;
+    }
+
+  /* Store the policy string in an expandable variable. */
+
+  libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
+  for (c = 0; dmarc_policy_description[c].name; c++)
+    if (tmp_ans == dmarc_policy_description[c].value)
+      {
+      dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
+      break;
+      }
+
+  /* Can't use exim's string manipulation functions so allocate memory
+  for libopendmarc using its max hostname length definition. */
+
+  dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
+  libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
+    dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
+  store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
+  dmarc_used_domain = dmarc_domain;
+
+  if (libdm_status != DMARC_PARSE_OKAY)
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "failure to read domainname used for DMARC lookup: %s",
+      opendmarc_policy_status_to_str(libdm_status));
+
+  dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
+  switch(libdm_status)
+    {
+    case DMARC_POLICY_ABSENT:     /* No DMARC record found */
+      dmarc_status = US"norecord";
+      dmarc_pass_fail = US"none";
+      dmarc_status_text = US"No DMARC record";
+      action = DMARC_RESULT_ACCEPT;
+      break;
+    case DMARC_FROM_DOMAIN_ABSENT:    /* No From: domain */
+      dmarc_status = US"nofrom";
+      dmarc_pass_fail = US"temperror";
+      dmarc_status_text = US"No From: domain found";
+      action = DMARC_RESULT_ACCEPT;
+      break;
+    case DMARC_POLICY_NONE:       /* Accept and report */
+      dmarc_status = US"none";
+      dmarc_pass_fail = US"none";
+      dmarc_status_text = US"None, Accept";
+      action = DMARC_RESULT_ACCEPT;
+      break;
+    case DMARC_POLICY_PASS:       /* Explicit accept */
+      dmarc_status = US"accept";
+      dmarc_pass_fail = US"pass";
+      dmarc_status_text = US"Accept";
+      action = DMARC_RESULT_ACCEPT;
+      break;
+    case DMARC_POLICY_REJECT:       /* Explicit reject */
+      dmarc_status = US"reject";
+      dmarc_pass_fail = US"fail";
+      dmarc_status_text = US"Reject";
+      action = DMARC_RESULT_REJECT;
+      break;
+    case DMARC_POLICY_QUARANTINE:       /* Explicit quarantine */
+      dmarc_status = US"quarantine";
+      dmarc_pass_fail = US"fail";
+      dmarc_status_text = US"Quarantine";
+      action = DMARC_RESULT_QUARANTINE;
+      break;
+    default:
+      dmarc_status = US"temperror";
+      dmarc_pass_fail = US"temperror";
+      dmarc_status_text = US"Internal Policy Error";
+      action = DMARC_RESULT_TEMPFAIL;
+      break;
+    }
+
+  libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
+  if (libdm_status != DMARC_PARSE_OKAY)
+    log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
+                            opendmarc_policy_status_to_str(libdm_status));
+
+  if (has_dmarc_record)
+    {
+    log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
+                          "spf_align=%s dkim_align=%s enforcement='%s'",
+                          spf_sender_domain, dmarc_used_domain,
+                          sa==DMARC_POLICY_SPF_ALIGNMENT_PASS  ?"yes":"no",
+                          da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
+                          dmarc_status_text);
+    history_file_status = dmarc_write_history_file(dkim_history_buffer);
+    /* Now get the forensic reporting addresses, if any */
+    ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
+    dmarc_send_forensic_report(ruf);
+    }
+  }
+
+/* shut down libopendmarc */
+if (dmarc_pctx)
+  (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
+if (!f.dmarc_disable_verify)
+  (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
+
+return OK;
+}
+
+static uschar *
+dmarc_exim_expand_defaults(int what)
+{
+if (what == DMARC_VERIFY_STATUS)
+  return f.dmarc_disable_verify ?  US"off" : US"none";
+return US"";
+}
+
+static uschar *
+dmarc_exim_expand_query(int what)
+{
+if (f.dmarc_disable_verify || !dmarc_pctx)
+  return dmarc_exim_expand_defaults(what);
+
+if (what == DMARC_VERIFY_STATUS)
+  return dmarc_status;
+return US"";
+}
+
+
+static gstring *
+authres_dmarc(gstring * g)
+{
+if (f.dmarc_has_been_checked)
+  {
+  int start = 0;               /* Compiler quietening */
+  DEBUG(D_acl) start = gstring_length(g);
+  g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
+  if (header_from_sender)
+    g = string_append(g, 2, US" header.from=", header_from_sender);
+  DEBUG(D_acl) debug_printf("DMARC:\tauthres '%.*s'\n",
+                 gstring_length(g) - start - 3, g->s + start + 3);
+  }
+else
+  DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
+return g;
+}
+
+/******************************************************************************/
+/* Module API */
+
+static optionlist dmarc_options[] = {
+  { "dmarc_forensic_sender",    opt_stringptr,      {&dmarc_forensic_sender} },
+  { "dmarc_history_file",       opt_stringptr,      {&dmarc_history_file} },
+  { "dmarc_tld_file",           opt_stringptr,      {&dmarc_tld_file} },
+};
+
+static void * dmarc_functions[] = {
+  dmarc_process,
+  dmarc_exim_expand_query,
+  authres_dmarc,
+  dmarc_store_data,
+};
+
+/* dmarc_forensic_sender is provided for visibility of the the option setting
+by moan_send_message. We do not document it as a config-visible $variable.
+We could provide it via a function but there's little advantage. */
+
+static var_entry dmarc_variables[] = {
+  { "dmarc_domain_policy", vtype_stringptr,   &dmarc_domain_policy },
+  { "dmarc_forensic_sender", vtype_stringptr, &dmarc_forensic_sender },
+  { "dmarc_status",        vtype_stringptr,   &dmarc_status },
+  { "dmarc_status_text",   vtype_stringptr,   &dmarc_status_text },
+  { "dmarc_used_domain",   vtype_stringptr,   &dmarc_used_domain },
+};
+
+misc_module_info dmarc_module_info =
+{
+  .name =              US"dmarc",
+# if SUPPORT_SPF==2
+  .dyn_magic =         MISC_MODULE_MAGIC,
+# endif
+  .init =              dmarc_init,
+  .lib_vers_report =   dmarc_version_report,
+  .smtp_reset =                dmarc_smtp_reset,
+  .msg_init =          dmarc_msg_init,
+
+  .options =           dmarc_options,
+  .options_count =     nelem(dmarc_options),
+
+  .functions =         dmarc_functions,
+  .functions_count =   nelem(dmarc_functions),
+
+  .variables =         dmarc_variables,
+  .variables_count =   nelem(dmarc_variables),
+};
+
+# endif /* SUPPORT_SPF */
+#endif /* SUPPORT_DMARC */
+/* vi: aw ai sw=2
+ */
diff --git a/src/src/miscmods/dmarc.h b/src/src/miscmods/dmarc.h
new file mode 100644 (file)
index 0000000..c1cafd0
--- /dev/null
@@ -0,0 +1,57 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Experimental DMARC support.
+   Copyright (c) The Exim Maintainers 2021 - 2023
+   Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014
+   License: GPL */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
+   All rights reserved, licensed for use per LICENSE.opendmarc. */
+
+#ifdef SUPPORT_DMARC
+
+# include <opendmarc/dmarc.h>
+# ifdef SUPPORT_SPF
+#  include <spf2/spf.h>
+# endif /* SUPPORT_SPF */
+
+#define DMARC_VERIFY_STATUS    1
+
+#define DMARC_HIST_OK          1
+#define DMARC_HIST_DISABLED    2
+#define DMARC_HIST_EMPTY       3
+#define DMARC_HIST_FILE_ERR    4
+#define DMARC_HIST_WRITE_ERR   5
+
+/* From opendmarc.c */
+#define DMARC_RESULT_REJECT     0
+#define DMARC_RESULT_DISCARD    1
+#define DMARC_RESULT_ACCEPT     2
+#define DMARC_RESULT_TEMPFAIL   3
+#define DMARC_RESULT_QUARANTINE 4
+
+/* From opendmarc-ar.h */
+/* ARES_RESULT_T -- type for specifying an authentication result */
+#define ARES_RESULT_UNDEFINED   (-1)
+#define ARES_RESULT_PASS    0
+#define ARES_RESULT_UNUSED  1
+#define ARES_RESULT_SOFTFAIL    2
+#define ARES_RESULT_NEUTRAL 3
+#define ARES_RESULT_TEMPERROR   4
+#define ARES_RESULT_PERMERROR   5
+#define ARES_RESULT_NONE    6
+#define ARES_RESULT_FAIL    7
+#define ARES_RESULT_POLICY  8
+#define ARES_RESULT_NXDOMAIN    9
+#define ARES_RESULT_SIGNED  10
+#define ARES_RESULT_UNKNOWN 11
+#define ARES_RESULT_DISCARD 12
+
+#define        DMARC_ARC_POLICY_RESULT_PASS    0
+#define        DMARC_ARC_POLICY_RESULT_UNUSED  1
+#define        DMARC_ARC_POLICY_RESULT_FAIL    2
+
+#endif /* SUPPORT_DMARC */
index a7b6c6a8dd693439502e8f72fbcc20250273e612..f28fd0cbfe33b3bc9531af3ccf170651aae8ea9e 100644 (file)
@@ -287,29 +287,30 @@ return TRUE;
    messages on the same SMTP connection (that come from the
    same host with the same HELO string).
 
-Return: Boolean success
+Return: OK/FAIL
 */
 
-static BOOL
-spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
+static int
+spf_conn_init(const uschar * spf_helo_domain, const uschar * spf_remote_addr)
 {
 DEBUG(D_receive)
   debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
 
-if (!spf_server && !spf_init(NULL)) return FALSE;
+if (!spf_server && !spf_init(NULL))
+  return FAIL;
 
 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
   {
   DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
     primary_hostname);
   spf_server = NULL;
-  return FALSE;
+  return FAIL;
   }
 
 spf_request = SPF_request_new(spf_server);
 
-if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
-   && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
+if (  SPF_request_set_ipv4_str(spf_request, CCS spf_remote_addr)
+   && SPF_request_set_ipv6_str(spf_request, CCS spf_remote_addr)
    )
   {
   DEBUG(D_receive)
@@ -317,19 +318,19 @@ if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
   spf_server = NULL;
   spf_request = NULL;
-  return FALSE;
+  return FAIL;
   }
 
-if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
+if (SPF_request_set_helo_dom(spf_request, CCS spf_helo_domain))
   {
   DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
     spf_helo_domain);
   spf_server = NULL;
   spf_request = NULL;
-  return FALSE;
+  return FAIL;
   }
 
-return TRUE;
+return OK;
 }
 
 static void
@@ -564,11 +565,9 @@ static optionlist spf_options[] = {
 };
 
 static void * spf_functions[] = {
-  spf_conn_init,
   spf_process,
   authres_spf,
   spf_get_response,            /* ugly; for dmarc */
-  spf_smtp_reset,
   
   spf_lookup_open,
   spf_lookup_close,
@@ -592,6 +591,8 @@ misc_module_info spf_module_info =
 # endif
   .init =              spf_init,
   .lib_vers_report =   spf_lib_version_report,
+  .conn_init =         spf_conn_init,
+  .smtp_reset =                spf_smtp_reset,
 
   .options =           spf_options,
   .options_count =     nelem(spf_options),
index 19d29190bff5222f2664e32136153b21a082db98..08258f5d1374d36f8f2ac665324438d2ad8e75da 100644 (file)
@@ -178,8 +178,7 @@ header From: and grab the address from that for the envelope FROM. */
 
 GET_OPTION("dmarc_forensic_sender");
 if (  ident == ERRMESS_DMARC_FORENSIC
-   && dmarc_forensic_sender
-   && (s = expand_string(dmarc_forensic_sender))
+   && (s = expand_string(US"$dmarc_forensic_sender"))  /* a hack... */
    && *s
    && (s2 = expand_string(string_sprintf("${address:%s}", s)))
    && *s2
index 1fe6b7341b95ccad538e29caa592327106d84d10..ae7073229a63a36b3d9605253f313f2f06d4917e 100644 (file)
@@ -129,9 +129,9 @@ static optionlist optionlist_config[] = {
   { "dkim_verify_signers",      opt_stringptr,   {&dkim_verify_signers} },
 #endif
 #ifdef SUPPORT_DMARC
-  { "dmarc_forensic_sender",    opt_stringptr,   {&dmarc_forensic_sender} },
-  { "dmarc_history_file",       opt_stringptr,   {&dmarc_history_file} },
-  { "dmarc_tld_file",           opt_stringptr,   {&dmarc_tld_file} },
+  { "dmarc_forensic_sender",    opt_module,     {US"dmarc"} },
+  { "dmarc_history_file",       opt_module,     {US"dmarc"} },
+  { "dmarc_tld_file",           opt_module,     {US"dmarc"} },
 #endif
   { "dns_again_means_nonexist", opt_stringptr,   {&dns_again_means_nonexist} },
   { "dns_check_names_pattern",  opt_stringptr,   {&check_dns_names_pattern} },
@@ -1707,7 +1707,7 @@ static BOOL
 readconf_handle_option(uschar *buffer, optionlist *oltop, int last,
   void *data_block, uschar *unknown_txt)
 {
-int ptr = 0;
+int ptr;
 int offset = 0;
 int count, type, value;
 int issecure = 0;
@@ -1721,11 +1721,16 @@ rmark reset_point;
 int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
-const uschar * s = buffer;
+const uschar * s;
 uschar **str_target;
 uschar name[EXIM_DRIVERNAME_MAX];
 uschar name2[EXIM_DRIVERNAME_MAX];
 
+sublist:
+
+s = buffer;
+ptr = 0;
+
 /* There may be leading spaces; thereafter, we expect an option name starting
 with a letter. */
 
@@ -1764,8 +1769,6 @@ if (Ustrncmp(name, "not_", 4) == 0)
   offset = 4;
   }
 
-sublist:
-
 /* Search the list for the given name. A non-existent name, or an option that
 is set twice, is a disaster. */
 
@@ -2454,7 +2457,6 @@ switch (type)
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
        "failed to find %s module for %s: %s", US ol->v.value, name, errstr);
 
-debug_printf("hunting for option %s in module %s\n", name, mi->name);
     oltop = mi->options;
     last = mi->options_count;
     goto sublist;
@@ -3516,6 +3518,9 @@ if (!*spool_directory)
 /* Expand the spool directory name; it may, for example, contain the primary
 host name. Same comment about failure. */
 
+DEBUG(D_any) if (Ustrchr(spool_directory, '$'))
+  debug_printf("Expanding spool_directory option\n");
+
 if (!(s = expand_string(spool_directory)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand spool_directory "
     "\"%s\": %s", spool_directory, expand_string_message);
index 336b374102cef35ff325056edfdc69821cac9769..37b152f48b6036bdda14a50852754be601189969 100644 (file)
@@ -17,7 +17,7 @@ extern int dcc_ok;
 #endif
 
 #ifdef SUPPORT_DMARC
-# include "dmarc.h"
+# include "miscmods/dmarc.h"
 #endif
 
 /*************************************************
@@ -1835,9 +1835,8 @@ if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
   dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
 #endif
 
-#ifdef SUPPORT_DMARC
-if (sender_host_address) dmarc_conn_init();    /* initialize libopendmarc */
-#endif
+if (misc_mod_msg_init() != OK)
+  goto TIDYUP;
 
 /* In SMTP sessions we may receive several messages in one connection. Before
 each subsequent one, we wait for the clock to tick at the level of message-id
@@ -3627,7 +3626,14 @@ else
 #endif /* WITH_CONTENT_SCAN */
 
 #ifdef SUPPORT_DMARC
-    dmarc_store_data(dmarc_from_header);
+    {
+    misc_module_info * mi = misc_mod_findonly(US"dmarc");
+    if (mi)
+      {
+      typedef int (*fn_t)(header_line *);
+      (((fn_t *) mi->functions)[3]) (dmarc_from_header);
+      }
+    }
 #endif
 
 #ifndef DISABLE_PRDR
index f9bd3ece81a96a65f63f24cfbcf1fe3680c3bb99..adf6c59cb966169b673ca09f01b8578fd2f1e939 100644 (file)
@@ -1681,16 +1681,6 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
-#ifdef SUPPORT_SPF
-  {
-  misc_module_info * mi = misc_mod_findonly(US"spf");
-  if (mi)
-    {
-    typedef void (*fn_t)(void);
-    (((fn_t *) mi->functions)[4])();   /* spf_smtp_reset*/
-    }
-  }
-#endif
 #ifndef DISABLE_DKIM
 dkim_cur_signer = dkim_signers =
 dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
@@ -1701,8 +1691,6 @@ dkim_key_length = 0;
 #endif
 #ifdef SUPPORT_DMARC
 f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE;
-dmarc_domain_policy = dmarc_status = dmarc_status_text =
-dmarc_used_domain = NULL;
 #endif
 #ifdef EXPERIMENTAL_ARC
 arc_state = arc_state_reason = NULL;
@@ -1742,6 +1730,7 @@ while (acl_warn_logged)
   store_free(this);
   }
 
+misc_mod_smtp_reset();
 message_tidyup();
 store_reset(reset_point);
 
@@ -4025,24 +4014,14 @@ while (done <= 0)
          }
        }
 
-#ifdef SUPPORT_SPF
-      /* If we have an spf module, set up SPF context */
-      {
-      misc_module_info * mi = misc_mod_findonly(US"spf");
-      if (mi)
+      /* For any misc-module having a connection-init routine, call it. */
+      
+      if (misc_mod_conn_init(sender_helo_name, sender_host_address) != OK)
        {
-       /* We have hardwired function-call numbers, and also prototypes for the
-       functions.  We could do a function name table search for the number
-       but I can't see how to deal with prototypes.  Is a K&R non-prototyped
-       function still usable with today's compilers? */
-
-       typedef BOOL (*fn_t)(uschar *, uschar *);
-       fn_t fn = ((fn_t *) mi->functions)[0];  /* spf_conn_init */
-
-       (void) fn(sender_helo_name, sender_host_address);
+       DEBUG(D_receive) debug_printf("A module conn-init routine failed\n");
+       done = 1;
+       break;
        }
-      }
-#endif
 
       /* Apply an ACL check if one is defined; afterwards, recheck
       synchronization in case the client started sending in a delay. */
index 2c8c77c430e0237ace2eed565f4be9e033fd56c4..46abac7289aee7957d14ace78bb1a44a23725a9c 100644 (file)
@@ -1023,6 +1023,9 @@ typedef struct misc_module_info {
   unsigned     dyn_magic;
   BOOL         (*init)(void *);        /* arg is the misc_module_info ptr */
   gstring *    (*lib_vers_report)(gstring *);  /* underlying library */
+  int          (*conn_init)(const uschar *, const uschar *);
+  void         (*smtp_reset)(void);
+  int          (*msg_init)(void);
 
   void *       options;
   unsigned     options_count;
index dcf6d76b23db726ead86f090e6452d638bc6fda9..ae227810cbb24c07af5bbbdf1cab6a3e6a4006c9 100755 (executable)
@@ -1561,8 +1561,8 @@ RESET_AFTER_EXTRA_LINE_READ:
     # Not all platforms build with SPF enabled
     next if /(^$time_pid?spf_conn_init|spf_compile\.c)/;
     next if /try option spf_smtp_comment_template$/;
-    next if /loading module 'spf'$/;
-    next if /^Loaded "spf"$/;
+    next if /loading module '(?:dmarc|spf)'$/;
+    next if /^$time_pid?Loaded "(?:dmarc|spf)"$/;
 
     # Not all platforms have sendfile support
     next if /^cannot use sendfile for body: no support$/;
index 29241cfd3e053b107e62596b14a849d79a348f33..514a770f81236a9ab5c4d0452c4382bda7e7d7ca 100644 (file)
@@ -1,5 +1,6 @@
 Exim version x.yz ....
 Hints DB:
+Expanding spool_directory option
  search_open: lsearch "TESTSUITE/aux-fixed/0437.ls"
  search_find: file="TESTSUITE/aux-fixed/0437.ls"
    key="spool" partial=-1 affix=NULL starflags=0 opts=NULL