build: use pkg-config for i18n
[exim.git] / src / src / acl.c
index 74b59b0fe7f13c895140825cd4a46499c1aadb26..ab05b13a966e92c134631db97698d656a60e5e10 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -57,9 +57,7 @@ static int msgcond[] = {
 
 #endif
 
 
 #endif
 
-/* ACL condition and modifier codes - keep in step with the table that
-follows.
-down. */
+/* ACL condition and modifier codes */
 
 enum { ACLC_ACL,
        ACLC_ADD_HEADER,
 
 enum { ACLC_ACL,
        ACLC_ADD_HEADER,
@@ -119,7 +117,8 @@ enum { ACLC_ACL,
        ACLC_SPF_GUESS,
 #endif
        ACLC_UDPSEND,
        ACLC_SPF_GUESS,
 #endif
        ACLC_UDPSEND,
-       ACLC_VERIFY };
+       ACLC_VERIFY,
+};
 
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
 "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
 
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
 "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
@@ -130,27 +129,28 @@ being the prefix of another; the binary-search in the list will go wrong. */
 typedef struct condition_def {
   uschar       *name;
 
 typedef struct condition_def {
   uschar       *name;
 
-/* Flag to indicate the condition/modifier has a string expansion done
-at the outer level. In the other cases, expansion already occurs in the
-checking functions. */
-  BOOL         expand_at_top:1;
-
-  BOOL         is_modifier:1;
+  /* Flags for actions or checks to do during readconf for this condition */
+  unsigned     flags;
+#define ACD_EXP                BIT(0)  /* do expansion at outer level*/
+#define ACD_MOD                BIT(1)  /* is a modifier */
+#define ACD_LOAD       BIT(2)  /* supported by a dynamic-load module */
 
 
-/* Bit map vector of which conditions and modifiers are not allowed at certain
-times. For each condition and modifier, there's a bitmap of dis-allowed times.
-For some, it is easier to specify the negation of a small number of allowed
-times. */
+  /* Bit map vector of which conditions and modifiers are not allowed at certain
+  times. For each condition and modifier, there's a bitmap of dis-allowed times.
+  For some, it is easier to specify the negation of a small number of allowed
+  times. */
   unsigned     forbids;
   unsigned     forbids;
+#define FORBIDDEN(times)       (times)
+#define PERMITTED(times)       ((unsigned) ~(times))
 
 } condition_def;
 
 static condition_def conditions[] = {
 
 } condition_def;
 
 static condition_def conditions[] = {
-  [ACLC_ACL] =                 { US"acl",              FALSE, FALSE,   0 },
+  [ACLC_ACL] =                 { US"acl",              0,
+                                 FORBIDDEN(0) },
 
 
-  [ACLC_ADD_HEADER] =          { US"add_header",       TRUE, TRUE,
-                                 (unsigned int)
-                                 ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+  [ACLC_ADD_HEADER] =          { US"add_header",       ACD_EXP | ACD_MOD,
+                                 PERMITTED(ACL_BIT_MAIL | ACL_BIT_RCPT |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
@@ -160,13 +160,14 @@ static condition_def conditions[] = {
                                    ACL_BIT_NOTSMTP_START),
   },
 
                                    ACL_BIT_NOTSMTP_START),
   },
 
-  [ACLC_AUTHENTICATED] =       { US"authenticated",    FALSE, FALSE,
-                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
-                                   ACL_BIT_CONNECT | ACL_BIT_HELO,
+  [ACLC_AUTHENTICATED] =       { US"authenticated",    0,
+                                 FORBIDDEN(ACL_BIT_NOTSMTP |
+                                   ACL_BIT_NOTSMTP_START |
+                                   ACL_BIT_CONNECT | ACL_BIT_HELO),
   },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  [ACLC_BMI_OPTIN] =           { US"bmi_optin",        TRUE, TRUE,
-                                 ACL_BIT_AUTH |
+  [ACLC_BMI_OPTIN] =           { US"bmi_optin",        ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(ACL_BIT_AUTH |
                                    ACL_BIT_CONNECT | ACL_BIT_HELO |
                                    ACL_BIT_DATA | ACL_BIT_MIME |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_CONNECT | ACL_BIT_HELO |
                                    ACL_BIT_DATA | ACL_BIT_MIME |
 # ifndef DISABLE_PRDR
@@ -176,20 +177,22 @@ static condition_def conditions[] = {
                                    ACL_BIT_MAILAUTH |
                                    ACL_BIT_MAIL | ACL_BIT_STARTTLS |
                                    ACL_BIT_VRFY | ACL_BIT_PREDATA |
                                    ACL_BIT_MAILAUTH |
                                    ACL_BIT_MAIL | ACL_BIT_STARTTLS |
                                    ACL_BIT_VRFY | ACL_BIT_PREDATA |
-                                   ACL_BIT_NOTSMTP_START,
+                                   ACL_BIT_NOTSMTP_START),
   },
 #endif
   },
 #endif
-  [ACLC_CONDITION] =           { US"condition",        TRUE, FALSE,    0 },
-  [ACLC_CONTINUE] =            { US"continue", TRUE, TRUE,     0 },
+  [ACLC_CONDITION] =           { US"condition",        ACD_EXP,
+                                 FORBIDDEN(0) },
+  [ACLC_CONTINUE] =            { US"continue",         ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
 
   /* Certain types of control are always allowed, so we let it through
   always and check in the control processing itself. */
 
   /* Certain types of control are always allowed, so we let it through
   always and check in the control processing itself. */
-  [ACLC_CONTROL] =             { US"control",  TRUE, TRUE,     0 },
+  [ACLC_CONTROL] =             { US"control",          ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
 
 #ifdef EXPERIMENTAL_DCC
 
 #ifdef EXPERIMENTAL_DCC
-  [ACLC_DCC] =                 { US"dcc",              TRUE, FALSE,
-                                 (unsigned int)
-                                 ~(ACL_BIT_DATA |
+  [ACLC_DCC] =                 { US"dcc",              ACD_EXP,
+                                 PERMITTED(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 # endif
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 # endif
@@ -197,57 +200,82 @@ static condition_def conditions[] = {
   },
 #endif
 #ifdef WITH_CONTENT_SCAN
   },
 #endif
 #ifdef WITH_CONTENT_SCAN
-  [ACLC_DECODE] =              { US"decode",           TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
+  [ACLC_DECODE] =              { US"decode",           ACD_EXP,
+                                 PERMITTED(ACL_BIT_MIME) },
 
 #endif
 
 #endif
-  [ACLC_DELAY] =               { US"delay",            TRUE, TRUE, ACL_BIT_NOTQUIT },
+  [ACLC_DELAY] =               { US"delay",            ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(ACL_BIT_NOTQUIT) },
 #ifndef DISABLE_DKIM
 #ifndef DISABLE_DKIM
-  [ACLC_DKIM_SIGNER] =         { US"dkim_signers",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
-  [ACLC_DKIM_STATUS] =         { US"dkim_status",      TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
+  [ACLC_DKIM_SIGNER] =         { US"dkim_signers",
+# if SUPPORT_DKIM==2
+                                 ACD_LOAD |
+# endif
+                                 ACD_EXP, 
+                                 PERMITTED(ACL_BIT_DKIM) },
+  [ACLC_DKIM_STATUS] =         { US"dkim_status",
+# if SUPPORT_DKIM==2
+                                 ACD_LOAD |
+# endif
+                                 ACD_EXP,
+                                 PERMITTED(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME
+# ifndef DISABLE_PRDR
+                                 | ACL_BIT_PRDR
+# endif
+      ),
+  },
 #endif
 #ifdef SUPPORT_DMARC
 #endif
 #ifdef SUPPORT_DMARC
-  [ACLC_DMARC_STATUS] =                { US"dmarc_status",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
+  [ACLC_DMARC_STATUS] =                { US"dmarc_status",
+# if SUPPORT_DMARC==2
+                                 ACD_LOAD |
+# endif
+                                 ACD_EXP,
+                                 PERMITTED(ACL_BIT_DATA) },
 #endif
 
   /* Explicit key lookups can be made in non-smtp ACLs so pass
   always and check in the verify processing itself. */
 #endif
 
   /* Explicit key lookups can be made in non-smtp ACLs so pass
   always and check in the verify processing itself. */
-  [ACLC_DNSLISTS] =            { US"dnslists", TRUE, FALSE,    0 },
+  [ACLC_DNSLISTS] =            { US"dnslists",         ACD_EXP,
+                                 FORBIDDEN(0) },
 
 
-  [ACLC_DOMAINS] =             { US"domains",  FALSE, FALSE,
-                                 (unsigned int)
-                                 ~(ACL_BIT_RCPT | ACL_BIT_VRFY
+  [ACLC_DOMAINS] =             { US"domains",          0,
+                                 PERMITTED(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
 #ifndef DISABLE_PRDR
-                                 |ACL_BIT_PRDR
+                                 | ACL_BIT_PRDR
 #endif
       ),
   },
 #endif
       ),
   },
-  [ACLC_ENCRYPTED] =           { US"encrypted",        FALSE, FALSE,
-                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
-                                   ACL_BIT_CONNECT
+  [ACLC_ENCRYPTED] =           { US"encrypted",        0,
+                                 FORBIDDEN(ACL_BIT_NOTSMTP |
+                                   ACL_BIT_NOTSMTP_START | ACL_BIT_CONNECT)
   },
 
   },
 
-  [ACLC_ENDPASS] =             { US"endpass",  TRUE, TRUE,     0 },
+  [ACLC_ENDPASS] =             { US"endpass",  ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
 
 
-  [ACLC_HOSTS] =               { US"hosts",            FALSE, FALSE,
-                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+  [ACLC_HOSTS] =               { US"hosts",            0,
+                                 FORBIDDEN(ACL_BIT_NOTSMTP |
+                                   ACL_BIT_NOTSMTP_START),
   },
   },
-  [ACLC_LOCAL_PARTS] =         { US"local_parts",      FALSE, FALSE,
-                                 (unsigned int)
-                                 ~(ACL_BIT_RCPT | ACL_BIT_VRFY
+  [ACLC_LOCAL_PARTS] =         { US"local_parts",      0,
+                                 PERMITTED(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
                                  | ACL_BIT_PRDR
 #endif
       ),
   },
 
 #ifndef DISABLE_PRDR
                                  | ACL_BIT_PRDR
 #endif
       ),
   },
 
-  [ACLC_LOG_MESSAGE] =         { US"log_message",      TRUE, TRUE,     0 },
-  [ACLC_LOG_REJECT_TARGET] =   { US"log_reject_target", TRUE, TRUE,    0 },
-  [ACLC_LOGWRITE] =            { US"logwrite", TRUE, TRUE,     0 },
+  [ACLC_LOG_MESSAGE] =         { US"log_message",      ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
+  [ACLC_LOG_REJECT_TARGET] =   { US"log_reject_target", ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
+  [ACLC_LOGWRITE] =            { US"logwrite",         ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
 
 #ifdef WITH_CONTENT_SCAN
 
 #ifdef WITH_CONTENT_SCAN
-  [ACLC_MALWARE] =             { US"malware",  TRUE, FALSE,
-                                 (unsigned int)
-                                   ~(ACL_BIT_DATA |
+  [ACLC_MALWARE] =             { US"malware",          ACD_EXP,
+                                 PERMITTED(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
 # endif
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
 # endif
@@ -255,26 +283,29 @@ static condition_def conditions[] = {
   },
 #endif
 
   },
 #endif
 
-  [ACLC_MESSAGE] =             { US"message",  TRUE, TRUE,     0 },
+  [ACLC_MESSAGE] =             { US"message",          ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-  [ACLC_MIME_REGEX] =          { US"mime_regex",       TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
+  [ACLC_MIME_REGEX] =          { US"mime_regex",       ACD_EXP,
+                                 PERMITTED(ACL_BIT_MIME) },
 #endif
 
 #endif
 
-  [ACLC_QUEUE] =               { US"queue",            TRUE, TRUE,
-                                 ACL_BIT_NOTSMTP |
+  [ACLC_QUEUE] =               { US"queue",            ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(ACL_BIT_NOTSMTP |
 #ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 #endif
 #ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 #endif
-                                 ACL_BIT_DATA,
+                                 ACL_BIT_DATA),
   },
 
   },
 
-  [ACLC_RATELIMIT] =           { US"ratelimit",        TRUE, FALSE,    0 },
-  [ACLC_RECIPIENTS] =          { US"recipients",       FALSE, FALSE, (unsigned int) ~ACL_BIT_RCPT },
+  [ACLC_RATELIMIT] =           { US"ratelimit",        ACD_EXP,
+                                 FORBIDDEN(0) },
+  [ACLC_RECIPIENTS] =          { US"recipients",       0,
+                                 PERMITTED(ACL_BIT_RCPT) },
 
 #ifdef WITH_CONTENT_SCAN
 
 #ifdef WITH_CONTENT_SCAN
-  [ACLC_REGEX] =               { US"regex",            TRUE, FALSE,
-                                 (unsigned int)
-                                 ~(ACL_BIT_DATA |
+  [ACLC_REGEX] =               { US"regex",            ACD_EXP,
+                                 PERMITTED(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
 # endif
 # ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
 # endif
@@ -283,9 +314,8 @@ static condition_def conditions[] = {
   },
 
 #endif
   },
 
 #endif
-  [ACLC_REMOVE_HEADER] =       { US"remove_header",    TRUE, TRUE,
-                                 (unsigned int)
-                                 ~(ACL_BIT_MAIL|ACL_BIT_RCPT |
+  [ACLC_REMOVE_HEADER] =       { US"remove_header",    ACD_EXP | ACD_MOD,
+                                 PERMITTED(ACL_BIT_MAIL|ACL_BIT_RCPT |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
                                    ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
                                    ACL_BIT_PRDR |
@@ -293,27 +323,29 @@ static condition_def conditions[] = {
                                    ACL_BIT_MIME | ACL_BIT_NOTSMTP |
                                    ACL_BIT_NOTSMTP_START),
   },
                                    ACL_BIT_MIME | ACL_BIT_NOTSMTP |
                                    ACL_BIT_NOTSMTP_START),
   },
-  [ACLC_SEEN] =                        { US"seen",             TRUE, FALSE,    0 },
-  [ACLC_SENDER_DOMAINS] =      { US"sender_domains",   FALSE, FALSE,
-                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+  [ACLC_SEEN] =                        { US"seen",             ACD_EXP,
+                                 FORBIDDEN(0) },
+  [ACLC_SENDER_DOMAINS] =      { US"sender_domains",   0,
+                                 FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
                                    ACL_BIT_HELO |
                                    ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
                                    ACL_BIT_ETRN | ACL_BIT_EXPN |
                                    ACL_BIT_HELO |
                                    ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
                                    ACL_BIT_ETRN | ACL_BIT_EXPN |
-                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY,
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY),
   },
   },
-  [ACLC_SENDERS] =             { US"senders",  FALSE, FALSE,
-                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+  [ACLC_SENDERS] =             { US"senders",  0,
+                                 FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
                                    ACL_BIT_HELO |
                                    ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
                                    ACL_BIT_ETRN | ACL_BIT_EXPN |
                                    ACL_BIT_HELO |
                                    ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
                                    ACL_BIT_ETRN | ACL_BIT_EXPN |
-                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY,
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY),
   },
 
   },
 
-  [ACLC_SET] =                 { US"set",              TRUE, TRUE,     0 },
+  [ACLC_SET] =                 { US"set",              ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
 
 #ifdef WITH_CONTENT_SCAN
 
 #ifdef WITH_CONTENT_SCAN
-  [ACLC_SPAM] =                        { US"spam",             TRUE, FALSE,
-                                 (unsigned int) ~(ACL_BIT_DATA |
+  [ACLC_SPAM] =                        { US"spam",             ACD_EXP,
+                                 PERMITTED(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 # endif
 # ifndef DISABLE_PRDR
                                  ACL_BIT_PRDR |
 # endif
@@ -321,26 +353,36 @@ static condition_def conditions[] = {
   },
 #endif
 #ifdef SUPPORT_SPF
   },
 #endif
 #ifdef SUPPORT_SPF
-  [ACLC_SPF] =                 { US"spf",              TRUE, FALSE,
-                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+  [ACLC_SPF] =                 { US"spf",
+# if SUPPORT_SPF==2
+                                 ACD_LOAD |
+# endif
+                                 ACD_EXP,
+                                 FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
                                    ACL_BIT_HELO | ACL_BIT_MAILAUTH |
                                    ACL_BIT_ETRN | ACL_BIT_EXPN |
                                    ACL_BIT_STARTTLS | ACL_BIT_VRFY |
                                    ACL_BIT_HELO | ACL_BIT_MAILAUTH |
                                    ACL_BIT_ETRN | ACL_BIT_EXPN |
                                    ACL_BIT_STARTTLS | ACL_BIT_VRFY |
-                                   ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+                                   ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START),
   },
   },
-  [ACLC_SPF_GUESS] =           { US"spf_guess",        TRUE, FALSE,
-                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+  [ACLC_SPF_GUESS] =           { US"spf_guess",
+# if SUPPORT_SPF==2
+                                 ACD_LOAD |
+# endif
+                                 ACD_EXP,
+                                 FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
                                    ACL_BIT_HELO | ACL_BIT_MAILAUTH |
                                    ACL_BIT_ETRN | ACL_BIT_EXPN |
                                    ACL_BIT_STARTTLS | ACL_BIT_VRFY |
                                    ACL_BIT_HELO | ACL_BIT_MAILAUTH |
                                    ACL_BIT_ETRN | ACL_BIT_EXPN |
                                    ACL_BIT_STARTTLS | ACL_BIT_VRFY |
-                                   ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+                                   ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START),
   },
 #endif
   },
 #endif
-  [ACLC_UDPSEND] =             { US"udpsend",          TRUE, TRUE,     0 },
+  [ACLC_UDPSEND] =             { US"udpsend",          ACD_EXP | ACD_MOD,
+                                 FORBIDDEN(0) },
 
   /* Certain types of verify are always allowed, so we let it through
   always and check in the verify function itself */
 
   /* Certain types of verify are always allowed, so we let it through
   always and check in the verify function itself */
-  [ACLC_VERIFY] =              { US"verify",           TRUE, FALSE, 0 },
+  [ACLC_VERIFY] =              { US"verify",           ACD_EXP,
+                                 FORBIDDEN(0) },
 };
 
 
 };
 
 
@@ -352,7 +394,7 @@ features_acl(void)
 for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
   {
   uschar buf[64], * p, * s;
 for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
   {
   uschar buf[64], * p, * s;
-  int n = sprintf(CS buf, "_ACL_%s_", c->is_modifier ? "MOD" : "COND");
+  int n = sprintf(CS buf, "_ACL_%s_", c->flags & ACD_MOD ? "MOD" : "COND");
   for (p = buf + n, s = c->name; *s; s++) *p++ = toupper(*s);
   *p = '\0';
   builtin_macro_create(buf);
   for (p = buf + n, s = c->name; *s; s++) *p++ = toupper(*s);
   *p = '\0';
   builtin_macro_create(buf);
@@ -360,11 +402,50 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
 }
 #endif
 
 }
 #endif
 
+/******************************************************************************/
 
 #ifndef MACRO_PREDEF
 
 
 #ifndef MACRO_PREDEF
 
-/* Return values from decode_control(); used as index so keep in step
-with the controls_list table that follows! */
+/* These tables support loading of dynamic modules triggered by an ACL
+condition use, spotted during readconf. See acl_read(). */
+
+# ifdef LOOKUP_MODULE_DIR
+typedef struct condition_module {
+  const uschar *       mod_name;       /* module for the givien conditions */
+  misc_module_info *   info;           /* NULL when not loaded */
+  const int *          conditions;     /* array of ACLC_*, -1 terminated */
+} condition_module;
+
+#  if SUPPORT_SPF==2
+static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 };
+#  endif
+#  if SUPPORT_DKIM==2
+static int dkim_condx[] = { ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, -1 };
+#  endif
+#  if SUPPORT_DMARC==2
+static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 };
+#  endif
+
+/* These are modules which can be loaded on seeing an ACL condition
+during readconf, The "arc" module is handled by custom coding. */
+
+static condition_module condition_modules[] = {
+#  if SUPPORT_SPF==2
+  {.mod_name = US"spf", .conditions = spf_condx},
+#  endif
+#  if SUPPORT_DKIM==2
+  {.mod_name = US"dkim", .conditions = dkim_condx},
+#  endif
+#  if SUPPORT_DMARC==2
+  {.mod_name = US"dmarc", .conditions = dmarc_condx},
+#  endif
+};
+
+# endif        /*LOOKUP_MODULE_DIR*/
+
+/****************************/
+
+/* Return values from decode_control() */
 
 enum {
   CONTROL_AUTH_UNADVERTISED,
 
 enum {
   CONTROL_AUTH_UNADVERTISED,
@@ -404,6 +485,9 @@ enum {
 #ifdef SUPPORT_I18N
   CONTROL_UTF8_DOWNCONVERT,
 #endif
 #ifdef SUPPORT_I18N
   CONTROL_UTF8_DOWNCONVERT,
 #endif
+#ifndef DISABLE_WELLKNOWN
+  CONTROL_WELLKNOWN,
+#endif
 };
 
 
 };
 
 
@@ -557,7 +641,12 @@ static control_def controls_list[] = {
 #ifdef SUPPORT_I18N
 [CONTROL_UTF8_DOWNCONVERT] =
   { US"utf8_downconvert",        TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY)
 #ifdef SUPPORT_I18N
 [CONTROL_UTF8_DOWNCONVERT] =
   { US"utf8_downconvert",        TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY)
-  }
+  },
+#endif
+#ifndef DISABLE_WELLKNOWN
+[CONTROL_WELLKNOWN] =
+  { US"wellknown",               TRUE, (unsigned) ~ACL_BIT_WELLKNOWN
+  },
 #endif
 };
 
 #endif
 };
 
@@ -793,16 +882,16 @@ return TRUE;
 
 static BOOL
 acl_data_to_cond(const uschar * s, acl_condition_block * cond,
 
 static BOOL
 acl_data_to_cond(const uschar * s, acl_condition_block * cond,
-  const uschar * name, uschar ** error)
+  const uschar * name, BOOL taint, uschar ** error)
 {
 if (*s++ != '=')
   {
   *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
 {
 if (*s++ != '=')
   {
   *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
-    conditions[cond->type].is_modifier ? US"modifier" : US"condition");
-  return FALSE;;
+    conditions[cond->type].flags & ACD_MOD ? US"modifier" : US"condition");
+  return FALSE;
   }
 Uskip_whitespace(&s);
   }
 Uskip_whitespace(&s);
-cond->arg = string_copy(s);
+cond->arg = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s);
 return TRUE;
 }
 
 return TRUE;
 }
 
@@ -864,7 +953,7 @@ while ((s = (*func)()))
 
   if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0)
     {
 
   if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0)
     {
-    if (!this)
+    if (!this)         /* not handling a verb right now */
       {
       *error = string_sprintf("unknown ACL verb \"%s\" in \"%s\"", name,
         saveline);
       {
       *error = string_sprintf("unknown ACL verb \"%s\" in \"%s\"", name,
         saveline);
@@ -909,7 +998,7 @@ while ((s = (*func)()))
 
   /* The modifiers may not be negated */
 
 
   /* The modifiers may not be negated */
 
-  if (negated && conditions[c].is_modifier)
+  if (negated && conditions[c].flags & ACD_MOD)
     {
     *error = string_sprintf("ACL error: negation is not allowed with "
       "\"%s\"", conditions[c].name);
     {
     *error = string_sprintf("ACL error: negation is not allowed with "
       "\"%s\"", conditions[c].name);
@@ -927,6 +1016,51 @@ while ((s = (*func)()))
     return NULL;
     }
 
     return NULL;
     }
 
+#ifdef LOOKUP_MODULE_DIR
+  if (conditions[c].flags & ACD_LOAD)
+    {                          /* a loadable module supports this condition */
+    condition_module * cm;
+    uschar * s = NULL;
+
+    /* Over the list of modules we support, check the list of ACL conditions
+    each supports.  This assumes no duplicates. */
+
+    for (cm = condition_modules;
+        cm < condition_modules + nelem(condition_modules); cm++)
+      for (const int * cond = cm->conditions; *cond != -1; cond++)
+       if (*cond == c) goto found;
+    found:
+
+    if (cm >= condition_modules + nelem(condition_modules))
+      {                                                /* shouldn't happen */
+      *error = string_sprintf("ACL error: failed to locate support for '%s'",
+                             conditions[c].name);
+      return NULL;
+      }
+    if (  !cm->info                            /* module not loaded */
+       && !(cm->info = misc_mod_find(cm->mod_name, &s)))
+      {
+      *error = string_sprintf("ACL error: failed to find module for '%s': %s",
+                             conditions[c].name, s);
+      return NULL;
+      }
+    }
+# ifdef EXPERIMENTAL_ARC
+  else if (c == ACLC_VERIFY)   /* Special handling for verify=arc; */
+    {  /* not invented a more general method yet- flag in verify_type_list? */
+    const uschar * t = s;
+    uschar * e;
+    if (  *t++ == '=' && Uskip_whitespace(&t) && Ustrncmp(t, "arc", 3) == 0
+       && !misc_mod_find(US"arc", &e))
+      {
+      *error = string_sprintf("ACL error: failed to find module for '%s': %s",
+                             conditions[c].name, e);
+      return NULL;
+      }
+    }
+# endif
+#endif /*LOOKUP_MODULE_DIR*/
+
   cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
   cond->next = NULL;
   cond->type = c;
   cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
   cond->next = NULL;
   cond->type = c;
@@ -952,7 +1086,7 @@ while ((s = (*func)()))
   "endpass" has no data */
 
   if (c != ACLC_ENDPASS)
   "endpass" has no data */
 
   if (c != ACLC_ENDPASS)
-    if (!acl_data_to_cond(s, cond, name, error)) return NULL;
+    if (!acl_data_to_cond(s, cond, name, FALSE, error)) return NULL;
   }
 
 return yield;
   }
 
 return yield;
@@ -1092,7 +1226,7 @@ for (header_line * h = acl_added_headers; h; h = h->next)
   g = string_append_listele_n(g, '\n', h->text, i);
   }
 
   g = string_append_listele_n(g, '\n', h->text, i);
   }
 
-return g ? g->s : NULL;
+return string_from_gstring(g);
 }
 
 
 }
 
 
@@ -1139,9 +1273,9 @@ Returns:         nothing
 */
 
 static void
 */
 
 static void
-acl_warn(int where, uschar *user_message, uschar *log_message)
+acl_warn(int where, uschar * user_message, uschar * log_message)
 {
 {
-if (log_message != NULL && log_message != user_message)
+if (log_message && log_message != user_message)
   {
   uschar *text;
   string_item *logged;
   {
   uschar *text;
   string_item *logged;
@@ -1152,9 +1286,9 @@ if (log_message != NULL && log_message != user_message)
   /* If a sender verification has failed, and the log message is "sender verify
   failed", add the failure message. */
 
   /* If a sender verification has failed, and the log message is "sender verify
   failed", add the failure message. */
 
-  if (sender_verified_failed != NULL &&
-      sender_verified_failed->message != NULL &&
-      strcmpic(log_message, US"sender verify failed") == 0)
+  if (  sender_verified_failed
+     && sender_verified_failed->message
+     && strcmpic(log_message, US"sender verify failed") == 0)
     text = string_sprintf("%s: %s", text, sender_verified_failed->message);
 
   /* Search previously logged warnings. They are kept in malloc
     text = string_sprintf("%s: %s", text, sender_verified_failed->message);
 
   /* Search previously logged warnings. They are kept in malloc
@@ -1359,7 +1493,7 @@ uschar * target = store_get(TARGET_SIZE, GET_TAINTED);
 client's HELO domain. If the client has not said HELO, use its IP address
 instead. If it's a local client (exim -bs), CSA isn't applicable. */
 
 client's HELO domain. If the client has not said HELO, use its IP address
 instead. If it's a local client (exim -bs), CSA isn't applicable. */
 
-while (isspace(*domain) && *domain != '\0') ++domain;
+while (isspace(*domain) && *domain) ++domain;
 if (*domain == '\0') domain = sender_helo_name;
 if (!domain) domain = sender_host_address;
 if (!sender_host_address) return CSA_UNKNOWN;
 if (*domain == '\0') domain = sender_helo_name;
 if (!domain) domain = sender_host_address;
 if (!sender_host_address) return CSA_UNKNOWN;
@@ -1434,6 +1568,7 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
 
   /* Extract the numerical SRV fields (p is incremented) */
 
 
   /* Extract the numerical SRV fields (p is incremented) */
 
+  if (rr_bad_size(rr, 3 * sizeof(uint16_t))) continue;
   GETSHORT(priority, p);
   GETSHORT(weight, p);
   GETSHORT(port, p);
   GETSHORT(priority, p);
   GETSHORT(weight, p);
   GETSHORT(port, p);
@@ -1703,10 +1838,10 @@ BOOL no_details = FALSE;
 BOOL success_on_redirect = FALSE;
 BOOL quota = FALSE;
 int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT;
 BOOL success_on_redirect = FALSE;
 BOOL quota = FALSE;
 int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT;
-address_item *sender_vaddr = NULL;
-uschar *verify_sender_address = NULL;
-uschar *pm_mailfrom = NULL;
-uschar *se_mailfrom = NULL;
+address_item * sender_vaddr = NULL;
+const 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.
 
 /* 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.
@@ -1778,19 +1913,11 @@ switch(vp->value)
 
 #ifdef EXPERIMENTAL_ARC
   case VERIFY_ARC:
 
 #ifdef EXPERIMENTAL_ARC
   case VERIFY_ARC:
-    {  /* Do Authenticated Received Chain checks in a separate function. */
-    const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0);
-    int csep = 0;
-    uschar * cond;
-
-    if (!(arc_state = acl_verify_arc())) return DEFER;
-    DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
-      arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");
-
-    if (!condlist) condlist = US"none:pass";
-    while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
-      if (Ustrcmp(arc_state, cond) == 0) return OK;
-    return FAIL;
+    {
+    const misc_module_info * mi = misc_mod_findonly(US"arc");
+    typedef int (*fn_t)(const uschar *);
+    if (mi) return (((fn_t *) mi->functions)[ARC_VERIFY])
+                               (CUS string_nextinlist(&list, &sep, NULL, 0));
     }
 #endif
 
     }
 #endif
 
@@ -1859,9 +1986,10 @@ switch(vp->value)
       verify_sender_address = sender_address;
     else
       {
       verify_sender_address = sender_address;
     else
       {
-      while (isspace(*s)) s++;
-      if (*s++ != '=') goto BAD_VERIFY;
-      while (isspace(*s)) s++;
+      if (Uskip_whitespace(&s) != '=')
+       goto BAD_VERIFY;
+      s++;
+      Uskip_whitespace(&s);
       verify_sender_address = string_copy(s);
       }
     }
       verify_sender_address = string_copy(s);
       }
     }
@@ -1903,13 +2031,13 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
     callout = CALLOUT_TIMEOUT_DEFAULT;
     if (*(ss += 7))
       {
     callout = CALLOUT_TIMEOUT_DEFAULT;
     if (*(ss += 7))
       {
-      while (isspace(*ss)) ss++;
+      Uskip_whitespace(&ss);
       if (*ss++ == '=')
         {
        const uschar * sublist = ss;
         int optsep = ',';
 
       if (*ss++ == '=')
         {
        const uschar * sublist = ss;
         int optsep = ',';
 
-        while (isspace(*sublist)) sublist++;
+       Uskip_whitespace(&sublist);
         for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
           {
          callout_opt_t * op;
         for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
           {
          callout_opt_t * op;
@@ -1923,14 +2051,14 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
          if (op->has_option)
            {
            opt += Ustrlen(op->name);
          if (op->has_option)
            {
            opt += Ustrlen(op->name);
-            while (isspace(*opt)) opt++;
+            Uskip_whitespace(&opt);
             if (*opt++ != '=')
               {
               *log_msgptr = string_sprintf("'=' expected after "
                 "\"%s\" in ACL verify condition \"%s\"", op->name, arg);
               return ERROR;
               }
             if (*opt++ != '=')
               {
               *log_msgptr = string_sprintf("'=' expected after "
                 "\"%s\" in ACL verify condition \"%s\"", op->name, arg);
               return ERROR;
               }
-            while (isspace(*opt)) opt++;
+            Uskip_whitespace(&opt);
            }
          if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0)
            return ERROR;
            }
          if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0)
            return ERROR;
@@ -1973,14 +2101,14 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
     quota = TRUE;
     if (*(ss += 5))
       {
     quota = TRUE;
     if (*(ss += 5))
       {
-      while (isspace(*ss)) ss++;
+      Uskip_whitespace(&ss);
       if (*ss++ == '=')
         {
        const uschar * sublist = ss;
         int optsep = ',';
        int period;
 
       if (*ss++ == '=')
         {
        const uschar * sublist = ss;
         int optsep = ',';
        int period;
 
-        while (isspace(*sublist)) sublist++;
+        Uskip_whitespace(&sublist);
         for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
          if (Ustrncmp(opt, "cachepos=", 9) == 0)
            if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
         for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
          if (Ustrncmp(opt, "cachepos=", 9) == 0)
            if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
@@ -2550,6 +2678,7 @@ else switch(mode)
     anchor = NULL; /* silence an "unused" complaint */
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
       "internal ACL error: unknown ratelimit mode %d", mode);
     anchor = NULL; /* silence an "unused" complaint */
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
       "internal ACL error: unknown ratelimit mode %d", mode);
+    /*NOTREACHED*/
     break;
   }
 
     break;
   }
 
@@ -2568,7 +2697,7 @@ if ((t = tree_search(*anchor, key)))
 /* We aren't using a pre-computed rate, so get a previously recorded rate
 from the database, which will be updated and written back if required. */
 
 /* We aren't using a pre-computed rate, so get a previously recorded rate
 from the database, which will be updated and written back if required. */
 
-if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE, TRUE)))
+if (!(dbm = dbfn_open(US"ratelimit", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
   {
   store_pool = old_pool;
   sender_rate = NULL;
   {
   store_pool = old_pool;
   sender_rate = NULL;
@@ -2950,7 +3079,7 @@ while ((ele = string_nextinlist(&list, &slash, NULL, 0)))
   else
     goto badopt;
 
   else
     goto badopt;
 
-if (!(dbm = dbfn_open(US"seen", O_RDWR, &dbblock, TRUE, TRUE)))
+if (!(dbm = dbfn_open(US"seen", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
   {
   HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n");
   *log_msgptr = US"database for 'seen' not available";
   {
   HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n");
   *log_msgptr = US"database for 'seen' not available";
@@ -3112,6 +3241,80 @@ return DEFER;
 
 
 
 
 
 
+#ifndef DISABLE_WELLKNOWN
+/*************************************************
+*   The "wellknown" ACL modifier                 *
+*************************************************/
+
+/* Called by acl_check_condition() below.
+
+Retrieve the given file and encode content as xtext.
+Prefix with a summary line giving the length of plaintext.
+Leave a global pointer to the whole, for output by
+the smtp verb handler code (smtp_in.c).
+
+Arguments:
+  arg          the option string for wellknown=
+  log_msgptr   for error messages
+
+Returns:       OK/FAIL
+*/
+
+static int
+wellknown_process(const uschar * arg, uschar ** log_msgptr)
+{
+struct stat statbuf;
+FILE * rf;
+gstring * g;
+
+wellknown_response = NULL;
+if (f.no_multiline_responses) return FAIL;
+
+/* Check for file existence */
+
+if (!*arg) return FAIL;
+if (Ustat(arg, &statbuf) != 0)
+  { *log_msgptr = US"stat"; goto fail; }
+
+/*XXX perhaps refuse to serve a group- or world-writeable file? */
+
+if (!(rf = Ufopen(arg, "r")))
+  { *log_msgptr = US"open"; goto fail; }
+
+/* Set up summary line for output */
+
+g = string_fmt_append(NULL, "SIZE=%lu\n", (long) statbuf.st_size);
+
+#define LINE_LIM 75
+for (int n = 0, ch; (ch = fgetc(rf)) != EOF; )
+  {
+  /* Xtext-encode, adding output linebreaks for input linebreaks
+  or when the line gets long enough */
+
+  if (ch == '\n')
+    { g = string_fmt_append(g, "+%02X", ch); n = LINE_LIM; }
+  else if (ch < 33 || ch > 126 || ch == '+' || ch == '=')
+    { g = string_fmt_append(g, "+%02X", ch); n += 3; }
+  else
+    { g = string_fmt_append(g, "%c", ch); n++; }
+
+  if (n >= LINE_LIM)
+    { g = string_catn(g, US"\n", 1); n = 0; }
+  }
+#undef LINE_LIM
+
+gstring_release_unused(g);
+wellknown_response = string_from_gstring(g);
+return OK;
+
+fail:
+  *log_msgptr = string_sprintf("wellknown: failed to %s file \"%s\": %s",
+                 *log_msgptr, arg, strerror(errno));
+  return FAIL;
+}
+#endif
+
+
 /*************************************************
 *   Handle conditions/modifiers on an ACL item   *
 *************************************************/
 /*************************************************
 *   Handle conditions/modifiers on an ACL item   *
 *************************************************/
@@ -3186,7 +3389,7 @@ for (; cb; cb = cb->next)
   of them, but not for all, because expansion happens down in some lower level
   checking functions in some cases. */
 
   of them, but not for all, because expansion happens down in some lower level
   checking functions in some cases. */
 
-  if (!conditions[cb->type].expand_at_top)
+  if (!(conditions[cb->type].flags & ACD_EXP))
     arg = cb->arg;
 
   else if (!(arg = expand_string_2(cb->arg, &textonly)))
     arg = cb->arg;
 
   else if (!(arg = expand_string_2(cb->arg, &textonly)))
@@ -3203,7 +3406,7 @@ for (; cb; cb = cb->next)
     {
     int lhswidth = 0;
     debug_printf_indent("check %s%s %n",
     {
     int lhswidth = 0;
     debug_printf_indent("check %s%s %n",
-      (!conditions[cb->type].is_modifier && cb->u.negated)? "!":"",
+      (!(conditions[cb->type].flags & ACD_MOD) && cb->u.negated) ? "!":"",
       conditions[cb->type].name, &lhswidth);
 
     if (cb->type == ACLC_SET)
       conditions[cb->type].name, &lhswidth);
 
     if (cb->type == ACLC_SET)
@@ -3235,7 +3438,7 @@ for (; cb; cb = cb->next)
   if ((conditions[cb->type].forbids & (1 << where)) != 0)
     {
     *log_msgptr = string_sprintf("cannot %s %s condition in %s ACL",
   if ((conditions[cb->type].forbids & (1 << where)) != 0)
     {
     *log_msgptr = string_sprintf("cannot %s %s condition in %s ACL",
-      conditions[cb->type].is_modifier ? "use" : "test",
+      conditions[cb->type].flags & ACD_MOD ? "use" : "test",
       conditions[cb->type].name, acl_wherenames[where]);
     return ERROR;
     }
       conditions[cb->type].name, acl_wherenames[where]);
     return ERROR;
     }
@@ -3301,7 +3504,7 @@ for (; cb; cb = cb->next)
 
     case ACLC_CONTROL:
       {
 
     case ACLC_CONTROL:
       {
-      const uschar *p = NULL;
+      const uschar * p = NULL;
       control_type = decode_control(arg, &p, where, log_msgptr);
 
       /* Check if this control makes sense at this time */
       control_type = decode_control(arg, &p, where, log_msgptr);
 
       /* Check if this control makes sense at this time */
@@ -3313,6 +3516,7 @@ for (; cb; cb = cb->next)
        return ERROR;
        }
 
        return ERROR;
        }
 
+      /*XXX ought to sort these, just for sanity */
       switch(control_type)
        {
        case CONTROL_AUTH_UNADVERTISED:
       switch(control_type)
        {
        case CONTROL_AUTH_UNADVERTISED:
@@ -3427,7 +3631,7 @@ for (; cb; cb = cb->next)
        case CONTROL_FAKEREJECT:
          cancel_cutthrough_connection(TRUE, US"fakereject");
        case CONTROL_FAKEDEFER:
        case CONTROL_FAKEREJECT:
          cancel_cutthrough_connection(TRUE, US"fakereject");
        case CONTROL_FAKEDEFER:
-         fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
+         fake_response = control_type == CONTROL_FAKEDEFER ? DEFER : FAIL;
          if (*p == '/')
            {
            const uschar *pp = p + 1;
          if (*p == '/')
            {
            const uschar *pp = p + 1;
@@ -3658,8 +3862,13 @@ for (; cb; cb = cb->next)
            break;
            }
          return ERROR;
            break;
            }
          return ERROR;
-#endif
+#endif /*I18N*/
 
 
+#ifndef DISABLE_WELLKNOWN
+       case CONTROL_WELLKNOWN:
+         rc = *p == '/' ? wellknown_process(p+1, log_msgptr) : FAIL;
+         break;
+#endif
        }
       break;
       }
        }
       break;
       }
@@ -3754,28 +3963,48 @@ for (; cb; cb = cb->next)
 
 #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
 
 #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
-      if (dkim_cur_signer)
-       rc = match_isinlist(dkim_cur_signer,
-                          &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
-      else
-       rc = FAIL;
-      break;
-
     case ACLC_DKIM_STATUS:
     case ACLC_DKIM_STATUS:
-      rc = match_isinlist(dkim_verify_status,
-                         &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+      /* See comment on ACLC_SPF wrt. coding issues */
+      {
+      misc_module_info * mi = misc_mod_find(US"dkim", &log_message);
+      typedef int (*fn_t)(const uschar *);
+      rc = mi
+       ? (((fn_t *) mi->functions)
+                     [cb->type == ACLC_DKIM_SIGNER
+                       ? DKIM_SIGNER_ISINLIST
+                       : DKIM_STATUS_LISTMATCH]) (arg)
+       : DEFER;
       break;
       break;
+      }
 #endif
 
 #ifdef SUPPORT_DMARC
     case ACLC_DMARC_STATUS:
 #endif
 
 #ifdef SUPPORT_DMARC
     case ACLC_DMARC_STATUS:
+      /* See comment on ACLC_SPF wrt. coding issues */
+      {
+      misc_module_info * mi = misc_mod_find(US"dmarc", &log_message);
+      typedef uschar * (*efn_t)(int);
+      uschar * expanded_query;
+
+      if (!mi)
+       { rc = DEFER; break; }                  /* shouldn't happen */
+
       if (!f.dmarc_has_been_checked)
       if (!f.dmarc_has_been_checked)
-       dmarc_process();
-      f.dmarc_has_been_checked = TRUE;
+       {
+       typedef int (*pfn_t)(void);
+       (void) (((pfn_t *) mi->functions)[DMARC_PROCESS]) ();
+       f.dmarc_has_been_checked = TRUE;
+       }
+
       /* used long way of dmarc_exim_expand_query() in case we need more
       /* used long way of dmarc_exim_expand_query() in case we need more
-       * view into the process in the future. */
-      rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
+      view into the process in the future. */
+
+      /*XXX is this call used with any other arg? */
+      expanded_query = (((efn_t *) mi->functions)[DMARC_EXPAND_QUERY])
+                                                     (DMARC_VERIFY_STATUS);
+      rc = match_isinlist(expanded_query,
                          &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
                          &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+      }
       break;
 #endif
 
       break;
 #endif
 
@@ -3876,7 +4105,7 @@ for (; cb; cb = cb->next)
           }
         s++;
         }
           }
         s++;
         }
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
 
       if (logbits == 0) logbits = LOG_MAIN;
       log_write(0, logbits, "%s", string_printing(s));
 
       if (logbits == 0) logbits = LOG_MAIN;
       log_write(0, logbits, "%s", string_printing(s));
@@ -3939,11 +4168,11 @@ for (; cb; cb = cb->next)
        CUSS &recipient_data);
       break;
 
        CUSS &recipient_data);
       break;
 
-    #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
     case ACLC_REGEX:
       rc = regex(&arg, textonly);
       break;
     case ACLC_REGEX:
       rc = regex(&arg, textonly);
       break;
-    #endif
+#endif
 
     case ACLC_REMOVE_HEADER:
       setup_remove_header(arg);
 
     case ACLC_REMOVE_HEADER:
       setup_remove_header(arg);
@@ -3979,11 +4208,19 @@ for (; cb; cb = cb->next)
 #endif
         )
         store_pool = POOL_PERM;
 #endif
         )
         store_pool = POOL_PERM;
+
 #ifndef DISABLE_DKIM   /* Overwriteable dkim result variables */
 #ifndef DISABLE_DKIM   /* Overwriteable dkim result variables */
-      if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0)
-       dkim_verify_status = string_copy(arg);
-      else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
-       dkim_verify_reason = string_copy(arg);
+      if (  Ustrcmp(cb->u.varname, "dkim_verify_status") == 0
+        || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0
+         )
+         {
+         misc_module_info * mi = misc_mod_findonly(US"dkim");
+         typedef void (*fn_t)(const uschar *, void *);
+         
+         if (mi)
+           (((fn_t *) mi->functions)[DKIM_SETVAR])
+                                       (cb->u.varname, string_copy(arg));
+         }
       else
 #endif
        acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
       else
 #endif
        acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
@@ -4010,12 +4247,28 @@ for (; cb; cb = cb->next)
 
 #ifdef SUPPORT_SPF
     case ACLC_SPF:
 
 #ifdef SUPPORT_SPF
     case ACLC_SPF:
-      rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL);
-      break;
-
     case ACLC_SPF_GUESS:
     case ACLC_SPF_GUESS:
-      rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS);
+      /* We have hardwired function-call numbers, and also prototypes for the
+      functions.  We could do a function name table search or (simpler)
+      a module include file with defines for the numbers
+      but I can't see how to deal with prototypes.  Is a K&R non-prototyped
+      function still usable with today's compilers (but we would lose on
+      type-checking)?  We could macroize the typedef, and even the function
+      table access - but it obscures how it works rather. */
+      {
+      misc_module_info * mi = misc_mod_find(US"spf", &log_message);
+      typedef int (*fn_t)(const uschar **, const uschar *, int);
+      fn_t fn;
+
+      if (!mi)
+       { rc = DEFER; break; }                  /* shouldn't happen */
+
+      fn = ((fn_t *) mi->functions)[SPF_PROCESS];
+
+      rc = fn(&arg, sender_address,
+             cb->type == ACLC_SPF ? SPF_PROCESS_NORMAL : SPF_PROCESS_GUESS);
       break;
       break;
+      }
 #endif
 
     case ACLC_UDPSEND:
 #endif
 
     case ACLC_UDPSEND:
@@ -4043,7 +4296,7 @@ for (; cb; cb = cb->next)
 
   /* If a condition was negated, invert OK/FAIL. */
 
 
   /* If a condition was negated, invert OK/FAIL. */
 
-  if (!conditions[cb->type].is_modifier && cb->u.negated)
+  if (!(conditions[cb->type].flags & ACD_MOD) && cb->u.negated)
     if (rc == OK) rc = FAIL;
     else if (rc == FAIL || rc == FAIL_DROP) rc = OK;
 
     if (rc == OK) rc = FAIL;
     else if (rc == FAIL || rc == FAIL_DROP) rc = OK;
 
@@ -4300,19 +4553,17 @@ if (!s)
 /* At top level, we expand the incoming string. At lower levels, it has already
 been expanded as part of condition processing. */
 
 /* At top level, we expand the incoming string. At lower levels, it has already
 been expanded as part of condition processing. */
 
-if (acl_level == 0)
+if (acl_level != 0)
+  ss = s;
+else if (!(ss = expand_string(s)))
   {
   {
-  if (!(ss = expand_string(s)))
-    {
-    if (f.expand_string_forcedfail) return OK;
-    *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
-      expand_string_message);
-    return ERROR;
-    }
+  if (f.expand_string_forcedfail) return OK;
+  *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
+    expand_string_message);
+  return ERROR;
   }
   }
-else ss = s;
 
 
-while (isspace(*ss)) ss++;
+Uskip_whitespace(&ss);
 
 /* If we can't find a named ACL, the default is to parse it as an inline one.
 (Unless it begins with a slash; non-existent files give rise to an error.) */
 
 /* If we can't find a named ACL, the default is to parse it as an inline one.
 (Unless it begins with a slash; non-existent files give rise to an error.) */
@@ -4344,7 +4595,7 @@ if (Ustrchr(ss, ' ') == NULL)
       HDEBUG(D_acl) debug_printf_indent("ACL \"%s\" is empty: implicit DENY\n", ss);
       return FAIL;
       }
       HDEBUG(D_acl) debug_printf_indent("ACL \"%s\" is empty: implicit DENY\n", ss);
       return FAIL;
       }
-    acl_name = string_sprintf("ACL \"%s\"", ss);
+    acl_name = string_sprintf("ACL %s", ss);
     HDEBUG(D_acl) debug_printf_indent("using ACL \"%s\"\n", ss);
     }
 
     HDEBUG(D_acl) debug_printf_indent("using ACL \"%s\"\n", ss);
     }
 
@@ -4377,7 +4628,7 @@ if (Ustrchr(ss, ' ') == NULL)
     acl_text[statbuf.st_size] = 0;
     (void)close(fd);
 
     acl_text[statbuf.st_size] = 0;
     (void)close(fd);
 
-    acl_name = string_sprintf("ACL \"%s\"", ss);
+    acl_name = string_sprintf("ACL %s", ss);
     HDEBUG(D_acl) debug_printf_indent("read ACL from file %s\n", ss);
     }
   }
     HDEBUG(D_acl) debug_printf_indent("read ACL from file %s\n", ss);
     }
   }
@@ -4415,8 +4666,15 @@ while ((acl_current = acl))
   *log_msgptr = *user_msgptr = NULL;
   f.acl_temp_details = FALSE;
 
   *log_msgptr = *user_msgptr = NULL;
   f.acl_temp_details = FALSE;
 
-  HDEBUG(D_acl) debug_printf_indent("processing \"%s\" (%s %d)\n",
-    verbs[acl->verb], acl->srcfile, acl->srcline);
+  config_filename = acl->srcfile;
+  config_lineno = acl->srcline;
+
+  HDEBUG(D_acl)
+    {
+    debug_printf_indent("processing %s \"%s\"", acl_name, verbs[acl->verb]);
+    if (config_lineno) debug_printf(" (%s %d)", config_filename, config_lineno);
+    debug_printf("\n");
+    }
 
   /* Clear out any search error message from a previous check before testing
   this condition. */
 
   /* Clear out any search error message from a previous check before testing
   this condition. */
@@ -4435,7 +4693,7 @@ while ((acl_current = acl))
        verbs[acl->verb], acl_name);
       if (basic_errno != ERRNO_CALLOUTDEFER)
        {
        verbs[acl->verb], acl_name);
       if (basic_errno != ERRNO_CALLOUTDEFER)
        {
-       if (search_error_message != NULL && *search_error_message != 0)
+       if (search_error_message && *search_error_message)
          *log_msgptr = search_error_message;
        if (smtp_return_error_details) f.acl_temp_details = TRUE;
        }
          *log_msgptr = search_error_message;
        if (smtp_return_error_details) f.acl_temp_details = TRUE;
        }
@@ -4549,10 +4807,18 @@ while ((acl_current = acl))
       if (cond == OK)
        acl_warn(where, *user_msgptr, *log_msgptr);
       else if (cond == DEFER && LOGGING(acl_warn_skipped))
       if (cond == OK)
        acl_warn(where, *user_msgptr, *log_msgptr);
       else if (cond == DEFER && LOGGING(acl_warn_skipped))
-       log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: "
-         "condition test deferred%s%s", host_and_ident(TRUE),
-         *log_msgptr ? US": " : US"",
-         *log_msgptr ? *log_msgptr : US"");
+       if (config_lineno > 0)
+         log_write(0, LOG_MAIN,
+           "%s Warning: ACL 'warn' statement skipped (in %s at line %d of %s):"
+           " condition test deferred%s%s",
+           host_and_ident(TRUE), acl_name, config_lineno, config_filename,
+           *log_msgptr ? US": " : US"", *log_msgptr ? *log_msgptr : US"");
+       else
+         log_write(0, LOG_MAIN,
+           "%s Warning: ACL 'warn' statement skipped (in %s):"
+           " condition test deferred%s%s",
+           host_and_ident(TRUE), acl_name,
+           *log_msgptr ? US": " : US"", *log_msgptr ? *log_msgptr : US"");
       *log_msgptr = *user_msgptr = NULL;  /* In case implicit DENY follows */
       break;
 
       *log_msgptr = *user_msgptr = NULL;  /* In case implicit DENY follows */
       break;
 
@@ -4601,8 +4867,8 @@ if (!(tmp = string_dequote(&s)) || !(name = expand_string(tmp)))
 
 for (i = 0; i < 9; i++)
   {
 
 for (i = 0; i < 9; i++)
   {
-  while (*s && isspace(*s)) s++;
-  if (!*s) break;
+  if (!Uskip_whitespace(&s))
+    break;
   if (!(tmp = string_dequote(&s)) || !(tmp_arg[i] = expand_string(tmp)))
     {
     tmp = name;
   if (!(tmp = string_dequote(&s)) || !(tmp_arg[i] = expand_string(tmp)))
     {
     tmp = name;
@@ -4626,6 +4892,7 @@ while (i < 9)
 acl_level++;
 ret = acl_check_internal(where, addr, name, user_msgptr, log_msgptr);
 acl_level--;
 acl_level++;
 ret = acl_check_internal(where, addr, name, user_msgptr, log_msgptr);
 acl_level--;
+config_lineno = 0;
 
 acl_narg = sav_narg;
 for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i];
 
 acl_narg = sav_narg;
 for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i];
@@ -4671,6 +4938,7 @@ if (where == ACL_WHERE_RCPT)
 acl_level++;
 rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
 acl_level--;
 acl_level++;
 rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
 acl_level--;
+config_lineno = 0;
 return rc;
 }
 
 return rc;
 }
 
@@ -4694,11 +4962,10 @@ Returns:       OK         access is granted by an ACCEPT verb
                DEFER      can't tell at the moment
                ERROR      disaster
 */
                DEFER      can't tell at the moment
                ERROR      disaster
 */
-int acl_where = ACL_WHERE_UNKNOWN;
 
 int
 
 int
-acl_check(int where, uschar *recipient, uschar *s, uschar **user_msgptr,
-  uschar **log_msgptr)
+acl_check(int where, const uschar * recipient, uschar * s,
+  uschar ** user_msgptr, uschar ** log_msgptr)
 {
 int rc;
 address_item adb;
 {
 int rc;
 address_item adb;
@@ -4739,6 +5006,7 @@ acl_level = 0;
 rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
 acl_level = 0;
 acl_where = ACL_WHERE_UNKNOWN;
 rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
 acl_level = 0;
 acl_where = ACL_WHERE_UNKNOWN;
+config_lineno = 0;
 
 /* Cutthrough - if requested,
 and WHERE_RCPT and not yet opened conn as result of recipient-verify,
 
 /* Cutthrough - if requested,
 and WHERE_RCPT and not yet opened conn as result of recipient-verify,
@@ -4914,9 +5182,11 @@ FILE * f = (FILE *)ctx;
 putc('-', f);
 if (is_tainted(value))
   {
 putc('-', f);
 if (is_tainted(value))
   {
-  int q = quoter_for_address(value);
+  const uschar * quoter_name;
   putc('-', f);
   putc('-', f);
-  if (is_real_quoter(q)) fprintf(f, "(%s)", lookup_list[q]->name);
+  (void) quoter_for_address(value, &quoter_name);
+  if (quoter_name)
+    fprintf(f, "(%s)", quoter_name);
   }
 fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 }
   }
 fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 }
@@ -4925,7 +5195,7 @@ fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 
 
 uschar *
 
 
 uschar *
-acl_standalone_setvar(const uschar * s)
+acl_standalone_setvar(const uschar * s, BOOL taint)
 {
 acl_condition_block * cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
 uschar * errstr = NULL, * log_msg = NULL;
 {
 acl_condition_block * cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
 uschar * errstr = NULL, * log_msg = NULL;
@@ -4935,7 +5205,7 @@ int e;
 cond->next = NULL;
 cond->type = ACLC_SET;
 if (!acl_varname_to_cond(&s, cond, &errstr)) return errstr;
 cond->next = NULL;
 cond->type = ACLC_SET;
 if (!acl_varname_to_cond(&s, cond, &errstr)) return errstr;
-if (!acl_data_to_cond(s, cond, US"'-be'", &errstr)) return errstr;
+if (!acl_data_to_cond(s, cond, US"'-be'", taint, &errstr)) return errstr;
 
 if (acl_check_condition(ACL_WARN, cond, ACL_WHERE_UNKNOWN,
                            NULL, 0, &endpass_seen, &errstr, &log_msg, &e) != OK)
 
 if (acl_check_condition(ACL_WARN, cond, ACL_WHERE_UNKNOWN,
                            NULL, 0, &endpass_seen, &errstr, &log_msg, &e) != OK)