28fce06244f889c5c9518361ee884baaa1c875b5
[exim.git] / src / src / dmarc.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4 /* DMARC support.
5    Copyright (c) The Exim Maintainers 2019 - 2024
6    Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014
7    License: GPL */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
11    All rights reserved, licensed for use per LICENSE.opendmarc. */
12
13 /* Code for calling dmarc checks via libopendmarc. Called from acl.c. */
14
15 #include "exim.h"
16 #ifdef SUPPORT_DMARC
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
21 # else
22
23 #  include "functions.h"
24 #  include "dmarc.h"
25 #  include "pdkim/pdkim.h"
26
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;
40
41 typedef struct dmarc_exim_p {
42   uschar *name;
43   int    value;
44 } dmarc_exim_p;
45
46 static dmarc_exim_p dmarc_policy_description[] = {
47   /* name               value */
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 },
52   { NULL,           0 }
53 };
54
55
56 gstring *
57 dmarc_version_report(gstring * g)
58 {
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);
62 }
63
64
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. */
68
69 static error_block *
70 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
71 {
72 error_block *eb = store_malloc(sizeof(error_block));
73 if (!eblock)
74   eblock = eb;
75 else
76   {
77   /* Find the end of the eblock struct and point it at eb */
78   error_block *tmp = eblock;
79   while(tmp->next)
80     tmp = tmp->next;
81   tmp->next = eb;
82   }
83 eb->text1 = t1;
84 eb->text2 = t2;
85 eb->next  = NULL;
86 return eblock;
87 }
88
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) */
92
93 int
94 dmarc_init(void)
95 {
96 int *netmask   = NULL;   /* Ignored */
97 int is_ipv6    = 0;
98
99 /* Set some sane defaults.  Also clears previous results when
100 multiple messages in one connection. */
101
102 dmarc_pctx         = NULL;
103 dmarc_status       = US"none";
104 dmarc_abort        = FALSE;
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;
111
112 /* ACLs have "control=dmarc_disable_verify" */
113 if (f.dmarc_disable_verify == TRUE)
114   return OK;
115
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)
120   {
121   log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
122                        opendmarc_policy_status_to_str(libdm_status));
123   dmarc_abort = TRUE;
124   }
125 if (!dmarc_tld_file || !*dmarc_tld_file)
126   {
127   DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
128   dmarc_abort = TRUE;
129   }
130 else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
131   {
132   log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
133                        dmarc_tld_file, strerror(errno));
134   dmarc_abort = TRUE;
135   }
136 if (!sender_host_address)
137   {
138   DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
139   dmarc_abort = TRUE;
140   }
141 /* This catches locally originated email and startup errors above. */
142 if (!dmarc_abort)
143   {
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)))
146     {
147     log_write(0, LOG_MAIN|LOG_PANIC,
148       "DMARC failure creating policy context: ip=%s", sender_host_address);
149     dmarc_abort = TRUE;
150     }
151   }
152
153 return OK;
154 }
155
156
157 /* dmarc_store_data stores the header data so that subsequent dmarc_process can
158 access the data.
159 Called after the entire message has been received, with the From: header. */
160
161 int
162 dmarc_store_data(header_line * hdr)
163 {
164 /* No debug output because would change every test debug output */
165 if (!f.dmarc_disable_verify)
166   from_header = hdr;
167 return OK;
168 }
169
170
171 static void
172 dmarc_send_forensic_report(u_char ** ruf)
173 {
174 uschar *recipient, *save_sender;
175 BOOL  send_status = FALSE;
176 error_block *eblock = NULL;
177 FILE *message_file = NULL;
178
179 /* Earlier ACL does not have *required* control=dmarc_enable_forensic */
180 if (!f.dmarc_enable_forensic)
181   return;
182
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
187    )
188   if (ruf)
189     {
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);
198
199     for (int c = 0; ruf[c]; c++)
200       {
201       recipient = string_copylc(ruf[c]);
202       if (Ustrncmp(recipient, "mailto:",7))
203         continue;
204       /* Move to first character past the colon */
205       recipient += 7;
206       DEBUG(D_receive)
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)
210         continue;
211
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);
216       }
217     }
218 }
219
220
221 /* Look up a DNS dmarc record for the given domain.  Return it or NULL */
222
223 static uschar *
224 dmarc_dns_lookup(uschar * dom)
225 {
226 dns_answer * dnsa = store_get_dns_answer();
227 dns_scan dnss;
228 int rc = dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL);
229
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)
234       {
235       uschar *record = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
236       store_free_dns_answer(dnsa);
237       return record;
238       }
239 store_free_dns_answer(dnsa);
240 return NULL;
241 }
242
243
244 static int
245 dmarc_write_history_file(const gstring * dkim_history_buffer)
246 {
247 int history_file_fd = 0;
248 ssize_t written_len;
249 int tmp_ans;
250 u_char ** rua; /* aggregate report addressees */
251 gstring * g;
252
253 if (!dmarc_history_file)
254   {
255   DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
256   return DMARC_HIST_DISABLED;
257   }
258 if (!host_checking)
259   {
260   uschar * s = string_copy(dmarc_history_file);         /* need a writeable copy */
261   if ((history_file_fd = log_open_as_exim(s)) < 0)
262     {
263     log_write(0, LOG_MAIN|LOG_PANIC,
264               "failure to create DMARC history file: %s: %s",
265               s, strerror(errno));
266     return DMARC_HIST_FILE_ERR;
267     }
268   }
269
270 /* Generate the contents of the history file entry */
271
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"));
276
277 if (spf_response)
278   g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
279
280 if (dkim_history_buffer)
281   g = string_fmt_append(g, "%Y", dkim_history_buffer);
282
283 g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
284   dmarc_used_domain, dmarc_policy);
285
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]);
289 else
290   g = string_catn(g, US"rua -\n", 6);
291
292 opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
293 g = string_fmt_append(g, "pct %d\n", tmp_ans);
294
295 opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
296 g = string_fmt_append(g, "adkim %d\n", tmp_ans);
297
298 opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
299 g = string_fmt_append(g, "aspf %d\n", tmp_ans);
300
301 opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
302 g = string_fmt_append(g, "p %d\n", tmp_ans);
303
304 opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
305 g = string_fmt_append(g, "sp %d\n", tmp_ans);
306
307 g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
308   da, sa, action);
309
310 #if DMARC_API >= 100400
311 # ifdef EXPERIMENTAL_ARC
312 g = arc_dmarc_hist_append(g);
313 # else
314 g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
315                       ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
316 # endif
317 #endif
318
319 /* Write the contents to the history file */
320 DEBUG(D_receive)
321   {
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");
325   expand_level++;
326     debug_printf_indent("%Y", g);
327   expand_level--;
328   }
329
330 if (!host_checking)
331   {
332   written_len = write_to_fd_buf(history_file_fd,
333                                 g->s,
334                                 gstring_length(g));
335   if (written_len == 0)
336     {
337     log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
338                            dmarc_history_file);
339     return DMARC_HIST_WRITE_ERR;
340     }
341   (void)close(history_file_fd);
342   }
343 return DMARC_HIST_OK;
344 }
345
346
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. */
351
352 int
353 dmarc_process(void)
354 {
355 int sr, origin;             /* used in SPF section */
356 int dmarc_spf_result  = 0;  /* stores spf into dmarc conn ctx */
357 int tmp_ans, c;
358 pdkim_signature * sig = dkim_signatures;
359 uschar * rr;
360 BOOL has_dmarc_record = TRUE;
361 u_char ** ruf; /* forensic report addressees, if called for */
362
363 /* ACLs have "control=dmarc_disable_verify" */
364 if (f.dmarc_disable_verify)
365   return OK;
366
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.  */
372
373 if (!from_header)
374   {
375   DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
376   dmarc_abort = TRUE;
377   }
378 else if (!dmarc_abort)
379   {
380   uschar * errormsg;
381   int dummy, domain;
382   uschar * p;
383   uschar saveend;
384
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;
391   *p = saveend;
392
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. */
395
396   if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
397     dmarc_abort = TRUE;
398   libdm_status = dmarc_abort
399     ? DMARC_PARSE_OKAY
400     : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
401   if (libdm_status != DMARC_PARSE_OKAY)
402     {
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);
406     dmarc_abort = TRUE;
407     }
408   }
409
410 /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
411 instead do this in the ACLs.  */
412
413 if (!dmarc_abort && !sender_host_authenticated)
414   {
415   uschar * dmarc_domain;
416   gstring * dkim_history_buffer = NULL;
417
418   /* Use the envelope sender domain for this part of DMARC */
419
420   spf_sender_domain = expand_string(US"$sender_address_domain");
421   if (!spf_response)
422     {
423     /* No spf data means null envelope sender so generate a domain name
424     from the sender_helo_name  */
425
426     if (!spf_sender_domain)
427       {
428       spf_sender_domain = sender_helo_name;
429       log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
430                              spf_sender_domain);
431       DEBUG(D_receive)
432         debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
433           spf_sender_domain);
434       }
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"";
439     }
440   else
441     {
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 :
455                             ARES_RESULT_UNKNOWN;
456     origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
457     spf_human_readable = US spf_response->header_comment;
458     DEBUG(D_receive)
459       debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
460     }
461   if (strcmp( CCS spf_sender_domain, "") == 0)
462     dmarc_abort = TRUE;
463   if (!dmarc_abort)
464     {
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));
470     }
471
472   /* Now we cycle through the dkim signature results and put into
473   the opendmarc context, further building the DMARC reply. */
474
475   for(pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
476     {
477     int dkim_result, dkim_ares_result, vs, ves;
478
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,
486
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
490                                                sig->selector,
491 #endif
492                                                dkim_result, US"");
493     DEBUG(D_receive)
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));
499
500     dkim_ares_result =
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 :
510       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);
514 #else
515     dkim_history_buffer = string_fmt_append(dkim_history_buffer,
516       "dkim %s %d\n", sig->domain, dkim_ares_result);
517 #endif
518     }
519
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
523   diversion. */
524
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)
529     {
530     case DMARC_DNS_ERROR_NXDOMAIN:
531     case DMARC_DNS_ERROR_NO_RECORD:
532       DEBUG(D_receive)
533         debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
534       has_dmarc_record = FALSE;
535       break;
536     case DMARC_PARSE_OKAY:
537       DEBUG(D_receive)
538         debug_printf_indent("DMARC record found for %s\n", header_from_sender);
539       break;
540     case DMARC_PARSE_ERROR_BAD_VALUE:
541       DEBUG(D_receive)
542         debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
543       has_dmarc_record = FALSE;
544       break;
545     default:
546       /* everything else, skip dmarc */
547       DEBUG(D_receive)
548         debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
549                       opendmarc_policy_status_to_str(libdm_status),
550                       from_header->text);
551       has_dmarc_record = FALSE;
552       break;
553     }
554
555   /* Store the policy string in an expandable variable. */
556
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)
560       {
561       dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
562       break;
563       }
564
565   /* Can't use exim's string manipulation functions so allocate memory
566   for libopendmarc using its max hostname length definition. */
567
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;
573
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));
578
579   dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
580   switch(libdm_status)
581     {
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;
587       break;
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;
593       break;
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;
599       break;
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;
605       break;
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;
611       break;
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;
617       break;
618     default:
619       dmarc_status = US"temperror";
620       dmarc_pass_fail = US"temperror";
621       dmarc_status_text = US"Internal Policy Error";
622       action = DMARC_RESULT_TEMPFAIL;
623       break;
624     }
625
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));
630
631   if (has_dmarc_record)
632     {
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",
638                            dmarc_status_text);
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);
643     }
644   }
645
646 /* shut down libopendmarc */
647 if (dmarc_pctx)
648   (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
649 if (!f.dmarc_disable_verify)
650   (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
651
652 return OK;
653 }
654
655 uschar *
656 dmarc_exim_expand_query(int what)
657 {
658 if (f.dmarc_disable_verify || !dmarc_pctx)
659   return dmarc_exim_expand_defaults(what);
660
661 if (what == DMARC_VERIFY_STATUS)
662   return dmarc_status;
663 return US"";
664 }
665
666 uschar *
667 dmarc_exim_expand_defaults(int what)
668 {
669 if (what == DMARC_VERIFY_STATUS)
670   return f.dmarc_disable_verify ?  US"off" : US"none";
671 return US"";
672 }
673
674
675 gstring *
676 authres_dmarc(gstring * g)
677 {
678 if (f.dmarc_has_been_checked)
679   {
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);
687   }
688 else
689   DEBUG(D_acl) debug_printf("DMARC:\tno authres\n");
690 return g;
691 }
692
693 # endif /* SUPPORT_SPF */
694 #endif /* SUPPORT_DMARC */
695 /* vi: aw ai sw=2
696  */