[exim-cvs] dkim dynamic module

Góra strony
Delete this message
Reply to this message
Autor: Exim Git Commits Mailing List
Data:  
Dla: exim-cvs
Temat: [exim-cvs] dkim dynamic module
Gitweb: https://git.exim.org/exim.git/commitdiff/9a0f997bac85d8f234238162f3cee4524b6f989c
Commit:     9a0f997bac85d8f234238162f3cee4524b6f989c
Parent:     e89891fa5e27a1a6f895f45d324f2b407c41539b
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Wed Sep 4 21:46:26 2024 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Wed Sep 4 21:46:26 2024 +0100


    dkim dynamic module
---
 doc/doc-txt/NewStuff                      |   6 +-
 src/OS/Makefile-Base                      |  35 +-
 src/scripts/Configure-Makefile            |   2 +-
 src/scripts/MakeLinks                     |  25 +-
 src/scripts/drivers-Makefile              |  29 +-
 src/src/EDITME                            |   4 +
 src/src/acl.c                             |  45 ++-
 src/src/arc.c                             | 365 +++++++++++-------
 src/src/config.h.defaults                 |   3 +
 src/src/daemon.c                          |  13 -
 src/src/dkim.h                            |  33 --
 src/src/drtables.c                        |  61 ++-
 src/src/exim.c                            |   6 +-
 src/src/exim.h                            |   3 +-
 src/src/expand.c                          |  78 ++--
 src/src/functions.h                       |  15 +-
 src/src/globals.c                         |  19 -
 src/src/globals.h                         |  19 -
 src/src/hash.c                            |  43 ---
 src/src/hash.h                            |  10 -
 src/src/miscmods/Makefile                 |  26 +-
 src/src/miscmods/README                   |  11 +-
 src/src/{ => miscmods}/dkim.c             | 595 ++++++++++++++++++++++++++----
 src/src/miscmods/dkim.h                   |  47 +++
 src/src/miscmods/dkim_api.h               |  36 ++
 src/src/{ => miscmods}/dkim_transport.c   |   7 +-
 src/src/miscmods/dmarc.c                  |  35 +-
 src/src/miscmods/dmarc_api.h              |   3 +-
 src/src/{ => miscmods}/pdkim/Makefile     |   0
 src/src/{ => miscmods}/pdkim/README       |   0
 src/src/{ => miscmods}/pdkim/crypt_ver.h  |   0
 src/src/{ => miscmods}/pdkim/pdkim.c      |  15 +-
 src/src/{ => miscmods}/pdkim/pdkim.h      |   2 +-
 src/src/{ => miscmods}/pdkim/pdkim_hash.h |   0
 src/src/{ => miscmods}/pdkim/signing.c    |  33 +-
 src/src/{ => miscmods}/pdkim/signing.h    |  10 +-
 src/src/miscmods/spf.c                    |   2 +-
 src/src/miscmods/spf_api.h                |   3 +-
 src/src/pdkim/config.h                    |   4 -
 src/src/readconf.c                        |  12 +-
 src/src/receive.c                         | 133 ++-----
 src/src/smtp_in.c                         |  53 +--
 src/src/spool_in.c                        |  11 +-
 src/src/string.c                          |   2 +-
 src/src/structs.h                         |   1 +
 src/src/tls-gnu.c                         |   4 +-
 src/src/tls-openssl.c                     |   4 +-
 src/src/transports/smtp.c                 |  22 +-
 test/runtest                              |   5 +-
 49 files changed, 1234 insertions(+), 656 deletions(-)


diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 640bd58cd..1189ce3f3 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -14,9 +14,9 @@ Version 4.98

3. Events smtp:fail:protocol and smtp:fail:syntax

- 4. JSON and LDAP lookup support, SPF support, all the router and authenticator
-    drivers, and all the transport drivers except smtp, can now be built as
-    loadable modules
+ 4. JSON and LDAP lookup support, SPF, DKIM and DMARC support, all the router
+    and authenticator drivers, and all the transport drivers except smtp, can
+    now be built as loadable modules


 Version 4.98
 ------------
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 591b4261c..12319967e 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -221,15 +221,15 @@ macro-spa.o :        auths/spa.c
 macro-authtls.o:    auths/tls.c
     @echo "$(CC) -DMACRO_PREDEF auths/tls.c"
     $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/tls.c
-macro-dkim.o:        dkim.c
-    @echo "$(CC) -DMACRO_PREDEF dkim.c"
-    $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ dkim.c
+macro-dkim.o:        miscmods/dkim.c
+    @echo "$(CC) -DMACRO_PREDEF miscmods/dkim.c"
+    $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ miscmods/dkim.c
 macro-malware.o:    malware.c
     @echo "$(CC) -DMACRO_PREDEF malware.c"
     $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ malware.c
-macro-signing.o:    pdkim/signing.c
-    @echo "$(CC) -DMACRO_PREDEF pdkim/signing.c"
-    $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ pdkim/signing.c
+macro-signing.o:    miscmods/signing.c
+    @echo "$(CC) -DMACRO_PREDEF miscmods/signing.c"
+    $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ miscmods/signing.c


 macro_predef: $(OBJ_MACRO)
     @echo "$(LNCC) -o $@"
@@ -244,7 +244,7 @@ macro.c: macro_predef
 # problem, but it does no harm. Other make programs will just ignore this.


 .PHONY: all config utils \
-    buildauths buildlookups buildpdkim buildrouters \
+    buildauths buildlookups buildrouters \
         buildtransports buildmisc dynmodules checklocalmake clean



@@ -515,7 +515,7 @@ OBJ_AUTHS = call_pam.o call_pwcheck.o call_radius.o check_serv_cond.o \

 OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
         directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \
-        filtertest.o globals.o dkim.o dkim_transport.o dnsbl.o hash.o \
+        filtertest.o globals.o dnsbl.o hash.o \
         header.o host.o host_address.o ip.o log.o lss.o match.o md5.o moan.o \
         os.o parse.o priv.o proxy.o queue.o \
         rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o regex_cache.o \
@@ -526,13 +526,13 @@ OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
         local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \
         $(OBJ_EXPERIMENTAL)


-exim:   buildlookups buildauths pdkim/pdkim.a \
+exim:   buildlookups buildauths \
         buildrouters buildtransports buildmisc \
         $(OBJ_EXIM) version.o
     @echo "$(LNCC) -o exim"
     $(FE)$(PURIFY) $(LNCC) -o exim $(LFLAGS) $(OBJ_EXIM) version.o \
       routers/routers.a transports/transports.a lookups/lookups.a \
-      auths/auths.a pdkim/pdkim.a miscmods/miscmods.a \
+      auths/auths.a miscmods/miscmods.a \
       $(LIBRESOLV) $(LIBS) $(LIBS_EXIM) $(IPV6_LIBS) $(EXTRALIBS) \
       $(EXTRALIBS_EXIM) $(DBMLIB) $(LOOKUP_LIBS) $(AUTH_LIBS) \
       $(PERL_LIBS) $(TLS_LIBS) $(PCRE_LIBS) $(LDFLAGS)
@@ -685,6 +685,7 @@ HDRS  =    blob.h \
     hintsdb/hints_tdb.h \
     local_scan.h \
     macros.h \
+    miscmods/dkim_api.h \
     miscmods/dmarc_api.h \
     miscmods/spf_api.h \
     mytypes.h \
@@ -706,6 +707,7 @@ PHDRS = ../config.h \
     ../hintsdb/hints_tdb.h \
     ../local_scan.h \
     ../macros.h \
+    ../miscmods/dkim_api.h \
     ../miscmods/dmarc_api.h \
     ../miscmods/spf_api.h \
     ../mytypes.h \
@@ -886,8 +888,6 @@ transport.o:     $(HDRS) transport.c
 tree.o:          $(HDRS) tree.c
 verify.o:        $(HDRS) transports/smtp.h verify.c
 xtextencode.o:   $(HDRS) xtextencode.c
-dkim.o:          $(HDRS) pdkim/pdkim.h dkim.c
-dkim_transport.o: $(HDRS) dkim_transport.c


# Dependencies for WITH_CONTENT_SCAN modules

@@ -900,7 +900,7 @@ spool_mbox.o:    $(HDRS) spool_mbox.c


# Dependencies for EXPERIMENTAL_* modules

-arc.o:        $(HDRS) pdkim/pdkim.h arc.c
+arc.o:        $(HDRS) miscmods/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
@@ -1065,15 +1065,6 @@ buildauths: config
        INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
      @echo " "


-# The PDKIM library
-
-buildpdkim: pdkim/pdkim.a
-pdkim/pdkim.a: config
-     @cd pdkim && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
-       FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \
-       INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
-     @echo " "
-
 buildmisc: config
      @cd miscmods && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) \
        CC="$(CC)" CFLAGS="$(CFLAGS)" \
diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile
index c3019f846..12f0ddd9c 100755
--- a/src/scripts/Configure-Makefile
+++ b/src/scripts/Configure-Makefile
@@ -311,7 +311,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 DMARC
+ miscmods   SUPPORT    _DKIM DMARC SPF
 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 f657abd5b..a6521a95e 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -90,13 +90,20 @@ done
cd ..

 # miscellaneous modules
+# Note that the file in the miscmods/pdkim/ source subdir get linked to the 
+# destination miscmods/ dir
 d="miscmods"
 mkdir $d
 cd $d
 # Makefile is generated
-for f in dmarc.c dmarc.h dmarc_api.h dummy.c spf.c spf.h spf_api.h
+for f in dummy.c \
+    dkim.c dkim_transport.c dkim.h dkim_api.h \
+    pdkim/crypt_ver.h pdkim/pdkim.c pdkim/pdkim.h \
+    pdkim/pdkim_hash.h pdkim/signing.c pdkim/signing.h \
+    dmarc.c dmarc.h dmarc_api.h \
+    spf.c spf.h spf_api.h
 do
-  ln -s ../../src/$d/$f $f
+  ln -s ../../src/$d/$f `basename $f`
 done
 cd ..


@@ -110,17 +117,6 @@ do
done
cd ..

-# Likewise for the code for the PDKIM library
-d="pdkim"
-mkdir $d
-cd $d
-for f in README Makefile crypt_ver.h pdkim.c \
- pdkim.h hash.c hash.h signing.c signing.h blob.h
-do
- ln -s ../../src/$d/$f $f
-done
-cd ..
-
# The basic source files for Exim and utilities. NB local_scan.h gets linked,
# but local_scan.c does not, because its location is taken from the build-time
# configuration. Likewise for the os.c file, which gets build dynamically.
@@ -140,7 +136,6 @@ 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 \
valgrind.h memcheck.h \
macro_predef.c macro_predef.h
do
@@ -155,7 +150,7 @@ done

# EXPERIMENTAL_*
for f in arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
- danessl.h imap_utf7.c spf.c spf.h utf8.c xclient.c
+ danessl.h imap_utf7.c utf8.c xclient.c
do
ln -s ../src/$f $f
done
diff --git a/src/scripts/drivers-Makefile b/src/scripts/drivers-Makefile
index 2dd958043..085eedbda 100755
--- a/src/scripts/drivers-Makefile
+++ b/src/scripts/drivers-Makefile
@@ -95,13 +95,25 @@ fi
# command-line, not just check the Makefile.

want_dynamic() {
- local dyn_name="$1"
+ local dyn_name="${1#_}"
local re="(${classdef}|EXPERIMENTAL)_${dyn_name}[ $tab]*=[ $tab]*2"
+ #XXX Solaris does not support -E on grep. Must use egrep.
env | grep -E -q "^$re"
if [ $? -eq 0 ]; then return 0; fi
grep -E -q "^[ $tab]*$re" "$defs_source"
}

+want_not_disabled() {
+  local want_name="${1#_}"
+  [ "$local_want_name" = "$1" ] && return 0;
+  local re="DISABLED_${want_name}[ $tab]*=[ $tab]*."
+  env | grep -E -q "^$re"
+  [ $? -ne 0 ] && return 0
+  grep -E -q "^[ $tab]*$re" "$defs_source"
+  [ $? -ne 0 ] && return 0
+  return 1
+}
+
 want_at_all() {
   local want_name="$1"
   local re="(${classdef}|EXPERIMENTAL)_${want_name}[ $tab]*=[ $tab]*."
@@ -135,20 +147,21 @@ emit_module_rule() {
       echo >&2 "Missing CFLAGS_DYNAMIC prevents building dynamic $name"
       exit 1
     fi
-    MODS="${MODS} ${mod_name}.so"
+    MODS="${MODS} ${mod_name#_}.so"
     grep "^${classdef}_${name}_PC" "$defs_source" 1>&2
     pkgconf=$(grep "^${classdef}_${name}_PC" "$defs_source")
     if [ $? -eq 0 ]; then
       pkgconf=$(echo $pkgconf | sed 's/^.*= *//')
-      echo "${classdef}_${mod_name}_INCLUDE = $(pkg-config --cflags $pkgconf)"
-      echo "${classdef}_${mod_name}_LIBS = $(pkg-config --libs $pkgconf)"
+      echo "${classdef}_${mod_name#_}_INCLUDE = $(pkg-config --cflags $pkgconf)"
+      echo "${classdef}_${mod_name#_}_LIBS = $(pkg-config --libs $pkgconf)"
     else
       grep "^${classdef}_${name}_" "$defs_source"
-      echo "${classdef}_${mod_name}_INCLUDE = \$(${classdef}_${name}_INCLUDE)"
-      echo "${classdef}_${mod_name}_LIBS = \$(${classdef}_${name}_LIBS)"
+      echo "${classdef}_${mod_name#_}_INCLUDE = \$(${classdef}_${name}_INCLUDE)"
+      echo "${classdef}_${mod_name#_}_LIBS = \$(${classdef}_${name}_LIBS)"
     fi
-  elif want_at_all "$name"
-  then
+  elif want_not_disabled "$name"; then
+    OBJ="${OBJ} ${mod_name#_}.o"
+  elif want_at_all "$name"; then
     OBJ="${OBJ} ${mod_name}.o"
   fi
 }
diff --git a/src/src/EDITME b/src/src/EDITME
index 35c497697..9d458842a 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -586,6 +586,10 @@ DISABLE_MAL_MKS=yes
 # turned on by default.  See the spec for information on conditionally
 # disabling it.  To disable the inclusion of the entire feature, set
 # DISABLE_DKIM to "yes"
+#
+# It is possible to build the support as a dynamic-load module. In addition
+# to not defining DISABLE_DKIM, define SUPPORT_DKIM=2.  The usual rules on
+# defines for includes and libs apply.


# DISABLE_DKIM=yes

diff --git a/src/src/acl.c b/src/src/acl.c
index 023ac2ff6..878278313 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3934,23 +3934,19 @@ for (; cb; cb = cb->next)

 #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
-      if (dkim_cur_signer)
-    rc = match_isinlist(dkim_cur_signer,
-                          &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
-      else
-    rc = FAIL;
-      break;
-
     case ACLC_DKIM_STATUS:
-      {        /* return good for any match */
-      const uschar * s = dkim_verify_status ? dkim_verify_status : US"none";
-      int sep = 0;
-      for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); )
-    if (   (rc = match_isinlist(ss, &arg,
-                    0, NULL, NULL, MCL_STRING, TRUE, NULL))
-        == OK) break;
-      }
+      /* See comment on ACLC_SPF wrt. coding issues */
+      {
+      misc_module_info * mi = misc_mod_find(US"dkim", &log_message);
+      typedef int (*fn_t)(const uschar *);
+      rc = mi
+    ? (((fn_t *) mi->functions)
+              [cb->type == ACLC_DKIM_SIGNER
+            ? DKIM_SIGNER_ISINLIST
+            : DKIM_STATUS_LISTMATCH]) (arg)
+    : DEFER;
       break;
+      }
 #endif


 #ifdef SUPPORT_DMARC
