ARC initial implementation. Experimental. Bug 2162
authorJeremy Harris <jgh146exb@wizmail.org>
Fri, 2 Mar 2018 23:53:32 +0000 (23:53 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sat, 3 Mar 2018 17:35:18 +0000 (17:35 +0000)
39 files changed:
doc/doc-txt/NewStuff
doc/doc-txt/OptionLists.txt
doc/doc-txt/experimental-spec.txt
src/OS/Makefile-Base
src/scripts/MakeLinks
src/src/EDITME
src/src/acl.c
src/src/arc.c [new file with mode: 0644]
src/src/config.h.defaults
src/src/dkim.c
src/src/dkim_transport.c
src/src/exim.c
src/src/expand.c
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/macro_predef.c
src/src/pdkim/pdkim.c
src/src/pdkim/pdkim.h
src/src/pdkim/signing.c
src/src/pdkim/signing.h
src/src/receive.c
src/src/string.c
src/src/structs.h
src/src/transports/smtp.c
src/src/transports/smtp.h
test/aux-fixed/4560.mlistfooter [new file with mode: 0644]
test/confs/4560 [new file with mode: 0644]
test/log/4501
test/log/4502
test/log/4503
test/log/4504
test/log/4506
test/log/4560 [new file with mode: 0644]
test/mail/4560.a [new file with mode: 0644]
test/runtest
test/scripts/4560-ARC/4560 [new file with mode: 0644]
test/scripts/4560-ARC/REQUIRES [new file with mode: 0644]
test/stderr/4520

index 37f53bf..071d4a5 100644 (file)
@@ -44,6 +44,8 @@ Version 4.91
 12. Expansion item ${authresults {<machine>}} for creating an
     Authentication-Results: header.
 
+13. EXPERIMENTAL_ARC.  See the experimental.spec file.
+
 
 Version 4.90
 ------------
index 5728643..1fe72be 100644 (file)
@@ -82,6 +82,7 @@ allow_localhost                      boolean         false         smtp
 allow_mx_to_ip                       boolean         false         main              3.14
 allow_symlink                        boolean         false         appendfile
 allow_utf8_domains                   boolean         false         main              4.14
+arc_sign                            string*         unset         smtp              4.91 with Experimental_ARC
 auth_advertise_hosts                 host list       "*"           main              4.00
 authenticated_sender                 string*         unset         smtp              4.14
 authenticated_sender_force           boolean         false         smtp              4.61
index d5140d5..4ed6f25 100644 (file)
@@ -756,6 +756,53 @@ to your Local/Makefile. (Re-)build/install exim. exim -d should show
 Experimental_QUEUEFILE in the line "Support for:".
 
 
+ARC support
+-----------
+Specification: https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-11
+Note that this is not an RFC yet, so may change.
+
+ARC is intended to support the utility of SPF and DKIM in the presence of
+intermediaries in the transmission path - forwarders and mailinglists -
+by establishing a cryptographically-signed chain in headers.
+
+Normally one would only bother doing ARC-signing when functioning as
+an intermediary.  One might do verify for local destinations.
+
+ARC uses the notion of a "ADministrative Management Domain" (ADMD).
+Described in RFC 5598 (section 2.3), this is essentially the set of
+mail-handling systems that the mail transits.  A label should be chosen to
+identify the ADMD.  Messages should be ARC-verified on entry to the ADMD,
+and ARC-signed on exit from it.
+
+
+Verification
+--
+An ACL condition is provided to perform the "verifier actions" detailed
+in section 6 of the above specification.  It may be called from the DATA ACL
+and succeeds if the result matches any of a given list.
+It also records the highest ARC instance number (the chain size)
+and verification result for later use in creating an Authentication-Results:
+standard header.
+
+  verify = arc/<acceptable_list>   none:fail:pass
+
+  add_header = :at_start:${authresults {<admd-identifier>}}
+
+       Note that it would be wise to strip incoming messages of A-R headers
+       that claim to be from our own <admd-identifier>.
+
+Receive log lines for an ARC pass will be tagged "ARC".
+
+
+Signing
+--
+arc_sign = <admd-identifier> : <selector> : <privkey>
+An option on the smtp transport, which constructs and prepends to the message
+an ARC set of headers.  The textually-first Authentication-Results: header
+is used as a basis (you must have added one on entry to the ADMD).
+
+
+
 --------------------------------------------------------------
 End of file
 --------------------------------------------------------------
index eefc02b..8d191ca 100644 (file)
@@ -477,7 +477,8 @@ convert4r4: config ../src/convert4r4.src
 # are thrown away by the linker.
 
 OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o
-OBJ_EXPERIMENTAL = bmi_spam.o \
+OBJ_EXPERIMENTAL =     arc.o \
+                       bmi_spam.o \
                        dane.o \
                        dcc.o \
                        dmarc.o \
@@ -827,6 +828,7 @@ spool_mbox.o:    $(HDRS) spool_mbox.c
 
 # Dependencies for EXPERIMENTAL_* modules
 
+arc.o:         $(HDRS) pdkim/pdkim.h arc.c
 bmi_spam.o:    $(HDRS) bmi_spam.c
 dane.o:                $(HDRS) dane.c dane-openssl.c
 dcc.o:         $(HDRS) dcc.h dcc.c
index 6314fe4..f5a4e50 100755 (executable)
@@ -122,7 +122,7 @@ do
 done
 
 # EXPERIMENTAL_*
-for f in  bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
+for f in  arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
   danessl.h imap_utf7.c spf.c spf.h srs.c srs.h utf8.c
 do
   ln -s ../src/$f $f
index b1b9af2..1882137 100644 (file)
@@ -485,6 +485,10 @@ DISABLE_MAL_MKS=yes
 # CFLAGS += -I/usr/local/include
 # LDFLAGS += -lopendmarc
 
+# Uncomment the following line to add ARC (Authenticated Received Chain)
+# support.  You must have SPF and DKIM support enabled also.
+# EXPERIMENTAL_ARC
+
 # Uncomment the following lines to add Brightmail AntiSpam support. You need
 # to have the Brightmail client SDK installed. Please check the experimental
 # documentation for implementation details. You need to edit the CFLAGS and
index 61316a8..4be5e07 100644 (file)
@@ -958,7 +958,7 @@ else
 
 /* Loop for multiple header lines, taking care about continuations */
 
-for (p = q; *p != 0; )
+for (p = q; *p; p = q)
   {
   const uschar *s;
   uschar * hdr;
@@ -970,7 +970,7 @@ for (p = q; *p != 0; )
   for (;;)
     {
     q = Ustrchr(q, '\n');              /* we know there was a newline */
-    if (*(++q) != ' ' && *q != '\t') break;
+    if (*++q != ' ' && *q != '\t') break;
     }
 
   /* If the line starts with a colon, interpret the instruction for where to
@@ -1005,24 +1005,22 @@ for (p = q; *p != 0; )
   to the front of it. */
 
   for (s = p; s < q - 1; s++)
-    {
     if (*s == ':' || !isgraph(*s)) break;
-    }
 
-  hdr = string_sprintf("%s%.*s", (*s == ':')? "" : "X-ACL-Warn: ", (int) (q - p), p);
+  hdr = string_sprintf("%s%.*s", *s == ':' ? "" : "X-ACL-Warn: ", (int) (q - p), p);
   hlen = Ustrlen(hdr);
 
   /* See if this line has already been added */
 
-  while (*hptr != NULL)
+  while (*hptr)
     {
     if (Ustrncmp((*hptr)->text, hdr, hlen) == 0) break;
-    hptr = &((*hptr)->next);
+    hptr = &(*hptr)->next;
     }
 
   /* Add if not previously present */
 
-  if (*hptr == NULL)
+  if (!*hptr)
     {
     header_line *h = store_get(sizeof(header_line));
     h->text = hdr;
@@ -1030,12 +1028,8 @@ for (p = q; *p != 0; )
     h->type = newtype;
     h->slen = hlen;
     *hptr = h;
-    hptr = &(h->next);
+    hptr = &h->next;
     }
-
-  /* Advance for next header line within the string */
-
-  p = q;
   }
 }
 
