12. Expansion item ${authresults {<machine>}} for creating an
Authentication-Results: header.
+13. EXPERIMENTAL_ARC. See the experimental.spec file.
+
Version 4.90
------------
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
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
--------------------------------------------------------------
# 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 \
# 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
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
# 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
/* 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;
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
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;
h->type = newtype;
h->slen = hlen;
*hptr = h;
- hptr = &(h->next);
+ hptr = &h->next;
}
-
- /* Advance for next header line within the string */
-
- p = q;
}
}
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;
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
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
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;
/* 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)
--- /dev/null
+/*************************************************
+* 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
+ */
#define DISABLE_MAL_CMDLINE
/* EXPERIMENTAL features */
+#define EXPERIMENTAL_ARC
#define EXPERIMENTAL_BRIGHTMAIL
#define EXPERIMENTAL_DCC
#define EXPERIMENTAL_DSN_INFO
+pdkim_ctx dkim_sign_ctx;
int dkim_verify_oldpool;
pdkim_ctx *dkim_verify_ctx = 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;
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 */
/* 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);
}
}
+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.
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;
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;
)
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; }
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,
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);
authres_dkim(gstring * g)
{
pdkim_signature * sig;
+int start;
+
+DEBUG(D_acl) start = g->ptr;
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:
{
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;
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;
}
gstring * dkim_signature;
int hsize;
const uschar * errstr;
+uschar * verrstr;
BOOL rc;
DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
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
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
{
/* 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
#ifdef EXPERIMENTAL_SRS
fprintf(f, " Experimental_SRS");
#endif
+#ifdef EXPERIMENTAL_ARC
+ fprintf(f, " Experimental_ARC");
+#endif
#ifdef EXPERIMENTAL_BRIGHTMAIL
fprintf(f, " Experimental_Brightmail");
#endif
#endif
#ifndef DISABLE_DKIM
yield = authres_dkim(yield);
+#endif
+#ifdef EXPERIMENTAL_ARC
+ yield = authres_arc(yield);
#endif
continue;
}
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;
}
}
+/* 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 *
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 **);
#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 **);
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
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 * );
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, ...);
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;
{ 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;
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 */
#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
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 }
}
+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)
/* -------------------------------------------------------------------------- */
/* Print debugging functions */
-static void
+void
pdkim_quoteprint(const uschar *data, int len)
{
int i;
debug_printf("\n");
}
-static void
+void
pdkim_hexprint(const uschar *data, int len)
{
int i;
/* -------------------------------------------------------------------------- */
/* 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;
}
+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
/* -------------------------------------------------------------------------- */
-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);
"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++)
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
-if (!pdkim_set_bodyhash(ctx, sig))
+if (!pdkim_set_sig_bodyhash(ctx, sig))
return NULL;
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 = ';';
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)
--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 -------------------------------------------------------------- */
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'
)
{
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)
)
{
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;
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
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;
/* -------------------------------------------------------------------------- */
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;
/* 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");
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;
}
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;
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;
} 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.
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 *,
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,
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);
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
}
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;
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;
}
-/* 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)
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);
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 *);
#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))
/* Handle the case when a buffer is provided. */
-if (buffer != NULL)
+if (buffer)
{
int p = 0;
for (; *s != 0; s++)
}
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 */
return g->s;
}
+void
+gstring_reset_unused(gstring * g)
+{
+store_reset(g->s + (g->size = g->ptr + 1));
+}
+
/*************************************************
* Add chars to string *
*************************************************/
uschar *dkim_strict;
uschar *dkim_hash;
BOOL dot_stuffed;
+ BOOL force_bodyhash;
+#ifdef EXPERIMENTAL_ARC
+ uschar *arc_signspec;
+#endif
};
/* End of structs.h */
(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,
.fallback_hosts = NULL,
.hostlist = NULL,
.fallback_hostlist = NULL,
- .authenticated_sender = NULL,
.helo_data = US"$primary_hostname",
.interface = NULL,
.port = NULL,
.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
};
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);
#ifndef DISABLE_DKIM
struct ob_dkim dkim;
#endif
+#ifdef EXPERIMENTAL_ARC
+ uschar *arc_sign;
+#endif
} smtp_transport_options_block;
/* smtp connect context */
--- /dev/null
+
+-----
+This is a generic mailinglist footer
+----
--- /dev/null
+# 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
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
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
******** 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
******** 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
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
--- /dev/null
+
+******** 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
--- /dev/null
+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? 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. 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==--
+
# 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 ========
--- /dev/null
+# 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? 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. 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
--- /dev/null
+support Experimental_ARC
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: >>>>>>>>>>>>