@@ -4183,11 +4179,19 @@ for (; cb; cb = cb->next)
 #endif
      )
         store_pool = POOL_PERM;
+
 #ifndef DISABLE_DKIM    /* Overwriteable dkim result variables */
-      if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0)
-    dkim_verify_status = string_copy(arg);
-      else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
-    dkim_verify_reason = string_copy(arg);
+      if (  Ustrcmp(cb->u.varname, "dkim_verify_status") == 0
+     || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0
+         )
+      {
+      misc_module_info * mi = misc_mod_findonly(US"dkim");
+      typedef void (*fn_t)(const uschar *, void *);
+      
+      if (mi)
+        (((fn_t *) mi->functions)[DKIM_SETVAR])
+                    (cb->u.varname, string_copy(arg));
+      }
       else
 #endif
     acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
@@ -4216,7 +4220,8 @@ for (; cb; cb = cb->next)
     case ACLC_SPF:
     case ACLC_SPF_GUESS:
       /* We have hardwired function-call numbers, and also prototypes for the
-      functions.  We could do a function name table search for the number
+      functions.  We could do a function name table search or (simpler)
+      a module include file with defines for the numbers
       but I can't see how to deal with prototypes.  Is a K&R non-prototyped
       function still usable with today's compilers (but we would lose on
       type-checking)?  We could macroize the typedef, and even the function
diff --git a/src/src/arc.c b/src/src/arc.c
index d24b61114..a065ca8e3 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -15,16 +15,13 @@
 # else


# include "functions.h"
-# include "pdkim/pdkim.h"
-# include "pdkim/signing.h"
+# include "miscmods/pdkim.h"
+# include "miscmods/signing.h"

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

-extern pdkim_ctx * dkim_verify_ctx;
-extern pdkim_ctx dkim_sign_ctx;
-
 #define ARC_SIGN_OPT_TSTAMP    BIT(0)
 #define ARC_SIGN_OPT_EXPIRE    BIT(1)


@@ -100,6 +97,8 @@ typedef enum line_extract {
le_all
} line_extract_t;

+static misc_module_info * arc_dkim_mod_info;
+
static time_t now;
static time_t expire;
static hdr_rlist * headers_rlist;
@@ -132,6 +131,23 @@ arc_parse_line() gathering only the 'i' tag (instance) information.
*/


+/******************************************************************************/
+
+/* We need a module init function, to check on the dkim module being present
+(and we may as well stack it's modinfo ptr)
+
+For now (until we do an arc module), called from exim.c main().
+*/
+BOOL
+arc_init(void)
+{
+uschar * errstr = NULL;
+if ((arc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
+ return TRUE;
+log_write(0, LOG_MAIN|LOG_PANIC, "arc: %s", errstr);
+return FALSE;
+}
+
/******************************************************************************/


@@ -586,6 +602,39 @@ return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
}

 /******************************************************************************/
+/* Service routines provided by the dkim module */
+
+static int
+arc_dkim_hashname_blob_to_type(const blob * name)
+{
+typedef int (*fn_t)(const blob *);
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHNAME_TO_TYPE]) (name);
+}
+static hashmethod
+arc_dkim_hashtype_to_method(int hashtype)
+{
+typedef hashmethod (*fn_t)(int);
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHTYPE_TO_METHOD]) (hashtype);
+}
+static hashmethod
+arc_dkim_hashname_blob_to_method(const blob * name)
+{
+typedef hashmethod (*fn_t)(const blob *);
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHNAME_TO_METHOD]) (name);
+}
+
+/******************************************************************************/
+
+/* Do a "relaxed" canonicalization of a header */
+static uschar *
+arc_relax_header_n(const uschar * text, int len, BOOL append_crlf)
+{
+typedef uschar * (*fn_t)(const uschar *, int, BOOL);
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HEADER_RELAX])
+                        (text, len, append_crlf);
+}
+
+


 /* Return the hash of headers from the message that the AMS claims it
 signed.
@@ -599,14 +648,12 @@ const uschar * hn;
 int sep = ':';
 hdr_rlist * r;
 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
-int hashtype = pdkim_hashname_to_hashtype(
-            ams->a_hash.data, ams->a_hash.len);
+hashmethod hm = arc_dkim_hashname_blob_to_method(&ams->a_hash);
 hctx hhash_ctx;
 const uschar * s;
 int len;


-if (  hashtype == -1
-   || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
   {
   DEBUG(D_acl)
       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
@@ -628,7 +675,7 @@ while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
        && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
        )
       {
-      if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
+      if (relaxed) s = arc_relax_header_n(s, r->h->slen, TRUE);


       DEBUG(D_acl) debug_printf("%Z\n", s);
       exim_sha_update_string(&hhash_ctx, s);
@@ -640,7 +687,7 @@ while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))


s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
if (relaxed)
- len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
+ len = Ustrlen(s = arc_relax_header_n(s, len, FALSE));
DEBUG(D_acl) debug_printf("%.*Z\n", len, s);
exim_sha_update(&hhash_ctx, s, len);

@@ -653,33 +700,34 @@ return;



-static pdkim_pubkey *
-arc_line_to_pubkey(arc_line * al)
+static blob *
+arc_line_to_pubkey(arc_line * al, const uschar ** errstr)
 {
-uschar * dns_txt;
-pdkim_pubkey * p;
-
-if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
-      (int)al->s.len, al->s.data, (int)al->d.len, al->d.data))))
-  {
-  DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
-  return NULL;
-  }
-
-if (  !(p = pdkim_parse_pubkey_record(dns_txt))
-   || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
-   )
+typedef const uschar * (*fn_t)(const uschar *, blob **, const uschar **);
+blob * pubkey;
+const uschar * hashes;
+const uschar * srvtype =
+  (((fn_t *) arc_dkim_mod_info->functions)[DKIM_DNS_PUBKEY])
+    (string_sprintf("%.*s._domainkey.%.*s",
+          (int)al->s.len, al->s.data, (int)al->d.len, al->d.data),
+    &pubkey, &hashes);
+
+/*XXX do we need a blob-string printf %handler?  Other types of blob? */
+
+if (!srvtype)
+  { *errstr = US"pubkey dns lookup fail"; return NULL; }
+if ((Ustrcmp(srvtype, "*") != 0 && Ustrcmp(srvtype, "email") != 0))
   {
-  DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
+  *errstr = string_sprintf("pubkey format error: srvtype '%s'", srvtype);
   return NULL;
   }


/* If the pubkey limits use to specified hashes, reject unusable
signatures. XXX should we have looked for multiple dns records? */

-if (p->hashes)
+if (hashes)
{
- const uschar * list = p->hashes, * ele;
+ const uschar * list = hashes, * ele;
int sep = ':';

   while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
@@ -687,11 +735,38 @@ if (p->hashes)
   if (!ele)
     {
     DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
-                  p->hashes, (int)al->a.len, al->a.data);
+                  hashes, (int)al->a.len, al->a.data);
+    *errstr = US"no usable sig for this pubkey hash list";
     return NULL;
     }
   }
-return p;
+return pubkey;
+}
+
+
+
+
+/* Set up a body hashing method on the given signature-context
+(creates a new one if needed, or uses an already-present one).
+
+Arguments:
+    signing        TRUE for signing, FALSE for verification
+    c        canonicalization spec, text form
+    ah        hash, text form
+    bodylen        byte count for message body
+
+Return:    pointer to hashing method struct
+*/
+
+static pdkim_bodyhash *
+arc_set_bodyhash(BOOL signing,
+  const blob * c, const blob * ah, long bodylen)
+{
+typedef pdkim_bodyhash * (*fn_t)(BOOL,
+  const blob * canon, const blob * hash, long bodylen);
+
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SET_BODYHASH])
+            (signing, c, ah, bodylen);
 }



@@ -700,22 +775,72 @@ return p;
 static pdkim_bodyhash *
 arc_ams_setup_vfy_bodyhash(arc_line * ams)
 {
-int canon_head = -1, canon_body = -1;
-long bodylen;
-
-if (!ams->c.data) ams->c.data = US"simple";    /* RFC 6376 (DKIM) default */
-pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
-bodylen = ams->l.data
-    ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
-
-return pdkim_set_bodyhash(dkim_verify_ctx,
-    pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
-    canon_body,
-    bodylen);
+blob * c = &ams->c;
+long bodylen = ams->l.data
+    ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10)
+    : -1;
+
+if (!c->data)
+  {
+  c->data = US"simple";    /* RFC 6376 (DKIM) default */
+  c->len = 6;
+  }
+
+return arc_set_bodyhash(FALSE, c, &ams->a_hash, bodylen);
 }




+static void
+arc_decode_base64(const uschar * str, blob * b)
+{ 
+int dlen = b64decode(str, &b->data, str);
+if (dlen < 0) b->data = NULL;
+b->len = dlen;
+}
+
+
+
+static int
+arc_sig_verify(arc_set * as, arc_line * al, hashmethod hm,
+  blob * hhash_computed, blob * sighash,
+  const uschar * why, const uschar ** errstr_p)
+{
+blob * pubkey;
+const uschar * errstr = NULL;
+int rc;
+typedef int (*fn_t)
+    (const blob *, const blob *, hashmethod, const blob *, const uschar **);
+
+/* Get the public key from DNS */
+
+/*XXX dkim module */
+if (!(pubkey = arc_line_to_pubkey(al, &errstr)))
+  {
+  *errstr_p = string_sprintf("%s (%s)", errstr, why);
+  return ERROR;
+  }
+
+rc = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIG_VERIFY])
+              (sighash, hhash_computed, hm, pubkey, &errstr);
+switch (rc)
+  {
+  case OK:
+    break;
+  case FAIL:
+    DEBUG(D_acl)
+      debug_printf("ARC i=%d %s verify %s\n", as->instance, why, errstr);
+    break;
+  case ERROR:
+    DEBUG(D_acl) debug_printf("ARC verify %s init: %s\n", why, errstr);
+    break;
+  }
+return rc;
+}
+
+
+
+
 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
 and without a DKIM v= tag.
 */
