build: use pkg-config for i18n
[exim.git] / src / src / route.c
index 857fc65db40ef6f78e4bfbdd1025c9dbf2a4c76a..a54a7e84d29d3d189a9855f9ff684a0de8714f92 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* 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,
+                 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 = sizeof(optionlist_routers)/sizeof(optionlist);
+int optionlist_routers_size = nelem(optionlist_routers);
 
 
 #ifdef MACRO_PREDEF
 
 
 #ifdef MACRO_PREDEF
@@ -150,16 +155,16 @@ int optionlist_routers_size = sizeof(optionlist_routers)/sizeof(optionlist);
 void
 options_routers(void)
 {
 void
 options_routers(void)
 {
-struct router_info * ri;
 uschar buf[64];
 
 options_from_list(optionlist_routers, nelem(optionlist_routers), US"ROUTERS", NULL);
 
 uschar buf[64];
 
 options_from_list(optionlist_routers, nelem(optionlist_routers), US"ROUTERS", NULL);
 
-for (ri = routers_available; ri->driver_name[0]; ri++)
+for (driver_info * di = (driver_info *)routers_available; di; di = di->next)
   {
   {
-  spf(buf, sizeof(buf), "_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);
   }
 }
 
   }
 }
 
@@ -187,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;
@@ -199,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);
 }
 
 
 }
 
 
@@ -222,20 +227,63 @@ function. */
 void
 route_init(void)
 {
 void
 route_init(void)
 {
-router_instance *r;
+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;
+
 
 
-readconf_driver_init(US"router",
-  (driver_instance **)(&routers),     /* chain anchor */
-  (driver_info *)routers_available,   /* available drivers */
+/* 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 (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.) */
@@ -244,16 +292,13 @@ for (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) != 0 &&
-      r->transport_name == NULL &&
-      !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) != 0 &&
-       r->transport_name != NULL)
+  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. */
@@ -266,40 +311,48 @@ for (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 */
 
-  if (r->redirect_router_name != NULL)
+  if (r->redirect_router_name)
     set_router(r, r->redirect_router_name, &(r->redirect_router), FALSE);
 
     set_router(r, r->redirect_router_name, &(r->redirect_router), FALSE);
 
-  if (r->pass_router_name != NULL)
+  if (r->pass_router_name)
     set_router(r, r->pass_router_name, &(r->pass_router), TRUE);
 
     set_router(r, r->pass_router_name, &(r->pass_router), TRUE);
 
+#ifdef notdef
   DEBUG(D_route) debug_printf("DSN: %s %s\n", r->name,
        r->dsn_lasthop ? "lasthop set" : "propagating DSN");
   DEBUG(D_route) debug_printf("DSN: %s %s\n", r->name,
        r->dsn_lasthop ? "lasthop set" : "propagating DSN");
+#endif
   }
 }
 
   }
 }
 
@@ -317,9 +370,11 @@ is finished, via this function. */
 void
 route_tidyup(void)
 {
 void
 route_tidyup(void)
 {
-router_instance *r;
-for (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);
+  }
 }
 
 
 }
 
 
