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