@@ -725,12 +850,11 @@ arc_ams_verify(arc_ctx * ctx, arc_set * as)
 {
 arc_line * ams = as->hdr_ams;
 pdkim_bodyhash * b;
-pdkim_pubkey * p;
 blob sighash;
-blob hhash;
-ev_ctx vctx;
-int hashtype;
+blob hhash_computed;
+hashmethod hm;
 const uschar * errstr;
+int rc;


as->ams_verify_done = US"in-progress";

@@ -771,7 +895,7 @@ DEBUG(D_acl)
/* We know the bh-tag blob is of a nul-term string, so safe as a string */

 if (  !ams->bh.data
-   || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
+   || (arc_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
    || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
    )
   {
@@ -786,38 +910,21 @@ if (  !ams->bh.data


DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);

-/* Get the public key from DNS */
-
-if (!(p = arc_line_to_pubkey(ams)))
- return as->ams_verify_done = arc_state_reason = US"pubkey problem";
-
/* We know the b-tag blob is of a nul-term string, so safe as a string */
-pdkim_decode_base64(ams->b.data, &sighash);
+arc_decode_base64(ams->b.data, &sighash);

-arc_get_verify_hhash(ctx, ams, &hhash);
+arc_get_verify_hhash(ctx, ams, &hhash_computed);

-/* Setup the interface to the signing library */
-
-if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
- {
- DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
- as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
- return US"fail";
- }
-
-hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
-if (hashtype == -1)
+if ((hm = arc_dkim_hashname_blob_to_method(&ams->a_hash)) < 0)
{
DEBUG(D_acl) debug_printf("ARC i=%d AMS verify bad a_hash\n", as->instance);
return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
}

-if ((errstr = exim_dkim_verify(&vctx,
-      pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
-  {
-  DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
-  return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
-  }
+rc = arc_sig_verify(as, ams, hm, &hhash_computed, &sighash, US"AMS", &errstr);
+if (rc != OK)
+  return as->ams_verify_done = arc_state_reason =
+    rc == FAIL ? US"AMS sig nonverify" : errstr;


DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
as->ams_verify_passed = TRUE;
@@ -901,13 +1008,12 @@ arc_seal_verify(arc_ctx * ctx, arc_set * as)
{
arc_line * hdr_as = as->hdr_as;
arc_set * as2;
-int hashtype;
+hashmethod hm;
hctx hhash_ctx;
blob hhash_computed;
blob sighash;
-ev_ctx vctx;
-pdkim_pubkey * p;
const uschar * errstr;
+int rc;

 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
 /*
@@ -935,10 +1041,9 @@ if (  as->instance == 1 && !arc_cv_match(hdr_as, US"none")
            the ARC-Seal.
 */


-hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
+hm = arc_dkim_hashname_blob_to_method(&hdr_as->a_hash);

-if (  hashtype == -1
-   || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
   {
   DEBUG(D_acl)
       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
@@ -967,7 +1072,8 @@ for (as2 = ctx->arcset_chain;


   al = as2->hdr_aar;
   if (!(s = al->relaxed))
-    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+    /*XXX dkim module */
+    al->relaxed = s = arc_relax_header_n(al->complete->text,
                         al->complete->slen, TRUE);
   len = Ustrlen(s);
   DEBUG(D_acl) debug_printf("%Z\n", s);
@@ -975,7 +1081,8 @@ for (as2 = ctx->arcset_chain;


   al = as2->hdr_ams;
   if (!(s = al->relaxed))
-    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+    /*XXX dkim module */
+    al->relaxed = s = arc_relax_header_n(al->complete->text,
                         al->complete->slen, TRUE);
   len = Ustrlen(s);
   DEBUG(D_acl) debug_printf("%Z\n", s);
@@ -983,10 +1090,12 @@ for (as2 = ctx->arcset_chain;


   al = as2->hdr_as;
   if (as2->instance == as->instance)
-    s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
+    /*XXX dkim module */
+    s = arc_relax_header_n(al->rawsig_no_b_val.data,
                     al->rawsig_no_b_val.len, FALSE);
   else if (!(s = al->relaxed))
-    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+    /*XXX dkim module */
+    al->relaxed = s = arc_relax_header_n(al->complete->text,
                         al->complete->slen, TRUE);
   len = Ustrlen(s);
   DEBUG(D_acl) debug_printf("%Z\n", s);
@@ -1009,12 +1118,9 @@ DEBUG(D_acl)
 /*
        6.  Retrieve the public key identified by the "s" and "d" tags in
            the ARC-Seal, as described in Section 4.1.6.
-*/


-if (!(p = arc_line_to_pubkey(hdr_as)))
- return US"pubkey problem";
+Done below, in arc_sig_verify().

-/*
        7.  Determine whether the signature portion ("b" tag) of the ARC-
            Seal and the digest computed above are valid according to the
            public key.  (See also Section Section 8.4 for failure case
@@ -1025,21 +1131,12 @@ if (!(p = arc_line_to_pubkey(hdr_as)))
 */


/* We know the b-tag blob is of a nul-term string, so safe as a string */
-pdkim_decode_base64(hdr_as->b.data, &sighash);
-
-if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
- {
- DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
- return US"fail";
- }
+arc_decode_base64(hdr_as->b.data, &sighash);

-if ((errstr = exim_dkim_verify(&vctx,
-          pdkim_hashes[hashtype].exim_hashmethod,
-          &hhash_computed, &sighash)))
+rc = arc_sig_verify(as, hdr_as, hm, &hhash_computed, &sighash, US"AS", &errstr);
+if (rc != OK)
   {
-  DEBUG(D_acl)
-    debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
-  arc_state_reason = US"seal sigverify error";
+  if (rc == FAIL) arc_state_reason = US"seal sigverify error";
   return US"fail";
   }


@@ -1076,12 +1173,6 @@ const uschar * res;

memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));

-if (!dkim_verify_ctx)
-  {
-  DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
-  return NULL;
-  }
-
 /* AS evaluation, per
 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
 */
@@ -1285,10 +1376,13 @@ arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
   blob * sig, const uschar * why)
 {
 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
-  ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
+  ? HASH_SHA2_512
+  : arc_dkim_hashtype_to_method(hashtype);
+
 blob hhash;
-es_ctx sctx;
 const uschar * errstr;
+typedef const uschar * (*fn_t)
+              (const blob *, hashmethod, const uschar *, blob *);


DEBUG(D_transport)
{
@@ -1296,7 +1390,7 @@ DEBUG(D_transport)
debug_printf("ARC: %s header data for signing:\n", why);
debug_printf("%.*Z\n", hdata->ptr, hdata->s);

- (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+ (void) exim_sha_init(&hhash_ctx, hm);
exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
exim_sha_finish(&hhash_ctx, &hhash);
debug_printf("ARC: header hash: %.*H\n", hhash.len, hhash.data);
@@ -1305,7 +1399,7 @@ DEBUG(D_transport)
if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
{
hctx hhash_ctx;
- (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+ (void) exim_sha_init(&hhash_ctx, arc_dkim_hashtype_to_method(hashtype));
exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
exim_sha_finish(&hhash_ctx, &hhash);
}
@@ -1315,8 +1409,9 @@ else
hhash.len = hdata->ptr;
}

-if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
-   || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
+errstr = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIGN_DATA])
+                          (&hhash, hm, privkey, sig);
+if (errstr)
   {
   log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
   DEBUG(D_transport)
@@ -1324,6 +1419,7 @@ if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
       privkey);
   return FALSE;
   }
+
 return TRUE;
 }


@@ -1333,7 +1429,7 @@ static gstring *
 arc_sign_append_sig(gstring * g, blob * sig)
 {
 /*debug_printf("%s: raw sig %.*H\n", __FUNCTION__, sig->len, sig->data);*/
-sig->data = pdkim_encode_base64(sig);
+sig->data = b64encode(sig->data, sig->len);
 sig->len = Ustrlen(sig->data);
 for (;;)
   {
@@ -1360,7 +1456,8 @@ arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
 uschar * s;
 gstring * hdata = NULL;
 int col;
-int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);    /*XXX hardwired */
+const blob ams_h = {.data = US"sha256", .len = 6};    /*XXX hardwired */
+int hashtype = arc_dkim_hashname_blob_to_type(&ams_h);
 blob sig;
 int ams_off;
 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED);
@@ -1378,7 +1475,7 @@ if (options & ARC_SIGN_OPT_TSTAMP)
 if (options & ARC_SIGN_OPT_EXPIRE)
   g = string_fmt_append(g, "; x=%lu", (u_long)expire);
 g = string_fmt_append(g, ";\r\n\tbh=%s;\r\n\th=",
-      pdkim_encode_base64(bodyhash));
+      b64encode(bodyhash->data, bodyhash->len));


 for(col = 3; rheaders; rheaders = rheaders->prev)
   {
@@ -1406,7 +1503,8 @@ for(col = 3; rheaders; rheaders = rheaders->prev)
       /* Accumulate header for hashing/signing */


       hdata = string_cat(hdata,
-        pdkim_relax_header_n(htext, rheaders->h->slen, TRUE));    /*XXX hardwired */
+        /*XXX dkim module */
+        arc_relax_header_n(htext, rheaders->h->slen, TRUE));    /*XXX hardwired */
       break;
       }
     }
@@ -1420,7 +1518,8 @@ g = string_catn(g, US";\r\n\tb=;", 7);


/* Include the pseudo-header in the accumulation */

-s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
+/*XXX dkim module */
+s = arc_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
hdata = string_cat(hdata, s);

/* Calculate the signature from the accumulation */
@@ -1483,7 +1582,8 @@ header_line * h = (header_line *)(al+1);
uschar * badline_str;

 gstring * hdata = NULL;
-int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);    /*XXX hardwired */
+const blob as_h = {.data = US"sha256", .len = 6};    /*XXX hardwired */
+int hashtype = arc_dkim_hashname_blob_to_type(&as_h);
 blob sig;


/*
@@ -1533,15 +1633,18 @@ for (arc_set * as = Ustrcmp(status, US"fail") == 0
badline_str = US"aar";
if (!(l = as->hdr_aar)) goto badline;
h = l->complete;
- hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+ /*XXX dkim module */
+ hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, TRUE));
badline_str = US"ams";
if (!(l = as->hdr_ams)) goto badline;
h = l->complete;
- hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+ /*XXX dkim module */
+ hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, TRUE));
badline_str = US"as";
if (!(l = as->hdr_as)) goto badline;
h = l->complete;
- hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
+ /*XXX dkim module */
+ hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, !!as->next));
}

/* Calculate the signature from the accumulation */
@@ -1567,21 +1670,19 @@ badline:

/**************************************/

-/* Return pointer to pdkim_bodyhash for given hash method, creating new
-method if needed.
-*/
+/*XXX not static currently as the smtp tpt calls us */
+/* Really returns pdkim_bodyhash* - but there's an ordering
+problem for functions.h so call it void* */

 void *
 arc_ams_setup_sign_bodyhash(void)
 {
-int canon_head, canon_body;
+blob canon = {.data = US"relaxed", .len = 7};    /*XXX hardwired */
+blob hash =  {.data = US"sha256",  .len = 6};    /*XXX hardwired */


 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
-pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body);    /*XXX hardwired */
-return pdkim_set_bodyhash(&dkim_sign_ctx,
-    pdkim_hashname_to_hashtype(US"sha256", 6),            /*XXX hardwired */
-    canon_body,
-    -1);
+
+return arc_set_bodyhash(TRUE, &canon, &hash, -1);
 }



@@ -1767,6 +1868,10 @@ g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
     - ? oversigning?
   - Covers the data
   - we must have requested a suitable bodyhash previously
+XXX so where was that done?  I don't see it!
+XXX ah, ok - the smtp tpt calls arc_ams_setup_sign_bodyhash() directly, early
+    -> should pref use a better named call to make the point, but that
+    can wait until arc becomes a module
 */


b = arc_ams_setup_sign_bodyhash();
@@ -1827,8 +1932,6 @@ arc_line al;
pdkim_bodyhash * b;
uschar * errstr;

-if (!dkim_verify_ctx) return US"no dkim context";
-
if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";

DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 13b203e80..d602886a0 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -167,6 +167,9 @@ Do not put spaces between # and the 'define'.
#define SUPPORT_SRS
#define SUPPORT_TRANSLATE_IP_ADDRESS

+/* Required to support dynamic-module build */
+#define SUPPORT_DKIM
+
#define SYSLOG_LOG_PID
#define SYSLOG_LONG_LINES

diff --git a/src/src/daemon.c b/src/src/daemon.c
index 456c586da..fc8c7fdd2 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -2562,19 +2562,6 @@ else    /* no listening sockets, only queue-runs */
 dns_pattern_init();
 smtp_deliver_init();    /* Used for callouts */


-#ifndef DISABLE_DKIM
-  {
-# ifdef MEASURE_TIMING
-  struct timeval t0;
-  gettimeofday(&t0, NULL);
-# endif
-  dkim_exim_init();
-# ifdef MEASURE_TIMING
-  report_time_since(&t0, US"dkim_exim_init (delta)");
-# endif
-  }
-#endif
-
 #ifdef WITH_CONTENT_SCAN
 malware_init();
 #endif
diff --git a/src/src/dkim.h b/src/src/dkim.h
deleted file mode 100644
index 915c6c739..000000000
--- a/src/src/dkim.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge, 1995 - 2018 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-void    dkim_exim_init(void);
-gstring * dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
-void    dkim_exim_verify_init(BOOL);
-void    dkim_exim_verify_feed(uschar *, int);
-void    dkim_exim_verify_finish(void);
-void    dkim_exim_verify_log_all(void);
-int     dkim_exim_acl_run(uschar *, gstring **, uschar **, uschar **);
-uschar *dkim_exim_expand_query(int);
-
-#define DKIM_ALGO               1
-#define DKIM_BODYLENGTH         2
-#define DKIM_CANON_BODY         3
-#define DKIM_CANON_HEADERS      4
-#define DKIM_COPIEDHEADERS      5
-#define DKIM_CREATED            6
-#define DKIM_EXPIRES            7
-#define DKIM_HEADERNAMES        8
-#define DKIM_IDENTITY           9
-#define DKIM_KEY_GRANULARITY   10
-#define DKIM_KEY_SRVTYPE       11
-#define DKIM_KEY_NOTES         12
-#define DKIM_KEY_TESTING       13
-#define DKIM_NOSUBDOMAINS      14
-#define DKIM_VERIFY_STATUS     15
-#define DKIM_VERIFY_REASON     16
diff --git a/src/src/drtables.c b/src/src/drtables.c
index 9ed55e29a..61ced3e6a 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -431,12 +431,16 @@ misc_module_info * misc_module_list = NULL;
 static void
 misc_mod_add(misc_module_info * mi)
 {
-if (mi->init) mi->init(mi);
-DEBUG(D_any) if (mi->lib_vers_report)
-  debug_printf_indent("%Y", mi->lib_vers_report(NULL));
+if (mi->init && mi->init(mi))
+  {
+  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;
+ }
+else DEBUG(D_any)
+ debug_printf_indent("module init call failed for %s\n", mi->name);
}


@@ -453,7 +457,10 @@ const char * errormsg;

DEBUG(D_any) debug_printf_indent("loading module '%s'\n", name);
if (!(dl = mod_open(name, US"miscmod", errstr)))
+ {
+ DEBUG(D_any) debug_printf_indent(" mod_open: %s\n", *errstr);
return NULL;
+ }

 mi = (struct misc_module_info *) dlsym(dl,
                     CS string_sprintf("%s_module_info", name));
@@ -546,6 +553,39 @@ for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
 return OK;
 }


+/* Ditto, authres.  Having to sort the responses (mainly for the testsuite)
+is pretty painful - maybe we should sort the modules on insertion to
+the list? */
+
+gstring *
+misc_mod_authres(gstring * g)
+{
+typedef struct {
+  const uschar * name;
+  gstring *     res;
+} pref;
+pref prefs[] = {
+  {US"spf", NULL}, {US"dkim", NULL}, {US"dmarc", NULL}, {US"arc", NULL}
+};
+gstring * others = NULL;
+
+for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+  if (mi->authres)
+    {
+    pref * p;
+    for (p = prefs; p < prefs + nelem(prefs); p++)
+      if (Ustrcmp(p->name, mi->name) == 0) break;
+
+    if (p) p->res = (mi->authres)(NULL);
+    else   others = (mi->authres)(others);
+    }
+
+for (pref * p = prefs; p < prefs + nelem(prefs); p++)
+  g = gstring_append(g, p->res);
+return gstring_append(g, others);
+}
+
+




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


+#if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2)
+extern misc_module_info dkim_module_info;
+#endif
#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
extern misc_module_info dmarc_module_info;
#endif
@@ -709,16 +752,18 @@ init_misc_mod_list(void)
{
static BOOL onetime = FALSE;
if (onetime) return;
+onetime = TRUE;

+#if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2)
+misc_mod_add(&dkim_module_info);
+#endif
#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
+/* dmarc depends on spf so this add must go after, for the both-static case */
misc_mod_add(&dmarc_module_info);
#endif
-
-onetime = TRUE;
}


diff --git a/src/src/exim.c b/src/src/exim.c
index 5ad54ffc1..ca98e25de 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -4211,6 +4211,9 @@ is equivalent to the ability to modify a setuid binary!
 This needs to happen before we read the main configuration. */
 init_lookup_list();
 init_misc_mod_list();
+#ifdef EXPERIMENTAL_ARC
+arc_init();    /*XXX temporary, until we do an arc module */
+#endif


/*XXX this excrescence could move to the testsuite standard config setup file */
#ifdef SUPPORT_I18N
@@ -5610,9 +5613,6 @@ if (host_checking)

       return_path = sender_address = NULL;
       dnslist_domain = dnslist_matched = NULL;
-#ifndef DISABLE_DKIM
-      dkim_cur_signer = NULL;
-#endif
       acl_var_m = NULL;
       deliver_localpart_orig = NULL;
       deliver_domain_orig = NULL;
diff --git a/src/src/exim.h b/src/src/exim.h
index c996a2f8c..8260dc75f 100644
--- a/src/src/exim.h
+++ b/src/src/exim.h
@@ -547,7 +547,8 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 # include "miscmods/spf_api.h"
 #endif
 #ifndef DISABLE_DKIM
-# include "dkim.h"
+# include "miscmods/dkim.h"
+# include "miscmods/dkim_api.h"
 #endif
 #ifdef SUPPORT_DMARC
 # include "miscmods/dmarc.h"
