From 617d39327e65b7fccc41a12b4a5e2940d6327c9f Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 2 Mar 2018 23:53:32 +0000 Subject: [PATCH] ARC initial implementation. Experimental. Bug 2162 --- doc/doc-txt/NewStuff | 2 + doc/doc-txt/OptionLists.txt | 1 + doc/doc-txt/experimental-spec.txt | 47 + src/OS/Makefile-Base | 4 +- src/scripts/MakeLinks | 2 +- src/src/EDITME | 4 + src/src/acl.c | 48 +- src/src/arc.c | 1716 +++++++++++++++++++++++++++++ src/src/config.h.defaults | 1 + src/src/dkim.c | 163 +-- src/src/dkim_transport.c | 23 +- src/src/exim.c | 3 + src/src/expand.c | 49 +- src/src/functions.h | 17 + src/src/globals.c | 12 +- src/src/globals.h | 6 + src/src/macro_predef.c | 3 + src/src/pdkim/pdkim.c | 164 ++- src/src/pdkim/pdkim.h | 31 +- src/src/pdkim/signing.c | 8 +- src/src/pdkim/signing.h | 2 +- src/src/receive.c | 4 + src/src/string.c | 9 +- src/src/structs.h | 4 + src/src/transports/smtp.c | 33 +- src/src/transports/smtp.h | 3 + test/aux-fixed/4560.mlistfooter | 4 + test/confs/4560 | 84 ++ test/log/4501 | 2 +- test/log/4502 | 2 +- test/log/4503 | 5 +- test/log/4504 | 5 +- test/log/4506 | 12 +- test/log/4560 | 102 ++ test/mail/4560.a | 453 ++++++++ test/runtest | 3 + test/scripts/4560-ARC/4560 | 359 ++++++ test/scripts/4560-ARC/REQUIRES | 1 + test/stderr/4520 | 1 + 39 files changed, 3204 insertions(+), 188 deletions(-) create mode 100644 src/src/arc.c create mode 100644 test/aux-fixed/4560.mlistfooter create mode 100644 test/confs/4560 create mode 100644 test/log/4560 create mode 100644 test/mail/4560.a create mode 100644 test/scripts/4560-ARC/4560 create mode 100644 test/scripts/4560-ARC/REQUIRES diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 37f53bf89..071d4a5dc 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -44,6 +44,8 @@ Version 4.91 12. Expansion item ${authresults {}} for creating an Authentication-Results: header. +13. EXPERIMENTAL_ARC. See the experimental.spec file. + Version 4.90 ------------ diff --git a/doc/doc-txt/OptionLists.txt b/doc/doc-txt/OptionLists.txt index 5728643a8..1fe72be6b 100644 --- a/doc/doc-txt/OptionLists.txt +++ b/doc/doc-txt/OptionLists.txt @@ -82,6 +82,7 @@ allow_localhost boolean false smtp allow_mx_to_ip boolean false main 3.14 allow_symlink boolean false appendfile allow_utf8_domains boolean false main 4.14 +arc_sign string* unset smtp 4.91 with Experimental_ARC auth_advertise_hosts host list "*" main 4.00 authenticated_sender string* unset smtp 4.14 authenticated_sender_force boolean false smtp 4.61 diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index d5140d58b..4ed6f2518 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -756,6 +756,53 @@ to your Local/Makefile. (Re-)build/install exim. exim -d should show Experimental_QUEUEFILE in the line "Support for:". +ARC support +----------- +Specification: https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-11 +Note that this is not an RFC yet, so may change. + +ARC is intended to support the utility of SPF and DKIM in the presence of +intermediaries in the transmission path - forwarders and mailinglists - +by establishing a cryptographically-signed chain in headers. + +Normally one would only bother doing ARC-signing when functioning as +an intermediary. One might do verify for local destinations. + +ARC uses the notion of a "ADministrative Management Domain" (ADMD). +Described in RFC 5598 (section 2.3), this is essentially the set of +mail-handling systems that the mail transits. A label should be chosen to +identify the ADMD. Messages should be ARC-verified on entry to the ADMD, +and ARC-signed on exit from it. + + +Verification +-- +An ACL condition is provided to perform the "verifier actions" detailed +in section 6 of the above specification. It may be called from the DATA ACL +and succeeds if the result matches any of a given list. +It also records the highest ARC instance number (the chain size) +and verification result for later use in creating an Authentication-Results: +standard header. + + verify = arc/ none:fail:pass + + add_header = :at_start:${authresults {}} + + Note that it would be wise to strip incoming messages of A-R headers + that claim to be from our own . + +Receive log lines for an ARC pass will be tagged "ARC". + + +Signing +-- +arc_sign = : : +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 -------------------------------------------------------------- diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index eefc02bbd..8d191caac 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -477,7 +477,8 @@ convert4r4: config ../src/convert4r4.src # are thrown away by the linker. OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o -OBJ_EXPERIMENTAL = bmi_spam.o \ +OBJ_EXPERIMENTAL = arc.o \ + bmi_spam.o \ dane.o \ dcc.o \ dmarc.o \ @@ -827,6 +828,7 @@ spool_mbox.o: $(HDRS) spool_mbox.c # Dependencies for EXPERIMENTAL_* modules +arc.o: $(HDRS) pdkim/pdkim.h arc.c bmi_spam.o: $(HDRS) bmi_spam.c dane.o: $(HDRS) dane.c dane-openssl.c dcc.o: $(HDRS) dcc.h dcc.c diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 6314fe441..f5a4e5015 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -122,7 +122,7 @@ do done # EXPERIMENTAL_* -for f in bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \ +for f in arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \ danessl.h imap_utf7.c spf.c spf.h srs.c srs.h utf8.c do ln -s ../src/$f $f diff --git a/src/src/EDITME b/src/src/EDITME index b1b9af2c6..18821377a 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -485,6 +485,10 @@ DISABLE_MAL_MKS=yes # CFLAGS += -I/usr/local/include # LDFLAGS += -lopendmarc +# Uncomment the following line to add ARC (Authenticated Received Chain) +# support. You must have SPF and DKIM support enabled also. +# EXPERIMENTAL_ARC + # Uncomment the following lines to add Brightmail AntiSpam support. You need # to have the Brightmail client SDK installed. Please check the experimental # documentation for implementation details. You need to edit the CFLAGS and diff --git a/src/src/acl.c b/src/src/acl.c index 61316a81e..4be5e071a 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -958,7 +958,7 @@ else /* Loop for multiple header lines, taking care about continuations */ -for (p = q; *p != 0; ) +for (p = q; *p; p = q) { const uschar *s; uschar * hdr; @@ -970,7 +970,7 @@ for (p = q; *p != 0; ) for (;;) { q = Ustrchr(q, '\n'); /* we know there was a newline */ - if (*(++q) != ' ' && *q != '\t') break; + if (*++q != ' ' && *q != '\t') break; } /* If the line starts with a colon, interpret the instruction for where to @@ -1005,24 +1005,22 @@ for (p = q; *p != 0; ) to the front of it. */ for (s = p; s < q - 1; s++) - { if (*s == ':' || !isgraph(*s)) break; - } - hdr = string_sprintf("%s%.*s", (*s == ':')? "" : "X-ACL-Warn: ", (int) (q - p), p); + hdr = string_sprintf("%s%.*s", *s == ':' ? "" : "X-ACL-Warn: ", (int) (q - p), p); hlen = Ustrlen(hdr); /* See if this line has already been added */ - while (*hptr != NULL) + while (*hptr) { if (Ustrncmp((*hptr)->text, hdr, hlen) == 0) break; - hptr = &((*hptr)->next); + hptr = &(*hptr)->next; } /* Add if not previously present */ - if (*hptr == NULL) + if (!*hptr) { header_line *h = store_get(sizeof(header_line)); h->text = hdr; @@ -1030,12 +1028,8 @@ for (p = q; *p != 0; ) h->type = newtype; h->slen = hlen; *hptr = h; - hptr = &(h->next); + hptr = &h->next; } - - /* Advance for next header line within the string */ - - p = q; } } @@ -1662,9 +1656,9 @@ if (!(vp->where_allowed & BIT(where))) switch(vp->value) { case VERIFY_REV_HOST_LKUP: - if (sender_host_address == NULL) return OK; + if (!sender_host_address) return OK; if ((rc = acl_verify_reverse(user_msgptr, log_msgptr)) == DEFER) - while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) + while ((ss = string_nextinlist(&list, &sep, NULL, 0))) if (strcmpic(ss, US"defer_ok") == 0) return OK; return rc; @@ -1683,7 +1677,7 @@ switch(vp->value) occurred earlier. If not, we can attempt the verification now. */ if (!helo_verified && !helo_verify_failed) smtp_verify_helo(); - return helo_verified? OK : FAIL; + return helo_verified ? OK : FAIL; case VERIFY_CSA: /* Do Client SMTP Authorization checks in a separate function, and turn the @@ -1696,6 +1690,23 @@ switch(vp->value) DEBUG(D_acl) debug_printf_indent("CSA result %s\n", csa_status); return csa_return_code[rc]; +#ifdef EXPERIMENTAL_ARC + case VERIFY_ARC: + { /* Do Authenticated Received Chain checks in a separate function. */ + const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0); + int csep = 0; + uschar * cond; + + if (!(arc_state = acl_verify_arc())) return DEFER; + DEBUG(D_acl) debug_printf_indent("ARC verify result %s\n", arc_state); + + if (!condlist) condlist = US"none:pass"; + while ((cond = string_nextinlist(&condlist, &csep, NULL, 0))) + if (Ustrcmp(arc_state, cond) == 0) return OK; + return FAIL; + } +#endif + case VERIFY_HDR_SYNTAX: /* Check that all relevant header lines have the correct 5322-syntax. If there is a syntax error, we return details of the error to the sender if configured to @@ -1715,7 +1726,7 @@ switch(vp->value) See RFC 5322, 2.2. and RFC 6532, 3. */ rc = verify_check_header_names_ascii(log_msgptr); - if (rc != OK && smtp_return_error_details && *log_msgptr != NULL) + if (rc != OK && smtp_return_error_details && *log_msgptr) *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr); return rc; @@ -1723,8 +1734,7 @@ switch(vp->value) /* Check that no recipient of this message is "blind", that is, every envelope recipient must be mentioned in either To: or Cc:. */ - rc = verify_check_notblind(); - if (rc != OK) + if ((rc = verify_check_notblind()) != OK) { *log_msgptr = string_sprintf("bcc recipient detected"); if (smtp_return_error_details) diff --git a/src/src/arc.c b/src/src/arc.c new file mode 100644 index 000000000..1323a451a --- /dev/null +++ b/src/src/arc.c @@ -0,0 +1,1716 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ +/* Experimental ARC support for Exim + Copyright (c) Jeremy Harris 2018 + License: GPL +*/ + +#include "exim.h" +#ifdef EXPERIMENTAL_ARC +# if !defined SUPPORT_SPF +# error SPF must also be enabled for ARC +# elif defined DISABLE_DKIM +# error DKIM must also be enabled for ARC +# else + +# include "functions.h" +# include "pdkim/pdkim.h" +# include "pdkim/signing.h" + +extern pdkim_ctx * dkim_verify_ctx; +extern pdkim_ctx dkim_sign_ctx; + +/******************************************************************************/ + +typedef struct hdr_rlist { + struct hdr_rlist * prev; + BOOL used; + header_line * h; +} hdr_rlist; + +typedef struct arc_line { + header_line * complete; /* including the header name; nul-term */ + uschar * relaxed; + + /* identified tag contents */ + /*XXX t= for AS? */ + blob i; + blob cv; + blob a; + blob b; + blob bh; + blob d; + blob h; + blob s; + blob c; + blob l; + + /* tag content sub-portions */ + blob a_algo; + blob a_hash; + + blob c_head; + blob c_body; + + /* modified copy of b= field in line */ + blob rawsig_no_b_val; +} arc_line; + +typedef struct arc_set { + struct arc_set * next; + struct arc_set * prev; + + unsigned instance; + arc_line * hdr_aar; + arc_line * hdr_ams; + arc_line * hdr_as; + + BOOL ams_verify_done; + BOOL ams_verify_passed; +} arc_set; + +typedef struct arc_ctx { + arc_set * arcset_chain; + arc_set * arcset_chain_last; +} arc_ctx; + +#define ARC_HDR_AAR US"ARC-Authentication-Results:" +#define ARC_HDRLEN_AAR 27 +#define ARC_HDR_AMS US"ARC-Message-Signature:" +#define ARC_HDRLEN_AMS 22 +#define ARC_HDR_AS US"ARC-Seal:" +#define ARC_HDRLEN_AS 9 +#define HDR_AR US"Authentication-Results:" +#define HDRLEN_AR 23 + +static hdr_rlist * headers_rlist; +static arc_ctx arc_sign_ctx = { NULL }; + + +/******************************************************************************/ + + +/* Get the instance number from the header. +Return 0 on error */ +static unsigned +arc_instance_from_hdr(const arc_line * al) +{ +uschar * s = al->i.data; +if (!s || !al->i.len) return 0; +return (unsigned) atoi(s); +} + + +static uschar * +skip_fws(uschar * s) +{ +uschar c = *s; +while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s; +return s; +} + + +/* Locate instance struct on chain, inserting a new one if +needed. The chain is in increasing-instance-number order +by the "next" link, and we have a "prev" link also. +*/ + +static arc_set * +arc_find_set(arc_ctx * ctx, unsigned i) +{ +arc_set ** pas, * as, * next, * prev; + +for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain; + as = *pas; pas = &as->next) + { + if (as->instance > i) break; + if (as->instance == i) + { + DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i); + return as; + } + next = as->next; + prev = as; + } + +DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i); +*pas = as = store_get(sizeof(arc_set)); +memset(as, 0, sizeof(arc_set)); +as->next = next; +as->prev = prev; +as->instance = i; +if (next) + next->prev = as; +else + ctx->arcset_chain_last = as; +return as; +} + + + +/* Insert a tag content into the line structure. +Note this is a reference to existing data, not a copy. +Check for already-seen tag. +The string-pointer is on the '=' for entry. Update it past the +content (to the ;) on return; +*/ + +static uschar * +arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss) +{ +uschar * s = *ss; +uschar c = *++s; +blob * b = (blob *)(US al + loff); +size_t len = 0; + +/* [FWS] tag-value [FWS] */ + +if (b->data) return US"fail"; +s = skip_fws(s); /* FWS */ + +b->data = s; +while ((c = *s) && c != ';') { len++; s++; } +*ss = s; +while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r')) + { s--; len--; } /* FWS */ +b->len = len; +return NULL; +} + + +/* Inspect a header line, noting known tag fields. +Check for duplicates. */ + +static uschar * +arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only) +{ +uschar * s = h->text + off; +uschar * r; +uschar c; + +al->complete = h; + +if (!instance_only) + { + al->rawsig_no_b_val.data = store_get(h->slen + 1); + memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */ + r = al->rawsig_no_b_val.data + off; + al->rawsig_no_b_val.len = off; + } + +/* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */ + +while ((c = *s)) + { + char tagchar; + uschar * t; + unsigned i = 0; + uschar * fieldstart = s; + uschar * bstart = NULL, * bend; + + /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */ + + s = skip_fws(s); /* FWS */ + if (!*s) break; +/* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */ + tagchar = *s++; + s = skip_fws(s); /* FWS */ + if (!*s) break; + + if (!instance_only || tagchar == 'i') switch (tagchar) + { + case 'a': /* a= AMS algorithm */ + { + if (*s != '=') return US"no 'a' value"; + if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup"; + + /* substructure: algo-hash (eg. rsa-sha256) */ + + t = al->a_algo.data = al->a.data; + while (*t != '-') + if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value"; + al->a_algo.len = i; + if (*t++ != '-') return US"no '-' in 'a' value"; + al->a_hash.data = t; + al->a_hash.len = al->a.len - i - 1; + } + break; + case 'b': + { + gstring * g = NULL; + + switch (*s) + { + case '=': /* b= AMS signature */ + if (al->b.data) return US"already b data"; + bstart = s+1; + + /* The signature can have FWS inserted in the content; + make a stripped copy */ + + while ((c = *++s) && c != ';') + if (c != ' ' && c != '\t' && c != '\n' && c != '\r') + g = string_catn(g, s, 1); + al->b.data = string_from_gstring(g); + al->b.len = g->ptr; + gstring_reset_unused(g); + bend = s; + break; + case 'h': /* bh= AMS body hash */ + s = skip_fws(++s); /* FWS */ + if (*s != '=') return US"no bh value"; + if (al->bh.data) return US"already bh data"; + + /* The bodyhash can have FWS inserted in the content; + make a stripped copy */ + + while ((c = *++s) && c != ';') + if (c != ' ' && c != '\t' && c != '\n' && c != '\r') + g = string_catn(g, s, 1); + al->bh.data = string_from_gstring(g); + al->bh.len = g->ptr; + gstring_reset_unused(g); + break; + default: + return US"b? tag"; + } + } + break; + case 'c': + switch (*s) + { + case '=': /* c= AMS canonicalisation */ + if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup"; + + /* substructure: head/body (eg. relaxed/simple)) */ + + t = al->c_head.data = al->c.data; + while (isalpha(*t)) + if (!*t++ || ++i > al->a.len) break; + al->c_head.len = i; + if (*t++ == '/') /* /body is optional */ + { + al->c_body.data = t; + al->c_body.len = al->c.len - i - 1; + } + else + { + al->c_body.data = US"simple"; + al->c_body.len = 6; + } + break; + case 'v': /* cv= AS validity */ + if (*++s != '=') return US"cv tag val"; + if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup"; + break; + default: + return US"c? tag"; + } + break; + case 'd': /* d= AMS domain */ + if (*s != '=') return US"d tag val"; + if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup"; + break; + case 'h': /* h= AMS headers */ + if (*s != '=') return US"h tag val"; + if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup"; + break; + case 'i': /* i= ARC set instance */ + if (*s != '=') return US"i tag val"; + if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup"; + if (instance_only) goto done; + break; + case 'l': /* l= bodylength */ + if (*s != '=') return US"l tag val"; + if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup"; + break; + case 's': /* s= AMS selector */ + if (*s != '=') return US"s tag val"; + if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup"; + break; + } + + while ((c = *s) && c != ';') s++; + if (c) s++; /* ; after tag-spec */ + + /* for all but the b= tag, copy the field including FWS. For the b=, + drop the tag content. */ + + if (!instance_only) + if (bstart) + { + size_t n = bstart - fieldstart; + memcpy(r, fieldstart, n); /* FWS "b=" */ + r += n; + al->rawsig_no_b_val.len += n; + n = s - bend; + memcpy(r, bend, n); /* FWS ";" */ + r += n; + al->rawsig_no_b_val.len += n; + } + else + { + size_t n = s - fieldstart; + memcpy(r, fieldstart, n); + r += n; + al->rawsig_no_b_val.len += n; + } + } + +if (!instance_only) + *r = '\0'; + +done: +/* debug_printf("%s: finshed\n", __FUNCTION__); */ +return NULL; +} + + +/* Insert one header line in the correct set of the chain, +adding instances as needed and checking for duplicate lines. +*/ + +static uschar * +arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff, + BOOL instance_only) +{ +int i; +arc_set * as; +arc_line * al = store_get(sizeof(arc_line)), ** alp; +uschar * e; + +memset(al, 0, sizeof(arc_line)); + +if ((e = arc_parse_line(al, h, off, instance_only))) + { + DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e); + return US"line parse"; + } +if (!(i = arc_instance_from_hdr(al))) return US"instance find"; +if (!(as = arc_find_set(ctx, i))) return US"set find"; +if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr"; + +*alp = al; +return NULL; +} + + + + +static const uschar * +arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only) +{ +const uschar * e; + +/*debug_printf("consider hdr '%s'\n", h->text);*/ +if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0) + { + DEBUG(D_acl) + { + int len = h->slen; + uschar * s; + for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; ) + s--, len--; + debug_printf("ARC: found AAR: %.*s\n", len, h->text); + } + if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar), + TRUE))) + { + DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e); + return US"inserting AAR"; + } + } +else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0) + { + arc_line * ams; + + DEBUG(D_acl) + { + int len = h->slen; + uschar * s; + for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; ) + s--, len--; + debug_printf("ARC: found AMS: %.*s\n", len, h->text); + } + if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams), + instance_only))) + { + DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e); + return US"inserting AMS"; + } + + /* defaults */ + /*XXX dubious selection of ams here */ + ams = ctx->arcset_chain->hdr_ams; + if (!ams->c.data) + { + ams->c_head.data = US"simple"; ams->c_head.len = 6; + ams->c_body = ams->c_head; + } + } +else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0) + { + DEBUG(D_acl) + { + int len = h->slen; + uschar * s; + for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; ) + s--, len--; + debug_printf("ARC: found AS: %.*s\n", len, h->text); + } + if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as), + instance_only))) + { + DEBUG(D_acl) debug_printf("inserting AS: %s\n", e); + return US"inserting AS"; + } + } +return NULL; +} + + + +/* Gather the chain of arc sets from the headers. +Check for duplicates while that is done. Also build the +reverse-order headers list; + +Return: ARC state if determined, eg. by lack of any ARC chain. +*/ + +static const uschar * +arc_vfy_collect_hdrs(arc_ctx * ctx) +{ +header_line * h; +hdr_rlist * r, * rprev = NULL; +const uschar * e; + +DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n"); +for (h = header_list; h; h = h->next) + { + r = store_get(sizeof(hdr_rlist)); + r->prev = rprev; + r->used = FALSE; + r->h = h; + rprev = r; + + if ((e = arc_try_header(ctx, h, FALSE))) + return e; + } +headers_rlist = r; + +if (!ctx->arcset_chain) return US"none"; +return NULL; +} + + +static BOOL +arc_cv_match(arc_line * al, const uschar * s) +{ +return Ustrncmp(s, al->cv.data, al->cv.len) == 0; +} + +/******************************************************************************/ + +/* Return the hash of headers from the message that the AMS claims it +signed. +*/ + +static void +arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash) +{ +const uschar * headernames = string_copyn(ams->h.data, ams->h.len); +const uschar * hn; +int sep = ':'; +hdr_rlist * r; +BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0; +int hashtype = pdkim_hashname_to_hashtype( + ams->a_hash.data, ams->a_hash.len); +hctx hhash_ctx; +const uschar * s; +int len; + +if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod)) + { + DEBUG(D_acl) + debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n"); + return; + } + +/* For each headername in the list from the AMS (walking in order) +walk the message headers in reverse order, adding to the hash any +found for the first time. For that last point, maintain used-marks +on the list of message headers. */ + +DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n"); + +for (r = headers_rlist; r; r = r->prev) + r->used = FALSE; +while ((hn = string_nextinlist(&headernames, &sep, NULL, 0))) + for (r = headers_rlist; r; r = r->prev) + if ( !r->used + && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0 + ) + { + if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE); + + len = Ustrlen(s); + DEBUG(D_acl) pdkim_quoteprint(s, len); + exim_sha_update(&hhash_ctx, s, Ustrlen(s)); + r->used = TRUE; + break; + } + +/* Finally add in the signature header (with the b= tag stripped) */ + +s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len; +if (relaxed) + len = Ustrlen(s = pdkim_relax_header_n(s, len, TRUE)); +DEBUG(D_acl) pdkim_quoteprint(s, len); +exim_sha_update(&hhash_ctx, s, len); + +exim_sha_finish(&hhash_ctx, hhash); +DEBUG(D_acl) + { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); } +return; +} + + + + +static pdkim_pubkey * +arc_line_to_pubkey(arc_line * al) +{ +uschar * dns_txt; +pdkim_pubkey * p; + +if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s", + al->s.len, al->s.data, al->d.len, al->d.data)))) + { + DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n"); + return NULL; + } + +if ( !(p = pdkim_parse_pubkey_record(dns_txt)) + || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0) + ) + { + DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n"); + return NULL; + } + +/* If the pubkey limits use to specified hashes, reject unusable +signatures. XXX should we have looked for multiple dns records? */ + +if (p->hashes) + { + const uschar * list = p->hashes, * ele; + int sep = ':'; + + while ((ele = string_nextinlist(&list, &sep, NULL, 0))) + if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break; + if (!ele) + { + DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n", + p->hashes, al->a.len, al->a.data); + return NULL; + } + } +return p; +} + + + + +static pdkim_bodyhash * +arc_ams_setup_vfy_bodyhash(arc_line * ams) +{ +int canon_head, canon_body; +long bodylen; + +pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body); +bodylen = ams->l.data + ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1; + +return pdkim_set_bodyhash(dkim_verify_ctx, + pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len), + canon_body, + bodylen); +} + + + +/* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag +and without a DKIM v= tag. +*/ + +static uschar * +arc_ams_verify(arc_ctx * ctx, arc_set * as) +{ +arc_line * ams = as->hdr_ams; +pdkim_bodyhash * b; +pdkim_pubkey * p; +blob sighash; +blob hhash; +ev_ctx vctx; +int hashtype; +const uschar * errstr; + +as->ams_verify_done = TRUE; + +/* Check the AMS has all the required tags: + "a=" algorithm + "b=" signature + "bh=" body hash + "d=" domain (for key lookup) + "h=" headers (included in signature) + "s=" key-selector (for key lookup) +*/ +if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data + || !ams->h.data || !ams->s.data) + return US"fail"; + + +/* The bodyhash should have been created earlier, and the dkim code should +have managed calculating it during message input. Find the reference to it. */ + +if (!(b = arc_ams_setup_vfy_bodyhash(ams))) + return US"fail"; + +DEBUG(D_acl) + { + debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n" + " Body %.*s computed: ", + as->instance, b->signed_body_bytes, + ams->a_hash.len, ams->a_hash.data); + pdkim_hexprint(CUS b->bh.data, b->bh.len); + } + +/* We know the bh-tag blob is of a nul-term string, so safe as a string */ + +if ( !ams->bh.data + || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len) + || memcmp(sighash.data, b->bh.data, b->bh.len) != 0 + ) + { + DEBUG(D_acl) + { + debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance); + pdkim_hexprint(sighash.data, sighash.len); + debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance); + } + return US"body hash compare mismatch"; + } + +DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance); + +/* Get the public key from DNS */ + +if (!(p = arc_line_to_pubkey(ams))) + return US"pubkey problem"; + +/* We know the b-tag blob is of a nul-term string, so safe as a string */ +pdkim_decode_base64(ams->b.data, &sighash); + +arc_get_verify_hhash(ctx, ams, &hhash); + +/* Setup the interface to the signing library */ + +if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx))) + { + DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr); + return US"fail"; + } + +hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len); + +if ((errstr = exim_dkim_verify(&vctx, + pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash))) + { + DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr); + return US"ams sig verify fail"; + } + +DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance); +as->ams_verify_passed = TRUE; +return NULL; +} + + + +/* Check the sets are instance-continuous and that all +members are present. Check that no arc_seals are "fail". +Set the highest instance number global. +Verify the latest AMS. +*/ +static uschar * +arc_headers_check(arc_ctx * ctx) +{ +arc_set * as; +int inst; +BOOL ams_fail_found = FALSE; +uschar * ret = NULL; + +if (!(as = ctx->arcset_chain)) + return US"none"; + +for(inst = 0; as; as = as->next) + { + if ( as->instance != ++inst + || !as->hdr_aar || !as->hdr_ams || !as->hdr_as + || arc_cv_match(as->hdr_as, US"fail") + ) + { + DEBUG(D_acl) debug_printf("ARC i=%d fail" + " (cv, sequence or missing header)\n", as->instance); + ret = US"fail"; + } + + /* Evaluate the oldest-pass AMS validation while we're here. + It does not affect the AS chain validation but is reported as + auxilary info. */ + + if (!ams_fail_found) + if (arc_ams_verify(ctx, as)) + ams_fail_found = TRUE; + else + arc_oldest_pass = inst; + } + +arc_received = ctx->arcset_chain_last; +arc_received_instance = inst; +if (ret) + return ret; + +/* We can skip the latest-AMS validation, if we already did it. */ + +as = ctx->arcset_chain_last; +if (as->ams_verify_done ? !as->ams_verify_passed : !!arc_ams_verify(ctx, as)) + return US"fail"; + +return NULL; +} + + +/******************************************************************************/ +static const uschar * +arc_seal_verify(arc_ctx * ctx, arc_set * as) +{ +arc_line * hdr_as = as->hdr_as; +arc_set * as2; +int hashtype; +hctx hhash_ctx; +blob hhash_computed; +blob sighash; +ev_ctx vctx; +pdkim_pubkey * p; +const uschar * errstr; + +DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance); +/* + 1. If the value of the "cv" tag on that seal is "fail", the + chain state is "fail" and the algorithm stops here. (This + step SHOULD be skipped if the earlier step (2.1) was + performed) [it was] + + 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv + == "none" && i != 1)) then the chain state is "fail" and the + algorithm stops here (note that the ordering of the logic is + structured for short-circuit evaluation). +*/ + +if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none") + || arc_cv_match(hdr_as, US"none") && as->instance != 1 + ) + return US"fail"; + +/* + 3. Initialize a hash function corresponding to the "a" tag of + the ARC-Seal. +*/ + +hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len); + +if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod)) + { + DEBUG(D_acl) + debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n"); + return US"fail"; + } + +/* + 4. Compute the canonicalized form of the ARC header fields, in + the order described in Section 5.4.2, using the "relaxed" + header canonicalization defined in Section 3.4.2 of + [RFC6376]. Pass the canonicalized result to the hash + function. +*/ + +DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n"); +for (as2 = ctx->arcset_chain; + as2 && as2->instance <= as->instance; + as2 = as2->next) + { + arc_line * al; + uschar * s; + int len; + + al = as2->hdr_aar; + if (!(s = al->relaxed)) + al->relaxed = s = pdkim_relax_header_n(al->complete->text, + al->complete->slen, TRUE); + len = Ustrlen(s); + DEBUG(D_acl) pdkim_quoteprint(s, len); + exim_sha_update(&hhash_ctx, s, len); + + al = as2->hdr_ams; + if (!(s = al->relaxed)) + al->relaxed = s = pdkim_relax_header_n(al->complete->text, + al->complete->slen, TRUE); + len = Ustrlen(s); + DEBUG(D_acl) pdkim_quoteprint(s, len); + exim_sha_update(&hhash_ctx, s, len); + + al = as2->hdr_as; + if (as2->instance == as->instance) + s = pdkim_relax_header_n(al->rawsig_no_b_val.data, + al->rawsig_no_b_val.len, TRUE); + else if (!(s = al->relaxed)) + al->relaxed = s = pdkim_relax_header_n(al->complete->text, + al->complete->slen, TRUE); + len = Ustrlen(s); + DEBUG(D_acl) pdkim_quoteprint(s, len); + exim_sha_update(&hhash_ctx, s, len); + } + +/* + 5. Retrieve the final digest from the hash function. +*/ + +exim_sha_finish(&hhash_ctx, &hhash_computed); +DEBUG(D_acl) + { + debug_printf("ARC i=%d AS Header %.*s computed: ", + as->instance, hdr_as->a_hash.len, hdr_as->a_hash.data); + pdkim_hexprint(hhash_computed.data, hhash_computed.len); + } + + +/* + 6. Retrieve the public key identified by the "s" and "d" tags in + the ARC-Seal, as described in Section 4.1.6. +*/ + +if (!(p = arc_line_to_pubkey(hdr_as))) + return US"pubkey problem"; + +/* + 7. Determine whether the signature portion ("b" tag) of the ARC- + Seal and the digest computed above are valid according to the + public key. (See also Section Section 8.4 for failure case + handling) + + 8. If the signature is not valid, the chain state is "fail" and + the algorithm stops here. +*/ + +/* We know the b-tag blob is of a nul-term string, so safe as a string */ +pdkim_decode_base64(hdr_as->b.data, &sighash); + +if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx))) + { + DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr); + return US"fail"; + } + +hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len); + +if ((errstr = exim_dkim_verify(&vctx, + pdkim_hashes[hashtype].exim_hashmethod, + &hhash_computed, &sighash))) + { + DEBUG(D_acl) + debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr); + return US"fail"; + } + +DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance); +return NULL; +} + + +static const uschar * +arc_verify_seals(arc_ctx * ctx) +{ +arc_set * as = ctx->arcset_chain; + +if (!as) + return US"none"; + +while (as) + { + if (arc_seal_verify(ctx, as)) return US"fail"; + as = as->next; + } +DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n"); +return NULL; +} +/******************************************************************************/ + +/* Do ARC verification. Called from DATA ACL, on a verify = arc +condition. No arguments; we are checking globals. + +Return: The ARC state, or NULL on error. +*/ + +const uschar * +acl_verify_arc(void) +{ +arc_ctx ctx = { NULL }; +const uschar * res; + +/* AS evaluation, per +https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6 +*/ +/* 1. Collect all ARC sets currently on the message. If there were + none, the ARC state is "none" and the algorithm stops here. +*/ + +if ((res = arc_vfy_collect_hdrs(&ctx))) + goto out; + +/* 2. If the form of any ARC set is invalid (e.g., does not contain + exactly one of each of the three ARC-specific header fields), + then the chain state is "fail" and the algorithm stops here. + + 1. To avoid the overhead of unnecessary computation and delay + from crypto and DNS operations, the cv value for all ARC- + Seal(s) MAY be checked at this point. If any of the values + are "fail", then the overall state of the chain is "fail" and + the algorithm stops here. + + 3. Conduct verification of the ARC-Message-Signature header field + bearing the highest instance number. If this verification fails, + then the chain state is "fail" and the algorithm stops here. +*/ + +if ((res = arc_headers_check(&ctx))) + goto out; + +/* 4. For each ARC-Seal from the "N"th instance to the first, apply the + following logic: + + 1. If the value of the "cv" tag on that seal is "fail", the + chain state is "fail" and the algorithm stops here. (This + step SHOULD be skipped if the earlier step (2.1) was + performed) + + 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv + == "none" && i != 1)) then the chain state is "fail" and the + algorithm stops here (note that the ordering of the logic is + structured for short-circuit evaluation). + + 3. Initialize a hash function corresponding to the "a" tag of + the ARC-Seal. + + 4. Compute the canonicalized form of the ARC header fields, in + the order described in Section 5.4.2, using the "relaxed" + header canonicalization defined in Section 3.4.2 of + [RFC6376]. Pass the canonicalized result to the hash + function. + + 5. Retrieve the final digest from the hash function. + + 6. Retrieve the public key identified by the "s" and "d" tags in + the ARC-Seal, as described in Section 4.1.6. + + 7. Determine whether the signature portion ("b" tag) of the ARC- + Seal and the digest computed above are valid according to the + public key. (See also Section Section 8.4 for failure case + handling) + + 8. If the signature is not valid, the chain state is "fail" and + the algorithm stops here. + + 5. If all seals pass validation, then the chain state is "pass", and + the algorithm is complete. +*/ + +if ((res = arc_verify_seals(&ctx))) + goto out; + +res = US"pass"; + +out: + return res; +} + +/******************************************************************************/ + +/* Prepend the header to the rlist */ + +static hdr_rlist * +arc_rlist_entry(hdr_rlist * list, const uschar * s, int len) +{ +hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line)); +header_line * h = r->h = (header_line *)(r+1); + +r->prev = list; +r->used = FALSE; +h->next = NULL; +h->type = 0; +h->slen = len; +h->text = US s; + +/* This works for either NL or CRLF lines; also nul-termination */ +while (*++s) + if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break; +s++; /* move past end of line */ + +return r; +} + + +/* Walk the given headers strings identifying each header, and construct +a reverse-order list. Also parse ARC-chain headers and build the chain +struct, retaining pointers into the string. +*/ + +static hdr_rlist * +arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders) +{ +const uschar * s; +hdr_rlist * rheaders = NULL; + +s = sigheaders ? sigheaders->s : NULL; +if (s) while (*s) + { + uschar * s2; + + /* This works for either NL or CRLF lines; also nul-termination */ + while (*++s2) + if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break; + s2++; /* move past end of line */ + + rheaders = arc_rlist_entry(rheaders, s, s2 - s); + s = s2; + } +return rheaders; +} + + + +/* Return the A-R content, without identity, with line-ending and +NUL termination. */ + +static BOOL +arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret) +{ +header_line * h; +int ilen = Ustrlen(identity); + +ret->data = NULL; +for(h = headers; h; h = h->next) + { + uschar * s = h->text, c; + int len = h->slen; + + if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue; + s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */ + while ( len > 0 + && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) + s++, len--; /* FWS */ + if (Ustrncmp(s, identity, ilen) != 0) continue; + s += ilen; len -= ilen; /* identity */ + if (len <= 0) continue; + if ((c = *s) && c == ';') s++, len--; /* identity terminator */ + while ( len > 0 + && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) + s++, len--; /* FWS */ + if (len <= 0) continue; + ret->data = s; + ret->len = len; + return TRUE; + } +return FALSE; +} + + + +/* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */ + +static gstring * +arc_sign_append_aar(gstring * g, arc_ctx * ctx, + const uschar * identity, int instance, blob * ar) +{ +int aar_off = g ? g->ptr : 0; +arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line)); +arc_line * al = (arc_line *)(as+1); +header_line * h = (header_line *)(al+1); + +g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR); +g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity)); +g = string_catn(g, US ar->data, ar->len); + +h->slen = g->ptr - aar_off; +h->text = g->s + aar_off; +al->complete = h; +as->next = NULL; +as->prev = ctx->arcset_chain_last; +as->instance = instance; +as->hdr_aar = al; +if (instance == 1) + ctx->arcset_chain = as; +else + ctx->arcset_chain_last->next = as; +ctx->arcset_chain_last = as; + +DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text); +return g; +} + + + +static BOOL +arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey, + blob * sig, const uschar * why) +{ +hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE + ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod; +blob hhash; +es_ctx sctx; +const uschar * errstr; + +DEBUG(D_transport) + { + hctx hhash_ctx; + debug_printf("ARC: %s header data for signing:\n", why); + pdkim_quoteprint(hdata->s, hdata->ptr); + + (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod); + exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr); + exim_sha_finish(&hhash_ctx, &hhash); + debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len); + } + +if (FALSE /*need hash for Ed25519 or GCrypt signing*/ ) + { + hctx hhash_ctx; + (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod); + exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr); + exim_sha_finish(&hhash_ctx, &hhash); + } +else + { + hhash.data = hdata->s; + hhash.len = hdata->ptr; + } + +if ( (errstr = exim_dkim_signing_init(privkey, &sctx)) + || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig))) + { + log_write(0, LOG_MAIN|LOG_PANIC, "ARC: %s signing: %s\n", why, errstr); + return FALSE; + } +return TRUE; +} + + + +static gstring * +arc_sign_append_sig(gstring * g, blob * sig) +{ +/*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/ +sig->data = pdkim_encode_base64(sig); +sig->len = Ustrlen(sig->data); +for (;;) + { + int len = MIN(sig->len, 74); + g = string_catn(g, sig->data, len); + if ((sig->len -= len) == 0) break; + sig->data += len; + g = string_catn(g, US"\r\n\t ", 5); + } +g = string_catn(g, US";\r\n", 3); +gstring_reset_unused(g); +string_from_gstring(g); +return g; +} + + +/* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */ + +static gstring * +arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance, + const uschar * identity, const uschar * selector, blob * bodyhash, + hdr_rlist * rheaders, const uschar * privkey) +{ +uschar * s; +gstring * hdata = NULL; +int col; +int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */ +blob sig; +int ams_off; +arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line)); +header_line * h = (header_line *)(al+1); + +/* debug_printf("%s\n", __FUNCTION__); */ + +/* Construct the to-be-signed AMS pseudo-header: everything but the sig. */ + +ams_off = g->ptr; +g = string_append(g, 10, + ARC_HDR_AMS, + US" i=", string_sprintf("%d", instance), + US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */ + US"; s=", selector, + US";\r\n\tbh=", pdkim_encode_base64(bodyhash), + US";\r\n\th="); + +for(col = 3; rheaders; rheaders = rheaders->prev) + { + const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS; + uschar * name, * htext = rheaders->h->text; + int sep = ':'; + + /* Spot headers of interest */ + + while ((name = string_nextinlist(&hnames, &sep, NULL, 0))) + { + int len = Ustrlen(name); + if (strncasecmp(CCS htext, CCS name, len) == 0) + { + /* If too long, fold line in h= field */ + + if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3; + + /* Add name to h= list */ + + g = string_catn(g, name, len); + g = string_catn(g, US":", 1); + col += len + 1; + + /* Accumulate header for hashing/signing */ + + hdata = string_cat(hdata, + pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */ + break; + } + } + } + +/* Lose the last colon from the h= list */ + +if (g->s[g->ptr - 1] == ':') g->ptr--; + +g = string_catn(g, US";\r\n\tb=;", 7); + +/* Include the pseudo-header in the accumulation */ +/*XXX should that be prepended rather than appended? */ +/*XXX also need to include at the verify stage */ + +s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, TRUE); +hdata = string_cat(hdata, s); + +/* Calculate the signature from the accumulation */ +/*XXX does that need further relaxation? there are spaces embedded in the b= strings! */ + +if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS")) + return NULL; + +/* Lose the trailing semicolon from the psuedo-header, and append the signature +(folded over lines) and termination to complete it. */ + +g->ptr--; +g = arc_sign_append_sig(g, &sig); + +h->slen = g->ptr - ams_off; +h->text = g->s + ams_off; +al->complete = h; +ctx->arcset_chain_last->hdr_ams = al; + +DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text); +return g; +} + + + +/* Look for an arc= result in an A-R header blob. We know that its data +happens to be a NUL-term string. */ + +static uschar * +arc_ar_cv_status(blob * ar) +{ +const uschar * resinfo = ar->data; +int sep = ';'; +uschar * methodspec, * s; + +while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0))) + if (Ustrncmp(methodspec, US"arc=", 4) == 0) + { + uschar c; + for (s = methodspec += 4; + (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++; + return string_copyn(methodspec, s - methodspec); + } +return NULL; +} + + + +/* Build the AS header and prepend it */ + +static gstring * +arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx, + int instance, const uschar * identity, const uschar * selector, blob * ar, + const uschar * privkey) +{ +gstring * arcset; +arc_set * as; +uschar * status = arc_ar_cv_status(ar); +arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line)); +header_line * h = (header_line *)(al+1); +uschar * s; + +gstring * hdata = NULL; +int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */ +blob sig; + +/* +- Generate AS + - no body coverage + - no h= tag; implicit coverage + - arc status from A-R + - if fail: + - coverage is just the new ARC set + including self (but with an empty b= in self) + - if non-fail: + - all ARC set headers, set-number order, aar then ams then as, + including self (but with an empty b= in self) +*/ + +/* Construct the AS except for the signature */ + +arcset = string_append(NULL, 10, + ARC_HDR_AS, + US" i=", string_sprintf("%d", instance), + US"; cv=", status, + US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */ + US"; s=", selector, /*XXX same as AMS */ + US";\r\n\t b=;"); + +h->slen = arcset->ptr; +h->text = arcset->s; +al->complete = h; +ctx->arcset_chain_last->hdr_as = al; + +/* For any but "fail" chain-verify status, walk the entire chain in order by +instance. For fail, only the new arc-set. Accumulate the elements walked. */ + +for (as = Ustrcmp(status, US"fail") == 0 + ? ctx->arcset_chain_last : ctx->arcset_chain; + as; as = as->next) + { + /* Accumulate AAR then AMS then AS. Relaxed canonicalisation + is required per standard. */ + + h = as->hdr_aar->complete; + hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE)); + h = as->hdr_ams->complete; + hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE)); + h = as->hdr_as->complete; + hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE)); + } + +/* Calculate the signature from the accumulation */ + +if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS")) + return NULL; + +/* Lose the trailing semicolon */ +arcset->ptr--; +arcset = arc_sign_append_sig(arcset, &sig); +DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s); + +/* Finally, append the AMS and AAR to the new AS */ + +return string_catn(arcset, arcset_interim->s, arcset_interim->ptr); +} + + +/**************************************/ + +/* Return pointer to pdkim_bodyhash for given hash method, creating new +method if needed. +*/ + +void * +arc_ams_setup_sign_bodyhash(void) +{ +int canon_head, canon_body; + +DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n"); +pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */ +return pdkim_set_bodyhash(&dkim_sign_ctx, + pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */ + canon_body, + -1); +} + + + +/* A "normal" header line, identified by DKIM processing. These arrive before +the call to arc_sign(), which carries any newly-created DKIM headers - and +those go textually before the normal ones in the message. + +We have to take the feed from DKIM as, in the transport-filter case, the +headers are not in memory at the time of the call to arc_sign(). + +Take a copy of the header and construct a reverse-order list. +Also parse ARC-chain headers and build the chain struct, retaining pointers +into the copies. +*/ + +static const uschar * +arc_header_sign_feed(gstring * g) +{ +uschar * s = string_copyn(g->s, g->ptr); +headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr); +return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE); +} + + + +/* ARC signing. Called from the smtp transport, if the arc_sign option is set. +The dkim_exim_sign() function has already been called, so will have hashed the +message body for us so long as we requested a hash previously. + +Arguments: + signspec Three-element colon-sep list: identity, selector, privkey + Already expanded + sigheaders Any signature headers already generated, eg. by DKIM, or NULL + errstr Error string + +Return value + Set of headers to prepend to the message, including the supplied sigheaders + but not the plainheaders. +*/ + +gstring * +arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr) +{ +const uschar * identity, * selector, * privkey; +int sep = 0; +header_line * headers; +hdr_rlist * rheaders; +blob ar; +int instance; +gstring * g = NULL; +pdkim_bodyhash * b; + +/* Parse the signing specification */ + +identity = string_nextinlist(&signspec, &sep, NULL, 0); +selector = string_nextinlist(&signspec, &sep, NULL, 0); +if ( !*identity | !*selector + || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey) + { + log_write(0, LOG_MAIN|LOG_PANIC, "ARC: bad signing-specification"); + return NULL; + } +if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey))) + return NULL; + +DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity); + +/* +- scan headers for existing ARC chain & A-R (with matching system-identfier) + - paniclog & skip on problems (no A-R) +*/ + +/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it */ + +string_from_gstring(sigheaders); +if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders))) + { + hdr_rlist ** rp; + for (rp = &rheaders; *rp; ) rp = &(*rp)->prev; + *rp = headers_rlist; + headers_rlist = rheaders; + } +else + rheaders = headers_rlist; +/* Finally, build a normal-order headers list */ +/*XXX only needed for hunt-the-AR? */ +{ +header_line * hnext = NULL; +for (; rheaders; hnext = rheaders->h, rheaders = rheaders->prev) + rheaders->h->next = hnext; +headers = hnext; +} + +instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1; + +if (!(arc_sign_find_ar(headers, identity, &ar))) + { + log_write(0, LOG_MAIN|LOG_PANIC, "ARC: no Authentication-Results header for signing"); + return sigheaders ? sigheaders : string_get(0); + } + +/* +- Generate AAR + - copy the A-R; prepend i= & identity +*/ + +g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar); + +/* +- Generate AMS + - Looks fairly like a DKIM sig + - Cover all DKIM sig headers as well as the usuals + - ? oversigning? + - Covers the data + - we must have requested a suitable bodyhash previously +*/ + +b = arc_ams_setup_sign_bodyhash(); +g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector, + &b->bh, headers_rlist, privkey); + +/* +- Generate AS + - no body coverage + - no h= tag; implicit coverage + - arc status from A-R + - if fail: + - coverage is just the new ARC set + including self (but with an empty b= in self) + - if non-fail: + - all ARC set headers, set-number order, aar then ams then as, + including self (but with an empty b= in self) +*/ + +g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey); + +/* Finally, append the dkim headers and return the lot. */ + +g = string_catn(g, sigheaders->s, sigheaders->ptr); +(void) string_from_gstring(g); +gstring_reset_unused(g); +return g; +} + + +/******************************************************************************/ + +/* Check to see if the line is an AMS and if so, set up to validate it. +Called from the DKIM input processing. This must be done now as the message +body data is hashed during input. + +We call the DKIM code to request a body-hash; it has the facility already +and the hash parameters might be common with other requests. +*/ + +static const uschar * +arc_header_vfy_feed(gstring * g) +{ +header_line h; +arc_line al; +pdkim_bodyhash * b; +uschar * errstr; + +if (!dkim_verify_ctx) return US"no dkim context"; + +if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS"; + +DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n"); +/* Parse the AMS header */ + +h.next = NULL; +h.slen = g->size; +h.text = g->s; +memset(&al, 0, sizeof(arc_line)); +if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE))) + { + DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr); + return US"line parsing error"; + } + +/* defaults */ +if (!al.c.data) + { + al.c_body.data = US"simple"; al.c_body.len = 6; + al.c_head = al.c_body; + } + +/* Ask the dkim code to calc a bodyhash with those specs */ + +if (!(b = arc_ams_setup_vfy_bodyhash(&al))) + return US"dkim hash setup fail"; + +/* Discard the reference; search again at verify time, knowing that one +should have been created here. */ + +return NULL; +} + + + +/* A header line has been identified by DKIM processing. + +Arguments: + g Header line + is_vfy TRUE for verify mode or FALSE for signing mode + +Return: + NULL for success, or an error string (probably unused) +*/ + +const uschar * +arc_header_feed(gstring * g, BOOL is_vfy) +{ +return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g); +} + + + +/******************************************************************************/ + +/* Construct an Authenticate-Results header portion, for the ARC module */ + +gstring * +authres_arc(gstring * g) +{ +if (arc_state) + { + arc_line * highest_ams; + int start; + DEBUG(D_acl) start = g->ptr; + + g = string_append(g, 2, US";\n\tarc=", arc_state); + if (arc_received_instance > 0) + { + g = string_append(g, 3, US" (i=", + string_sprintf("%d", arc_received_instance), US") header.s="); + highest_ams = arc_received->hdr_ams; + g = string_catn(g, highest_ams->s.data, highest_ams->s.len); + + g = string_append(g, 2, + US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass)); + + if (sender_host_address) + g = string_append(g, 2, US" smtp.client-ip=", sender_host_address); + } + DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n", + g->ptr - start - 3, g->s + start + 3); + } +else + DEBUG(D_acl) debug_printf("ARC: no authres\n"); +return g; +} + + +# endif /* SUPPORT_SPF */ +#endif /* EXPERIMENTAL_ARC */ +/* vi: aw ai sw=2 + */ diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index 2e6985aea..ce478d558 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -190,6 +190,7 @@ Do not put spaces between # and the 'define'. #define DISABLE_MAL_CMDLINE /* EXPERIMENTAL features */ +#define EXPERIMENTAL_ARC #define EXPERIMENTAL_BRIGHTMAIL #define EXPERIMENTAL_DCC #define EXPERIMENTAL_DSN_INFO diff --git a/src/src/dkim.c b/src/src/dkim.c index 092eb55c6..1767eb620 100644 --- a/src/src/dkim.c +++ b/src/src/dkim.c @@ -26,6 +26,7 @@ builtin_macro_create_var(US"_DKIM_SIGN_HEADERS", US PDKIM_DEFAULT_SIGN_HEADERS); +pdkim_ctx dkim_sign_ctx; int dkim_verify_oldpool; pdkim_ctx *dkim_verify_ctx = NULL; @@ -38,8 +39,8 @@ static const uschar * dkim_collect_error = NULL; /*XXX the caller only uses the first record if we return multiple. */ -static uschar * -dkim_exim_query_dns_txt(char * name) +uschar * +dkim_exim_query_dns_txt(uschar * name) { dns_answer dnsa; dns_scan dnss; @@ -47,7 +48,7 @@ dns_record *rr; gstring * g = NULL; lookup_dnssec_authenticated = NULL; -if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED) +if (dns_lookup(&dnsa, name, T_TXT, NULL) != DNS_SUCCEED) return NULL; /*XXX better error detail? logging? */ /* Search for TXT record */ @@ -75,7 +76,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); /* check if this looks like a DKIM record */ if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0) { - store_reset(g->s + g->ptr + 1); + gstring_reset_unused(g); return string_from_gstring(g); } @@ -563,6 +564,16 @@ switch (what) } +void +dkim_exim_sign_init(void) +{ +int old_pool = store_pool; +store_pool = POOL_MAIN; +pdkim_init_context(&dkim_sign_ctx, FALSE, &dkim_exim_query_dns_txt); +store_pool = old_pool; +} + + /* Generate signatures for the given file. If a prefix is given, prepend it to the file for the calculations. @@ -575,10 +586,9 @@ gstring * dkim_exim_sign(int fd, off_t off, uschar * prefix, struct ob_dkim * dkim, const uschar ** errstr) { -const uschar * dkim_domain; +const uschar * dkim_domain = NULL; int sep = 0; gstring * seen_doms = NULL; -pdkim_ctx ctx; pdkim_signature * sig; gstring * sigbuf; int pdkim_rc; @@ -587,18 +597,21 @@ uschar buf[4096]; int save_errno = 0; int old_pool = store_pool; uschar * errwhen; +const uschar * s; -store_pool = POOL_MAIN; +if (dkim->dot_stuffed) + dkim_sign_ctx.flags |= PDKIM_DOT_TERM; -pdkim_init_context(&ctx, dkim->dot_stuffed, &dkim_exim_query_dns_txt); +store_pool = POOL_MAIN; -if (!(dkim_domain = expand_cstring(dkim->dkim_domain))) +if ((s = dkim->dkim_domain) && !(dkim_domain = expand_cstring(s))) /* expansion error, do not send message. */ { errwhen = US"dkim_domain"; goto expand_bad; } /* Set $dkim_domain expansion variable to each unique domain in list. */ -while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0))) +if (dkim_domain) + while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0))) { const uschar * dkim_sel; int sel_sep = 0; @@ -667,39 +680,10 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0))) ) continue; /* don't sign, but no error */ - if (dkim_private_key_expanded[0] == '/') - { - int privkey_fd, off = 0, len; - - /* Looks like a filename, load the private key. */ - - memset(big_buffer, 0, big_buffer_size); - - if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0) - { - log_write(0, LOG_MAIN | LOG_PANIC, "unable to open " - "private key file for reading: %s", - dkim_private_key_expanded); - goto bad; - } - - do - { - if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0) - { - (void) close(privkey_fd); - log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s", - dkim_private_key_expanded); - goto bad; - } - off += len; - } - while (len > 0); - - (void) close(privkey_fd); - big_buffer[off] = '\0'; - dkim_private_key_expanded = big_buffer; - } + if ( dkim_private_key_expanded[0] == '/' + && !(dkim_private_key_expanded = + expand_file_big_buffer(dkim_private_key_expanded))) + goto bad; if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash))) { errwhen = US"dkim_hash"; goto expand_bad; } @@ -710,7 +694,7 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0))) else if (!*dkim_identity_expanded) dkim_identity_expanded = NULL; - if (!(sig = pdkim_init_sign(&ctx, dkim_signing_domain, + if (!(sig = pdkim_init_sign(&dkim_sign_ctx, dkim_signing_domain, dkim_signing_selector, dkim_private_key_expanded, dkim_hash_expanded, @@ -725,51 +709,61 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0))) pdkim_canon, pdkim_canon, -1, 0, 0); - if (!pdkim_set_bodyhash(&ctx, sig)) + if (!pdkim_set_sig_bodyhash(&dkim_sign_ctx, sig)) goto bad; - if (!ctx.sig) /* link sig to context chain */ - ctx.sig = sig; + if (!dkim_sign_ctx.sig) /* link sig to context chain */ + dkim_sign_ctx.sig = sig; else { - pdkim_signature * n = ctx.sig; + pdkim_signature * n = dkim_sign_ctx.sig; while (n->next) n = n->next; n->next = sig; } } } -if (!ctx.sig) + +/* We may need to carry on with the data-feed even if there are no DKIM sigs to +produce, if some other package (eg. ARC) is signing. */ + +if (!dkim_sign_ctx.sig && !dkim->force_bodyhash) { DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n"); sigbuf = string_get(1); /* return a zero-len string */ - goto CLEANUP; } - -if (prefix && (pdkim_rc = pdkim_feed(&ctx, prefix, Ustrlen(prefix))) != PDKIM_OK) - goto pk_bad; - -if (lseek(fd, off, SEEK_SET) < 0) - sread = -1; else - while ((sread = read(fd, &buf, sizeof(buf))) > 0) - if ((pdkim_rc = pdkim_feed(&ctx, buf, sread)) != PDKIM_OK) - goto pk_bad; - -/* Handle failed read above. */ -if (sread == -1) { - debug_printf("DKIM: Error reading -K file.\n"); - save_errno = errno; - goto bad; - } + if (prefix && (pdkim_rc = pdkim_feed(&dkim_sign_ctx, prefix, Ustrlen(prefix))) != PDKIM_OK) + goto pk_bad; -/* Build string of headers, one per signature */ + if (lseek(fd, off, SEEK_SET) < 0) + sread = -1; + else + while ((sread = read(fd, &buf, sizeof(buf))) > 0) + if ((pdkim_rc = pdkim_feed(&dkim_sign_ctx, buf, sread)) != PDKIM_OK) + goto pk_bad; -if ((pdkim_rc = pdkim_feed_finish(&ctx, &sig, errstr)) != PDKIM_OK) - goto pk_bad; + /* Handle failed read above. */ + if (sread == -1) + { + debug_printf("DKIM: Error reading -K file.\n"); + save_errno = errno; + goto bad; + } + + /* Build string of headers, one per signature */ -for (sigbuf = NULL; sig; sig = sig->next) - sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n"); + if ((pdkim_rc = pdkim_feed_finish(&dkim_sign_ctx, &sig, errstr)) != PDKIM_OK) + goto pk_bad; + + if (!sig) + { + DEBUG(D_transport) debug_printf("DKIM: no signatures to use\n"); + sigbuf = string_get(1); /* return a zero-len string */ + } + else for (sigbuf = NULL; sig; sig = sig->next) + sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n"); + } CLEANUP: (void) string_from_gstring(sigbuf); @@ -797,6 +791,9 @@ gstring * authres_dkim(gstring * g) { pdkim_signature * sig; +int start; + +DEBUG(D_acl) start = g->ptr; for (sig = dkim_signatures; sig; sig = sig->next) { @@ -812,21 +809,21 @@ for (sig = dkim_signatures; sig; sig = sig->next) switch (sig->verify_ext_status) { case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: - g = string_cat(g, US"tmperror (pubkey unavailable)"); break; + g = string_cat(g, US"tmperror (pubkey unavailable)\n\t\t"); break; case PDKIM_VERIFY_INVALID_BUFFER_SIZE: - g = string_cat(g, US"permerror (overlong public key record)"); break; + g = string_cat(g, US"permerror (overlong public key record)\n\t\t"); break; case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: - g = string_cat(g, US"neutral (syntax error in public key record)"); + g = string_cat(g, US"neutral (syntax error in public key record)\n\t\t"); break; case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: - g = string_cat(g, US"neutral (signature tag missing or invalid)"); + g = string_cat(g, US"neutral (signature tag missing or invalid)\n\t\t"); break; case PDKIM_VERIFY_INVALID_DKIM_VERSION: - g = string_cat(g, US"neutral (unsupported DKIM version)"); + g = string_cat(g, US"neutral (unsupported DKIM version)\n\t\t"); break; default: - g = string_cat(g, US"permerror (unspecified problem)"); break; + g = string_cat(g, US"permerror (unspecified problem)\n\t\t"); break; } break; case PDKIM_VERIFY_FAIL: @@ -834,14 +831,14 @@ for (sig = dkim_signatures; sig; sig = sig->next) { case PDKIM_VERIFY_FAIL_BODY: g = string_cat(g, - US"fail (body hash mismatch; body probably modified in transit)"); + US"fail (body hash mismatch; body probably modified in transit)\n\t\t"); break; case PDKIM_VERIFY_FAIL_MESSAGE: g = string_cat(g, - US"fail (signature did not verify; headers probably modified in transit)"); + US"fail (signature did not verify; headers probably modified in transit)\n\t\t"); break; default: - g = string_cat(g, US"fail (unspecified reason)"); + g = string_cat(g, US"fail (unspecified reason)\n\t\t"); break; } break; @@ -853,6 +850,12 @@ for (sig = dkim_signatures; sig; sig = sig->next) if (sig->selector) g = string_append(g, 2, US" header.s=", sig->selector); g = string_append(g, 2, US" header.a=", dkim_sig_to_a_tag(sig)); } + +DEBUG(D_acl) + if (g->ptr == start) + debug_printf("DKIM: no authres\n"); + else + debug_printf("DKIM: authres '%.*s'\n", g->ptr - start - 3, g->s + start + 3); return g; } diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c index 866df263f..28d567b03 100644 --- a/src/src/dkim_transport.c +++ b/src/src/dkim_transport.c @@ -129,6 +129,7 @@ uschar * hdrs; gstring * dkim_signature; int hsize; const uschar * errstr; +uschar * verrstr; BOOL rc; DEBUG(D_transport) debug_printf("dkim signing direct-mode\n"); @@ -160,6 +161,16 @@ if (!(dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET, return FALSE; } +#ifdef EXPERIMENTAL_ARC +if (dkim->arc_signspec) /* Prepend ARC headers */ + if (!(dkim_signature = + arc_sign(dkim->arc_signspec, dkim_signature, &verrstr))) + { + *err = verrstr; + return FALSE; + } +#endif + /* Write the signature and headers into the deliver-out-buffer. This should mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands (transport_write_message() sizes the BDAT for the buffered amount) - for short @@ -268,6 +279,15 @@ if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr))) else dlen = dkim_signature->ptr; +#ifdef EXPERIMENTAL_ARC +if (dkim->arc_signspec) /* Prepend ARC headers */ + { + if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err))) + goto CLEANUP; + dlen = dkim_signature->ptr; + } +#endif + #ifndef OS_SENDFILE if (options & topt_use_bdat) #endif @@ -351,7 +371,8 @@ dkim_transport_write_message(transport_ctx * tctx, { /* If we can't sign, just call the original function. */ -if (!(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)) +if ( !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector) + && !dkim->force_bodyhash) return transport_write_message(tctx, 0); /* If there is no filter command set up, construct the message and calculate diff --git a/src/src/exim.c b/src/src/exim.c index 9fceaf524..ad7635d98 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -875,6 +875,9 @@ fprintf(f, "Support for:"); #ifdef EXPERIMENTAL_SRS fprintf(f, " Experimental_SRS"); #endif +#ifdef EXPERIMENTAL_ARC + fprintf(f, " Experimental_ARC"); +#endif #ifdef EXPERIMENTAL_BRIGHTMAIL fprintf(f, " Experimental_Brightmail"); #endif diff --git a/src/src/expand.c b/src/src/expand.c index d49f943b0..009049db4 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -4144,6 +4144,9 @@ while (*s != 0) #endif #ifndef DISABLE_DKIM yield = authres_dkim(yield); +#endif +#ifdef EXPERIMENTAL_ARC + yield = authres_arc(yield); #endif continue; } @@ -7530,10 +7533,9 @@ terminating brace. */ if (ket_ends && *s == 0) { - expand_string_message = malformed_header? - US"missing } at end of string - could be header name not terminated by colon" - : - US"missing } at end of string"; + expand_string_message = malformed_header + ? US"missing } at end of string - could be header name not terminated by colon" + : US"missing } at end of string"; goto EXPAND_FAILED; } @@ -7866,6 +7868,45 @@ return ( ( Ustrstr(s, "failed to expand") != NULL } +/* Read given named file into big_buffer. Use for keying material etc. +The content will have an ascii NUL appended. + +Arguments: + filename as it says + +Return: pointer to buffer, or NULL on error. +*/ + +uschar * +expand_file_big_buffer(const uschar * filename) +{ +int fd, off = 0, len; + +if ((fd = open(CS filename, O_RDONLY)) < 0) + { + log_write(0, LOG_MAIN | LOG_PANIC, "unable to open file for reading: %s", + filename); + return NULL; + } + +do + { + if ((len = read(fd, big_buffer + off, big_buffer_size - 2 - off)) < 0) + { + (void) close(fd); + log_write(0, LOG_MAIN|LOG_PANIC, "unable to read file: %s", filename); + return NULL; + } + off += len; + } +while (len > 0); + +(void) close(fd); +big_buffer[off] = '\0'; +return big_buffer; +} + + /************************************************* * Error-checking for testsuite * diff --git a/src/src/functions.h b/src/src/functions.h index d537ac331..1f201fc69 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -88,6 +88,14 @@ extern int acl_eval(int, uschar *, uschar **, uschar **); extern tree_node *acl_var_create(uschar *); extern void acl_var_write(uschar *, uschar *, void *); + +#ifdef EXPERIMENTAL_ARC +extern void *arc_ams_setup_sign_bodyhash(void); +extern const uschar *arc_header_feed(gstring *, BOOL); +extern gstring *arc_sign(const uschar *, gstring *, uschar **); +extern const uschar *acl_verify_arc(void); +#endif + extern void assert_no_variables(void *, int, const char *, int); extern int auth_call_pam(const uschar *, uschar **); extern int auth_call_pwcheck(uschar *, uschar **); @@ -111,6 +119,9 @@ extern gstring *authres_spf(gstring *); #ifndef DISABLE_DKIM extern gstring *authres_dkim(gstring *); #endif +#ifdef EXPERIMENTAL_ARC +extern gstring *authres_arc(gstring *); +#endif extern uschar *b64encode(uschar *, int); extern int b64decode(const uschar *, uschar **); @@ -166,6 +177,9 @@ extern void delivery_re_exec(int); extern BOOL directory_make(const uschar *, const uschar *, int, BOOL); #ifndef DISABLE_DKIM +extern uschar *dkim_exim_query_dns_txt(uschar *); +extern void dkim_exim_sign_init(void); + extern BOOL dkim_transport_write_message(transport_ctx *, struct ob_dkim *, const uschar ** errstr); #endif @@ -198,6 +212,7 @@ extern int exp_bool(address_item *addr, uschar *mtype, uschar *mname, unsigned dgb_opt, uschar *oname, BOOL bvalue, uschar *svalue, BOOL *rvalue); extern BOOL expand_check_condition(uschar *, uschar *, uschar *); +extern uschar *expand_file_big_buffer(const uschar *); extern uschar *expand_string(uschar *); /* public, cannot make const */ extern const uschar *expand_cstring(const uschar *); /* ... so use this one */ extern uschar *expand_hide_passwords(uschar * ); @@ -214,6 +229,8 @@ extern BOOL filter_system_interpret(address_item **, uschar **); extern uschar * fn_hdrs_added(void); +extern void gstring_reset_unused(gstring *); + extern void header_add(int, const char *, ...); extern int header_checkname(header_line *, BOOL); extern BOOL header_match(uschar *, BOOL, BOOL, string_item *, int, ...); diff --git a/src/src/globals.c b/src/src/globals.c index 5f1c87fc0..1db5ece32 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -420,7 +420,15 @@ BOOL allow_domain_literals = FALSE; BOOL allow_mx_to_ip = FALSE; BOOL allow_unqualified_recipient = TRUE; /* For local messages */ BOOL allow_unqualified_sender = TRUE; /* Reset for SMTP */ -BOOL allow_utf8_domains = FALSE; +BOOL allow_utf8_domains = FALSE; + +#ifdef EXPERIMENTAL_ARC +struct arc_set *arc_received = NULL; +int arc_received_instance = 0; +int arc_oldest_pass = 0; +const uschar *arc_state = NULL; +#endif + uschar *authenticated_fail_id = NULL; uschar *authenticated_id = NULL; uschar *authenticated_sender = NULL; @@ -785,7 +793,7 @@ header_name header_names[] = { { US"to", 2, TRUE, htype_to } }; -int header_names_size = sizeof(header_names)/sizeof(header_name); +int header_names_size = nelem(header_names); BOOL header_rewritten = FALSE; uschar *helo_accept_junk_hosts = NULL; diff --git a/src/src/globals.h b/src/src/globals.h index 801a00fb3..03af195f9 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -216,6 +216,12 @@ extern BOOL allow_domain_literals; /* As it says */ extern BOOL allow_mx_to_ip; /* Allow MX records to -> ip address */ extern BOOL allow_unqualified_recipient; /* As it says */ extern BOOL allow_unqualified_sender; /* Ditto */ +#ifdef EXPERIMENTAL_ARC +struct arc_set *arc_received; /* highest ARC instance evaluation struct */ +extern int arc_received_instance; /* highest ARC instance number in headers */ +extern int arc_oldest_pass; /* lowest passing instance number in headers */ +extern const uschar *arc_state; /* verification state */ +#endif extern BOOL allow_utf8_domains; /* For experimenting */ extern uschar *authenticated_fail_id; /* ID that failed authentication */ extern uschar *authenticated_id; /* ID that was authenticated */ diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c index 601ceef66..32c05a807 100644 --- a/src/src/macro_predef.c +++ b/src/src/macro_predef.c @@ -180,6 +180,9 @@ due to conflicts with other common macros. */ #ifdef EXPERIMENTAL_SRS builtin_macro_create(US"_HAVE_SRS"); #endif +#ifdef EXPERIMENTAL_ARC + builtin_macro_create(US"_HAVE_ARC"); +#endif #ifdef EXPERIMENTAL_BRIGHTMAIL builtin_macro_create(US"_HAVE_BRIGHTMAIL"); #endif diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c index c75b0ae78..d7e6d5bce 100644 --- a/src/src/pdkim/pdkim.c +++ b/src/src/pdkim/pdkim.c @@ -71,11 +71,7 @@ const uschar * pdkim_canons[] = { NULL }; -typedef struct { - const uschar * dkim_hashname; - hashmethod exim_hashmethod; -} pdkim_hashtype; -static const pdkim_hashtype pdkim_hashes[] = { +const pdkim_hashtype pdkim_hashes[] = { { US"sha1", HASH_SHA1 }, { US"sha256", HASH_SHA2_256 }, { US"sha512", HASH_SHA2_512 } @@ -125,6 +121,34 @@ return string_sprintf("%s-%s", } +int +pdkim_hashname_to_hashtype(const uschar * s, unsigned len) +{ +int i; +if (!len) len = Ustrlen(s); +for (i = 0; i < nelem(pdkim_hashes); i++) + if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0) + return i; +return -1; +} + +void +pdkim_cstring_to_canons(const uschar * s, unsigned len, + int * canon_head, int * canon_body) +{ +int i; +if (!len) len = Ustrlen(s); +for (i = 0; pdkim_combined_canons[i].str; i++) + if ( Ustrncmp(s, pdkim_combined_canons[i].str, len) == 0 + && len == Ustrlen(pdkim_combined_canons[i].str)) + { + *canon_head = pdkim_combined_canons[i].canon_headers; + *canon_body = pdkim_combined_canons[i].canon_body; + break; + } +} + + const char * pdkim_verify_status_str(int status) @@ -177,7 +201,7 @@ switch(status) /* -------------------------------------------------------------------------- */ /* Print debugging functions */ -static void +void pdkim_quoteprint(const uschar *data, int len) { int i; @@ -203,7 +227,7 @@ for (i = 0; i < len; i++) debug_printf("\n"); } -static void +void pdkim_hexprint(const uschar *data, int len) { int i; @@ -302,16 +326,16 @@ return PDKIM_FAIL; /* -------------------------------------------------------------------------- */ /* Performs "relaxed" canonicalization of a header. */ -static uschar * -pdkim_relax_header(const uschar * header, BOOL append_crlf) +uschar * +pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf) { BOOL past_field_name = FALSE; BOOL seen_wsp = FALSE; const uschar * p; -uschar * relaxed = store_get(Ustrlen(header)+3); +uschar * relaxed = store_get(len+3); uschar * q = relaxed; -for (p = header; *p; p++) +for (p = header; p - header < len; p++) { uschar c = *p; @@ -347,6 +371,13 @@ return relaxed; } +uschar * +pdkim_relax_header(const uschar * header, BOOL append_crlf) +{ +return pdkim_relax_header_n(header, Ustrlen(header), append_crlf); +} + + /* -------------------------------------------------------------------------- */ #define PDKIM_QP_ERROR_DECODE -1 @@ -407,16 +438,15 @@ return n; /* -------------------------------------------------------------------------- */ -static void +void pdkim_decode_base64(const uschar * str, blob * b) { -int dlen; -dlen = b64decode(str, &b->data); +int dlen = b64decode(str, &b->data); if (dlen < 0) b->data = NULL; b->len = dlen; } -static uschar * +uschar * pdkim_encode_base64(blob * b) { return b64encode(b->data, b->len); @@ -541,24 +571,13 @@ for (p = raw_hdr; ; p++) "DKIM: ignoring signature due to nonhandled keytype in a=%s", cur_val->s); - for (++s, i = 0; i < nelem(pdkim_hashes); i++) - if (Ustrcmp(s, pdkim_hashes[i].dkim_hashname) == 0) - { sig->hashtype = i; break; } - if (sig->hashtype < 0) - log_write(0, LOG_MAIN, - "DKIM: ignoring signature due to nonhandled hashtype in a=%s", - cur_val->s); + sig->hashtype = pdkim_hashname_to_hashtype(++s, 0); break; } case 'c': /* canonicalization */ - for (i = 0; pdkim_combined_canons[i].str; i++) - if (Ustrcmp(cur_val->s, pdkim_combined_canons[i].str) == 0) - { - sig->canon_headers = pdkim_combined_canons[i].canon_headers; - sig->canon_body = pdkim_combined_canons[i].canon_body; - break; - } + pdkim_cstring_to_canons(cur_val->s, 0, + &sig->canon_headers, &sig->canon_body); break; case 'q': /* Query method (for pubkey)*/ for (i = 0; pdkim_querymethods[i]; i++) @@ -626,7 +645,7 @@ DEBUG(D_acl) "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); } -if (!pdkim_set_bodyhash(ctx, sig)) +if (!pdkim_set_sig_bodyhash(ctx, sig)) return NULL; return sig; @@ -635,8 +654,8 @@ return sig; /* -------------------------------------------------------------------------- */ -static pdkim_pubkey * -pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record) +pdkim_pubkey * +pdkim_parse_pubkey_record(const uschar *raw_record) { const uschar * ele; int sep = ';'; @@ -772,7 +791,11 @@ pdkim_bodyhash * b; pdkim_signature * sig; for (b = ctx->bodyhash; b; b = b->next) /* Finish hashes */ + { + DEBUG(D_acl) debug_printf("PDKIM: finish bodyhash %d/%d/%d len %d\n", + b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes); exim_sha_finish(&b->body_hash_ctx, &b->bh); + } /* Traverse all signatures */ for (sig = ctx->sig; sig; sig = sig->next) @@ -940,6 +963,11 @@ if ( (ctx->cur_header->ptr > 1) && --ctx->cur_header->ptr; (void) string_from_gstring(ctx->cur_header); +#ifdef EXPERIMENTAL_ARC +/* Feed the header line to ARC processing */ +(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN)); +#endif + if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL; /* SIGNING -------------------------------------------------------------- */ @@ -1319,7 +1347,7 @@ pdkim_pubkey * p; dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain); -if ( !(dns_txt_reply = ctx->dns_txt_callback(CS dns_txt_name)) +if ( !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name)) || dns_txt_reply[0] == '\0' ) { @@ -1338,7 +1366,7 @@ DEBUG(D_acl) pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply)); } -if ( !(p = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply)) +if ( !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply)) || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0) ) { @@ -1406,15 +1434,18 @@ else DEBUG(D_acl) debug_printf( "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); +/* Build (and/or evaluate) body hash. Do this even if no DKIM sigs, in case we +have a hash to do for ARC. */ + +pdkim_finish_bodyhash(ctx); + if (!ctx->sig) { DEBUG(D_acl) debug_printf("PDKIM: no signatures\n"); + *return_signatures = NULL; return PDKIM_OK; } -/* Build (and/or evaluate) body hash */ -pdkim_finish_bodyhash(ctx); - for (sig = ctx->sig; sig; sig = sig->next) { hctx hhash_ctx; @@ -1423,6 +1454,14 @@ for (sig = ctx->sig; sig; sig = sig->next) gstring * hdata = NULL; es_ctx sctx; + if ( !(ctx->flags & PDKIM_MODE_SIGN) + && sig->verify_status == PDKIM_VERIFY_FAIL) + { + DEBUG(D_acl) + debug_printf("PDKIM: [%s] abandoning this signature\n", sig->domain); + continue; + } + /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA suging only, as it happens) and for either GnuTLS and OpenSSL when we are signing with EC (specifically, Ed25519). The former is because the GCrypt @@ -1480,7 +1519,7 @@ for (sig = ctx->sig; sig; sig = sig->next) the signature header */ /*XXX extend for non-RSA algos */ - if ((*err = exim_dkim_signing_init(US sig->privkey, &sctx))) + if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx))) { log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err); return PDKIM_ERR_RSA_PRIVKEY; @@ -1774,7 +1813,7 @@ return ctx->flags & PDKIM_MODE_SIGN || verify_pass /* -------------------------------------------------------------------------- */ DLLEXPORT pdkim_ctx * -pdkim_init_verify(uschar * (*dns_txt_callback)(char *), BOOL dot_stuffing) +pdkim_init_verify(uschar * (*dns_txt_callback)(uschar *), BOOL dot_stuffing) { pdkim_ctx * ctx; @@ -1868,29 +1907,35 @@ return; /* Set up a blob for calculating the bodyhash according to the -needs of this signature. Use an existing one if possible, or -create a new one. +given needs. Use an existing one if possible, or create a new one. -Return: hashblob pointer, or NULL on error (only used as a boolean). +Return: hashblob pointer, or NULL on error */ pdkim_bodyhash * -pdkim_set_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig) +pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method, + long bodylength) { pdkim_bodyhash * b; for (b = ctx->bodyhash; b; b = b->next) - if ( sig->hashtype == b->hashtype - && sig->canon_body == b->canon_method - && sig->bodylength == b->bodylength) - goto old; + if ( hashtype == b->hashtype + && canon_method == b->canon_method + && bodylength == b->bodylength) + { + DEBUG(D_receive) debug_printf("PDKIM: using existing bodyhash %d/%d/%d\n", + hashtype, canon_method, bodylength); + return b; + } +DEBUG(D_receive) debug_printf("PDKIM: new bodyhash %d/%d/%d\n", + hashtype, canon_method, bodylength); b = store_get(sizeof(pdkim_bodyhash)); b->next = ctx->bodyhash; -b->hashtype = sig->hashtype; -b->canon_method = sig->canon_body; -b->bodylength = sig->bodylength; +b->hashtype = hashtype; +b->canon_method = canon_method; +b->bodylength = bodylength; if (!exim_sha_init(&b->body_hash_ctx, /*XXX hash method: extend for sha512 */ - pdkim_hashes[sig->hashtype].exim_hashmethod)) + pdkim_hashes[hashtype].exim_hashmethod)) { DEBUG(D_acl) debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n"); @@ -1899,8 +1944,21 @@ if (!exim_sha_init(&b->body_hash_ctx, /*XXX hash method: extend for sha512 */ b->signed_body_bytes = 0; b->num_buffered_blanklines = 0; ctx->bodyhash = b; +return b; +} + -old: +/* Set up a blob for calculating the bodyhash according to the +needs of this signature. Use an existing one if possible, or +create a new one. + +Return: hashblob pointer, or NULL on error (only used as a boolean). +*/ +pdkim_bodyhash * +pdkim_set_sig_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig) +{ +pdkim_bodyhash * b = pdkim_set_bodyhash(ctx, + sig->hashtype, sig->canon_body, sig->bodylength); sig->calc_body_hash = b; return b; } @@ -1911,7 +1969,7 @@ return b; void pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed, - uschar * (*dns_txt_callback)(char *)) + uschar * (*dns_txt_callback)(uschar *)) { memset(ctx, 0, sizeof(pdkim_ctx)); ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN; diff --git a/src/src/pdkim/pdkim.h b/src/src/pdkim/pdkim.h index 775581be7..59ac03888 100644 --- a/src/src/pdkim/pdkim.h +++ b/src/src/pdkim/pdkim.h @@ -279,7 +279,7 @@ typedef struct pdkim_ctx { pdkim_bodyhash *bodyhash; /* Callback for dns/txt query method (verification only) */ - uschar * (*dns_txt_callback)(char *); + uschar * (*dns_txt_callback)(uschar *); /* Coder's little helpers */ gstring *cur_header; @@ -290,6 +290,17 @@ typedef struct pdkim_ctx { } pdkim_ctx; +/******************************************************************************/ + +typedef struct { + const uschar * dkim_hashname; + hashmethod exim_hashmethod; +} pdkim_hashtype; +extern const pdkim_hashtype pdkim_hashes[]; + +/******************************************************************************/ + + /* -------------------------------------------------------------------------- */ /* API functions. Please see the sample code in sample/test_sign.c and sample/test_verify.c for documentation. @@ -301,7 +312,7 @@ extern "C" { void pdkim_init (void); -void pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(char *)); +void pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(uschar *)); DLLEXPORT pdkim_signature *pdkim_init_sign (pdkim_ctx *, @@ -309,7 +320,7 @@ pdkim_signature *pdkim_init_sign (pdkim_ctx *, const uschar **); DLLEXPORT -pdkim_ctx *pdkim_init_verify (uschar * (*)(char *), BOOL); +pdkim_ctx *pdkim_init_verify (uschar * (*)(uschar *), BOOL); DLLEXPORT void pdkim_set_optional (pdkim_signature *, char *, char *,int, int, @@ -317,7 +328,10 @@ void pdkim_set_optional (pdkim_signature *, char *, char *,int, int, unsigned long, unsigned long); -pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, pdkim_signature *); +int pdkim_hashname_to_hashtype(const uschar *, unsigned); +void pdkim_cstring_to_canons(const uschar *, unsigned, int *, int *); +pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, int, int, long); +pdkim_bodyhash *pdkim_set_sig_bodyhash(pdkim_ctx *, pdkim_signature *); DLLEXPORT int pdkim_feed (pdkim_ctx *, uschar *, int); @@ -330,7 +344,14 @@ void pdkim_free_ctx (pdkim_ctx *); const uschar * pdkim_errstr(int); -uschar * dkim_sig_to_a_tag(const pdkim_signature * sig); +extern uschar * pdkim_encode_base64(blob *); +extern void pdkim_decode_base64(const uschar *, blob *); +extern void pdkim_hexprint(const uschar *, int); +extern void pdkim_quoteprint(const uschar *, int); +extern pdkim_pubkey * pdkim_parse_pubkey_record(const uschar *); +extern uschar * pdkim_relax_header_n(const uschar *, int, BOOL); +extern uschar * pdkim_relax_header(const uschar *, BOOL); +extern uschar * dkim_sig_to_a_tag(const pdkim_signature *); #ifdef __cplusplus } diff --git a/src/src/pdkim/signing.c b/src/src/pdkim/signing.c index c1b9aed2a..b182c9a20 100644 --- a/src/src/pdkim/signing.c +++ b/src/src/pdkim/signing.c @@ -86,7 +86,7 @@ return string_cat(g, s); Return: NULL for success, or an error string */ const uschar * -exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) +exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx) { gnutls_datum_t k = { .data = privkey_pem, .size = Ustrlen(privkey_pem) }; gnutls_x509_privkey_t x509_key; @@ -345,7 +345,7 @@ Only handles RSA keys. Return: NULL for success, or an error string */ const uschar * -exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) +exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx) { uschar * s1, * s2; blob der; @@ -694,7 +694,7 @@ ERR_load_crypto_strings(); } -/* accumulate data (was gnutls-onl but now needed for OpenSSL non-EC too +/* accumulate data (was gnutls-only but now needed for OpenSSL non-EC too because now using hash-and-sign interface) */ gstring * exim_dkim_data_append(gstring * g, uschar * s) @@ -707,7 +707,7 @@ return string_cat(g, s); Return: NULL for success, or an error string */ const uschar * -exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) +exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx) { BIO * bp = BIO_new_mem_buf(privkey_pem, -1); diff --git a/src/src/pdkim/signing.h b/src/src/pdkim/signing.h index fa12ebb18..96a0720fd 100644 --- a/src/src/pdkim/signing.h +++ b/src/src/pdkim/signing.h @@ -88,7 +88,7 @@ typedef struct { extern void exim_dkim_init(void); extern gstring * exim_dkim_data_append(gstring *, uschar *); -extern const uschar * exim_dkim_signing_init(uschar *, es_ctx *); +extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *); extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *); extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *); extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *); diff --git a/src/src/receive.c b/src/src/receive.c index d14409c58..b502a381c 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -3960,6 +3960,10 @@ if (LOGGING(8bitmime)) #ifndef DISABLE_DKIM if (LOGGING(dkim) && dkim_verify_overall) g = string_append(g, 2, US" DKIM=", dkim_verify_overall); +# ifdef EXPERIMENTAL_ARC +if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0) + g = string_catn(g, US" ARC", 4); +# endif #endif if (LOGGING(receive_time)) diff --git a/src/src/string.c b/src/src/string.c index 50442bced..29a87c572 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -914,7 +914,7 @@ sep_is_special = iscntrl(sep); /* Handle the case when a buffer is provided. */ -if (buffer != NULL) +if (buffer) { int p = 0; for (; *s != 0; s++) @@ -960,6 +960,7 @@ else } while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; buffer = string_from_gstring(g); + gstring_reset_unused(g); } /* Update the current pointer and return the new string */ @@ -1073,6 +1074,12 @@ g->s[g->ptr] = '\0'; return g->s; } +void +gstring_reset_unused(gstring * g) +{ +store_reset(g->s + (g->size = g->ptr + 1)); +} + /************************************************* * Add chars to string * *************************************************/ diff --git a/src/src/structs.h b/src/src/structs.h index 29dee2dbe..98c95010d 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -879,6 +879,10 @@ struct ob_dkim { uschar *dkim_strict; uschar *dkim_hash; BOOL dot_stuffed; + BOOL force_bodyhash; +#ifdef EXPERIMENTAL_ARC + uschar *arc_signspec; +#endif }; /* End of structs.h */ diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index d3af04cc8..2d81f3bff 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -24,6 +24,10 @@ optionlist smtp_transport_options[] = { (void *)offsetof(smtp_transport_options_block, address_retry_include_sender) }, { "allow_localhost", opt_bool, (void *)offsetof(smtp_transport_options_block, allow_localhost) }, +#ifdef EXPERIMENTAL_ARC + { "arc_sign", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, arc_sign) }, +#endif { "authenticated_sender", opt_stringptr, (void *)offsetof(smtp_transport_options_block, authenticated_sender) }, { "authenticated_sender_force", opt_bool, @@ -209,7 +213,6 @@ smtp_transport_options_block smtp_transport_option_defaults = { .fallback_hosts = NULL, .hostlist = NULL, .fallback_hostlist = NULL, - .authenticated_sender = NULL, .helo_data = US"$primary_hostname", .interface = NULL, .port = NULL, @@ -287,7 +290,15 @@ smtp_transport_options_block smtp_transport_option_defaults = { .dkim_sign_headers = NULL, .dkim_strict = NULL, .dkim_hash = US"sha256", - .dot_stuffed = FALSE}, + .dot_stuffed = FALSE, + .force_bodyhash = FALSE, +# ifdef EXPERIMENTAL_ARC + .arc_signspec = NULL, +# endif + }, +# ifdef EXPERIMENTAL_ARC + .arc_sign = NULL, +# endif #endif }; @@ -2965,6 +2976,24 @@ else transport_count = 0; #ifndef DISABLE_DKIM + dkim_exim_sign_init(); +# ifdef EXPERIMENTAL_ARC + { + uschar * s = sx.ob->arc_sign; + if (s) + { + if (!(sx.ob->dkim.arc_signspec = expand_string(s))) + { + message = US"failed to expand arc_sign"; + sx.ok = FALSE; + goto SEND_FAILED; + } + /* Ask dkim code to hash the body for ARC */ + (void) arc_ams_setup_sign_bodyhash(); + sx.ob->dkim.force_bodyhash = TRUE; + } + } +# endif sx.ok = dkim_transport_write_message(&tctx, &sx.ob->dkim, CUSS &message); #else sx.ok = transport_write_message(&tctx, 0); diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index 14c0c7556..749c6f778 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -90,6 +90,9 @@ typedef struct { #ifndef DISABLE_DKIM struct ob_dkim dkim; #endif +#ifdef EXPERIMENTAL_ARC + uschar *arc_sign; +#endif } smtp_transport_options_block; /* smtp connect context */ diff --git a/test/aux-fixed/4560.mlistfooter b/test/aux-fixed/4560.mlistfooter new file mode 100644 index 000000000..aa1ec54c8 --- /dev/null +++ b/test/aux-fixed/4560.mlistfooter @@ -0,0 +1,4 @@ + +----- +This is a generic mailinglist footer +---- diff --git a/test/confs/4560 b/test/confs/4560 new file mode 100644 index 000000000..1012a3897 --- /dev/null +++ b/test/confs/4560 @@ -0,0 +1,84 @@ +# Exim test configuration 4560 + +SERVER= + +.include DIR/aux-var/std_conf_prefix + +primary_hostname = test.ex + +# ----- Main settings ----- + +acl_smtp_rcpt = accept +acl_smtp_data = check_data + +log_selector = +received_recipients +dkim_verbose +queue_only + +# ----- ACL ----- +begin acl + +check_data: + warn !verify = arc +.ifdef OPTION + accept +.else + accept add_header = :at_start:${authresults {$primary_hostname}} +.endif + +# ----- Routers ----- + +begin routers + +d1: + driver = accept + local_parts = ^a + transport = tfile + +r2: + driver = redirect + local_parts = ^m + data = ${substr_1:$local_part}@$domain + redirect_router = mlist + +redir: + driver = redirect + data = ${substr_1:$local_part}@$domain + redirect_router = fwd + +fwd: + driver = accept + transport = tsmtp + +mlist: + driver = accept + transport = tmlist + +# ----- Transports ----- + +begin transports + +tfile: + driver = appendfile + file = DIR/test-mail/$local_part + user = CALLER + +tsmtp: + driver = smtp + hosts = HOSTIPV4 + port = PORT_D + allow_localhost +.ifndef OPTION + arc_sign = $primary_hostname : sel : DIR/aux-fixed/dkim/dkim.private +.endif + +tmlist: + driver = smtp + hosts = HOSTIPV4 + port = PORT_D + allow_localhost + transport_filter = /bin/cat - DIR/aux-fixed/TESTNUM.mlistfooter +.ifndef OPTION + arc_sign = $primary_hostname : sel : DIR/aux-fixed/dkim/dkim.private +.endif + +# End diff --git a/test/log/4501 b/test/log/4501 index b4f8d3a74..04edb3284 100644 --- a/test/log/4501 +++ b/test/log/4501 @@ -7,5 +7,5 @@ 1999-03-02 09:44:33 10HmaX-0005vi-00 <= pass@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss DKIM=test.ex id=qwerty1234@disco-zombie.net 1999-03-02 09:44:33 10HmaY-0005vi-00 signer: test.ex bits: 1024 1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [verification failed - body hash mismatch (body probably modified in transit)] -1999-03-02 09:44:33 10HmaY-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit) header.d=test.ex header.s=sel header.a=rsa-sha1 +1999-03-02 09:44:33 10HmaY-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n header.d=test.ex header.s=sel header.a=rsa-sha1 1999-03-02 09:44:33 10HmaY-0005vi-00 <= fail@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net diff --git a/test/log/4502 b/test/log/4502 index b5dcd81c8..50d38e012 100644 --- a/test/log/4502 +++ b/test/log/4502 @@ -16,5 +16,5 @@ 1999-03-02 09:44:33 10HmbA-0005vi-00 PDKIM: d=test.ex s=sel_bad [failed key import] 1999-03-02 09:44:33 10HmbA-0005vi-00 signer: test.ex bits: 1024 1999-03-02 09:44:33 10HmbA-0005vi-00 DKIM: d=test.ex s=sel_bad c=relaxed/relaxed a=rsa-sha1 b=1024 [invalid - syntax error in public key record] -1999-03-02 09:44:33 10HmbA-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (syntax error in public key record) header.d=test.ex header.s=sel_bad header.a=rsa-sha1 +1999-03-02 09:44:33 10HmbA-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (syntax error in public key record)\n header.d=test.ex header.s=sel_bad header.a=rsa-sha1 1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=564CFC9B.1040905@yahoo.com diff --git a/test/log/4503 b/test/log/4503 index 3a502a1fe..c91c79650 100644 --- a/test/log/4503 +++ b/test/log/4503 @@ -1,8 +1,7 @@ ******** SERVER ******** 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: validation error: Public key signature verification has failed. 1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 1024 -1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha512 b=1024 [verification failed - signature did not verify (headers probably modified in transit)] -1999-03-02 09:44:33 10HmaX-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (signature did not verify; headers probably modified in transit) header.d=test.ex header.s=sel header.a=rsa-sha512 +1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha512 b=1024 [verification failed - body hash mismatch (body probably modified in transit)] +1999-03-02 09:44:33 10HmaX-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n header.d=test.ex header.s=sel header.a=rsa-sha512 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net diff --git a/test/log/4504 b/test/log/4504 index 43389c8a2..5af68d0dd 100644 --- a/test/log/4504 +++ b/test/log/4504 @@ -1,8 +1,7 @@ ******** SERVER ******** 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: validation error: Public key signature verification has failed. 1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 1024 -1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel2 c=simple/simple a=rsa-sha512 b=1024 [verification failed - signature did not verify (headers probably modified in transit)] -1999-03-02 09:44:33 10HmaX-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (signature did not verify; headers probably modified in transit) header.d=test.ex header.s=sel2 header.a=rsa-sha512 +1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel2 c=simple/simple a=rsa-sha512 b=1024 [verification failed - body hash mismatch (body probably modified in transit)] +1999-03-02 09:44:33 10HmaX-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n header.d=test.ex header.s=sel2 header.a=rsa-sha512 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net diff --git a/test/log/4506 b/test/log/4506 index 55bad6163..4e57f3e84 100644 --- a/test/log/4506 +++ b/test/log/4506 @@ -3,15 +3,15 @@ 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 1999-03-02 09:44:33 10HmaY-0005vi-00 signer: test.ex bits: 0 1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=0 [invalid - signature tag missing or invalid] -1999-03-02 09:44:33 10HmaY-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid) header.d=test.ex header.s=sel header.a=rsa-sha1 +1999-03-02 09:44:33 10HmaY-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid)\n header.d=test.ex header.s=sel header.a=rsa-sha1 1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net 1999-03-02 09:44:33 10HmaZ-0005vi-00 signer: test.ex bits: 1024 -1999-03-02 09:44:33 10HmaZ-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [invalid - signature tag missing or invalid] -1999-03-02 09:44:33 10HmaZ-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid) header.d=test.ex header.s=sel header.a=rsa-sha1 +1999-03-02 09:44:33 10HmaZ-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [verification failed - body hash mismatch (body probably modified in transit)] +1999-03-02 09:44:33 10HmaZ-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n header.d=test.ex header.s=sel header.a=rsa-sha1 1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net 1999-03-02 09:44:33 10HmbA-0005vi-00 signer: test.ex bits: 1024 1999-03-02 09:44:33 10HmbA-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [verification failed - body hash mismatch (body probably modified in transit)] -1999-03-02 09:44:33 10HmbA-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit) header.d=test.ex header.s=sel header.a=rsa-sha1 +1999-03-02 09:44:33 10HmbA-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (body hash mismatch; body probably modified in transit)\n header.d=test.ex header.s=sel header.a=rsa-sha1 1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net 1999-03-02 09:44:33 10HmbB-0005vi-00 DKIM: validation error: LONG_LINE 1999-03-02 09:44:33 10HmbB-0005vi-00 DKIM: Error during validation, disabling signature verification: LONG_LINE @@ -19,13 +19,13 @@ 1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net 1999-03-02 09:44:33 10HmbC-0005vi-00 signer: test.ex bits: 512 1999-03-02 09:44:33 10HmbC-0005vi-00 DKIM: d=test.ex s=ses_sha256 c=simple/simple a=rsa-sha1 b=512 [verification failed - unspecified reason] -1999-03-02 09:44:33 10HmbC-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (unspecified reason) header.d=test.ex header.s=ses_sha256 header.a=rsa-sha1 +1999-03-02 09:44:33 10HmbC-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=fail (unspecified reason)\n header.d=test.ex header.s=ses_sha256 header.a=rsa-sha1 1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 1999-03-02 09:44:33 10HmbD-0005vi-00 unknown 1999-03-02 09:44:33 10HmbD-0005vi-00 signer: test.ex bits: 0 1999-03-02 09:44:33 10HmbD-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=0 [invalid - signature tag missing or invalid] -1999-03-02 09:44:33 10HmbD-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid) header.d=test.ex header.s=sel header.a=rsa-sha1 +1999-03-02 09:44:33 10HmbD-0005vi-00 Authentication-Results: myhost.test.ex;\n dkim=neutral (signature tag missing or invalid)\n header.d=test.ex header.s=sel header.a=rsa-sha1 1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 0 diff --git a/test/log/4560 b/test/log/4560 new file mode 100644 index 000000000..101afb73b --- /dev/null +++ b/test/log/4560 @@ -0,0 +1,102 @@ + +******** SERVER ******** +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss for za@test.ex +1999-03-02 09:44:33 Start queue run: pid=pppp +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@bloggs.com H=the.local.host.name (test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss ARC for a@test.ex +1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 R=d1 T=tfile +1999-03-02 09:44:33 10HmbP-0005vi-00 Completed +1999-03-02 09:44:33 End queue run: pid=pppp diff --git a/test/mail/4560.a b/test/mail/4560.a new file mode 100644 index 000000000..c0432b93a --- /dev/null +++ b/test/mail/4560.a @@ -0,0 +1,453 @@ +From CALLER@bloggs.com Tue Mar 02 09:44:33 1999 +Authentication-Results: test.ex; + iprev=pass (the.local.host.name); + arc=pass (i=1) header.s=sel arc.oldest-pass=1 smtp.client-ip=ip4.ip4.ip4.ip4 +Received: from the.local.host.name ([ip4.ip4.ip4.ip4] helo=test.ex) + by test.ex with esmtp (Exim x.yz) + (envelope-from ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ) + 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 ; 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 +Message-ID: <1426665656.110316.1517535248039.JavaMail.zimbra@convivian.com> +In-Reply-To: +References: + + + <29030059.107105.1517497494557.JavaMail.zimbra@convivian.com> + <4f60039a-a754-ae4c-1543-0a978d9e13be@rolandturner.com> + <1544831589.110194.1517532064123.JavaMail.zimbra@convivian.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 +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 +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +From: Jered Floyd via arc-discuss +Reply-To: Jered Floyd +Content-Type: multipart/mixed; boundary="===============2728806607597782871==" +Errors-To: arc-discuss-bounces@dmarc.org +Sender: "arc-discuss" + +--===============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 + +

