1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 Copyright (c) The Exim Maintainers 2019 - 2024
6 Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
10 /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
11 All rights reserved, licensed for use per LICENSE.opendmarc. */
13 /* Code for calling dmarc checks via libopendmarc. Called from acl.c. */
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
23 # include "functions.h"
25 # include "pdkim/pdkim.h"
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;
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;
43 typedef struct dmarc_exim_p {
48 static dmarc_exim_p dmarc_policy_description[] = {
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 },
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);
69 dmarc_version_report(gstring * g)
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);
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. */
82 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
84 error_block *eb = store_malloc(sizeof(error_block));
89 /* Find the end of the eblock struct and point it at eb */
90 error_block *tmp = eblock;
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) */
106 dmarc_conn_init(void)
108 int *netmask = NULL; /* Ignored */
111 /* Set some sane defaults. Also clears previous results when
112 multiple messages in one connection. */
115 dmarc_status = US"none";
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;
125 /* ACLs have "control=dmarc_disable_verify" */
126 if (f.dmarc_disable_verify)
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)
134 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
135 opendmarc_policy_status_to_str(libdm_status));
138 if (!dmarc_tld_file || !*dmarc_tld_file)
140 DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
143 else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
145 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
146 dmarc_tld_file, strerror(errno));
149 if (!sender_host_address)
151 DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
154 /* This catches locally originated email and startup errors above. */
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)))
160 log_write(0, LOG_MAIN|LOG_PANIC,
161 "DMARC failure creating policy context: ip=%s", sender_host_address);
170 /* dmarc_store_data stores the header data so that subsequent dmarc_process can
172 Called after the entire message has been received, with the From: header. */
175 dmarc_store_data(header_line * hdr)
177 /* No debug output because would change every test debug output */
178 if (!f.dmarc_disable_verify)
185 dmarc_send_forensic_report(u_char ** ruf)
187 uschar *recipient, *save_sender;
188 BOOL send_status = FALSE;
189 error_block *eblock = NULL;
190 FILE *message_file = NULL;
192 /* Earlier ACL does not have *required* control=dmarc_enable_forensic */
193 if (!f.dmarc_enable_forensic)
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
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);
212 for (int c = 0; ruf[c]; c++)
214 recipient = string_copylc(ruf[c]);
215 if (Ustrncmp(recipient, "mailto:",7))
217 /* Move to first character past the colon */
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)
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);
234 /* Look up a DNS dmarc record for the given domain. Return it or NULL */
237 dmarc_dns_lookup(uschar * dom)
239 dns_answer * dnsa = store_get_dns_answer();
241 int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
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)
248 uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
249 store_free_dns_answer(dnsa);
252 store_free_dns_answer(dnsa);
258 dmarc_write_history_file(const gstring * dkim_history_buffer)
260 int history_file_fd = 0;
263 u_char ** rua; /* aggregate report addressees */
266 if (!dmarc_history_file)
268 DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
269 return DMARC_HIST_DISABLED;
273 uschar * s = string_copy(dmarc_history_file); /* need a writeable copy */
274 if ((history_file_fd = log_open_as_exim(s)) < 0)
276 log_write(0, LOG_MAIN|LOG_PANIC,
277 "failure to create DMARC history file: %s: %s",
279 return DMARC_HIST_FILE_ERR;
283 /* Generate the contents of the history file entry */
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"));
291 g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
293 if (dkim_history_buffer)
294 g = string_fmt_append(g, "%Y", dkim_history_buffer);
296 g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
297 dmarc_used_domain, dmarc_policy);
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]);
303 g = string_catn(g, US"rua -\n", 6);
305 opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
306 g = string_fmt_append(g, "pct %d\n", tmp_ans);
308 opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
309 g = string_fmt_append(g, "adkim %d\n", tmp_ans);
311 opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
312 g = string_fmt_append(g, "aspf %d\n", tmp_ans);
314 opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
315 g = string_fmt_append(g, "p %d\n", tmp_ans);
317 opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
318 g = string_fmt_append(g, "sp %d\n", tmp_ans);
320 g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
323 #if DMARC_API >= 100400
324 # ifdef EXPERIMENTAL_ARC
325 g = arc_dmarc_hist_append(g);
327 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
328 ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
332 /* Write the contents to the history file */
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");
339 debug_printf_indent("%Y", g);
345 written_len = write_to_fd_buf(history_file_fd,
348 if (written_len == 0)
350 log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
352 return DMARC_HIST_WRITE_ERR;
354 (void)close(history_file_fd);
356 return DMARC_HIST_OK;
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. */
368 int sr, origin; /* used in SPF section */
369 int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */
371 pdkim_signature * sig = dkim_signatures;
373 BOOL has_dmarc_record = TRUE;
374 u_char ** ruf; /* forensic report addressees, if called for */
376 /* ACLs have "control=dmarc_disable_verify" */
377 if (f.dmarc_disable_verify)
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. */
388 DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
391 else if (!dmarc_abort)
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;
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. */
409 if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
411 libdm_status = dmarc_abort
413 : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
414 if (libdm_status != DMARC_PARSE_OKAY)
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);
423 /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
424 instead do this in the ACLs. */
426 if (!dmarc_abort && !sender_host_authenticated)
428 uschar * dmarc_domain;
429 gstring * dkim_history_buffer = NULL;
431 /* Use the envelope sender domain for this part of DMARC */
433 spf_sender_domain = expand_string(US"$sender_address_domain");
436 misc_module_info * mi = misc_mod_findonly(US"spf");
437 typedef SPF_response_t * (*fn_t)(void);
439 spf_response_p = ((fn_t *) mi->functions)[3](); /* spf_get_response */
444 /* No spf data means null envelope sender so generate a domain name
445 from the sender_helo_name */
447 if (!spf_sender_domain)
449 spf_sender_domain = sender_helo_name;
450 log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
453 debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
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"";
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 :
477 origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
478 spf_human_readable = US spf_response_p->header_comment;
480 debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
482 if (strcmp( CCS spf_sender_domain, "") == 0)
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));
493 /* Now we cycle through the dkim signature results and put into
494 the opendmarc context, further building the DMARC reply. */
496 for(pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
498 int dkim_result, dkim_ares_result, vs, ves;
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,
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
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));
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 :
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);
536 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
537 "dkim %s %d\n", sig->domain, dkim_ares_result);
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
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)
551 case DMARC_DNS_ERROR_NXDOMAIN:
552 case DMARC_DNS_ERROR_NO_RECORD:
554 debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
555 has_dmarc_record = FALSE;
557 case DMARC_PARSE_OKAY:
559 debug_printf_indent("DMARC record found for %s\n", header_from_sender);
561 case DMARC_PARSE_ERROR_BAD_VALUE:
563 debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
564 has_dmarc_record = FALSE;
567 /* everything else, skip dmarc */
569 debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
570 opendmarc_policy_status_to_str(libdm_status),
572 has_dmarc_record = FALSE;
576 /* Store the policy string in an expandable variable. */
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)
582 dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
586 /* Can't use exim's string manipulation functions so allocate memory
587 for libopendmarc using its max hostname length definition. */
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;
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));
600 dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
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;
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;
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;
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;
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;
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;
640 dmarc_status = US"temperror";
641 dmarc_pass_fail = US"temperror";
642 dmarc_status_text = US"Internal Policy Error";
643 action = DMARC_RESULT_TEMPFAIL;
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));
652 if (has_dmarc_record)
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",
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);
667 /* shut down libopendmarc */
669 (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
670 if (!f.dmarc_disable_verify)
671 (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
677 dmarc_exim_expand_query(int what)
679 if (f.dmarc_disable_verify || !dmarc_pctx)
680 return dmarc_exim_expand_defaults(what);
682 if (what == DMARC_VERIFY_STATUS)
688 dmarc_exim_expand_defaults(int what)
690 if (what == DMARC_VERIFY_STATUS)
691 return f.dmarc_disable_verify ? US"off" : US"none";
697 authres_dmarc(gstring * g)
699 if (f.dmarc_has_been_checked)
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);
710 DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
714 # endif /* SUPPORT_SPF */
715 #endif /* SUPPORT_DMARC */