Further tidies and minor fixes to the tables that control which ACL
[users/heiko/exim.git] / src / src / acl.c
index fbc6223743a22db795ca90665d2dacc4bf4c1f5d..704e9cb5e096211556873c9631e29afea7d3495a 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/acl.c,v 1.15 2005/01/12 14:23:28 ph10 Exp $ */
+/* $Cambridge: exim/src/src/acl.c,v 1.26 2005/03/29 10:56:48 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -45,7 +45,15 @@ ACLC_CONDITION, ACLC_CONTROL,
        ACLC_DELAY,
 #ifdef WITH_OLD_DEMIME
        ACLC_DEMIME,
-#endif        
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+       ACLC_DK_DOMAIN_SOURCE,
+       ACLC_DK_POLICY,
+       ACLC_DK_SENDER_DOMAINS,
+       ACLC_DK_SENDER_LOCAL_PARTS,
+       ACLC_DK_SENDERS,
+       ACLC_DK_STATUS,
+#endif
        ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS,
        ACLC_HOSTS, ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE,
 #ifdef WITH_CONTENT_SCAN
@@ -61,7 +69,7 @@ ACLC_CONDITION, ACLC_CONTROL,
 #endif
        ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET,
 #ifdef WITH_CONTENT_SCAN
-       ACLC_SPAM,       
+       ACLC_SPAM,
 #endif
 #ifdef EXPERIMENTAL_SPF
        ACLC_SPF,
@@ -77,13 +85,21 @@ static uschar *conditions[] = { US"acl", US"authenticated",
   US"bmi_optin",
 #endif
   US"condition",
-  US"control", 
+  US"control",
 #ifdef WITH_CONTENT_SCAN
   US"decode",
 #endif
   US"delay",
 #ifdef WITH_OLD_DEMIME
   US"demime",
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  US"dk_domain_source",
+  US"dk_policy",
+  US"dk_sender_domains",
+  US"dk_sender_local_parts",
+  US"dk_senders",
+  US"dk_status",
 #endif
   US"dnslists", US"domains", US"encrypted",
   US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite",
@@ -106,12 +122,12 @@ static uschar *conditions[] = { US"acl", US"authenticated",
   US"spf",
 #endif
   US"verify" };
-  
+
 /* ACL control names */
 
 static uschar *controls[] = { US"error", US"caseful_local_part",
   US"caselower_local_part", US"enforce_sync", US"no_enforce_sync", US"freeze",
-  US"queue_only", US"submission", US"no_multiline"}; 
+  US"queue_only", US"submission", US"no_multiline"};
 
 /* Flags to indicate for which conditions /modifiers a string expansion is done
 at the outer level. In the other cases, expansion already occurs in the
@@ -122,7 +138,7 @@ static uschar cond_expand_at_top[] = {
   FALSE,   /* authenticated */
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   TRUE,    /* bmi_optin */
-#endif  
+#endif
   TRUE,    /* condition */
   TRUE,    /* control */
 #ifdef WITH_CONTENT_SCAN
