build: use pkg-config for i18n
[exim.git] / src / src / readconf.c
index e77458d683a9da89a48e2405a6d2e7546d70ae62..5ef776fea048e8730ffd548fdda97b1a4d006132 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2023 */
+/* 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 */
@@ -48,7 +48,7 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_data_prdr",       opt_stringptr,   {&acl_smtp_data_prdr} },
 #endif
 #ifndef DISABLE_DKIM
   { "acl_smtp_data_prdr",       opt_stringptr,   {&acl_smtp_data_prdr} },
 #endif
 #ifndef DISABLE_DKIM
-  { "acl_smtp_dkim",            opt_stringptr,   {&acl_smtp_dkim} },
+  { "acl_smtp_dkim",            opt_module,     {US"dkim"} },
 #endif
   { "acl_smtp_etrn",            opt_stringptr,   {&acl_smtp_etrn} },
   { "acl_smtp_expn",            opt_stringptr,   {&acl_smtp_expn} },
 #endif
   { "acl_smtp_etrn",            opt_stringptr,   {&acl_smtp_etrn} },
   { "acl_smtp_expn",            opt_stringptr,   {&acl_smtp_expn} },
@@ -66,6 +66,9 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_starttls",        opt_stringptr,   {&acl_smtp_starttls} },
 #endif
   { "acl_smtp_vrfy",            opt_stringptr,   {&acl_smtp_vrfy} },
   { "acl_smtp_starttls",        opt_stringptr,   {&acl_smtp_starttls} },
 #endif
   { "acl_smtp_vrfy",            opt_stringptr,   {&acl_smtp_vrfy} },
+#ifndef DISABLE_WELLKNOWN
+  { "acl_smtp_wellknown",       opt_stringptr,   {&acl_smtp_wellknown} },
+#endif
   { "add_environment",          opt_stringptr,   {&add_environment} },
   { "admin_groups",             opt_gidlist,     {&admin_groups} },
   { "allow_domain_literals",    opt_bool,        {&allow_domain_literals} },
   { "add_environment",          opt_stringptr,   {&add_environment} },
   { "admin_groups",             opt_gidlist,     {&admin_groups} },
   { "allow_domain_literals",    opt_bool,        {&allow_domain_literals} },
