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