@@ -1662,9 +1656,9 @@ if (!(vp->where_allowed & BIT(where)))
 switch(vp->value)
   {
   case VERIFY_REV_HOST_LKUP:
-    if (sender_host_address == NULL) return OK;
+    if (!sender_host_address) return OK;
     if ((rc = acl_verify_reverse(user_msgptr, log_msgptr)) == DEFER)
-      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+      while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
        if (strcmpic(ss, US"defer_ok") == 0)
          return OK;
     return rc;
@@ -1683,7 +1677,7 @@ switch(vp->value)
     occurred earlier. If not, we can attempt the verification now. */
 
     if (!helo_verified && !helo_verify_failed) smtp_verify_helo();
-    return helo_verified? OK : FAIL;
+    return helo_verified ? OK : FAIL;
 
   case VERIFY_CSA:
     /* Do Client SMTP Authorization checks in a separate function, and turn the
@@ -1696,6 +1690,23 @@ switch(vp->value)
     DEBUG(D_acl) debug_printf_indent("CSA result %s\n", csa_status);
     return csa_return_code[rc];
 
+#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\n", arc_state);
+
+    if (!condlist) condlist = US"none:pass";
+    while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
+      if (Ustrcmp(arc_state, cond) == 0) return OK;
+    return FAIL;
+    }
+#endif
+
   case VERIFY_HDR_SYNTAX:
     /* Check that all relevant header lines have the correct 5322-syntax. If there is
     a syntax error, we return details of the error to the sender if configured to
@@ -1715,7 +1726,7 @@ switch(vp->value)
     See RFC 5322, 2.2. and RFC 6532, 3. */
 
     rc = verify_check_header_names_ascii(log_msgptr);
-    if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
+    if (rc != OK && smtp_return_error_details && *log_msgptr)
       *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
     return rc;
 
@@ -1723,8 +1734,7 @@ switch(vp->value)
     /* Check that no recipient of this message is "blind", that is, every envelope
     recipient must be mentioned in either To: or Cc:. */
 
-    rc = verify_check_notblind();
-    if (rc != OK)
+    if ((rc = verify_check_notblind()) != OK)
       {
       *log_msgptr = string_sprintf("bcc recipient detected");
       if (smtp_return_error_details)
diff --git a/src/src/arc.c b/src/src/arc.c
new file mode 100644 (file)
index 0000000..1323a45
--- /dev/null
@@ -0,0 +1,1716 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+/* Experimental ARC support for Exim
+   Copyright (c) Jeremy Harris 2018
+   License: GPL
+*/
+
+#include "exim.h"
+#ifdef EXPERIMENTAL_ARC
+# if !defined SUPPORT_SPF
+#  error SPF must also be enabled for ARC
+# elif defined DISABLE_DKIM
+#  error DKIM must also be enabled for ARC
+# else
+
+#  include "functions.h"
+#  include "pdkim/pdkim.h"
+#  include "pdkim/signing.h"
+
+extern pdkim_ctx * dkim_verify_ctx;
+extern pdkim_ctx dkim_sign_ctx;
+
+/******************************************************************************/
+
+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;
+
+  /* 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;
+
+  BOOL                 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
+
+static hdr_rlist * headers_rlist;
+static arc_ctx arc_sign_ctx = { NULL };
+
+
+/******************************************************************************/
+
+
+/* Get the instance number from the header.
+Return 0 on error */
+static unsigned
+arc_instance_from_hdr(const arc_line * al)
+{
+uschar * s = al->i.data;
+if (!s || !al->i.len) return 0;
+return (unsigned) atoi(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));
+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 duplicates. */
+
+static uschar *
+arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only)
+{
+uschar * s = h->text + off;
+uschar * r;
+uschar c;
+
+al->complete = h;
+
+if (!instance_only)
+  {
+  al->rawsig_no_b_val.data = store_get(h->slen + 1);
+  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] */
+
+  s = skip_fws(s);                                             /* FWS */
+  if (!*s) break;
+/* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */
+  tagchar = *s++;
+  s = skip_fws(s);                                             /* FWS */
+  if (!*s) break;
+
+  if (!instance_only || tagchar == 'i') switch (tagchar)
+    {
+    case 'a':                          /* a= AMS algorithm */
+      {
+      if (*s != '=') return US"no 'a' value";
+      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':
+      {
+      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);
+         al->b.data = string_from_gstring(g);
+         al->b.len = g->ptr;
+         gstring_reset_unused(g);
+         bend = s;
+         break;
+       case 'h':                       /* bh= AMS body hash */
+         s = skip_fws(++s);                                    /* FWS */
+         if (*s != '=') return US"no bh value";
+         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);
+         al->bh.data = string_from_gstring(g);
+         al->bh.len = g->ptr;
+         gstring_reset_unused(g);
+         break;
+       default:
+         return US"b? tag";
+       }
+      }
+      break;
+    case 'c':
+      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 */
+         if (*++s != '=') return US"cv tag val";
+         if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup";
+         break;
+       default:
+         return US"c? tag";
+       }
+      break;
+    case 'd':                          /* d= AMS domain */
+      if (*s != '=') return US"d tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup";
+      break;
+    case 'h':                          /* h= AMS headers */
+      if (*s != '=') return US"h tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup";
+      break;
+    case 'i':                          /* i= ARC set instance */
+      if (*s != '=') return US"i tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup";
+      if (instance_only) goto done;
+      break;
+    case 'l':                          /* l= bodylength */
+      if (*s != '=') return US"l tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup";
+      break;
+    case 's':                          /* s= AMS selector */
+      if (*s != '=') return US"s tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup";
+      break;
+    }
+
+  while ((c = *s) && c != ';') s++;
+  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 (!instance_only)
+    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 (!instance_only)
+  *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,
+  BOOL instance_only)
+{
+int i;
+arc_set * as;
+arc_line * al = store_get(sizeof(arc_line)), ** alp;
+uschar * e;
+
+memset(al, 0, sizeof(arc_line));
+
+if ((e = arc_parse_line(al, h, off, instance_only)))
+  {
+  DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
+  return US"line parse";
+  }
+if (!(i = arc_instance_from_hdr(al)))  return US"instance find";
+if (!(as = arc_find_set(ctx, i)))      return US"set find";
+if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
+
+*alp = al;
+return NULL;
+}
+
+
+
+
+static const uschar *
+arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only)
+{
+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),
+                         TRUE)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
+    return US"inserting AAR";
+    }
+  }
+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),
+                         instance_only)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
+    return US"inserting AMS";
+    }
+
+  /* defaults */
+  /*XXX dubious selection of ams here */
+  ams = ctx->arcset_chain->hdr_ams;
+  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),
+                         instance_only)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
+    return US"inserting AS";
+    }
+  }
+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;
+
+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, * 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));
+  r->prev = rprev;
+  r->used = FALSE;
+  r->h = h;
+  rprev = r;
+
+  if ((e = arc_try_header(ctx, h, FALSE)))
+    return e;
+  }
+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;
+}
+
+/******************************************************************************/
+
+/* 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;
+int hashtype = pdkim_hashname_to_hashtype(
+                   ams->a_hash.data, ams->a_hash.len);
+hctx hhash_ctx;
+const uschar * s;
+int len;
+
+if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+  {
+  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 = pdkim_relax_header_n(s, r->h->slen, TRUE);
+
+      len = Ustrlen(s);
+      DEBUG(D_acl) pdkim_quoteprint(s, len);
+      exim_sha_update(&hhash_ctx, s, Ustrlen(s));
+      r->used = TRUE;
+      break;
+      }
+
+/* Finally add in the signature header (with the b= tag stripped) */
+
+s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
+if (relaxed)
+  len = Ustrlen(s = pdkim_relax_header_n(s, len, TRUE));
+DEBUG(D_acl) pdkim_quoteprint(s, len);
+exim_sha_update(&hhash_ctx, s, len);
+
+exim_sha_finish(&hhash_ctx, hhash);
+DEBUG(D_acl)
+  { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
+return;
+}
+
+
+
+
+static pdkim_pubkey *
+arc_line_to_pubkey(arc_line * al)
+{
+uschar * dns_txt;
+pdkim_pubkey * p;
+
+if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
+         al->s.len, al->s.data, al->d.len, al->d.data))))
+  {
+  DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
+  return NULL;
+  }
+
+if (  !(p = pdkim_parse_pubkey_record(dns_txt))
+   || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
+   )
+  {
+  DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
+  return NULL;
+  }
+
+/* If the pubkey limits use to specified hashes, reject unusable
+signatures. XXX should we have looked for multiple dns records? */
+
+if (p->hashes)
+  {
+  const uschar * list = p->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",
+                             p->hashes, al->a.len, al->a.data);
+    return NULL;
+    }
+  }
+return p;
+}
+
+
+
+
+static pdkim_bodyhash *
+arc_ams_setup_vfy_bodyhash(arc_line * ams)
+{
+int canon_head, canon_body;
+long bodylen;
+
+pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
+bodylen = ams->l.data
+       ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
+
+return pdkim_set_bodyhash(dkim_verify_ctx,
+       pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
+       canon_body,
+       bodylen);
+}
+
+
+
+/* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
+and without a DKIM v= tag.
+*/
+
+static uschar *
+arc_ams_verify(arc_ctx * ctx, arc_set * as)
+{
+arc_line * ams = as->hdr_ams;
+pdkim_bodyhash * b;
+pdkim_pubkey * p;
+blob sighash;
+blob hhash;
+ev_ctx vctx;
+int hashtype;
+const uschar * errstr;
+
+as->ams_verify_done = TRUE;
+
+/* 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)
+  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)))
+  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,
+              ams->a_hash.len, ams->a_hash.data);
+  pdkim_hexprint(CUS b->bh.data, b->bh.len);
+  }
+
+/* We know the bh-tag blob is of a nul-term string, so safe as a string */
+
+if (  !ams->bh.data
+   || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
+   || 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);
+    pdkim_hexprint(sighash.data, sighash.len);
+    debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
+    }
+  return US"body hash compare mismatch";
+  }
+
+DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
+
+/* Get the public key from DNS */
+
+if (!(p = arc_line_to_pubkey(ams)))
+  return US"pubkey problem";
+
+/* We know the b-tag blob is of a nul-term string, so safe as a string */
+pdkim_decode_base64(ams->b.data, &sighash);
+
+arc_get_verify_hhash(ctx, ams, &hhash);
+
+/* Setup the interface to the signing library */
+
+if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
+  {
+  DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
+  return US"fail";
+  }
+
+hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
+
+if ((errstr = exim_dkim_verify(&vctx,
+         pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
+  {
+  DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
+  return US"ams sig verify fail";
+  }
+
+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;
+uschar * ret = NULL;
+
+if (!(as = ctx->arcset_chain))
+  return US"none";
+
+for(inst = 0; as; as = as->next)
+  {
+  if (  as->instance != ++inst
+     || !as->hdr_aar || !as->hdr_ams || !as->hdr_as
+     || arc_cv_match(as->hdr_as, US"fail")
+     )
+    {
+    DEBUG(D_acl) debug_printf("ARC i=%d fail"
+      " (cv, sequence or missing header)\n", as->instance);
+    ret = US"fail";
+    }
+
+  /* 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_received = ctx->arcset_chain_last;
+arc_received_instance = inst;
+if (ret)
+  return ret;
+
+/* We can skip the latest-AMS validation, if we already did it. */
+
+as = ctx->arcset_chain_last;
+if (as->ams_verify_done ? !as->ams_verify_passed : !!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;
+int hashtype;
+hctx hhash_ctx;
+blob hhash_computed;
+blob sighash;
+ev_ctx vctx;
+pdkim_pubkey * p;
+const uschar * errstr;
+
+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
+   )
+  return US"fail";
+
+/*
+       3.  Initialize a hash function corresponding to the "a" tag of
+           the ARC-Seal.
+*/
+
+hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
+
+if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+  {
+  DEBUG(D_acl)
+      debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
+  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.
+*/
+
+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 = pdkim_relax_header_n(al->complete->text,
+                                           al->complete->slen, TRUE);
+  len = Ustrlen(s);
+  DEBUG(D_acl) pdkim_quoteprint(s, len);
+  exim_sha_update(&hhash_ctx, s, len);
+
+  al = as2->hdr_ams;
+  if (!(s = al->relaxed))
+    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+                                           al->complete->slen, TRUE);
+  len = Ustrlen(s);
+  DEBUG(D_acl) pdkim_quoteprint(s, len);
+  exim_sha_update(&hhash_ctx, s, len);
+
+  al = as2->hdr_as;
+  if (as2->instance == as->instance)
+    s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
+                                       al->rawsig_no_b_val.len, TRUE);
+  else if (!(s = al->relaxed))
+    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+                                           al->complete->slen, TRUE);
+  len = Ustrlen(s);
+  DEBUG(D_acl) pdkim_quoteprint(s, len);
+  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, hdr_as->a_hash.len, hdr_as->a_hash.data);
+  pdkim_hexprint(hhash_computed.data, hhash_computed.len);
+  }
+
+
+/*
+       6.  Retrieve the public key identified by the "s" and "d" tags in
+           the ARC-Seal, as described in Section 4.1.6.
+*/
+
+if (!(p = arc_line_to_pubkey(hdr_as)))
+  return US"pubkey problem";
+
+/*
+       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 */
+pdkim_decode_base64(hdr_as->b.data, &sighash);
+
+if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
+  {
+  DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
+  return US"fail";
+  }
+
+hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
+
+if ((errstr = exim_dkim_verify(&vctx,
+             pdkim_hashes[hashtype].exim_hashmethod,
+             &hhash_computed, &sighash)))
+  {
+  DEBUG(D_acl)
+    debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
+  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;
+
+if (!as)
+  return US"none";
+
+while (as)
+  {
+  if (arc_seal_verify(ctx, as)) return US"fail";
+  as = as->next;
+  }
+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)
+{
+arc_ctx ctx = { NULL };
+const uschar * res;
+
+/* 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(&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(&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(&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));
+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;
+
+/* This works for either NL or CRLF lines; also nul-termination */
+while (*++s)
+  if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
+s++;           /* move past end of line */
+
+return r;
+}
+
+
+/* Walk the given headers strings identifying each header, and construct
+a reverse-order list.  Also parse ARC-chain headers and build the chain
+struct, retaining pointers into the string.
+*/
+
+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)
+  {
+  uschar * s2;
+
+  /* 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 = g ? g->ptr : 0;
+arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
+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_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
+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 : pdkim_hashes[hashtype].exim_hashmethod;
+blob hhash;
+es_ctx sctx;
+const uschar * errstr;
+
+DEBUG(D_transport)
+  {
+  hctx hhash_ctx;
+  debug_printf("ARC: %s header data for signing:\n", why);
+  pdkim_quoteprint(hdata->s, hdata->ptr);
+
+  (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+  exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
+  exim_sha_finish(&hhash_ctx, &hhash);
+  debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
+  }
+
+if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
+  {
+  hctx hhash_ctx;
+  (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+  exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
+  exim_sha_finish(&hhash_ctx, &hhash);
+  }
+else
+  {
+  hhash.data = hdata->s;
+  hhash.len = hdata->ptr;
+  }
+
+if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
+   || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "ARC: %s signing: %s\n", why, errstr);
+  return FALSE;
+  }
+return TRUE;
+}
+
+
+
+static gstring *
+arc_sign_append_sig(gstring * g, blob * sig)
+{
+/*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
+sig->data = pdkim_encode_base64(sig);
+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_reset_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)
+{
+uschar * s;
+gstring * hdata = NULL;
+int col;
+int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);      /*XXX hardwired */
+blob sig;
+int ams_off;
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
+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 = g->ptr;
+g = string_append(g, 10,
+      ARC_HDR_AMS,
+      US" i=", string_sprintf("%d", instance),
+      US"; a=rsa-sha256; c=relaxed; d=", identity,             /*XXX hardwired */
+      US"; s=", selector,
+      US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
+      US";\r\n\th=");
+
+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,
+               pdkim_relax_header_n(htext, rheaders->h->slen, TRUE));  /*XXX hardwired */
+      break;
+      }
+    }
+  }
+
+/* Lose the last colon from the h= list */
+
+if (g->s[g->ptr - 1] == ':') g->ptr--;
+
+g = string_catn(g, US";\r\n\tb=;", 7);
+
+/* Include the pseudo-header in the accumulation */
+/*XXX should that be prepended rather than appended? */
+/*XXX also need to include at the verify stage */
+
+s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, TRUE);
+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. */
+
+g->ptr--;
+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 NULL;
+}
+
+
+
+/* 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)
+{
+gstring * arcset;
+arc_set * as;
+uschar * status = arc_ar_cv_status(ar);
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
+header_line * h = (header_line *)(al+1);
+uschar * s;
+
+gstring * hdata = NULL;
+int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);      /*XXX hardwired */
+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)
+*/
+
+/* Construct the AS except for the signature */
+
+arcset = string_append(NULL, 10,
+         ARC_HDR_AS,
+         US" i=", string_sprintf("%d", instance),
+         US"; cv=", status,
+         US"; a=rsa-sha256; c=relaxed; d=", identity,          /*XXX hardwired */
+         US"; s=", selector,                                   /*XXX same as AMS */
+         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 (as = Ustrcmp(status, US"fail") == 0
+       ? ctx->arcset_chain_last : ctx->arcset_chain;
+     as; as = as->next)
+  {
+  /* Accumulate AAR then AMS then AS.  Relaxed canonicalisation
+  is required per standard. */
+
+  h = as->hdr_aar->complete;
+  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+  h = as->hdr_ams->complete;
+  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+  h = as->hdr_as->complete;
+  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+  }
+
+/* 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);
+}
+
+
+/**************************************/
+
+/* Return pointer to pdkim_bodyhash for given hash method, creating new
+method if needed.
+*/
+
+void *
+arc_ams_setup_sign_bodyhash(void)
+{
+int canon_head, canon_body;
+
+DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
+pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body);     /*XXX hardwired */
+return pdkim_set_bodyhash(&dkim_sign_ctx,
+       pdkim_hashname_to_hashtype(US"sha256", 6),                      /*XXX hardwired */
+       canon_body,
+       -1);
+}
+
+
+
+/* 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_copyn(g->s, g->ptr);
+headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
+return arc_try_header(&arc_sign_ctx, headers_rlist->h, 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
+               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;
+int sep = 0;
+header_line * headers;
+hdr_rlist * rheaders;
+blob ar;
+int instance;
+gstring * g = NULL;
+pdkim_bodyhash * b;
+
+/* Parse the signing specification */
+
+identity = string_nextinlist(&signspec, &sep, NULL, 0);
+selector = string_nextinlist(&signspec, &sep, NULL, 0);
+if (  !*identity | !*selector
+   || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "ARC: bad signing-specification");
+  return NULL;
+  }
+if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
+  return NULL;
+
+DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
+
+/*
+- scan headers for existing ARC chain & A-R (with matching system-identfier)
+  - paniclog & skip on problems (no A-R)
+*/
+
+/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it */
+
+string_from_gstring(sigheaders);
+if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
+  {
+  hdr_rlist ** rp;
+  for (rp = &rheaders; *rp; ) rp = &(*rp)->prev;
+  *rp = headers_rlist;
+  headers_rlist = rheaders;
+  }
+else
+  rheaders = headers_rlist;
+/* Finally, build a normal-order headers list */
+/*XXX only needed for hunt-the-AR? */
+{
+header_line * hnext = NULL;
+for (; rheaders; hnext = rheaders->h, rheaders = rheaders->prev)
+  rheaders->h->next = hnext;
+headers = hnext;
+}
+
+instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
+
+if (!(arc_sign_find_ar(headers, identity, &ar)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "ARC: no Authentication-Results header for signing");
+  return sigheaders ? sigheaders : string_get(0);
+  }
+
+/*
+- 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
+*/
+
+b = arc_ams_setup_sign_bodyhash();
+g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
+      &b->bh, headers_rlist, privkey);
+
+/*
+- 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)
+*/
+
+g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
+
+/* Finally, append the dkim headers and return the lot. */
+
+g = string_catn(g, sigheaders->s, sigheaders->ptr);
+(void) string_from_gstring(g);
+gstring_reset_unused(g);
+return g;
+}
+
+
+/******************************************************************************/
+
+/* 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 (!dkim_verify_ctx) return US"no dkim context";
+
+if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
+
+DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
+/* Parse the AMS header */
+
+h.next = NULL;
+h.slen = g->size;
+h.text = g->s;
+memset(&al, 0, sizeof(arc_line));
+if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
+  {
+  DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
+  return US"line parsing error";
+  }
+
+/* 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;
+}
+
+
+
+/* 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 an Authenticate-Results header portion, for the ARC module */
+
+gstring *
+authres_arc(gstring * g)
+{
+if (arc_state)
+  {
+  arc_line * highest_ams;
+  int start;
+  DEBUG(D_acl) start = g->ptr;
+
+  g = string_append(g, 2, US";\n\tarc=", arc_state);
+  if (arc_received_instance > 0)
+    {
+    g = string_append(g, 3, US" (i=",
+      string_sprintf("%d", arc_received_instance), US") header.s=");
+    highest_ams = arc_received->hdr_ams;
+    g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
+
+    g = string_append(g, 2,
+      US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
+
+    if (sender_host_address)
+      g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
+    }
+  DEBUG(D_acl) debug_printf("ARC:  authres '%.*s'\n",
+                 g->ptr - start - 3, g->s + start + 3);
+  }
+else
+  DEBUG(D_acl) debug_printf("ARC:  no authres\n");
+return g;
+}
+
+
+# endif /* SUPPORT_SPF */
+#endif /* EXPERIMENTAL_ARC */
+/* vi: aw ai sw=2
+ */
index 2e6985a..ce478d5 100644 (file)
@@ -190,6 +190,7 @@ Do not put spaces between # and the 'define'.
 #define DISABLE_MAL_CMDLINE
 
 /* EXPERIMENTAL features */
+#define EXPERIMENTAL_ARC
 #define EXPERIMENTAL_BRIGHTMAIL
 #define EXPERIMENTAL_DCC
 #define EXPERIMENTAL_DSN_INFO
index 092eb55..1767eb6 100644 (file)
@@ -26,6 +26,7 @@ builtin_macro_create_var(US"_DKIM_SIGN_HEADERS", US PDKIM_DEFAULT_SIGN_HEADERS);
 
 
 
+pdkim_ctx dkim_sign_ctx;
 
 int dkim_verify_oldpool;
 pdkim_ctx *dkim_verify_ctx = NULL;
@@ -38,8 +39,8 @@ static const uschar * dkim_collect_error = NULL;
 /*XXX the caller only uses the first record if we return multiple.
 */
 
-static uschar *
-dkim_exim_query_dns_txt(char * name)
+uschar *
+dkim_exim_query_dns_txt(uschar * name)
 {
 dns_answer dnsa;
 dns_scan dnss;
@@ -47,7 +48,7 @@ dns_record *rr;
 gstring * g = NULL;
 
 lookup_dnssec_authenticated = NULL;
-if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED)
+if (dns_lookup(&dnsa, name, T_TXT, NULL) != DNS_SUCCEED)
   return NULL; /*XXX better error detail?  logging? */
 
 /* Search for TXT record */
@@ -75,7 +76,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     /* check if this looks like a DKIM record */
     if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0)
       {
-      store_reset(g->s + g->ptr + 1);
+      gstring_reset_unused(g);
       return string_from_gstring(g);
       }
 
