arc dynamic module
authorJeremy Harris <jgh146exb@wizmail.org>
Fri, 6 Sep 2024 11:29:23 +0000 (12:29 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Fri, 6 Sep 2024 11:31:18 +0000 (12:31 +0100)
25 files changed:
doc/doc-txt/NewStuff
doc/doc-txt/experimental-spec.txt
src/OS/Makefile-Base
src/scripts/Configure-Makefile
src/scripts/MakeLinks
src/src/acl.c
src/src/arc.c [deleted file]
src/src/config.h.defaults
src/src/drtables.c
src/src/exim.c
src/src/exim.h
src/src/expand.c
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/miscmods/Makefile
src/src/miscmods/arc.c [new file with mode: 0644]
src/src/miscmods/arc_api.h [new file with mode: 0644]
src/src/miscmods/dkim_transport.c
src/src/miscmods/dmarc.c
src/src/miscmods/pdkim/pdkim.c
src/src/miscmods/spf_api.h
src/src/receive.c
src/src/smtp_in.c
src/src/transports/smtp.c

index 1189ce3f322d0db3c08eda236fad3b9488e335c2..5220408e80fc876548f874d1ad132d34ac1c9215 100644 (file)
@@ -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
 ------------
index 56ee10f828df594bc868fa2499572d8f03017bc0..a73007700e6aebb45834ad2c9d63a309d541fecf 100644 (file)
@@ -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
 --
index 12319967eb534e5650510c1606f488c56b842b7a..857c44776726d248e691d0ec067332be4d4fcbe0 100644 (file)
@@ -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
index 12f0ddd9c1f82454127972b38ec42125bc32a7f7..1eb79a291bcd6a9f045a909675c40de5ec3c0382 100755 (executable)
@@ -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.
index a6521a95e0d72b0c8449fee10e00e857635e817a..481f36fe3fe5d8d3f575cf0f746f87608d360b78 100755 (executable)
@@ -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
index 878278313931c2d4024e2861fe947616e20adbd8..18d892ec56913fd60833bb5e4e344de2c4476ea1 100644 (file)
@@ -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/arc.c b/src/src/arc.c
deleted file mode 100644 (file)
index a065ca8..0000000
+++ /dev/null
@@ -1,2126 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-/* Experimental ARC support for Exim
-   Copyright (c) The Exim Maintainers 2021 - 2024
-   Copyright (c) Jeremy Harris 2018 - 2020
-   License: GPL
-   SPDX-License-Identifier: GPL-2.0-or-later
-*/
-
-#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"
-
-#  ifdef SUPPORT_DMARC
-#   include "miscmods/dmarc.h"
-#  endif
-
-#define ARC_SIGN_OPT_TSTAMP    BIT(0)
-#define ARC_SIGN_OPT_EXPIRE    BIT(1)
-
-#define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30)      /* one month */
-
-/******************************************************************************/
-
-typedef struct hdr_rlist {
-  struct hdr_rlist *   prev;
-  BOOL                 used;
-  header_line *                h;
-} hdr_rlist;
-
-typedef struct arc_line {
-  header_line *        complete;       /* including the header name; nul-term */
-  uschar *     relaxed;
-
-  /* identified tag contents */
-  /*XXX t= for AS? */
-  blob         i;
-  blob         cv;
-  blob         a;
-  blob         b;
-  blob         bh;
-  blob         d;
-  blob         h;
-  blob         s;
-  blob         c;
-  blob         l;
-  blob         ip;
-
-  /* tag content sub-portions */
-  blob         a_algo;
-  blob         a_hash;
-
-  blob         c_head;
-  blob         c_body;
-
-  /* modified copy of b= field in line */
-  blob         rawsig_no_b_val;
-} arc_line;
-
-typedef struct arc_set {
-  struct arc_set *     next;
-  struct arc_set *     prev;
-
-  unsigned             instance;
-  arc_line *           hdr_aar;
-  arc_line *           hdr_ams;
-  arc_line *           hdr_as;
-
-  const uschar *       ams_verify_done;
-  BOOL                 ams_verify_passed;
-} arc_set;
-
-typedef struct arc_ctx {
-  arc_set *    arcset_chain;
-  arc_set *    arcset_chain_last;
-} arc_ctx;
-
-#define ARC_HDR_AAR    US"ARC-Authentication-Results:"
-#define ARC_HDRLEN_AAR 27
-#define ARC_HDR_AMS    US"ARC-Message-Signature:"
-#define ARC_HDRLEN_AMS 22
-#define ARC_HDR_AS     US"ARC-Seal:"
-#define ARC_HDRLEN_AS  9
-#define HDR_AR         US"Authentication-Results:"
-#define HDRLEN_AR      23
-
-typedef enum line_extract {
-  le_instance_only,
-  le_instance_plus_ip,
-  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;
-static arc_ctx arc_sign_ctx = { NULL };
-static arc_ctx arc_verify_ctx = { NULL };
-
-/* We build a context for either Sign or Verify.
-
-For Verify, it's a fresh new one for ACL verify=arc - there is no connection
-with the single line handling done during reception via the DKIM feed.
-
-For Verify we do it twice; initially during reception (via the DKIM feed)
-and then later for the full verification.
-
-The former only looks at AMS headers, to discover what hash(es) we need done for
-ARC on the message body; we call back to the DKIM code to set up so that it does
-them for us during reception.  That call needs info from many of the AMS tags;
-arc_parse_line() for only the AMS is called asking for all the tag types.
-That context is then discarded.
-
-Later, for Verify, we look at ARC headers again and then grab the hash result
-from the DKIM layer.  arc_parse_line() is called for all 3 line types,
-gathering info for only 'i' and 'ip' tags from AAR headers,
-for all tag types from AMS and AS headers.
-
-
-For Sign, while running through the existing headers (before adding any for
-this signing operation, we "take copies" of the headers, we call
-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;
-}
-
-/******************************************************************************/
-
-
-/* Get the instance number from the header.
-Return 0 on error */
-static unsigned
-arc_instance_from_hdr(const arc_line * al)
-{
-const uschar * s = al->i.data;
-if (!s || !al->i.len) return 0;
-return (unsigned) atoi(CCS s);
-}
-
-
-static uschar *
-skip_fws(uschar * s)
-{
-uschar c = *s;
-while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
-return s;
-}
-
-
-/* Locate instance struct on chain, inserting a new one if
-needed.  The chain is in increasing-instance-number order
-by the "next" link, and we have a "prev" link also.
-*/
-
-static arc_set *
-arc_find_set(arc_ctx * ctx, unsigned i)
-{
-arc_set ** pas, * as, * next, * prev;
-
-for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
-     as = *pas; pas = &as->next)
-  {
-  if (as->instance > i) break;
-  if (as->instance == i)
-    {
-    DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
-    return as;
-    }
-  next = as->next;
-  prev = as;
-  }
-
-DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
-*pas = as = store_get(sizeof(arc_set), GET_UNTAINTED);
-memset(as, 0, sizeof(arc_set));
-as->next = next;
-as->prev = prev;
-as->instance = i;
-if (next)
-  next->prev = as;
-else
-  ctx->arcset_chain_last = as;
-return as;
-}
-
-
-
-/* Insert a tag content into the line structure.
-Note this is a reference to existing data, not a copy.
-Check for already-seen tag.
-The string-pointer is on the '=' for entry.  Update it past the
-content (to the ;) on return;
-*/
-
-static uschar *
-arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
-{
-uschar * s = *ss;
-uschar c = *++s;
-blob * b = (blob *)(US al + loff);
-size_t len = 0;
-
-/* [FWS] tag-value [FWS] */
-
-if (b->data) return US"fail";
-s = skip_fws(s);                                               /* FWS */
-
-b->data = s;
-while ((c = *s) && c != ';') { len++; s++; }
-*ss = s;
-while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
-  { s--; len--; }                                              /* FWS */
-b->len = len;
-return NULL;
-}
-
-
-/* Inspect a header line, noting known tag fields.
-Check for duplicate named tags.
-
-See the file block comment for how this is used.
-
-Return: NULL for good, or an error string
-*/
-
-static uschar *
-arc_parse_line(arc_line * al, header_line * h, unsigned off, line_extract_t l_ext)
-{
-uschar * s = h->text + off;
-uschar * r = NULL;
-uschar c;
-
-al->complete = h;
-
-if (l_ext == le_all)           /* need to grab rawsig_no_b */
-  {
-  al->rawsig_no_b_val.data = store_get(h->slen + 1, GET_TAINTED);
-  memcpy(al->rawsig_no_b_val.data, h->text, off);      /* copy the header name blind */
-  r = al->rawsig_no_b_val.data + off;
-  al->rawsig_no_b_val.len = off;
-  }
-
-/* tag-list  =  tag-spec *( ";" tag-spec ) [ ";" ] */
-
-while ((c = *s))
-  {
-  char tagchar;
-  uschar * t;
-  unsigned i = 0;
-  uschar * fieldstart = s;
-  uschar * bstart = NULL, * bend;
-
-  /* tag-spec  =  [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
-  /*X or just a naked FQDN, in a AAR ! */
-
-  s = skip_fws(s);                                             /* leading FWS */
-  if (!*s) break;
-  tagchar = *s++;
-  if (!*(s = skip_fws(s))) break;                              /* FWS */
-
-  switch (tagchar)
-    {
-    case 'a':                          /* a= AMS algorithm */
-      if (l_ext == le_all && *s == '=')
-       {
-       if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
-
-       /* substructure: algo-hash   (eg. rsa-sha256) */
-
-       t = al->a_algo.data = al->a.data;
-       while (*t != '-')
-         if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
-       al->a_algo.len = i;
-       if (*t++ != '-') return US"no '-' in 'a' value";
-       al->a_hash.data = t;
-       al->a_hash.len = al->a.len - i - 1;
-       }
-      break;
-    case 'b':
-      if (l_ext == le_all)
-       {
-       gstring * g = NULL;
-
-       switch (*s)
-         {
-         case '=':                     /* b= AMS signature */
-           if (al->b.data) return US"already b data";
-           bstart = s+1;
-
-           /* The signature can have FWS inserted in the content;
-           make a stripped copy */
-
-           while ((c = *++s) && c != ';')
-             if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
-               g = string_catn(g, s, 1);
-           if (!g) return US"no b= value";
-           al->b.len = len_string_from_gstring(g, &al->b.data);
-           gstring_release_unused(g);
-           bend = s;
-           break;
-         case 'h':                     /* bh= AMS body hash */
-           s = skip_fws(++s);                                  /* FWS */
-           if (*s == '=')
-             {
-             if (al->bh.data) return US"already bh data";
-
-             /* The bodyhash can have FWS inserted in the content;
-             make a stripped copy */
-
-             while ((c = *++s) && c != ';')
-               if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
-                 g = string_catn(g, s, 1);
-             if (!g) return US"no bh= value";
-             al->bh.len = len_string_from_gstring(g, &al->bh.data);
-             gstring_release_unused(g);
-             }
-           break;
-         default:
-           return US"b? tag";
-         }
-       }
-      break;
-    case 'c':
-      if (l_ext == le_all) switch (*s)
-       {
-       case '=':                       /* c= AMS canonicalisation */
-         if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
-
-         /* substructure: head/body   (eg. relaxed/simple)) */
-
-         t = al->c_head.data = al->c.data;
-         while (isalpha(*t))
-           if (!*t++ || ++i > al->a.len) break;
-         al->c_head.len = i;
-         if (*t++ == '/')              /* /body is optional */
-           {
-           al->c_body.data = t;
-           al->c_body.len = al->c.len - i - 1;
-           }
-         else
-           {
-           al->c_body.data = US"simple";
-           al->c_body.len = 6;
-           }
-         break;
-       case 'v':                       /* cv= AS validity */
-         s = skip_fws(s);
-         if (*++s == '=')
-           if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s))
-             return US"cv tag dup";
-         break;
-       }
-      break;
-    case 'd':                          /* d= AMS domain */
-      if (l_ext == le_all && *s == '=')
-       if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s))
-         return US"d tag dup";
-      break;
-    case 'h':                          /* h= AMS headers */
-      if (*s == '=')
-       if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s))
-         return US"h tag dup";
-      break;
-    case 'i':                          /* i= ARC set instance */
-      if (*s == '=')
-       {
-       if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s))
-         return US"i tag dup";
-       if (l_ext == le_instance_only)
-         goto done;                    /* early-out */
-       }
-      break;
-    case 'l':                          /* l= bodylength */
-      if (l_ext == le_all && *s == '=')
-       if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s))
-         return US"l tag dup";
-      break;
-    case 's':
-      if (*s == '=' && l_ext == le_all)
-       {
-       if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s))
-         return US"s tag dup";
-       }
-      else if (  l_ext == le_instance_plus_ip
-             && Ustrncmp(s, "mtp.remote-ip", 13) == 0)
-       {                       /* smtp.remote-ip= AAR reception data */
-       s += 13;
-       s = skip_fws(s);
-       if (*s != '=') return US"smtp.remote_ip tag val";
-       if (arc_insert_tagvalue(al, offsetof(arc_line, ip), &s))
-         return US"ip tag dup";
-       }
-      break;
-    }
-
-  while ((c = *s) && c != ';') s++;    /* end of this tag=value */
-  if (c) s++;                          /* ; after tag-spec */
-
-  /* for all but the b= tag, copy the field including FWS.  For the b=,
-  drop the tag content. */
-
-  if (r)
-    if (bstart)
-      {
-      size_t n = bstart - fieldstart;
-      memcpy(r, fieldstart, n);                /* FWS "b=" */
-      r += n;
-      al->rawsig_no_b_val.len += n;
-      n = s - bend;
-      memcpy(r, bend, n);              /* FWS ";" */
-      r += n;
-      al->rawsig_no_b_val.len += n;
-      }
-    else
-      {
-      size_t n = s - fieldstart;
-      memcpy(r, fieldstart, n);
-      r += n;
-      al->rawsig_no_b_val.len += n;
-      }
-  }
-
-if (r)
-  *r = '\0';
-
-done:
-/* debug_printf("%s: finshed\n", __FUNCTION__); */
-return NULL;
-}
-
-
-/* Insert one header line in the correct set of the chain,
-adding instances as needed and checking for duplicate lines.
-*/
-
-static uschar *
-arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
-  line_extract_t l_ext, arc_line ** alp_ret)
-{
-unsigned i;
-arc_set * as;
-arc_line * al = store_get(sizeof(arc_line), GET_UNTAINTED), ** alp;
-uschar * e;
-
-memset(al, 0, sizeof(arc_line));
-
-if ((e = arc_parse_line(al, h, off, l_ext)))
-  {
-  DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
-  return string_sprintf("line parse: %s", e);
-  }
-if (!(i = arc_instance_from_hdr(al)))  return US"instance find";
-if (i > 50)                            return US"overlarge instance number";
-if (!(as = arc_find_set(ctx, i)))      return US"set find";
-if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
-
-*alp = al;
-if (alp_ret) *alp_ret = al;
-return NULL;
-}
-
-
-
-/* Called for both Sign and Verify */
-
-static const uschar *
-arc_try_header(arc_ctx * ctx, header_line * h, BOOL is_signing)
-{
-const uschar * e;
-
-/*debug_printf("consider hdr '%s'\n", h->text);*/
-if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
-  {
-  DEBUG(D_acl)
-    {
-    int len = h->slen;
-    uschar * s;
-    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
-      s--, len--;
-    debug_printf("ARC: found AAR: %.*s\n", len, h->text);
-    }
-  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
-             is_signing ? le_instance_only : le_instance_plus_ip, NULL)))
-    {
-    DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
-    return string_sprintf("inserting AAR: %s", e);
-    }
-  }
-else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
-  {
-  arc_line * ams;
-
-  DEBUG(D_acl)
-    {
-    int len = h->slen;
-    uschar * s;
-    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
-      s--, len--;
-    debug_printf("ARC: found AMS: %.*s\n", len, h->text);
-    }
-  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
-             is_signing ? le_instance_only : le_all, &ams)))
-    {
-    DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
-    return string_sprintf("inserting AMS: %s", e);
-    }
-
-  /* defaults */
-  if (!ams->c.data)
-    {
-    ams->c_head.data = US"simple"; ams->c_head.len = 6;
-    ams->c_body = ams->c_head;
-    }
-  }
-else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
-  {
-  DEBUG(D_acl)
-    {
-    int len = h->slen;
-    uschar * s;
-    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
-      s--, len--;
-    debug_printf("ARC: found AS: %.*s\n", len, h->text);
-    }
-  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
-           is_signing ? le_instance_only : le_all, NULL)))
-    {
-    DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
-    return string_sprintf("inserting AS: %s", e);
-    }
-  }
-return NULL;
-}
-
-
-
-/* Gather the chain of arc sets from the headers.
-Check for duplicates while that is done.  Also build the
-reverse-order headers list.
-Called on an ACL verify=arc condition.
-
-Return: ARC state if determined, eg. by lack of any ARC chain.
-*/
-
-static const uschar *
-arc_vfy_collect_hdrs(arc_ctx * ctx)
-{
-header_line * h;
-hdr_rlist * r = NULL, * rprev = NULL;
-const uschar * e;
-
-DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
-for (h = header_list; h; h = h->next)
-  {
-  r = store_get(sizeof(hdr_rlist), GET_UNTAINTED);
-  r->prev = rprev;
-  r->used = FALSE;
-  r->h = h;
-  rprev = r;
-
-  if ((e = arc_try_header(ctx, h, FALSE)))
-    {
-    arc_state_reason = string_sprintf("collecting headers: %s", e);
-    return US"fail";
-    }
-  }
-headers_rlist = r;
-
-if (!ctx->arcset_chain) return US"none";
-return NULL;
-}
-
-
-static BOOL
-arc_cv_match(arc_line * al, const uschar * s)
-{
-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.
-*/
-
-static void
-arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
-{
-const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
-const uschar * hn;
-int sep = ':';
-hdr_rlist * r;
-BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
-hashmethod hm = arc_dkim_hashname_blob_to_method(&ams->a_hash);
-hctx hhash_ctx;
-const uschar * s;
-int len;
-
-if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
-  {
-  DEBUG(D_acl)
-      debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
-  return;
-  }
-
-/* For each headername in the list from the AMS (walking in order)
-walk the message headers in reverse order, adding to the hash any
-found for the first time. For that last point, maintain used-marks
-on the list of message headers. */
-
-DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
-
-for (r = headers_rlist; r; r = r->prev)
-  r->used = FALSE;
-while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
-  for (r = headers_rlist; r; r = r->prev)
-    if (  !r->used
-       && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
-       )
-      {
-      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);
-      r->used = TRUE;
-      break;
-      }
-
-/* Finally add in the signature header (with the b= tag stripped); no CRLF */
-
-s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
-if (relaxed)
-  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);
-
-exim_sha_finish(&hhash_ctx, hhash);
-DEBUG(D_acl)
-  { debug_printf("ARC: header hash: %.*H\n", hhash->len, hhash->data); }
-return;
-}
-
-
-
-
-static blob *
-arc_line_to_pubkey(arc_line * al, const uschar ** errstr)
-{
-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))
-  {
-  *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 (hashes)
-  {
-  const uschar * list = hashes, * ele;
-  int sep = ':';
-
-  while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
-    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);
-    *errstr = US"no usable sig for this pubkey hash list";
-    return NULL;
-    }
-  }
-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);
-}
-
-
-
-
-static pdkim_bodyhash *
-arc_ams_setup_vfy_bodyhash(arc_line * ams)
-{
-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.
-*/
-
-static const uschar *
-arc_ams_verify(arc_ctx * ctx, arc_set * as)
-{
-arc_line * ams = as->hdr_ams;
-pdkim_bodyhash * b;
-blob sighash;
-blob hhash_computed;
-hashmethod hm;
-const uschar * errstr;
-int rc;
-
-as->ams_verify_done = US"in-progress";
-
-/* Check the AMS has all the required tags:
-   "a="  algorithm
-   "b="  signature
-   "bh=" body hash
-   "d="  domain (for key lookup)
-   "h="  headers (included in signature)
-   "s="  key-selector (for key lookup)
-*/
-if (  !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
-   || !ams->h.data || !ams->s.data)
-  {
-  as->ams_verify_done = arc_state_reason = US"required tag missing";
-  return US"fail";
-  }
-
-
-/* The bodyhash should have been created earlier, and the dkim code should
-have managed calculating it during message input.  Find the reference to it. */
-
-if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
-  {
-  as->ams_verify_done = arc_state_reason = US"internal hash setup error";
-  return US"fail";
-  }
-
-DEBUG(D_acl)
-  {
-  debug_printf("ARC i=%d AMS   Body bytes hashed: %lu\n"
-              "              Body %.*s computed: ",
-              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);
-  }
-
-/* We know the bh-tag blob is of a nul-term string, so safe as a string */
-
-if (  !ams->bh.data
-   || (arc_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
-   || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
-   )
-  {
-  DEBUG(D_acl)
-    {
-    debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
-    debug_printf("%.*H\n", sighash.len, sighash.data);
-    debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
-    }
-  return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
-  }
-
-DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
-
-/* We know the b-tag blob is of a nul-term string, so safe as a string */
-arc_decode_base64(ams->b.data, &sighash);
-
-arc_get_verify_hhash(ctx, ams, &hhash_computed);
-
-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";
-  }
-
-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;
-return NULL;
-}
-
-
-
-/* Check the sets are instance-continuous and that all
-members are present.  Check that no arc_seals are "fail".
-Set the highest instance number global.
-Verify the latest AMS.
-*/
-static uschar *
-arc_headers_check(arc_ctx * ctx)
-{
-arc_set * as;
-int inst;
-BOOL ams_fail_found = FALSE;
-
-if (!(as = ctx->arcset_chain_last))
-  return US"none";
-
-for(inst = as->instance; as; as = as->prev, inst--)
-  {
-  if (as->instance != inst)
-    arc_state_reason = string_sprintf("i=%d (sequence; expected %d)",
-      as->instance, inst);
-  else if (!as->hdr_aar || !as->hdr_ams || !as->hdr_as)
-    arc_state_reason = string_sprintf("i=%d (missing header)", as->instance);
-  else if (arc_cv_match(as->hdr_as, US"fail"))
-    arc_state_reason = string_sprintf("i=%d (cv)", as->instance);
-  else
-    goto good;
-
-  DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
-  return US"fail";
-
-  good:
-  /* Evaluate the oldest-pass AMS validation while we're here.
-  It does not affect the AS chain validation but is reported as
-  auxilary info. */
-
-  if (!ams_fail_found)
-    if (arc_ams_verify(ctx, as))
-      ams_fail_found = TRUE;
-    else
-      arc_oldest_pass = inst;
-  arc_state_reason = NULL;
-  }
-if (inst != 0)
-  {
-  arc_state_reason = string_sprintf("(sequence; expected i=%d)", inst);
-  DEBUG(D_acl) debug_printf("ARC chain fail %s\n", arc_state_reason);
-  return US"fail";
-  }
-
-arc_received = ctx->arcset_chain_last;
-arc_received_instance = arc_received->instance;
-
-/* We can skip the latest-AMS validation, if we already did it. */
-
-as = ctx->arcset_chain_last;
-if (!as->ams_verify_passed)
-  {
-  if (as->ams_verify_done)
-    {
-    arc_state_reason = as->ams_verify_done;
-    return US"fail";
-    }
-  if (!!arc_ams_verify(ctx, as))
-    return US"fail";
-  }
-return NULL;
-}
-
-
-/******************************************************************************/
-static const uschar *
-arc_seal_verify(arc_ctx * ctx, arc_set * as)
-{
-arc_line * hdr_as = as->hdr_as;
-arc_set * as2;
-hashmethod hm;
-hctx hhash_ctx;
-blob hhash_computed;
-blob sighash;
-const uschar * errstr;
-int rc;
-
-DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
-/*
-       1.  If the value of the "cv" tag on that seal is "fail", the
-           chain state is "fail" and the algorithm stops here.  (This
-           step SHOULD be skipped if the earlier step (2.1) was
-           performed) [it was]
-
-       2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
-           == "none" && i != 1)) then the chain state is "fail" and the
-           algorithm stops here (note that the ordering of the logic is
-           structured for short-circuit evaluation).
-*/
-
-if (  as->instance == 1 && !arc_cv_match(hdr_as, US"none")
-   || arc_cv_match(hdr_as, US"none") && as->instance != 1
-   )
-  {
-  arc_state_reason = US"seal cv state";
-  return US"fail";
-  }
-
-/*
-       3.  Initialize a hash function corresponding to the "a" tag of
-           the ARC-Seal.
-*/
-
-hm = arc_dkim_hashname_blob_to_method(&hdr_as->a_hash);
-
-if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
-  {
-  DEBUG(D_acl)
-      debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
-  arc_state_reason = US"seal hash setup error";
-  return US"fail";
-  }
-
-/*
-       4.  Compute the canonicalized form of the ARC header fields, in
-           the order described in Section 5.4.2, using the "relaxed"
-           header canonicalization defined in Section 3.4.2 of
-           [RFC6376].  Pass the canonicalized result to the hash
-           function.
-
-Headers are CRLF-separated, but the last one is not crlf-terminated.
-*/
-
-DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
-for (as2 = ctx->arcset_chain;
-     as2 && as2->instance <= as->instance;
-     as2 = as2->next)
-  {
-  arc_line * al;
-  uschar * s;
-  int len;
-
-  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);
-  DEBUG(D_acl) debug_printf("%Z\n", s);
-  exim_sha_update(&hhash_ctx, s, len);
-
-  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);
-  DEBUG(D_acl) debug_printf("%Z\n", s);
-  exim_sha_update(&hhash_ctx, s, len);
-
-  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);
-  DEBUG(D_acl) debug_printf("%Z\n", s);
-  exim_sha_update(&hhash_ctx, s, len);
-  }
-
-/*
-       5.  Retrieve the final digest from the hash function.
-*/
-
-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("%.*H\n", hhash_computed.len, hhash_computed.data);
-  }
-
-
-/*
-       6.  Retrieve the public key identified by the "s" and "d" tags in
-           the ARC-Seal, as described in Section 4.1.6.
-
-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
-           handling)
-
-       8.  If the signature is not valid, the chain state is "fail" and
-           the algorithm stops here.
-*/
-
-/* We know the b-tag blob is of a nul-term string, so safe as a string */
-arc_decode_base64(hdr_as->b.data, &sighash);
-
-rc = arc_sig_verify(as, hdr_as, hm, &hhash_computed, &sighash, US"AS", &errstr);
-if (rc != OK)
-  {
-  if (rc == FAIL) arc_state_reason = US"seal sigverify error";
-  return US"fail";
-  }
-
-DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
-return NULL;
-}
-
-
-static const uschar *
-arc_verify_seals(arc_ctx * ctx)
-{
-arc_set * as = ctx->arcset_chain_last;
-
-if (!as)
-  return US"none";
-
-for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail";
-
-DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
-return NULL;
-}
-/******************************************************************************/
-
-/* Do ARC verification.  Called from DATA ACL, on a verify = arc
-condition.  No arguments; we are checking globals.
-
-Return:  The ARC state, or NULL on error.
-*/
-
-const uschar *
-acl_verify_arc(void)
-{
-const uschar * res;
-
-memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
-
-/* AS evaluation, per
-https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
-*/
-/* 1.  Collect all ARC sets currently on the message.  If there were
-       none, the ARC state is "none" and the algorithm stops here.
-*/
-
-if ((res = arc_vfy_collect_hdrs(&arc_verify_ctx)))
-  goto out;
-
-/* 2.  If the form of any ARC set is invalid (e.g., does not contain
-       exactly one of each of the three ARC-specific header fields),
-       then the chain state is "fail" and the algorithm stops here.
-
-       1.  To avoid the overhead of unnecessary computation and delay
-           from crypto and DNS operations, the cv value for all ARC-
-           Seal(s) MAY be checked at this point.  If any of the values
-           are "fail", then the overall state of the chain is "fail" and
-           the algorithm stops here.
-
-   3.  Conduct verification of the ARC-Message-Signature header field
-       bearing the highest instance number.  If this verification fails,
-       then the chain state is "fail" and the algorithm stops here.
-*/
-
-if ((res = arc_headers_check(&arc_verify_ctx)))
-  goto out;
-
-/* 4.  For each ARC-Seal from the "N"th instance to the first, apply the
-       following logic:
-
-       1.  If the value of the "cv" tag on that seal is "fail", the
-           chain state is "fail" and the algorithm stops here.  (This
-           step SHOULD be skipped if the earlier step (2.1) was
-           performed)
-
-       2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
-           == "none" && i != 1)) then the chain state is "fail" and the
-           algorithm stops here (note that the ordering of the logic is
-           structured for short-circuit evaluation).
-
-       3.  Initialize a hash function corresponding to the "a" tag of
-           the ARC-Seal.
-
-       4.  Compute the canonicalized form of the ARC header fields, in
-           the order described in Section 5.4.2, using the "relaxed"
-           header canonicalization defined in Section 3.4.2 of
-           [RFC6376].  Pass the canonicalized result to the hash
-           function.
-
-       5.  Retrieve the final digest from the hash function.
-
-       6.  Retrieve the public key identified by the "s" and "d" tags in
-           the ARC-Seal, as described in Section 4.1.6.
-
-       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
-           handling)
-
-       8.  If the signature is not valid, the chain state is "fail" and
-           the algorithm stops here.
-
-   5.  If all seals pass validation, then the chain state is "pass", and
-       the algorithm is complete.
-*/
-
-if ((res = arc_verify_seals(&arc_verify_ctx)))
-  goto out;
-
-res = US"pass";
-
-out:
-  return res;
-}
-
-/******************************************************************************/
-
-/* Prepend the header to the rlist */
-
-static hdr_rlist *
-arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
-{
-hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), GET_UNTAINTED);
-header_line * h = r->h = (header_line *)(r+1);
-
-r->prev = list;
-r->used = FALSE;
-h->next = NULL;
-h->type = 0;
-h->slen = len;
-h->text = US s;
-
-return r;
-}
-
-
-/* Walk the given headers strings identifying each header, and construct
-a reverse-order list.
-*/
-
-static hdr_rlist *
-arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
-{
-const uschar * s;
-hdr_rlist * rheaders = NULL;
-
-s = sigheaders ? sigheaders->s : NULL;
-if (s) while (*s)
-  {
-  const uschar * s2 = s;
-
-  /* This works for either NL or CRLF lines; also nul-termination */
-  while (*++s2)
-    if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
-  s2++;                /* move past end of line */
-
-  rheaders = arc_rlist_entry(rheaders, s, s2 - s);
-  s = s2;
-  }
-return rheaders;
-}
-
-
-
-/* Return the A-R content, without identity, with line-ending and
-NUL termination. */
-
-static BOOL
-arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
-{
-header_line * h;
-int ilen = Ustrlen(identity);
-
-ret->data = NULL;
-for(h = headers; h; h = h->next)
-  {
-  uschar * s = h->text, c;
-  int len = h->slen;
-
-  if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
-  s += HDRLEN_AR, len -= HDRLEN_AR;            /* header name */
-  while (  len > 0
-       && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
-    s++, len--;                                        /* FWS */
-  if (Ustrncmp(s, identity, ilen) != 0) continue;
-  s += ilen; len -= ilen;                      /* identity */
-  if (len <= 0) continue;
-  if ((c = *s) && c == ';') s++, len--;                /* identity terminator */
-  while (  len > 0
-       && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
-    s++, len--;                                        /* FWS */
-  if (len <= 0) continue;
-  ret->data = s;
-  ret->len = len;
-  return TRUE;
-  }
-return FALSE;
-}
-
-
-
-/* Append a constructed AAR including CRLF.  Add it to the arc_ctx too.  */
-
-static gstring *
-arc_sign_append_aar(gstring * g, arc_ctx * ctx,
-  const uschar * identity, int instance, blob * ar)
-{
-int aar_off = gstring_length(g);
-arc_set * as =
-  store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), GET_UNTAINTED);
-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);
-
-h->slen = g->ptr - aar_off;
-h->text = g->s + aar_off;
-al->complete = h;
-as->next = NULL;
-as->prev = ctx->arcset_chain_last;
-as->instance = instance;
-as->hdr_aar = al;
-if (instance == 1)
-  ctx->arcset_chain = as;
-else
-  ctx->arcset_chain_last->next = as;
-ctx->arcset_chain_last = as;
-
-DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
-return g;
-}
-
-
-
-static BOOL
-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
-  : arc_dkim_hashtype_to_method(hashtype);
-
-blob hhash;
-const uschar * errstr;
-typedef const uschar * (*fn_t)
-                         (const blob *, hashmethod, const uschar *, blob *);
-
-DEBUG(D_transport)
-  {
-  hctx hhash_ctx;
-  debug_printf("ARC: %s header data for signing:\n", why);
-  debug_printf("%.*Z\n", hdata->ptr, hdata->s);
-
-  (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);
-  }
-
-if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
-  {
-  hctx hhash_ctx;
-  (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);
-  }
-else
-  {
-  hhash.data = hdata->s;
-  hhash.len = hdata->ptr;
-  }
-
-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)
-    debug_printf("private key, or private-key file content, was: '%s'\n",
-      privkey);
-  return FALSE;
-  }
-
-return TRUE;
-}
-
-
-
-static gstring *
-arc_sign_append_sig(gstring * g, blob * sig)
-{
-/*debug_printf("%s: raw sig %.*H\n", __FUNCTION__, sig->len, sig->data);*/
-sig->data = b64encode(sig->data, sig->len);
-sig->len = Ustrlen(sig->data);
-for (;;)
-  {
-  int len = MIN(sig->len, 74);
-  g = string_catn(g, sig->data, len);
-  if ((sig->len -= len) == 0) break;
-  sig->data += len;
-  g = string_catn(g, US"\r\n\t  ", 5);
-  }
-g = string_catn(g, US";\r\n", 3);
-gstring_release_unused(g);
-string_from_gstring(g);
-return g;
-}
-
-
-/* Append a constructed AMS including CRLF.  Add it to the arc_ctx too. */
-
-static gstring *
-arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
-  const uschar * identity, const uschar * selector, blob * bodyhash,
-  hdr_rlist * rheaders, const uschar * privkey, unsigned options)
-{
-uschar * s;
-gstring * hdata = NULL;
-int col;
-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);
-header_line * h = (header_line *)(al+1);
-
-/* debug_printf("%s\n", __FUNCTION__); */
-
-/* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
-
-ams_off = gstring_length(g);
-g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s",
-      ARC_HDR_AMS, instance, identity, selector);      /*XXX hardwired a= */
-if (options & ARC_SIGN_OPT_TSTAMP)
-  g = string_fmt_append(g, "; t=%lu", (u_long)now);
-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=",
-      b64encode(bodyhash->data, bodyhash->len));
-
-for(col = 3; rheaders; rheaders = rheaders->prev)
-  {
-  const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
-  uschar * name, * htext = rheaders->h->text;
-  int sep = ':';
-
-  /* Spot headers of interest */
-
-  while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
-    {
-    int len = Ustrlen(name);
-    if (strncasecmp(CCS htext, CCS name, len) == 0)
-      {
-      /* If too long, fold line in h= field */
-
-      if (col + len > 78) g = string_catn(g, US"\r\n\t  ", 5), col = 3;
-
-      /* Add name to h= list */
-
-      g = string_catn(g, name, len);
-      g = string_catn(g, US":", 1);
-      col += len + 1;
-
-      /* Accumulate header for hashing/signing */
-
-      hdata = string_cat(hdata,
-               /*XXX dkim module */
-               arc_relax_header_n(htext, rheaders->h->slen, TRUE));    /*XXX hardwired */
-      break;
-      }
-    }
-  }
-
-/* Lose the last colon from the h= list */
-
-gstring_trim_trailing(g, ':');
-
-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);
-
-/* Calculate the signature from the accumulation */
-/*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
-
-if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
-  return NULL;
-
-/* Lose the trailing semicolon from the psuedo-header, and append the signature
-(folded over lines) and termination to complete it. */
-
-gstring_trim(g, 1);
-g = arc_sign_append_sig(g, &sig);
-
-h->slen = g->ptr - ams_off;
-h->text = g->s + ams_off;
-al->complete = h;
-ctx->arcset_chain_last->hdr_ams = al;
-
-DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
-return g;
-}
-
-
-
-/* Look for an arc= result in an A-R header blob.  We know that its data
-happens to be a NUL-term string. */
-
-static uschar *
-arc_ar_cv_status(blob * ar)
-{
-const uschar * resinfo = ar->data;
-int sep = ';';
-uschar * methodspec, * s;
-
-while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
-  if (Ustrncmp(methodspec, US"arc=", 4) == 0)
-    {
-    uschar c;
-    for (s = methodspec += 4;
-         (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
-    return string_copyn(methodspec, s - methodspec);
-    }
-return US"none";
-}
-
-
-
-/* Build the AS header and prepend it */
-
-static gstring *
-arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
-  int instance, const uschar * identity, const uschar * selector, blob * ar,
-  const uschar * privkey, unsigned options)
-{
-gstring * arcset;
-uschar * status = arc_ar_cv_status(ar);
-arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED);
-header_line * h = (header_line *)(al+1);
-uschar * badline_str;
-
-gstring * hdata = NULL;
-const blob as_h = {.data = US"sha256", .len = 6};      /*XXX hardwired */
-int hashtype = arc_dkim_hashname_blob_to_type(&as_h);
-blob sig;
-
-/*
-- Generate AS
-  - no body coverage
-  - no h= tag; implicit coverage
-  - arc status from A-R
-    - if fail:
-      - coverage is just the new ARC set
-        including self (but with an empty b= in self)
-    - if non-fail:
-      - all ARC set headers, set-number order, aar then ams then as,
-        including self (but with an empty b= in self)
-*/
-DEBUG(D_transport) debug_printf("ARC: building AS for status '%s'\n", status);
-
-/* Construct the AS except for the signature */
-
-arcset = string_append(NULL, 9,
-         ARC_HDR_AS,
-         US" i=", string_sprintf("%d", instance),
-         US"; cv=", status,
-         US"; a=rsa-sha256; d=", identity,                     /*XXX hardwired */
-         US"; s=", selector);                                  /*XXX same as AMS */
-if (options & ARC_SIGN_OPT_TSTAMP)
-  arcset = string_append(arcset, 2,
-      US"; t=", string_sprintf("%lu", (u_long)now));
-arcset = string_cat(arcset,
-         US";\r\n\t b=;");
-
-h->slen = arcset->ptr;
-h->text = arcset->s;
-al->complete = h;
-ctx->arcset_chain_last->hdr_as = al;
-
-/* For any but "fail" chain-verify status, walk the entire chain in order by
-instance.  For fail, only the new arc-set.  Accumulate the elements walked. */
-
-for (arc_set * as = Ustrcmp(status, US"fail") == 0
-       ? ctx->arcset_chain_last : ctx->arcset_chain;
-     as; as = as->next)
-  {
-  arc_line * l;
-  /* Accumulate AAR then AMS then AS.  Relaxed canonicalisation
-  is required per standard. */
-
-  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));
-  }
-
-/* Calculate the signature from the accumulation */
-
-if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
-  return NULL;
-
-/* Lose the trailing semicolon */
-arcset->ptr--;
-arcset = arc_sign_append_sig(arcset, &sig);
-DEBUG(D_transport) debug_printf("ARC: AS  '%.*s'\n", arcset->ptr - 2, arcset->s);
-
-/* Finally, append the AMS and AAR to the new AS */
-
-return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
-
-badline:
-  DEBUG(D_transport)
-    debug_printf("ARC: while building AS, missing %s in chain\n", badline_str);
-  return NULL;
-}
-
-
-/**************************************/
-
-/*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)
-{
-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");
-
-return arc_set_bodyhash(TRUE, &canon, &hash, -1);
-}
-
-
-
-void
-arc_sign_init(void)
-{
-memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
-headers_rlist = NULL;
-}
-
-
-
-/* A "normal" header line, identified by DKIM processing.  These arrive before
-the call to arc_sign(), which carries any newly-created DKIM headers - and
-those go textually before the normal ones in the message.
-
-We have to take the feed from DKIM as, in the transport-filter case, the
-headers are not in memory at the time of the call to arc_sign().
-
-Take a copy of the header and construct a reverse-order list.
-Also parse ARC-chain headers and build the chain struct, retaining pointers
-into the copies.
-*/
-
-static const uschar *
-arc_header_sign_feed(gstring * g)
-{
-uschar * s = string_copy_from_gstring(g);
-headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
-return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
-}
-
-
-
-/* Per RFCs 6376, 7489 the only allowed chars in either an ADMD id
-or a selector are ALPHA/DIGGIT/'-'/'.'
-
-Check, to help catch misconfigurations such as a missing selector
-element in the arc_sign list.
-*/
-
-static BOOL
-arc_valid_id(const uschar * s)
-{
-for (uschar c; c = *s++; )
-  if (!isalnum(c) && c != '-' && c != '.') return FALSE;
-return TRUE;
-}
-
-
-
-/* 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.
-
-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.
-*/
-
-gstring *
-arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
-{
-const uschar * identity, * selector, * privkey, * opts, * s;
-unsigned options = 0;
-int sep = 0;
-header_line * headers;
-hdr_rlist * rheaders;
-blob ar;
-int instance;
-gstring * g = NULL;
-pdkim_bodyhash * b;
-
-expire = now = 0;
-
-/* Parse the signing specification */
-
-if (!(identity = string_nextinlist(&signspec, &sep, NULL, 0)) || !*identity)
-  { s = US"identity"; goto bad_arg_ret; }
-if (!(selector = string_nextinlist(&signspec, &sep, NULL, 0)) || !*selector)
-  { s = US"selector"; goto bad_arg_ret; }
-if (!(privkey = string_nextinlist(&signspec, &sep, NULL, 0))  || !*privkey)
-  { s = US"privkey"; goto bad_arg_ret; }
-if (!arc_valid_id(identity))
-  { s = US"identity"; goto bad_arg_ret; }
-if (!arc_valid_id(selector))
-  { s = US"selector"; goto bad_arg_ret; }
-if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
-  goto ret_sigheaders;
-
-if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
-  {
-  int osep = ',';
-  while ((s = string_nextinlist(&opts, &osep, NULL, 0)))
-    if (Ustrcmp(s, "timestamps") == 0)
-      {
-      options |= ARC_SIGN_OPT_TSTAMP;
-      if (!now) now = time(NULL);
-      }
-    else if (Ustrncmp(s, "expire", 6) == 0)
-      {
-      options |= ARC_SIGN_OPT_EXPIRE;
-      if (*(s += 6) == '=')
-       if (*++s == '+')
-         {
-         if (!(expire = (time_t)atoi(CS ++s)))
-           expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA;
-         if (!now) now = time(NULL);
-         expire += now;
-         }
-       else
-         expire = (time_t)atol(CS s);
-      else
-       {
-       if (!now) now = time(NULL);
-       expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA;
-       }
-      }
-  }
-
-DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
-
-/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
-Then scan the list for an A-R header. */
-
-string_from_gstring(sigheaders);
-if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
-  {
-  hdr_rlist ** rp;
-  for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
-  *rp = rheaders;
-  }
-
-/* Finally, build a normal-order headers list */
-/*XXX only needed for hunt-the-AR? */
-/*XXX also, we really should be accepting any number of ADMD-matching ARs */
-  {
-  header_line * hnext = NULL;
-  for (rheaders = headers_rlist; rheaders;
-       hnext = rheaders->h, rheaders = rheaders->prev)
-    rheaders->h->next = hnext;
-  headers = hnext;
-  }
-
-if (!(arc_sign_find_ar(headers, identity, &ar)))
-  {
-  log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
-  goto ret_sigheaders;
-  }
-
-/* We previously built the data-struct for the existing ARC chain, if any, using a headers
-feed from the DKIM module.  Use that to give the instance number for the ARC set we are
-about to build. */
-
-DEBUG(D_transport)
-  if (arc_sign_ctx.arcset_chain_last)
-    debug_printf("ARC: existing chain highest instance: %d\n",
-      arc_sign_ctx.arcset_chain_last->instance);
-  else
-    debug_printf("ARC: no existing chain\n");
-
-instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
-
-/*
-- Generate AAR
-  - copy the A-R; prepend i= & identity
-*/
-
-g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
-
-/*
-- Generate AMS
-  - Looks fairly like a DKIM sig
-  - Cover all DKIM sig headers as well as the usuals
-    - ? 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();
-g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
-      &b->bh, headers_rlist, privkey, options);
-
-/*
-- Generate AS
-  - no body coverage
-  - no h= tag; implicit coverage
-  - arc status from A-R
-    - if fail:
-      - coverage is just the new ARC set
-        including self (but with an empty b= in self)
-    - if non-fail:
-      - all ARC set headers, set-number order, aar then ams then as,
-        including self (but with an empty b= in self)
-*/
-
-if (g)
-  g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
-      privkey, options);
-
-/* Finally, append the dkim headers and return the lot. */
-
-if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
-
-out:
-  if (!g) return string_get(1);
-  (void) string_from_gstring(g);
-  gstring_release_unused(g);
-  return g;
-
-
-bad_arg_ret:
-  log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", s);
-ret_sigheaders:
-  g = sigheaders;
-  goto out;
-}
-
-
-/******************************************************************************/
-
-/* Check to see if the line is an AMS and if so, set up to validate it.
-Called from the DKIM input processing.  This must be done now as the message
-body data is hashed during input.
-
-We call the DKIM code to request a body-hash; it has the facility already
-and the hash parameters might be common with other requests.
-*/
-
-static const uschar *
-arc_header_vfy_feed(gstring * g)
-{
-header_line h;
-arc_line al;
-pdkim_bodyhash * b;
-uschar * errstr;
-
-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");
-/* Parse the AMS header */
-
-memset(&al, 0, sizeof(arc_line));
-h.next = NULL;
-h.slen = len_string_from_gstring(g, &h.text);
-if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, le_all)))
-  {
-  DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
-  goto badline;
-  }
-
-if (!al.a_hash.data)
-  {
-  DEBUG(D_acl) debug_printf("ARC: no a_hash from '%.*s'\n", h.slen, h.text);
-  goto badline;
-  }
-
-/* defaults */
-if (!al.c.data)
-  {
-  al.c_body.data = US"simple"; al.c_body.len = 6;
-  al.c_head = al.c_body;
-  }
-
-/* Ask the dkim code to calc a bodyhash with those specs */
-
-if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
-  return US"dkim hash setup fail";
-
-/* Discard the reference; search again at verify time, knowing that one
-should have been created here. */
-
-return NULL;
-
-badline:
-  return US"line parsing error";
-}
-
-
-
-/* A header line has been identified by DKIM processing.
-
-Arguments:
-  g            Header line
-  is_vfy       TRUE for verify mode or FALSE for signing mode
-
-Return:
-  NULL for success, or an error string (probably unused)
-*/
-
-const uschar *
-arc_header_feed(gstring * g, BOOL is_vfy)
-{
-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 *
-fn_arc_domains(void)
-{
-arc_set * as;
-unsigned inst;
-gstring * g = NULL;
-
-for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++)
-  {
-  arc_line * hdr_as = as->hdr_as;
-  if (hdr_as)
-    {
-    blob * d = &hdr_as->d;
-
-    for (; inst < as->instance; inst++)
-      g = string_catn(g, US":", 1);
-
-    g = d->data && d->len
-      ? string_append_listele_n(g, ':', d->data, d->len)
-      : string_catn(g, US":", 1);
-    }
-  else
-    g = string_catn(g, US":", 1);
-  }
-if (!g) return US"";
-return string_from_gstring(g);
-}
-
-
-/* Construct an Authentication-Results header portion, for the ARC module */
-
-gstring *
-authres_arc(gstring * g)
-{
-if (arc_state)
-  {
-  arc_line * highest_ams;
-  int start = 0;               /* Compiler quietening */
-  DEBUG(D_acl) start = gstring_length(g);
-
-  g = string_append(g, 2, US";\n\tarc=", arc_state);
-  if (arc_received_instance > 0)
-    {
-    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);
-
-    if (sender_host_address)
-      g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
-    }
-  else if (arc_state_reason)
-    g = string_append(g, 3, US" (", arc_state_reason, US")");
-  DEBUG(D_acl) debug_printf("ARC:\tauthres '%.*s'\n",
-                 gstring_length(g) - start - 3, g->s + start + 3);
-  }
-else
-  DEBUG(D_acl) debug_printf("ARC:\tno authres\n");
-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)
-{
-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);
-  /*XXX would we prefer this backwards? */
-  for (arc_set * as = arc_verify_ctx.arcset_chain; as;
-       as = as->next, first = FALSE)
-    {
-    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""
-                          );
-      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_catn(g, US")", 1);
-      }
-    }
-  g = string_catn(g, US" ]\n", 3);
-  }
-else
-  g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
-                       ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
-return g;
-}
-#  endif
-
-
-# endif /* DISABLE_DKIM */
-#endif /* EXPERIMENTAL_ARC */
-/* vi: aw ai sw=2
- */
index d602886a0084710d4bfa2eccca6890c0ea9a8c46..20a288d66f6abee0a245b06c8671b39931ff7f8b 100644 (file)
@@ -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
index 61ced3e6a5e92944e841d70ca0263022cd8e36af..32765aedc264c4cae36a48122e54e4dd3adb5417 100644 (file)
@@ -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
 }
 
