[exim-cvs] arc dynamic module

Αρχική Σελίδα
Delete this message
Reply to this message
Συντάκτης: Exim Git Commits Mailing List
Ημερομηνία:  
Προς: exim-cvs
Αντικείμενο: [exim-cvs] arc dynamic module
Gitweb: https://git.exim.org/exim.git/commitdiff/9b604221c5e94f8146f48e47a76865c11eedb7a1
Commit:     9b604221c5e94f8146f48e47a76865c11eedb7a1
Parent:     23e95e18a9b2e5e2cfbd2e3548c0107edb4f92a8
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Fri Sep 6 12:29:23 2024 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Fri Sep 6 12:31:18 2024 +0100


    arc dynamic module
---
 doc/doc-txt/NewStuff                      |   6 +-
 doc/doc-txt/experimental-spec.txt         |   2 +
 src/OS/Makefile-Base                      |   6 +-
 src/scripts/Configure-Makefile            |   2 +-
 src/scripts/MakeLinks                     |   3 +-
 src/src/acl.c                             |  67 ++++++---
 src/src/config.h.defaults                 |   1 +
 src/src/drtables.c                        |  13 +-
 src/src/exim.c                            |   3 -
 src/src/exim.h                            |   3 +
 src/src/expand.c                          |  11 +-
 src/src/functions.h                       |  16 --
 src/src/globals.c                         |   8 -
 src/src/globals.h                         |   7 -
 src/src/miscmods/Makefile                 |   1 +
 src/src/{ => miscmods}/arc.c              | 237 ++++++++++++++++++------------
 src/src/miscmods/{spf_api.h => arc_api.h} |  13 +-
 src/src/miscmods/dkim_transport.c         |  40 +++--
 src/src/miscmods/dmarc.c                  |  27 +++-
 src/src/miscmods/pdkim/pdkim.c            |  32 ++--
 src/src/miscmods/spf_api.h                |   8 +-
 src/src/receive.c                         |  11 +-
 src/src/smtp_in.c                         |   5 +-
 src/src/transports/smtp.c                 |  37 ++---
 24 files changed, 333 insertions(+), 226 deletions(-)


diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 1189ce3f3..5220408e8 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, DKIM and DMARC 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, DMARC and ARC 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/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index 56ee10f82..a73007700 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -498,6 +498,8 @@ Enable using EXPERIMENTAL_ARC=yes in your Local/Makefile.
You must also have DKIM present (not disabled), and you very likely
want to have SPF enabled.

+It is possible to build as a dynamic-load module: set also SUPPORT_ARC=2.
+

Verification
--
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 12319967e..857c44776 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -495,8 +495,7 @@ transport-filter.pl: config ../src/transport-filter.src
# are thrown away by the linker.

 OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o
-OBJ_EXPERIMENTAL =    arc.o \
-            bmi_spam.o \
+OBJ_EXPERIMENTAL =    bmi_spam.o \
             dane.o \
             dcc.o \
             imap_utf7.o \
@@ -685,6 +684,7 @@ HDRS  =    blob.h \
     hintsdb/hints_tdb.h \
     local_scan.h \
     macros.h \
+    miscmods/arc_api.h \
     miscmods/dkim_api.h \
     miscmods/dmarc_api.h \
     miscmods/spf_api.h \
@@ -707,6 +707,7 @@ PHDRS = ../config.h \
     ../hintsdb/hints_tdb.h \
     ../local_scan.h \
     ../macros.h \
+    ../miscmods/arc_api.h \
     ../miscmods/dkim_api.h \
     ../miscmods/dmarc_api.h \
     ../miscmods/spf_api.h \
@@ -900,7 +901,6 @@ spool_mbox.o:    $(HDRS) spool_mbox.c


# Dependencies for EXPERIMENTAL_* modules

-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
diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile
index 12f0ddd9c..1eb79a291 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    _DKIM DMARC SPF
+ miscmods   SUPPORT    ARC _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 a6521a95e..481f36fe3 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -97,6 +97,7 @@ mkdir $d
 cd $d
 # Makefile is generated
 for f in dummy.c \
+    arc.c arc_api.h \
     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 \
@@ -149,7 +150,7 @@ do
 done


 # EXPERIMENTAL_*
-for f in  arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
+for f in  bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
   danessl.h imap_utf7.c utf8.c xclient.c
 do
   ln -s ../src/$f $f