@@ -119,16 +122,16 @@ static optionlist optionlist_config[] = {
 #endif
   { "disable_ipv6",             opt_bool,        {&disable_ipv6} },
 #ifndef DISABLE_DKIM
 #endif
   { "disable_ipv6",             opt_bool,        {&disable_ipv6} },
 #ifndef DISABLE_DKIM
-  { "dkim_verify_hashes",       opt_stringptr,   {&dkim_verify_hashes} },
-  { "dkim_verify_keytypes",     opt_stringptr,   {&dkim_verify_keytypes} },
-  { "dkim_verify_min_keysizes", opt_stringptr,   {&dkim_verify_min_keysizes} },
-  { "dkim_verify_minimal",      opt_bool,        {&dkim_verify_minimal} },
-  { "dkim_verify_signers",      opt_stringptr,   {&dkim_verify_signers} },
+  { "dkim_verify_hashes",       opt_module,     {US"dkim"} },
+  { "dkim_verify_keytypes",     opt_module,     {US"dkim"} },
+  { "dkim_verify_min_keysizes", opt_module,     {US"dkim"} },
+  { "dkim_verify_minimal",      opt_module,     {US"dkim"} },
+  { "dkim_verify_signers",      opt_module,     {US"dkim"} },
 #endif
 #ifdef SUPPORT_DMARC
 #endif
 #ifdef SUPPORT_DMARC
-  { "dmarc_forensic_sender",    opt_stringptr,   {&dmarc_forensic_sender} },
-  { "dmarc_history_file",       opt_stringptr,   {&dmarc_history_file} },
-  { "dmarc_tld_file",           opt_stringptr,   {&dmarc_tld_file} },
+  { "dmarc_forensic_sender",    opt_module,     {US"dmarc"} },
+  { "dmarc_history_file",       opt_module,     {US"dmarc"} },
+  { "dmarc_tld_file",           opt_module,     {US"dmarc"} },
 #endif
   { "dns_again_means_nonexist", opt_stringptr,   {&dns_again_means_nonexist} },
   { "dns_check_names_pattern",  opt_stringptr,   {&check_dns_names_pattern} },
 #endif
   { "dns_again_means_nonexist", opt_stringptr,   {&dns_again_means_nonexist} },
   { "dns_check_names_pattern",  opt_stringptr,   {&check_dns_names_pattern} },
@@ -209,7 +212,7 @@ static optionlist optionlist_config[] = {
   { "ldap_start_tls",           opt_bool,        {&eldap_start_tls} },
   { "ldap_version",             opt_int,         {&eldap_version} },
 #endif
   { "ldap_start_tls",           opt_bool,        {&eldap_start_tls} },
   { "ldap_version",             opt_int,         {&eldap_version} },
 #endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   { "limits_advertise_hosts", opt_stringptr, {&limits_advertise_hosts} },
 #endif
   { "local_from_check",         opt_bool,        {&local_from_check} },
   { "limits_advertise_hosts", opt_stringptr, {&limits_advertise_hosts} },
 #endif
   { "local_from_check",         opt_bool,        {&local_from_check} },
@@ -293,7 +296,7 @@ static optionlist optionlist_config[] = {
   { "received_header_text",     opt_stringptr,   {&received_header_text} },
   { "received_headers_max",     opt_int,         {&received_headers_max} },
   { "recipient_unqualified_hosts", opt_stringptr, {&recipient_unqualified_hosts} },
   { "received_header_text",     opt_stringptr,   {&received_header_text} },
   { "received_headers_max",     opt_int,         {&received_headers_max} },
   { "recipient_unqualified_hosts", opt_stringptr, {&recipient_unqualified_hosts} },
-  { "recipients_max",           opt_int,         {&recipients_max} },
+  { "recipients_max",           opt_stringptr,   {&recipients_max} },
   { "recipients_max_reject",    opt_bool,        {&recipients_max_reject} },
 #ifdef LOOKUP_REDIS
   { "redis_servers",            opt_stringptr,   {&redis_servers} },
   { "recipients_max_reject",    opt_bool,        {&recipients_max_reject} },
 #ifdef LOOKUP_REDIS
   { "redis_servers",            opt_stringptr,   {&redis_servers} },
@@ -341,8 +344,8 @@ static optionlist optionlist_config[] = {
   { "spamd_address",            opt_stringptr,   {&spamd_address} },
 #endif
 #ifdef SUPPORT_SPF
   { "spamd_address",            opt_stringptr,   {&spamd_address} },
 #endif
 #ifdef SUPPORT_SPF
-  { "spf_guess",                opt_stringptr,   {&spf_guess} },
-  { "spf_smtp_comment_template",opt_stringptr,   {&spf_smtp_comment_template} },
+  { "spf_guess",                opt_module,     {US"spf"} },
+  { "spf_smtp_comment_template",opt_module,     {US"spf"} },
 #endif
   { "split_spool_directory",    opt_bool,        {&split_spool_directory} },
   { "spool_directory",          opt_stringptr,   {&spool_directory} },
 #endif
   { "split_spool_directory",    opt_bool,        {&split_spool_directory} },
   { "spool_directory",          opt_stringptr,   {&spool_directory} },
@@ -402,6 +405,9 @@ static optionlist optionlist_config[] = {
   { "uucp_from_pattern",        opt_stringptr,   {&uucp_from_pattern} },
   { "uucp_from_sender",         opt_stringptr,   {&uucp_from_sender} },
   { "warn_message_file",        opt_stringptr,   {&warn_message_file} },
   { "uucp_from_pattern",        opt_stringptr,   {&uucp_from_pattern} },
   { "uucp_from_sender",         opt_stringptr,   {&uucp_from_sender} },
   { "warn_message_file",        opt_stringptr,   {&warn_message_file} },
+#ifndef DISABLE_WELLKNOWN
+  { "wellknown_advertise_hosts",opt_stringptr,  {&wellknown_advertise_hosts} },
+#endif
   { "write_rejectlog",          opt_bool,        {&write_rejectlog} },
 };
 
   { "write_rejectlog",          opt_bool,        {&write_rejectlog} },
 };
 
@@ -426,13 +432,17 @@ options_auths(void)
 {
 uschar buf[EXIM_DRIVERNAME_MAX];
 
 {
 uschar buf[EXIM_DRIVERNAME_MAX];
 
-options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL);
+options_from_list(optionlist_auths, optionlist_auths_size,
+  US"AUTHENTICATORS", NULL);
 
 
-for (struct auth_info * ai = auths_available; ai->driver_name[0]; ai++)
+for (driver_info * di = (driver_info *)auths_available; di; di = di->next)
   {
   {
-  spf(buf, sizeof(buf), US"_DRIVER_AUTHENTICATOR_%T", ai->driver_name);
+  auth_info * ai = (auth_info *)di;
+
+  spf(buf, sizeof(buf), US"_DRIVER_AUTHENTICATOR_%T", di->driver_name);
   builtin_macro_create(buf);
   builtin_macro_create(buf);
-  options_from_list(ai->options, (unsigned)*ai->options_count, US"AUTHENTICATOR", ai->driver_name);
+  options_from_list(di->options, (unsigned)*di->options_count,
+    US"AUTHENTICATOR", di->driver_name);
 
   if (ai->macros_create) (ai->macros_create)();
   }
 
   if (ai->macros_create) (ai->macros_create)();
   }
@@ -592,46 +602,115 @@ static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
 pointer variables in the options table or in option tables for various drivers.
 For debugging output, it is useful to be able to find the name of the option
 which is currently being processed. This function finds it, if it exists, by
 pointer variables in the options table or in option tables for various drivers.
 For debugging output, it is useful to be able to find the name of the option
 which is currently being processed. This function finds it, if it exists, by
-searching the table(s).
+searching the table(s) for a value with the given content.
 
 Arguments:   a value that is presumed to be in the table above
 Returns:     the option name, or an empty string
 */
 
 
 Arguments:   a value that is presumed to be in the table above
 Returns:     the option name, or an empty string
 */
 
-uschar *
-readconf_find_option(void *p)
+const uschar *
+readconf_find_option(void * listptr)
 {
 {
-for (int i = 0; i < nelem(optionlist_config); i++)
-  if (p == optionlist_config[i].v.value) return US optionlist_config[i].name;
+uschar * list = * USS listptr;
+const uschar * name = NULL, * drname = NULL;
 
 
-for (router_instance * r = routers; r; r = r->next)
-  {
-  router_info *ri = r->info;
-  for (int i = 0; i < *ri->options_count; i++)
-    {
-    if ((ri->options[i].type & opt_mask) != opt_stringptr) continue;
-    if (p == CS (r->options_block) + ri->options[i].v.offset)
-      return US ri->options[i].name;
-    }
-  }
+for (optionlist * o = optionlist_config;              /* main-config options */
+     o < optionlist_config + optionlist_config_size; o++)
+  if (listptr == o->v.value)
+    return US o->name;
 
 
-for (transport_instance * t = transports; t; t = t->next)
-  {
-  transport_info *ti = t->info;
-  for (int i = 0; i < *ti->options_count; i++)
+if (router_name)
+  for (const driver_instance * rd = (driver_instance *)routers;
+       rd; rd = rd->next) if (Ustrcmp(rd->name, router_name) == 0)
     {
     {
-    optionlist * op = &ti->options[i];
-    if ((op->type & opt_mask) != opt_stringptr) continue;
-    if (p == (  op->type & opt_public
-            ? CS t
-            : CS t->options_block
-            )
-            + op->v.offset)
-       return US op->name;
+    const router_instance * r = (router_instance *)rd;
+    const router_info * ri = (router_info *)rd->info;
+
+    /* Check for a listptr match first */
+
+    for (optionlist * o = optionlist_routers;          /* generic options */
+       o < optionlist_routers + optionlist_routers_size; o++)
+      if (  (o->type & opt_mask) == opt_stringptr
+        && listptr == CS r + o->v.offset)
+       return US o->name;
+
+    for (optionlist * o = ri->drinfo.options;          /* private options */
+       o < ri->drinfo.options + *ri->drinfo.options_count; o++)
+      if (  (o->type & opt_mask) == opt_stringptr
+        && listptr == CS rd->options_block + o->v.offset)
+       return US o->name;
+
+    /* Check for a list addr match, unless null */
+
+    if (!list) continue;
+
+    for (optionlist * o = optionlist_routers;          /* generic options */
+       o < optionlist_routers + optionlist_routers_size; o++)
+      if (  (o->type & opt_mask) == opt_stringptr
+        && list == * USS(CS r + o->v.offset))
+       if (name)
+         return string_sprintf("DUP: %s %s vs. %s %s",
+                               drname, name, rd->name, o->name);
+       else
+         { name = US o->name; drname = rd->name; }
+
+    for (optionlist * o = ri->drinfo.options;          /* private options */
+       o < ri->drinfo.options + *ri->drinfo.options_count; o++)
+      if (  (o->type & opt_mask) == opt_stringptr
+        && list == * USS(CS rd->options_block + o->v.offset))
+       if (name)
+         return string_sprintf("DUP: %s %s vs. %s %s",
+                               drname, name, rd->name, o->name);
+       else
+         { name = US o->name; drname = rd->name; }
     }
     }
-  }
 
 
-return US"";
+if (transport_name)
+  for (transport_instance * t = transports; t; t = t->drinst.next)
+    if (Ustrcmp(t->drinst.name, transport_name) == 0)
+      {
+      const transport_info * ti = t->drinst.info;
+
+      /* Check for a listptr match first */
+
+      for (optionlist * o = optionlist_transports;     /* generic options */
+         o < optionlist_transports + optionlist_transports_size; o++)
+       if (  (o->type & opt_mask) == opt_stringptr
+          && listptr == CS t + o->v.offset)
+         return US o->name;
+
+      for (optionlist * o = ti->drinfo.options;                /* private options */
+         o < ti->drinfo.options + *ti->drinfo.options_count; o++)
+       if (  (o->type & opt_mask) == opt_stringptr
+          && listptr == CS t->drinst.options_block + o->v.offset)
+         return US o->name;
+
+      /* Check for a list addr match, unless null */
+
+      if (!list) continue;
+
+      for (optionlist * o = optionlist_transports;     /* generic options */
+         o < optionlist_transports + optionlist_transports_size; o++)
+       if (  (o->type & opt_mask) == opt_stringptr
+          && list == * USS(CS t + o->v.offset))
+         if (name)
+           return string_sprintf("DUP: %s %s vs. %s %s",
+                                 drname, name, t->drinst.name, o->name);
+         else
+           { name = US o->name; drname = t->drinst.name; }
+
+      for (optionlist * o = ti->drinfo.options;                /* private options */
+         o < ti->drinfo.options + *ti->drinfo.options_count; o++)
+       if (  (o->type & opt_mask) == opt_stringptr
+          && list == * USS(CS t->drinst.options_block + o->v.offset))
+         if (name)
+           return string_sprintf("DUP: %s %s vs. %s %s",
+                                 drname, name, t->drinst.name, o->name);
+         else
+           { name = US o->name; drname = t->drinst.name; }
+      }
+
+return name ? name : US"";
 }
 
 
 }
 
 
@@ -1050,7 +1129,7 @@ for (;;)
          (Ustrncmp(ss+8, "_if_exists", 10) == 0 && isspace(ss[18]))))
     {
     uschar *t;
          (Ustrncmp(ss+8, "_if_exists", 10) == 0 && isspace(ss[18]))))
     {
     uschar *t;
-    int include_if_exists = isspace(ss[8])? 0 : 10;
+    int include_if_exists = isspace(ss[8]) ? 0 : 10;
     config_file_item *save;
     struct stat statbuf;
 
     config_file_item *save;
     struct stat statbuf;
 
@@ -1153,6 +1232,12 @@ if (strncmpic(s, US"begin ", 6) == 0)
   return NULL;
   }
 
   return NULL;
   }
 
