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 },
59 uschar * dmarc_domain_policy = NULL; /* Declared policy of used domain */
60 uschar * dmarc_status = NULL; /* One word value */
61 uschar * dmarc_status_text = NULL; /* Human readable value */
62 uschar * dmarc_used_domain = NULL; /* Domain libopendmarc chose for DMARC policy lookup */
65 uschar * dmarc_forensic_sender = NULL; /* Set sender address for forensic reports */
66 uschar * dmarc_history_file = NULL; /* File to store dmarc results */
67 uschar * dmarc_tld_file = NULL; /* Mozilla TLDs text file */
70 /* One-time initialisation for dmarc. Ensure the spf module is available. */
76 if (!(spf_mod_info = misc_mod_find(US"spf", &errstr)))
77 log_write(0, LOG_MAIN|LOG_PANIC_DIE,
78 "dmarc: failed to find SPF module: %s", errstr);
83 dmarc_version_report(gstring * g)
85 return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
86 (OPENDMARC_LIB_VERSION & 0xff000000) >> 24,
87 (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
88 (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8,
89 (OPENDMARC_LIB_VERSION & 0x000000ff));
93 /* Accept an error_block struct, initialize if empty, parse to the
94 end, and append the two strings passed to it. Used for adding
95 variable amounts of value:pair data to the forensic emails. */
98 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
100 error_block *eb = store_malloc(sizeof(error_block));
105 /* Find the end of the eblock struct and point it at eb */
106 error_block *tmp = eblock;
117 /* dmarc_msg_init sets up a context that can be re-used for several
118 messages on the same SMTP connection (that come from the
119 same host with the same HELO string).
120 However, we seem to only use it for one; we destroy some sort of context
121 at the tail end of dmarc_process(). */
126 int *netmask = NULL; /* Ignored */
129 /* Set some sane defaults. Also clears previous results when
130 multiple messages in one connection. */
133 dmarc_status = US"none";
135 dmarc_pass_fail = US"skipped";
136 dmarc_used_domain = US"";
137 f.dmarc_has_been_checked = FALSE;
138 header_from_sender = NULL;
139 spf_response_p = NULL;
140 spf_sender_domain = NULL;
141 spf_human_readable = NULL;
143 /* ACLs have "control=dmarc_disable_verify" */
144 if (f.dmarc_disable_verify)
147 (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
148 dmarc_ctx.nscount = 0;
149 libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
150 if (libdm_status != DMARC_PARSE_OKAY)
152 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
153 opendmarc_policy_status_to_str(libdm_status));
156 if (!dmarc_tld_file || !*dmarc_tld_file)
158 DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
161 else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
163 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
164 dmarc_tld_file, strerror(errno));
167 if (!sender_host_address)
169 DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
172 /* This catches locally originated email and startup errors above. */
175 is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
176 if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
178 log_write(0, LOG_MAIN|LOG_PANIC,
179 "DMARC failure creating policy context: ip=%s", sender_host_address);
189 dmarc_smtp_reset(void)
191 dmarc_domain_policy = dmarc_status = dmarc_status_text =
192 dmarc_used_domain = NULL;
196 /* dmarc_store_data stores the header data so that subsequent dmarc_process can
198 Called after the entire message has been received, with the From: header. */
201 dmarc_store_data(header_line * hdr)
203 /* No debug output because would change every test debug output */
204 if (!f.dmarc_disable_verify)
211 dmarc_send_forensic_report(u_char ** ruf)
213 uschar *recipient, *save_sender;
214 BOOL send_status = FALSE;
215 error_block *eblock = NULL;
216 FILE *message_file = NULL;
218 /* Earlier ACL does not have *required* control=dmarc_enable_forensic */
219 if (!f.dmarc_enable_forensic)
222 if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT
223 || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE
224 || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_REJECT
225 || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_QUARANTINE
229 eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain);
230 eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
231 eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
232 eblock = add_to_eblock(eblock, US"SPF Alignment",
233 sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no");
234 eblock = add_to_eblock(eblock, US"DKIM Alignment",
235 da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no");
236 eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
238 for (int c = 0; ruf[c]; c++)
240 recipient = string_copylc(ruf[c]);
241 if (Ustrncmp(recipient, "mailto:",7))
243 /* Move to first character past the colon */
246 debug_printf_indent("DMARC forensic report to %s%s\n", recipient,
247 (host_checking || f.running_in_test_harness) ? " (not really)" : "");
248 if (host_checking || f.running_in_test_harness)
251 if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
252 header_list, message_file, NULL))
253 log_write(0, LOG_MAIN|LOG_PANIC,
254 "failure to send DMARC forensic report to %s", recipient);
260 /* Look up a DNS dmarc record for the given domain. Return it or NULL */
263 dmarc_dns_lookup(uschar * dom)
265 dns_answer * dnsa = store_get_dns_answer();
267 int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
269 if (rc == DNS_SUCCEED)
270 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
271 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
272 if (rr->type == T_TXT && rr->size > 3)
274 uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
275 store_free_dns_answer(dnsa);
278 store_free_dns_answer(dnsa);
284 dmarc_write_history_file(const gstring * dkim_history_buffer)
286 int history_file_fd = 0;
289 u_char ** rua; /* aggregate report addressees */
292 if (!dmarc_history_file)
294 DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
295 return DMARC_HIST_DISABLED;
299 uschar * s = string_copy(dmarc_history_file); /* need a writeable copy */
300 if ((history_file_fd = log_open_as_exim(s)) < 0)
302 log_write(0, LOG_MAIN|LOG_PANIC,
303 "failure to create DMARC history file: %s: %s",
305 return DMARC_HIST_FILE_ERR;
309 /* Generate the contents of the history file entry */
311 g = string_fmt_append(NULL,
312 "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
313 message_id, primary_hostname, time(NULL), sender_host_address,
314 header_from_sender, expand_string(US"$sender_address_domain"));
317 g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
319 if (dkim_history_buffer)
320 g = string_fmt_append(g, "%Y", dkim_history_buffer);
322 g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
323 dmarc_used_domain, dmarc_policy);
325 if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
326 for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
327 g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]);
329 g = string_catn(g, US"rua -\n", 6);
331 opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
332 g = string_fmt_append(g, "pct %d\n", tmp_ans);
334 opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
335 g = string_fmt_append(g, "adkim %d\n", tmp_ans);
337 opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
338 g = string_fmt_append(g, "aspf %d\n", tmp_ans);
340 opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
341 g = string_fmt_append(g, "p %d\n", tmp_ans);
343 opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
344 g = string_fmt_append(g, "sp %d\n", tmp_ans);
346 g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
349 #if DMARC_API >= 100400
350 # ifdef EXPERIMENTAL_ARC
351 g = arc_dmarc_hist_append(g);
353 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
354 ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
358 /* Write the contents to the history file */
361 debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
362 host_checking ? " (not really)" : "");
363 debug_printf_indent("DMARC history data for debugging:\n");
365 debug_printf_indent("%Y", g);
371 written_len = write_to_fd_buf(history_file_fd,
374 if (written_len == 0)
376 log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
378 return DMARC_HIST_WRITE_ERR;
380 (void)close(history_file_fd);
382 return DMARC_HIST_OK;
386 /* dmarc_process adds the envelope sender address to the existing
387 context (if any), retrieves the result, sets up expansion
388 strings and evaluates the condition outcome.
389 Called for the first ACL dmarc= condition. */
394 int sr, origin; /* used in SPF section */
395 int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */
397 pdkim_signature * sig = dkim_signatures;
399 BOOL has_dmarc_record = TRUE;
400 u_char ** ruf; /* forensic report addressees, if called for */
402 /* ACLs have "control=dmarc_disable_verify" */
403 if (f.dmarc_disable_verify)
406 /* Store the header From: sender domain for this part of DMARC.
407 If there is no from_header struct, then it's likely this message
408 is locally generated and relying on fixups to add it. Just skip
409 the entire DMARC system if we can't find a From: header....or if
410 there was a previous error. */
414 DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
417 else if (!dmarc_abort)
424 f.parse_allow_group = TRUE;
425 p = parse_find_address_end(from_header->text, FALSE);
426 saveend = *p; *p = '\0';
427 if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
428 &dummy, &dummy, &domain, FALSE)))
429 header_from_sender += domain;
432 /* The opendmarc library extracts the domain from the email address, but
433 only try to store it if it's not empty. Otherwise, skip out of DMARC. */
435 if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
437 libdm_status = dmarc_abort
439 : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
440 if (libdm_status != DMARC_PARSE_OKAY)
442 log_write(0, LOG_MAIN|LOG_PANIC,
443 "failure to store header From: in DMARC: %s, header was '%s'",
444 opendmarc_policy_status_to_str(libdm_status), from_header->text);
449 /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
450 instead do this in the ACLs. */
452 if (!dmarc_abort && !sender_host_authenticated)
454 uschar * dmarc_domain;
455 gstring * dkim_history_buffer = NULL;
457 /* Use the envelope sender domain for this part of DMARC */
459 spf_sender_domain = expand_string(US"$sender_address_domain");
462 typedef SPF_response_t * (*fn_t)(void);
464 /*XXX ugly use of a pointer */
465 spf_response_p = ((fn_t *) spf_mod_info->functions)[SPF_GET_RESPONSE]();
470 /* No spf data means null envelope sender so generate a domain name
471 from the sender_helo_name */
473 if (!spf_sender_domain)
475 spf_sender_domain = sender_helo_name;
476 log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
479 debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
482 dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
483 dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
484 origin = DMARC_POLICY_SPF_ORIGIN_HELO;
485 spf_human_readable = US"";
489 sr = spf_response_p->result;
490 dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE :
491 sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS :
492 sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL :
493 sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
494 DMARC_POLICY_SPF_OUTCOME_NONE;
495 dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL :
496 sr == SPF_RESULT_PASS ? ARES_RESULT_PASS :
497 sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL :
498 sr == SPF_RESULT_SOFTFAIL ? ARES_RESULT_SOFTFAIL :
499 sr == SPF_RESULT_NONE ? ARES_RESULT_NONE :
500 sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
501 sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
503 origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
504 spf_human_readable = US spf_response_p->header_comment;
506 debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
508 if (strcmp( CCS spf_sender_domain, "") == 0)
512 libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
513 dmarc_spf_result, origin, spf_human_readable);
514 if (libdm_status != DMARC_PARSE_OKAY)
515 log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
516 opendmarc_policy_status_to_str(libdm_status));
519 /* Now we cycle through the dkim signature results and put into
520 the opendmarc context, further building the DMARC reply. */
522 for(pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
524 int dkim_result, dkim_ares_result, vs, ves;
526 vs = sig->verify_status & ~PDKIM_VERIFY_POLICY;
527 ves = sig->verify_ext_status;
528 dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
529 vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
530 vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
531 DMARC_POLICY_DKIM_OUTCOME_NONE;
532 libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
534 /* The opendmarc project broke its API in a way we can't detect easily.
535 The EDITME provides a DMARC_API variable */
536 #if DMARC_API >= 100400
541 debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
542 if (libdm_status != DMARC_PARSE_OKAY)
543 log_write(0, LOG_MAIN|LOG_PANIC,
544 "failure to store dkim (%s) for DMARC: %s",
545 sig->domain, opendmarc_policy_status_to_str(libdm_status));
548 vs == PDKIM_VERIFY_PASS ? ARES_RESULT_PASS :
549 vs == PDKIM_VERIFY_FAIL ? ARES_RESULT_FAIL :
550 vs == PDKIM_VERIFY_NONE ? ARES_RESULT_NONE :
551 vs == PDKIM_VERIFY_INVALID ?
552 ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
553 ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR :
554 ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD ? ARES_RESULT_PERMERROR :
555 ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT ? ARES_RESULT_PERMERROR :
556 ARES_RESULT_UNKNOWN :
558 #if DMARC_API >= 100400
559 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
560 "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
562 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
563 "dkim %s %d\n", sig->domain, dkim_ares_result);
567 /* Look up DMARC policy record in DNS. We do this explicitly, rather than
568 letting the dmarc library do it with opendmarc_policy_query_dmarc(), so that
569 our dns access path is used for debug tracing and for the testsuite
572 libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
573 ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
574 : DMARC_DNS_ERROR_NO_RECORD;
575 switch (libdm_status)
577 case DMARC_DNS_ERROR_NXDOMAIN:
578 case DMARC_DNS_ERROR_NO_RECORD:
580 debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
581 has_dmarc_record = FALSE;
583 case DMARC_PARSE_OKAY:
585 debug_printf_indent("DMARC record found for %s\n", header_from_sender);
587 case DMARC_PARSE_ERROR_BAD_VALUE:
589 debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
590 has_dmarc_record = FALSE;
593 /* everything else, skip dmarc */
595 debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
596 opendmarc_policy_status_to_str(libdm_status),
598 has_dmarc_record = FALSE;
602 /* Store the policy string in an expandable variable. */
604 libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
605 for (c = 0; dmarc_policy_description[c].name; c++)
606 if (tmp_ans == dmarc_policy_description[c].value)
608 dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
612 /* Can't use exim's string manipulation functions so allocate memory
613 for libopendmarc using its max hostname length definition. */
615 dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
616 libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
617 dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
618 store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
619 dmarc_used_domain = dmarc_domain;
621 if (libdm_status != DMARC_PARSE_OKAY)
622 log_write(0, LOG_MAIN|LOG_PANIC,
623 "failure to read domainname used for DMARC lookup: %s",
624 opendmarc_policy_status_to_str(libdm_status));
626 dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
629 case DMARC_POLICY_ABSENT: /* No DMARC record found */
630 dmarc_status = US"norecord";
631 dmarc_pass_fail = US"none";
632 dmarc_status_text = US"No DMARC record";
633 action = DMARC_RESULT_ACCEPT;
635 case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */
636 dmarc_status = US"nofrom";
637 dmarc_pass_fail = US"temperror";
638 dmarc_status_text = US"No From: domain found";
639 action = DMARC_RESULT_ACCEPT;
641 case DMARC_POLICY_NONE: /* Accept and report */
642 dmarc_status = US"none";
643 dmarc_pass_fail = US"none";
644 dmarc_status_text = US"None, Accept";
645 action = DMARC_RESULT_ACCEPT;
647 case DMARC_POLICY_PASS: /* Explicit accept */
648 dmarc_status = US"accept";
649 dmarc_pass_fail = US"pass";
650 dmarc_status_text = US"Accept";
651 action = DMARC_RESULT_ACCEPT;
653 case DMARC_POLICY_REJECT: /* Explicit reject */
654 dmarc_status = US"reject";
655 dmarc_pass_fail = US"fail";
656 dmarc_status_text = US"Reject";
657 action = DMARC_RESULT_REJECT;
659 case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */
660 dmarc_status = US"quarantine";
661 dmarc_pass_fail = US"fail";
662 dmarc_status_text = US"Quarantine";
663 action = DMARC_RESULT_QUARANTINE;
666 dmarc_status = US"temperror";
667 dmarc_pass_fail = US"temperror";
668 dmarc_status_text = US"Internal Policy Error";
669 action = DMARC_RESULT_TEMPFAIL;
673 libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
674 if (libdm_status != DMARC_PARSE_OKAY)
675 log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
676 opendmarc_policy_status_to_str(libdm_status));
678 if (has_dmarc_record)
680 log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
681 "spf_align=%s dkim_align=%s enforcement='%s'",
682 spf_sender_domain, dmarc_used_domain,
683 sa==DMARC_POLICY_SPF_ALIGNMENT_PASS ?"yes":"no",
684 da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
686 history_file_status = dmarc_write_history_file(dkim_history_buffer);
687 /* Now get the forensic reporting addresses, if any */
688 ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
689 dmarc_send_forensic_report(ruf);
693 /* shut down libopendmarc */
695 (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
696 if (!f.dmarc_disable_verify)
697 (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
703 dmarc_exim_expand_defaults(int what)
705 if (what == DMARC_VERIFY_STATUS)
706 return f.dmarc_disable_verify ? US"off" : US"none";
711 dmarc_exim_expand_query(int what)
713 if (f.dmarc_disable_verify || !dmarc_pctx)
714 return dmarc_exim_expand_defaults(what);
716 if (what == DMARC_VERIFY_STATUS)
723 authres_dmarc(gstring * g)
725 if (f.dmarc_has_been_checked)
727 int start = 0; /* Compiler quietening */
728 DEBUG(D_acl) start = gstring_length(g);
729 g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
730 if (header_from_sender)
731 g = string_append(g, 2, US" header.from=", header_from_sender);
732 DEBUG(D_acl) debug_printf("DMARC:\tauthres '%.*s'\n",
733 gstring_length(g) - start - 3, g->s + start + 3);
736 DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
740 /******************************************************************************/
743 static optionlist dmarc_options[] = {
744 { "dmarc_forensic_sender", opt_stringptr, {&dmarc_forensic_sender} },
745 { "dmarc_history_file", opt_stringptr, {&dmarc_history_file} },
746 { "dmarc_tld_file", opt_stringptr, {&dmarc_tld_file} },
749 static void * dmarc_functions[] = {
750 [DMARC_PROCESS] = dmarc_process,
751 [DMARC_EXPAND_QUERY] = dmarc_exim_expand_query,
752 [DMARC_AUTHRES] = authres_dmarc,
753 [DMARC_STORE_DATA] = dmarc_store_data,
756 /* dmarc_forensic_sender is provided for visibility of the the option setting
757 by moan_send_message. We do not document it as a config-visible $variable.
758 We could provide it via a function but there's little advantage. */
760 static var_entry dmarc_variables[] = {
761 { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy },
762 { "dmarc_forensic_sender", vtype_stringptr, &dmarc_forensic_sender },
763 { "dmarc_status", vtype_stringptr, &dmarc_status },
764 { "dmarc_status_text", vtype_stringptr, &dmarc_status_text },
765 { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain },
768 misc_module_info dmarc_module_info =
772 .dyn_magic = MISC_MODULE_MAGIC,
775 .lib_vers_report = dmarc_version_report,
776 .smtp_reset = dmarc_smtp_reset,
777 .msg_init = dmarc_msg_init,
779 .options = dmarc_options,
780 .options_count = nelem(dmarc_options),
782 .functions = dmarc_functions,
783 .functions_count = nelem(dmarc_functions),
785 .variables = dmarc_variables,
786 .variables_count = nelem(dmarc_variables),
789 # endif /* SUPPORT_SPF */
790 #endif /* SUPPORT_DMARC */