diff --git a/src/src/acl.c b/src/src/acl.c
index 878278313..18d892ec5 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -207,9 +207,17 @@ static condition_def conditions[] = {
   [ACLC_DELAY] =        { US"delay",        ACD_EXP | ACD_MOD,
                   FORBIDDEN(ACL_BIT_NOTQUIT) },
 #ifndef DISABLE_DKIM
-  [ACLC_DKIM_SIGNER] =        { US"dkim_signers",    ACD_EXP, 
+  [ACLC_DKIM_SIGNER] =        { US"dkim_signers",
+# if SUPPORT_DKIM==2
+                  ACD_LOAD |
+# endif
+                  ACD_EXP, 
                   PERMITTED(ACL_BIT_DKIM) },
-  [ACLC_DKIM_STATUS] =        { US"dkim_status",    ACD_EXP,
+  [ACLC_DKIM_STATUS] =        { US"dkim_status",
+# if SUPPORT_DKIM==2
+                  ACD_LOAD |
+# endif
+                  ACD_EXP,
                   PERMITTED(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME
 # ifndef DISABLE_PRDR
                   | ACL_BIT_PRDR
@@ -394,6 +402,7 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
 }
 #endif


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

#ifndef MACRO_PREDEF

@@ -410,20 +419,31 @@ typedef struct condition_module {
# if SUPPORT_SPF==2
static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 };
# endif
+# if SUPPORT_DKIM==2
+static int dkim_condx[] = { ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, -1 };
+# endif
# if SUPPORT_DMARC==2
static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 };
# endif

+/* These are modules which can be loaded on seeing an ACL condition
+during readconf, The "arc" module is handled by custom coding. */
+
static condition_module condition_modules[] = {
# if SUPPORT_SPF==2
{.mod_name = US"spf", .conditions = spf_condx},
# endif
-# if SUPPORT_SPF==2
+# if SUPPORT_DKIM==2
+ {.mod_name = US"dkim", .conditions = dkim_condx},
+# endif
+# if SUPPORT_DMARC==2
{.mod_name = US"dmarc", .conditions = dmarc_condx},
# endif
};

-# endif
+# endif    /*LOOKUP_MODULE_DIR*/
+
+/****************************/


/* Return values from decode_control() */

@@ -933,7 +953,7 @@ while ((s = (*func)()))

   if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0)
     {
-    if (!this)
+    if (!this)        /* not handling a verb right now */
       {
       *error = string_sprintf("unknown ACL verb \"%s\" in \"%s\"", name,
         saveline);
@@ -1002,6 +1022,9 @@ while ((s = (*func)()))
     condition_module * cm;
     uschar * s = NULL;


+    /* Over the list of modules we support, check the list of ACL conditions
+    each supports.  This assumes no duplicates. */
+
     for (cm = condition_modules;
         cm < condition_modules + nelem(condition_modules); cm++)
       for (const int * cond = cm->conditions; *cond != -1; cond++)
@@ -1022,7 +1045,21 @@ while ((s = (*func)()))
       return NULL;
       }
     }
-#endif
+# ifdef EXPERIMENTAL_ARC
+  else if (c == ACLC_VERIFY)    /* Special handling for verify=arc; */
+    {    /* not invented a more general method yet- flag in verify_type_list? */
+    const uschar * t = s;
+    uschar * e;
+    if (  *t++ == '=' && Uskip_whitespace(&t) && Ustrncmp(t, "arc", 3) == 0
+       && !misc_mod_find(US"arc", &e))
+      {
+      *error = string_sprintf("ACL error: failed to find module for '%s': %s",
+                  conditions[c].name, e);
+      return NULL;
+      }
+    }
+# endif
+#endif    /*LOOKUP_MODULE_DIR*/


cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
cond->next = NULL;
@@ -1876,19 +1913,11 @@ switch(vp->value)

 #ifdef EXPERIMENTAL_ARC
   case VERIFY_ARC:
-    {    /* Do Authenticated Received Chain checks in a separate function. */
-    const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0);
-    int csep = 0;
-    uschar * cond;
-
-    if (!(arc_state = acl_verify_arc())) return DEFER;
-    DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
-      arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");
-
-    if (!condlist) condlist = US"none:pass";
-    while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
-      if (Ustrcmp(arc_state, cond) == 0) return OK;
-    return FAIL;
+    {
+    const misc_module_info * mi = misc_mod_findonly(US"arc");
+    typedef int (*fn_t)(const uschar *);
+    if (mi) return (((fn_t *) mi->functions)[ARC_VERIFY])
+                (CUS string_nextinlist(&list, &sep, NULL, 0));
     }
 #endif


diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index d602886a0..20a288d66 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -169,6 +169,7 @@ Do not put spaces between # and the 'define'.

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

#define SYSLOG_LOG_PID
#define SYSLOG_LONG_LINES
diff --git a/src/src/drtables.c b/src/src/drtables.c
index 61ced3e6a..32765aedc 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -441,6 +441,7 @@ if (mi->init && mi->init(mi))
}
else DEBUG(D_any)
debug_printf_indent("module init call failed for %s\n", mi->name);
+/* fprintf(stderr,"misc_mod_add: added %s\n", mi->name); */
}


@@ -746,6 +747,9 @@ extern misc_module_info dmarc_module_info;
#if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
extern misc_module_info spf_module_info;
#endif
+#if defined(EXPERIMENTAL_ARC) && (!defined(SUPPORT_ARC) || SUPPORT_ARC!=2)
+extern misc_module_info arc_module_info;
+#endif

void
init_misc_mod_list(void)
@@ -755,14 +759,17 @@ if (onetime) return;
onetime = TRUE;

#if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2)
-misc_mod_add(&dkim_module_info);
+ misc_mod_add(&dkim_module_info);
#endif
#if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
-misc_mod_add(&spf_module_info);
+ 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);
+ misc_mod_add(&dmarc_module_info);
+#endif
+#if defined(EXPERIMENTAL_ARC) && (!defined(SUPPORT_ARC) || SUPPORT_ARC!=2)
+ misc_mod_add(&arc_module_info);
#endif
}

