ARC initial implementation. Experimental. Bug 2162
[exim.git] / src / src / arc.c
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
21 extern pdkim_ctx * dkim_verify_ctx;
22 extern pdkim_ctx dkim_sign_ctx;
23
24 /******************************************************************************/
25
26 typedef struct hdr_rlist {
27   struct hdr_rlist *    prev;
28   BOOL                  used;
29   header_line *         h;
30 } hdr_rlist;
31
32 typedef 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
60 typedef 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
73 typedef 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
87 static hdr_rlist * headers_rlist;
88 static arc_ctx arc_sign_ctx = { NULL };
89
90
91 /******************************************************************************/
92
93
94 /* Get the instance number from the header.
95 Return 0 on error */
96 static unsigned
97 arc_instance_from_hdr(const arc_line * al)
98 {
99 uschar * s = al->i.data;
100 if (!s || !al->i.len) return 0;
101 return (unsigned) atoi(s);
102 }
103
104
105 static uschar *
106 skip_fws(uschar * s)
107 {
108 uschar c = *s;
109 while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
110 return s;
111 }
112
113
114 /* Locate instance struct on chain, inserting a new one if
115 needed.  The chain is in increasing-instance-number order
116 by the "next" link, and we have a "prev" link also.
117 */
118
119 static arc_set *
120 arc_find_set(arc_ctx * ctx, unsigned i)
121 {
122 arc_set ** pas, * as, * next, * prev;
123
124 for (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
137 DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
138 *pas = as = store_get(sizeof(arc_set));
139 memset(as, 0, sizeof(arc_set));
140 as->next = next;
141 as->prev = prev;
142 as->instance = i;
143 if (next)
144   next->prev = as;
145 else
146   ctx->arcset_chain_last = as;
147 return as;
148 }
149
150
151
152 /* Insert a tag content into the line structure.
153 Note this is a reference to existing data, not a copy.
154 Check for already-seen tag.
155 The string-pointer is on the '=' for entry.  Update it past the
156 content (to the ;) on return;
157 */
158
159 static uschar *
160 arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
161 {
162 uschar * s = *ss;
163 uschar c = *++s;
164 blob * b = (blob *)(US al + loff);
165 size_t len = 0;
166
167 /* [FWS] tag-value [FWS] */
168
169 if (b->data) return US"fail";
170 s = skip_fws(s);                                                /* FWS */
171
172 b->data = s;
173 while ((c = *s) && c != ';') { len++; s++; }
174 *ss = s;
175 while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
176   { s--; len--; }                                               /* FWS */
177 b->len = len;
178 return NULL;
179 }
180
181
182 /* Inspect a header line, noting known tag fields.
183 Check for duplicates. */
184
185 static uschar *
186 arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only)
187 {
188 uschar * s = h->text + off;
189 uschar * r;
190 uschar c;
191
192 al->complete = h;
193
194 if (!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
204 while ((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
361 if (!instance_only)
362   *r = '\0';
363
364 done:
365 /* debug_printf("%s: finshed\n", __FUNCTION__); */
366 return NULL;
367 }
368
369
370 /* Insert one header line in the correct set of the chain,
371 adding instances as needed and checking for duplicate lines.
372 */
373
374 static uschar *
375 arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
376   BOOL instance_only)
377 {
378 int i;
379 arc_set * as;
380 arc_line * al = store_get(sizeof(arc_line)), ** alp;
381 uschar * e;
382
383 memset(al, 0, sizeof(arc_line));
384
385 if ((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   }
390 if (!(i = arc_instance_from_hdr(al)))   return US"instance find";
391 if (!(as = arc_find_set(ctx, i)))       return US"set find";
392 if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
393
394 *alp = al;
395 return NULL;
396 }
397
398
399
400
401 static const uschar *
402 arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only)
403 {
404 const uschar * e;
405
406 /*debug_printf("consider hdr '%s'\n", h->text);*/
407 if (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   }
424 else 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   }
452 else 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   }
469 return NULL;
470 }
471
472
473
474 /* Gather the chain of arc sets from the headers.
475 Check for duplicates while that is done.  Also build the
476 reverse-order headers list;
477
478 Return: ARC state if determined, eg. by lack of any ARC chain.
479 */
480
481 static const uschar *
482 arc_vfy_collect_hdrs(arc_ctx * ctx)
483 {
484 header_line * h;
485 hdr_rlist * r, * rprev = NULL;
486 const uschar * e;
487
488 DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
489 for (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   }
500 headers_rlist = r;
501
502 if (!ctx->arcset_chain) return US"none";
503 return NULL;
504 }
505
506
507 static BOOL
508 arc_cv_match(arc_line * al, const uschar * s)
509 {
510 return 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
516 signed.
517 */
518
519 static void
520 arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
521 {
522 const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
523 const uschar * hn;
524 int sep = ':';
525 hdr_rlist * r;
526 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
527 int hashtype = pdkim_hashname_to_hashtype(
528                     ams->a_hash.data, ams->a_hash.len);
529 hctx hhash_ctx;
530 const uschar * s;
531 int len;
532
533 if (!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)
541 walk the message headers in reverse order, adding to the hash any
542 found for the first time. For that last point, maintain used-marks
543 on the list of message headers. */
544
545 DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
546
547 for (r = headers_rlist; r; r = r->prev)
548   r->used = FALSE;
549 while ((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
566 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
567 if (relaxed)
568   len = Ustrlen(s = pdkim_relax_header_n(s, len, TRUE));
569 DEBUG(D_acl) pdkim_quoteprint(s, len);
570 exim_sha_update(&hhash_ctx, s, len);
571
572 exim_sha_finish(&hhash_ctx, hhash);
573 DEBUG(D_acl)
574   { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
575 return;
576 }
577
578
579
580
581 static pdkim_pubkey *
582 arc_line_to_pubkey(arc_line * al)
583 {
584 uschar * dns_txt;
585 pdkim_pubkey * p;
586
587 if (!(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
594 if (  !(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
603 signatures. XXX should we have looked for multiple dns records? */
604
605 if (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   }
619 return p;
620 }
621
622
623
624
625 static pdkim_bodyhash *
626 arc_ams_setup_vfy_bodyhash(arc_line * ams)
627 {
628 int canon_head, canon_body;
629 long bodylen;
630
631 pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
632 bodylen = ams->l.data
633         ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
634
635 return 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
644 and without a DKIM v= tag.
645 */
646
647 static uschar *
648 arc_ams_verify(arc_ctx * ctx, arc_set * as)
649 {
650 arc_line * ams = as->hdr_ams;
651 pdkim_bodyhash * b;
652 pdkim_pubkey * p;
653 blob sighash;
654 blob hhash;
655 ev_ctx vctx;
656 int hashtype;
657 const uschar * errstr;
658
659 as->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 */
669 if (  !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
675 have managed calculating it during message input.  Find the reference to it. */
676
677 if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
678   return US"fail";
679
680 DEBUG(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
691 if (  !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
705 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
706
707 /* Get the public key from DNS */
708
709 if (!(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 */
713 pdkim_decode_base64(ams->b.data, &sighash);
714
715 arc_get_verify_hhash(ctx, ams, &hhash);
716
717 /* Setup the interface to the signing library */
718
719 if ((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
725 hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
726
727 if ((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
734 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
735 as->ams_verify_passed = TRUE;
736 return NULL;
737 }
738
739
740
741 /* Check the sets are instance-continuous and that all
742 members are present.  Check that no arc_seals are "fail".
743 Set the highest instance number global.
744 Verify the latest AMS.
745 */
746 static uschar *
747 arc_headers_check(arc_ctx * ctx)
748 {
749 arc_set * as;
750 int inst;
751 BOOL ams_fail_found = FALSE;
752 uschar * ret = NULL;
753
754 if (!(as = ctx->arcset_chain))
755   return US"none";
756
757 for(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
780 arc_received = ctx->arcset_chain_last;
781 arc_received_instance = inst;
782 if (ret)
783   return ret;
784
785 /* We can skip the latest-AMS validation, if we already did it. */
786
787 as = ctx->arcset_chain_last;
788 if (as->ams_verify_done ? !as->ams_verify_passed : !!arc_ams_verify(ctx, as))
789   return US"fail";
790
791 return NULL;
792 }
793
794
795 /******************************************************************************/
796 static const uschar *
797 arc_seal_verify(arc_ctx * ctx, arc_set * as)
798 {
799 arc_line * hdr_as = as->hdr_as;
800 arc_set * as2;
801 int hashtype;
802 hctx hhash_ctx;
803 blob hhash_computed;
804 blob sighash;
805 ev_ctx vctx;
806 pdkim_pubkey * p;
807 const uschar * errstr;
808
809 DEBUG(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
822 if (  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
832 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
833
834 if (!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
849 DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
850 for (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
890 exim_sha_finish(&hhash_ctx, &hhash_computed);
891 DEBUG(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
904 if (!(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 */
918 pdkim_decode_base64(hdr_as->b.data, &sighash);
919
920 if ((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
926 hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
927
928 if ((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
937 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
938 return NULL;
939 }
940
941
942 static const uschar *
943 arc_verify_seals(arc_ctx * ctx)
944 {
945 arc_set * as = ctx->arcset_chain;
946
947 if (!as)
948   return US"none";
949
950 while (as)
951   {
952   if (arc_seal_verify(ctx, as)) return US"fail";
953   as = as->next;
954   }
955 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
956 return NULL;
957 }
958 /******************************************************************************/
959
960 /* Do ARC verification.  Called from DATA ACL, on a verify = arc
961 condition.  No arguments; we are checking globals.
962
963 Return:  The ARC state, or NULL on error.
964 */
965
966 const uschar *
967 acl_verify_arc(void)
968 {
969 arc_ctx ctx = { NULL };
970 const uschar * res;
971
972 /* AS evaluation, per
973 https://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
979 if ((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
997 if ((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
1039 if ((res = arc_verify_seals(&ctx)))
1040   goto out;
1041
1042 res = US"pass";
1043
1044 out:
1045   return res;
1046 }
1047
1048 /******************************************************************************/
1049
1050 /* Prepend the header to the rlist */
1051
1052 static hdr_rlist *
1053 arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
1054 {
1055 hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
1056 header_line * h = r->h = (header_line *)(r+1);
1057
1058 r->prev = list;
1059 r->used = FALSE;
1060 h->next = NULL;
1061 h->type = 0;
1062 h->slen = len;
1063 h->text = US s;
1064
1065 /* This works for either NL or CRLF lines; also nul-termination */
1066 while (*++s)
1067   if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
1068 s++;            /* move past end of line */
1069
1070 return r;
1071 }
1072
1073
1074 /* Walk the given headers strings identifying each header, and construct
1075 a reverse-order list.  Also parse ARC-chain headers and build the chain
1076 struct, retaining pointers into the string.
1077 */
1078
1079 static hdr_rlist *
1080 arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
1081 {
1082 const uschar * s;
1083 hdr_rlist * rheaders = NULL;
1084
1085 s = sigheaders ? sigheaders->s : NULL;
1086 if (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   }
1098 return rheaders;
1099 }
1100
1101
1102
1103 /* Return the A-R content, without identity, with line-ending and
1104 NUL termination. */
1105
1106 static BOOL
1107 arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
1108 {
1109 header_line * h;
1110 int ilen = Ustrlen(identity);
1111
1112 ret->data = NULL;
1113 for(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   }
1135 return FALSE;
1136 }
1137
1138
1139
1140 /* Append a constructed AAR including CRLF.  Add it to the arc_ctx too.  */
1141
1142 static gstring *
1143 arc_sign_append_aar(gstring * g, arc_ctx * ctx,
1144   const uschar * identity, int instance, blob * ar)
1145 {
1146 int aar_off = g ? g->ptr : 0;
1147 arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
1148 arc_line * al = (arc_line *)(as+1);
1149 header_line * h = (header_line *)(al+1);
1150
1151 g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
1152 g = string_cat(g, string_sprintf(" i=%d; %s;\r\n\t", instance, identity));
1153 g = string_catn(g, US ar->data, ar->len);
1154
1155 h->slen = g->ptr - aar_off;
1156 h->text = g->s + aar_off;
1157 al->complete = h;
1158 as->next = NULL;
1159 as->prev = ctx->arcset_chain_last;
1160 as->instance = instance;
1161 as->hdr_aar = al;
1162 if (instance == 1)
1163   ctx->arcset_chain = as;
1164 else
1165   ctx->arcset_chain_last->next = as;
1166 ctx->arcset_chain_last = as;
1167
1168 DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
1169 return g;
1170 }
1171
1172
1173
1174 static BOOL
1175 arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
1176   blob * sig, const uschar * why)
1177 {
1178 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
1179   ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
1180 blob hhash;
1181 es_ctx sctx;
1182 const uschar * errstr;
1183
1184 DEBUG(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
1196 if (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   }
1203 else
1204   {
1205   hhash.data = hdata->s;
1206   hhash.len = hdata->ptr;
1207   }
1208
1209 if (  (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   }
1215 return TRUE;
1216 }
1217
1218
1219
1220 static gstring *
1221 arc_sign_append_sig(gstring * g, blob * sig)
1222 {
1223 /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
1224 sig->data = pdkim_encode_base64(sig);
1225 sig->len = Ustrlen(sig->data);
1226 for (;;)
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   }
1234 g = string_catn(g, US";\r\n", 3);
1235 gstring_reset_unused(g);
1236 string_from_gstring(g);
1237 return g;
1238 }
1239
1240
1241 /* Append a constructed AMS including CRLF.  Add it to the arc_ctx too. */
1242
1243 static gstring *
1244 arc_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 {
1248 uschar * s;
1249 gstring * hdata = NULL;
1250 int col;
1251 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);       /*XXX hardwired */
1252 blob sig;
1253 int ams_off;
1254 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1255 header_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
1261 ams_off = g->ptr;
1262 g = 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
1270 for(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
1304 if (g->s[g->ptr - 1] == ':') g->ptr--;
1305
1306 g = 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
1312 s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, TRUE);
1313 hdata = 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
1318 if (!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
1324 g->ptr--;
1325 g = arc_sign_append_sig(g, &sig);
1326
1327 h->slen = g->ptr - ams_off;
1328 h->text = g->s + ams_off;
1329 al->complete = h;
1330 ctx->arcset_chain_last->hdr_ams = al;
1331
1332 DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
1333 return g;
1334 }
1335
1336
1337
1338 /* Look for an arc= result in an A-R header blob.  We know that its data
1339 happens to be a NUL-term string. */
1340
1341 static uschar *
1342 arc_ar_cv_status(blob * ar)
1343 {
1344 const uschar * resinfo = ar->data;
1345 int sep = ';';
1346 uschar * methodspec, * s;
1347
1348 while ((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     }
1356 return NULL;
1357 }
1358
1359
1360
1361 /* Build the AS header and prepend it */
1362
1363 static gstring *
1364 arc_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 {
1368 gstring * arcset;
1369 arc_set * as;
1370 uschar * status = arc_ar_cv_status(ar);
1371 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
1372 header_line * h = (header_line *)(al+1);
1373 uschar * s;
1374
1375 gstring * hdata = NULL;
1376 int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);       /*XXX hardwired */
1377 blob 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
1394 arcset = 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
1402 h->slen = arcset->ptr;
1403 h->text = arcset->s;
1404 al->complete = h;
1405 ctx->arcset_chain_last->hdr_as = al;
1406
1407 /* For any but "fail" chain-verify status, walk the entire chain in order by
1408 instance.  For fail, only the new arc-set.  Accumulate the elements walked. */
1409
1410 for (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
1427 if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
1428   return NULL;
1429
1430 /* Lose the trailing semicolon */
1431 arcset->ptr--;
1432 arcset = arc_sign_append_sig(arcset, &sig);
1433 DEBUG(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
1437 return 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
1444 method if needed.
1445 */
1446
1447 void *
1448 arc_ams_setup_sign_bodyhash(void)
1449 {
1450 int canon_head, canon_body;
1451
1452 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
1453 pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body);      /*XXX hardwired */
1454 return 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
1463 the call to arc_sign(), which carries any newly-created DKIM headers - and
1464 those go textually before the normal ones in the message.
1465
1466 We have to take the feed from DKIM as, in the transport-filter case, the
1467 headers are not in memory at the time of the call to arc_sign().
1468
1469 Take a copy of the header and construct a reverse-order list.
1470 Also parse ARC-chain headers and build the chain struct, retaining pointers
1471 into the copies.
1472 */
1473
1474 static const uschar *
1475 arc_header_sign_feed(gstring * g)
1476 {
1477 uschar * s = string_copyn(g->s, g->ptr);
1478 headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
1479 return 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.
1485 The dkim_exim_sign() function has already been called, so will have hashed the
1486 message body for us so long as we requested a hash previously.
1487
1488 Arguments:
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
1494 Return value
1495   Set of headers to prepend to the message, including the supplied sigheaders
1496   but not the plainheaders.
1497 */
1498
1499 gstring *
1500 arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
1501 {
1502 const uschar * identity, * selector, * privkey;
1503 int sep = 0;
1504 header_line * headers;
1505 hdr_rlist * rheaders;
1506 blob ar;
1507 int instance;
1508 gstring * g = NULL;
1509 pdkim_bodyhash * b;
1510
1511 /* Parse the signing specification */
1512
1513 identity = string_nextinlist(&signspec, &sep, NULL, 0);
1514 selector = string_nextinlist(&signspec, &sep, NULL, 0);
1515 if (  !*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   }
1521 if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
1522   return NULL;
1523
1524 DEBUG(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
1533 string_from_gstring(sigheaders);
1534 if ((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   }
1541 else
1542   rheaders = headers_rlist;
1543 /* Finally, build a normal-order headers list */
1544 /*XXX only needed for hunt-the-AR? */
1545 {
1546 header_line * hnext = NULL;
1547 for (; rheaders; hnext = rheaders->h, rheaders = rheaders->prev)
1548   rheaders->h->next = hnext;
1549 headers = hnext;
1550 }
1551
1552 instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
1553
1554 if (!(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
1565 g = 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
1576 b = arc_ams_setup_sign_bodyhash();
1577 g = 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
1593 g = 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
1597 g = string_catn(g, sigheaders->s, sigheaders->ptr);
1598 (void) string_from_gstring(g);
1599 gstring_reset_unused(g);
1600 return g;
1601 }
1602
1603
1604 /******************************************************************************/
1605
1606 /* Check to see if the line is an AMS and if so, set up to validate it.
1607 Called from the DKIM input processing.  This must be done now as the message
1608 body data is hashed during input.
1609
1610 We call the DKIM code to request a body-hash; it has the facility already
1611 and the hash parameters might be common with other requests.
1612 */
1613
1614 static const uschar *
1615 arc_header_vfy_feed(gstring * g)
1616 {
1617 header_line h;
1618 arc_line al;
1619 pdkim_bodyhash * b;
1620 uschar * errstr;
1621
1622 if (!dkim_verify_ctx) return US"no dkim context";
1623
1624 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
1625
1626 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
1627 /* Parse the AMS header */
1628
1629 h.next = NULL;
1630 h.slen = g->size;
1631 h.text = g->s;
1632 memset(&al, 0, sizeof(arc_line));
1633 if ((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 */
1640 if (!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
1648 if (!(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
1652 should have been created here. */
1653
1654 return NULL;
1655 }
1656
1657
1658
1659 /* A header line has been identified by DKIM processing.
1660
1661 Arguments:
1662   g             Header line
1663   is_vfy        TRUE for verify mode or FALSE for signing mode
1664
1665 Return:
1666   NULL for success, or an error string (probably unused)
1667 */
1668
1669 const uschar *
1670 arc_header_feed(gstring * g, BOOL is_vfy)
1671 {
1672 return 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
1681 gstring *
1682 authres_arc(gstring * g)
1683 {
1684 if (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   }
1707 else
1708   DEBUG(D_acl) debug_printf("ARC:  no authres\n");
1709 return g;
1710 }
1711
1712
1713 # endif /* SUPPORT_SPF */
1714 #endif /* EXPERIMENTAL_ARC */
1715 /* vi: aw ai sw=2
1716  */