X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b436dd41913533c013e88c513e2ceab89c86f120..master:/src/src/route.c diff --git a/src/src/route.c b/src/src/route.c index b6930493f..a54a7e84d 100644 --- a/src/src/route.c +++ b/src/src/route.c @@ -2,8 +2,10 @@ * 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. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Functions concerned with routing, and the list of generic router options. */ @@ -14,132 +16,133 @@ /* 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, - (void *)(offsetof(router_instance, expand_gid)) }, + LOFF(expand_gid) }, { "*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, - (void *)(offsetof(router_instance, expand_unseen)) }, + LOFF(expand_unseen) }, { "*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, - (void *)(offsetof(router_instance, gid_set)) }, + LOFF(gid_set) }, { "*set_user", opt_bool | opt_hidden | opt_public, - (void *)(offsetof(router_instance, uid_set)) }, + LOFF(uid_set) }, { "address_data", opt_stringptr|opt_public, - (void *)(offsetof(router_instance, address_data)) }, + LOFF(address_data) }, { "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, - (void *)(offsetof(router_instance, bmi_deliver_alternate)) }, + LOFF(bmi_deliver_alternate) }, { "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, - (void *)(offsetof(router_instance, bmi_dont_deliver)) }, + LOFF(bmi_dont_deliver) }, { "bmi_rule", opt_stringptr|opt_public, - (void *)(offsetof(router_instance, bmi_rule)) }, + LOFF(bmi_rule) }, #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, - (void *)(offsetof(router_instance, caseful_local_part)) }, + LOFF(caseful_local_part) }, { "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, - (void *)offsetof(router_instance, condition) }, + LOFF(condition) }, { "debug_print", opt_stringptr | opt_public, - (void *)offsetof(router_instance, debug_string) }, + LOFF(debug_string) }, { "disable_logging", opt_bool | opt_public, - (void *)offsetof(router_instance, disable_logging) }, + LOFF(disable_logging) }, { "dnssec_request_domains", opt_stringptr|opt_public, - (void *)offsetof(router_instance, dnssec.request) }, + LOFF(dnssec.request) }, { "dnssec_require_domains", opt_stringptr|opt_public, - (void *)offsetof(router_instance, dnssec.require) }, + LOFF(dnssec.require) }, { "domains", opt_stringptr|opt_public, - (void *)offsetof(router_instance, domains) }, + LOFF(domains) }, { "driver", opt_stringptr|opt_public, - (void *)offsetof(router_instance, driver_name) }, + LOFF(drinst.driver_name) }, { "dsn_lasthop", opt_bool|opt_public, - (void *)offsetof(router_instance, dsn_lasthop) }, + LOFF(dsn_lasthop) }, { "errors_to", opt_stringptr|opt_public, - (void *)(offsetof(router_instance, errors_to)) }, + LOFF(errors_to) }, { "expn", opt_bool|opt_public, - (void *)offsetof(router_instance, expn) }, + LOFF(expn) }, { "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, - (void *)offsetof(router_instance, fail_verify_recipient) }, + LOFF(fail_verify_recipient) }, { "fail_verify_sender", opt_bool|opt_public, - (void *)offsetof(router_instance, fail_verify_sender) }, + LOFF(fail_verify_sender) }, { "fallback_hosts", opt_stringptr|opt_public, - (void *)offsetof(router_instance, fallback_hosts) }, + LOFF(fallback_hosts) }, { "group", opt_expand_gid | opt_public, - (void *)(offsetof(router_instance, gid)) }, + LOFF(gid) }, { "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, - (void *)offsetof(router_instance, remove_headers) }, + LOFF(remove_headers) }, { "ignore_target_hosts",opt_stringptr|opt_public, - (void *)offsetof(router_instance, ignore_target_hosts) }, + LOFF(ignore_target_hosts) }, { "initgroups", opt_bool | opt_public, - (void *)(offsetof(router_instance, initgroups)) }, + LOFF(initgroups) }, { "local_part_prefix", opt_stringptr|opt_public, - (void *)offsetof(router_instance, prefix) }, + LOFF(prefix) }, { "local_part_prefix_optional",opt_bool|opt_public, - (void *)offsetof(router_instance, prefix_optional) }, + LOFF(prefix_optional) }, { "local_part_suffix", opt_stringptr|opt_public, - (void *)offsetof(router_instance, suffix) }, + LOFF(suffix) }, { "local_part_suffix_optional",opt_bool|opt_public, - (void *)offsetof(router_instance, suffix_optional) }, + LOFF(suffix_optional) }, { "local_parts", opt_stringptr|opt_public, - (void *)offsetof(router_instance, local_parts) }, + LOFF(local_parts) }, { "log_as_local", opt_bool|opt_public, - (void *)offsetof(router_instance, log_as_local) }, + LOFF(log_as_local) }, { "more", opt_expand_bool|opt_public, - (void *)offsetof(router_instance, more) }, + LOFF(more) }, { "pass_on_timeout", opt_bool|opt_public, - (void *)offsetof(router_instance, pass_on_timeout) }, + LOFF(pass_on_timeout) }, { "pass_router", opt_stringptr|opt_public, - (void *)offsetof(router_instance, pass_router_name) }, + LOFF(pass_router_name) }, { "redirect_router", opt_stringptr|opt_public, - (void *)offsetof(router_instance, redirect_router_name) }, + LOFF(redirect_router_name) }, { "require_files", opt_stringptr|opt_public, - (void *)offsetof(router_instance, require_files) }, + LOFF(require_files) }, { "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, - (void *)offsetof(router_instance, router_home_directory) }, + LOFF(router_home_directory) }, { "self", opt_stringptr|opt_public, - (void *)(offsetof(router_instance, self)) }, + LOFF(self) }, { "senders", opt_stringptr|opt_public, - (void *)offsetof(router_instance, senders) }, + LOFF(senders) }, { "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, - (void *)offsetof(router_instance, translate_ip_address) }, + LOFF(translate_ip_address) }, #endif { "transport", opt_stringptr|opt_public, - (void *)offsetof(router_instance, transport_name) }, + LOFF(transport_name) }, { "transport_current_directory", opt_stringptr|opt_public, - (void *)offsetof(router_instance, current_directory) }, + LOFF(current_directory) }, { "transport_home_directory", opt_stringptr|opt_public, - (void *)offsetof(router_instance, home_directory) }, + LOFF(home_directory) }, { "unseen", opt_expand_bool|opt_public, - (void *)offsetof(router_instance, unseen) }, + LOFF(unseen) }, { "user", opt_expand_uid | opt_public, - (void *)(offsetof(router_instance, uid)) }, + LOFF(uid) }, { "verify", opt_bool_verify|opt_hidden|opt_public, - (void *)offsetof(router_instance, verify_sender) }, + LOFF(verify_sender) }, { "verify_only", opt_bool|opt_public, - (void *)offsetof(router_instance, verify_only) }, + LOFF(verify_only) }, { "verify_recipient", opt_bool|opt_public, - (void *)offsetof(router_instance, verify_recipient) }, + LOFF(verify_recipient) }, { "verify_sender", opt_bool|opt_public, - (void *)offsetof(router_instance, verify_sender) } + LOFF(verify_sender) } }; 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); -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); - 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; -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; @@ -200,11 +204,11 @@ for (rr = routers; rr; rr = rr->next) 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, - "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) { -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 */ - 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.) */ @@ -243,14 +292,13 @@ for (router_instance * r = routers; r; r = r->next) /* 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 " - "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 " - "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. */ @@ -263,29 +311,35 @@ for (router_instance * r = routers; r; r = r->next) 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; - 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 " - "%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) - 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. */ - 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 */ @@ -316,8 +370,11 @@ is finished, via this function. */ 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 + vp if set, pointer to place for size of wildcard portion 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; -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] == '*') @@ -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--) - 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 - if (strncmpic(prefix, local_part, plen) == 0) return plen; + if (strncmpic(prefix, local_part, plen) == 0) + { + if (vp) *vp = 0; + return plen; + } } return 0; @@ -375,31 +442,40 @@ is a wildcard. 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 -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; -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] == '*') { - 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++) - 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) + { + if (vp) *vp = 0; return slen; + } } return 0; @@ -435,9 +511,9 @@ Returns: OK item is in list */ 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 */ @@ -585,14 +661,13 @@ gid_t gid = 0; /* For picky compilers */ BOOL ugid_set = FALSE; const uschar *listptr; uschar *check; -uschar buffer[1024]; 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; -while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))) +while ((check = string_nextinlist(&listptr, &sep, NULL, 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 */ - 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. */ @@ -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 (comma != NULL) *comma = 0; + if (comma) *comma = 0; ok = route_finduser(ss, &pw, &uid); - if (comma != NULL) *comma = ','; + if (comma) *comma = ','; 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 (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; } - } else - { 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 */ @@ -668,13 +741,13 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))) if (*ss == '+') { eacces_code = 1; - while (isspace((*(++ss)))); + while (isspace(*++ss)); } if (*ss == '!') { invert = TRUE; - while (isspace((*(++ss)))); + while (isspace(*++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); - 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 @@ -737,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)); - 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"); - _exit(1); + exim_underbar_exit(EXIT_FAILURE); } /* 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; } - } signal(SIGCHLD, oldsignal); /* restore */ if ((status == 0) == invert) return SKIP; @@ -833,12 +905,13 @@ Returns: OK if all the tests succeed */ 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; -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 @@ -856,7 +929,7 @@ f.search_find_defer = FALSE; 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; } @@ -865,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", - r->name); + rname); return SKIP; } @@ -876,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", - r->name, verify, r->verify_sender, r->verify_recipient); + rname, verify, r->verify_sender, r->verify_recipient); return SKIP; } @@ -884,13 +957,13 @@ if ((verify == v_sender && !r->verify_sender) || 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. */ -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; @@ -902,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. */ +check_local_part = string_copy(addr->cc_local_part); if (!addr->prefix && !addr->suffix) - { localpart_cache = addr->localpart_cache; - check_local_part = addr->cc_local_part; - } 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 ((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) @@ -927,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 -local_user_{uid,gid}. */ +local_user_{uid,gid} and local_part_data. */ if (r->check_local_user) { @@ -935,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", - r->name, addr->local_part); + rname, addr->local_part); 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; @@ -949,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. */ +GET_OPTION("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; } + 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. */ -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; @@ -987,7 +1057,7 @@ debug_print_string(r->debug_string); 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; } @@ -996,8 +1066,9 @@ if ((rc = check_files(r->require_files, perror)) != OK) 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) { @@ -1006,7 +1077,7 @@ if (r->condition) 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; } } @@ -1019,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) - 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; } } @@ -1028,7 +1099,7 @@ if (r->bmi_rule) 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; } @@ -1038,7 +1109,7 @@ if ( r->bmi_deliver_alternate ) { 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; } @@ -1048,7 +1119,7 @@ if ( r->bmi_deliver_default ) { 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 @@ -1101,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, - cache_set? "using cached result" : "cache not available"); + cache_set ? "using cached result" : "cache not available"); if (!cache_set) { @@ -1115,7 +1186,7 @@ if (!cache_set) 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 */ @@ -1333,8 +1404,9 @@ Returns: nothing */ 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); @@ -1370,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 - * 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. */ -new->start_router = addr->router->next; +new->start_router = addr->router->drinst.next; new->next = *addr_new; *addr_new = new; @@ -1417,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 * drname = r->drinst.name; +GET_OPTION("set"); if (!varlist) return OK; /* Walk the varlist, creating variables */ @@ -1435,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, - "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; - while (isspace(*assignment)) assignment++; + Uskip_whitespace(&assignment); if (!(val = expand_string(US assignment))) if (f.expand_string_forcedfail) @@ -1452,7 +1525,8 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); ) /* 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; @@ -1469,18 +1543,24 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); ) 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))) - { - node = store_get(sizeof(tree_node) + Ustrlen(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'\n", name, 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; @@ -1522,8 +1602,9 @@ route_address(address_item *addr, address_item **paddr_local, { 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) { @@ -1537,15 +1618,14 @@ instead of at the first router. */ 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; - 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. */ @@ -1559,7 +1639,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) 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 @@ -1595,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", - r->name, parent->address); + rname_l, parent->address); loop_detected = TRUE; break; } @@ -1616,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. */ - 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); @@ -1629,17 +1709,29 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) 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) { - 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) { - 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; } } @@ -1648,18 +1740,22 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) 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; - 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) { - 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; } } @@ -1667,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. */ - 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 @@ -1675,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) { - router_name = NULL; + driver_srcfile = router_name = NULL; driver_srcline = 0; if (rc == SKIP) continue; addr->message = error; yield = rc; @@ -1694,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. */ - switch (set_router_vars(addr, r)) + switch (rc = set_router_vars(addr, r)) { 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 @@ -1708,7 +1806,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) 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) @@ -1718,7 +1816,8 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) /* 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; @@ -1726,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"); - router_name = NULL; + driver_srcfile = router_name = NULL; driver_srcline = 0; r = NULL; break; } @@ -1736,7 +1835,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) 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; } @@ -1773,27 +1872,31 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) /* 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) { - 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. */ - 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; @@ -1807,8 +1910,8 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) 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); } @@ -1819,13 +1922,14 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr) 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 */ - 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; @@ -1850,18 +1954,22 @@ if (!r) 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; } @@ -1872,12 +1980,8 @@ if (!r) 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"" : 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""); goto ROUTE_EXIT; } @@ -1887,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", - 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). */ @@ -1907,6 +2011,7 @@ networking, so it is included in the binary only if requested. */ #ifdef SUPPORT_TRANSLATE_IP_ADDRESS +GET_OPTION("translate_ip_address"); if (r->translate_ip_address) { int rc; @@ -1965,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 */ -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 */ -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 - ? addr->transport->name : US""); + ? addr->transport->drinst.name : US""); if (addr->prop.errors_address) debug_printf(" errors to %s\n", addr->prop.errors_address); @@ -1999,8 +2104,8 @@ DEBUG(D_route) 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. */ @@ -2009,10 +2114,23 @@ if (yield == DEFER && addr->message) 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; } + + +/* 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 */