1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 Copyright (c) The Exim Maintainers 2019 - 2023
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;
34 extern SPF_response_t *spf_response;
35 int dmarc_spf_ares_result = 0;
36 uschar *spf_sender_domain = NULL;
37 uschar *spf_human_readable = NULL;
38 u_char *header_from_sender = NULL;
39 int history_file_status = DMARC_HIST_OK;
41 typedef struct dmarc_exim_p {
46 static dmarc_exim_p dmarc_policy_description[] = {
48 { US"", DMARC_RECORD_P_UNSPECIFIED },
49 { US"none", DMARC_RECORD_P_NONE },
50 { US"quarantine", DMARC_RECORD_P_QUARANTINE },
51 { US"reject", DMARC_RECORD_P_REJECT },
57 dmarc_version_report(gstring * g)
59 return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
60 (OPENDMARC_LIB_VERSION & 0xff000000) >> 24, (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
61 (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8, OPENDMARC_LIB_VERSION & 0x000000ff);
65 /* Accept an error_block struct, initialize if empty, parse to the
66 end, and append the two strings passed to it. Used for adding
67 variable amounts of value:pair data to the forensic emails. */
70 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
72 error_block *eb = store_malloc(sizeof(error_block));
77 /* Find the end of the eblock struct and point it at eb */
78 error_block *tmp = eblock;
89 /* dmarc_init sets up a context that can be re-used for several
90 messages on the same SMTP connection (that come from the
91 same host with the same HELO string) */
96 int *netmask = NULL; /* Ignored */
99 /* Set some sane defaults. Also clears previous results when
100 multiple messages in one connection. */
103 dmarc_status = US"none";
105 dmarc_pass_fail = US"skipped";
106 dmarc_used_domain = US"";
107 f.dmarc_has_been_checked = FALSE;
108 header_from_sender = NULL;
109 spf_sender_domain = NULL;
110 spf_human_readable = NULL;
112 /* ACLs have "control=dmarc_disable_verify" */
113 if (f.dmarc_disable_verify == TRUE)
116 (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
117 dmarc_ctx.nscount = 0;
118 libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
119 if (libdm_status != DMARC_PARSE_OKAY)
121 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
122 opendmarc_policy_status_to_str(libdm_status));
125 if (!dmarc_tld_file || !*dmarc_tld_file)
127 DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
130 else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
132 log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
133 dmarc_tld_file, strerror(errno));
136 if (!sender_host_address)
138 DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
141 /* This catches locally originated email and startup errors above. */
144 is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
145 if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
147 log_write(0, LOG_MAIN|LOG_PANIC,
148 "DMARC failure creating policy context: ip=%s", sender_host_address);
157 /* dmarc_store_data stores the header data so that subsequent dmarc_process can
159 Called after the entire message has been received, with the From: header. */
162 dmarc_store_data(header_line * hdr)
164 /* No debug output because would change every test debug output */
165 if (!f.dmarc_disable_verify)
172 dmarc_send_forensic_report(u_char ** ruf)
174 uschar *recipient, *save_sender;
175 BOOL send_status = FALSE;
176 error_block *eblock = NULL;
177 FILE *message_file = NULL;
179 /* Earlier ACL does not have *required* control=dmarc_enable_forensic */
180 if (!f.dmarc_enable_forensic)
183 if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT
184 || dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE
185 || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_REJECT
186 || dmarc_policy == DMARC_POLICY_NONE && action == DMARC_RESULT_QUARANTINE
190 eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain);
191 eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
192 eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
193 eblock = add_to_eblock(eblock, US"SPF Alignment",
194 sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no");
195 eblock = add_to_eblock(eblock, US"DKIM Alignment",
196 da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no");
197 eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
199 for (int c = 0; ruf[c]; c++)
201 recipient = string_copylc(ruf[c]);
202 if (Ustrncmp(recipient, "mailto:",7))
204 /* Move to first character past the colon */
207 debug_printf_indent("DMARC forensic report to %s%s\n", recipient,
208 (host_checking || f.running_in_test_harness) ? " (not really)" : "");
209 if (host_checking || f.running_in_test_harness)
212 if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
213 header_list, message_file, NULL))
214 log_write(0, LOG_MAIN|LOG_PANIC,
215 "failure to send DMARC forensic report to %s", recipient);
221 /* Look up a DNS dmarc record for the given domain. Return it or NULL */
224 dmarc_dns_lookup(uschar * dom)
226 dns_answer * dnsa = store_get_dns_answer();
228 int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
230 if (rc == DNS_SUCCEED)
231 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
232 rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
233 if (rr->type == T_TXT && rr->size > 3)
235 uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
236 store_free_dns_answer(dnsa);
239 store_free_dns_answer(dnsa);
245 dmarc_write_history_file(const gstring * dkim_history_buffer)
247 int history_file_fd = 0;
250 u_char ** rua; /* aggregate report addressees */
253 if (!dmarc_history_file)
255 DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
256 return DMARC_HIST_DISABLED;
260 uschar * s = string_copy(dmarc_history_file); /* need a writeable copy */
261 if ((history_file_fd = log_open_as_exim(s)) < 0)
263 log_write(0, LOG_MAIN|LOG_PANIC,
264 "failure to create DMARC history file: %s: %s",
266 return DMARC_HIST_FILE_ERR;
270 /* Generate the contents of the history file entry */
272 g = string_fmt_append(NULL,
273 "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
274 message_id, primary_hostname, time(NULL), sender_host_address,
275 header_from_sender, expand_string(US"$sender_address_domain"));
278 g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
280 if (dkim_history_buffer)
281 g = string_fmt_append(g, "%Y", dkim_history_buffer);
283 g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
284 dmarc_used_domain, dmarc_policy);
286 if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
287 for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
288 g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]);
290 g = string_catn(g, US"rua -\n", 6);
292 opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
293 g = string_fmt_append(g, "pct %d\n", tmp_ans);
295 opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
296 g = string_fmt_append(g, "adkim %d\n", tmp_ans);
298 opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
299 g = string_fmt_append(g, "aspf %d\n", tmp_ans);
301 opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
302 g = string_fmt_append(g, "p %d\n", tmp_ans);
304 opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
305 g = string_fmt_append(g, "sp %d\n", tmp_ans);
307 g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
310 #if DMARC_API >= 100400
311 # ifdef EXPERIMENTAL_ARC
312 g = arc_dmarc_hist_append(g);
314 g = string_fmt_append(g, "arc %d\narc_policy $d json:[]\n",
315 ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
319 /* Write the contents to the history file */
322 debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
323 host_checking ? " (not really)" : "");
324 debug_printf_indent("DMARC history data for debugging:\n");
326 debug_printf_indent("%Y", g);
332 written_len = write_to_fd_buf(history_file_fd,
335 if (written_len == 0)
337 log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
339 return DMARC_HIST_WRITE_ERR;
341 (void)close(history_file_fd);
343 return DMARC_HIST_OK;
347 /* dmarc_process adds the envelope sender address to the existing
348 context (if any), retrieves the result, sets up expansion
349 strings and evaluates the condition outcome.
350 Called for the first ACL dmarc= condition. */
355 int sr, origin; /* used in SPF section */
356 int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */
358 pdkim_signature * sig = dkim_signatures;
360 BOOL has_dmarc_record = TRUE;
361 u_char ** ruf; /* forensic report addressees, if called for */
363 /* ACLs have "control=dmarc_disable_verify" */
364 if (f.dmarc_disable_verify)
367 /* Store the header From: sender domain for this part of DMARC.
368 If there is no from_header struct, then it's likely this message
369 is locally generated and relying on fixups to add it. Just skip
370 the entire DMARC system if we can't find a From: header....or if
371 there was a previous error. */
375 DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
378 else if (!dmarc_abort)
385 f.parse_allow_group = TRUE;
386 p = parse_find_address_end(from_header->text, FALSE);
387 saveend = *p; *p = '\0';
388 if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
389 &dummy, &dummy, &domain, FALSE)))
390 header_from_sender += domain;
393 /* The opendmarc library extracts the domain from the email address, but
394 only try to store it if it's not empty. Otherwise, skip out of DMARC. */
396 if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
398 libdm_status = dmarc_abort
400 : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
401 if (libdm_status != DMARC_PARSE_OKAY)
403 log_write(0, LOG_MAIN|LOG_PANIC,
404 "failure to store header From: in DMARC: %s, header was '%s'",
405 opendmarc_policy_status_to_str(libdm_status), from_header->text);
410 /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
411 instead do this in the ACLs. */
413 if (!dmarc_abort && !sender_host_authenticated)
415 uschar * dmarc_domain;
416 gstring * dkim_history_buffer = NULL;
418 /* Use the envelope sender domain for this part of DMARC */
420 spf_sender_domain = expand_string(US"$sender_address_domain");
423 /* No spf data means null envelope sender so generate a domain name
424 from the sender_helo_name */
426 if (!spf_sender_domain)
428 spf_sender_domain = sender_helo_name;
429 log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
432 debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
435 dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
436 dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
437 origin = DMARC_POLICY_SPF_ORIGIN_HELO;
438 spf_human_readable = US"";
442 sr = spf_response->result;
443 dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE :
444 sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS :
445 sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL :
446 sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
447 DMARC_POLICY_SPF_OUTCOME_NONE;
448 dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL :
449 sr == SPF_RESULT_PASS ? ARES_RESULT_PASS :
450 sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL :
451 sr == SPF_RESULT_SOFTFAIL ? ARES_RESULT_SOFTFAIL :
452 sr == SPF_RESULT_NONE ? ARES_RESULT_NONE :
453 sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
454 sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
456 origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
457 spf_human_readable = US spf_response->header_comment;
459 debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
461 if (strcmp( CCS spf_sender_domain, "") == 0)
465 libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
466 dmarc_spf_result, origin, spf_human_readable);
467 if (libdm_status != DMARC_PARSE_OKAY)
468 log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
469 opendmarc_policy_status_to_str(libdm_status));
472 /* Now we cycle through the dkim signature results and put into
473 the opendmarc context, further building the DMARC reply. */
475 for(pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
477 int dkim_result, dkim_ares_result, vs, ves;
479 vs = sig->verify_status & ~PDKIM_VERIFY_POLICY;
480 ves = sig->verify_ext_status;
481 dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
482 vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
483 vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
484 DMARC_POLICY_DKIM_OUTCOME_NONE;
485 libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
487 /* The opendmarc project broke its API in a way we can't detect easily.
488 The EDITME provides a DMARC_API variable */
489 #if DMARC_API >= 100400
494 debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
495 if (libdm_status != DMARC_PARSE_OKAY)
496 log_write(0, LOG_MAIN|LOG_PANIC,
497 "failure to store dkim (%s) for DMARC: %s",
498 sig->domain, opendmarc_policy_status_to_str(libdm_status));
501 vs == PDKIM_VERIFY_PASS ? ARES_RESULT_PASS :
502 vs == PDKIM_VERIFY_FAIL ? ARES_RESULT_FAIL :
503 vs == PDKIM_VERIFY_NONE ? ARES_RESULT_NONE :
504 vs == PDKIM_VERIFY_INVALID ?
505 ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
506 ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE ? ARES_RESULT_PERMERROR :
507 ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD ? ARES_RESULT_PERMERROR :
508 ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT ? ARES_RESULT_PERMERROR :
509 ARES_RESULT_UNKNOWN :
511 #if DMARC_API >= 100400
512 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
513 "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
515 dkim_history_buffer = string_fmt_append(dkim_history_buffer,
516 "dkim %s %d\n", sig->domain, dkim_ares_result);
520 /* Look up DMARC policy record in DNS. We do this explicitly, rather than
521 letting the dmarc library do it with opendmarc_policy_query_dmarc(), so that
522 our dns access path is used for debug tracing and for the testsuite
525 libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
526 ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
527 : DMARC_DNS_ERROR_NO_RECORD;
528 switch (libdm_status)
530 case DMARC_DNS_ERROR_NXDOMAIN:
531 case DMARC_DNS_ERROR_NO_RECORD:
533 debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
534 has_dmarc_record = FALSE;
536 case DMARC_PARSE_OKAY:
538 debug_printf_indent("DMARC record found for %s\n", header_from_sender);
540 case DMARC_PARSE_ERROR_BAD_VALUE:
542 debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
543 has_dmarc_record = FALSE;
546 /* everything else, skip dmarc */
548 debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
549 opendmarc_policy_status_to_str(libdm_status),
551 has_dmarc_record = FALSE;
555 /* Store the policy string in an expandable variable. */
557 libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
558 for (c = 0; dmarc_policy_description[c].name; c++)
559 if (tmp_ans == dmarc_policy_description[c].value)
561 dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
565 /* Can't use exim's string manipulation functions so allocate memory
566 for libopendmarc using its max hostname length definition. */
568 dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
569 libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
570 dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
571 store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
572 dmarc_used_domain = dmarc_domain;
574 if (libdm_status != DMARC_PARSE_OKAY)
575 log_write(0, LOG_MAIN|LOG_PANIC,
576 "failure to read domainname used for DMARC lookup: %s",
577 opendmarc_policy_status_to_str(libdm_status));
579 dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
582 case DMARC_POLICY_ABSENT: /* No DMARC record found */
583 dmarc_status = US"norecord";
584 dmarc_pass_fail = US"none";
585 dmarc_status_text = US"No DMARC record";
586 action = DMARC_RESULT_ACCEPT;
588 case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */
589 dmarc_status = US"nofrom";
590 dmarc_pass_fail = US"temperror";
591 dmarc_status_text = US"No From: domain found";
592 action = DMARC_RESULT_ACCEPT;
594 case DMARC_POLICY_NONE: /* Accept and report */
595 dmarc_status = US"none";
596 dmarc_pass_fail = US"none";
597 dmarc_status_text = US"None, Accept";
598 action = DMARC_RESULT_ACCEPT;
600 case DMARC_POLICY_PASS: /* Explicit accept */
601 dmarc_status = US"accept";
602 dmarc_pass_fail = US"pass";
603 dmarc_status_text = US"Accept";
604 action = DMARC_RESULT_ACCEPT;
606 case DMARC_POLICY_REJECT: /* Explicit reject */
607 dmarc_status = US"reject";
608 dmarc_pass_fail = US"fail";
609 dmarc_status_text = US"Reject";
610 action = DMARC_RESULT_REJECT;
612 case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */
613 dmarc_status = US"quarantine";
614 dmarc_pass_fail = US"fail";
615 dmarc_status_text = US"Quarantine";
616 action = DMARC_RESULT_QUARANTINE;
619 dmarc_status = US"temperror";
620 dmarc_pass_fail = US"temperror";
621 dmarc_status_text = US"Internal Policy Error";
622 action = DMARC_RESULT_TEMPFAIL;
626 libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
627 if (libdm_status != DMARC_PARSE_OKAY)
628 log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
629 opendmarc_policy_status_to_str(libdm_status));
631 if (has_dmarc_record)
633 log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
634 "spf_align=%s dkim_align=%s enforcement='%s'",
635 spf_sender_domain, dmarc_used_domain,
636 sa==DMARC_POLICY_SPF_ALIGNMENT_PASS ?"yes":"no",
637 da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
639 history_file_status = dmarc_write_history_file(dkim_history_buffer);
640 /* Now get the forensic reporting addresses, if any */
641 ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
642 dmarc_send_forensic_report(ruf);
646 /* shut down libopendmarc */
648 (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
649 if (!f.dmarc_disable_verify)
650 (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
656 dmarc_exim_expand_query(int what)
658 if (f.dmarc_disable_verify || !dmarc_pctx)
659 return dmarc_exim_expand_defaults(what);
661 if (what == DMARC_VERIFY_STATUS)
667 dmarc_exim_expand_defaults(int what)
669 if (what == DMARC_VERIFY_STATUS)
670 return f.dmarc_disable_verify ? US"off" : US"none";
676 authres_dmarc(gstring * g)
678 if (f.dmarc_has_been_checked)
680 int start = 0; /* Compiler quietening */
681 DEBUG(D_acl) start = gstring_length(g);
682 g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
683 if (header_from_sender)
684 g = string_append(g, 2, US" header.from=", header_from_sender);
685 DEBUG(D_acl) debug_printf("DMARC:\tauthres '%.*s'\n",
686 gstring_length(g) - start - 3, g->s + start + 3);
689 DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
693 # endif /* SUPPORT_SPF */
694 #endif /* SUPPORT_DMARC */