build: use pkg-config for i18n
[exim.git] / src / src / route.c
index 41716bc0e4ad2d9bcb5c6049501d3a52137f36b9..a54a7e84d29d3d189a9855f9ff684a0de8714f92 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* 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 */
 
 /* Functions concerned with routing, and the list of generic router options. */
 
 
 /* Functions concerned with routing, and the list of generic router options. */
 
 
 /* Generic options for routers, all of which live inside router_instance
 data blocks and which therefore have the opt_public flag set. */
 
 /* Generic options for routers, all of which live inside router_instance
 data blocks and which therefore have the opt_public flag set. */
+#define LOFF(field) OPT_OFF(router_instance, field)
 
 optionlist optionlist_routers[] = {
   { "*expand_group",      opt_stringptr | opt_hidden | opt_public,
 
 optionlist optionlist_routers[] = {
   { "*expand_group",      opt_stringptr | opt_hidden | opt_public,
-                 (void *)(offsetof(router_instance, expand_gid)) },
+                 LOFF(expand_gid) },
   { "*expand_more",       opt_stringptr | opt_hidden | opt_public,
   { "*expand_more",       opt_stringptr | opt_hidden | opt_public,
-                 (void *)(offsetof(router_instance, expand_more)) },
+                 LOFF(expand_more) },
   { "*expand_unseen",     opt_stringptr | opt_hidden | opt_public,
   { "*expand_unseen",     opt_stringptr | opt_hidden | opt_public,
-                 (void *)(offsetof(router_instance, expand_unseen)) },
+                 LOFF(expand_unseen) },
   { "*expand_user",       opt_stringptr | opt_hidden | opt_public,
   { "*expand_user",       opt_stringptr | opt_hidden | opt_public,
-                 (void *)(offsetof(router_instance, expand_uid)) },
+                 LOFF(expand_uid) },
   { "*set_group",         opt_bool | opt_hidden | opt_public,
   { "*set_group",         opt_bool | opt_hidden | opt_public,
-                 (void *)(offsetof(router_instance, gid_set)) },
+                 LOFF(gid_set) },
   { "*set_user",          opt_bool | opt_hidden | opt_public,
   { "*set_user",          opt_bool | opt_hidden | opt_public,
-                 (void *)(offsetof(router_instance, uid_set)) },
+                 LOFF(uid_set) },
   { "address_data",       opt_stringptr|opt_public,
   { "address_data",       opt_stringptr|opt_public,
-                 (void *)(offsetof(router_instance, address_data)) },
+                 LOFF(address_data) },
   { "address_test",       opt_bool|opt_public,
   { "address_test",       opt_bool|opt_public,
-                 (void *)(offsetof(router_instance, address_test)) },
+                 LOFF(address_test) },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   { "bmi_deliver_alternate",   opt_bool | opt_public,
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   { "bmi_deliver_alternate",   opt_bool | opt_public,
-                 (void *)(offsetof(router_instance, bmi_deliver_alternate)) },
+                 LOFF(bmi_deliver_alternate) },
   { "bmi_deliver_default",   opt_bool | opt_public,
   { "bmi_deliver_default",   opt_bool | opt_public,
-                 (void *)(offsetof(router_instance, bmi_deliver_default)) },
+                 LOFF(bmi_deliver_default) },
   { "bmi_dont_deliver",   opt_bool | opt_public,
   { "bmi_dont_deliver",   opt_bool | opt_public,
-                 (void *)(offsetof(router_instance, bmi_dont_deliver)) },
+                 LOFF(bmi_dont_deliver) },
   { "bmi_rule",           opt_stringptr|opt_public,
   { "bmi_rule",           opt_stringptr|opt_public,
-                 (void *)(offsetof(router_instance, bmi_rule)) },
+                 LOFF(bmi_rule) },
 #endif
   { "cannot_route_message", opt_stringptr | opt_public,
 #endif
   { "cannot_route_message", opt_stringptr | opt_public,
-                 (void *)(offsetof(router_instance, cannot_route_message)) },
+                 LOFF(cannot_route_message) },
   { "caseful_local_part", opt_bool | opt_public,
   { "caseful_local_part", opt_bool | opt_public,
-                 (void *)(offsetof(router_instance, caseful_local_part)) },
+                 LOFF(caseful_local_part) },
   { "check_local_user",   opt_bool | opt_public,
   { "check_local_user",   opt_bool | opt_public,
-                 (void *)(offsetof(router_instance, check_local_user)) },
+                 LOFF(check_local_user) },
   { "condition",          opt_stringptr|opt_public|opt_rep_con,
   { "condition",          opt_stringptr|opt_public|opt_rep_con,
-                 (void *)offsetof(router_instance, condition) },
+                 LOFF(condition) },
   { "debug_print",        opt_stringptr | opt_public,
   { "debug_print",        opt_stringptr | opt_public,
-                 (void *)offsetof(router_instance, debug_string) },
+                 LOFF(debug_string) },
   { "disable_logging",    opt_bool | opt_public,
   { "disable_logging",    opt_bool | opt_public,
-                 (void *)offsetof(router_instance, disable_logging) },
+                 LOFF(disable_logging) },
   { "dnssec_request_domains",            opt_stringptr|opt_public,
   { "dnssec_request_domains",            opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, dnssec.request) },
+                 LOFF(dnssec.request) },
   { "dnssec_require_domains",            opt_stringptr|opt_public,
   { "dnssec_require_domains",            opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, dnssec.require) },
+                 LOFF(dnssec.require) },
   { "domains",            opt_stringptr|opt_public,
   { "domains",            opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, domains) },
+                 LOFF(domains) },
   { "driver",             opt_stringptr|opt_public,
   { "driver",             opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, driver_name) },
+                 LOFF(drinst.driver_name) },
   { "dsn_lasthop",        opt_bool|opt_public,
   { "dsn_lasthop",        opt_bool|opt_public,
-                 (void *)offsetof(router_instance, dsn_lasthop) },
+                 LOFF(dsn_lasthop) },
   { "errors_to",          opt_stringptr|opt_public,
   { "errors_to",          opt_stringptr|opt_public,
-                 (void *)(offsetof(router_instance, errors_to)) },
+                 LOFF(errors_to) },
   { "expn",               opt_bool|opt_public,
   { "expn",               opt_bool|opt_public,
-                 (void *)offsetof(router_instance, expn) },
+                 LOFF(expn) },
   { "fail_verify",        opt_bool_verify|opt_hidden|opt_public,
   { "fail_verify",        opt_bool_verify|opt_hidden|opt_public,
-                 (void *)offsetof(router_instance, fail_verify_sender) },
+                 LOFF(fail_verify_sender) },
   { "fail_verify_recipient", opt_bool|opt_public,
   { "fail_verify_recipient", opt_bool|opt_public,
-                 (void *)offsetof(router_instance, fail_verify_recipient) },
+                 LOFF(fail_verify_recipient) },
   { "fail_verify_sender", opt_bool|opt_public,
   { "fail_verify_sender", opt_bool|opt_public,
-                 (void *)offsetof(router_instance, fail_verify_sender) },
+                 LOFF(fail_verify_sender) },
   { "fallback_hosts",     opt_stringptr|opt_public,
   { "fallback_hosts",     opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, fallback_hosts) },
+                 LOFF(fallback_hosts) },
   { "group",              opt_expand_gid | opt_public,
   { "group",              opt_expand_gid | opt_public,
-                 (void *)(offsetof(router_instance, gid)) },
+                 LOFF(gid) },
   { "headers_add",        opt_stringptr|opt_public|opt_rep_str,
   { "headers_add",        opt_stringptr|opt_public|opt_rep_str,
-                 (void *)offsetof(router_instance, extra_headers) },
+                 LOFF(extra_headers) },
   { "headers_remove",     opt_stringptr|opt_public|opt_rep_str,
   { "headers_remove",     opt_stringptr|opt_public|opt_rep_str,
-                 (void *)offsetof(router_instance, remove_headers) },
+                 LOFF(remove_headers) },
   { "ignore_target_hosts",opt_stringptr|opt_public,
   { "ignore_target_hosts",opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, ignore_target_hosts) },
+                 LOFF(ignore_target_hosts) },
   { "initgroups",         opt_bool | opt_public,
   { "initgroups",         opt_bool | opt_public,
-                 (void *)(offsetof(router_instance, initgroups)) },
+                 LOFF(initgroups) },
   { "local_part_prefix",  opt_stringptr|opt_public,
   { "local_part_prefix",  opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, prefix) },