@@ -131,6 +147,14 @@ static uschar cond_expand_at_top[] = {
   TRUE,    /* delay */
 #ifdef WITH_OLD_DEMIME
   TRUE,    /* demime */
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  TRUE,    /* dk_domain_source */
+  TRUE,    /* dk_policy */
+  TRUE,    /* dk_sender_domains */
+  TRUE,    /* dk_sender_local_parts */
+  TRUE,    /* dk_senders */
+  TRUE,    /* dk_status */
 #endif
   TRUE,    /* dnslists */
   FALSE,   /* domains */
@@ -170,7 +194,7 @@ static uschar cond_modifiers[] = {
   FALSE,   /* authenticated */
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   TRUE,    /* bmi_optin */
-#endif  
+#endif
   FALSE,   /* condition */
   TRUE,    /* control */
 #ifdef WITH_CONTENT_SCAN
@@ -179,6 +203,14 @@ static uschar cond_modifiers[] = {
   TRUE,    /* delay */
 #ifdef WITH_OLD_DEMIME
   FALSE,   /* demime */
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  FALSE,   /* dk_domain_source */
+  FALSE,   /* dk_policy */
+  FALSE,   /* dk_sender_domains */
+  FALSE,   /* dk_sender_local_parts */
+  FALSE,   /* dk_senders */
+  FALSE,   /* dk_status */
 #endif
   FALSE,   /* dnslists */
   FALSE,   /* domains */
@@ -212,120 +244,132 @@ static uschar cond_modifiers[] = {
 };
 
 /* Bit map vector of which conditions are not allowed at certain times. For
-each condition, there's a bitmap of dis-allowed times. */
+each condition, there's a bitmap of dis-allowed times. For some, it is easier
+to specify the negation of a small number of allowed times. */
 
 static unsigned int cond_forbids[] = {
   0,                                               /* acl */
-   
+
   (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* authenticated */
     (1<<ACL_WHERE_HELO),
-  
+
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   (1<<ACL_WHERE_AUTH)|                             /* bmi_optin */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|                                       
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_PREDATA),
 #endif
-  
+
   0,                                               /* condition */
-  
+
   /* Certain types of control are always allowed, so we let it through
-  always and check in the control processing itself */
-  
+  always and check in the control processing itself. */
+
   0,                                               /* control */
-  
+
 #ifdef WITH_CONTENT_SCAN
-  (1<<ACL_WHERE_AUTH)|                             /* decode */
-    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
-    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_RCPT),
+  (unsigned int)
+  ~(1<<ACL_WHERE_MIME),                            /* decode */
 #endif
 
   0,                                               /* delay */
-  
+
 #ifdef WITH_OLD_DEMIME
-  (1<<ACL_WHERE_AUTH)|                             /* demime */
-    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
-    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_MIME),
+  (unsigned int)
+  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* demime */
 #endif
-  
-  (1<<ACL_WHERE_NOTSMTP),                          /* dnslists */
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* domains */
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  (1<<ACL_WHERE_AUTH)|                             /* dk_domain_source */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* encrypted */
-    (1<<ACL_WHERE_HELO),
-     
-  0,                                               /* endpass */
-   
-  (1<<ACL_WHERE_NOTSMTP),                          /* hosts */
-
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* local_parts */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_policy */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),
 
-  0,                                               /* log_message */
-   
-  0,                                               /* logwrite */
-  
-#ifdef WITH_CONTENT_SCAN
-  (1<<ACL_WHERE_AUTH)|                             /* malware */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_sender_domains */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_MIME),
-#endif
-
-  0,                                               /* message */
+    (1<<ACL_WHERE_VRFY),
 
-#ifdef WITH_CONTENT_SCAN
-  (1<<ACL_WHERE_AUTH)|                             /* mime_regex */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_sender_local_parts */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_RCPT),
-#endif
+    (1<<ACL_WHERE_VRFY),
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* recipients */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_senders */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),
 