+#ifdef LOOKUP_MODULE_DIR
+/* Check for any required module load operations */
+
+//mod_load_check(s);
+#endif
+
 /* Return the first non-blank character. */
 
 return s;
 /* Return the first non-blank character. */
 
 return s;
@@ -1622,7 +1707,7 @@ static BOOL
 readconf_handle_option(uschar *buffer, optionlist *oltop, int last,
   void *data_block, uschar *unknown_txt)
 {
 readconf_handle_option(uschar *buffer, optionlist *oltop, int last,
   void *data_block, uschar *unknown_txt)
 {
-int ptr = 0;
+int ptr;
 int offset = 0;
 int count, type, value;
 int issecure = 0;
 int offset = 0;
 int count, type, value;
 int issecure = 0;
@@ -1636,16 +1721,20 @@ rmark reset_point;
 int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
 int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
-const uschar * s = buffer;
+const uschar * s;
 uschar **str_target;
 uschar name[EXIM_DRIVERNAME_MAX];
 uschar name2[EXIM_DRIVERNAME_MAX];
 
 uschar **str_target;
 uschar name[EXIM_DRIVERNAME_MAX];
 uschar name2[EXIM_DRIVERNAME_MAX];
 
+sublist:
+
+s = buffer;
+ptr = 0;
+
 /* There may be leading spaces; thereafter, we expect an option name starting
 with a letter. */
 
 /* There may be leading spaces; thereafter, we expect an option name starting
 with a letter. */
 
-while (isspace(*s)) s++;
-if (!isalpha(*s))
+if (!isalpha( Uskip_whitespace(&s) ))
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "option setting expected: %s", s);
 
 /* Read the name of the option, and skip any subsequent white space. If
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "option setting expected: %s", s);
 
 /* Read the name of the option, and skip any subsequent white space. If
@@ -1660,7 +1749,7 @@ for (int n = 0; n < 2; n++)
     s++;
     }
   name[ptr] = 0;
     s++;
     }
   name[ptr] = 0;
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
   if (Ustrcmp(name, "hide") != 0) break;
   issecure = opt_secure;
   ptr = 0;
   if (Ustrcmp(name, "hide") != 0) break;
   issecure = opt_secure;
   ptr = 0;
@@ -1720,7 +1809,7 @@ else if (*s && (offset != 0 || *s != '='))
 
 /* Skip white space after = */
 
 
 /* Skip white space after = */
 
-if (*s == '=') while (isspace((*(++s))));
+if (*s == '=') while (isspace(*++s));
 
 /* If there is a data block and the opt_public flag is not set, change
 the data block pointer to the private options block. */
 
 /* If there is a data block and the opt_public flag is not set, change
 the data block pointer to the private options block. */
@@ -2202,8 +2291,7 @@ switch (type)
        log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
          "absolute value of integer \"%s\" is too large (overflow)", s);
 
        log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
          "absolute value of integer \"%s\" is too large (overflow)", s);
 