diff --git a/src/src/expand.c b/src/src/expand.c
index af3816051..02680771f 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -490,27 +490,28 @@ static var_entry var_table[] = {
   { "dcc_result",          vtype_stringptr,   &dcc_result },
 #endif
 #ifndef DISABLE_DKIM
-  { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
-  { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
-  { "dkim_canon_body",     vtype_dkim,        (void *)DKIM_CANON_BODY },
-  { "dkim_canon_headers",  vtype_dkim,        (void *)DKIM_CANON_HEADERS },
-  { "dkim_copiedheaders",  vtype_dkim,        (void *)DKIM_COPIEDHEADERS },
-  { "dkim_created",        vtype_dkim,        (void *)DKIM_CREATED },
-  { "dkim_cur_signer",     vtype_stringptr,   &dkim_cur_signer },
-  { "dkim_domain",         vtype_stringptr,   &dkim_signing_domain },
-  { "dkim_expires",        vtype_dkim,        (void *)DKIM_EXPIRES },
-  { "dkim_headernames",    vtype_dkim,        (void *)DKIM_HEADERNAMES },
-  { "dkim_identity",       vtype_dkim,        (void *)DKIM_IDENTITY },
-  { "dkim_key_granularity",vtype_dkim,        (void *)DKIM_KEY_GRANULARITY },
-  { "dkim_key_length",     vtype_int,         &dkim_key_length },
-  { "dkim_key_nosubdomains",vtype_dkim,       (void *)DKIM_NOSUBDOMAINS },
-  { "dkim_key_notes",      vtype_dkim,        (void *)DKIM_KEY_NOTES },
-  { "dkim_key_srvtype",    vtype_dkim,        (void *)DKIM_KEY_SRVTYPE },
-  { "dkim_key_testing",    vtype_dkim,        (void *)DKIM_KEY_TESTING },
-  { "dkim_selector",       vtype_stringptr,   &dkim_signing_selector },
-  { "dkim_signers",        vtype_stringptr,   &dkim_signers },
-  { "dkim_verify_reason",  vtype_stringptr,   &dkim_verify_reason },
-  { "dkim_verify_status",  vtype_stringptr,   &dkim_verify_status },
+  { "dkim_algo",           vtype_module,    US"dkim" },
+  { "dkim_bodylength",     vtype_module,    US"dkim" },
+  { "dkim_canon_body",     vtype_module,    US"dkim" },
+  { "dkim_canon_headers",  vtype_module,    US"dkim" },
+  { "dkim_copiedheaders",  vtype_module,    US"dkim" },
+  { "dkim_created",        vtype_module,    US"dkim" },
+  { "dkim_cur_signer",     vtype_module,    US"dkim" },
+  { "dkim_domain",         vtype_module,    US"dkim" },
+  { "dkim_expires",        vtype_module,    US"dkim" },
+  { "dkim_headernames",    vtype_module,    US"dkim" },
+  { "dkim_identity",       vtype_module,    US"dkim" },
+  { "dkim_key_granularity",vtype_module,    US"dkim" },
+  { "dkim_key_length",     vtype_module,    US"dkim" },
+  { "dkim_key_nosubdomains",vtype_module,    US"dkim" },
+  { "dkim_key_notes",      vtype_module,    US"dkim" },
+  { "dkim_key_srvtype",    vtype_module,    US"dkim" },
+  { "dkim_key_testing",    vtype_module,    US"dkim" },
+  { "dkim_selector",       vtype_module,    US"dkim" },
+  { "dkim_signers",        vtype_module,    US"dkim" },
+  { "dkim_verify_reason",  vtype_module,    US"dkim" },
+  { "dkim_verify_signers", vtype_module,    US"dkim" },
+  { "dkim_verify_status",  vtype_module,    US"dkim" },
 #endif
 #ifdef SUPPORT_DMARC
   { "dmarc_domain_policy", vtype_module,    US"dmarc" },
@@ -2131,7 +2132,13 @@ switch (vp->type)


 #ifndef DISABLE_DKIM
   case vtype_dkim:
-    return dkim_exim_expand_query((int)(long)val);
+    {
+    misc_module_info * mi = misc_mod_findonly(US"dkim");
+    typedef uschar * (*fn_t)(int);
+    return mi
+      ? (((fn_t *) mi->functions)[DKIM_EXPAND_QUERY]) ((int)(long)val)
+      : US"";
+    }
 #endif


   case vtype_module:
@@ -4882,32 +4889,7 @@ while (*s)
       yield = authres_local(yield, sub_arg[0]);
       yield = authres_iprev(yield);
       yield = authres_smtpauth(yield);
-#ifdef SUPPORT_SPF
-    {
-    misc_module_info * mi = misc_mod_findonly(US"spf");
-    if (mi)
-      {
-      typedef gstring * (*fn_t)(gstring *);
-      fn_t fn = ((fn_t *) mi->functions)[SPF_AUTHRES];
-      yield = fn(yield);
-      }
-    }
-#endif
-#ifndef DISABLE_DKIM
-      yield = authres_dkim(yield);
-#endif
-#ifdef SUPPORT_DMARC
-    {
-    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)[DMARC_AUTHRES];
-      yield = fn(yield);
-      }
-    }
-#endif
+      yield = misc_mod_authres(yield);
 #ifdef EXPERIMENTAL_ARC
       yield = authres_arc(yield);
 #endif
diff --git a/src/src/functions.h b/src/src/functions.h
index 3a980318f..fba5ec688 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -1,3 +1,4 @@
+extern BOOL arc_init(void);
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
@@ -144,9 +145,6 @@ extern uschar *authenticator_current_name(void);
 #ifdef EXPERIMENTAL_ARC
 extern gstring *authres_arc(gstring *);
 #endif
-#ifndef DISABLE_DKIM
-extern gstring *authres_dkim(gstring *);
-#endif
 extern gstring *authres_smtpauth(gstring *);


 extern uschar *b64encode(const uschar *, int);
@@ -222,13 +220,6 @@ extern void    delivery_re_exec(int);


 extern void    die_tainted(const uschar *, const uschar *, int);
 extern BOOL    directory_make(const uschar *, const uschar *, int, BOOL);
-#ifndef DISABLE_DKIM
-extern uschar *dkim_exim_query_dns_txt(const uschar *);
-extern void    dkim_exim_sign_init(void);
-
-extern BOOL    dkim_transport_write_message(transport_ctx *,
-          struct ob_dkim *, const uschar ** errstr);
-#endif
 extern dns_address *dns_address_from_rr(dns_answer *, dns_record *);
 extern int     dns_basic_lookup(dns_answer *, const uschar *, int);
 extern uschar *dns_build_reverse(const uschar *);
@@ -375,6 +366,7 @@ extern int     mime_regex(const uschar **, BOOL);
 extern void    mime_set_anomaly(int);
 #endif


+extern gstring *misc_mod_authres(gstring *);
 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);
@@ -552,6 +544,7 @@ extern int     smtp_setup_msg(void);
 extern int     smtp_sock_connect(smtp_connect_args *, int, const blob *);
 extern BOOL    smtp_start_session(void);
 extern int     smtp_ungetc(int);
+extern void    smtp_verify_feed(const uschar *, unsigned);
 extern BOOL    smtp_verify_helo(void);
 extern int     smtp_write_command(void *, int, const char *, ...) PRINTF_FUNCTION(3,4);
 #ifdef WITH_CONTENT_SCAN
@@ -1112,7 +1105,7 @@ g->s = s;
 static inline gstring *
 gstring_append(gstring * dest, gstring * item)
 {
-return string_catn(dest, item->s, item->ptr);
+return item ? string_catn(dest, item->s, item->ptr) : dest;
 }



diff --git a/src/src/globals.c b/src/src/globals.c
index cfa75f1d7..6fae1582f 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -864,25 +864,6 @@ address_item *deliver_recipients = NULL;
uschar *deliver_selectstring = NULL;
uschar *deliver_selectstring_sender = NULL;

-#ifndef DISABLE_DKIM
-unsigned dkim_collect_input      = 0;
-uschar *dkim_cur_signer          = NULL;
-int     dkim_key_length          = 0;
-void   *dkim_signatures         = NULL;
-uschar *dkim_signers             = NULL;
-uschar *dkim_signing_domain      = NULL;
-uschar *dkim_signing_selector    = NULL;
-gstring *dkim_signing_record     = NULL;
-uschar *dkim_verify_hashes       = US"sha256:sha512";
-uschar *dkim_verify_keytypes     = US"ed25519:rsa";
-uschar *dkim_verify_min_keysizes = US"rsa=1024 ed25519=250";
-BOOL    dkim_verify_minimal      = FALSE;
-uschar *dkim_verify_overall      = NULL;
-uschar *dkim_verify_signers      = US"$dkim_signers";
-uschar *dkim_verify_status     = NULL;
-uschar *dkim_verify_reason     = NULL;
-#endif
-
 uschar *dns_again_means_nonexist = NULL;
 int     dns_csa_search_limit   = 5;
 int    dns_cname_loops           = 1;
diff --git a/src/src/globals.h b/src/src/globals.h
index 8173d771e..1f03cefee 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -545,25 +545,6 @@ extern BOOL    disable_fsync;          /* Not for normal use */
 #endif
 extern BOOL    disable_ipv6;           /* Don't do any IPv6 things */


-#ifndef DISABLE_DKIM
-extern unsigned dkim_collect_input;    /* Runtime count of dkim signtures; tracks whether SMTP input is fed to DKIM validation */
-extern uschar *dkim_cur_signer;        /* Expansion variable, holds the current "signer" domain or identity during a acl_smtp_dkim run */
-extern int     dkim_key_length;        /* Expansion variable, length of signing key in bits */
-extern void   *dkim_signatures;           /* Actually a (pdkim_signature *) but most files do not need to know */
-extern uschar *dkim_signers;           /* Expansion variable, holds colon-separated list of domains and identities that have signed a message */
-extern gstring *dkim_signing_record;   /* domains+selectors used */
-extern uschar *dkim_signing_domain;    /* Expansion variable, domain used for signing a message. */
-extern uschar *dkim_signing_selector;  /* Expansion variable, selector used for signing a message. */
-extern uschar *dkim_verify_hashes;     /* Preference order for signatures */
-extern uschar *dkim_verify_keytypes;   /* Preference order for signatures */
-extern uschar *dkim_verify_min_keysizes; /* list of minimum key sizes, keyed by algo */
-extern BOOL    dkim_verify_minimal;    /* Shortcircuit signature verification */
-extern uschar *dkim_verify_overall;    /* First successful domain verified, or null */
-extern uschar *dkim_verify_signers;    /* Colon-separated list of domains for each of which we call the DKIM ACL */
-extern uschar *dkim_verify_status;     /* result for this signature */
-extern uschar *dkim_verify_reason;     /* result for this signature */
-#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 BOOL    dns_csa_use_reverse;    /* Check CSA in reverse DNS? (non-standard) */
diff --git a/src/src/hash.c b/src/src/hash.c
index 17a52fe43..d629a52bd 100644
--- a/src/src/hash.c
+++ b/src/src/hash.c
@@ -227,49 +227,6 @@ memcpy(b->data, gcry_md_read(h->sha, 0), h->hashlen);




-#elif defined(SHA_POLARSSL)
-# define HAVE_PARTIAL_SHA
-/******************************************************************************/
-
-BOOL
-exim_sha_init(hctx * h, hashmethod m)
-{
-/*XXX extend for sha512 */
-switch (h->method = m)
-  {
-  case HASH_SHA1:   h->hashlen = 20; sha1_starts(&h->u.sha1);    break;
-  case HASH_SHA2_256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
-  default:        h->hashlen = 0; return FALSE;
-  }
-return TRUE;
-}
-
-
-void
-exim_sha_update(hctx * h, const uschar * data, int len)
-{
-switch (h->method)
-  {
-  case HASH_SHA1:   sha1_update(h->u.sha1, US data, len); break;
-  case HASH_SHA2_256: sha2_update(h->u.sha2, US data, len); break;
-  }
-}
-
-
-void
-exim_sha_finish(hctx * h, blob * b)
-{
-b->data = store_get(b->len = h->hashlen, GET_INTAINTED);
-switch (h->method)
-  {
-  case HASH_SHA1:   sha1_finish(h->u.sha1, b->data); break;
-  case HASH_SHA2_256: sha2_finish(h->u.sha2, b->data); break;
-  }
-}
-
-
-
-
 #elif defined(SHA_NATIVE)
 /******************************************************************************/
 /* Only sha-1 supported */
diff --git a/src/src/hash.h b/src/src/hash.h
index 788c9f0ad..9ad837b62 100644
--- a/src/src/hash.h
+++ b/src/src/hash.h
@@ -19,10 +19,6 @@
 # include <gnutls/crypto.h>
 #elif defined(SHA_GCRYPT)
 # include <gcrypt.h>
-#elif defined(SHA_POLARSSL)
-# include "pdkim/pdkim.h"        /*XXX ugly */
-# include "pdkim/polarssl/sha1.h"
-# include "pdkim/polarssl/sha2.h"
 #endif



@@ -63,12 +59,6 @@ typedef struct {
 #elif defined(SHA_GCRYPT)
   gcry_md_hd_t sha;          /* Either SHA1 or SHA256 block               */


-#elif defined(SHA_POLARSSL)
-  union {
-    sha1_context sha1;       /* SHA1 block                                */
-    sha2_context sha2;       /* SHA256 block                              */
-  } u;
-
 #elif defined(SHA_NATIVE)
   sha1 sha1;
 #endif
diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile
index 3bc535720..d25d5ede0 100644
--- a/src/src/miscmods/Makefile
+++ b/src/src/miscmods/Makefile
@@ -25,10 +25,28 @@ miscmods.a:    $(OBJ)
         $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c


 .c.so:;         @echo "$(CC) -shared $*.c"
-        $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@
-
-dmarc.o dmarc.so:    $(HDRS) ../pdkim/pdkim.h dmarc.h dmarc.c
+        $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \
+            -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
+            $(DLFLAGS) $*.c -o $@
+
+# Note that the sources from pdkim/ are linked into the build.../miscmods/ dir
+# by scripts/Makelinks.
+dkim.o  dkim.so:    $(HDRS) dkim.h dkim.c dkim_transport.c \
+            crypt_ver.h pdkim.h pdkim_hash.h pdkim.c \
+            signing.h signing.c
+dmarc.o dmarc.so:    $(HDRS) pdkim.h dmarc.h dmarc.c
 dummy.o:        dummy.c
-spf.o spf.so:        $(HDRS) spf.h spf.c
+spf.o   spf.so:        $(HDRS) spf.h spf.c
+
+dkim.o:
+        @echo "$(CC) dkim.c dkim_transport.c pdkim.c signing.c"
+        $(FE)$(CC) -r $(CFLAGS) $(INCLUDE) \
+            dkim.c dkim_transport.c pdkim.c signing.c -o $@
+
+dkim.so:
+        @echo "$(CC) -shared dkim.c dkim_transport.c pdkim.c signing.c"
+        $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \
+            -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
+            $(DLFLAGS) dkim.c dkim_transport.c pdkim.c signing.c -o $@


# End
diff --git a/src/src/miscmods/README b/src/src/miscmods/README
index e534a4eed..d1b7a1632 100644
--- a/src/src/miscmods/README
+++ b/src/src/miscmods/README
@@ -49,6 +49,7 @@ The variables table defins $variables for expansion, using the same
definition entry struct as the main var_table in expand.c;
entries here should have their proper vtype_<type> and should be duplicated
in the main table but with vtype_module and the module name.
+Entries must be in order by the variable name.

 There are service functions to locate and to locate-or-load modules
 by name; these hide the static/dynamic aspect of a module.  Most
@@ -79,6 +80,14 @@ Write an include file with anything callers need to know, in particular
 and add it to HDRS and PHDRS in OS/Makefile-Base.
 Add a SUPPORT_<foo> line to Local/Makefile, and (if dynamic) any
 SUPPORT_<foo>_INCLUDE or SUPPORT_<foo>_LIBS required.
-Add the capitalised module name <foo> to the "miscmods" like in
+Add the capitalised module name <foo> to the "miscmods" line in
 scripts/Configure-Makefile.
 Add all the filenames to the "miscmods" list in scripts/Makelinks
+
+For statically-linked modules the SUPPORT_<foo> line should say "yes",
+for dynamic: "2".
+
+If include-by-default is wanted for the module, use DISABLE_<foo> instead
+of SUPPORT_<foo> (and leave it commented out as appropriate), and prefix
+the name in the "miscmods" line with an underbar ("_").
+For dynamic builds, a SUPPORT_ line is still needed.
diff --git a/src/src/dkim.c b/src/src/miscmods/dkim.c
similarity index 65%
rename from src/src/dkim.c
rename to src/src/miscmods/dkim.c
index 68f074889..38677097b 100644
--- a/src/src/dkim.c
+++ b/src/src/miscmods/dkim.c
@@ -10,14 +10,15 @@
 /* Code for DKIM support. Other DKIM relevant code is in
    receive.c, transport.c and transports/smtp.c */


-#include "exim.h"
+#include "../exim.h"

#ifndef DISABLE_DKIM

-# include "pdkim/pdkim.h"
+# include "pdkim.h"
+# include "signing.h"

# ifdef MACRO_PREDEF
-# include "macro_predef.h"
+# include "../macro_predef.h"

 void
 params_dkim(void)
@@ -27,25 +28,56 @@ builtin_macro_create_var(US"_DKIM_OVERSIGN_HEADERS", US PDKIM_OVERSIGN_HEADERS);
 }
 # else    /*!MACRO_PREDEF*/