+                 LOFF(prefix) },
   { "local_part_prefix_optional",opt_bool|opt_public,
   { "local_part_prefix_optional",opt_bool|opt_public,
-                 (void *)offsetof(router_instance, prefix_optional) },
+                 LOFF(prefix_optional) },
   { "local_part_suffix",  opt_stringptr|opt_public,
   { "local_part_suffix",  opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, suffix) },
+                 LOFF(suffix) },
   { "local_part_suffix_optional",opt_bool|opt_public,
   { "local_part_suffix_optional",opt_bool|opt_public,
-                 (void *)offsetof(router_instance, suffix_optional) },
+                 LOFF(suffix_optional) },
   { "local_parts",        opt_stringptr|opt_public,
   { "local_parts",        opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, local_parts) },
+                 LOFF(local_parts) },
   { "log_as_local",       opt_bool|opt_public,
   { "log_as_local",       opt_bool|opt_public,
-                 (void *)offsetof(router_instance, log_as_local) },
+                 LOFF(log_as_local) },
   { "more",               opt_expand_bool|opt_public,
   { "more",               opt_expand_bool|opt_public,
-                 (void *)offsetof(router_instance, more) },
+                 LOFF(more) },
   { "pass_on_timeout",    opt_bool|opt_public,
   { "pass_on_timeout",    opt_bool|opt_public,
-                 (void *)offsetof(router_instance, pass_on_timeout) },
+                 LOFF(pass_on_timeout) },
   { "pass_router",       opt_stringptr|opt_public,
   { "pass_router",       opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, pass_router_name) },
+                 LOFF(pass_router_name) },
   { "redirect_router",    opt_stringptr|opt_public,
   { "redirect_router",    opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, redirect_router_name) },
+                 LOFF(redirect_router_name) },
   { "require_files",      opt_stringptr|opt_public,
   { "require_files",      opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, require_files) },
+                 LOFF(require_files) },
   { "retry_use_local_part", opt_bool|opt_public,
   { "retry_use_local_part", opt_bool|opt_public,
-                 (void *)offsetof(router_instance, retry_use_local_part) },
+                 LOFF(retry_use_local_part) },
   { "router_home_directory", opt_stringptr|opt_public,
   { "router_home_directory", opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, router_home_directory) },
+                 LOFF(router_home_directory) },
   { "self",               opt_stringptr|opt_public,
   { "self",               opt_stringptr|opt_public,
-                 (void *)(offsetof(router_instance, self)) },
+                 LOFF(self) },
   { "senders",            opt_stringptr|opt_public,
   { "senders",            opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, senders) },
+                 LOFF(senders) },
   { "set",                opt_stringptr|opt_public|opt_rep_str,
   { "set",                opt_stringptr|opt_public|opt_rep_str,
-                 (void *)offsetof(router_instance, set) },
+                 LOFF(set) },
   #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
   { "translate_ip_address", opt_stringptr|opt_public,
   #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
   { "translate_ip_address", opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, translate_ip_address) },
+                 LOFF(translate_ip_address) },
   #endif
   { "transport",          opt_stringptr|opt_public,
   #endif
   { "transport",          opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, transport_name) },
+                 LOFF(transport_name) },
   { "transport_current_directory", opt_stringptr|opt_public,
   { "transport_current_directory", opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, current_directory) },
+                 LOFF(current_directory) },
   { "transport_home_directory", opt_stringptr|opt_public,
   { "transport_home_directory", opt_stringptr|opt_public,
-                 (void *)offsetof(router_instance, home_directory) },
+                 LOFF(home_directory) },
   { "unseen",             opt_expand_bool|opt_public,
   { "unseen",             opt_expand_bool|opt_public,
-                 (void *)offsetof(router_instance, unseen) },
+                 LOFF(unseen) },
   { "user",               opt_expand_uid | opt_public,
   { "user",               opt_expand_uid | opt_public,
-                 (void *)(offsetof(router_instance, uid)) },
+                 LOFF(uid) },
   { "verify",             opt_bool_verify|opt_hidden|opt_public,
   { "verify",             opt_bool_verify|opt_hidden|opt_public,
-                 (void *)offsetof(router_instance, verify_sender) },
+                 LOFF(verify_sender) },
   { "verify_only",        opt_bool|opt_public,
   { "verify_only",        opt_bool|opt_public,
-                 (void *)offsetof(router_instance, verify_only) },
+                 LOFF(verify_only) },
   { "verify_recipient",   opt_bool|opt_public,
   { "verify_recipient",   opt_bool|opt_public,
-                 (void *)offsetof(router_instance, verify_recipient) },
+                 LOFF(verify_recipient) },
   { "verify_sender",      opt_bool|opt_public,
   { "verify_sender",      opt_bool|opt_public,
-                 (void *)offsetof(router_instance, verify_sender) }
+                 LOFF(verify_sender) }
 };
 
 int optionlist_routers_size = nelem(optionlist_routers);
 };
 
 int optionlist_routers_size = nelem(optionlist_routers);
@@ -156,11 +159,12 @@ uschar buf[64];
 
 options_from_list(optionlist_routers, nelem(optionlist_routers), US"ROUTERS", NULL);
 
 
 options_from_list(optionlist_routers, nelem(optionlist_routers), US"ROUTERS", NULL);
 
-for (router_info * ri = routers_available; ri->driver_name[0]; ri++)
+for (driver_info * di = (driver_info *)routers_available; di; di = di->next)
   {
   {
-  spf(buf, sizeof(buf), US"_DRIVER_ROUTER_%T", ri->driver_name);
+  spf(buf, sizeof(buf), US"_DRIVER_ROUTER_%T", di->driver_name);
   builtin_macro_create(buf);
   builtin_macro_create(buf);
-  options_from_list(ri->options, (unsigned)*ri->options_count, US"ROUTER", ri->driver_name);
+  options_from_list(di->options, (unsigned)*di->options_count,
+                   US"ROUTER", di->driver_name);
   }
 }
 
   }
 }
 
@@ -188,9 +192,9 @@ set_router(router_instance *r, uschar *name, router_instance **ptr, BOOL after)
 BOOL afterthis = FALSE;
 router_instance *rr;
 
 BOOL afterthis = FALSE;
 router_instance *rr;
 
-for (rr = routers; rr; rr = rr->next)
+for (rr = routers; rr; rr = rr->drinst.next)
   {
   {
-  if (Ustrcmp(name, rr->name) == 0)
+  if (Ustrcmp(name, rr->drinst.name) == 0)
     {
     *ptr = rr;
     break;
     {
     *ptr = rr;
     break;
@@ -200,11 +204,11 @@ for (rr = routers; rr; rr = rr->next)
 
 if (!rr)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
 
 if (!rr)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
-    "new_router \"%s\" not found for \"%s\" router", name, r->name);
+    "new_router \"%s\" not found for \"%s\" router", name, r->drinst.name);
 
 if (after && !afterthis)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
 
 if (after && !afterthis)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
-    "new_router \"%s\" does not follow \"%s\" router", name, r->name);
+    "new_router \"%s\" does not follow \"%s\" router", name, r->drinst.name);
 }
 
 
 }
 
 
