summaryrefslogtreecommitdiffstats
path: root/src/routers
diff options
context:
space:
mode:
Diffstat (limited to 'src/routers')
-rw-r--r--src/routers/Makefile43
-rw-r--r--src/routers/README57
-rw-r--r--src/routers/accept.c142
-rw-r--r--src/routers/accept.h31
-rw-r--r--src/routers/dnslookup.c478
-rw-r--r--src/routers/dnslookup.h42
-rw-r--r--src/routers/ipliteral.c206
-rw-r--r--src/routers/ipliteral.h34
-rw-r--r--src/routers/iplookup.c422
-rw-r--r--src/routers/iplookup.h41
-rw-r--r--src/routers/manualroute.c493
-rw-r--r--src/routers/manualroute.h39
-rw-r--r--src/routers/queryprogram.c559
-rw-r--r--src/routers/queryprogram.h40
-rw-r--r--src/routers/redirect.c913
-rw-r--r--src/routers/redirect.h77
-rw-r--r--src/routers/rf_change_domain.c84
-rw-r--r--src/routers/rf_expand_data.c48
-rw-r--r--src/routers/rf_functions.h31
-rw-r--r--src/routers/rf_get_errors_address.c132
-rw-r--r--src/routers/rf_get_munge_headers.c122
-rw-r--r--src/routers/rf_get_transport.c96
-rw-r--r--src/routers/rf_get_ugid.c80
-rw-r--r--src/routers/rf_lookup_hostlist.c262
-rw-r--r--src/routers/rf_queue_add.c108
-rw-r--r--src/routers/rf_self_action.c123
-rw-r--r--src/routers/rf_set_ugid.c44
27 files changed, 4747 insertions, 0 deletions
diff --git a/src/routers/Makefile b/src/routers/Makefile
new file mode 100644
index 0000000..8f2432d
--- /dev/null
+++ b/src/routers/Makefile
@@ -0,0 +1,43 @@
+# Make file for building a library containing all the available routers and
+# calling it routers.a. This is called from the main make file, after cd'ing
+# to the directors subdirectory. The library also contains functions that
+# are called only from within the individual routers.
+
+OBJ = accept.o dnslookup.o ipliteral.o iplookup.o manualroute.o \
+ queryprogram.o redirect.o \
+ rf_change_domain.o rf_expand_data.o rf_get_errors_address.o \
+ rf_get_munge_headers.o rf_get_transport.o rf_get_ugid.o \
+ rf_lookup_hostlist.o \
+ rf_queue_add.o rf_self_action.o \
+ rf_set_ugid.o
+
+routers.a: $(OBJ)
+ @$(RM_COMMAND) -f routers.a
+ @echo "$(AR) routers.a"
+ @$(AR) routers.a $(OBJ)
+ $(RANLIB) $@
+
+.SUFFIXES: .o .c
+.c.o:; @echo "$(CC) $*.c"
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
+
+rf_change_domain.o: $(HDRS) rf_change_domain.c rf_functions.h
+rf_expand_data.o: $(HDRS) rf_expand_data.c rf_functions.h
+rf_get_errors_address.o: $(HDRS) rf_get_errors_address.c rf_functions.h
+rf_get_munge_headers.o: $(HDRS) rf_get_munge_headers.c rf_functions.h
+rf_get_transport.o: $(HDRS) rf_get_transport.c rf_functions.h
+rf_get_ugid.o: $(HDRS) rf_get_ugid.c rf_functions.h
+rf_lookup_hostlist.o: $(HDRS) rf_lookup_hostlist.c rf_functions.h
+rf_queue_add.o: $(HDRS) rf_queue_add.c rf_functions.h
+rf_self_action.o: $(HDRS) rf_self_action.c rf_functions.h
+rf_set_ugid.o: $(HDRS) rf_set_ugid.c rf_functions.h
+
+accept.o: $(HDRS) accept.c rf_functions.h accept.h
+dnslookup.o: $(HDRS) dnslookup.c rf_functions.h dnslookup.h
+ipliteral.o: $(HDRS) ipliteral.c rf_functions.h ipliteral.h
+iplookup.o: $(HDRS) iplookup.c rf_functions.h iplookup.h
+manualroute.o: $(HDRS) manualroute.c rf_functions.h manualroute.h
+queryprogram.o: $(HDRS) queryprogram.c rf_functions.h queryprogram.h
+redirect.o: $(HDRS) redirect.c rf_functions.h redirect.h
+
+# End
diff --git a/src/routers/README b/src/routers/README
new file mode 100644
index 0000000..a674e8b
--- /dev/null
+++ b/src/routers/README
@@ -0,0 +1,57 @@
+ROUTERS:
+
+The yield of a router is one of:
+
+ OK the address was routed and either added to one of the
+ addr_local or addr_remote chains, or one or more new
+ addresses were added to addr_new. The original may be added
+ to addr_succeed.
+
+ REROUTED this is used when a child address is created and added to
+ addr_new as a consequence of a domain widening or because
+ "self = reroute" was encountered. The only time it is handled
+ differently from OK is when verifying, to force it to
+ continue with the child address.
+
+ DECLINE the address was not routed; pass to next router unless
+ no_more is set. It is permitted for additional addresses to
+ have been added to addr_new (or indeed for addresses to have
+ been put on the other chains).
+
+ PASS the address was not routed, but might have been modified;
+ pass to the next router unconditionally.
+
+ DISCARD the address was discarded (:blackhole: or "seen finish")
+
+ FAIL the address was not routed; do not pass to any subsequent
+ routers, i.e. cause routing to fail.
+
+ DEFER retry this address later.
+
+Both ERROR and DEFER cause the message to be kept on the queue; either may
+request freezing, but nowadays we try not to request freezing from routers
+because it may hold up other addresses in the message.
+
+
+When routing succeeds, the following field in the address can be set:
+
+ transport points to the transport instance control block
+
+ uid, gid are the uid and gid under which a local transport is to be
+ run if the transport does not itself specify them.
+
+ initgroups is set true if initgroups() is to be called when using the
+ uid and gid set up by the router.
+
+ fallback_hosts fallback host list - relevant only if the router sets up
+ a remote transport for the address.
+
+ errors_address where to send error messages for this address.
+
+ extra_headers additional headers to be added to the message for this
+ address.
+
+ remove_headers the names of headers to be removed from the message for this
+ address
+
+****
diff --git a/src/routers/accept.c b/src/routers/accept.c
new file mode 100644
index 0000000..3cb43b6
--- /dev/null
+++ b/src/routers/accept.c
@@ -0,0 +1,142 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+#include "accept.h"
+
+
+/* Options specific to the accept router. Because some compilers do not like
+empty declarations ("undefined" in the Standard) we put in a dummy value. */
+
+optionlist accept_router_options[] = {
+ { "", opt_hidden, {NULL} }
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int accept_router_options_count =
+ sizeof(accept_router_options)/sizeof(optionlist);
+
+/* Default private options block for the accept router. Again, a dummy
+value is used. */
+
+accept_router_options_block accept_router_option_defaults = {
+ NULL /* dummy */
+};
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+void accept_router_init(router_instance *rblock) {}
+int accept_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+/*************************************************
+* Initialization entry point *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to enable
+consistency checks to be done, or anything else that needs to be set up. */
+
+void accept_router_init(router_instance *rblock)
+{
+/*
+accept_router_options_block *ob =
+ (accept_router_options_block *)(rblock->options_block);
+*/
+
+/* By default, log deliveries via this router as local deliveries. We can't
+just leave it as TRUE_UNSET, because the global default is FALSE. */
+
+if (rblock->log_as_local == TRUE_UNSET) rblock->log_as_local = TRUE;
+}
+
+
+
+/*************************************************
+* Main entry point *
+*************************************************/
+
+/* See local README for interface description. This router returns:
+
+DEFER
+ . verifying the errors address caused a deferment or a big disaster such
+ as an expansion failure (rf_get_errors_address)
+ . expanding a headers_{add,remove} string caused a deferment or another
+ expansion error (rf_get_munge_headers)
+ . a problem in rf_get_transport: no transport when one is needed;
+ failed to expand dynamic transport; failed to find dynamic transport
+ . failure to expand or find a uid/gid (rf_get_ugid via rf_queue_add)
+
+OK
+ added address to addr_local or addr_remote, as appropriate for the
+ type of transport
+*/
+
+int accept_router_entry(
+ router_instance *rblock, /* data for this instantiation */
+ address_item *addr, /* address we are working on */
+ struct passwd *pw, /* passwd entry after check_local_user */
+ int verify, /* v_none/v_recipient/v_sender/v_expn */
+ address_item **addr_local, /* add it to this if it's local */
+ address_item **addr_remote, /* add it to this if it's remote */
+ address_item **addr_new, /* put new addresses on here */
+ address_item **addr_succeed) /* put old address here on success */
+{
+/*
+accept_router_options_block *ob =
+ (accept_router_options_block *)(rblock->options_block);
+*/
+int rc;
+uschar *errors_to;
+uschar *remove_headers;
+header_line *extra_headers;
+
+addr_new = addr_new; /* Keep picky compilers happy */
+addr_succeed = addr_succeed;
+
+DEBUG(D_route) debug_printf("%s router called for %s\n domain = %s\n",
+ rblock->name, addr->address, addr->domain);
+
+/* Set up the errors address, if any. */
+
+rc = rf_get_errors_address(addr, rblock, verify, &errors_to);
+if (rc != OK) return rc;
+
+/* Set up the additional and removable headers for the address. */
+
+rc = rf_get_munge_headers(addr, rblock, &extra_headers, &remove_headers);
+if (rc != OK) return rc;
+
+/* Set the transport and accept the address; update its errors address and
+header munging. Initialization ensures that there is a transport except when
+verifying. */
+
+if (!rf_get_transport(rblock->transport_name, &(rblock->transport),
+ addr, rblock->name, NULL)) return DEFER;
+
+addr->transport = rblock->transport;
+addr->prop.errors_address = errors_to;
+addr->prop.extra_headers = extra_headers;
+addr->prop.remove_headers = remove_headers;
+
+return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? OK : DEFER;
+}
+
+#endif /*!MACRO_PREDEF*/
+/* End of routers/accept.c */
diff --git a/src/routers/accept.h b/src/routers/accept.h
new file mode 100644
index 0000000..43494fb
--- /dev/null
+++ b/src/routers/accept.h
@@ -0,0 +1,31 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Private structure for the private options (there aren't any). */
+
+typedef struct {
+ uschar *dummy;
+} accept_router_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist accept_router_options[];
+extern int accept_router_options_count;
+
+/* Block containing default values. */
+
+extern accept_router_options_block accept_router_option_defaults;
+
+/* The main and initialization entry points for the router */
+
+extern int accept_router_entry(router_instance *, address_item *,
+ struct passwd *, int, address_item **, address_item **,
+ address_item **, address_item **);
+
+extern void accept_router_init(router_instance *);
+
+/* End of routers/accept.h */
diff --git a/src/routers/dnslookup.c b/src/routers/dnslookup.c
new file mode 100644
index 0000000..abc41a3
--- /dev/null
+++ b/src/routers/dnslookup.c
@@ -0,0 +1,478 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "rf_functions.h"
+#include "dnslookup.h"
+
+
+
+/* Options specific to the dnslookup router. */
+#define LOFF(field) OPT_OFF(dnslookup_router_options_block, field)
+
+optionlist dnslookup_router_options[] = {
+ { "check_secondary_mx", opt_bool, LOFF(check_secondary_mx) },
+ { "check_srv", opt_stringptr, LOFF(check_srv) },
+ { "fail_defer_domains", opt_stringptr, LOFF(fail_defer_domains) },
+ { "ipv4_only", opt_stringptr, LOFF(ipv4_only) },
+ { "ipv4_prefer", opt_stringptr, LOFF(ipv4_prefer) },
+ { "mx_domains", opt_stringptr, LOFF(mx_domains) },
+ { "mx_fail_domains", opt_stringptr, LOFF(mx_fail_domains) },
+ { "qualify_single", opt_bool, LOFF(qualify_single) },
+ { "rewrite_headers", opt_bool, LOFF(rewrite_headers) },
+ { "same_domain_copy_routing", opt_bool|opt_public, OPT_OFF(router_instance, same_domain_copy_routing) },
+ { "search_parents", opt_bool, LOFF(search_parents) },
+ { "srv_fail_domains", opt_stringptr, LOFF(srv_fail_domains) },
+ { "widen_domains", opt_stringptr, LOFF(widen_domains) }
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int dnslookup_router_options_count =
+ sizeof(dnslookup_router_options)/sizeof(optionlist);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+dnslookup_router_options_block dnslookup_router_option_defaults = {0};
+void dnslookup_router_init(router_instance *rblock) {}
+int dnslookup_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+
+/* Default private options block for the dnslookup router. */
+
+dnslookup_router_options_block dnslookup_router_option_defaults = {
+ .check_secondary_mx = FALSE,
+ .qualify_single = TRUE,
+ .search_parents = FALSE,
+ .rewrite_headers = TRUE,
+ .widen_domains = NULL,
+ .mx_domains = NULL,
+ .mx_fail_domains = NULL,
+ .srv_fail_domains = NULL,
+ .check_srv = NULL,
+ .fail_defer_domains = NULL,
+ .ipv4_only = NULL,
+ .ipv4_prefer = NULL,
+};
+
+
+
+/*************************************************
+* Initialization entry point *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to enable
+consistency checks to be done, or anything else that needs to be set up. */
+
+void
+dnslookup_router_init(router_instance *rblock)
+{
+/*
+dnslookup_router_options_block *ob =
+ (dnslookup_router_options_block *)(rblock->options_block);
+*/
+rblock = rblock;
+}
+
+
+
+/*************************************************
+* Main entry point *
+*************************************************/
+
+/* See local README for interface details. This router returns:
+
+DECLINE
+ . the domain does not exist in the DNS
+ . MX records point to non-existent hosts (including RHS = IP address)
+ . a single SRV record has a host name of "." (=> no service)
+ . syntactically invalid mail domain
+ . check_secondary_mx set, and local host not in host list
+
+DEFER
+ . lookup defer for mx_domains
+ . timeout etc on DNS lookup
+ . verifying the errors address caused a deferment or a big disaster such
+ as an expansion failure (rf_get_errors_address)
+ . expanding a headers_{add,remove} string caused a deferment or another
+ expansion error (rf_get_munge_headers)
+ . a problem in rf_get_transport: no transport when one is needed;
+ failed to expand dynamic transport; failed to find dynamic transport
+ . failure to expand or find a uid/gid (rf_get_ugid via rf_queue_add)
+ . self = "freeze", self = "defer"
+
+PASS
+ . timeout etc on DNS lookup and pass_on_timeout set
+ . self = "pass"
+
+REROUTED
+ . routed to local host, but name was expanded by DNS lookup, so a
+ re-routing should take place
+ . self = "reroute"
+
+ In both cases the new address will have been set up as a child
+
+FAIL
+ . self = "fail"
+
+OK
+ added address to addr_local or addr_remote, as appropriate for the
+ type of transport; this includes the self="send" case.
+*/
+
+int
+dnslookup_router_entry(
+ router_instance *rblock, /* data for this instantiation */
+ address_item *addr, /* address we are working on */
+ struct passwd *pw, /* passwd entry after check_local_user */
+ int verify, /* v_none/v_recipient/v_sender/v_expn */
+ address_item **addr_local, /* add it to this if it's local */
+ address_item **addr_remote, /* add it to this if it's remote */
+ address_item **addr_new, /* put new addresses on here */
+ address_item **addr_succeed) /* put old address here on success */
+{
+host_item h;
+int rc;
+int widen_sep = 0;
+int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
+dnslookup_router_options_block *ob =
+ (dnslookup_router_options_block *)(rblock->options_block);
+uschar *srv_service = NULL;
+uschar *widen = NULL;
+const uschar *pre_widen = addr->domain;
+const uschar *post_widen = NULL;
+const uschar *fully_qualified_name;
+const uschar *listptr;
+uschar widen_buffer[256];
+
+addr_new = addr_new; /* Keep picky compilers happy */
+addr_succeed = addr_succeed;
+
+DEBUG(D_route)
+ debug_printf("%s router called for %s\n domain = %s\n",
+ rblock->name, addr->address, addr->domain);
+
+/* If an SRV check is required, expand the service name */
+
+if (ob->check_srv)
+ {
+ if ( !(srv_service = expand_string(ob->check_srv))
+ && !f.expand_string_forcedfail)
+ {
+ addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
+ rblock->name, ob->check_srv, expand_string_message);
+ return DEFER;
+ }
+ else whichrrs |= HOST_FIND_BY_SRV;
+ }
+
+/* Set up the first of any widening domains. The code further down copes with
+either pre- or post-widening, but at present there is no way to turn on
+pre-widening, as actually doing so seems like a rather bad idea, and nobody has
+requested it. Pre-widening would cause local abbreviated names to take
+precedence over global names. For example, if the domain is "xxx.ch" it might
+be something in the "ch" toplevel domain, but it also might be xxx.ch.xyz.com.
+The choice of pre- or post-widening affects which takes precedence. If ever
+somebody comes up with some kind of requirement for pre-widening, presumably
+with some conditions under which it is done, it can be selected here.
+
+The rewrite_headers option works only when routing an address at transport
+time, because the alterations to the headers are not persistent so must be
+worked out immediately before they are used. Sender addresses are routed for
+verification purposes, but never at transport time, so any header changes that
+you might expect as a result of sender domain widening do not occur. Therefore
+we do not perform widening when verifying sender addresses; however, widening
+sender addresses is OK if we do not have to rewrite the headers. A corollary
+of this is that if the current address is not the original address, then it
+does not appear in the message header so it is also OK to widen. The
+suppression of widening for sender addresses is silent because it is the
+normal desirable behaviour. */
+
+if ( ob->widen_domains
+ && (verify != v_sender || !ob->rewrite_headers || addr->parent))
+ {
+ listptr = ob->widen_domains;
+ widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
+ sizeof(widen_buffer));
+
+/****
+ if (some condition requiring pre-widening)
+ {
+ post_widen = pre_widen;
+ pre_widen = NULL;
+ }
+****/
+ }
+
+/* Loop to cope with explicit widening of domains as configured. This code
+copes with widening that may happen before or after the original name. The
+decision as to which is taken above. */
+
+for (;;)
+ {
+ int flags = whichrrs;
+ BOOL removed = FALSE;
+
+ if (pre_widen)
+ {
+ h.name = pre_widen;
+ pre_widen = NULL;
+ }
+ else if (widen)
+ {
+ h.name = string_sprintf("%s.%s", addr->domain, widen);
+ widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
+ sizeof(widen_buffer));
+ DEBUG(D_route) debug_printf("%s router widened %s to %s\n", rblock->name,
+ addr->domain, h.name);
+ }
+ else if (post_widen)
+ {
+ h.name = post_widen;
+ post_widen = NULL;
+ DEBUG(D_route) debug_printf("%s router trying %s after widening failed\n",
+ rblock->name, h.name);
+ }
+ else return DECLINE;
+
+ /* Check if we must request only. or prefer, ipv4 */
+
+ if ( ob->ipv4_only
+ && expand_check_condition(ob->ipv4_only, rblock->name, US"router"))
+ flags = flags & ~HOST_FIND_BY_AAAA | HOST_FIND_IPV4_ONLY;
+ else if (f.search_find_defer)
+ return DEFER;
+ if ( ob->ipv4_prefer
+ && expand_check_condition(ob->ipv4_prefer, rblock->name, US"router"))
+ flags |= HOST_FIND_IPV4_FIRST;
+ else if (f.search_find_defer)
+ return DEFER;
+
+ /* Set up the rest of the initial host item. Others may get chained on if
+ there is more than one IP address. We set it up here instead of outside the
+ loop so as to re-initialize if a previous try succeeded but was rejected
+ because of not having an MX record. */
+
+ h.next = NULL;
+ h.address = NULL;
+ h.port = PORT_NONE;
+ h.mx = MX_NONE;
+ h.status = hstatus_unknown;
+ h.why = hwhy_unknown;
+ h.last_try = 0;
+
+ /* Unfortunately, we cannot set the mx_only option in advance, because the
+ DNS lookup may extend an unqualified name. Therefore, we must do the test
+ subsequently. We use the same logic as that for widen_domains above to avoid
+ requesting a header rewrite that cannot work. */
+
+ if (verify != v_sender || !ob->rewrite_headers || addr->parent)
+ {
+ if (ob->qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
+ if (ob->search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
+ }
+
+ rc = host_find_bydns(&h, CUS rblock->ignore_target_hosts, flags,
+ srv_service, ob->srv_fail_domains, ob->mx_fail_domains,
+ &rblock->dnssec,
+ &fully_qualified_name, &removed);
+
+ if (removed) setflag(addr, af_local_host_removed);
+
+ /* If host found with only address records, test for the domain's being in
+ the mx_domains list. Note that this applies also to SRV records; the name of
+ the option is historical. */
+
+ if ((rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) && h.mx < 0 &&
+ ob->mx_domains)
+ switch(match_isinlist(fully_qualified_name,
+ CUSS &(ob->mx_domains), 0,
+ &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
+ {
+ case DEFER:
+ addr->message = US"lookup defer for mx_domains";
+ return DEFER;
+
+ case OK:
+ DEBUG(D_route) debug_printf("%s router rejected %s: no MX record(s)\n",
+ rblock->name, fully_qualified_name);
+ continue;
+ }
+
+ /* Deferral returns forthwith, and anything other than failure breaks the
+ loop. */
+
+ if (rc == HOST_FIND_SECURITY)
+ {
+ addr->message = US"host lookup done insecurely";
+ return DEFER;
+ }
+ if (rc == HOST_FIND_AGAIN)
+ {
+ if (rblock->pass_on_timeout)
+ {
+ DEBUG(D_route) debug_printf("%s router timed out, and pass_on_timeout is set\n",
+ rblock->name);
+ return PASS;
+ }
+ addr->message = US"host lookup did not complete";
+ return DEFER;
+ }
+
+ if (rc != HOST_FIND_FAILED) break;
+
+ if (ob->fail_defer_domains)
+ switch(match_isinlist(fully_qualified_name,
+ CUSS &ob->fail_defer_domains, 0,
+ &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
+ {
+ case DEFER:
+ addr->message = US"lookup defer for fail_defer_domains option";
+ return DEFER;
+
+ case OK:
+ DEBUG(D_route) debug_printf("%s router: matched fail_defer_domains\n",
+ rblock->name);
+ addr->message = US"missing MX, or all MXs point to missing A records,"
+ " and defer requested";
+ return DEFER;
+ }
+ /* Check to see if the failure is the result of MX records pointing to
+ non-existent domains, and if so, set an appropriate error message; the case
+ of an MX or SRV record pointing to "." is another special case that we can
+ detect. Otherwise "unknown mail domain" is used, which is confusing. Also, in
+ this case don't do the widening. We need check only the first host to see if
+ its MX has been filled in, but there is no address, because if there were any
+ usable addresses returned, we would not have had HOST_FIND_FAILED.
+
+ As a common cause of this problem is MX records with IP addresses on the
+ RHS, give a special message in this case. */
+
+ if (h.mx >= 0 && h.address == NULL)
+ {
+ setflag(addr, af_pass_message); /* This is not a security risk */
+ if (h.name[0] == 0)
+ addr->message = US"an MX or SRV record indicated no SMTP service";
+ else
+ {
+ addr->basic_errno = ERRNO_UNKNOWNHOST;
+ addr->message = US"all relevant MX records point to non-existent hosts";
+ if (!allow_mx_to_ip && string_is_ip_address(h.name, NULL) != 0)
+ {
+ addr->user_message =
+ string_sprintf("It appears that the DNS operator for %s\n"
+ "has installed an invalid MX record with an IP address\n"
+ "instead of a domain name on the right hand side.", addr->domain);
+ addr->message = string_sprintf("%s or (invalidly) to IP addresses",
+ addr->message);
+ }
+ }
+ return DECLINE;
+ }
+
+ /* If there's a syntax error, do not continue with any widening, and note
+ the error. */
+
+ if (f.host_find_failed_syntax)
+ {
+ addr->message = string_sprintf("mail domain \"%s\" is syntactically "
+ "invalid", h.name);
+ return DECLINE;
+ }
+ }
+
+/* If the original domain name has been changed as a result of the host lookup,
+set up a child address for rerouting and request header rewrites if so
+configured. Then yield REROUTED. However, if the only change is a change of
+case in the domain name, which some resolvers yield (others don't), just change
+the domain name in the original address so that the official version is used in
+RCPT commands. */
+
+if (Ustrcmp(addr->domain, fully_qualified_name) != 0)
+ {
+ if (strcmpic(addr->domain, fully_qualified_name) == 0)
+ {
+ uschar *at = Ustrrchr(addr->address, '@');
+ memcpy(at+1, fully_qualified_name, Ustrlen(at+1));
+ }
+ else
+ {
+ rf_change_domain(addr, fully_qualified_name, ob->rewrite_headers, addr_new);
+ return REROUTED;
+ }
+ }
+
+/* If the yield is HOST_FOUND_LOCAL, the remote domain name either found MX
+records with the lowest numbered one pointing to a host with an IP address that
+is set on one of the interfaces of this machine, or found A records or got
+addresses from gethostbyname() that contain one for this machine. This can
+happen quite legitimately if the original name was a shortened form of a
+domain, but we will have picked that up already via the name change test above.
+
+Otherwise, the action to be taken can be configured by the self option, the
+handling of which is in a separate function, as it is also required for other
+routers. */
+
+if (rc == HOST_FOUND_LOCAL)
+ {
+ rc = rf_self_action(addr, &h, rblock->self_code, rblock->self_rewrite,
+ rblock->self, addr_new);
+ if (rc != OK) return rc;
+ }
+
+/* Otherwise, insist on being a secondary MX if so configured */
+
+else if (ob->check_secondary_mx && !testflag(addr, af_local_host_removed))
+ {
+ DEBUG(D_route) debug_printf("check_secondary_mx set and local host not secondary\n");
+ return DECLINE;
+ }
+
+/* Set up the errors address, if any. */
+
+rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
+if (rc != OK) return rc;
+
+/* Set up the additional and removable headers for this address. */
+
+rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
+ &addr->prop.remove_headers);
+if (rc != OK) return rc;
+
+/* Get store in which to preserve the original host item, chained on
+to the address. */
+
+addr->host_list = store_get(sizeof(host_item), FALSE);
+addr->host_list[0] = h;
+
+/* Fill in the transport and queue the address for delivery. */
+
+if (!rf_get_transport(rblock->transport_name, &(rblock->transport),
+ addr, rblock->name, NULL))
+ return DEFER;
+
+addr->transport = rblock->transport;
+
+return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
+ OK : DEFER;
+}
+
+#endif /*!MACRO_PREDEF*/
+/* End of routers/dnslookup.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/routers/dnslookup.h b/src/routers/dnslookup.h
new file mode 100644
index 0000000..b7e0915
--- /dev/null
+++ b/src/routers/dnslookup.h
@@ -0,0 +1,42 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Private structure for the private options. */
+
+typedef struct {
+ BOOL check_secondary_mx;
+ BOOL qualify_single;
+ BOOL search_parents;
+ BOOL rewrite_headers;
+ uschar *widen_domains;
+ uschar *mx_domains;
+ uschar *mx_fail_domains;
+ uschar *srv_fail_domains;
+ uschar *check_srv;
+ uschar *fail_defer_domains;
+ uschar *ipv4_only;
+ uschar *ipv4_prefer;
+} dnslookup_router_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist dnslookup_router_options[];
+extern int dnslookup_router_options_count;
+
+/* Block containing default values. */
+
+extern dnslookup_router_options_block dnslookup_router_option_defaults;
+
+/* The main and initialization entry points for the router */
+
+extern int dnslookup_router_entry(router_instance *, address_item *,
+ struct passwd *, int, address_item **, address_item **,
+ address_item **, address_item **);
+
+extern void dnslookup_router_init(router_instance *);
+
+/* End of routers/dnslookup.h */
diff --git a/src/routers/ipliteral.c b/src/routers/ipliteral.c
new file mode 100644
index 0000000..0c13673
--- /dev/null
+++ b/src/routers/ipliteral.c
@@ -0,0 +1,206 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+#include "ipliteral.h"
+
+
+/* Options specific to the ipliteral router. Because some compilers do not like
+empty declarations ("undefined" in the Standard) we put in a dummy value. */
+
+optionlist ipliteral_router_options[] = {
+ { "", opt_hidden, {NULL} }
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int ipliteral_router_options_count =
+ sizeof(ipliteral_router_options)/sizeof(optionlist);
+
+/* Default private options block for the ipliteral router. Again, a dummy
+value is present to keep some compilers happy. */
+
+ipliteral_router_options_block ipliteral_router_option_defaults = { 0 };
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+void ipliteral_router_init(router_instance *rblock) {}
+int ipliteral_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+/*************************************************
+* Initialization entry point *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to enable
+consistency checks to be done, or anything else that needs to be set up. */
+
+void
+ipliteral_router_init(router_instance *rblock)
+{
+/*
+ipliteral_router_options_block *ob =
+ (ipliteral_router_options_block *)(rblock->options_block);
+*/
+rblock = rblock;
+}
+
+
+
+/*************************************************
+* Main entry point *
+*************************************************/
+
+/* See local README for interface details. This router returns:
+
+DECLINE
+ . the domain is not in the form of an IP literal
+
+DEFER
+ . verifying the errors address caused a deferment or a big disaster such
+ as an expansion failure (rf_get_errors_address)
+ . expanding a headers_{add,remove} string caused a deferment or another
+ expansion error (rf_get_munge_headers)
+ . a problem in rf_get_transport: no transport when one is needed;
+ failed to expand dynamic transport; failed to find dynamic transport
+ . failure to expand or find a uid/gid (rf_get_ugid via rf_queue_add)
+ . self = "freeze", self = "defer"
+
+PASS
+ . self = "pass"
+
+REROUTED
+ . self = "reroute"
+
+FAIL
+ . self = "fail"
+
+OK
+ added address to addr_local or addr_remote, as appropriate for the
+ type of transport; this includes the self="send" case.
+*/
+
+int
+ipliteral_router_entry(
+ router_instance *rblock, /* data for this instantiation */
+ address_item *addr, /* address we are working on */
+ struct passwd *pw, /* passwd entry after check_local_user */
+ int verify, /* v_none/v_recipient/v_sender/v_expn */
+ address_item **addr_local, /* add it to this if it's local */
+ address_item **addr_remote, /* add it to this if it's remote */
+ address_item **addr_new, /* put new addresses on here */
+ address_item **addr_succeed) /* put old address here on success */
+{
+/*
+ipliteral_router_options_block *ob =
+ (ipliteral_router_options_block *)(rblock->options_block);
+*/
+host_item *h;
+const uschar *domain = addr->domain;
+const uschar *ip;
+int len = Ustrlen(domain);
+int rc, ipv;
+
+addr_new = addr_new; /* Keep picky compilers happy */
+addr_succeed = addr_succeed;
+
+DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
+ rblock->name, addr->address, addr->domain);
+
+/* Check that the domain is an IP address enclosed in square brackets. Remember
+to allow for the "official" form of IPv6 addresses. If not, the router
+declines. Otherwise route to the single IP address, setting the host name to
+"(unnamed)". */
+
+if (domain[0] != '[' || domain[len-1] != ']') return DECLINE;
+ip = string_copyn(domain+1, len-2);
+if (strncmpic(ip, US"IPV6:", 5) == 0 || strncmpic(ip, US"IPV4:", 5) == 0)
+ ip += 5;
+
+ipv = string_is_ip_address(ip, NULL);
+if (ipv == 0 || (disable_ipv6 && ipv == 6))
+ return DECLINE;
+
+/* It seems unlikely that ignore_target_hosts will be used with this router,
+but if it is set, it should probably work. */
+
+if (verify_check_this_host(CUSS&rblock->ignore_target_hosts,
+ NULL, domain, ip, NULL) == OK)
+ {
+ DEBUG(D_route)
+ debug_printf("%s is in ignore_target_hosts\n", ip);
+ addr->message = US"IP literal host explicitly ignored";
+ return DECLINE;
+ }
+
+/* Set up a host item */
+
+h = store_get(sizeof(host_item), FALSE);
+
+h->next = NULL;
+h->address = string_copy(ip);
+h->port = PORT_NONE;
+h->name = domain;
+h->mx = MX_NONE;
+h->status = hstatus_unknown;
+h->why = hwhy_unknown;
+h->last_try = 0;
+
+/* Determine whether the host is the local host, and if so, take action
+according to the configuration. */
+
+if (host_scan_for_local_hosts(h, &h, NULL) == HOST_FOUND_LOCAL)
+ {
+ int rc = rf_self_action(addr, h, rblock->self_code, rblock->self_rewrite,
+ rblock->self, addr_new);
+ if (rc != OK) return rc;
+ }
+
+/* Address is routed to this host */
+
+addr->host_list = h;
+
+/* Set up the errors address, if any. */
+
+rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
+if (rc != OK) return rc;
+
+/* Set up the additional and removable headers for this address. */
+
+rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
+ &addr->prop.remove_headers);
+if (rc != OK) return rc;
+
+/* Fill in the transport, queue the address for local or remote delivery, and
+yield success. For local delivery, of course, the IP address won't be used. If
+just verifying, there need not be a transport, in which case it doesn't matter
+which queue we put the address on. This is all now handled by the route_queue()
+function. */
+
+if (!rf_get_transport(rblock->transport_name, &(rblock->transport),
+ addr, rblock->name, NULL))
+ return DEFER;
+
+addr->transport = rblock->transport;
+
+return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
+ OK : DEFER;
+}
+
+#endif /*!MACRO_PREDEF*/
+/* End of routers/ipliteral.c */
diff --git a/src/routers/ipliteral.h b/src/routers/ipliteral.h
new file mode 100644
index 0000000..1ddb38b
--- /dev/null
+++ b/src/routers/ipliteral.h
@@ -0,0 +1,34 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+/* Private structure for the private options. Some compilers do not like empty
+structures - the Standard, alas, says "undefined behaviour" for an empty
+structure - so we have to put in a dummy value. */
+
+typedef struct {
+ int dummy;
+} ipliteral_router_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist ipliteral_router_options[];
+extern int ipliteral_router_options_count;
+
+/* Block containing default values. */
+
+extern ipliteral_router_options_block ipliteral_router_option_defaults;
+
+/* The main and initialization entry points for the router */
+
+extern int ipliteral_router_entry(router_instance *, address_item *,
+ struct passwd *, int, address_item **, address_item **,
+ address_item **, address_item **);
+
+extern void ipliteral_router_init(router_instance *);
+
+/* End of routers/ipliteral.h */
diff --git a/src/routers/iplookup.c b/src/routers/iplookup.c
new file mode 100644
index 0000000..02b43ee
--- /dev/null
+++ b/src/routers/iplookup.c
@@ -0,0 +1,422 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+#include "iplookup.h"
+
+
+/* IP connection types */
+
+#define ip_udp 0
+#define ip_tcp 1
+
+
+/* Options specific to the iplookup router. */
+
+optionlist iplookup_router_options[] = {
+ { "hosts", opt_stringptr,
+ OPT_OFF(iplookup_router_options_block, hosts) },
+ { "optional", opt_bool,
+ OPT_OFF(iplookup_router_options_block, optional) },
+ { "port", opt_int,
+ OPT_OFF(iplookup_router_options_block, port) },
+ { "protocol", opt_stringptr,
+ OPT_OFF(iplookup_router_options_block, protocol_name) },
+ { "query", opt_stringptr,
+ OPT_OFF(iplookup_router_options_block, query) },
+ { "reroute", opt_stringptr,
+ OPT_OFF(iplookup_router_options_block, reroute) },
+ { "response_pattern", opt_stringptr,
+ OPT_OFF(iplookup_router_options_block, response_pattern) },
+ { "timeout", opt_time,
+ OPT_OFF(iplookup_router_options_block, timeout) }
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int iplookup_router_options_count =
+ sizeof(iplookup_router_options)/sizeof(optionlist);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+iplookup_router_options_block iplookup_router_option_defaults = {0};
+void iplookup_router_init(router_instance *rblock) {}
+int iplookup_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+/* Default private options block for the iplookup router. */
+
+iplookup_router_options_block iplookup_router_option_defaults = {
+ -1, /* port */
+ ip_udp, /* protocol */
+ 5, /* timeout */
+ NULL, /* protocol_name */
+ NULL, /* hosts */
+ NULL, /* query; NULL => local_part@domain */
+ NULL, /* response_pattern; NULL => don't apply regex */
+ NULL, /* reroute; NULL => just used returned data */
+ NULL, /* re_response_pattern; compiled pattern */
+ FALSE /* optional */
+};
+
+
+
+/*************************************************
+* Initialization entry point *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to enable
+consistency checks to be done, or anything else that needs to be set up. */
+
+void
+iplookup_router_init(router_instance *rblock)
+{
+iplookup_router_options_block *ob =
+ (iplookup_router_options_block *)(rblock->options_block);
+
+/* A port and a host list must be given */
+
+if (ob->port < 0)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "a port must be specified", rblock->name);
+
+if (ob->hosts == NULL)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "a host list must be specified", rblock->name);
+
+/* Translate protocol name into value */
+
+if (ob->protocol_name != NULL)
+ {
+ if (Ustrcmp(ob->protocol_name, "udp") == 0) ob->protocol = ip_udp;
+ else if (Ustrcmp(ob->protocol_name, "tcp") == 0) ob->protocol = ip_tcp;
+ else log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "protocol not specified as udp or tcp", rblock->name);
+ }
+
+/* If a response pattern is given, compile it now to get the error early. */
+
+if (ob->response_pattern != NULL)
+ ob->re_response_pattern =
+ regex_must_compile(ob->response_pattern, FALSE, TRUE);
+}
+
+
+
+/*************************************************
+* Main entry point *
+*************************************************/
+
+/* See local README for interface details. This router returns:
+
+DECLINE
+ . pattern or identification match on returned data failed
+
+DEFER
+ . failed to expand the query or rerouting string
+ . failed to create socket ("optional" not set)
+ . failed to find a host, failed to connect, timed out ("optional" not set)
+ . rerouting string is not in the form localpart@domain
+ . verifying the errors address caused a deferment or a big disaster such
+ as an expansion failure (rf_get_errors_address)
+ . expanding a headers_{add,remove} string caused a deferment or another
+ expansion error (rf_get_munge_headers)
+
+PASS
+ . failed to create socket ("optional" set)
+ . failed to find a host, failed to connect, timed out ("optional" set)
+
+OK
+ . new address added to addr_new
+*/
+
+int
+iplookup_router_entry(
+ router_instance *rblock, /* data for this instantiation */
+ address_item *addr, /* address we are working on */
+ struct passwd *pw, /* passwd entry after check_local_user */
+ int verify, /* v_none/v_recipient/v_sender/v_expn */
+ address_item **addr_local, /* add it to this if it's local */
+ address_item **addr_remote, /* add it to this if it's remote */
+ address_item **addr_new, /* put new addresses on here */
+ address_item **addr_succeed) /* put old address here on success */
+{
+uschar *query = NULL;
+uschar *reply;
+uschar *hostname, *reroute, *domain;
+const uschar *listptr;
+uschar host_buffer[256];
+host_item *host = store_get(sizeof(host_item), FALSE);
+address_item *new_addr;
+iplookup_router_options_block *ob =
+ (iplookup_router_options_block *)(rblock->options_block);
+const pcre *re = ob->re_response_pattern;
+int count, query_len, rc;
+int sep = 0;
+
+addr_local = addr_local; /* Keep picky compilers happy */
+addr_remote = addr_remote;
+addr_succeed = addr_succeed;
+pw = pw;
+
+DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
+ rblock->name, addr->address, addr->domain);
+
+reply = store_get(256, TRUE); /* tainted data */
+
+/* Build the query string to send. If not explicitly given, a default of
+"user@domain user@domain" is used. */
+
+if (ob->query == NULL)
+ query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
+ addr->local_part, addr->domain);
+else
+ {
+ query = expand_string(ob->query);
+ if (query == NULL)
+ {
+ addr->message = string_sprintf("%s router: failed to expand %s: %s",
+ rblock->name, ob->query, expand_string_message);
+ return DEFER;
+ }
+ }
+
+query_len = Ustrlen(query);
+DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
+ string_printing(query));
+
+/* Now connect to the required port for each of the hosts in turn, until a
+response it received. Initialization insists on the port being set and there
+being a host list. */
+
+listptr = ob->hosts;
+while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
+ sizeof(host_buffer))))
+ {
+ host_item *h;
+
+ DEBUG(D_route) debug_printf("calling host %s\n", hostname);
+
+ host->name = hostname;
+ host->address = NULL;
+ host->port = PORT_NONE;
+ host->mx = MX_NONE;
+ host->next = NULL;
+
+ if (string_is_ip_address(host->name, NULL) != 0)
+ host->address = host->name;
+ else
+ {
+/*XXX might want dnssec request/require on an iplookup router? */
+ int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
+ if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
+ }
+
+ /* Loop for possible multiple IP addresses for the given name. */
+
+ for (h = host; h; h = h->next)
+ {
+ int host_af;
+ client_conn_ctx query_cctx = {0};
+
+ /* Skip any hosts for which we have no address */
+
+ if (!h->address) continue;
+
+ /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
+ detected by checking for a colon in the address. */
+
+ host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
+
+ query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
+ host_af);
+ if (query_cctx.sock < 0)
+ {
+ if (ob->optional) return PASS;
+ addr->message = string_sprintf("failed to create socket in %s router",
+ rblock->name);
+ return DEFER;
+ }
+
+ /* Connect to the remote host, under a timeout. In fact, timeouts can occur
+ here only for TCP calls; for a UDP socket, "connect" always works (the
+ router will timeout later on the read call). */
+/*XXX could take advantage of TFO */
+
+ if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
+ ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
+ {
+ close(query_cctx.sock);
+ DEBUG(D_route)
+ debug_printf("connection to %s failed: %s\n", h->address,
+ strerror(errno));
+ continue;
+ }
+
+ /* Send the query. If it fails, just continue with the next address. */
+
+ if (send(query_cctx.sock, query, query_len, 0) < 0)
+ {
+ DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
+ (void)close(query_cctx.sock);
+ continue;
+ }
+
+ /* Read the response and close the socket. If the read fails, try the
+ next IP address. */
+
+ count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
+ (void)close(query_cctx.sock);
+ if (count <= 0)
+ {
+ DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
+ "timed out" : "recv failed", h->address);
+ *reply = 0;
+ continue;
+ }
+
+ /* Success; break the loop */
+
+ reply[count] = 0;
+ DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
+ rblock->name, string_printing(reply), h->address);
+ break;
+ }
+
+ /* If h == NULL we have tried all the IP addresses and failed on all of them,
+ so we must continue to try more host names. Otherwise we have succeeded. */
+
+ if (h) break;
+ }
+
+
+/* If hostname is NULL, we have failed to find any host, or failed to
+connect to any of the IP addresses, or timed out while reading or writing to
+those we have connected to. In all cases, we must pass if optional and
+defer otherwise. */
+
+if (hostname == NULL)
+ {
+ DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
+ if (ob->optional) return PASS;
+ addr->message = string_sprintf("%s router: failed to communicate with any "
+ "host", rblock->name);
+ return DEFER;
+ }
+
+
+/* If a response pattern was supplied, match the returned string against it. A
+failure to match causes the router to decline. After a successful match, the
+numerical variables for expanding the rerouted address are set up. */
+
+if (re != NULL)
+ {
+ if (!regex_match_and_setup(re, reply, 0, -1))
+ {
+ DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
+ rblock->name, ob->response_pattern, reply);
+ return DECLINE;
+ }
+ }
+
+
+/* If no response pattern was supplied, set up $0 as the response up to the
+first white space (if any). Also, if no query was specified, check that what
+follows the white space matches user@domain. */
+
+else
+ {
+ int n = 0;
+ while (reply[n] != 0 && !isspace(reply[n])) n++;
+ expand_nmax = 0;
+ expand_nstring[0] = reply;
+ expand_nlength[0] = n;
+
+ if (ob->query == NULL)
+ {
+ int nn = n;
+ while (isspace(reply[nn])) nn++;
+ if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
+ {
+ DEBUG(D_route) debug_printf("%s router: failed to match identification "
+ "in response %s\n", rblock->name, reply);
+ return DECLINE;
+ }
+ }
+
+ reply[n] = 0; /* Terminate for the default case */
+ }
+
+/* If an explicit rerouting string is specified, expand it. Otherwise, use
+what was sent back verbatim. */
+
+if (ob->reroute != NULL)
+ {
+ reroute = expand_string(ob->reroute);
+ expand_nmax = -1;
+ if (reroute == NULL)
+ {
+ addr->message = string_sprintf("%s router: failed to expand %s: %s",
+ rblock->name, ob->reroute, expand_string_message);
+ return DEFER;
+ }
+ }
+else reroute = reply;
+
+/* We should now have a new address in the form user@domain. */
+
+domain = Ustrchr(reroute, '@');
+if (domain == NULL)
+ {
+ log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
+ "user@domain", rblock->name, reroute);
+ addr->message = string_sprintf("%s router: reroute string %s is not of the "
+ "form user@domain", rblock->name, reroute);
+ return DEFER;
+ }
+
+/* Create a child address with the old one as parent. Put the new address on
+the chain of new addressess. */
+
+new_addr = deliver_make_addr(reroute, TRUE);
+new_addr->parent = addr;
+
+new_addr->prop = addr->prop;
+
+if (addr->child_count == USHRT_MAX)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
+ "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
+addr->child_count++;
+new_addr->next = *addr_new;
+*addr_new = new_addr;
+
+/* Set up the errors address, if any, and the additional and removable headers
+for this new address. */
+
+rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
+if (rc != OK) return rc;
+
+rc = rf_get_munge_headers(addr, rblock, &new_addr->prop.extra_headers,
+ &new_addr->prop.remove_headers);
+if (rc != OK) return rc;
+
+return OK;
+}
+
+#endif /*!MACRO_PREDEF*/
+/* End of routers/iplookup.c */
diff --git a/src/routers/iplookup.h b/src/routers/iplookup.h
new file mode 100644
index 0000000..d5217ef
--- /dev/null
+++ b/src/routers/iplookup.h
@@ -0,0 +1,41 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+/* Private structure for the private options. */
+
+typedef struct {
+ int port;
+ int protocol;
+ int timeout;
+ uschar *protocol_name;
+ uschar *hosts;
+ uschar *query;
+ uschar *response_pattern;
+ uschar *reroute;
+ const pcre *re_response_pattern;
+ BOOL optional;
+} iplookup_router_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist iplookup_router_options[];
+extern int iplookup_router_options_count;
+
+/* Block containing default values. */
+
+extern iplookup_router_options_block iplookup_router_option_defaults;
+
+/* The main and initialization entry points for the router */
+
+extern int iplookup_router_entry(router_instance *, address_item *,
+ struct passwd *, int, address_item **, address_item **,
+ address_item **, address_item **);
+
+extern void iplookup_router_init(router_instance *);
+
+/* End of routers/iplookup.h */
diff --git a/src/routers/manualroute.c b/src/routers/manualroute.c
new file mode 100644
index 0000000..e0a180e
--- /dev/null
+++ b/src/routers/manualroute.c
@@ -0,0 +1,493 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+#include "manualroute.h"
+
+
+/* Options specific to the manualroute router. */
+
+optionlist manualroute_router_options[] = {
+ { "host_all_ignored", opt_stringptr,
+ OPT_OFF(manualroute_router_options_block, host_all_ignored) },
+ { "host_find_failed", opt_stringptr,
+ OPT_OFF(manualroute_router_options_block, host_find_failed) },
+ { "hosts_randomize", opt_bool,
+ OPT_OFF(manualroute_router_options_block, hosts_randomize) },
+ { "route_data", opt_stringptr,
+ OPT_OFF(manualroute_router_options_block, route_data) },
+ { "route_list", opt_stringptr,
+ OPT_OFF(manualroute_router_options_block, route_list) },
+ { "same_domain_copy_routing", opt_bool|opt_public,
+ OPT_OFF(router_instance, same_domain_copy_routing) }
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int manualroute_router_options_count =
+ sizeof(manualroute_router_options)/sizeof(optionlist);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+manualroute_router_options_block manualroute_router_option_defaults = {0};
+void manualroute_router_init(router_instance *rblock) {}
+int manualroute_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+/* Default private options block for the manualroute router. */
+
+manualroute_router_options_block manualroute_router_option_defaults = {
+ -1, /* host_all_ignored code (unset) */
+ -1, /* host_find_failed code (unset) */
+ FALSE, /* hosts_randomize */
+ US"defer", /* host_all_ignored */
+ US"freeze", /* host_find_failed */
+ NULL, /* route_data */
+ NULL /* route_list */
+};
+
+
+/* Names and values for host_find_failed and host_all_ignored. */
+
+static uschar *hff_names[] = {
+ US"ignore", /* MUST be first - not valid for host_all_ignored */
+ US"decline",
+ US"defer",
+ US"fail",
+ US"freeze",
+ US"pass" };
+
+static int hff_codes[] = { hff_ignore, hff_decline, hff_defer, hff_fail,
+ hff_freeze, hff_pass };
+
+static int hff_count= sizeof(hff_codes)/sizeof(int);
+
+
+
+/*************************************************
+* Initialization entry point *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to enable
+consistency checks to be done, or anything else that needs to be set up. */
+
+void
+manualroute_router_init(router_instance *rblock)
+{
+manualroute_router_options_block *ob =
+ (manualroute_router_options_block *)(rblock->options_block);
+
+/* Host_find_failed must be a recognized word */
+
+for (int i = 0; i < hff_count; i++)
+ if (Ustrcmp(ob->host_find_failed, hff_names[i]) == 0)
+ {
+ ob->hff_code = hff_codes[i];
+ break;
+ }
+if (ob->hff_code < 0)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "unrecognized setting for host_find_failed option", rblock->name);
+
+for (int i = 1; i < hff_count; i++) /* NB starts at 1 to skip "ignore" */
+ if (Ustrcmp(ob->host_all_ignored, hff_names[i]) == 0)
+ {
+ ob->hai_code = hff_codes[i];
+ break;
+ }
+if (ob->hai_code < 0)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "unrecognized setting for host_all_ignored option", rblock->name);
+
+/* One of route_list or route_data must be specified */
+
+if ((ob->route_list == NULL && ob->route_data == NULL) ||
+ (ob->route_list != NULL && ob->route_data != NULL))
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "route_list or route_data (but not both) must be specified",
+ rblock->name);
+}
+
+
+
+
+/*************************************************
+* Parse a route item *
+*************************************************/
+
+/* The format of a route list item is:
+
+ <domain> [<host[list]> [<options>]]
+
+if obtained from route_list. The domain is absent if the string came from
+route_data, in which case domain==NULL. The domain and the host list may be
+enclosed in quotes.
+
+Arguments:
+ s pointer to route list item
+ domain if not NULL, where to put the domain pointer
+ hostlist where to put the host[list] pointer
+ options where to put the options pointer
+
+Returns: FALSE if domain expected and string is empty;
+ TRUE otherwise
+*/
+
+static BOOL
+parse_route_item(const uschar *s, const uschar **domain, const uschar **hostlist,
+ const uschar **options)
+{
+while (*s != 0 && isspace(*s)) s++;
+
+if (domain)
+ {
+ if (!*s) return FALSE; /* missing data */
+ *domain = string_dequote(&s);
+ while (*s && isspace(*s)) s++;
+ }
+
+*hostlist = string_dequote(&s);
+while (*s && isspace(*s)) s++;
+*options = s;
+return TRUE;
+}
+
+
+
+/*************************************************
+* Main entry point *
+*************************************************/
+
+/* The manualroute router provides a manual routing facility (surprise,
+surprise). The data that defines the routing can either be set in route_data
+(which means it can be found by, for example, looking up the domain in a file),
+or a list of domain patterns and their corresponding data can be provided in
+route_list. */
+
+/* See local README for interface details. This router returns:
+
+DECLINE
+ . no pattern in route_list matched (route_data not set)
+ . route_data was an empty string (route_list not set)
+ . forced expansion failure in route_data (rf_expand_data)
+ . forced expansion of host list
+ . host_find_failed = decline
+
+DEFER
+ . transport not defined when needed
+ . lookup defer in route_list when matching domain pattern
+ . non-forced expansion failure in route_data
+ . non-forced expansion failure in host list
+ . unknown routing option
+ . host list missing for remote transport (not verifying)
+ . timeout etc on host lookup (pass_on_timeout not set)
+ . verifying the errors address caused a deferment or a big disaster such
+ as an expansion failure (rf_get_errors_address)
+ . expanding a headers_{add,remove} string caused a deferment or another
+ expansion error (rf_get_munge_headers)
+ . a problem in rf_get_transport: no transport when one is needed;
+ failed to expand dynamic transport; failed to find dynamic transport
+ . failure to expand or find a uid/gid (rf_get_ugid via rf_queue_add)
+ . host_find_failed = freeze or defer
+ . self = freeze or defer
+
+PASS
+ . timeout etc on host lookup (pass_on_timeout set)
+ . host_find_failed = pass
+ . self = pass
+
+REROUTED
+ . self = reroute
+
+FAIL
+ . host_find_failed = fail
+ . self = fail
+
+OK
+ . added address to addr_local or addr_remote, as appropriate for the
+ type of transport; this includes the self="send" case.
+*/
+
+int
+manualroute_router_entry(
+ router_instance *rblock, /* data for this instantiation */
+ address_item *addr, /* address we are working on */
+ struct passwd *pw, /* passwd entry after check_local_user */
+ int verify, /* v_none/v_recipient/v_sender/v_expn */
+ address_item **addr_local, /* add it to this if it's local */
+ address_item **addr_remote, /* add it to this if it's remote */
+ address_item **addr_new, /* put new addresses on here */
+ address_item **addr_succeed) /* put old address here on success */
+{
+int rc, lookup_type;
+uschar *route_item = NULL;
+const uschar *options = NULL;
+const uschar *hostlist = NULL;
+const uschar *domain;
+uschar *newhostlist;
+const uschar *listptr;
+manualroute_router_options_block *ob =
+ (manualroute_router_options_block *)(rblock->options_block);
+transport_instance *transport = NULL;
+BOOL individual_transport_set = FALSE;
+BOOL randomize = ob->hosts_randomize;
+
+addr_new = addr_new; /* Keep picky compilers happy */
+addr_succeed = addr_succeed;
+
+DEBUG(D_route) debug_printf("%s router called for %s\n domain = %s\n",
+ rblock->name, addr->address, addr->domain);
+
+/* The initialization check ensures that either route_list or route_data is
+set. */
+
+if (ob->route_list)
+ {
+ int sep = -(';'); /* Default is semicolon */
+ listptr = ob->route_list;
+
+ while ((route_item = string_nextinlist(&listptr, &sep, NULL, 0)) != NULL)
+ {
+ int rc;
+
+ DEBUG(D_route) debug_printf("route_item = %s\n", route_item);
+ if (!parse_route_item(route_item, &domain, &hostlist, &options))
+ continue; /* Ignore blank items */
+
+ /* Check the current domain; if it matches, break the loop */
+
+ if ((rc = match_isinlist(addr->domain, &domain, UCHAR_MAX+1,
+ &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, CUSS &lookup_value)) == OK)
+ break;
+
+ /* If there was a problem doing the check, defer */
+
+ if (rc == DEFER)
+ {
+ addr->message = US"lookup defer in route_list";
+ return DEFER;
+ }
+ }
+
+ if (!route_item) return DECLINE; /* No pattern in the list matched */
+ }
+
+/* Handle a single routing item in route_data. If it expands to an empty
+string, decline. */
+
+else
+ {
+ if (!(route_item = rf_expand_data(addr, ob->route_data, &rc)))
+ return rc;
+ (void) parse_route_item(route_item, NULL, &hostlist, &options);
+ if (!hostlist[0]) return DECLINE;
+ }
+
+/* Expand the hostlist item. It may then pointing to an empty string, or to a
+single host or a list of hosts; options is pointing to the rest of the
+routelist item, which is either empty or contains various option words. */
+
+DEBUG(D_route) debug_printf("original list of hosts = '%s' options = '%s'\n",
+ hostlist, options);
+
+newhostlist = expand_string_copy(hostlist);
+lookup_value = NULL; /* Finished with */
+expand_nmax = -1;
+
+/* If the expansion was forced to fail, just decline. Otherwise there is a
+configuration problem. */
+
+if (!newhostlist)
+ {
+ if (f.expand_string_forcedfail) return DECLINE;
+ addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
+ rblock->name, hostlist, expand_string_message);
+ return DEFER;
+ }
+else hostlist = newhostlist;
+
+DEBUG(D_route) debug_printf("expanded list of hosts = '%s' options = '%s'\n",
+ hostlist, options);
+
+/* Set default lookup type and scan the options */
+
+lookup_type = LK_DEFAULT;
+
+while (*options)
+ {
+ unsigned n;
+ const uschar *s = options;
+ while (*options != 0 && !isspace(*options)) options++;
+ n = options-s;
+
+ if (Ustrncmp(s, "randomize", n) == 0) randomize = TRUE;
+ else if (Ustrncmp(s, "no_randomize", n) == 0) randomize = FALSE;
+ else if (Ustrncmp(s, "byname", n) == 0)
+ lookup_type = lookup_type & ~(LK_DEFAULT | LK_BYDNS) | LK_BYNAME;
+ else if (Ustrncmp(s, "bydns", n) == 0)
+ lookup_type = lookup_type & ~(LK_DEFAULT | LK_BYNAME) & LK_BYDNS;
+ else if (Ustrncmp(s, "ipv4_prefer", n) == 0) lookup_type |= LK_IPV4_PREFER;
+ else if (Ustrncmp(s, "ipv4_only", n) == 0) lookup_type |= LK_IPV4_ONLY;
+ else
+ {
+ transport_instance *t;
+ for (t = transports; t; t = t->next)
+ if (Ustrncmp(t->name, s, n) == 0)
+ {
+ transport = t;
+ individual_transport_set = TRUE;
+ break;
+ }
+
+ if (!t)
+ {
+ s = string_sprintf("unknown routing option or transport name \"%s\"", s);
+ log_write(0, LOG_MAIN, "Error in %s router: %s", rblock->name, s);
+ addr->message = string_sprintf("error in router: %s", s);
+ return DEFER;
+ }
+ }
+
+ if (*options)
+ {
+ options++;
+ while (*options != 0 && isspace(*options)) options++;
+ }
+ }
+
+/* Set up the errors address, if any. */
+
+rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
+if (rc != OK) return rc;
+
+/* Set up the additional and removable headers for this address. */
+
+rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
+ &addr->prop.remove_headers);
+if (rc != OK) return rc;
+
+/* If an individual transport is not set, get the transport for this router, if
+any. It might be expanded, or it might be unset if this router has verify_only
+set. */
+
+if (!individual_transport_set)
+ {
+ if (!rf_get_transport(rblock->transport_name, &(rblock->transport), addr,
+ rblock->name, NULL))
+ return DEFER;
+ transport = rblock->transport;
+ }
+
+/* Deal with the case of a local transport. The host list is passed over as a
+single text string that ends up in $host. */
+
+if (transport && transport->info->local)
+ {
+ if (hostlist[0])
+ {
+ host_item *h;
+ addr->host_list = h = store_get(sizeof(host_item), FALSE);
+ h->name = string_copy(hostlist);
+ h->address = NULL;
+ h->port = PORT_NONE;
+ h->mx = MX_NONE;
+ h->status = hstatus_unknown;
+ h->why = hwhy_unknown;
+ h->last_try = 0;
+ h->next = NULL;
+ }
+
+ /* There is nothing more to do other than to queue the address for the
+ local transport, filling in any uid/gid. This can be done by the common
+ rf_queue_add() function. */
+
+ addr->transport = transport;
+ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
+ OK : DEFER;
+ }
+
+/* There is either no transport (verify_only) or a remote transport. A host
+list is mandatory in either case, except when verifying, in which case the
+address is just accepted. */
+
+if (!hostlist[0])
+ {
+ if (verify != v_none) goto ROUTED;
+ addr->message = string_sprintf("error in %s router: no host(s) specified "
+ "for domain %s", rblock->name, addr->domain);
+ log_write(0, LOG_MAIN, "%s", addr->message);
+ return DEFER;
+ }
+
+/* Otherwise we finish the routing here by building a chain of host items
+for the list of configured hosts, and then finding their addresses. */
+
+host_build_hostlist(&addr->host_list, hostlist, randomize);
+rc = rf_lookup_hostlist(rblock, addr, rblock->ignore_target_hosts, lookup_type,
+ ob->hff_code, addr_new);
+if (rc != OK) return rc;
+
+/* If host_find_failed is set to "ignore", it is possible for all the hosts to
+be ignored, in which case we will end up with an empty host list. What happens
+is controlled by host_all_ignored. */
+
+if (!addr->host_list)
+ {
+ int i;
+ DEBUG(D_route) debug_printf("host_find_failed ignored every host\n");
+ if (ob->hai_code == hff_decline) return DECLINE;
+ if (ob->hai_code == hff_pass) return PASS;
+
+ for (i = 0; i < hff_count; i++)
+ if (ob->hai_code == hff_codes[i]) break;
+
+ addr->message = string_sprintf("lookup failed for all hosts in %s router: "
+ "host_find_failed=ignore host_all_ignored=%s", rblock->name, hff_names[i]);
+
+ if (ob->hai_code == hff_defer) return DEFER;
+ if (ob->hai_code == hff_fail) return FAIL;
+
+ addr->special_action = SPECIAL_FREEZE;
+ return DEFER;
+ }
+
+/* Finally, since we have done all the routing here, there must be a transport
+defined for these hosts. It will be a remote one, as a local transport is
+dealt with above. However, we don't need one if verifying only. */
+
+if (transport == NULL && verify == v_none)
+ {
+ log_write(0, LOG_MAIN, "Error in %s router: no transport defined",
+ rblock->name);
+ addr->message = US"error in router: transport missing";
+ return DEFER;
+ }
+
+/* Fill in the transport, queue for remote delivery. The yield of
+rf_queue_add() is always TRUE for a remote transport. */
+
+ROUTED:
+
+addr->transport = transport;
+(void)rf_queue_add(addr, addr_local, addr_remote, rblock, NULL);
+return OK;
+}
+
+#endif /*!MACRO_PREDEF*/
+/* End of routers/manualroute.c */
diff --git a/src/routers/manualroute.h b/src/routers/manualroute.h
new file mode 100644
index 0000000..9c20b6f
--- /dev/null
+++ b/src/routers/manualroute.h
@@ -0,0 +1,39 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Header for the manualroute router */
+
+/* Structure for the private options. */
+
+typedef struct {
+ int hai_code;
+ int hff_code;
+ BOOL hosts_randomize;
+ uschar *host_all_ignored;
+ uschar *host_find_failed;
+ uschar *route_data;
+ uschar *route_list;
+} manualroute_router_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist manualroute_router_options[];
+extern int manualroute_router_options_count;
+
+/* Block containing default values. */
+
+extern manualroute_router_options_block manualroute_router_option_defaults;
+
+/* The main and initialization entry points for the router */
+
+extern int manualroute_router_entry(router_instance *, address_item *,
+ struct passwd *, int, address_item **, address_item **,
+ address_item **, address_item **);
+
+extern void manualroute_router_init(router_instance *);
+
+/* End of routers/manualroute.h */
diff --git a/src/routers/queryprogram.c b/src/routers/queryprogram.c
new file mode 100644
index 0000000..03c84fc
--- /dev/null
+++ b/src/routers/queryprogram.c
@@ -0,0 +1,559 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "rf_functions.h"
+#include "queryprogram.h"
+
+
+
+/* Options specific to the queryprogram router. */
+
+optionlist queryprogram_router_options[] = {
+ { "*expand_command_group", opt_bool | opt_hidden,
+ OPT_OFF(queryprogram_router_options_block, expand_cmd_gid) },
+ { "*expand_command_user", opt_bool | opt_hidden,
+ OPT_OFF(queryprogram_router_options_block, expand_cmd_uid) },
+ { "*set_command_group", opt_bool | opt_hidden,
+ OPT_OFF(queryprogram_router_options_block, cmd_gid_set) },
+ { "*set_command_user", opt_bool | opt_hidden,
+ OPT_OFF(queryprogram_router_options_block, cmd_uid_set) },
+ { "command", opt_stringptr,
+ OPT_OFF(queryprogram_router_options_block, command) },
+ { "command_group",opt_expand_gid,
+ OPT_OFF(queryprogram_router_options_block, cmd_gid) },
+ { "command_user", opt_expand_uid,
+ OPT_OFF(queryprogram_router_options_block, cmd_uid) },
+ { "current_directory", opt_stringptr,
+ OPT_OFF(queryprogram_router_options_block, current_directory) },
+ { "timeout", opt_time,
+ OPT_OFF(queryprogram_router_options_block, timeout) }
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int queryprogram_router_options_count =
+ sizeof(queryprogram_router_options)/sizeof(optionlist);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+queryprogram_router_options_block queryprogram_router_option_defaults = {0};
+void queryprogram_router_init(router_instance *rblock) {}
+int queryprogram_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+/* Default private options block for the queryprogram router. */
+
+queryprogram_router_options_block queryprogram_router_option_defaults = {
+ NULL, /* command */
+ 60*60, /* timeout */
+ (uid_t)(-1), /* cmd_uid */
+ (gid_t)(-1), /* cmd_gid */
+ FALSE, /* cmd_uid_set */
+ FALSE, /* cmd_gid_set */
+ US"/", /* current_directory */
+ NULL, /* expand_cmd_gid */
+ NULL /* expand_cmd_uid */
+};
+
+
+
+/*************************************************
+* Initialization entry point *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to enable
+consistency checks to be done, or anything else that needs to be set up. */
+
+void
+queryprogram_router_init(router_instance *rblock)
+{
+queryprogram_router_options_block *ob =
+ (queryprogram_router_options_block *)(rblock->options_block);
+
+/* A command must be given */
+
+if (ob->command == NULL)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "a command specification is required", rblock->name);
+
+/* A uid/gid must be supplied */
+
+if (!ob->cmd_uid_set && ob->expand_cmd_uid == NULL)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "command_user must be specified", rblock->name);
+}
+
+
+
+/*************************************************
+* Process a set of generated new addresses *
+*************************************************/
+
+/* This function sets up a set of newly generated child addresses and puts them
+on the new address chain.
+
+Arguments:
+ rblock router block
+ addr_new new address chain
+ addr original address
+ generated list of generated addresses
+ addr_prop the propagated data block, containing errors_to,
+ header change stuff, and address_data
+
+Returns: nothing
+*/
+
+static void
+add_generated(router_instance *rblock, address_item **addr_new,
+ address_item *addr, address_item *generated,
+ address_item_propagated *addr_prop)
+{
+while (generated != NULL)
+ {
+ BOOL ignore_error = addr->prop.ignore_error;
+ address_item *next = generated;
+
+ generated = next->next;
+
+ next->parent = addr;
+ next->prop = *addr_prop;
+ next->prop.ignore_error = next->prop.ignore_error || ignore_error;
+ next->start_router = rblock->redirect_router;
+
+ next->next = *addr_new;
+ *addr_new = next;
+
+ if (addr->child_count == USHRT_MAX)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
+ "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
+ addr->child_count++;
+
+ DEBUG(D_route)
+ debug_printf("%s router generated %s\n", rblock->name, next->address);
+ }
+}
+
+
+
+
+/*************************************************
+* Main entry point *
+*************************************************/
+
+/* See local README for interface details. This router returns:
+
+DECLINE
+ . DECLINE returned
+ . self = DECLINE
+
+PASS
+ . PASS returned
+ . timeout of host lookup and pass_on_timeout set
+ . self = PASS
+
+DEFER
+ . verifying the errors address caused a deferment or a big disaster such
+ as an expansion failure (rf_get_errors_address)
+ . expanding a headers_{add,remove} string caused a deferment or another
+ expansion error (rf_get_munge_headers)
+ . a problem in rf_get_transport: no transport when one is needed;
+ failed to expand dynamic transport; failed to find dynamic transport
+ . bad lookup type
+ . problem looking up host (rf_lookup_hostlist)
+ . self = DEFER or FREEZE
+ . failure to set up uid/gid for running the command
+ . failure of transport_set_up_command: too many arguments, expansion fail
+ . failure to create child process
+ . child process crashed or timed out or didn't return data
+ . :defer: in data
+ . DEFER or FREEZE returned
+ . problem in redirection data
+ . unknown transport name or trouble expanding router transport
+
+FAIL
+ . :fail: in data
+ . FAIL returned
+ . self = FAIL
+
+OK
+ . address added to addr_local or addr_remote for delivery
+ . new addresses added to addr_new
+*/
+
+int
+queryprogram_router_entry(
+ router_instance *rblock, /* data for this instantiation */
+ address_item *addr, /* address we are working on */
+ struct passwd *pw, /* passwd entry after check_local_user */
+ int verify, /* v_none/v_recipient/v_sender/v_expn */
+ address_item **addr_local, /* add it to this if it's local */
+ address_item **addr_remote, /* add it to this if it's remote */
+ address_item **addr_new, /* put new addresses on here */
+ address_item **addr_succeed) /* put old address here on success */
+{
+int fd_in, fd_out, len, rc;
+pid_t pid;
+struct passwd *upw = NULL;
+uschar buffer[1024];
+const uschar **argvptr;
+uschar *rword, *rdata, *s;
+address_item_propagated addr_prop;
+queryprogram_router_options_block *ob =
+ (queryprogram_router_options_block *)(rblock->options_block);
+uschar *current_directory = ob->current_directory;
+ugid_block ugid;
+uid_t curr_uid = getuid();
+gid_t curr_gid = getgid();
+uid_t uid = ob->cmd_uid;
+gid_t gid = ob->cmd_gid;
+uid_t *puid = &uid;
+gid_t *pgid = &gid;
+
+DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
+ rblock->name, addr->address, addr->domain);
+
+ugid.uid_set = ugid.gid_set = FALSE;
+
+/* Set up the propagated data block with the current address_data and the
+errors address and extra header stuff. */
+
+bzero(&addr_prop, sizeof(addr_prop));
+addr_prop.address_data = deliver_address_data;
+tree_dup((tree_node **)&addr_prop.variables, addr->prop.variables);
+
+rc = rf_get_errors_address(addr, rblock, verify, &addr_prop.errors_address);
+if (rc != OK) return rc;
+
+rc = rf_get_munge_headers(addr, rblock, &addr_prop.extra_headers,
+ &addr_prop.remove_headers);
+if (rc != OK) return rc;
+
+#ifdef EXPERIMENTAL_SRS
+addr_prop.srs_sender = NULL;
+#endif
+
+/* Get the fixed or expanded uid under which the command is to run
+(initialization ensures that one or the other is set). */
+
+if (!ob->cmd_uid_set)
+ {
+ if (!route_find_expanded_user(ob->expand_cmd_uid, rblock->name, US"router",
+ &upw, &uid, &(addr->message)))
+ return DEFER;
+ }
+
+/* Get the fixed or expanded gid, or take the gid from the passwd entry. */
+
+if (!ob->cmd_gid_set)
+ {
+ if (ob->expand_cmd_gid != NULL)
+ {
+ if (route_find_expanded_group(ob->expand_cmd_gid, rblock->name,
+ US"router", &gid, &(addr->message)))
+ return DEFER;
+ }
+ else if (upw != NULL)
+ {
+ gid = upw->pw_gid;
+ }
+ else
+ {
+ addr->message = string_sprintf("command_user set without command_group "
+ "for %s router", rblock->name);
+ return DEFER;
+ }
+ }
+
+DEBUG(D_route) debug_printf("requires uid=%ld gid=%ld current_directory=%s\n",
+ (long int)uid, (long int)gid, current_directory);
+
+/* If we are not running as root, we will not be able to change uid/gid. */
+
+if (curr_uid != root_uid && (uid != curr_uid || gid != curr_gid))
+ {
+ DEBUG(D_route)
+ {
+ debug_printf("not running as root: cannot change uid/gid\n");
+ debug_printf("subprocess will run with uid=%ld gid=%ld\n",
+ (long int)curr_uid, (long int)curr_gid);
+ }
+ puid = pgid = NULL;
+ }
+
+/* Set up the command to run */
+
+if (!transport_set_up_command(&argvptr, /* anchor for arg list */
+ ob->command, /* raw command */
+ TRUE, /* expand the arguments */
+ 0, /* not relevant when... */
+ NULL, /* no transporting address */
+ US"queryprogram router", /* for error messages */
+ &(addr->message))) /* where to put error message */
+ {
+ return DEFER;
+ }
+
+/* Create the child process, making it a group leader. */
+
+if ((pid = child_open_uid(argvptr, NULL, 0077, puid, pgid, &fd_in, &fd_out,
+ current_directory, TRUE, US"queryprogram-cmd")) < 0)
+ {
+ addr->message = string_sprintf("%s router couldn't create child process: %s",
+ rblock->name, strerror(errno));
+ return DEFER;
+ }
+
+/* Nothing is written to the standard input. */
+
+(void)close(fd_in);
+
+/* Wait for the process to finish, applying the timeout, and inspect its return
+code. */
+
+if ((rc = child_close(pid, ob->timeout)) != 0)
+ {
+ if (rc > 0)
+ addr->message = string_sprintf("%s router: command returned non-zero "
+ "code %d", rblock->name, rc);
+
+ else if (rc == -256)
+ {
+ addr->message = string_sprintf("%s router: command timed out",
+ rblock->name);
+ killpg(pid, SIGKILL); /* Kill the whole process group */
+ }
+
+ else if (rc == -257)
+ addr->message = string_sprintf("%s router: wait() failed: %s",
+ rblock->name, strerror(errno));
+
+ else
+ addr->message = string_sprintf("%s router: command killed by signal %d",
+ rblock->name, -rc);
+
+ return DEFER;
+ }
+
+/* Read the pipe to get the command's output, and then close it. */
+
+len = read(fd_out, buffer, sizeof(buffer) - 1);
+(void)close(fd_out);
+
+/* Failure to return any data is an error. */
+
+if (len <= 0)
+ {
+ addr->message = string_sprintf("%s router: command failed to return data",
+ rblock->name);
+ return DEFER;
+ }
+
+/* Get rid of leading and trailing white space, and pick off the first word of
+the result. */
+
+while (len > 0 && isspace(buffer[len-1])) len--;
+buffer[len] = 0;
+
+DEBUG(D_route) debug_printf("command wrote: %s\n", buffer);
+
+rword = buffer;
+while (isspace(*rword)) rword++;
+rdata = rword;
+while (*rdata != 0 && !isspace(*rdata)) rdata++;
+if (*rdata != 0) *rdata++ = 0;
+
+/* The word must be a known yield name. If it is "REDIRECT", the rest of the
+line is redirection data, as for a .forward file. It may not contain filter
+data, and it may not contain anything other than addresses (no files, no pipes,
+no specials). */
+
+if (strcmpic(rword, US"REDIRECT") == 0)
+ {
+ int filtertype;
+ redirect_block redirect;
+ address_item *generated = NULL;
+
+ redirect.string = rdata;
+ redirect.isfile = FALSE;
+
+ rc = rda_interpret(&redirect, /* redirection data */
+ RDO_BLACKHOLE | /* forbid :blackhole: */
+ RDO_FAIL | /* forbid :fail: */
+ RDO_INCLUDE | /* forbid :include: */
+ RDO_REWRITE, /* rewrite generated addresses */
+ NULL, /* :include: directory not relevant */
+ NULL, /* sieve vacation directory not relevant */
+ NULL, /* sieve enotify mailto owner not relevant */
+ NULL, /* sieve useraddress not relevant */
+ NULL, /* sieve subaddress not relevant */
+ &ugid, /* uid/gid (but not set) */
+ &generated, /* where to hang the results */
+ &(addr->message), /* where to put messages */
+ NULL, /* don't skip syntax errors */
+ &filtertype, /* not used; will always be FILTER_FORWARD */
+ string_sprintf("%s router", rblock->name));
+
+ switch (rc)
+ {
+ /* FF_DEFER and FF_FAIL can arise only as a result of explicit commands.
+ If a configured message was supplied, allow it to be included in an SMTP
+ response after verifying. */
+
+ case FF_DEFER:
+ if (addr->message == NULL) addr->message = US"forced defer";
+ else addr->user_message = addr->message;
+ return DEFER;
+
+ case FF_FAIL:
+ add_generated(rblock, addr_new, addr, generated, &addr_prop);
+ if (addr->message == NULL) addr->message = US"forced rejection";
+ else addr->user_message = addr->message;
+ return FAIL;
+
+ case FF_DELIVERED:
+ break;
+
+ case FF_NOTDELIVERED: /* an empty redirection list is bad */
+ addr->message = US"no addresses supplied";
+ /* Fall through */
+
+ case FF_ERROR:
+ default:
+ addr->basic_errno = ERRNO_BADREDIRECT;
+ addr->message = string_sprintf("error in redirect data: %s", addr->message);
+ return DEFER;
+ }
+
+ /* Handle the generated addresses, if any. */
+
+ add_generated(rblock, addr_new, addr, generated, &addr_prop);
+
+ /* Put the original address onto the succeed queue so that any retry items
+ that get attached to it get processed. */
+
+ addr->next = *addr_succeed;
+ *addr_succeed = addr;
+
+ return OK;
+ }
+
+/* Handle other returns that are not ACCEPT */
+
+if (strcmpic(rword, US"accept") != 0)
+ {
+ if (strcmpic(rword, US"decline") == 0) return DECLINE;
+ if (strcmpic(rword, US"pass") == 0) return PASS;
+ addr->message = string_copy(rdata); /* data is a message */
+ if (strcmpic(rword, US"fail") == 0)
+ {
+ setflag(addr, af_pass_message);
+ return FAIL;
+ }
+ if (strcmpic(rword, US"freeze") == 0) addr->special_action = SPECIAL_FREEZE;
+ else if (strcmpic(rword, US"defer") != 0)
+ {
+ addr->message = string_sprintf("bad command yield: %s %s", rword, rdata);
+ log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message);
+ }
+ return DEFER;
+ }
+
+/* The command yielded "ACCEPT". The rest of the string is a number of keyed
+fields from which we can fish out values using the "extract" expansion
+function. To use this feature, we must put the string into the $value variable,
+i.e. set lookup_value. */
+
+lookup_value = rdata;
+s = expand_string(US"${extract{data}{$value}}");
+if (*s != 0) addr_prop.address_data = string_copy(s);
+
+s = expand_string(US"${extract{transport}{$value}}");
+lookup_value = NULL;
+
+/* If we found a transport name, find the actual transport */
+
+if (*s != 0)
+ {
+ transport_instance *transport;
+ for (transport = transports; transport; transport = transport->next)
+ if (Ustrcmp(transport->name, s) == 0) break;
+ if (!transport)
+ {
+ addr->message = string_sprintf("unknown transport name %s yielded by "
+ "command", s);
+ log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message);
+ return DEFER;
+ }
+ addr->transport = transport;
+ }
+
+/* No transport given; get the transport from the router configuration. It may
+be fixed or expanded, but there will be an error if it is unset, requested by
+the last argument not being NULL. */
+
+else
+ {
+ if (!rf_get_transport(rblock->transport_name, &(rblock->transport), addr,
+ rblock->name, US"transport"))
+ return DEFER;
+ addr->transport = rblock->transport;
+ }
+
+/* See if a host list is given, and if so, look up the addresses. */
+
+lookup_value = rdata;
+s = expand_string(US"${extract{hosts}{$value}}");
+
+if (*s != 0)
+ {
+ int lookup_type = LK_DEFAULT;
+ uschar *ss = expand_string(US"${extract{lookup}{$value}}");
+ lookup_value = NULL;
+
+ if (*ss != 0)
+ {
+ if (Ustrcmp(ss, "byname") == 0) lookup_type = LK_BYNAME;
+ else if (Ustrcmp(ss, "bydns") == 0) lookup_type = LK_BYDNS;
+ else
+ {
+ addr->message = string_sprintf("bad lookup type \"%s\" yielded by "
+ "command", ss);
+ log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message);
+ return DEFER;
+ }
+ }
+
+ host_build_hostlist(&(addr->host_list), s, FALSE); /* pro tem no randomize */
+
+ rc = rf_lookup_hostlist(rblock, addr, rblock->ignore_target_hosts,
+ lookup_type, hff_defer, addr_new);
+ if (rc != OK) return rc;
+ }
+lookup_value = NULL;
+
+/* Put the errors address, extra headers, and address_data into this address */
+
+addr->prop = addr_prop;
+
+/* Queue the address for local or remote delivery. */
+
+return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
+ OK : DEFER;
+}
+
+#endif /*!MACRO_PREDEF*/
+/* End of routers/queryprogram.c */
diff --git a/src/routers/queryprogram.h b/src/routers/queryprogram.h
new file mode 100644
index 0000000..93046bd
--- /dev/null
+++ b/src/routers/queryprogram.h
@@ -0,0 +1,40 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+/* Private structure for the private options. */
+
+typedef struct {
+ uschar *command;
+ int timeout;
+ uid_t cmd_uid;
+ gid_t cmd_gid;
+ BOOL cmd_uid_set;
+ BOOL cmd_gid_set;
+ uschar *current_directory;
+ uschar *expand_cmd_gid;
+ uschar *expand_cmd_uid;
+} queryprogram_router_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist queryprogram_router_options[];
+extern int queryprogram_router_options_count;
+
+/* Block containing default values. */
+
+extern queryprogram_router_options_block queryprogram_router_option_defaults;
+
+/* The main and initialization entry points for the router */
+
+extern int queryprogram_router_entry(router_instance *, address_item *,
+ struct passwd *, int, address_item **, address_item **,
+ address_item **, address_item **);
+
+extern void queryprogram_router_init(router_instance *);
+
+/* End of routers/queryprogram.h */
diff --git a/src/routers/redirect.c b/src/routers/redirect.c
new file mode 100644
index 0000000..cd225d8
--- /dev/null
+++ b/src/routers/redirect.c
@@ -0,0 +1,913 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+#include "redirect.h"
+
+
+
+/* Options specific to the redirect router. */
+#define LOFF(field) OPT_OFF(redirect_router_options_block, field)
+
+optionlist redirect_router_options[] = {
+ { "allow_defer", opt_bit | (RDON_DEFER << 16),
+ LOFF(bit_options) },
+ { "allow_fail", opt_bit | (RDON_FAIL << 16),
+ LOFF(bit_options) },
+ { "allow_filter", opt_bit | (RDON_FILTER << 16),
+ LOFF(bit_options) },
+ { "allow_freeze", opt_bit | (RDON_FREEZE << 16),
+ LOFF(bit_options) },
+ { "check_ancestor", opt_bool, LOFF(check_ancestor) },
+ { "check_group", opt_bool, LOFF(check_group) },
+ { "check_owner", opt_bool, LOFF(check_owner) },
+ { "data", opt_stringptr, LOFF(data) },
+ { "directory_transport",opt_stringptr, LOFF(directory_transport_name) },
+ { "file", opt_stringptr, LOFF(file) },
+ { "file_transport", opt_stringptr, LOFF(file_transport_name) },
+
+ { "filter_prepend_home",opt_bit | (RDON_PREPEND_HOME << 16),
+ LOFF(bit_options) },
+ { "forbid_blackhole", opt_bit | (RDON_BLACKHOLE << 16),
+ LOFF(bit_options) },
+ { "forbid_exim_filter", opt_bit | (RDON_EXIM_FILTER << 16),
+ LOFF(bit_options) },
+ { "forbid_file", opt_bool,
+ LOFF(forbid_file) },
+ { "forbid_filter_dlfunc", opt_bit | (RDON_DLFUNC << 16),
+ LOFF(bit_options) },
+ { "forbid_filter_existstest", opt_bit | (RDON_EXISTS << 16),
+ LOFF(bit_options) },
+ { "forbid_filter_logwrite",opt_bit | (RDON_LOG << 16),
+ LOFF(bit_options) },
+ { "forbid_filter_lookup", opt_bit | (RDON_LOOKUP << 16),
+ LOFF(bit_options) },
+ { "forbid_filter_perl", opt_bit | (RDON_PERL << 16),
+ LOFF(bit_options) },
+ { "forbid_filter_readfile", opt_bit | (RDON_READFILE << 16),
+ LOFF(bit_options) },
+ { "forbid_filter_readsocket", opt_bit | (RDON_READSOCK << 16),
+ LOFF(bit_options) },
+ { "forbid_filter_reply",opt_bool,
+ LOFF(forbid_filter_reply) },
+ { "forbid_filter_run", opt_bit | (RDON_RUN << 16),
+ LOFF(bit_options) },
+ { "forbid_include", opt_bit | (RDON_INCLUDE << 16),
+ LOFF(bit_options) },
+ { "forbid_pipe", opt_bool,
+ LOFF(forbid_pipe) },
+ { "forbid_sieve_filter",opt_bit | (RDON_SIEVE_FILTER << 16),
+ LOFF(bit_options) },
+ { "forbid_smtp_code", opt_bool,
+ LOFF(forbid_smtp_code) },
+ { "hide_child_in_errmsg", opt_bool,
+ LOFF( hide_child_in_errmsg) },
+ { "ignore_eacces", opt_bit | (RDON_EACCES << 16),
+ LOFF(bit_options) },
+ { "ignore_enotdir", opt_bit | (RDON_ENOTDIR << 16),
+ LOFF(bit_options) },
+
+ { "include_directory", opt_stringptr, LOFF( include_directory) },
+ { "modemask", opt_octint, LOFF(modemask) },
+ { "one_time", opt_bool, LOFF(one_time) },
+ { "owners", opt_uidlist, LOFF(owners) },
+ { "owngroups", opt_gidlist, LOFF(owngroups) },
+ { "pipe_transport", opt_stringptr, LOFF(pipe_transport_name) },
+ { "qualify_domain", opt_stringptr, LOFF(qualify_domain) },
+ { "qualify_preserve_domain", opt_bool, LOFF(qualify_preserve_domain) },
+ { "repeat_use", opt_bool | opt_public, OPT_OFF(router_instance, repeat_use) },
+ { "reply_transport", opt_stringptr, LOFF(reply_transport_name) },
+
+ { "rewrite", opt_bit | (RDON_REWRITE << 16),
+ LOFF(bit_options) },
+
+ { "sieve_enotify_mailto_owner", opt_stringptr, LOFF(sieve_enotify_mailto_owner) },
+ { "sieve_subaddress", opt_stringptr, LOFF(sieve_subaddress) },
+ { "sieve_useraddress", opt_stringptr, LOFF(sieve_useraddress) },
+ { "sieve_vacation_directory", opt_stringptr, LOFF(sieve_vacation_directory) },
+ { "skip_syntax_errors", opt_bool, LOFF(skip_syntax_errors) },
+#ifdef EXPERIMENTAL_SRS
+ { "srs", opt_stringptr, LOFF(srs) },
+ { "srs_alias", opt_stringptr, LOFF(srs_alias) },
+ { "srs_condition", opt_stringptr, LOFF(srs_condition) },
+ { "srs_dbinsert", opt_stringptr, LOFF(srs_dbinsert) },
+ { "srs_dbselect", opt_stringptr, LOFF(srs_dbselect) },
+#endif
+ { "syntax_errors_text", opt_stringptr, LOFF(syntax_errors_text) },
+ { "syntax_errors_to", opt_stringptr, LOFF(syntax_errors_to) }
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int redirect_router_options_count =
+ sizeof(redirect_router_options)/sizeof(optionlist);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+redirect_router_options_block redirect_router_option_defaults = {0};
+void redirect_router_init(router_instance *rblock) {}
+int redirect_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+/* Default private options block for the redirect router. */
+
+redirect_router_options_block redirect_router_option_defaults = {
+ NULL, /* directory_transport */
+ NULL, /* file_transport */
+ NULL, /* pipe_transport */
+ NULL, /* reply_transport */
+ NULL, /* data */
+ NULL, /* directory_transport_name */
+ NULL, /* file */
+ NULL, /* file_dir */
+ NULL, /* file_transport_name */
+ NULL, /* include_directory */
+ NULL, /* pipe_transport_name */
+ NULL, /* reply_transport_name */
+ NULL, /* sieve_subaddress */
+ NULL, /* sieve_useraddress */
+ NULL, /* sieve_vacation_directory */
+ NULL, /* sieve_enotify_mailto_owner */
+ NULL, /* syntax_errors_text */
+ NULL, /* syntax_errors_to */
+ NULL, /* qualify_domain */
+ NULL, /* owners */
+ NULL, /* owngroups */
+#ifdef EXPERIMENTAL_SRS
+ NULL, /* srs */
+ NULL, /* srs_alias */
+ NULL, /* srs_condition */
+ NULL, /* srs_dbinsert */
+ NULL, /* srs_dbselect */
+#endif
+ 022, /* modemask */
+ RDO_REWRITE | RDO_PREPEND_HOME, /* bit_options */
+ FALSE, /* check_ancestor */
+ TRUE_UNSET, /* check_owner */
+ TRUE_UNSET, /* check_group */
+ FALSE, /* forbid_file */
+ FALSE, /* forbid_filter_reply */
+ FALSE, /* forbid_pipe */
+ FALSE, /* forbid_smtp_code */
+ FALSE, /* hide_child_in_errmsg */
+ FALSE, /* one_time */
+ FALSE, /* qualify_preserve_domain */
+ FALSE /* skip_syntax_errors */
+};
+
+
+
+/*************************************************
+* Initialization entry point *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to enable
+consistency checks to be done, or anything else that needs to be set up. */
+
+void redirect_router_init(router_instance *rblock)
+{
+redirect_router_options_block *ob =
+ (redirect_router_options_block *)(rblock->options_block);
+
+/* Either file or data must be set, but not both */
+
+if ((ob->file == NULL) == (ob->data == NULL))
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "%sone of \"file\" or \"data\" must be specified",
+ rblock->name, (ob->file == NULL)? "" : "only ");
+
+/* Onetime aliases can only be real addresses. Headers can't be manipulated.
+The combination of one_time and unseen is not allowed. We can't check the
+expansion of "unseen" here, but we assume that if it is set to anything other
+than false, there is likely to be a problem. */
+
+if (ob->one_time)
+ {
+ ob->forbid_pipe = ob->forbid_file = ob->forbid_filter_reply = TRUE;
+ if (rblock->extra_headers || rblock->remove_headers)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "\"headers_add\" and \"headers_remove\" are not permitted with "
+ "\"one_time\"", rblock->name);
+ if (rblock->unseen || rblock->expand_unseen)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "\"unseen\" may not be used with \"one_time\"", rblock->name);
+ }
+
+/* The defaults for check_owner and check_group depend on other settings. The
+defaults are: Check the owner if check_local_user or owners is set; check the
+group if check_local_user is set without a restriction on the group write bit,
+or if owngroups is set. */
+
+if (ob->check_owner == TRUE_UNSET)
+ ob->check_owner = rblock->check_local_user ||
+ (ob->owners && ob->owners[0] != 0);
+
+if (ob->check_group == TRUE_UNSET)
+ ob->check_group = (rblock->check_local_user && (ob->modemask & 020) == 0) ||
+ (ob->owngroups != NULL && ob->owngroups[0] != 0);
+
+/* If explicit qualify domain set, the preserve option is locked out */
+
+if (ob->qualify_domain && ob->qualify_preserve_domain)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "only one of \"qualify_domain\" or \"qualify_preserve_domain\" must be set",
+ rblock->name);
+
+/* If allow_filter is set, either user or check_local_user must be set. */
+
+if (!rblock->check_local_user &&
+ !rblock->uid_set &&
+ rblock->expand_uid == NULL &&
+ (ob->bit_options & RDO_FILTER) != 0)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
+ "\"user\" or \"check_local_user\" must be set with \"allow_filter\"",
+ rblock->name);
+}
+
+
+
+/*************************************************
+* Get errors address and header mods *
+*************************************************/
+
+/* This function is called when new addresses are generated, in order to
+sort out errors address and header modifications. We put the errors address
+into the parent address (even though it is never used from there because that
+address is never transported) so that it can be retrieved if any of the
+children gets routed by an "unseen" router. The clone of the child that is
+passed on must have the original errors_address value.
+
+Arguments:
+ rblock the router control block
+ addr the address being routed
+ verify v_none/v_recipient/v_sender/v_expn
+ addr_prop point to the propagated block, which is where the
+ new values are to be placed
+
+Returns: the result of rf_get_errors_address() or rf_get_munge_headers(),
+ which is either OK or DEFER
+*/
+
+static int
+sort_errors_and_headers(router_instance *rblock, address_item *addr,
+ int verify, address_item_propagated *addr_prop)
+{
+int frc = rf_get_errors_address(addr, rblock, verify,
+ &addr_prop->errors_address);
+if (frc != OK) return frc;
+addr->prop.errors_address = addr_prop->errors_address;
+return rf_get_munge_headers(addr, rblock, &addr_prop->extra_headers,
+ &addr_prop->remove_headers);
+}
+
+
+
+/*************************************************
+* Process a set of generated new addresses *
+*************************************************/
+
+/* This function sets up a set of newly generated child addresses and puts them
+on the new address chain. Copy in the uid, gid and permission flags for use by
+pipes and files, set the parent, and "or" its af_ignore_error flag. Also record
+the setting for any starting router.
+
+If the generated address is the same as one of its ancestors, and the
+check_ancestor flag is set, do not use this generated address, but replace it
+with a copy of the input address. This is to cope with cases where A is aliased
+to B and B has a .forward file pointing to A, though it is usually set on the
+forwardfile rather than the aliasfile. We can't just pass on the old
+address by returning FAIL, because it must act as a general parent for
+generated addresses, and only get marked "done" when all its children are
+delivered.
+
+Arguments:
+ rblock router block
+ addr_new new address chain
+ addr original address
+ generated list of generated addresses
+ addr_prop the propagated block, containing the errors_address,
+ header modification stuff, and address_data
+ ugidptr points to uid/gid data for files, pipes, autoreplies
+ pw password entry, set if ob->check_local_user is TRUE
+
+Returns: nothing
+*/
+
+static void
+add_generated(router_instance *rblock, address_item **addr_new,
+ address_item *addr, address_item *generated,
+ address_item_propagated *addr_prop, ugid_block *ugidptr, struct passwd *pw)
+{
+redirect_router_options_block *ob =
+ (redirect_router_options_block *)(rblock->options_block);
+
+while (generated)
+ {
+ address_item *parent;
+ address_item *next = generated;
+ uschar *errors_address = next->prop.errors_address;
+
+ generated = next->next;
+ next->parent = addr;
+ next->start_router = rblock->redirect_router;
+ if (addr->child_count == USHRT_MAX)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
+ "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
+ addr->child_count++;
+
+ next->next = *addr_new;
+ *addr_new = next;
+
+ /* Don't do the "one_time" thing for the first pass of a 2-stage queue run. */
+
+ if (ob->one_time && !f.queue_2stage)
+ {
+ for (parent = addr; parent->parent; parent = parent->parent) ;
+ next->onetime_parent = parent->address;
+ }
+
+ if (ob->hide_child_in_errmsg) setflag(next, af_hide_child);
+
+ /* If check_ancestor is set, we want to know if any ancestor of this address
+ is the address we are about to generate. The check must be done caselessly
+ unless the ancestor was routed by a case-sensitive router. */
+
+ if (ob->check_ancestor)
+ for (parent = addr; parent; parent = parent->parent)
+ if ((parent->router && parent->router->caseful_local_part
+ ? Ustrcmp(next->address, parent->address)
+ : strcmpic(next->address, parent->address)
+ ) == 0)
+ {
+ DEBUG(D_route) debug_printf("generated parent replaced by child\n");
+ next->address = string_copy(addr->address);
+ break;
+ }
+
+ /* A user filter may, under some circumstances, set up an errors address.
+ If so, we must take care to re-instate it when we copy in the propagated
+ data so that it overrides any errors_to setting on the router. */
+
+ {
+ BOOL ignore_error = next->prop.ignore_error;
+ next->prop = *addr_prop;
+ next->prop.ignore_error = ignore_error || addr->prop.ignore_error;
+ }
+ if (errors_address) next->prop.errors_address = errors_address;
+
+ /* For pipes, files, and autoreplies, record this router as handling them,
+ because they don't go through the routing process again. Then set up uid,
+ gid, home and current directories for transporting. */
+
+ if (testflag(next, af_pfr))
+ {
+ next->router = rblock;
+ rf_set_ugid(next, ugidptr); /* Will contain pw values if not overridden */
+
+ /* When getting the home directory out of the password information, wrap it
+ in \N...\N to avoid expansion later. In Cygwin, home directories can
+ contain $ characters. */
+
+ if (rblock->home_directory != NULL)
+ next->home_dir = rblock->home_directory;
+ else if (rblock->check_local_user)
+ next->home_dir = string_sprintf("\\N%s\\N", pw->pw_dir);
+ else if (rblock->router_home_directory != NULL &&
+ testflag(addr, af_home_expanded))
+ {
+ next->home_dir = deliver_home;
+ setflag(next, af_home_expanded);
+ }
+
+ next->current_dir = rblock->current_directory;
+
+ /* Permission options */
+
+ if (!ob->forbid_pipe) setflag(next, af_allow_pipe);
+ if (!ob->forbid_file) setflag(next, af_allow_file);
+ if (!ob->forbid_filter_reply) setflag(next, af_allow_reply);
+
+ /* If the transport setting fails, the error gets picked up at the outer
+ level from the setting of basic_errno in the address. */
+
+ if (next->address[0] == '|')
+ {
+ address_pipe = next->address;
+ if (rf_get_transport(ob->pipe_transport_name, &(ob->pipe_transport),
+ next, rblock->name, US"pipe_transport"))
+ next->transport = ob->pipe_transport;
+ address_pipe = NULL;
+ }
+ else if (next->address[0] == '>')
+ {
+ if (rf_get_transport(ob->reply_transport_name, &(ob->reply_transport),
+ next, rblock->name, US"reply_transport"))
+ next->transport = ob->reply_transport;
+ }
+ else /* must be file or directory */
+ {
+ int len = Ustrlen(next->address);
+ address_file = next->address;
+ if (next->address[len-1] == '/')
+ {
+ if (rf_get_transport(ob->directory_transport_name,
+ &(ob->directory_transport), next, rblock->name,
+ US"directory_transport"))
+ next->transport = ob->directory_transport;
+ }
+ else
+ {
+ if (rf_get_transport(ob->file_transport_name, &(ob->file_transport),
+ next, rblock->name, US"file_transport"))
+ next->transport = ob->file_transport;
+ }
+ address_file = NULL;
+ }
+ }
+
+#ifdef SUPPORT_I18N
+ if (!next->prop.utf8_msg)
+ next->prop.utf8_msg = string_is_utf8(next->address)
+ || (sender_address && string_is_utf8(sender_address));
+#endif
+
+ DEBUG(D_route)
+ {
+ debug_printf("%s router generated %s\n %serrors_to=%s transport=%s\n",
+ rblock->name,
+ next->address,
+ testflag(next, af_pfr)? "pipe, file, or autoreply\n " : "",
+ next->prop.errors_address,
+ (next->transport == NULL)? US"NULL" : next->transport->name);
+
+ if (testflag(next, af_uid_set))
+ debug_printf(" uid=%ld ", (long int)(next->uid));
+ else
+ debug_printf(" uid=unset ");
+
+ if (testflag(next, af_gid_set))
+ debug_printf("gid=%ld ", (long int)(next->gid));
+ else
+ debug_printf("gid=unset ");
+
+#ifdef SUPPORT_I18N
+ if (next->prop.utf8_msg) debug_printf("utf8 ");
+#endif
+
+ debug_printf("home=%s\n", next->home_dir);
+ }
+ }
+}
+
+
+/*************************************************
+* Main entry point *
+*************************************************/
+
+/* See local README for interface description. This router returns:
+
+DECLINE
+ . empty address list, or filter did nothing significant
+
+DEFER
+ . verifying the errors address caused a deferment or a big disaster such
+ as an expansion failure (rf_get_errors_address)
+ . expanding a headers_{add,remove} string caused a deferment or another
+ expansion error (rf_get_munge_headers)
+ . :defer: or "freeze" in a filter
+ . error in address list or filter
+ . skipped syntax errors, but failed to send the message
+
+DISCARD
+ . address was :blackhole:d or "seen finish"ed
+
+FAIL
+ . :fail:
+
+OK
+ . new addresses added to addr_new
+*/
+
+int redirect_router_entry(
+ router_instance *rblock, /* data for this instantiation */
+ address_item *addr, /* address we are working on */
+ struct passwd *pw, /* passwd entry after check_local_user */
+ int verify, /* v_none/v_recipient/v_sender/v_expn */
+ address_item **addr_local, /* add it to this if it's local */
+ address_item **addr_remote, /* add it to this if it's remote */
+ address_item **addr_new, /* put new addresses on here */
+ address_item **addr_succeed) /* put old address here on success */
+{
+redirect_router_options_block *ob =
+ (redirect_router_options_block *)(rblock->options_block);
+address_item *generated = NULL;
+const uschar *save_qualify_domain_recipient = qualify_domain_recipient;
+uschar *discarded = US"discarded";
+address_item_propagated addr_prop;
+error_block *eblock = NULL;
+ugid_block ugid;
+redirect_block redirect;
+int filtertype = FILTER_UNSET;
+int yield = OK;
+int options = ob->bit_options;
+int frc = 0;
+int xrc = 0;
+
+addr_local = addr_local; /* Keep picky compilers happy */
+addr_remote = addr_remote;
+
+/* Initialize the data to be propagated to the children */
+
+addr_prop.address_data = deliver_address_data;
+addr_prop.domain_data = deliver_domain_data;
+addr_prop.localpart_data = deliver_localpart_data;
+addr_prop.errors_address = NULL;
+addr_prop.extra_headers = NULL;
+addr_prop.remove_headers = NULL;
+addr_prop.variables = NULL;
+tree_dup((tree_node **)&addr_prop.variables, addr->prop.variables);
+
+#ifdef EXPERIMENTAL_SRS
+addr_prop.srs_sender = NULL;
+#endif
+#ifdef SUPPORT_I18N
+addr_prop.utf8_msg = addr->prop.utf8_msg;
+addr_prop.utf8_downcvt = addr->prop.utf8_downcvt;
+addr_prop.utf8_downcvt_maybe = addr->prop.utf8_downcvt_maybe;
+#endif
+
+
+/* When verifying and testing addresses, the "logwrite" command in filters
+must be bypassed. */
+
+if (verify == v_none && !f.address_test_mode) options |= RDO_REALLOG;
+
+/* Sort out the fixed or dynamic uid/gid. This uid is used (a) for reading the
+file (and interpreting a filter) and (b) for running the transports for
+generated file and pipe addresses. It is not (necessarily) the same as the uids
+that may own the file. Exim panics if an expanded string is not a number and
+can't be found in the password file. Other errors set the freezing bit. */
+
+if (!rf_get_ugid(rblock, addr, &ugid)) return DEFER;
+
+if (!ugid.uid_set && pw != NULL)
+ {
+ ugid.uid = pw->pw_uid;
+ ugid.uid_set = TRUE;
+ }
+
+if (!ugid.gid_set && pw != NULL)
+ {
+ ugid.gid = pw->pw_gid;
+ ugid.gid_set = TRUE;
+ }
+
+#ifdef EXPERIMENTAL_SRS
+ /* Perform SRS on recipient/return-path as required */
+
+ if(ob->srs != NULL)
+ {
+ BOOL usesrs = TRUE;
+
+ if(ob->srs_condition != NULL)
+ usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL);
+
+ if(usesrs)
+ {
+ int srs_action = 0, n_srs;
+ uschar *res;
+ uschar *usedomain;
+
+ /* What are we doing? */
+ if(Ustrcmp(ob->srs, "forward") == 0)
+ srs_action = 1;
+ else if(Ustrcmp(ob->srs, "reverseandforward") == 0)
+ {
+ srs_action = 3;
+
+ if((ob->srs_dbinsert == NULL) ^ (ob->srs_dbselect == NULL))
+ return DEFER;
+ }
+ else if(Ustrcmp(ob->srs, "reverse") == 0)
+ srs_action = 2;
+
+ /* Reverse SRS */
+ if(srs_action & 2)
+ {
+ srs_orig_recipient = addr->address;
+
+ eximsrs_init();
+ if(ob->srs_dbselect)
+ eximsrs_db_set(TRUE, ob->srs_dbselect);
+/* Comment this out for now...
+// else
+// eximsrs_db_set(TRUE, NULL);
+*/
+
+ if((n_srs = eximsrs_reverse(&res, addr->address)) == OK)
+ {
+ srs_recipient = res;
+ DEBUG(D_any)
+ debug_printf("SRS (reverse): Recipient '%s' rewritten to '%s'\n", srs_orig_recipient, srs_recipient);
+ }
+
+ eximsrs_done();
+
+ if(n_srs != OK)
+ return n_srs;
+ }
+
+ /* Forward SRS */
+ /* No point in actually performing SRS if we are just verifying a recipient */
+ if((srs_action & 1) && verify == v_none &&
+ (sender_address ? sender_address[0] != 0 : FALSE))
+ {
+
+ srs_orig_sender = sender_address;
+ eximsrs_init();
+ if(ob->srs_dbinsert)
+ eximsrs_db_set(FALSE, ob->srs_dbinsert);
+/* Comment this out for now...
+// else
+// eximsrs_db_set(FALSE, NULL);
+*/
+
+ if (!(usedomain = ob->srs_alias ? expand_string(ob->srs_alias) : NULL))
+ usedomain = string_copy(deliver_domain);
+
+ if((n_srs = eximsrs_forward(&res, sender_address, usedomain)) == OK)
+ {
+ addr_prop.srs_sender = res;
+ DEBUG(D_any)
+ debug_printf("SRS (forward): Sender '%s' rewritten to '%s'\n", srs_orig_sender, res);
+ }
+
+ eximsrs_done();
+
+ if(n_srs != OK)
+ return n_srs;
+ }
+ }
+ }
+#endif
+
+/* Call the function that interprets redirection data, either inline or from a
+file. This is a separate function so that the system filter can use it. It will
+run the function in a subprocess if necessary. If qualify_preserve_domain is
+set, temporarily reset qualify_domain_recipient to the current domain so that
+any unqualified addresses get qualified with the same domain as the incoming
+address. Otherwise, if a local qualify_domain is provided, set that up. */
+
+if (ob->qualify_preserve_domain)
+ qualify_domain_recipient = addr->domain;
+else if (ob->qualify_domain)
+ {
+ uschar *new_qdr = rf_expand_data(addr, ob->qualify_domain, &xrc);
+ if (!new_qdr) return xrc;
+ qualify_domain_recipient = new_qdr;
+ }
+
+redirect.owners = ob->owners;
+redirect.owngroups = ob->owngroups;
+redirect.modemask = ob->modemask;
+redirect.check_owner = ob->check_owner;
+redirect.check_group = ob->check_group;
+redirect.pw = pw;
+
+redirect.string = (redirect.isfile = (ob->file != NULL))
+ ? ob->file : ob->data;
+
+frc = rda_interpret(&redirect, options, ob->include_directory,
+ ob->sieve_vacation_directory, ob->sieve_enotify_mailto_owner,
+ ob->sieve_useraddress, ob->sieve_subaddress, &ugid, &generated,
+ &(addr->message), ob->skip_syntax_errors? &eblock : NULL, &filtertype,
+ string_sprintf("%s router (recipient is %s)", rblock->name, addr->address));
+
+qualify_domain_recipient = save_qualify_domain_recipient;
+
+/* Handle exceptional returns from filtering or processing an address list.
+For FAIL and FREEZE we honour any previously set up deliveries by a filter. */
+
+switch (frc)
+ {
+ case FF_NONEXIST:
+ addr->message = addr->user_message = NULL;
+ return DECLINE;
+
+ case FF_BLACKHOLE:
+ DEBUG(D_route) debug_printf("address :blackhole:d\n");
+ generated = NULL;
+ discarded = US":blackhole:";
+ frc = FF_DELIVERED;
+ break;
+
+ /* FF_DEFER and FF_FAIL can arise only as a result of explicit commands
+ (:defer: or :fail: in an alias file or "fail" in a filter). If a configured
+ message was supplied, allow it to be included in an SMTP response after
+ verifying. Remove any SMTP code if it is not allowed. */
+
+ case FF_DEFER:
+ yield = DEFER;
+ goto SORT_MESSAGE;
+
+ case FF_FAIL:
+ if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK)
+ return xrc;
+ add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
+ yield = FAIL;
+
+ SORT_MESSAGE:
+ if (!addr->message)
+ addr->message = yield == FAIL ? US"forced rejection" : US"forced defer";
+ else
+ {
+ int ovector[3];
+ if (ob->forbid_smtp_code &&
+ pcre_exec(regex_smtp_code, NULL, CS addr->message,
+ Ustrlen(addr->message), 0, PCRE_EOPT,
+ ovector, sizeof(ovector)/sizeof(int)) >= 0)
+ {
+ DEBUG(D_route) debug_printf("SMTP code at start of error message "
+ "is ignored because forbid_smtp_code is set\n");
+ addr->message += ovector[1];
+ }
+ addr->user_message = addr->message;
+ setflag(addr, af_pass_message);
+ }
+ return yield;
+
+ /* As in the case of a system filter, a freeze does not happen after a manual
+ thaw. In case deliveries were set up by the filter, we set the child count
+ high so that their completion does not mark the original address done. */
+
+ case FF_FREEZE:
+ if (!f.deliver_manual_thaw)
+ {
+ if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop))
+ != OK) return xrc;
+ add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
+ if (addr->message == NULL) addr->message = US"frozen by filter";
+ addr->special_action = SPECIAL_FREEZE;
+ addr->child_count = 9999;
+ return DEFER;
+ }
+ frc = FF_NOTDELIVERED;
+ break;
+
+ /* Handle syntax errors and :include: failures and lookup defers */
+
+ case FF_ERROR:
+ case FF_INCLUDEFAIL:
+
+ /* If filtertype is still FILTER_UNSET, it means that the redirection data
+ was never inspected, so the error was an expansion failure or failure to open
+ the file, or whatever. In these cases, the existing error message is probably
+ sufficient. */
+
+ if (filtertype == FILTER_UNSET) return DEFER;
+
+ /* If it was a filter and skip_syntax_errors is set, we want to set up
+ the error message so that it can be logged and mailed to somebody. */
+
+ if (filtertype != FILTER_FORWARD && ob->skip_syntax_errors)
+ {
+ eblock = store_get(sizeof(error_block), FALSE);
+ eblock->next = NULL;
+ eblock->text1 = addr->message;
+ eblock->text2 = NULL;
+ addr->message = addr->user_message = NULL;
+ }
+
+ /* Otherwise set up the error for the address and defer. */
+
+ else
+ {
+ addr->basic_errno = ERRNO_BADREDIRECT;
+ addr->message = string_sprintf("error in %s %s: %s",
+ filtertype == FILTER_FORWARD ? "redirect" : "filter",
+ ob->data ? "data" : "file",
+ addr->message);
+ return DEFER;
+ }
+ }
+
+
+/* Yield is either FF_DELIVERED (significant action) or FF_NOTDELIVERED (no
+significant action). Before dealing with these, however, we must handle the
+effect of skip_syntax_errors.
+
+If skip_syntax_errors was set and there were syntax errors in an address list,
+error messages will be present in eblock. Log them and send a message if so
+configured. We cannot do this earlier, because the error message must not be
+sent as the local user. If there were no valid addresses, generated will be
+NULL. In this case, the router declines.
+
+For a filter file, the error message has been fudged into an eblock. After
+dealing with it, the router declines. */
+
+if (eblock != NULL)
+ {
+ if (!moan_skipped_syntax_errors(
+ rblock->name, /* For message content */
+ eblock, /* Ditto */
+ (verify != v_none || f.address_test_mode)?
+ NULL : ob->syntax_errors_to, /* Who to mail */
+ generated != NULL, /* True if not all failed */
+ ob->syntax_errors_text)) /* Custom message */
+ return DEFER;
+
+ if (filtertype != FILTER_FORWARD || generated == NULL)
+ {
+ addr->message = US"syntax error in redirection data";
+ return DECLINE;
+ }
+ }
+
+/* Sort out the errors address and any header modifications, and handle the
+generated addresses, if any. If there are no generated addresses, we must avoid
+calling sort_errors_and_headers() in case this router declines - that function
+may modify the errors_address field in the current address, and we don't want
+to do that for a decline. */
+
+if (generated != NULL)
+ {
+ if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK)
+ return xrc;
+ add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
+ }
+
+/* FF_DELIVERED with no generated addresses is what we get when an address list
+contains :blackhole: or a filter contains "seen finish" without having
+generated anything. Log what happened to this address, and return DISCARD. */
+
+if (frc == FF_DELIVERED)
+ {
+ if (generated == NULL && verify == v_none && !f.address_test_mode)
+ {
+ log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
+ rblock->name);
+ yield = DISCARD;
+ }
+ }
+
+/* For an address list, FF_NOTDELIVERED always means that no addresses were
+generated. For a filter, addresses may or may not have been generated. If none
+were, it's the same as an empty address list, and the router declines. However,
+if addresses were generated, we can't just decline because successful delivery
+of the base address gets it marked "done", so deferred generated addresses
+never get tried again. We have to generate a new version of the base address,
+as if there were a "deliver" command in the filter file, with the original
+address as parent. */
+
+else
+ {
+ address_item *next;
+
+ if (generated == NULL) return DECLINE;
+
+ next = deliver_make_addr(addr->address, FALSE);
+ next->parent = addr;
+ addr->child_count++;
+ next->next = *addr_new;
+ *addr_new = next;
+
+ /* Set the data that propagates. */
+
+ next->prop = addr_prop;
+
+ DEBUG(D_route) debug_printf("%s router autogenerated %s\n%s%s%s",
+ rblock->name,
+ next->address,
+ (addr_prop.errors_address != NULL)? " errors to " : "",
+ (addr_prop.errors_address != NULL)? addr_prop.errors_address : US"",
+ (addr_prop.errors_address != NULL)? "\n" : "");
+ }
+
+/* Control gets here only when the address has been completely handled. Put the
+original address onto the succeed queue so that any retry items that get
+attached to it get processed. */
+
+addr->next = *addr_succeed;
+*addr_succeed = addr;
+
+return yield;
+}
+
+#endif /*!MACRO_PREDEF*/
+/* End of routers/redirect.c */
diff --git a/src/routers/redirect.h b/src/routers/redirect.h
new file mode 100644
index 0000000..74e14af
--- /dev/null
+++ b/src/routers/redirect.h
@@ -0,0 +1,77 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Header for the redirect router */
+
+/* Private structure for the private options. */
+
+typedef struct {
+ transport_instance *directory_transport;
+ transport_instance *file_transport;
+ transport_instance *pipe_transport;
+ transport_instance *reply_transport;
+
+ uschar *data;
+ uschar *directory_transport_name;
+ uschar *file;
+ uschar *file_dir;
+ uschar *file_transport_name;
+ uschar *include_directory;
+ uschar *pipe_transport_name;
+ uschar *reply_transport_name;
+ uschar *sieve_subaddress;
+ uschar *sieve_useraddress;
+ uschar *sieve_vacation_directory;
+ uschar *sieve_enotify_mailto_owner;
+ uschar *syntax_errors_text;
+ uschar *syntax_errors_to;
+ uschar *qualify_domain;
+
+ uid_t *owners;
+ gid_t *owngroups;
+
+#ifdef EXPERIMENTAL_SRS
+ uschar *srs;
+ uschar *srs_alias;
+ uschar *srs_condition;
+ uschar *srs_dbinsert;
+ uschar *srs_dbselect;
+#endif
+
+ int modemask;
+ int bit_options;
+ BOOL check_ancestor;
+ BOOL check_group;
+ BOOL check_owner;
+ BOOL forbid_file;
+ BOOL forbid_filter_reply;
+ BOOL forbid_pipe;
+ BOOL forbid_smtp_code;
+ BOOL hide_child_in_errmsg;
+ BOOL one_time;
+ BOOL qualify_preserve_domain;
+ BOOL skip_syntax_errors;
+} redirect_router_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist redirect_router_options[];
+extern int redirect_router_options_count;
+
+/* Block containing default values. */
+
+extern redirect_router_options_block redirect_router_option_defaults;
+
+/* The main and initialization entry points for the router */
+
+extern int redirect_router_entry(router_instance *, address_item *,
+ struct passwd *, int, address_item **, address_item **,
+ address_item **, address_item **);
+
+extern void redirect_router_init(router_instance *);
+
+/* End of routers/redirect.h */
diff --git a/src/routers/rf_change_domain.c b/src/routers/rf_change_domain.c
new file mode 100644
index 0000000..39c41fc
--- /dev/null
+++ b/src/routers/rf_change_domain.c
@@ -0,0 +1,84 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+
+/*************************************************
+* Change domain in an address *
+*************************************************/
+
+/* When a router wants to change the address that is being routed, it is like a
+redirection. We insert a new parent of the current address to hold the original
+information, and change the data in the original address, which is now the
+child. The child address is put onto the addr_new chain. Pick up the local part
+from the "address" field so as to get it in external form - caseful, and with
+any quoting retained.
+
+Arguments:
+ addr the address block
+ domain the new domain
+ rewrite TRUE if headers lines are to be rewritten
+ addr_new the new address chain
+
+Returns: nothing
+*/
+
+void
+rf_change_domain(address_item *addr, const uschar *domain, BOOL rewrite,
+ address_item **addr_new)
+{
+address_item *parent = store_get(sizeof(address_item), FALSE);
+uschar *at = Ustrrchr(addr->address, '@');
+uschar *address = string_sprintf("%.*s@%s",
+ (int)(at - addr->address), addr->address, domain);
+
+DEBUG(D_route) debug_printf("domain changed to %s\n", domain);
+
+/* The current address item is made into the parent, and a new address is set
+up in the old space. */
+
+*parent = *addr;
+
+/* First copy in initializing values, to wipe out stuff such as the named
+domain cache. Then copy over the propagating fields from the parent. Then set
+up the new fields. */
+
+*addr = address_defaults;
+addr->prop = parent->prop;
+
+addr->address = address;
+addr->unique = string_copy(address);
+addr->parent = parent;
+parent->child_count = 1;
+
+addr->next = *addr_new;
+*addr_new = addr;
+
+/* Rewrite header lines if requested */
+
+if (rewrite)
+ {
+ DEBUG(D_route|D_rewrite) debug_printf("rewriting header lines\n");
+ for (header_line * h = header_list; h != NULL; h = h->next)
+ {
+ header_line *newh =
+ rewrite_header(h, parent->domain, domain,
+ global_rewrite_rules, rewrite_existflags, TRUE);
+ if (newh)
+ {
+ h = newh;
+ f.header_rewritten = TRUE;
+ }
+ }
+ }
+}
+
+/* End of rf_change_domain.c */
diff --git a/src/routers/rf_expand_data.c b/src/routers/rf_expand_data.c
new file mode 100644
index 0000000..6a8ad17
--- /dev/null
+++ b/src/routers/rf_expand_data.c
@@ -0,0 +1,48 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+/*************************************************
+* Expand data string and handle errors *
+*************************************************/
+
+/* This little function is used by a couple of routers for expanding things. It
+just saves repeating this code too many times. It does an expansion, and
+chooses a suitable return code on error.
+
+Arguments:
+ addr the address that's being routed
+ s the string to be expanded
+ prc pointer to where to put the return code on failure
+
+Returns: the expanded string, or NULL (with prc set) on failure
+*/
+
+uschar *
+rf_expand_data(address_item *addr, uschar *s, int *prc)
+{
+uschar *yield = expand_string(s);
+if (yield != NULL) return yield;
+if (f.expand_string_forcedfail)
+ {
+ DEBUG(D_route) debug_printf("forced failure for expansion of \"%s\"\n", s);
+ *prc = DECLINE;
+ }
+else
+ {
+ addr->message = string_sprintf("failed to expand \"%s\": %s", s,
+ expand_string_message);
+ *prc = DEFER;
+ }
+return NULL;
+}
+
+/* End of routers/rf_expand_data.c */
diff --git a/src/routers/rf_functions.h b/src/routers/rf_functions.h
new file mode 100644
index 0000000..f310d5a
--- /dev/null
+++ b/src/routers/rf_functions.h
@@ -0,0 +1,31 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Header for the functions that are shared by the routers */
+
+
+extern void rf_add_generated(router_instance *, address_item **,
+ address_item *, address_item *, uschar *, header_line *,
+ uschar *, ugid_block *, struct passwd *);
+extern void rf_change_domain(address_item *, const uschar *, BOOL, address_item **);
+extern uschar *rf_expand_data(address_item *, uschar *, int *);
+extern int rf_get_errors_address(address_item *, router_instance *,
+ int, uschar **);
+extern int rf_get_munge_headers(address_item *, router_instance *,
+ header_line **, uschar **);
+extern BOOL rf_get_transport(uschar *, transport_instance **, address_item *,
+ uschar *, uschar *);
+extern BOOL rf_get_ugid(router_instance *, address_item *, ugid_block *);
+extern int rf_lookup_hostlist(router_instance *, address_item *, uschar *,
+ int, int, address_item **);
+extern BOOL rf_queue_add(address_item *, address_item **, address_item **,
+ router_instance *, struct passwd *);
+extern int rf_self_action(address_item *, host_item *, int, BOOL, uschar *,
+ address_item **);
+extern void rf_set_ugid(address_item *, ugid_block *);
+
+/* End of rf_functions.h */
diff --git a/src/routers/rf_get_errors_address.c b/src/routers/rf_get_errors_address.c
new file mode 100644
index 0000000..b9cf781
--- /dev/null
+++ b/src/routers/rf_get_errors_address.c
@@ -0,0 +1,132 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+/*************************************************
+* Get errors address for a router *
+*************************************************/
+
+/* This function is called by routers to sort out the errors address for a
+particular address. If there is a setting in the router block, then expand and
+verify it, and if it works, use it. Otherwise use any setting that is in the
+address itself. This might be NULL, meaning unset (the message's sender is then
+used). Verification isn't done when the original address is just being
+verified, as otherwise there might be routing loops if someone sets up a silly
+configuration.
+
+Arguments:
+ addr the input address
+ rblock the router instance
+ verify v_none / v_recipient / v_sender / v_expn
+ errors_to point the errors address here
+
+Returns: OK if no problem
+ DEFER if verifying the address caused a deferment
+ or a big disaster (e.g. expansion failure)
+*/
+
+int
+rf_get_errors_address(address_item *addr, router_instance *rblock,
+ int verify, uschar **errors_to)
+{
+uschar *s;
+
+*errors_to = addr->prop.errors_address;
+if (rblock->errors_to == NULL) return OK;
+
+s = expand_string(rblock->errors_to);
+
+if (s == NULL)
+ {
+ if (f.expand_string_forcedfail)
+ {
+ DEBUG(D_route)
+ debug_printf("forced expansion failure - ignoring errors_to\n");
+ return OK;
+ }
+ addr->message = string_sprintf("%s router failed to expand \"%s\": %s",
+ rblock->name, rblock->errors_to, expand_string_message);
+ return DEFER;
+ }
+
+/* If the errors_to address is empty, it means "ignore errors" */
+
+if (*s == 0)
+ {
+ addr->prop.ignore_error = TRUE; /* For locally detected errors */
+ *errors_to = US""; /* Return path for SMTP */
+ return OK;
+ }
+
+/* If we are already verifying, do not check the errors address, in order to
+save effort (but we do verify when testing an address). When we do verify, set
+the sender address to null, because that's what it will be when sending an
+error message, and there are now configuration options that control the running
+of routers by checking the sender address. When testing an address, there may
+not be a sender address. We also need to save and restore the expansion values
+associated with an address. */
+
+if (verify != v_none)
+ {
+ *errors_to = s;
+ DEBUG(D_route)
+ debug_printf("skipped verify errors_to address: already verifying\n");
+ }
+else
+ {
+ BOOL save_address_test_mode = f.address_test_mode;
+ int save1 = 0;
+ int i;
+ const uschar ***p;
+ const uschar *address_expansions_save[ADDRESS_EXPANSIONS_COUNT];
+ address_item *snew = deliver_make_addr(s, FALSE);
+
+ if (sender_address)
+ {
+ save1 = sender_address[0];
+ sender_address[0] = 0;
+ }
+
+ for (i = 0, p = address_expansions; *p;)
+ address_expansions_save[i++] = **p++;
+ f.address_test_mode = FALSE;
+
+ /* NOTE: the address is verified as a recipient, not a sender. This is
+ perhaps confusing. It isn't immediately obvious what to do: we want to have
+ some confidence that we can deliver to the address, in which case it will be
+ a recipient, but on the other hand, it will be passed on in SMTP deliveries
+ as a sender. However, I think on balance recipient is right because sender
+ verification is really about the *incoming* sender of the message.
+
+ If this code is changed, note that you must set vopt_fake_sender instead of
+ vopt_is_recipient, as otherwise sender_address may be altered because
+ verify_address() thinks it is dealing with *the* sender of the message. */
+
+ DEBUG(D_route|D_verify)
+ debug_printf("------ Verifying errors address %s ------\n", s);
+ if (verify_address(snew, NULL,
+ vopt_is_recipient /* vopt_fake_sender is the alternative */
+ | vopt_qualify, -1, -1, -1, NULL, NULL, NULL) == OK)
+ *errors_to = snew->address;
+ DEBUG(D_route|D_verify)
+ debug_printf("------ End verifying errors address %s ------\n", s);
+
+ f.address_test_mode = save_address_test_mode;
+ for (i = 0, p = address_expansions; *p; )
+ **p++ = address_expansions_save[i++];
+
+ if (sender_address) sender_address[0] = save1;
+ }
+
+return OK;
+}
+
+/* End of rf_get_errors_address.c */
diff --git a/src/routers/rf_get_munge_headers.c b/src/routers/rf_get_munge_headers.c
new file mode 100644
index 0000000..5c95d2d
--- /dev/null
+++ b/src/routers/rf_get_munge_headers.c
@@ -0,0 +1,122 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+/*************************************************
+* Get additional headers for a router *
+*************************************************/
+
+/* This function is called by routers to sort out the additional headers
+and header remove list for a particular address.
+
+Arguments:
+ addr the input address
+ rblock the router instance
+ extra_headers points to where to hang the header chain
+ remove_headers points to where to hang the remove list
+
+Returns: OK if no problem
+ DEFER if expanding a string caused a deferment
+ or a big disaster (e.g. expansion failure)
+*/
+
+int
+rf_get_munge_headers(address_item *addr, router_instance *rblock,
+ header_line **extra_headers, uschar **remove_headers)
+{
+/* Default is to retain existing headers */
+*extra_headers = addr->prop.extra_headers;
+
+if (rblock->extra_headers)
+ {
+ const uschar * list = rblock->extra_headers;
+ int sep = '\n';
+ uschar * s;
+ int slen;
+
+ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+ if (!(s = expand_string(s)))
+ {
+ if (!f.expand_string_forcedfail)
+ {
+ addr->message = string_sprintf(
+ "%s router failed to expand add_headers item \"%s\": %s",
+ rblock->name, s, expand_string_message);
+ return DEFER;
+ }
+ }
+ else if ((slen = Ustrlen(s)) > 0)
+ {
+ /* Expand succeeded. Put extra headers at the start of the chain because
+ further down it may point to headers from other routers, which may be
+ shared with other addresses. The output function outputs them in reverse
+ order. */
+
+ header_line * h = store_get(sizeof(header_line), FALSE);
+
+ /* We used to use string_sprintf() to add the newline if needed, but that
+ causes problems if the header line is exceedingly long (e.g. adding
+ something to a pathologically long line). So avoid it. */
+
+ if (s[slen-1] == '\n')
+ h->text = s;
+ else
+ {
+ h->text = store_get(slen+2, is_tainted(s));
+ memcpy(h->text, s, slen);
+ h->text[slen++] = '\n';
+ h->text[slen] = 0;
+ }
+
+ h->next = *extra_headers;
+ h->type = htype_other;
+ h->slen = slen;
+ *extra_headers = h;
+ }
+ }
+
+/* Default is to retain existing removes */
+*remove_headers = addr->prop.remove_headers;
+
+/* Expand items from colon-sep list separately, then build new list */
+if (rblock->remove_headers)
+ {
+ const uschar * list = rblock->remove_headers;
+ int sep = ':';
+ uschar * s;
+ gstring * g = NULL;
+
+ if (*remove_headers)
+ g = string_cat(NULL, *remove_headers);
+
+ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+ if (!(s = expand_string(s)))
+ {
+ if (!f.expand_string_forcedfail)
+ {
+ addr->message = string_sprintf(
+ "%s router failed to expand remove_headers item \"%s\": %s",
+ rblock->name, s, expand_string_message);
+ return DEFER;
+ }
+ }
+ else if (*s)
+ g = string_append_listele(g, ':', s);
+
+ if (g)
+ *remove_headers = g->s;
+ }
+
+return OK;
+}
+
+/* vi: aw ai sw=2
+*/
+/* End of rf_get_munge_headers.c */
diff --git a/src/routers/rf_get_transport.c b/src/routers/rf_get_transport.c
new file mode 100644
index 0000000..4a43818
--- /dev/null
+++ b/src/routers/rf_get_transport.c
@@ -0,0 +1,96 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+/*************************************************
+* Get transport for a router *
+*************************************************/
+
+/* If transport_name contains $, it must be expanded each time and used as a
+transport name. Otherwise, look up the transport only if the destination is not
+already set.
+
+Some routers (e.g. accept) insist that their transport option is set at
+initialization time. However, for some (e.g. file_transport in redirect), there
+is no such check, because the transport may not be required. Calls to this
+function from the former type of router have require_name = NULL, because it
+will never be used. NULL is also used in verify_only cases, where a transport
+is not required.
+
+Arguments:
+ tpname the text of the transport name
+ tpptr where to put the transport
+ addr the address being processed
+ router_name for error messages
+ require_name used in the error message if transport is unset
+
+Returns: TRUE if *tpptr is already set and tpname has no '$' in it;
+ TRUE if a transport has been placed in tpptr;
+ FALSE if there's a problem, in which case
+ addr->message contains a message, and addr->basic_errno has
+ ERRNO_BADTRANSPORT set in it.
+*/
+
+BOOL
+rf_get_transport(uschar *tpname, transport_instance **tpptr, address_item *addr,
+ uschar *router_name, uschar *require_name)
+{
+uschar *ss;
+BOOL expandable;
+
+if (!tpname)
+ {
+ if (!require_name) return TRUE;
+ addr->basic_errno = ERRNO_BADTRANSPORT;
+ addr->message = string_sprintf("%s unset in %s router", require_name,
+ router_name);
+ return FALSE;
+ }
+
+expandable = Ustrchr(tpname, '$') != NULL;
+if (*tpptr != NULL && !expandable) return TRUE;
+
+if (expandable)
+ {
+ if (!(ss = expand_string(tpname)))
+ {
+ addr->basic_errno = ERRNO_BADTRANSPORT;
+ addr->message = string_sprintf("failed to expand transport "
+ "\"%s\" in %s router: %s", tpname, router_name, expand_string_message);
+ return FALSE;
+ }
+ if (is_tainted(ss))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "attempt to use tainted value '%s' from '%s' for transport", ss, tpname);
+ addr->basic_errno = ERRNO_BADTRANSPORT;
+ /* Avoid leaking info to an attacker */
+ addr->message = US"internal configuration error";
+ return FALSE;
+ }
+ }
+else
+ ss = tpname;
+
+for (transport_instance * tp = transports; tp; tp = tp->next)
+ if (Ustrcmp(tp->name, ss) == 0)
+ {
+ DEBUG(D_route) debug_printf("set transport %s\n", ss);
+ *tpptr = tp;
+ return TRUE;
+ }
+
+addr->basic_errno = ERRNO_BADTRANSPORT;
+addr->message = string_sprintf("transport \"%s\" not found in %s router", ss,
+ router_name);
+return FALSE;
+}
+
+/* End of rf_get_transport.c */
diff --git a/src/routers/rf_get_ugid.c b/src/routers/rf_get_ugid.c
new file mode 100644
index 0000000..1735e59
--- /dev/null
+++ b/src/routers/rf_get_ugid.c
@@ -0,0 +1,80 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+/*************************************************
+* Get uid/gid for a router *
+*************************************************/
+
+/* This function is called by routers to sort out the uid/gid values which are
+passed with an address for use by local transports.
+
+Arguments:
+ rblock the router block
+ addr the address being worked on
+ ugid pointer to a ugid block to fill in
+
+Returns: TRUE if all goes well, else FALSE
+*/
+
+BOOL
+rf_get_ugid(router_instance *rblock, address_item *addr, ugid_block *ugid)
+{
+struct passwd *upw = NULL;
+
+/* Initialize from fixed values */
+
+ugid->uid = rblock->uid;
+ugid->gid = rblock->gid;
+ugid->uid_set = rblock->uid_set;
+ugid->gid_set = rblock->gid_set;
+ugid->initgroups = rblock->initgroups;
+
+/* If there is no fixed uid set, see if there's a dynamic one that can
+be expanded and possibly looked up. */
+
+if (!ugid->uid_set && rblock->expand_uid != NULL)
+ {
+ if (route_find_expanded_user(rblock->expand_uid, rblock->name, US"router",
+ &upw, &(ugid->uid), &(addr->message))) ugid->uid_set = TRUE;
+ else return FALSE;
+ }
+
+/* Likewise for the gid */
+
+if (!ugid->gid_set && rblock->expand_gid != NULL)
+ {
+ if (route_find_expanded_group(rblock->expand_gid, rblock->name, US"router",
+ &(ugid->gid), &(addr->message))) ugid->gid_set = TRUE;
+ else return FALSE;
+ }
+
+/* If a uid is set, then a gid must also be available; use one from the passwd
+lookup if it happened. */
+
+if (ugid->uid_set && !ugid->gid_set)
+ {
+ if (upw != NULL)
+ {
+ ugid->gid = upw->pw_gid;
+ ugid->gid_set = TRUE;
+ }
+ else
+ {
+ addr->message = string_sprintf("user set without group for %s router",
+ rblock->name);
+ return FALSE;
+ }
+ }
+
+return TRUE;
+}
+
+/* End of rf_get_ugid.c */
diff --git a/src/routers/rf_lookup_hostlist.c b/src/routers/rf_lookup_hostlist.c
new file mode 100644
index 0000000..79a7799
--- /dev/null
+++ b/src/routers/rf_lookup_hostlist.c
@@ -0,0 +1,262 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) The Exim Maintainers 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+
+/*************************************************
+* Look up IP addresses for a set of hosts *
+*************************************************/
+
+/* This function is called by a router to fill in the IP addresses for a set of
+hosts that are attached to an address. Each host has its name and MX value set;
+and those that need processing have their address fields set NULL. Multihomed
+hosts cause additional blocks to be inserted into the chain.
+
+This function also supports pseudo-hosts whose names end with "/MX". In this
+case, MX records are looked up for the name, and the list of hosts obtained
+replaces the incoming "host". In other words, "x/MX" is shorthand for "those
+hosts pointed to by x's MX records".
+
+It is also possible for a port to be specified along with the host name or IP
+address. The syntax is to add ":port" on to the end. This doesn't work with
+IPv6 addresses, so we allow IP addresses to be enclosed in [] in order to make
+this work. The specification of the port must come last, that is, after "/MX"
+if that is present.
+
+Arguments:
+ rblock the router block
+ addr the address being routed
+ ignore_target_hosts list of hosts to ignore
+ lookup_type LK_DEFAULT or LK_BYNAME or LK_BYDNS,
+ plus LK_IPV4_{ONLY,PREFER}
+ hff_code what to do for host find failed
+ addr_new passed to rf_self_action for self=reroute
+
+Returns: OK
+ DEFER host lookup defer
+ PASS timeout etc and pass_on_timeout set
+ self_action: PASS, DECLINE, DEFER, FAIL, FREEZE
+ hff_code after host find failed
+*/
+
+int
+rf_lookup_hostlist(router_instance *rblock, address_item *addr,
+ uschar *ignore_target_hosts, int lookup_type, int hff_code,
+ address_item **addr_new)
+{
+BOOL self_send = FALSE;
+
+/* Look up each host address. A lookup may add additional items into the chain
+if there are multiple addresses. Hence the use of next_h to start each cycle of
+the loop at the next original host. If any host is identified as being the local
+host, omit it and any subsequent hosts - i.e. treat the list like an ordered
+list of MX hosts. If the first host is the local host, act according to the
+"self" option in the configuration. */
+
+for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
+ {
+ const uschar *canonical_name;
+ int rc, len, port, mx, sort_key;
+
+ next_h = h->next;
+ if (h->address) { prev = h; continue; }
+
+ DEBUG(D_route|D_host_lookup)
+ debug_printf("finding IP address for %s\n", h->name);
+
+ /* Handle any port setting that may be on the name; it will be removed
+ from the end of the name. */
+
+ port = host_item_get_port(h);
+
+ /* Store the previous mx and sort_key values, which were assigned in
+ host_build_hostlist and will be overwritten by host_find_bydns. */
+
+ mx = h->mx;
+ sort_key = h->sort_key;
+
+ /* If the name ends with "/MX", we interpret it to mean "the list of hosts
+ pointed to by MX records with this name", and the MX record values override
+ the ordering from host_build_hostlist. */
+
+ len = Ustrlen(h->name);
+ if (len > 3 && strcmpic(h->name + len - 3, US"/mx") == 0)
+ {
+ int whichrrs = lookup_type & LK_IPV4_ONLY
+ ? HOST_FIND_BY_MX | HOST_FIND_IPV4_ONLY
+ : lookup_type & LK_IPV4_PREFER
+ ? HOST_FIND_BY_MX | HOST_FIND_IPV4_FIRST
+ : HOST_FIND_BY_MX;
+
+ DEBUG(D_route|D_host_lookup)
+ debug_printf("doing DNS MX lookup for %s\n", h->name);
+
+ mx = MX_NONE;
+ h->name = string_copyn(h->name, len - 3);
+ rc = host_find_bydns(h,
+ ignore_target_hosts,
+ whichrrs, /* look only for MX records */
+ NULL, /* SRV service not relevant */
+ NULL, /* failing srv domains not relevant */
+ NULL, /* no special mx failing domains */
+ &rblock->dnssec, /* dnssec request/require */
+ NULL, /* fully_qualified_name */
+ NULL); /* indicate local host removed */
+ }
+
+ /* If explicitly configured to look up by name, or if the "host name" is
+ actually an IP address, do a byname lookup. */
+
+ else if (lookup_type & LK_BYNAME || string_is_ip_address(h->name, NULL) != 0)
+ {
+ DEBUG(D_route|D_host_lookup) debug_printf("calling host_find_byname\n");
+ rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE,
+ &canonical_name, TRUE);
+ }
+
+ /* Otherwise, do a DNS lookup. If that yields "host not found", and the
+ lookup type is the default (i.e. "bydns" is not explicitly configured),
+ follow up with a byname lookup, just in case. */
+
+ else
+ {
+ BOOL removed;
+ int whichrrs = lookup_type & LK_IPV4_ONLY
+ ? HOST_FIND_BY_A
+ : lookup_type & LK_IPV4_PREFER
+ ? HOST_FIND_BY_A | HOST_FIND_BY_AAAA | HOST_FIND_IPV4_FIRST
+ : HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
+
+ DEBUG(D_route|D_host_lookup) debug_printf("doing DNS lookup\n");
+ switch (rc = host_find_bydns(h, ignore_target_hosts, whichrrs, NULL,
+ NULL, NULL,
+ &rblock->dnssec, /* domains for request/require */
+ &canonical_name, &removed))
+ {
+ case HOST_FOUND:
+ if (removed) setflag(addr, af_local_host_removed);
+ break;
+ case HOST_FIND_FAILED:
+ if (lookup_type & LK_DEFAULT)
+ {
+ DEBUG(D_route|D_host_lookup)
+ debug_printf("DNS lookup failed: trying %s\n",
+ f.running_in_test_harness
+ ? "host_fake_gethostbyname" : "getipnodebyname");
+ rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE,
+ &canonical_name, TRUE);
+ }
+ break;
+ }
+ }
+
+ /* Temporary failure defers, unless pass_on_timeout is set */
+
+ if (rc == HOST_FIND_SECURITY)
+ {
+ addr->message = string_sprintf("host lookup for %s done insecurely" , h->name);
+ addr->basic_errno = ERRNO_DNSDEFER;
+ return DEFER;
+ }
+ if (rc == HOST_FIND_AGAIN)
+ {
+ if (rblock->pass_on_timeout)
+ {
+ DEBUG(D_route)
+ debug_printf("%s router timed out and pass_on_timeout set\n",
+ rblock->name);
+ return PASS;
+ }
+ addr->message = string_sprintf("host lookup for %s did not complete "
+ "(DNS timeout?)", h->name);
+ addr->basic_errno = ERRNO_DNSDEFER;
+ return DEFER;
+ }
+
+ /* Permanent failure is controlled by host_find_failed */
+
+ if (rc == HOST_FIND_FAILED)
+ {
+ if (hff_code == hff_ignore)
+ {
+ if (prev == NULL) addr->host_list = next_h; else prev->next = next_h;
+ continue; /* With the next host, leave prev unchanged */
+ }
+
+ if (hff_code == hff_pass) return PASS;
+ if (hff_code == hff_decline) return DECLINE;
+
+ addr->basic_errno = ERRNO_UNKNOWNHOST;
+ addr->message =
+ string_sprintf("lookup of host \"%s\" failed in %s router%s",
+ h->name, rblock->name,
+ f.host_find_failed_syntax? ": syntax error in name" : "");
+
+ if (hff_code == hff_defer) return DEFER;
+ if (hff_code == hff_fail) return FAIL;
+
+ addr->special_action = SPECIAL_FREEZE;
+ return DEFER;
+ }
+
+ /* Deal with the settings that were previously cleared:
+ port, mx and sort_key. */
+
+ if (port != PORT_NONE)
+ for (host_item * hh = h; hh != next_h; hh = hh->next)
+ hh->port = port;
+
+ if (mx != MX_NONE)
+ for (host_item * hh = h; hh != next_h; hh = hh->next)
+ {
+ hh->mx = mx;
+ hh->sort_key = sort_key;
+ }
+
+ /* A local host gets chopped, with its successors, if there are previous
+ hosts. Otherwise the self option is used. If it is set to "send", any
+ subsequent hosts that are also the local host do NOT get chopped. */
+
+ if (rc == HOST_FOUND_LOCAL && !self_send)
+ {
+ if (prev)
+ {
+ DEBUG(D_route)
+ {
+ debug_printf("Removed from host list:\n");
+ for (; h; h = h->next) debug_printf(" %s\n", h->name);
+ }
+ prev->next = NULL;
+ setflag(addr, af_local_host_removed);
+ break;
+ }
+ rc = rf_self_action(addr, h, rblock->self_code, rblock->self_rewrite,
+ rblock->self, addr_new);
+ if (rc != OK)
+ {
+ addr->host_list = NULL; /* Kill the host list for */
+ return rc; /* anything other than "send" */
+ }
+ self_send = TRUE;
+ }
+
+ /* Ensure that prev is the host before next_h; this will not be h if a lookup
+ found multiple addresses or multiple MX records. */
+
+ prev = h;
+ while (prev->next != next_h) prev = prev->next;
+ }
+
+return OK;
+}
+
+/* End of rf_lookup_hostlist.c */
diff --git a/src/routers/rf_queue_add.c b/src/routers/rf_queue_add.c
new file mode 100644
index 0000000..4dab60d
--- /dev/null
+++ b/src/routers/rf_queue_add.c
@@ -0,0 +1,108 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+/*************************************************
+* Queue address for transport *
+*************************************************/
+
+/* This function is called to put an address onto the local or remote transport
+queue, as appropriate. When the driver is for verifying only, a transport need
+not be set, in which case it doesn't actually matter which queue the address
+gets put on.
+
+The generic uid/gid options are inspected and put into the address if they are
+set. For a remote transport, if there are fallback hosts, they are added to the
+address.
+
+Arguments:
+ addr the address, with the transport field set (if not verify only)
+ paddr_local pointer to the anchor of the local transport chain
+ paddr_remote pointer to the anchor of the remote transport chain
+ rblock the router block
+ pw password entry if check_local_user was set, or NULL
+
+Returns: FALSE on error; the only case is failing to get a uid/gid
+*/
+
+BOOL
+rf_queue_add(address_item *addr, address_item **paddr_local,
+ address_item **paddr_remote, router_instance *rblock, struct passwd *pw)
+{
+addr->prop.domain_data = deliver_domain_data; /* Save these values for */
+addr->prop.localpart_data = deliver_localpart_data; /* use in the transport */
+
+/* Handle a local transport */
+
+if (addr->transport && addr->transport->info->local)
+ {
+ ugid_block ugid;
+
+ /* Default uid/gid and transport-time home directory are from the passwd file
+ when check_local_user is set, but can be overridden by explicit settings.
+ When getting the home directory out of the password information, set the
+ flag that prevents expansion later. */
+
+ if (pw)
+ {
+ addr->uid = pw->pw_uid;
+ addr->gid = pw->pw_gid;
+ setflag(addr, af_uid_set);
+ setflag(addr, af_gid_set);
+ setflag(addr, af_home_expanded);
+ addr->home_dir = string_copy(US pw->pw_dir);
+ }
+
+ if (!rf_get_ugid(rblock, addr, &ugid)) return FALSE;
+ rf_set_ugid(addr, &ugid);
+
+ /* transport_home_directory (in rblock->home_directory) takes priority;
+ otherwise use the expanded value of router_home_directory. The flag also
+ tells the transport not to re-expand it. */
+
+ if (rblock->home_directory)
+ {
+ addr->home_dir = rblock->home_directory;
+ clearflag(addr, af_home_expanded);
+ }
+ else if (!addr->home_dir && testflag(addr, af_home_expanded))
+ addr->home_dir = deliver_home;
+
+ addr->current_dir = rblock->current_directory;
+
+ addr->next = *paddr_local;
+ *paddr_local = addr;
+ }
+
+/* For a remote transport, set up the fallback host list, and keep a count of
+the total number of addresses routed to remote transports. */
+
+else
+ {
+ addr->fallback_hosts = rblock->fallback_hostlist;
+ addr->next = *paddr_remote;
+ *paddr_remote = addr;
+ remote_delivery_count++;
+ }
+
+DEBUG(D_route)
+ {
+ debug_printf("queued for %s transport: local_part = %s\ndomain = %s\n"
+ " errors_to=%s\n",
+ addr->transport ? addr->transport->name : US"<unset>",
+ addr->local_part, addr->domain, addr->prop.errors_address);
+ debug_printf(" domain_data=%s local_part_data=%s\n", addr->prop.domain_data,
+ addr->prop.localpart_data);
+ }
+
+return TRUE;
+}
+
+/* End of rf_queue_add.c */
diff --git a/src/routers/rf_self_action.c b/src/routers/rf_self_action.c
new file mode 100644
index 0000000..9a4dc3c
--- /dev/null
+++ b/src/routers/rf_self_action.c
@@ -0,0 +1,123 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+
+/*************************************************
+* Decode actions for self reference *
+*************************************************/
+
+/* This function is called from a number of routers on receiving
+HOST_FOUND_LOCAL when looking up a supposedly remote host. The action is
+controlled by a generic configuration option called "self" on each router,
+which can be one of:
+
+ . freeze: Log the incident, freeze, and return DEFER
+
+ . defer: Log the incident and return DEFER
+
+ . fail: Fail the address
+
+ . send: Carry on with the delivery regardless -
+ this makes sense only if the SMTP
+ listener on this machine is a differently
+ configured MTA
+
+ . pass: The router passes; the address
+ gets passed to the next router, overriding
+ the setting of no_more
+
+ . reroute:<new-domain> Change the domain to the given domain
+ and return REROUTE so it gets passed back
+ to the routers.
+
+ . reroute:rewrite:<new-domain> The same, but headers containing the
+ old domain get rewritten.
+
+These string values are interpreted earlier on, and passed into this function
+as the values of "code" and "rewrite".
+
+Arguments:
+ addr the address being routed
+ host the host that is local, with MX set (or -1 if MX not used)
+ code the action to be taken (one of the self_xxx enums)
+ rewrite TRUE if rewriting headers required for REROUTED
+ new new domain to be used for REROUTED
+ addr_new child chain for REROUTEED
+
+Returns: DEFER, REROUTED, PASS, FAIL, or OK, according to the value of code.
+*/
+
+int
+rf_self_action(address_item *addr, host_item *host, int code, BOOL rewrite,
+ uschar *new, address_item **addr_new)
+{
+uschar * msg = host->mx >= 0
+ ? US"lowest numbered MX record points to local host"
+ : US"remote host address is the local host";
+
+switch (code)
+ {
+ case self_freeze:
+
+ /* If there is no message id, this is happening during an address
+ verification, so give information about the address that is being verified,
+ and where it has come from. Otherwise, during message delivery, the normal
+ logging for the address will be sufficient. */
+
+ if (message_id[0] == 0)
+ if (sender_fullhost)
+ log_write(0, LOG_MAIN, "%s: %s (while verifying <%s> from host %s)",
+ msg, addr->domain, addr->address, sender_fullhost);
+ else
+ log_write(0, LOG_MAIN, "%s: %s (while routing <%s>)", msg,
+ addr->domain, addr->address);
+ else
+ log_write(0, LOG_MAIN, "%s: %s", msg, addr->domain);
+
+ addr->message = msg;
+ addr->special_action = SPECIAL_FREEZE;
+ return DEFER;
+
+ case self_defer:
+ addr->message = msg;
+ return DEFER;
+
+ case self_reroute:
+ DEBUG(D_route)
+ debug_printf("%s: %s: domain changed to %s\n", msg, addr->domain, new);
+ rf_change_domain(addr, new, rewrite, addr_new);
+ return REROUTED;
+
+ case self_send:
+ DEBUG(D_route)
+ debug_printf("%s: %s: configured to try delivery anyway\n", msg, addr->domain);
+ return OK;
+
+ case self_pass: /* This is soft failure; pass to next router */
+ DEBUG(D_route)
+ debug_printf("%s: %s: passed to next router (self = pass)\n", msg, addr->domain);
+ addr->message = msg;
+ addr->self_hostname = string_copy(host->name);
+ return PASS;
+
+ case self_fail:
+ DEBUG(D_route)
+ debug_printf("%s: %s: address failed (self = fail)\n", msg, addr->domain);
+ addr->message = msg;
+ setflag(addr, af_pass_message);
+ return FAIL;
+ }
+
+return DEFER; /* paranoia */
+}
+
+/* End of rf_self_action.c */
diff --git a/src/routers/rf_set_ugid.c b/src/routers/rf_set_ugid.c
new file mode 100644
index 0000000..e1346b4
--- /dev/null
+++ b/src/routers/rf_set_ugid.c
@@ -0,0 +1,44 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "rf_functions.h"
+
+
+/*************************************************
+* Set uid/gid from block into address *
+*************************************************/
+
+/* This function copies any set uid or gid from a ugid block into an
+address.
+
+Arguments:
+ addr the address
+ ugid the ugid block
+
+Returns: nothing
+*/
+
+void
+rf_set_ugid(address_item *addr, ugid_block *ugid)
+{
+if (ugid->uid_set)
+ {
+ addr->uid = ugid->uid;
+ setflag(addr, af_uid_set);
+ }
+
+if (ugid->gid_set)
+ {
+ addr->gid = ugid->gid;
+ setflag(addr, af_gid_set);
+ }
+
+if (ugid->initgroups) setflag(addr, af_initgroups);
+}
+
+/* End of rf_set_ugid.c */