-#ifdef WITH_CONTENT_SCAN
-  (1<<ACL_WHERE_AUTH)|                             /* regex */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_status */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_MIME),
+    (1<<ACL_WHERE_VRFY),
+#endif
+
+  (1<<ACL_WHERE_NOTSMTP),                          /* dnslists */
+
+  (unsigned int)
+  ~(1<<ACL_WHERE_RCPT),                            /* domains */
+
+  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* encrypted */
+    (1<<ACL_WHERE_HELO),
+
+  0,                                               /* endpass */
+
+  (1<<ACL_WHERE_NOTSMTP),                          /* hosts */
+
+  (unsigned int)
+  ~(1<<ACL_WHERE_RCPT),                            /* local_parts */
+
+  0,                                               /* log_message */
+
+  0,                                               /* logwrite */
+
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* malware */
+#endif
+
+  0,                                               /* message */
+
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~(1<<ACL_WHERE_MIME),                            /* mime_regex */
+#endif
+
+  (unsigned int)
+  ~(1<<ACL_WHERE_RCPT),                            /* recipients */
+
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|    /* regex */
+    (1<<ACL_WHERE_MIME)),
 #endif
 
   (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* sender_domains */
@@ -343,13 +387,8 @@ static unsigned int cond_forbids[] = {
   0,                                               /* set */
 
 #ifdef WITH_CONTENT_SCAN
-  (1<<ACL_WHERE_AUTH)|                             /* spam */
-    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
-    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
-    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_MIME),
+  (unsigned int)
+  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* spam */
 #endif
 
 #ifdef EXPERIMENTAL_SPF
@@ -369,15 +408,18 @@ static unsigned int cond_forbids[] = {
 
 /* Return values from decode_control() */
 
-enum { 
+enum {
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   CONTROL_BMI_RUN,
-#endif  
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  CONTROL_DK_VERIFY,
+#endif
   CONTROL_ERROR, CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART,
   CONTROL_ENFORCE_SYNC, CONTROL_NO_ENFORCE_SYNC, CONTROL_FREEZE,
   CONTROL_QUEUE_ONLY, CONTROL_SUBMISSION,
 #ifdef WITH_CONTENT_SCAN
-  CONTROL_NO_MBOX_UNSPOOL, 
+  CONTROL_NO_MBOX_UNSPOOL,
 #endif
   CONTROL_FAKEREJECT, CONTROL_NO_MULTILINE };
 
@@ -389,42 +431,47 @@ static unsigned int control_forbids[] = {
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   0,                                               /* bmi_run */
 #endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP),      /* dk_verify */
+#endif
 
   0,                                               /* error */
-  
-  (unsigned int) 
+
+  (unsigned int)
   ~(1<<ACL_WHERE_RCPT),                            /* caseful_local_part */
-   
-  (unsigned int) 
+
+  (unsigned int)
   ~(1<<ACL_WHERE_RCPT),                            /* caselower_local_part */
-   
+
   (1<<ACL_WHERE_NOTSMTP),                          /* enforce_sync */
-   
+
   (1<<ACL_WHERE_NOTSMTP),                          /* no_enforce_sync */
-   
-  (unsigned int) 
+
+  (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* freeze */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-    (1<<ACL_WHERE_NOTSMTP)),
-     
-  (unsigned int) 
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
+
+  (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* queue_only */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-    (1<<ACL_WHERE_NOTSMTP)),
-     
-  (unsigned int) 
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
+
+  (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* submission */
-    (1<<ACL_WHERE_PREDATA)),                       
+    (1<<ACL_WHERE_PREDATA)),
 
 #ifdef WITH_CONTENT_SCAN
-  (unsigned int) 
+  (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* no_mbox_unspool */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)),
+    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+    (1<<ACL_WHERE_MIME)),
 #endif
 
-  (unsigned int) 
+  (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* fakereject */
-    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)),
+    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+    (1<<ACL_WHERE_MIME)),
 
   (1<<ACL_WHERE_NOTSMTP)                           /* no_multiline */
 };