diff --git a/src/src/exim.c b/src/src/exim.c
index ca98e25de..2349260df 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -4211,9 +4211,6 @@ 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
diff --git a/src/src/exim.h b/src/src/exim.h
index 8260dc75f..f5043aea9 100644
--- a/src/src/exim.h
+++ b/src/src/exim.h
@@ -555,6 +555,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
# include "miscmods/dmarc_api.h"
# include <opendmarc/dmarc.h>
#endif
+#ifdef EXPERIMENTAL_ARC
+# include "miscmods/arc_api.h"
+#endif

 /* The following stuff must follow the inclusion of config.h because it
 requires various things that are set therein. */
diff --git a/src/src/expand.c b/src/src/expand.c
index 02680771f..a41ba98cf 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -454,10 +454,10 @@ static var_entry var_table[] = {
   { "address_file",        vtype_stringptr,   &address_file },
   { "address_pipe",        vtype_stringptr,   &address_pipe },
 #ifdef EXPERIMENTAL_ARC
-  { "arc_domains",         vtype_string_func, (void *) &fn_arc_domains },
-  { "arc_oldest_pass",     vtype_int,         &arc_oldest_pass },
-  { "arc_state",           vtype_stringptr,   &arc_state },
-  { "arc_state_reason",    vtype_stringptr,   &arc_state_reason },
+  { "arc_domains",         vtype_module,    US"arc" },
+  { "arc_oldest_pass",     vtype_module,    US"arc" },
+  { "arc_state",           vtype_module,    US"arc" },
+  { "arc_state_reason",    vtype_module,    US"arc" },
 #endif
   { "authenticated_fail_id",vtype_stringptr,  &authenticated_fail_id },
   { "authenticated_id",    vtype_stringptr,   &authenticated_id },
@@ -4890,9 +4890,6 @@ while (*s)
       yield = authres_iprev(yield);
       yield = authres_smtpauth(yield);
       yield = misc_mod_authres(yield);
-#ifdef EXPERIMENTAL_ARC
-      yield = authres_arc(yield);
-#endif
       break;
       }


diff --git a/src/src/functions.h b/src/src/functions.h
index 4f4e615ca..deec54590 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -1,4 +1,3 @@
-extern BOOL arc_init(void);
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
@@ -111,18 +110,6 @@ extern void    acl_var_write(uschar *, uschar *, void *);
 extern void    add_driver_info(driver_info **, const driver_info *, size_t);



-#ifdef EXPERIMENTAL_ARC
-# ifdef SUPPORT_DMARC
-extern gstring *arc_dmarc_hist_append(gstring *);
-# endif
-extern void   *arc_ams_setup_sign_bodyhash(void);
-extern const uschar *arc_header_feed(gstring *, BOOL);
-extern gstring *arc_sign(const uschar *, gstring *, uschar **);
-extern void     arc_sign_init(void);
-extern const uschar *acl_verify_arc(void);
-extern uschar * fn_arc_domains(void);
-#endif
-
 extern void    assert_no_variables(void *, int, const char *, int);
 extern int     auth_call_pam(const uschar *, uschar **);
 extern int     auth_call_pwcheck(uschar *, uschar **);
@@ -142,9 +129,6 @@ extern int     auth_read_input(const uschar *);
 extern gstring * auth_show_supported(gstring *);
 extern uschar *authenticator_current_name(void);


-#ifdef EXPERIMENTAL_ARC
-extern gstring *authres_arc(gstring *);
-#endif
extern gstring *authres_smtpauth(gstring *);

 extern uschar *b64encode(const uschar *, int);
diff --git a/src/src/globals.c b/src/src/globals.c
index 6fae1582f..c65ddf413 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -615,14 +615,6 @@ tree_node *addresslist_anchor  = NULL;
 int     addresslist_count      = 0;
 gid_t  *admin_groups           = NULL;


-#ifdef EXPERIMENTAL_ARC
-struct arc_set *arc_received    = NULL;
-int     arc_received_instance    = 0;
-int     arc_oldest_pass        = 0;
-const uschar *arc_state        = NULL;
-const uschar *arc_state_reason    = NULL;
-#endif
-
 uschar *authenticated_fail_id  = NULL;
 uschar *authenticated_id       = NULL;
 uschar *authenticated_sender   = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 1f03cefee..2f2f023e3 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -360,13 +360,6 @@ extern int     addresslist_count;      /* Number defined */
 extern gid_t  *admin_groups;           /* List of admin groups */
 extern BOOL    allow_domain_literals;  /* As it says */
 extern BOOL    allow_mx_to_ip;         /* Allow MX records to -> ip address */
-#ifdef EXPERIMENTAL_ARC
-extern struct arc_set *arc_received;   /* highest ARC instance evaluation struct */
-extern int     arc_received_instance;  /* highest ARC instance number in headers */
-extern int     arc_oldest_pass;        /* lowest passing instance number in headers */
-extern const uschar *arc_state;           /* verification state */
-extern const uschar *arc_state_reason;
-#endif
 extern BOOL    allow_utf8_domains;     /* For experimenting */
 extern uschar *authenticated_fail_id;  /* ID that failed authentication */
 extern uschar *authenticated_id;       /* ID that was authenticated */
diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile
index 3013a88da..64a66276f 100644
--- a/src/src/miscmods/Makefile
+++ b/src/src/miscmods/Makefile
@@ -31,6 +31,7 @@ miscmods.a:    $(OBJ)


 # Note that the sources from pdkim/ are linked into the build.../miscmods/ dir
 # by scripts/Makelinks.
+arc.o    arc.so:        $(HDRS) pdkim.h arc.c
 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
diff --git a/src/src/arc.c b/src/src/miscmods/arc.c
similarity index 92%
rename from src/src/arc.c
rename to src/src/miscmods/arc.c
index a065ca8e3..db546e1ab 100644
--- a/src/src/arc.c
+++ b/src/src/miscmods/arc.c
@@ -8,20 +8,25 @@
    SPDX-License-Identifier: GPL-2.0-or-later
 */


-#include "exim.h"
+#include "../exim.h"
#if defined EXPERIMENTAL_ARC
# if defined DISABLE_DKIM
# error DKIM must also be enabled for ARC
# else

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

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

+struct arc_set *arc_received = NULL;    /* highest ARC instance eval struct */
+int     arc_received_instance = 0;    /* highest ARC instance num in hdrs */
+int     arc_oldest_pass = 0;        /* lowest passing inst num in hdrs */
+const uschar *arc_state = NULL;        /* verification state */
+const uschar *arc_state_reason = NULL;
+
+/******************************************************************************/
 #define ARC_SIGN_OPT_TSTAMP    BIT(0)
 #define ARC_SIGN_OPT_EXPIRE    BIT(1)


@@ -134,20 +139,26 @@ 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().
+(and we may as well stash it's modinfo ptr)
*/
-BOOL
-arc_init(void)
+
+static BOOL
+arc_init(void * dummy)
{
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);
+log_write(0, LOG_MAIN, "arc: %s", errstr);
return FALSE;
}

+static void
+arc_smtp_reset(void)
+{
+arc_state = arc_state_reason = NULL;
+arc_received_instance = 0;
+}
+
/******************************************************************************/


@@ -708,11 +719,7 @@ 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? */
+    (string_sprintf("%b._domainkey.%b", &al->s, &al->d), &pubkey, &hashes);


 if (!srvtype)
   { *errstr = US"pubkey dns lookup fail"; return NULL; }
@@ -734,8 +741,7 @@ if (hashes)
     if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
   if (!ele)
     {
-    DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
-                  hashes, (int)al->a.len, al->a.data);
+    DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%b\n", hashes, &al->a);
     *errstr = US"no usable sig for this pubkey hash list";
     return NULL;
     }
@@ -886,10 +892,9 @@ if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
 DEBUG(D_acl)
   {
   debug_printf("ARC i=%d AMS   Body bytes hashed: %lu\n"
-           "              Body %.*s computed: ",
+           "              Body %b computed: %.*H\n",
            as->instance, b->signed_body_bytes,
-           (int)ams->a_hash.len, ams->a_hash.data);
-  debug_printf("%.*H\n", b->bh.len, b->bh.data);
+           &ams->a_hash, b->bh.len, b->bh.data);
   }


