ARC: add $arc_state, $arc_state_reason and add reason to authres string
[exim.git] / src / src / arc.c
CommitLineData
617d3932
JH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4/* Experimental ARC support for Exim
5 Copyright (c) Jeremy Harris 2018
6 License: GPL
7*/
8
9#include "exim.h"
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
15# else
16
17# include "functions.h"
18# include "pdkim/pdkim.h"
19# include "pdkim/signing.h"
20
21extern pdkim_ctx * dkim_verify_ctx;
22extern pdkim_ctx dkim_sign_ctx;
23
24/******************************************************************************/
25
26typedef struct hdr_rlist {
27 struct hdr_rlist * prev;
28 BOOL used;
29 header_line * h;
30} hdr_rlist;
31
32typedef struct arc_line {
33 header_line * complete; /* including the header name; nul-term */
34 uschar * relaxed;
35
36 /* identified tag contents */
37 /*XXX t= for AS? */
38 blob i;
39 blob cv;
40 blob a;
41 blob b;
42 blob bh;
43 blob d;
44 blob h;
45 blob s;
46 blob c;
47 blob l;
48
49 /* tag content sub-portions */
50 blob a_algo;
51 blob a_hash;
52
53 blob c_head;
54 blob c_body;
55
56 /* modified copy of b= field in line */
57 blob rawsig_no_b_val;
58} arc_line;
59
60typedef struct arc_set {
61 struct arc_set * next;
62 struct arc_set * prev;
63
64 unsigned instance;
65 arc_line * hdr_aar;
66 arc_line * hdr_ams;
67 arc_line * hdr_as;
68
93c931f8 69 const uschar * ams_verify_done;
617d3932
JH
70 BOOL ams_verify_passed;
71} arc_set;
72
73typedef struct arc_ctx {
74 arc_set * arcset_chain;
75 arc_set * arcset_chain_last;
76} arc_ctx;
77
78#define ARC_HDR_AAR US"ARC-Authentication-Results:"
79#define ARC_HDRLEN_AAR 27
80#define ARC_HDR_AMS US"ARC-Message-Signature:"
81#define ARC_HDRLEN_AMS 22
82#define ARC_HDR_AS US"ARC-Seal:"
83#define ARC_HDRLEN_AS 9
84#define HDR_AR US"Authentication-Results:"
85#define HDRLEN_AR 23
86
87static hdr_rlist * headers_rlist;
88static arc_ctx arc_sign_ctx = { NULL };
89
90
91/******************************************************************************/
92
93
94/* Get the instance number from the header.
95Return 0 on error */
96static unsigned
97arc_instance_from_hdr(const arc_line * al)
98{
978e78de 99const uschar * s = al->i.data;
617d3932 100if (!s || !al->i.len) return 0;
978e78de 101return (unsigned) atoi(CCS s);
617d3932
JH
102}
103
104
105static uschar *
106skip_fws(uschar * s)
107{
108uschar c = *s;
109while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
110return s;
111}
112
113
114/* Locate instance struct on chain, inserting a new one if
115needed. The chain is in increasing-instance-number order
116by the "next" link, and we have a "prev" link also.
117*/
118
119static arc_set *
120arc_find_set(arc_ctx * ctx, unsigned i)
121{
122arc_set ** pas, * as, * next, * prev;
123
124for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
125 as = *pas; pas = &as->next)
126 {
127 if (as->instance > i) break;
128 if (as->instance == i)
129 {
130 DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
131 return as;
132 }
133 next = as->next;
134 prev = as;
135 }
136
137DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
138*pas = as = store_get(sizeof(arc_set));
139memset(as, 0, sizeof(arc_set));
140as->next = next;
141as->prev = prev;
142as->instance = i;
143if (next)
144 next->prev = as;
145else
146 ctx->arcset_chain_last = as;
147return as;
148}
149
150
151
152/* Insert a tag content into the line structure.
153Note this is a reference to existing data, not a copy.
154Check for already-seen tag.
155The string-pointer is on the '=' for entry. Update it past the
156content (to the ;) on return;
157*/
158
159static uschar *
160arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
161{
162uschar * s = *ss;
163uschar c = *++s;
164blob * b = (blob *)(US al + loff);
165size_t len = 0;
166
167/* [FWS] tag-value [FWS] */
168
169if (b->data) return US"fail";
170s = skip_fws(s); /* FWS */
171
172b->data = s;
173while ((c = *s) && c != ';') { len++; s++; }
174*ss = s;
175while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
176 { s--; len--; } /* FWS */
177b->len = len;
178return NULL;
179}
180
181
182/* Inspect a header line, noting known tag fields.
183Check for duplicates. */
184
185static uschar *
186arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only)
187{
188uschar * s = h->text + off;
978e78de 189uschar * r = NULL; /* compiler-quietening */
617d3932
JH
190uschar c;
191
192al->complete = h;
193
194if (!instance_only)
195 {
196 al->rawsig_no_b_val.data = store_get(h->slen + 1);
197 memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */
198 r = al->rawsig_no_b_val.data + off;
199 al->rawsig_no_b_val.len = off;
200 }
201
202/* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */
203
204while ((c = *s))
205 {
206 char tagchar;
207 uschar * t;
208 unsigned i = 0;
209 uschar * fieldstart = s;
210 uschar * bstart = NULL, * bend;
211
212 /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
213
214 s = skip_fws(s); /* FWS */
215 if (!*s) break;
216/* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */
217 tagchar = *s++;
218 s = skip_fws(s); /* FWS */
219 if (!*s) break;
220
221 if (!instance_only || tagchar == 'i') switch (tagchar)
222 {
223 case 'a': /* a= AMS algorithm */
224 {
225 if (*s != '=') return US"no 'a' value";
226 if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
227
228 /* substructure: algo-hash (eg. rsa-sha256) */
229
230 t = al->a_algo.data = al->a.data;
231 while (*t != '-')
232 if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
233 al->a_algo.len = i;
234 if (*t++ != '-') return US"no '-' in 'a' value";
235 al->a_hash.data = t;
236 al->a_hash.len = al->a.len - i - 1;
237 }
238 break;
239 case 'b':
240 {
241 gstring * g = NULL;
242
243 switch (*s)
244 {
245 case '=': /* b= AMS signature */
246 if (al->b.data) return US"already b data";
247 bstart = s+1;
248
249 /* The signature can have FWS inserted in the content;
250 make a stripped copy */
251
252 while ((c = *++s) && c != ';')
253 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
254 g = string_catn(g, s, 1);
255 al->b.data = string_from_gstring(g);
256 al->b.len = g->ptr;
257 gstring_reset_unused(g);
258 bend = s;
259 break;
260 case 'h': /* bh= AMS body hash */
261 s = skip_fws(++s); /* FWS */
262 if (*s != '=') return US"no bh value";
263 if (al->bh.data) return US"already bh data";
264
265 /* The bodyhash can have FWS inserted in the content;
266 make a stripped copy */
267
268 while ((c = *++s) && c != ';')
269 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
270 g = string_catn(g, s, 1);
271 al->bh.data = string_from_gstring(g);
272 al->bh.len = g->ptr;
273 gstring_reset_unused(g);
274 break;
275 default:
276 return US"b? tag";
277 }
278 }
279 break;
280 case 'c':
281 switch (*s)
282 {
283 case '=': /* c= AMS canonicalisation */
284 if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
285
286 /* substructure: head/body (eg. relaxed/simple)) */
287
288 t = al->c_head.data = al->c.data;
289 while (isalpha(*t))
290 if (!*t++ || ++i > al->a.len) break;
291 al->c_head.len = i;
292 if (*t++ == '/') /* /body is optional */
293 {
294 al->c_body.data = t;
295 al->c_body.len = al->c.len - i - 1;
296 }
297 else
298 {
299 al->c_body.data = US"simple";
300 al->c_body.len = 6;
301 }
302 break;
303 case 'v': /* cv= AS validity */
304 if (*++s != '=') return US"cv tag val";
305 if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup";
306 break;
307 default:
308 return US"c? tag";
309 }
310 break;
311 case 'd': /* d= AMS domain */
312 if (*s != '=') return US"d tag val";
313 if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup";
314 break;
315 case 'h': /* h= AMS headers */
316 if (*s != '=') return US"h tag val";
317 if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup";
318 break;
319 case 'i': /* i= ARC set instance */
320 if (*s != '=') return US"i tag val";
321 if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup";
322 if (instance_only) goto done;
323 break;
324 case 'l': /* l= bodylength */
325 if (*s != '=') return US"l tag val";
326 if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup";
327 break;
328 case 's': /* s= AMS selector */
329 if (*s != '=') return US"s tag val";
330 if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup";
331 break;
332 }
333
334 while ((c = *s) && c != ';') s++;
335 if (c) s++; /* ; after tag-spec */
336
337 /* for all but the b= tag, copy the field including FWS. For the b=,
338 drop the tag content. */
339
340 if (!instance_only)
341 if (bstart)
342 {
343 size_t n = bstart - fieldstart;
344 memcpy(r, fieldstart, n); /* FWS "b=" */
345 r += n;
346 al->rawsig_no_b_val.len += n;
347 n = s - bend;
348 memcpy(r, bend, n); /* FWS ";" */
349 r += n;
350 al->rawsig_no_b_val.len += n;
351 }
352 else
353 {
354 size_t n = s - fieldstart;
355 memcpy(r, fieldstart, n);
356 r += n;
357 al->rawsig_no_b_val.len += n;
358 }
359 }
360
361if (!instance_only)
362 *r = '\0';
363
364done:
365/* debug_printf("%s: finshed\n", __FUNCTION__); */
366return NULL;
367}
368
369
370/* Insert one header line in the correct set of the chain,
371adding instances as needed and checking for duplicate lines.
372*/
373
374static uschar *
375arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
376 BOOL instance_only)
377{
378int i;
379arc_set * as;
380arc_line * al = store_get(sizeof(arc_line)), ** alp;
381uschar * e;
382
383memset(al, 0, sizeof(arc_line));
384
385if ((e = arc_parse_line(al, h, off, instance_only)))
386 {
387 DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
388 return US"line parse";
389 }
390if (!(i = arc_instance_from_hdr(al))) return US"instance find";
391if (!(as = arc_find_set(ctx, i))) return US"set find";
392if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
393
394*alp = al;
395return NULL;
396}
397
398
399
400
401static const uschar *
402arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only)
403{
404const uschar * e;
405
406/*debug_printf("consider hdr '%s'\n", h->text);*/
407if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
408 {
409 DEBUG(D_acl)
410 {
411 int len = h->slen;
412 uschar * s;
413 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
414 s--, len--;
415 debug_printf("ARC: found AAR: %.*s\n", len, h->text);
416 }
417 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
418 TRUE)))
419 {
420 DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
421 return US"inserting AAR";
422 }
423 }
424else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
425 {
426 arc_line * ams;
427
428 DEBUG(D_acl)
429 {
430 int len = h->slen;
431 uschar * s;
432 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
433 s--, len--;
434 debug_printf("ARC: found AMS: %.*s\n", len, h->text);
435 }
436 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
437 instance_only)))
438 {
439 DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
440 return US"inserting AMS";
441 }
442
443 /* defaults */
444 /*XXX dubious selection of ams here */
445 ams = ctx->arcset_chain->hdr_ams;
446 if (!ams->c.data)
447 {
448 ams->c_head.data = US"simple"; ams->c_head.len = 6;
449 ams->c_body = ams->c_head;
450 }
451 }
452else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
453 {
454 DEBUG(D_acl)
455 {
456 int len = h->slen;
457 uschar * s;
458 for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
459 s--, len--;
460 debug_printf("ARC: found AS: %.*s\n", len, h->text);
461 }
462 if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
463 instance_only)))
464 {
465 DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
466 return US"inserting AS";
467 }
468 }
469return NULL;
470}
471
472
473
474/* Gather the chain of arc sets from the headers.
475Check for duplicates while that is done. Also build the
476reverse-order headers list;
477
478Return: ARC state if determined, eg. by lack of any ARC chain.
479*/
480
481static const uschar *
482arc_vfy_collect_hdrs(arc_ctx * ctx)
483{
484header_line * h;
978e78de 485hdr_rlist * r = NULL, * rprev = NULL;
617d3932
JH
486const uschar * e;
487
488DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
489for (h = header_list; h; h = h->next)
490 {
491 r = store_get(sizeof(hdr_rlist));
492 r->prev = rprev;
493 r->used = FALSE;
494 r->h = h;
495 rprev = r;
496
497 if ((e = arc_try_header(ctx, h, FALSE)))
93c931f8
JH
498 {
499 arc_state_reason = string_sprintf("collecting headers: %s", e);
500 return US"fail";
501 }
617d3932
JH
502 }
503headers_rlist = r;
504
505if (!ctx->arcset_chain) return US"none";
506return NULL;
507}
508
509
510static BOOL
511arc_cv_match(arc_line * al, const uschar * s)
512{
513return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
514}
515
516/******************************************************************************/
517
518/* Return the hash of headers from the message that the AMS claims it
519signed.
520*/
521
522static void
523arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
524{
525const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
526const uschar * hn;
527int sep = ':';
528hdr_rlist * r;
529BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
530int hashtype = pdkim_hashname_to_hashtype(
531 ams->a_hash.data, ams->a_hash.len);
532hctx hhash_ctx;
533const uschar * s;
534int len;
535
536if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
537 {
538 DEBUG(D_acl)
539 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
540 return;
541 }
542
543/* For each headername in the list from the AMS (walking in order)
544walk the message headers in reverse order, adding to the hash any
545found for the first time. For that last point, maintain used-marks
546on the list of message headers. */
547
548DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
549
550for (r = headers_rlist; r; r = r->prev)
551 r->used = FALSE;
552while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
553 for (r = headers_rlist; r; r = r->prev)
554 if ( !r->used
555 && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
556 )
557 {
558 if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
559
560 len = Ustrlen(s);
561 DEBUG(D_acl) pdkim_quoteprint(s, len);
562 exim_sha_update(&hhash_ctx, s, Ustrlen(s));
563 r->used = TRUE;
564 break;
565 }
566
567/* Finally add in the signature header (with the b= tag stripped) */
568
569s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
570if (relaxed)
571 len = Ustrlen(s = pdkim_relax_header_n(s, len, TRUE));
572DEBUG(D_acl) pdkim_quoteprint(s, len);
573exim_sha_update(&hhash_ctx, s, len);
574
575exim_sha_finish(&hhash_ctx, hhash);
576DEBUG(D_acl)
577 { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
578return;
579}
580
581
582
583
584static pdkim_pubkey *
585arc_line_to_pubkey(arc_line * al)
586{
587uschar * dns_txt;
588pdkim_pubkey * p;
589
590if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
591 al->s.len, al->s.data, al->d.len, al->d.data))))
592 {
593 DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
594 return NULL;
595 }
596
597if ( !(p = pdkim_parse_pubkey_record(dns_txt))
598 || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
599 )
600 {
601 DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
602 return NULL;
603 }
604
605/* If the pubkey limits use to specified hashes, reject unusable
606signatures. XXX should we have looked for multiple dns records? */
607
608if (p->hashes)
609 {
610 const uschar * list = p->hashes, * ele;
611 int sep = ':';
612
613 while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
614 if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
615 if (!ele)
616 {
617 DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
978e78de 618 p->hashes, (int)al->a.len, al->a.data);
617d3932
JH
619 return NULL;
620 }
621 }
622return p;
623}
624
625
626
627
628static pdkim_bodyhash *
629arc_ams_setup_vfy_bodyhash(arc_line * ams)
630{
631int canon_head, canon_body;
632long bodylen;
633
634pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
635bodylen = ams->l.data
636 ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
637
638return pdkim_set_bodyhash(dkim_verify_ctx,
639 pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
640 canon_body,
641 bodylen);
642}
643
644
645
646/* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
647and without a DKIM v= tag.
648*/
649
93c931f8 650static const uschar *
617d3932
JH
651arc_ams_verify(arc_ctx * ctx, arc_set * as)
652{
653arc_line * ams = as->hdr_ams;
654pdkim_bodyhash * b;
655pdkim_pubkey * p;
656blob sighash;
657blob hhash;
658ev_ctx vctx;
659int hashtype;
660const uschar * errstr;
661
93c931f8 662as->ams_verify_done = US"in-progress";
617d3932
JH
663
664/* Check the AMS has all the required tags:
665 "a=" algorithm
666 "b=" signature
667 "bh=" body hash
668 "d=" domain (for key lookup)
669 "h=" headers (included in signature)
670 "s=" key-selector (for key lookup)
671*/
672if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
673 || !ams->h.data || !ams->s.data)
93c931f8
JH
674 {
675 as->ams_verify_done = arc_state_reason = US"required tag missing";
617d3932 676 return US"fail";
93c931f8 677 }
617d3932
JH
678
679
680/* The bodyhash should have been created earlier, and the dkim code should
681have managed calculating it during message input. Find the reference to it. */
682
683if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
93c931f8
JH
684 {
685 as->ams_verify_done = arc_state_reason = US"internal hash setup error";
617d3932 686 return US"fail";
93c931f8 687 }
617d3932
JH
688
689DEBUG(D_acl)
690 {
691 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
692 " Body %.*s computed: ",
693 as->instance, b->signed_body_bytes,
978e78de 694 (int)ams->a_hash.len, ams->a_hash.data);
617d3932
JH
695 pdkim_hexprint(CUS b->bh.data, b->bh.len);
696 }
697
698/* We know the bh-tag blob is of a nul-term string, so safe as a string */
699
700if ( !ams->bh.data
701 || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
702 || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
703 )
704 {
705 DEBUG(D_acl)
706 {
707 debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
708 pdkim_hexprint(sighash.data, sighash.len);
709 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
710 }
93c931f8 711 return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
617d3932
JH
712 }
713
714DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
715
716/* Get the public key from DNS */
717
718if (!(p = arc_line_to_pubkey(ams)))
93c931f8 719 return as->ams_verify_done = arc_state_reason = US"pubkey problem";
617d3932
JH
720
721/* We know the b-tag blob is of a nul-term string, so safe as a string */
722pdkim_decode_base64(ams->b.data, &sighash);
723
724arc_get_verify_hhash(ctx, ams, &hhash);
725
726/* Setup the interface to the signing library */
727
728if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
729 {
730 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
93c931f8 731 as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
617d3932
JH
732 return US"fail";
733 }
734
735hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
736
737if ((errstr = exim_dkim_verify(&vctx,
738 pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
739 {
740 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
93c931f8 741 return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
617d3932
JH
742 }
743
744DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
745as->ams_verify_passed = TRUE;
746return NULL;
747}
748
749
750
751/* Check the sets are instance-continuous and that all
752members are present. Check that no arc_seals are "fail".
753Set the highest instance number global.
754Verify the latest AMS.
755*/
756static uschar *
757arc_headers_check(arc_ctx * ctx)
758{
759arc_set * as;
760int inst;
761BOOL ams_fail_found = FALSE;
762uschar * ret = NULL;
763
764if (!(as = ctx->arcset_chain))
765 return US"none";
766
767for(inst = 0; as; as = as->next)
768 {
769 if ( as->instance != ++inst
770 || !as->hdr_aar || !as->hdr_ams || !as->hdr_as
771 || arc_cv_match(as->hdr_as, US"fail")
772 )
773 {
93c931f8
JH
774 arc_state_reason = string_sprintf("i=%d fail"
775 " (cv, sequence or missing header)", as->instance);
776 DEBUG(D_acl) debug_printf("ARC %s\n", arc_state_reason);
617d3932
JH
777 ret = US"fail";
778 }
779
780 /* Evaluate the oldest-pass AMS validation while we're here.
781 It does not affect the AS chain validation but is reported as
782 auxilary info. */
783
784 if (!ams_fail_found)
785 if (arc_ams_verify(ctx, as))
786 ams_fail_found = TRUE;
787 else
788 arc_oldest_pass = inst;
93c931f8 789 arc_state_reason = NULL;
617d3932
JH
790 }
791
792arc_received = ctx->arcset_chain_last;
793arc_received_instance = inst;
794if (ret)
795 return ret;
796
797/* We can skip the latest-AMS validation, if we already did it. */
798
799as = ctx->arcset_chain_last;
93c931f8
JH
800if (as->ams_verify_done && !as->ams_verify_passed)
801 {
802 arc_state_reason = as->ams_verify_done;
803 return US"fail";
804 }
805if (!!arc_ams_verify(ctx, as))
617d3932
JH
806 return US"fail";
807
808return NULL;
809}
810
811
812/******************************************************************************/
813static const uschar *
814arc_seal_verify(arc_ctx * ctx, arc_set * as)
815{
816arc_line * hdr_as = as->hdr_as;
817arc_set * as2;
818int hashtype;
819hctx hhash_ctx;
820blob hhash_computed;
821blob sighash;
822ev_ctx vctx;
823pdkim_pubkey * p;
824const uschar * errstr;
825
826DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
827/*
828 1. If the value of the "cv" tag on that seal is "fail", the
829 chain state is "fail" and the algorithm stops here. (This
830 step SHOULD be skipped if the earlier step (2.1) was
831 performed) [it was]
832
833 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
834 == "none" && i != 1)) then the chain state is "fail" and the
835 algorithm stops here (note that the ordering of the logic is
836 structured for short-circuit evaluation).
837*/
838
839if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none")
840 || arc_cv_match(hdr_as, US"none") && as->instance != 1
841 )
93c931f8
JH
842 {
843 arc_state_reason = US"seal cv state";
617d3932 844 return US"fail";
93c931f8 845 }
617d3932
JH
846
847/*
848 3. Initialize a hash function corresponding to the "a" tag of
849 the ARC-Seal.
850*/
851
852hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
853
854if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
855 {
856 DEBUG(D_acl)
857 debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
93c931f8 858 arc_state_reason = US"seal hash setup error";
617d3932
JH
859 return US"fail";
860 }
861
862/*
863 4. Compute the canonicalized form of the ARC header fields, in
864 the order described in Section 5.4.2, using the "relaxed"
865 header canonicalization defined in Section 3.4.2 of
866 [RFC6376]. Pass the canonicalized result to the hash
867 function.
868*/
869
870DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
871for (as2 = ctx->arcset_chain;
872 as2 && as2->instance <= as->instance;
873 as2 = as2->next)
874 {
875 arc_line * al;
876 uschar * s;
877 int len;
878
879 al = as2->hdr_aar;
880 if (!(s = al->relaxed))
881 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
882 al->complete->slen, TRUE);
883 len = Ustrlen(s);
884 DEBUG(D_acl) pdkim_quoteprint(s, len);
885 exim_sha_update(&hhash_ctx, s, len);
886
887 al = as2->hdr_ams;
888 if (!(s = al->relaxed))
889 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
890 al->complete->slen, TRUE);
891 len = Ustrlen(s);
892 DEBUG(D_acl) pdkim_quoteprint(s, len);
893 exim_sha_update(&hhash_ctx, s, len);
894
895 al = as2->hdr_as;
896 if (as2->instance == as->instance)
897 s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
898 al->rawsig_no_b_val.len, TRUE);
899 else if (!(s = al->relaxed))
900 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
901 al->complete->slen, TRUE);
902 len = Ustrlen(s);
903 DEBUG(D_acl) pdkim_quoteprint(s, len);
904 exim_sha_update(&hhash_ctx, s, len);
905 }
906
907/*
908 5. Retrieve the final digest from the hash function.
909*/
910
911exim_sha_finish(&hhash_ctx, &hhash_computed);
912DEBUG(D_acl)
913 {
914 debug_printf("ARC i=%d AS Header %.*s computed: ",
978e78de 915 as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
617d3932
JH
916 pdkim_hexprint(hhash_computed.data, hhash_computed.len);
917 }
918
919
920/*
921 6. Retrieve the public key identified by the "s" and "d" tags in
922 the ARC-Seal, as described in Section 4.1.6.
923*/
924
925if (!(p = arc_line_to_pubkey(hdr_as)))
926 return US"pubkey problem";
927
928/*
929 7. Determine whether the signature portion ("b" tag) of the ARC-
930 Seal and the digest computed above are valid according to the
931 public key. (See also Section Section 8.4 for failure case
932 handling)
933
934 8. If the signature is not valid, the chain state is "fail" and
935 the algorithm stops here.
936*/
937
938/* We know the b-tag blob is of a nul-term string, so safe as a string */
939pdkim_decode_base64(hdr_as->b.data, &sighash);
940
941if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
942 {
943 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
944 return US"fail";
945 }
946
947hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
948
949if ((errstr = exim_dkim_verify(&vctx,
950 pdkim_hashes[hashtype].exim_hashmethod,
951 &hhash_computed, &sighash)))
952 {
953 DEBUG(D_acl)
954 debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
93c931f8 955 arc_state_reason = US"seal sigverify init error";
617d3932
JH
956 return US"fail";
957 }
958
959DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
960return NULL;
961}
962
963
964static const uschar *
965arc_verify_seals(arc_ctx * ctx)
966{
967arc_set * as = ctx->arcset_chain;
968
969if (!as)
970 return US"none";
971
972while (as)
973 {
974 if (arc_seal_verify(ctx, as)) return US"fail";
975 as = as->next;
976 }
977DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
978return NULL;
979}
980/******************************************************************************/
981
982/* Do ARC verification. Called from DATA ACL, on a verify = arc
983condition. No arguments; we are checking globals.
984
985Return: The ARC state, or NULL on error.
986*/
987
988const uschar *
989acl_verify_arc(void)
990{
991arc_ctx ctx = { NULL };
992const uschar * res;
993
994/* AS evaluation, per
995https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
996*/
997/* 1. Collect all ARC sets currently on the message. If there were
998 none, the ARC state is "none" and the algorithm stops here.
999*/
1000
1001if ((res = arc_vfy_collect_hdrs(&ctx)))
1002 goto out;
1003
1004/* 2. If the form of any ARC set is invalid (e.g., does not contain
1005 exactly one of each of the three ARC-specific header fields),
1006 then the chain state is "fail" and the algorithm stops here.
1007
1008 1. To avoid the overhead of unnecessary computation and delay
1009 from crypto and DNS operations, the cv value for all ARC-
1010 Seal(s) MAY be checked at this point. If any of the values
1011 are "fail", then the overall state of the chain is "fail" and
1012 the algorithm stops here.
1013
1014 3. Conduct verification of the ARC-Message-Signature header field
1015 bearing the highest instance number. If this verification fails,
1016 then the chain state is "fail" and the algorithm stops here.
1017*/
1018
1019if ((res = arc_headers_check(&ctx)))
1020 goto out;
1021
1022/* 4. For each ARC-Seal from the "N"th instance to the first, apply the
1023 following logic:
1024
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
1028 performed)
1029
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).
1034
1035 3. Initialize a hash function corresponding to the "a" tag of
1036 the ARC-Seal.
1037
1038 4. Compute the canonicalized form of the ARC header fields, in
1039 the order described in Section 5.4.2, using the "relaxed"
1040 header canonicalization defined in Section 3.4.2 of
1041 [RFC6376]. Pass the canonicalized result to the hash
1042 function.
1043
1044 5. Retrieve the final digest from the hash function.
1045
1046 6. Retrieve the public key identified by the "s" and "d" tags in
1047 the ARC-Seal, as described in Section 4.1.6.
1048
1049 7. Determine whether the signature portion ("b" tag) of the ARC-
1050 Seal and the digest computed above are valid according to the
1051 public key. (See also Section Section 8.4 for failure case
1052 handling)
1053
1054 8. If the signature is not valid, the chain state is "fail" and
1055 the algorithm stops here.
1056
1057 5. If all seals pass validation, then the chain state is "pass", and
1058 the algorithm is complete.
1059*/
1060
1061if ((res = arc_verify_seals(&ctx)))
1062 goto out;
1063
1064res = US"pass";
1065
1066out:
1067 return res;
1068}
1069
1070/******************************************************************************/
1071
1072/* Prepend the header to the rlist */
1073
1074static hdr_rlist *
1075arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1076{
1077hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
1078header_line * h = r->h = (header_line *)(r+1);
1079
1080r->prev = list;
1081r->used = FALSE;
1082h->next = NULL;
1083h->type = 0;
1084h->slen = len;
1085h->text = US s;
1086
1087/* This works for either NL or CRLF lines; also nul-termination */
1088while (*++s)
1089 if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
1090s++; /* move past end of line */
1091
1092return r;
1093}
1094
1095
1096/* Walk the given headers strings identifying each header, and construct
1097a reverse-order list. Also parse ARC-chain headers and build the chain
1098struct, retaining pointers into the string.
1099*/
1100
1101static hdr_rlist *
1102arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1103{
1104const uschar * s;
1105hdr_rlist * rheaders = NULL;
1106
1107s = sigheaders ? sigheaders->s : NULL;
1108if (s) while (*s)
1109 {
978e78de 1110 const uschar * s2 = s;
617d3932
JH
1111
1112 /* This works for either NL or CRLF lines; also nul-termination */
1113 while (*++s2)
1114 if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1115 s2++; /* move past end of line */
1116
1117 rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1118 s = s2;
1119 }
1120return rheaders;
1121}
1122
1123
1124
1125/* Return the A-R content, without identity, with line-ending and
1126NUL termination. */
1127
1128static BOOL
1129arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1130{
1131header_line * h;
1132int ilen = Ustrlen(identity);
1133
1134ret->data = NULL;
1135for(h = headers; h; h = h->next)
1136 {
1137 uschar * s = h->text, c;
1138 int len = h->slen;
1139
1140 if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1141 s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */
1142 while ( len > 0
1143 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1144 s++, len--; /* FWS */
1145 if (Ustrncmp(s, identity, ilen) != 0) continue;
1146 s += ilen; len -= ilen; /* identity */
1147 if (len <= 0) continue;
1148 if ((c = *s) && c == ';') s++, len--; /* identity terminator */
1149 while ( len > 0
1150 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1151 s++, len--; /* FWS */
1152 if (len <= 0) continue;
1153 ret->data = s;
1154 ret->len = len;
1155 return TRUE;
1156 }
1157return FALSE;
1158}
1159
1160
1161
1162/* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
1163
1164static gstring *
1165arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1166 const uschar * identity, int instance, blob * ar)
1167{
1168int aar_off = g ? g->ptr : 0;
1169arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
1170arc_line * al = (arc_line *)(as+1);
1171header_line * h = (header_line *)(al+1);
1172
1173g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1174g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
1175g = string_catn(g, US ar->data, ar->len);
1176
1177h->slen = g->ptr - aar_off;
1178h->text = g->s + aar_off;
1179al->complete = h;
1180as->next = NULL;
1181as->prev = ctx->arcset_chain_last;
1182as->instance = instance;
1183as->hdr_aar = al;
1184if (instance == 1)
1185 ctx->arcset_chain = as;
1186else
1187 ctx->arcset_chain_last->next = as;
1188ctx->arcset_chain_last = as;
1189
1190DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1191return g;
1192}
1193
1194
1195
1196static BOOL
1197arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1198 blob * sig, const uschar * why)
1199{
1200hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1201 ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1202blob hhash;
1203es_ctx sctx;
1204const uschar * errstr;
1205
1206DEBUG(D_transport)
1207 {
1208 hctx hhash_ctx;
1209 debug_printf("ARC: %s header data for signing:\n", why);
1210 pdkim_quoteprint(hdata->s, hdata->ptr);
1211
1212 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1213 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1214 exim_sha_finish(&hhash_ctx, &hhash);
1215 debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
1216 }
1217
1218if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1219 {
1220 hctx hhash_ctx;
1221 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1222 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1223 exim_sha_finish(&hhash_ctx, &hhash);
1224 }
1225else
1226 {
1227 hhash.data = hdata->s;
1228 hhash.len = hdata->ptr;
1229 }
1230
1231if ( (errstr = exim_dkim_signing_init(privkey, &sctx))
1232 || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
1233 {
1234 log_write(0, LOG_MAIN|LOG_PANIC, "ARC: %s signing: %s\n", why, errstr);
1235 return FALSE;
1236 }
1237return TRUE;
1238}
1239
1240
1241
1242static gstring *
1243arc_sign_append_sig(gstring * g, blob * sig)
1244{
1245/*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1246sig->data = pdkim_encode_base64(sig);
1247sig->len = Ustrlen(sig->data);
1248for (;;)
1249 {
1250 int len = MIN(sig->len, 74);
1251 g = string_catn(g, sig->data, len);
1252 if ((sig->len -= len) == 0) break;
1253 sig->data += len;
1254 g = string_catn(g, US"\r\n\t ", 5);
1255 }
1256g = string_catn(g, US";\r\n", 3);
1257gstring_reset_unused(g);
1258string_from_gstring(g);
1259return g;
1260}
1261
1262
1263/* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
1264
1265static gstring *
1266arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1267 const uschar * identity, const uschar * selector, blob * bodyhash,
1268 hdr_rlist * rheaders, const uschar * privkey)
1269{
1270uschar * s;
1271gstring * hdata = NULL;
1272int col;
1273int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1274blob sig;
1275int ams_off;
1276arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1277header_line * h = (header_line *)(al+1);
1278
1279/* debug_printf("%s\n", __FUNCTION__); */
1280
1281/* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1282
1283ams_off = g->ptr;
1284g = string_append(g, 10,
1285 ARC_HDR_AMS,
1286 US" i=", string_sprintf("%d", instance),
1287 US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */
1288 US"; s=", selector,
1289 US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
1290 US";\r\n\th=");
1291
1292for(col = 3; rheaders; rheaders = rheaders->prev)
1293 {
1294 const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1295 uschar * name, * htext = rheaders->h->text;
1296 int sep = ':';
1297
1298 /* Spot headers of interest */
1299
1300 while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1301 {
1302 int len = Ustrlen(name);
1303 if (strncasecmp(CCS htext, CCS name, len) == 0)
1304 {
1305 /* If too long, fold line in h= field */
1306
1307 if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3;
1308
1309 /* Add name to h= list */
1310
1311 g = string_catn(g, name, len);
1312 g = string_catn(g, US":", 1);
1313 col += len + 1;
1314
1315 /* Accumulate header for hashing/signing */
1316
1317 hdata = string_cat(hdata,
1318 pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */
1319 break;
1320 }
1321 }
1322 }
1323
1324/* Lose the last colon from the h= list */
1325
1326if (g->s[g->ptr - 1] == ':') g->ptr--;
1327
1328g = string_catn(g, US";\r\n\tb=;", 7);
1329
1330/* Include the pseudo-header in the accumulation */
1331/*XXX should that be prepended rather than appended? */
1332/*XXX also need to include at the verify stage */
1333
1334s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, TRUE);
1335hdata = string_cat(hdata, s);
1336
1337/* Calculate the signature from the accumulation */
1338/*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1339
1340if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1341 return NULL;
1342
1343/* Lose the trailing semicolon from the psuedo-header, and append the signature
1344(folded over lines) and termination to complete it. */
1345
1346g->ptr--;
1347g = arc_sign_append_sig(g, &sig);
1348
1349h->slen = g->ptr - ams_off;
1350h->text = g->s + ams_off;
1351al->complete = h;
1352ctx->arcset_chain_last->hdr_ams = al;
1353
1354DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1355return g;
1356}
1357
1358
1359
1360/* Look for an arc= result in an A-R header blob. We know that its data
1361happens to be a NUL-term string. */
1362
1363static uschar *
1364arc_ar_cv_status(blob * ar)
1365{
1366const uschar * resinfo = ar->data;
1367int sep = ';';
1368uschar * methodspec, * s;
1369
1370while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1371 if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1372 {
1373 uschar c;
1374 for (s = methodspec += 4;
1375 (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1376 return string_copyn(methodspec, s - methodspec);
1377 }
1378return NULL;
1379}
1380
1381
1382
1383/* Build the AS header and prepend it */
1384
1385static gstring *
1386arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1387 int instance, const uschar * identity, const uschar * selector, blob * ar,
1388 const uschar * privkey)
1389{
1390gstring * arcset;
1391arc_set * as;
1392uschar * status = arc_ar_cv_status(ar);
1393arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1394header_line * h = (header_line *)(al+1);
617d3932
JH
1395
1396gstring * hdata = NULL;
1397int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1398blob sig;
1399
1400/*
1401- Generate AS
1402 - no body coverage
1403 - no h= tag; implicit coverage
1404 - arc status from A-R
1405 - if fail:
1406 - coverage is just the new ARC set
1407 including self (but with an empty b= in self)
1408 - if non-fail:
1409 - all ARC set headers, set-number order, aar then ams then as,
1410 including self (but with an empty b= in self)
1411*/
1412
1413/* Construct the AS except for the signature */
1414
1415arcset = string_append(NULL, 10,
1416 ARC_HDR_AS,
1417 US" i=", string_sprintf("%d", instance),
1418 US"; cv=", status,
1419 US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */
1420 US"; s=", selector, /*XXX same as AMS */
1421 US";\r\n\t b=;");
1422
1423h->slen = arcset->ptr;
1424h->text = arcset->s;
1425al->complete = h;
1426ctx->arcset_chain_last->hdr_as = al;
1427
1428/* For any but "fail" chain-verify status, walk the entire chain in order by
1429instance. For fail, only the new arc-set. Accumulate the elements walked. */
1430
1431for (as = Ustrcmp(status, US"fail") == 0
1432 ? ctx->arcset_chain_last : ctx->arcset_chain;
1433 as; as = as->next)
1434 {
1435 /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
1436 is required per standard. */
1437
1438 h = as->hdr_aar->complete;
1439 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1440 h = as->hdr_ams->complete;
1441 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1442 h = as->hdr_as->complete;
1443 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1444 }
1445
1446/* Calculate the signature from the accumulation */
1447
1448if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1449 return NULL;
1450
1451/* Lose the trailing semicolon */
1452arcset->ptr--;
1453arcset = arc_sign_append_sig(arcset, &sig);
1454DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s);
1455
1456/* Finally, append the AMS and AAR to the new AS */
1457
1458return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1459}
1460
1461
1462/**************************************/
1463
1464/* Return pointer to pdkim_bodyhash for given hash method, creating new
1465method if needed.
1466*/
1467
1468void *
1469arc_ams_setup_sign_bodyhash(void)
1470{
1471int canon_head, canon_body;
1472
1473DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1474pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */
1475return pdkim_set_bodyhash(&dkim_sign_ctx,
1476 pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */
1477 canon_body,
1478 -1);
1479}
1480
1481
1482
1483/* A "normal" header line, identified by DKIM processing. These arrive before
1484the call to arc_sign(), which carries any newly-created DKIM headers - and
1485those go textually before the normal ones in the message.
1486
1487We have to take the feed from DKIM as, in the transport-filter case, the
1488headers are not in memory at the time of the call to arc_sign().
1489
1490Take a copy of the header and construct a reverse-order list.
1491Also parse ARC-chain headers and build the chain struct, retaining pointers
1492into the copies.
1493*/
1494
1495static const uschar *
1496arc_header_sign_feed(gstring * g)
1497{
1498uschar * s = string_copyn(g->s, g->ptr);
1499headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1500return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1501}
1502
1503
1504
1505/* ARC signing. Called from the smtp transport, if the arc_sign option is set.
1506The dkim_exim_sign() function has already been called, so will have hashed the
1507message body for us so long as we requested a hash previously.
1508
1509Arguments:
1510 signspec Three-element colon-sep list: identity, selector, privkey
1511 Already expanded
1512 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1513 errstr Error string
1514
1515Return value
1516 Set of headers to prepend to the message, including the supplied sigheaders
1517 but not the plainheaders.
1518*/
1519
1520gstring *
1521arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1522{
1523const uschar * identity, * selector, * privkey;
1524int sep = 0;
1525header_line * headers;
1526hdr_rlist * rheaders;
1527blob ar;
1528int instance;
1529gstring * g = NULL;
1530pdkim_bodyhash * b;
1531
1532/* Parse the signing specification */
1533
1534identity = string_nextinlist(&signspec, &sep, NULL, 0);
1535selector = string_nextinlist(&signspec, &sep, NULL, 0);
1536if ( !*identity | !*selector
1537 || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1538 {
1539 log_write(0, LOG_MAIN|LOG_PANIC, "ARC: bad signing-specification");
1540 return NULL;
1541 }
1542if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1543 return NULL;
1544
1545DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1546
1547/*
1548- scan headers for existing ARC chain & A-R (with matching system-identfier)
1549 - paniclog & skip on problems (no A-R)
1550*/
1551
1552/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it */
1553
1554string_from_gstring(sigheaders);
1555if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1556 {
1557 hdr_rlist ** rp;
1558 for (rp = &rheaders; *rp; ) rp = &(*rp)->prev;
1559 *rp = headers_rlist;
1560 headers_rlist = rheaders;
1561 }
1562else
1563 rheaders = headers_rlist;
1564/* Finally, build a normal-order headers list */
1565/*XXX only needed for hunt-the-AR? */
1566{
1567header_line * hnext = NULL;
1568for (; rheaders; hnext = rheaders->h, rheaders = rheaders->prev)
1569 rheaders->h->next = hnext;
1570headers = hnext;
1571}
1572
1573instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1574
1575if (!(arc_sign_find_ar(headers, identity, &ar)))
1576 {
1577 log_write(0, LOG_MAIN|LOG_PANIC, "ARC: no Authentication-Results header for signing");
1578 return sigheaders ? sigheaders : string_get(0);
1579 }
1580
1581/*
1582- Generate AAR
1583 - copy the A-R; prepend i= & identity
1584*/
1585
1586g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1587
1588/*
1589- Generate AMS
1590 - Looks fairly like a DKIM sig
1591 - Cover all DKIM sig headers as well as the usuals
1592 - ? oversigning?
1593 - Covers the data
1594 - we must have requested a suitable bodyhash previously
1595*/
1596
1597b = arc_ams_setup_sign_bodyhash();
1598g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1599 &b->bh, headers_rlist, privkey);
1600
1601/*
1602- Generate AS
1603 - no body coverage
1604 - no h= tag; implicit coverage
1605 - arc status from A-R
1606 - if fail:
1607 - coverage is just the new ARC set
1608 including self (but with an empty b= in self)
1609 - if non-fail:
1610 - all ARC set headers, set-number order, aar then ams then as,
1611 including self (but with an empty b= in self)
1612*/
1613
1614g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
1615
1616/* Finally, append the dkim headers and return the lot. */
1617
1618g = string_catn(g, sigheaders->s, sigheaders->ptr);
1619(void) string_from_gstring(g);
1620gstring_reset_unused(g);
1621return g;
1622}
1623
1624
1625/******************************************************************************/
1626
1627/* Check to see if the line is an AMS and if so, set up to validate it.
1628Called from the DKIM input processing. This must be done now as the message
1629body data is hashed during input.
1630
1631We call the DKIM code to request a body-hash; it has the facility already
1632and the hash parameters might be common with other requests.
1633*/
1634
1635static const uschar *
1636arc_header_vfy_feed(gstring * g)
1637{
1638header_line h;
1639arc_line al;
1640pdkim_bodyhash * b;
1641uschar * errstr;
1642
1643if (!dkim_verify_ctx) return US"no dkim context";
1644
1645if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1646
1647DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1648/* Parse the AMS header */
1649
1650h.next = NULL;
1651h.slen = g->size;
1652h.text = g->s;
1653memset(&al, 0, sizeof(arc_line));
1654if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
1655 {
1656 DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1657 return US"line parsing error";
1658 }
1659
1660/* defaults */
1661if (!al.c.data)
1662 {
1663 al.c_body.data = US"simple"; al.c_body.len = 6;
1664 al.c_head = al.c_body;
1665 }
1666
1667/* Ask the dkim code to calc a bodyhash with those specs */
1668
1669if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1670 return US"dkim hash setup fail";
1671
1672/* Discard the reference; search again at verify time, knowing that one
1673should have been created here. */
1674
1675return NULL;
1676}
1677
1678
1679
1680/* A header line has been identified by DKIM processing.
1681
1682Arguments:
1683 g Header line
1684 is_vfy TRUE for verify mode or FALSE for signing mode
1685
1686Return:
1687 NULL for success, or an error string (probably unused)
1688*/
1689
1690const uschar *
1691arc_header_feed(gstring * g, BOOL is_vfy)
1692{
1693return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1694}
1695
1696
1697
1698/******************************************************************************/
1699
1700/* Construct an Authenticate-Results header portion, for the ARC module */
1701
1702gstring *
1703authres_arc(gstring * g)
1704{
1705if (arc_state)
1706 {
1707 arc_line * highest_ams;
978e78de 1708 int start = 0; /* Compiler quietening */
617d3932
JH
1709 DEBUG(D_acl) start = g->ptr;
1710
1711 g = string_append(g, 2, US";\n\tarc=", arc_state);
1712 if (arc_received_instance > 0)
1713 {
1714 g = string_append(g, 3, US" (i=",
93c931f8
JH
1715 string_sprintf("%d", arc_received_instance), US")");
1716 if (arc_state_reason)
1717 g = string_append(g, 3, US"(", arc_state_reason, US")");
1718 g = string_catn(g, US" header.s=", 10);
617d3932
JH
1719 highest_ams = arc_received->hdr_ams;
1720 g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1721
1722 g = string_append(g, 2,
1723 US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
1724
1725 if (sender_host_address)
1726 g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
1727 }
1728 DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n",
1729 g->ptr - start - 3, g->s + start + 3);
1730 }
1731else
1732 DEBUG(D_acl) debug_printf("ARC: no authres\n");
1733return g;
1734}
1735
1736
1737# endif /* SUPPORT_SPF */
1738#endif /* EXPERIMENTAL_ARC */
1739/* vi: aw ai sw=2
1740 */