@@ -440,6 +487,9 @@ typedef struct control_def {
 static control_def controls_list[] = {
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   { US"bmi_run",                CONTROL_BMI_RUN, FALSE},
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  { US"dk_verify",              CONTROL_DK_VERIFY, FALSE},
 #endif
   { US"caseful_local_part",     CONTROL_CASEFUL_LOCAL_PART, FALSE},
   { US"caselower_local_part",   CONTROL_CASELOWER_LOCAL_PART, FALSE},
@@ -781,7 +831,7 @@ if (hlen > 0)
         {
         newtype = htype_add_rfc;
         p += 14;
-        }        
+        }
       else if (strncmpic(p, US":at_start:", 10) == 0)
         {
         newtype = htype_add_top;
@@ -935,6 +985,13 @@ address_item *sender_vaddr = NULL;
 uschar *verify_sender_address = NULL;
 uschar *pm_mailfrom = NULL;
 uschar *se_mailfrom = NULL;
+
+/* Some of the verify items have slash-separated options; some do not. Diagnose
+an error if options are given for items that don't expect them. This code has
+now got very message. Refactoring to use a table would be a good idea one day.
+*/
+
+uschar *slash = Ustrchr(arg, '/');
 uschar *list = arg;
 uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
 
@@ -944,6 +1001,7 @@ if (ss == NULL) goto BAD_VERIFY;
 
 if (strcmpic(ss, US"reverse_host_lookup") == 0)
   {
+  if (slash != NULL) goto NO_OPTIONS;
   if (sender_host_address == NULL) return OK;
   return acl_verify_reverse(user_msgptr, log_msgptr);
   }
@@ -954,6 +1012,7 @@ mandatory verification, the connection doesn't last this long.) */
 
 if (strcmpic(ss, US"certificate") == 0)
   {
+  if (slash != NULL) goto NO_OPTIONS;
   if (tls_certificate_verified) return OK;
   *user_msgptr = US"no verified certificate";
   return FAIL;
@@ -961,42 +1020,51 @@ if (strcmpic(ss, US"certificate") == 0)
 
 /* We can test the result of optional HELO verification */
 
-if (strcmpic(ss, US"helo") == 0) return helo_verified? OK : FAIL;
+if (strcmpic(ss, US"helo") == 0)
+  {
+  if (slash != NULL) goto NO_OPTIONS;
+  return helo_verified? OK : FAIL;
+  }
 
-/* Handle header verification options - permitted only after DATA or a non-SMTP
-message. */
+/* Check that all relevant header lines have the correct syntax. If there is
+a syntax error, we return details of the error to the sender if configured to
+send out full details. (But a "message" setting on the ACL can override, as
+always). */
 
-if (strncmpic(ss, US"header_", 7) == 0)
+if (strcmpic(ss, US"header_syntax") == 0)
   {
+  if (slash != NULL) goto NO_OPTIONS;
   if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP)
     {
     *log_msgptr = string_sprintf("cannot check header contents in ACL for %s "
       "(only possible in ACL for DATA)", acl_wherenames[where]);
     return ERROR;
     }
+  rc = verify_check_headers(log_msgptr);
+  if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
+    *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+  return rc;
+  }
 
-  /* Check that all relevant header lines have the correct syntax. If there is
-  a syntax error, we return details of the error to the sender if configured to
-  send out full details. (But a "message" setting on the ACL can override, as
-  always). */
-
-  if (strcmpic(ss+7, US"syntax") == 0)
-    {
-    int rc = verify_check_headers(log_msgptr);
-    if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
-      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
-    return rc;
-    }
 
-  /* Check that there is at least one verifiable sender address in the relevant
-  header lines. This can be followed by callout and defer options, just like
-  sender and recipient. */
+/* The remaining verification tests check recipient and sender addresses,
+either from the envelope or from the header. There are a number of
+slash-separated options that are common to all of them. */
 
-  else if (strcmpic(ss+7, US"sender") == 0) verify_header_sender = TRUE;
 
-  /* Unknown verify argument starting with "header_" */
+/* Check that there is at least one verifiable sender address in the relevant
+header lines. This can be followed by callout and defer options, just like
+sender and recipient. */
 
-  else goto BAD_VERIFY;
+if (strcmpic(ss, US"header_sender") == 0)
+  {
+  if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP)
+    {
+    *log_msgptr = string_sprintf("cannot check header contents in ACL for %s "
+      "(only possible in ACL for DATA)", acl_wherenames[where]);
+    return ERROR;
+    }
+  verify_header_sender = TRUE;
   }
 
 /* Otherwise, first item in verify argument must be "sender" or "recipient".
@@ -1034,7 +1102,8 @@ else
     }
   }
 
-/* Remaining items are optional */
+/* Remaining items are optional; they apply to sender and recipient
+verification, including "header sender" verification. */
 
 while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
       != NULL)
@@ -1071,11 +1140,11 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
         uschar *opt;
         uschar buffer[256];
         while (isspace(*ss)) ss++;
-        
-        /* This callout option handling code has become a mess as new options 
-        have been added in an ad hoc manner. It should be tidied up into some 
+
+        /* This callout option handling code has become a mess as new options
+        have been added in an ad hoc manner. It should be tidied up into some
         kind of table-driven thing. */
+
         while ((opt = string_nextinlist(&ss, &optsep, buffer, sizeof(buffer)))
               != NULL)
           {
@@ -1207,13 +1276,19 @@ message if giving out verification details. */
 
 if (verify_header_sender)
   {
+  int verrno;
   rc = verify_check_header_address(user_msgptr, log_msgptr, callout,
-    callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options);
-  if (smtp_return_error_details)
+    callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options,
+    &verrno);
+  if (rc != OK)
     {
-    if (*user_msgptr == NULL && *log_msgptr != NULL)
-      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
-    if (rc == DEFER) acl_temp_details = TRUE;
+    *basic_errno = verrno;
+    if (smtp_return_error_details)
+      {
+      if (*user_msgptr == NULL && *log_msgptr != NULL)
+        *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+      if (rc == DEFER) acl_temp_details = TRUE;
+      }
     }
   }
 