@@ -223,18 +227,63 @@ function. */
 void
 route_init(void)
 {
 void
 route_init(void)
 {
-readconf_driver_init(US"router",
-  (driver_instance **)(&routers),     /* chain anchor */
-  (driver_info *)routers_available,   /* available drivers */
+int old_pool = store_pool;
+store_pool = POOL_PERM;
+  {
+  driver_info ** anchor = (driver_info **) &routers_available;
+
+  /* Add the router drivers that are built for static linkage to the
+  list of availables. */
+
+#if defined(ROUTER_ACCEPT) && ROUTER_ACCEPT!=2
+  extern router_info accept_router_info;
+  add_driver_info(anchor, &accept_router_info.drinfo, sizeof(router_info));
+#endif
+#if defined(ROUTER_DNSLOOKUP) && ROUTER_DNSLOOKUP!=2
+  extern router_info dnslookup_router_info;
+  add_driver_info(anchor, &dnslookup_router_info.drinfo, sizeof(router_info));
+#endif
+# if defined(ROUTER_IPLITERAL) && ROUTER_IPLITERAL!=2
+  extern router_info ipliteral_router_info;
+  add_driver_info(anchor, &ipliteral_router_info.drinfo, sizeof(router_info));
+#endif
+#if defined(ROUTER_IPLOOKUP) && ROUTER_IPLOOKUP!=2
+  extern router_info iplookup_router_info;
+  add_driver_info(anchor, &iplookup_router_info.drinfo, sizeof(router_info));
+#endif
+#if defined(ROUTER_MANUALROUTE) && ROUTER_MANUALROUTE!=2
+  extern router_info manualroute_router_info;
+  add_driver_info(anchor, &manualroute_router_info.drinfo, sizeof(router_info));
+#endif
+#if defined(ROUTER_REDIRECT) && ROUTER_REDIRECT!=2
+  extern router_info redirect_router_info;
+  add_driver_info(anchor, &redirect_router_info.drinfo, sizeof(router_info));
+#endif
+#if defined(ROUTER_QUERYPROGRAM) && ROUTER_QUERYPROGRAM!=2
+  extern router_info queryprogram_router_info;
+  add_driver_info(anchor, &queryprogram_router_info.drinfo, sizeof(router_info));
+#endif
+  }
+store_pool = old_pool;
+
+
+/* Read the config file "routers" section, creating a routers instance list.
+For any yet-undiscovered driver, check for a loadable module and add it to
+those available. */
+
+readconf_driver_init((driver_instance **)&routers,     /* chain anchor */
+  (driver_info **)&routers_available, /* available drivers */
   sizeof(router_info),                /* size of info blocks */
   &router_defaults,                   /* default values for generic options */
   sizeof(router_instance),            /* size of instance block */
   optionlist_routers,                 /* generic options */
   sizeof(router_info),                /* size of info blocks */
   &router_defaults,                   /* default values for generic options */
   sizeof(router_instance),            /* size of instance block */
   optionlist_routers,                 /* generic options */
-  optionlist_routers_size);
+  optionlist_routers_size,
+  US"router");
 
 
-for (router_instance * r = routers; r; r = r->next)
+for (router_instance * r = routers; r; r = r->drinst.next)
   {
   {
-  uschar *s = r->self;
+  uschar * s = r->self;
+  router_info * ri = r->drinst.info;
 
   /* If log_as_local is unset, its overall default is FALSE. (The accept
   router defaults it to TRUE.) */
 
   /* If log_as_local is unset, its overall default is FALSE. (The accept
   router defaults it to TRUE.) */
@@ -243,14 +292,13 @@ for (router_instance * r = routers; r; r = r->next)
 
   /* Check for transport or no transport on certain routers */
 
 
   /* Check for transport or no transport on certain routers */
 
-  if (  (r->info->ri_flags & ri_yestransport)
-     && !r->transport_name && !r->verify_only)
+  if (ri->ri_flags & ri_yestransport && !r->transport_name && !r->verify_only)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n  "
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n  "
-      "a transport is required for this router", r->name);
+      "a transport is required for this router", r->drinst.name);
 
 
-  if ((r->info->ri_flags & ri_notransport) && r->transport_name)
+  if (ri->ri_flags & ri_notransport && r->transport_name)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n  "
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n  "
-      "a transport must not be defined for this router", r->name);
+      "a transport must not be defined for this router", r->drinst.name);
 
   /* The "self" option needs to be decoded into a code value and possibly a
   new domain string and a rewrite boolean. */
 
   /* The "self" option needs to be decoded into a code value and possibly a
   new domain string and a rewrite boolean. */
@@ -263,29 +311,35 @@ for (router_instance * r = routers; r; r = r->next)
   else if (Ustrncmp(s, "reroute:", 8) == 0)
     {
     s += 8;
   else if (Ustrncmp(s, "reroute:", 8) == 0)
     {
     s += 8;
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     if (Ustrncmp(s, "rewrite:", 8) == 0)
       {
       r->self_rewrite = TRUE;
       s += 8;
     if (Ustrncmp(s, "rewrite:", 8) == 0)
       {
       r->self_rewrite = TRUE;
       s += 8;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       }
     r->self = s;
     r->self_code = self_reroute;
     }
 
   else log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
       }
     r->self = s;
     r->self_code = self_reroute;
     }
 
   else log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
-      "%s is not valid for the self option", r->name, s);
+      "%s is not valid for the self option", r->drinst.name, s);
 
   /* If any router has check_local_user set, default retry_use_local_part
   TRUE; otherwise its default is FALSE. */
 
   if (r->retry_use_local_part == TRUE_UNSET)
 
   /* If any router has check_local_user set, default retry_use_local_part
   TRUE; otherwise its default is FALSE. */
 
   if (r->retry_use_local_part == TRUE_UNSET)
-    r->retry_use_local_part = r->check_local_user;
+    r->retry_use_local_part =
+      r->check_local_user || r->local_parts || r->condition || r->prefix || r->suffix || r->senders || r->require_files;
 
   /* Build a host list if fallback hosts is set. */
 
 
   /* Build a host list if fallback hosts is set. */
 
-  host_build_hostlist(&(r->fallback_hostlist), r->fallback_hosts, FALSE);
+    {
+    int old_pool = store_pool;
+    store_pool = POOL_PERM;
+    host_build_hostlist(&r->fallback_hostlist, r->fallback_hosts, FALSE);
+    store_pool = old_pool;
+    }
 
   /* Check redirect_router and pass_router are valid */
 
 
   /* Check redirect_router and pass_router are valid */
 
@@ -316,8 +370,11 @@ is finished, via this function. */
 void
 route_tidyup(void)
 {
 void
 route_tidyup(void)
 {
-for (router_instance * r = routers; r; r = r->next)
-  if (r->info->tidyup) (r->info->tidyup)(r);
+for (router_instance * r = routers; r; r = r->drinst.next)
+  {
+  router_info * ri = r->drinst.info;
+  if (ri->tidyup) (ri->tidyup)(r);
+  }
 }
 
 
 }
 
 
@@ -333,19 +390,20 @@ wildcard.
 Arguments:
   local_part    the local part to check
   prefixes      the list of prefixes
 Arguments:
   local_part    the local part to check
   prefixes      the list of prefixes
+  vp           if set, pointer to place for size of wildcard portion
 
 Returns:        length of matching prefix or zero
 */
 
 int
 
 Returns:        length of matching prefix or zero
 */
 
 int
-route_check_prefix(const uschar *local_part, const uschar *prefixes)
+route_check_prefix(const uschar * local_part, const uschar * prefixes,
+  unsigned * vp)
 {
 int sep = 0;
 uschar *prefix;
 const uschar *listptr = prefixes;
 {
 int sep = 0;
 uschar *prefix;
 const uschar *listptr = prefixes;
-uschar prebuf[64];
 
 
-while ((prefix = string_nextinlist(&listptr, &sep, prebuf, sizeof(prebuf))))
+while ((prefix = string_nextinlist(&listptr, &sep, NULL, 0)))
   {
   int plen = Ustrlen(prefix);
   if (prefix[0] == '*')
   {
   int plen = Ustrlen(prefix);
   if (prefix[0] == '*')
@@ -353,10 +411,19 @@ while ((prefix = string_nextinlist(&listptr, &sep, prebuf, sizeof(prebuf))))
     prefix++;
     for (const uschar * p = local_part + Ustrlen(local_part) - (--plen);
          p >= local_part; p--)
     prefix++;
     for (const uschar * p = local_part + Ustrlen(local_part) - (--plen);
          p >= local_part; p--)
-      if (strncmpic(prefix, p, plen) == 0) return plen + p - local_part;
+      if (strncmpic(prefix, p, plen) == 0)
+       {
+       unsigned vlen = p - local_part;
+       if (vp) *vp = vlen;
+       return plen + vlen;
+       }
     }
   else
     }
   else
-    if (strncmpic(prefix, local_part, plen) == 0) return plen;
+    if (strncmpic(prefix, local_part, plen) == 0)
+      {
+      if (vp) *vp = 0;
+      return plen;
+      }
   }
 
 return 0;
   }
 
 return 0;
@@ -375,31 +442,40 @@ is a wildcard.
 Arguments:
   local_part    the local part to check
   suffixes      the list of suffixes
 Arguments:
   local_part    the local part to check
   suffixes      the list of suffixes
+  vp           if set, pointer to place for size of wildcard portion
 
 Returns:        length of matching suffix or zero
 */
 
 int
 
 Returns:        length of matching suffix or zero
 */
 
 int