+/* Options */

+uschar *dkim_verify_hashes    = US"sha256:sha512";
+uschar *dkim_verify_keytypes    = US"ed25519:rsa";
+uschar *dkim_verify_min_keysizes = US"rsa=1024 ed25519=250";
+BOOL    dkim_verify_minimal    = FALSE;
+uschar *dkim_verify_signers    = US"$dkim_signers";
+
+/* $variables */
+
+uschar *dkim_cur_signer        = NULL;
+int     dkim_key_length        = 0;
+uschar *dkim_signers        = NULL;
+uschar *dkim_signing_domain    = NULL;
+uschar *dkim_signing_selector    = NULL;
+uschar *dkim_verify_reason    = NULL;
+uschar *dkim_verify_status    = NULL;
+
+/* Working variables */
+
+unsigned dkim_collect_input    = 0;
+void   *dkim_signatures        = NULL;
+gstring *dkim_signing_record    = NULL;
+uschar *dkim_vdom_firstpass    = NULL;
+
+
+extern BOOL    dkim_transport_write_message(transport_ctx *,
+                  struct ob_dkim *, const uschar ** errstr);
+
+/****************************************/


pdkim_ctx dkim_sign_ctx;

int dkim_verify_oldpool;
-pdkim_ctx *dkim_verify_ctx = NULL;
+pdkim_ctx * dkim_verify_ctx = NULL;
pdkim_signature *dkim_cur_sig = NULL;
static const uschar * dkim_collect_error = NULL;

#define DKIM_MAX_SIGNATURES 20
+static void dkim_exim_verify_pause(BOOL pause);


+/****************************************/

/* Look up the DKIM record in DNS for the given hostname.
Will use the first found if there are multiple.
The return string is tainted, having come from off-site.
*/

-uschar *
+static uschar *
 dkim_exim_query_dns_txt(const uschar * name)
 {
 dns_answer * dnsa = store_get_dns_answer();
@@ -93,20 +125,93 @@ return NULL;    /*XXX better error detail?  logging? */
 }



-void
-dkim_exim_init(void)
+
+/* Module API:  Lookup a DNS DKIM record and parse the pubkey.
+
+Arguments:
+    dnsname        record to lookup in DNS
+    pubkey_p    pointer for return of pubkey
+    hashes_p    pointer for return of hashes
+
+Return: srvtype, or NULL on error
+*/
+
+static const uschar *
+dkim_exim_parse_dns_pubkey(const uschar * dnsname, blob ** pubkey_p,
+  const uschar ** hashes_p)
+{
+const uschar * dnstxt = dkim_exim_query_dns_txt(dnsname);
+pdkim_pubkey * p;
+
+if (!dnstxt)
+  {
+  DEBUG(D_acl) debug_printf_indent("pubkey dns lookup fail\n");
+  return NULL;
+  }
+if (!(p = pdkim_parse_pubkey_record(dnstxt)))
+  {
+  DEBUG(D_acl) debug_printf_indent("pubkey dns record format error\n");
+  return NULL;
+  }
+*pubkey_p = &p->key;
+*hashes_p = p->hashes;
+return p->srvtype;
+}
+
+
+
+
+/* Return:
+    OK    verify succesful
+    FAIL    verify did not pass
+    ERROR    problem setting up the pubkey
+*/
+
+static int
+dkim_exim_sig_verify(const blob * sighash, const blob * data_hash,
+  hashmethod hash, const blob * pubkey, const uschar ** errstr_p)
 {
-if (f.dkim_init_done) return;
+ev_ctx vctx;
+const uschar * errstr;
+int rc = OK;
+
+if ((errstr = exim_dkim_verify_init(pubkey, KEYFMT_DER, &vctx, NULL)))
+  rc = ERROR;
+else if ((errstr = exim_dkim_verify(&vctx, hash, data_hash, sighash)))
+  rc = FAIL;
+
+*errstr_p = errstr;
+return rc;
+}
+
+
+
+/****************************************/
+
+static BOOL
+dkim_exim_init(void * dummy)
+{
+if (f.dkim_init_done) return TRUE;
 f.dkim_init_done = TRUE;
 pdkim_init();
+return TRUE;
 }




-void
-dkim_exim_verify_init(BOOL dot_stuffing)
+/* Module API: Set up for verification of a message being received.
+Always returns OK.
+*/
+
+static int
+dkim_exim_verify_init(void)
{
-dkim_exim_init();
+BOOL dot_stuffing = chunking_state <= CHUNKING_OFFERED;
+
+if (!smtp_input || smtp_batched_input || f.dkim_disable_verify)
+ return OK;
+
+dkim_exim_init(NULL);

/* There is a store-reset between header & body reception for the main pool
(actually, after every header line) so cannot use that as we need the data we
@@ -126,6 +231,7 @@ if (dkim_verify_ctx)
/* Create new context */

dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
+dkim_exim_verify_pause(FALSE);
dkim_collect_input = dkim_verify_ctx ? DKIM_MAX_SIGNATURES : 0;
dkim_collect_error = NULL;

@@ -134,18 +240,21 @@ receive_get_cache(chunking_state == CHUNKING_LAST
           ? chunking_data_left : GETC_BUFFER_UNLIMITED);


store_pool = dkim_verify_oldpool;
+return OK;
}


-/* Submit a chunk of data for verification input.
+/* Module API : Submit a chunk of data for verification input.
+A NULL data pointer indicates end-of-message.
Only use the data when the feed is activated. */
-void
-dkim_exim_verify_feed(uschar * data, int len)
+
+static void
+dkim_exim_verify_feed(const uschar * data, unsigned len)
{
int rc;

 store_pool = POOL_MESSAGE;
-if (  dkim_collect_input
+if (  (dkim_collect_input || !data)
    && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
   {
   dkim_collect_error = pdkim_errstr(rc);
@@ -157,6 +266,74 @@ store_pool = dkim_verify_oldpool;
 }



+/* Module API: pause/resume the verification data feed */
+
+static void
+dkim_exim_verify_pause(BOOL pause)
+{
+static unsigned save = 0;
+static BOOL paused = FALSE;
+
+if (!pause)
+  {
+  if (paused)
+    { dkim_collect_input = save; paused = FALSE; }
+  }
+else
+  if (!paused)
+    { save = dkim_collect_input; dkim_collect_input = 0; paused = TRUE; }
+}
+
+/* Module API: Finish off the body hashes, calculate sigs and do compares */
+
+static void
+dkim_exim_verify_finish(void)
+{
+int rc;
+gstring * g = NULL;
+const uschar * errstr = NULL;
+
+store_pool = POOL_MESSAGE;
+
+/* Delete eventual previous signature chain */
+
+dkim_signers = NULL;
+dkim_signatures = NULL;
+
+if (dkim_collect_error)
+  {
+  log_write(0, LOG_MAIN,
+      "DKIM: Error during validation, disabling signature verification: %.100s",
+      dkim_collect_error);
+  f.dkim_disable_verify = TRUE;
+  goto out;
+  }
+
+dkim_collect_input = 0;
+
+/* Finish DKIM operation and fetch link to signatures chain */
+
+rc = pdkim_feed_finish(dkim_verify_ctx, (pdkim_signature **)&dkim_signatures,
+            &errstr);
+if (rc != PDKIM_OK && errstr)
+  log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
+
+/* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
+
+for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
+  {
+  if (sig->domain)   g = string_append_listele(g, ':', sig->domain);
+  if (sig->identity) g = string_append_listele(g, ':', sig->identity);
+  }
+gstring_release_unused(g);
+dkim_signers = string_from_gstring(g);
+
+out:
+store_pool = dkim_verify_oldpool;
+}
+
+
+
 /* Log the result for the given signature */
 static void
 dkim_exim_verify_log_sig(pdkim_signature * sig)
@@ -168,12 +345,12 @@ if (!sig) return;


/* Remember the domain for the first pass result */

-if (  !dkim_verify_overall
+if (  !dkim_vdom_firstpass
    && dkim_verify_status
       ? Ustrcmp(dkim_verify_status, US"pass") == 0
       : sig->verify_status == PDKIM_VERIFY_PASS
    )
-  dkim_verify_overall = string_copy(sig->domain);
+  dkim_vdom_firstpass= string_copy(sig->domain);


/* Rewrite the sig result if the ACL overrode it. This is only
needed because the DMARC code (sigh) peeks at the dkim sigs.
@@ -294,7 +471,8 @@ return;
}


-/* Log a line for each signature */
+/* Module API: Log a line for each signature */
+
void
dkim_exim_verify_log_all(void)
{
@@ -303,55 +481,22 @@ for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
}


-void
-dkim_exim_verify_finish(void)
-{
-int rc;
-gstring * g = NULL;
-const uschar * errstr = NULL;
-
-store_pool = POOL_MESSAGE;
-
-/* Delete eventual previous signature chain */
-
-dkim_signers = NULL;
-dkim_signatures = NULL;
-
-if (dkim_collect_error)
-  {
-  log_write(0, LOG_MAIN,
-      "DKIM: Error during validation, disabling signature verification: %.100s",
-      dkim_collect_error);
-  f.dkim_disable_verify = TRUE;
-  goto out;
-  }
-
-dkim_collect_input = 0;
-
-/* Finish DKIM operation and fetch link to signatures chain */
-
-rc = pdkim_feed_finish(dkim_verify_ctx, (pdkim_signature **)&dkim_signatures,
-            &errstr);
-if (rc != PDKIM_OK && errstr)
-  log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
-
-/* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
-
-for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
-  {
-  if (sig->domain)   g = string_append_listele(g, ':', sig->domain);
-  if (sig->identity) g = string_append_listele(g, ':', sig->identity);
-  }
-gstring_release_unused(g);
-dkim_signers = string_from_gstring(g);
+/* Module API: append a log element with domain for the first passing sig */


-out:
-store_pool = dkim_verify_oldpool;
+gstring *
+dkim_exim_vdom_firstpass(gstring * g)
+{
+if (dkim_vdom_firstpass)
+ g = string_append(g, 2, US" DKIM=", dkim_vdom_firstpass);
+return g;
}


+/* For one signature, run the DKIM ACL, log the sig result,
+and append ths sig status to the status list.
+
+Args as per dkim_exim_acl_run() below */

-/* Args as per dkim_exim_acl_run() below */
 static int
 dkim_acl_call(uschar * id, gstring ** res_ptr,
   uschar ** user_msgptr, uschar ** log_msgptr)
@@ -387,7 +532,7 @@ Returns:       OK         access is granted by an ACCEPT verb
                ERROR      disaster
 */


-int
+static int
dkim_exim_acl_run(uschar * id, gstring ** res_ptr,
uschar ** user_msgptr, uschar ** log_msgptr)
{
@@ -442,6 +587,146 @@ return dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr);
}


+/* Module API:
+Loop over dkim_verify_signers option doing ACL calls.  If one return any
+non-OK value stop and return that, else return OK.
+*/
+
+int
+    /*XXX need a user_msgptr */
+dkim_exim_acl_entry(uschar ** user_msgptr, uschar ** log_msgptr)
+{
+int rc = OK;
+
+GET_OPTION("dkim_verify_signers");
+if (dkim_verify_signers && *dkim_verify_signers)
+  {
+  const uschar * dkim_verify_signers_expanded =
+              expand_cstring(dkim_verify_signers);
+  gstring * results = NULL, * seen_items = NULL;
+  int signer_sep = 0, old_pool = store_pool;
+
+  if (!dkim_verify_signers_expanded)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "expansion of dkim_verify_signers option failed: %s",
+      expand_string_message);
+    return DEFER;
+    }
+
+  store_pool = POOL_PERM;   /* Allow created variables to live to data ACL */
+
+  /* Loop over signers we want to verify, calling ACL.  Default to OK
+  when no signers are present.  Each call from here expands to an ACL
+  call per matching sig in the message. */
+
+  for (uschar * item;
+      item = string_nextinlist(&dkim_verify_signers_expanded,
+                    &signer_sep, NULL, 0); )
+    {
+    /* Prevent running ACL for an empty item */
+    if (!item || !*item) continue;
+
+    /* Only run ACL once for each domain or identity,
+    no matter how often it appears in the expanded list. */
+    if (seen_items)
+      {
+      uschar * seen_item;
+      const uschar * seen_items_list = string_from_gstring(seen_items);
+      int seen_sep = ':';
+      BOOL seen_this_item = FALSE;
+
+      while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
+                        NULL, 0)))
+    if (Ustrcmp(seen_item, item) == 0)
+      {
+      seen_this_item = TRUE;
+      break;
+      }
+
+      if (seen_this_item)
+    {
+    DEBUG(D_receive)
+      debug_printf("acl_smtp_dkim: skipping signer %s, "
+        "already seen\n", item);
+    continue;
+    }
+
+      seen_items = string_catn(seen_items, US":", 1);
+      }
+    seen_items = string_cat(seen_items, item);
+
+    if ((rc = dkim_exim_acl_run(item, &results, user_msgptr, log_msgptr)) != OK)
+      {
+      DEBUG(D_receive)
+    debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+      "skipping remaining items\n", rc, item);
+      break;
+      }
+    if (dkim_verify_minimal && Ustrcmp(dkim_verify_status, "pass") == 0)
+      break;
+    }            /* signers loop */
+
+  dkim_verify_status = string_from_gstring(results);
+  store_pool = old_pool;
+  }
+else
+  dkim_exim_verify_log_all();
+
+return rc;
+}
+
+/******************************************************************************/
+
+/* Module API */
+
+static int
+dkim_exim_signer_isinlist(const uschar * l)
+{
+return dkim_cur_signer
+  ? match_isinlist(dkim_cur_signer, &l, 0, NULL, NULL, MCL_STRING, TRUE, NULL)
+  : FAIL;
+}
+
+/* Module API */
+
+static int
+dkim_exim_status_listmatch(const uschar * l)
+{                        /* return good for any match */
+const uschar * s = dkim_verify_status ? dkim_verify_status : US"none";
+int sep = 0, rc = FAIL;
+for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); )
+  if (   (rc = match_isinlist(ss, &l, 0, NULL, NULL, MCL_STRING, TRUE, NULL))
+      == OK) break;
+return rc;
+}
+
+/* Module API: Overwriteable dkim result variables */
+
+static void
+dkim_exim_setvar(const uschar * name, void * val)
+{
+if (Ustrcmp(name, "dkim_verify_status") == 0)
+  dkim_verify_status = val;
+else if (Ustrcmp(name, "dkim_verify_reason") == 0)
+  dkim_verify_reason = val;
+}
+
+/******************************************************************************/
+
+static void
+dkim_smtp_reset(void)
+{
+dkim_cur_signer = dkim_signers =
+dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
+f.dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
+dkim_vdom_firstpass = dkim_verify_status = dkim_verify_reason = NULL;
+dkim_key_length = 0;
+}
+
+/******************************************************************************/
+
 static uschar *
 dkim_exim_expand_defaults(int what)
 {
@@ -468,6 +753,8 @@ switch (what)
 }



