[exim-cvs] dmarc dynamic module

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] dmarc dynamic module
Gitweb: https://git.exim.org/exim.git/commitdiff/7dc8d146a675f52b441310e731314d86c66b2114
Commit:     7dc8d146a675f52b441310e731314d86c66b2114
Parent:     e93ae25c007845adff89736d7d292fe75b8a0c7a
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Wed Aug 28 22:01:24 2024 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Thu Aug 29 09:33:26 2024 +0100


    dmarc dynamic module
---
 doc/doc-docbook/spec.xfpt      |  14 ++++-
 src/OS/Makefile-Base           |   2 -
 src/scripts/Configure-Makefile |  12 +++-
 src/scripts/MakeLinks          |   4 +-
 src/src/EDITME                 |   7 +++
 src/src/acl.c                  |  51 +++++++++++++---
 src/src/arc.c                  |   2 +-
 src/src/daemon.c               |   3 -
 src/src/drtables.c             |  56 ++++++++++++++++--
 src/src/exim.c                 |   6 +-
 src/src/exim.h                 |   2 +-
 src/src/expand.c               |  21 +++++--
 src/src/functions.h            |   8 ++-
 src/src/globals.c              |   9 ---
 src/src/globals.h              |   9 ---
 src/src/lookups/spf.c          |   6 +-
 src/src/miscmods/Makefile      |   3 +-
 src/src/{ => miscmods}/dmarc.c | 128 ++++++++++++++++++++++++++++++++---------
 src/src/{ => miscmods}/dmarc.h |  13 +----
 src/src/miscmods/spf.c         |  27 ++++-----
 src/src/moan.c                 |   3 +-
 src/src/readconf.c             |  21 ++++---
 src/src/receive.c              |  16 ++++--
 src/src/smtp_in.c              |  35 +++--------
 src/src/structs.h              |   3 +
 test/runtest                   |   4 +-
 test/stderr/0437               |   1 +
 27 files changed, 312 insertions(+), 154 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 428cbc079..90fb6dd47 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -32055,12 +32055,20 @@ This control turns off DKIM verification processing entirely. For details on
the operation and configuration of DKIM, see section &<<SECDKIM>>&.


-.vitem &*control&~=&~dmarc_disable_verify*&
+.vitem &*control&~=&~enforce_sync*& &&&
+       &*control&~=&~no_enforce_sync*&
+
+.vitem &*control&~=&~dmarc_disable_verify*& &&&
+       &*control&~=&~dmarc_enable_forensic*&
 .cindex "disable DMARC verify"
-.cindex "DMARC" "disable verify"
-This control turns off DMARC verification processing entirely.  For details on
+.cindex DMARC "disable verify"
+.cindex DMARC controls
+.cindex DMARC "forensic mails"
+These control affect DMARC processing.  For details on
 the operation and configuration of DMARC, see section &<<SECDMARC>>&.


