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