+/* Module API: return a computed value for a variable expansion */
+
uschar *
dkim_exim_expand_query(int what)
{
@@ -585,12 +872,14 @@ switch (what)
}


-void
+/* Module API */
+
+static void
dkim_exim_sign_init(void)
{
int old_pool = store_pool;

-dkim_exim_init();
+dkim_exim_init(NULL);
store_pool = POOL_MAIN;
pdkim_init_context(&dkim_sign_ctx, FALSE, &dkim_exim_query_dns_txt);
store_pool = old_pool;
@@ -834,6 +1123,95 @@ expand_bad:



+#ifdef SUPPORT_DMARC
+
+/* Module API */
+
+static const pdkim_signature *
+dkim_sigs_list(void)
+{
+return dkim_signatures;
+}
+#endif
+
+#ifdef EXPERIMENTAL_ARC
+
+/* Module API */
+static int
+dkim_hashname_to_type(const blob * name)
+{
+return pdkim_hashname_to_hashtype(name->data, name->len);
+}
+
+/* Module API */
+hashmethod
+dkim_hashtype_to_method(int hashtype)
+{
+return hashtype >= 0 ? pdkim_hashes[hashtype].exim_hashmethod : -1;
+}
+
+/* Module API */
+hashmethod
+dkim_hashname_to_method(const blob * name)
+{
+return dkim_hashtype_to_method(dkim_hashname_to_type(name));
+}
+
+/*  Module API: Set up a body hashing method on the given signature-context
+(creates a new one if needed, or uses an already-present one).
+
+Arguments:
+    signing        TRUE to use dkim's signing context, else dkim_verify_ctx
+        canon        canonicalization spec, text form
+        hash        hash spec, text form
+        bodylen         byte count for message body
+
+Return: pointer to hashing method struct
+*/
+
+static pdkim_bodyhash *
+dkim_set_bodyhash(BOOL signing,
+  const blob * canon, const blob * hashname, long bodylen)
+{
+int canon_head = -1, canon_body = -1;
+
+pdkim_cstring_to_canons(canon->data, canon->len, &canon_head, &canon_body);
+return pdkim_set_bodyhash(signing ? &dkim_sign_ctx: dkim_verify_ctx,
+        dkim_hashname_to_type(hashname),
+        canon_body,
+        bodylen);
+}
+
+/* Module API: Sign a blob of data (which might already be a hash, if
+Ed25519 or GCrypt signing).
+
+Arguments:
+    data        to be signed
+    hm        hash to be applied to the data
+    privkey        private key for siging, PEM format
+    signature    pointer for result blob
+
+Return: NULL, or error string on failure
+*/
+
+static const uschar *
+dkim_sign_blob(const blob * data, hashmethod hm, const uschar * privkey,
+  blob * signature)
+{
+es_ctx sctx;
+const uschar * errstr;
+
+if ((errstr = exim_dkim_signing_init(privkey, &sctx)))
+  { DEBUG(D_transport) debug_printf("signing key setup: %s\n", errstr); }
+else errstr = exim_dkim_sign(&sctx, hm, data, signature);
+
+return errstr;
+}
+
+#endif    /*EXPERIMENTAL_ARC*/
+
+
+/* Module API */


gstring *
authres_dkim(gstring * g)
@@ -909,6 +1287,93 @@ DEBUG(D_acl)
return g;
}

