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