@@ -563,6 +564,16 @@ switch (what)
 }
 
 
+void
+dkim_exim_sign_init(void)
+{
+int old_pool = store_pool;
+store_pool = POOL_MAIN;
+pdkim_init_context(&dkim_sign_ctx, FALSE, &dkim_exim_query_dns_txt);
+store_pool = old_pool;
+}
+
+
 /* Generate signatures for the given file.
 If a prefix is given, prepend it to the file for the calculations.
 
@@ -575,10 +586,9 @@ gstring *
 dkim_exim_sign(int fd, off_t off, uschar * prefix,
   struct ob_dkim * dkim, const uschar ** errstr)
 {
-const uschar * dkim_domain;
+const uschar * dkim_domain = NULL;
 int sep = 0;
 gstring * seen_doms = NULL;
-pdkim_ctx ctx;
 pdkim_signature * sig;
 gstring * sigbuf;
 int pdkim_rc;
@@ -587,18 +597,21 @@ uschar buf[4096];
 int save_errno = 0;
 int old_pool = store_pool;
 uschar * errwhen;
+const uschar * s;
 
-store_pool = POOL_MAIN;
+if (dkim->dot_stuffed)
+  dkim_sign_ctx.flags |= PDKIM_DOT_TERM;
 
-pdkim_init_context(&ctx, dkim->dot_stuffed, &dkim_exim_query_dns_txt);
+store_pool = POOL_MAIN;
 
-if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
+if ((s = dkim->dkim_domain) && !(dkim_domain = expand_cstring(s)))
   /* expansion error, do not send message. */
   { errwhen = US"dkim_domain"; goto expand_bad; }
 
 /* Set $dkim_domain expansion variable to each unique domain in list. */
 
-while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
+if (dkim_domain)
+  while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
   {
   const uschar * dkim_sel;
   int sel_sep = 0;
@@ -667,39 +680,10 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
        )
       continue;                /* don't sign, but no error */
 
-    if (dkim_private_key_expanded[0] == '/')
-      {
-      int privkey_fd, off = 0, len;
-
-      /* Looks like a filename, load the private key. */
-
-      memset(big_buffer, 0, big_buffer_size);
-
-      if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
-       {
-       log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
-                  "private key file for reading: %s",
-                  dkim_private_key_expanded);
-       goto bad;
-       }
-
-      do
-       {
-       if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
-         {
-         (void) close(privkey_fd);
-         log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
-                    dkim_private_key_expanded);
-         goto bad;
-         }
-       off += len;
-       }
-      while (len > 0);
-
-      (void) close(privkey_fd);
-      big_buffer[off] = '\0';
-      dkim_private_key_expanded = big_buffer;
-      }
+    if (  dkim_private_key_expanded[0] == '/'
+       && !(dkim_private_key_expanded =
+            expand_file_big_buffer(dkim_private_key_expanded)))
+      goto bad;
 
     if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
       { errwhen = US"dkim_hash"; goto expand_bad; }
@@ -710,7 +694,7 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
       else if (!*dkim_identity_expanded)
        dkim_identity_expanded = NULL;
 