+The &"disable"& turns off DMARC verification processing entirely.
+

 .vitem &*control&~=&~dscp/*&<&'value'&>
 .cindex "&ACL;" "setting DSCP value"
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index caae1e536..43cec361c 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -499,7 +499,6 @@ OBJ_EXPERIMENTAL =    arc.o \
             bmi_spam.o \
             dane.o \
             dcc.o \
-            dmarc.o \
             imap_utf7.o \
             utf8.o \
             xclient.o
@@ -901,7 +900,6 @@ arc.o:        $(HDRS) pdkim/pdkim.h arc.c
 bmi_spam.o:    $(HDRS) bmi_spam.c
 dane.o:        $(HDRS) dane.c dane-openssl.c
 dcc.o:        $(HDRS) dcc.h dcc.c
-dmarc.o:    $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c
 imap_utf7.o:    $(HDRS) imap_utf7.c
 utf8.o:        $(HDRS) utf8.c
 xclient.o:    $(HDRS) xclient.c
diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile
index af0de26e4..2b8a9bcb5 100755
--- a/src/scripts/Configure-Makefile
+++ b/src/scripts/Configure-Makefile
@@ -29,6 +29,16 @@ fi


archtype=`../scripts/arch-type` || exit 1

+# Linux now whines about egrep, saying "use grep -E".
+# Solarix doesn't support -E on grep.  Thanks so much for
+# going non-back-compatible, Linux.
+if echo 1 | grep -E 1 >/dev/null; then
+  egrep="grep -E"
+else
+  egrep="egrep"
+fi
+
+
 # Now test for either the non-existence of Makefile, or for any of its
 # components being newer. Note that the "newer" script gives the right
 # answer (for our purposes) when the first file is non-existent.
@@ -311,7 +321,7 @@ done <<-END
  routers    ROUTER    ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT
  transports TRANSPORT    APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP
  auths        AUTH    CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS
- miscmods   SUPPORT    SPF
+ miscmods   SUPPORT    SPF DMARC
 END


# See if there is a definition of EXIM_PERL in what we have built so far.
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index 09d18b63c..e45097243 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -94,7 +94,7 @@ d="miscmods"
mkdir $d
cd $d
# Makefile is generated
-for f in spf.c spf.h
+for f in dmarc.c dmarc.h spf.c spf.h
do
ln -s ../../src/$d/$f $f
done
@@ -140,7 +140,7 @@ for f in blob.h dbfunctions.h exim.h functions.h globals.h \
string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-cipher-stdname.c \
tls-gnu.c tls-openssl.c \
tod.c transport.c tree.c verify.c version.c xtextencode.c \
- dkim.c dkim.h dkim_transport.c dmarc.c dmarc.h \
+ dkim.c dkim.h dkim_transport.c \
valgrind.h memcheck.h \
macro_predef.c macro_predef.h
do
diff --git a/src/src/EDITME b/src/src/EDITME
index e507ab3cd..35c497697 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -642,9 +642,16 @@ DISABLE_MAL_MKS=yes

 # Uncomment the following line to add DMARC checking capability, implemented
 # using libopendmarc libraries. You must have SPF and DKIM support enabled also.
+#
+# If set to "2" instead of "yes" then the support will be
+# built as a module and must be installed into LOOKUP_MODULE_DIR (the name
+# is historic).  The same rules as for other module builds apply; use
+# SUPPORT_DMARC_{INCLUDE,LIBS}.
+#
 # SUPPORT_DMARC=yes
 # CFLAGS += -I/usr/local/include
 # LDFLAGS += -lopendmarc
+#
 # Uncomment the following if you need to change the default. You can
 # override it at runtime (main config option dmarc_tld_file)
 # DMARC_TLD_FILE=/etc/exim/opendmarc.tlds
diff --git a/src/src/acl.c b/src/src/acl.c
index e285da65c..30fc09174 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -218,7 +218,11 @@ static condition_def conditions[] = {
   },
 #endif
 #ifdef SUPPORT_DMARC
-  [ACLC_DMARC_STATUS] =        { US"dmarc_status",    ACD_EXP,
+  [ACLC_DMARC_STATUS] =        { US"dmarc_status",
+# if SUPPORT_DMARC==2
+                  ACD_LOAD |
+# endif
+                  ACD_EXP,
                   PERMITTED(ACL_BIT_DATA) },
 #endif


@@ -393,6 +397,9 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)

#ifndef MACRO_PREDEF

+/* These tables support loading of dynamic modules triggered by an ACL
+condition use, spotted during readconf. See acl_read(). */
+
 # ifdef LOOKUP_MODULE_DIR
 typedef struct condition_module {
   const uschar *    mod_name;    /* module for the givien conditions */
@@ -403,11 +410,17 @@ typedef struct condition_module {
 #  if SUPPORT_SPF==2
 static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 };
 #  endif
+#  if SUPPORT_DMARC==2
+static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 };
+#  endif


static condition_module condition_modules[] = {
# if SUPPORT_SPF==2
{.mod_name = US"spf", .conditions = spf_condx},
# endif
+# if SUPPORT_SPF==2
+ {.mod_name = US"dmarc", .conditions = dmarc_condx},
+# endif
};

# endif
@@ -3942,14 +3955,36 @@ for (; cb; cb = cb->next)

 #ifdef SUPPORT_DMARC
     case ACLC_DMARC_STATUS:
