1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4 /* Experimental ARC support for Exim
5 Copyright (c) The Exim Maintainers 2021 - 2024
6 Copyright (c) Jeremy Harris 2018 - 2020
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"
23 struct arc_set *arc_received = NULL; /* highest ARC instance eval struct */
24 int arc_received_instance = 0; /* highest ARC instance num in hdrs */
25 int arc_oldest_pass = 0; /* lowest passing inst num in hdrs */
26 const uschar *arc_state = NULL; /* verification state */
27 const uschar *arc_state_reason = NULL;
29 /******************************************************************************/
30 #define ARC_SIGN_OPT_TSTAMP BIT(0)
31 #define ARC_SIGN_OPT_EXPIRE BIT(1)
33 #define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30) /* one month */
35 /******************************************************************************/
37 typedef struct hdr_rlist {
38 struct hdr_rlist * prev;
43 typedef struct arc_line {
44 header_line * complete; /* including the header name; nul-term */
47 /* identified tag contents */
61 /* tag content sub-portions */
68 /* modified copy of b= field in line */
72 typedef struct arc_set {
73 struct arc_set * next;
74 struct arc_set * prev;
81 const uschar * ams_verify_done;
82 BOOL ams_verify_passed;
85 typedef struct arc_ctx {
86 arc_set * arcset_chain;
87 arc_set * arcset_chain_last;
90 #define ARC_HDR_AAR US"ARC-Authentication-Results:"
91 #define ARC_HDRLEN_AAR 27
92 #define ARC_HDR_AMS US"ARC-Message-Signature:"
93 #define ARC_HDRLEN_AMS 22
94 #define ARC_HDR_AS US"ARC-Seal:"
95 #define ARC_HDRLEN_AS 9
96 #define HDR_AR US"Authentication-Results:"
99 typedef enum line_extract {
105 static misc_module_info * arc_dkim_mod_info;
108 static time_t expire;
109 static hdr_rlist * headers_rlist;
110 static arc_ctx arc_sign_ctx = { NULL };
111 static arc_ctx arc_verify_ctx = { NULL };
113 /* We build a context for either Sign or Verify.
115 For Verify, it's a fresh new one for ACL verify=arc - there is no connection
116 with the single line handling done during reception via the DKIM feed.
118 For Verify we do it twice; initially during reception (via the DKIM feed)
119 and then later for the full verification.
121 The former only looks at AMS headers, to discover what hash(es) we need done for
122 ARC on the message body; we call back to the DKIM code to set up so that it does
123 them for us during reception. That call needs info from many of the AMS tags;
124 arc_parse_line() for only the AMS is called asking for all the tag types.
125 That context is then discarded.
127 Later, for Verify, we look at ARC headers again and then grab the hash result
128 from the DKIM layer. arc_parse_line() is called for all 3 line types,
129 gathering info for only 'i' and 'ip' tags from AAR headers,
130 for all tag types from AMS and AS headers.
133 For Sign, while running through the existing headers (before adding any for
134 this signing operation, we "take copies" of the headers, we call
135 arc_parse_line() gathering only the 'i' tag (instance) information.
139 /******************************************************************************/
141 /* We need a module init function, to check on the dkim module being present
142 (and we may as well stash it's modinfo ptr)
146 arc_init(void * dummy)
148 uschar * errstr = NULL;
149 if ((arc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
151 log_write(0, LOG_MAIN, "arc: %s", errstr);
158 arc_state = arc_state_reason = NULL;
159 arc_received_instance = 0;
162 /******************************************************************************/
165 /* Get the instance number from the header.
168 arc_instance_from_hdr(const arc_line * al)
170 const uschar * s = al->i.data;
171 if (!s || !al->i.len) return 0;
172 return (unsigned) atoi(CCS s);
180 while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
185 /* Locate instance struct on chain, inserting a new one if
186 needed. The chain is in increasing-instance-number order
187 by the "next" link, and we have a "prev" link also.
191 arc_find_set(arc_ctx * ctx, unsigned i)
193 arc_set ** pas, * as, * next, * prev;
195 for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
196 as = *pas; pas = &as->next)
198 if (as->instance > i) break;
199 if (as->instance == i)
201 DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
208 DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
209 *pas = as = store_get(sizeof(arc_set), GET_UNTAINTED);
210 memset(as, 0, sizeof(arc_set));
217 ctx->arcset_chain_last = as;
223 /* Insert a tag content into the line structure.
224 Note this is a reference to existing data, not a copy.
225 Check for already-seen tag.
226 The string-pointer is on the '=' for entry. Update it past the
227 content (to the ;) on return;
231 arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
235 blob * b = (blob *)(US al + loff);
238 /* [FWS] tag-value [FWS] */
240 if (b->data) return US"fail";
241 s = skip_fws(s); /* FWS */
244 while ((c = *s) && c != ';') { len++; s++; }
246 while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
247 { s--; len--; } /* FWS */
253 /* Inspect a header line, noting known tag fields.
254 Check for duplicate named tags.
256 See the file block comment for how this is used.
258 Return: NULL for good, or an error string
262 arc_parse_line(arc_line * al, header_line * h, unsigned off, line_extract_t l_ext)
264 uschar * s = h->text + off;
270 if (l_ext == le_all) /* need to grab rawsig_no_b */
272 al->rawsig_no_b_val.data = store_get(h->slen + 1, GET_TAINTED);
273 memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */
274 r = al->rawsig_no_b_val.data + off;
275 al->rawsig_no_b_val.len = off;
278 /* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */
285 uschar * fieldstart = s;
286 uschar * bstart = NULL, * bend;
288 /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
289 /*X or just a naked FQDN, in a AAR ! */
291 s = skip_fws(s); /* leading FWS */
294 if (!*(s = skip_fws(s))) break; /* FWS */
298 case 'a': /* a= AMS algorithm */
299 if (l_ext == le_all && *s == '=')
301 if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
303 /* substructure: algo-hash (eg. rsa-sha256) */
305 t = al->a_algo.data = al->a.data;
307 if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
309 if (*t++ != '-') return US"no '-' in 'a' value";
311 al->a_hash.len = al->a.len - i - 1;
321 case '=': /* b= AMS signature */
322 if (al->b.data) return US"already b data";
325 /* The signature can have FWS inserted in the content;
326 make a stripped copy */
328 while ((c = *++s) && c != ';')
329 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
330 g = string_catn(g, s, 1);
331 if (!g) return US"no b= value";
332 al->b.len = len_string_from_gstring(g, &al->b.data);
333 gstring_release_unused(g);
336 case 'h': /* bh= AMS body hash */
337 s = skip_fws(++s); /* FWS */
340 if (al->bh.data) return US"already bh data";
342 /* The bodyhash can have FWS inserted in the content;
343 make a stripped copy */
345 while ((c = *++s) && c != ';')
346 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
347 g = string_catn(g, s, 1);
348 if (!g) return US"no bh= value";
349 al->bh.len = len_string_from_gstring(g, &al->bh.data);
350 gstring_release_unused(g);
359 if (l_ext == le_all) switch (*s)
361 case '=': /* c= AMS canonicalisation */
362 if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
364 /* substructure: head/body (eg. relaxed/simple)) */
366 t = al->c_head.data = al->c.data;
368 if (!*t++ || ++i > al->a.len) break;
370 if (*t++ == '/') /* /body is optional */
373 al->c_body.len = al->c.len - i - 1;
377 al->c_body.data = US"simple";
381 case 'v': /* cv= AS validity */
384 if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s))
385 return US"cv tag dup";
389 case 'd': /* d= AMS domain */
390 if (l_ext == le_all && *s == '=')
391 if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s))
392 return US"d tag dup";
394 case 'h': /* h= AMS headers */
396 if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s))
397 return US"h tag dup";
399 case 'i': /* i= ARC set instance */
402 if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s))
403 return US"i tag dup";
404 if (l_ext == le_instance_only)
405 goto done; /* early-out */
408 case 'l': /* l= bodylength */
409 if (l_ext == le_all && *s == '=')
410 if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s))
411 return US"l tag dup";
414 if (*s == '=' && l_ext == le_all)
416 if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s))
417 return US"s tag dup";
419 else if ( l_ext == le_instance_plus_ip
420 && Ustrncmp(s, "mtp.remote-ip", 13) == 0)
421 { /* smtp.remote-ip= AAR reception data */
424 if (*s != '=') return US"smtp.remote_ip tag val";
425 if (arc_insert_tagvalue(al, offsetof(arc_line, ip), &s))
426 return US"ip tag dup";
431 while ((c = *s) && c != ';') s++; /* end of this tag=value */
432 if (c) s++; /* ; after tag-spec */
434 /* for all but the b= tag, copy the field including FWS. For the b=,
435 drop the tag content. */
440 size_t n = bstart - fieldstart;
441 memcpy(r, fieldstart, n); /* FWS "b=" */
443 al->rawsig_no_b_val.len += n;
445 memcpy(r, bend, n); /* FWS ";" */
447 al->rawsig_no_b_val.len += n;
451 size_t n = s - fieldstart;
452 memcpy(r, fieldstart, n);
454 al->rawsig_no_b_val.len += n;
462 /* debug_printf("%s: finshed\n", __FUNCTION__); */
467 /* Insert one header line in the correct set of the chain,
468 adding instances as needed and checking for duplicate lines.
472 arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
473 line_extract_t l_ext, arc_line ** alp_ret)
477 arc_line * al = store_get(sizeof(arc_line), GET_UNTAINTED), ** alp;
480 memset(al, 0, sizeof(arc_line));
482 if ((e = arc_parse_line(al, h, off, l_ext)))
484 DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
485 return string_sprintf("line parse: %s", e);
487 if (!(i = arc_instance_from_hdr(al))) return US"instance find";
488 if (i > 50) return US"overlarge instance number";
489 if (!(as = arc_find_set(ctx, i))) return US"set find";
490 if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
493 if (alp_ret) *alp_ret = al;
499 /* Called for both Sign and Verify */
501 static const uschar *
502 arc_try_header(arc_ctx * ctx, header_line * h, BOOL is_signing)
506 /*debug_printf("consider hdr '%s'\n", h->text);*/
507 if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
513 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
515 debug_printf("ARC: found AAR: %.*s\n", len, h->text);
517 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
518 is_signing ? le_instance_only : le_instance_plus_ip, NULL)))
520 DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
521 return string_sprintf("inserting AAR: %s", e);
524 else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
532 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
534 debug_printf("ARC: found AMS: %.*s\n", len, h->text);
536 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
537 is_signing ? le_instance_only : le_all, &ams)))
539 DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
540 return string_sprintf("inserting AMS: %s", e);
546 ams->c_head.data = US"simple"; ams->c_head.len = 6;
547 ams->c_body = ams->c_head;
550 else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
556 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
558 debug_printf("ARC: found AS: %.*s\n", len, h->text);
560 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
561 is_signing ? le_instance_only : le_all, NULL)))
563 DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
564 return string_sprintf("inserting AS: %s", e);
572 /* Gather the chain of arc sets from the headers.
573 Check for duplicates while that is done. Also build the
574 reverse-order headers list.
575 Called on an ACL verify=arc condition.
577 Return: ARC state if determined, eg. by lack of any ARC chain.
580 static const uschar *
581 arc_vfy_collect_hdrs(arc_ctx * ctx)
584 hdr_rlist * r = NULL, * rprev = NULL;
587 DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
588 for (h = header_list; h; h = h->next)
590 r = store_get(sizeof(hdr_rlist), GET_UNTAINTED);
596 if ((e = arc_try_header(ctx, h, FALSE)))
598 arc_state_reason = string_sprintf("collecting headers: %s", e);
604 if (!ctx->arcset_chain) return US"none";
610 arc_cv_match(arc_line * al, const uschar * s)
612 return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
615 /******************************************************************************/
616 /* Service routines provided by the dkim module */
619 arc_dkim_hashname_blob_to_type(const blob * name)
621 typedef int (*fn_t)(const blob *);
622 return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHNAME_TO_TYPE]) (name);
625 arc_dkim_hashtype_to_method(int hashtype)
627 typedef hashmethod (*fn_t)(int);
628 return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHTYPE_TO_METHOD]) (hashtype);
631 arc_dkim_hashname_blob_to_method(const blob * name)
633 typedef hashmethod (*fn_t)(const blob *);
634 return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHNAME_TO_METHOD]) (name);
637 /******************************************************************************/
639 /* Do a "relaxed" canonicalization of a header */
641 arc_relax_header_n(const uschar * text, int len, BOOL append_crlf)
643 typedef uschar * (*fn_t)(const uschar *, int, BOOL);
644 return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HEADER_RELAX])
645 (text, len, append_crlf);
650 /* Return the hash of headers from the message that the AMS claims it
655 arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
657 const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
661 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
662 hashmethod hm = arc_dkim_hashname_blob_to_method(&ams->a_hash);
667 if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
670 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
674 /* For each headername in the list from the AMS (walking in order)
675 walk the message headers in reverse order, adding to the hash any
676 found for the first time. For that last point, maintain used-marks
677 on the list of message headers. */
679 DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
681 for (r = headers_rlist; r; r = r->prev)
683 while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
684 for (r = headers_rlist; r; r = r->prev)
686 && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
689 if (relaxed) s = arc_relax_header_n(s, r->h->slen, TRUE);
691 DEBUG(D_acl) debug_printf("%Z\n", s);
692 exim_sha_update_string(&hhash_ctx, s);
697 /* Finally add in the signature header (with the b= tag stripped); no CRLF */
699 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
701 len = Ustrlen(s = arc_relax_header_n(s, len, FALSE));
702 DEBUG(D_acl) debug_printf("%.*Z\n", len, s);
703 exim_sha_update(&hhash_ctx, s, len);
705 exim_sha_finish(&hhash_ctx, hhash);
707 { debug_printf("ARC: header hash: %.*H\n", hhash->len, hhash->data); }
715 arc_line_to_pubkey(arc_line * al, const uschar ** errstr)
717 typedef const uschar * (*fn_t)(const uschar *, blob **, const uschar **);
719 const uschar * hashes;
720 const uschar * srvtype =
721 (((fn_t *) arc_dkim_mod_info->functions)[DKIM_DNS_PUBKEY])
722 (string_sprintf("%b._domainkey.%b", &al->s, &al->d), &pubkey, &hashes);
725 { *errstr = US"pubkey dns lookup fail"; return NULL; }
726 if ((Ustrcmp(srvtype, "*") != 0 && Ustrcmp(srvtype, "email") != 0))
728 *errstr = string_sprintf("pubkey format error: srvtype '%s'", srvtype);
732 /* If the pubkey limits use to specified hashes, reject unusable
733 signatures. XXX should we have looked for multiple dns records? */
737 const uschar * list = hashes, * ele;
740 while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
741 if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
744 DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%b\n", hashes, &al->a);
745 *errstr = US"no usable sig for this pubkey hash list";
755 /* Set up a body hashing method on the given signature-context
756 (creates a new one if needed, or uses an already-present one).
759 signing TRUE for signing, FALSE for verification
760 c canonicalization spec, text form
762 bodylen byte count for message body
764 Return: pointer to hashing method struct
767 static pdkim_bodyhash *
768 arc_set_bodyhash(BOOL signing,
769 const blob * c, const blob * ah, long bodylen)
771 typedef pdkim_bodyhash * (*fn_t)(BOOL,
772 const blob * canon, const blob * hash, long bodylen);
774 return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SET_BODYHASH])
775 (signing, c, ah, bodylen);
781 static pdkim_bodyhash *
782 arc_ams_setup_vfy_bodyhash(arc_line * ams)
785 long bodylen = ams->l.data
786 ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10)
791 c->data = US"simple"; /* RFC 6376 (DKIM) default */
795 return arc_set_bodyhash(FALSE, c, &ams->a_hash, bodylen);
801 arc_decode_base64(const uschar * str, blob * b)
803 int dlen = b64decode(str, &b->data, str);
804 if (dlen < 0) b->data = NULL;
811 arc_sig_verify(arc_set * as, arc_line * al, hashmethod hm,
812 blob * hhash_computed, blob * sighash,
813 const uschar * why, const uschar ** errstr_p)
816 const uschar * errstr = NULL;
819 (const blob *, const blob *, hashmethod, const blob *, const uschar **);
821 /* Get the public key from DNS */
824 if (!(pubkey = arc_line_to_pubkey(al, &errstr)))
826 *errstr_p = string_sprintf("%s (%s)", errstr, why);
830 rc = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIG_VERIFY])
831 (sighash, hhash_computed, hm, pubkey, &errstr);
838 debug_printf("ARC i=%d %s verify %s\n", as->instance, why, errstr);
841 DEBUG(D_acl) debug_printf("ARC verify %s init: %s\n", why, errstr);
850 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
851 and without a DKIM v= tag.
854 static const uschar *
855 arc_ams_verify(arc_ctx * ctx, arc_set * as)
857 arc_line * ams = as->hdr_ams;
862 const uschar * errstr;
865 as->ams_verify_done = US"in-progress";
867 /* Check the AMS has all the required tags:
871 "d=" domain (for key lookup)
872 "h=" headers (included in signature)
873 "s=" key-selector (for key lookup)
875 if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
876 || !ams->h.data || !ams->s.data)
878 as->ams_verify_done = arc_state_reason = US"required tag missing";
883 /* The bodyhash should have been created earlier, and the dkim code should
884 have managed calculating it during message input. Find the reference to it. */
886 if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
888 as->ams_verify_done = arc_state_reason = US"internal hash setup error";
894 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
895 " Body %b computed: %.*H\n",
896 as->instance, b->signed_body_bytes,
897 &ams->a_hash, b->bh.len, b->bh.data);
900 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
903 || (arc_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
904 || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
909 debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
910 debug_printf("%.*H\n", sighash.len, sighash.data);
911 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
913 return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
916 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
918 /* We know the b-tag blob is of a nul-term string, so safe as a string */
919 arc_decode_base64(ams->b.data, &sighash);
921 arc_get_verify_hhash(ctx, ams, &hhash_computed);
923 if ((hm = arc_dkim_hashname_blob_to_method(&ams->a_hash)) < 0)
925 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify bad a_hash\n", as->instance);
926 return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
929 rc = arc_sig_verify(as, ams, hm, &hhash_computed, &sighash, US"AMS", &errstr);
931 return as->ams_verify_done = arc_state_reason =
932 rc == FAIL ? US"AMS sig nonverify" : errstr;
934 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
935 as->ams_verify_passed = TRUE;
941 /* Check the sets are instance-continuous and that all
942 members are present. Check that no arc_seals are "fail".
943 Set the highest instance number global.
944 Verify the latest AMS.
947 arc_headers_check(arc_ctx * ctx)
951 BOOL ams_fail_found = FALSE;
953 if (!(as = ctx->arcset_chain_last))
956 for(inst = as->instance; as; as = as->prev, inst--)
958 if (as->instance != inst)
959 arc_state_reason = string_sprintf("i=%d (sequence; expected %d)",
961 else if (!as->hdr_aar || !as->hdr_ams || !as->hdr_as)
962 arc_state_reason = string_sprintf("i=%d (missing header)", as->instance);
963 else if (arc_cv_match(as->hdr_as, US"fail"))
964 arc_state_reason = string_sprintf("i=%d (cv)", as->instance);
968 DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
972 /* Evaluate the oldest-pass AMS validation while we're here.
973 It does not affect the AS chain validation but is reported as
977 if (arc_ams_verify(ctx, as))
978 ams_fail_found = TRUE;
980 arc_oldest_pass = inst;
981 arc_state_reason = NULL;
985 arc_state_reason = string_sprintf("(sequence; expected i=%d)", inst);
986 DEBUG(D_acl) debug_printf("ARC chain fail %s\n", arc_state_reason);
990 arc_received = ctx->arcset_chain_last;
991 arc_received_instance = arc_received->instance;
993 /* We can skip the latest-AMS validation, if we already did it. */
995 as = ctx->arcset_chain_last;
996 if (!as->ams_verify_passed)
998 if (as->ams_verify_done)
1000 arc_state_reason = as->ams_verify_done;
1003 if (!!arc_ams_verify(ctx, as))
1010 /******************************************************************************/
1011 static const uschar *
1012 arc_seal_verify(arc_ctx * ctx, arc_set * as)
1014 arc_line * hdr_as = as->hdr_as;
1018 blob hhash_computed;
1020 const uschar * errstr;
1023 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
1025 1. If the value of the "cv" tag on that seal is "fail", the
1026 chain state is "fail" and the algorithm stops here. (This
1027 step SHOULD be skipped if the earlier step (2.1) was
1030 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1031 == "none" && i != 1)) then the chain state is "fail" and the
1032 algorithm stops here (note that the ordering of the logic is
1033 structured for short-circuit evaluation).
1036 if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none")
1037 || arc_cv_match(hdr_as, US"none") && as->instance != 1
1040 arc_state_reason = US"seal cv state";
1045 3. Initialize a hash function corresponding to the "a" tag of
1049 hm = arc_dkim_hashname_blob_to_method(&hdr_as->a_hash);
1051 if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
1054 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
1055 arc_state_reason = US"seal hash setup error";
1060 4. Compute the canonicalized form of the ARC header fields, in
1061 the order described in Section 5.4.2, using the "relaxed"
1062 header canonicalization defined in Section 3.4.2 of
1063 [RFC6376]. Pass the canonicalized result to the hash
1066 Headers are CRLF-separated, but the last one is not crlf-terminated.
1069 DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
1070 for (as2 = ctx->arcset_chain;
1071 as2 && as2->instance <= as->instance;
1079 if (!(s = al->relaxed))
1080 al->relaxed = s = arc_relax_header_n(al->complete->text,
1081 al->complete->slen, TRUE);
1083 DEBUG(D_acl) debug_printf("%Z\n", s);
1084 exim_sha_update(&hhash_ctx, s, len);
1087 if (!(s = al->relaxed))
1088 al->relaxed = s = arc_relax_header_n(al->complete->text,
1089 al->complete->slen, TRUE);
1091 DEBUG(D_acl) debug_printf("%Z\n", s);
1092 exim_sha_update(&hhash_ctx, s, len);
1095 if (as2->instance == as->instance)
1096 s = arc_relax_header_n(al->rawsig_no_b_val.data,
1097 al->rawsig_no_b_val.len, FALSE);
1098 else if (!(s = al->relaxed))
1099 al->relaxed = s = arc_relax_header_n(al->complete->text,
1100 al->complete->slen, TRUE);
1102 DEBUG(D_acl) debug_printf("%Z\n", s);
1103 exim_sha_update(&hhash_ctx, s, len);
1107 5. Retrieve the final digest from the hash function.
1110 exim_sha_finish(&hhash_ctx, &hhash_computed);
1113 debug_printf("ARC i=%d AS Header %b computed: ",
1114 as->instance, &hdr_as->a_hash);
1115 debug_printf("%.*H\n", hhash_computed.len, hhash_computed.data);
1120 6. Retrieve the public key identified by the "s" and "d" tags in
1121 the ARC-Seal, as described in Section 4.1.6.
1123 Done below, in arc_sig_verify().
1125 7. Determine whether the signature portion ("b" tag) of the ARC-
1126 Seal and the digest computed above are valid according to the
1127 public key. (See also Section Section 8.4 for failure case
1130 8. If the signature is not valid, the chain state is "fail" and
1131 the algorithm stops here.
1134 /* We know the b-tag blob is of a nul-term string, so safe as a string */
1135 arc_decode_base64(hdr_as->b.data, &sighash);
1137 rc = arc_sig_verify(as, hdr_as, hm, &hhash_computed, &sighash, US"AS", &errstr);
1140 if (rc == FAIL) arc_state_reason = US"seal sigverify error";
1144 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
1149 static const uschar *
1150 arc_verify_seals(arc_ctx * ctx)
1152 arc_set * as = ctx->arcset_chain_last;
1157 for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail";
1159 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
1162 /******************************************************************************/
1164 /* Do ARC verification. Called from DATA ACL, on a verify = arc
1165 condition. Set arc_state, and compare with given list of acceptable states.
1168 condlist list of resulta to test for OK/FAIL return;
1169 NULL for default list
1171 Return: OK/FAIL, or DEFER on error
1175 acl_verify_arc(const uschar * condlist)
1179 memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
1181 /* AS evaluation, per
1182 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
1184 /* 1. Collect all ARC sets currently on the message. If there were
1185 none, the ARC state is "none" and the algorithm stops here.
1188 if ((res = arc_vfy_collect_hdrs(&arc_verify_ctx)))
1191 /* 2. If the form of any ARC set is invalid (e.g., does not contain
1192 exactly one of each of the three ARC-specific header fields),
1193 then the chain state is "fail" and the algorithm stops here.
1195 1. To avoid the overhead of unnecessary computation and delay
1196 from crypto and DNS operations, the cv value for all ARC-
1197 Seal(s) MAY be checked at this point. If any of the values
1198 are "fail", then the overall state of the chain is "fail" and
1199 the algorithm stops here.
1201 3. Conduct verification of the ARC-Message-Signature header field
1202 bearing the highest instance number. If this verification fails,
1203 then the chain state is "fail" and the algorithm stops here.
1206 if ((res = arc_headers_check(&arc_verify_ctx)))
1209 /* 4. For each ARC-Seal from the "N"th instance to the first, apply the
1212 1. If the value of the "cv" tag on that seal is "fail", the
1213 chain state is "fail" and the algorithm stops here. (This
1214 step SHOULD be skipped if the earlier step (2.1) was
1217 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1218 == "none" && i != 1)) then the chain state is "fail" and the
1219 algorithm stops here (note that the ordering of the logic is
1220 structured for short-circuit evaluation).
1222 3. Initialize a hash function corresponding to the "a" tag of
1225 4. Compute the canonicalized form of the ARC header fields, in
1226 the order described in Section 5.4.2, using the "relaxed"
1227 header canonicalization defined in Section 3.4.2 of
1228 [RFC6376]. Pass the canonicalized result to the hash
1231 5. Retrieve the final digest from the hash function.
1233 6. Retrieve the public key identified by the "s" and "d" tags in
1234 the ARC-Seal, as described in Section 4.1.6.
1236 7. Determine whether the signature portion ("b" tag) of the ARC-
1237 Seal and the digest computed above are valid according to the
1238 public key. (See also Section Section 8.4 for failure case
1241 8. If the signature is not valid, the chain state is "fail" and
1242 the algorithm stops here.
1244 5. If all seals pass validation, then the chain state is "pass", and
1245 the algorithm is complete.
1248 if ((res = arc_verify_seals(&arc_verify_ctx)))
1258 if (!(arc_state = res))
1261 DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
1262 arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");
1264 if (!condlist) condlist = US"none:pass";
1265 while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
1266 if (Ustrcmp(res, cond) == 0) return OK;
1274 return arc_state && Ustrcmp(arc_state, "pass") == 0;
1277 /******************************************************************************/
1279 /* Prepend the header to the rlist */
1282 arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1284 hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), GET_UNTAINTED);
1285 header_line * h = r->h = (header_line *)(r+1);
1298 /* Walk the given headers strings identifying each header, and construct
1299 a reverse-order list.
1303 arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1306 hdr_rlist * rheaders = NULL;
1308 s = sigheaders ? sigheaders->s : NULL;
1311 const uschar * s2 = s;
1313 /* This works for either NL or CRLF lines; also nul-termination */
1315 if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1316 s2++; /* move past end of line */
1318 rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1326 /* Return the A-R content, without identity, with line-ending and
1330 arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1333 int ilen = Ustrlen(identity);
1336 for(h = headers; h; h = h->next)
1338 uschar * s = h->text, c;
1341 if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1342 s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */
1344 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1345 s++, len--; /* FWS */
1346 if (Ustrncmp(s, identity, ilen) != 0) continue;
1347 s += ilen; len -= ilen; /* identity */
1348 if (len <= 0) continue;
1349 if ((c = *s) && c == ';') s++, len--; /* identity terminator */
1351 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1352 s++, len--; /* FWS */
1353 if (len <= 0) continue;
1363 /* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
1366 arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1367 const uschar * identity, int instance, blob * ar)
1369 int aar_off = gstring_length(g);
1371 store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), GET_UNTAINTED);
1372 arc_line * al = (arc_line *)(as+1);
1373 header_line * h = (header_line *)(al+1);
1375 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1376 g = string_fmt_append(g, " i=%d; %s; smtp.remote-ip=%s;\r\n\t%b",
1377 instance, identity, sender_host_address, ar);
1379 h->slen = g->ptr - aar_off;
1380 h->text = g->s + aar_off;
1383 as->prev = ctx->arcset_chain_last;
1384 as->instance = instance;
1387 ctx->arcset_chain = as;
1389 ctx->arcset_chain_last->next = as;
1390 ctx->arcset_chain_last = as;
1392 DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1399 arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1400 blob * sig, const uschar * why)
1402 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1404 : arc_dkim_hashtype_to_method(hashtype);
1407 const uschar * errstr;
1408 typedef const uschar * (*fn_t)
1409 (const blob *, hashmethod, const uschar *, blob *);
1414 debug_printf("ARC: %s header data for signing:\n", why);
1415 debug_printf("%.*Z\n", hdata->ptr, hdata->s);
1417 (void) exim_sha_init(&hhash_ctx, hm);
1418 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1419 exim_sha_finish(&hhash_ctx, &hhash);
1420 debug_printf("ARC: header hash: %.*H\n", hhash.len, hhash.data);
1423 if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1426 (void) exim_sha_init(&hhash_ctx, arc_dkim_hashtype_to_method(hashtype));
1427 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1428 exim_sha_finish(&hhash_ctx, &hhash);
1432 hhash.data = hdata->s;
1433 hhash.len = hdata->ptr;
1436 errstr = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIGN_DATA])
1437 (&hhash, hm, privkey, sig);
1440 log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
1442 debug_printf("private key, or private-key file content, was: '%s'\n",
1453 arc_sign_append_sig(gstring * g, blob * sig)
1455 /*debug_printf("%s: raw sig %.*H\n", __FUNCTION__, sig->len, sig->data);*/
1456 sig->data = b64encode(sig->data, sig->len);
1457 sig->len = Ustrlen(sig->data);
1460 int len = MIN(sig->len, 74);
1461 g = string_catn(g, sig->data, len);
1462 if ((sig->len -= len) == 0) break;
1464 g = string_catn(g, US"\r\n\t ", 5);
1466 g = string_catn(g, US";\r\n", 3);
1467 gstring_release_unused(g);
1468 string_from_gstring(g);
1473 /* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
1476 arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1477 const uschar * identity, const uschar * selector, blob * bodyhash,
1478 hdr_rlist * rheaders, const uschar * privkey, unsigned options)
1481 gstring * hdata = NULL;
1483 const blob ams_h = {.data = US"sha256", .len = 6}; /*XXX hardwired */
1484 int hashtype = arc_dkim_hashname_blob_to_type(&ams_h);
1487 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED);
1488 header_line * h = (header_line *)(al+1);
1490 /* debug_printf("%s\n", __FUNCTION__); */
1492 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1494 ams_off = gstring_length(g);
1495 g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s",
1496 ARC_HDR_AMS, instance, identity, selector); /*XXX hardwired a= */
1497 if (options & ARC_SIGN_OPT_TSTAMP)
1498 g = string_fmt_append(g, "; t=%lu", (u_long)now);
1499 if (options & ARC_SIGN_OPT_EXPIRE)
1500 g = string_fmt_append(g, "; x=%lu", (u_long)expire);
1501 g = string_fmt_append(g, ";\r\n\tbh=%s;\r\n\th=",
1502 b64encode(bodyhash->data, bodyhash->len));
1504 for(col = 3; rheaders; rheaders = rheaders->prev)
1506 const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1507 uschar * name, * htext = rheaders->h->text;
1510 /* Spot headers of interest */
1512 while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1514 int len = Ustrlen(name);
1515 if (strncasecmp(CCS htext, CCS name, len) == 0)
1517 /* If too long, fold line in h= field */
1519 if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3;
1521 /* Add name to h= list */
1523 g = string_catn(g, name, len);
1524 g = string_catn(g, US":", 1);
1527 /* Accumulate header for hashing/signing */
1529 hdata = string_cat(hdata,
1530 arc_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */
1536 /* Lose the last colon from the h= list */
1538 gstring_trim_trailing(g, ':');
1540 g = string_catn(g, US";\r\n\tb=;", 7);
1542 /* Include the pseudo-header in the accumulation */
1544 s = arc_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
1545 hdata = string_cat(hdata, s);
1547 /* Calculate the signature from the accumulation */
1548 /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1550 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1553 /* Lose the trailing semicolon from the psuedo-header, and append the signature
1554 (folded over lines) and termination to complete it. */
1557 g = arc_sign_append_sig(g, &sig);
1559 h->slen = g->ptr - ams_off;
1560 h->text = g->s + ams_off;
1562 ctx->arcset_chain_last->hdr_ams = al;
1564 DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1570 /* Look for an arc= result in an A-R header blob. We know that its data
1571 happens to be a NUL-term string. */
1574 arc_ar_cv_status(blob * ar)
1576 const uschar * resinfo = ar->data;
1578 uschar * methodspec, * s;
1580 while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1581 if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1584 for (s = methodspec += 4;
1585 (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1586 return string_copyn(methodspec, s - methodspec);
1593 /* Build the AS header and prepend it */
1596 arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1597 int instance, const uschar * identity, const uschar * selector, blob * ar,
1598 const uschar * privkey, unsigned options)
1601 uschar * status = arc_ar_cv_status(ar);
1602 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED);
1603 header_line * h = (header_line *)(al+1);
1604 uschar * badline_str;
1606 gstring * hdata = NULL;
1607 const blob as_h = {.data = US"sha256", .len = 6}; /*XXX hardwired */
1608 int hashtype = arc_dkim_hashname_blob_to_type(&as_h);
1614 - no h= tag; implicit coverage
1615 - arc status from A-R
1617 - coverage is just the new ARC set
1618 including self (but with an empty b= in self)
1620 - all ARC set headers, set-number order, aar then ams then as,
1621 including self (but with an empty b= in self)
1623 DEBUG(D_transport) debug_printf("ARC: building AS for status '%s'\n", status);
1625 /* Construct the AS except for the signature */
1627 arcset = string_append(NULL, 9,
1629 US" i=", string_sprintf("%d", instance),
1631 US"; a=rsa-sha256; d=", identity, /*XXX hardwired */
1632 US"; s=", selector); /*XXX same as AMS */
1633 if (options & ARC_SIGN_OPT_TSTAMP)
1634 arcset = string_append(arcset, 2,
1635 US"; t=", string_sprintf("%lu", (u_long)now));
1636 arcset = string_cat(arcset,
1639 h->slen = arcset->ptr;
1640 h->text = arcset->s;
1642 ctx->arcset_chain_last->hdr_as = al;
1644 /* For any but "fail" chain-verify status, walk the entire chain in order by
1645 instance. For fail, only the new arc-set. Accumulate the elements walked. */
1647 for (arc_set * as = Ustrcmp(status, US"fail") == 0
1648 ? ctx->arcset_chain_last : ctx->arcset_chain;
1652 /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
1653 is required per standard. */
1655 badline_str = US"aar";
1656 if (!(l = as->hdr_aar)) goto badline;
1658 hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, TRUE));
1659 badline_str = US"ams";
1660 if (!(l = as->hdr_ams)) goto badline;
1662 hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, TRUE));
1663 badline_str = US"as";
1664 if (!(l = as->hdr_as)) goto badline;
1666 hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, !!as->next));
1669 /* Calculate the signature from the accumulation */
1671 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1674 /* Lose the trailing semicolon */
1676 arcset = arc_sign_append_sig(arcset, &sig);
1677 DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s);
1679 /* Finally, append the AMS and AAR to the new AS */
1681 return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1685 debug_printf("ARC: while building AS, missing %s in chain\n", badline_str);
1690 /**************************************/
1692 static pdkim_bodyhash *
1693 arc_ams_setup_sign_bodyhash(void)
1695 blob canon = {.data = US"relaxed", .len = 7}; /*XXX hardwired */
1696 blob hash = {.data = US"sha256", .len = 6}; /*XXX hardwired */
1698 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1700 return arc_set_bodyhash(TRUE, &canon, &hash, -1);
1705 /* Module API: initilise, and set up a bodyhash for AMS */
1710 blob canon = {.data = US"relaxed", .len = 7}; /*XXX hardwired */
1711 blob hash = {.data = US"sha256", .len = 6}; /*XXX hardwired */
1713 memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
1714 headers_rlist = NULL;
1716 (void) arc_ams_setup_sign_bodyhash();
1721 /* A "normal" header line, identified by DKIM processing. These arrive before
1722 the call to arc_sign(), which carries any newly-created DKIM headers - and
1723 those go textually before the normal ones in the message.
1725 We have to take the feed from DKIM as, in the transport-filter case, the
1726 headers are not in memory at the time of the call to arc_sign().
1728 Take a copy of the header and construct a reverse-order list.
1729 Also parse ARC-chain headers and build the chain struct, retaining pointers
1733 static const uschar *
1734 arc_header_sign_feed(gstring * g)
1736 uschar * s = string_copy_from_gstring(g);
1737 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1738 return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1743 /* Per RFCs 6376, 7489 the only allowed chars in either an ADMD id
1744 or a selector are ALPHA/DIGGIT/'-'/'.'
1746 Check, to help catch misconfigurations such as a missing selector
1747 element in the arc_sign list.
1751 arc_valid_id(const uschar * s)
1753 for (uschar c; c = *s++; )
1754 if (!isalnum(c) && c != '-' && c != '.') return FALSE;
1760 /* Module API: ARC signing.
1762 Called from the smtp transport, if the arc_sign option is set.
1763 The dkim_exim_sign() function has already been called, so will have hashed the
1764 message body for us so long as we requested a hash previously.
1767 signspec Three-element colon-sep list: identity, selector, privkey.
1768 Optional fourth element: comma-sep list of options.
1770 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1774 Set of headers to prepend to the message, including the supplied sigheaders
1775 but not the plainheaders.
1779 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1781 const uschar * identity, * selector, * privkey, * opts, * s;
1782 unsigned options = 0;
1784 header_line * headers;
1785 hdr_rlist * rheaders;
1793 /* Parse the signing specification */
1795 if (!(identity = string_nextinlist(&signspec, &sep, NULL, 0)) || !*identity)
1796 { s = US"identity"; goto bad_arg_ret; }
1797 if (!(selector = string_nextinlist(&signspec, &sep, NULL, 0)) || !*selector)
1798 { s = US"selector"; goto bad_arg_ret; }
1799 if (!(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1800 { s = US"privkey"; goto bad_arg_ret; }
1801 if (!arc_valid_id(identity))
1802 { s = US"identity"; goto bad_arg_ret; }
1803 if (!arc_valid_id(selector))
1804 { s = US"selector"; goto bad_arg_ret; }
1805 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1806 goto ret_sigheaders;
1808 if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
1811 while ((s = string_nextinlist(&opts, &osep, NULL, 0)))
1812 if (Ustrcmp(s, "timestamps") == 0)
1814 options |= ARC_SIGN_OPT_TSTAMP;
1815 if (!now) now = time(NULL);
1817 else if (Ustrncmp(s, "expire", 6) == 0)
1819 options |= ARC_SIGN_OPT_EXPIRE;
1820 if (*(s += 6) == '=')
1823 if (!(expire = (time_t)atoi(CS ++s)))
1824 expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA;
1825 if (!now) now = time(NULL);
1829 expire = (time_t)atol(CS s);
1832 if (!now) now = time(NULL);
1833 expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA;
1838 DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1840 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
1841 Then scan the list for an A-R header. */
1843 string_from_gstring(sigheaders);
1844 if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1847 for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
1851 /* Finally, build a normal-order headers list */
1852 /*XXX only needed for hunt-the-AR? */
1853 /*XXX also, we really should be accepting any number of ADMD-matching ARs */
1855 header_line * hnext = NULL;
1856 for (rheaders = headers_rlist; rheaders;
1857 hnext = rheaders->h, rheaders = rheaders->prev)
1858 rheaders->h->next = hnext;
1862 if (!(arc_sign_find_ar(headers, identity, &ar)))
1864 log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
1865 goto ret_sigheaders;
1868 /* We previously built the data-struct for the existing ARC chain, if any, using a headers
1869 feed from the DKIM module. Use that to give the instance number for the ARC set we are
1873 if (arc_sign_ctx.arcset_chain_last)
1874 debug_printf("ARC: existing chain highest instance: %d\n",
1875 arc_sign_ctx.arcset_chain_last->instance);
1877 debug_printf("ARC: no existing chain\n");
1879 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1883 - copy the A-R; prepend i= & identity
1886 g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1890 - Looks fairly like a DKIM sig
1891 - Cover all DKIM sig headers as well as the usuals
1894 - we must have requested a suitable bodyhash previously
1895 [done in arc_sign_init()]
1898 b = arc_ams_setup_sign_bodyhash();
1899 g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1900 &b->bh, headers_rlist, privkey, options);
1905 - no h= tag; implicit coverage
1906 - arc status from A-R
1908 - coverage is just the new ARC set
1909 including self (but with an empty b= in self)
1911 - all ARC set headers, set-number order, aar then ams then as,
1912 including self (but with an empty b= in self)
1916 g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
1919 /* Finally, append the dkim headers and return the lot. */
1921 if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
1924 if (!g) return string_get(1);
1925 (void) string_from_gstring(g);
1926 gstring_release_unused(g);
1931 log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", s);
1938 /******************************************************************************/
1940 /* Check to see if the line is an AMS and if so, set up to validate it.
1941 Called from the DKIM input processing. This must be done now as the message
1942 body data is hashed during input.
1944 We call the DKIM code to request a body-hash; it has the facility already
1945 and the hash parameters might be common with other requests.
1948 static const uschar *
1949 arc_header_vfy_feed(gstring * g)
1956 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1958 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1959 /* Parse the AMS header */
1961 memset(&al, 0, sizeof(arc_line));
1963 h.slen = len_string_from_gstring(g, &h.text);
1964 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, le_all)))
1966 DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1970 if (!al.a_hash.data)
1972 DEBUG(D_acl) debug_printf("ARC: no a_hash from '%.*s'\n", h.slen, h.text);
1979 al.c_body.data = US"simple"; al.c_body.len = 6;
1980 al.c_head = al.c_body;
1983 /* Ask the dkim code to calc a bodyhash with those specs */
1985 if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1986 return US"dkim hash setup fail";
1988 /* Discard the reference; search again at verify time, knowing that one
1989 should have been created here. */
1994 return US"line parsing error";
1999 /* Module API: A header line has been identified by DKIM processing;
2000 feed it to ARC processing.
2004 is_vfy TRUE for verify mode or FALSE for signing mode
2007 NULL for success, or an error string (probably unused)
2010 static const uschar *
2011 arc_header_feed(gstring * g, BOOL is_vfy)
2013 return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
2018 /******************************************************************************/
2020 /* Construct the list of domains from the ARC chain after validation */
2023 fn_arc_domains(void)
2029 for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++)
2031 arc_line * hdr_as = as->hdr_as;
2034 blob * d = &hdr_as->d;
2036 for (; inst < as->instance; inst++)
2037 g = string_catn(g, US":", 1);
2039 g = d->data && d->len
2040 ? string_append_listele_n(g, ':', d->data, d->len)
2041 : string_catn(g, US":", 1);
2044 g = string_catn(g, US":", 1);
2046 if (!g) return US"";
2047 return string_from_gstring(g);
2051 /* Construct an Authentication-Results header portion, for the ARC module */
2054 authres_arc(gstring * g)
2058 int start = 0; /* Compiler quietening */
2059 DEBUG(D_acl) start = gstring_length(g);
2061 g = string_append(g, 2, US";\n\tarc=", arc_state);
2062 if (arc_received_instance > 0)
2064 g = string_fmt_append(g, " (i=%d)", arc_received_instance);
2065 if (arc_state_reason)
2066 g = string_append(g, 3, US"(", arc_state_reason, US")");
2068 g = string_fmt_append(g, " header.s=%b arc.oldest-pass=%d",
2069 &arc_received->hdr_ams->s,
2072 if (sender_host_address)
2073 g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
2075 else if (arc_state_reason)
2076 g = string_append(g, 3, US" (", arc_state_reason, US")");
2077 DEBUG(D_acl) debug_printf("ARC:\tauthres '%.*s'\n",
2078 gstring_length(g) - start - 3, g->s + start + 3);
2081 DEBUG(D_acl) debug_printf("ARC:\tno authres\n");
2086 # ifdef SUPPORT_DMARC
2088 /* Module API: obtain ARC info for DMARC history.
2090 gp pointer for return of arcset info string
2092 status string, or NULL if none
2095 static const uschar *
2096 arc_arcset_string(gstring ** gp)
2102 /*XXX would we prefer this backwards? */
2103 for (arc_set * as = arc_verify_ctx.arcset_chain; as; as = as->next)
2105 arc_line * line = as->hdr_as;
2108 g = string_append_listele_fmt(g, ',', " (\"i\":%u" /*)*/
2111 as->instance, &line->d, &line->s);
2113 if ((line = as->hdr_aar))
2115 blob * ip = &line->ip;
2116 if (ip->data && ip->len)
2117 g = string_fmt_append(g, ", \"ip\":\"%#b\"", ip);
2120 g = string_catn(g, US")", 1);
2130 /******************************************************************************/
2133 static void * arc_functions[] = {
2134 [ARC_VERIFY] = acl_verify_arc,
2135 [ARC_HEADER_FEED] = arc_header_feed,
2136 [ARC_STATE_IS_PASS] = arc_is_pass,
2137 [ARC_SIGN_INIT] = arc_sign_init,
2138 [ARC_SIGN] = arc_sign,
2139 # ifdef SUPPORT_DMARC
2140 [ARC_ARCSET_INFO] = arc_arcset_string,
2144 static var_entry arc_variables[] = {
2145 { "arc_domains", vtype_string_func, (void *) &fn_arc_domains },
2146 { "arc_oldest_pass", vtype_int, &arc_oldest_pass },
2147 { "arc_state", vtype_stringptr, &arc_state },
2148 { "arc_state_reason", vtype_stringptr, &arc_state_reason },
2151 misc_module_info arc_module_info =
2155 .dyn_magic = MISC_MODULE_MAGIC,
2158 .smtp_reset = arc_smtp_reset,
2159 .authres = authres_arc,
2161 .functions = arc_functions,
2162 .functions_count = nelem(arc_functions),
2164 .variables = arc_variables,
2165 .variables_count = nelem(arc_variables),
2168 # endif /* DISABLE_DKIM */
2169 #endif /* EXPERIMENTAL_ARC */