+/******************************************************************************/
+/* Module API */
+
+static optionlist dkim_options[] = {
+  { "acl_smtp_dkim",        opt_stringptr,   {&acl_smtp_dkim} },
+  { "dkim_verify_hashes",       opt_stringptr,   {&dkim_verify_hashes} },
+  { "dkim_verify_keytypes",     opt_stringptr,   {&dkim_verify_keytypes} },
+  { "dkim_verify_min_keysizes", opt_stringptr,   {&dkim_verify_min_keysizes} },
+  { "dkim_verify_minimal",      opt_bool,        {&dkim_verify_minimal} },
+  { "dkim_verify_signers",      opt_stringptr,   {&dkim_verify_signers} },
+};
+
+static void * dkim_functions[] = {
+  [DKIM_VERIFY_FEED] =        dkim_exim_verify_feed,
+  [DKIM_VERIFY_PAUSE] =        dkim_exim_verify_pause,
+  [DKIM_VERIFY_FINISH] =    dkim_exim_verify_finish,
+  [DKIM_ACL_ENTRY] =        dkim_exim_acl_entry,
+  [DKIM_VERIFY_LOG_ALL] =    dkim_exim_verify_log_all,
+  [DKIM_VDOM_FIRSTPASS] =    dkim_exim_vdom_firstpass,
+
+  [DKIM_SIGNER_ISINLIST] =    dkim_exim_signer_isinlist,
+  [DKIM_STATUS_LISTMATCH] =    dkim_exim_status_listmatch,
+
+  [DKIM_SETVAR] =        dkim_exim_setvar,
+  [DKIM_EXPAND_QUERY] =        dkim_exim_expand_query,
+
+  [DKIM_TRANSPORT_INIT] =    dkim_exim_sign_init,
+  [DKIM_TRANSPORT_WRITE] =    dkim_transport_write_message,
+
+#ifdef SUPPORT_DMARC
+  [DKIM_SIGS_LIST] =        dkim_sigs_list,
+#endif
+#ifdef EXPERIMENTAL_ARC
+  [DKIM_HASHNAME_TO_TYPE] =    dkim_hashname_to_type,
+  [DKIM_HASHTYPE_TO_METHOD] =    dkim_hashtype_to_method,
+  [DKIM_HASHNAME_TO_METHOD] =    dkim_hashname_to_method,
+  [DKIM_SET_BODYHASH] =        dkim_set_bodyhash,
+  [DKIM_DNS_PUBKEY] =        dkim_exim_parse_dns_pubkey,
+  [DKIM_SIG_VERIFY] =        dkim_exim_sig_verify,
+  [DKIM_HEADER_RELAX] =        pdkim_relax_header_n,
+  [DKIM_SIGN_DATA] =        dkim_sign_blob,
+#endif
+};
+
+static var_entry dkim_variables[] = {
+  { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
+  { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
+  { "dkim_canon_body",     vtype_dkim,        (void *)DKIM_CANON_BODY },
+  { "dkim_canon_headers",  vtype_dkim,        (void *)DKIM_CANON_HEADERS },
+  { "dkim_copiedheaders",  vtype_dkim,        (void *)DKIM_COPIEDHEADERS },
+  { "dkim_created",        vtype_dkim,        (void *)DKIM_CREATED },
+  { "dkim_cur_signer",     vtype_stringptr,   &dkim_cur_signer },
+  { "dkim_domain",         vtype_stringptr,   &dkim_signing_domain },
+  { "dkim_expires",        vtype_dkim,        (void *)DKIM_EXPIRES },
+  { "dkim_headernames",    vtype_dkim,        (void *)DKIM_HEADERNAMES },
+  { "dkim_identity",       vtype_dkim,        (void *)DKIM_IDENTITY },
+  { "dkim_key_granularity",vtype_dkim,        (void *)DKIM_KEY_GRANULARITY },
+  { "dkim_key_length",     vtype_int,         &dkim_key_length },
+  { "dkim_key_nosubdomains",vtype_dkim,       (void *)DKIM_NOSUBDOMAINS },
+  { "dkim_key_notes",      vtype_dkim,        (void *)DKIM_KEY_NOTES },
+  { "dkim_key_srvtype",    vtype_dkim,        (void *)DKIM_KEY_SRVTYPE },
+  { "dkim_key_testing",    vtype_dkim,        (void *)DKIM_KEY_TESTING },
+  { "dkim_selector",       vtype_stringptr,   &dkim_signing_selector },
+  { "dkim_signers",        vtype_stringptr,   &dkim_signers },
+  { "dkim_verify_reason",  vtype_stringptr,   &dkim_verify_reason },
+  { "dkim_verify_status",  vtype_stringptr,   &dkim_verify_status },
+};
+
+misc_module_info dkim_module_info = {
+  .name =        US"dkim",
+# if SUPPORT_DKIM==2
+  .dyn_magic =        MISC_MODULE_MAGIC,
+# endif
+  .init =        dkim_exim_init,
+  .msg_init =        dkim_exim_verify_init,
+  .authres =        authres_dkim,
+  .smtp_reset =        dkim_smtp_reset,
+
+  .options =        dkim_options,
+  .options_count =    nelem(dkim_options),
+
+  .functions =        dkim_functions,
+  .functions_count =    nelem(dkim_functions),
+
+  .variables =        dkim_variables,
+  .variables_count =    nelem(dkim_variables),
+};


 # endif    /*!MACRO_PREDEF*/
 #endif    /*!DISABLE_DKIM*/
diff --git a/src/src/miscmods/dkim.h b/src/src/miscmods/dkim.h
new file mode 100644
index 000000000..aa14d58d2
--- /dev/null
+++ b/src/src/miscmods/dkim.h
@@ -0,0 +1,47 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge, 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+gstring * dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
+uschar *dkim_exim_expand_query(int);
+
+
+#define DKIM_ALGO               1
+#define DKIM_BODYLENGTH         2
+#define DKIM_CANON_BODY         3
+#define DKIM_CANON_HEADERS      4
+#define DKIM_COPIEDHEADERS      5
+#define DKIM_CREATED            6
+#define DKIM_EXPIRES            7
+#define DKIM_HEADERNAMES        8
+#define DKIM_IDENTITY           9
+#define DKIM_KEY_GRANULARITY   10
+#define DKIM_KEY_SRVTYPE       11
+#define DKIM_KEY_NOTES         12
+#define DKIM_KEY_TESTING       13
+#define DKIM_NOSUBDOMAINS      14
+#define DKIM_VERIFY_STATUS     15
+#define DKIM_VERIFY_REASON     16
+
+
+extern unsigned dkim_collect_input;    /* Runtime count of dkim signtures; tracks whether SMTP input is fed to DKIM validation */
+extern uschar *dkim_cur_signer;        /* Expansion variable, holds the current "signer" domain or identity during a acl_smtp_dkim run */
+extern int     dkim_key_length;        /* Expansion variable, length of signing key in bits */
+extern void   *dkim_signatures;        /* Actually a (pdkim_signature *) but most files do not need to know */
+extern uschar *dkim_signers;           /* Expansion variable, holds colon-separated list of domains and identities that have signed a message */
+extern gstring *dkim_signing_record;   /* domains+selectors used */
+extern uschar *dkim_signing_domain;    /* Expansion variable, domain used for signing a message. */
+extern uschar *dkim_signing_selector;  /* Expansion variable, selector used for signing a message. */
+extern uschar *dkim_verify_hashes;     /* Preference order for signatures */
+extern uschar *dkim_verify_keytypes;   /* Preference order for signatures */
+extern uschar *dkim_verify_min_keysizes; /* list of minimum key sizes, keyed by algo */
+extern BOOL    dkim_verify_minimal;    /* Shortcircuit signature verification */
+extern uschar *dkim_vdom_firstpass;    /* First successful domain verified, or null */
+extern uschar *dkim_verify_signers;    /* Colon-separated list of domains for each of which we call the DKIM ACL */
+extern uschar *dkim_verify_status;     /* result for this signature */
+extern uschar *dkim_verify_reason;     /* result for this signature */
+
diff --git a/src/src/miscmods/dkim_api.h b/src/src/miscmods/dkim_api.h
new file mode 100644
index 000000000..54e1141ff
--- /dev/null
+++ b/src/src/miscmods/dkim_api.h
@@ -0,0 +1,36 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2024 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the dkim module */
+
+
+/* Function table entry numbers */
+
+#define DKIM_VERIFY_FEED    0
+#define DKIM_VERIFY_PAUSE    1
+#define DKIM_VERIFY_FINISH    2
+#define DKIM_ACL_ENTRY        3
+#define DKIM_VERIFY_LOG_ALL    4
+#define DKIM_VDOM_FIRSTPASS    5
+#define DKIM_SIGNER_ISINLIST    6
+#define DKIM_STATUS_LISTMATCH    7
+#define DKIM_SETVAR        8
+#define DKIM_EXPAND_QUERY    9
+#define DKIM_TRANSPORT_INIT    10
+#define DKIM_TRANSPORT_WRITE    11
+
+#define DKIM_SIGS_LIST        12
+
+#define DKIM_HASHNAME_TO_TYPE    13
+#define DKIM_HASHTYPE_TO_METHOD    14
+#define DKIM_HASHNAME_TO_METHOD    15
+#define DKIM_SET_BODYHASH    16
+#define DKIM_DNS_PUBKEY        17
+#define DKIM_SIG_VERIFY        18
+#define DKIM_HEADER_RELAX    19
+#define DKIM_SIGN_DATA        20
diff --git a/src/src/dkim_transport.c b/src/src/miscmods/dkim_transport.c
similarity index 98%
rename from src/src/dkim_transport.c
rename to src/src/miscmods/dkim_transport.c
index 63870c57f..0500da2be 100644
--- a/src/src/dkim_transport.c
+++ b/src/src/miscmods/dkim_transport.c
@@ -10,7 +10,7 @@
 /* Transport shim for dkim signing */



-#include "exim.h"
+#include "../exim.h"

 #ifndef DISABLE_DKIM    /* rest of file */


@@ -154,7 +154,8 @@ if (!rc) return FALSE;
/* Get signatures for headers plus spool data file */

 #ifdef EXPERIMENTAL_ARC
-arc_sign_init();
+arc_sign_init();    /*XXX perhaps move this call back to the smtp tpt
+          around where it currently calls arc_ams_setup_sign_bodyhash() ? */
 #endif


/* The dotstuffed status of the datafile depends on whether it was stored
@@ -395,7 +396,7 @@ if ( !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)

/* If there is no filter command set up, construct the message and calculate
a dkim signature of it, send the signature and a reconstructed message. This
-avoids using a temprary file. */
+avoids using a temporary file. */

 if (  !transport_filter_argv
    || !*transport_filter_argv
diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c
index 37648d045..4a8beab66 100644
--- a/src/src/miscmods/dmarc.c
+++ b/src/src/miscmods/dmarc.c
@@ -22,7 +22,7 @@


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

 OPENDMARC_LIB_T     dmarc_ctx;
 DMARC_POLICY_T     *dmarc_pctx = NULL;
@@ -32,7 +32,8 @@ BOOL dmarc_abort  = FALSE;
 uschar *dmarc_pass_fail = US"skipped";
 header_line *from_header   = NULL;


-misc_module_info * spf_mod_info;
+static misc_module_info * dmarc_dkim_mod_info;
+static misc_module_info * dmarc_spf_mod_info;
 SPF_response_t   *spf_response_p;
 int dmarc_spf_ares_result  = 0;
 uschar *spf_sender_domain  = NULL;
@@ -73,9 +74,19 @@ 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);
+if (!(dmarc_spf_mod_info = misc_mod_find(US"spf", &errstr)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
+  return FALSE;
+  }
+
+/*XXX not yet used, but will be */
+if (!(dmarc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
+  return FALSE;
+  }
+
 return TRUE;
 }


@@ -188,6 +199,8 @@ return OK;
 static void
 dmarc_smtp_reset(void)
 {
+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;
 }
@@ -394,7 +407,6 @@ 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 */
@@ -453,6 +465,7 @@ if (!dmarc_abort && !sender_host_authenticated)
   {
   uschar * dmarc_domain;
   gstring * dkim_history_buffer = NULL;
+  typedef const pdkim_signature * (*sigs_fn_t)(void);


/* Use the envelope sender domain for this part of DMARC */

@@ -460,9 +473,9 @@ if (!dmarc_abort && !sender_host_authenticated)

     {
     typedef SPF_response_t * (*fn_t)(void);
-    if (spf_mod_info)
+    if (dmarc_spf_mod_info)
       /*XXX ugly use of a pointer */
-      spf_response_p = ((fn_t *) spf_mod_info->functions)[SPF_GET_RESPONSE]();
+      spf_response_p = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESPONSE]();
     }


if (!spf_response_p)
@@ -519,7 +532,9 @@ if (!dmarc_abort && !sender_host_authenticated)
/* 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)
+  for(const pdkim_signature * sig =
+          (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])();
+      sig; sig = sig->next)
     {
     int dkim_result, dkim_ares_result, vs, ves;


@@ -749,7 +764,6 @@ static optionlist dmarc_options[] = {
 static void * dmarc_functions[] = {
   [DMARC_PROCESS] =    dmarc_process,
   [DMARC_EXPAND_QUERY] = dmarc_exim_expand_query,
-  [DMARC_AUTHRES] =    authres_dmarc,
   [DMARC_STORE_DATA] =    dmarc_store_data,
 };


@@ -775,6 +789,7 @@ misc_module_info dmarc_module_info =
   .lib_vers_report =    dmarc_version_report,
   .smtp_reset =        dmarc_smtp_reset,
   .msg_init =        dmarc_msg_init,
+  .authres =        authres_dmarc,


   .options =        dmarc_options,
   .options_count =    nelem(dmarc_options),
diff --git a/src/src/miscmods/dmarc_api.h b/src/src/miscmods/dmarc_api.h
index 6ba8a5060..9d9ef62da 100644
--- a/src/src/miscmods/dmarc_api.h
+++ b/src/src/miscmods/dmarc_api.h
@@ -13,5 +13,4 @@


 #define    DMARC_PROCESS        0
 #define DMARC_EXPAND_QUERY    1
-#define DMARC_AUTHRES        2
-#define DMARC_STORE_DATA    3
+#define DMARC_STORE_DATA    2
diff --git a/src/src/pdkim/Makefile b/src/src/miscmods/pdkim/Makefile
similarity index 100%
rename from src/src/pdkim/Makefile
rename to src/src/miscmods/pdkim/Makefile
diff --git a/src/src/pdkim/README b/src/src/miscmods/pdkim/README
similarity index 100%
rename from src/src/pdkim/README
rename to src/src/miscmods/pdkim/README
diff --git a/src/src/pdkim/crypt_ver.h b/src/src/miscmods/pdkim/crypt_ver.h
similarity index 100%
rename from src/src/pdkim/crypt_ver.h
rename to src/src/miscmods/pdkim/crypt_ver.h
diff --git a/src/src/pdkim/pdkim.c b/src/src/miscmods/pdkim/pdkim.c
similarity index 99%
rename from src/src/pdkim/pdkim.c
rename to src/src/miscmods/pdkim/pdkim.c
index 42e67e6aa..cdbdfc5e0 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/miscmods/pdkim/pdkim.c
@@ -298,6 +298,7 @@ return PDKIM_FAIL;



/* -------------------------------------------------------------------------- */
+/* Module API */
/* Performs "relaxed" canonicalization of a header. */

uschar *
@@ -422,7 +423,7 @@ b->len = dlen;
uschar *
pdkim_encode_base64(blob * b)
{
-return b64encode(CUS b->data, b->len);
+return b64encode(b->data, b->len);
}


@@ -1005,13 +1006,13 @@ return PDKIM_OK;
#define HEADER_BUFFER_FRAG_SIZE 256

DLLEXPORT int
-pdkim_feed(pdkim_ctx * ctx, uschar * data, int len)
+pdkim_feed(pdkim_ctx * ctx, const uschar * data, unsigned len)
{
/* Alternate EOD signal, used in non-dotstuffing mode */
if (!data)
pdkim_body_complete(ctx);

-else for (int p = 0; p < len; p++)
+else for (unsigned p = 0; p < len; p++)
{
uschar c = data[p];
int rc;
@@ -2008,6 +2009,12 @@ pdkim_bodyhash * b;

if (hashtype == -1 || canon_method == -1) return NULL;

+if (!ctx)
+  {
+  DEBUG(D_receive) debug_printf("pdkim_set_bodyhash: null context\n");
+  return NULL;
+  }
+
 for (b = ctx->bodyhash; b; b = b->next)
   if (  hashtype == b->hashtype
      && canon_method == b->canon_method
@@ -2073,7 +2080,7 @@ DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
 void
 pdkim_init(void)
 {
-exim_dkim_init();
+exim_dkim_signers_init();
 }



diff --git a/src/src/pdkim/pdkim.h b/src/src/miscmods/pdkim/pdkim.h
similarity index 99%
rename from src/src/pdkim/pdkim.h
rename to src/src/miscmods/pdkim/pdkim.h
index 5f91d3bc7..b86014f8f 100644
--- a/src/src/pdkim/pdkim.h
+++ b/src/src/miscmods/pdkim/pdkim.h
@@ -344,7 +344,7 @@ pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, int, int, long);
pdkim_bodyhash *pdkim_set_sig_bodyhash(pdkim_ctx *, pdkim_signature *);

 DLLEXPORT
-int        pdkim_feed         (pdkim_ctx *, uschar *, int);
+int        pdkim_feed         (pdkim_ctx *, const uschar *, unsigned);
 DLLEXPORT
 int        pdkim_feed_finish  (pdkim_ctx *, pdkim_signature **, const uschar **);


diff --git a/src/src/pdkim/pdkim_hash.h b/src/src/miscmods/pdkim/pdkim_hash.h
similarity index 100%
rename from src/src/pdkim/pdkim_hash.h
rename to src/src/miscmods/pdkim/pdkim_hash.h
diff --git a/src/src/pdkim/signing.c b/src/src/miscmods/pdkim/signing.c
similarity index 95%
rename from src/src/pdkim/signing.c
rename to src/src/miscmods/pdkim/signing.c
index b564fb929..44f2e12ac 100644
--- a/src/src/pdkim/signing.c
+++ b/src/src/miscmods/pdkim/signing.c
@@ -64,7 +64,7 @@ DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,


void
-exim_dkim_init(void)
+exim_dkim_signers_init(void)
{
#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
DEBUG(D_tls)
@@ -125,10 +125,16 @@ return NULL;
/* allocate mem for signature (when signing) */
/* hash & sign data. No way to do incremental.

+Arguments:
+    sign_ctx    library-specific context for a signature (incl. key)
+    hash        hash method to apply to data
+    data        data to be signed
+    sig        returned signature
+
 Return: NULL for success, or an error string */


const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, const blob * data, blob * sig)
{
gnutls_datum_t k_data = { .data = data->data, .size = data->len };
gnutls_digest_algorithm_t dig;
@@ -159,7 +165,7 @@ return NULL;
Return: NULL for success, or an error string */

const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+exim_dkim_verify_init(const blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
unsigned * bits)
{
gnutls_datum_t k;
@@ -197,7 +203,8 @@ return ret;
Return: NULL for success, or an error string */

 const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, const blob * data_hash,
+  const blob * sig)
 {
 gnutls_datum_t k = { .data = data_hash->data, .size = data_hash->len };
 gnutls_datum_t s = { .data = sig->data,       .size = sig->len };
@@ -301,7 +308,7 @@ return NULL;



void
-exim_dkim_init(void)
+exim_dkim_signers_init(void)
{
/* Version check should be the very first call because it
makes sure that important subsystems are initialized. */
@@ -484,7 +491,7 @@ asn_err: return US asn1_strerror(rc);
Return: NULL for success, or an error string */

const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, const blob * data, blob * sig)
{
char * sexp_hash;
gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
@@ -559,7 +566,7 @@ return NULL;
Return: NULL for success, or an error string */

const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+exim_dkim_verify_init(const blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
unsigned * bits)
{
/*
@@ -648,7 +655,8 @@ XXX though we appear to be doing a hash, too!
Return: NULL for success, or an error string */

const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, const blob * data_hash,
+ const blob * sig)
{
/*
cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
@@ -702,7 +710,7 @@ return NULL;
/******************************************************************************/

void
-exim_dkim_init(void)
+exim_dkim_signers_init(void)
{
ERR_load_crypto_strings();
}
@@ -747,7 +755,7 @@ return NULL;
Return: NULL for success with the signaature in the sig blob, or an error string */

const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, const blob * data, blob * sig)
{
const EVP_MD * md;
EVP_MD_CTX * ctx;
@@ -804,7 +812,7 @@ return US ERR_error_string(ERR_get_error(), NULL);
Return: NULL for success, or an error string */

const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+exim_dkim_verify_init(const blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
unsigned * bits)
{
const uschar * s = pubkey->data;
@@ -841,7 +849,8 @@ return ret;
Return: NULL for success, or an error string */

const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data, blob * sig)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, const blob * data,
+ const blob * sig)
{
const EVP_MD * md;

diff --git a/src/src/pdkim/signing.h b/src/src/miscmods/pdkim/signing.h
similarity index 83%
rename from src/src/pdkim/signing.h
rename to src/src/miscmods/pdkim/signing.h
index 7760ce73f..ce801a395 100644
--- a/src/src/pdkim/signing.h
+++ b/src/src/miscmods/pdkim/signing.h
@@ -86,13 +86,15 @@ typedef struct {
#endif


-extern void exim_dkim_init(void);
+extern void exim_dkim_signers_init(void);
extern gstring * exim_dkim_data_append(gstring *, uschar *);

 extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *);
-extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *);
-extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *, unsigned *);
-extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *);
+extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, const blob *, blob *);
+extern const uschar * exim_dkim_verify_init(const blob *, keyformat, ev_ctx *,
+            unsigned *);
+extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, const blob *,
+            const blob *);


 #endif    /*DISABLE_DKIM*/
 /* End of File */
diff --git a/src/src/miscmods/spf.c b/src/src/miscmods/spf.c
index ea23c1c65..14937e799 100644
--- a/src/src/miscmods/spf.c
+++ b/src/src/miscmods/spf.c
@@ -566,7 +566,6 @@ static optionlist spf_options[] = {


 static void * spf_functions[] = {
   [SPF_PROCESS] =    spf_process,
-  [SPF_AUTHRES] =    authres_spf,
   [SPF_GET_RESPONSE] =    spf_get_response,        /* ugly; for dmarc */


   [SPF_OPEN] =        spf_lookup_open,
@@ -593,6 +592,7 @@ misc_module_info spf_module_info =
   .lib_vers_report =    spf_lib_version_report,
   .conn_init =        spf_conn_init,
   .smtp_reset =        spf_smtp_reset,
+  .authres =        authres_spf,


   .options =        spf_options,
   .options_count =    nelem(spf_options),
diff --git a/src/src/miscmods/spf_api.h b/src/src/miscmods/spf_api.h
index 3801e3e3f..117a7ae6a 100644
--- a/src/src/miscmods/spf_api.h
+++ b/src/src/miscmods/spf_api.h
@@ -6,13 +6,12 @@
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */


-/* API definitions for the spfmodule */
+/* API definitions for the spf module */


/* Function table entry numbers */

 #define    SPF_PROCESS        0
-#define SPF_AUTHRES        1
 #define SPF_GET_RESPONSE    2
 #define SPF_OPEN        3
 #define SPF_CLOSE        4
diff --git a/src/src/pdkim/config.h b/src/src/pdkim/config.h
deleted file mode 100644
index fdd4cfe4f..000000000
--- a/src/src/pdkim/config.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#define POLARSSL_BASE64_C
-
-
-
diff --git a/src/src/readconf.c b/src/src/readconf.c
index ae7073229..5aabd0194 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -48,7 +48,7 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_data_prdr",       opt_stringptr,   {&acl_smtp_data_prdr} },
 #endif
 #ifndef DISABLE_DKIM
-  { "acl_smtp_dkim",            opt_stringptr,   {&acl_smtp_dkim} },
+  { "acl_smtp_dkim",            opt_module,     {US"dkim"} },
 #endif
   { "acl_smtp_etrn",            opt_stringptr,   {&acl_smtp_etrn} },
   { "acl_smtp_expn",            opt_stringptr,   {&acl_smtp_expn} },
@@ -122,11 +122,11 @@ static optionlist optionlist_config[] = {
 #endif
   { "disable_ipv6",             opt_bool,        {&disable_ipv6} },
 #ifndef DISABLE_DKIM
-  { "dkim_verify_hashes",       opt_stringptr,   {&dkim_verify_hashes} },
-  { "dkim_verify_keytypes",     opt_stringptr,   {&dkim_verify_keytypes} },
-  { "dkim_verify_min_keysizes", opt_stringptr,   {&dkim_verify_min_keysizes} },
-  { "dkim_verify_minimal",      opt_bool,        {&dkim_verify_minimal} },
-  { "dkim_verify_signers",      opt_stringptr,   {&dkim_verify_signers} },
+  { "dkim_verify_hashes",       opt_module,     {US"dkim"} },
+  { "dkim_verify_keytypes",     opt_module,     {US"dkim"} },
+  { "dkim_verify_min_keysizes", opt_module,     {US"dkim"} },
+  { "dkim_verify_minimal",      opt_module,     {US"dkim"} },
+  { "dkim_verify_signers",      opt_module,     {US"dkim"} },
 #endif
 #ifdef SUPPORT_DMARC
   { "dmarc_forensic_sender",    opt_module,     {US"dmarc"} },
diff --git a/src/src/receive.c b/src/src/receive.c
index a6b7722bf..541e9320d 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1828,13 +1828,6 @@ mime_is_rfc822         = 0;
 mime_part_count        = -1;
 #endif


-#ifndef DISABLE_DKIM
-/* Call into DKIM to set up the context. In CHUNKING mode
-we clear the dot-stuffing flag */
-if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
- dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
-#endif
-
if (misc_mod_msg_init() != OK)
goto TIDYUP;

@@ -3517,100 +3510,47 @@ else
 #ifndef DISABLE_DKIM
     if (!f.dkim_disable_verify)
       {
-      /* Finish off the body hashes, calculate sigs and do compares */
-      dkim_exim_verify_finish();
+      misc_module_info * mi = misc_mod_findonly(US"dkim");
+      if (mi)
+    {
+    typedef void (*vfin_fn_t)(void);
+    typedef int  (*vacl_fn_t)(uschar **, uschar**);
+    typedef void (*vlog_fn_t)(void);


-      /* Check if we must run the DKIM ACL */
-      GET_OPTION("acl_smtp_dkim");
-      if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
-        {
-        uschar * dkim_verify_signers_expanded =
-          expand_string(dkim_verify_signers);
-    gstring * results = NULL, * seen_items = NULL;
-    int signer_sep = 0, old_pool = store_pool;
-    const uschar * ptr;
-    uschar * item;
-
-    store_pool = POOL_PERM;   /* Allow created variables to live to data ACL */
-
-        if (!(ptr = dkim_verify_signers_expanded))
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "expansion of dkim_verify_signers option failed: %s",
-            expand_string_message);
-
-    /* Loop over signers we want to verify, calling ACL.  Default to OK
-    when no signers are present.  Each call from here expands to a n ACL
-    call per matching sig in the message. */
-
-    rc = OK;
-    while ((item = string_nextinlist(&ptr, &signer_sep, NULL, 0)))
-      {
-      /* Prevent running ACL for an empty item */
-      if (!item || !*item) continue;
+    /* Finish off the body hashes, calculate sigs and do compares */


-      /* Only run ACL once for each domain or identity,
-      no matter how often it appears in the expanded list. */
-      if (seen_items)
-        {
-        uschar * seen_item;
-        const uschar * seen_items_list = string_from_gstring(seen_items);
-        int seen_sep = ':';
-        BOOL seen_this_item = FALSE;
-
-        while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
-                          NULL, 0)))
-          if (Ustrcmp(seen_item,item) == 0)
-        {
-        seen_this_item = TRUE;
-        break;
-        }
-
-        if (seen_this_item)
-          {
-          DEBUG(D_receive)
-        debug_printf("acl_smtp_dkim: skipping signer %s, "
-          "already seen\n", item);
-          continue;
-          }
+    (((vfin_fn_t *) mi->functions)[DKIM_VERIFY_FINISH]) ();


-        seen_items = string_catn(seen_items, US":", 1);
-        }
-      seen_items = string_cat(seen_items, item);
+    /* Check if we must run the DKIM ACL */
+
+    GET_OPTION("acl_smtp_dkim");
+    if (acl_smtp_dkim)
+      {
+      rc = (((vacl_fn_t *) mi->functions)[DKIM_ACL_ENTRY])
+                            (&user_msg, &log_msg);
+      add_acl_headers(ACL_WHERE_DKIM, US"DKIM");


-      rc = dkim_exim_acl_run(item, &results, &user_msg, &log_msg);
       if (rc != OK)
         {
-        DEBUG(D_receive)
-          debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
-        "skipping remaining items\n", rc, item);
         cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
-        break;
+
+        if (rc != DISCARD)
+          {
+          Uunlink(spool_name);
+          if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
+        smtp_yield = FALSE;    /* No more msgs after dropped conn */
+          smtp_reply = US"";    /* Indicate reply already sent */
+          goto NOT_ACCEPTED;    /* Skip to end of function */
+          }
+        recipients_count = 0;
+        blackholed_by = US"DKIM ACL";
+        if (log_msg)
+          blackhole_log_msg = string_sprintf(": %s", log_msg);
         }
-      else
-        if (dkim_verify_minimal && Ustrcmp(dkim_verify_status, "pass") == 0)
-          break;
-      }
-    dkim_verify_status = string_from_gstring(results);
-    store_pool = old_pool;
-    add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
-    if (rc == DISCARD)
-      {
-      recipients_count = 0;
-      blackholed_by = US"DKIM ACL";
-      if (log_msg)
-        blackhole_log_msg = string_sprintf(": %s", log_msg);
-      }
-    else if (rc != OK)
-      {
-      Uunlink(spool_name);
-      if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
-        smtp_yield = FALSE;    /* No more messages after dropped connection */
-      smtp_reply = US"";       /* Indicate reply already sent */
-      goto NOT_ACCEPTED;            /* Skip to end of function */
       }
-        }
-      else                /* No acl or no wanted signers */
-    dkim_exim_verify_log_all();
+    else    /* No ACL; just log */
+      (((vlog_fn_t *) mi->functions)[DKIM_VERIFY_LOG_ALL]) ();
+    }
       }
 #endif /* DISABLE_DKIM */


@@ -4189,8 +4129,13 @@ if (LOGGING(8bitmime))
g = string_fmt_append(g, " M8S=%d", body_8bitmime);

 #ifndef DISABLE_DKIM
-if (LOGGING(dkim) && dkim_verify_overall)
-  g = string_append(g, 2, US" DKIM=", dkim_verify_overall);
+if (LOGGING(dkim))
+  {
+  misc_module_info * mi = misc_mod_findonly(US"dkim");
+  typedef gstring * (*fn_t)(gstring *);
+  if (mi)
+    g = (((fn_t *) mi->functions)[DKIM_VDOM_FIRSTPASS]) (g);
+  }
 # ifdef EXPERIMENTAL_ARC
 if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0)
   g = string_catn(g, US" ARC", 4);
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index adf6c59cb..e75894850 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -464,6 +464,23 @@ smtp_had_eof = smtp_had_error = 0;




+#ifndef DISABLE_DKIM
+/* Feed received message data to the dkim module */
+/*XXX maybe a global dkim_info? */
+void
+smtp_verify_feed(const uschar * s, unsigned n)
+{
+static misc_module_info * dkim_mi = NULL;
+typedef void (*fn_t)(const uschar *, int);
+
+if (!dkim_mi && !(dkim_mi = misc_mod_findonly(US"dkim")))
+ return;
+
+(((fn_t *) dkim_mi->functions)[DKIM_VERIFY_FEED]) (s, n);
+}
+#endif
+
+
/* Refill the buffer, and notify DKIM verification code.
Return false for error or EOF.
*/
@@ -507,7 +524,7 @@ if (rc <= 0)
return FALSE;
}
#ifndef DISABLE_DKIM
-dkim_exim_verify_feed(smtp_inbuffer, rc);
+smtp_verify_feed(smtp_inbuffer, rc);
#endif
smtp_inend = smtp_inbuffer + rc;
smtp_inptr = smtp_inbuffer;
@@ -570,7 +587,7 @@ int n = smtp_inend - smtp_inptr;
if (n > lim)
n = lim;
if (n > 0)
- dkim_exim_verify_feed(smtp_inptr, n);
+ smtp_verify_feed(smtp_inptr, n);
#endif
}

