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)
26 /******************************************************************************/
28 typedef struct hdr_rlist
{
29 struct hdr_rlist
* prev
;
34 typedef struct arc_line
{
35 header_line
* complete
; /* including the header name; nul-term */
38 /* identified tag contents */
51 /* tag content sub-portions */
58 /* modified copy of b= field in line */
62 typedef struct arc_set
{
63 struct arc_set
* next
;
64 struct arc_set
* prev
;
71 const uschar
* ams_verify_done
;
72 BOOL ams_verify_passed
;
75 typedef struct arc_ctx
{
76 arc_set
* arcset_chain
;
77 arc_set
* arcset_chain_last
;
80 #define ARC_HDR_AAR US"ARC-Authentication-Results:"
81 #define ARC_HDRLEN_AAR 27
82 #define ARC_HDR_AMS US"ARC-Message-Signature:"
83 #define ARC_HDRLEN_AMS 22
84 #define ARC_HDR_AS US"ARC-Seal:"
85 #define ARC_HDRLEN_AS 9
86 #define HDR_AR US"Authentication-Results:"
89 static hdr_rlist
* headers_rlist
;
90 static arc_ctx arc_sign_ctx
= { NULL
};
93 /******************************************************************************/
96 /* Get the instance number from the header.
99 arc_instance_from_hdr(const arc_line
* al
)
101 const uschar
* s
= al
->i
.data
;
102 if (!s
|| !al
->i
.len
) return 0;
103 return (unsigned) atoi(CCS s
);
111 while (c
&& (c
== ' ' || c
== '\t' || c
== '\n' || c
== '\r')) c
= *++s
;
116 /* Locate instance struct on chain, inserting a new one if
117 needed. The chain is in increasing-instance-number order
118 by the "next" link, and we have a "prev" link also.
122 arc_find_set(arc_ctx
* ctx
, unsigned i
)
124 arc_set
** pas
, * as
, * next
, * prev
;
126 for (pas
= &ctx
->arcset_chain
, prev
= NULL
, next
= ctx
->arcset_chain
;
127 as
= *pas
; pas
= &as
->next
)
129 if (as
->instance
> i
) break;
130 if (as
->instance
== i
)
132 DEBUG(D_acl
) debug_printf("ARC: existing instance %u\n", i
);
139 DEBUG(D_acl
) debug_printf("ARC: new instance %u\n", i
);
140 *pas
= as
= store_get(sizeof(arc_set
));
141 memset(as
, 0, sizeof(arc_set
));
148 ctx
->arcset_chain_last
= as
;
154 /* Insert a tag content into the line structure.
155 Note this is a reference to existing data, not a copy.
156 Check for already-seen tag.
157 The string-pointer is on the '=' for entry. Update it past the
158 content (to the ;) on return;
162 arc_insert_tagvalue(arc_line
* al
, unsigned loff
, uschar
** ss
)
166 blob
* b
= (blob
*)(US al
+ loff
);
169 /* [FWS] tag-value [FWS] */
171 if (b
->data
) return US
"fail";
172 s
= skip_fws(s
); /* FWS */
175 while ((c
= *s
) && c
!= ';') { len
++; s
++; }
177 while (len
&& ((c
= s
[-1]) == ' ' || c
== '\t' || c
== '\n' || c
== '\r'))
178 { s
--; len
--; } /* FWS */
184 /* Inspect a header line, noting known tag fields.
185 Check for duplicates. */
188 arc_parse_line(arc_line
* al
, header_line
* h
, unsigned off
, BOOL instance_only
)
190 uschar
* s
= h
->text
+ off
;
191 uschar
* r
= NULL
; /* compiler-quietening */
198 al
->rawsig_no_b_val
.data
= store_get(h
->slen
+ 1);
199 memcpy(al
->rawsig_no_b_val
.data
, h
->text
, off
); /* copy the header name blind */
200 r
= al
->rawsig_no_b_val
.data
+ off
;
201 al
->rawsig_no_b_val
.len
= off
;
204 /* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */
211 uschar
* fieldstart
= s
;
212 uschar
* bstart
= NULL
, * bend
;
214 /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
216 s
= skip_fws(s
); /* FWS */
218 /* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */
220 s
= skip_fws(s
); /* FWS */
223 if (!instance_only
|| tagchar
== 'i') switch (tagchar
)
225 case 'a': /* a= AMS algorithm */
227 if (*s
!= '=') return US
"no 'a' value";
228 if (arc_insert_tagvalue(al
, offsetof(arc_line
, a
), &s
)) return US
"a tag dup";
230 /* substructure: algo-hash (eg. rsa-sha256) */
232 t
= al
->a_algo
.data
= al
->a
.data
;
234 if (!*t
++ || ++i
> al
->a
.len
) return US
"no '-' in 'a' value";
236 if (*t
++ != '-') return US
"no '-' in 'a' value";
238 al
->a_hash
.len
= al
->a
.len
- i
- 1;
247 case '=': /* b= AMS signature */
248 if (al
->b
.data
) return US
"already b data";
251 /* The signature can have FWS inserted in the content;
252 make a stripped copy */
254 while ((c
= *++s
) && c
!= ';')
255 if (c
!= ' ' && c
!= '\t' && c
!= '\n' && c
!= '\r')
256 g
= string_catn(g
, s
, 1);
257 al
->b
.data
= string_from_gstring(g
);
259 gstring_reset_unused(g
);
262 case 'h': /* bh= AMS body hash */
263 s
= skip_fws(++s
); /* FWS */
264 if (*s
!= '=') return US
"no bh value";
265 if (al
->bh
.data
) return US
"already bh data";
267 /* The bodyhash can have FWS inserted in the content;
268 make a stripped copy */
270 while ((c
= *++s
) && c
!= ';')
271 if (c
!= ' ' && c
!= '\t' && c
!= '\n' && c
!= '\r')
272 g
= string_catn(g
, s
, 1);
273 al
->bh
.data
= string_from_gstring(g
);
275 gstring_reset_unused(g
);
285 case '=': /* c= AMS canonicalisation */
286 if (arc_insert_tagvalue(al
, offsetof(arc_line
, c
), &s
)) return US
"c tag dup";
288 /* substructure: head/body (eg. relaxed/simple)) */
290 t
= al
->c_head
.data
= al
->c
.data
;
292 if (!*t
++ || ++i
> al
->a
.len
) break;
294 if (*t
++ == '/') /* /body is optional */
297 al
->c_body
.len
= al
->c
.len
- i
- 1;
301 al
->c_body
.data
= US
"simple";
305 case 'v': /* cv= AS validity */
306 if (*++s
!= '=') return US
"cv tag val";
307 if (arc_insert_tagvalue(al
, offsetof(arc_line
, cv
), &s
)) return US
"cv tag dup";
313 case 'd': /* d= AMS domain */
314 if (*s
!= '=') return US
"d tag val";
315 if (arc_insert_tagvalue(al
, offsetof(arc_line
, d
), &s
)) return US
"d tag dup";
317 case 'h': /* h= AMS headers */
318 if (*s
!= '=') return US
"h tag val";
319 if (arc_insert_tagvalue(al
, offsetof(arc_line
, h
), &s
)) return US
"h tag dup";
321 case 'i': /* i= ARC set instance */
322 if (*s
!= '=') return US
"i tag val";
323 if (arc_insert_tagvalue(al
, offsetof(arc_line
, i
), &s
)) return US
"i tag dup";
324 if (instance_only
) goto done
;
326 case 'l': /* l= bodylength */
327 if (*s
!= '=') return US
"l tag val";
328 if (arc_insert_tagvalue(al
, offsetof(arc_line
, l
), &s
)) return US
"l tag dup";
330 case 's': /* s= AMS selector */
331 if (*s
!= '=') return US
"s tag val";
332 if (arc_insert_tagvalue(al
, offsetof(arc_line
, s
), &s
)) return US
"s tag dup";
336 while ((c
= *s
) && c
!= ';') s
++;
337 if (c
) s
++; /* ; after tag-spec */
339 /* for all but the b= tag, copy the field including FWS. For the b=,
340 drop the tag content. */
345 size_t n
= bstart
- fieldstart
;
346 memcpy(r
, fieldstart
, n
); /* FWS "b=" */
348 al
->rawsig_no_b_val
.len
+= n
;
350 memcpy(r
, bend
, n
); /* FWS ";" */
352 al
->rawsig_no_b_val
.len
+= n
;
356 size_t n
= s
- fieldstart
;
357 memcpy(r
, fieldstart
, n
);
359 al
->rawsig_no_b_val
.len
+= n
;
367 /* debug_printf("%s: finshed\n", __FUNCTION__); */
372 /* Insert one header line in the correct set of the chain,
373 adding instances as needed and checking for duplicate lines.
377 arc_insert_hdr(arc_ctx
* ctx
, header_line
* h
, unsigned off
, unsigned hoff
,
382 arc_line
* al
= store_get(sizeof(arc_line
)), ** alp
;
385 memset(al
, 0, sizeof(arc_line
));
387 if ((e
= arc_parse_line(al
, h
, off
, instance_only
)))
389 DEBUG(D_acl
) if (e
) debug_printf("ARC: %s\n", e
);
390 return US
"line parse";
392 if (!(i
= arc_instance_from_hdr(al
))) return US
"instance find";
393 if (!(as
= arc_find_set(ctx
, i
))) return US
"set find";
394 if (*(alp
= (arc_line
**)(US as
+ hoff
))) return US
"dup hdr";
403 static const uschar
*
404 arc_try_header(arc_ctx
* ctx
, header_line
* h
, BOOL instance_only
)
408 /*debug_printf("consider hdr '%s'\n", h->text);*/
409 if (strncmpic(ARC_HDR_AAR
, h
->text
, ARC_HDRLEN_AAR
) == 0)
415 for (s
= h
->text
+ h
->slen
; s
[-1] == '\r' || s
[-1] == '\n'; )
417 debug_printf("ARC: found AAR: %.*s\n", len
, h
->text
);
419 if ((e
= arc_insert_hdr(ctx
, h
, ARC_HDRLEN_AAR
, offsetof(arc_set
, hdr_aar
),
422 DEBUG(D_acl
) debug_printf("inserting AAR: %s\n", e
);
423 return US
"inserting AAR";
426 else if (strncmpic(ARC_HDR_AMS
, h
->text
, ARC_HDRLEN_AMS
) == 0)
434 for (s
= h
->text
+ h
->slen
; s
[-1] == '\r' || s
[-1] == '\n'; )
436 debug_printf("ARC: found AMS: %.*s\n", len
, h
->text
);
438 if ((e
= arc_insert_hdr(ctx
, h
, ARC_HDRLEN_AMS
, offsetof(arc_set
, hdr_ams
),
441 DEBUG(D_acl
) debug_printf("inserting AMS: %s\n", e
);
442 return US
"inserting AMS";
446 /*XXX dubious selection of ams here */
447 ams
= ctx
->arcset_chain
->hdr_ams
;
450 ams
->c_head
.data
= US
"simple"; ams
->c_head
.len
= 6;
451 ams
->c_body
= ams
->c_head
;
454 else if (strncmpic(ARC_HDR_AS
, h
->text
, ARC_HDRLEN_AS
) == 0)
460 for (s
= h
->text
+ h
->slen
; s
[-1] == '\r' || s
[-1] == '\n'; )
462 debug_printf("ARC: found AS: %.*s\n", len
, h
->text
);
464 if ((e
= arc_insert_hdr(ctx
, h
, ARC_HDRLEN_AS
, offsetof(arc_set
, hdr_as
),
467 DEBUG(D_acl
) debug_printf("inserting AS: %s\n", e
);
468 return US
"inserting AS";
476 /* Gather the chain of arc sets from the headers.
477 Check for duplicates while that is done. Also build the
478 reverse-order headers list;
480 Return: ARC state if determined, eg. by lack of any ARC chain.
483 static const uschar
*
484 arc_vfy_collect_hdrs(arc_ctx
* ctx
)
487 hdr_rlist
* r
= NULL
, * rprev
= NULL
;
490 DEBUG(D_acl
) debug_printf("ARC: collecting arc sets\n");
491 for (h
= header_list
; h
; h
= h
->next
)
493 r
= store_get(sizeof(hdr_rlist
));
499 if ((e
= arc_try_header(ctx
, h
, FALSE
)))
501 arc_state_reason
= string_sprintf("collecting headers: %s", e
);
507 if (!ctx
->arcset_chain
) return US
"none";
513 arc_cv_match(arc_line
* al
, const uschar
* s
)
515 return Ustrncmp(s
, al
->cv
.data
, al
->cv
.len
) == 0;
518 /******************************************************************************/
520 /* Return the hash of headers from the message that the AMS claims it
525 arc_get_verify_hhash(arc_ctx
* ctx
, arc_line
* ams
, blob
* hhash
)
527 const uschar
* headernames
= string_copyn(ams
->h
.data
, ams
->h
.len
);
531 BOOL relaxed
= Ustrncmp(US
"relaxed", ams
->c_head
.data
, ams
->c_head
.len
) == 0;
532 int hashtype
= pdkim_hashname_to_hashtype(
533 ams
->a_hash
.data
, ams
->a_hash
.len
);
538 if (!exim_sha_init(&hhash_ctx
, pdkim_hashes
[hashtype
].exim_hashmethod
))
541 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
545 /* For each headername in the list from the AMS (walking in order)
546 walk the message headers in reverse order, adding to the hash any
547 found for the first time. For that last point, maintain used-marks
548 on the list of message headers. */
550 DEBUG(D_acl
) debug_printf("ARC: AMS header data for verification:\n");
552 for (r
= headers_rlist
; r
; r
= r
->prev
)
554 while ((hn
= string_nextinlist(&headernames
, &sep
, NULL
, 0)))
555 for (r
= headers_rlist
; r
; r
= r
->prev
)
557 && strncasecmp(CCS (s
= r
->h
->text
), CCS hn
, Ustrlen(hn
)) == 0
560 if (relaxed
) s
= pdkim_relax_header_n(s
, r
->h
->slen
, TRUE
);
563 DEBUG(D_acl
) pdkim_quoteprint(s
, len
);
564 exim_sha_update(&hhash_ctx
, s
, Ustrlen(s
));
569 /* Finally add in the signature header (with the b= tag stripped); no CRLF */
571 s
= ams
->rawsig_no_b_val
.data
, len
= ams
->rawsig_no_b_val
.len
;
573 len
= Ustrlen(s
= pdkim_relax_header_n(s
, len
, FALSE
));
574 DEBUG(D_acl
) pdkim_quoteprint(s
, len
);
575 exim_sha_update(&hhash_ctx
, s
, len
);
577 exim_sha_finish(&hhash_ctx
, hhash
);
579 { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash
->data
, hhash
->len
); }
586 static pdkim_pubkey
*
587 arc_line_to_pubkey(arc_line
* al
)
592 if (!(dns_txt
= dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
593 al
->s
.len
, al
->s
.data
, al
->d
.len
, al
->d
.data
))))
595 DEBUG(D_acl
) debug_printf("pubkey dns lookup fail\n");
599 if ( !(p
= pdkim_parse_pubkey_record(dns_txt
))
600 || (Ustrcmp(p
->srvtype
, "*") != 0 && Ustrcmp(p
->srvtype
, "email") != 0)
603 DEBUG(D_acl
) debug_printf("pubkey dns lookup format error\n");
607 /* If the pubkey limits use to specified hashes, reject unusable
608 signatures. XXX should we have looked for multiple dns records? */
612 const uschar
* list
= p
->hashes
, * ele
;
615 while ((ele
= string_nextinlist(&list
, &sep
, NULL
, 0)))
616 if (Ustrncmp(ele
, al
->a_hash
.data
, al
->a_hash
.len
) == 0) break;
619 DEBUG(D_acl
) debug_printf("pubkey h=%s vs sig a=%.*s\n",
620 p
->hashes
, (int)al
->a
.len
, al
->a
.data
);
630 static pdkim_bodyhash
*
631 arc_ams_setup_vfy_bodyhash(arc_line
* ams
)
633 int canon_head
, canon_body
;
636 if (!ams
->c
.data
) ams
->c
.data
= US
"simple"; /* RFC 6376 (DKIM) default */
637 pdkim_cstring_to_canons(ams
->c
.data
, ams
->c
.len
, &canon_head
, &canon_body
);
638 bodylen
= ams
->l
.data
639 ? strtol(CS
string_copyn(ams
->l
.data
, ams
->l
.len
), NULL
, 10) : -1;
641 return pdkim_set_bodyhash(dkim_verify_ctx
,
642 pdkim_hashname_to_hashtype(ams
->a_hash
.data
, ams
->a_hash
.len
),
649 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
650 and without a DKIM v= tag.
653 static const uschar
*
654 arc_ams_verify(arc_ctx
* ctx
, arc_set
* as
)
656 arc_line
* ams
= as
->hdr_ams
;
663 const uschar
* errstr
;
665 as
->ams_verify_done
= US
"in-progress";
667 /* Check the AMS has all the required tags:
671 "d=" domain (for key lookup)
672 "h=" headers (included in signature)
673 "s=" key-selector (for key lookup)
675 if ( !ams
->a
.data
|| !ams
->b
.data
|| !ams
->bh
.data
|| !ams
->d
.data
676 || !ams
->h
.data
|| !ams
->s
.data
)
678 as
->ams_verify_done
= arc_state_reason
= US
"required tag missing";
683 /* The bodyhash should have been created earlier, and the dkim code should
684 have managed calculating it during message input. Find the reference to it. */
686 if (!(b
= arc_ams_setup_vfy_bodyhash(ams
)))
688 as
->ams_verify_done
= arc_state_reason
= US
"internal hash setup error";
694 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
695 " Body %.*s computed: ",
696 as
->instance
, b
->signed_body_bytes
,
697 (int)ams
->a_hash
.len
, ams
->a_hash
.data
);
698 pdkim_hexprint(CUS b
->bh
.data
, b
->bh
.len
);
701 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
704 || (pdkim_decode_base64(ams
->bh
.data
, &sighash
), sighash
.len
!= b
->bh
.len
)
705 || memcmp(sighash
.data
, b
->bh
.data
, b
->bh
.len
) != 0
710 debug_printf("ARC i=%d AMS Body hash from headers: ", as
->instance
);
711 pdkim_hexprint(sighash
.data
, sighash
.len
);
712 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as
->instance
);
714 return as
->ams_verify_done
= arc_state_reason
= US
"AMS body hash miscompare";
717 DEBUG(D_acl
) debug_printf("ARC i=%d AMS Body hash compared OK\n", as
->instance
);
719 /* Get the public key from DNS */
721 if (!(p
= arc_line_to_pubkey(ams
)))
722 return as
->ams_verify_done
= arc_state_reason
= US
"pubkey problem";
724 /* We know the b-tag blob is of a nul-term string, so safe as a string */
725 pdkim_decode_base64(ams
->b
.data
, &sighash
);
727 arc_get_verify_hhash(ctx
, ams
, &hhash
);
729 /* Setup the interface to the signing library */
731 if ((errstr
= exim_dkim_verify_init(&p
->key
, KEYFMT_DER
, &vctx
)))
733 DEBUG(D_acl
) debug_printf("ARC verify init: %s\n", errstr
);
734 as
->ams_verify_done
= arc_state_reason
= US
"internal sigverify init error";
738 hashtype
= pdkim_hashname_to_hashtype(ams
->a_hash
.data
, ams
->a_hash
.len
);
740 if ((errstr
= exim_dkim_verify(&vctx
,
741 pdkim_hashes
[hashtype
].exim_hashmethod
, &hhash
, &sighash
)))
743 DEBUG(D_acl
) debug_printf("ARC i=%d AMS verify %s\n", as
->instance
, errstr
);
744 return as
->ams_verify_done
= arc_state_reason
= US
"AMS sig nonverify";
747 DEBUG(D_acl
) debug_printf("ARC i=%d AMS verify pass\n", as
->instance
);
748 as
->ams_verify_passed
= TRUE
;
754 /* Check the sets are instance-continuous and that all
755 members are present. Check that no arc_seals are "fail".
756 Set the highest instance number global.
757 Verify the latest AMS.
760 arc_headers_check(arc_ctx
* ctx
)
764 BOOL ams_fail_found
= FALSE
;
767 if (!(as
= ctx
->arcset_chain
))
770 for(inst
= 0; as
; as
= as
->next
)
772 if ( as
->instance
!= ++inst
773 || !as
->hdr_aar
|| !as
->hdr_ams
|| !as
->hdr_as
774 || arc_cv_match(as
->hdr_as
, US
"fail")
777 arc_state_reason
= string_sprintf("i=%d"
778 " (cv, sequence or missing header)", as
->instance
);
779 DEBUG(D_acl
) debug_printf("ARC chain fail at %s\n", arc_state_reason
);
783 /* Evaluate the oldest-pass AMS validation while we're here.
784 It does not affect the AS chain validation but is reported as
788 if (arc_ams_verify(ctx
, as
))
789 ams_fail_found
= TRUE
;
791 arc_oldest_pass
= inst
;
792 arc_state_reason
= NULL
;
795 arc_received
= ctx
->arcset_chain_last
;
796 arc_received_instance
= inst
;
800 /* We can skip the latest-AMS validation, if we already did it. */
802 as
= ctx
->arcset_chain_last
;
803 if (as
->ams_verify_done
&& !as
->ams_verify_passed
)
805 arc_state_reason
= as
->ams_verify_done
;
808 if (!!arc_ams_verify(ctx
, as
))
815 /******************************************************************************/
816 static const uschar
*
817 arc_seal_verify(arc_ctx
* ctx
, arc_set
* as
)
819 arc_line
* hdr_as
= as
->hdr_as
;
827 const uschar
* errstr
;
829 DEBUG(D_acl
) debug_printf("ARC: AS vfy i=%d\n", as
->instance
);
831 1. If the value of the "cv" tag on that seal is "fail", the
832 chain state is "fail" and the algorithm stops here. (This
833 step SHOULD be skipped if the earlier step (2.1) was
836 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
837 == "none" && i != 1)) then the chain state is "fail" and the
838 algorithm stops here (note that the ordering of the logic is
839 structured for short-circuit evaluation).
842 if ( as
->instance
== 1 && !arc_cv_match(hdr_as
, US
"none")
843 || arc_cv_match(hdr_as
, US
"none") && as
->instance
!= 1
846 arc_state_reason
= US
"seal cv state";
851 3. Initialize a hash function corresponding to the "a" tag of
855 hashtype
= pdkim_hashname_to_hashtype(hdr_as
->a_hash
.data
, hdr_as
->a_hash
.len
);
857 if (!exim_sha_init(&hhash_ctx
, pdkim_hashes
[hashtype
].exim_hashmethod
))
860 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
861 arc_state_reason
= US
"seal hash setup error";
866 4. Compute the canonicalized form of the ARC header fields, in
867 the order described in Section 5.4.2, using the "relaxed"
868 header canonicalization defined in Section 3.4.2 of
869 [RFC6376]. Pass the canonicalized result to the hash
872 Headers are CRLF-separated, but the last one is not crlf-terminated.
875 DEBUG(D_acl
) debug_printf("ARC: AS header data for verification:\n");
876 for (as2
= ctx
->arcset_chain
;
877 as2
&& as2
->instance
<= as
->instance
;
885 if (!(s
= al
->relaxed
))
886 al
->relaxed
= s
= pdkim_relax_header_n(al
->complete
->text
,
887 al
->complete
->slen
, TRUE
);
889 DEBUG(D_acl
) pdkim_quoteprint(s
, len
);
890 exim_sha_update(&hhash_ctx
, s
, len
);
893 if (!(s
= al
->relaxed
))
894 al
->relaxed
= s
= pdkim_relax_header_n(al
->complete
->text
,
895 al
->complete
->slen
, TRUE
);
897 DEBUG(D_acl
) pdkim_quoteprint(s
, len
);
898 exim_sha_update(&hhash_ctx
, s
, len
);
901 if (as2
->instance
== as
->instance
)
902 s
= pdkim_relax_header_n(al
->rawsig_no_b_val
.data
,
903 al
->rawsig_no_b_val
.len
, FALSE
);
904 else if (!(s
= al
->relaxed
))
905 al
->relaxed
= s
= pdkim_relax_header_n(al
->complete
->text
,
906 al
->complete
->slen
, TRUE
);
908 DEBUG(D_acl
) pdkim_quoteprint(s
, len
);
909 exim_sha_update(&hhash_ctx
, s
, len
);
913 5. Retrieve the final digest from the hash function.
916 exim_sha_finish(&hhash_ctx
, &hhash_computed
);
919 debug_printf("ARC i=%d AS Header %.*s computed: ",
920 as
->instance
, (int)hdr_as
->a_hash
.len
, hdr_as
->a_hash
.data
);
921 pdkim_hexprint(hhash_computed
.data
, hhash_computed
.len
);
926 6. Retrieve the public key identified by the "s" and "d" tags in
927 the ARC-Seal, as described in Section 4.1.6.
930 if (!(p
= arc_line_to_pubkey(hdr_as
)))
931 return US
"pubkey problem";
934 7. Determine whether the signature portion ("b" tag) of the ARC-
935 Seal and the digest computed above are valid according to the
936 public key. (See also Section Section 8.4 for failure case
939 8. If the signature is not valid, the chain state is "fail" and
940 the algorithm stops here.
943 /* We know the b-tag blob is of a nul-term string, so safe as a string */
944 pdkim_decode_base64(hdr_as
->b
.data
, &sighash
);
946 if ((errstr
= exim_dkim_verify_init(&p
->key
, KEYFMT_DER
, &vctx
)))
948 DEBUG(D_acl
) debug_printf("ARC verify init: %s\n", errstr
);
952 hashtype
= pdkim_hashname_to_hashtype(hdr_as
->a_hash
.data
, hdr_as
->a_hash
.len
);
954 if ((errstr
= exim_dkim_verify(&vctx
,
955 pdkim_hashes
[hashtype
].exim_hashmethod
,
956 &hhash_computed
, &sighash
)))
959 debug_printf("ARC i=%d AS headers verify: %s\n", as
->instance
, errstr
);
960 arc_state_reason
= US
"seal sigverify error";
964 DEBUG(D_acl
) debug_printf("ARC: AS vfy i=%d pass\n", as
->instance
);
969 static const uschar
*
970 arc_verify_seals(arc_ctx
* ctx
)
972 arc_set
* as
= ctx
->arcset_chain
;
979 if (arc_seal_verify(ctx
, as
)) return US
"fail";
982 DEBUG(D_acl
) debug_printf("ARC: AS vfy overall pass\n");
985 /******************************************************************************/
987 /* Do ARC verification. Called from DATA ACL, on a verify = arc
988 condition. No arguments; we are checking globals.
990 Return: The ARC state, or NULL on error.
996 arc_ctx ctx
= { NULL
};
999 if (!dkim_verify_ctx
)
1001 DEBUG(D_acl
) debug_printf("ARC: no DKIM verify context\n");
1005 /* AS evaluation, per
1006 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
1008 /* 1. Collect all ARC sets currently on the message. If there were
1009 none, the ARC state is "none" and the algorithm stops here.
1012 if ((res
= arc_vfy_collect_hdrs(&ctx
)))
1015 /* 2. If the form of any ARC set is invalid (e.g., does not contain
1016 exactly one of each of the three ARC-specific header fields),
1017 then the chain state is "fail" and the algorithm stops here.
1019 1. To avoid the overhead of unnecessary computation and delay
1020 from crypto and DNS operations, the cv value for all ARC-
1021 Seal(s) MAY be checked at this point. If any of the values
1022 are "fail", then the overall state of the chain is "fail" and
1023 the algorithm stops here.
1025 3. Conduct verification of the ARC-Message-Signature header field
1026 bearing the highest instance number. If this verification fails,
1027 then the chain state is "fail" and the algorithm stops here.
1030 if ((res
= arc_headers_check(&ctx
)))
1033 /* 4. For each ARC-Seal from the "N"th instance to the first, apply the
1036 1. If the value of the "cv" tag on that seal is "fail", the
1037 chain state is "fail" and the algorithm stops here. (This
1038 step SHOULD be skipped if the earlier step (2.1) was
1041 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1042 == "none" && i != 1)) then the chain state is "fail" and the
1043 algorithm stops here (note that the ordering of the logic is
1044 structured for short-circuit evaluation).
1046 3. Initialize a hash function corresponding to the "a" tag of
1049 4. Compute the canonicalized form of the ARC header fields, in
1050 the order described in Section 5.4.2, using the "relaxed"
1051 header canonicalization defined in Section 3.4.2 of
1052 [RFC6376]. Pass the canonicalized result to the hash
1055 5. Retrieve the final digest from the hash function.
1057 6. Retrieve the public key identified by the "s" and "d" tags in
1058 the ARC-Seal, as described in Section 4.1.6.
1060 7. Determine whether the signature portion ("b" tag) of the ARC-
1061 Seal and the digest computed above are valid according to the
1062 public key. (See also Section Section 8.4 for failure case
1065 8. If the signature is not valid, the chain state is "fail" and
1066 the algorithm stops here.
1068 5. If all seals pass validation, then the chain state is "pass", and
1069 the algorithm is complete.
1072 if ((res
= arc_verify_seals(&ctx
)))
1081 /******************************************************************************/
1083 /* Prepend the header to the rlist */
1086 arc_rlist_entry(hdr_rlist
* list
, const uschar
* s
, int len
)
1088 hdr_rlist
* r
= store_get(sizeof(hdr_rlist
) + sizeof(header_line
));
1089 header_line
* h
= r
->h
= (header_line
*)(r
+1);
1098 /* This works for either NL or CRLF lines; also nul-termination */
1100 if (*s
== '\n' && s
[1] != '\t' && s
[1] != ' ') break;
1101 s
++; /* move past end of line */
1107 /* Walk the given headers strings identifying each header, and construct
1108 a reverse-order list.
1112 arc_sign_scan_headers(arc_ctx
* ctx
, gstring
* sigheaders
)
1115 hdr_rlist
* rheaders
= NULL
;
1117 s
= sigheaders
? sigheaders
->s
: NULL
;
1120 const uschar
* s2
= s
;
1122 /* This works for either NL or CRLF lines; also nul-termination */
1124 if (*s2
== '\n' && s2
[1] != '\t' && s2
[1] != ' ') break;
1125 s2
++; /* move past end of line */
1127 rheaders
= arc_rlist_entry(rheaders
, s
, s2
- s
);
1135 /* Return the A-R content, without identity, with line-ending and
1139 arc_sign_find_ar(header_line
* headers
, const uschar
* identity
, blob
* ret
)
1142 int ilen
= Ustrlen(identity
);
1145 for(h
= headers
; h
; h
= h
->next
)
1147 uschar
* s
= h
->text
, c
;
1150 if (Ustrncmp(s
, HDR_AR
, HDRLEN_AR
) != 0) continue;
1151 s
+= HDRLEN_AR
, len
-= HDRLEN_AR
; /* header name */
1153 && (c
= *s
) && (c
== ' ' || c
== '\t' || c
== '\r' || c
== '\n'))
1154 s
++, len
--; /* FWS */
1155 if (Ustrncmp(s
, identity
, ilen
) != 0) continue;
1156 s
+= ilen
; len
-= ilen
; /* identity */
1157 if (len
<= 0) continue;
1158 if ((c
= *s
) && c
== ';') s
++, len
--; /* identity terminator */
1160 && (c
= *s
) && (c
== ' ' || c
== '\t' || c
== '\r' || c
== '\n'))
1161 s
++, len
--; /* FWS */
1162 if (len
<= 0) continue;
1172 /* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
1175 arc_sign_append_aar(gstring
* g
, arc_ctx
* ctx
,
1176 const uschar
* identity
, int instance
, blob
* ar
)
1178 int aar_off
= g
? g
->ptr
: 0;
1179 arc_set
* as
= store_get(sizeof(arc_set
) + sizeof(arc_line
) + sizeof(header_line
));
1180 arc_line
* al
= (arc_line
*)(as
+1);
1181 header_line
* h
= (header_line
*)(al
+1);
1183 g
= string_catn(g
, ARC_HDR_AAR
, ARC_HDRLEN_AAR
);
1184 g
= string_cat(g
, string_sprintf(" i=%d; %s;\r\n\t", instance
, identity
));
1185 g
= string_catn(g
, US ar
->data
, ar
->len
);
1187 h
->slen
= g
->ptr
- aar_off
;
1188 h
->text
= g
->s
+ aar_off
;
1191 as
->prev
= ctx
->arcset_chain_last
;
1192 as
->instance
= instance
;
1195 ctx
->arcset_chain
= as
;
1197 ctx
->arcset_chain_last
->next
= as
;
1198 ctx
->arcset_chain_last
= as
;
1200 DEBUG(D_transport
) debug_printf("ARC: AAR '%.*s'\n", h
->slen
- 2, h
->text
);
1207 arc_sig_from_pseudoheader(gstring
* hdata
, int hashtype
, const uschar
* privkey
,
1208 blob
* sig
, const uschar
* why
)
1210 hashmethod hm
= /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1211 ? HASH_SHA2_512
: pdkim_hashes
[hashtype
].exim_hashmethod
;
1214 const uschar
* errstr
;
1219 debug_printf("ARC: %s header data for signing:\n", why
);
1220 pdkim_quoteprint(hdata
->s
, hdata
->ptr
);
1222 (void) exim_sha_init(&hhash_ctx
, pdkim_hashes
[hashtype
].exim_hashmethod
);
1223 exim_sha_update(&hhash_ctx
, hdata
->s
, hdata
->ptr
);
1224 exim_sha_finish(&hhash_ctx
, &hhash
);
1225 debug_printf("ARC: header hash: "); pdkim_hexprint(hhash
.data
, hhash
.len
);
1228 if (FALSE
/*need hash for Ed25519 or GCrypt signing*/ )
1231 (void) exim_sha_init(&hhash_ctx
, pdkim_hashes
[hashtype
].exim_hashmethod
);
1232 exim_sha_update(&hhash_ctx
, hdata
->s
, hdata
->ptr
);
1233 exim_sha_finish(&hhash_ctx
, &hhash
);
1237 hhash
.data
= hdata
->s
;
1238 hhash
.len
= hdata
->ptr
;
1241 if ( (errstr
= exim_dkim_signing_init(privkey
, &sctx
))
1242 || (errstr
= exim_dkim_sign(&sctx
, hm
, &hhash
, sig
)))
1244 log_write(0, LOG_MAIN
, "ARC: %s signing: %s\n", why
, errstr
);
1253 arc_sign_append_sig(gstring
* g
, blob
* sig
)
1255 /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1256 sig
->data
= pdkim_encode_base64(sig
);
1257 sig
->len
= Ustrlen(sig
->data
);
1260 int len
= MIN(sig
->len
, 74);
1261 g
= string_catn(g
, sig
->data
, len
);
1262 if ((sig
->len
-= len
) == 0) break;
1264 g
= string_catn(g
, US
"\r\n\t ", 5);
1266 g
= string_catn(g
, US
";\r\n", 3);
1267 gstring_reset_unused(g
);
1268 string_from_gstring(g
);
1273 /* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
1276 arc_sign_append_ams(gstring
* g
, arc_ctx
* ctx
, int instance
,
1277 const uschar
* identity
, const uschar
* selector
, blob
* bodyhash
,
1278 hdr_rlist
* rheaders
, const uschar
* privkey
, unsigned options
)
1281 gstring
* hdata
= NULL
;
1283 int hashtype
= pdkim_hashname_to_hashtype(US
"sha256", 6); /*XXX hardwired */
1286 arc_line
* al
= store_get(sizeof(header_line
) + sizeof(arc_line
));
1287 header_line
* h
= (header_line
*)(al
+1);
1289 /* debug_printf("%s\n", __FUNCTION__); */
1291 /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1294 g
= string_append(g
, 7,
1296 US
" i=", string_sprintf("%d", instance
),
1297 US
"; a=rsa-sha256; c=relaxed; d=", identity
, /*XXX hardwired */
1298 US
"; s=", selector
);
1299 if (options
& ARC_SIGN_OPT_TSTAMP
)
1300 g
= string_append(g
, 2,
1301 US
"; t=", string_sprintf("%lu", (u_long
)time(NULL
)));
1302 g
= string_append(g
, 3,
1303 US
";\r\n\tbh=", pdkim_encode_base64(bodyhash
),
1306 for(col
= 3; rheaders
; rheaders
= rheaders
->prev
)
1308 const uschar
* hnames
= US
"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS
;
1309 uschar
* name
, * htext
= rheaders
->h
->text
;
1312 /* Spot headers of interest */
1314 while ((name
= string_nextinlist(&hnames
, &sep
, NULL
, 0)))
1316 int len
= Ustrlen(name
);
1317 if (strncasecmp(CCS htext
, CCS name
, len
) == 0)
1319 /* If too long, fold line in h= field */
1321 if (col
+ len
> 78) g
= string_catn(g
, US
"\r\n\t ", 5), col
= 3;
1323 /* Add name to h= list */
1325 g
= string_catn(g
, name
, len
);
1326 g
= string_catn(g
, US
":", 1);
1329 /* Accumulate header for hashing/signing */
1331 hdata
= string_cat(hdata
,
1332 pdkim_relax_header_n(htext
, rheaders
->h
->slen
, TRUE
)); /*XXX hardwired */
1338 /* Lose the last colon from the h= list */
1340 if (g
->s
[g
->ptr
- 1] == ':') g
->ptr
--;
1342 g
= string_catn(g
, US
";\r\n\tb=;", 7);
1344 /* Include the pseudo-header in the accumulation */
1346 s
= pdkim_relax_header_n(g
->s
+ ams_off
, g
->ptr
- ams_off
, FALSE
);
1347 hdata
= string_cat(hdata
, s
);
1349 /* Calculate the signature from the accumulation */
1350 /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1352 if (!arc_sig_from_pseudoheader(hdata
, hashtype
, privkey
, &sig
, US
"AMS"))
1355 /* Lose the trailing semicolon from the psuedo-header, and append the signature
1356 (folded over lines) and termination to complete it. */
1359 g
= arc_sign_append_sig(g
, &sig
);
1361 h
->slen
= g
->ptr
- ams_off
;
1362 h
->text
= g
->s
+ ams_off
;
1364 ctx
->arcset_chain_last
->hdr_ams
= al
;
1366 DEBUG(D_transport
) debug_printf("ARC: AMS '%.*s'\n", h
->slen
- 2, h
->text
);
1372 /* Look for an arc= result in an A-R header blob. We know that its data
1373 happens to be a NUL-term string. */
1376 arc_ar_cv_status(blob
* ar
)
1378 const uschar
* resinfo
= ar
->data
;
1380 uschar
* methodspec
, * s
;
1382 while ((methodspec
= string_nextinlist(&resinfo
, &sep
, NULL
, 0)))
1383 if (Ustrncmp(methodspec
, US
"arc=", 4) == 0)
1386 for (s
= methodspec
+= 4;
1387 (c
= *s
) && c
!= ';' && c
!= ' ' && c
!= '\r' && c
!= '\n'; ) s
++;
1388 return string_copyn(methodspec
, s
- methodspec
);
1395 /* Build the AS header and prepend it */
1398 arc_sign_prepend_as(gstring
* arcset_interim
, arc_ctx
* ctx
,
1399 int instance
, const uschar
* identity
, const uschar
* selector
, blob
* ar
,
1400 const uschar
* privkey
, unsigned options
)
1404 uschar
* status
= arc_ar_cv_status(ar
);
1405 arc_line
* al
= store_get(sizeof(header_line
) + sizeof(arc_line
));
1406 header_line
* h
= (header_line
*)(al
+1);
1408 gstring
* hdata
= NULL
;
1409 int hashtype
= pdkim_hashname_to_hashtype(US
"sha256", 6); /*XXX hardwired */
1415 - no h= tag; implicit coverage
1416 - arc status from A-R
1418 - coverage is just the new ARC set
1419 including self (but with an empty b= in self)
1421 - all ARC set headers, set-number order, aar then ams then as,
1422 including self (but with an empty b= in self)
1425 /* Construct the AS except for the signature */
1427 arcset
= string_append(NULL
, 9,
1429 US
" i=", string_sprintf("%d", instance
),
1431 US
"; a=rsa-sha256; d=", identity
, /*XXX hardwired */
1432 US
"; s=", selector
); /*XXX same as AMS */
1433 if (options
& ARC_SIGN_OPT_TSTAMP
)
1434 arcset
= string_append(arcset
, 2,
1435 US
"; t=", string_sprintf("%lu", (u_long
)time(NULL
)));
1436 arcset
= string_cat(arcset
,
1439 h
->slen
= arcset
->ptr
;
1440 h
->text
= arcset
->s
;
1442 ctx
->arcset_chain_last
->hdr_as
= al
;
1444 /* For any but "fail" chain-verify status, walk the entire chain in order by
1445 instance. For fail, only the new arc-set. Accumulate the elements walked. */
1447 for (as
= Ustrcmp(status
, US
"fail") == 0
1448 ? ctx
->arcset_chain_last
: ctx
->arcset_chain
;
1451 /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
1452 is required per standard. */
1454 h
= as
->hdr_aar
->complete
;
1455 hdata
= string_cat(hdata
, pdkim_relax_header_n(h
->text
, h
->slen
, TRUE
));
1456 h
= as
->hdr_ams
->complete
;
1457 hdata
= string_cat(hdata
, pdkim_relax_header_n(h
->text
, h
->slen
, TRUE
));
1458 h
= as
->hdr_as
->complete
;
1459 hdata
= string_cat(hdata
, pdkim_relax_header_n(h
->text
, h
->slen
, !!as
->next
));
1462 /* Calculate the signature from the accumulation */
1464 if (!arc_sig_from_pseudoheader(hdata
, hashtype
, privkey
, &sig
, US
"AS"))
1467 /* Lose the trailing semicolon */
1469 arcset
= arc_sign_append_sig(arcset
, &sig
);
1470 DEBUG(D_transport
) debug_printf("ARC: AS '%.*s'\n", arcset
->ptr
- 2, arcset
->s
);
1472 /* Finally, append the AMS and AAR to the new AS */
1474 return string_catn(arcset
, arcset_interim
->s
, arcset_interim
->ptr
);
1478 /**************************************/
1480 /* Return pointer to pdkim_bodyhash for given hash method, creating new
1485 arc_ams_setup_sign_bodyhash(void)
1487 int canon_head
, canon_body
;
1489 DEBUG(D_transport
) debug_printf("ARC: requesting bodyhash\n");
1490 pdkim_cstring_to_canons(US
"relaxed", 7, &canon_head
, &canon_body
); /*XXX hardwired */
1491 return pdkim_set_bodyhash(&dkim_sign_ctx
,
1492 pdkim_hashname_to_hashtype(US
"sha256", 6), /*XXX hardwired */
1502 memset(&arc_sign_ctx
, 0, sizeof(arc_sign_ctx
));
1507 /* A "normal" header line, identified by DKIM processing. These arrive before
1508 the call to arc_sign(), which carries any newly-created DKIM headers - and
1509 those go textually before the normal ones in the message.
1511 We have to take the feed from DKIM as, in the transport-filter case, the
1512 headers are not in memory at the time of the call to arc_sign().
1514 Take a copy of the header and construct a reverse-order list.
1515 Also parse ARC-chain headers and build the chain struct, retaining pointers
1519 static const uschar
*
1520 arc_header_sign_feed(gstring
* g
)
1522 uschar
* s
= string_copyn(g
->s
, g
->ptr
);
1523 headers_rlist
= arc_rlist_entry(headers_rlist
, s
, g
->ptr
);
1524 return arc_try_header(&arc_sign_ctx
, headers_rlist
->h
, TRUE
);
1529 /* ARC signing. Called from the smtp transport, if the arc_sign option is set.
1530 The dkim_exim_sign() function has already been called, so will have hashed the
1531 message body for us so long as we requested a hash previously.
1534 signspec Three-element colon-sep list: identity, selector, privkey.
1535 Optional fourth element: comma-sep list of options.
1537 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1541 Set of headers to prepend to the message, including the supplied sigheaders
1542 but not the plainheaders.
1546 arc_sign(const uschar
* signspec
, gstring
* sigheaders
, uschar
** errstr
)
1548 const uschar
* identity
, * selector
, * privkey
, * opts
, * s
;
1549 unsigned options
= 0;
1551 header_line
* headers
;
1552 hdr_rlist
* rheaders
;
1558 /* Parse the signing specification */
1560 identity
= string_nextinlist(&signspec
, &sep
, NULL
, 0);
1561 selector
= string_nextinlist(&signspec
, &sep
, NULL
, 0);
1562 if ( !*identity
| !*selector
1563 || !(privkey
= string_nextinlist(&signspec
, &sep
, NULL
, 0)) || !*privkey
)
1565 log_write(0, LOG_MAIN
, "ARC: bad signing-specification (%s)",
1566 !*identity
? "identity" : !*selector
? "selector" : "private-key");
1567 return sigheaders
? sigheaders
: string_get(0);
1569 if (*privkey
== '/' && !(privkey
= expand_file_big_buffer(privkey
)))
1570 return sigheaders
? sigheaders
: string_get(0);
1572 if ((opts
= string_nextinlist(&signspec
, &sep
, NULL
, 0)))
1575 while ((s
= string_nextinlist(&opts
, &osep
, NULL
, 0)))
1576 if (Ustrcmp(s
, "timestamps") == 0)
1577 options
|= ARC_SIGN_OPT_TSTAMP
;
1580 DEBUG(D_transport
) debug_printf("ARC: sign for %s\n", identity
);
1582 /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
1583 Then scan the list for an A-R header. */
1585 string_from_gstring(sigheaders
);
1586 if ((rheaders
= arc_sign_scan_headers(&arc_sign_ctx
, sigheaders
)))
1589 for (rp
= &rheaders
; *rp
; ) rp
= &(*rp
)->prev
;
1590 *rp
= headers_rlist
;
1591 headers_rlist
= rheaders
;
1594 rheaders
= headers_rlist
;
1596 /* Finally, build a normal-order headers list */
1597 /*XXX only needed for hunt-the-AR? */
1599 header_line
* hnext
= NULL
;
1600 for (; rheaders
; hnext
= rheaders
->h
, rheaders
= rheaders
->prev
)
1601 rheaders
->h
->next
= hnext
;
1605 if (!(arc_sign_find_ar(headers
, identity
, &ar
)))
1607 log_write(0, LOG_MAIN
, "ARC: no Authentication-Results header for signing");
1608 return sigheaders
? sigheaders
: string_get(0);
1611 /* We previously built the data-struct for the existing ARC chain, if any, using a headers
1612 feed from the DKIM module. Use that to give the instance number for the ARC set we are
1616 if (arc_sign_ctx
.arcset_chain_last
)
1617 debug_printf("ARC: existing chain highest instance: %d\n",
1618 arc_sign_ctx
.arcset_chain_last
->instance
);
1620 debug_printf("ARC: no existing chain\n");
1622 instance
= arc_sign_ctx
.arcset_chain_last
? arc_sign_ctx
.arcset_chain_last
->instance
+ 1 : 1;
1626 - copy the A-R; prepend i= & identity
1629 g
= arc_sign_append_aar(g
, &arc_sign_ctx
, identity
, instance
, &ar
);
1633 - Looks fairly like a DKIM sig
1634 - Cover all DKIM sig headers as well as the usuals
1637 - we must have requested a suitable bodyhash previously
1640 b
= arc_ams_setup_sign_bodyhash();
1641 g
= arc_sign_append_ams(g
, &arc_sign_ctx
, instance
, identity
, selector
,
1642 &b
->bh
, headers_rlist
, privkey
, options
);
1647 - no h= tag; implicit coverage
1648 - arc status from A-R
1650 - coverage is just the new ARC set
1651 including self (but with an empty b= in self)
1653 - all ARC set headers, set-number order, aar then ams then as,
1654 including self (but with an empty b= in self)
1657 g
= arc_sign_prepend_as(g
, &arc_sign_ctx
, instance
, identity
, selector
, &ar
,
1660 /* Finally, append the dkim headers and return the lot. */
1662 g
= string_catn(g
, sigheaders
->s
, sigheaders
->ptr
);
1663 (void) string_from_gstring(g
);
1664 gstring_reset_unused(g
);
1669 /******************************************************************************/
1671 /* Check to see if the line is an AMS and if so, set up to validate it.
1672 Called from the DKIM input processing. This must be done now as the message
1673 body data is hashed during input.
1675 We call the DKIM code to request a body-hash; it has the facility already
1676 and the hash parameters might be common with other requests.
1679 static const uschar
*
1680 arc_header_vfy_feed(gstring
* g
)
1687 if (!dkim_verify_ctx
) return US
"no dkim context";
1689 if (strncmpic(ARC_HDR_AMS
, g
->s
, ARC_HDRLEN_AMS
) != 0) return US
"not AMS";
1691 DEBUG(D_receive
) debug_printf("ARC: spotted AMS header\n");
1692 /* Parse the AMS header */
1697 memset(&al
, 0, sizeof(arc_line
));
1698 if ((errstr
= arc_parse_line(&al
, &h
, ARC_HDRLEN_AMS
, FALSE
)))
1700 DEBUG(D_acl
) if (errstr
) debug_printf("ARC: %s\n", errstr
);
1701 return US
"line parsing error";
1707 al
.c_body
.data
= US
"simple"; al
.c_body
.len
= 6;
1708 al
.c_head
= al
.c_body
;
1711 /* Ask the dkim code to calc a bodyhash with those specs */
1713 if (!(b
= arc_ams_setup_vfy_bodyhash(&al
)))
1714 return US
"dkim hash setup fail";
1716 /* Discard the reference; search again at verify time, knowing that one
1717 should have been created here. */
1724 /* A header line has been identified by DKIM processing.
1728 is_vfy TRUE for verify mode or FALSE for signing mode
1731 NULL for success, or an error string (probably unused)
1735 arc_header_feed(gstring
* g
, BOOL is_vfy
)
1737 return is_vfy
? arc_header_vfy_feed(g
) : arc_header_sign_feed(g
);
1742 /******************************************************************************/
1744 /* Construct an Authenticate-Results header portion, for the ARC module */
1747 authres_arc(gstring
* g
)
1751 arc_line
* highest_ams
;
1752 int start
= 0; /* Compiler quietening */
1753 DEBUG(D_acl
) start
= g
->ptr
;
1755 g
= string_append(g
, 2, US
";\n\tarc=", arc_state
);
1756 if (arc_received_instance
> 0)
1758 g
= string_append(g
, 3, US
" (i=",
1759 string_sprintf("%d", arc_received_instance
), US
")");
1760 if (arc_state_reason
)
1761 g
= string_append(g
, 3, US
"(", arc_state_reason
, US
")");
1762 g
= string_catn(g
, US
" header.s=", 10);
1763 highest_ams
= arc_received
->hdr_ams
;
1764 g
= string_catn(g
, highest_ams
->s
.data
, highest_ams
->s
.len
);
1766 g
= string_append(g
, 2,
1767 US
" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass
));
1769 if (sender_host_address
)
1770 g
= string_append(g
, 2, US
" smtp.client-ip=", sender_host_address
);
1772 else if (arc_state_reason
)
1773 g
= string_append(g
, 3, US
" (", arc_state_reason
, US
")");
1774 DEBUG(D_acl
) debug_printf("ARC: authres '%.*s'\n",
1775 g
->ptr
- start
- 3, g
->s
+ start
+ 3);
1778 DEBUG(D_acl
) debug_printf("ARC: no authres\n");
1783 # endif /* SUPPORT_SPF */
1784 #endif /* EXPERIMENTAL_ARC */