+      /* See comment on ACLC_SPF wrt. coding issues */
+      {
+      misc_module_info * mi = misc_mod_find(US"dmarc", &log_message);
+      typedef uschar * (*efn_t)(int);
+      uschar * expanded_query;
+
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
+      if (!mi)
+    { rc = DEFER; break; }            /* shouldn't happen */
+
+debug_printf("%s %d: mi %p\n", __FUNCTION__, __LINE__, mi);
       if (!f.dmarc_has_been_checked)
-    dmarc_process();
-      f.dmarc_has_been_checked = TRUE;
+    {
+    typedef int (*pfn_t)(void);
+    (void) (((pfn_t *) mi->functions)[0]) ();    /* dmarc_process */
+    f.dmarc_has_been_checked = TRUE;
+    }


+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
       /* used long way of dmarc_exim_expand_query() in case we need more
       view into the process in the future. */
-      rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
+
+      /*XXX is this call used with any other arg? */
+      expanded_query = (((efn_t *) mi->functions)[1]) (DMARC_VERIFY_STATUS);
+
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
+      rc = match_isinlist(expanded_query,
               &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+debug_printf("%s %d\n", __FUNCTION__, __LINE__);
+      }
       break;
 #endif


@@ -4185,8 +4220,10 @@ for (; cb; cb = cb->next)
 #ifdef SUPPORT_SPF
     case ACLC_SPF:
     case ACLC_SPF_GUESS:
-      /* Hardwire the offset of the function in the module functions table
-      for now.  Work out a more general mech later. */
+      /* We have hardwired function-call numbers, and also prototypes for the
+      functions.  We could do a function name table search for the number
+      but I can't see how to deal with prototypes.  Is a K&R non-prototyped
+      function still usable with today's compilers? */
       {
       misc_module_info * mi = misc_mod_find(US"spf", &log_message);
       typedef int (*fn_t)(const uschar **, const uschar *, int);
@@ -4195,7 +4232,7 @@ for (; cb; cb = cb->next)
       if (!mi)
     { rc = DEFER; break; }            /* shouldn't happen */


-      fn = ((fn_t *) mi->functions)[1];
+      fn = ((fn_t *) mi->functions)[0];        /* spf_process() */


       rc = fn(&arg, sender_address,
           cb->type == ACLC_SPF ? SPF_PROCESS_NORMAL : SPF_PROCESS_GUESS);
diff --git a/src/src/arc.c b/src/src/arc.c
index 48f69a8cf..2dcbf2efb 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -19,7 +19,7 @@
 #  include "pdkim/signing.h"


# ifdef SUPPORT_DMARC
-# include "dmarc.h"
+# include "miscmods/dmarc.h"
# endif

 extern pdkim_ctx * dkim_verify_ctx;
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 49bf74a11..456c586da 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -2578,9 +2578,6 @@ smtp_deliver_init();    /* Used for callouts */
 #ifdef WITH_CONTENT_SCAN
 malware_init();
 #endif
-#ifdef SUPPORT_DMARC
-dmarc_init();
-#endif
 #ifndef DISABLE_TLS
 tls_daemon_init();
 #endif
diff --git a/src/src/drtables.c b/src/src/drtables.c
index 35a376dd1..9ed55e29a 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -432,8 +432,8 @@ static void
 misc_mod_add(misc_module_info * mi)
 {
 if (mi->init) mi->init(mi);
-DEBUG(D_lookup) if (mi->lib_vers_report)
-  debug_printf_indent("%Y\n", mi->lib_vers_report(NULL));
+DEBUG(D_any) if (mi->lib_vers_report)
+  debug_printf_indent("%Y", mi->lib_vers_report(NULL));


 mi->next = misc_module_list;
 misc_module_list = mi;
@@ -459,8 +459,10 @@ mi = (struct misc_module_info *) dlsym(dl,
                     CS string_sprintf("%s_module_info", name));
 if ((errormsg = dlerror()))
   {
-  fprintf(stderr, "%s does not appear to be an spf module (%s)\n", name, errormsg);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be an spf module (%s)", name, errormsg);
+  fprintf(stderr, "%s does not appear to be a '%s' module (%s)\n",
+      name, name, errormsg);
+  log_write(0, LOG_MAIN|LOG_PANIC,
+    "%s does not contain the expected module info symbol (%s)", name, errormsg);
   dlclose(dl);
   return NULL;
   }
@@ -491,6 +493,7 @@ misc_mod_findonly(const uschar * name)
 for (misc_module_info * mi = misc_module_list; mi; mi = mi->next)
   if (Ustrcmp(name, mi->name) == 0)
     return mi;
+return NULL;
 }


/* Find a "misc" module, possibly already loaded, by name. */
@@ -508,6 +511,42 @@ return NULL;
}


+/* For any "misc" module having a connection-init routine, call it. */
+
+int
+misc_mod_conn_init(const uschar * sender_helo_name,
+  const uschar * sender_host_address)
+{
+for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+  if (mi->conn_init)
+    if ((mi->conn_init) (sender_helo_name, sender_host_address) != OK)
+      return FAIL;
+return OK;
+}
+
+/* Ditto, smtp-reset */
+
+void
+misc_mod_smtp_reset(void)
+{
+for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+  if (mi->smtp_reset)
+    (mi->smtp_reset)();
+}
+
+/* Ditto, msg-init */
+
+int
+misc_mod_msg_init(void)
+{
+for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+  if (mi->msg_init)
+    if ((mi->msg_init)() != OK)
+      return FAIL;
+return OK;
+}
+
+




@@ -658,6 +697,9 @@ DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
}


+#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
+extern misc_module_info dmarc_module_info;
+#endif
#if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
extern misc_module_info spf_module_info;
#endif
@@ -667,9 +709,15 @@ init_misc_mod_list(void)
{
static BOOL onetime = FALSE;
if (onetime) return;
+
#if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
+/* dmarc depends on spf so this add must go first, for the dmarc-static case */
misc_mod_add(&spf_module_info);
#endif
+#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
+misc_mod_add(&dmarc_module_info);
+#endif
+
onetime = TRUE;
}