@@ -335,31 +390,40 @@ 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] == '*')
     {
-    const uschar *p;
     prefix++;
     prefix++;
-    for (p = local_part + Ustrlen(local_part) - (--plen);
+    for (const uschar * p = local_part + Ustrlen(local_part) - (--plen);
          p >= local_part; p--)
          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;
@@ -378,32 +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 *p, *pend;
-    pend = local_part + alen - (--slen) + 1;
-    for (p = local_part; p < pend; p++)
-      if (strncmpic(suffix, p, slen) == 0) return alen - (p - local_part);
+    const uschar * pend = local_part + alen - (--slen) + 1;
+    for (const uschar * p = local_part; p < pend; p++)
+      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;
@@ -439,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 */
 
@@ -589,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;
@@ -606,7 +677,7 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
 
   if (!ss)
     {
 
   if (!ss)
     {
-    if (expand_string_forcedfail) continue;
+    if (f.expand_string_forcedfail) continue;
     *perror = string_sprintf("failed to expand \"%s\" for require_files: %s",
       check, expand_string_message);
     goto RETURN_DEFER;
     *perror = string_sprintf("failed to expand \"%s\" for require_files: %s",
       check, expand_string_message);
     goto RETURN_DEFER;
@@ -614,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. */
@@ -628,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)
       {
@@ -640,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 */
 
@@ -672,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 != '/')
@@ -719,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
@@ -741,21 +810,20 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer))))
       {
       exim_setugid(uid, gid, TRUE,
         string_sprintf("require_files check, file=%s", ss));
       {
       exim_setugid(uid, gid, TRUE,
         string_sprintf("require_files check, file=%s", ss));
-      if (route_check_access(ss, uid, gid, 4)) _exit(0);
+      if (route_check_access(ss, uid, gid, 4))
+       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");
-      _exit(1);
+      exim_underbar_exit(EXIT_FAILURE);
       }
 
     /* In the parent, wait for the child to finish */
 
     while (waitpid(pid, &status, 0) < 0)
       }
 
     /* In the parent, wait for the child to finish */
 
     while (waitpid(pid, &status, 0) < 0)
-     {
      if (errno != EINTR)  /* unexpected error, interpret as failure */
        {
        status = 1;
        break;
        }
      if (errno != EINTR)  /* unexpected error, interpret as failure */
        {
        status = 1;
        break;
        }
-     }
 
     signal(SIGCHLD, oldsignal);   /* restore */
     if ((status == 0) == invert) return SKIP;
 
     signal(SIGCHLD, oldsignal);   /* restore */
     if ((status == 0) == invert) return SKIP;
@@ -837,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
@@ -854,22 +923,22 @@ deliver_localpart_data = NULL;
 sender_data = NULL;
 local_user_gid = (gid_t)(-1);
 local_user_uid = (uid_t)(-1);
 sender_data = NULL;
 local_user_gid = (gid_t)(-1);
 local_user_uid = (uid_t)(-1);
-search_find_defer = FALSE;
+f.search_find_defer = FALSE;
 
 /* Skip this router if not verifying and it has verify_only set */
 
 if ((verify == v_none || verify == v_expn) && r->verify_only)
   {
 
 /* Skip this router if not verifying and it has verify_only set */
 
 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;
   }
 
 /* Skip this router if testing an address (-bt) and address_test is not set */
 
   return SKIP;
   }
 
 /* Skip this router if testing an address (-bt) and address_test is not set */
 
-if (address_test_mode && !r->address_test)
+if (f.address_test_mode && !r->address_test)
   {
   DEBUG(D_route) debug_printf("%s router skipped: address_test is unset\n",
   {
   DEBUG(D_route) debug_printf("%s router skipped: address_test is unset\n",
-    r->name);
+    rname);
   return SKIP;
   }
 
   return SKIP;
   }
 
@@ -880,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;
   }
 
@@ -888,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;
@@ -906,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)
@@ -931,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)
   {
@@ -939,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;
@@ -953,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 (!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;
 
@@ -991,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;
   }
@@ -1000,17 +1066,18 @@ 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 (search_find_defer)
+    if (f.search_find_defer)
       {
       *perror = US"condition check lookup defer";
       DEBUG(D_route) debug_printf("%s\n", *perror);
       return DEFER;
       }
     DEBUG(D_route)
       {
       *perror = US"condition check lookup defer";
       DEBUG(D_route) debug_printf("%s\n", *perror);
       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;
     }
   }
@@ -1023,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;
     }
   }
@@ -1032,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;
   }
 
@@ -1042,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;
   }
 
