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