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. */
74 dmarc_init(void * dummy)
77 if (!(dmarc_spf_mod_info = misc_mod_find(US"spf", &errstr)))
79 log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
83 if (!(dmarc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
85 log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
93 dmarc_version_report(gstring * g)
95 return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
96 (OPENDMARC_LIB_VERSION & 0xff000000) >> 24,
97 (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
98 (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8,
99 (OPENDMARC_LIB_VERSION & 0x000000ff));
103 /* Accept an error_block struct, initialize if empty, parse to the
104 end, and append the two strings passed to it. Used for adding
105 variable amounts of value:pair data to the forensic emails. */
108 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
110 error_block *eb = store_malloc(sizeof(error_block));
115 /* Find the end of the eblock struct and point it at eb */
116 error_block *tmp = eblock;
127 /* dmarc_msg_init sets up a context that can be re-used for several
128 messages on the same SMTP connection (that come from the
129 same host with the same HELO string).
130 However, we seem to only use it for one; we destroy some sort of context
131 at the tail end of dmarc_process(). */
136 int *netmask = NULL; /* Ignored */
139 /* Set some sane defaults. Also clears previous results when
140 multiple messages in one connection. */
143 dmarc_status = US"none";
145 dmarc_pass_fail = US"skipped";
146 dmarc_used_domain = US"";
147 f.dmarc_has_been_checked = FALSE;
148 header_from_sender = NULL;
149 spf_response_p = NULL;
150 spf_sender_domain = NULL;
151 spf_human_readable = NULL;
153 /* ACLs have "control=dmarc_disable_verify" */
154 if (f.dmarc_disable_verify)
157 (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
158 dmarc_ctx.nscount = 0;
159 libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
160 if (libdm_status != DMARC_PARSE_OKAY)
162 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
163 opendmarc_policy_status_to_str(libdm_status));
166 if (!dmarc_tld_file || !*dmarc_tld_file)
168 DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
171 else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
173 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
174 dmarc_tld_file, strerror(errno));
177 if (!sender_host_address)
179 DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
182 /* This catches locally originated email and startup errors above. */
185 is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
186 if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
188 log_write(0, LOG_MAIN|LOG_PANIC,
189 "DMARC failure creating policy context: ip=%s", sender_host_address);
199 dmarc_smtp_reset(void)
201 f.dmarc_has_been_checked = f.dmarc_disable_verify =
202 f.dmarc_enable_forensic = FALSE;
203 dmarc_domain_policy = dmarc_status = dmarc_status_text =
204 dmarc_used_domain = NULL;
208 /* dmarc_store_data stores the header data so that subsequent dmarc_process can
210 Called after the entire message has been received, with the From: header. */
213 dmarc_store_data(header_line * hdr)
215 /* No debug output because would change every test debug output */
216 if (!f.dmarc_disable_verify)
223 dmarc_send_forensic_report(u_char ** ruf)
225 uschar *recipient, *save_sender;
226 BOOL send_status = FALSE;
227 error_block *eblock = NULL;
228 FILE *message_file = NULL;
230 /* Earlier ACL does not have *required* control=dmarc_enable_forensic */
231 if (!f.dmarc_enable_forensic)
234 if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT
235 || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE
236 || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_REJECT
237 || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_QUARANTINE
241 eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain);
242 eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
243 eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
244 eblock = add_to_eblock(eblock, US"SPF Alignment",
245 sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no");
246 eblock = add_to_eblock(eblock, US"DKIM Alignment",
247 da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no");
248 eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
250 for (int c = 0; ruf[c]; c++)
252 recipient = string_copylc(ruf[c]);
253 if (Ustrncmp(recipient, "mailto:",7))
255 /* Move to first character past the colon */
258 debug_printf_indent("DMARC forensic report to %s%s\n", recipient,
259 (host_checking || f.running_in_test_harness) ? " (not really)" : "");
260 if (host_checking || f.running_in_test_harness)
263 if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
264 header_list, message_file, NULL))
265 log_write(0, LOG_MAIN|LOG_PANIC,
266 "failure to send DMARC forensic report to %s", recipient);
272 /* Look up a DNS dmarc record for the given domain. Return it or NULL */
275 dmarc_dns_lookup(uschar * dom)
277 dns_answer * dnsa = store_get_dns_answer();
279 int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
281 if (rc == DNS_SUCCEED)
282 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
283 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
284 if (rr->type == T_TXT && rr->size > 3)
286 uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
287 store_free_dns_answer(dnsa);
290 store_free_dns_answer(dnsa);
296 dmarc_write_history_file(const gstring * dkim_history_buffer)
298 int history_file_fd = 0;
301 u_char ** rua; /* aggregate report addressees */
304 if (!dmarc_history_file)
306 DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
307 return DMARC_HIST_DISABLED;
311 uschar * s = string_copy(dmarc_history_file); /* need a writeable copy */
312 if ((history_file_fd = log_open_as_exim(s)) < 0)
314 log_write(0, LOG_MAIN|LOG_PANIC,
315 "failure to create DMARC history file: %s: %s",
317 return DMARC_HIST_FILE_ERR;
321 /* Generate the contents of the history file entry */
323 g = string_fmt_append(NULL,
324 "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
325 message_id, primary_hostname, time(NULL), sender_host_address,
326 header_from_sender, expand_string(US"$sender_address_domain"));
329 g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
331 if (dkim_history_buffer)
332 g = string_fmt_append(g, "%Y", dkim_history_buffer);
334 g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
335 dmarc_used_domain, dmarc_policy);
337 if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
338 for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
339 g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]);
341 g = string_catn(g, US"rua -\n", 6);
343 opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
344 g = string_fmt_append(g, "pct %d\n", tmp_ans);
346 opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
347 g = string_fmt_append(g, "adkim %d\n", tmp_ans);
349 opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
350 g = string_fmt_append(g, "aspf %d\n", tmp_ans);
352 opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
353 g = string_fmt_append(g, "p %d\n", tmp_ans);
355 opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
356 g = string_fmt_append(g, "sp %d\n", tmp_ans);
358 g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
361 #if DMARC_API >= 100400
362 # ifdef EXPERIMENTAL_ARC
363 g = arc_dmarc_hist_append(g);
365 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
366 ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
370 /* Write the contents to the history file */
373 debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
374 host_checking ? " (not really)" : "");
375 debug_printf_indent("DMARC history data for debugging:\n");
377 debug_printf_indent("%Y", g);
383 written_len = write_to_fd_buf(history_file_fd,
386 if (written_len == 0)
388 log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
390 return DMARC_HIST_WRITE_ERR;
392 (void)close(history_file_fd);
394 return DMARC_HIST_OK;
398 /* dmarc_process adds the envelope sender address to the existing
399 context (if any), retrieves the result, sets up expansion
400 strings and evaluates the condition outcome.
401 Called for the first ACL dmarc= condition. */
406 int sr, origin; /* used in SPF section */
407 int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */
410 BOOL has_dmarc_record = TRUE;
411 u_char ** ruf; /* forensic report addressees, if called for */
413 /* ACLs have "control=dmarc_disable_verify" */
414 if (f.dmarc_disable_verify)
417 /* Store the header From: sender domain for this part of DMARC.
418 If there is no from_header struct, then it's likely this message
419 is locally generated and relying on fixups to add it. Just skip
420 the entire DMARC system if we can't find a From: header....or if
421 there was a previous error. */
425 DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
428 else if (!dmarc_abort)
435 f.parse_allow_group = TRUE;
436 p = parse_find_address_end(from_header->text, FALSE);
437 saveend = *p; *p = '\0';
438 if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
439 &dummy, &dummy, &domain, FALSE)))
440 header_from_sender += domain;
443 /* The opendmarc library extracts the domain from the email address, but
444 only try to store it if it's not empty. Otherwise, skip out of DMARC. */
446 if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
448 libdm_status = dmarc_abort
450 : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
451 if (libdm_status != DMARC_PARSE_OKAY)
453 log_write(0, LOG_MAIN|LOG_PANIC,
454 "failure to store header From: in DMARC: %s, header was '%s'",
455 opendmarc_policy_status_to_str(libdm_status), from_header->text);
460 /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
461 instead do this in the ACLs. */
463 if (!dmarc_abort && !sender_host_authenticated)
465 uschar * dmarc_domain;
466 gstring * dkim_history_buffer = NULL;
467 typedef const pdkim_signature * (*sigs_fn_t)(void);
469 /* Use the envelope sender domain for this part of DMARC */
471 spf_sender_domain = expand_string(US"$sender_address_domain");
474 typedef SPF_response_t * (*fn_t)(void);
475 if (dmarc_spf_mod_info)
476 /*XXX ugly use of a pointer */
477 spf_response_p = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESPONSE]();
482 /* No spf data means null envelope sender so generate a domain name
483 from the sender_helo_name */
485 if (!spf_sender_domain)
487 spf_sender_domain = sender_helo_name;
488 log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
491 debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
494 dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
495 dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
496 origin = DMARC_POLICY_SPF_ORIGIN_HELO;
497 spf_human_readable = US"";
501 sr = spf_response_p->result;
502 dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE :
503 sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS :
504 sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL :
505 sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
506 DMARC_POLICY_SPF_OUTCOME_NONE;
507 dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL :
508 sr == SPF_RESULT_PASS ? ARES_RESULT_PASS :
509 sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL :
510 sr == SPF_RESULT_SOFTFAIL ? ARES_RESULT_SOFTFAIL :
511 sr == SPF_RESULT_NONE ? ARES_RESULT_NONE :
512 sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
513 sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
515 origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
516 spf_human_readable = US spf_response_p->header_comment;
518 debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
520 if (strcmp( CCS spf_sender_domain, "") == 0)
524 libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
525 dmarc_spf_result, origin, spf_human_readable);
526 if (libdm_status != DMARC_PARSE_OKAY)
527 log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
528 opendmarc_policy_status_to_str(libdm_status));
531 /* Now we cycle through the dkim signature results and put into
532 the opendmarc context, further building the DMARC reply. */
534 for(const pdkim_signature * sig =
535 (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])();
536 sig; sig = sig->next)
538 int dkim_result, dkim_ares_result, vs, ves;
540 vs = sig->verify_status & ~PDKIM_VERIFY_POLICY;
541 ves = sig->verify_ext_status;
542 dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
543 vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
544 vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
545 DMARC_POLICY_DKIM_OUTCOME_NONE;
546 libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
548 /* The opendmarc project broke its API in a way we can't detect easily.
549 The EDITME provides a DMARC_API variable */
550 #if DMARC_API >= 100400
555 debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
556 if (libdm_status != DMARC_PARSE_OKAY)
557 log_write(0, LOG_MAIN|LOG_PANIC,
558 "failure to store dkim (%s) for DMARC: %s",
559 sig->domain, opendmarc_policy_status_to_str(libdm_status));
562 vs == PDKIM_VERIFY_PASS ? ARES_RESULT_PASS :
563 vs == PDKIM_VERIFY_FAIL ? ARES_RESULT_FAIL :
564 vs == PDKIM_VERIFY_NONE ? ARES_RESULT_NONE :
565 vs == PDKIM_VERIFY_INVALID ?
566 ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
567 ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR :
568 ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD ? ARES_RESULT_PERMERROR :
569 ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT ? ARES_RESULT_PERMERROR :
570 ARES_RESULT_UNKNOWN :
572 #if DMARC_API >= 100400
573 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
574 "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
576 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
577 "dkim %s %d\n", sig->domain, dkim_ares_result);
581 /* Look up DMARC policy record in DNS. We do this explicitly, rather than
582 letting the dmarc library do it with opendmarc_policy_query_dmarc(), so that
583 our dns access path is used for debug tracing and for the testsuite
586 libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
587 ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
588 : DMARC_DNS_ERROR_NO_RECORD;
589 switch (libdm_status)
591 case DMARC_DNS_ERROR_NXDOMAIN:
592 case DMARC_DNS_ERROR_NO_RECORD:
594 debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
595 has_dmarc_record = FALSE;
597 case DMARC_PARSE_OKAY:
599 debug_printf_indent("DMARC record found for %s\n", header_from_sender);
601 case DMARC_PARSE_ERROR_BAD_VALUE:
603 debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
604 has_dmarc_record = FALSE;
607 /* everything else, skip dmarc */
609 debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
610 opendmarc_policy_status_to_str(libdm_status),
612 has_dmarc_record = FALSE;
616 /* Store the policy string in an expandable variable. */
618 libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
619 for (c = 0; dmarc_policy_description[c].name; c++)
620 if (tmp_ans == dmarc_policy_description[c].value)
622 dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
626 /* Can't use exim's string manipulation functions so allocate memory
627 for libopendmarc using its max hostname length definition. */
629 dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
630 libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
631 dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
632 store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
633 dmarc_used_domain = dmarc_domain;
635 if (libdm_status != DMARC_PARSE_OKAY)
636 log_write(0, LOG_MAIN|LOG_PANIC,
637 "failure to read domainname used for DMARC lookup: %s",
638 opendmarc_policy_status_to_str(libdm_status));
640 dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
643 case DMARC_POLICY_ABSENT: /* No DMARC record found */
644 dmarc_status = US"norecord";
645 dmarc_pass_fail = US"none";
646 dmarc_status_text = US"No DMARC record";
647 action = DMARC_RESULT_ACCEPT;
649 case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */
650 dmarc_status = US"nofrom";
651 dmarc_pass_fail = US"temperror";
652 dmarc_status_text = US"No From: domain found";
653 action = DMARC_RESULT_ACCEPT;
655 case DMARC_POLICY_NONE: /* Accept and report */
656 dmarc_status = US"none";
657 dmarc_pass_fail = US"none";
658 dmarc_status_text = US"None, Accept";
659 action = DMARC_RESULT_ACCEPT;
661 case DMARC_POLICY_PASS: /* Explicit accept */
662 dmarc_status = US"accept";
663 dmarc_pass_fail = US"pass";
664 dmarc_status_text = US"Accept";
665 action = DMARC_RESULT_ACCEPT;
667 case DMARC_POLICY_REJECT: /* Explicit reject */
668 dmarc_status = US"reject";
669 dmarc_pass_fail = US"fail";
670 dmarc_status_text = US"Reject";
671 action = DMARC_RESULT_REJECT;
673 case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */
674 dmarc_status = US"quarantine";
675 dmarc_pass_fail = US"fail";
676 dmarc_status_text = US"Quarantine";
677 action = DMARC_RESULT_QUARANTINE;
680 dmarc_status = US"temperror";
681 dmarc_pass_fail = US"temperror";
682 dmarc_status_text = US"Internal Policy Error";
683 action = DMARC_RESULT_TEMPFAIL;
687 libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
688 if (libdm_status != DMARC_PARSE_OKAY)
689 log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
690 opendmarc_policy_status_to_str(libdm_status));
692 if (has_dmarc_record)
694 log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
695 "spf_align=%s dkim_align=%s enforcement='%s'",
696 spf_sender_domain, dmarc_used_domain,
697 sa==DMARC_POLICY_SPF_ALIGNMENT_PASS ?"yes":"no",
698 da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
700 history_file_status = dmarc_write_history_file(dkim_history_buffer);
701 /* Now get the forensic reporting addresses, if any */
702 ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
703 dmarc_send_forensic_report(ruf);
707 /* shut down libopendmarc */
709 (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
710 if (!f.dmarc_disable_verify)
711 (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
717 dmarc_exim_expand_defaults(int what)
719 if (what == DMARC_VERIFY_STATUS)
720 return f.dmarc_disable_verify ? US"off" : US"none";
725 dmarc_exim_expand_query(int what)
727 if (f.dmarc_disable_verify || !dmarc_pctx)
728 return dmarc_exim_expand_defaults(what);
730 if (what == DMARC_VERIFY_STATUS)
737 authres_dmarc(gstring * g)
739 if (f.dmarc_has_been_checked)
741 int start = 0; /* Compiler quietening */
742 DEBUG(D_acl) start = gstring_length(g);
743 g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
744 if (header_from_sender)
745 g = string_append(g, 2, US" header.from=", header_from_sender);
746 DEBUG(D_acl) debug_printf("DMARC:\tauthres '%.*s'\n",
747 gstring_length(g) - start - 3, g->s + start + 3);
750 DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
754 /******************************************************************************/
757 static optionlist dmarc_options[] = {
758 { "dmarc_forensic_sender", opt_stringptr, {&dmarc_forensic_sender} },
759 { "dmarc_history_file", opt_stringptr, {&dmarc_history_file} },
760 { "dmarc_tld_file", opt_stringptr, {&dmarc_tld_file} },
763 static void * dmarc_functions[] = {
764 [DMARC_PROCESS] = dmarc_process,
765 [DMARC_EXPAND_QUERY] = dmarc_exim_expand_query,
766 [DMARC_STORE_DATA] = dmarc_store_data,
769 /* dmarc_forensic_sender is provided for visibility of the the option setting
770 by moan_send_message. We do not document it as a config-visible $variable.
771 We could provide it via a function but there's little advantage. */
773 static var_entry dmarc_variables[] = {
774 { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy },
775 { "dmarc_forensic_sender", vtype_stringptr, &dmarc_forensic_sender },
776 { "dmarc_status", vtype_stringptr, &dmarc_status },
777 { "dmarc_status_text", vtype_stringptr, &dmarc_status_text },
778 { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain },
781 misc_module_info dmarc_module_info =
785 .dyn_magic = MISC_MODULE_MAGIC,
788 .lib_vers_report = dmarc_version_report,
789 .smtp_reset = dmarc_smtp_reset,
790 .msg_init = dmarc_msg_init,
791 .authres = authres_dmarc,
793 .options = dmarc_options,
794 .options_count = nelem(dmarc_options),
796 .functions = dmarc_functions,
797 .functions_count = nelem(dmarc_functions),
799 .variables = dmarc_variables,
800 .variables_count = nelem(dmarc_variables),
803 # endif /* SUPPORT_SPF */
804 #endif /* SUPPORT_DMARC */