-      while (isspace(*endptr)) endptr++;
-      if (*endptr)
+      if (Uskip_whitespace(&endptr))
        extra_chars_error(endptr, inttype, US"integer value for ", name);
 
       value = (int)lvalue;
        extra_chars_error(endptr, inttype, US"integer value for ", name);
 
       value = (int)lvalue;
@@ -2251,8 +2339,7 @@ switch (type)
     if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
       "absolute value of integer \"%s\" is too large (overflow)", s);
 
     if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
       "absolute value of integer \"%s\" is too large (overflow)", s);
 
-    while (isspace(*endptr)) endptr++;
-    if (*endptr != 0)
+    if (Uskip_whitespace(&endptr))
       extra_chars_error(endptr, inttype, US"integer value for ", name);
 
     if (data_block)
       extra_chars_error(endptr, inttype, US"integer value for ", name);
 
     if (data_block)
@@ -2347,7 +2434,7 @@ switch (type)
       list[count+1] = value;
       if (snext == NULL) break;
       s = snext + 1;
       list[count+1] = value;
       if (snext == NULL) break;
       s = snext + 1;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       }
 
     if (count > list[0] - 2)
       }
 
     if (count > list[0] - 2)
@@ -2361,6 +2448,19 @@ switch (type)
   case opt_func:
     ol->v.fn(name, s, 0);
     break;
   case opt_func:
     ol->v.fn(name, s, 0);
     break;
+
+  case opt_module:
+    {
+    uschar * errstr;
+    misc_module_info * mi = misc_mod_find(US ol->v.value, &errstr);
+    if (!mi)
+      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+       "failed to find %s module for %s: %s", US ol->v.value, name, errstr);
+
+    oltop = mi->options;
+    last = mi->options_count;
+    goto sublist;
+    }
   }
 
 return TRUE;
   }
 
 return TRUE;
@@ -2741,7 +2841,7 @@ Returns:      Boolean success
 */
 
 BOOL
 */
 
 BOOL