@@ -1273,7 +1348,7 @@ else if (verify_sender_address != NULL)
     {
     BOOL routed = TRUE;
     uschar *save_address_data = deliver_address_data;
-      
+
     sender_vaddr = deliver_make_addr(verify_sender_address, TRUE);
     if (no_details) setflag(sender_vaddr, af_sverify_told);
     if (verify_sender_address[0] != 0)
@@ -1319,16 +1394,16 @@ else if (verify_sender_address != NULL)
     sender_vaddr->special_action = rc;
     sender_vaddr->next = sender_verified_list;
     sender_verified_list = sender_vaddr;
-    
-    /* Restore the recipient address data, which might have been clobbered by 
+
+    /* Restore the recipient address data, which might have been clobbered by
     the sender verification. */
-  
+
     deliver_address_data = save_address_data;
     }
-    
+
   /* Put the sender address_data value into $sender_address_data */
 
-  sender_address_data = sender_vaddr->p.address_data; 
+  sender_address_data = sender_vaddr->p.address_data;
   }
 
 /* A recipient address just gets a straightforward verify; again we must handle
@@ -1345,8 +1420,10 @@ else
   rc = verify_address(&addr2, NULL, verify_options|vopt_is_recipient, callout,
     callout_overall, callout_connect, se_mailfrom, pm_mailfrom, NULL);
   HDEBUG(D_acl) debug_printf("----------- end verify ------------\n");
+
   *log_msgptr = addr2.message;
-  *user_msgptr = addr2.user_message;
+  *user_msgptr = (addr2.user_message != NULL)?
+    addr2.user_message : addr2.message;
   *basic_errno = addr2.basic_errno;
 
   /* Make $address_data visible */
@@ -1400,9 +1477,17 @@ return rc;
 
 BAD_VERIFY:
 *log_msgptr = string_sprintf("expected \"sender[=address]\", \"recipient\", "
-  "\"header_syntax\" or \"header_sender\" at start of ACL condition "
+  "\"helo\", \"header_syntax\", \"header_sender\" or "
+  "\"reverse_host_lookup\" at start of ACL condition "
   "\"verify %s\"", arg);
 return ERROR;
+
+/* Options supplied when not allowed come here */
+
+NO_OPTIONS:
+*log_msgptr = string_sprintf("unexpected '/' found in \"%s\" "
+  "(this verify item has no options)", arg);
+return ERROR;
 }
 
 
@@ -1495,7 +1580,7 @@ int sep = '/';
 for (; cb != NULL; cb = cb->next)
   {
   uschar *arg;
-  int control_type; 
+  int control_type;
 
   /* The message and log_message items set up messages to be used in
   case of rejection. They are expanded later. */
@@ -1631,7 +1716,7 @@ for (; cb != NULL; cb = cb->next)
       *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL",
         controls[control_type], acl_wherenames[where]);
       return ERROR;
-      }                                                     
+      }
 
     switch(control_type)
       {
@@ -1640,7 +1725,11 @@ for (; cb != NULL; cb = cb->next)
       bmi_run = 1;
       break;
 #endif
-      
+#ifdef EXPERIMENTAL_DOMAINKEYS
+      case CONTROL_DK_VERIFY:
+      dk_do_verify = 1;
+      break;
+#endif
       case CONTROL_ERROR:
       return ERROR;
 
@@ -1673,16 +1762,16 @@ for (; cb != NULL; cb = cb->next)
       case CONTROL_FAKEREJECT:
       fake_reject = TRUE;
       if (*p == '/')
-        { 
+        {
         uschar *pp = p + 1;
-        while (*pp != 0) pp++; 
+        while (*pp != 0) pp++;
         fake_reject_text = expand_string(string_copyn(p+1, pp-p));
         p = pp;
         }
        else
         {
         /* Explicitly reset to default string */
-        fake_reject_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legit message, it may still be delivered to the target recipient(s).";
+        fake_reject_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
         }
       break;
 
@@ -1698,22 +1787,22 @@ for (; cb != NULL; cb = cb->next)
       case CONTROL_SUBMISSION:
       submission_mode = TRUE;
       while (*p == '/')
-        { 
+        {
         if (Ustrncmp(p, "/sender_retain", 14) == 0)
           {
           p += 14;
           active_local_sender_retain = TRUE;
-          active_local_from_check = FALSE;   
-          }  
+          active_local_from_check = FALSE;
+          }
         else if (Ustrncmp(p, "/domain=", 8) == 0)
           {
           uschar *pp = p + 8;
-          while (*pp != 0 && *pp != '/') pp++; 
+          while (*pp != 0 && *pp != '/') pp++;
           submission_domain = string_copyn(p+8, pp-p);
-          p = pp; 
+          p = pp;
           }
-        else break;   
-        }   
+        else break;
+        }
       if (*p != 0)
         {
         *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
@@ -1747,10 +1836,10 @@ for (; cb != NULL; cb = cb->next)
           HDEBUG(D_acl)
             debug_printf("delay skipped in -bh checking mode\n");
           }
-        else 
+        else
           {
           while (delay > 0) delay = sleep(delay);
-          } 
+          }
         }
       }
     break;