index ca98e25def56ba9c2e258cf8b176878310ea296a..2349260dfb063cec4253bf82e352a9904ecbbb11 100644 (file)
@@ -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
index 8260dc75f56769ef2bf378a5f5f3df509e8bdce3..f5043aea9857d8db2575cbea3b9af82e8c69de53 100644 (file)
@@ -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. */
index 02680771f5269520f41c9e874250061781b5983c..a41ba98cfce168a13bf28e942ff90b357e9e543f 100644 (file)
@@ -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;
       }
 
index 4f4e615ca03a87d1441411d1c9116c650122c383..deec54590bc8cd4c3e22b3404b56bd92ff291856 100644 (file)
@@ -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);
index 6fae1582f430d4af2eaef4460e8e99ceef8eef50..c65ddf41368d778eea6415dd0095b67cb72f870b 100644 (file)
@@ -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;
index 1f03cefee6f9211bb3315ef71efff4ef7ab9b72e..2f2f023e336d7d70f78f1896f9360b99f766d3f3 100644 (file)
@@ -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 */
index 3013a88da4a4358049bbf892f274f43a9957a4e9..64a66276f3a48a538b6697e954b504a2e6db51e1 100644 (file)
@@ -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/miscmods/arc.c b/src/src/miscmods/arc.c
new file mode 100644 (file)
index 0000000..db546e1
--- /dev/null
@@ -0,0 +1,2171 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+/* Experimental ARC support for Exim
+   Copyright (c) The Exim Maintainers 2021 - 2024
+   Copyright (c) Jeremy Harris 2018 - 2020
+   License: GPL
+   SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "../exim.h"
+#if defined EXPERIMENTAL_ARC
+# if defined DISABLE_DKIM
+#  error DKIM must also be enabled for ARC
+# else
+
+#  include "../functions.h"
+#  include "pdkim.h"
+#  include "signing.h"
+
+/* 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)
+
+#define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30)      /* one month */
+
+/******************************************************************************/
+
+typedef struct hdr_rlist {
+  struct hdr_rlist *   prev;
+  BOOL                 used;
+  header_line *                h;
+} hdr_rlist;
+
+typedef struct arc_line {
+  header_line *        complete;       /* including the header name; nul-term */
+  uschar *     relaxed;
+
+  /* identified tag contents */
+  /*XXX t= for AS? */
+  blob         i;
+  blob         cv;
+  blob         a;
+  blob         b;
+  blob         bh;
+  blob         d;
+  blob         h;
+  blob         s;
+  blob         c;
+  blob         l;
+  blob         ip;
+
+  /* tag content sub-portions */
+  blob         a_algo;
+  blob         a_hash;
+
+  blob         c_head;
+  blob         c_body;
+
+  /* modified copy of b= field in line */
+  blob         rawsig_no_b_val;
+} arc_line;
+
+typedef struct arc_set {
+  struct arc_set *     next;
+  struct arc_set *     prev;
+
+  unsigned             instance;
+  arc_line *           hdr_aar;
+  arc_line *           hdr_ams;
+  arc_line *           hdr_as;
+
+  const uschar *       ams_verify_done;
+  BOOL                 ams_verify_passed;
+} arc_set;
+
+typedef struct arc_ctx {
+  arc_set *    arcset_chain;
+  arc_set *    arcset_chain_last;
+} arc_ctx;
+
+#define ARC_HDR_AAR    US"ARC-Authentication-Results:"
+#define ARC_HDRLEN_AAR 27
+#define ARC_HDR_AMS    US"ARC-Message-Signature:"
+#define ARC_HDRLEN_AMS 22
+#define ARC_HDR_AS     US"ARC-Seal:"
+#define ARC_HDRLEN_AS  9
+#define HDR_AR         US"Authentication-Results:"
+#define HDRLEN_AR      23
+
+typedef enum line_extract {
+  le_instance_only,
+  le_instance_plus_ip,
+  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;
+static arc_ctx arc_sign_ctx = { NULL };
+static arc_ctx arc_verify_ctx = { NULL };
+
+/* We build a context for either Sign or Verify.
+
+For Verify, it's a fresh new one for ACL verify=arc - there is no connection
+with the single line handling done during reception via the DKIM feed.
+
+For Verify we do it twice; initially during reception (via the DKIM feed)
+and then later for the full verification.
+
+The former only looks at AMS headers, to discover what hash(es) we need done for
+ARC on the message body; we call back to the DKIM code to set up so that it does
+them for us during reception.  That call needs info from many of the AMS tags;
+arc_parse_line() for only the AMS is called asking for all the tag types.
+That context is then discarded.
+
+Later, for Verify, we look at ARC headers again and then grab the hash result
+from the DKIM layer.  arc_parse_line() is called for all 3 line types,
+gathering info for only 'i' and 'ip' tags from AAR headers,
+for all tag types from AMS and AS headers.
+
+
+For Sign, while running through the existing headers (before adding any for
+this signing operation, we "take copies" of the headers, we call
+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 stash it's modinfo ptr)
+*/
+
+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, "arc: %s", errstr);
+return FALSE;
+}
+
+static void
+arc_smtp_reset(void)
+{
+arc_state = arc_state_reason = NULL;
+arc_received_instance = 0;
+}
+
+/******************************************************************************/
+
+
+/* Get the instance number from the header.
+Return 0 on error */
+static unsigned
+arc_instance_from_hdr(const arc_line * al)
+{
+const uschar * s = al->i.data;
+if (!s || !al->i.len) return 0;
+return (unsigned) atoi(CCS s);
+}
+
+
+static uschar *
+skip_fws(uschar * s)
+{
+uschar c = *s;
+while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
+return s;
+}
+
+
+/* Locate instance struct on chain, inserting a new one if
+needed.  The chain is in increasing-instance-number order
+by the "next" link, and we have a "prev" link also.
+*/
+
+static arc_set *
+arc_find_set(arc_ctx * ctx, unsigned i)
+{
+arc_set ** pas, * as, * next, * prev;
+
+for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
+     as = *pas; pas = &as->next)
+  {
+  if (as->instance > i) break;
+  if (as->instance == i)
+    {
+    DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
+    return as;
+    }
+  next = as->next;
+  prev = as;
+  }
+
+DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
+*pas = as = store_get(sizeof(arc_set), GET_UNTAINTED);
+memset(as, 0, sizeof(arc_set));
+as->next = next;
+as->prev = prev;
+as->instance = i;
+if (next)
+  next->prev = as;
+else
+  ctx->arcset_chain_last = as;
+return as;
+}
+
+
+
+/* Insert a tag content into the line structure.
+Note this is a reference to existing data, not a copy.
+Check for already-seen tag.
+The string-pointer is on the '=' for entry.  Update it past the
+content (to the ;) on return;
+*/
+
+static uschar *
+arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
+{
+uschar * s = *ss;
+uschar c = *++s;
+blob * b = (blob *)(US al + loff);
+size_t len = 0;
+
+/* [FWS] tag-value [FWS] */
+
+if (b->data) return US"fail";
+s = skip_fws(s);                                               /* FWS */
+
+b->data = s;
+while ((c = *s) && c != ';') { len++; s++; }
+*ss = s;
+while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
+  { s--; len--; }                                              /* FWS */
+b->len = len;
+return NULL;
+}
+
+
+/* Inspect a header line, noting known tag fields.
+Check for duplicate named tags.
+
+See the file block comment for how this is used.
+
+Return: NULL for good, or an error string
+*/
+
+static uschar *
+arc_parse_line(arc_line * al, header_line * h, unsigned off, line_extract_t l_ext)
+{
+uschar * s = h->text + off;
+uschar * r = NULL;
+uschar c;
+
+al->complete = h;
+
+if (l_ext == le_all)           /* need to grab rawsig_no_b */
+  {
+  al->rawsig_no_b_val.data = store_get(h->slen + 1, GET_TAINTED);
+  memcpy(al->rawsig_no_b_val.data, h->text, off);      /* copy the header name blind */
+  r = al->rawsig_no_b_val.data + off;
+  al->rawsig_no_b_val.len = off;
+  }
+
+/* tag-list  =  tag-spec *( ";" tag-spec ) [ ";" ] */
+
+while ((c = *s))
+  {
+  char tagchar;
+  uschar * t;
+  unsigned i = 0;
+  uschar * fieldstart = s;
+  uschar * bstart = NULL, * bend;
+
+  /* tag-spec  =  [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
+  /*X or just a naked FQDN, in a AAR ! */
+
+  s = skip_fws(s);                                             /* leading FWS */
+  if (!*s) break;
+  tagchar = *s++;
+  if (!*(s = skip_fws(s))) break;                              /* FWS */
+
+  switch (tagchar)
+    {
+    case 'a':                          /* a= AMS algorithm */
+      if (l_ext == le_all && *s == '=')
+       {
+       if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
+
+       /* substructure: algo-hash   (eg. rsa-sha256) */
+
+       t = al->a_algo.data = al->a.data;
+       while (*t != '-')
+         if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
+       al->a_algo.len = i;
+       if (*t++ != '-') return US"no '-' in 'a' value";
+       al->a_hash.data = t;
+       al->a_hash.len = al->a.len - i - 1;
+       }
+      break;
+    case 'b':
+      if (l_ext == le_all)
+       {
+       gstring * g = NULL;
+
+       switch (*s)
+         {
+         case '=':                     /* b= AMS signature */
+           if (al->b.data) return US"already b data";
+           bstart = s+1;
+
+           /* The signature can have FWS inserted in the content;
+           make a stripped copy */
+
+           while ((c = *++s) && c != ';')
+             if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+               g = string_catn(g, s, 1);
+           if (!g) return US"no b= value";
+           al->b.len = len_string_from_gstring(g, &al->b.data);
+           gstring_release_unused(g);
+           bend = s;
+           break;
+         case 'h':                     /* bh= AMS body hash */
+           s = skip_fws(++s);                                  /* FWS */
+           if (*s == '=')
+             {
+             if (al->bh.data) return US"already bh data";
+
+             /* The bodyhash can have FWS inserted in the content;
+             make a stripped copy */
+
+             while ((c = *++s) && c != ';')
+               if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+                 g = string_catn(g, s, 1);
+             if (!g) return US"no bh= value";
+             al->bh.len = len_string_from_gstring(g, &al->bh.data);
+             gstring_release_unused(g);
+             }
+           break;
+         default:
+           return US"b? tag";
+         }
+       }
+      break;
+    case 'c':
+      if (l_ext == le_all) switch (*s)
+       {
+       case '=':                       /* c= AMS canonicalisation */
+         if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
+
+         /* substructure: head/body   (eg. relaxed/simple)) */
+
+         t = al->c_head.data = al->c.data;
+         while (isalpha(*t))
+           if (!*t++ || ++i > al->a.len) break;
+         al->c_head.len = i;
+         if (*t++ == '/')              /* /body is optional */
+           {
+           al->c_body.data = t;
+           al->c_body.len = al->c.len - i - 1;
+           }
+         else
+           {
+           al->c_body.data = US"simple";
+           al->c_body.len = 6;
+           }
+         break;
+       case 'v':                       /* cv= AS validity */
+         s = skip_fws(s);
+         if (*++s == '=')
+           if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s))
+             return US"cv tag dup";
+         break;
+       }
+      break;
+    case 'd':                          /* d= AMS domain */
+      if (l_ext == le_all && *s == '=')
+       if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s))
+         return US"d tag dup";
+      break;
+    case 'h':                          /* h= AMS headers */
+      if (*s == '=')
+       if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s))
+         return US"h tag dup";
+      break;
+    case 'i':                          /* i= ARC set instance */
+      if (*s == '=')
+       {
+       if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s))
+         return US"i tag dup";
+       if (l_ext == le_instance_only)
+         goto done;                    /* early-out */
+       }
+      break;
+    case 'l':                          /* l= bodylength */
+      if (l_ext == le_all && *s == '=')
+       if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s))
+         return US"l tag dup";
+      break;
+    case 's':
+      if (*s == '=' && l_ext == le_all)
+       {
+       if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s))
+         return US"s tag dup";
+       }
+      else if (  l_ext == le_instance_plus_ip
+             && Ustrncmp(s, "mtp.remote-ip", 13) == 0)
+       {                       /* smtp.remote-ip= AAR reception data */
+       s += 13;
+       s = skip_fws(s);
+       if (*s != '=') return US"smtp.remote_ip tag val";
+       if (arc_insert_tagvalue(al, offsetof(arc_line, ip), &s))
+         return US"ip tag dup";
+       }
+      break;
+    }
+
+  while ((c = *s) && c != ';') s++;    /* end of this tag=value */
+  if (c) s++;                          /* ; after tag-spec */
+
+  /* for all but the b= tag, copy the field including FWS.  For the b=,
+  drop the tag content. */
+
+  if (r)
+    if (bstart)
+      {
+      size_t n = bstart - fieldstart;
+      memcpy(r, fieldstart, n);                /* FWS "b=" */
+      r += n;
+      al->rawsig_no_b_val.len += n;
+      n = s - bend;
+      memcpy(r, bend, n);              /* FWS ";" */
+      r += n;
+      al->rawsig_no_b_val.len += n;
+      }
+    else
+      {
+      size_t n = s - fieldstart;
+      memcpy(r, fieldstart, n);
+      r += n;
+      al->rawsig_no_b_val.len += n;
+      }
+  }
+
+if (r)
+  *r = '\0';
+
+done:
+/* debug_printf("%s: finshed\n", __FUNCTION__); */
+return NULL;
+}
+
+
+/* Insert one header line in the correct set of the chain,
+adding instances as needed and checking for duplicate lines.
+*/
+
+static uschar *
+arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
+  line_extract_t l_ext, arc_line ** alp_ret)
+{
+unsigned i;
+arc_set * as;
+arc_line * al = store_get(sizeof(arc_line), GET_UNTAINTED), ** alp;
+uschar * e;
+
+memset(al, 0, sizeof(arc_line));
+
+if ((e = arc_parse_line(al, h, off, l_ext)))
+  {
+  DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
+  return string_sprintf("line parse: %s", e);
+  }
+if (!(i = arc_instance_from_hdr(al)))  return US"instance find";
+if (i > 50)                            return US"overlarge instance number";
+if (!(as = arc_find_set(ctx, i)))      return US"set find";
+if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
+
+*alp = al;
+if (alp_ret) *alp_ret = al;
+return NULL;
+}
+
+
+
+/* Called for both Sign and Verify */
+
+static const uschar *
+arc_try_header(arc_ctx * ctx, header_line * h, BOOL is_signing)
+{
+const uschar * e;
+
+/*debug_printf("consider hdr '%s'\n", h->text);*/
+if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
+  {
+  DEBUG(D_acl)
+    {
+    int len = h->slen;
+    uschar * s;
+    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+      s--, len--;
+    debug_printf("ARC: found AAR: %.*s\n", len, h->text);
+    }
+  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
+             is_signing ? le_instance_only : le_instance_plus_ip, NULL)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
+    return string_sprintf("inserting AAR: %s", e);
+    }
+  }
+else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
+  {
+  arc_line * ams;
+
+  DEBUG(D_acl)
+    {
+    int len = h->slen;
+    uschar * s;
+    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+      s--, len--;
+    debug_printf("ARC: found AMS: %.*s\n", len, h->text);
+    }
+  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
+             is_signing ? le_instance_only : le_all, &ams)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
+    return string_sprintf("inserting AMS: %s", e);
+    }
+
+  /* defaults */
+  if (!ams->c.data)
+    {
+    ams->c_head.data = US"simple"; ams->c_head.len = 6;
+    ams->c_body = ams->c_head;
+    }
+  }
+else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
+  {
+  DEBUG(D_acl)
+    {
+    int len = h->slen;
+    uschar * s;
+    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+      s--, len--;
+    debug_printf("ARC: found AS: %.*s\n", len, h->text);
+    }
+  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
+           is_signing ? le_instance_only : le_all, NULL)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
+    return string_sprintf("inserting AS: %s", e);
+    }
+  }
+return NULL;
+}
+
+
+
+/* Gather the chain of arc sets from the headers.
+Check for duplicates while that is done.  Also build the
+reverse-order headers list.
+Called on an ACL verify=arc condition.
+
+Return: ARC state if determined, eg. by lack of any ARC chain.
+*/
+
+static const uschar *
+arc_vfy_collect_hdrs(arc_ctx * ctx)
+{
+header_line * h;
+hdr_rlist * r = NULL, * rprev = NULL;
+const uschar * e;
+
+DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
+for (h = header_list; h; h = h->next)
+  {
+  r = store_get(sizeof(hdr_rlist), GET_UNTAINTED);
+  r->prev = rprev;
+  r->used = FALSE;
+  r->h = h;
+  rprev = r;
+
+  if ((e = arc_try_header(ctx, h, FALSE)))
+    {
+    arc_state_reason = string_sprintf("collecting headers: %s", e);
+    return US"fail";
+    }
+  }
+headers_rlist = r;
+
+if (!ctx->arcset_chain) return US"none";
+return NULL;
+}
+
+
+static BOOL
+arc_cv_match(arc_line * al, const uschar * s)
+{
+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.
+*/
+
+static void
+arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
+{
+const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
+const uschar * hn;
+int sep = ':';
+hdr_rlist * r;
+BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
+hashmethod hm = arc_dkim_hashname_blob_to_method(&ams->a_hash);
+hctx hhash_ctx;
+const uschar * s;
+int len;
+
+if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
+  {
+  DEBUG(D_acl)
+      debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
+  return;
+  }
+
+/* For each headername in the list from the AMS (walking in order)
+walk the message headers in reverse order, adding to the hash any
+found for the first time. For that last point, maintain used-marks
+on the list of message headers. */
+
+DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
+
+for (r = headers_rlist; r; r = r->prev)
+  r->used = FALSE;
+while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
+  for (r = headers_rlist; r; r = r->prev)
+    if (  !r->used
+       && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
+       )
+      {
+      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);
+      r->used = TRUE;
+      break;
+      }
+
+/* Finally add in the signature header (with the b= tag stripped); no CRLF */
+
+s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
+if (relaxed)
+  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);
+
+exim_sha_finish(&hhash_ctx, hhash);
+DEBUG(D_acl)
+  { debug_printf("ARC: header hash: %.*H\n", hhash->len, hhash->data); }
+return;
+}
+
+
+
+
+static blob *
+arc_line_to_pubkey(arc_line * al, const uschar ** errstr)
+{
+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("%b._domainkey.%b", &al->s, &al->d), &pubkey, &hashes);
+
+if (!srvtype)
+  { *errstr = US"pubkey dns lookup fail"; return NULL; }
+if ((Ustrcmp(srvtype, "*") != 0 && Ustrcmp(srvtype, "email") != 0))
+  {
+  *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 (hashes)
+  {
+  const uschar * list = hashes, * ele;
+  int sep = ':';
+
+  while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+    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=%b\n", hashes, &al->a);
+    *errstr = US"no usable sig for this pubkey hash list";
+    return NULL;
+    }
+  }
+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);
+}
+
+
+
+
+static pdkim_bodyhash *
+arc_ams_setup_vfy_bodyhash(arc_line * ams)
+{
+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.
+*/
+
+static const uschar *
+arc_ams_verify(arc_ctx * ctx, arc_set * as)
+{
+arc_line * ams = as->hdr_ams;
+pdkim_bodyhash * b;
+blob sighash;
+blob hhash_computed;
+hashmethod hm;
+const uschar * errstr;
+int rc;
+
+as->ams_verify_done = US"in-progress";
+
+/* Check the AMS has all the required tags:
+   "a="  algorithm
+   "b="  signature
+   "bh=" body hash
+   "d="  domain (for key lookup)
+   "h="  headers (included in signature)
+   "s="  key-selector (for key lookup)
+*/
+if (  !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
+   || !ams->h.data || !ams->s.data)
+  {
+  as->ams_verify_done = arc_state_reason = US"required tag missing";
+  return US"fail";
+  }
+
+
+/* The bodyhash should have been created earlier, and the dkim code should
+have managed calculating it during message input.  Find the reference to it. */
+
+if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
+  {
+  as->ams_verify_done = arc_state_reason = US"internal hash setup error";
+  return US"fail";
+  }
+
+DEBUG(D_acl)
+  {
+  debug_printf("ARC i=%d AMS   Body bytes hashed: %lu\n"
+              "              Body %b computed: %.*H\n",
+              as->instance, b->signed_body_bytes,
+              &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 */
+
+if (  !ams->bh.data
+   || (arc_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
+   || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
+   )
+  {
+  DEBUG(D_acl)
+    {
+    debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
+    debug_printf("%.*H\n", sighash.len, sighash.data);
+    debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
+    }
+  return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
+  }
+
+DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
+
+/* We know the b-tag blob is of a nul-term string, so safe as a string */
+arc_decode_base64(ams->b.data, &sighash);
+
+arc_get_verify_hhash(ctx, ams, &hhash_computed);
+
+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";
+  }
+
+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;
+return NULL;
+}
+
+
+
+/* Check the sets are instance-continuous and that all
+members are present.  Check that no arc_seals are "fail".
+Set the highest instance number global.
+Verify the latest AMS.
+*/
+static uschar *
+arc_headers_check(arc_ctx * ctx)
+{
+arc_set * as;
+int inst;
+BOOL ams_fail_found = FALSE;
+
+if (!(as = ctx->arcset_chain_last))
+  return US"none";
+
+for(inst = as->instance; as; as = as->prev, inst--)
+  {
+  if (as->instance != inst)
+    arc_state_reason = string_sprintf("i=%d (sequence; expected %d)",
+      as->instance, inst);
+  else if (!as->hdr_aar || !as->hdr_ams || !as->hdr_as)
+    arc_state_reason = string_sprintf("i=%d (missing header)", as->instance);
+  else if (arc_cv_match(as->hdr_as, US"fail"))
+    arc_state_reason = string_sprintf("i=%d (cv)", as->instance);
+  else
+    goto good;
+
+  DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
+  return US"fail";
+
+  good:
+  /* Evaluate the oldest-pass AMS validation while we're here.
+  It does not affect the AS chain validation but is reported as
+  auxilary info. */
+
+  if (!ams_fail_found)
+    if (arc_ams_verify(ctx, as))
+      ams_fail_found = TRUE;
+    else
+      arc_oldest_pass = inst;
+  arc_state_reason = NULL;
+  }
+if (inst != 0)
+  {
+  arc_state_reason = string_sprintf("(sequence; expected i=%d)", inst);
+  DEBUG(D_acl) debug_printf("ARC chain fail %s\n", arc_state_reason);
+  return US"fail";
+  }
+
+arc_received = ctx->arcset_chain_last;
+arc_received_instance = arc_received->instance;
+
+/* We can skip the latest-AMS validation, if we already did it. */
+
+as = ctx->arcset_chain_last;
+if (!as->ams_verify_passed)
+  {
+  if (as->ams_verify_done)
+    {
+    arc_state_reason = as->ams_verify_done;
+    return US"fail";
+    }
+  if (!!arc_ams_verify(ctx, as))
+    return US"fail";
+  }
+return NULL;
+}
+
+
+/******************************************************************************/
+static const uschar *
+arc_seal_verify(arc_ctx * ctx, arc_set * as)
+{
+arc_line * hdr_as = as->hdr_as;
+arc_set * as2;
+hashmethod hm;
+hctx hhash_ctx;
+blob hhash_computed;
+blob sighash;
+const uschar * errstr;
+int rc;
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
+/*
+       1.  If the value of the "cv" tag on that seal is "fail", the
+           chain state is "fail" and the algorithm stops here.  (This
+           step SHOULD be skipped if the earlier step (2.1) was
+           performed) [it was]
+
+       2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
+           == "none" && i != 1)) then the chain state is "fail" and the
+           algorithm stops here (note that the ordering of the logic is
+           structured for short-circuit evaluation).
+*/
+
+if (  as->instance == 1 && !arc_cv_match(hdr_as, US"none")
+   || arc_cv_match(hdr_as, US"none") && as->instance != 1
+   )
+  {
+  arc_state_reason = US"seal cv state";
+  return US"fail";
+  }
+
+/*
+       3.  Initialize a hash function corresponding to the "a" tag of
+           the ARC-Seal.
+*/
+
+hm = arc_dkim_hashname_blob_to_method(&hdr_as->a_hash);
+
+if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
+  {
+  DEBUG(D_acl)
+      debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
+  arc_state_reason = US"seal hash setup error";
+  return US"fail";
+  }
+
+/*
+       4.  Compute the canonicalized form of the ARC header fields, in
+           the order described in Section 5.4.2, using the "relaxed"
+           header canonicalization defined in Section 3.4.2 of
+           [RFC6376].  Pass the canonicalized result to the hash
+           function.
+
+Headers are CRLF-separated, but the last one is not crlf-terminated.
+*/
+
+DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
+for (as2 = ctx->arcset_chain;
+     as2 && as2->instance <= as->instance;
+     as2 = as2->next)
+  {
+  arc_line * al;
+  uschar * s;
+  int len;
+
+  al = as2->hdr_aar;
+  if (!(s = al->relaxed))
+    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);
+  exim_sha_update(&hhash_ctx, s, len);
+
+  al = as2->hdr_ams;
+  if (!(s = al->relaxed))
+    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);
+  exim_sha_update(&hhash_ctx, s, len);
+
+  al = as2->hdr_as;
+  if (as2->instance == as->instance)
+    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 = arc_relax_header_n(al->complete->text,
+                                           al->complete->slen, TRUE);
+  len = Ustrlen(s);
+  DEBUG(D_acl) debug_printf("%Z\n", s);
+  exim_sha_update(&hhash_ctx, s, len);
+  }
+
+/*
+       5.  Retrieve the final digest from the hash function.
+*/
+
+exim_sha_finish(&hhash_ctx, &hhash_computed);
+DEBUG(D_acl)
+  {
+  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);
+  }
+
+
+/*
+       6.  Retrieve the public key identified by the "s" and "d" tags in
+           the ARC-Seal, as described in Section 4.1.6.
+
+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
+           handling)
+
+       8.  If the signature is not valid, the chain state is "fail" and
+           the algorithm stops here.
+*/
+
+/* We know the b-tag blob is of a nul-term string, so safe as a string */
+arc_decode_base64(hdr_as->b.data, &sighash);
+
+rc = arc_sig_verify(as, hdr_as, hm, &hhash_computed, &sighash, US"AS", &errstr);
+if (rc != OK)
+  {
+  if (rc == FAIL) arc_state_reason = US"seal sigverify error";
+  return US"fail";
+  }
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
+return NULL;
+}
+
+
+static const uschar *
+arc_verify_seals(arc_ctx * ctx)
+{
+arc_set * as = ctx->arcset_chain_last;
+
+if (!as)
+  return US"none";
+
+for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail";
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
+return NULL;
+}
+/******************************************************************************/
+
+/* Do ARC verification.  Called from DATA ACL, on a verify = arc
+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:  OK/FAIL, or DEFER on error
+*/
+
+static int
+acl_verify_arc(const uschar * condlist)
+{
+const uschar * res;
+
+memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
+
+/* AS evaluation, per
+https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
+*/
+/* 1.  Collect all ARC sets currently on the message.  If there were
+       none, the ARC state is "none" and the algorithm stops here.
+*/
+
+if ((res = arc_vfy_collect_hdrs(&arc_verify_ctx)))
+  goto out;
+
+/* 2.  If the form of any ARC set is invalid (e.g., does not contain
+       exactly one of each of the three ARC-specific header fields),
+       then the chain state is "fail" and the algorithm stops here.
+
+       1.  To avoid the overhead of unnecessary computation and delay
+           from crypto and DNS operations, the cv value for all ARC-
+           Seal(s) MAY be checked at this point.  If any of the values
+           are "fail", then the overall state of the chain is "fail" and
+           the algorithm stops here.
+
+   3.  Conduct verification of the ARC-Message-Signature header field
+       bearing the highest instance number.  If this verification fails,
+       then the chain state is "fail" and the algorithm stops here.
+*/
+
+if ((res = arc_headers_check(&arc_verify_ctx)))
+  goto out;
+
+/* 4.  For each ARC-Seal from the "N"th instance to the first, apply the
+       following logic:
+
+       1.  If the value of the "cv" tag on that seal is "fail", the
+           chain state is "fail" and the algorithm stops here.  (This
+           step SHOULD be skipped if the earlier step (2.1) was
+           performed)
+
+       2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
+           == "none" && i != 1)) then the chain state is "fail" and the
+           algorithm stops here (note that the ordering of the logic is
+           structured for short-circuit evaluation).
+
+       3.  Initialize a hash function corresponding to the "a" tag of
+           the ARC-Seal.
+
+       4.  Compute the canonicalized form of the ARC header fields, in
+           the order described in Section 5.4.2, using the "relaxed"
+           header canonicalization defined in Section 3.4.2 of
+           [RFC6376].  Pass the canonicalized result to the hash
+           function.
+
+       5.  Retrieve the final digest from the hash function.
+
+       6.  Retrieve the public key identified by the "s" and "d" tags in
+           the ARC-Seal, as described in Section 4.1.6.
+
+       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
+           handling)
+
+       8.  If the signature is not valid, the chain state is "fail" and
+           the algorithm stops here.
+
+   5.  If all seals pass validation, then the chain state is "pass", and
+       the algorithm is complete.
+*/
+
+if ((res = arc_verify_seals(&arc_verify_ctx)))
+  goto out;
+
+res = US"pass";
+
+out:
+  {
+  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;
+}
+
+/******************************************************************************/
+
+/* Prepend the header to the rlist */
+
+static hdr_rlist *
+arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
+{
+hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), GET_UNTAINTED);
+header_line * h = r->h = (header_line *)(r+1);
+
+r->prev = list;
+r->used = FALSE;
+h->next = NULL;
+h->type = 0;
+h->slen = len;
+h->text = US s;
+
+return r;
+}
+
+
+/* Walk the given headers strings identifying each header, and construct
+a reverse-order list.
+*/
+
+static hdr_rlist *
+arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
+{
+const uschar * s;
+hdr_rlist * rheaders = NULL;
+
+s = sigheaders ? sigheaders->s : NULL;
+if (s) while (*s)
+  {
+  const uschar * s2 = s;
+
+  /* This works for either NL or CRLF lines; also nul-termination */
+  while (*++s2)
+    if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
+  s2++;                /* move past end of line */
+
+  rheaders = arc_rlist_entry(rheaders, s, s2 - s);
+  s = s2;
+  }
+return rheaders;
+}
+
+
+
+/* Return the A-R content, without identity, with line-ending and
+NUL termination. */
+
+static BOOL
+arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
+{
+header_line * h;
+int ilen = Ustrlen(identity);
+
+ret->data = NULL;
+for(h = headers; h; h = h->next)
+  {
+  uschar * s = h->text, c;
+  int len = h->slen;
+
+  if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
+  s += HDRLEN_AR, len -= HDRLEN_AR;            /* header name */
+  while (  len > 0
+       && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
+    s++, len--;                                        /* FWS */
+  if (Ustrncmp(s, identity, ilen) != 0) continue;
+  s += ilen; len -= ilen;                      /* identity */
+  if (len <= 0) continue;
+  if ((c = *s) && c == ';') s++, len--;                /* identity terminator */
+  while (  len > 0
+       && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
+    s++, len--;                                        /* FWS */
+  if (len <= 0) continue;
+  ret->data = s;
+  ret->len = len;
+  return TRUE;
+  }
+return FALSE;
+}
+
+
+
+/* Append a constructed AAR including CRLF.  Add it to the arc_ctx too.  */
+
+static gstring *
+arc_sign_append_aar(gstring * g, arc_ctx * ctx,
+  const uschar * identity, int instance, blob * ar)
+{
+int aar_off = gstring_length(g);
+arc_set * as =
+  store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), GET_UNTAINTED);
+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%b",
+                        instance, identity, sender_host_address, ar);
+
+h->slen = g->ptr - aar_off;
+h->text = g->s + aar_off;
+al->complete = h;
+as->next = NULL;
+as->prev = ctx->arcset_chain_last;
+as->instance = instance;
+as->hdr_aar = al;
+if (instance == 1)
+  ctx->arcset_chain = as;
+else
+  ctx->arcset_chain_last->next = as;
+ctx->arcset_chain_last = as;
+
+DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
+return g;
+}
+
+
+
+static BOOL
+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
+  : arc_dkim_hashtype_to_method(hashtype);
+
+blob hhash;
+const uschar * errstr;
+typedef const uschar * (*fn_t)
+                         (const blob *, hashmethod, const uschar *, blob *);
+
+DEBUG(D_transport)
+  {
+  hctx hhash_ctx;
+  debug_printf("ARC: %s header data for signing:\n", why);
+  debug_printf("%.*Z\n", hdata->ptr, hdata->s);
+
+  (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);
+  }
+
+if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
+  {
+  hctx hhash_ctx;
+  (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);
+  }
+else
+  {
+  hhash.data = hdata->s;
+  hhash.len = hdata->ptr;
+  }
+
+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)
+    debug_printf("private key, or private-key file content, was: '%s'\n",
+      privkey);
+  return FALSE;
+  }
+
+return TRUE;
+}
+
+
+
+static gstring *
+arc_sign_append_sig(gstring * g, blob * sig)
+{
+/*debug_printf("%s: raw sig %.*H\n", __FUNCTION__, sig->len, sig->data);*/
+sig->data = b64encode(sig->data, sig->len);
+sig->len = Ustrlen(sig->data);
+for (;;)
+  {
+  int len = MIN(sig->len, 74);
+  g = string_catn(g, sig->data, len);
+  if ((sig->len -= len) == 0) break;
+  sig->data += len;
+  g = string_catn(g, US"\r\n\t  ", 5);
+  }
+g = string_catn(g, US";\r\n", 3);
+gstring_release_unused(g);
+string_from_gstring(g);
+return g;
+}
+
+
+/* Append a constructed AMS including CRLF.  Add it to the arc_ctx too. */
+
+static gstring *
+arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
+  const uschar * identity, const uschar * selector, blob * bodyhash,
+  hdr_rlist * rheaders, const uschar * privkey, unsigned options)
+{
+uschar * s;
+gstring * hdata = NULL;
+int col;
+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);
+header_line * h = (header_line *)(al+1);
+
+/* debug_printf("%s\n", __FUNCTION__); */
+
+/* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
+
+ams_off = gstring_length(g);
+g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s",
+      ARC_HDR_AMS, instance, identity, selector);      /*XXX hardwired a= */
+if (options & ARC_SIGN_OPT_TSTAMP)
+  g = string_fmt_append(g, "; t=%lu", (u_long)now);
+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=",
+      b64encode(bodyhash->data, bodyhash->len));
+
+for(col = 3; rheaders; rheaders = rheaders->prev)
+  {
+  const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
+  uschar * name, * htext = rheaders->h->text;
+  int sep = ':';
+
+  /* Spot headers of interest */
+
+  while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
+    {
+    int len = Ustrlen(name);
+    if (strncasecmp(CCS htext, CCS name, len) == 0)
+      {
+      /* If too long, fold line in h= field */
+
+      if (col + len > 78) g = string_catn(g, US"\r\n\t  ", 5), col = 3;
+
+      /* Add name to h= list */
+
+      g = string_catn(g, name, len);
+      g = string_catn(g, US":", 1);
+      col += len + 1;
+
+      /* Accumulate header for hashing/signing */
+
+      hdata = string_cat(hdata,
+               arc_relax_header_n(htext, rheaders->h->slen, TRUE));    /*XXX hardwired */
+      break;
+      }
+    }
+  }
+
+/* Lose the last colon from the h= list */
+
+gstring_trim_trailing(g, ':');
+
+g = string_catn(g, US";\r\n\tb=;", 7);
+
+/* Include the pseudo-header in the accumulation */
+
+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 */
+/*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
+
+if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
+  return NULL;
+
+/* Lose the trailing semicolon from the psuedo-header, and append the signature
+(folded over lines) and termination to complete it. */
+
+gstring_trim(g, 1);
+g = arc_sign_append_sig(g, &sig);
+
+h->slen = g->ptr - ams_off;
+h->text = g->s + ams_off;
+al->complete = h;
+ctx->arcset_chain_last->hdr_ams = al;
+
+DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
+return g;
+}
+
+
+
+/* Look for an arc= result in an A-R header blob.  We know that its data
+happens to be a NUL-term string. */
+
+static uschar *
+arc_ar_cv_status(blob * ar)
+{
+const uschar * resinfo = ar->data;
+int sep = ';';
+uschar * methodspec, * s;
+
+while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
+  if (Ustrncmp(methodspec, US"arc=", 4) == 0)
+    {
+    uschar c;
+    for (s = methodspec += 4;
+         (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
+    return string_copyn(methodspec, s - methodspec);
+    }
+return US"none";
+}
+
+
+
+/* Build the AS header and prepend it */
+
+static gstring *
+arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
+  int instance, const uschar * identity, const uschar * selector, blob * ar,
+  const uschar * privkey, unsigned options)
+{
+gstring * arcset;
+uschar * status = arc_ar_cv_status(ar);
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED);
+header_line * h = (header_line *)(al+1);
+uschar * badline_str;
+
+gstring * hdata = NULL;
+const blob as_h = {.data = US"sha256", .len = 6};      /*XXX hardwired */
+int hashtype = arc_dkim_hashname_blob_to_type(&as_h);
+blob sig;
+
+/*
+- Generate AS
+  - no body coverage
+  - no h= tag; implicit coverage
+  - arc status from A-R
+    - if fail:
+      - coverage is just the new ARC set
+        including self (but with an empty b= in self)
+    - if non-fail:
+      - all ARC set headers, set-number order, aar then ams then as,
+        including self (but with an empty b= in self)
+*/
+DEBUG(D_transport) debug_printf("ARC: building AS for status '%s'\n", status);
+
+/* Construct the AS except for the signature */
+
+arcset = string_append(NULL, 9,
+         ARC_HDR_AS,
+         US" i=", string_sprintf("%d", instance),
+         US"; cv=", status,
+         US"; a=rsa-sha256; d=", identity,                     /*XXX hardwired */
+         US"; s=", selector);                                  /*XXX same as AMS */
+if (options & ARC_SIGN_OPT_TSTAMP)
+  arcset = string_append(arcset, 2,
+      US"; t=", string_sprintf("%lu", (u_long)now));
+arcset = string_cat(arcset,
+         US";\r\n\t b=;");
+
+h->slen = arcset->ptr;
+h->text = arcset->s;
+al->complete = h;
+ctx->arcset_chain_last->hdr_as = al;
+
+/* For any but "fail" chain-verify status, walk the entire chain in order by
+instance.  For fail, only the new arc-set.  Accumulate the elements walked. */
+
+for (arc_set * as = Ustrcmp(status, US"fail") == 0
+       ? ctx->arcset_chain_last : ctx->arcset_chain;
+     as; as = as->next)
+  {
+  arc_line * l;
+  /* Accumulate AAR then AMS then AS.  Relaxed canonicalisation
+  is required per standard. */
+
+  badline_str = US"aar";
+  if (!(l = as->hdr_aar)) goto badline;
+  h = l->complete;
+  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, 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, arc_relax_header_n(h->text, h->slen, !!as->next));
+  }
+
+/* Calculate the signature from the accumulation */
+
+if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
+  return NULL;
+
+/* Lose the trailing semicolon */
+arcset->ptr--;
+arcset = arc_sign_append_sig(arcset, &sig);
+DEBUG(D_transport) debug_printf("ARC: AS  '%.*s'\n", arcset->ptr - 2, arcset->s);
+
+/* Finally, append the AMS and AAR to the new AS */
+
+return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
+
+badline:
+  DEBUG(D_transport)
+    debug_printf("ARC: while building AS, missing %s in chain\n", badline_str);
+  return NULL;
+}
+
+
+/**************************************/
+
+static pdkim_bodyhash *
+arc_ams_setup_sign_bodyhash(void)
+{
+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");
+
+return arc_set_bodyhash(TRUE, &canon, &hash, -1);
+}
+
+
+
+/* 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();
+}
+
+
+
+/* A "normal" header line, identified by DKIM processing.  These arrive before
+the call to arc_sign(), which carries any newly-created DKIM headers - and
+those go textually before the normal ones in the message.
+
+We have to take the feed from DKIM as, in the transport-filter case, the
+headers are not in memory at the time of the call to arc_sign().
+
+Take a copy of the header and construct a reverse-order list.
+Also parse ARC-chain headers and build the chain struct, retaining pointers
+into the copies.
+*/
+
+static const uschar *
+arc_header_sign_feed(gstring * g)
+{
+uschar * s = string_copy_from_gstring(g);
+headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
+return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
+}
+
+
+
+/* Per RFCs 6376, 7489 the only allowed chars in either an ADMD id
+or a selector are ALPHA/DIGGIT/'-'/'.'
+
+Check, to help catch misconfigurations such as a missing selector
+element in the arc_sign list.
+*/
+
+static BOOL
+arc_valid_id(const uschar * s)
+{
+for (uschar c; c = *s++; )
+  if (!isalnum(c) && c != '-' && c != '.') return FALSE;
+return TRUE;
+}
+
+
+
+/* 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.
+
+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 *
+arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
+{
+const uschar * identity, * selector, * privkey, * opts, * s;
+unsigned options = 0;
+int sep = 0;
+header_line * headers;
+hdr_rlist * rheaders;
+blob ar;
+int instance;
+gstring * g = NULL;
+pdkim_bodyhash * b;
+
+expire = now = 0;
+
+/* Parse the signing specification */
+
+if (!(identity = string_nextinlist(&signspec, &sep, NULL, 0)) || !*identity)
+  { s = US"identity"; goto bad_arg_ret; }
+if (!(selector = string_nextinlist(&signspec, &sep, NULL, 0)) || !*selector)
+  { s = US"selector"; goto bad_arg_ret; }
+if (!(privkey = string_nextinlist(&signspec, &sep, NULL, 0))  || !*privkey)
+  { s = US"privkey"; goto bad_arg_ret; }
+if (!arc_valid_id(identity))
+  { s = US"identity"; goto bad_arg_ret; }
+if (!arc_valid_id(selector))
+  { s = US"selector"; goto bad_arg_ret; }
+if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
+  goto ret_sigheaders;
+
+if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
+  {
+  int osep = ',';
+  while ((s = string_nextinlist(&opts, &osep, NULL, 0)))
+    if (Ustrcmp(s, "timestamps") == 0)
+      {
+      options |= ARC_SIGN_OPT_TSTAMP;
+      if (!now) now = time(NULL);
+      }
+    else if (Ustrncmp(s, "expire", 6) == 0)
+      {
+      options |= ARC_SIGN_OPT_EXPIRE;
+      if (*(s += 6) == '=')
+       if (*++s == '+')
+         {
+         if (!(expire = (time_t)atoi(CS ++s)))
+           expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA;
+         if (!now) now = time(NULL);
+         expire += now;
+         }
+       else
+         expire = (time_t)atol(CS s);
+      else
+       {
+       if (!now) now = time(NULL);
+       expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA;
+       }
+      }
+  }
+
+DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
+
+/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
+Then scan the list for an A-R header. */
+
+string_from_gstring(sigheaders);
+if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
+  {
+  hdr_rlist ** rp;
+  for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
+  *rp = rheaders;
+  }
+
+/* Finally, build a normal-order headers list */
+/*XXX only needed for hunt-the-AR? */
+/*XXX also, we really should be accepting any number of ADMD-matching ARs */
+  {
+  header_line * hnext = NULL;
+  for (rheaders = headers_rlist; rheaders;
+       hnext = rheaders->h, rheaders = rheaders->prev)
+    rheaders->h->next = hnext;
+  headers = hnext;
+  }
+
+if (!(arc_sign_find_ar(headers, identity, &ar)))
+  {
+  log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
+  goto ret_sigheaders;
+  }
+
+/* We previously built the data-struct for the existing ARC chain, if any, using a headers
+feed from the DKIM module.  Use that to give the instance number for the ARC set we are
+about to build. */
+
+DEBUG(D_transport)
+  if (arc_sign_ctx.arcset_chain_last)
+    debug_printf("ARC: existing chain highest instance: %d\n",
+      arc_sign_ctx.arcset_chain_last->instance);
+  else
+    debug_printf("ARC: no existing chain\n");
+
+instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
+
+/*
+- Generate AAR
+  - copy the A-R; prepend i= & identity
+*/
+
+g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
+
+/*
+- Generate AMS
+  - Looks fairly like a DKIM sig
+  - Cover all DKIM sig headers as well as the usuals
+    - ? oversigning?
+  - Covers the data
+  - we must have requested a suitable bodyhash previously
+    [done in arc_sign_init()]
+*/
+
+b = arc_ams_setup_sign_bodyhash();
+g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
+      &b->bh, headers_rlist, privkey, options);
+
+/*
+- Generate AS
+  - no body coverage
+  - no h= tag; implicit coverage
+  - arc status from A-R
+    - if fail:
+      - coverage is just the new ARC set
+        including self (but with an empty b= in self)
+    - if non-fail:
+      - all ARC set headers, set-number order, aar then ams then as,
+        including self (but with an empty b= in self)
+*/
+
+if (g)
+  g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
+      privkey, options);
+
+/* Finally, append the dkim headers and return the lot. */
+
+if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
+
+out:
+  if (!g) return string_get(1);
+  (void) string_from_gstring(g);
+  gstring_release_unused(g);
+  return g;
+
+
+bad_arg_ret:
+  log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", s);
+ret_sigheaders:
+  g = sigheaders;
+  goto out;
+}
+
+
+/******************************************************************************/
+
+/* Check to see if the line is an AMS and if so, set up to validate it.
+Called from the DKIM input processing.  This must be done now as the message
+body data is hashed during input.
+
+We call the DKIM code to request a body-hash; it has the facility already
+and the hash parameters might be common with other requests.
+*/
+
+static const uschar *
+arc_header_vfy_feed(gstring * g)
+{
+header_line h;
+arc_line al;
+pdkim_bodyhash * b;
+uschar * errstr;
+
+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");
+/* Parse the AMS header */
+
+memset(&al, 0, sizeof(arc_line));
+h.next = NULL;
+h.slen = len_string_from_gstring(g, &h.text);
+if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, le_all)))
+  {
+  DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
+  goto badline;
+  }
+
+if (!al.a_hash.data)
+  {
+  DEBUG(D_acl) debug_printf("ARC: no a_hash from '%.*s'\n", h.slen, h.text);
+  goto badline;
+  }
+
+/* defaults */
+if (!al.c.data)
+  {
+  al.c_body.data = US"simple"; al.c_body.len = 6;
+  al.c_head = al.c_body;
+  }
+
+/* Ask the dkim code to calc a bodyhash with those specs */
+
+if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
+  return US"dkim hash setup fail";
+
+/* Discard the reference; search again at verify time, knowing that one
+should have been created here. */
+
+return NULL;
+
+badline:
+  return US"line parsing error";
+}
+
+
+
+/* Module API: A header line has been identified by DKIM processing;
+feed it to ARC processing.
+
+Arguments:
+  g            Header line
+  is_vfy       TRUE for verify mode or FALSE for signing mode
+
+Return:
+  NULL for success, or an error string (probably unused)
+*/
+
+static const uschar *
+arc_header_feed(gstring * g, BOOL is_vfy)
+{
+return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
+}
+
+
+
+/******************************************************************************/
+
+/* Construct the list of domains from the ARC chain after validation */
+
+const uschar *
+fn_arc_domains(void)
+{
+arc_set * as;
+unsigned inst;
+gstring * g = NULL;
+
+for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++)
+  {
+  arc_line * hdr_as = as->hdr_as;
+  if (hdr_as)
+    {
+    blob * d = &hdr_as->d;
+
+    for (; inst < as->instance; inst++)
+      g = string_catn(g, US":", 1);
+
+    g = d->data && d->len
+      ? string_append_listele_n(g, ':', d->data, d->len)
+      : string_catn(g, US":", 1);
+    }
+  else
+    g = string_catn(g, US":", 1);
+  }
+if (!g) return US"";
+return string_from_gstring(g);
+}
+
+
+/* Construct an Authentication-Results header portion, for the ARC module */
+
+gstring *
+authres_arc(gstring * g)
+{
+if (arc_state)
+  {
+  int start = 0;               /* Compiler quietening */
+  DEBUG(D_acl) start = gstring_length(g);
+
+  g = string_append(g, 2, US";\n\tarc=", arc_state);
+  if (arc_received_instance > 0)
+    {
+    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_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);
+    }
+  else if (arc_state_reason)
+    g = string_append(g, 3, US" (", arc_state_reason, US")");
+  DEBUG(D_acl) debug_printf("ARC:\tauthres '%.*s'\n",
+                 gstring_length(g) - start - 3, g->s + start + 3);
+  }
+else
+  DEBUG(D_acl) debug_printf("ARC:\tno authres\n");
+return g;
+}
+
+
+#  ifdef SUPPORT_DMARC
+
+/* 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)
+  {
+  gstring * g = NULL;
+
+  /*XXX would we prefer this backwards? */
+  for (arc_set * as = arc_verify_ctx.arcset_chain; as; as = as->next)
+    {
+    arc_line * line = as->hdr_as;
+    if (line)
+      {
+      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\":\"%#b\"", ip);
+       }
+                                                                         /*(*/
+      g = string_catn(g, US")", 1);
+      }
+    }
+  *gp = 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/arc_api.h b/src/src/miscmods/arc_api.h
new file mode 100644 (file)
index 0000000..cf24a4c
--- /dev/null
@@ -0,0 +1,19 @@
+/*************************************************
+*     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 arcmodule */
+
+
+/* Function table entry numbers */
+
+#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
index 0500da2bee57e14210e0413cc9e5ca2139f522f8..e2d1705e3722d32092cfda6a5601f424554a0364 100644 (file)
@@ -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;
   }
index d977d29feec5d71019838e1d1c2aad994bf6b7df..e192bda1b958f94007295e031c6613fff15285ac 100644 (file)
@@ -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);
index cdbdfc5e0d3e02534335f74f6cf788d8a143e8b3..7c2f3421793e62270862b0579da8c262aff43f05 100644 (file)
@@ -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;
 }
 
index 117a7ae6a002858150e3271e5c8b72819054f07c..0e1907d9ab133d826445e2eddaf904c4cf468750 100644 (file)
@@ -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
index 541e9320daab2b7fa1024e1d78a5d377acfe341b..ae4e1ff7ef50cba54ea50f5f2946eb0154982b4a 100644 (file)
@@ -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))
index e7589485060becd6803e66a9ffd873aff888f6dd..e76790fea241c42ac92a9aef33bfadb842715093 100644 (file)
@@ -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 */
index 36b5e61fce8d4e52fb787c03c0a2b0851294989f..594b42e1f3f0b1099bdd4dcd585d62aafeab3d2c 100644 (file)
@@ -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*/
     }