diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 09:44:07 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 09:44:07 +0000 |
commit | 39ce00b8d520cbecbd6af87257e8fb11df0ec273 (patch) | |
tree | 4c21a2674c19e5c44be3b3550b476b9e63d8ae3d /src/routers | |
parent | Initial commit. (diff) | |
download | exim4-39ce00b8d520cbecbd6af87257e8fb11df0ec273.tar.xz exim4-39ce00b8d520cbecbd6af87257e8fb11df0ec273.zip |
Adding upstream version 4.94.2.upstream/4.94.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/routers')
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 */ |