-readconf_print(const uschar *name, uschar *type, BOOL no_labels)
+readconf_print(const uschar * name, const uschar * type, BOOL no_labels)
 {
 BOOL names_only = FALSE;
 optionlist *ol2 = NULL;
 {
 BOOL names_only = FALSE;
 optionlist *ol2 = NULL;
@@ -2947,6 +3047,8 @@ if (names_only)
 for (; d; d = d->next)
   {
   BOOL rc = FALSE;
 for (; d; d = d->next)
   {
   BOOL rc = FALSE;
+  driver_info * di = d->info;
+
   if (!name)
     printf("\n%s %s:\n", d->name, type);
   else if (Ustrcmp(d->name, name) != 0) continue;
   if (!name)
     printf("\n%s %s:\n", d->name, type);
   else if (Ustrcmp(d->name, name) != 0) continue;
@@ -2955,11 +3057,11 @@ for (; d; d = d->next)
     if (!(ol->type & opt_hidden))
       rc |= print_ol(ol, US ol->name, d, ol2, size, no_labels);
 
     if (!(ol->type & opt_hidden))
       rc |= print_ol(ol, US ol->name, d, ol2, size, no_labels);
 
-  for (optionlist * ol = d->info->options;
-       ol < d->info->options + *(d->info->options_count); ol++)
+  for (optionlist * ol = di->options;
+       ol < di->options + *di->options_count; ol++)
     if (!(ol->type & opt_hidden))
     if (!(ol->type & opt_hidden))
-      rc |= print_ol(ol, US ol->name, d, d->info->options,
-                   *d->info->options_count, no_labels);
+      rc |= print_ol(ol, US ol->name, d, di->options,
+                   *di->options_count, no_labels);
 
   if (name) return rc;
   }
 
   if (name) return rc;
   }
@@ -3001,7 +3103,7 @@ read_named_list(tree_node **anchorp, int *numberp, int max, uschar *s,
 BOOL forcecache = FALSE;
 uschar *ss;
 tree_node *t;
 BOOL forcecache = FALSE;
 uschar *ss;
 tree_node *t;
-namedlist_block * nb = store_get_perm(sizeof(namedlist_block), FALSE);
+namedlist_block * nb = store_get_perm(sizeof(namedlist_block), GET_UNTAINTED);
 
 if (Ustrncmp(s, "_cache", 6) == 0)
   {
 
 if (Ustrncmp(s, "_cache", 6) == 0)
   {
@@ -3121,8 +3223,8 @@ readconf_main(BOOL nowarn)
 {
 int sep = 0;
 struct stat statbuf;
 {
 int sep = 0;
 struct stat statbuf;
-uschar *s, *filename;
-const uschar *list = config_main_filelist;
+uschar * s, * filename;
+const uschar * list = config_main_filelist;
 
 /* Loop through the possible file names */
 
 
 /* Loop through the possible file names */
 
@@ -3210,7 +3312,7 @@ if (config_file)
     if (os_getcwd(buf, PATH_MAX) == NULL)
       {
       perror("exim: getcwd");
     if (os_getcwd(buf, PATH_MAX) == NULL)
       {
       perror("exim: getcwd");
-      exit(EXIT_FAILURE);
+      exim_exit(EXIT_FAILURE);
       }
     g = string_cat(NULL, buf);
 
       }
     g = string_cat(NULL, buf);
 
@@ -3240,7 +3342,7 @@ to a safe place. Later we change to $spool_directory. */
 if (Uchdir("/") < 0)
   {
   perror("exim: chdir `/': ");
 if (Uchdir("/") < 0)
   {
   perror("exim: chdir `/': ");
-  exit(EXIT_FAILURE);
+  exim_exit(EXIT_FAILURE);
   }
 
 /* Check the status of the file we have opened, if we have retained root
   }
 
 /* Check the status of the file we have opened, if we have retained root
@@ -3416,6 +3518,9 @@ if (!*spool_directory)
 /* Expand the spool directory name; it may, for example, contain the primary
 host name. Same comment about failure. */
 
 /* Expand the spool directory name; it may, for example, contain the primary
 host name. Same comment about failure. */
 
+DEBUG(D_any) if (Ustrchr(spool_directory, '$'))
+  debug_printf("Expanding spool_directory option\n");
+
 if (!(s = expand_string(spool_directory)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand spool_directory "
     "\"%s\": %s", spool_directory, expand_string_message);
 if (!(s = expand_string(spool_directory)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand spool_directory "
     "\"%s\": %s", spool_directory, expand_string_message);
@@ -3459,7 +3564,7 @@ leading "log_". */
 if (syslog_facility_str)
   {
   int i;
 if (syslog_facility_str)
   {
   int i;
-  uschar *s = syslog_facility_str;
+  uschar * s = syslog_facility_str;
 
   if ((Ustrlen(syslog_facility_str) >= 4) &&
         (strncmpic(syslog_facility_str, US"log_", 4) == 0))
 
   if ((Ustrlen(syslog_facility_str) >= 4) &&
         (strncmpic(syslog_facility_str, US"log_", 4) == 0))
@@ -3481,10 +3586,11 @@ if (syslog_facility_str)
 
 if (*pid_file_path)
   {
 
 if (*pid_file_path)
   {
-  if (!(s = expand_string(pid_file_path)))
+  const uschar * t = expand_cstring(pid_file_path);
+  if (!t)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path "
       "\"%s\": %s", pid_file_path, expand_string_message);
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path "
       "\"%s\": %s", pid_file_path, expand_string_message);
-  pid_file_path = s;
+  pid_file_path = t;
   }
 
 /* Set default value of process_log_path */
   }
 
 /* Set default value of process_log_path */
@@ -3569,8 +3675,7 @@ if (host_number_string)
         "failed to expand localhost_number \"%s\": %s",
         host_number_string, expand_string_message);
   n = Ustrtol(s, &end, 0);
         "failed to expand localhost_number \"%s\": %s",
         host_number_string, expand_string_message);
   n = Ustrtol(s, &end, 0);
-  while (isspace(*end)) end++;
-  if (*end)
+  if (Uskip_whitespace(&end))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "localhost_number value is not a number: %s", s);
   if (n > LOCALHOST_MAX)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "localhost_number value is not a number: %s", s);
   if (n > LOCALHOST_MAX)
@@ -3618,6 +3723,18 @@ if (!nowarn && !keep_environment && environ && *environ)
 
 
 
 
 
 
+/* Add a driver info struct to a list. */
+
+void
+add_driver_info(driver_info ** drlist_p, const driver_info * newent,
+  size_t size)
+{
+driver_info * listent = store_get(size, newent);
+memcpy(listent, newent, size);
+listent->next = *drlist_p;
+*drlist_p= listent;
+}
+
 /*************************************************
 *          Initialize one driver                 *
 *************************************************/
 /*************************************************
 *          Initialize one driver                 *
 *************************************************/
@@ -3630,34 +3747,105 @@ set by another incarnation of the same driver).
 Arguments:
   d                   pointer to driver instance block, with generic
                         options filled in
 Arguments:
   d                   pointer to driver instance block, with generic
                         options filled in
-  drivers_available   vector of available drivers
+  info_anchor        list of available drivers
   size_of_info        size of each block in drivers_available
   size_of_info        size of each block in drivers_available
-  class               class of driver, for error message
+  class               class of driver
 
 Returns:              pointer to the driver info block
 */
 
 static driver_info *
 
 Returns:              pointer to the driver info block
 */
 
 static driver_info *
-init_driver(driver_instance *d, driver_info *drivers_available,
-  int size_of_info, uschar *class)
+init_driver(driver_instance * d, driver_info ** info_anchor,
+  int size_of_info, const uschar * class)
 {
 {
-for (driver_info * dd = drivers_available; dd->driver_name[0] != 0;
-     dd = (driver_info *)((US dd) + size_of_info))
-  if (Ustrcmp(d->driver_name, dd->driver_name) == 0)
+driver_info * di;
+int len;
+#ifdef LOOKUP_MODULE_DIR
+DIR * dd;
+#endif
+
+/* First scan the list of driver seen so far. */
+
+for (di = *info_anchor; di; di = di->next)
+  if (Ustrcmp(d->driver_name, di->driver_name) == 0)
+    goto found;
+
+#ifdef LOOKUP_MODULE_DIR
+/* Potentially a loadable module. Look for a file with the right name. */
+
+if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC,
+           "Couldn't open %s: not loading driver modules\n", LOOKUP_MODULE_DIR);
+  }
+else
+  {
+  uschar * fname = string_sprintf("%s_%s." DYNLIB_FN_EXT, d->driver_name, class), * sname;
+  const char * errormsg;
+
+  DEBUG(D_any) debug_printf("Loading %s %s driver from %s\n",
+                           d->driver_name, class, LOOKUP_MODULE_DIR);
+
+  for(struct dirent * ent; ent = readdir(dd); ) if (Ustrcmp(ent->d_name, fname) == 0)
     {
     {
-    int len = dd->options_len;
-    d->info = dd;
-    d->options_block = store_get_perm(len, FALSE);
-    memcpy(d->options_block, dd->options_block, len);
-    for (int i = 0; i < *(dd->options_count); i++)
-      dd->options[i].type &= ~opt_set;
-    return dd;
+    void * dl = dlopen(CS string_sprintf(LOOKUP_MODULE_DIR "/%s", fname), RTLD_NOW);
+    static driver_magics dm[] = {
+      { ROUTER_MAGIC,  US"router" },
+      { TRANSPORT_MAGIC, US"transport" },
+      { AUTH_MAGIC,    US"auth" },
+    };
+
+    if (!dl)
+      {
+      errormsg = dlerror();
+      log_write(0, LOG_MAIN|LOG_PANIC, "Error loading %s %s driver: %s\n",
+               d->driver_name, class, errormsg);
+      break;
+      }
+    (void) dlerror();          /* cf. comment in init_lookup_list() */
+
+    di = (driver_info *) dlsym(dl, CS string_sprintf("_%s_info", class));
+    if ((errormsg = dlerror()))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+               "%s does not appear to be a %s module (%s)\n", fname, class, errormsg);
+      dlclose(dl);
+      break;
+      }
+    for(driver_magics * dmp = dm; dmp < dm + nelem(dm); dmp++)
+      if(Ustrcmp(dmp->class, class) == 0 && dmp->magic == di->dyn_magic)
+       {
+       int old_pool = store_pool;
+       store_pool = POOL_PERM;
+       add_driver_info(info_anchor, di, size_of_info);
+       store_pool = old_pool;
+       DEBUG(D_any) debug_printf("Loaded %s %s\n", d->driver_name, class);
+       closedir(dd);
+       goto found;
+       }
+
+    log_write(0, LOG_MAIN|LOG_PANIC,
+             "%s module %s is not compatible with this version of Exim\n",
+             class, d->driver_name);
+    dlclose(dl);
+    break;
     }
     }
+  closedir(dd);
+  }
+#endif /* LOOKUP_MODULE_DIR */
 
 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
   "%s %s: cannot find %s driver \"%s\"", class, d->name, class, d->driver_name);
 
 
 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
   "%s %s: cannot find %s driver \"%s\"", class, d->name, class, d->driver_name);
 
-return NULL;   /* never obeyed */
+found:
+
+len = di->options_len;
+d->info = di;
+d->options_block = store_get_perm(len, GET_UNTAINTED);
+memcpy(d->options_block, di->options_block, len);
+for (int i = 0; i < *di->options_count; i++)
+  di->options[i].type &= ~opt_set;
+return di;
 }
 
 
 }
 
 