@@ -1052,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
@@ -1105,7 +1172,7 @@ route_finduser(const uschar *s, struct passwd **pw, uid_t *return_uid)
 BOOL cache_set = (Ustrcmp(lastname, s) == 0);
 
 DEBUG(D_uid) debug_printf("seeking password data for user \"%s\": %s\n", s,
 BOOL cache_set = (Ustrcmp(lastname, s) == 0);
 
 DEBUG(D_uid) debug_printf("seeking password data for user \"%s\": %s\n", s,
-  cache_set? "using cached result" : "cache not available");
+  cache_set ? "using cached result" : "cache not available");
 
 if (!cache_set)
   {
 
 if (!cache_set)
   {
@@ -1119,7 +1186,7 @@ if (!cache_set)
     return TRUE;
     }
 
     return TRUE;
     }
 
-  (void)string_format(lastname, sizeof(lastname), "%s", s);
+  string_format_nt(lastname, sizeof(lastname), "%s", s);
 
   /* Force failure if string length is greater than given maximum */
 
 
   /* Force failure if string length is greater than given maximum */
 
@@ -1337,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);
@@ -1365,24 +1433,25 @@ new->prop.errors_address = parent->prop.errors_address;
 
 /* Copy the propagated flags and address_data from the original. */
 
 
 /* Copy the propagated flags and address_data from the original. */
 
-copyflag(new, addr, af_propagate);
+new->prop.ignore_error = addr->prop.ignore_error;
 new->prop.address_data = addr->prop.address_data;
 new->prop.address_data = addr->prop.address_data;
+new->prop.variables = NULL;
+tree_dup((tree_node **)&new->prop.variables, addr->prop.variables);
 new->dsn_flags = addr->dsn_flags;
 new->dsn_orcpt = addr->dsn_orcpt;
 
 
 /* As it has turned out, we haven't set headers_add or headers_remove for the
 new->dsn_flags = addr->dsn_flags;
 new->dsn_orcpt = addr->dsn_orcpt;
 
 
 /* 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;
 
@@ -1409,6 +1478,97 @@ if (addr->transport && tree_search(tree_nonrecipients, addr->unique))
 
 
 
 
 
 
+/************************************************/
+/* Add router-assigned variables
+Return OK/DEFER/FAIL/PASS */
+
+static int
+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 * drname = r->drinst.name;
+
+GET_OPTION("set");
+if (!varlist) return OK;
+
+/* Walk the varlist, creating variables */
+
+for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
+  {
+  const uschar * assignment = ele;
+  int esep = '=';
+  uschar * name = string_nextinlist(&assignment, &esep, NULL, 0);
+  uschar * val;
+  tree_node * node;
+
+  /* Variable name must exist and start "r_". */
+
+  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, drname);
+    return FAIL;
+    }
+  name += 2;
+
+  Uskip_whitespace(&assignment);
+
+  if (!(val = expand_string(US assignment)))
+    if (f.expand_string_forcedfail)
+      {
+      int yield;
+      BOOL more;
+      DEBUG(D_route) debug_printf("forced failure in expansion of \"%s\" "
+         "(router variable): decline action taken\n", ele);
+
+      /* Expand "more" if necessary; DEFER => an expansion failed */
+
+      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;
+
+      if (!more)
+       {
+       DEBUG(D_route)
+         debug_printf("\"more\"=false: skipping remaining routers\n");
+       router_name = NULL;
+       r = NULL;
+       return FAIL;
+       }
+      return PASS;
+      }
+    else
+      {
+      addr->message = string_sprintf("expansion of \"%s\" failed "
+       "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 */
+    node = store_get(sizeof(tree_node) + Ustrlen(name), GET_UNTAINTED);
+    Ustrcpy(node->name, name);
+    (void)tree_insertnode(root, node);
+    }
+  node->data.ptr = US val;
+  DEBUG(D_route) debug_printf("set r_%s%s = '%s'%s\n",
+                   name, is_tainted(name)?" (tainted)":"",
+                   val, is_tainted(val)?" (tainted)":"");
+
+  /* All expansions after this point need visibility of that variable */
+  router_var = *root;
+  }
+return OK;
+}
+
+
 /*************************************************
 *                 Route one address              *
 *************************************************/
 /*************************************************
 *                 Route one address              *
 *************************************************/