diff --git a/src/src/exim.c b/src/src/exim.c
index ecc25d6bc..5ad54ffc1 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -1395,9 +1395,9 @@ DEBUG(D_any)
#ifdef SUPPORT_I18N
g = utf8_version_report(g);
#endif
-#ifdef SUPPORT_DMARC
- g = dmarc_version_report(g);
-#endif
+
+/*XXX do we need a "show misc-mods version-report" ?
+Currently they are output in misc_mod_add() */

show_string(is_stdout, g);
g = NULL;
diff --git a/src/src/exim.h b/src/src/exim.h
index 470adb351..284748c5d 100644
--- a/src/src/exim.h
+++ b/src/src/exim.h
@@ -549,7 +549,7 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
# include "dkim.h"
#endif
#ifdef SUPPORT_DMARC
-# include "dmarc.h"
+# include "miscmods/dmarc.h"
# include <opendmarc/dmarc.h>
#endif

diff --git a/src/src/expand.c b/src/src/expand.c
index b3a1575a7..a6b05bd87 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -513,10 +513,10 @@ static var_entry var_table[] = {
   { "dkim_verify_status",  vtype_stringptr,   &dkim_verify_status },
 #endif
 #ifdef SUPPORT_DMARC
-  { "dmarc_domain_policy", vtype_stringptr,   &dmarc_domain_policy },
-  { "dmarc_status",        vtype_stringptr,   &dmarc_status },
-  { "dmarc_status_text",   vtype_stringptr,   &dmarc_status_text },
-  { "dmarc_used_domain",   vtype_stringptr,   &dmarc_used_domain },
+  { "dmarc_domain_policy", vtype_module,    US"dmarc" },
+  { "dmarc_status",        vtype_module,    US"dmarc" },
+  { "dmarc_status_text",   vtype_module,    US"dmarc" },
+  { "dmarc_used_domain",   vtype_module,    US"dmarc" },
 #endif
   { "dnslist_domain",      vtype_stringptr,   &dnslist_domain },
   { "dnslist_matched",     vtype_stringptr,   &dnslist_matched },
@@ -4888,7 +4888,7 @@ while (*s)
     if (mi)
       {
       typedef gstring * (*fn_t)(gstring *);
-      fn_t fn = ((fn_t *) mi->functions)[2];    /* authres_spf */
+      fn_t fn = ((fn_t *) mi->functions)[1];    /* authres_spf */
       yield = fn(yield);
       }
     }
@@ -4897,7 +4897,16 @@ while (*s)
       yield = authres_dkim(yield);
 #endif
 #ifdef SUPPORT_DMARC
-      yield = authres_dmarc(yield);
+    {
+    misc_module_info * mi = misc_mod_findonly(US"dmarc");
+    if (mi)
+      {
+      /*XXX is authres common enough to be generic? */
+      typedef gstring * (*fn_t)(gstring *);
+      fn_t fn = ((fn_t *) mi->functions)[2];    /* authres_dmarc*/
+      yield = fn(yield);
+      }
+    }
 #endif
 #ifdef EXPERIMENTAL_ARC
       yield = authres_arc(yield);
diff --git a/src/src/functions.h b/src/src/functions.h
index aaec6461f..3a980318f 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -147,9 +147,6 @@ extern gstring *authres_arc(gstring *);
 #ifndef DISABLE_DKIM
 extern gstring *authres_dkim(gstring *);
 #endif
-#ifdef SUPPORT_DMARC
-extern gstring *authres_dmarc(gstring *);
-#endif
 extern gstring *authres_smtpauth(gstring *);


 extern uschar *b64encode(const uschar *, int);
@@ -377,8 +374,13 @@ extern ssize_t mime_decode_base64(FILE *, FILE *, uschar *);
 extern int     mime_regex(const uschar **, BOOL);
 extern void    mime_set_anomaly(int);
 #endif
+
+extern int     misc_mod_conn_init(const uschar *, const uschar *);
 extern misc_module_info * misc_mod_find(const uschar * modname, uschar **);
 extern misc_module_info * misc_mod_findonly(const uschar * modname);
+extern int     misc_mod_msg_init(void);
+extern void    misc_mod_smtp_reset(void);
+
 extern uschar *moan_check_errorcopy(const uschar *);
 extern BOOL    moan_skipped_syntax_errors(uschar *, error_block *, uschar *,
                  BOOL, uschar *);
diff --git a/src/src/globals.c b/src/src/globals.c
index f2287d41c..cfa75f1d7 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -882,15 +882,6 @@ uschar *dkim_verify_signers      = US"$dkim_signers";
 uschar *dkim_verify_status     = NULL;
 uschar *dkim_verify_reason     = NULL;
 #endif