@@ -3666,10 +3854,12 @@ return NULL;   /* never obeyed */
 static void
 driver_init_fini(driver_instance * d, const uschar * class)
 {
 static void
 driver_init_fini(driver_instance * d, const uschar * class)
 {
+driver_info * di = d->info;
+
 if (!d->driver_name)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "no driver defined for %s \"%s\"", class, d->name);
 if (!d->driver_name)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "no driver defined for %s \"%s\"", class, d->name);
-(d->info->init)(d);
+(di->init)(d);
 }
 
 
 }
 
 
@@ -3688,28 +3878,29 @@ driver_instance must map the first portions of all the _info and _instance
 blocks for this shared code to work.
 
 Arguments:
 blocks for this shared code to work.
 
 Arguments:
-  class                      "router", "transport", or "authenticator"
   anchor                     &routers, &transports, &auths
   anchor                     &routers, &transports, &auths
-  drivers_available          available drivers
+  info_anchor               available drivers
   size_of_info               size of each info block
   instance_default           points to default data for an instance
   instance_size              size of instance block
   driver_optionlist          generic option list
   driver_optionlist_count    count of generic option list
   size_of_info               size of each info block
   instance_default           points to default data for an instance
   instance_size              size of instance block
   driver_optionlist          generic option list
   driver_optionlist_count    count of generic option list