-route_check_suffix(const uschar *local_part, const uschar *suffixes)
+route_check_suffix(const uschar * local_part, const uschar * suffixes,
+  unsigned * vp)
 {
 int sep = 0;
 int alen = Ustrlen(local_part);
 uschar *suffix;
 const uschar *listptr = suffixes;
 {
 int sep = 0;
 int alen = Ustrlen(local_part);
 uschar *suffix;
 const uschar *listptr = suffixes;
-uschar sufbuf[64];
 
 
-while ((suffix = string_nextinlist(&listptr, &sep, sufbuf, sizeof(sufbuf))))
+while ((suffix = string_nextinlist(&listptr, &sep, NULL, 0)))
   {
   int slen = Ustrlen(suffix);
   if (suffix[slen-1] == '*')
     {
   {
   int slen = Ustrlen(suffix);
   if (suffix[slen-1] == '*')
     {
-    const uschar *pend = local_part + alen - (--slen) + 1;
+    const uschar * pend = local_part + alen - (--slen) + 1;
     for (const uschar * p = local_part; p < pend; p++)
     for (const uschar * p = local_part; p < pend; p++)
-      if (strncmpic(suffix, p, slen) == 0) return alen - (p - local_part);
+      if (strncmpic(suffix, p, slen) == 0)
+       {
+       int tlen = alen - (p - local_part);
+       if (vp) *vp = tlen - slen;
+       return tlen;
+       }
     }
   else
     if (alen > slen && strncmpic(suffix, local_part + alen - slen, slen) == 0)
     }
   else
     if (alen > slen && strncmpic(suffix, local_part + alen - slen, slen) == 0)
+      {
+      if (vp) *vp = 0;
       return slen;
       return slen;
+      }
   }
 
 return 0;
   }
 
 return 0;
@@ -435,9 +511,9 @@ Returns:         OK     item is in list
 */
 
 static int
 */
 
 static int
-route_check_dls(uschar *rname, uschar *type, const uschar *list,
-  tree_node **anchorptr, unsigned int *cache_bits, int listtype,
-  const uschar *domloc, const uschar **ldata, BOOL caseless, uschar **perror)
+route_check_dls(const uschar * rname, const uschar * type, const uschar * list,
+  tree_node ** anchorptr, unsigned int * cache_bits, int listtype,
+  const uschar * domloc, const uschar ** ldata, BOOL caseless, uschar ** perror)
 {
 if (!list) return OK;   /* Empty list always succeeds */
 
 {
 if (!list) return OK;   /* Empty list always succeeds */
 
@@ -585,14 +661,13 @@ gid_t gid = 0;            /* For picky compilers */
 BOOL ugid_set = FALSE;
 const uschar *listptr;
 uschar *check;
 BOOL ugid_set = FALSE;
 const uschar *listptr;
 uschar *check;
-uschar buffer[1024];
 
 if (!s) return OK;
 
 
 if (!s) return OK;
 
-DEBUG(D_route) debug_printf("checking require_files\n");
+DEBUG(D_route|D_expand) debug_printf("checking require_files\n");
 
 listptr = s;
 
 listptr = s;
-while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
+while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
   {
   int rc;
   int eacces_code = 0;
   {
   int rc;
   int eacces_code = 0;
@@ -610,7 +685,7 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
 
   /* Empty items are just skipped */
 
 
   /* Empty items are just skipped */
 
-  if (*ss == 0) continue;
+  if (!*ss) continue;
 
   /* If there are no slashes in the string, we have a user name or uid, with
   optional group/gid. */
 
   /* If there are no slashes in the string, we have a user name or uid, with
   optional group/gid. */
@@ -624,9 +699,9 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
     /* If there's a comma, temporarily terminate the user name/number
     at that point. Then set the uid. */
 
     /* If there's a comma, temporarily terminate the user name/number
     at that point. Then set the uid. */
 
-    if (comma != NULL) *comma = 0;
+    if (comma) *comma = 0;
     ok = route_finduser(ss, &pw, &uid);
     ok = route_finduser(ss, &pw, &uid);
-    if (comma != NULL) *comma = ',';
+    if (comma) *comma = ',';
 
     if (!ok)
       {
 
     if (!ok)
       {
@@ -636,24 +711,22 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
 
     /* If there was no comma, the gid is that associated with the user. */
 
 
     /* If there was no comma, the gid is that associated with the user. */
 
-    if (comma == NULL)
-      {
-      if (pw != NULL) gid = pw->pw_gid; else
+    if (!comma)
+      if (pw)
+       gid = pw->pw_gid;
+      else
         {
         *perror = string_sprintf("group missing after numerical uid %d for "
           "require_files", (int)uid);
         goto RETURN_DEFER;
         }
         {
         *perror = string_sprintf("group missing after numerical uid %d for "
           "require_files", (int)uid);
         goto RETURN_DEFER;
         }
-      }
     else
     else
-      {
       if (!route_findgroup(comma + 1, &gid))
         {
         *perror = string_sprintf("group \"%s\" for require_files not found\n",
           comma + 1);
         goto RETURN_DEFER;
         }
       if (!route_findgroup(comma + 1, &gid))
         {
         *perror = string_sprintf("group \"%s\" for require_files not found\n",
           comma + 1);
         goto RETURN_DEFER;
         }
-      }
 
     /* Note that we have values set, and proceed to next item */
 
 
     /* Note that we have values set, and proceed to next item */
 
@@ -668,13 +741,13 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
   if (*ss == '+')
     {
     eacces_code = 1;
   if (*ss == '+')
     {
     eacces_code = 1;
-    while (isspace((*(++ss))));
+    while (isspace(*++ss));
     }
 
   if (*ss == '!')
     {
     invert = TRUE;
     }
 
   if (*ss == '!')
     {
     invert = TRUE;
-    while (isspace((*(++ss))));
+    while (isspace(*++ss));
     }
 
   if (*ss != '/')
     }
 
   if (*ss != '/')
@@ -715,7 +788,7 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
     otherwise. Save the old state for resetting on the wait. */
 
     oldsignal = signal(SIGCHLD, SIG_DFL);
     otherwise. Save the old state for resetting on the wait. */
 
     oldsignal = signal(SIGCHLD, SIG_DFL);
-    pid = fork();
+    pid = exim_fork(US"require-files");
 
     /* If fork() fails, reinstate the original error and behave as if
     this block of code were not present. This is the same behaviour as happens
 
     /* If fork() fails, reinstate the original error and behave as if
     this block of code were not present. This is the same behaviour as happens
@@ -738,9 +811,9 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
       exim_setugid(uid, gid, TRUE,
         string_sprintf("require_files check, file=%s", ss));
       if (route_check_access(ss, uid, gid, 4))
       exim_setugid(uid, gid, TRUE,
         string_sprintf("require_files check, file=%s", ss));
       if (route_check_access(ss, uid, gid, 4))
-       exim_underbar_exit(0);
+       exim_underbar_exit(EXIT_SUCCESS);
       DEBUG(D_route) debug_printf("route_check_access() failed\n");
       DEBUG(D_route) debug_printf("route_check_access() failed\n");
-      exim_underbar_exit(1);
+      exim_underbar_exit(EXIT_FAILURE);
       }
 
     /* In the parent, wait for the child to finish */
       }
 
     /* In the parent, wait for the child to finish */
@@ -832,12 +905,13 @@ Returns:       OK if all the tests succeed
 */
 
 static BOOL
 */
 
 static BOOL
-check_router_conditions(router_instance *r, address_item *addr, int verify,
-  struct passwd **pw, uschar **perror)
+check_router_conditions(router_instance * r, address_item * addr, int verify,
+  struct passwd ** pw, uschar ** perror)
 {
 int rc;
 {
 int rc;
-uschar *check_local_part;
-unsigned int *localpart_cache;
+uschar * check_local_part;
+unsigned int * localpart_cache;
+const uschar * rname = r->drinst.name;
 
 /* Reset variables to hold a home directory and data from lookup of a domain or
 local part, and ensure search_find_defer is unset, in case there aren't any
 
 /* Reset variables to hold a home directory and data from lookup of a domain or
 local part, and ensure search_find_defer is unset, in case there aren't any
@@ -855,7 +929,7 @@ f.search_find_defer = FALSE;
 
 if ((verify == v_none || verify == v_expn) && r->verify_only)
   {
 
 if ((verify == v_none || verify == v_expn) && r->verify_only)
   {
-  DEBUG(D_route) debug_printf("%s router skipped: verify_only set\n", r->name);
+  DEBUG(D_route) debug_printf("%s router skipped: verify_only set\n", rname);
   return SKIP;
   }
 
   return SKIP;
   }
 
@@ -864,7 +938,7 @@ if ((verify == v_none || verify == v_expn) && r->verify_only)
 if (f.address_test_mode && !r->address_test)
   {
   DEBUG(D_route) debug_printf("%s router skipped: address_test is unset\n",
 if (f.address_test_mode && !r->address_test)
   {
   DEBUG(D_route) debug_printf("%s router skipped: address_test is unset\n",
-    r->name);
+    rname);
   return SKIP;
   }
 
   return SKIP;
   }
 
@@ -875,7 +949,7 @@ if ((verify == v_sender && !r->verify_sender) ||
     (verify == v_recipient && !r->verify_recipient))
   {
   DEBUG(D_route) debug_printf("%s router skipped: verify %d %d %d\n",
     (verify == v_recipient && !r->verify_recipient))
   {
   DEBUG(D_route) debug_printf("%s router skipped: verify %d %d %d\n",
-    r->name, verify, r->verify_sender, r->verify_recipient);
+    rname, verify, r->verify_sender, r->verify_recipient);
   return SKIP;
   }
 
   return SKIP;
   }
 
@@ -883,13 +957,13 @@ if ((verify == v_sender && !r->verify_sender) ||
 
 if (verify == v_expn && !r->expn)
   {
 
 if (verify == v_expn && !r->expn)
   {
-  DEBUG(D_route) debug_printf("%s router skipped: no_expn set\n", r->name);
+  DEBUG(D_route) debug_printf("%s router skipped: no_expn set\n", rname);
   return SKIP;
   }
 
 /* Skip this router if there's a domain mismatch. */
 
   return SKIP;
   }
 
 /* Skip this router if there's a domain mismatch. */
 
-if ((rc = route_check_dls(r->name, US"domains", r->domains, &domainlist_anchor,
+if ((rc = route_check_dls(rname, US"domains", r->domains, &domainlist_anchor,
      addr->domain_cache, TRUE, addr->domain, CUSS &deliver_domain_data,
      MCL_DOMAIN, perror)) != OK)
   return rc;
      addr->domain_cache, TRUE, addr->domain, CUSS &deliver_domain_data,
      MCL_DOMAIN, perror)) != OK)
   return rc;
@@ -901,22 +975,19 @@ because that doesn't have the prefix or suffix stripped. A bit of massaging is
 required. Also, we only use the match cache for local parts that have not had
 a prefix or suffix stripped. */
 
 required. Also, we only use the match cache for local parts that have not had
 a prefix or suffix stripped. */
 
+check_local_part = string_copy(addr->cc_local_part);
 if (!addr->prefix && !addr->suffix)
 if (!addr->prefix && !addr->suffix)
-  {
   localpart_cache = addr->localpart_cache;
   localpart_cache = addr->localpart_cache;
-  check_local_part = addr->cc_local_part;
-  }
 else
   {
   localpart_cache = NULL;
 else
   {
   localpart_cache = NULL;
-  check_local_part = string_copy(addr->cc_local_part);
   if (addr->prefix)
     check_local_part += Ustrlen(addr->prefix);
   if (addr->suffix)
     check_local_part[Ustrlen(check_local_part) - Ustrlen(addr->suffix)] = 0;
   }
 
   if (addr->prefix)
     check_local_part += Ustrlen(addr->prefix);
   if (addr->suffix)
     check_local_part[Ustrlen(check_local_part) - Ustrlen(addr->suffix)] = 0;
   }
 
-if ((rc = route_check_dls(r->name, US"local_parts", r->local_parts,
+if ((rc = route_check_dls(rname, US"local_parts", r->local_parts,
        &localpartlist_anchor, localpart_cache, MCL_LOCALPART,
        check_local_part, CUSS &deliver_localpart_data,
        !r->caseful_local_part, perror)) != OK)
        &localpartlist_anchor, localpart_cache, MCL_LOCALPART,
        check_local_part, CUSS &deliver_localpart_data,
        !r->caseful_local_part, perror)) != OK)
@@ -926,7 +997,7 @@ if ((rc = route_check_dls(r->name, US"local_parts", r->local_parts,
 login of a local user. Note: the third argument to route_finduser() must be
 NULL here, to prevent a numeric string being taken as a numeric uid. If the
 user is found, set deliver_home to the home directory, and also set
 login of a local user. Note: the third argument to route_finduser() must be
 NULL here, to prevent a numeric string being taken as a numeric uid. If the
 user is found, set deliver_home to the home directory, and also set
-local_user_{uid,gid}.  */
+local_user_{uid,gid} and local_part_data.  */
 
 if (r->check_local_user)
   {
 
 if (r->check_local_user)
   {
@@ -934,9 +1005,11 @@ if (r->check_local_user)
   if (!route_finduser(addr->local_part, pw, NULL))
     {
     DEBUG(D_route) debug_printf("%s router skipped: %s is not a local user\n",
   if (!route_finduser(addr->local_part, pw, NULL))
     {
     DEBUG(D_route) debug_printf("%s router skipped: %s is not a local user\n",
-      r->name, addr->local_part);
+      rname, addr->local_part);
     return SKIP;
     }
     return SKIP;
     }
+  addr->prop.localpart_data =
+    deliver_localpart_data = string_copy(US (*pw)->pw_name);
   deliver_home = string_copy(US (*pw)->pw_dir);
   local_user_gid = (*pw)->pw_gid;
   local_user_uid = (*pw)->pw_uid;
   deliver_home = string_copy(US (*pw)->pw_dir);
   local_user_gid = (*pw)->pw_gid;
   local_user_uid = (*pw)->pw_uid;
@@ -948,31 +1021,29 @@ check_local_user before any subsequent expansions are done. Otherwise, $home
 could mean different things for different options, which would be extremely
 confusing. */
 
 could mean different things for different options, which would be extremely
 confusing. */
 
+GET_OPTION("router_home_directory");
 if (r->router_home_directory)
   {
 if (r->router_home_directory)
   {
-  uschar *router_home = expand_string(r->router_home_directory);
-  if (!router_home)
-    {
-    if (!f.expand_string_forcedfail)
-      {
-      *perror = string_sprintf("failed to expand \"%s\" for "
-        "router_home_directory: %s", r->router_home_directory,
-        expand_string_message);
-      return DEFER;
-      }
-    }
-  else
+  uschar * router_home = expand_string(r->router_home_directory);
+  if (router_home)
     {
     setflag(addr, af_home_expanded); /* Note set from router_home_directory */
     deliver_home = router_home;
     }
     {
     setflag(addr, af_home_expanded); /* Note set from router_home_directory */
     deliver_home = router_home;
     }
+  else if (!f.expand_string_forcedfail)
+    {
+    *perror = string_sprintf("failed to expand \"%s\" for "
+      "router_home_directory: %s", r->router_home_directory,
+      expand_string_message);
+    return DEFER;
+    }
   }
 
 /* Skip if the sender condition is not met. We leave this one till after the
 local user check so that $home is set - enabling the possibility of letting
 individual recipients specify lists of acceptable/unacceptable senders. */
 
   }
 
 /* Skip if the sender condition is not met. We leave this one till after the
 local user check so that $home is set - enabling the possibility of letting
 individual recipients specify lists of acceptable/unacceptable senders. */
 
-if ((rc = route_check_dls(r->name, US"senders", r->senders, NULL,
+if ((rc = route_check_dls(rname, US"senders", r->senders, NULL,
      sender_address_cache, MCL_ADDRESS, NULL, NULL, FALSE, perror)) != OK)
   return rc;
 
      sender_address_cache, MCL_ADDRESS, NULL, NULL, FALSE, perror)) != OK)
   return rc;
 
@@ -986,7 +1057,7 @@ debug_print_string(r->debug_string);
 
 if ((rc = check_files(r->require_files, perror)) != OK)
   {
 
 if ((rc = check_files(r->require_files, perror)) != OK)
   {
-  DEBUG(D_route) debug_printf("%s router %s: file check\n", r->name,
+  DEBUG(D_route) debug_printf("%s router %s: file check\n", rname,
     (rc == SKIP)? "skipped" : "deferred");
   return rc;
   }
     (rc == SKIP)? "skipped" : "deferred");
   return rc;
   }
@@ -995,8 +1066,9 @@ if ((rc = check_files(r->require_files, perror)) != OK)
 
 if (r->condition)
   {
 
 if (r->condition)
   {
-  DEBUG(D_route) debug_printf("checking \"condition\" \"%.80s\"...\n", r->condition);
-  if (!expand_check_condition(r->condition, r->name, US"router"))
+  DEBUG(D_route|D_expand)
+    debug_printf("checking \"condition\" \"%.80s\"...\n", r->condition);
+  if (!expand_check_condition(r->condition, rname, US"router"))
     {
     if (f.search_find_defer)
       {
     {
     if (f.search_find_defer)
       {
@@ -1005,7 +1077,7 @@ if (r->condition)
       return DEFER;
       }
     DEBUG(D_route)
       return DEFER;
       }
     DEBUG(D_route)
-      debug_printf("%s router skipped: condition failure\n", r->name);
+      debug_printf("%s router skipped: condition failure\n", rname);
     return SKIP;
     }
   }
     return SKIP;
     }
   }
@@ -1018,7 +1090,7 @@ if (r->bmi_rule)
   if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0)
     {    /* none of the rules fired */
     DEBUG(D_route)
   if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0)
     {    /* none of the rules fired */
     DEBUG(D_route)
-      debug_printf("%s router skipped: none of bmi_rule rules fired\n", r->name);
+      debug_printf("%s router skipped: none of bmi_rule rules fired\n", rname);
     return SKIP;
     }
   }
     return SKIP;
     }
   }
@@ -1027,7 +1099,7 @@ if (r->bmi_rule)
 if (r->bmi_dont_deliver && bmi_deliver == 1)
   {
   DEBUG(D_route)
 if (r->bmi_dont_deliver && bmi_deliver == 1)
   {
   DEBUG(D_route)
-    debug_printf("%s router skipped: bmi_dont_deliver is FALSE\n", r->name);
+    debug_printf("%s router skipped: bmi_dont_deliver is FALSE\n", rname);
   return SKIP;
   }
 
   return SKIP;
   }
 
@@ -1037,7 +1109,7 @@ if (  r->bmi_deliver_alternate
    )
   {
   DEBUG(D_route)
    )
   {
   DEBUG(D_route)
-    debug_printf("%s router skipped: bmi_deliver_alternate is FALSE\n", r->name);
+    debug_printf("%s router skipped: bmi_deliver_alternate is FALSE\n", rname);
   return SKIP;
   }
 
   return SKIP;
   }
 
@@ -1047,7 +1119,7 @@ if (  r->bmi_deliver_default
    )
   {
   DEBUG(D_route)
    )
   {
   DEBUG(D_route)
-    debug_printf("%s router skipped: bmi_deliver_default is FALSE\n", r->name);
+    debug_printf("%s router skipped: bmi_deliver_default is FALSE\n", rname);
   return SKIP;
   }
 #endif
   return SKIP;
   }
 #endif
@@ -1332,8 +1404,9 @@ Returns:        nothing
 */
 
 static void
 */
 
 static void
-route_unseen(uschar *name, address_item *addr, address_item **paddr_local,
-  address_item **paddr_remote, address_item **addr_new)
+route_unseen(const uschar * name, address_item * addr,
+  address_item **paddr_local, address_item ** paddr_remote,
+  address_item ** addr_new)
 {
 address_item *parent = deliver_make_addr(addr->address, TRUE);
 address_item *new = deliver_make_addr(addr->address, TRUE);
 {
 address_item *parent = deliver_make_addr(addr->address, TRUE);
 address_item *new = deliver_make_addr(addr->address, TRUE);
@@ -1369,17 +1442,16 @@ new->dsn_orcpt = addr->dsn_orcpt;
 
 
 /* As it has turned out, we haven't set headers_add or headers_remove for the
 
 
 /* As it has turned out, we haven't set headers_add or headers_remove for the
- * clone. Thinking about it, it isn't entirely clear whether they should be
- * copied from the original parent, like errors_address, or taken from the
- * unseen router, like address_data and the flags. Until somebody brings this
- * up, I propose to leave the code as it is.
- */
+clone. Thinking about it, it isn't entirely clear whether they should be
+copied from the original parent, like errors_address, or taken from the
+unseen router, like address_data and the flags. Until somebody brings this
+up, I propose to leave the code as it is.  */
 
 
 /* Set the cloned address to start at the next router, and put it onto the
 chain of new addresses. */
 
 
 
 /* Set the cloned address to start at the next router, and put it onto the
 chain of new addresses. */
 
-new->start_router = addr->router->next;
+new->start_router = addr->router->drinst.next;
 new->next = *addr_new;
 *addr_new = new;
 
 new->next = *addr_new;
 *addr_new = new;
 
@@ -1416,7 +1488,9 @@ set_router_vars(address_item * addr, const router_instance * r)
 const uschar * varlist = r->set;
 tree_node ** root = (tree_node **) &addr->prop.variables;
 int sep = ';';
 const uschar * varlist = r->set;
 tree_node ** root = (tree_node **) &addr->prop.variables;
 int sep = ';';
+const uschar * drname = r->drinst.name;
 
 
+GET_OPTION("set");
 if (!varlist) return OK;
 
 /* Walk the varlist, creating variables */
 if (!varlist) return OK;
 
 /* Walk the varlist, creating variables */
@@ -1434,12 +1508,12 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
   if (!name || name[0] != 'r' || name[1] != '_' || !name[2])
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
   if (!name || name[0] != 'r' || name[1] != '_' || !name[2])
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
-       "bad router variable name '%s' in router '%s'\n", name, r->name);
+       "bad router variable name '%s' in router '%s'\n", name, drname);
     return FAIL;
     }
   name += 2;
 
     return FAIL;
     }
   name += 2;
 
-  while (isspace(*assignment)) assignment++;
+  Uskip_whitespace(&assignment);
 
   if (!(val = expand_string(US assignment)))
     if (f.expand_string_forcedfail)
 
   if (!(val = expand_string(US assignment)))
     if (f.expand_string_forcedfail)
@@ -1451,7 +1525,8 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
 
       /* Expand "more" if necessary; DEFER => an expansion failed */
 
 
       /* Expand "more" if necessary; DEFER => an expansion failed */
 
-      yield = exp_bool(addr, US"router", r->name, D_route,
+      GET_OPTION("more");
+      yield = exp_bool(addr, US"router", drname, D_route,
                      US"more", r->more, r->expand_more, &more);
       if (yield != OK) return yield;
 
                      US"more", r->more, r->expand_more, &more);
       if (yield != OK) return yield;
 
@@ -1468,13 +1543,17 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
     else
       {
       addr->message = string_sprintf("expansion of \"%s\" failed "
     else
       {
       addr->message = string_sprintf("expansion of \"%s\" failed "
-       "in %s router: %s", ele, r->name, expand_string_message);
-      return DEFER;
+       "in %s router: %s", ele, drname, expand_string_message);
+      /* Caller will replace that for logging, if a DB lookup, to avoid exposing
+      passwords */
+      DEBUG(D_route) debug_printf("%s\n", addr->message);
+      if (!f.search_find_defer)
+      return f.search_find_defer ? DEFER : FAIL;
       }
 
   if (!(node = tree_search(*root, name)))
     {                          /* name should never be tainted */
       }
 
   if (!(node = tree_search(*root, name)))
     {                          /* name should never be tainted */
-    node = store_get(sizeof(tree_node) + Ustrlen(name), FALSE);
+    node = store_get(sizeof(tree_node) + Ustrlen(name), GET_UNTAINTED);
     Ustrcpy(node->name, name);
     (void)tree_insertnode(root, node);
     }
     Ustrcpy(node->name, name);
     (void)tree_insertnode(root, node);
     }
@@ -1523,8 +1602,9 @@ route_address(address_item *addr, address_item **paddr_local,
 {
 int yield = OK;
 BOOL unseen;
 {
 int yield = OK;
 BOOL unseen;
-router_instance *r, *nextr;
-const uschar *old_domain = addr->domain;
+router_instance * r, * nextr;
+const uschar * old_domain = addr->domain;
+const uschar * rname_l;
 
 HDEBUG(D_route)
   {
 
 HDEBUG(D_route)
   {
@@ -1538,15 +1618,14 @@ instead of at the first router. */
 
 for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   {
 
 for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   {
-  uschar *error;
-  struct passwd *pw = NULL;
+  uschar * error;
+  struct passwd * pw = NULL;
   struct passwd pwcopy;
   struct passwd pwcopy;
-  BOOL loop_detected = FALSE;
-  BOOL more;
-  int loopcount = 0;
-  int rc;
+  BOOL loop_detected = FALSE, more;
+  int loopcount = 0, rc;
 
 
-  DEBUG(D_route) debug_printf("--------> %s router <--------\n", r->name);
+  rname_l = r->drinst.name;
+  DEBUG(D_route) debug_printf("--------> %s router <--------\n", rname_l);
 
   /* Reset any search error message from the previous router. */
 
 
   /* Reset any search error message from the previous router. */
 
@@ -1560,7 +1639,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   next router. */
 
   addr->router = r;
   next router. */
 
   addr->router = r;
-  nextr = r->next;
+  nextr = r->drinst.next;
 
   /* Loop protection: If this address has an ancestor with the same address,
   and that ancestor was routed by this router, we skip this router. This
 
   /* Loop protection: If this address has an ancestor with the same address,
   and that ancestor was routed by this router, we skip this router. This
@@ -1596,7 +1675,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
       if (break_loop)
         {
         DEBUG(D_route) debug_printf("%s router skipped: previously routed %s\n",
       if (break_loop)
         {
         DEBUG(D_route) debug_printf("%s router skipped: previously routed %s\n",
-          r->name, parent->address);
+          rname_l, parent->address);
         loop_detected = TRUE;
         break;
         }
         loop_detected = TRUE;
         break;
         }
@@ -1617,9 +1696,9 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   /* Default no affixes and select whether to use a caseful or caseless local
   part in this router. */
 
   /* Default no affixes and select whether to use a caseful or caseless local
   part in this router. */
 
-  addr->prefix = addr->suffix = NULL;
-  addr->local_part = r->caseful_local_part?
-    addr->cc_local_part : addr->lc_local_part;
+  addr->prefix = addr->prefix_v = addr->suffix = addr->suffix_v = NULL;
+  addr->local_part = r->caseful_local_part
+    addr->cc_local_part : addr->lc_local_part;
 
   DEBUG(D_route) debug_printf("local_part=%s domain=%s\n", addr->local_part,
     addr->domain);
 
   DEBUG(D_route) debug_printf("local_part=%s domain=%s\n", addr->local_part,
     addr->domain);
@@ -1630,17 +1709,29 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if (r->prefix)
     {
 
   if (r->prefix)
     {
-    int plen = route_check_prefix(addr->local_part, r->prefix);
+    unsigned vlen;
+    int plen = route_check_prefix(addr->local_part, r->prefix, &vlen);
     if (plen > 0)
       {
     if (plen > 0)
       {
-      addr->prefix = string_copyn(addr->local_part, plen);
+      /* If the variable-part is zero-length then the prefix was not
+      wildcarded and we can detaint-copy it since it matches the
+      (non-expandable) router option.  Otherwise copy the (likely) tainted match
+      and the variable-part of the match from the local_part. */
+
+      if (vlen)
+       {
+       addr->prefix = string_copyn(addr->local_part, plen);
+       addr->prefix_v = string_copyn(addr->local_part, vlen);
+       }
+      else
+       addr->prefix = string_copyn_taint(addr->local_part, plen, GET_UNTAINTED);
       addr->local_part += plen;
       DEBUG(D_route) debug_printf("stripped prefix %s\n", addr->prefix);
       }
     else if (!r->prefix_optional)
       {
       addr->local_part += plen;
       DEBUG(D_route) debug_printf("stripped prefix %s\n", addr->prefix);
       }
     else if (!r->prefix_optional)
       {
-      DEBUG(D_route) debug_printf("%s router skipped: prefix mismatch\n",
-        r->name);
+      DEBUG(D_route)
+       debug_printf("%s router skipped: prefix mismatch\n", rname_l);
       continue;
       }
     }
       continue;
       }
     }
@@ -1649,18 +1740,22 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if (r->suffix)
     {
 
   if (r->suffix)
     {
-    int slen = route_check_suffix(addr->local_part, r->suffix);
+    unsigned vlen;
+    int slen = route_check_suffix(addr->local_part, r->suffix, &vlen);
     if (slen > 0)
       {
       int lplen = Ustrlen(addr->local_part) - slen;
     if (slen > 0)
       {
       int lplen = Ustrlen(addr->local_part) - slen;
-      addr->suffix = addr->local_part + lplen;
+      addr->suffix = vlen
+       ? addr->local_part + lplen
+       : string_copy_taint(addr->local_part + lplen, GET_UNTAINTED);
+      addr->suffix_v = addr->suffix + Ustrlen(addr->suffix) - vlen;
       addr->local_part = string_copyn(addr->local_part, lplen);
       DEBUG(D_route) debug_printf("stripped suffix %s\n", addr->suffix);
       }
     else if (!r->suffix_optional)
       {
       addr->local_part = string_copyn(addr->local_part, lplen);
       DEBUG(D_route) debug_printf("stripped suffix %s\n", addr->suffix);
       }
     else if (!r->suffix_optional)
       {
-      DEBUG(D_route) debug_printf("%s router skipped: suffix mismatch\n",
-        r->name);
+      DEBUG(D_route)
+       debug_printf("%s router skipped: suffix mismatch\n", rname_l);
       continue;
       }
     }
       continue;
       }
     }
@@ -1668,7 +1763,9 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   /* Set the expansion variables now that we have the affixes and the case of
   the local part sorted. */
 
   /* Set the expansion variables now that we have the affixes and the case of
   the local part sorted. */
 
-  router_name = r->name;
+  router_name = rname_l;
+  driver_srcfile = r->drinst.srcfile;
+  driver_srcline = r->drinst.srcline;
   deliver_set_expansions(addr);
 
   /* For convenience, the pre-router checks are in a separate function, which
   deliver_set_expansions(addr);
 
   /* For convenience, the pre-router checks are in a separate function, which
@@ -1676,7 +1773,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if ((rc = check_router_conditions(r, addr, verify, &pw, &error)) != OK)
     {
 
   if ((rc = check_router_conditions(r, addr, verify, &pw, &error)) != OK)
     {
-    router_name = NULL;
+    driver_srcfile = router_name = NULL; driver_srcline = 0;
     if (rc == SKIP) continue;
     addr->message = error;
     yield = rc;
     if (rc == SKIP) continue;
     addr->message = error;
     yield = rc;
@@ -1695,11 +1792,11 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   router traversal.  On the addr string they are held as a variable tree, so
   as to maintain the post-expansion taints separate. */
 
   router traversal.  On the addr string they are held as a variable tree, so
   as to maintain the post-expansion taints separate. */
 
-  switch (set_router_vars(addr, r))
+  switch (rc = set_router_vars(addr, r))
     {
     case OK:   break;
     case PASS: continue;               /* with next router */
     {
     case OK:   break;
     case PASS: continue;               /* with next router */
-    default:    goto ROUTE_EXIT;
+    default:   yield = rc; goto ROUTE_EXIT;
     }
 
   /* Finally, expand the address_data field in the router. Forced failure
     }
 
   /* Finally, expand the address_data field in the router. Forced failure
@@ -1709,7 +1806,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if (r->address_data)
     {
 
   if (r->address_data)
     {
-    DEBUG(D_route) debug_printf("processing address_data\n");
+    DEBUG(D_route|D_expand) debug_printf("processing address_data\n");
     if (!(deliver_address_data = expand_string(r->address_data)))
       {
       if (f.expand_string_forcedfail)
     if (!(deliver_address_data = expand_string(r->address_data)))
       {
       if (f.expand_string_forcedfail)
@@ -1719,7 +1816,8 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
         /* Expand "more" if necessary; DEFER => an expansion failed */
 
 
         /* Expand "more" if necessary; DEFER => an expansion failed */
 
-        yield = exp_bool(addr, US"router", r->name, D_route,
+       GET_OPTION("more");
+        yield = exp_bool(addr, US"router", rname_l, D_route,
                        US"more", r->more, r->expand_more, &more);
         if (yield != OK) goto ROUTE_EXIT;
 
                        US"more", r->more, r->expand_more, &more);
         if (yield != OK) goto ROUTE_EXIT;
 
@@ -1727,7 +1825,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
           {
           DEBUG(D_route)
             debug_printf("\"more\"=false: skipping remaining routers\n");
           {
           DEBUG(D_route)
             debug_printf("\"more\"=false: skipping remaining routers\n");
-         router_name = NULL;
+         driver_srcfile = router_name = NULL; driver_srcline = 0;
           r = NULL;
           break;
           }
           r = NULL;
           break;
           }
@@ -1737,7 +1835,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
       else
         {
         addr->message = string_sprintf("expansion of \"%s\" failed "
       else
         {
         addr->message = string_sprintf("expansion of \"%s\" failed "
-          "in %s router: %s", r->address_data, r->name, expand_string_message);
+          "in %s router: %s", r->address_data, rname_l, expand_string_message);
         yield = DEFER;
         goto ROUTE_EXIT;
         }
         yield = DEFER;
         goto ROUTE_EXIT;
         }
@@ -1774,16 +1872,19 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   /* Run the router, and handle the consequences. */
 
 
   /* Run the router, and handle the consequences. */
 
-  HDEBUG(D_route) debug_printf("calling %s router\n", r->name);
+  HDEBUG(D_route) debug_printf("calling %s router\n", rname_l);
 
 
-  yield = (r->info->code)(r, addr, pw, verify, paddr_local, paddr_remote,
-    addr_new, addr_succeed);
+    {
+    router_info * ri = r->drinst.info;
+    yield = (ri->code)(r, addr, pw, verify, paddr_local, paddr_remote,
+      addr_new, addr_succeed);
+    }
 
 
-  router_name = NULL;
+  driver_srcfile = router_name = NULL; driver_srcline = 0;
 
   if (yield == FAIL)
     {
 
   if (yield == FAIL)
     {
-    HDEBUG(D_route) debug_printf("%s router forced address failure\n", r->name);
+    HDEBUG(D_route) debug_printf("%s router forced address failure\n", rname_l);
     goto ROUTE_EXIT;
     }
 
     goto ROUTE_EXIT;
     }
 
@@ -1795,7 +1896,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
        )
      && (yield == OK || yield == PASS))
     {
        )
      && (yield == OK || yield == PASS))
     {
-    addr->message = string_sprintf("%s router forced verify failure", r->name);
+    addr->message = string_sprintf("%s router forced verify failure", rname_l);
     if (*paddr_remote == addr) *paddr_remote = addr->next;
     if (*paddr_local == addr) *paddr_local = addr->next;
     yield = FAIL;
     if (*paddr_remote == addr) *paddr_remote = addr->next;
     if (*paddr_local == addr) *paddr_local = addr->next;
     yield = FAIL;
@@ -1809,7 +1910,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   HDEBUG(D_route)
     {
 
   HDEBUG(D_route)
     {
-    debug_printf("%s router %s for %s\n", r->name,
+    debug_printf("%s router %s for %s\n", rname_l,
       yield == PASS ? "passed" : "declined", addr->address);
     if (Ustrcmp(old_domain, addr->domain) != 0)
       debug_printf("domain %s rewritten\n", old_domain);
       yield == PASS ? "passed" : "declined", addr->address);
     if (Ustrcmp(old_domain, addr->domain) != 0)
       debug_printf("domain %s rewritten\n", old_domain);
@@ -1821,13 +1922,14 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if (yield == PASS)
     {
 
   if (yield == PASS)
     {
-    if (r->pass_router != NULL) nextr = r->pass_router;
+    if (r->pass_router) nextr = r->pass_router;
     }
   else
     {
     /* Expand "more" if necessary */
 
     }
   else
     {
     /* Expand "more" if necessary */
 
-    yield = exp_bool(addr, US"router", r->name, D_route,
+    GET_OPTION("more");
+    yield = exp_bool(addr, US"router", rname_l, D_route,
                        US"more", r->more, r->expand_more, &more);
     if (yield != OK) goto ROUTE_EXIT;
 
                        US"more", r->more, r->expand_more, &more);
     if (yield != OK) goto ROUTE_EXIT;
 
@@ -1852,18 +1954,22 @@ if (!r)
   HDEBUG(D_route) debug_printf("no more routers\n");
   if (!addr->message)
     {
   HDEBUG(D_route) debug_printf("no more routers\n");
   if (!addr->message)
     {
-    uschar *message = US"Unrouteable address";
-    if (addr->router && addr->router->cannot_route_message)
+    uschar * message = US"Unrouteable address";
+    if (addr->router)
       {
       {
-      uschar *expmessage = expand_string(addr->router->cannot_route_message);
-      if (!expmessage)
-        {
-        if (!f.expand_string_forcedfail)
-          log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
-            "cannot_route_message in %s router: %s", addr->router->name,
-            expand_string_message);
-        }
-      else message = expmessage;
+      uschar * s = addr->router->cannot_route_message;
+      GET_OPTION("cannot_route_message");
+      if (s)
+       {
+       if ((s = expand_string(s)))
+         message = s;
+       else
+         if (!f.expand_string_forcedfail)
+           log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
+             "cannot_route_message in %s router: %s",
+             addr->router->drinst.name,
+             expand_string_message);
+       }
       }
     addr->user_message = addr->message = message;
     }
       }
     addr->user_message = addr->message = message;
     }
@@ -1875,7 +1981,7 @@ if (!r)
 if (yield == DEFER)
   {
   HDEBUG(D_route) debug_printf("%s router: defer for %s\n  message: %s\n",
 if (yield == DEFER)
   {
   HDEBUG(D_route) debug_printf("%s router: defer for %s\n  message: %s\n",
-      r->name, addr->address, addr->message ? addr->message : US"<none>");
+      rname_l, addr->address, addr->message ? addr->message : US"<none>");
   goto ROUTE_EXIT;
   }
 
   goto ROUTE_EXIT;
   }
 
@@ -1885,7 +1991,7 @@ if (yield == DISCARD) goto ROUTE_EXIT;
 
 if (yield != OK && yield != REROUTED)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router returned unknown value %d",
 
 if (yield != OK && yield != REROUTED)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router returned unknown value %d",
-    r->name, yield);
+    rname_l, yield);
 
 /* If the yield was REROUTED, the router put a child address on the new chain
 as a result of a domain change of some sort (widening, typically). */
 
 /* If the yield was REROUTED, the router put a child address on the new chain
 as a result of a domain change of some sort (widening, typically). */
@@ -1905,6 +2011,7 @@ networking, so it is included in the binary only if requested. */
 
 #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
 
 
 #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
 
+GET_OPTION("translate_ip_address");
 if (r->translate_ip_address)
   {
   int rc;
 if (r->translate_ip_address)
   {
   int rc;
@@ -1963,20 +2070,20 @@ if (r->translate_ip_address)
 /* See if this is an unseen routing; first expand the option if necessary.
 DEFER can be given if the expansion fails */
 
 /* See if this is an unseen routing; first expand the option if necessary.
 DEFER can be given if the expansion fails */
 
-yield = exp_bool(addr, US"router", r->name, D_route,
+yield = exp_bool(addr, US"router", rname_l, D_route,
                US"unseen", r->unseen, r->expand_unseen, &unseen);
 if (yield != OK) goto ROUTE_EXIT;
 
 /* Debugging output recording a successful routing */
 
                US"unseen", r->unseen, r->expand_unseen, &unseen);
 if (yield != OK) goto ROUTE_EXIT;
 
 /* Debugging output recording a successful routing */
 
-HDEBUG(D_route) debug_printf("routed by %s router%s\n", r->name,
-    unseen? " (unseen)" : "");
+HDEBUG(D_route) debug_printf("routed by %s router%s\n", rname_l,
+    unseen ? " (unseen)" : "");
 
 DEBUG(D_route)
   {
   debug_printf("  envelope to: %s\n", addr->address);
   debug_printf("  transport: %s\n", addr->transport
 
 DEBUG(D_route)
   {
   debug_printf("  envelope to: %s\n", addr->address);
   debug_printf("  transport: %s\n", addr->transport
-    ? addr->transport->name : US"<none>");
+    ? addr->transport->drinst.name : US"<none>");
 
   if (addr->prop.errors_address)
     debug_printf("  errors to %s\n", addr->prop.errors_address);
 
   if (addr->prop.errors_address)
     debug_printf("  errors to %s\n", addr->prop.errors_address);
@@ -1997,8 +2104,8 @@ DEBUG(D_route)
 the "unseen" option (ignore if there are no further routers). */
 
 addr->message = NULL;
 the "unseen" option (ignore if there are no further routers). */
 
 addr->message = NULL;
-if (unseen && r->next)
-  route_unseen(r->name, addr, paddr_local, paddr_remote, addr_new);
+if (unseen && r->drinst.next)
+  route_unseen(rname_l, addr, paddr_local, paddr_remote, addr_new);
 
 /* Unset the address expansions, and return the final result. */
 
 
 /* Unset the address expansions, and return the final result. */
 
@@ -2007,10 +2114,23 @@ if (yield == DEFER && addr->message)
   addr->message = expand_hide_passwords(addr->message);
 
 deliver_set_expansions(NULL);
   addr->message = expand_hide_passwords(addr->message);
 
 deliver_set_expansions(NULL);
-router_name = NULL;
+driver_srcfile = router_name = NULL; driver_srcline = 0;
 f.disable_logging = FALSE;
 return yield;
 }
 
 f.disable_logging = FALSE;
 return yield;
 }
 
+
+
+/* For error messages, a string describing the config location associated
+with current processing.  NULL if we are not in a router. */
+/* Name only, for now */
+
+uschar *
+router_current_name(void)
+{
+if (!router_name) return NULL;
+return string_sprintf(" (router %s, %s %d)", router_name, driver_srcfile, driver_srcline);
+}
+
 #endif /*!MACRO_PREDEF*/
 /* End of route.c */
 #endif /*!MACRO_PREDEF*/
 /* End of route.c */