664daa737319e56037bf8104dc0089fda02611af
[exim.git] / src / src / dmarc.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4 /* DMARC support.
5    Copyright (c) The Exim Maintainers 2019 - 2024
6    Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014
7    License: GPL */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
11    All rights reserved, licensed for use per LICENSE.opendmarc. */
12
13 /* Code for calling dmarc checks via libopendmarc. Called from acl.c. */
14
15 #include "exim.h"
16 #ifdef SUPPORT_DMARC
17 # if !defined SUPPORT_SPF
18 #  error SPF must also be enabled for DMARC
19 # elif defined DISABLE_DKIM
20 #  error DKIM must also be enabled for DMARC
21 # else
22
23 #  include "functions.h"
24 #  include "dmarc.h"
25 #  include "pdkim/pdkim.h"
26
27 OPENDMARC_LIB_T     dmarc_ctx;
28 DMARC_POLICY_T     *dmarc_pctx = NULL;
29 OPENDMARC_STATUS_T  libdm_status, action, dmarc_policy;
30 OPENDMARC_STATUS_T  da, sa, action;
31 BOOL dmarc_abort  = FALSE;
32 uschar *dmarc_pass_fail = US"skipped";
33 header_line *from_header   = NULL;
34
35 misc_module_info * spf_mod_info;
36 SPF_response_t   *spf_response_p;
37 int dmarc_spf_ares_result  = 0;
38 uschar *spf_sender_domain  = NULL;
39 uschar *spf_human_readable = NULL;
40 u_char *header_from_sender = NULL;
41 int history_file_status    = DMARC_HIST_OK;
42
43 typedef struct dmarc_exim_p {
44   uschar *name;
45   int    value;
46 } dmarc_exim_p;
47
48 static dmarc_exim_p dmarc_policy_description[] = {
49   /* name               value */
50   { US"",           DMARC_RECORD_P_UNSPECIFIED },
51   { US"none",       DMARC_RECORD_P_NONE },
52   { US"quarantine", DMARC_RECORD_P_QUARANTINE },
53   { US"reject",     DMARC_RECORD_P_REJECT },
54   { NULL,           0 }
55 };
56
57
58 int
59 dmarc_init(void)
60 {
61 uschar * errstr;
62 if (!(spf_mod_info = misc_mod_find(US"spf", &errstr)))
63   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
64             "dmarc: failed to find SPF module: %s", errstr);
65 return TRUE;
66 }
67
68 gstring *
69 dmarc_version_report(gstring * g)
70 {
71 return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
72     (OPENDMARC_LIB_VERSION & 0xff000000) >> 24, (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
73     (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8, OPENDMARC_LIB_VERSION & 0x000000ff);
74 }
75
76
77 /* Accept an error_block struct, initialize if empty, parse to the
78 end, and append the two strings passed to it.  Used for adding
79 variable amounts of value:pair data to the forensic emails. */
80
81 static error_block *
82 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
83 {
84 error_block *eb = store_malloc(sizeof(error_block));
85 if (!eblock)
86   eblock = eb;
87 else
88   {
89   /* Find the end of the eblock struct and point it at eb */
90   error_block *tmp = eblock;
91   while(tmp->next)
92     tmp = tmp->next;
93   tmp->next = eb;
94   }
95 eb->text1 = t1;
96 eb->text2 = t2;
97 eb->next  = NULL;
98 return eblock;
99 }
100
101 /* dmarc_conn_init sets up a context that can be re-used for several
102 messages on the same SMTP connection (that come from the
103 same host with the same HELO string) */
104
105 int
106 dmarc_conn_init(void)
107 {
108 int *netmask   = NULL;   /* Ignored */
109 int is_ipv6    = 0;
110
111 /* Set some sane defaults.  Also clears previous results when
112 multiple messages in one connection. */
113
114 dmarc_pctx         = NULL;
115 dmarc_status       = US"none";
116 dmarc_abort        = FALSE;
117 dmarc_pass_fail    = US"skipped";
118 dmarc_used_domain  = US"";
119 f.dmarc_has_been_checked = FALSE;
120 header_from_sender = NULL;
121 spf_response_p     = NULL;
122 spf_sender_domain  = NULL;
123 spf_human_readable = NULL;
124
125 /* ACLs have "control=dmarc_disable_verify" */
126 if (f.dmarc_disable_verify)
127   return OK;
128
129 (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
130 dmarc_ctx.nscount = 0;
131 libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
132 if (libdm_status != DMARC_PARSE_OKAY)
133   {
134   log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
135                        opendmarc_policy_status_to_str(libdm_status));
136   dmarc_abort = TRUE;
137   }
138 if (!dmarc_tld_file || !*dmarc_tld_file)
139   {
140   DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
141   dmarc_abort = TRUE;
142   }
143 else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
144   {
145   log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
146                        dmarc_tld_file, strerror(errno));
147   dmarc_abort = TRUE;
148   }
149 if (!sender_host_address)
150   {
151   DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
152   dmarc_abort = TRUE;
153   }
154 /* This catches locally originated email and startup errors above. */
155 if (!dmarc_abort)
156   {
157   is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
158   if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
159     {
160     log_write(0, LOG_MAIN|LOG_PANIC,
161       "DMARC failure creating policy context: ip=%s", sender_host_address);
162     dmarc_abort = TRUE;
163     }
164   }
165
166 return OK;
167 }
168
169
170 /* dmarc_store_data stores the header data so that subsequent dmarc_process can
171 access the data.
172 Called after the entire message has been received, with the From: header. */
173
174 int
175 dmarc_store_data(header_line * hdr)
176 {
177 /* No debug output because would change every test debug output */
178 if (!f.dmarc_disable_verify)
179   from_header = hdr;
180 return OK;
181 }
182
183
184 static void
185 dmarc_send_forensic_report(u_char ** ruf)
186 {
187 uschar *recipient, *save_sender;
188 BOOL  send_status = FALSE;
189 error_block *eblock = NULL;
190 FILE *message_file = NULL;
191
192 /* Earlier ACL does not have *required* control=dmarc_enable_forensic */
193 if (!f.dmarc_enable_forensic)
194   return;
195
196 if (  dmarc_policy == DMARC_POLICY_REJECT     && action == DMARC_RESULT_REJECT
197    || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE
198    || dmarc_policy == DMARC_POLICY_NONE       && action == DMARC_RESULT_REJECT
199    || dmarc_policy == DMARC_POLICY_NONE       && action == DMARC_RESULT_QUARANTINE
200    )
201   if (ruf)
202     {
203     eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain);
204     eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
205     eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
206     eblock = add_to_eblock(eblock, US"SPF Alignment",
207                      sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no");
208     eblock = add_to_eblock(eblock, US"DKIM Alignment",
209                      da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no");
210     eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
211
212     for (int c = 0; ruf[c]; c++)
213       {
214       recipient = string_copylc(ruf[c]);
215       if (Ustrncmp(recipient, "mailto:",7))
216         continue;
217       /* Move to first character past the colon */
218       recipient += 7;
219       DEBUG(D_receive)
220         debug_printf_indent("DMARC forensic report to %s%s\n", recipient,
221              (host_checking || f.running_in_test_harness) ? " (not really)" : "");
222       if (host_checking || f.running_in_test_harness)
223         continue;
224
225       if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
226                             header_list, message_file, NULL))
227         log_write(0, LOG_MAIN|LOG_PANIC,
228           "failure to send DMARC forensic report to %s", recipient);
229       }
230     }
231 }
232
233
234 /* Look up a DNS dmarc record for the given domain.  Return it or NULL */
235
236 static uschar *
237 dmarc_dns_lookup(uschar * dom)
238 {
239 dns_answer * dnsa = store_get_dns_answer();
240 dns_scan dnss;
241 int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
242
243 if (rc == DNS_SUCCEED)
244   for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
245        rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
246     if (rr->type == T_TXT && rr->size > 3)
247       {
248       uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
249       store_free_dns_answer(dnsa);
250       return record;
251       }
252 store_free_dns_answer(dnsa);
253 return NULL;
254 }
255
256
257 static int
258 dmarc_write_history_file(const gstring * dkim_history_buffer)
259 {
260 int history_file_fd = 0;
261 ssize_t written_len;
262 int tmp_ans;
263 u_char ** rua; /* aggregate report addressees */
264 gstring * g;
265
266 if (!dmarc_history_file)
267   {
268   DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
269   return DMARC_HIST_DISABLED;
270   }
271 if (!host_checking)
272   {
273   uschar * s = string_copy(dmarc_history_file);         /* need a writeable copy */
274   if ((history_file_fd = log_open_as_exim(s)) < 0)
275     {
276     log_write(0, LOG_MAIN|LOG_PANIC,
277               "failure to create DMARC history file: %s: %s",
278               s, strerror(errno));
279     return DMARC_HIST_FILE_ERR;
280     }
281   }
282
283 /* Generate the contents of the history file entry */
284
285 g = string_fmt_append(NULL,
286   "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
287   message_id, primary_hostname, time(NULL), sender_host_address,
288   header_from_sender, expand_string(US"$sender_address_domain"));
289
290 if (spf_response_p)
291   g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
292
293 if (dkim_history_buffer)
294   g = string_fmt_append(g, "%Y", dkim_history_buffer);
295
296 g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
297   dmarc_used_domain, dmarc_policy);
298
299 if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
300   for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
301     g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]);
302 else
303   g = string_catn(g, US"rua -\n", 6);
304
305 opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
306 g = string_fmt_append(g, "pct %d\n", tmp_ans);
307
308 opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
309 g = string_fmt_append(g, "adkim %d\n", tmp_ans);
310
311 opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
312 g = string_fmt_append(g, "aspf %d\n", tmp_ans);
313
314 opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
315 g = string_fmt_append(g, "p %d\n", tmp_ans);
316
317 opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
318 g = string_fmt_append(g, "sp %d\n", tmp_ans);
319
320 g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
321   da, sa, action);
322
323 #if DMARC_API >= 100400
324 # ifdef EXPERIMENTAL_ARC
325 g = arc_dmarc_hist_append(g);
326 # else
327 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
328                       ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
329 # endif
330 #endif
331
332 /* Write the contents to the history file */
333 DEBUG(D_receive)
334   {
335   debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
336              host_checking ? " (not really)" : "");
337   debug_printf_indent("DMARC history data for debugging:\n");
338   expand_level++;
339     debug_printf_indent("%Y", g);
340   expand_level--;
341   }
342
343 if (!host_checking)
344   {
345   written_len = write_to_fd_buf(history_file_fd,
346                                 g->s,
347                                 gstring_length(g));
348   if (written_len == 0)
349     {
350     log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
351                            dmarc_history_file);
352     return DMARC_HIST_WRITE_ERR;
353     }
354   (void)close(history_file_fd);
355   }
356 return DMARC_HIST_OK;
357 }
358
359
360 /* dmarc_process adds the envelope sender address to the existing
361 context (if any), retrieves the result, sets up expansion
362 strings and evaluates the condition outcome.
363 Called for the first ACL dmarc= condition. */
364
365 int
366 dmarc_process(void)
367 {
368 int sr, origin;             /* used in SPF section */
369 int dmarc_spf_result  = 0;  /* stores spf into dmarc conn ctx */
370 int tmp_ans, c;
371 pdkim_signature * sig = dkim_signatures;
372 uschar * rr;
373 BOOL has_dmarc_record = TRUE;
374 u_char ** ruf; /* forensic report addressees, if called for */
375
376 /* ACLs have "control=dmarc_disable_verify" */
377 if (f.dmarc_disable_verify)
378   return OK;
379
380 /* Store the header From: sender domain for this part of DMARC.
381 If there is no from_header struct, then it's likely this message
382 is locally generated and relying on fixups to add it.  Just skip
383 the entire DMARC system if we can't find a From: header....or if
384 there was a previous error.  */
385
386 if (!from_header)
387   {
388   DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
389   dmarc_abort = TRUE;
390   }
391 else if (!dmarc_abort)
392   {
393   uschar * errormsg;
394   int dummy, domain;
395   uschar * p;
396   uschar saveend;
397
398   f.parse_allow_group = TRUE;
399   p = parse_find_address_end(from_header->text, FALSE);
400   saveend = *p; *p = '\0';
401   if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
402                               &dummy, &dummy, &domain, FALSE)))
403     header_from_sender += domain;
404   *p = saveend;
405
406   /* The opendmarc library extracts the domain from the email address, but
407   only try to store it if it's not empty.  Otherwise, skip out of DMARC. */
408
409   if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
410     dmarc_abort = TRUE;
411   libdm_status = dmarc_abort
412     ? DMARC_PARSE_OKAY
413     : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
414   if (libdm_status != DMARC_PARSE_OKAY)
415     {
416     log_write(0, LOG_MAIN|LOG_PANIC,
417               "failure to store header From: in DMARC: %s, header was '%s'",
418               opendmarc_policy_status_to_str(libdm_status), from_header->text);
419     dmarc_abort = TRUE;
420     }
421   }
422
423 /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
424 instead do this in the ACLs.  */
425
426 if (!dmarc_abort && !sender_host_authenticated)
427   {
428   uschar * dmarc_domain;
429   gstring * dkim_history_buffer = NULL;
430
431   /* Use the envelope sender domain for this part of DMARC */
432
433   spf_sender_domain = expand_string(US"$sender_address_domain");
434
435     {
436     misc_module_info * mi = misc_mod_findonly(US"spf");
437     typedef SPF_response_t * (*fn_t)(void);
438     if (mi)
439       spf_response_p = ((fn_t *) mi->functions)[3]();   /* spf_get_response */
440     }
441
442   if (!spf_response_p)
443     {
444     /* No spf data means null envelope sender so generate a domain name
445     from the sender_helo_name  */
446
447     if (!spf_sender_domain)
448       {
449       spf_sender_domain = sender_helo_name;
450       log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
451                              spf_sender_domain);
452       DEBUG(D_receive)
453         debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
454           spf_sender_domain);
455       }
456     dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
457     dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
458     origin = DMARC_POLICY_SPF_ORIGIN_HELO;
459     spf_human_readable = US"";
460     }
461   else
462     {
463     sr = spf_response_p->result;
464     dmarc_spf_result = sr == SPF_RESULT_NEUTRAL  ? DMARC_POLICY_SPF_OUTCOME_NONE :
465                        sr == SPF_RESULT_PASS     ? DMARC_POLICY_SPF_OUTCOME_PASS :
466                        sr == SPF_RESULT_FAIL     ? DMARC_POLICY_SPF_OUTCOME_FAIL :
467                        sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
468                        DMARC_POLICY_SPF_OUTCOME_NONE;
469     dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL   ? ARES_RESULT_NEUTRAL :
470                             sr == SPF_RESULT_PASS      ? ARES_RESULT_PASS :
471                             sr == SPF_RESULT_FAIL      ? ARES_RESULT_FAIL :
472                             sr == SPF_RESULT_SOFTFAIL  ? ARES_RESULT_SOFTFAIL :
473                             sr == SPF_RESULT_NONE      ? ARES_RESULT_NONE :
474                             sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
475                             sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
476                             ARES_RESULT_UNKNOWN;
477     origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
478     spf_human_readable = US spf_response_p->header_comment;
479     DEBUG(D_receive)
480       debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
481     }
482   if (strcmp( CCS spf_sender_domain, "") == 0)
483     dmarc_abort = TRUE;
484   if (!dmarc_abort)
485     {
486     libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
487                                 dmarc_spf_result, origin, spf_human_readable);
488     if (libdm_status != DMARC_PARSE_OKAY)
489       log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
490                            opendmarc_policy_status_to_str(libdm_status));
491     }
492
493   /* Now we cycle through the dkim signature results and put into
494   the opendmarc context, further building the DMARC reply. */
495
496   for(pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
497     {
498     int dkim_result, dkim_ares_result, vs, ves;
499
500     vs  = sig->verify_status & ~PDKIM_VERIFY_POLICY;
501     ves = sig->verify_ext_status;
502     dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
503                   vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
504                   vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
505                   DMARC_POLICY_DKIM_OUTCOME_NONE;
506     libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
507
508 /* The opendmarc project broke its API in a way we can't detect easily.
509 The EDITME provides a DMARC_API variable */
510 #if DMARC_API >= 100400
511                                                sig->selector,
512 #endif
513                                                dkim_result, US"");
514     DEBUG(D_receive)
515       debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
516     if (libdm_status != DMARC_PARSE_OKAY)
517       log_write(0, LOG_MAIN|LOG_PANIC,
518                 "failure to store dkim (%s) for DMARC: %s",
519                 sig->domain, opendmarc_policy_status_to_str(libdm_status));
520
521     dkim_ares_result =
522       vs == PDKIM_VERIFY_PASS    ? ARES_RESULT_PASS :
523       vs == PDKIM_VERIFY_FAIL    ? ARES_RESULT_FAIL :
524       vs == PDKIM_VERIFY_NONE    ? ARES_RESULT_NONE :
525       vs == PDKIM_VERIFY_INVALID ?
526        ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
527        ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE        ? ARES_RESULT_PERMERROR :
528        ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD   ? ARES_RESULT_PERMERROR :
529        ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT      ? ARES_RESULT_PERMERROR :
530        ARES_RESULT_UNKNOWN :
531       ARES_RESULT_UNKNOWN;
532 #if DMARC_API >= 100400
533     dkim_history_buffer = string_fmt_append(dkim_history_buffer,
534       "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
535 #else
536     dkim_history_buffer = string_fmt_append(dkim_history_buffer,
537       "dkim %s %d\n", sig->domain, dkim_ares_result);
538 #endif
539     }
540
541   /* Look up DMARC policy record in DNS.  We do this explicitly, rather than
542   letting the dmarc library do it with opendmarc_policy_query_dmarc(), so that
543   our dns access path is used for debug tracing and for the testsuite
544   diversion. */
545
546   libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
547     ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
548     : DMARC_DNS_ERROR_NO_RECORD;
549   switch (libdm_status)
550     {
551     case DMARC_DNS_ERROR_NXDOMAIN:
552     case DMARC_DNS_ERROR_NO_RECORD:
553       DEBUG(D_receive)
554         debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
555       has_dmarc_record = FALSE;
556       break;
557     case DMARC_PARSE_OKAY:
558       DEBUG(D_receive)
559         debug_printf_indent("DMARC record found for %s\n", header_from_sender);
560       break;
561     case DMARC_PARSE_ERROR_BAD_VALUE:
562       DEBUG(D_receive)
563         debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
564       has_dmarc_record = FALSE;
565       break;
566     default:
567       /* everything else, skip dmarc */
568       DEBUG(D_receive)
569         debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
570                       opendmarc_policy_status_to_str(libdm_status),
571                       from_header->text);
572       has_dmarc_record = FALSE;
573       break;
574     }
575
576   /* Store the policy string in an expandable variable. */
577
578   libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
579   for (c = 0; dmarc_policy_description[c].name; c++)
580     if (tmp_ans == dmarc_policy_description[c].value)
581       {
582       dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
583       break;
584       }
585
586   /* Can't use exim's string manipulation functions so allocate memory
587   for libopendmarc using its max hostname length definition. */
588
589   dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
590   libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
591     dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
592   store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
593   dmarc_used_domain = dmarc_domain;
594
595   if (libdm_status != DMARC_PARSE_OKAY)
596     log_write(0, LOG_MAIN|LOG_PANIC,
597       "failure to read domainname used for DMARC lookup: %s",
598       opendmarc_policy_status_to_str(libdm_status));
599
600   dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
601   switch(libdm_status)
602     {
603     case DMARC_POLICY_ABSENT:     /* No DMARC record found */
604       dmarc_status = US"norecord";
605       dmarc_pass_fail = US"none";
606       dmarc_status_text = US"No DMARC record";
607       action = DMARC_RESULT_ACCEPT;
608       break;
609     case DMARC_FROM_DOMAIN_ABSENT:    /* No From: domain */
610       dmarc_status = US"nofrom";
611       dmarc_pass_fail = US"temperror";
612       dmarc_status_text = US"No From: domain found";
613       action = DMARC_RESULT_ACCEPT;
614       break;
615     case DMARC_POLICY_NONE:       /* Accept and report */
616       dmarc_status = US"none";
617       dmarc_pass_fail = US"none";
618       dmarc_status_text = US"None, Accept";
619       action = DMARC_RESULT_ACCEPT;
620       break;
621     case DMARC_POLICY_PASS:       /* Explicit accept */
622       dmarc_status = US"accept";
623       dmarc_pass_fail = US"pass";
624       dmarc_status_text = US"Accept";
625       action = DMARC_RESULT_ACCEPT;
626       break;
627     case DMARC_POLICY_REJECT:       /* Explicit reject */
628       dmarc_status = US"reject";
629       dmarc_pass_fail = US"fail";
630       dmarc_status_text = US"Reject";
631       action = DMARC_RESULT_REJECT;
632       break;
633     case DMARC_POLICY_QUARANTINE:       /* Explicit quarantine */
634       dmarc_status = US"quarantine";
635       dmarc_pass_fail = US"fail";
636       dmarc_status_text = US"Quarantine";
637       action = DMARC_RESULT_QUARANTINE;
638       break;
639     default:
640       dmarc_status = US"temperror";
641       dmarc_pass_fail = US"temperror";
642       dmarc_status_text = US"Internal Policy Error";
643       action = DMARC_RESULT_TEMPFAIL;
644       break;
645     }
646
647   libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
648   if (libdm_status != DMARC_PARSE_OKAY)
649     log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
650                              opendmarc_policy_status_to_str(libdm_status));
651
652   if (has_dmarc_record)
653     {
654     log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
655                            "spf_align=%s dkim_align=%s enforcement='%s'",
656                            spf_sender_domain, dmarc_used_domain,
657                            sa==DMARC_POLICY_SPF_ALIGNMENT_PASS  ?"yes":"no",
658                            da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
659                            dmarc_status_text);
660     history_file_status = dmarc_write_history_file(dkim_history_buffer);
661     /* Now get the forensic reporting addresses, if any */
662     ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
663     dmarc_send_forensic_report(ruf);
664     }
665   }
666
667 /* shut down libopendmarc */
668 if (dmarc_pctx)
669   (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
670 if (!f.dmarc_disable_verify)
671   (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
672
673 return OK;
674 }
675
676 uschar *
677 dmarc_exim_expand_query(int what)
678 {
679 if (f.dmarc_disable_verify || !dmarc_pctx)
680   return dmarc_exim_expand_defaults(what);
681
682 if (what == DMARC_VERIFY_STATUS)
683   return dmarc_status;
684 return US"";
685 }
686
687 uschar *
688 dmarc_exim_expand_defaults(int what)
689 {
690 if (what == DMARC_VERIFY_STATUS)
691   return f.dmarc_disable_verify ?  US"off" : US"none";
692 return US"";
693 }
694
695
696 gstring *
697 authres_dmarc(gstring * g)
698 {
699 if (f.dmarc_has_been_checked)
700   {
701   int start = 0;                /* Compiler quietening */
702   DEBUG(D_acl) start = gstring_length(g);
703   g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
704   if (header_from_sender)
705     g = string_append(g, 2, US" header.from=", header_from_sender);
706   DEBUG(D_acl) debug_printf("DMARC:\tauthres '%.*s'\n",
707                   gstring_length(g) - start - 3, g->s + start + 3);
708   }
709 else
710   DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
711 return g;
712 }
713
714 # endif /* SUPPORT_SPF */
715 #endif /* SUPPORT_DMARC */
716 /* vi: aw ai sw=2
717  */