-    if (!(sig = pdkim_init_sign(&ctx, dkim_signing_domain,
+    if (!(sig = pdkim_init_sign(&dkim_sign_ctx, dkim_signing_domain,
                          dkim_signing_selector,
                          dkim_private_key_expanded,
                          dkim_hash_expanded,
@@ -725,51 +709,61 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
                        pdkim_canon,
                        pdkim_canon, -1, 0, 0);
 
-    if (!pdkim_set_bodyhash(&ctx, sig))
+    if (!pdkim_set_sig_bodyhash(&dkim_sign_ctx, sig))
       goto bad;
 
-    if (!ctx.sig)              /* link sig to context chain */
-      ctx.sig = sig;
+    if (!dkim_sign_ctx.sig)            /* link sig to context chain */
+      dkim_sign_ctx.sig = sig;
     else
       {
-      pdkim_signature * n = ctx.sig;
+      pdkim_signature * n = dkim_sign_ctx.sig;
       while (n->next) n = n->next;
       n->next = sig;
       }
     }
   }
-if (!ctx.sig)
+
+/* We may need to carry on with the data-feed even if there are no DKIM sigs to
+produce, if some other package (eg. ARC) is signing. */
+
+if (!dkim_sign_ctx.sig && !dkim->force_bodyhash)
   {
   DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
   sigbuf = string_get(1);      /* return a zero-len string */
-  goto CLEANUP;
   }
-
-if (prefix && (pdkim_rc = pdkim_feed(&ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
-  goto pk_bad;
-
-if (lseek(fd, off, SEEK_SET) < 0)
-  sread = -1;
 else
-  while ((sread = read(fd, &buf, sizeof(buf))) > 0)
-    if ((pdkim_rc = pdkim_feed(&ctx, buf, sread)) != PDKIM_OK)
-      goto pk_bad;
-
-/* Handle failed read above. */
-if (sread == -1)
   {
-  debug_printf("DKIM: Error reading -K file.\n");
-  save_errno = errno;
-  goto bad;
-  }
+  if (prefix && (pdkim_rc = pdkim_feed(&dkim_sign_ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
+    goto pk_bad;
 
-/* Build string of headers, one per signature */
+  if (lseek(fd, off, SEEK_SET) < 0)
+    sread = -1;
+  else
+    while ((sread = read(fd, &buf, sizeof(buf))) > 0)
+      if ((pdkim_rc = pdkim_feed(&dkim_sign_ctx, buf, sread)) != PDKIM_OK)
+       goto pk_bad;
 
-if ((pdkim_rc = pdkim_feed_finish(&ctx, &sig, errstr)) != PDKIM_OK)
-  goto pk_bad;
+  /* Handle failed read above. */
+  if (sread == -1)
+    {
+    debug_printf("DKIM: Error reading -K file.\n");
+    save_errno = errno;
+    goto bad;
+    }
+
+  /* Build string of headers, one per signature */
 
-for (sigbuf = NULL; sig; sig = sig->next)
-  sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
+  if ((pdkim_rc = pdkim_feed_finish(&dkim_sign_ctx, &sig, errstr)) != PDKIM_OK)
+    goto pk_bad;
+
+  if (!sig)
+    {
+    DEBUG(D_transport) debug_printf("DKIM: no signatures to use\n");
+    sigbuf = string_get(1);    /* return a zero-len string */
+    }
+  else for (sigbuf = NULL; sig; sig = sig->next)
+    sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
+  }
 
 CLEANUP:
   (void) string_from_gstring(sigbuf);
@@ -797,6 +791,9 @@ gstring *
 authres_dkim(gstring * g)
 {
 pdkim_signature * sig;
+int start;
+
+DEBUG(D_acl) start = g->ptr;
 
 for (sig = dkim_signatures; sig; sig = sig->next)
   {
@@ -812,21 +809,21 @@ for (sig = dkim_signatures; sig; sig = sig->next)
       switch (sig->verify_ext_status)
        {
        case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
-          g = string_cat(g, US"tmperror (pubkey unavailable)"); break;
+          g = string_cat(g, US"tmperror (pubkey unavailable)\n\t\t"); break;
         case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
-          g = string_cat(g, US"permerror (overlong public key record)"); break;
+          g = string_cat(g, US"permerror (overlong public key record)\n\t\t"); break;
         case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
         case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
-          g = string_cat(g, US"neutral (syntax error in public key record)");
+          g = string_cat(g, US"neutral (syntax error in public key record)\n\t\t");
           break;
         case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
-          g = string_cat(g, US"neutral (signature tag missing or invalid)");
+          g = string_cat(g, US"neutral (signature tag missing or invalid)\n\t\t");
           break;
         case PDKIM_VERIFY_INVALID_DKIM_VERSION:
-          g = string_cat(g, US"neutral (unsupported DKIM version)");
+          g = string_cat(g, US"neutral (unsupported DKIM version)\n\t\t");
           break;
         default:
-          g = string_cat(g, US"permerror (unspecified problem)"); break;
+          g = string_cat(g, US"permerror (unspecified problem)\n\t\t"); break;
        }
       break;
     case PDKIM_VERIFY_FAIL:
@@ -834,14 +831,14 @@ for (sig = dkim_signatures; sig; sig = sig->next)
        {
        case PDKIM_VERIFY_FAIL_BODY:
           g = string_cat(g,
-           US"fail (body hash mismatch; body probably modified in transit)");
+           US"fail (body hash mismatch; body probably modified in transit)\n\t\t");
          break;
         case PDKIM_VERIFY_FAIL_MESSAGE:
           g = string_cat(g,
-           US"fail (signature did not verify; headers probably modified in transit)");
+           US"fail (signature did not verify; headers probably modified in transit)\n\t\t");
          break;
         default:
-          g = string_cat(g, US"fail (unspecified reason)");
+          g = string_cat(g, US"fail (unspecified reason)\n\t\t");
          break;
        }
       break;
@@ -853,6 +850,12 @@ for (sig = dkim_signatures; sig; sig = sig->next)
   if (sig->selector) g = string_append(g, 2, US" header.s=", sig->selector);
   g = string_append(g, 2, US" header.a=", dkim_sig_to_a_tag(sig));
   }
+
+DEBUG(D_acl)
+  if (g->ptr == start)
+    debug_printf("DKIM: no authres\n");
+  else
+    debug_printf("DKIM: authres '%.*s'\n", g->ptr - start - 3, g->s + start + 3);
 return g;
 }
 
index 866df26..28d567b 100644 (file)
@@ -129,6 +129,7 @@ uschar * hdrs;
 gstring * dkim_signature;
 int hsize;
 const uschar * errstr;
+uschar * verrstr;
 BOOL rc;
 
 DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
@@ -160,6 +161,16 @@ if (!(dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
     return FALSE;
     }
 
+#ifdef EXPERIMENTAL_ARC
+if (dkim->arc_signspec)                        /* Prepend ARC headers */
+  if (!(dkim_signature =
+       arc_sign(dkim->arc_signspec, dkim_signature, &verrstr)))
+    {
+    *err = verrstr;
+    return FALSE;
+    }
+#endif
+
 /* Write the signature and headers into the deliver-out-buffer.  This should
 mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
 (transport_write_message() sizes the BDAT for the buffered amount) - for short
@@ -268,6 +279,15 @@ if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
 else
   dlen = dkim_signature->ptr;
 
+#ifdef EXPERIMENTAL_ARC
+if (dkim->arc_signspec)                                /* Prepend ARC headers */
+  {
+  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
+    goto CLEANUP;
+  dlen = dkim_signature->ptr;
+  }
+#endif
+
 #ifndef OS_SENDFILE
 if (options & topt_use_bdat)
 #endif
@@ -351,7 +371,8 @@ dkim_transport_write_message(transport_ctx * tctx,
 {
 /* If we can't sign, just call the original function. */
 
-if (!(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector))
+if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
+   && !dkim->force_bodyhash)
   return transport_write_message(tctx, 0);
 
 /* If there is no filter command set up, construct the message and calculate
index 9fceaf5..ad7635d 100644 (file)
@@ -875,6 +875,9 @@ fprintf(f, "Support for:");
 #ifdef EXPERIMENTAL_SRS
   fprintf(f, " Experimental_SRS");
 #endif
+#ifdef EXPERIMENTAL_ARC
+  fprintf(f, " Experimental_ARC");
+#endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   fprintf(f, " Experimental_Brightmail");
 #endif
index d49f943..009049d 100644 (file)
@@ -4145,6 +4145,9 @@ while (*s != 0)
 #ifndef DISABLE_DKIM
       yield = authres_dkim(yield);
 #endif
+#ifdef EXPERIMENTAL_ARC
+      yield = authres_arc(yield);
+#endif
       continue;
       }
 
@@ -7530,10 +7533,9 @@ terminating brace. */
 
 if (ket_ends && *s == 0)
   {
-  expand_string_message = malformed_header?
-    US"missing } at end of string - could be header name not terminated by colon"
-    :
-    US"missing } at end of string";
+  expand_string_message = malformed_header
+    ? US"missing } at end of string - could be header name not terminated by colon"
+    : US"missing } at end of string";
   goto EXPAND_FAILED;
   }
 
@@ -7866,6 +7868,45 @@ return (  (  Ustrstr(s, "failed to expand") != NULL
 }
 
 
+/* Read given named file into big_buffer.  Use for keying material etc.
+The content will have an ascii NUL appended.
+
+Arguments:
+ filename      as it says
+
+Return:  pointer to buffer, or NULL on error.
+*/
+
+uschar *
+expand_file_big_buffer(const uschar * filename)
+{
+int fd, off = 0, len;
+
+if ((fd = open(CS filename, O_RDONLY)) < 0)
+  {
+  log_write(0, LOG_MAIN | LOG_PANIC, "unable to open file for reading: %s",
+            filename);
+  return NULL;
+  }
+
+do
+  {
+  if ((len = read(fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
+    {
+    (void) close(fd);
+    log_write(0, LOG_MAIN|LOG_PANIC, "unable to read file: %s", filename);
+    return NULL;
+    }
+  off += len;
+  }
+while (len > 0);
+
+(void) close(fd);
+big_buffer[off] = '\0';
+return big_buffer;
+}
+
+
 
 /*************************************************
 * Error-checking for testsuite                   *
index d537ac3..1f201fc 100644 (file)
@@ -88,6 +88,14 @@ extern int     acl_eval(int, uschar *, uschar **, uschar **);
 
 extern tree_node *acl_var_create(uschar *);
 extern void    acl_var_write(uschar *, uschar *, void *);
+
+#ifdef EXPERIMENTAL_ARC
+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 const uschar *acl_verify_arc(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 **);
@@ -111,6 +119,9 @@ extern gstring *authres_spf(gstring *);
 #ifndef DISABLE_DKIM
 extern gstring *authres_dkim(gstring *);
 #endif
+#ifdef EXPERIMENTAL_ARC
+extern gstring *authres_arc(gstring *);
+#endif
 
 extern uschar *b64encode(uschar *, int);
 extern int     b64decode(const uschar *, uschar **);
@@ -166,6 +177,9 @@ extern void    delivery_re_exec(int);
 
 extern BOOL    directory_make(const uschar *, const uschar *, int, BOOL);
 #ifndef DISABLE_DKIM
+extern uschar *dkim_exim_query_dns_txt(uschar *);
+extern void    dkim_exim_sign_init(void);
+
 extern BOOL    dkim_transport_write_message(transport_ctx *,
                  struct ob_dkim *, const uschar ** errstr);
 #endif
@@ -198,6 +212,7 @@ extern int     exp_bool(address_item *addr,
   uschar *mtype, uschar *mname, unsigned dgb_opt, uschar *oname, BOOL bvalue,
   uschar *svalue, BOOL *rvalue);
 extern BOOL    expand_check_condition(uschar *, uschar *, uschar *);
+extern uschar *expand_file_big_buffer(const uschar *);
 extern uschar *expand_string(uschar *);        /* public, cannot make const */
 extern const uschar *expand_cstring(const uschar *); /* ... so use this one */
 extern uschar *expand_hide_passwords(uschar * );
@@ -214,6 +229,8 @@ extern BOOL    filter_system_interpret(address_item **, uschar **);
 
 extern uschar * fn_hdrs_added(void);
 
+extern void    gstring_reset_unused(gstring *);
+
 extern void    header_add(int, const char *, ...);
 extern int     header_checkname(header_line *, BOOL);
 extern BOOL    header_match(uschar *, BOOL, BOOL, string_item *, int, ...);
index 5f1c87f..1db5ece 100644 (file)
@@ -420,7 +420,15 @@ BOOL    allow_domain_literals  = FALSE;
 BOOL    allow_mx_to_ip         = FALSE;
 BOOL    allow_unqualified_recipient = TRUE;    /* For local messages */
 BOOL    allow_unqualified_sender = TRUE;       /* Reset for SMTP */
-BOOL    allow_utf8_domains     = FALSE;
+BOOL    allow_utf8_domains     = FALSE;
+
+#ifdef EXPERIMENTAL_ARC
+struct arc_set *arc_received   = NULL;
+int     arc_received_instance  = 0;
+int     arc_oldest_pass                = 0;
+const uschar *arc_state                = NULL;
+#endif
+
 uschar *authenticated_fail_id  = NULL;
 uschar *authenticated_id       = NULL;
 uschar *authenticated_sender   = NULL;
@@ -785,7 +793,7 @@ header_name header_names[] = {
   { US"to",             2,     TRUE,           htype_to }
 };
 
-int header_names_size          = sizeof(header_names)/sizeof(header_name);
+int header_names_size          = nelem(header_names);
 
 BOOL    header_rewritten       = FALSE;
 uschar *helo_accept_junk_hosts = NULL;
index 801a00f..03af195 100644 (file)
@@ -216,6 +216,12 @@ extern BOOL    allow_domain_literals;  /* As it says */
 extern BOOL    allow_mx_to_ip;         /* Allow MX records to -> ip address */
 extern BOOL    allow_unqualified_recipient; /* As it says */
 extern BOOL    allow_unqualified_sender; /* Ditto */
+#ifdef EXPERIMENTAL_ARC
+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 */
+#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 601ceef..32c05a8 100644 (file)
@@ -180,6 +180,9 @@ due to conflicts with other common macros. */
 #ifdef EXPERIMENTAL_SRS
   builtin_macro_create(US"_HAVE_SRS");
 #endif
+#ifdef EXPERIMENTAL_ARC
+  builtin_macro_create(US"_HAVE_ARC");
+#endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   builtin_macro_create(US"_HAVE_BRIGHTMAIL");
 #endif
index c75b0ae..d7e6d5b 100644 (file)
@@ -71,11 +71,7 @@ const uschar * pdkim_canons[] = {
   NULL
 };
 
-typedef struct {
-  const uschar * dkim_hashname;
-  hashmethod    exim_hashmethod;
-} pdkim_hashtype;
-static const pdkim_hashtype pdkim_hashes[] = {
+const pdkim_hashtype pdkim_hashes[] = {
   { US"sha1",   HASH_SHA1 },
   { US"sha256", HASH_SHA2_256 },
   { US"sha512", HASH_SHA2_512 }
@@ -125,6 +121,34 @@ return string_sprintf("%s-%s",
 }
 
 
+int
+pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
+{
+int i;
+if (!len) len = Ustrlen(s);
+for (i = 0; i < nelem(pdkim_hashes); i++)
+  if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0)
+    return i;
+return -1;
+}
+
+void
+pdkim_cstring_to_canons(const uschar * s, unsigned len,
+  int * canon_head, int * canon_body)
+{
+int i;
+if (!len) len = Ustrlen(s);
+for (i = 0; pdkim_combined_canons[i].str; i++)
+  if (  Ustrncmp(s, pdkim_combined_canons[i].str, len) == 0
+     && len == Ustrlen(pdkim_combined_canons[i].str))
+    {
+    *canon_head = pdkim_combined_canons[i].canon_headers;
+    *canon_body = pdkim_combined_canons[i].canon_body;
+    break;
+    }
+}
+
+
 
 const char *
 pdkim_verify_status_str(int status)
@@ -177,7 +201,7 @@ switch(status)
 
 /* -------------------------------------------------------------------------- */
 /* Print debugging functions */
-static void
+void
 pdkim_quoteprint(const uschar *data, int len)
 {
 int i;
@@ -203,7 +227,7 @@ for (i = 0; i < len; i++)
 debug_printf("\n");
 }
 
-static void
+void
 pdkim_hexprint(const uschar *data, int len)
 {
 int i;
@@ -302,16 +326,16 @@ return PDKIM_FAIL;
 /* -------------------------------------------------------------------------- */
 /* Performs "relaxed" canonicalization of a header. */
 
-static uschar *
-pdkim_relax_header(const uschar * header, BOOL append_crlf)
+uschar *
+pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
 {
 BOOL past_field_name = FALSE;
 BOOL seen_wsp = FALSE;
 const uschar * p;
-uschar * relaxed = store_get(Ustrlen(header)+3);
+uschar * relaxed = store_get(len+3);
 uschar * q = relaxed;
 
-for (p = header; *p; p++)
+for (p = header; p - header < len; p++)
   {
   uschar c = *p;
 
@@ -347,6 +371,13 @@ return relaxed;
 }
 
 
+uschar *
+pdkim_relax_header(const uschar * header, BOOL append_crlf)
+{
+return pdkim_relax_header_n(header, Ustrlen(header), append_crlf);
+}
+
+
 /* -------------------------------------------------------------------------- */
 #define PDKIM_QP_ERROR_DECODE -1
 
@@ -407,16 +438,15 @@ return n;
 
 /* -------------------------------------------------------------------------- */
 
-static void
+void
 pdkim_decode_base64(const uschar * str, blob * b)
 {
-int dlen;
-dlen = b64decode(str, &b->data);
+int dlen = b64decode(str, &b->data);
 if (dlen < 0) b->data = NULL;
 b->len = dlen;
 }
 
-static uschar *
+uschar *
 pdkim_encode_base64(blob * b)
 {
 return b64encode(b->data, b->len);
@@ -541,24 +571,13 @@ for (p = raw_hdr; ; p++)
                "DKIM: ignoring signature due to nonhandled keytype in a=%s",
                cur_val->s);
 
-           for (++s, i = 0; i < nelem(pdkim_hashes); i++)
-             if (Ustrcmp(s, pdkim_hashes[i].dkim_hashname) == 0)
-               { sig->hashtype = i; break; }
-           if (sig->hashtype < 0)
-             log_write(0, LOG_MAIN,
-               "DKIM: ignoring signature due to nonhandled hashtype in a=%s",
-               cur_val->s);
+           sig->hashtype = pdkim_hashname_to_hashtype(++s, 0);
            break;
            }
 
          case 'c':                                     /* canonicalization */
-           for (i = 0; pdkim_combined_canons[i].str; i++)
-             if (Ustrcmp(cur_val->s, pdkim_combined_canons[i].str) == 0)
-               {
-               sig->canon_headers = pdkim_combined_canons[i].canon_headers;
-               sig->canon_body    = pdkim_combined_canons[i].canon_body;
-               break;
-               }
+           pdkim_cstring_to_canons(cur_val->s, 0,
+                                   &sig->canon_headers, &sig->canon_body);
            break;
          case 'q':                             /* Query method (for pubkey)*/
            for (i = 0; pdkim_querymethods[i]; i++)
@@ -626,7 +645,7 @@ DEBUG(D_acl)
          "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
   }
 
-if (!pdkim_set_bodyhash(ctx, sig))
+if (!pdkim_set_sig_bodyhash(ctx, sig))
   return NULL;
 
 return sig;
@@ -635,8 +654,8 @@ return sig;
 
 /* -------------------------------------------------------------------------- */
 
-static pdkim_pubkey *
-pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record)
+pdkim_pubkey *
+pdkim_parse_pubkey_record(const uschar *raw_record)
 {
 const uschar * ele;
 int sep = ';';
@@ -772,7 +791,11 @@ pdkim_bodyhash * b;
 pdkim_signature * sig;
 
 for (b = ctx->bodyhash; b; b = b->next)                /* Finish hashes */
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: finish bodyhash %d/%d/%d len %d\n",
+           b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes);
   exim_sha_finish(&b->body_hash_ctx, &b->bh);
+  }
 
 /* Traverse all signatures */
 for (sig = ctx->sig; sig; sig = sig->next)
@@ -940,6 +963,11 @@ if ( (ctx->cur_header->ptr > 1) &&
   --ctx->cur_header->ptr;
 (void) string_from_gstring(ctx->cur_header);
 
+#ifdef EXPERIMENTAL_ARC
+/* Feed the header line to ARC processing */
+(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN));
+#endif
+
 if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
 
 /* SIGNING -------------------------------------------------------------- */
@@ -1319,7 +1347,7 @@ pdkim_pubkey * p;
 
 dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
 
-if (  !(dns_txt_reply = ctx->dns_txt_callback(CS dns_txt_name))
+if (  !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
    || dns_txt_reply[0] == '\0'
    )
   {
@@ -1338,7 +1366,7 @@ DEBUG(D_acl)
   pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
   }
 
-if (  !(p = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply))
+if (  !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
    || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
    )
   {
@@ -1406,15 +1434,18 @@ else
   DEBUG(D_acl) debug_printf(
       "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
+/* Build (and/or evaluate) body hash.  Do this even if no DKIM sigs, in case we
+have a hash to do for ARC. */
+
+pdkim_finish_bodyhash(ctx);
+
 if (!ctx->sig)
   {
   DEBUG(D_acl) debug_printf("PDKIM: no signatures\n");
+  *return_signatures = NULL;
   return PDKIM_OK;
   }
 
-/* Build (and/or evaluate) body hash */
-pdkim_finish_bodyhash(ctx);
-
 for (sig = ctx->sig; sig; sig = sig->next)
   {
   hctx hhash_ctx;
@@ -1423,6 +1454,14 @@ for (sig = ctx->sig; sig; sig = sig->next)
   gstring * hdata = NULL;
   es_ctx sctx;
 
+  if (  !(ctx->flags & PDKIM_MODE_SIGN)
+     && sig->verify_status == PDKIM_VERIFY_FAIL)
+    {
+    DEBUG(D_acl)
+       debug_printf("PDKIM: [%s] abandoning this signature\n", sig->domain);
+    continue;
+    }
+
   /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
   suging only, as it happens) and for either GnuTLS and OpenSSL when we are
   signing with EC (specifically, Ed25519).  The former is because the GCrypt
@@ -1480,7 +1519,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
     the signature header  */
 
 /*XXX extend for non-RSA algos */
-    if ((*err = exim_dkim_signing_init(US sig->privkey, &sctx)))
+    if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
       return PDKIM_ERR_RSA_PRIVKEY;
@@ -1774,7 +1813,7 @@ return ctx->flags & PDKIM_MODE_SIGN  ||  verify_pass
 /* -------------------------------------------------------------------------- */
 
 DLLEXPORT pdkim_ctx *
-pdkim_init_verify(uschar * (*dns_txt_callback)(char *), BOOL dot_stuffing)
+pdkim_init_verify(uschar * (*dns_txt_callback)(uschar *), BOOL dot_stuffing)
 {
 pdkim_ctx * ctx;
 
@@ -1868,29 +1907,35 @@ return;
 
 
 /* Set up a blob for calculating the bodyhash according to the
-needs of this signature.  Use an existing one if possible, or
-create a new one.
+given needs.  Use an existing one if possible, or create a new one.
 
-Return: hashblob pointer, or NULL on error (only used as a boolean).
+Return: hashblob pointer, or NULL on error
 */
 pdkim_bodyhash *
-pdkim_set_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
+pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
+       long bodylength)
 {
 pdkim_bodyhash * b;
 
 for (b = ctx->bodyhash; b; b = b->next)
-  if (  sig->hashtype == b->hashtype
-     && sig->canon_body == b->canon_method
-     && sig->bodylength == b->bodylength)
-    goto old;
+  if (  hashtype == b->hashtype
+     && canon_method == b->canon_method
+     && bodylength == b->bodylength)
+    {
+    DEBUG(D_receive) debug_printf("PDKIM: using existing bodyhash %d/%d/%d\n",
+                                 hashtype, canon_method, bodylength);
+    return b;
+    }
 
+DEBUG(D_receive) debug_printf("PDKIM: new bodyhash %d/%d/%d\n",
+                             hashtype, canon_method, bodylength);
 b = store_get(sizeof(pdkim_bodyhash));
 b->next = ctx->bodyhash;
-b->hashtype = sig->hashtype;
-b->canon_method = sig->canon_body;
-b->bodylength = sig->bodylength;
+b->hashtype = hashtype;
+b->canon_method = canon_method;
+b->bodylength = bodylength;
 if (!exim_sha_init(&b->body_hash_ctx,          /*XXX hash method: extend for sha512 */
-                 pdkim_hashes[sig->hashtype].exim_hashmethod))
+                 pdkim_hashes[hashtype].exim_hashmethod))
   {
   DEBUG(D_acl)
     debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n");
@@ -1899,8 +1944,21 @@ if (!exim_sha_init(&b->body_hash_ctx,            /*XXX hash method: extend for sha512 */
 b->signed_body_bytes = 0;
 b->num_buffered_blanklines = 0;
 ctx->bodyhash = b;
+return b;
+}
+
 
-old:
+/* Set up a blob for calculating the bodyhash according to the
+needs of this signature.  Use an existing one if possible, or
+create a new one.
+
+Return: hashblob pointer, or NULL on error (only used as a boolean).
+*/
+pdkim_bodyhash *
+pdkim_set_sig_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
+{
+pdkim_bodyhash * b = pdkim_set_bodyhash(ctx,
+                       sig->hashtype, sig->canon_body, sig->bodylength);
 sig->calc_body_hash = b;
 return b;
 }
@@ -1911,7 +1969,7 @@ return b;
 
 void
 pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
-  uschar * (*dns_txt_callback)(char *))
+  uschar * (*dns_txt_callback)(uschar *))
 {
 memset(ctx, 0, sizeof(pdkim_ctx));
 ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
index 775581b..59ac038 100644 (file)
@@ -279,7 +279,7 @@ typedef struct pdkim_ctx {
   pdkim_bodyhash *bodyhash;
 
   /* Callback for dns/txt query method (verification only) */
-  uschar * (*dns_txt_callback)(char *);
+  uschar * (*dns_txt_callback)(uschar *);
 
   /* Coder's little helpers */
   gstring   *cur_header;
@@ -290,6 +290,17 @@ typedef struct pdkim_ctx {
 } pdkim_ctx;
 
 
+/******************************************************************************/
+
+typedef struct {
+  const uschar * dkim_hashname;
+  hashmethod    exim_hashmethod;
+} pdkim_hashtype;
+extern const pdkim_hashtype pdkim_hashes[];
+
+/******************************************************************************/
+
+
 /* -------------------------------------------------------------------------- */
 /* API functions. Please see the sample code in sample/test_sign.c and
    sample/test_verify.c for documentation.
@@ -301,7 +312,7 @@ extern "C" {
 
 void      pdkim_init         (void);
 
-void      pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(char *));
+void      pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(uschar *));
 
 DLLEXPORT
 pdkim_signature *pdkim_init_sign    (pdkim_ctx *,
@@ -309,7 +320,7 @@ pdkim_signature *pdkim_init_sign    (pdkim_ctx *,
                               const uschar **);
 
 DLLEXPORT
-pdkim_ctx *pdkim_init_verify  (uschar * (*)(char *), BOOL);
+pdkim_ctx *pdkim_init_verify  (uschar * (*)(uschar *), BOOL);
 
 DLLEXPORT
 void       pdkim_set_optional (pdkim_signature *, char *, char *,int, int,
@@ -317,7 +328,10 @@ void       pdkim_set_optional (pdkim_signature *, char *, char *,int, int,
                                unsigned long,
                                unsigned long);
 
-pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, pdkim_signature *);
+int            pdkim_hashname_to_hashtype(const uschar *, unsigned);
+void           pdkim_cstring_to_canons(const uschar *, unsigned, int *, int *);
+pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, int, int, long);
+pdkim_bodyhash *pdkim_set_sig_bodyhash(pdkim_ctx *, pdkim_signature *);
 
 DLLEXPORT
 int        pdkim_feed         (pdkim_ctx *, uschar *, int);
@@ -330,7 +344,14 @@ void       pdkim_free_ctx     (pdkim_ctx *);
 
 const uschar * pdkim_errstr(int);
 
-uschar *       dkim_sig_to_a_tag(const pdkim_signature * sig);
+extern uschar *                pdkim_encode_base64(blob *);
+extern void            pdkim_decode_base64(const uschar *, blob *);
+extern void            pdkim_hexprint(const uschar *, int);
+extern void            pdkim_quoteprint(const uschar *, int);
+extern pdkim_pubkey *  pdkim_parse_pubkey_record(const uschar *);
+extern uschar *                pdkim_relax_header_n(const uschar *, int, BOOL);
+extern uschar *                pdkim_relax_header(const uschar *, BOOL);
+extern uschar *                dkim_sig_to_a_tag(const pdkim_signature *);
 
 #ifdef __cplusplus
 }
index c1b9aed..b182c9a 100644 (file)
@@ -86,7 +86,7 @@ return string_cat(g, s);
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
 {
 gnutls_datum_t k = { .data = privkey_pem, .size = Ustrlen(privkey_pem) };
 gnutls_x509_privkey_t x509_key;
@@ -345,7 +345,7 @@ Only handles RSA keys.
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
 {
 uschar * s1, * s2;
 blob der;
@@ -694,7 +694,7 @@ ERR_load_crypto_strings();
 }
 
 
-/* accumulate data (was gnutls-onl but now needed for OpenSSL non-EC too
+/* accumulate data (was gnutls-only but now needed for OpenSSL non-EC too
 because now using hash-and-sign interface) */
 gstring *
 exim_dkim_data_append(gstring * g, uschar * s)
@@ -707,7 +707,7 @@ return string_cat(g, s);
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
 {
 BIO * bp = BIO_new_mem_buf(privkey_pem, -1);
 
index fa12ebb..96a0720 100644 (file)
@@ -88,7 +88,7 @@ typedef struct {
 extern void exim_dkim_init(void);
 extern gstring * exim_dkim_data_append(gstring *, uschar *);
 
-extern const uschar * exim_dkim_signing_init(uschar *, es_ctx *);
+extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *);
 extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *);
 extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *);
 extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *);
index d14409c..b502a38 100644 (file)
@@ -3960,6 +3960,10 @@ if (LOGGING(8bitmime))
 #ifndef DISABLE_DKIM
 if (LOGGING(dkim) && dkim_verify_overall)
   g = string_append(g, 2, US" DKIM=", dkim_verify_overall);
+# ifdef EXPERIMENTAL_ARC
+if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0)
+  g = string_catn(g, US" ARC", 4);
+# endif
 #endif
 
 if (LOGGING(receive_time))
index 50442bc..29a87c5 100644 (file)
@@ -914,7 +914,7 @@ sep_is_special = iscntrl(sep);
 
 /* Handle the case when a buffer is provided. */
 
-if (buffer != NULL)
+if (buffer)
   {
   int p = 0;
   for (; *s != 0; s++)
@@ -960,6 +960,7 @@ else
     }
   while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--;
   buffer = string_from_gstring(g);
+  gstring_reset_unused(g);
   }
 
 /* Update the current pointer and return the new string */
@@ -1073,6 +1074,12 @@ g->s[g->ptr] = '\0';
 return g->s;
 }
 
+void
+gstring_reset_unused(gstring * g)
+{
+store_reset(g->s + (g->size = g->ptr + 1));
+}
+
 /*************************************************
 *             Add chars to string                *
 *************************************************/
index 29dee2d..98c9501 100644 (file)
@@ -879,6 +879,10 @@ struct ob_dkim {
   uschar *dkim_strict;
   uschar *dkim_hash;
   BOOL    dot_stuffed;
+  BOOL    force_bodyhash;
+#ifdef EXPERIMENTAL_ARC
+  uschar *arc_signspec;
+#endif
 };
 
 /* End of structs.h */
index d3af04c..2d81f3b 100644 (file)
@@ -24,6 +24,10 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
   { "allow_localhost",      opt_bool,
       (void *)offsetof(smtp_transport_options_block, allow_localhost) },
+#ifdef EXPERIMENTAL_ARC
+  { "arc_sign", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, arc_sign) },
+#endif
   { "authenticated_sender", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, authenticated_sender) },
   { "authenticated_sender_force", opt_bool,
@@ -209,7 +213,6 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .fallback_hosts =            NULL,
   .hostlist =                  NULL,
   .fallback_hostlist =         NULL,
-  .authenticated_sender =      NULL,
   .helo_data =                 US"$primary_hostname",
   .interface =                 NULL,
   .port =                      NULL,
@@ -287,7 +290,15 @@ smtp_transport_options_block smtp_transport_option_defaults = {
     .dkim_sign_headers =       NULL,
     .dkim_strict =             NULL,
     .dkim_hash =               US"sha256",
-    .dot_stuffed =             FALSE},
+    .dot_stuffed =             FALSE,
+    .force_bodyhash =          FALSE,
+# ifdef EXPERIMENTAL_ARC
+    .arc_signspec =            NULL,
+# endif
+    },
+# ifdef EXPERIMENTAL_ARC
+  .arc_sign =                  NULL,
+# endif
 #endif
 };
 
@@ -2965,6 +2976,24 @@ else
   transport_count = 0;
 
 #ifndef DISABLE_DKIM
+  dkim_exim_sign_init();
+# ifdef EXPERIMENTAL_ARC
+    {
+    uschar * s = sx.ob->arc_sign;
+    if (s)
+      {
+      if (!(sx.ob->dkim.arc_signspec = expand_string(s)))
+       {
+       message = US"failed to expand arc_sign";
+       sx.ok = FALSE;
+       goto SEND_FAILED;
+       }
+      /* Ask dkim code to hash the body for ARC */
+      (void) arc_ams_setup_sign_bodyhash();
+      sx.ob->dkim.force_bodyhash = TRUE;
+      }
+    }
+# endif
   sx.ok = dkim_transport_write_message(&tctx, &sx.ob->dkim, CUSS &message);
 #else
   sx.ok = transport_write_message(&tctx, 0);
index 14c0c75..749c6f7 100644 (file)
@@ -90,6 +90,9 @@ typedef struct {
 #ifndef DISABLE_DKIM
   struct ob_dkim dkim;
 #endif
+#ifdef EXPERIMENTAL_ARC
+  uschar *arc_sign;
+#endif
 } smtp_transport_options_block;
 
 /* smtp connect context */
diff --git a/test/aux-fixed/4560.mlistfooter b/test/aux-fixed/4560.mlistfooter
new file mode 100644 (file)
index 0000000..aa1ec54
--- /dev/null
@@ -0,0 +1,4 @@
+
+-----
+This is a generic mailinglist footer
+----
diff --git a/test/confs/4560 b/test/confs/4560
new file mode 100644 (file)
index 0000000..1012a38
--- /dev/null
@@ -0,0 +1,84 @@
+# Exim test configuration 4560
+
+SERVER=
+
+.include DIR/aux-var/std_conf_prefix
+
+primary_hostname = test.ex
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+acl_smtp_data = check_data
+
+log_selector = +received_recipients +dkim_verbose
+queue_only
+
+# ----- ACL -----
+begin acl
+
+check_data:
+  warn !verify =       arc
+.ifdef OPTION
+  accept
+.else
+  accept add_header =  :at_start:${authresults {$primary_hostname}}
+.endif
+  
+# ----- Routers -----
+
+begin routers
+
+d1:
+  driver = accept
+  local_parts = ^a
+  transport = tfile
+
+r2:
+  driver =     redirect
+  local_parts = ^m
+  data =       ${substr_1:$local_part}@$domain
+  redirect_router = mlist
+
+redir:
+  driver =     redirect
+  data =       ${substr_1:$local_part}@$domain
+  redirect_router = fwd
+
+fwd:
+  driver =     accept
+  transport =  tsmtp
+
+mlist:
+  driver =     accept
+  transport =  tmlist
+
+# ----- Transports -----
+
+begin transports
+
+tfile:
+  driver =     appendfile
+  file =       DIR/test-mail/$local_part
+  user =       CALLER
+
+tsmtp:
+  driver =     smtp
+  hosts =      HOSTIPV4
+  port =       PORT_D
+  allow_localhost
+.ifndef OPTION
+  arc_sign =   $primary_hostname : sel : DIR/aux-fixed/dkim/dkim.private
+.endif
+
+tmlist:
+  driver =     smtp
+  hosts =      HOSTIPV4
+  port =       PORT_D
+  allow_localhost
+  transport_filter =   /bin/cat - DIR/aux-fixed/TESTNUM.mlistfooter
+.ifndef OPTION
+  arc_sign =   $primary_hostname : sel : DIR/aux-fixed/dkim/dkim.private
+.endif
+
+# End
index b4f8d3a..04edb32 100644 (file)
@@ -7,5 +7,5 @@
 1999-03-02 09:44:33 10HmaX-0005vi-00 <= pass@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss DKIM=test.ex id=qwerty1234@disco-zombie.net
 1999-03-02 09:44:33 10HmaY-0005vi-00 signer: test.ex bits: 1024
 1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [verification failed - body hash mismatch (body probably modified in transit)]
-1999-03-02 09:44:33 10HmaY-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit) header.d=test.ex header.s=sel header.a=rsa-sha1
+1999-03-02 09:44:33 10HmaY-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n              header.d=test.ex header.s=sel header.a=rsa-sha1
 1999-03-02 09:44:33 10HmaY-0005vi-00 <= fail@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
index b5dcd81..50d38e0 100644 (file)
@@ -16,5 +16,5 @@
 1999-03-02 09:44:33 10HmbA-0005vi-00 PDKIM: d=test.ex s=sel_bad [failed key import]
 1999-03-02 09:44:33 10HmbA-0005vi-00 signer: test.ex bits: 1024
 1999-03-02 09:44:33 10HmbA-0005vi-00 DKIM: d=test.ex s=sel_bad c=relaxed/relaxed a=rsa-sha1 b=1024 [invalid - syntax error in public key record]
-1999-03-02 09:44:33 10HmbA-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (syntax error in public key record) header.d=test.ex header.s=sel_bad header.a=rsa-sha1
+1999-03-02 09:44:33 10HmbA-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (syntax error in public key record)\n               header.d=test.ex header.s=sel_bad header.a=rsa-sha1
 1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=564CFC9B.1040905@yahoo.com
index 3a502a1..c91c796 100644 (file)
@@ -1,8 +1,7 @@
 
 ******** SERVER ********
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
-1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: validation error: Public key signature verification has failed.
 1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 1024
-1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha512 b=1024 [verification failed - signature did not verify (headers probably modified in transit)]
-1999-03-02 09:44:33 10HmaX-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (signature did not verify; headers probably modified in transit) header.d=test.ex header.s=sel header.a=rsa-sha512
+1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha512 b=1024 [verification failed - body hash mismatch (body probably modified in transit)]
+1999-03-02 09:44:33 10HmaX-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n              header.d=test.ex header.s=sel header.a=rsa-sha512
 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
index 43389c8..5af68d0 100644 (file)
@@ -1,8 +1,7 @@
 
 ******** SERVER ********
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
-1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: validation error: Public key signature verification has failed.
 1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 1024
-1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel2 c=simple/simple a=rsa-sha512 b=1024 [verification failed - signature did not verify (headers probably modified in transit)]
-1999-03-02 09:44:33 10HmaX-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (signature did not verify; headers probably modified in transit) header.d=test.ex header.s=sel2 header.a=rsa-sha512
+1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel2 c=simple/simple a=rsa-sha512 b=1024 [verification failed - body hash mismatch (body probably modified in transit)]
+1999-03-02 09:44:33 10HmaX-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n              header.d=test.ex header.s=sel2 header.a=rsa-sha512
 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
index 55bad61..4e57f3e 100644 (file)
@@ -3,15 +3,15 @@
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
 1999-03-02 09:44:33 10HmaY-0005vi-00 signer: test.ex bits: 0
 1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=0 [invalid - signature tag missing or invalid]
-1999-03-02 09:44:33 10HmaY-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid) header.d=test.ex header.s=sel header.a=rsa-sha1
+1999-03-02 09:44:33 10HmaY-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid)\n                header.d=test.ex header.s=sel header.a=rsa-sha1
 1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
 1999-03-02 09:44:33 10HmaZ-0005vi-00 signer: test.ex bits: 1024
-1999-03-02 09:44:33 10HmaZ-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [invalid - signature tag missing or invalid]
-1999-03-02 09:44:33 10HmaZ-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid) header.d=test.ex header.s=sel header.a=rsa-sha1
+1999-03-02 09:44:33 10HmaZ-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [verification failed - body hash mismatch (body probably modified in transit)]
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n              header.d=test.ex header.s=sel header.a=rsa-sha1
 1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
 1999-03-02 09:44:33 10HmbA-0005vi-00 signer: test.ex bits: 1024
 1999-03-02 09:44:33 10HmbA-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [verification failed - body hash mismatch (body probably modified in transit)]
-1999-03-02 09:44:33 10HmbA-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit) header.d=test.ex header.s=sel header.a=rsa-sha1
+1999-03-02 09:44:33 10HmbA-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n              header.d=test.ex header.s=sel header.a=rsa-sha1
 1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
 1999-03-02 09:44:33 10HmbB-0005vi-00 DKIM: validation error: LONG_LINE
 1999-03-02 09:44:33 10HmbB-0005vi-00 DKIM: Error during validation, disabling signature verification: LONG_LINE
 1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
 1999-03-02 09:44:33 10HmbC-0005vi-00 signer: test.ex bits: 512
 1999-03-02 09:44:33 10HmbC-0005vi-00 DKIM: d=test.ex s=ses_sha256 c=simple/simple a=rsa-sha1 b=512 [verification failed - unspecified reason]
-1999-03-02 09:44:33 10HmbC-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (unspecified reason) header.d=test.ex header.s=ses_sha256 header.a=rsa-sha1
+1999-03-02 09:44:33 10HmbC-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (unspecified reason)\n                 header.d=test.ex header.s=ses_sha256 header.a=rsa-sha1
 1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
 1999-03-02 09:44:33 10HmbD-0005vi-00 unknown
 1999-03-02 09:44:33 10HmbD-0005vi-00 signer: test.ex bits: 0
 1999-03-02 09:44:33 10HmbD-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=0 [invalid - signature tag missing or invalid]
-1999-03-02 09:44:33 10HmbD-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid) header.d=test.ex header.s=sel header.a=rsa-sha1
+1999-03-02 09:44:33 10HmbD-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid)\n                header.d=test.ex header.s=sel header.a=rsa-sha1
 1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
 1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 0