@@ -726,19 +743,24 @@ bdat_getc(unsigned lim)
uschar * user_msg = NULL;
uschar * log_msg;

-for(;;)
- {
#ifndef DISABLE_DKIM
- unsigned dkim_save;
+misc_module_info * dkim_info = misc_mod_findonly(US"dkim");
+typedef void (*dkim_pause_t)(BOOL);
+dkim_pause_t dkim_pause;
+
+dkim_pause = dkim_info
+ ? ((dkim_pause_t *) dkim_info->functions)[DKIM_VERIFY_PAUSE] : NULL;
#endif

+for(;;)
+  {
+
   if (chunking_data_left > 0)
     return lwr_receive_getc(chunking_data_left--);


bdat_pop_receive_functions();
#ifndef DISABLE_DKIM
- dkim_save = dkim_collect_input;
- dkim_collect_input = 0;
+ if (dkim_pause) dkim_pause(TRUE);
#endif

   /* Unless PIPELINING was offered, there should be no next command
@@ -767,9 +789,7 @@ for(;;)
   if (chunking_state == CHUNKING_LAST)
     {
 #ifndef DISABLE_DKIM
-    dkim_collect_input = dkim_save;
-    dkim_exim_verify_feed(NULL, 0);    /* notify EOD */
-    dkim_collect_input = 0;
+    smtp_verify_feed(NULL, 0);    /* notify EOD */
 #endif
     return EOD;
     }
@@ -843,7 +863,7 @@ next_cmd:


       bdat_push_receive_functions();
 #ifndef DISABLE_DKIM
-      dkim_collect_input = dkim_save;
+      if (dkim_pause) dkim_pause(FALSE);
 #endif
       break;    /* to top of main loop */
       }
@@ -1681,17 +1701,6 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
-#ifndef DISABLE_DKIM
-dkim_cur_signer = dkim_signers =
-dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
-f.dkim_disable_verify = FALSE;
-dkim_collect_input = 0;
-dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
-dkim_key_length = 0;
-#endif
-#ifdef SUPPORT_DMARC
-f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE;
-#endif
 #ifdef EXPERIMENTAL_ARC
 arc_state = arc_state_reason = NULL;
 arc_received_instance = 0;
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index bb54571be..43b30986d 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -269,9 +269,18 @@ bmi_verdicts = NULL;
 #endif


#ifndef DISABLE_DKIM
-dkim_signers = NULL;
f.dkim_disable_verify = FALSE;
+# ifdef COMPILE_UTILITY
+dkim_signers = NULL;
dkim_collect_input = 0;
+#else
+ {
+ misc_module_info * mi = misc_mod_findonly(US"dkim");
+ /* We used to clear only dkim_signers, dkim_collect_input. This does more
+ but I think it is safe. */
+ if (mi) mi->smtp_reset();
+ }
+# endif
#endif

 #ifndef DISABLE_TLS
diff --git a/src/src/string.c b/src/src/string.c
index 2b62233d8..dfda2d405 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1688,7 +1688,7 @@ while (*fp)
         case '}' : zg = string_catn(zg, US"{BC}", 4); break;
         default:
           {
-          unsigned char u = *s;
+          uschar u = *s;
           if ( (u < 32) || (u > 127) )
         zg = string_fmt_append(zg, "{%02x}", u);
           else
diff --git a/src/src/structs.h b/src/src/structs.h
index 46abac728..ef311b677 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -1026,6 +1026,7 @@ typedef struct misc_module_info {
   int        (*conn_init)(const uschar *, const uschar *);
   void        (*smtp_reset)(void);
   int        (*msg_init)(void);
+  gstring *    (*authres)(gstring *);


   void *    options;
   unsigned    options_count;
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 06cd4a5f8..7963e2c97 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -3904,7 +3904,7 @@ else if (inbytes < 0)
   return FALSE;
   }
 #ifndef DISABLE_DKIM
-dkim_exim_verify_feed(state->xfer_buffer, inbytes);
+smtp_verify_feed(state->xfer_buffer, inbytes);
 #endif
 state->xfer_buffer_hwm = (int) inbytes;
 state->xfer_buffer_lwm = 0;
@@ -3980,7 +3980,7 @@ int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm;
 if (n > lim)
   n = lim;
 if (n > 0)
-  dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
+  smtp_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
 #endif
 }


diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 033bd0e10..302404b6c 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -4545,7 +4545,7 @@ switch(error)
}

#ifndef DISABLE_DKIM
-dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+smtp_verify_feed(ssl_xfer_buffer, inbytes);
#endif
ssl_xfer_buffer_hwm = inbytes;
ssl_xfer_buffer_lwm = 0;
@@ -4615,7 +4615,7 @@ int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm;
if (n > lim)
n = lim;
if (n > 0)
- dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n);
+ smtp_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n);
#endif
}

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index d25a2b1f6..cdd2a404f 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -46,6 +46,7 @@ optionlist smtp_transport_options[] = {
   { "data_timeout",         opt_time,       LOFF(data_timeout) },
   { "delay_after_cutoff",   opt_bool,       LOFF(delay_after_cutoff) },
 #ifndef DISABLE_DKIM
+  /*XXX dkim module */
   { "dkim_canon", opt_stringptr,       LOFF(dkim.dkim_canon) },
   { "dkim_domain", opt_stringptr,       LOFF(dkim.dkim_domain) },
   { "dkim_hash", opt_stringptr,           LOFF(dkim.dkim_hash) },
@@ -4103,13 +4104,18 @@ else


 #ifndef DISABLE_DKIM
   {
+  typedef void (*fn_t)(void);
+  misc_module_info * mi;
 # ifdef MEASURE_TIMING
   struct timeval t0;
   gettimeofday(&t0, NULL);
 # endif
-  dkim_exim_sign_init();
-# ifdef EXPERIMENTAL_ARC
+
+  if ((mi = misc_mod_find(US"dkim", NULL)))
     {
+    (((fn_t *) mi->functions)[DKIM_TRANSPORT_INIT]) ();
+
+# ifdef EXPERIMENTAL_ARC
     uschar * s = ob->arc_sign;
     if (s)
       {
@@ -4129,8 +4135,8 @@ else
     ob->dkim.force_bodyhash = TRUE;
     }
       }
+# endif    /*ARC*/
     }
-# endif
 # ifdef MEASURE_TIMING
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
@@ -4175,7 +4181,15 @@ else
     }


 #ifndef DISABLE_DKIM
-  sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
+    {
+    misc_module_info * mi = misc_mod_find(US"dkim", NULL);
+    typedef BOOL (*fn_t)(transport_ctx *, struct ob_dkim *, const uschar **);
+
+    sx->ok = mi
+      ? (((fn_t *) mi->functions)[DKIM_TRANSPORT_WRITE])
+                      (&tctx, &ob->dkim, CUSS &message)
+      : transport_write_message(&tctx, 0);
+    }
 #else
   sx->ok = transport_write_message(&tctx, 0);
 #endif
diff --git a/test/runtest b/test/runtest
index 70499312d..83659ea19 100755
--- a/test/runtest
+++ b/test/runtest
@@ -1560,8 +1560,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 '(?:dmarc|spf)'$/;
-    next if /^$time_pid?Loaded "(?:dmarc|spf)"$/;
+    next if /loading module '(?:dkim|dmarc|spf)'$/;
+    next if /^$time_pid?Loaded "(?:dkim|dmarc|spf)"$/;


     # Not all platforms have sendfile support
     next if /^cannot use sendfile for body: no support$/;
@@ -4147,7 +4147,6 @@ system("sudo cp eximdir/exim eximdir/exim_exim;" .
        "sudo chmod 06755 eximdir/exim_exim");


# Copy any libraries that were built for dynamic load
-# Currently this is only for lookup methods

($parm_exim_dir) = $parm_exim =~ m?^(.*)/exim?;


--
## 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/