@@ -1442,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)
   {
@@ -1457,16 +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;
-  address_item *parent;
-  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. */
 
@@ -1474,13 +1633,13 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   /* There are some weird cases where logging is disabled */
 
 
   /* There are some weird cases where logging is disabled */
 
-  disable_logging = r->disable_logging;
+  f.disable_logging = r->disable_logging;
 
   /* Record the last router to handle the address, and set the default
   next router. */
 
   addr->router = r;
 
   /* Record the last router to handle the address, and set the default
   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
@@ -1494,7 +1653,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   continually adding to an address, for example), put a long stop counter on
   the number of parents. */
 
   continually adding to an address, for example), put a long stop counter on
   the number of parents. */
 
-  for (parent = addr->parent; parent; parent = parent->parent)
+  for (address_item * parent = addr->parent; parent; parent = parent->parent)
     {
     if (parent->router == r)
       {
     {
     if (parent->router == r)
       {
@@ -1516,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;
         }
@@ -1537,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);
@@ -1550,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;
       }
     }
@@ -1569,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;
       }
     }
@@ -1588,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
@@ -1596,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;
@@ -1609,6 +1786,19 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   search_error_message = NULL;
 
 
   search_error_message = NULL;
 
+  /* Add any variable-settings that are on the router, to the set on the
+  addr. Expansion is done here and not later when the addr is used.  There may
+  be multiple settings, gathered during readconf; this code gathers them during
+  router traversal.  On the addr string they are held as a variable tree, so
+  as to maintain the post-expansion taints separate. */
+
+  switch (rc = set_router_vars(addr, r))
+    {
+    case OK:   break;
+    case PASS: continue;               /* with next router */
+    default:   yield = rc; goto ROUTE_EXIT;
+    }
+
   /* Finally, expand the address_data field in the router. Forced failure
   behaves as if the router declined. Any other failure is more serious. On
   success, the string is attached to the address for all subsequent processing.
   /* Finally, expand the address_data field in the router. Forced failure
   behaves as if the router declined. Any other failure is more serious. On
   success, the string is attached to the address for all subsequent processing.
@@ -1616,18 +1806,18 @@ 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");
-    deliver_address_data = expand_string(r->address_data);
-    if (!deliver_address_data)
+    DEBUG(D_route|D_expand) debug_printf("processing address_data\n");
+    if (!(deliver_address_data = expand_string(r->address_data)))
       {
       {
-      if (expand_string_forcedfail)
+      if (f.expand_string_forcedfail)
         {
         DEBUG(D_route) debug_printf("forced failure in expansion of \"%s\" "
             "(address_data): decline action taken\n", r->address_data);
 
         /* Expand "more" if necessary; DEFER => an expansion failed */
 
         {
         DEBUG(D_route) debug_printf("forced failure in expansion of \"%s\" "
             "(address_data): decline action taken\n", r->address_data);
 
         /* 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;
 
@@ -1635,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;
           }
@@ -1645,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;
         }
@@ -1672,10 +1862,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
     pw = &pwcopy;
     }
 
     pw = &pwcopy;
     }
 
-  /* Run the router, and handle the consequences. */
-
-  /* ... but let us check on DSN before. If this should be the last hop for DSN
-  set flag. */
+  /* If this should be the last hop for DSN flag the addr. */
 
   if (r->dsn_lasthop && !(addr->dsn_flags & rf_dsnlasthop))
     {
 
   if (r->dsn_lasthop && !(addr->dsn_flags & rf_dsnlasthop))
     {
@@ -1683,27 +1870,33 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
     HDEBUG(D_route) debug_printf("DSN: last hop for %s\n", addr->address);
     }
 
     HDEBUG(D_route) debug_printf("DSN: last hop for %s\n", addr->address);
     }
 
-  HDEBUG(D_route) debug_printf("calling %s router\n", r->name);
+  /* Run the router, and handle the consequences. */
+
+  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;
     }
 
   /* If succeeded while verifying but fail_verify is set, convert into
   a failure, and take it off the local or remote delivery list. */
 
     goto ROUTE_EXIT;
     }
 
   /* If succeeded while verifying but fail_verify is set, convert into
   a failure, and take it off the local or remote delivery list. */
 
