Avoid doing logging in signal-handlers. Bug 1007
[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
d9604f37 567/* Finally add in the signature header (with the b= tag stripped); no CRLF */
617d3932
JH
568
569s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
570if (relaxed)
d9604f37 571 len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
617d3932
JH
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
b3d9ebf5 634if (!ams->c.data) ams->c.data = US"simple"; /* RFC 6376 (DKIM) default */
617d3932
JH
635pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
636bodylen = ams->l.data
637 ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
638
639return pdkim_set_bodyhash(dkim_verify_ctx,
640 pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
641 canon_body,
642 bodylen);
643}
644
645
646
647/* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
648and without a DKIM v= tag.
649*/
650
93c931f8 651static const uschar *
617d3932
JH
652arc_ams_verify(arc_ctx * ctx, arc_set * as)
653{
654arc_line * ams = as->hdr_ams;
655pdkim_bodyhash * b;
656pdkim_pubkey * p;
657blob sighash;
658blob hhash;
659ev_ctx vctx;
660int hashtype;
661const uschar * errstr;
662
93c931f8 663as->ams_verify_done = US"in-progress";
617d3932
JH
664
665/* Check the AMS has all the required tags:
666 "a=" algorithm
667 "b=" signature
668 "bh=" body hash
669 "d=" domain (for key lookup)
670 "h=" headers (included in signature)
671 "s=" key-selector (for key lookup)
672*/
673if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
674 || !ams->h.data || !ams->s.data)
93c931f8
JH
675 {
676 as->ams_verify_done = arc_state_reason = US"required tag missing";
617d3932 677 return US"fail";
93c931f8 678 }
617d3932
JH
679
680
681/* The bodyhash should have been created earlier, and the dkim code should
682have managed calculating it during message input. Find the reference to it. */
683
684if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
93c931f8
JH
685 {
686 as->ams_verify_done = arc_state_reason = US"internal hash setup error";
617d3932 687 return US"fail";
93c931f8 688 }
617d3932
JH
689
690DEBUG(D_acl)
691 {
692 debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
693 " Body %.*s computed: ",
694 as->instance, b->signed_body_bytes,
978e78de 695 (int)ams->a_hash.len, ams->a_hash.data);
617d3932
JH
696 pdkim_hexprint(CUS b->bh.data, b->bh.len);
697 }
698
699/* We know the bh-tag blob is of a nul-term string, so safe as a string */
700
701if ( !ams->bh.data
702 || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
703 || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
704 )
705 {
706 DEBUG(D_acl)
707 {
708 debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
709 pdkim_hexprint(sighash.data, sighash.len);
710 debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
711 }
93c931f8 712 return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
617d3932
JH
713 }
714
715DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
716
717/* Get the public key from DNS */
718
719if (!(p = arc_line_to_pubkey(ams)))
93c931f8 720 return as->ams_verify_done = arc_state_reason = US"pubkey problem";
617d3932
JH
721
722/* We know the b-tag blob is of a nul-term string, so safe as a string */
723pdkim_decode_base64(ams->b.data, &sighash);
724
725arc_get_verify_hhash(ctx, ams, &hhash);
726
727/* Setup the interface to the signing library */
728
729if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
730 {
731 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
93c931f8 732 as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
617d3932
JH
733 return US"fail";
734 }
735
736hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
737
738if ((errstr = exim_dkim_verify(&vctx,
739 pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
740 {
741 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
93c931f8 742 return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
617d3932
JH
743 }
744
745DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
746as->ams_verify_passed = TRUE;
747return NULL;
748}
749
750
751
752/* Check the sets are instance-continuous and that all
753members are present. Check that no arc_seals are "fail".
754Set the highest instance number global.
755Verify the latest AMS.
756*/
757static uschar *
758arc_headers_check(arc_ctx * ctx)
759{
760arc_set * as;
761int inst;
762BOOL ams_fail_found = FALSE;
617d3932
JH
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 {
b3d9ebf5 774 arc_state_reason = string_sprintf("i=%d"
93c931f8 775 " (cv, sequence or missing header)", as->instance);
b3d9ebf5
JH
776 DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
777 return US"fail";
617d3932
JH
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;
617d3932
JH
794
795/* We can skip the latest-AMS validation, if we already did it. */
796
797as = ctx->arcset_chain_last;
3c676fa8 798if (!as->ams_verify_passed)
93c931f8 799 {
3c676fa8
JH
800 if (as->ams_verify_done)
801 {
802 arc_state_reason = as->ams_verify_done;
803 return US"fail";
804 }
805 if (!!arc_ams_verify(ctx, as))
806 return US"fail";
93c931f8 807 }
617d3932
JH
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.
d9604f37
JH
868
869Headers are CRLF-separated, but the last one is not crlf-terminated.
617d3932
JH
870*/
871
872DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
873for (as2 = ctx->arcset_chain;
874 as2 && as2->instance <= as->instance;
875 as2 = as2->next)
876 {
877 arc_line * al;
878 uschar * s;
879 int len;
880
881 al = as2->hdr_aar;
882 if (!(s = al->relaxed))
883 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
884 al->complete->slen, TRUE);
885 len = Ustrlen(s);
886 DEBUG(D_acl) pdkim_quoteprint(s, len);
887 exim_sha_update(&hhash_ctx, s, len);
888
889 al = as2->hdr_ams;
890 if (!(s = al->relaxed))
891 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
892 al->complete->slen, TRUE);
893 len = Ustrlen(s);
894 DEBUG(D_acl) pdkim_quoteprint(s, len);
895 exim_sha_update(&hhash_ctx, s, len);
896
897 al = as2->hdr_as;
898 if (as2->instance == as->instance)
899 s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
d9604f37 900 al->rawsig_no_b_val.len, FALSE);
617d3932
JH
901 else if (!(s = al->relaxed))
902 al->relaxed = s = pdkim_relax_header_n(al->complete->text,
903 al->complete->slen, TRUE);
904 len = Ustrlen(s);
905 DEBUG(D_acl) pdkim_quoteprint(s, len);
906 exim_sha_update(&hhash_ctx, s, len);
907 }
908
909/*
910 5. Retrieve the final digest from the hash function.
911*/
912
913exim_sha_finish(&hhash_ctx, &hhash_computed);
914DEBUG(D_acl)
915 {
916 debug_printf("ARC i=%d AS Header %.*s computed: ",
978e78de 917 as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
617d3932
JH
918 pdkim_hexprint(hhash_computed.data, hhash_computed.len);
919 }
920
921
922/*
923 6. Retrieve the public key identified by the "s" and "d" tags in
924 the ARC-Seal, as described in Section 4.1.6.
925*/
926
927if (!(p = arc_line_to_pubkey(hdr_as)))
928 return US"pubkey problem";
929
930/*
931 7. Determine whether the signature portion ("b" tag) of the ARC-
932 Seal and the digest computed above are valid according to the
933 public key. (See also Section Section 8.4 for failure case
934 handling)
935
936 8. If the signature is not valid, the chain state is "fail" and
937 the algorithm stops here.
938*/
939
940/* We know the b-tag blob is of a nul-term string, so safe as a string */
941pdkim_decode_base64(hdr_as->b.data, &sighash);
942
943if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
944 {
945 DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
946 return US"fail";
947 }
948
949hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
950
951if ((errstr = exim_dkim_verify(&vctx,
952 pdkim_hashes[hashtype].exim_hashmethod,
953 &hhash_computed, &sighash)))
954 {
955 DEBUG(D_acl)
956 debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
d9604f37 957 arc_state_reason = US"seal sigverify error";
617d3932
JH
958 return US"fail";
959 }
960
961DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
962return NULL;
963}
964
965
966static const uschar *
967arc_verify_seals(arc_ctx * ctx)
968{
969arc_set * as = ctx->arcset_chain;
970
971if (!as)
972 return US"none";
973
974while (as)
975 {
976 if (arc_seal_verify(ctx, as)) return US"fail";
977 as = as->next;
978 }
979DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
980return NULL;
981}
982/******************************************************************************/
983
984/* Do ARC verification. Called from DATA ACL, on a verify = arc
985condition. No arguments; we are checking globals.
986
987Return: The ARC state, or NULL on error.
988*/
989
990const uschar *
991acl_verify_arc(void)
992{
993arc_ctx ctx = { NULL };
994const uschar * res;
995
311fbe57
JH
996if (!dkim_verify_ctx)
997 {
998 DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
999 return NULL;
1000 }
1001
617d3932
JH
1002/* AS evaluation, per
1003https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
1004*/
1005/* 1. Collect all ARC sets currently on the message. If there were
1006 none, the ARC state is "none" and the algorithm stops here.
1007*/
1008
1009if ((res = arc_vfy_collect_hdrs(&ctx)))
1010 goto out;
1011
1012/* 2. If the form of any ARC set is invalid (e.g., does not contain
1013 exactly one of each of the three ARC-specific header fields),
1014 then the chain state is "fail" and the algorithm stops here.
1015
1016 1. To avoid the overhead of unnecessary computation and delay
1017 from crypto and DNS operations, the cv value for all ARC-
1018 Seal(s) MAY be checked at this point. If any of the values
1019 are "fail", then the overall state of the chain is "fail" and
1020 the algorithm stops here.
1021
1022 3. Conduct verification of the ARC-Message-Signature header field
1023 bearing the highest instance number. If this verification fails,
1024 then the chain state is "fail" and the algorithm stops here.
1025*/
1026
1027if ((res = arc_headers_check(&ctx)))
1028 goto out;
1029
1030/* 4. For each ARC-Seal from the "N"th instance to the first, apply the
1031 following logic:
1032
1033 1. If the value of the "cv" tag on that seal is "fail", the
1034 chain state is "fail" and the algorithm stops here. (This
1035 step SHOULD be skipped if the earlier step (2.1) was
1036 performed)
1037
1038 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
1039 == "none" && i != 1)) then the chain state is "fail" and the
1040 algorithm stops here (note that the ordering of the logic is
1041 structured for short-circuit evaluation).
1042
1043 3. Initialize a hash function corresponding to the "a" tag of
1044 the ARC-Seal.
1045
1046 4. Compute the canonicalized form of the ARC header fields, in
1047 the order described in Section 5.4.2, using the "relaxed"
1048 header canonicalization defined in Section 3.4.2 of
1049 [RFC6376]. Pass the canonicalized result to the hash
1050 function.
1051
1052 5. Retrieve the final digest from the hash function.
1053
1054 6. Retrieve the public key identified by the "s" and "d" tags in
1055 the ARC-Seal, as described in Section 4.1.6.
1056
1057 7. Determine whether the signature portion ("b" tag) of the ARC-
1058 Seal and the digest computed above are valid according to the
1059 public key. (See also Section Section 8.4 for failure case
1060 handling)
1061
1062 8. If the signature is not valid, the chain state is "fail" and
1063 the algorithm stops here.
1064
1065 5. If all seals pass validation, then the chain state is "pass", and
1066 the algorithm is complete.
1067*/
1068
1069if ((res = arc_verify_seals(&ctx)))
1070 goto out;
1071
1072res = US"pass";
1073
1074out:
1075 return res;
1076}
1077
1078/******************************************************************************/
1079
1080/* Prepend the header to the rlist */
1081
1082static hdr_rlist *
1083arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1084{
1085hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
1086header_line * h = r->h = (header_line *)(r+1);
1087
1088r->prev = list;
1089r->used = FALSE;
1090h->next = NULL;
1091h->type = 0;
1092h->slen = len;
1093h->text = US s;
1094
1095/* This works for either NL or CRLF lines; also nul-termination */
1096while (*++s)
1097 if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
1098s++; /* move past end of line */
1099
1100return r;
1101}
1102
1103
1104/* Walk the given headers strings identifying each header, and construct
b3d9ebf5 1105a reverse-order list.
617d3932
JH
1106*/
1107
1108static hdr_rlist *
1109arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1110{
1111const uschar * s;
1112hdr_rlist * rheaders = NULL;
1113
1114s = sigheaders ? sigheaders->s : NULL;
1115if (s) while (*s)
1116 {
978e78de 1117 const uschar * s2 = s;
617d3932
JH
1118
1119 /* This works for either NL or CRLF lines; also nul-termination */
1120 while (*++s2)
1121 if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
1122 s2++; /* move past end of line */
1123
1124 rheaders = arc_rlist_entry(rheaders, s, s2 - s);
1125 s = s2;
1126 }
1127return rheaders;
1128}
1129
1130
1131
1132/* Return the A-R content, without identity, with line-ending and
1133NUL termination. */
1134
1135static BOOL
1136arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1137{
1138header_line * h;
1139int ilen = Ustrlen(identity);
1140
1141ret->data = NULL;
1142for(h = headers; h; h = h->next)
1143 {
1144 uschar * s = h->text, c;
1145 int len = h->slen;
1146
1147 if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
1148 s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */
1149 while ( len > 0
1150 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1151 s++, len--; /* FWS */
1152 if (Ustrncmp(s, identity, ilen) != 0) continue;
1153 s += ilen; len -= ilen; /* identity */
1154 if (len <= 0) continue;
1155 if ((c = *s) && c == ';') s++, len--; /* identity terminator */
1156 while ( len > 0
1157 && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
1158 s++, len--; /* FWS */
1159 if (len <= 0) continue;
1160 ret->data = s;
1161 ret->len = len;
1162 return TRUE;
1163 }
1164return FALSE;
1165}
1166
1167
1168
1169/* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
1170
1171static gstring *
1172arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1173 const uschar * identity, int instance, blob * ar)
1174{
1175int aar_off = g ? g->ptr : 0;
1176arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
1177arc_line * al = (arc_line *)(as+1);
1178header_line * h = (header_line *)(al+1);
1179
1180g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1181g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
1182g = string_catn(g, US ar->data, ar->len);
1183
1184h->slen = g->ptr - aar_off;
1185h->text = g->s + aar_off;
1186al->complete = h;
1187as->next = NULL;
1188as->prev = ctx->arcset_chain_last;
1189as->instance = instance;
1190as->hdr_aar = al;
1191if (instance == 1)
1192 ctx->arcset_chain = as;
1193else
1194 ctx->arcset_chain_last->next = as;
1195ctx->arcset_chain_last = as;
1196
1197DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1198return g;
1199}
1200
1201
1202
1203static BOOL
1204arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1205 blob * sig, const uschar * why)
1206{
1207hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1208 ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1209blob hhash;
1210es_ctx sctx;
1211const uschar * errstr;
1212
1213DEBUG(D_transport)
1214 {
1215 hctx hhash_ctx;
1216 debug_printf("ARC: %s header data for signing:\n", why);
1217 pdkim_quoteprint(hdata->s, hdata->ptr);
1218
1219 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1220 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1221 exim_sha_finish(&hhash_ctx, &hhash);
1222 debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
1223 }
1224
1225if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
1226 {
1227 hctx hhash_ctx;
1228 (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
1229 exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
1230 exim_sha_finish(&hhash_ctx, &hhash);
1231 }
1232else
1233 {
1234 hhash.data = hdata->s;
1235 hhash.len = hdata->ptr;
1236 }
1237
1238if ( (errstr = exim_dkim_signing_init(privkey, &sctx))
1239 || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
1240 {
c3d43245 1241 log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
617d3932
JH
1242 return FALSE;
1243 }
1244return TRUE;
1245}
1246
1247
1248
1249static gstring *
1250arc_sign_append_sig(gstring * g, blob * sig)
1251{
1252/*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1253sig->data = pdkim_encode_base64(sig);
1254sig->len = Ustrlen(sig->data);
1255for (;;)
1256 {
1257 int len = MIN(sig->len, 74);
1258 g = string_catn(g, sig->data, len);
1259 if ((sig->len -= len) == 0) break;
1260 sig->data += len;
1261 g = string_catn(g, US"\r\n\t ", 5);
1262 }
1263g = string_catn(g, US";\r\n", 3);
1264gstring_reset_unused(g);
1265string_from_gstring(g);
1266return g;
1267}
1268
1269
1270/* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
1271
1272static gstring *
1273arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
1274 const uschar * identity, const uschar * selector, blob * bodyhash,
1275 hdr_rlist * rheaders, const uschar * privkey)
1276{
1277uschar * s;
1278gstring * hdata = NULL;
1279int col;
1280int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1281blob sig;
1282int ams_off;
1283arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1284header_line * h = (header_line *)(al+1);
1285
1286/* debug_printf("%s\n", __FUNCTION__); */
1287
1288/* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
1289
1290ams_off = g->ptr;
1291g = string_append(g, 10,
1292 ARC_HDR_AMS,
1293 US" i=", string_sprintf("%d", instance),
1294 US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */
1295 US"; s=", selector,
1296 US";\r\n\tbh=", pdkim_encode_base64(bodyhash),
1297 US";\r\n\th=");
1298
1299for(col = 3; rheaders; rheaders = rheaders->prev)
1300 {
1301 const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
1302 uschar * name, * htext = rheaders->h->text;
1303 int sep = ':';
1304
1305 /* Spot headers of interest */
1306
1307 while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
1308 {
1309 int len = Ustrlen(name);
1310 if (strncasecmp(CCS htext, CCS name, len) == 0)
1311 {
1312 /* If too long, fold line in h= field */
1313
1314 if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3;
1315
1316 /* Add name to h= list */
1317
1318 g = string_catn(g, name, len);
1319 g = string_catn(g, US":", 1);
1320 col += len + 1;
1321
1322 /* Accumulate header for hashing/signing */
1323
1324 hdata = string_cat(hdata,
1325 pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */
1326 break;
1327 }
1328 }
1329 }
1330
1331/* Lose the last colon from the h= list */
1332
1333if (g->s[g->ptr - 1] == ':') g->ptr--;
1334
1335g = string_catn(g, US";\r\n\tb=;", 7);
1336
1337/* Include the pseudo-header in the accumulation */
617d3932 1338
d9604f37 1339s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
617d3932
JH
1340hdata = string_cat(hdata, s);
1341
1342/* Calculate the signature from the accumulation */
1343/*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
1344
1345if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
1346 return NULL;
1347
1348/* Lose the trailing semicolon from the psuedo-header, and append the signature
1349(folded over lines) and termination to complete it. */
1350
1351g->ptr--;
1352g = arc_sign_append_sig(g, &sig);
1353
1354h->slen = g->ptr - ams_off;
1355h->text = g->s + ams_off;
1356al->complete = h;
1357ctx->arcset_chain_last->hdr_ams = al;
1358
1359DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1360return g;
1361}
1362
1363
1364
1365/* Look for an arc= result in an A-R header blob. We know that its data
1366happens to be a NUL-term string. */
1367
1368static uschar *
1369arc_ar_cv_status(blob * ar)
1370{
1371const uschar * resinfo = ar->data;
1372int sep = ';';
1373uschar * methodspec, * s;
1374
1375while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
1376 if (Ustrncmp(methodspec, US"arc=", 4) == 0)
1377 {
1378 uschar c;
1379 for (s = methodspec += 4;
1380 (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
1381 return string_copyn(methodspec, s - methodspec);
1382 }
3d0a6e0f 1383return US"none";
617d3932
JH
1384}
1385
1386
1387
1388/* Build the AS header and prepend it */
1389
1390static gstring *
1391arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
1392 int instance, const uschar * identity, const uschar * selector, blob * ar,
1393 const uschar * privkey)
1394{
1395gstring * arcset;
1396arc_set * as;
1397uschar * status = arc_ar_cv_status(ar);
1398arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1399header_line * h = (header_line *)(al+1);
617d3932
JH
1400
1401gstring * hdata = NULL;
1402int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
1403blob sig;
1404
1405/*
1406- Generate AS
1407 - no body coverage
1408 - no h= tag; implicit coverage
1409 - arc status from A-R
1410 - if fail:
1411 - coverage is just the new ARC set
1412 including self (but with an empty b= in self)
1413 - if non-fail:
1414 - all ARC set headers, set-number order, aar then ams then as,
1415 including self (but with an empty b= in self)
1416*/
1417
1418/* Construct the AS except for the signature */
1419
1420arcset = string_append(NULL, 10,
1421 ARC_HDR_AS,
1422 US" i=", string_sprintf("%d", instance),
1423 US"; cv=", status,
d4772796 1424 US"; a=rsa-sha256; d=", identity, /*XXX hardwired */
617d3932
JH
1425 US"; s=", selector, /*XXX same as AMS */
1426 US";\r\n\t b=;");
1427
1428h->slen = arcset->ptr;
1429h->text = arcset->s;
1430al->complete = h;
1431ctx->arcset_chain_last->hdr_as = al;
1432
1433/* For any but "fail" chain-verify status, walk the entire chain in order by
1434instance. For fail, only the new arc-set. Accumulate the elements walked. */
1435
1436for (as = Ustrcmp(status, US"fail") == 0
1437 ? ctx->arcset_chain_last : ctx->arcset_chain;
1438 as; as = as->next)
1439 {
1440 /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
1441 is required per standard. */
1442
1443 h = as->hdr_aar->complete;
1444 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1445 h = as->hdr_ams->complete;
1446 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
1447 h = as->hdr_as->complete;
d9604f37 1448 hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
617d3932
JH
1449 }
1450
1451/* Calculate the signature from the accumulation */
1452
1453if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1454 return NULL;
1455
1456/* Lose the trailing semicolon */
1457arcset->ptr--;
1458arcset = arc_sign_append_sig(arcset, &sig);
1459DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s);
1460
1461/* Finally, append the AMS and AAR to the new AS */
1462
1463return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
1464}
1465
1466
1467/**************************************/
1468
1469/* Return pointer to pdkim_bodyhash for given hash method, creating new
1470method if needed.
1471*/
1472
1473void *
1474arc_ams_setup_sign_bodyhash(void)
1475{
1476int canon_head, canon_body;
1477
1478DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1479pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */
1480return pdkim_set_bodyhash(&dkim_sign_ctx,
1481 pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */
1482 canon_body,
1483 -1);
1484}
1485
1486
1487
b3d9ebf5
JH
1488void
1489arc_sign_init(void)
1490{
1491memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
1492}
1493
1494
1495
617d3932
JH
1496/* A "normal" header line, identified by DKIM processing. These arrive before
1497the call to arc_sign(), which carries any newly-created DKIM headers - and
1498those go textually before the normal ones in the message.
1499
1500We have to take the feed from DKIM as, in the transport-filter case, the
1501headers are not in memory at the time of the call to arc_sign().
1502
1503Take a copy of the header and construct a reverse-order list.
1504Also parse ARC-chain headers and build the chain struct, retaining pointers
1505into the copies.
1506*/
1507
1508static const uschar *
1509arc_header_sign_feed(gstring * g)
1510{
1511uschar * s = string_copyn(g->s, g->ptr);
1512headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1513return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
1514}
1515
1516
1517
1518/* ARC signing. Called from the smtp transport, if the arc_sign option is set.
1519The dkim_exim_sign() function has already been called, so will have hashed the
1520message body for us so long as we requested a hash previously.
1521
1522Arguments:
1523 signspec Three-element colon-sep list: identity, selector, privkey
1524 Already expanded
1525 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
1526 errstr Error string
1527
1528Return value
1529 Set of headers to prepend to the message, including the supplied sigheaders
1530 but not the plainheaders.
1531*/
1532
1533gstring *
1534arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1535{
1536const uschar * identity, * selector, * privkey;
1537int sep = 0;
1538header_line * headers;
1539hdr_rlist * rheaders;
1540blob ar;
1541int instance;
1542gstring * g = NULL;
1543pdkim_bodyhash * b;
1544
1545/* Parse the signing specification */
1546
1547identity = string_nextinlist(&signspec, &sep, NULL, 0);
1548selector = string_nextinlist(&signspec, &sep, NULL, 0);
1549if ( !*identity | !*selector
1550 || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
1551 {
c3d43245 1552 log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)",
9f7d9fa1 1553 !*identity ? "identity" : !*selector ? "selector" : "private-key");
c3d43245 1554 return sigheaders ? sigheaders : string_get(0);
617d3932
JH
1555 }
1556if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
c3d43245 1557 return sigheaders ? sigheaders : string_get(0);
617d3932
JH
1558
1559DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
1560
b3d9ebf5
JH
1561/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
1562Then scan the list for an A-R header. */
617d3932
JH
1563
1564string_from_gstring(sigheaders);
1565if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
1566 {
1567 hdr_rlist ** rp;
0c2250d1
JH
1568 for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
1569 *rp = rheaders;
617d3932 1570 }
b3d9ebf5 1571
617d3932
JH
1572/* Finally, build a normal-order headers list */
1573/*XXX only needed for hunt-the-AR? */
0c2250d1 1574/*XXX also, we really should be accepting any number of ADMD-matching ARs */
b3d9ebf5
JH
1575 {
1576 header_line * hnext = NULL;
0c2250d1
JH
1577 for (rheaders = headers_rlist; rheaders;
1578 hnext = rheaders->h, rheaders = rheaders->prev)
b3d9ebf5
JH
1579 rheaders->h->next = hnext;
1580 headers = hnext;
1581 }
617d3932
JH
1582
1583if (!(arc_sign_find_ar(headers, identity, &ar)))
1584 {
c3d43245 1585 log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
617d3932
JH
1586 return sigheaders ? sigheaders : string_get(0);
1587 }
1588
b3d9ebf5
JH
1589/* We previously built the data-struct for the existing ARC chain, if any, using a headers
1590feed from the DKIM module. Use that to give the instance number for the ARC set we are
1591about to build. */
1592
1593DEBUG(D_transport)
1594 if (arc_sign_ctx.arcset_chain_last)
1595 debug_printf("ARC: existing chain highest instance: %d\n",
1596 arc_sign_ctx.arcset_chain_last->instance);
1597 else
1598 debug_printf("ARC: no existing chain\n");
1599
1600instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1601
617d3932
JH
1602/*
1603- Generate AAR
1604 - copy the A-R; prepend i= & identity
1605*/
1606
1607g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
1608
1609/*
1610- Generate AMS
1611 - Looks fairly like a DKIM sig
1612 - Cover all DKIM sig headers as well as the usuals
1613 - ? oversigning?
1614 - Covers the data
1615 - we must have requested a suitable bodyhash previously
1616*/
1617
1618b = arc_ams_setup_sign_bodyhash();
1619g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
1620 &b->bh, headers_rlist, privkey);
1621
1622/*
1623- Generate AS
1624 - no body coverage
1625 - no h= tag; implicit coverage
1626 - arc status from A-R
1627 - if fail:
1628 - coverage is just the new ARC set
1629 including self (but with an empty b= in self)
1630 - if non-fail:
1631 - all ARC set headers, set-number order, aar then ams then as,
1632 including self (but with an empty b= in self)
1633*/
1634
1635g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
1636
1637/* Finally, append the dkim headers and return the lot. */
1638
1639g = string_catn(g, sigheaders->s, sigheaders->ptr);
1640(void) string_from_gstring(g);
1641gstring_reset_unused(g);
1642return g;
1643}
1644
1645
1646/******************************************************************************/
1647
1648/* Check to see if the line is an AMS and if so, set up to validate it.
1649Called from the DKIM input processing. This must be done now as the message
1650body data is hashed during input.
1651
1652We call the DKIM code to request a body-hash; it has the facility already
1653and the hash parameters might be common with other requests.
1654*/
1655
1656static const uschar *
1657arc_header_vfy_feed(gstring * g)
1658{
1659header_line h;
1660arc_line al;
1661pdkim_bodyhash * b;
1662uschar * errstr;
1663
1664if (!dkim_verify_ctx) return US"no dkim context";
1665
1666if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1667
1668DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1669/* Parse the AMS header */
1670
1671h.next = NULL;
1672h.slen = g->size;
1673h.text = g->s;
1674memset(&al, 0, sizeof(arc_line));
1675if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
1676 {
1677 DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
1678 return US"line parsing error";
1679 }
1680
1681/* defaults */
1682if (!al.c.data)
1683 {
1684 al.c_body.data = US"simple"; al.c_body.len = 6;
1685 al.c_head = al.c_body;
1686 }
1687
1688/* Ask the dkim code to calc a bodyhash with those specs */
1689
1690if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
1691 return US"dkim hash setup fail";
1692
1693/* Discard the reference; search again at verify time, knowing that one
1694should have been created here. */
1695
1696return NULL;
1697}
1698
1699
1700
1701/* A header line has been identified by DKIM processing.
1702
1703Arguments:
1704 g Header line
1705 is_vfy TRUE for verify mode or FALSE for signing mode
1706
1707Return:
1708 NULL for success, or an error string (probably unused)
1709*/
1710
1711const uschar *
1712arc_header_feed(gstring * g, BOOL is_vfy)
1713{
1714return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
1715}
1716
1717
1718
1719/******************************************************************************/
1720
1721/* Construct an Authenticate-Results header portion, for the ARC module */
1722
1723gstring *
1724authres_arc(gstring * g)
1725{
1726if (arc_state)
1727 {
1728 arc_line * highest_ams;
978e78de 1729 int start = 0; /* Compiler quietening */
617d3932
JH
1730 DEBUG(D_acl) start = g->ptr;
1731
1732 g = string_append(g, 2, US";\n\tarc=", arc_state);
1733 if (arc_received_instance > 0)
1734 {
1735 g = string_append(g, 3, US" (i=",
93c931f8
JH
1736 string_sprintf("%d", arc_received_instance), US")");
1737 if (arc_state_reason)
1738 g = string_append(g, 3, US"(", arc_state_reason, US")");
1739 g = string_catn(g, US" header.s=", 10);
617d3932
JH
1740 highest_ams = arc_received->hdr_ams;
1741 g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
1742
1743 g = string_append(g, 2,
1744 US" arc.oldest-pass=", string_sprintf("%d", arc_oldest_pass));
1745
1746 if (sender_host_address)
1747 g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
1748 }
b3d9ebf5
JH
1749 else if (arc_state_reason)
1750 g = string_append(g, 3, US" (", arc_state_reason, US")");
617d3932
JH
1751 DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n",
1752 g->ptr - start - 3, g->s + start + 3);
1753 }
1754else
1755 DEBUG(D_acl) debug_printf("ARC: no authres\n");
1756return g;
1757}
1758
1759
1760# endif /* SUPPORT_SPF */
1761#endif /* EXPERIMENTAL_ARC */
1762/* vi: aw ai sw=2
1763 */