-#ifdef SUPPORT_DMARC
-uschar *dmarc_domain_policy     = NULL;
-uschar *dmarc_forensic_sender   = NULL;
-uschar *dmarc_history_file      = NULL;
-uschar *dmarc_status            = NULL;
-uschar *dmarc_status_text       = NULL;
-uschar *dmarc_tld_file          = NULL;
-uschar *dmarc_used_domain       = NULL;
-#endif


 uschar *dns_again_means_nonexist = NULL;
 int     dns_csa_search_limit   = 5;
diff --git a/src/src/globals.h b/src/src/globals.h
index 9b30e502c..8173d771e 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -563,15 +563,6 @@ extern uschar *dkim_verify_signers;    /* Colon-separated list of domains for ea
 extern uschar *dkim_verify_status;     /* result for this signature */
 extern uschar *dkim_verify_reason;     /* result for this signature */
 #endif
-#ifdef SUPPORT_DMARC
-extern uschar *dmarc_domain_policy;    /* Expansion for declared policy of used domain */
-extern uschar *dmarc_forensic_sender;  /* Set sender address for forensic reports */
-extern uschar *dmarc_history_file;     /* Expansion variable, file to store dmarc results */
-extern uschar *dmarc_status;           /* Expansion variable, one word value */
-extern uschar *dmarc_status_text;      /* Expansion variable, human readable value */
-extern uschar *dmarc_tld_file;         /* Mozilla TLDs text file */
-extern uschar *dmarc_used_domain;      /* Expansion variable, domain libopendmarc chose for DMARC policy lookup */
-#endif


 extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */
 extern int     dns_csa_search_limit;   /* How deep to search for CSA SRV records */
diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c
index 4e0824911..a06f9117d 100644
--- a/src/src/lookups/spf.c
+++ b/src/src/lookups/spf.c
@@ -39,7 +39,7 @@ misc_module_info * mi = misc_mod_find(US"spf", errmsg);
 if (mi)
   {
   typedef void * (*fn_t)(const uschar *, uschar **);
-  return (((fn_t *) mi->functions)[5]) (filename, errmsg);
+  return (((fn_t *) mi->functions)[3]) (filename, errmsg);
   }
 return NULL;
 }
@@ -52,7 +52,7 @@ misc_module_info * mi = misc_mod_find(US"spf", NULL);
 if (mi)
   {
   typedef void (*fn_t)(void *);
-  return (((fn_t *) mi->functions)[6]) (handle);
+  return (((fn_t *) mi->functions)[4]) (handle);
   }
 }


