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"
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 static misc_module_info * dmarc_dkim_mod_info;
36 static misc_module_info * dmarc_spf_mod_info;
37 SPF_response_t *spf_response_p;
38 int dmarc_spf_ares_result = 0;
39 uschar *spf_sender_domain = NULL;
40 uschar *spf_human_readable = NULL;
41 u_char *header_from_sender = NULL;
42 int history_file_status = DMARC_HIST_OK;
44 typedef struct dmarc_exim_p {
49 static dmarc_exim_p dmarc_policy_description[] = {
51 { US"", DMARC_RECORD_P_UNSPECIFIED },
52 { US"none", DMARC_RECORD_P_NONE },
53 { US"quarantine", DMARC_RECORD_P_QUARANTINE },
54 { US"reject", DMARC_RECORD_P_REJECT },
60 uschar * dmarc_domain_policy = NULL; /* Declared policy of used domain */
61 uschar * dmarc_status = NULL; /* One word value */
62 uschar * dmarc_status_text = NULL; /* Human readable value */
63 uschar * dmarc_used_domain = NULL; /* Domain libopendmarc chose for DMARC policy lookup */
66 uschar * dmarc_forensic_sender = NULL; /* Set sender address for forensic reports */
67 uschar * dmarc_history_file = NULL; /* File to store dmarc results */
68 uschar * dmarc_tld_file = NULL; /* Mozilla TLDs text file */
71 /* One-time initialisation for dmarc. Ensure the spf module is available. */
77 if (!(dmarc_spf_mod_info = misc_mod_find(US"spf", &errstr)))
79 log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
83 /*XXX not yet used, but will be */
84 if (!(dmarc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
86 log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
94 dmarc_version_report(gstring * g)
96 return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
97 (OPENDMARC_LIB_VERSION & 0xff000000) >> 24,
98 (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
99 (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8,
100 (OPENDMARC_LIB_VERSION & 0x000000ff));
104 /* Accept an error_block struct, initialize if empty, parse to the
105 end, and append the two strings passed to it. Used for adding
106 variable amounts of value:pair data to the forensic emails. */
109 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
111 error_block *eb = store_malloc(sizeof(error_block));
116 /* Find the end of the eblock struct and point it at eb */
117 error_block *tmp = eblock;
128 /* dmarc_msg_init sets up a context that can be re-used for several
129 messages on the same SMTP connection (that come from the
130 same host with the same HELO string).
131 However, we seem to only use it for one; we destroy some sort of context
132 at the tail end of dmarc_process(). */
137 int *netmask = NULL; /* Ignored */
140 /* Set some sane defaults. Also clears previous results when
141 multiple messages in one connection. */
144 dmarc_status = US"none";
146 dmarc_pass_fail = US"skipped";
147 dmarc_used_domain = US"";
148 f.dmarc_has_been_checked = FALSE;
149 header_from_sender = NULL;
150 spf_response_p = NULL;
151 spf_sender_domain = NULL;
152 spf_human_readable = NULL;
154 /* ACLs have "control=dmarc_disable_verify" */
155 if (f.dmarc_disable_verify)
158 (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
159 dmarc_ctx.nscount = 0;
160 libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
161 if (libdm_status != DMARC_PARSE_OKAY)
163 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
164 opendmarc_policy_status_to_str(libdm_status));
167 if (!dmarc_tld_file || !*dmarc_tld_file)
169 DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
172 else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
174 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
175 dmarc_tld_file, strerror(errno));
178 if (!sender_host_address)
180 DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
183 /* This catches locally originated email and startup errors above. */
186 is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
187 if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
189 log_write(0, LOG_MAIN|LOG_PANIC,
190 "DMARC failure creating policy context: ip=%s", sender_host_address);
200 dmarc_smtp_reset(void)
202 f.dmarc_has_been_checked = f.dmarc_disable_verify =
203 f.dmarc_enable_forensic = FALSE;
204 dmarc_domain_policy = dmarc_status = dmarc_status_text =
205 dmarc_used_domain = NULL;
209 /* dmarc_store_data stores the header data so that subsequent dmarc_process can
211 Called after the entire message has been received, with the From: header. */
214 dmarc_store_data(header_line * hdr)
216 /* No debug output because would change every test debug output */
217 if (!f.dmarc_disable_verify)
224 dmarc_send_forensic_report(u_char ** ruf)
226 uschar *recipient, *save_sender;
227 BOOL send_status = FALSE;
228 error_block *eblock = NULL;
229 FILE *message_file = NULL;
231 /* Earlier ACL does not have *required* control=dmarc_enable_forensic */
232 if (!f.dmarc_enable_forensic)
235 if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT
236 || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE
237 || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_REJECT
238 || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_QUARANTINE
242 eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain);
243 eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
244 eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
245 eblock = add_to_eblock(eblock, US"SPF Alignment",
246 sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no");
247 eblock = add_to_eblock(eblock, US"DKIM Alignment",
248 da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no");
249 eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
251 for (int c = 0; ruf[c]; c++)
253 recipient = string_copylc(ruf[c]);
254 if (Ustrncmp(recipient, "mailto:",7))
256 /* Move to first character past the colon */
259 debug_printf_indent("DMARC forensic report to %s%s\n", recipient,
260 (host_checking || f.running_in_test_harness) ? " (not really)" : "");
261 if (host_checking || f.running_in_test_harness)
264 if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
265 header_list, message_file, NULL))
266 log_write(0, LOG_MAIN|LOG_PANIC,
267 "failure to send DMARC forensic report to %s", recipient);
273 /* Look up a DNS dmarc record for the given domain. Return it or NULL */
276 dmarc_dns_lookup(uschar * dom)
278 dns_answer * dnsa = store_get_dns_answer();
280 int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
282 if (rc == DNS_SUCCEED)
283 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
284 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
285 if (rr->type == T_TXT && rr->size > 3)
287 uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
288 store_free_dns_answer(dnsa);
291 store_free_dns_answer(dnsa);
297 dmarc_write_history_file(const gstring * dkim_history_buffer)
299 int history_file_fd = 0;
302 u_char ** rua; /* aggregate report addressees */
305 if (!dmarc_history_file)
307 DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
308 return DMARC_HIST_DISABLED;
312 uschar * s = string_copy(dmarc_history_file); /* need a writeable copy */
313 if ((history_file_fd = log_open_as_exim(s)) < 0)
315 log_write(0, LOG_MAIN|LOG_PANIC,
316 "failure to create DMARC history file: %s: %s",
318 return DMARC_HIST_FILE_ERR;
322 /* Generate the contents of the history file entry */
324 g = string_fmt_append(NULL,
325 "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
326 message_id, primary_hostname, time(NULL), sender_host_address,
327 header_from_sender, expand_string(US"$sender_address_domain"));
330 g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
332 if (dkim_history_buffer)
333 g = string_fmt_append(g, "%Y", dkim_history_buffer);
335 g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
336 dmarc_used_domain, dmarc_policy);
338 if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
339 for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
340 g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]);
342 g = string_catn(g, US"rua -\n", 6);
344 opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
345 g = string_fmt_append(g, "pct %d\n", tmp_ans);
347 opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
348 g = string_fmt_append(g, "adkim %d\n", tmp_ans);
350 opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
351 g = string_fmt_append(g, "aspf %d\n", tmp_ans);
353 opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
354 g = string_fmt_append(g, "p %d\n", tmp_ans);
356 opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
357 g = string_fmt_append(g, "sp %d\n", tmp_ans);
359 g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
362 #if DMARC_API >= 100400
363 # ifdef EXPERIMENTAL_ARC
364 g = arc_dmarc_hist_append(g);
366 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
367 ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
371 /* Write the contents to the history file */
374 debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
375 host_checking ? " (not really)" : "");
376 debug_printf_indent("DMARC history data for debugging:\n");
378 debug_printf_indent("%Y", g);
384 written_len = write_to_fd_buf(history_file_fd,
387 if (written_len == 0)
389 log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
391 return DMARC_HIST_WRITE_ERR;
393 (void)close(history_file_fd);
395 return DMARC_HIST_OK;
399 /* dmarc_process adds the envelope sender address to the existing
400 context (if any), retrieves the result, sets up expansion
401 strings and evaluates the condition outcome.
402 Called for the first ACL dmarc= condition. */
407 int sr, origin; /* used in SPF section */
408 int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */
411 BOOL has_dmarc_record = TRUE;
412 u_char ** ruf; /* forensic report addressees, if called for */
414 /* ACLs have "control=dmarc_disable_verify" */
415 if (f.dmarc_disable_verify)
418 /* Store the header From: sender domain for this part of DMARC.
419 If there is no from_header struct, then it's likely this message
420 is locally generated and relying on fixups to add it. Just skip
421 the entire DMARC system if we can't find a From: header....or if
422 there was a previous error. */
426 DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
429 else if (!dmarc_abort)
436 f.parse_allow_group = TRUE;
437 p = parse_find_address_end(from_header->text, FALSE);
438 saveend = *p; *p = '\0';
439 if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
440 &dummy, &dummy, &domain, FALSE)))
441 header_from_sender += domain;
444 /* The opendmarc library extracts the domain from the email address, but
445 only try to store it if it's not empty. Otherwise, skip out of DMARC. */
447 if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
449 libdm_status = dmarc_abort
451 : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
452 if (libdm_status != DMARC_PARSE_OKAY)
454 log_write(0, LOG_MAIN|LOG_PANIC,
455 "failure to store header From: in DMARC: %s, header was '%s'",
456 opendmarc_policy_status_to_str(libdm_status), from_header->text);
461 /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
462 instead do this in the ACLs. */
464 if (!dmarc_abort && !sender_host_authenticated)
466 uschar * dmarc_domain;
467 gstring * dkim_history_buffer = NULL;
468 typedef const pdkim_signature * (*sigs_fn_t)(void);
470 /* Use the envelope sender domain for this part of DMARC */
472 spf_sender_domain = expand_string(US"$sender_address_domain");
475 typedef SPF_response_t * (*fn_t)(void);
476 if (dmarc_spf_mod_info)
477 /*XXX ugly use of a pointer */
478 spf_response_p = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESPONSE]();
483 /* No spf data means null envelope sender so generate a domain name
484 from the sender_helo_name */
486 if (!spf_sender_domain)
488 spf_sender_domain = sender_helo_name;
489 log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
492 debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
495 dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
496 dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
497 origin = DMARC_POLICY_SPF_ORIGIN_HELO;
498 spf_human_readable = US"";
502 sr = spf_response_p->result;
503 dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE :
504 sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS :
505 sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL :
506 sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
507 DMARC_POLICY_SPF_OUTCOME_NONE;
508 dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL :
509 sr == SPF_RESULT_PASS ? ARES_RESULT_PASS :
510 sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL :
511 sr == SPF_RESULT_SOFTFAIL ? ARES_RESULT_SOFTFAIL :
512 sr == SPF_RESULT_NONE ? ARES_RESULT_NONE :
513 sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
514 sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
516 origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
517 spf_human_readable = US spf_response_p->header_comment;
519 debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
521 if (strcmp( CCS spf_sender_domain, "") == 0)
525 libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
526 dmarc_spf_result, origin, spf_human_readable);
527 if (libdm_status != DMARC_PARSE_OKAY)
528 log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
529 opendmarc_policy_status_to_str(libdm_status));
532 /* Now we cycle through the dkim signature results and put into
533 the opendmarc context, further building the DMARC reply. */
535 for(const pdkim_signature * sig =
536 (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])();
537 sig; sig = sig->next)
539 int dkim_result, dkim_ares_result, vs, ves;
541 vs = sig->verify_status & ~PDKIM_VERIFY_POLICY;
542 ves = sig->verify_ext_status;
543 dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
544 vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
545 vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
546 DMARC_POLICY_DKIM_OUTCOME_NONE;
547 libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
549 /* The opendmarc project broke its API in a way we can't detect easily.
550 The EDITME provides a DMARC_API variable */
551 #if DMARC_API >= 100400
556 debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
557 if (libdm_status != DMARC_PARSE_OKAY)
558 log_write(0, LOG_MAIN|LOG_PANIC,
559 "failure to store dkim (%s) for DMARC: %s",
560 sig->domain, opendmarc_policy_status_to_str(libdm_status));
563 vs == PDKIM_VERIFY_PASS ? ARES_RESULT_PASS :
564 vs == PDKIM_VERIFY_FAIL ? ARES_RESULT_FAIL :
565 vs == PDKIM_VERIFY_NONE ? ARES_RESULT_NONE :
566 vs == PDKIM_VERIFY_INVALID ?
567 ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
568 ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR :
569 ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD ? ARES_RESULT_PERMERROR :
570 ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT ? ARES_RESULT_PERMERROR :
571 ARES_RESULT_UNKNOWN :
573 #if DMARC_API >= 100400
574 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
575 "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
577 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
578 "dkim %s %d\n", sig->domain, dkim_ares_result);
582 /* Look up DMARC policy record in DNS. We do this explicitly, rather than
583 letting the dmarc library do it with opendmarc_policy_query_dmarc(), so that
584 our dns access path is used for debug tracing and for the testsuite
587 libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
588 ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
589 : DMARC_DNS_ERROR_NO_RECORD;
590 switch (libdm_status)
592 case DMARC_DNS_ERROR_NXDOMAIN:
593 case DMARC_DNS_ERROR_NO_RECORD:
595 debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
596 has_dmarc_record = FALSE;
598 case DMARC_PARSE_OKAY:
600 debug_printf_indent("DMARC record found for %s\n", header_from_sender);
602 case DMARC_PARSE_ERROR_BAD_VALUE:
604 debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
605 has_dmarc_record = FALSE;
608 /* everything else, skip dmarc */
610 debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
611 opendmarc_policy_status_to_str(libdm_status),
613 has_dmarc_record = FALSE;
617 /* Store the policy string in an expandable variable. */
619 libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
620 for (c = 0; dmarc_policy_description[c].name; c++)
621 if (tmp_ans == dmarc_policy_description[c].value)
623 dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
627 /* Can't use exim's string manipulation functions so allocate memory
628 for libopendmarc using its max hostname length definition. */
630 dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
631 libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
632 dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
633 store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
634 dmarc_used_domain = dmarc_domain;
636 if (libdm_status != DMARC_PARSE_OKAY)
637 log_write(0, LOG_MAIN|LOG_PANIC,
638 "failure to read domainname used for DMARC lookup: %s",
639 opendmarc_policy_status_to_str(libdm_status));
641 dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
644 case DMARC_POLICY_ABSENT: /* No DMARC record found */
645 dmarc_status = US"norecord";
646 dmarc_pass_fail = US"none";
647 dmarc_status_text = US"No DMARC record";
648 action = DMARC_RESULT_ACCEPT;
650 case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */
651 dmarc_status = US"nofrom";
652 dmarc_pass_fail = US"temperror";
653 dmarc_status_text = US"No From: domain found";
654 action = DMARC_RESULT_ACCEPT;
656 case DMARC_POLICY_NONE: /* Accept and report */
657 dmarc_status = US"none";
658 dmarc_pass_fail = US"none";
659 dmarc_status_text = US"None, Accept";
660 action = DMARC_RESULT_ACCEPT;
662 case DMARC_POLICY_PASS: /* Explicit accept */
663 dmarc_status = US"accept";
664 dmarc_pass_fail = US"pass";
665 dmarc_status_text = US"Accept";
666 action = DMARC_RESULT_ACCEPT;
668 case DMARC_POLICY_REJECT: /* Explicit reject */
669 dmarc_status = US"reject";
670 dmarc_pass_fail = US"fail";
671 dmarc_status_text = US"Reject";
672 action = DMARC_RESULT_REJECT;
674 case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */
675 dmarc_status = US"quarantine";
676 dmarc_pass_fail = US"fail";
677 dmarc_status_text = US"Quarantine";
678 action = DMARC_RESULT_QUARANTINE;
681 dmarc_status = US"temperror";
682 dmarc_pass_fail = US"temperror";
683 dmarc_status_text = US"Internal Policy Error";
684 action = DMARC_RESULT_TEMPFAIL;
688 libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
689 if (libdm_status != DMARC_PARSE_OKAY)
690 log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
691 opendmarc_policy_status_to_str(libdm_status));
693 if (has_dmarc_record)
695 log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
696 "spf_align=%s dkim_align=%s enforcement='%s'",
697 spf_sender_domain, dmarc_used_domain,
698 sa==DMARC_POLICY_SPF_ALIGNMENT_PASS ?"yes":"no",
699 da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
701 history_file_status = dmarc_write_history_file(dkim_history_buffer);
702 /* Now get the forensic reporting addresses, if any */
703 ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
704 dmarc_send_forensic_report(ruf);
708 /* shut down libopendmarc */
710 (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
711 if (!f.dmarc_disable_verify)
712 (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
718 dmarc_exim_expand_defaults(int what)
720 if (what == DMARC_VERIFY_STATUS)
721 return f.dmarc_disable_verify ? US"off" : US"none";
726 dmarc_exim_expand_query(int what)
728 if (f.dmarc_disable_verify || !dmarc_pctx)
729 return dmarc_exim_expand_defaults(what);
731 if (what == DMARC_VERIFY_STATUS)
738 authres_dmarc(gstring * g)
740 if (f.dmarc_has_been_checked)
742 int start = 0; /* Compiler quietening */
743 DEBUG(D_acl) start = gstring_length(g);
744 g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
745 if (header_from_sender)
746 g = string_append(g, 2, US" header.from=", header_from_sender);
747 DEBUG(D_acl) debug_printf("DMARC:\tauthres '%.*s'\n",
748 gstring_length(g) - start - 3, g->s + start + 3);
751 DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
755 /******************************************************************************/
758 static optionlist dmarc_options[] = {
759 { "dmarc_forensic_sender", opt_stringptr, {&dmarc_forensic_sender} },
760 { "dmarc_history_file", opt_stringptr, {&dmarc_history_file} },
761 { "dmarc_tld_file", opt_stringptr, {&dmarc_tld_file} },
764 static void * dmarc_functions[] = {
765 [DMARC_PROCESS] = dmarc_process,
766 [DMARC_EXPAND_QUERY] = dmarc_exim_expand_query,
767 [DMARC_STORE_DATA] = dmarc_store_data,
770 /* dmarc_forensic_sender is provided for visibility of the the option setting
771 by moan_send_message. We do not document it as a config-visible $variable.
772 We could provide it via a function but there's little advantage. */
774 static var_entry dmarc_variables[] = {
775 { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy },
776 { "dmarc_forensic_sender", vtype_stringptr, &dmarc_forensic_sender },
777 { "dmarc_status", vtype_stringptr, &dmarc_status },
778 { "dmarc_status_text", vtype_stringptr, &dmarc_status_text },
779 { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain },
782 misc_module_info dmarc_module_info =
786 .dyn_magic = MISC_MODULE_MAGIC,
789 .lib_vers_report = dmarc_version_report,
790 .smtp_reset = dmarc_smtp_reset,
791 .msg_init = dmarc_msg_init,
792 .authres = authres_dmarc,
794 .options = dmarc_options,
795 .options_count = nelem(dmarc_options),
797 .functions = dmarc_functions,
798 .functions_count = nelem(dmarc_functions),
800 .variables = dmarc_variables,
801 .variables_count = nelem(dmarc_variables),
804 # endif /* SUPPORT_SPF */
805 #endif /* SUPPORT_DMARC */