-  if (((verify == v_sender && r->fail_verify_sender) ||
-       (verify == v_recipient && r->fail_verify_recipient)) &&
-      (yield == OK || yield == PASS))
+  if (  (  verify == v_sender && r->fail_verify_sender
+       || verify == v_recipient && r->fail_verify_recipient
+       )
+     && (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;
@@ -1717,8 +1910,8 @@ 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,
-      (yield == PASS)? "passed" : "declined", addr->address);
+    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);
     }
     if (Ustrcmp(old_domain, addr->domain) != 0)
       debug_printf("domain %s rewritten\n", old_domain);
     }
@@ -1729,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;
 
@@ -1760,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 (!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;
     }
@@ -1782,12 +1980,8 @@ if (!r)
 
 if (yield == DEFER)
   {
 
 if (yield == DEFER)
   {
-  HDEBUG(D_route)
-    {
-    debug_printf("%s router: defer for %s\n", r->name, addr->address);
-    debug_printf("  message: %s\n", (addr->message == NULL)?
-      US"<none>" : addr->message);
-    }
+  HDEBUG(D_route) debug_printf("%s router: defer for %s\n  message: %s\n",
+      rname_l, addr->address, addr->message ? addr->message : US"<none>");
   goto ROUTE_EXIT;
   }
 
   goto ROUTE_EXIT;
   }
 
@@ -1797,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). */
@@ -1817,12 +2011,12 @@ 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;
   int old_pool = store_pool;
 if (r->translate_ip_address)
   {
   int rc;
   int old_pool = store_pool;
-  host_item *h;
-  for (h = addr->host_list; h; h = h->next)
+  for (host_item * h = addr->host_list; h; h = h->next)
     {
     uschar *newaddress;
     uschar *oldaddress, *oldname;
     {
     uschar *newaddress;
     uschar *oldaddress, *oldname;
@@ -1835,7 +2029,7 @@ if (r->translate_ip_address)
 
     if (!newaddress)
       {
 
     if (!newaddress)
       {
-      if (expand_string_forcedfail) continue;
+      if (f.expand_string_forcedfail) continue;
       addr->basic_errno = ERRNO_EXPANDFAIL;
       addr->message = string_sprintf("translate_ip_address expansion "
         "failed: %s", expand_string_message);
       addr->basic_errno = ERRNO_EXPANDFAIL;
       addr->message = string_sprintf("translate_ip_address expansion "
         "failed: %s", expand_string_message);
@@ -1876,27 +2070,25 @@ 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(D_route)
   {
-  host_item *h;
-
   debug_printf("  envelope to: %s\n", addr->address);
   debug_printf("  envelope to: %s\n", addr->address);
-  debug_printf("  transport: %s\n", (addr->transport == NULL)?
-    US"<none>" : addr->transport->name);
+  debug_printf("  transport: %s\n", addr->transport
+    ? 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);
 
-  for (h = addr->host_list; h; h = h->next)
+  for (host_item * h = addr->host_list; h; h = h->next)
     {
     debug_printf("  host %s", h->name);
     if (h->address) debug_printf(" [%s]", h->address);
     {
     debug_printf("  host %s", h->name);
     if (h->address) debug_printf(" [%s]", h->address);
@@ -1912,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. */
 
@@ -1922,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;
-disable_logging = FALSE;
+driver_srcfile = router_name = NULL; driver_srcline = 0;
+f.disable_logging = FALSE;
 return yield;
 }
 
 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 */