/* We know the bh-tag blob is of a nul-term string, so safe as a string */
@@ -1072,7 +1077,6 @@ for (as2 = ctx->arcset_chain;

   al = as2->hdr_aar;
   if (!(s = al->relaxed))
-    /*XXX dkim module */
     al->relaxed = s = arc_relax_header_n(al->complete->text,
                         al->complete->slen, TRUE);
   len = Ustrlen(s);
@@ -1081,7 +1085,6 @@ for (as2 = ctx->arcset_chain;


   al = as2->hdr_ams;
   if (!(s = al->relaxed))
-    /*XXX dkim module */
     al->relaxed = s = arc_relax_header_n(al->complete->text,
                         al->complete->slen, TRUE);
   len = Ustrlen(s);
@@ -1090,11 +1093,9 @@ for (as2 = ctx->arcset_chain;


   al = as2->hdr_as;
   if (as2->instance == as->instance)
-    /*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))
-    /*XXX dkim module */
     al->relaxed = s = arc_relax_header_n(al->complete->text,
                         al->complete->slen, TRUE);
   len = Ustrlen(s);
@@ -1109,8 +1110,8 @@ for (as2 = ctx->arcset_chain;
 exim_sha_finish(&hhash_ctx, &hhash_computed);
 DEBUG(D_acl)
   {
-  debug_printf("ARC i=%d AS Header %.*s computed: ",
-    as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
+  debug_printf("ARC i=%d AS Header %b computed: ",
+        as->instance, &hdr_as->a_hash);
   debug_printf("%.*H\n", hhash_computed.len, hhash_computed.data);
   }


@@ -1161,13 +1162,17 @@ return NULL;
/******************************************************************************/

 /* Do ARC verification.  Called from DATA ACL, on a verify = arc
-condition.  No arguments; we are checking globals.
+condition.  Set arc_state, and compare with given list of acceptable states.
+
+Arguments:
+    condlist    list of resulta to test for OK/FAIL return;
+            NULL for default list


-Return: The ARC state, or NULL on error.
+Return: OK/FAIL, or DEFER on error
*/

-const uschar *
-acl_verify_arc(void)
+static int
+acl_verify_arc(const uschar * condlist)
{
const uschar * res;

@@ -1246,7 +1251,27 @@ if ((res = arc_verify_seals(&arc_verify_ctx)))
res = US"pass";

 out:
-  return res;
+  {
+  int csep = 0;
+  uschar * cond;
+
+  if (!(arc_state = res))
+    return DEFER;
+
+  DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
+    arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");
+
+  if (!condlist) condlist = US"none:pass";
+  while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
+    if (Ustrcmp(res, cond) == 0) return OK;
+  return FAIL;
+  }
+}
+
+static BOOL
+arc_is_pass(void)
+{
+return arc_state && Ustrcmp(arc_state, "pass") == 0;
 }


/******************************************************************************/
@@ -1348,9 +1373,8 @@ arc_line * al = (arc_line *)(as+1);
header_line * h = (header_line *)(al+1);

 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
-g = string_fmt_append(g, " i=%d; %s; smtp.remote-ip=%s;\r\n\t",
-             instance, identity, sender_host_address);
-g = string_catn(g, US ar->data, ar->len);
+g = string_fmt_append(g, " i=%d; %s; smtp.remote-ip=%s;\r\n\t%b",
+             instance, identity, sender_host_address, ar);


 h->slen = g->ptr - aar_off;
 h->text = g->s + aar_off;
@@ -1503,7 +1527,6 @@ for(col = 3; rheaders; rheaders = rheaders->prev)
       /* Accumulate header for hashing/signing */


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


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

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

@@ -1633,17 +1655,14 @@ for (arc_set * as = Ustrcmp(status, US"fail") == 0
badline_str = US"aar";
if (!(l = as->hdr_aar)) goto badline;
h = l->complete;
- /*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;
- /*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;
- /*XXX dkim module */
hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, !!as->next));
}

@@ -1670,11 +1689,7 @@ badline:

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

-/*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 *
+static pdkim_bodyhash *
 arc_ams_setup_sign_bodyhash(void)
 {
 blob canon = {.data = US"relaxed", .len = 7};    /*XXX hardwired */
@@ -1687,11 +1702,18 @@ return arc_set_bodyhash(TRUE, &canon, &hash, -1);




-void
+/* Module API: initilise, and set up a bodyhash for AMS */
+
+static void
 arc_sign_init(void)
 {
+blob canon = {.data = US"relaxed", .len = 7};    /*XXX hardwired */
+blob hash =  {.data = US"sha256",  .len = 6};    /*XXX hardwired */
+
 memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
 headers_rlist = NULL;
+
+(void) arc_ams_setup_sign_bodyhash();
 }



@@ -1735,7 +1757,9 @@ return TRUE;



-/* ARC signing. Called from the smtp transport, if the arc_sign option is set.
+/* Module API: ARC signing.
+
+Called from the smtp transport, if the arc_sign option is set.
The dkim_exim_sign() function has already been called, so will have hashed the
message body for us so long as we requested a hash previously.

@@ -1751,7 +1775,7 @@ Return value
but not the plainheaders.
*/

-gstring *
+static gstring *
 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
 {
 const uschar * identity, * selector, * privkey, * opts, * s;
@@ -1868,10 +1892,7 @@ 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
+    [done in arc_sign_init()]
 */


b = arc_ams_setup_sign_bodyhash();
@@ -1975,7 +1996,8 @@ badline:



-/* A header line has been identified by DKIM processing.
+/* Module API: A header line has been identified by DKIM processing;
+feed it to ARC processing.

 Arguments:
   g        Header line
@@ -1985,7 +2007,7 @@ Return:
   NULL for success, or an error string (probably unused)
 */


-const uschar *
+static const uschar *
arc_header_feed(gstring * g, BOOL is_vfy)
{
return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
@@ -1997,7 +2019,7 @@ return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);

/* Construct the list of domains from the ARC chain after validation */

-uschar *
+const uschar *
 fn_arc_domains(void)
 {
 arc_set * as;
@@ -2033,7 +2055,6 @@ authres_arc(gstring * g)
 {
 if (arc_state)
   {
-  arc_line * highest_ams;
   int start = 0;        /* Compiler quietening */
   DEBUG(D_acl) start = gstring_length(g);


@@ -2043,11 +2064,10 @@ if (arc_state)
     g = string_fmt_append(g, " (i=%d)", arc_received_instance);
     if (arc_state_reason)
       g = string_append(g, 3, US"(", arc_state_reason, US")");
-    g = string_catn(g, US" header.s=", 10);
-    highest_ams = arc_received->hdr_ams;
-    g = string_catn(g, highest_ams->s.data, highest_ams->s.len);


-    g = string_fmt_append(g, " arc.oldest-pass=%d", arc_oldest_pass);
+    g = string_fmt_append(g, " header.s=%b arc.oldest-pass=%d",
+                &arc_received->hdr_ams->s,
+                arc_oldest_pass);


     if (sender_host_address)
       g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
@@ -2064,62 +2084,87 @@ return g;



# ifdef SUPPORT_DMARC
-/* Append a DMARC history record pair for ARC, to the given history set */

-gstring *
-arc_dmarc_hist_append(gstring * g)
+/* Module API: obtain ARC info for DMARC history.
+Arguments:
+    gp    pointer for return of arcset info string
+Return:
+    status string, or NULL if none
+*/
+
+static const uschar *
+arc_arcset_string(gstring ** gp)
 {
 if (arc_state)
   {
-  BOOL first = TRUE;
-  int i = Ustrcmp(arc_state, "pass") == 0 ? ARES_RESULT_PASS
-      : Ustrcmp(arc_state, "fail") == 0 ? ARES_RESULT_FAIL
-      : ARES_RESULT_UNKNOWN;
-  g = string_fmt_append(g, "arc %d\n", i);
-  g = string_fmt_append(g, "arc_policy %d json[",
-              i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS
-              : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL
-              : DMARC_ARC_POLICY_RESULT_UNUSED);
+  gstring * g = NULL;
+
   /*XXX would we prefer this backwards? */
-  for (arc_set * as = arc_verify_ctx.arcset_chain; as;
-    as = as->next, first = FALSE)
+  for (arc_set * as = arc_verify_ctx.arcset_chain; as; as = as->next)
     {
     arc_line * line = as->hdr_as;
     if (line)
       {
-      blob * d = &line->d;
-      blob * s = &line->s;
-
-      if (!first)
-    g = string_catn(g, US",", 1);
-
-      g = string_fmt_append(g, " (\"i\":%u,"            /*)*/
-                " \"d\":\"%.*s\","
-                " \"s\":\"%.*s\"",
-          as->instance,
-          d->data ? (int)d->len : 0, d->data && d->len ? d->data : US"",
-          s->data ? (int)s->len : 0, s->data && s->len ? s->data : US""
-               );
+      g = string_append_listele_fmt(g, ',', " (\"i\":%u"                 /*)*/
+                        ", \"d\":\"%#b\""
+                        ", \"s\":\"%#b\"",
+          as->instance, &line->d, &line->s);
+
       if ((line = as->hdr_aar))
     {
     blob * ip = &line->ip;
     if (ip->data && ip->len)
-      g = string_fmt_append(g, ", \"ip\":\"%.*s\"", (int)ip->len, ip->data);
+      g = string_fmt_append(g, ", \"ip\":\"%#b\"", ip);
     }
-
+                                      /*(*/
       g = string_catn(g, US")", 1);
       }
     }
-  g = string_catn(g, US" ]\n", 3);
+  *gp = g;
   }
-else
-  g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
-            ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
-return g;
+return arc_state;
 }
 #  endif



+/******************************************************************************/
+/* Module API */
+
+static void * arc_functions[] = {
+  [ARC_VERIFY] =    acl_verify_arc,
+  [ARC_HEADER_FEED] =    arc_header_feed,
+  [ARC_STATE_IS_PASS] =    arc_is_pass,
+  [ARC_SIGN_INIT] =    arc_sign_init,
+  [ARC_SIGN] =        arc_sign,
+# ifdef SUPPORT_DMARC
+  [ARC_ARCSET_INFO] =    arc_arcset_string,
+# endif
+};
+
+static var_entry arc_variables[] = {
+  { "arc_domains",         vtype_string_func, (void *) &fn_arc_domains },
+  { "arc_oldest_pass",     vtype_int,         &arc_oldest_pass },
+  { "arc_state",           vtype_stringptr,   &arc_state },
+  { "arc_state_reason",    vtype_stringptr,   &arc_state_reason },
+};
+
+misc_module_info arc_module_info =
+{
+  .name =        US"arc",
+# if SUPPORT_SPF==2
+  .dyn_magic =        MISC_MODULE_MAGIC,
+# endif
+  .init =        arc_init,
+  .smtp_reset =        arc_smtp_reset,
+  .authres =        authres_arc,
+
+  .functions =        arc_functions,
+  .functions_count =    nelem(arc_functions),
+
+  .variables =        arc_variables,
+  .variables_count =    nelem(arc_variables),
+};
+
 # endif /* DISABLE_DKIM */
 #endif /* EXPERIMENTAL_ARC */
 /* vi: aw ai sw=2
diff --git a/src/src/miscmods/spf_api.h b/src/src/miscmods/arc_api.h
similarity index 65%
copy from src/src/miscmods/spf_api.h
copy to src/src/miscmods/arc_api.h
index 117a7ae6a..cf24a4cb3 100644
--- a/src/src/miscmods/spf_api.h
+++ b/src/src/miscmods/arc_api.h
@@ -6,13 +6,14 @@
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */


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


/* Function table entry numbers */

-#define    SPF_PROCESS        0
-#define SPF_GET_RESPONSE    2
-#define SPF_OPEN        3
-#define SPF_CLOSE        4
-#define SPF_FIND        5
+#define    ARC_VERIFY        0
+#define ARC_HEADER_FEED        1
+#define ARC_STATE_IS_PASS    2
+#define ARC_SIGN_INIT        3
+#define ARC_SIGN        4
+#define ARC_ARCSET_INFO        5
diff --git a/src/src/miscmods/dkim_transport.c b/src/src/miscmods/dkim_transport.c
index 0500da2be..e2d1705e3 100644
--- a/src/src/miscmods/dkim_transport.c
+++ b/src/src/miscmods/dkim_transport.c
@@ -109,6 +109,32 @@ return TRUE;




+/* Prepend ARC-signing headers to given set of headers
+
+Arguments:
+  signspec    Three-element colon-sep list: identity, selector, privkey.
+        Optional fourth element: comma-sep list of options.
+        Already expanded
+  sigheaders    Any signature headers already generated, eg. by DKIM, or NULL
+  errstr    Error string
+
+Return value
+  Set of headers to prepend to the message, including the supplied sigheaders
+  but not the plainheaders.
+*/
+
+static gstring *
+dkt_arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr_p)
+{
+const misc_module_info * mi = misc_mod_findonly(US"arc");
+typedef gstring * (*fn_t)(const uschar *, gstring *, uschar **);
+if (mi)
+  return (((fn_t *) mi->functions)[ARC_SIGN]) (signspec, sigheaders, errstr_p);
+*errstr_p = US"failed to find arc module";
+return NULL;
+}
+
+


 /* This function is a wrapper around transport_write_message().
    It is only called from the smtp transport if DKIM or Domainkeys support
@@ -153,11 +179,6 @@ if (!rc) return FALSE;


/* Get signatures for headers plus spool data file */

-#ifdef EXPERIMENTAL_ARC
-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
 in wireformat. */


@@ -174,7 +195,7 @@ if (!(dkim_signature = dkim_exim_sign(deliver_datafile,
 if (dkim->arc_signspec)            /* Prepend ARC headers */
   {
   uschar * e = NULL;
-  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e)))
+  if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature, &e)))
     {
     *err = e;
     return FALSE;
@@ -275,10 +296,6 @@ if (!rc)
   goto CLEANUP;
   }


-#ifdef EXPERIMENTAL_ARC
-arc_sign_init();
-#endif
-
 /* Feed the file to the goats^W DKIM lib.  At this point the dotstuffed
 status of the file depends on the output of transport_write_message() just
 above, which should be the result of the end_dot flag in tctx->options. */
@@ -299,7 +316,8 @@ else
 #ifdef EXPERIMENTAL_ARC
 if (dkim->arc_signspec)                /* Prepend ARC headers */
   {
-  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
+  if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature,
+                      USS err)))
     goto CLEANUP;
   dlen = dkim_signature->ptr;
   }
diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c
index d977d29fe..e192bda1b 100644
--- a/src/src/miscmods/dmarc.c
+++ b/src/src/miscmods/dmarc.c
@@ -360,7 +360,32 @@ g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",


 #if DMARC_API >= 100400
 # ifdef EXPERIMENTAL_ARC
-g = arc_dmarc_hist_append(g);
+  {
+  const misc_module_info * mi = misc_mod_findonly(US"arc");
+  const uschar * s;
+  gstring * g2 = NULL;
+  typedef const uschar * (*fn_t)(gstring **);
+
+  if (mi && (s = (((fn_t *) mi->functions)[ARC_ARCSET_INFO]) (&g2)))
+    {
+    int i = Ustrcmp(s, "pass") == 0 ? ARES_RESULT_PASS
+        : Ustrcmp(s, "fail") == 0 ? ARES_RESULT_FAIL
+        : ARES_RESULT_UNKNOWN;
+
+    g = string_fmt_append(g, "arc %d\n"
+                 "arc_policy %d json[%#Y ]\n",
+              i,
+              i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS
+              : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL
+              : DMARC_ARC_POLICY_RESULT_UNUSED,
+              g2
+              );
+    }
+  else
+    string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
+            ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
+  }
+
 # else
 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
               ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
diff --git a/src/src/miscmods/pdkim/pdkim.c b/src/src/miscmods/pdkim/pdkim.c
index cdbdfc5e0..7c2f34217 100644
--- a/src/src/miscmods/pdkim/pdkim.c
+++ b/src/src/miscmods/pdkim/pdkim.c
@@ -935,13 +935,19 @@ return;
 static int
 pdkim_header_complete(pdkim_ctx * ctx)
 {
-if (ctx->cur_header->ptr > 1)
-  gstring_trim_trailing(ctx->cur_header, '\r');
-(void) string_from_gstring(ctx->cur_header);
+gstring * g = ctx->cur_header;
+const misc_module_info * mi;
+typedef const uschar * (*fn_t)(gstring *, BOOL);
+
+if (gstring_length(g) > 1)
+  gstring_trim_trailing(g, '\r');
+(void) string_from_gstring(g);


 #ifdef EXPERIMENTAL_ARC
-/* Feed the header line to ARC processing */
-(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN));
+/* Feed the header line also to ARC processing */
+if ((mi = misc_mod_findonly(US"arc")))
+  (((fn_t *) mi->functions)[ARC_HEADER_FEED])
+                      (g, !(ctx->flags & PDKIM_MODE_SIGN));
 #endif


if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
@@ -951,7 +957,7 @@ if (ctx->flags & PDKIM_MODE_SIGN)
for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) /* Traverse all signatures */

     /* Add header to the signed headers list (in reverse order) */
