1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4 /* Experimental ARC support for Exim
5 Copyright (c) Jeremy Harris 2018
10 #ifdef EXPERIMENTAL_ARC
11 # if !defined SUPPORT_SPF
12 # error SPF must also be enabled for ARC
13 # elif 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"
21 extern pdkim_ctx * dkim_verify_ctx;
22 extern pdkim_ctx dkim_sign_ctx;
24 #define ARC_SIGN_OPT_TSTAMP BIT(0)
25 #define ARC_SIGN_OPT_EXPIRE BIT(1)
27 #define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30) /* one month */
29 /******************************************************************************/
31 typedef struct hdr_rlist {
32 struct hdr_rlist * prev;
37 typedef struct arc_line {
38 header_line * complete; /* including the header name; nul-term */
41 /* identified tag contents */
54 /* tag content sub-portions */
61 /* modified copy of b= field in line */
65 typedef struct arc_set {
66 struct arc_set * next;
67 struct arc_set * prev;
74 const uschar * ams_verify_done;
75 BOOL ams_verify_passed;
78 typedef struct arc_ctx {
79 arc_set * arcset_chain;
80 arc_set * arcset_chain_last;
83 #define ARC_HDR_AAR US"ARC-Authentication-Results:"
84 #define ARC_HDRLEN_AAR 27
85 #define ARC_HDR_AMS US"ARC-Message-Signature:"
86 #define ARC_HDRLEN_AMS 22
87 #define ARC_HDR_AS US"ARC-Seal:"
88 #define ARC_HDRLEN_AS 9
89 #define HDR_AR US"Authentication-Results:"
94 static hdr_rlist * headers_rlist;
95 static arc_ctx arc_sign_ctx = { NULL };
98 /******************************************************************************/
101 /* Get the instance number from the header.
104 arc_instance_from_hdr(const arc_line * al)
106 const uschar * s = al->i.data;
107 if (!s || !al->i.len) return 0;
108 return (unsigned) atoi(CCS s);
116 while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
121 /* Locate instance struct on chain, inserting a new one if
122 needed. The chain is in increasing-instance-number order
123 by the "next" link, and we have a "prev" link also.
127 arc_find_set(arc_ctx * ctx, unsigned i)
129 arc_set ** pas, * as, * next, * prev;
131 for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
132 as = *pas; pas = &as->next)
134 if (as->instance > i) break;
135 if (as->instance == i)
137 DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
144 DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
145 *pas = as = store_get(sizeof(arc_set));
146 memset(as, 0, sizeof(arc_set));
153 ctx->arcset_chain_last = as;
159 /* Insert a tag content into the line structure.
160 Note this is a reference to existing data, not a copy.
161 Check for already-seen tag.
162 The string-pointer is on the '=' for entry. Update it past the
163 content (to the ;) on return;
167 arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
171 blob * b = (blob *)(US al + loff);
174 /* [FWS] tag-value [FWS] */
176 if (b->data) return US"fail";
177 s = skip_fws(s); /* FWS */
180 while ((c = *s) && c != ';') { len++; s++; }
182 while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
183 { s--; len--; } /* FWS */
189 /* Inspect a header line, noting known tag fields.
190 Check for duplicates. */
193 arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only)
195 uschar * s = h->text + off;
196 uschar * r = NULL; /* compiler-quietening */
203 al->rawsig_no_b_val.data = store_get(h->slen + 1);
204 memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */
205 r = al->rawsig_no_b_val.data + off;
206 al->rawsig_no_b_val.len = off;
209 /* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */
216 uschar * fieldstart = s;
217 uschar * bstart = NULL, * bend;
219 /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
221 s = skip_fws(s); /* FWS */
223 /* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */
225 s = skip_fws(s); /* FWS */
228 if (!instance_only || tagchar == 'i') switch (tagchar)
230 case 'a': /* a= AMS algorithm */
232 if (*s != '=') return US"no 'a' value";
233 if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
235 /* substructure: algo-hash (eg. rsa-sha256) */
237 t = al->a_algo.data = al->a.data;
239 if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
241 if (*t++ != '-') return US"no '-' in 'a' value";
243 al->a_hash.len = al->a.len - i - 1;
252 case '=': /* b= AMS signature */
253 if (al->b.data) return US"already b data";
256 /* The signature can have FWS inserted in the content;
257 make a stripped copy */
259 while ((c = *++s) && c != ';')
260 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
261 g = string_catn(g, s, 1);
262 al->b.data = string_from_gstring(g);
264 gstring_reset_unused(g);
267 case 'h': /* bh= AMS body hash */
268 s = skip_fws(++s); /* FWS */
269 if (*s != '=') return US"no bh value";
270 if (al->bh.data) return US"already bh data";
272 /* The bodyhash can have FWS inserted in the content;
273 make a stripped copy */
275 while ((c = *++s) && c != ';')
276 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
277 g = string_catn(g, s, 1);
278 al->bh.data = string_from_gstring(g);
280 gstring_reset_unused(g);
290 case '=': /* c= AMS canonicalisation */
291 if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
293 /* substructure: head/body (eg. relaxed/simple)) */
295 t = al->c_head.data = al->c.data;
297 if (!*t++ || ++i > al->a.len) break;
299 if (*t++ == '/') /* /body is optional */
302 al->c_body.len = al->c.len - i - 1;
306 al->c_body.data = US"simple";
310 case 'v': /* cv= AS validity */
311 if (*++s != '=') return US"cv tag val";
312 if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup";
318 case 'd': /* d= AMS domain */
319 if (*s != '=') return US"d tag val";
320 if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup";
322 case 'h': /* h= AMS headers */
323 if (*s != '=') return US"h tag val";
324 if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup";
326 case 'i': /* i= ARC set instance */
327 if (*s != '=') return US"i tag val";
328 if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup";
329 if (instance_only) goto done;
331 case 'l': /* l= bodylength */
332 if (*s != '=') return US"l tag val";
333 if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup";
335 case 's': /* s= AMS selector */
336 if (*s != '=') return US"s tag val";
337 if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup";
341 while ((c = *s) && c != ';') s++;
342 if (c) s++; /* ; after tag-spec */
344 /* for all but the b= tag, copy the field including FWS. For the b=,
345 drop the tag content. */
350 size_t n = bstart - fieldstart;
351 memcpy(r, fieldstart, n); /* FWS "b=" */
353 al->rawsig_no_b_val.len += n;
355 memcpy(r, bend, n); /* FWS ";" */
357 al->rawsig_no_b_val.len += n;
361 size_t n = s - fieldstart;
362 memcpy(r, fieldstart, n);
364 al->rawsig_no_b_val.len += n;
372 /* debug_printf("%s: finshed\n", __FUNCTION__); */
377 /* Insert one header line in the correct set of the chain,
378 adding instances as needed and checking for duplicate lines.
382 arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
387 arc_line * al = store_get(sizeof(arc_line)), ** alp;
390 memset(al, 0, sizeof(arc_line));
392 if ((e = arc_parse_line(al, h, off, instance_only)))
394 DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
395 return US"line parse";
397 if (!(i = arc_instance_from_hdr(al))) return US"instance find";
398 if (i > 50) return US"overlarge instance number";
399 if (!(as = arc_find_set(ctx, i))) return US"set find";
400 if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
409 static const uschar *
410 arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only)
414 /*debug_printf("consider hdr '%s'\n", h->text);*/
415 if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
421 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
423 debug_printf("ARC: found AAR: %.*s\n", len, h->text);
425 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
428 DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
429 return US"inserting AAR";
432 else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
440 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
442 debug_printf("ARC: found AMS: %.*s\n", len, h->text);
444 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
447 DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
448 return US"inserting AMS";
452 /*XXX dubious selection of ams here */
453 ams = ctx->arcset_chain->hdr_ams;
456 ams->c_head.data = US"simple"; ams->c_head.len = 6;
457 ams->c_body = ams->c_head;
460 else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
466 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
468 debug_printf("ARC: found AS: %.*s\n", len, h->text);
470 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
473 DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
474 return US"inserting AS";
482 /* Gather the chain of arc sets from the headers.
483 Check for duplicates while that is done. Also build the
484 reverse-order headers list;
486 Return: ARC state if determined, eg. by lack of any ARC chain.
489 static const uschar *
490 arc_vfy_collect_hdrs(arc_ctx * ctx)
493 hdr_rlist * r = NULL, * rprev = NULL;
496 DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
497 for (h = header_list; h; h = h->next)
499 r = store_get(sizeof(hdr_rlist));
505 if ((e = arc_try_header(ctx, h, FALSE)))
507 arc_state_reason = string_sprintf("collecting headers: %s", e);
513 if (!ctx->arcset_chain) return US"none";
519 arc_cv_match(arc_line * al, const uschar * s)
521 return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
524 /******************************************************************************/
526 /* Return the hash of headers from the message that the AMS claims it
531 arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
533 const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
537 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
538 int hashtype = pdkim_hashname_to_hashtype(
539 ams->a_hash.data, ams->a_hash.len);
544 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
547 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
551 /* For each headername in the list from the AMS (walking in order)
552 walk the message headers in reverse order, adding to the hash any
553 found for the first time. For that last point, maintain used-marks
554 on the list of message headers. */
556 DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
558 for (r = headers_rlist; r; r = r->prev)
560 while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
561 for (r = headers_rlist; r; r = r->prev)
563 && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
566 if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
569 DEBUG(D_acl) pdkim_quoteprint(s, len);
570 exim_sha_update(&hhash_ctx, s, Ustrlen(s));
575 /* Finally add in the signature header (with the b= tag stripped); no CRLF */
577 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
579 len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
580 DEBUG(D_acl) pdkim_quoteprint(s, len);
581 exim_sha_update(&hhash_ctx, s, len);
583 exim_sha_finish(&hhash_ctx, hhash);
585 { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
592 static pdkim_pubkey *
593 arc_line_to_pubkey(arc_line * al)
598 if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
599 al->s.len, al->s.data, al->d.len, al->d.data))))
601 DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
605 if ( !(p = pdkim_parse_pubkey_record(dns_txt))
606 || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
609 DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
613 /* If the pubkey limits use to specified hashes, reject unusable
614 signatures. XXX should we have looked for multiple dns records? */
618 const uschar * list = p->hashes, * ele;
621 while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
622 if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
625 DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
626 p->hashes, (int)al->a.len, al->a.data);
636 static pdkim_bodyhash *
637 arc_ams_setup_vfy_bodyhash(arc_line * ams)
639 int canon_head, canon_body;
642 if (!ams->c.data) ams->c.data = US"simple"; /* RFC 6376 (DKIM) default */
643 pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
644 bodylen = ams->l.data
645 ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
647 return pdkim_set_bodyhash(dkim_verify_ctx,
648 pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
655 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
656 and without a DKIM v= tag.
659 static const uschar *
660 arc_ams_verify(arc_ctx * ctx, arc_set * as)
662 arc_line * ams = as->hdr_ams;
669 const uschar * errstr;
671 as->ams_verify_done = US"in-progress";
673 /* Check the AMS has all the required tags:
677 "d=" domain (for key lookup)
678 "h=" headers (included in signature)
679 "s=" key-selector (for key lookup)
681 if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
682 || !ams->h.data || !ams->s.data)
684 as->ams_verify_done = arc_state_reason = US"required tag missing";
689 /* The bodyhash should have been created earlier, and the dkim code should
690 have managed calculating it during message input. Find the reference to it. */
692 if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
694 as->ams_verify_done = arc_state_reason = US"internal hash setup error";
700 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
701 " Body %.*s computed: ",
702 as->instance, b->signed_body_bytes,
703 (int)ams->a_hash.len, ams->a_hash.data);
704 pdkim_hexprint(CUS b->bh.data, b->bh.len);
707 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
710 || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
711 || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
716 debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
717 pdkim_hexprint(sighash.data, sighash.len);
718 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
720 return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
723 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
725 /* Get the public key from DNS */
727 if (!(p = arc_line_to_pubkey(ams)))
728 return as->ams_verify_done = arc_state_reason = US"pubkey problem";
730 /* We know the b-tag blob is of a nul-term string, so safe as a string */
731 pdkim_decode_base64(ams->b.data, &sighash);
733 arc_get_verify_hhash(ctx, ams, &hhash);
735 /* Setup the interface to the signing library */
737 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
739 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
740 as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
744 hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
746 if ((errstr = exim_dkim_verify(&vctx,
747 pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
749 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
750 return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
753 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
754 as->ams_verify_passed = TRUE;
760 /* Check the sets are instance-continuous and that all
761 members are present. Check that no arc_seals are "fail".
762 Set the highest instance number global.
763 Verify the latest AMS.
766 arc_headers_check(arc_ctx * ctx)
770 BOOL ams_fail_found = FALSE;
772 if (!(as = ctx->arcset_chain))
775 for(inst = 0; as; as = as->next)
777 if ( as->instance != ++inst
778 || !as->hdr_aar || !as->hdr_ams || !as->hdr_as
779 || arc_cv_match(as->hdr_as, US"fail")
782 arc_state_reason = string_sprintf("i=%d"
783 " (cv, sequence or missing header)", as->instance);
784 DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
788 /* Evaluate the oldest-pass AMS validation while we're here.
789 It does not affect the AS chain validation but is reported as
793 if (arc_ams_verify(ctx, as))
794 ams_fail_found = TRUE;
796 arc_oldest_pass = inst;
797 arc_state_reason = NULL;
800 arc_received = ctx->arcset_chain_last;
801 arc_received_instance = inst;
803 /* We can skip the latest-AMS validation, if we already did it. */
805 as = ctx->arcset_chain_last;
806 if (!as->ams_verify_passed)
808 if (as->ams_verify_done)
810 arc_state_reason = as->ams_verify_done;
813 if (!!arc_ams_verify(ctx, as))
820 /******************************************************************************/
821 static const uschar *
822 arc_seal_verify(arc_ctx * ctx, arc_set * as)
824 arc_line * hdr_as = as->hdr_as;
832 const uschar * errstr;
834 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
836 1. If the value of the "cv" tag on that seal is "fail", the
837 chain state is "fail" and the algorithm stops here. (This
838 step SHOULD be skipped if the earlier step (2.1) was
841 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
842 == "none" && i != 1)) then the chain state is "fail" and the
843 algorithm stops here (note that the ordering of the logic is
844 structured for short-circuit evaluation).
847 if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none")
848 || arc_cv_match(hdr_as, US"none") && as->instance != 1
851 arc_state_reason = US"seal cv state";
856 3. Initialize a hash function corresponding to the "a" tag of
860 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
862 if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
865 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
866 arc_state_reason = US"seal hash setup error";
871 4. Compute the canonicalized form of the ARC header fields, in
872 the order described in Section 5.4.2, using the "relaxed"
873 header canonicalization defined in Section 3.4.2 of
874 [RFC6376]. Pass the canonicalized result to the hash
877 Headers are CRLF-separated, but the last one is not crlf-terminated.
880 DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
881 for (as2 = ctx->arcset_chain;
882 as2 && as2->instance <= as->instance;
890 if (!(s = al->relaxed))
891 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
892 al->complete->slen, TRUE);
894 DEBUG(D_acl) pdkim_quoteprint(s, len);
895 exim_sha_update(&hhash_ctx, s, len);
898 if (!(s = al->relaxed))
899 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
900 al->complete->slen, TRUE);
902 DEBUG(D_acl) pdkim_quoteprint(s, len);
903 exim_sha_update(&hhash_ctx, s, len);
906 if (as2->instance == as->instance)
907 s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
908 al->rawsig_no_b_val.len, FALSE);
909 else if (!(s = al->relaxed))
910 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
911 al->complete->slen, TRUE);
913 DEBUG(D_acl) pdkim_quoteprint(s, len);
914 exim_sha_update(&hhash_ctx, s, len);
918 5. Retrieve the final digest from the hash function.
921 exim_sha_finish(&hhash_ctx, &hhash_computed);
924 debug_printf("ARC i=%d AS Header %.*s computed: ",
925 as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
926 pdkim_hexprint(hhash_computed.data, hhash_computed.len);
931 6. Retrieve the public key identified by the "s" and "d" tags in
932 the ARC-Seal, as described in Section 4.1.6.
935 if (!(p = arc_line_to_pubkey(hdr_as)))
936 return US"pubkey problem";
939 7. Determine whether the signature portion ("b" tag) of the ARC-
940 Seal and the digest computed above are valid according to the
941 public key. (See also Section Section 8.4 for failure case
944 8. If the signature is not valid, the chain state is "fail" and
945 the algorithm stops here.
948 /* We know the b-tag blob is of a nul-term string, so safe as a string */
949 pdkim_decode_base64(hdr_as->b.data, &sighash);
951 if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
953 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
957 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
959 if ((errstr = exim_dkim_verify(&vctx,
960 pdkim_hashes[hashtype].exim_hashmethod,
961 &hhash_computed, &sighash)))
964 debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
965 arc_state_reason = US"seal sigverify error";
969 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
974 static const uschar *
975 arc_verify_seals(arc_ctx * ctx)
977 arc_set * as = ctx->arcset_chain;
984 if (arc_seal_verify(ctx, as)) return US"fail";
987 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
990 /******************************************************************************/
992 /* Do ARC verification. Called from DATA ACL, on a verify = arc
993 condition. No arguments; we are checking globals.
995 Return: The ARC state, or NULL on error.
1001 arc_ctx ctx = { NULL };
1004 if (!dkim_verify_ctx)
1006 DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
1010 /* AS evaluation, per
1011 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
1013 /* 1. Collect all ARC sets currently on the message. If there were
1014 none, the ARC state is "none" and the algorithm stops here.
1017 if ((res = arc_vfy_collect_hdrs(&ctx)))
1020 /* 2. If the form of any ARC set is invalid (e.g., does not contain
1021 exactly one of each of the three ARC-specific header fields),
1022 then the chain state is "fail" and the algorithm stops here.
1024 1. To avoid the overhead of unnecessary computation and delay
1025 from crypto and DNS operations, the cv value for all ARC-
1026 Seal(s) MAY be checked at this point. If any of the values
1027 are "fail", then the overall state of the chain is "fail" and
1028 the algorithm stops here.
1030 3. Conduct verification of the ARC-Message-Signature header field
1031 bearing the highest instance number. If this verification fails,
1032 then the chain state is "fail" and the algorithm stops here.
1035 if ((res = arc_headers_check(&ctx)))
1038 /* 4. For each ARC-Seal from the "N"th instance to the first, apply the
1041 1. If the value of the "cv" tag on that seal is "fail", the
1042 chain state is "fail" and the algorithm stops here. (This
1043 step SHOULD be skipped if the earlier step (2.1) was
1046 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1047 == "none" && i != 1)) then the chain state is "fail" and the
1048 algorithm stops here (note that the ordering of the logic is
1049 structured for short-circuit evaluation).
1051 3. Initialize a hash function corresponding to the "a" tag of
1054 4. Compute the canonicalized form of the ARC header fields, in
1055 the order described in Section 5.4.2, using the "relaxed"
1056 header canonicalization defined in Section 3.4.2 of
1057 [RFC6376]. Pass the canonicalized result to the hash
1060 5. Retrieve the final digest from the hash function.
1062 6. Retrieve the public key identified by the "s" and "d" tags in
1063 the ARC-Seal, as described in Section 4.1.6.
1065 7. Determine whether the signature portion ("b" tag) of the ARC-
1066 Seal and the digest computed above are valid according to the
1067 public key. (See also Section Section 8.4 for failure case
1070 8. If the signature is not valid, the chain state is "fail" and
1071 the algorithm stops here.
1073 5. If all seals pass validation, then the chain state is "pass", and
1074 the algorithm is complete.
1077 if ((res = arc_verify_seals(&ctx)))
1086 /******************************************************************************/
1088 /* Prepend the header to the rlist */
1091 arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1093 hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
1094 header_line * h = r->h = (header_line *)(r+1);
1103 /* This works for either NL or CRLF lines; also nul-termination */
1105 if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
1106 s++; /* move past end of line */
1112 /* Walk the given headers strings identifying each header, and construct
1113 a reverse-order list.
1117 arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1120 hdr_rlist * rheaders = NULL;
1122 s = sigheaders ? sigheaders->s : NULL;
1125 const uschar * s2 = s;
1127 /* This works for either NL or CRLF lines; also nul-termination */
1129 if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1130 s2++; /* move past end of line */
1132 rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1140 /* Return the A-R content, without identity, with line-ending and
1144 arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1147 int ilen = Ustrlen(identity);
1150 for(h = headers; h; h = h->next)
1152 uschar * s = h->text, c;
1155 if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1156 s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */
1158 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1159 s++, len--; /* FWS */
1160 if (Ustrncmp(s, identity, ilen) != 0) continue;
1161 s += ilen; len -= ilen; /* identity */
1162 if (len <= 0) continue;
1163 if ((c = *s) && c == ';') s++, len--; /* identity terminator */
1165 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1166 s++, len--; /* FWS */
1167 if (len <= 0) continue;
1177 /* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
1180 arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1181 const uschar * identity, int instance, blob * ar)
1183 int aar_off = g ? g->ptr : 0;
1184 arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
1185 arc_line * al = (arc_line *)(as+1);
1186 header_line * h = (header_line *)(al+1);
1188 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1189 g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
1190 g = string_catn(g, US ar->data, ar->len);
1192 h->slen = g->ptr - aar_off;
1193 h->text = g->s + aar_off;
1196 as->prev = ctx->arcset_chain_last;
1197 as->instance = instance;
1200 ctx->arcset_chain = as;
1202 ctx->arcset_chain_last->next = as;
1203 ctx->arcset_chain_last = as;
1205 DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1212 arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1213 blob * sig, const uschar * why)
1215 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1216 ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1219 const uschar * errstr;
1224 debug_printf("ARC: %s header data for signing:\n", why);
1225 pdkim_quoteprint(hdata->s, hdata->ptr);
1227 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1228 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1229 exim_sha_finish(&hhash_ctx, &hhash);
1230 debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
1233 if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1236 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1237 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1238 exim_sha_finish(&hhash_ctx, &hhash);
1242 hhash.data = hdata->s;
1243 hhash.len = hdata->ptr;
1246 if ( (errstr = exim_dkim_signing_init(privkey, &sctx))
1247 || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
1249 log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
1258 arc_sign_append_sig(gstring * g, blob * sig)
1260 /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1261 sig->data = pdkim_encode_base64(sig);
1262 sig->len = Ustrlen(sig->data);
1265 int len = MIN(sig->len, 74);
1266 g = string_catn(g, sig->data, len);
1267 if ((sig->len -= len) == 0) break;
1269 g = string_catn(g, US"\r\n\t ", 5);
1271 g = string_catn(g, US";\r\n", 3);
1272 gstring_reset_unused(g);
1273 string_from_gstring(g);
1278 /* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
1281 arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1282 const uschar * identity, const uschar * selector, blob * bodyhash,
1283 hdr_rlist * rheaders, const uschar * privkey, unsigned options)
1286 gstring * hdata = NULL;
1288 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1291 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1292 header_line * h = (header_line *)(al+1);
1294 /* debug_printf("%s\n", __FUNCTION__); */
1296 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1299 g = string_append(g, 7,
1301 US" i=", string_sprintf("%d", instance),
1302 US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */
1303 US"; s=", selector);
1304 if (options & ARC_SIGN_OPT_TSTAMP)
1305 g = string_append(g, 2,
1306 US"; t=", string_sprintf("%lu", (u_long)now));
1307 if (options & ARC_SIGN_OPT_EXPIRE)
1308 g = string_append(g, 2,
1309 US"; x=", string_sprintf("%lu", (u_long)expire));
1310 g = string_append(g, 3,
1311 US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
1314 for(col = 3; rheaders; rheaders = rheaders->prev)
1316 const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1317 uschar * name, * htext = rheaders->h->text;
1320 /* Spot headers of interest */
1322 while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1324 int len = Ustrlen(name);
1325 if (strncasecmp(CCS htext, CCS name, len) == 0)
1327 /* If too long, fold line in h= field */
1329 if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3;
1331 /* Add name to h= list */
1333 g = string_catn(g, name, len);
1334 g = string_catn(g, US":", 1);
1337 /* Accumulate header for hashing/signing */
1339 hdata = string_cat(hdata,
1340 pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */
1346 /* Lose the last colon from the h= list */
1348 if (g->s[g->ptr - 1] == ':') g->ptr--;
1350 g = string_catn(g, US";\r\n\tb=;", 7);
1352 /* Include the pseudo-header in the accumulation */
1354 s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
1355 hdata = string_cat(hdata, s);
1357 /* Calculate the signature from the accumulation */
1358 /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1360 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1363 /* Lose the trailing semicolon from the psuedo-header, and append the signature
1364 (folded over lines) and termination to complete it. */
1367 g = arc_sign_append_sig(g, &sig);
1369 h->slen = g->ptr - ams_off;
1370 h->text = g->s + ams_off;
1372 ctx->arcset_chain_last->hdr_ams = al;
1374 DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1380 /* Look for an arc= result in an A-R header blob. We know that its data
1381 happens to be a NUL-term string. */
1384 arc_ar_cv_status(blob * ar)
1386 const uschar * resinfo = ar->data;
1388 uschar * methodspec, * s;
1390 while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1391 if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1394 for (s = methodspec += 4;
1395 (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1396 return string_copyn(methodspec, s - methodspec);
1403 /* Build the AS header and prepend it */
1406 arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1407 int instance, const uschar * identity, const uschar * selector, blob * ar,
1408 const uschar * privkey, unsigned options)
1412 uschar * status = arc_ar_cv_status(ar);
1413 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1414 header_line * h = (header_line *)(al+1);
1416 gstring * hdata = NULL;
1417 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1423 - no h= tag; implicit coverage
1424 - arc status from A-R
1426 - coverage is just the new ARC set
1427 including self (but with an empty b= in self)
1429 - all ARC set headers, set-number order, aar then ams then as,
1430 including self (but with an empty b= in self)
1433 /* Construct the AS except for the signature */
1435 arcset = string_append(NULL, 9,
1437 US" i=", string_sprintf("%d", instance),
1439 US"; a=rsa-sha256; d=", identity, /*XXX hardwired */
1440 US"; s=", selector); /*XXX same as AMS */
1441 if (options & ARC_SIGN_OPT_TSTAMP)
1442 arcset = string_append(arcset, 2,
1443 US"; t=", string_sprintf("%lu", (u_long)now));
1444 arcset = string_cat(arcset,
1447 h->slen = arcset->ptr;
1448 h->text = arcset->s;
1450 ctx->arcset_chain_last->hdr_as = al;
1452 /* For any but "fail" chain-verify status, walk the entire chain in order by
1453 instance. For fail, only the new arc-set. Accumulate the elements walked. */
1455 for (as = Ustrcmp(status, US"fail") == 0
1456 ? ctx->arcset_chain_last : ctx->arcset_chain;
1459 /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
1460 is required per standard. */
1462 h = as->hdr_aar->complete;
1463 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1464 h = as->hdr_ams->complete;
1465 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1466 h = as->hdr_as->complete;
1467 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
1470 /* Calculate the signature from the accumulation */
1472 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1475 /* Lose the trailing semicolon */
1477 arcset = arc_sign_append_sig(arcset, &sig);
1478 DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s);
1480 /* Finally, append the AMS and AAR to the new AS */
1482 return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1486 /**************************************/
1488 /* Return pointer to pdkim_bodyhash for given hash method, creating new
1493 arc_ams_setup_sign_bodyhash(void)
1495 int canon_head, canon_body;
1497 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1498 pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */
1499 return pdkim_set_bodyhash(&dkim_sign_ctx,
1500 pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */
1510 memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
1515 /* A "normal" header line, identified by DKIM processing. These arrive before
1516 the call to arc_sign(), which carries any newly-created DKIM headers - and
1517 those go textually before the normal ones in the message.
1519 We have to take the feed from DKIM as, in the transport-filter case, the
1520 headers are not in memory at the time of the call to arc_sign().
1522 Take a copy of the header and construct a reverse-order list.
1523 Also parse ARC-chain headers and build the chain struct, retaining pointers
1527 static const uschar *
1528 arc_header_sign_feed(gstring * g)
1530 uschar * s = string_copyn(g->s, g->ptr);
1531 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1532 return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1537 /* ARC signing. Called from the smtp transport, if the arc_sign option is set.
1538 The dkim_exim_sign() function has already been called, so will have hashed the
1539 message body for us so long as we requested a hash previously.
1542 signspec Three-element colon-sep list: identity, selector, privkey.
1543 Optional fourth element: comma-sep list of options.
1545 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1549 Set of headers to prepend to the message, including the supplied sigheaders
1550 but not the plainheaders.
1554 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1556 const uschar * identity, * selector, * privkey, * opts, * s;
1557 unsigned options = 0;
1559 header_line * headers;
1560 hdr_rlist * rheaders;
1568 /* Parse the signing specification */
1570 identity = string_nextinlist(&signspec, &sep, NULL, 0);
1571 selector = string_nextinlist(&signspec, &sep, NULL, 0);
1572 if ( !*identity || !*selector
1573 || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1575 log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)",
1576 !*identity ? "identity" : !*selector ? "selector" : "private-key");
1577 return sigheaders ? sigheaders : string_get(0);
1579 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1580 return sigheaders ? sigheaders : string_get(0);
1582 if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
1585 while ((s = string_nextinlist(&opts, &osep, NULL, 0)))
1586 if (Ustrcmp(s, "timestamps") == 0)
1588 options |= ARC_SIGN_OPT_TSTAMP;
1589 if (!now) now = time(NULL);
1591 else if (Ustrncmp(s, "expire", 6) == 0)
1593 options |= ARC_SIGN_OPT_EXPIRE;
1594 if (*(s += 6) == '=')
1597 if (!(expire = (time_t)atoi(++s)))
1598 expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA;
1599 if (!now) now = time(NULL);
1603 expire = (time_t)atol(s);
1606 if (!now) now = time(NULL);
1607 expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA;
1612 DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1614 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
1615 Then scan the list for an A-R header. */
1617 string_from_gstring(sigheaders);
1618 if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1621 for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
1625 /* Finally, build a normal-order headers list */
1626 /*XXX only needed for hunt-the-AR? */
1627 /*XXX also, we really should be accepting any number of ADMD-matching ARs */
1629 header_line * hnext = NULL;
1630 for (rheaders = headers_rlist; rheaders;
1631 hnext = rheaders->h, rheaders = rheaders->prev)
1632 rheaders->h->next = hnext;
1636 if (!(arc_sign_find_ar(headers, identity, &ar)))
1638 log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
1639 return sigheaders ? sigheaders : string_get(0);
1642 /* We previously built the data-struct for the existing ARC chain, if any, using a headers
1643 feed from the DKIM module. Use that to give the instance number for the ARC set we are
1647 if (arc_sign_ctx.arcset_chain_last)
1648 debug_printf("ARC: existing chain highest instance: %d\n",
1649 arc_sign_ctx.arcset_chain_last->instance);
1651 debug_printf("ARC: no existing chain\n");
1653 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1657 - copy the A-R; prepend i= & identity
1660 g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1664 - Looks fairly like a DKIM sig
1665 - Cover all DKIM sig headers as well as the usuals
1668 - we must have requested a suitable bodyhash previously
1671 b = arc_ams_setup_sign_bodyhash();
1672 g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1673 &b->bh, headers_rlist, privkey, options);
1678 - no h= tag; implicit coverage
1679 - arc status from A-R
1681 - coverage is just the new ARC set
1682 including self (but with an empty b= in self)
1684 - all ARC set headers, set-number order, aar then ams then as,
1685 including self (but with an empty b= in self)
1688 g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
1691 /* Finally, append the dkim headers and return the lot. */
1693 g = string_catn(g, sigheaders->s, sigheaders->ptr);
1694 (void) string_from_gstring(g);
1695 gstring_reset_unused(g);
1700 /******************************************************************************/
1702 /* Check to see if the line is an AMS and if so, set up to validate it.
1703 Called from the DKIM input processing. This must be done now as the message
1704 body data is hashed during input.
1706 We call the DKIM code to request a body-hash; it has the facility already
1707 and the hash parameters might be common with other requests.
1710 static const uschar *
1711 arc_header_vfy_feed(gstring * g)
1718 if (!dkim_verify_ctx) return US"no dkim context";
1720 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1722 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1723 /* Parse the AMS header */
1728 memset(&al, 0, sizeof(arc_line));
1729 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
1731 DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1732 return US"line parsing error";
1738 al.c_body.data = US"simple"; al.c_body.len = 6;
1739 al.c_head = al.c_body;
1742 /* Ask the dkim code to calc a bodyhash with those specs */
1744 if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1745 return US"dkim hash setup fail";
1747 /* Discard the reference; search again at verify time, knowing that one
1748 should have been created here. */
1755 /* A header line has been identified by DKIM processing.
1759 is_vfy TRUE for verify mode or FALSE for signing mode
1762 NULL for success, or an error string (probably unused)
1766 arc_header_feed(gstring * g, BOOL is_vfy)
1768 return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1773 /******************************************************************************/
1775 /* Construct an Authenticate-Results header portion, for the ARC module */
1778 authres_arc(gstring * g)
1782 arc_line * highest_ams;
1783 int start = 0; /* Compiler quietening */
1784 DEBUG(D_acl) start = g->ptr;
1786 g = string_append(g, 2, US";\n\tarc=", arc_state);
1787 if (arc_received_instance > 0)
1789 g = string_append(g, 3, US" (i=",
1790 string_sprintf("%d", arc_received_instance), US")");
1791 if (arc_state_reason)
1792 g = string_append(g, 3, US"(", arc_state_reason, US")");
1793 g = string_catn(g, US" header.s=", 10);
1794 highest_ams = arc_received->hdr_ams;
1795 g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1797 g = string_append(g, 2,
1798 US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
1800 if (sender_host_address)
1801 g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
1803 else if (arc_state_reason)
1804 g = string_append(g, 3, US" (", arc_state_reason, US")");
1805 DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n",
1806 g->ptr - start - 3, g->s + start + 3);
1809 DEBUG(D_acl) debug_printf("ARC: no authres\n");
1814 # endif /* SUPPORT_SPF */
1815 #endif /* EXPERIMENTAL_ARC */