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
364 const misc_module_info * mi = misc_mod_findonly(US"arc");
367 typedef const uschar * (*fn_t)(gstring **);
369 if (mi && (s = (((fn_t *) mi->functions)[ARC_ARCSET_INFO]) (&g2)))
371 int i = Ustrcmp(s, "pass") == 0 ? ARES_RESULT_PASS
372 : Ustrcmp(s, "fail") == 0 ? ARES_RESULT_FAIL
373 : ARES_RESULT_UNKNOWN;
375 g = string_fmt_append(g, "arc %d\n"
376 "arc_policy %d json[%#Y ]\n",
378 i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS
379 : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL
380 : DMARC_ARC_POLICY_RESULT_UNUSED,
385 string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
386 ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
390 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
391 ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
395 /* Write the contents to the history file */
398 debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
399 host_checking ? " (not really)" : "");
400 debug_printf_indent("DMARC history data for debugging:\n");
402 debug_printf_indent("%Y", g);
408 written_len = write_to_fd_buf(history_file_fd,
411 if (written_len == 0)
413 log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
415 return DMARC_HIST_WRITE_ERR;
417 (void)close(history_file_fd);
419 return DMARC_HIST_OK;
423 /* dmarc_process adds the envelope sender address to the existing
424 context (if any), retrieves the result, sets up expansion
425 strings and evaluates the condition outcome.
426 Called for the first ACL dmarc= condition. */
431 int sr, origin; /* used in SPF section */
432 int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */
435 BOOL has_dmarc_record = TRUE;
436 u_char ** ruf; /* forensic report addressees, if called for */
438 /* ACLs have "control=dmarc_disable_verify" */
439 if (f.dmarc_disable_verify)
442 /* Store the header From: sender domain for this part of DMARC.
443 If there is no from_header struct, then it's likely this message
444 is locally generated and relying on fixups to add it. Just skip
445 the entire DMARC system if we can't find a From: header....or if
446 there was a previous error. */
450 DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
453 else if (!dmarc_abort)
460 f.parse_allow_group = TRUE;
461 p = parse_find_address_end(from_header->text, FALSE);
462 saveend = *p; *p = '\0';
463 if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
464 &dummy, &dummy, &domain, FALSE)))
465 header_from_sender += domain;
468 /* The opendmarc library extracts the domain from the email address, but
469 only try to store it if it's not empty. Otherwise, skip out of DMARC. */
471 if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
473 libdm_status = dmarc_abort
475 : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
476 if (libdm_status != DMARC_PARSE_OKAY)
478 log_write(0, LOG_MAIN|LOG_PANIC,
479 "failure to store header From: in DMARC: %s, header was '%s'",
480 opendmarc_policy_status_to_str(libdm_status), from_header->text);
485 /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
486 instead do this in the ACLs. */
488 if (!dmarc_abort && !sender_host_authenticated)
490 uschar * dmarc_domain;
491 gstring * dkim_history_buffer = NULL;
492 typedef const pdkim_signature * (*sigs_fn_t)(void);
494 /* Use the envelope sender domain for this part of DMARC */
496 spf_sender_domain = expand_string(US"$sender_address_domain");
499 typedef SPF_response_t * (*fn_t)(void);
500 if (dmarc_spf_mod_info)
501 /*XXX ugly use of a pointer */
502 spf_response_p = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESPONSE]();
507 /* No spf data means null envelope sender so generate a domain name
508 from the sender_helo_name */
510 if (!spf_sender_domain)
512 spf_sender_domain = sender_helo_name;
513 log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
516 debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
519 dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
520 dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
521 origin = DMARC_POLICY_SPF_ORIGIN_HELO;
522 spf_human_readable = US"";
526 sr = spf_response_p->result;
527 dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE :
528 sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS :
529 sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL :
530 sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
531 DMARC_POLICY_SPF_OUTCOME_NONE;
532 dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL :
533 sr == SPF_RESULT_PASS ? ARES_RESULT_PASS :
534 sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL :
535 sr == SPF_RESULT_SOFTFAIL ? ARES_RESULT_SOFTFAIL :
536 sr == SPF_RESULT_NONE ? ARES_RESULT_NONE :
537 sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
538 sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
540 origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
541 spf_human_readable = US spf_response_p->header_comment;
543 debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
545 if (strcmp( CCS spf_sender_domain, "") == 0)
549 libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
550 dmarc_spf_result, origin, spf_human_readable);
551 if (libdm_status != DMARC_PARSE_OKAY)
552 log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
553 opendmarc_policy_status_to_str(libdm_status));
556 /* Now we cycle through the dkim signature results and put into
557 the opendmarc context, further building the DMARC reply. */
559 for(const pdkim_signature * sig =
560 (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])();
561 sig; sig = sig->next)
563 int dkim_result, dkim_ares_result, vs, ves;
565 vs = sig->verify_status & ~PDKIM_VERIFY_POLICY;
566 ves = sig->verify_ext_status;
567 dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
568 vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
569 vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
570 DMARC_POLICY_DKIM_OUTCOME_NONE;
571 libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
573 /* The opendmarc project broke its API in a way we can't detect easily.
574 The EDITME provides a DMARC_API variable */
575 #if DMARC_API >= 100400
580 debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
581 if (libdm_status != DMARC_PARSE_OKAY)
582 log_write(0, LOG_MAIN|LOG_PANIC,
583 "failure to store dkim (%s) for DMARC: %s",
584 sig->domain, opendmarc_policy_status_to_str(libdm_status));
587 vs == PDKIM_VERIFY_PASS ? ARES_RESULT_PASS :
588 vs == PDKIM_VERIFY_FAIL ? ARES_RESULT_FAIL :
589 vs == PDKIM_VERIFY_NONE ? ARES_RESULT_NONE :
590 vs == PDKIM_VERIFY_INVALID ?
591 ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
592 ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR :
593 ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD ? ARES_RESULT_PERMERROR :
594 ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT ? ARES_RESULT_PERMERROR :
595 ARES_RESULT_UNKNOWN :
597 #if DMARC_API >= 100400
598 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
599 "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
601 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
602 "dkim %s %d\n", sig->domain, dkim_ares_result);
606 /* Look up DMARC policy record in DNS. We do this explicitly, rather than
607 letting the dmarc library do it with opendmarc_policy_query_dmarc(), so that
608 our dns access path is used for debug tracing and for the testsuite
611 libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
612 ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
613 : DMARC_DNS_ERROR_NO_RECORD;
614 switch (libdm_status)
616 case DMARC_DNS_ERROR_NXDOMAIN:
617 case DMARC_DNS_ERROR_NO_RECORD:
619 debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
620 has_dmarc_record = FALSE;
622 case DMARC_PARSE_OKAY:
624 debug_printf_indent("DMARC record found for %s\n", header_from_sender);
626 case DMARC_PARSE_ERROR_BAD_VALUE:
628 debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
629 has_dmarc_record = FALSE;
632 /* everything else, skip dmarc */
634 debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
635 opendmarc_policy_status_to_str(libdm_status),
637 has_dmarc_record = FALSE;
641 /* Store the policy string in an expandable variable. */
643 libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
644 for (c = 0; dmarc_policy_description[c].name; c++)
645 if (tmp_ans == dmarc_policy_description[c].value)
647 dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
651 /* Can't use exim's string manipulation functions so allocate memory
652 for libopendmarc using its max hostname length definition. */
654 dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
655 libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
656 dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
657 store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
658 dmarc_used_domain = dmarc_domain;
660 if (libdm_status != DMARC_PARSE_OKAY)
661 log_write(0, LOG_MAIN|LOG_PANIC,
662 "failure to read domainname used for DMARC lookup: %s",
663 opendmarc_policy_status_to_str(libdm_status));
665 dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
668 case DMARC_POLICY_ABSENT: /* No DMARC record found */
669 dmarc_status = US"norecord";
670 dmarc_pass_fail = US"none";
671 dmarc_status_text = US"No DMARC record";
672 action = DMARC_RESULT_ACCEPT;
674 case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */
675 dmarc_status = US"nofrom";
676 dmarc_pass_fail = US"temperror";
677 dmarc_status_text = US"No From: domain found";
678 action = DMARC_RESULT_ACCEPT;
680 case DMARC_POLICY_NONE: /* Accept and report */
681 dmarc_status = US"none";
682 dmarc_pass_fail = US"none";
683 dmarc_status_text = US"None, Accept";
684 action = DMARC_RESULT_ACCEPT;
686 case DMARC_POLICY_PASS: /* Explicit accept */
687 dmarc_status = US"accept";
688 dmarc_pass_fail = US"pass";
689 dmarc_status_text = US"Accept";
690 action = DMARC_RESULT_ACCEPT;
692 case DMARC_POLICY_REJECT: /* Explicit reject */
693 dmarc_status = US"reject";
694 dmarc_pass_fail = US"fail";
695 dmarc_status_text = US"Reject";
696 action = DMARC_RESULT_REJECT;
698 case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */
699 dmarc_status = US"quarantine";
700 dmarc_pass_fail = US"fail";
701 dmarc_status_text = US"Quarantine";
702 action = DMARC_RESULT_QUARANTINE;
705 dmarc_status = US"temperror";
706 dmarc_pass_fail = US"temperror";
707 dmarc_status_text = US"Internal Policy Error";
708 action = DMARC_RESULT_TEMPFAIL;
712 libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
713 if (libdm_status != DMARC_PARSE_OKAY)
714 log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
715 opendmarc_policy_status_to_str(libdm_status));
717 if (has_dmarc_record)
719 log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
720 "spf_align=%s dkim_align=%s enforcement='%s'",
721 spf_sender_domain, dmarc_used_domain,
722 sa==DMARC_POLICY_SPF_ALIGNMENT_PASS ?"yes":"no",
723 da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
725 history_file_status = dmarc_write_history_file(dkim_history_buffer);
726 /* Now get the forensic reporting addresses, if any */
727 ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
728 dmarc_send_forensic_report(ruf);
732 /* shut down libopendmarc */
734 (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
735 if (!f.dmarc_disable_verify)
736 (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
742 dmarc_exim_expand_defaults(int what)
744 if (what == DMARC_VERIFY_STATUS)
745 return f.dmarc_disable_verify ? US"off" : US"none";
750 dmarc_exim_expand_query(int what)
752 if (f.dmarc_disable_verify || !dmarc_pctx)
753 return dmarc_exim_expand_defaults(what);
755 if (what == DMARC_VERIFY_STATUS)
762 authres_dmarc(gstring * g)
764 if (f.dmarc_has_been_checked)
766 int start = 0; /* Compiler quietening */
767 DEBUG(D_acl) start = gstring_length(g);
768 g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
769 if (header_from_sender)
770 g = string_append(g, 2, US" header.from=", header_from_sender);
771 DEBUG(D_acl) debug_printf("DMARC:\tauthres '%.*s'\n",
772 gstring_length(g) - start - 3, g->s + start + 3);
775 DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
779 /******************************************************************************/
782 static optionlist dmarc_options[] = {
783 { "dmarc_forensic_sender", opt_stringptr, {&dmarc_forensic_sender} },
784 { "dmarc_history_file", opt_stringptr, {&dmarc_history_file} },
785 { "dmarc_tld_file", opt_stringptr, {&dmarc_tld_file} },
788 static void * dmarc_functions[] = {
789 [DMARC_PROCESS] = dmarc_process,
790 [DMARC_EXPAND_QUERY] = dmarc_exim_expand_query,
791 [DMARC_STORE_DATA] = dmarc_store_data,
794 /* dmarc_forensic_sender is provided for visibility of the the option setting
795 by moan_send_message. We do not document it as a config-visible $variable.
796 We could provide it via a function but there's little advantage. */
798 static var_entry dmarc_variables[] = {
799 { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy },
800 { "dmarc_forensic_sender", vtype_stringptr, &dmarc_forensic_sender },
801 { "dmarc_status", vtype_stringptr, &dmarc_status },
802 { "dmarc_status_text", vtype_stringptr, &dmarc_status_text },
803 { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain },
806 misc_module_info dmarc_module_info =
810 .dyn_magic = MISC_MODULE_MAGIC,
813 .lib_vers_report = dmarc_version_report,
814 .smtp_reset = dmarc_smtp_reset,
815 .msg_init = dmarc_msg_init,
816 .authres = authres_dmarc,
818 .options = dmarc_options,
819 .options_count = nelem(dmarc_options),
821 .functions = dmarc_functions,
822 .functions_count = nelem(dmarc_functions),
824 .variables = dmarc_variables,
825 .variables_count = nelem(dmarc_variables),
828 # endif /* SUPPORT_SPF */
829 #endif /* SUPPORT_DMARC */