+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-- + +--===============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==-- + diff --git a/test/runtest b/test/runtest index 06597fe04..8925731d2 100755 --- a/test/runtest +++ b/test/runtest @@ -926,6 +926,9 @@ RESET_AFTER_EXTRA_LINE_READ: # Postgres server takes varible time to shut down; lives in various places s/^waiting for server to shut down\.+ done$/waiting for server to shut down.... done/; s/^\/.*postgres /POSTGRES /; + + # ARC is not always supported by the build + next if /^arc_sign =/; } # ======== stderr ======== diff --git a/test/scripts/4560-ARC/4560 b/test/scripts/4560-ARC/4560 new file mode 100644 index 000000000..2d23674c7 --- /dev/null +++ b/test/scripts/4560-ARC/4560 @@ -0,0 +1,359 @@ +# ARC verify and sign +# +exim -DSERVER=server -bd -oX PORT_D +**** +# +# We send this one through one forwarding hop. +# It starts off bare, so the forwarder reception gets an ARC status of "none". +# The outbound signs it with that, and the final receiver is happy to pass it. +# +client 127.0.0.1 PORT_D +??? 220 +HELO xxx +??? 250 +MAIL FROM: +??? 250 +RCPT TO: +??? 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: +??? 250 +RCPT TO: +??? 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: +??? 250 +RCPT TO: +??? 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: +??? 250 +RCPT TO: +??? 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: +??? 250 +RCPT TO: +??? 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: +??? 250 +RCPT TO: +??? 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 ; 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 +Message-ID: <1426665656.110316.1517535248039.JavaMail.zimbra@convivian.com> +In-Reply-To: +References: + + + <29030059.107105.1517497494557.JavaMail.zimbra@convivian.com> + <4f60039a-a754-ae4c-1543-0a978d9e13be@rolandturner.com> + <1544831589.110194.1517532064123.JavaMail.zimbra@convivian.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 +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 +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +From: Jered Floyd via arc-discuss +Reply-To: Jered Floyd +Content-Type: multipart/mixed; boundary="===============2728806607597782871==" +Errors-To: arc-discuss-bounces@dmarc.org +Sender: "arc-discuss" + +--===============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 + +