diff --git a/test/log/4560 b/test/log/4560
new file mode 100644 (file)
index 0000000..101afb7
--- /dev/null
@@ -0,0 +1,102 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss for za@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for a@test.ex
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex <za@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => a <a@test.ex> R=d1 T=tfile
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss for zza@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for za@test.ex
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => za@test.ex <zza@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for a@test.ex
+1999-03-02 09:44:33 10HmbA-0005vi-00 => a@test.ex <za@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbB-0005vi-00"
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => a <a@test.ex> R=d1 T=tfile
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss for zmza@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for mza@test.ex
+1999-03-02 09:44:33 10HmbC-0005vi-00 => mza@test.ex <zmza@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbD-0005vi-00"
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for za@test.ex
+1999-03-02 09:44:33 10HmbD-0005vi-00 => za@test.ex <mza@test.ex> R=mlist T=tmlist H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for a@test.ex
+1999-03-02 09:44:33 10HmbE-0005vi-00 => a@test.ex <za@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbF-0005vi-00"
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbF-0005vi-00 => a <a@test.ex> R=d1 T=tfile
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss for zzmza@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbH-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for zmza@test.ex
+1999-03-02 09:44:33 10HmbG-0005vi-00 => zmza@test.ex <zzmza@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbH-0005vi-00"
+1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbI-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for mza@test.ex
+1999-03-02 09:44:33 10HmbH-0005vi-00 => mza@test.ex <zmza@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbI-0005vi-00"
+1999-03-02 09:44:33 10HmbH-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbJ-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss for za@test.ex
+1999-03-02 09:44:33 10HmbI-0005vi-00 => za@test.ex <mza@test.ex> R=mlist T=tmlist H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbJ-0005vi-00"
+1999-03-02 09:44:33 10HmbI-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbK-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss for a@test.ex
+1999-03-02 09:44:33 10HmbJ-0005vi-00 => a@test.ex <za@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbK-0005vi-00"
+1999-03-02 09:44:33 10HmbJ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbK-0005vi-00 => a <a@test.ex> R=d1 T=tfile
+1999-03-02 09:44:33 10HmbK-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbL-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss for zza@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbM-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for za@test.ex
+1999-03-02 09:44:33 10HmbL-0005vi-00 => za@test.ex <zza@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbM-0005vi-00"
+1999-03-02 09:44:33 10HmbL-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbN-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for a@test.ex
+1999-03-02 09:44:33 10HmbM-0005vi-00 => a@test.ex <za@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbN-0005vi-00"
+1999-03-02 09:44:33 10HmbM-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbN-0005vi-00 => a <a@test.ex> R=d1 T=tfile
+1999-03-02 09:44:33 10HmbN-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbO-0005vi-00 DKIM: d=dmarc.org s=clochette c=simple/simple a=rsa-sha256 b=1024 t=1517535263 [verification succeeded]
+1999-03-02 09:44:33 10HmbO-0005vi-00 DKIM: d=convivian.com s=default c=simple/simple a=rsa-sha256 b=1024 t=1517535248 [verification failed - body hash mismatch (body probably modified in transit)]
+1999-03-02 09:44:33 10HmbO-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss DKIM=dmarc.org id=1426665656.110316.1517535248039.JavaMail.zimbra@convivian.com for za@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbP-0005vi-00 DKIM: d=dmarc.org s=clochette c=simple/simple a=rsa-sha256 b=1024 t=1517535263 [verification succeeded]
+1999-03-02 09:44:33 10HmbP-0005vi-00 DKIM: d=convivian.com s=default c=simple/simple a=rsa-sha256 b=1024 t=1517535248 [verification failed - body hash mismatch (body probably modified in transit)]
+1999-03-02 09:44:33 10HmbP-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss DKIM=dmarc.org id=1426665656.110316.1517535248039.JavaMail.zimbra@convivian.com for a@test.ex
+1999-03-02 09:44:33 10HmbO-0005vi-00 => a@test.ex <za@test.ex> R=fwd T=tsmtp H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbP-0005vi-00"
+1999-03-02 09:44:33 10HmbO-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbP-0005vi-00 => a <a@test.ex> R=d1 T=tfile
+1999-03-02 09:44:33 10HmbP-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/mail/4560.a b/test/mail/4560.a
new file mode 100644 (file)
index 0000000..c0432b9
--- /dev/null
@@ -0,0 +1,453 @@
+From CALLER@bloggs.com Tue Mar 02 09:44:33 1999
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmaY-0005vi-00
+       for a@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=1; cv=none; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=XLcAAITo9Vf1e7bfAAZQGHFU1YySleXuf5+r2KI9kYNg8hmFsv6p91L679/gYfo7XGzo6pl9Xh
+         +CXJIttJnXkgGx+zRg4hRoAqr3VNqDYA/IDvvglQCdBVu2/4JS1cPCznuW6RdTLR6b7kMx11Cu
+         jd3NsmP38X0Zo8mRETF+TLU=;
+ARC-Authentication-Results: i=1; test.ex;
+       arc=none
+ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=HUzfKKxIjPIa07lkj5uzDQ3q5YTRF/NwAUE7dhrrfvs=;
+       h=Subject;
+       b=CbJ1YQLNAWyRqMXq9y0WN10HlKn8Ylu+sVGztkUklgxaqQJCVKUiS7dZaKCBA0B7UqesGogzb5
+         y1aeJRCnWnUSL1gKXCjalHTp9XuWxGjd5cARh0AN/nmkXOFkgcIan7o4vB3UBF/T3NwLdewza+
+         caLY3oRoBpLwh0IBzibHKl0=;
+Authentication-Results: test.ex;
+       arc=none
+Received: from [127.0.0.1] (helo=xxx)
+       by test.ex with smtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmaX-0005vi-00
+       for za@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: Test
+
+This is a test body.
+
+From CALLER@bloggs.com Tue Mar 02 09:44:33 1999
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=2) header.s=sel arc.oldest-pass=2 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbB-0005vi-00
+       for a@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=2; cv=pass; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=QPT4AYm4FOMArfxOkzKDV/wEYbVVD4rZ7BKz7wzbMmLj/oyuObMvZ/zff/uFoegX6Xl0W7Ogs4
+         Oid4SiYGn8WmoUqxEuEGPo6/rnp93bPkjL6EVZcuqs8gK9JN+DC1/ubihCCj5zQkPcJEiq3fpV
+         t59JpYefg0lWAxMXRe7XkSQ=;
+ARC-Authentication-Results: i=2; test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=HUzfKKxIjPIa07lkj5uzDQ3q5YTRF/NwAUE7dhrrfvs=;
+       h=Subject;
+       b=T2xYov0qVT77eX6s3g2M3CB4ulYuxbD0o+iTCpfB/40nZTzl5LdIVEyk2ph/ijyqY2PJTpBjjt
+         iTHoJ4CBtVAkDwq75Wj+lh1OfrArWJatMyimkMwxX6b54KcXldIwB+7w6Tn3D9/sydBEduL82C
+         p1kh+Bb/X0QGczb0CzdXwhM=;
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbA-0005vi-00
+       for za@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=1; cv=none; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=XLcAAITo9Vf1e7bfAAZQGHFU1YySleXuf5+r2KI9kYNg8hmFsv6p91L679/gYfo7XGzo6pl9Xh
+         +CXJIttJnXkgGx+zRg4hRoAqr3VNqDYA/IDvvglQCdBVu2/4JS1cPCznuW6RdTLR6b7kMx11Cu
+         jd3NsmP38X0Zo8mRETF+TLU=;
+ARC-Authentication-Results: i=1; test.ex;
+       arc=none
+ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=HUzfKKxIjPIa07lkj5uzDQ3q5YTRF/NwAUE7dhrrfvs=;
+       h=Subject;
+       b=CbJ1YQLNAWyRqMXq9y0WN10HlKn8Ylu+sVGztkUklgxaqQJCVKUiS7dZaKCBA0B7UqesGogzb5
+         y1aeJRCnWnUSL1gKXCjalHTp9XuWxGjd5cARh0AN/nmkXOFkgcIan7o4vB3UBF/T3NwLdewza+
+         caLY3oRoBpLwh0IBzibHKl0=;
+Authentication-Results: test.ex;
+       arc=none
+Received: from [127.0.0.1] (helo=xxx)
+       by test.ex with smtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmaZ-0005vi-00
+       for zza@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: Test
+
+This is a test body.
+
+From CALLER@bloggs.com Tue Mar 02 09:44:33 1999
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=3) header.s=sel arc.oldest-pass=0 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbF-0005vi-00
+       for a@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=3; cv=pass; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=UZiwwzRnbDfp1Qy85n7I53xtu0tXHmyGcuzuv/QL/pXNNNGPGxS4x+qLliXV3yMyUzPYEYjOkB
+         zlbFTeha0LdIY6GksuprRSrVRqtoePCgl/9XLyrAtqXe4atZWYr8tpLfbdLGhS0SXAkNHgY/I0
+         tIhVDsdGN6Z0tMMWxwljKro=;
+ARC-Authentication-Results: i=3; test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=2) header.s=sel arc.oldest-pass=0 smtp.client-ip=ip4.ip4.ip4.ip4
+ARC-Message-Signature: i=3; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=31zA4VNQZ2yhHUh+7vWASIL9kKqo6pSym1QghgPyUkM=;
+       h=Subject;
+       b=Lf2jJs8SwbiYLrylYAOjQO4iIa+7tnGighj2gE5NWZj+SiJNQFgu+gHgkmA4xZc2meG58S7WPf
+         nG6rkqTU/uqBRAbWaEHP1VYDss/x47a/GImRx89dR1P7ZTRLMGgk0AusbvtFDMsKvOTd8QeWLc
+         DsScgtJ2MqYbikFuA0LxRIA=;
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=2) header.s=sel arc.oldest-pass=0 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbE-0005vi-00
+       for za@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=2; cv=pass; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=Ve0H0ri4edD3OUKRxMSyMWdVBiikGZwpYN/6lq6fxiFkgxV7atTuDfPJJ77xbuC/vmvLOWSa6x
+         JcN+stcJn6QcPNjmzoNbK5BLIWwFfLKW02Ao+qqm1DGqWnI6XD3r/oKleEvUc2XdatoYHXCbp7
+         qQO7e9u/Pzs+6u6dNA+KoJA=;
+ARC-Authentication-Results: i=2; test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=31zA4VNQZ2yhHUh+7vWASIL9kKqo6pSym1QghgPyUkM=;
+       h=Subject;
+       b=v3N5ukPvIJskEefYQVq9la9YvMbtrEETkzRVbExhcuf52gWH6PY6L8MWQr2BN4VZbWHPIfZN3S
+         GMQ21ewl0ZaHC4bAzidgK7NsViw8cfKnJwkvSm4FejpBDto93vQ0Jn2dntbjGZeDSaFx8AuV0m
+         oZRTJp3w8FnuMJ8Pl0bDLDM=;
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbD-0005vi-00
+       for mza@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=1; cv=none; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=XLcAAITo9Vf1e7bfAAZQGHFU1YySleXuf5+r2KI9kYNg8hmFsv6p91L679/gYfo7XGzo6pl9Xh
+         +CXJIttJnXkgGx+zRg4hRoAqr3VNqDYA/IDvvglQCdBVu2/4JS1cPCznuW6RdTLR6b7kMx11Cu
+         jd3NsmP38X0Zo8mRETF+TLU=;
+ARC-Authentication-Results: i=1; test.ex;
+       arc=none
+ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=HUzfKKxIjPIa07lkj5uzDQ3q5YTRF/NwAUE7dhrrfvs=;
+       h=Subject;
+       b=CbJ1YQLNAWyRqMXq9y0WN10HlKn8Ylu+sVGztkUklgxaqQJCVKUiS7dZaKCBA0B7UqesGogzb5
+         y1aeJRCnWnUSL1gKXCjalHTp9XuWxGjd5cARh0AN/nmkXOFkgcIan7o4vB3UBF/T3NwLdewza+
+         caLY3oRoBpLwh0IBzibHKl0=;
+Authentication-Results: test.ex;
+       arc=none
+Received: from [127.0.0.1] (helo=xxx)
+       by test.ex with smtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbC-0005vi-00
+       for zmza@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: Test
+
+This is a test body.
+
+-----
+This is a generic mailinglist footer
+----
+
+From CALLER@bloggs.com Tue Mar 02 09:44:33 1999
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=fail (i=3) header.s=sel arc.oldest-pass=0 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbK-0005vi-00
+       for a@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=3; cv=fail; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=Q7C2hXJPIS8zzONoXTI8rWioQs8SqYOBTXYLipH7fshFD+j83qqBfxoTQUzZmSjLq1ZDmIyJD/
+         Ni8eBtkRv3wnbcnp1nxuv3ATnUfFgjd4DjmKtBqIKK0r3yvOXooeK7uEbvJHapXg7uHFSLq62X
+         c7RWT/QCRAUexkZERkhQP6s=;
+ARC-Authentication-Results: i=3; test.ex;
+       iprev=pass (the.local.host.name);
+       arc=fail (i=2) header.s=sel arc.oldest-pass=0 smtp.client-ip=ip4.ip4.ip4.ip4
+ARC-Message-Signature: i=3; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=31zA4VNQZ2yhHUh+7vWASIL9kKqo6pSym1QghgPyUkM=;
+       h=Subject;
+       b=Lf2jJs8SwbiYLrylYAOjQO4iIa+7tnGighj2gE5NWZj+SiJNQFgu+gHgkmA4xZc2meG58S7WPf
+         nG6rkqTU/uqBRAbWaEHP1VYDss/x47a/GImRx89dR1P7ZTRLMGgk0AusbvtFDMsKvOTd8QeWLc
+         DsScgtJ2MqYbikFuA0LxRIA=;
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=fail (i=2) header.s=sel arc.oldest-pass=0 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbJ-0005vi-00
+       for za@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=2) header.s=sel arc.oldest-pass=2 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbI-0005vi-00
+       for mza@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=2; cv=pass; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=QPT4AYm4FOMArfxOkzKDV/wEYbVVD4rZ7BKz7wzbMmLj/oyuObMvZ/zff/uFoegX6Xl0W7Ogs4
+         Oid4SiYGn8WmoUqxEuEGPo6/rnp93bPkjL6EVZcuqs8gK9JN+DC1/ubihCCj5zQkPcJEiq3fpV
+         t59JpYefg0lWAxMXRe7XkSQ=;
+ARC-Authentication-Results: i=2; test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=HUzfKKxIjPIa07lkj5uzDQ3q5YTRF/NwAUE7dhrrfvs=;
+       h=Subject;
+       b=T2xYov0qVT77eX6s3g2M3CB4ulYuxbD0o+iTCpfB/40nZTzl5LdIVEyk2ph/ijyqY2PJTpBjjt
+         iTHoJ4CBtVAkDwq75Wj+lh1OfrArWJatMyimkMwxX6b54KcXldIwB+7w6Tn3D9/sydBEduL82C
+         p1kh+Bb/X0QGczb0CzdXwhM=;
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbH-0005vi-00
+       for zmza@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=1; cv=none; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=XLcAAITo9Vf1e7bfAAZQGHFU1YySleXuf5+r2KI9kYNg8hmFsv6p91L679/gYfo7XGzo6pl9Xh
+         +CXJIttJnXkgGx+zRg4hRoAqr3VNqDYA/IDvvglQCdBVu2/4JS1cPCznuW6RdTLR6b7kMx11Cu
+         jd3NsmP38X0Zo8mRETF+TLU=;
+ARC-Authentication-Results: i=1; test.ex;
+       arc=none
+ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=HUzfKKxIjPIa07lkj5uzDQ3q5YTRF/NwAUE7dhrrfvs=;
+       h=Subject;
+       b=CbJ1YQLNAWyRqMXq9y0WN10HlKn8Ylu+sVGztkUklgxaqQJCVKUiS7dZaKCBA0B7UqesGogzb5
+         y1aeJRCnWnUSL1gKXCjalHTp9XuWxGjd5cARh0AN/nmkXOFkgcIan7o4vB3UBF/T3NwLdewza+
+         caLY3oRoBpLwh0IBzibHKl0=;
+Authentication-Results: test.ex;
+       arc=none
+Received: from [127.0.0.1] (helo=xxx)
+       by test.ex with smtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbG-0005vi-00
+       for zzmza@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: Test
+
+This is a test body.
+
+-----
+This is a generic mailinglist footer
+----
+
+From CALLER@bloggs.com Tue Mar 02 09:44:33 1999
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbN-0005vi-00
+       for a@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbM-0005vi-00
+       for za@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=1; cv=none; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=XLcAAITo9Vf1e7bfAAZQGHFU1YySleXuf5+r2KI9kYNg8hmFsv6p91L679/gYfo7XGzo6pl9Xh
+         +CXJIttJnXkgGx+zRg4hRoAqr3VNqDYA/IDvvglQCdBVu2/4JS1cPCznuW6RdTLR6b7kMx11Cu
+         jd3NsmP38X0Zo8mRETF+TLU=;
+ARC-Authentication-Results: i=1; test.ex;
+       arc=none
+ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=HUzfKKxIjPIa07lkj5uzDQ3q5YTRF/NwAUE7dhrrfvs=;
+       h=Subject;
+       b=CbJ1YQLNAWyRqMXq9y0WN10HlKn8Ylu+sVGztkUklgxaqQJCVKUiS7dZaKCBA0B7UqesGogzb5
+         y1aeJRCnWnUSL1gKXCjalHTp9XuWxGjd5cARh0AN/nmkXOFkgcIan7o4vB3UBF/T3NwLdewza+
+         caLY3oRoBpLwh0IBzibHKl0=;
+Authentication-Results: test.ex;
+       arc=none
+Received: from [127.0.0.1] (helo=xxx)
+       by test.ex with smtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbL-0005vi-00
+       for zza@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: Test
+
+This is a test body.
+
+From CALLER@bloggs.com Tue Mar 02 09:44:33 1999
+Authentication-Results: test.ex;
+       iprev=pass (the.local.host.name);
+       dkim=pass header.d=dmarc.org header.s=clochette header.a=rsa-sha256;
+       dkim=fail (body hash mismatch; body probably modified in transit)
+                header.d=convivian.com header.s=default header.a=rsa-sha256;
+       arc=fail (i=2) header.s=sel arc.oldest-pass=0 smtp.client-ip=ip4.ip4.ip4.ip4
+Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex)
+       by test.ex with esmtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbP-0005vi-00
+       for a@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+ARC-Seal: i=2; cv=fail; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+        b=kyA9Jr7oTw0RjbIMJuRSDVS34QV/o7rR2vk8j9OoOrJCc4SGYoFdnwOPuZ0xnJ7PC6VBYqFuaF
+         0roSJ1UkJk7NJLjfw4UXF1gF01z+EBahwpYpLE1K7+wuejYBiu83ksxeNbMaejGCZGXRgTrx4N
+         r8h8iR9p7dSbp6/B7CxxoSg=;
+ARC-Authentication-Results: i=2; test.ex;
+       dkim=pass header.d=dmarc.org header.s=clochette header.a=rsa-sha256;
+       dkim=fail (body hash mismatch; body probably modified in transit)
+                header.d=convivian.com header.s=default header.a=rsa-sha256;
+       arc=fail (i=1) header.s=default arc.oldest-pass=0 smtp.client-ip=127.0.0.1
+ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed; d=test.ex; s=sel;
+       bh=DXU/xKzzQYeoYB254nZ0AzNm7z2YZ//FpTnhgIjPyt8=;
+       h=Sender:Content-Type:Reply-To:From:List-Subscribe:List-Help:List-Post:
+         List-Archive:List-Unsubscribe:List-Id:Subject:Cc:MIME-Version:References:
+         In-Reply-To:Message-ID:To:Date:DKIM-Signature:DKIM-Signature;
+       b=ZDh/1Pns8xp2aOFUIDqAIU8rNK+Wx+xBtsUqn+P8an0dPJIja0AexTNoPagabvXjNzT86Uf6dm
+         6gO1oFpzn63XNNaRJSrUDOMLe3pe5D8IS/0AFlqU9iwyDjmZqsnc8VnxXMgkDvEhrF5e1Mj9E+
+         Rw80B9DQMRhl1Va7HMZsLlI=;
+Authentication-Results: test.ex;
+       dkim=pass header.d=dmarc.org header.s=clochette header.a=rsa-sha256;
+       dkim=fail (body hash mismatch; body probably modified in transit)
+                header.d=convivian.com header.s=default header.a=rsa-sha256;
+       arc=fail (i=1) header.s=default arc.oldest-pass=0 smtp.client-ip=127.0.0.1
+Received: from [127.0.0.1] (helo=xxx)
+       by test.ex with smtp (Exim x.yz)
+       (envelope-from <CALLER@bloggs.com>)
+       id 10HmbO-0005vi-00
+       for za@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Received: from dragon.trusteddomain.org (localhost [127.0.0.1])
+       by dragon.trusteddomain.org (8.14.5/8.14.5) with ESMTP id w121YG2q036577;
+       Tue, 2 Mar 1999 09:44:33 +0000 (PST)
+       (envelope-from arc-discuss-bounces@dmarc.org)
+DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=dmarc.org;
+       s=clochette; t=1517535263;
+       bh=DXU/xKzzQYeoYB254nZ0AzNm7z2YZ//FpTnhgIjPyt8=;
+       h=Date:To:In-Reply-To:References:Cc:Subject:List-Id:
+        List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe:
+        From:Reply-To;
+       b=Z66qes0GxyXtv0ow232KSy/b44fPNLZL8JOXHiJLi9dHzIPyxsQd/Zb5NP8i3427g
+        a9tEyo8Rpz8DPbn351e+IlYqRGLfokTWgX+7NfMLy87p3SfnPytUu6PM8QiW2VC889
+        Tk0K+5xH5KSgkENaPdLBigHtunyNZaSofgKy5vBM=
+Authentication-Results: dragon.trusteddomain.org; sender-id=fail (NotPermitted) header.sender=arc-discuss-bounces@dmarc.org; spf=fail (NotPermitted) smtp.mfrom=arc-discuss-bounces@dmarc.org
+Received: from mailhub.convivian.com (mailhub.convivian.com [72.5.31.108])
+ by dragon.trusteddomain.org (8.14.5/8.14.5) with ESMTP id w121YEt6036571
+ for <arc-discuss@dmarc.org>; Tue, 2 Mar 1999 09:44:33 +0000 (PST)
+ (envelope-from jered@convivian.com)
+Authentication-Results: dragon.trusteddomain.org; dkim=pass
+ reason="1024-bit key"
+ header.d=convivian.com header.i=@convivian.com header.b=LHXEAl5e;
+ dkim-adsp=pass
+Authentication-Results: dragon.trusteddomain.org;
+ sender-id=pass header.from=jered@convivian.com;
+ spf=pass smtp.mfrom=jered@convivian.com
+Received: from zimbra8.internal.convivian.com (zimbra8.internal.convivian.com
+ [172.16.0.5])
+ by mailhub.convivian.com (Postfix) with ESMTP id 471DA66FB6;
+ Thu,  1 Feb 2018 20:34:08 -0500 (EST)
+ARC-Seal: i=1; a=rsa-sha256; d=convivian.com; s=default; t=1517535248; cv=none;
+ b=HkK4AhtPFBUHtRUKKzTON3wyMj7ZLq881P2qhWg+lO8Y50V9SEc8lJ4dBIM3cj3ftfAbooPSLHAVejA89bpS1eAvODci6pOPaQWkBZmpdu+yPIxqX3FyOaCdIaZFbXaMQ1Jg5Sraf5mkCESmfjR5bCguAaZsnPQDF6wSN8VhbQk=
+ARC-Message-Signature: i=1; a=rsa-sha256; d=convivian.com; s=default;
+ t=1517535248; c=relaxed/simple;
+ bh=9Cp8KoxNPc7FEuC29xB5bNWWadzdEFhXrX/8i+vd3g4=;
+ h=DKIM-Signature:Date:From:To:Cc:Message-ID:In-Reply-To:References:
+ Subject:MIME-Version:Content-Type:X-Originating-IP:X-Mailer:
+ Thread-Topic:Thread-Index:From;
+ b=jG+KnBrP2oq1z1upStMoWbM1fkS5zbUiir221Gy6h7ao5oy7Qc3m0pXgrSdhgGD4oX/kk2seEt2WAlPNwEsZyvYeG/80ctd/2+hwaVQ6JSOU83Rdd8im8HwMvXzXZIz8ATjPpOv21+xMrqlPSkD/l6X4VP+AAoVVkhW7f4GWcws=
+ARC-Authentication-Results: i=1; mailhub.convivian.com; none
+DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=convivian.com;
+ s=default; t=1517535248;
+ bh=9Cp8KoxNPc7FEuC29xB5bNWWadzdEFhXrX/8i+vd3g4=;
+ h=Date:From:To:Cc:In-Reply-To:References:Subject:From;
+ b=LHXEAl5elmfkdXNdK24QonXpkiG38neuJoS7fSQXwZVZkR+cdYNr6eBxx3DF4reJO
+ NgzV5GFyPX6+LdIqR6rnC8BXhjvJq+pxLW3/wKx39W3ANYWRFm1dgyWBz99NxNNvk/
+ ruQkYYBBk9GPM52EyHNMvHciRAyaSk+VluGj6c6M=
+Date: Tue, 2 Mar 1999 09:44:33 +0000 (EST)
+To: Brandon Long <blong@google.com>
+Message-ID: <1426665656.110316.1517535248039.JavaMail.zimbra@convivian.com>
+In-Reply-To: <CABa8R6s3e1k=c9wQBtNBWvPT4BrXv3-2NnynyAfRseZ-5s6NKg@mail.gmail.com>
+References: <CO2PR0501MB981081FA2C73CB83FA1C903F1FA0@CO2PR0501MB981.namprd05.prod.outlook.com>
+ <CAAQnKjAV3zEfP-J6JgTrv1jU9UPmf9dG9SPr-+q4jZ6PaGQjxg@mail.gmail.com>
+ <CAAQnKjBBLS9Lm2vnT3i+WUNhrvv2oDEMFEcyozw+YzyKS4G1qQ@mail.gmail.com>
+ <29030059.107105.1517497494557.JavaMail.zimbra@convivian.com>
+ <4f60039a-a754-ae4c-1543-0a978d9e13be@rolandturner.com>
+ <1544831589.110194.1517532064123.JavaMail.zimbra@convivian.com>
+ <CABa8R6s3e1k=c9wQBtNBWvPT4BrXv3-2NnynyAfRseZ-5s6NKg@mail.gmail.com>
+MIME-Version: 1.0
+X-Originating-IP: [172.16.0.5]
+X-Mailer: Zimbra 8.7.11_GA_1854 (ZimbraWebClient - FF58 (Mac)/8.7.11_GA_1854)
+Thread-Topic: Gmail support of ARC headers from third-parties
+Thread-Index: JantLkX01vLd7pyKcopbBWCs3yDbLQ==
+Cc: arc-discuss <arc-discuss@dmarc.org>
+Subject: Re: [arc-discuss] Gmail support of ARC headers from third-parties
+X-BeenThere: arc-discuss@dmarc.org
+X-Mailman-Version: 2.1.18
+Precedence: list
+List-Id: Discussion of the ARC protocol <arc-discuss.dmarc.org>
+List-Unsubscribe: <http://lists.dmarc.org/mailman/options/arc-discuss>,
+ <mailto:arc-discuss-request@dmarc.org?subject=unsubscribe>
+List-Archive: <http://lists.dmarc.org/pipermail/arc-discuss/>
+List-Post: <mailto:arc-discuss@dmarc.org>
+List-Help: <mailto:arc-discuss-request@dmarc.org?subject=help>
+List-Subscribe: <http://lists.dmarc.org/mailman/listinfo/arc-discuss>,
+ <mailto:arc-discuss-request@dmarc.org?subject=subscribe>
+From: Jered Floyd via arc-discuss <arc-discuss@dmarc.org>
+Reply-To: Jered Floyd <jered@convivian.com>
+Content-Type: multipart/mixed; boundary="===============2728806607597782871=="
+Errors-To: arc-discuss-bounces@dmarc.org
+Sender: "arc-discuss" <arc-discuss-bounces@dmarc.org>
+
+--===============2728806607597782871==
+Content-Type: multipart/alternative; 
+       boundary="=_bda8d35f-e3be-4e59-9fc8-f78ed0af3226"
+
+--=_bda8d35f-e3be-4e59-9fc8-f78ed0af3226
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+>> Couldn't the first untrusted ARC signer (working in reverse chronological order)
+>> simply have faked all the earlier headers and applied a "valid" ARC
+>> signature/seal? This is why I figured you must trust the entire chain if you
+>> want to trust the sender data.
+
+> They can't fake an earlier signature unless they have the private key for the
+> signing domain.
+
+> Ie, a non-modifying hop is basically a no-op, unless you want to trust their
+> auth results.
+
+OK, sure; I agree with that. But I guess I see ARC as primarily for indirect mail flows that break DKIM (i.e. Mailman), in which case I think trust is needed to bridge those hops? 
+
+--Jered 
+
+--=_bda8d35f-e3be-4e59-9fc8-f78ed0af3226
+Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div><br></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
+Couldn't the first untrusted ARC signer (working in reverse chronological order) simply have faked all the earlier headers and applied a "valid" ARC signature/seal?&nbsp; This is why I figured you must trust the entire chain if you want to trust the sender data.<br></blockquote><br><div>They can't fake an earlier signature unless they have the private key for the signing domain.</div><br><div>Ie, a non-modifying hop is basically a no-op, unless you want to trust their auth results.</div></div></div></blockquote><div>OK, sure; I agree with that.&nbsp; But I guess I see ARC as primarily for indirect mail flows that break DKIM (i.e. Mailman), in which case I think trust is needed to bridge those hops?<br></div><div><br data-mce-bogus="1"></div><div>--Jered<br data-mce-bogus="1"></div></div></div></body></html>
+--=_bda8d35f-e3be-4e59-9fc8-f78ed0af3226--
+
+--===============2728806607597782871==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+arc-discuss mailing list
+arc-discuss@dmarc.org
+http://lists.dmarc.org/mailman/listinfo/arc-discuss
+
+--===============2728806607597782871==--
+
index 06597fe..8925731 100755 (executable)
@@ -926,6 +926,9 @@ RESET_AFTER_EXTRA_LINE_READ:
     # Postgres server takes varible time to shut down; lives in various places
     s/^waiting for server to shut down\.+ done$/waiting for server to shut down.... done/;
     s/^\/.*postgres /POSTGRES /;