@@ -67,7 +67,7 @@ if (mi)
   {
   typedef int (*fn_t) (void *, const uschar *, const uschar *,
               int, uschar **, uschar **, uint *, const uschar *);
-  return (((fn_t *) mi->functions)[7]) (handle, filename, keystring, key_len,
+  return (((fn_t *) mi->functions)[5]) (handle, filename, keystring, key_len,
                       result, errmsg, do_cache, opts);
   }
 return FAIL;
diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile
index 59bf29836..d20b7a9f1 100644
--- a/src/src/miscmods/Makefile
+++ b/src/src/miscmods/Makefile
@@ -26,6 +26,7 @@ miscmods.a:    $(OBJ)
 .c.so:;         @echo "$(CC) -shared $*.c"
         $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@


-spf.o spf.so:    $(HDRS) spf.h spf.c
+spf.o spf.so:        $(HDRS) spf.h spf.c
+dmarc.o dmarc.so:    $(HDRS) ../pdkim/pdkim.h dmarc.h dmarc.c


# End
diff --git a/src/src/dmarc.c b/src/src/miscmods/dmarc.c
similarity index 87%
rename from src/src/dmarc.c
rename to src/src/miscmods/dmarc.c
index 664daa737..0a97bf6b7 100644
--- a/src/src/dmarc.c
+++ b/src/src/miscmods/dmarc.c
@@ -12,7 +12,7 @@

/* Code for calling dmarc checks via libopendmarc. Called from acl.c. */

-#include "exim.h"
+#include "../exim.h"
#ifdef SUPPORT_DMARC
# if !defined SUPPORT_SPF
# error SPF must also be enabled for DMARC
@@ -20,9 +20,9 @@
# error DKIM must also be enabled for DMARC
# else

-# include "functions.h"
+# include "../functions.h"
# include "dmarc.h"
-# include "pdkim/pdkim.h"
+# include "../pdkim/pdkim.h"

 OPENDMARC_LIB_T     dmarc_ctx;
 DMARC_POLICY_T     *dmarc_pctx = NULL;
@@ -55,8 +55,22 @@ static dmarc_exim_p dmarc_policy_description[] = {
 };



-int
-dmarc_init(void)
+/* $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)))
@@ -65,12 +79,14 @@ if (!(spf_mod_info = misc_mod_find(US"spf", &errstr)))
 return TRUE;
 }


-gstring *
+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);
+    (OPENDMARC_LIB_VERSION & 0xff000000) >> 24,
+    (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
+    (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8,
+    (OPENDMARC_LIB_VERSION & 0x000000ff));
 }



@@ -98,12 +114,14 @@ eb->next = NULL;
return eblock;
}

-/* dmarc_conn_init sets up a context that can be re-used for several
+/* 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) */
+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(). */

-int
-dmarc_conn_init(void)
+static int
+dmarc_msg_init()
 {
 int *netmask   = NULL;   /* Ignored */
 int is_ipv6    = 0;
@@ -167,11 +185,19 @@ 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. */

-int
+static int
dmarc_store_data(header_line * hdr)
{
/* No debug output because would change every test debug output */
@@ -362,7 +388,7 @@ context (if any), retrieves the result, sets up expansion
strings and evaluates the condition outcome.
Called for the first ACL dmarc= condition. */

-int
+static int
 dmarc_process(void)
 {
 int sr, origin;             /* used in SPF section */
@@ -433,10 +459,9 @@ if (!dmarc_abort && !sender_host_authenticated)
   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_mod_info)
+      spf_response_p = ((fn_t *) spf_mod_info->functions)[2]();    /* spf_get_response */
     }


if (!spf_response_p)
@@ -673,27 +698,27 @@ if (!f.dmarc_disable_verify)
return OK;
}

-uschar *
-dmarc_exim_expand_query(int what)
+static uschar *
+dmarc_exim_expand_defaults(int what)
{
-if (f.dmarc_disable_verify || !dmarc_pctx)
- return dmarc_exim_expand_defaults(what);
-
if (what == DMARC_VERIFY_STATUS)
- return dmarc_status;
+ return f.dmarc_disable_verify ? US"off" : US"none";
return US"";
}

-uschar *
-dmarc_exim_expand_defaults(int what)
+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 f.dmarc_disable_verify ? US"off" : US"none";
+ return dmarc_status;
return US"";
}


-gstring *
+static gstring *
authres_dmarc(gstring * g)
{
if (f.dmarc_has_been_checked)
@@ -711,6 +736,55 @@ else
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/dmarc.h b/src/src/miscmods/dmarc.h
similarity index 84%
rename from src/src/dmarc.h
rename to src/src/miscmods/dmarc.h
index dcf289f2d..c1cafd0d1 100644
--- a/src/src/dmarc.h
+++ b/src/src/miscmods/dmarc.h
@@ -13,20 +13,11 @@


#ifdef SUPPORT_DMARC

-# include "opendmarc/dmarc.h"
+# include <opendmarc/dmarc.h>
# ifdef SUPPORT_SPF
-# include "spf2/spf.h"
+# 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
diff --git a/src/src/miscmods/spf.c b/src/src/miscmods/spf.c
index a7b6c6a8d..f28fd0cbf 100644
--- a/src/src/miscmods/spf.c
+++ b/src/src/miscmods/spf.c
@@ -287,29 +287,30 @@ return TRUE;
    messages on the same SMTP connection (that come from the
    same host with the same HELO string).


-Return: Boolean success
+Return: OK/FAIL
*/

-static BOOL
-spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
+static int
+spf_conn_init(const uschar * spf_helo_domain, const uschar * spf_remote_addr)
{
DEBUG(D_receive)
debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);

-if (!spf_server && !spf_init(NULL)) return FALSE;
+if (!spf_server && !spf_init(NULL))
+ return FAIL;

 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
   {
   DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
     primary_hostname);
   spf_server = NULL;
-  return FALSE;
+  return FAIL;
   }


spf_request = SPF_request_new(spf_server);

-if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
-   && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
+if (  SPF_request_set_ipv4_str(spf_request, CCS spf_remote_addr)
+   && SPF_request_set_ipv6_str(spf_request, CCS spf_remote_addr)
    )
   {
   DEBUG(D_receive)
@@ -317,19 +318,19 @@ if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
   spf_server = NULL;
   spf_request = NULL;
-  return FALSE;
+  return FAIL;
   }


-if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
+if (SPF_request_set_helo_dom(spf_request, CCS spf_helo_domain))
   {
   DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
     spf_helo_domain);
   spf_server = NULL;
   spf_request = NULL;
-  return FALSE;
+  return FAIL;
   }


-return TRUE;
+return OK;
}

