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