-    sig->headers = pdkim_prepend_stringlist(sig->headers, ctx->cur_header->s);
+    sig->headers = pdkim_prepend_stringlist(sig->headers, g->s);


 /* VERIFICATION ----------------------------------------------------------- */
 /* DKIM-Signature: headers are added to the verification list */
@@ -959,9 +965,9 @@ else
   {
 #ifdef notdef
   DEBUG(D_acl) debug_printf("DKIM >> raw hdr: %.*Z\n",
-                ctx->cur_head->ptr, CUS ctx->cur_header->s);
+                ctx->cur_head->ptr, CUS g->s);
 #endif
-  if (strncasecmp(CCS ctx->cur_header->s,
+  if (strncasecmp(CCS g->s,
           DKIM_SIGNATURE_HEADERNAME,
           Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
     {
@@ -973,7 +979,7 @@ else
     DEBUG(D_acl) debug_printf(
     "DKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");


-    sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s);
+    sig = pdkim_parse_sig_header(ctx, g->s);


     if (!(last_sig = ctx->sig))
       ctx->sig = sig;
@@ -985,18 +991,18 @@ else


     if (dkim_collect_input && --dkim_collect_input == 0)
       {
-      ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
-      ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
+      ctx->headers = pdkim_prepend_stringlist(ctx->headers, g->s);
+      g->s[g->ptr = 0] = '\0';
       return PDKIM_ERR_EXCESS_SIGS;
       }
     }


/* all headers are stored for signature verification */
- ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+ ctx->headers = pdkim_prepend_stringlist(ctx->headers, g->s);
}

 BAIL:
-ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';    /* leave buffer for reuse */
+g->s[g->ptr = 0] = '\0';    /* leave buffer for reuse */
 return PDKIM_OK;
 }


diff --git a/src/src/miscmods/spf_api.h b/src/src/miscmods/spf_api.h
index 117a7ae6a..0e1907d9a 100644
--- a/src/src/miscmods/spf_api.h
+++ b/src/src/miscmods/spf_api.h
@@ -12,7 +12,7 @@
/* Function table entry numbers */

 #define    SPF_PROCESS        0
-#define SPF_GET_RESPONSE    2
-#define SPF_OPEN        3
-#define SPF_CLOSE        4
-#define SPF_FIND        5
+#define SPF_GET_RESPONSE    1
+#define SPF_OPEN        2
+#define SPF_CLOSE        3
+#define SPF_FIND        4
diff --git a/src/src/receive.c b/src/src/receive.c
index 541e9320d..ae4e1ff7e 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -4135,11 +4135,16 @@ if (LOGGING(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);
+   {
+    mi = misc_mod_findonly(US"arc");
+    typedef BOOL (*fn_t)(void);
+    if (mi && (((fn_t *) mi->functions)[ARC_STATE_IS_PASS]) ())
+      g = string_catn(g, US" ARC", 4);
+   }
 # endif
+  }
 #endif


 if (LOGGING(receive_time))
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index e75894850..e76790fea 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1701,10 +1701,7 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
-#ifdef EXPERIMENTAL_ARC
-arc_state = arc_state_reason = NULL;
-arc_received_instance = 0;
-#endif
+
 dsn_ret = 0;
 dsn_envid = NULL;
 deliver_host = deliver_host_address = NULL;    /* Can be set by ACL */
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 36b5e61fc..594b42e1f 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -32,7 +32,7 @@ optionlist smtp_transport_options[] = {
       LOFF(address_retry_include_sender) },
   { "allow_localhost",      opt_bool,       LOFF(allow_localhost) },
 #ifdef EXPERIMENTAL_ARC
-  { "arc_sign", opt_stringptr,           LOFF(arc_sign) },
+  { "arc_sign",            opt_stringptr, LOFF(arc_sign) },
 #endif
   { "authenticated_sender", opt_stringptr, LOFF(authenticated_sender) },
   { "authenticated_sender_force", opt_bool, LOFF(authenticated_sender_force) },
@@ -4104,8 +4104,8 @@ else


#ifndef DISABLE_DKIM
{
- typedef void (*fn_t)(void);
misc_module_info * mi;
+
# ifdef MEASURE_TIMING
struct timeval t0;
gettimeofday(&t0, NULL);
@@ -4113,27 +4113,30 @@ else

   if ((mi = misc_mod_find(US"dkim", NULL)))
     {
+    typedef void (*fn_t)(void);
     (((fn_t *) mi->functions)[DKIM_TRANSPORT_INIT]) ();


 # ifdef EXPERIMENTAL_ARC
-    uschar * s = ob->arc_sign;
-    if (s)
       {
-      if (!(ob->dkim.arc_signspec = s = expand_string(s)))
-    {
-    if (!f.expand_string_forcedfail)
+      uschar * s = ob->arc_sign;
+      if (s)
+    if (!(ob->dkim.arc_signspec = s = expand_string(s)))
       {
-      message = US"failed to expand arc_sign";
-      sx->ok = FALSE;
-      goto SEND_FAILED;
+      if (!f.expand_string_forcedfail)
+        {
+        message = US"failed to expand arc_sign";
+        sx->ok = FALSE;
+        goto SEND_FAILED;
+        }
+      }
+    else if (*s && (mi = misc_mod_find(US"arc", NULL)))
+      {
+      typedef void (*fn_t)(void);
+      (((fn_t *) mi->functions)[ARC_SIGN_INIT]) ();
+
+      /* Ask dkim code to hash the body for ARC */
+      ob->dkim.force_bodyhash = TRUE;
       }
-    }
-      else if (*s)
-    {
-    /* Ask dkim code to hash the body for ARC */
-    (void) arc_ams_setup_sign_bodyhash();
-    ob->dkim.force_bodyhash = TRUE;
-    }
       }
 # endif    /*ARC*/
     }


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