+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-- + +--===============2728806607597782871== +Content-Type: text/plain; charset="us-ascii" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Content-Disposition: inline + +_______________________________________________ +arc-discuss mailing list +arc-discuss@dmarc.org +http://lists.dmarc.org/mailman/listinfo/arc-discuss + +--===============2728806607597782871==-- +. +??? 250 +QUIT +??? 221 +**** +# +exim -DSERVER=server -DNOTDAEMON -q +**** +exim -DSERVER=server -DNOTDAEMON -q +**** +# +# +# +# +# +# +# +# +# +killdaemon +# +no_stdout_check +no_msglog_check diff --git a/test/scripts/4560-ARC/REQUIRES b/test/scripts/4560-ARC/REQUIRES new file mode 100644 index 000000000..117c09f77 --- /dev/null +++ b/test/scripts/4560-ARC/REQUIRES @@ -0,0 +1 @@ +support Experimental_ARC diff --git a/test/stderr/4520 b/test/stderr/4520 index 19ff3fb48..4e6a2666f 100644 --- a/test/stderr/4520 +++ b/test/stderr/4520 @@ -34,6 +34,7 @@ PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> WARNING: bad dkim key in dns PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< content{CR}{LF} +PDKIM: finish bodyhash 1/1/-1 len 9 PDKIM [test.ex] Body bytes (relaxed) hashed: 9 PDKIM [test.ex] Body sha256 computed: fc06f48221d98ad6106c3845b33a2a41152482ab9e697f736ad26db4853fa657 PDKIM >> Headers to be signed: >>>>>>>>>>>> -- 2.25.1