+  class                      "router", "transport", or "auth"
+                             for filename component (and error message)
 
 Returns:                     nothing
 */
 
 void
 readconf_driver_init(
 
 Returns:                     nothing
 */
 
 void
 readconf_driver_init(
-  uschar *class,
-  driver_instance **anchor,
-  driver_info *drivers_available,
+  driver_instance ** anchor,
+  driver_info ** info_anchor,
   int size_of_info,
   int size_of_info,
-  void *instance_default,
+  void * instance_default,
   int  instance_size,
   int  instance_size,
-  optionlist *driver_optionlist,
-  int  driver_optionlist_count)
+  optionlist * driver_optionlist,
+  int  driver_optionlist_count,
+  const uschar * class)
 {
 driver_instance ** p = anchor;
 driver_instance * d = NULL;
 {
 driver_instance ** p = anchor;
 driver_instance * d = NULL;
@@ -3762,10 +3953,10 @@ while ((buffer = get_config_line()))
     /* Set up a new driver instance data block on the chain, with
     its default values installed. */
 
     /* Set up a new driver instance data block on the chain, with
     its default values installed. */
 
-    d = store_get_perm(instance_size, FALSE);
+    d = store_get_perm(instance_size, GET_UNTAINTED);
     memcpy(d, instance_default, instance_size);
     *p = d;
     memcpy(d, instance_default, instance_size);
     *p = d;
-    p = &d->next;
+    p = (driver_instance **)&d->next;
     d->name = string_copy(name);
     d->srcfile = config_filename;
     d->srcline = config_lineno; 
     d->name = string_copy(name);
     d->srcfile = config_filename;
     d->srcline = config_lineno; 
@@ -3796,7 +3987,7 @@ while ((buffer = get_config_line()))
         driver_optionlist_count, d, NULL))
     {
     if (!d->info && d->driver_name)
         driver_optionlist_count, d, NULL))
     {
     if (!d->info && d->driver_name)
-      init_driver(d, drivers_available, size_of_info, class);
+      init_driver(d, info_anchor, size_of_info, class);
     }
 
   /* Handle private options - pass the generic block because some may
     }
 
   /* Handle private options - pass the generic block because some may
@@ -3804,8 +3995,11 @@ while ((buffer = get_config_line()))
   block. */
 
   else if (d->info)
   block. */
 
   else if (d->info)
-    readconf_handle_option(buffer, d->info->options,
-      *(d->info->options_count), d, US"option \"%s\" unknown");
+    {
+    driver_info * di = d->info;
+    readconf_handle_option(buffer, di->options,
+      *di->options_count, d, US"option \"%s\" unknown");
+    }
 
   /* The option is not generic and the driver name has not yet been given. */
 
 
   /* The option is not generic and the driver name has not yet been given. */
 
@@ -3837,12 +4031,13 @@ Returns:   TRUE if a dependency is found
 */
 
 BOOL
 */
 
 BOOL