+
+    # ARC is not always supported by the build
+    next if /^arc_sign =/;
     }
 
   # ======== stderr ========
diff --git a/test/scripts/4560-ARC/4560 b/test/scripts/4560-ARC/4560
new file mode 100644 (file)
index 0000000..2d23674
--- /dev/null
@@ -0,0 +1,359 @@
+# ARC verify and sign
+#
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+# We send this one through one forwarding hop.
+# It starts off bare, so the forwarder reception gets an ARC status of "none".
+# The outbound signs it with that, and the final receiver is happy to pass it.
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO xxx
+??? 250
+MAIL FROM:<CALLER@bloggs.com>
+??? 250
+RCPT TO:<za@test.ex>
+??? 250
+DATA
+??? 354
+Subject: Test
+
+This is a test body.
+.
+??? 250
+QUIT
+??? 221
+****
+#
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+#
+#
+#
+#
+#
+#
+#
+#
+#
+# We send this one through two forwarding hops.
+# It starts off bare, so the 1st forwarder reception gets an ARC status of "none".
+# The outbound signs it with that, and the 2nd forwarder is happy to pass it.
+# The outbound signs again, and the final receiver is happy.
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO xxx
+??? 250
+MAIL FROM:<CALLER@bloggs.com>
+??? 250
+RCPT TO:<zza@test.ex>
+??? 250
+DATA
+??? 354
+Subject: Test
+
+This is a test body.
+.
+??? 250
+QUIT
+??? 221
+****
+#
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+#
+#
+#
+#
+#
+#
+#
+#
+#
+# We send this one through one forwarder, one mailinglist, and one more forwarder
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO xxx
+??? 250
+MAIL FROM:<CALLER@bloggs.com>
+??? 250
+RCPT TO:<zmza@test.ex>
+??? 250
+DATA
+??? 354
+Subject: Test
+
+This is a test body.
+.
+??? 250
+QUIT
+??? 221
+****
+#
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+#
+#
+#
+#
+#
+#
+#
+#
+#
+# We send this one through two forwarders, then one ARC-unaware mailinglist
+# then one more forwarder
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO xxx
+??? 250
+MAIL FROM:<CALLER@bloggs.com>
+??? 250
+RCPT TO:<zzmza@test.ex>
+??? 250
+DATA
+??? 354
+Subject: Test
+
+This is a test body.
+.
+??? 250
+QUIT
+??? 221
+****
+#
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -DOPTION -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+#
+#
+#
+#
+#
+#
+#
+#
+#
+# We send this one through a forwarders, then an ARC-unaware forwarder
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO xxx
+??? 250
+MAIL FROM:<CALLER@bloggs.com>
+??? 250
+RCPT TO:<zza@test.ex>
+??? 250
+DATA
+??? 354
+Subject: Test
+
+This is a test body.
+.
+??? 250
+QUIT
+??? 221
+****
+#
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -DOPTION -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+#
+#
+#
+#
+#
+#
+#
+#
+#
+# We send this one through one forwarding hop.
+# It starts with one ARC-set.
+# The reception at the forwarder gets an ARC-fail, because the bodyhash does not
+# match - so the forwarder outbound ARC-signs as a fail,
+# and the final receiver evaluates ARC status as fail.
+# Mail original in https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-11#page-14
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO xxx
+??? 250
+MAIL FROM:<CALLER@bloggs.com>
+??? 250
+RCPT TO:<za@test.ex>
+??? 250
+DATA
+??? 354
+Received: from dragon.trusteddomain.org (localhost [127.0.0.1])
+       by dragon.trusteddomain.org (8.14.5/8.14.5) with ESMTP id w121YG2q036577;
+       Thu, 1 Feb 2018 17:34:20 -0800 (PST)
+       (envelope-from arc-discuss-bounces@dmarc.org)
+DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=dmarc.org;
+       s=clochette; t=1517535263;
+       bh=DXU/xKzzQYeoYB254nZ0AzNm7z2YZ//FpTnhgIjPyt8=;
+       h=Date:To:In-Reply-To:References:Cc:Subject:List-Id:
+        List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe:
+        From:Reply-To;
+       b=Z66qes0GxyXtv0ow232KSy/b44fPNLZL8JOXHiJLi9dHzIPyxsQd/Zb5NP8i3427g
+        a9tEyo8Rpz8DPbn351e+IlYqRGLfokTWgX+7NfMLy87p3SfnPytUu6PM8QiW2VC889
+        Tk0K+5xH5KSgkENaPdLBigHtunyNZaSofgKy5vBM=
+Authentication-Results: dragon.trusteddomain.org; sender-id=fail (NotPermitted) header.sender=arc-discuss-bounces@dmarc.org; spf=fail (NotPermitted) smtp.mfrom=arc-discuss-bounces@dmarc.org
+Received: from mailhub.convivian.com (mailhub.convivian.com [72.5.31.108])
+ by dragon.trusteddomain.org (8.14.5/8.14.5) with ESMTP id w121YEt6036571
+ for <arc-discuss@dmarc.org>; Thu, 1 Feb 2018 17:34:14 -0800 (PST)
+ (envelope-from jered@convivian.com)
+Authentication-Results: dragon.trusteddomain.org; dkim=pass
+ reason="1024-bit key"
+ header.d=convivian.com header.i=@convivian.com header.b=LHXEAl5e;
+ dkim-adsp=pass
+Authentication-Results: dragon.trusteddomain.org;
+ sender-id=pass header.from=jered@convivian.com;
+ spf=pass smtp.mfrom=jered@convivian.com
+Received: from zimbra8.internal.convivian.com (zimbra8.internal.convivian.com
+ [172.16.0.5])
+ by mailhub.convivian.com (Postfix) with ESMTP id 471DA66FB6;
+ Thu,  1 Feb 2018 20:34:08 -0500 (EST)
+ARC-Seal: i=1; a=rsa-sha256; d=convivian.com; s=default; t=1517535248; cv=none;
+ b=HkK4AhtPFBUHtRUKKzTON3wyMj7ZLq881P2qhWg+lO8Y50V9SEc8lJ4dBIM3cj3ftfAbooPSLHAVejA89bpS1eAvODci6pOPaQWkBZmpdu+yPIxqX3FyOaCdIaZFbXaMQ1Jg5Sraf5mkCESmfjR5bCguAaZsnPQDF6wSN8VhbQk=
+ARC-Message-Signature: i=1; a=rsa-sha256; d=convivian.com; s=default;
+ t=1517535248; c=relaxed/simple;
+ bh=9Cp8KoxNPc7FEuC29xB5bNWWadzdEFhXrX/8i+vd3g4=;
+ h=DKIM-Signature:Date:From:To:Cc:Message-ID:In-Reply-To:References:
+ Subject:MIME-Version:Content-Type:X-Originating-IP:X-Mailer:
+ Thread-Topic:Thread-Index:From;
+ b=jG+KnBrP2oq1z1upStMoWbM1fkS5zbUiir221Gy6h7ao5oy7Qc3m0pXgrSdhgGD4oX/kk2seEt2WAlPNwEsZyvYeG/80ctd/2+hwaVQ6JSOU83Rdd8im8HwMvXzXZIz8ATjPpOv21+xMrqlPSkD/l6X4VP+AAoVVkhW7f4GWcws=
+ARC-Authentication-Results: i=1; mailhub.convivian.com; none
+DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=convivian.com;
+ s=default; t=1517535248;
+ bh=9Cp8KoxNPc7FEuC29xB5bNWWadzdEFhXrX/8i+vd3g4=;
+ h=Date:From:To:Cc:In-Reply-To:References:Subject:From;
+ b=LHXEAl5elmfkdXNdK24QonXpkiG38neuJoS7fSQXwZVZkR+cdYNr6eBxx3DF4reJO
+ NgzV5GFyPX6+LdIqR6rnC8BXhjvJq+pxLW3/wKx39W3ANYWRFm1dgyWBz99NxNNvk/
+ ruQkYYBBk9GPM52EyHNMvHciRAyaSk+VluGj6c6M=
+Date: Thu, 1 Feb 2018 20:34:08 -0500 (EST)
+To: Brandon Long <blong@google.com>
+Message-ID: <1426665656.110316.1517535248039.JavaMail.zimbra@convivian.com>
+In-Reply-To: <CABa8R6s3e1k=c9wQBtNBWvPT4BrXv3-2NnynyAfRseZ-5s6NKg@mail.gmail.com>
+References: <CO2PR0501MB981081FA2C73CB83FA1C903F1FA0@CO2PR0501MB981.namprd05.prod.outlook.com>
+ <CAAQnKjAV3zEfP-J6JgTrv1jU9UPmf9dG9SPr-+q4jZ6PaGQjxg@mail.gmail.com>
+ <CAAQnKjBBLS9Lm2vnT3i+WUNhrvv2oDEMFEcyozw+YzyKS4G1qQ@mail.gmail.com>
+ <29030059.107105.1517497494557.JavaMail.zimbra@convivian.com>
+ <4f60039a-a754-ae4c-1543-0a978d9e13be@rolandturner.com>
+ <1544831589.110194.1517532064123.JavaMail.zimbra@convivian.com>
+ <CABa8R6s3e1k=c9wQBtNBWvPT4BrXv3-2NnynyAfRseZ-5s6NKg@mail.gmail.com>
+MIME-Version: 1.0
+X-Originating-IP: [172.16.0.5]
+X-Mailer: Zimbra 8.7.11_GA_1854 (ZimbraWebClient - FF58 (Mac)/8.7.11_GA_1854)
+Thread-Topic: Gmail support of ARC headers from third-parties
+Thread-Index: JantLkX01vLd7pyKcopbBWCs3yDbLQ==
+Cc: arc-discuss <arc-discuss@dmarc.org>
+Subject: Re: [arc-discuss] Gmail support of ARC headers from third-parties
+X-BeenThere: arc-discuss@dmarc.org
+X-Mailman-Version: 2.1.18
+Precedence: list
+List-Id: Discussion of the ARC protocol <arc-discuss.dmarc.org>
+List-Unsubscribe: <http://lists.dmarc.org/mailman/options/arc-discuss>,
+ <mailto:arc-discuss-request@dmarc.org?subject=unsubscribe>
+List-Archive: <http://lists.dmarc.org/pipermail/arc-discuss/>
+List-Post: <mailto:arc-discuss@dmarc.org>
+List-Help: <mailto:arc-discuss-request@dmarc.org?subject=help>
+List-Subscribe: <http://lists.dmarc.org/mailman/listinfo/arc-discuss>,
+ <mailto:arc-discuss-request@dmarc.org?subject=subscribe>
+From: Jered Floyd via arc-discuss <arc-discuss@dmarc.org>
+Reply-To: Jered Floyd <jered@convivian.com>
+Content-Type: multipart/mixed; boundary="===============2728806607597782871=="
+Errors-To: arc-discuss-bounces@dmarc.org
+Sender: "arc-discuss" <arc-discuss-bounces@dmarc.org>
+
+--===============2728806607597782871==
+Content-Type: multipart/alternative; 
+       boundary="=_bda8d35f-e3be-4e59-9fc8-f78ed0af3226"
+
+--=_bda8d35f-e3be-4e59-9fc8-f78ed0af3226
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+>> Couldn't the first untrusted ARC signer (working in reverse chronological order)
+>> simply have faked all the earlier headers and applied a "valid" ARC
+>> signature/seal? This is why I figured you must trust the entire chain if you
+>> want to trust the sender data.
+
+> They can't fake an earlier signature unless they have the private key for the
+> signing domain.
+
+> Ie, a non-modifying hop is basically a no-op, unless you want to trust their
+> auth results.
+
+OK, sure; I agree with that. But I guess I see ARC as primarily for indirect mail flows that break DKIM (i.e. Mailman), in which case I think trust is needed to bridge those hops? 
+
+--Jered 
+
+--=_bda8d35f-e3be-4e59-9fc8-f78ed0af3226
+Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div><br></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
+Couldn't the first untrusted ARC signer (working in reverse chronological order) simply have faked all the earlier headers and applied a "valid" ARC signature/seal?&nbsp; This is why I figured you must trust the entire chain if you want to trust the sender data.<br></blockquote><br><div>They can't fake an earlier signature unless they have the private key for the signing domain.</div><br><div>Ie, a non-modifying hop is basically a no-op, unless you want to trust their auth results.</div></div></div></blockquote><div>OK, sure; I agree with that.&nbsp; But I guess I see ARC as primarily for indirect mail flows that break DKIM (i.e. Mailman), in which case I think trust is needed to bridge those hops?<br></div><div><br data-mce-bogus="1"></div><div>--Jered<br data-mce-bogus="1"></div></div></div></body></html>
+--=_bda8d35f-e3be-4e59-9fc8-f78ed0af3226--
+
+--===============2728806607597782871==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+arc-discuss mailing list
+arc-discuss@dmarc.org
+http://lists.dmarc.org/mailman/listinfo/arc-discuss
+
+--===============2728806607597782871==--
+.
+??? 250
+QUIT
+??? 221
+****
+#
+exim -DSERVER=server -DNOTDAEMON -q
+****
+exim -DSERVER=server -DNOTDAEMON -q
+****
+#
+#
+#
+#
+#
+#
+#
+#
+#
+killdaemon
+#
+no_stdout_check
+no_msglog_check
diff --git a/test/scripts/4560-ARC/REQUIRES b/test/scripts/4560-ARC/REQUIRES
new file mode 100644 (file)
index 0000000..117c09f
--- /dev/null
@@ -0,0 +1 @@
+support Experimental_ARC
index 19ff3fb..4e6a266 100644 (file)
@@ -34,6 +34,7 @@ PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
 WARNING: bad dkim key in dns
 PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 content{CR}{LF}
+PDKIM: finish bodyhash 1/1/-1 len 9
 PDKIM [test.ex] Body bytes (relaxed) hashed: 9
 PDKIM [test.ex] Body sha256 computed: fc06f48221d98ad6106c3845b33a2a41152482ab9e697f736ad26db4853fa657
 PDKIM >> Headers to be signed:                            >>>>>>>>>>>>