1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4 /* Experimental ARC support for Exim
5 Copyright (c) Jeremy Harris 2018 - 2020
6 Copyright (c) The Exim Maintainers 2021 - 2023
8 SPDX-License-Identifier: GPL-2.0-or-later
12 #if defined EXPERIMENTAL_ARC
13 # if defined DISABLE_DKIM
14 # error DKIM must also be enabled for ARC
17 # include "functions.h"
18 # include "pdkim/pdkim.h"
19 # include "pdkim/signing.h"
25 extern pdkim_ctx * dkim_verify_ctx;
26 extern pdkim_ctx dkim_sign_ctx;
28 #define ARC_SIGN_OPT_TSTAMP BIT(0)
29 #define ARC_SIGN_OPT_EXPIRE BIT(1)
31 #define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30) /* one month */
33 /******************************************************************************/
35 typedef struct hdr_rlist {
36 struct hdr_rlist * prev;
41 typedef struct arc_line {
42 header_line * complete; /* including the header name; nul-term */
45 /* identified tag contents */
59 /* tag content sub-portions */
66 /* modified copy of b= field in line */
70 typedef struct arc_set {
71 struct arc_set * next;
72 struct arc_set * prev;
79 const uschar * ams_verify_done;
80 BOOL ams_verify_passed;
83 typedef struct arc_ctx {
84 arc_set * arcset_chain;
85 arc_set * arcset_chain_last;
88 #define ARC_HDR_AAR US"ARC-Authentication-Results:"
89 #define ARC_HDRLEN_AAR 27
90 #define ARC_HDR_AMS US"ARC-Message-Signature:"
91 #define ARC_HDRLEN_AMS 22
92 #define ARC_HDR_AS US"ARC-Seal:"
93 #define ARC_HDRLEN_AS 9
94 #define HDR_AR US"Authentication-Results:"
97 typedef enum line_extract {
104 static time_t expire;
105 static hdr_rlist * headers_rlist;
106 static arc_ctx arc_sign_ctx = { NULL };
107 static arc_ctx arc_verify_ctx = { NULL };
109 /* We build a context for either Sign or Verify.
111 For Verify, it's a fresh new one for ACL verify=arc - there is no connection
112 with the single line handling done during reception via the DKIM feed.
114 For Verify we do it twice; initially during reception (via the DKIM feed)
115 and then later for the full verification.
117 The former only looks at AMS headers, to discover what hash(es) we need done for
118 ARC on the message body; we call back to the DKIM code to set up so that it does
119 them for us during reception. That call needs info from many of the AMS tags;
120 arc_parse_line() for only the AMS is called asking for all the tag types.
121 That context is then discarded.
123 Later, for Verify, we look at ARC headers again and then grab the hash result
124 from the DKIM layer. arc_parse_line() is called for all 3 line types,
125 gathering info for only 'i' and 'ip' tags from AAR headers,
126 for all tag types from AMS and AS headers.
129 For Sign, while running through the existing headers (before adding any for
130 this signing operation, we "take copies" of the headers, we call
131 arc_parse_line() gathering only the 'i' tag (instance) information.
135 /******************************************************************************/
138 /* Get the instance number from the header.
141 arc_instance_from_hdr(const arc_line * al)
143 const uschar * s = al->i.data;
144 if (!s || !al->i.len) return 0;
145 return (unsigned) atoi(CCS s);
153 while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
158 /* Locate instance struct on chain, inserting a new one if
159 needed. The chain is in increasing-instance-number order
160 by the "next" link, and we have a "prev" link also.
164 arc_find_set(arc_ctx * ctx, unsigned i)
166 arc_set ** pas, * as, * next, * prev;
168 for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
169 as = *pas; pas = &as->next)
171 if (as->instance > i) break;
172 if (as->instance == i)
174 DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
181 DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
182 *pas = as = store_get(sizeof(arc_set), GET_UNTAINTED);
183 memset(as, 0, sizeof(arc_set));
190 ctx->arcset_chain_last = as;
196 /* Insert a tag content into the line structure.
197 Note this is a reference to existing data, not a copy.
198 Check for already-seen tag.
199 The string-pointer is on the '=' for entry. Update it past the
200 content (to the ;) on return;
204 arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
208 blob * b = (blob *)(US al + loff);
211 /* [FWS] tag-value [FWS] */
213 if (b->data) return US"fail";
214 s = skip_fws(s); /* FWS */
217 while ((c = *s) && c != ';') { len++; s++; }
219 while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
220 { s--; len--; } /* FWS */
226 /* Inspect a header line, noting known tag fields.
227 Check for duplicate named tags.
229 See the file block comment for how this is used.
231 Return: NULL for good, or an error string
235 arc_parse_line(arc_line * al, header_line * h, unsigned off, line_extract_t l_ext)
237 uschar * s = h->text + off;
243 if (l_ext == le_all) /* need to grab rawsig_no_b */
245 al->rawsig_no_b_val.data = store_get(h->slen + 1, GET_TAINTED);
246 memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */
247 r = al->rawsig_no_b_val.data + off;
248 al->rawsig_no_b_val.len = off;
251 /* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */
258 uschar * fieldstart = s;
259 uschar * bstart = NULL, * bend;
261 /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
262 /*X or just a naked FQDN, in a AAR ! */
264 s = skip_fws(s); /* leading FWS */
267 if (!*(s = skip_fws(s))) break; /* FWS */
271 case 'a': /* a= AMS algorithm */
272 if (l_ext == le_all && *s == '=')
274 if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
276 /* substructure: algo-hash (eg. rsa-sha256) */
278 t = al->a_algo.data = al->a.data;
280 if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
282 if (*t++ != '-') return US"no '-' in 'a' value";
284 al->a_hash.len = al->a.len - i - 1;
294 case '=': /* b= AMS signature */
295 if (al->b.data) return US"already b data";
298 /* The signature can have FWS inserted in the content;
299 make a stripped copy */
301 while ((c = *++s) && c != ';')
302 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
303 g = string_catn(g, s, 1);
304 if (!g) return US"no b= value";
305 al->b.len = len_string_from_gstring(g, &al->b.data);
306 gstring_release_unused(g);
309 case 'h': /* bh= AMS body hash */
310 s = skip_fws(++s); /* FWS */
313 if (al->bh.data) return US"already bh data";
315 /* The bodyhash can have FWS inserted in the content;
316 make a stripped copy */
318 while ((c = *++s) && c != ';')
319 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
320 g = string_catn(g, s, 1);
321 if (!g) return US"no bh= value";
322 al->bh.len = len_string_from_gstring(g, &al->bh.data);
323 gstring_release_unused(g);
332 if (l_ext == le_all) switch (*s)
334 case '=': /* c= AMS canonicalisation */
335 if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
337 /* substructure: head/body (eg. relaxed/simple)) */
339 t = al->c_head.data = al->c.data;
341 if (!*t++ || ++i > al->a.len) break;
343 if (*t++ == '/') /* /body is optional */
346 al->c_body.len = al->c.len - i - 1;
350 al->c_body.data = US"simple";
354 case 'v': /* cv= AS validity */
357 if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s))
358 return US"cv tag dup";
362 case 'd': /* d= AMS domain */
363 if (l_ext == le_all && *s == '=')
364 if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s))
365 return US"d tag dup";
367 case 'h': /* h= AMS headers */
369 if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s))
370 return US"h tag dup";
372 case 'i': /* i= ARC set instance */
375 if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s))
376 return US"i tag dup";
377 if (l_ext == le_instance_only)
378 goto done; /* early-out */
381 case 'l': /* l= bodylength */
382 if (l_ext == le_all && *s == '=')
383 if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s))
384 return US"l tag dup";
387 if (*s == '=' && l_ext == le_all)
389 if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s))
390 return US"s tag dup";
392 else if ( l_ext == le_instance_plus_ip
393 && Ustrncmp(s, "mtp.remote-ip", 13) == 0)
394 { /* smtp.remote-ip= AAR reception data */
397 if (*s != '=') return US"smtp.remote_ip tag val";
398 if (arc_insert_tagvalue(al, offsetof(arc_line, ip), &s))
399 return US"ip tag dup";
404 while ((c = *s) && c != ';') s++; /* end of this tag=value */
405 if (c) s++; /* ; after tag-spec */
407 /* for all but the b= tag, copy the field including FWS. For the b=,
408 drop the tag content. */
413 size_t n = bstart - fieldstart;
414 memcpy(r, fieldstart, n); /* FWS "b=" */
416 al->rawsig_no_b_val.len += n;
418 memcpy(r, bend, n); /* FWS ";" */
420 al->rawsig_no_b_val.len += n;
424 size_t n = s - fieldstart;
425 memcpy(r, fieldstart, n);
427 al->rawsig_no_b_val.len += n;
435 /* debug_printf("%s: finshed\n", __FUNCTION__); */
440 /* Insert one header line in the correct set of the chain,
441 adding instances as needed and checking for duplicate lines.
445 arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
446 line_extract_t l_ext, arc_line ** alp_ret)
450 arc_line * al = store_get(sizeof(arc_line), GET_UNTAINTED), ** alp;
453 memset(al, 0, sizeof(arc_line));
455 if ((e = arc_parse_line(al, h, off, l_ext)))
457 DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
458 return string_sprintf("line parse: %s", e);
460 if (!(i = arc_instance_from_hdr(al))) return US"instance find";
461 if (i > 50) return US"overlarge instance number";
462 if (!(as = arc_find_set(ctx, i))) return US"set find";
463 if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
466 if (alp_ret) *alp_ret = al;
472 /* Called for both Sign and Verify */
474 static const uschar *
475 arc_try_header(arc_ctx * ctx, header_line * h, BOOL is_signing)
479 /*debug_printf("consider hdr '%s'\n", h->text);*/
480 if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
486 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
488 debug_printf("ARC: found AAR: %.*s\n", len, h->text);
490 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
491 is_signing ? le_instance_only : le_instance_plus_ip, NULL)))
493 DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
494 return string_sprintf("inserting AAR: %s", e);
497 else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
505 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
507 debug_printf("ARC: found AMS: %.*s\n", len, h->text);
509 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
510 is_signing ? le_instance_only : le_all, &ams)))
512 DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
513 return string_sprintf("inserting AMS: %s", e);
519 ams->c_head.data = US"simple"; ams->c_head.len = 6;
520 ams->c_body = ams->c_head;
523 else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
529 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
531 debug_printf("ARC: found AS: %.*s\n", len, h->text);
533 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
534 is_signing ? le_instance_only : le_all, NULL)))
536 DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
537 return string_sprintf("inserting AS: %s", e);
545 /* Gather the chain of arc sets from the headers.
546 Check for duplicates while that is done. Also build the
547 reverse-order headers list.
548 Called on an ACL verify=arc condition.
550 Return: ARC state if determined, eg. by lack of any ARC chain.
553 static const uschar *
554 arc_vfy_collect_hdrs(arc_ctx * ctx)
557 hdr_rlist * r = NULL, * rprev = NULL;
560 DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
561 for (h = header_list; h; h = h->next)
563 r = store_get(sizeof(hdr_rlist), GET_UNTAINTED);
569 if ((e = arc_try_header(ctx, h, FALSE)))
571 arc_state_reason = string_sprintf("collecting headers: %s", e);
577 if (!ctx->arcset_chain) return US"none";
583 arc_cv_match(arc_line * al, const uschar * s)
585 return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
588 /******************************************************************************/
590 /* Return the hash of headers from the message that the AMS claims it
595 arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
597 const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
601 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
602 int hashtype = pdkim_hashname_to_hashtype(
603 ams->a_hash.data, ams->a_hash.len);
609 || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
612 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
616 /* For each headername in the list from the AMS (walking in order)
617 walk the message headers in reverse order, adding to the hash any
618 found for the first time. For that last point, maintain used-marks
619 on the list of message headers. */
621 DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
623 for (r = headers_rlist; r; r = r->prev)
625 while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
626 for (r = headers_rlist; r; r = r->prev)
628 && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
631 if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
634 DEBUG(D_acl) pdkim_quoteprint(s, len);
635 exim_sha_update_string(&hhash_ctx, s);
640 /* Finally add in the signature header (with the b= tag stripped); no CRLF */
642 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
644 len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
645 DEBUG(D_acl) pdkim_quoteprint(s, len);
646 exim_sha_update(&hhash_ctx, s, len);
648 exim_sha_finish(&hhash_ctx, hhash);
650 { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
657 static pdkim_pubkey *
658 arc_line_to_pubkey(arc_line * al)
663 if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
664 (int)al->s.len, al->s.data, (int)al->d.len, al->d.data))))
666 DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
670 if ( !(p = pdkim_parse_pubkey_record(dns_txt))
671 || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
674 DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
678 /* If the pubkey limits use to specified hashes, reject unusable
679 signatures. XXX should we have looked for multiple dns records? */
683 const uschar * list = p->hashes, * ele;
686 while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
687 if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
690 DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
691 p->hashes, (int)al->a.len, al->a.data);
701 static pdkim_bodyhash *
702 arc_ams_setup_vfy_bodyhash(arc_line * ams)
704 int canon_head = -1, canon_body = -1;
707 if (!ams->c.data) ams->c.data = US"simple"; /* RFC 6376 (DKIM) default */
708 pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
709 bodylen = ams->l.data
710 ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
712 return pdkim_set_bodyhash(dkim_verify_ctx,
713 pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
720 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
721 and without a DKIM v= tag.
724 static const uschar *
725 arc_ams_verify(arc_ctx * ctx, arc_set * as)
727 arc_line * ams = as->hdr_ams;
734 const uschar * errstr;
736 as->ams_verify_done = US"in-progress";
738 /* Check the AMS has all the required tags:
742 "d=" domain (for key lookup)
743 "h=" headers (included in signature)
744 "s=" key-selector (for key lookup)
746 if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
747 || !ams->h.data || !ams->s.data)
749 as->ams_verify_done = arc_state_reason = US"required tag missing";
754 /* The bodyhash should have been created earlier, and the dkim code should
755 have managed calculating it during message input. Find the reference to it. */
757 if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
759 as->ams_verify_done = arc_state_reason = US"internal hash setup error";
765 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
766 " Body %.*s computed: ",
767 as->instance, b->signed_body_bytes,
768 (int)ams->a_hash.len, ams->a_hash.data);
769 pdkim_hexprint(CUS b->bh.data, b->bh.len);
772 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
775 || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
776 || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
781 debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
782 pdkim_hexprint(sighash.data, sighash.len);
783 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
785 return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
788 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
790 /* Get the public key from DNS */
792 if (!(p = arc_line_to_pubkey(ams)))
793 return as->ams_verify_done = arc_state_reason = US"pubkey problem";
795 /* We know the b-tag blob is of a nul-term string, so safe as a string */
796 pdkim_decode_base64(ams->b.data, &sighash);
798 arc_get_verify_hhash(ctx, ams, &hhash);
800 /* Setup the interface to the signing library */
802 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
804 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
805 as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
809 hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
812 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify bad a_hash\n", as->instance);
813 return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
816 if ((errstr = exim_dkim_verify(&vctx,
817 pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
819 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
820 return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
823 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
824 as->ams_verify_passed = TRUE;
830 /* Check the sets are instance-continuous and that all
831 members are present. Check that no arc_seals are "fail".
832 Set the highest instance number global.
833 Verify the latest AMS.
836 arc_headers_check(arc_ctx * ctx)
840 BOOL ams_fail_found = FALSE;
842 if (!(as = ctx->arcset_chain_last))
845 for(inst = as->instance; as; as = as->prev, inst--)
847 if (as->instance != inst)
848 arc_state_reason = string_sprintf("i=%d (sequence; expected %d)",
850 else if (!as->hdr_aar || !as->hdr_ams || !as->hdr_as)
851 arc_state_reason = string_sprintf("i=%d (missing header)", as->instance);
852 else if (arc_cv_match(as->hdr_as, US"fail"))
853 arc_state_reason = string_sprintf("i=%d (cv)", as->instance);
857 DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
861 /* Evaluate the oldest-pass AMS validation while we're here.
862 It does not affect the AS chain validation but is reported as
866 if (arc_ams_verify(ctx, as))
867 ams_fail_found = TRUE;
869 arc_oldest_pass = inst;
870 arc_state_reason = NULL;
874 arc_state_reason = string_sprintf("(sequence; expected i=%d)", inst);
875 DEBUG(D_acl) debug_printf("ARC chain fail %s\n", arc_state_reason);
879 arc_received = ctx->arcset_chain_last;
880 arc_received_instance = arc_received->instance;
882 /* We can skip the latest-AMS validation, if we already did it. */
884 as = ctx->arcset_chain_last;
885 if (!as->ams_verify_passed)
887 if (as->ams_verify_done)
889 arc_state_reason = as->ams_verify_done;
892 if (!!arc_ams_verify(ctx, as))
899 /******************************************************************************/
900 static const uschar *
901 arc_seal_verify(arc_ctx * ctx, arc_set * as)
903 arc_line * hdr_as = as->hdr_as;
911 const uschar * errstr;
913 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
915 1. If the value of the "cv" tag on that seal is "fail", the
916 chain state is "fail" and the algorithm stops here. (This
917 step SHOULD be skipped if the earlier step (2.1) was
920 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
921 == "none" && i != 1)) then the chain state is "fail" and the
922 algorithm stops here (note that the ordering of the logic is
923 structured for short-circuit evaluation).
926 if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none")
927 || arc_cv_match(hdr_as, US"none") && as->instance != 1
930 arc_state_reason = US"seal cv state";
935 3. Initialize a hash function corresponding to the "a" tag of
939 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
942 || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
945 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
946 arc_state_reason = US"seal hash setup error";
951 4. Compute the canonicalized form of the ARC header fields, in
952 the order described in Section 5.4.2, using the "relaxed"
953 header canonicalization defined in Section 3.4.2 of
954 [RFC6376]. Pass the canonicalized result to the hash
957 Headers are CRLF-separated, but the last one is not crlf-terminated.
960 DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
961 for (as2 = ctx->arcset_chain;
962 as2 && as2->instance <= as->instance;
970 if (!(s = al->relaxed))
971 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
972 al->complete->slen, TRUE);
974 DEBUG(D_acl) pdkim_quoteprint(s, len);
975 exim_sha_update(&hhash_ctx, s, len);
978 if (!(s = al->relaxed))
979 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
980 al->complete->slen, TRUE);
982 DEBUG(D_acl) pdkim_quoteprint(s, len);
983 exim_sha_update(&hhash_ctx, s, len);
986 if (as2->instance == as->instance)
987 s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
988 al->rawsig_no_b_val.len, FALSE);
989 else if (!(s = al->relaxed))
990 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
991 al->complete->slen, TRUE);
993 DEBUG(D_acl) pdkim_quoteprint(s, len);
994 exim_sha_update(&hhash_ctx, s, len);
998 5. Retrieve the final digest from the hash function.
1001 exim_sha_finish(&hhash_ctx, &hhash_computed);
1004 debug_printf("ARC i=%d AS Header %.*s computed: ",
1005 as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
1006 pdkim_hexprint(hhash_computed.data, hhash_computed.len);
1011 6. Retrieve the public key identified by the "s" and "d" tags in
1012 the ARC-Seal, as described in Section 4.1.6.
1015 if (!(p = arc_line_to_pubkey(hdr_as)))
1016 return US"pubkey problem";
1019 7. Determine whether the signature portion ("b" tag) of the ARC-
1020 Seal and the digest computed above are valid according to the
1021 public key. (See also Section Section 8.4 for failure case
1024 8. If the signature is not valid, the chain state is "fail" and
1025 the algorithm stops here.
1028 /* We know the b-tag blob is of a nul-term string, so safe as a string */
1029 pdkim_decode_base64(hdr_as->b.data, &sighash);
1031 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
1033 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
1037 if ((errstr = exim_dkim_verify(&vctx,
1038 pdkim_hashes[hashtype].exim_hashmethod,
1039 &hhash_computed, &sighash)))
1042 debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
1043 arc_state_reason = US"seal sigverify error";
1047 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
1052 static const uschar *
1053 arc_verify_seals(arc_ctx * ctx)
1055 arc_set * as = ctx->arcset_chain_last;
1060 for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail";
1062 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
1065 /******************************************************************************/
1067 /* Do ARC verification. Called from DATA ACL, on a verify = arc
1068 condition. No arguments; we are checking globals.
1070 Return: The ARC state, or NULL on error.
1074 acl_verify_arc(void)
1078 memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
1080 if (!dkim_verify_ctx)
1082 DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
1086 /* AS evaluation, per
1087 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
1089 /* 1. Collect all ARC sets currently on the message. If there were
1090 none, the ARC state is "none" and the algorithm stops here.
1093 if ((res = arc_vfy_collect_hdrs(&arc_verify_ctx)))
1096 /* 2. If the form of any ARC set is invalid (e.g., does not contain
1097 exactly one of each of the three ARC-specific header fields),
1098 then the chain state is "fail" and the algorithm stops here.
1100 1. To avoid the overhead of unnecessary computation and delay
1101 from crypto and DNS operations, the cv value for all ARC-
1102 Seal(s) MAY be checked at this point. If any of the values
1103 are "fail", then the overall state of the chain is "fail" and
1104 the algorithm stops here.
1106 3. Conduct verification of the ARC-Message-Signature header field
1107 bearing the highest instance number. If this verification fails,
1108 then the chain state is "fail" and the algorithm stops here.
1111 if ((res = arc_headers_check(&arc_verify_ctx)))
1114 /* 4. For each ARC-Seal from the "N"th instance to the first, apply the
1117 1. If the value of the "cv" tag on that seal is "fail", the
1118 chain state is "fail" and the algorithm stops here. (This
1119 step SHOULD be skipped if the earlier step (2.1) was
1122 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1123 == "none" && i != 1)) then the chain state is "fail" and the
1124 algorithm stops here (note that the ordering of the logic is
1125 structured for short-circuit evaluation).
1127 3. Initialize a hash function corresponding to the "a" tag of
1130 4. Compute the canonicalized form of the ARC header fields, in
1131 the order described in Section 5.4.2, using the "relaxed"
1132 header canonicalization defined in Section 3.4.2 of
1133 [RFC6376]. Pass the canonicalized result to the hash
1136 5. Retrieve the final digest from the hash function.
1138 6. Retrieve the public key identified by the "s" and "d" tags in
1139 the ARC-Seal, as described in Section 4.1.6.
1141 7. Determine whether the signature portion ("b" tag) of the ARC-
1142 Seal and the digest computed above are valid according to the
1143 public key. (See also Section Section 8.4 for failure case
1146 8. If the signature is not valid, the chain state is "fail" and
1147 the algorithm stops here.
1149 5. If all seals pass validation, then the chain state is "pass", and
1150 the algorithm is complete.
1153 if ((res = arc_verify_seals(&arc_verify_ctx)))
1162 /******************************************************************************/
1164 /* Prepend the header to the rlist */
1167 arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1169 hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), GET_UNTAINTED);
1170 header_line * h = r->h = (header_line *)(r+1);
1183 /* Walk the given headers strings identifying each header, and construct
1184 a reverse-order list.
1188 arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1191 hdr_rlist * rheaders = NULL;
1193 s = sigheaders ? sigheaders->s : NULL;
1196 const uschar * s2 = s;
1198 /* This works for either NL or CRLF lines; also nul-termination */
1200 if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1201 s2++; /* move past end of line */
1203 rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1211 /* Return the A-R content, without identity, with line-ending and
1215 arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1218 int ilen = Ustrlen(identity);
1221 for(h = headers; h; h = h->next)
1223 uschar * s = h->text, c;
1226 if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1227 s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */
1229 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1230 s++, len--; /* FWS */
1231 if (Ustrncmp(s, identity, ilen) != 0) continue;
1232 s += ilen; len -= ilen; /* identity */
1233 if (len <= 0) continue;
1234 if ((c = *s) && c == ';') s++, len--; /* identity terminator */
1236 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1237 s++, len--; /* FWS */
1238 if (len <= 0) continue;
1248 /* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
1251 arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1252 const uschar * identity, int instance, blob * ar)
1254 int aar_off = gstring_length(g);
1256 store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), GET_UNTAINTED);
1257 arc_line * al = (arc_line *)(as+1);
1258 header_line * h = (header_line *)(al+1);
1260 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1261 g = string_fmt_append(g, " i=%d; %s; smtp.remote-ip=%s;\r\n\t",
1262 instance, identity, sender_host_address);
1263 g = string_catn(g, US ar->data, ar->len);
1265 h->slen = g->ptr - aar_off;
1266 h->text = g->s + aar_off;
1269 as->prev = ctx->arcset_chain_last;
1270 as->instance = instance;
1273 ctx->arcset_chain = as;
1275 ctx->arcset_chain_last->next = as;
1276 ctx->arcset_chain_last = as;
1278 DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1285 arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1286 blob * sig, const uschar * why)
1288 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1289 ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1292 const uschar * errstr;
1297 debug_printf("ARC: %s header data for signing:\n", why);
1298 pdkim_quoteprint(hdata->s, hdata->ptr);
1300 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1301 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1302 exim_sha_finish(&hhash_ctx, &hhash);
1303 debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
1306 if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1309 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1310 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1311 exim_sha_finish(&hhash_ctx, &hhash);
1315 hhash.data = hdata->s;
1316 hhash.len = hdata->ptr;
1319 if ( (errstr = exim_dkim_signing_init(privkey, &sctx))
1320 || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
1322 log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
1324 debug_printf("private key, or private-key file content, was: '%s'\n",
1334 arc_sign_append_sig(gstring * g, blob * sig)
1336 /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1337 sig->data = pdkim_encode_base64(sig);
1338 sig->len = Ustrlen(sig->data);
1341 int len = MIN(sig->len, 74);
1342 g = string_catn(g, sig->data, len);
1343 if ((sig->len -= len) == 0) break;
1345 g = string_catn(g, US"\r\n\t ", 5);
1347 g = string_catn(g, US";\r\n", 3);
1348 gstring_release_unused(g);
1349 string_from_gstring(g);
1354 /* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
1357 arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1358 const uschar * identity, const uschar * selector, blob * bodyhash,
1359 hdr_rlist * rheaders, const uschar * privkey, unsigned options)
1362 gstring * hdata = NULL;
1364 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1367 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED);
1368 header_line * h = (header_line *)(al+1);
1370 /* debug_printf("%s\n", __FUNCTION__); */
1372 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1374 ams_off = gstring_length(g);
1375 g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s",
1376 ARC_HDR_AMS, instance, identity, selector); /*XXX hardwired a= */
1377 if (options & ARC_SIGN_OPT_TSTAMP)
1378 g = string_fmt_append(g, "; t=%lu", (u_long)now);
1379 if (options & ARC_SIGN_OPT_EXPIRE)
1380 g = string_fmt_append(g, "; x=%lu", (u_long)expire);
1381 g = string_fmt_append(g, ";\r\n\tbh=%s;\r\n\th=",
1382 pdkim_encode_base64(bodyhash));
1384 for(col = 3; rheaders; rheaders = rheaders->prev)
1386 const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1387 uschar * name, * htext = rheaders->h->text;
1390 /* Spot headers of interest */
1392 while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1394 int len = Ustrlen(name);
1395 if (strncasecmp(CCS htext, CCS name, len) == 0)
1397 /* If too long, fold line in h= field */
1399 if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3;
1401 /* Add name to h= list */
1403 g = string_catn(g, name, len);
1404 g = string_catn(g, US":", 1);
1407 /* Accumulate header for hashing/signing */
1409 hdata = string_cat(hdata,
1410 pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */
1416 /* Lose the last colon from the h= list */
1418 gstring_trim_trailing(g, ':');
1420 g = string_catn(g, US";\r\n\tb=;", 7);
1422 /* Include the pseudo-header in the accumulation */
1424 s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
1425 hdata = string_cat(hdata, s);
1427 /* Calculate the signature from the accumulation */
1428 /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1430 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1433 /* Lose the trailing semicolon from the psuedo-header, and append the signature
1434 (folded over lines) and termination to complete it. */
1437 g = arc_sign_append_sig(g, &sig);
1439 h->slen = g->ptr - ams_off;
1440 h->text = g->s + ams_off;
1442 ctx->arcset_chain_last->hdr_ams = al;
1444 DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1450 /* Look for an arc= result in an A-R header blob. We know that its data
1451 happens to be a NUL-term string. */
1454 arc_ar_cv_status(blob * ar)
1456 const uschar * resinfo = ar->data;
1458 uschar * methodspec, * s;
1460 while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1461 if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1464 for (s = methodspec += 4;
1465 (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1466 return string_copyn(methodspec, s - methodspec);
1473 /* Build the AS header and prepend it */
1476 arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1477 int instance, const uschar * identity, const uschar * selector, blob * ar,
1478 const uschar * privkey, unsigned options)
1481 uschar * status = arc_ar_cv_status(ar);
1482 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED);
1483 header_line * h = (header_line *)(al+1);
1484 uschar * badline_str;
1486 gstring * hdata = NULL;
1487 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1493 - no h= tag; implicit coverage
1494 - arc status from A-R
1496 - coverage is just the new ARC set
1497 including self (but with an empty b= in self)
1499 - all ARC set headers, set-number order, aar then ams then as,
1500 including self (but with an empty b= in self)
1502 DEBUG(D_transport) debug_printf("ARC: building AS for status '%s'\n", status);
1504 /* Construct the AS except for the signature */
1506 arcset = string_append(NULL, 9,
1508 US" i=", string_sprintf("%d", instance),
1510 US"; a=rsa-sha256; d=", identity, /*XXX hardwired */
1511 US"; s=", selector); /*XXX same as AMS */
1512 if (options & ARC_SIGN_OPT_TSTAMP)
1513 arcset = string_append(arcset, 2,
1514 US"; t=", string_sprintf("%lu", (u_long)now));
1515 arcset = string_cat(arcset,
1518 h->slen = arcset->ptr;
1519 h->text = arcset->s;
1521 ctx->arcset_chain_last->hdr_as = al;
1523 /* For any but "fail" chain-verify status, walk the entire chain in order by
1524 instance. For fail, only the new arc-set. Accumulate the elements walked. */
1526 for (arc_set * as = Ustrcmp(status, US"fail") == 0
1527 ? ctx->arcset_chain_last : ctx->arcset_chain;
1531 /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
1532 is required per standard. */
1534 badline_str = US"aar";
1535 if (!(l = as->hdr_aar)) goto badline;
1537 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1538 badline_str = US"ams";
1539 if (!(l = as->hdr_ams)) goto badline;
1541 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1542 badline_str = US"as";
1543 if (!(l = as->hdr_as)) goto badline;
1545 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
1548 /* Calculate the signature from the accumulation */
1550 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1553 /* Lose the trailing semicolon */
1555 arcset = arc_sign_append_sig(arcset, &sig);
1556 DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s);
1558 /* Finally, append the AMS and AAR to the new AS */
1560 return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1564 debug_printf("ARC: while building AS, missing %s in chain\n", badline_str);
1569 /**************************************/
1571 /* Return pointer to pdkim_bodyhash for given hash method, creating new
1576 arc_ams_setup_sign_bodyhash(void)
1578 int canon_head, canon_body;
1580 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1581 pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */
1582 return pdkim_set_bodyhash(&dkim_sign_ctx,
1583 pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */
1593 memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
1594 headers_rlist = NULL;
1599 /* A "normal" header line, identified by DKIM processing. These arrive before
1600 the call to arc_sign(), which carries any newly-created DKIM headers - and
1601 those go textually before the normal ones in the message.
1603 We have to take the feed from DKIM as, in the transport-filter case, the
1604 headers are not in memory at the time of the call to arc_sign().
1606 Take a copy of the header and construct a reverse-order list.
1607 Also parse ARC-chain headers and build the chain struct, retaining pointers
1611 static const uschar *
1612 arc_header_sign_feed(gstring * g)
1614 uschar * s = string_copy_from_gstring(g);
1615 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1616 return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1621 /* Per RFCs 6376, 7489 the only allowed chars in either an ADMD id
1622 or a selector are ALPHA/DIGGIT/'-'/'.'
1624 Check, to help catch misconfigurations such as a missing selector
1625 element in the arc_sign list.
1629 arc_valid_id(const uschar * s)
1631 for (uschar c; c = *s++; )
1632 if (!isalnum(c) && c != '-' && c != '.') return FALSE;
1638 /* ARC signing. Called from the smtp transport, if the arc_sign option is set.
1639 The dkim_exim_sign() function has already been called, so will have hashed the
1640 message body for us so long as we requested a hash previously.
1643 signspec Three-element colon-sep list: identity, selector, privkey.
1644 Optional fourth element: comma-sep list of options.
1646 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1650 Set of headers to prepend to the message, including the supplied sigheaders
1651 but not the plainheaders.
1655 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1657 const uschar * identity, * selector, * privkey, * opts, * s;
1658 unsigned options = 0;
1660 header_line * headers;
1661 hdr_rlist * rheaders;
1669 /* Parse the signing specification */
1671 if (!(identity = string_nextinlist(&signspec, &sep, NULL, 0)) || !*identity)
1672 { s = US"identity"; goto bad_arg_ret; }
1673 if (!(selector = string_nextinlist(&signspec, &sep, NULL, 0)) || !*selector)
1674 { s = US"selector"; goto bad_arg_ret; }
1675 if (!(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1676 { s = US"privkey"; goto bad_arg_ret; }
1677 if (!arc_valid_id(identity))
1678 { s = US"identity"; goto bad_arg_ret; }
1679 if (!arc_valid_id(selector))
1680 { s = US"selector"; goto bad_arg_ret; }
1681 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1682 goto ret_sigheaders;
1684 if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
1687 while ((s = string_nextinlist(&opts, &osep, NULL, 0)))
1688 if (Ustrcmp(s, "timestamps") == 0)
1690 options |= ARC_SIGN_OPT_TSTAMP;
1691 if (!now) now = time(NULL);
1693 else if (Ustrncmp(s, "expire", 6) == 0)
1695 options |= ARC_SIGN_OPT_EXPIRE;
1696 if (*(s += 6) == '=')
1699 if (!(expire = (time_t)atoi(CS ++s)))
1700 expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA;
1701 if (!now) now = time(NULL);
1705 expire = (time_t)atol(CS s);
1708 if (!now) now = time(NULL);
1709 expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA;
1714 DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1716 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
1717 Then scan the list for an A-R header. */
1719 string_from_gstring(sigheaders);
1720 if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1723 for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
1727 /* Finally, build a normal-order headers list */
1728 /*XXX only needed for hunt-the-AR? */
1729 /*XXX also, we really should be accepting any number of ADMD-matching ARs */
1731 header_line * hnext = NULL;
1732 for (rheaders = headers_rlist; rheaders;
1733 hnext = rheaders->h, rheaders = rheaders->prev)
1734 rheaders->h->next = hnext;
1738 if (!(arc_sign_find_ar(headers, identity, &ar)))
1740 log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
1741 goto ret_sigheaders;
1744 /* We previously built the data-struct for the existing ARC chain, if any, using a headers
1745 feed from the DKIM module. Use that to give the instance number for the ARC set we are
1749 if (arc_sign_ctx.arcset_chain_last)
1750 debug_printf("ARC: existing chain highest instance: %d\n",
1751 arc_sign_ctx.arcset_chain_last->instance);
1753 debug_printf("ARC: no existing chain\n");
1755 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1759 - copy the A-R; prepend i= & identity
1762 g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1766 - Looks fairly like a DKIM sig
1767 - Cover all DKIM sig headers as well as the usuals
1770 - we must have requested a suitable bodyhash previously
1773 b = arc_ams_setup_sign_bodyhash();
1774 g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1775 &b->bh, headers_rlist, privkey, options);
1780 - no h= tag; implicit coverage
1781 - arc status from A-R
1783 - coverage is just the new ARC set
1784 including self (but with an empty b= in self)
1786 - all ARC set headers, set-number order, aar then ams then as,
1787 including self (but with an empty b= in self)
1791 g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
1794 /* Finally, append the dkim headers and return the lot. */
1796 if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
1799 if (!g) return string_get(1);
1800 (void) string_from_gstring(g);
1801 gstring_release_unused(g);
1806 log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", s);
1813 /******************************************************************************/
1815 /* Check to see if the line is an AMS and if so, set up to validate it.
1816 Called from the DKIM input processing. This must be done now as the message
1817 body data is hashed during input.
1819 We call the DKIM code to request a body-hash; it has the facility already
1820 and the hash parameters might be common with other requests.
1823 static const uschar *
1824 arc_header_vfy_feed(gstring * g)
1831 if (!dkim_verify_ctx) return US"no dkim context";
1833 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1835 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1836 /* Parse the AMS header */
1838 memset(&al, 0, sizeof(arc_line));
1840 h.slen = len_string_from_gstring(g, &h.text);
1841 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, le_all)))
1843 DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1847 if (!al.a_hash.data)
1849 DEBUG(D_acl) debug_printf("ARC: no a_hash from '%.*s'\n", h.slen, h.text);
1856 al.c_body.data = US"simple"; al.c_body.len = 6;
1857 al.c_head = al.c_body;
1860 /* Ask the dkim code to calc a bodyhash with those specs */
1862 if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1863 return US"dkim hash setup fail";
1865 /* Discard the reference; search again at verify time, knowing that one
1866 should have been created here. */
1871 return US"line parsing error";
1876 /* A header line has been identified by DKIM processing.
1880 is_vfy TRUE for verify mode or FALSE for signing mode
1883 NULL for success, or an error string (probably unused)
1887 arc_header_feed(gstring * g, BOOL is_vfy)
1889 return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1894 /******************************************************************************/
1896 /* Construct the list of domains from the ARC chain after validation */
1899 fn_arc_domains(void)
1905 for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++)
1907 arc_line * hdr_as = as->hdr_as;
1910 blob * d = &hdr_as->d;
1912 for (; inst < as->instance; inst++)
1913 g = string_catn(g, US":", 1);
1915 g = d->data && d->len
1916 ? string_append_listele_n(g, ':', d->data, d->len)
1917 : string_catn(g, US":", 1);
1920 g = string_catn(g, US":", 1);
1922 if (!g) return US"";
1923 return string_from_gstring(g);
1927 /* Construct an Authentication-Results header portion, for the ARC module */
1930 authres_arc(gstring * g)
1934 arc_line * highest_ams;
1935 int start = 0; /* Compiler quietening */
1936 DEBUG(D_acl) start = gstring_length(g);
1938 g = string_append(g, 2, US";\n\tarc=", arc_state);
1939 if (arc_received_instance > 0)
1941 g = string_fmt_append(g, " (i=%d)", arc_received_instance);
1942 if (arc_state_reason)
1943 g = string_append(g, 3, US"(", arc_state_reason, US")");
1944 g = string_catn(g, US" header.s=", 10);
1945 highest_ams = arc_received->hdr_ams;
1946 g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1948 g = string_fmt_append(g, " arc.oldest-pass=%d", arc_oldest_pass);
1950 if (sender_host_address)
1951 g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
1953 else if (arc_state_reason)
1954 g = string_append(g, 3, US" (", arc_state_reason, US")");
1955 DEBUG(D_acl) debug_printf("ARC:\tauthres '%.*s'\n",
1956 gstring_length(g) - start - 3, g->s + start + 3);
1959 DEBUG(D_acl) debug_printf("ARC:\tno authres\n");
1964 # ifdef SUPPORT_DMARC
1965 /* Append a DMARC history record pair for ARC, to the given history set */
1968 arc_dmarc_hist_append(gstring * g)
1973 int i = Ustrcmp(arc_state, "pass") == 0 ? ARES_RESULT_PASS
1974 : Ustrcmp(arc_state, "fail") == 0 ? ARES_RESULT_FAIL
1975 : ARES_RESULT_UNKNOWN;
1976 g = string_fmt_append(g, "arc %d\n", i);
1977 g = string_fmt_append(g, "arc_policy %d json[",
1978 i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS
1979 : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL
1980 : DMARC_ARC_POLICY_RESULT_UNUSED);
1981 /*XXX would we prefer this backwards? */
1982 for (arc_set * as = arc_verify_ctx.arcset_chain; as;
1983 as = as->next, first = FALSE)
1985 arc_line * line = as->hdr_as;
1988 blob * d = &line->d;
1989 blob * s = &line->s;
1992 g = string_catn(g, US",", 1);
1994 g = string_fmt_append(g, " (\"i\":%u," /*)*/
1998 d->data ? (int)d->len : 0, d->data && d->len ? d->data : US"",
1999 s->data ? (int)s->len : 0, s->data && s->len ? s->data : US""
2001 if ((line = as->hdr_aar))
2003 blob * ip = &line->ip;
2004 if (ip->data && ip->len)
2005 g = string_fmt_append(g, ", \"ip\":\"%.*s\"", (int)ip->len, ip->data);
2008 g = string_catn(g, US")", 1);
2011 g = string_catn(g, US" ]\n", 3);
2014 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
2015 ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
2021 # endif /* DISABLE_DKIM */
2022 #endif /* EXPERIMENTAL_ARC */