-readconf_depends(driver_instance *d, uschar *s)
+readconf_depends(driver_instance * d, uschar * s)
 {
 {
-int count = *(d->info->options_count);
-uschar *ss;
+driver_info * di = d->info;
+int count = *di->options_count;
+uschar * ss;
 
 
-for (optionlist * ol = d->info->options; ol < d->info->options + count; ol++)
+for (optionlist * ol = di->options; ol < di->options + count; ol++)
   if ((ol->type & opt_mask) == opt_stringptr)
     {
     void * options_block = ol->type & opt_public ? (void *)d : d->options_block;
   if ((ol->type & opt_mask) == opt_stringptr)
     {
     void * options_block = ol->type & opt_public ? (void *)d : d->options_block;
@@ -4018,10 +4213,9 @@ Returns:    time in seconds or fixed point number * 1000
 */
 
 static int
 */
 
 static int
-retry_arg(const uschar **paddr, int type)
+retry_arg(const uschar ** paddr, int type)
 {
 {
-const uschar *p = *paddr;
-const uschar *pp;
+const uschar * p = *paddr, * pp;
 
 if (*p++ != ',') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma expected");
 
 
 if (*p++ != ',') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma expected");
 
@@ -4029,7 +4223,7 @@ Uskip_whitespace(&p);
 pp = p;
 while (isalnum(*p) || (type == 1 && *p == '.')) p++;
 
 pp = p;
 while (isalnum(*p) || (type == 1 && *p == '.')) p++;
 
-if (*p != 0 && !isspace(*p) && *p != ',' && *p != ';')
+if (*p && !isspace(*p) && *p != ',' && *p != ';')
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma or semicolon expected");
 
 *paddr = p;
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma or semicolon expected");
 
 *paddr = p;
@@ -4160,36 +4354,86 @@ auths_init(void)
 #ifndef DISABLE_PIPE_CONNECT
 int nauths = 0;
 #endif
 #ifndef DISABLE_PIPE_CONNECT
 int nauths = 0;
 #endif
+int old_pool = store_pool;
+store_pool = POOL_PERM;
+  {
+  driver_info ** anchor = (driver_info **) &auths_available;
+
+  /* Add the transport drivers that are built for static linkage to the
+  list of availables. */
+
+#if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5!=2
+  extern auth_info cram_md5_auth_info;
+  add_driver_info(anchor, &cram_md5_auth_info.drinfo, sizeof(auth_info));
+#endif
+#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL!=2
+  extern auth_info cyrus_sasl_auth_info;
+  add_driver_info(anchor, &cyrus_sasl_auth_info.drinfo, sizeof(auth_info));
+#endif
+#if defined(AUTH_DOVECOT) && AUTH_DOVECOT!=2
+  extern auth_info dovecot_auth_info;
+  add_driver_info(anchor, &dovecot_auth_info.drinfo, sizeof(auth_info));
+#endif
+#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL!=2
+  extern auth_info external_auth_info;
+  add_driver_info(anchor, &external_auth_info.drinfo, sizeof(auth_info));
+#endif
+#if defined(AUTH_GSASL) && AUTH_GSASL!=2
+  extern auth_info gsasl_auth_info;
+  add_driver_info(anchor, &gsasl_auth_info.drinfo, sizeof(auth_info));
+#endif
+#if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI!=2
+  extern auth_info heimdal_gssapi_auth_info;
+  add_driver_info(anchor, &heimdal_gssapi_auth_info.drinfo, sizeof(auth_info));
+#endif
+#if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT!=2
+  extern auth_info plaintext_auth_info;
+  add_driver_info(anchor, &plaintext_auth_info.drinfo, sizeof(auth_info));
+#endif
+#if defined(AUTH_SPA) && AUTH_SPA!=2
+  extern auth_info spa_auth_info;
+  add_driver_info(anchor, &spa_auth_info.drinfo, sizeof(auth_info));
+#endif
+#if defined(AUTH_TLS) && AUTH_TLS!=2
+  extern auth_info tls_auth_info;
+  add_driver_info(anchor, &tls_auth_info.drinfo, sizeof(auth_info));
+#endif
+  }
+store_pool = old_pool;
+
+/* Read the config file "authenticators" section, creating an auth instance list.
+For any yet-undiscovered driver, check for a loadable module and add it to
+those available. */
 
 
-readconf_driver_init(US"authenticator",
-  (driver_instance **)(&auths),      /* chain anchor */
-  (driver_info *)auths_available,    /* available drivers */
+readconf_driver_init((driver_instance **)&auths,      /* chain anchor */
+  (driver_info **)&auths_available,  /* available drivers */
   sizeof(auth_info),                 /* size of info block */
   &auth_defaults,                    /* default values for generic options */
   sizeof(auth_instance),             /* size of instance block */
   optionlist_auths,                  /* generic options */
   sizeof(auth_info),                 /* size of info block */
   &auth_defaults,                    /* default values for generic options */
   sizeof(auth_instance),             /* size of instance block */
   optionlist_auths,                  /* generic options */
-  optionlist_auths_size);
+  optionlist_auths_size,
+  US"auth");
 
 
-for (auth_instance * au = auths; au; au = au->next)
+for (auth_instance * au = auths; au; au = au->drinst.next)
   {
   if (!au->public_name)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "no public name specified for "
   {
   if (!au->public_name)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "no public name specified for "
-      "the %s authenticator", au->name);
+      "the %s authenticator", au->drinst.name);
 
 
-  for (auth_instance * bu = au->next; bu; bu = bu->next)
+  for (auth_instance * bu = au->drinst.next; bu; bu = bu->drinst.next)
     if (strcmpic(au->public_name, bu->public_name) == 0)
       if (  au->client && bu->client
         || au->server && bu->server)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "two %s authenticators "
           "(%s and %s) have the same public name (%s)",
           au->client && bu->client ? US"client" : US"server",
     if (strcmpic(au->public_name, bu->public_name) == 0)
       if (  au->client && bu->client
         || au->server && bu->server)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "two %s authenticators "
           "(%s and %s) have the same public name (%s)",
           au->client && bu->client ? US"client" : US"server",
-         au->name, bu->name, au->public_name);
+         au->drinst.name, bu->drinst.name, au->public_name);
 #ifndef DISABLE_PIPE_CONNECT
   nauths++;
 #endif
   }
 #ifndef DISABLE_PIPE_CONNECT
 #ifndef DISABLE_PIPE_CONNECT
   nauths++;
 #endif
   }
 #ifndef DISABLE_PIPE_CONNECT
-f.smtp_in_early_pipe_no_auth = nauths > 16;
+f.smtp_in_early_pipe_no_auth = nauths > 16;    /* bits in bitmap limit */
 #endif
 }
 
 #endif
 }
 
@@ -4346,6 +4590,7 @@ while(next_section[0] != 0)
   int mid = last/2;
   int n = Ustrlen(next_section);
 
   int mid = last/2;
   int n = Ustrlen(next_section);
 
+  READCONF_DEBUG fprintf(stderr, "%s: %s\n", __FUNCTION__, next_section);
   if (tolower(next_section[n-1]) != 's') Ustrcpy(next_section+n, US"s");
 
   for (;;)
   if (tolower(next_section[n-1]) != 's') Ustrcpy(next_section+n, US"s");
 
   for (;;)
@@ -4378,6 +4623,7 @@ while(next_section[0] != 0)
   }
 
 (void)fclose(config_file);
   }
 
 (void)fclose(config_file);
+config_lineno = 0;             /* Ensure we don't log a spurious position */
 }
 
 /* Init the storage for the pre-parsed config lines */
 }
 
 /* Init the storage for the pre-parsed config lines */
@@ -4432,10 +4678,8 @@ for (const config_line_item * i = config_lines; i; i = i->next)
   r = store_mark();
 
   /* skip over to the first non-space */
   r = store_mark();
 
   /* skip over to the first non-space */
-  for (current = string_copy(i->line); *current && isspace(*current); ++current)
-    ;
-
-  if (!*current)
+  current = string_copy(i->line);
+  if (!Uskip_whitespace(&current))
     continue;
 
   /* Collapse runs of spaces. We stop this if we encounter one of the
     continue;
 
   /* Collapse runs of spaces. We stop this if we encounter one of the
@@ -4443,11 +4687,10 @@ for (const config_line_item * i = config_lines; i; i = i->next)
 
   for (p = current; *p; p++) if (isspace(*p))
     {
 
   for (p = current; *p; p++) if (isspace(*p))
     {
-    uschar *next;
+    uschar * next = p;
     if (*p != ' ') *p = ' ';
 
     if (*p != ' ') *p = ' ';
 
-    for (next = p; isspace(*next); ++next)
-      ;
+    Uskip_whitespace(&p);
 
     if (next - p > 1)
       memmove(p+1, next, Ustrlen(next)+1);
 
     if (next - p > 1)
       memmove(p+1, next, Ustrlen(next)+1);