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