ARC: add $arc_domains variable, for verify pass
[exim.git] / src / src / dmarc.c
index 0b45e100aba618af82703ae0b09c3c5db7958cae..956c16b4e70bdd0b688164e66d3bef8e25f9aeb9 100644 (file)
@@ -28,7 +28,6 @@ OPENDMARC_STATUS_T  libdm_status, action, dmarc_policy;
 OPENDMARC_STATUS_T  da, sa, action;
 BOOL dmarc_abort  = FALSE;
 uschar *dmarc_pass_fail = US"skipped";
-extern pdkim_signature  *dkim_signatures;
 header_line *from_header   = NULL;
 extern SPF_response_t   *spf_response;
 int dmarc_spf_ares_result  = 0;
@@ -84,7 +83,6 @@ dmarc_init()
 {
 int *netmask   = NULL;   /* Ignored */
 int is_ipv6    = 0;
-char *tld_file = dmarc_tld_file ? CS dmarc_tld_file : DMARC_TLD_FILE;
 
 /* Set some sane defaults.  Also clears previous results when
  * multiple messages in one connection. */
@@ -111,22 +109,27 @@ if (libdm_status != DMARC_PARSE_OKAY)
                       opendmarc_policy_status_to_str(libdm_status));
   dmarc_abort = TRUE;
   }
-if (dmarc_tld_file == NULL)
+if (!dmarc_tld_file)
+  {
+  DEBUG(D_receive) debug_printf("DMARC: no dmarc_tld_file\n");
   dmarc_abort = TRUE;
-else if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL))
+  }
+else if (opendmarc_tld_read_file(dmarc_tld_file, NULL, NULL, NULL))
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d",
-                      tld_file, errno);
+                      dmarc_tld_file, errno);
   dmarc_abort = TRUE;
   }
-if (sender_host_address == NULL)
+if (!sender_host_address)
+  {
+  DEBUG(D_receive) debug_printf("DMARC: no sender_host_address\n");
   dmarc_abort = TRUE;
+  }
 /* This catches locally originated email and startup errors above. */
 if (!dmarc_abort)
   {
   is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
-  dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6);
-  if (dmarc_pctx == NULL)
+  if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
       "DMARC failure creating policy context: ip=%s", sender_host_address);
@@ -218,7 +221,7 @@ dmarc_process()
 int sr, origin;             /* used in SPF section */
 int dmarc_spf_result  = 0;  /* stores spf into dmarc conn ctx */
 int tmp_ans, c;
-pdkim_signature *sig  = NULL;
+pdkim_signature * sig = dkim_signatures;
 BOOL has_dmarc_record = TRUE;
 u_char **ruf; /* forensic report addressees, if called for */
 
@@ -232,31 +235,34 @@ if (dmarc_disable_verify)
  * the entire DMARC system if we can't find a From: header....or if
  * there was a previous error.
  */
-if (!from_header || dmarc_abort)
+if (!from_header)
+  {
+  DEBUG(D_receive) debug_printf("DMARC: no From: header\n");
   dmarc_abort = TRUE;
-else
+  }
+else if (!dmarc_abort)
   {
-    uschar * errormsg;
-    int dummy, domain;
-    uschar * p;
-    uschar saveend;
-
-    parse_allow_group = TRUE;
-    p = parse_find_address_end(from_header->text, FALSE);
-    saveend = *p; *p = '\0';
-    if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
-                                &dummy, &dummy, &domain, FALSE)))
-      header_from_sender += domain;
-    *p = saveend;
-
-    /* The opendmarc library extracts the domain from the email address, but
-     * only try to store it if it's not empty.  Otherwise, skip out of DMARC. */
-    if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
-      dmarc_abort = TRUE;
-    libdm_status = dmarc_abort ?
-      DMARC_PARSE_OKAY :
-      opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
-    if (libdm_status != DMARC_PARSE_OKAY)
+  uschar * errormsg;
+  int dummy, domain;
+  uschar * p;
+  uschar saveend;
+
+  parse_allow_group = TRUE;
+  p = parse_find_address_end(from_header->text, FALSE);
+  saveend = *p; *p = '\0';
+  if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
+                             &dummy, &dummy, &domain, FALSE)))
+    header_from_sender += domain;
+  *p = saveend;
+
+  /* The opendmarc library extracts the domain from the email address, but
+   * only try to store it if it's not empty.  Otherwise, skip out of DMARC. */
+  if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
+    dmarc_abort = TRUE;
+  libdm_status = dmarc_abort
+    ? DMARC_PARSE_OKAY
+    : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
+  if (libdm_status != DMARC_PARSE_OKAY)
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
              "failure to store header From: in DMARC: %s, header was '%s'",
