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>>&.
 
 
 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 "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 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"
 
 .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 \
                        bmi_spam.o \
                        dane.o \
                        dcc.o \
-                       dmarc.o \
                        imap_utf7.o \
                        utf8.o \
                        xclient.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
 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
 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
 
 
 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.
 # 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
  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.
 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
 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
 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 \
   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
   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.
 
 # 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
 # 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
 # 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
   },
 #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
 
                                  PERMITTED(ACL_BIT_DATA) },
 #endif
 
@@ -393,6 +397,9 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
 
 #ifndef MACRO_PREDEF
 
 
 #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 */
 # 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_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
 
 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
 };
 
 # endif
@@ -3942,14 +3955,36 @@ for (; cb; cb = cb->next)
 
 #ifdef SUPPORT_DMARC
     case ACLC_DMARC_STATUS:
 
 #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)
       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. */
       /* 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);
                          &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
+      }
       break;
 #endif
 
       break;
 #endif
 
@@ -4185,8 +4220,10 @@ for (; cb; cb = cb->next)
 #ifdef SUPPORT_SPF
     case ACLC_SPF:
     case ACLC_SPF_GUESS:
 #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);
       {
       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 */
 
       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);
 
       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 "pdkim/signing.h"
 
 #  ifdef SUPPORT_DMARC
-#   include "dmarc.h"
+#   include "miscmods/dmarc.h"
 #  endif
 
 extern pdkim_ctx * dkim_verify_ctx;
 #  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 WITH_CONTENT_SCAN
 malware_init();
 #endif
-#ifdef SUPPORT_DMARC
-dmarc_init();
-#endif
 #ifndef DISABLE_TLS
 tls_daemon_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);
 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;
 
 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()))
   {
                                    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;
   }
   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;
 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. */
 }
 
 /* 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
 #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;
 {
 static BOOL onetime = FALSE;
 if (onetime) return;
+
 #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
 #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
 misc_mod_add(&spf_module_info);
 #endif
+#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
+misc_mod_add(&dmarc_module_info);
+#endif
+
 onetime = TRUE;
 }
 
 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_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;
 
   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 "dkim.h"
 #endif
 #ifdef SUPPORT_DMARC
-# include "dmarc.h"
+# include "miscmods/dmarc.h"
 # include <opendmarc/dmarc.h>
 #endif
 
 # 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
   { "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 },
 #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 *);
        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);
          }
        }
          yield = fn(yield);
          }
        }
@@ -4897,7 +4897,16 @@ while (*s)
       yield = authres_dkim(yield);
 #endif
 #ifdef SUPPORT_DMARC
       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);
 #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
 #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);
 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     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 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 *);
 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
 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;
 
 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
 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 */
 
 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 **);
 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;
 }
   }
 return NULL;
 }
@@ -52,7 +52,7 @@ misc_module_info * mi = misc_mod_find(US"spf", NULL);
 if (mi)
   {
   typedef void (*fn_t)(void *);
 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 *);
   {
   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;
                                      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 $@
 
 .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
 
 # 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).
 
    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);
 
 {
 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;
 
 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);
 
   }
 
 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)
    )
   {
   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;
       "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;
   {
   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
 }
 
 static void
@@ -564,11 +565,9 @@ static optionlist spf_options[] = {
 };
 
 static void * spf_functions[] = {
 };
 
 static void * spf_functions[] = {
-  spf_conn_init,
   spf_process,
   authres_spf,
   spf_get_response,            /* ugly; for dmarc */
   spf_process,
   authres_spf,
   spf_get_response,            /* ugly; for dmarc */
-  spf_smtp_reset,
   
   spf_lookup_open,
   spf_lookup_close,
   
   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,
 # 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),
 
   .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
 
 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
    && *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
   { "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} },
 #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)
 {
 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;
 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;
 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];
 
 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. */
 
 /* 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;
   }
 
   offset = 4;
   }
 
-sublist:
-
 /* Search the list for the given name. A non-existent name, or an option that
 is set twice, is a disaster. */
 
 /* 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);
 
       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;
     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. */
 
 /* 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);
 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
 #endif
 
 #ifdef SUPPORT_DMARC
-# include "dmarc.h"
+# include "miscmods/dmarc.h"
 #endif
 
 /*************************************************
 #endif
 
 /*************************************************
@@ -1835,9 +1835,8 @@ if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
   dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
 #endif
 
   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
 
 /* 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
 #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
 #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;
 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;
 #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;
 #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;
 #endif
 #ifdef EXPERIMENTAL_ARC
 arc_state = arc_state_reason = NULL;
@@ -1742,6 +1730,7 @@ while (acl_warn_logged)
   store_free(this);
   }
 
   store_free(this);
   }
 
+misc_mod_smtp_reset();
 message_tidyup();
 store_reset(reset_point);
 
 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. */
 
       /* 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 */
   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;
 
   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$/;
     # 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$/;
 
     # 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:
 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
  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