static void
@@ -564,11 +565,9 @@ static optionlist spf_options[] = {
};

 static void * spf_functions[] = {
-  spf_conn_init,
   spf_process,
   authres_spf,
   spf_get_response,        /* ugly; for dmarc */
-  spf_smtp_reset,


   spf_lookup_open,
   spf_lookup_close,
@@ -592,6 +591,8 @@ misc_module_info spf_module_info =
 # endif
   .init =        spf_init,
   .lib_vers_report =    spf_lib_version_report,
+  .conn_init =        spf_conn_init,
+  .smtp_reset =        spf_smtp_reset,


   .options =        spf_options,
   .options_count =    nelem(spf_options),
diff --git a/src/src/moan.c b/src/src/moan.c
index 19d29190b..08258f5d1 100644
--- a/src/src/moan.c
+++ b/src/src/moan.c
@@ -178,8 +178,7 @@ header From: and grab the address from that for the envelope FROM. */


 GET_OPTION("dmarc_forensic_sender");
 if (  ident == ERRMESS_DMARC_FORENSIC
-   && dmarc_forensic_sender
-   && (s = expand_string(dmarc_forensic_sender))
+   && (s = expand_string(US"$dmarc_forensic_sender"))    /* a hack... */
    && *s
    && (s2 = expand_string(string_sprintf("${address:%s}", s)))
    && *s2
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 1fe6b7341..ae7073229 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -129,9 +129,9 @@ static optionlist optionlist_config[] = {
   { "dkim_verify_signers",      opt_stringptr,   {&dkim_verify_signers} },
 #endif
 #ifdef SUPPORT_DMARC
-  { "dmarc_forensic_sender",    opt_stringptr,   {&dmarc_forensic_sender} },
-  { "dmarc_history_file",       opt_stringptr,   {&dmarc_history_file} },
-  { "dmarc_tld_file",           opt_stringptr,   {&dmarc_tld_file} },
+  { "dmarc_forensic_sender",    opt_module,     {US"dmarc"} },
+  { "dmarc_history_file",       opt_module,     {US"dmarc"} },
+  { "dmarc_tld_file",           opt_module,     {US"dmarc"} },
 #endif
   { "dns_again_means_nonexist", opt_stringptr,   {&dns_again_means_nonexist} },
   { "dns_check_names_pattern",  opt_stringptr,   {&check_dns_names_pattern} },
@@ -1707,7 +1707,7 @@ static BOOL
 readconf_handle_option(uschar *buffer, optionlist *oltop, int last,
   void *data_block, uschar *unknown_txt)
 {
-int ptr = 0;
+int ptr;
 int offset = 0;
 int count, type, value;
 int issecure = 0;
@@ -1721,11 +1721,16 @@ rmark reset_point;
 int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
-const uschar * s = buffer;
+const uschar * s;
 uschar **str_target;
 uschar name[EXIM_DRIVERNAME_MAX];
 uschar name2[EXIM_DRIVERNAME_MAX];


+sublist:
+
+s = buffer;
+ptr = 0;
+
/* There may be leading spaces; thereafter, we expect an option name starting
with a letter. */

@@ -1764,8 +1769,6 @@ if (Ustrncmp(name, "not_", 4) == 0)
offset = 4;
}

-sublist:
-
/* Search the list for the given name. A non-existent name, or an option that
is set twice, is a disaster. */

@@ -2454,7 +2457,6 @@ switch (type)
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
     "failed to find %s module for %s: %s", US ol->v.value, name, errstr);


-debug_printf("hunting for option %s in module %s\n", name, mi->name);
     oltop = mi->options;
     last = mi->options_count;
     goto sublist;
@@ -3516,6 +3518,9 @@ if (!*spool_directory)
 /* Expand the spool directory name; it may, for example, contain the primary
 host name. Same comment about failure. */


+DEBUG(D_any) if (Ustrchr(spool_directory, '$'))
+  debug_printf("Expanding spool_directory option\n");
+
 if (!(s = expand_string(spool_directory)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand spool_directory "
     "\"%s\": %s", spool_directory, expand_string_message);
diff --git a/src/src/receive.c b/src/src/receive.c
index 336b37410..37b152f48 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -17,7 +17,7 @@ extern int dcc_ok;
 #endif


#ifdef SUPPORT_DMARC
-# include "dmarc.h"
+# include "miscmods/dmarc.h"
#endif

/*************************************************
@@ -1835,9 +1835,8 @@ if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
#endif

-#ifdef SUPPORT_DMARC
-if (sender_host_address) dmarc_conn_init();    /* initialize libopendmarc */
-#endif
+if (misc_mod_msg_init() != OK)
+  goto TIDYUP;


/* In SMTP sessions we may receive several messages in one connection. Before
each subsequent one, we wait for the clock to tick at the level of message-id
@@ -3627,7 +3626,14 @@ else
#endif /* WITH_CONTENT_SCAN */

 #ifdef SUPPORT_DMARC
-    dmarc_store_data(dmarc_from_header);
+    {
+    misc_module_info * mi = misc_mod_findonly(US"dmarc");
+    if (mi)
+      {
+      typedef int (*fn_t)(header_line *);
+      (((fn_t *) mi->functions)[3]) (dmarc_from_header);
+      }
+    }
 #endif


 #ifndef DISABLE_PRDR
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index f9bd3ece8..adf6c59cb 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1681,16 +1681,6 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
-#ifdef SUPPORT_SPF
-  {
-  misc_module_info * mi = misc_mod_findonly(US"spf");
-  if (mi)
-    {
-    typedef void (*fn_t)(void);
-    (((fn_t *) mi->functions)[4])();    /* spf_smtp_reset*/
-    }
-  }
-#endif
 #ifndef DISABLE_DKIM
 dkim_cur_signer = dkim_signers =
 dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
@@ -1701,8 +1691,6 @@ dkim_key_length = 0;
 #endif
 #ifdef SUPPORT_DMARC
 f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE;
-dmarc_domain_policy = dmarc_status = dmarc_status_text =
-dmarc_used_domain = NULL;
 #endif
 #ifdef EXPERIMENTAL_ARC
 arc_state = arc_state_reason = NULL;
@@ -1742,6 +1730,7 @@ while (acl_warn_logged)
   store_free(this);
   }


+misc_mod_smtp_reset();
message_tidyup();
store_reset(reset_point);

@@ -4025,24 +4014,14 @@ while (done <= 0)
       }
     }