@@ -269,6 +275,8 @@ else
  * instead do this in the ACLs.  */
 if (!dmarc_abort && !sender_host_authenticated)
   {
+  uschar * dmarc_domain;
+
   /* Use the envelope sender domain for this part of DMARC */
   spf_sender_domain = expand_string(US"$sender_address_domain");
   if (!spf_response)
@@ -323,11 +331,11 @@ if (!dmarc_abort && !sender_host_authenticated)
 
   /* Now we cycle through the dkim signature results and put into
    * the opendmarc context, further building the DMARC reply.  */
-  sig = dkim_signatures;
   dkim_history_buffer = US"";
   while (sig)
     {
     int dkim_result, dkim_ares_result, vs, ves;
+
     vs  = sig->verify_status & ~PDKIM_VERIFY_POLICY;
     ves = sig->verify_ext_status;
     dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
@@ -396,9 +404,9 @@ if (!dmarc_abort && !sender_host_authenticated)
       }
 
   /* Can't use exim's string manipulation functions so allocate memory
-   * for libopendmarc using its max hostname length definition. */
+  for libopendmarc using its max hostname length definition. */
 
-  uschar *dmarc_domain = US calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar));
+  dmarc_domain = US calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar));
   libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
     dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
   dmarc_used_domain = string_copy(dmarc_domain);
@@ -409,8 +417,7 @@ if (!dmarc_abort && !sender_host_authenticated)
       "failure to read domainname used for DMARC lookup: %s",
       opendmarc_policy_status_to_str(libdm_status));
 
-  libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
-  dmarc_policy = libdm_status;
+  dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
   switch(libdm_status)
     {
     case DMARC_POLICY_ABSENT:     /* No DMARC record found */
@@ -467,8 +474,8 @@ if (!dmarc_abort && !sender_host_authenticated)
     log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
                           "spf_align=%s dkim_align=%s enforcement='%s'",
                           spf_sender_domain, dmarc_used_domain,
-                          (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?"yes":"no",
-                          (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?"yes":"no",
+                          sa==DMARC_POLICY_SPF_ALIGNMENT_PASS  ?"yes":"no",
+                          da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
                           dmarc_status_text);
     history_file_status = dmarc_write_history_file();
     /* Now get the forensic reporting addresses, if any */
@@ -486,7 +493,7 @@ if (!dmarc_disable_verify)
 return OK;
 }
 
-int
+static int
 dmarc_write_history_file()
 {
 int history_file_fd;
@@ -496,7 +503,10 @@ u_char **rua; /* aggregate report addressees */
 uschar *history_buffer = NULL;
 
 if (!dmarc_history_file)
+  {
+  DEBUG(D_receive) debug_printf("DMARC history file not set\n");
   return DMARC_HIST_DISABLED;
+  }
 history_file_fd = log_create(dmarc_history_file);
 
 if (history_file_fd < 0)
@@ -594,9 +604,12 @@ return US"";
 gstring *
 authres_dmarc(gstring * g)
 {
-g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
-if (header_from_sender)
-  g = string_append(g, 2, US"header.from=", header_from_sender);
+if (dmarc_has_been_checked)
+  {
+  g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
+  if (header_from_sender)
+    g = string_append(g, 2, US" header.from=", header_from_sender);
+  }
 return g;
 }