DMARC support by opendmarc libs
[exim.git] / src / src / dmarc.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4 /* Experimental DMARC support.
5    Copyright (c) Todd Lyons <tlyons@exim.org> 2012, 2013
6    License: GPL */
7
8 /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
9    All rights reserved, licensed for use per LICENSE.opendmarc. */
10
11 /* Code for calling dmarc checks via libopendmarc. Called from acl.c. */
12
13 #include "exim.h"
14 #ifdef EXPERIMENTAL_DMARC
15
16 #include "functions.h"
17 #include "dmarc.h"
18 #include "pdkim/pdkim.h"
19
20 OPENDMARC_LIB_T     dmarc_ctx;
21 DMARC_POLICY_T     *dmarc_pctx = NULL;
22 OPENDMARC_STATUS_T  libdm_status, action, dmarc_policy;
23 OPENDMARC_STATUS_T  da, sa, action;
24 BOOL dmarc_abort  = FALSE;
25 uschar *dmarc_pass_fail = US"skipped";
26 extern pdkim_signature  *dkim_signatures;
27 header_line *from_header   = NULL;
28 #ifdef EXPERIMENTAL_SPF
29 extern SPF_response_t   *spf_response;
30 int    dmarc_spf_result     = 0;
31 uschar *spf_sender_domain  = NULL;
32 uschar *spf_human_readable = NULL;
33 #endif
34 u_char *header_from_sender = NULL;
35 int history_file_status    = DMARC_HIST_OK;
36 uschar *history_buffer     = NULL;
37 uschar *dkim_history_buffer= NULL;
38
39 /* Accept an error_block struct, initialize if empty, parse to the
40  * end, and append the two strings passed to it.  Used for adding
41  * variable amounts of value:pair data to the forensic emails. */
42
43 static error_block *
44 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
45 {
46   error_block *eb = malloc(sizeof(error_block));
47   if (eblock == NULL)
48     eblock = eb;
49   else
50   {
51     /* Find the end of the eblock struct and point it at eb */
52     error_block *tmp = eblock;
53     while(tmp->next != NULL)
54       tmp = tmp->next;
55     tmp->next = eb;
56   }
57   eb->text1 = t1;
58   eb->text2 = t2;
59   eb->next  = NULL;
60   return eblock;
61 }
62
63 /* dmarc_init sets up a context that can be re-used for several
64    messages on the same SMTP connection (that come from the
65    same host with the same HELO string) */
66
67 int dmarc_init() {
68   int *netmask   = NULL;   /* Ignored */
69   int is_ipv6    = 0;
70   char *tld_file = (dmarc_tld_file == NULL) ?
71                    "/etc/exim/opendmarc.tlds" :
72                    (char *)dmarc_tld_file;
73
74   /* Set some sane defaults.  Also clears previous results when
75    * multiple messages in one connection. */
76   dmarc_pctx         = NULL;
77   dmarc_status       = US"none";
78   dmarc_abort        = FALSE;
79   dmarc_pass_fail    = US"skipped";
80   dmarc_used_domain  = US"";
81   header_from_sender = NULL;
82 #ifdef EXPERIMENTAL_SPF
83   spf_sender_domain  = NULL;
84   spf_human_readable = NULL;
85 #endif
86
87   /* ACLs have "control=dmarc_disable_verify" */
88   if (dmarc_disable_verify == TRUE)
89     return OK;
90
91   (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
92   dmarc_ctx.nscount = 0;
93   libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
94   if (libdm_status != DMARC_PARSE_OKAY)
95   {
96     log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
97                          opendmarc_policy_status_to_str(libdm_status));
98     dmarc_abort = TRUE;
99   }
100   if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL))
101   {
102     log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d",
103                          tld_file, errno);
104     dmarc_abort = TRUE;
105   }
106   if (sender_host_address == NULL)
107     dmarc_abort = TRUE;
108   /* This catches locally originated email and startup errors above. */
109   if ( dmarc_abort == FALSE )
110   {
111     is_ipv6 = string_is_ip_address(sender_host_address, netmask);
112     is_ipv6 = (is_ipv6 == 6) ? TRUE :
113               (is_ipv6 == 4) ? FALSE : FALSE;
114     dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6);
115     if (dmarc_pctx == NULL )
116     {
117       log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure creating policy context: ip=%s",
118                                        sender_host_address);
119       dmarc_abort = TRUE;
120     }
121   }
122
123   return OK;
124 }
125
126
127 /* dmarc_store_data stores the header data so that subsequent
128  * dmarc_process can access the data */
129
130 int dmarc_store_data(header_line *hdr) {
131   /* No debug output because would change every test debug output */
132   if (dmarc_disable_verify != TRUE)
133     from_header = hdr;
134   return OK;
135 }
136
137
138 /* dmarc_process adds the envelope sender address to the existing
139    context (if any), retrieves the result, sets up expansion
140    strings and evaluates the condition outcome. */
141
142 int dmarc_process() {
143     int sr, origin; /* used in SPF section */
144     pdkim_signature *sig  = NULL;
145     BOOL has_dmarc_record = TRUE;
146     u_char **ruf; /* forensic report addressees, if called for */
147
148   /* ACLs have "control=dmarc_disable_verify" */
149   if (dmarc_disable_verify == TRUE)
150   {
151     dmarc_ar_header = dmarc_auth_results_header(from_header, NULL);
152     return OK;
153   }
154
155   /* Store the header From: sender domain for this part of DMARC.
156    * If there is no from_header struct, then it's likely this message
157    * is locally generated and relying on fixups to add it.  Just skip
158    * the entire DMARC system if we can't find a From: header....or if
159    * there was a previous error.
160    */
161   if (from_header == NULL || dmarc_abort == TRUE)
162     dmarc_abort = TRUE;
163   else
164   {
165     /* I strongly encourage anybody who can make this better to contact me directly!
166      * <cannonball> Is this an insane way to extract the email address from the From: header?
167      * <jgh_hm> it's sure a horrid layer-crossing....
168      * <cannonball> I'm not denying that :-/
169      * <jgh_hm> there may well be no better though
170      */
171     header_from_sender = expand_string(
172                            string_sprintf("${domain:${extract{1}{:}{${addresses:%s}}}}",
173                              from_header->text) );
174     /* The opendmarc library extracts the domain from the email address, but
175      * only try to store it if it's not empty.  Otherwise, skip out of DMARC. */
176     if (strcmp( CCS header_from_sender, "") == 0)
177       dmarc_abort = TRUE;
178     libdm_status = (dmarc_abort == TRUE) ?
179                    DMARC_PARSE_OKAY :
180                    opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
181     if (libdm_status != DMARC_PARSE_OKAY)
182     {
183       log_write(0, LOG_MAIN|LOG_PANIC, "failure to store header From: in DMARC: %s, header was '%s'",
184                            opendmarc_policy_status_to_str(libdm_status), from_header->text);
185       dmarc_abort = TRUE;
186     }
187   }
188
189   /* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
190    * instead do this in the ACLs.  */
191   if (dmarc_abort == FALSE && sender_host_authenticated == NULL)
192   {
193 #ifdef EXPERIMENTAL_SPF
194     /* Use the envelope sender domain for this part of DMARC */
195     spf_sender_domain = expand_string(US"$sender_address_domain");
196     if ( spf_response == NULL )
197     {
198       /* No spf data means null envelope sender so generate a domain name
199        * from the sender_host_name || sender_helo_name  */
200       if (spf_sender_domain == NULL)
201       {
202         spf_sender_domain = (sender_host_name == NULL) ? sender_helo_name : sender_host_name;
203         uschar *subdomain = spf_sender_domain;
204         int count = 0;
205         while (subdomain && *subdomain != '.')
206         {
207           subdomain++;
208           count++;
209         }
210         /* If parsed characters in temp var "subdomain" and is pointing to
211          * a period now, get rid of the period and use that.  Otherwise
212          * will use whatever was first set in spf_sender_domain.  Goal is to
213          * generate a sane answer, not necessarily the right/best answer b/c
214          * at this point with a null sender, it's a bounce message, making
215          * the spf domain be subjective.  */
216         if (count > 0 && *subdomain == '.')
217         {
218           subdomain++;
219           spf_sender_domain = subdomain;
220         }
221         log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
222                                spf_sender_domain);
223         DEBUG(D_receive)
224           debug_printf("DMARC using synthesized SPF sender domain = %s\n", spf_sender_domain);
225       }
226       dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
227       origin = DMARC_POLICY_SPF_ORIGIN_HELO;
228       spf_human_readable = US"";
229     }
230     else
231     {
232       sr = spf_response->result;
233       dmarc_spf_result = (sr == SPF_RESULT_NEUTRAL)  ? DMARC_POLICY_SPF_OUTCOME_NONE :
234                          (sr == SPF_RESULT_PASS)     ? DMARC_POLICY_SPF_OUTCOME_PASS :
235                          (sr == SPF_RESULT_FAIL)     ? DMARC_POLICY_SPF_OUTCOME_FAIL :
236                          (sr == SPF_RESULT_SOFTFAIL) ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
237                          DMARC_POLICY_SPF_OUTCOME_NONE;
238       origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
239       spf_human_readable = (uschar *)spf_response->header_comment;
240       DEBUG(D_receive)
241         debug_printf("DMARC using SPF sender domain = %s\n", spf_sender_domain);
242     }
243     if (strcmp( CCS spf_sender_domain, "") == 0)
244       dmarc_abort = TRUE;
245     if (dmarc_abort == FALSE)
246     {
247       libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
248                                                 dmarc_spf_result, origin, spf_human_readable);
249       if (libdm_status != DMARC_PARSE_OKAY)
250         log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
251                              opendmarc_policy_status_to_str(libdm_status));
252     }
253 #endif /* EXPERIMENTAL_SPF */
254
255     /* Now we cycle through the dkim signature results and put into
256      * the opendmarc context, further building the DMARC reply.  */
257     sig = dkim_signatures;
258     dkim_history_buffer = US"";
259     while (sig != NULL)
260     {
261       int dkim_result, vs;
262       vs = sig->verify_status;
263       dkim_result = ( vs == PDKIM_VERIFY_PASS ) ? DMARC_POLICY_DKIM_OUTCOME_PASS :
264                     ( vs == PDKIM_VERIFY_FAIL ) ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
265                     ( vs == PDKIM_VERIFY_INVALID ) ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
266                     DMARC_POLICY_DKIM_OUTCOME_NONE;
267       libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, (uschar *)sig->domain,
268                                                  dkim_result, US"");
269       DEBUG(D_receive)
270         debug_printf("DMARC adding DKIM sender domain = %s\n", sig->domain);
271       if (libdm_status != DMARC_PARSE_OKAY)
272         log_write(0, LOG_MAIN|LOG_PANIC, "failure to store dkim (%s) for DMARC: %s",
273                              sig->domain, opendmarc_policy_status_to_str(libdm_status));
274
275       dkim_history_buffer = string_sprintf("%sdkim %s %d\n", dkim_history_buffer,
276                                                              sig->domain, dkim_result);
277       sig = sig->next;
278     }
279     libdm_status = opendmarc_policy_query_dmarc(dmarc_pctx, US"");
280     switch (libdm_status)
281     {
282       case DMARC_DNS_ERROR_NXDOMAIN:
283       case DMARC_DNS_ERROR_NO_RECORD:
284         DEBUG(D_receive)
285           debug_printf("DMARC no record found for %s\n", header_from_sender);
286         has_dmarc_record = FALSE;
287         break;
288       case DMARC_PARSE_OKAY:
289         DEBUG(D_receive)
290           debug_printf("DMARC record found for %s\n", header_from_sender);
291         break;
292       case DMARC_PARSE_ERROR_BAD_VALUE:
293         DEBUG(D_receive)
294           debug_printf("DMARC record parse error for %s\n", header_from_sender);
295         has_dmarc_record = FALSE;
296         break;
297       default:
298         /* everything else, skip dmarc */
299         DEBUG(D_receive)
300           debug_printf("DMARC skipping (%d), unsure what to do with %s",
301                         libdm_status, from_header->text);
302         has_dmarc_record = FALSE;
303         break;
304     }
305     /* Can't use exim's string manipulation functions so allocate memory
306      * for libopendmarc using its max hostname length definition. */
307     uschar *dmarc_domain = (uschar *)calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar));
308     libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx, dmarc_domain,
309                                                           DMARC_MAXHOSTNAMELEN-1);
310     dmarc_used_domain = string_copy(dmarc_domain);
311     free(dmarc_domain);
312     if (libdm_status != DMARC_PARSE_OKAY)
313     {
314       log_write(0, LOG_MAIN|LOG_PANIC, "failure to read domainname used for DMARC lookup: %s",
315                                        opendmarc_policy_status_to_str(libdm_status));
316     }
317     libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
318     dmarc_policy = libdm_status;
319     switch(libdm_status)
320     {
321       case DMARC_POLICY_ABSENT:     /* No DMARC record found */
322         dmarc_status = US"norecord";
323         dmarc_pass_fail = US"temperror";
324         dmarc_status_text = US"No DMARC record";
325         action = DMARC_RESULT_ACCEPT;
326         break;
327       case DMARC_FROM_DOMAIN_ABSENT:    /* No From: domain */
328         dmarc_status = US"nofrom";
329         dmarc_pass_fail = US"temperror";
330         dmarc_status_text = US"No From: domain found";
331         action = DMARC_RESULT_ACCEPT;
332         break;
333       case DMARC_POLICY_NONE:       /* Accept and report */
334         dmarc_status = US"none";
335         dmarc_pass_fail = US"none";
336         dmarc_status_text = US"None, Accept";
337         action = DMARC_RESULT_ACCEPT;
338         break;
339       case DMARC_POLICY_PASS:       /* Explicit accept */
340         dmarc_status = US"accept";
341         dmarc_pass_fail = US"pass";
342         dmarc_status_text = US"Accept";
343         action = DMARC_RESULT_ACCEPT;
344         break;
345       case DMARC_POLICY_REJECT:       /* Explicit reject */
346         dmarc_status = US"reject";
347         dmarc_pass_fail = US"fail";
348         dmarc_status_text = US"Reject";
349         action = DMARC_RESULT_REJECT;
350         break;
351       case DMARC_POLICY_QUARANTINE:       /* Explicit quarantine */
352         dmarc_status = US"quarantine";
353         dmarc_pass_fail = US"fail";
354         dmarc_status_text = US"Quarantine";
355         action = DMARC_RESULT_QUARANTINE;
356         break;
357       default:
358         dmarc_status = US"temperror";
359         dmarc_pass_fail = US"temperror";
360         dmarc_status_text = US"Internal Policy Error";
361         action = DMARC_RESULT_TEMPFAIL;
362         break;
363     }
364
365     libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
366     if (libdm_status != DMARC_PARSE_OKAY)
367     {
368       log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
369                                        opendmarc_policy_status_to_str(libdm_status));
370     }
371
372     if (has_dmarc_record == TRUE)
373     {
374       log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
375                              "spf_align=%s dkim_align=%s enforcement='%s'",
376                              spf_sender_domain, dmarc_used_domain,
377                              (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?"yes":"no",
378                              (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?"yes":"no",
379                              dmarc_status_text);
380       history_file_status = dmarc_write_history_file();
381       /* Now get the forensic reporting addresses, if any */
382       ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
383       dmarc_send_forensic_report(ruf);
384     }
385   }
386
387   /* set some global variables here */
388   dmarc_ar_header = dmarc_auth_results_header(from_header, NULL);
389
390   /* shut down libopendmarc */
391   if ( dmarc_pctx != NULL )
392     (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
393   if ( dmarc_disable_verify == FALSE )
394     (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
395
396   return OK;
397 }
398
399 int dmarc_write_history_file()
400 {
401   static int history_file_fd;
402   ssize_t written_len;
403   int tmp_ans;
404   u_char **rua; /* aggregate report addressees */
405
406   if (dmarc_history_file == NULL)
407     return DMARC_HIST_DISABLED;
408   history_file_fd = log_create(dmarc_history_file);
409
410   if (history_file_fd < 0)
411   {
412     log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
413                              dmarc_history_file);
414     return DMARC_HIST_FILE_ERR;
415   }
416
417   /* Generate the contents of the history file */
418   history_buffer = string_sprintf("job %s\n", message_id);
419   history_buffer = string_sprintf("%sreporter %s\n", history_buffer, primary_hostname);
420   history_buffer = string_sprintf("%sreceived %ld\n", history_buffer, time(NULL));
421   history_buffer = string_sprintf("%sipaddr %s\n", history_buffer, sender_host_address);
422   history_buffer = string_sprintf("%sfrom %s\n", history_buffer, header_from_sender);
423   history_buffer = string_sprintf("%smfrom %s\n", history_buffer,
424                      expand_string(US"$sender_address_domain"));
425
426 #ifdef EXPERIMENTAL_SPF
427   if (spf_response != NULL)
428     history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_result);
429 #else
430     history_buffer = string_sprintf("%sspf -1\n", history_buffer);
431 #endif /* EXPERIMENTAL_SPF */
432
433   history_buffer = string_sprintf("%s%s", history_buffer, dkim_history_buffer);
434   history_buffer = string_sprintf("%spdomain %s\n", history_buffer, dmarc_used_domain);
435   history_buffer = string_sprintf("%spolicy %d\n", history_buffer, dmarc_policy);
436
437   rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1);
438   if (rua != NULL)
439   {
440     for (tmp_ans = 0; rua[tmp_ans] != NULL; tmp_ans++)
441     {
442       history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]);
443     }
444   }
445   else
446     history_buffer = string_sprintf("%srua -\n", history_buffer);
447
448   opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
449   history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans);
450
451   opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
452   history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans);
453
454   opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
455   history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans);
456
457   opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
458   history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans);
459
460   opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
461   history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans);
462
463   history_buffer = string_sprintf("%salign_dkim %d\n", history_buffer, da);
464   history_buffer = string_sprintf("%salign_spf %d\n", history_buffer, sa);
465   history_buffer = string_sprintf("%saction %d\n", history_buffer, action);
466
467   /* Write the contents to the history file */
468   DEBUG(D_receive)
469     debug_printf("DMARC logging history data for opendmarc reporting%s\n",
470                  (host_checking || running_in_test_harness) ? " (not really)" : "");
471   if (host_checking || running_in_test_harness)
472   {
473     DEBUG(D_receive)
474       debug_printf("DMARC history data for debugging:\n%s", history_buffer);
475   }
476   else
477   {
478     written_len = write_to_fd_buf(history_file_fd,
479                                   history_buffer,
480                                   Ustrlen(history_buffer));
481     if (written_len == 0)
482     {
483       log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
484                              dmarc_history_file);
485       return DMARC_HIST_WRITE_ERR;
486     }
487     (void)close(history_file_fd);
488   }
489   return DMARC_HIST_OK;
490 }
491
492 void dmarc_send_forensic_report(u_char **ruf)
493 {
494   int   c;
495   uschar *recipient, *save_sender;
496   BOOL  send_status = FALSE;
497   error_block *eblock = NULL;
498   FILE *message_file = NULL;
499
500   /* Earlier ACL does not have *required* control=dmarc_enable_forensic */
501   if (dmarc_enable_forensic == FALSE)
502     return;
503
504   if ((dmarc_policy == DMARC_POLICY_REJECT     && action == DMARC_RESULT_REJECT) ||
505       (dmarc_policy == DMARC_POLICY_QUARANTINE && action == DMARC_RESULT_QUARANTINE) )
506   {
507     if (ruf != NULL)
508     {
509       eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_used_domain);
510       eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
511       eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
512       eblock = add_to_eblock(eblock, US"SPF Alignment",
513                              (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?US"yes":US"no");
514       eblock = add_to_eblock(eblock, US"DKIM Alignment",
515                              (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?US"yes":US"no");
516       eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
517       /* Set a sane default envelope sender */
518       dsn_from = dmarc_forensic_sender ? dmarc_forensic_sender :
519                  dsn_from ? dsn_from :
520                  string_sprintf("do-not-reply@%s",primary_hostname);
521       for (c = 0; ruf[c] != NULL; c++)
522       {
523         recipient = string_copylc(ruf[c]);
524         if (Ustrncmp(recipient, "mailto:",7))
525         continue;
526         /* Move to first character past the colon */
527         recipient += 7;
528         DEBUG(D_receive)
529           debug_printf("DMARC forensic report to %s%s\n", recipient,
530                        (host_checking || running_in_test_harness) ? " (not really)" : "");
531         if (host_checking || running_in_test_harness)
532           continue;
533         save_sender = sender_address;
534         sender_address = recipient;
535         send_status = moan_to_sender(ERRMESS_DMARC_FORENSIC, eblock,
536                                      header_list, message_file, FALSE);
537         sender_address = save_sender;
538         if (send_status == FALSE)
539           log_write(0, LOG_MAIN|LOG_PANIC, "failure to send DMARC forensic report to %s",
540                     recipient);
541       }
542     }
543   }
544 }
545
546 uschar *dmarc_exim_expand_query(int what)
547 {
548   if (dmarc_disable_verify || !dmarc_pctx)
549     return dmarc_exim_expand_defaults(what);
550
551   switch(what) {
552     case DMARC_VERIFY_STATUS:
553       return(dmarc_status);
554     default:
555       return US"";
556   }
557 }
558
559 uschar *dmarc_exim_expand_defaults(int what)
560 {
561   switch(what) {
562     case DMARC_VERIFY_STATUS:
563       return (dmarc_disable_verify) ?
564               US"off" :
565               US"none";
566     default:
567       return US"";
568   }
569 }
570
571 uschar *dmarc_auth_results_header(header_line *from_header, uschar *hostname)
572 {
573   uschar *hdr_tmp    = US"";
574
575   /* Allow a server hostname to be passed to this function, but is
576    * currently unused */
577   if (hostname == NULL)
578     hostname = primary_hostname;
579   hdr_tmp = string_sprintf("%s %s;", DMARC_AR_HEADER, hostname);
580
581 #if 0
582   /* I don't think this belongs here, but left it here commented out
583    * because it was a lot of work to get working right. */
584 #ifdef EXPERIMENTAL_SPF
585   if (spf_response != NULL) {
586     uschar *dmarc_ar_spf = US"";
587     int sr               = 0;
588     sr = spf_response->result;
589     dmarc_ar_spf = (sr == SPF_RESULT_NEUTRAL)  ? US"neutral" :
590                    (sr == SPF_RESULT_PASS)     ? US"pass" :
591                    (sr == SPF_RESULT_FAIL)     ? US"fail" :
592                    (sr == SPF_RESULT_SOFTFAIL) ? US"softfail" :
593                    US"none";
594     hdr_tmp = string_sprintf("%s spf=%s (%s) smtp.mail=%s;",
595                              hdr_tmp, dmarc_ar_spf_result,
596                              spf_response->header_comment,
597                              expand_string(US"$sender_address") );
598   }
599 #endif
600 #endif
601   hdr_tmp = string_sprintf("%s dmarc=%s",
602                            hdr_tmp, dmarc_pass_fail);
603   if (header_from_sender)
604     hdr_tmp = string_sprintf("%s header.from=%s",
605                              hdr_tmp, header_from_sender);
606   return hdr_tmp;
607 }
608
609 #endif
610
611 // vim:sw=2 expandtab