-#ifdef SUPPORT_SPF
-      /* If we have an spf module, set up SPF context */
-      {
-      misc_module_info * mi = misc_mod_findonly(US"spf");
-      if (mi)
+      /* For any misc-module having a connection-init routine, call it. */
+      
+      if (misc_mod_conn_init(sender_helo_name, sender_host_address) != OK)
     {
-    /* We have hardwired function-call numbers, and also prototypes for the
-    functions.  We could do a function name table search for the number
-    but I can't see how to deal with prototypes.  Is a K&R non-prototyped
-    function still usable with today's compilers? */
-
-    typedef BOOL (*fn_t)(uschar *, uschar *);
-    fn_t fn = ((fn_t *) mi->functions)[0];    /* spf_conn_init */
-
-    (void) fn(sender_helo_name, sender_host_address);
+    DEBUG(D_receive) debug_printf("A module conn-init routine failed\n");
+    done = 1;
+    break;
     }
-      }
-#endif


       /* Apply an ACL check if one is defined; afterwards, recheck
       synchronization in case the client started sending in a delay. */
diff --git a/src/src/structs.h b/src/src/structs.h
index 2c8c77c43..46abac728 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -1023,6 +1023,9 @@ typedef struct misc_module_info {
   unsigned    dyn_magic;
   BOOL        (*init)(void *);    /* arg is the misc_module_info ptr */
   gstring *    (*lib_vers_report)(gstring *);    /* underlying library */
+  int        (*conn_init)(const uschar *, const uschar *);
+  void        (*smtp_reset)(void);
+  int        (*msg_init)(void);


   void *    options;
   unsigned    options_count;
diff --git a/test/runtest b/test/runtest
index dcf6d76b2..ae227810c 100755
--- a/test/runtest
+++ b/test/runtest
@@ -1561,8 +1561,8 @@ RESET_AFTER_EXTRA_LINE_READ:
     # Not all platforms build with SPF enabled
     next if /(^$time_pid?spf_conn_init|spf_compile\.c)/;
     next if /try option spf_smtp_comment_template$/;
-    next if /loading module 'spf'$/;
-    next if /^Loaded "spf"$/;
+    next if /loading module '(?:dmarc|spf)'$/;
+    next if /^$time_pid?Loaded "(?:dmarc|spf)"$/;


     # Not all platforms have sendfile support
     next if /^cannot use sendfile for body: no support$/;
diff --git a/test/stderr/0437 b/test/stderr/0437
index 29241cfd3..514a770f8 100644
--- a/test/stderr/0437
+++ b/test/stderr/0437
@@ -1,5 +1,6 @@
 Exim version x.yz ....
 Hints DB:
+Expanding spool_directory option
  search_open: lsearch "TESTSUITE/aux-fixed/0437.ls"
  search_find: file="TESTSUITE/aux-fixed/0437.ls"
    key="spool" partial=-1 affix=NULL starflags=0 opts=NULL


--
## subscription configuration (requires account):
## https://lists.exim.org/mailman3/postorius/lists/exim-cvs.lists.exim.org/
## unsubscribe (doesn't require an account):
## exim-cvs-unsubscribe@???
## Exim details at http://www.exim.org/
## Please use the Wiki with this list - http://wiki.exim.org/