@@ -1761,6 +1850,93 @@ for (; cb != NULL; cb = cb->next)
     break;
 #endif
 
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  case ACLC_DK_DOMAIN_SOURCE:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    /* check header source of domain against given string */
+    switch (dk_verify_block->address_source) {
+      case DK_EXIM_ADDRESS_FROM_FROM:
+        rc = match_isinlist(US"from", &arg, 0, NULL,
+                            NULL, MCL_STRING, TRUE, NULL);
+      break;
+      case DK_EXIM_ADDRESS_FROM_SENDER:
+        rc = match_isinlist(US"sender", &arg, 0, NULL,
+                            NULL, MCL_STRING, TRUE, NULL);
+      break;
+      case DK_EXIM_ADDRESS_NONE:
+        rc = match_isinlist(US"none", &arg, 0, NULL,
+                            NULL, MCL_STRING, TRUE, NULL);
+      break;
+    }
+  break;
+  case ACLC_DK_POLICY:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    /* check policy against given string, default FAIL */
+    rc = FAIL;
+    if (dk_verify_block->signsall)
+      rc = match_isinlist(US"signsall", &arg, 0, NULL,
+                          NULL, MCL_STRING, TRUE, NULL);
+    if (dk_verify_block->testing)
+      rc = match_isinlist(US"testing", &arg, 0, NULL,
+                          NULL, MCL_STRING, TRUE, NULL);
+  break;
+  case ACLC_DK_SENDER_DOMAINS:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    if (dk_verify_block->domain != NULL)
+      rc = match_isinlist(dk_verify_block->domain, &arg, 0, &domainlist_anchor,
+                          NULL, MCL_DOMAIN, TRUE, NULL);
+    else rc = FAIL;
+  break;
+  case ACLC_DK_SENDER_LOCAL_PARTS:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    if (dk_verify_block->local_part != NULL)
+      rc = match_isinlist(dk_verify_block->local_part, &arg, 0, &localpartlist_anchor,
+                          NULL, MCL_LOCALPART, TRUE, NULL);
+    else rc = FAIL;
+  break;
+  case ACLC_DK_SENDERS:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    if (dk_verify_block->address != NULL)
+      rc = match_address_list(dk_verify_block->address, TRUE, TRUE, &arg, NULL, -1, 0, NULL);
+    else rc = FAIL;
+  break;
+  case ACLC_DK_STATUS:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    if (dk_verify_block->result > 0) {
+      switch(dk_verify_block->result) {
+        case DK_EXIM_RESULT_BAD_FORMAT:
+          rc = match_isinlist(US"bad format", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_NO_KEY:
+          rc = match_isinlist(US"no key", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_NO_SIGNATURE:
+          rc = match_isinlist(US"no signature", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_REVOKED:
+          rc = match_isinlist(US"revoked", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_NON_PARTICIPANT:
+          rc = match_isinlist(US"non-participant", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_GOOD:
+          rc = match_isinlist(US"good", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_BAD:
+          rc = match_isinlist(US"bad", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+      }
+    }
+  break;
+#endif
+
     case ACLC_DNSLISTS:
     rc = verify_check_dnsbl(&arg);
     break;
@@ -1840,7 +2016,7 @@ for (; cb != NULL; cb = cb->next)
       log_write(0, logbits, "%s", string_printing(s));
       }
     break;
-    
+
 #ifdef WITH_CONTENT_SCAN
     case ACLC_MALWARE:
       {