From 66387a737208e277990b0cbfe58db3db419f34b2 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 28 Jan 2018 12:42:01 +0000 Subject: [PATCH] Routing: dnslookup and manualroute routers: ipv4_only, ipv4_prefer options. Bug 2149 --- doc/doc-docbook/spec.xfpt | 33 ++++++++++- doc/doc-txt/NewStuff | 3 + src/src/host.c | 85 ++++++++++++++++------------ src/src/macros.h | 20 +++++-- src/src/routers/dnslookup.c | 49 +++++++++++----- src/src/routers/dnslookup.h | 4 +- src/src/routers/manualroute.c | 18 +++--- src/src/routers/queryprogram.c | 6 +- src/src/routers/rf_lookup_hostlist.c | 33 +++++++---- src/src/smtp_in.c | 2 +- src/src/transports/smtp.c | 2 +- src/src/verify.c | 2 +- test/confs/1009 | 13 +++++ test/scripts/1000-Basic-ipv6/1009 | 24 ++++++++ test/src/locate.pl | 1 + test/stdout/1009 | 21 +++++++ 16 files changed, 232 insertions(+), 84 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index c908009a4..7ad6f0275 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -14693,6 +14693,7 @@ If the resolver library does not support DNSSEC then this option has no effect. .option dns_ipv4_lookup main "domain list&!!" unset .cindex "IPv6" "DNS lookup for AAAA records" .cindex "DNS" "IPv6 lookup for AAAA records" +.cindex DNS "IPv6 disabling" When Exim is compiled with IPv6 support and &%disable_ipv6%& is not set, it looks for IPv6 address records (AAAA records) as well as IPv4 address records (A records) when trying to find IP addresses for hosts, unless the host's @@ -18745,7 +18746,9 @@ records. MX records of equal priority are sorted by Exim into a random order. Exim then looks for address records for the host names obtained from MX or SRV records. When a host has more than one IP address, they are sorted into a random order, -except that IPv6 addresses are always sorted before IPv4 addresses. If all the +.new +except that IPv6 addresses are sorted before IPv4 addresses. If all the +.wen IP addresses found are discarded by a setting of the &%ignore_target_hosts%& generic option, the router declines. @@ -18878,6 +18881,24 @@ However, it will result in any message with mistyped domains also being queued. +.new +.option ipv4_only "string&!!" unset +.cindex IPv6 disabling +.cindex DNS "IPv6 disabling" +The string is expanded, and if the result is anything but a forced failure, +or an empty string, or one of the strings “0” or “no” or “false” +(checked without regard to the case of the letters), +only A records are used. + +.option ipv4_prefer "string&!!" unset +.cindex IPv4 preference +.cindex DNS "IPv4 preference" +The string is expanded, and if the result is anything but a forced failure, +or an empty string, or one of the strings “0” or “no” or “false” +(checked without regard to the case of the letters), +A records are sorted before AAAA records (inverting the default). +.wen + .option mx_domains dnslookup "domain list&!!" unset .cindex "MX record" "required to exist" .cindex "SRV record" "required to exist" @@ -19482,8 +19503,8 @@ whether obtained from an MX lookup or not. .section "How the options are used" "SECThowoptused" -The options are a sequence of words; in practice no more than three are ever -present. One of the words can be the name of a transport; this overrides the +The options are a sequence of words, space-separated. +One of the words can be the name of a transport; this overrides the &%transport%& option on the router for this particular routing rule only. The other words (if present) control randomization of the list of hosts on a per-rule basis, and how the IP addresses of the hosts are to be found when @@ -19503,6 +19524,12 @@ also look in &_/etc/hosts_& or other sources of information. &%bydns%&: look up address records for the hosts directly in the DNS; fail if no address records are found. If there is a temporary DNS error (such as a timeout), delivery is deferred. +.new +.next +&%ipv4_only%&: in direct DNS lookups, look up only A records. +.next +&%ipv4_prefer%&: in direct DNS lookups, sort A records before AAAA records. +.wen .endlist For example: diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 560e15ef5..2a72852bb 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -27,6 +27,9 @@ Version 4.91 6. Receive duration on <= lines, under a new log_selector "receive_time". + 7. Options "ipv4_only" and "ipv4_prefer" on the dnslookup router and on + routing rules in the manualroute router. + Version 4.90 ------------ diff --git a/src/src/host.c b/src/src/host.c index 1f0d91959..72130f55a 100644 --- a/src/src/host.c +++ b/src/src/host.c @@ -1841,7 +1841,7 @@ for (hname = sender_host_name; hname; hname = *aliases++) d.request = sender_host_dnssec ? US"*" : NULL;; d.require = NULL; - if ( (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A, + if ( (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA, NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND || rc == HOST_FOUND_LOCAL ) @@ -2241,9 +2241,7 @@ field set to NULL, fill in its IP address from the DNS. If it is multi-homed, create additional host items for the additional addresses, copying all the other fields, and randomizing the order. -On IPv6 systems, A6 records are sought first (but only if support for A6 is -configured - they may never become mainstream), then AAAA records are sought, -and finally A records are sought as well. +On IPv6 systems, AAAA records are sought first, then A records. The host name may be changed if the DNS returns a different name - e.g. fully qualified or changed via CNAME. If fully_qualified_name is not NULL, dns_lookup @@ -2266,6 +2264,7 @@ Arguments: to something) dnssec_request if TRUE request the AD bit dnssec_require if TRUE require the AD bit + whichrrs select ipv4, ipv6 results Returns: HOST_FIND_FAILED couldn't find A record HOST_FIND_AGAIN try again later @@ -2278,7 +2277,7 @@ static int set_address_from_dns(host_item *host, host_item **lastptr, const uschar *ignore_target_hosts, BOOL allow_ip, const uschar **fully_qualified_name, - BOOL dnssec_request, BOOL dnssec_require) + BOOL dnssec_request, BOOL dnssec_require, int whichrrs) { dns_record *rr; host_item *thishostlast = NULL; /* Indicates not yet filled in anything */ @@ -2293,8 +2292,8 @@ those sites that feel they have to flaunt the RFC rules. */ if (allow_ip && string_is_ip_address(host->name, NULL) != 0) { #ifndef STAND_ALONE - if (ignore_target_hosts != NULL && - verify_check_this_host(&ignore_target_hosts, NULL, host->name, + if ( ignore_target_hosts + && verify_check_this_host(&ignore_target_hosts, NULL, host->name, host->name, NULL) == OK) return HOST_IGNORED; #endif @@ -2304,16 +2303,18 @@ if (allow_ip && string_is_ip_address(host->name, NULL) != 0) } /* On an IPv6 system, unless IPv6 is disabled, go round the loop up to twice, -looking for AAAA records the first time. However, unless -doing standalone testing, we force an IPv4 lookup if the domain matches -dns_ipv4_lookup is set. On an IPv4 system, go round the -loop once only, looking only for A records. */ +looking for AAAA records the first time. However, unless doing standalone +testing, we force an IPv4 lookup if the domain matches dns_ipv4_lookup global. +On an IPv4 system, go round the loop once only, looking only for A records. */ #if HAVE_IPV6 #ifndef STAND_ALONE - if (disable_ipv6 || (dns_ipv4_lookup != NULL && - match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL) == OK)) + if ( disable_ipv6 + || !(whichrrs & HOST_FIND_BY_AAAA) + || (dns_ipv4_lookup + && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL, + MCL_DOMAIN, TRUE, NULL) == OK) + ) i = 0; /* look up A records only */ else #endif /* STAND_ALONE */ @@ -2330,7 +2331,8 @@ for (; i >= 0; i--) { static int types[] = { T_A, T_AAAA }; int type = types[i]; - int randoffset = (i == 0)? 500 : 0; /* Ensures v6 sorts before v4 */ + int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0) + ? 500 : 0; /* Ensures v6/4 sort order */ dns_answer dnsa; dns_scan dnss; @@ -2532,10 +2534,13 @@ Arguments: whichrrs flags indicating which RRs to look for: HOST_FIND_BY_SRV => look for SRV HOST_FIND_BY_MX => look for MX - HOST_FIND_BY_A => look for A or AAAA + HOST_FIND_BY_A => look for A + HOST_FIND_BY_AAAA => look for AAAA also flags indicating how the lookup is done HOST_FIND_QUALIFY_SINGLE ) passed to the HOST_FIND_SEARCH_PARENTS ) resolver + HOST_FIND_IPV4_FIRST => reverse usual result ordering + HOST_FIND_IPV4_ONLY => MX results elide ipv6 srv_service when SRV used, the service name srv_fail_domains DNS errors for these domains => assume nonexist mx_fail_domains DNS errors for these domains => assume nonexist @@ -2716,7 +2721,7 @@ host. */ if (rc != DNS_SUCCEED) { - if ((whichrrs & HOST_FIND_BY_A) == 0) + if (!(whichrrs & (HOST_FIND_BY_A | HOST_FIND_BY_AAAA))) { DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n"); yield = HOST_FIND_FAILED; @@ -2729,7 +2734,7 @@ if (rc != DNS_SUCCEED) host->dnssec = DS_UNK; lookup_dnssec_authenticated = NULL; rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE, - fully_qualified_name, dnssec_request, dnssec_require); + fully_qualified_name, dnssec_request, dnssec_require, whichrrs); /* If one or more address records have been found, check that none of them are local. Since we know the host items all have their IP addresses @@ -3064,20 +3069,18 @@ for (h = host; h != last->next; h = h->next) if (h->address) continue; /* Inserted by a multihomed host */ rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip, - NULL, dnssec_request, dnssec_require); + NULL, dnssec_request, dnssec_require, + whichrrs & HOST_FIND_IPV4_ONLY + ? HOST_FIND_BY_A : HOST_FIND_BY_A | HOST_FIND_BY_AAAA); if (rc != HOST_FOUND) { h->status = hstatus_unusable; switch (rc) { - case HOST_FIND_AGAIN: - yield = rc; h->why = hwhy_deferred; break; - case HOST_FIND_SECURITY: - yield = rc; h->why = hwhy_insecure; break; - case HOST_IGNORED: - h->why = hwhy_ignored; break; - default: - h->why = hwhy_failed; break; + case HOST_FIND_AGAIN: yield = rc; h->why = hwhy_deferred; break; + case HOST_FIND_SECURITY: yield = rc; h->why = hwhy_insecure; break; + case HOST_IGNORED: h->why = hwhy_ignored; break; + default: h->why = hwhy_failed; break; } } } @@ -3128,12 +3131,22 @@ if (h != last && !disable_ipv6) for (h = host; h != last; h = h->next) host_item temp; host_item *next = h->next; - if (h->mx != next->mx || /* If next is different MX */ - h->address == NULL || /* OR this one is unset */ - Ustrchr(h->address, ':') != NULL || /* OR this one is IPv6 */ - (next->address != NULL && - Ustrchr(next->address, ':') == NULL)) /* OR next is IPv4 */ + if ( h->mx != next->mx /* If next is different MX */ + || !h->address /* OR this one is unset */ + ) + continue; /* move on to next */ + + if ( whichrrs & HOST_FIND_IPV4_FIRST + ? !Ustrchr(h->address, ':') /* OR this one is IPv4 */ + || next->address + && Ustrchr(next->address, ':') /* OR next is IPv6 */ + + : Ustrchr(h->address, ':') /* OR this one is IPv6 */ + || next->address + && !Ustrchr(next->address, ':') /* OR next is IPv4 */ + ) continue; /* move on to next */ + temp = *h; /* otherwise, swap */ temp.next = next->next; *h = *next; @@ -3194,7 +3207,7 @@ return yield; int main(int argc, char **cargv) { host_item h; -int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A; +int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA; BOOL byname = FALSE; BOOL qualify_single = TRUE; BOOL search_parents = FALSE; @@ -3236,15 +3249,15 @@ while (Ufgets(buffer, 256, stdin) != NULL) if (Ustrcmp(buffer, "byname") == 0) byname = TRUE; else if (Ustrcmp(buffer, "no_byname") == 0) byname = FALSE; - else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A; + else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A | HOST_FIND_BY_AAAA; else if (Ustrcmp(buffer, "mx_only") == 0) whichrrs = HOST_FIND_BY_MX; else if (Ustrcmp(buffer, "srv_only") == 0) whichrrs = HOST_FIND_BY_SRV; else if (Ustrcmp(buffer, "srv+a") == 0) - whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A; + whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A | HOST_FIND_BY_AAAA; else if (Ustrcmp(buffer, "srv+mx") == 0) whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX; else if (Ustrcmp(buffer, "srv+mx+a") == 0) - whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A; + whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA; else if (Ustrcmp(buffer, "qualify_single") == 0) qualify_single = TRUE; else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE; else if (Ustrcmp(buffer, "search_parents") == 0) search_parents = TRUE; diff --git a/src/src/macros.h b/src/src/macros.h index b97e99315..6658fa70b 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -750,7 +750,12 @@ enum { hwhy_unknown, hwhy_retry, hwhy_insecure, hwhy_failed, hwhy_deferred, /* Domain lookup types for routers */ -enum { lk_default, lk_byname, lk_bydns }; +#define LK_DEFAULT BIT(0) +#define LK_BYNAME BIT(1) +#define LK_BYDNS BIT(2) /* those 3 should be mutually exclusive */ + +#define LK_IPV4_ONLY BIT(3) +#define LK_IPV4_PREFER BIT(4) /* Values for the self_code fields */ @@ -819,11 +824,14 @@ enum { /* Flags for host_find_bydns() */ -#define HOST_FIND_BY_SRV 0x0001 -#define HOST_FIND_BY_MX 0x0002 -#define HOST_FIND_BY_A 0x0004 -#define HOST_FIND_QUALIFY_SINGLE 0x0008 -#define HOST_FIND_SEARCH_PARENTS 0x0010 +#define HOST_FIND_BY_SRV BIT(0) +#define HOST_FIND_BY_MX BIT(1) +#define HOST_FIND_BY_A BIT(2) +#define HOST_FIND_BY_AAAA BIT(3) +#define HOST_FIND_QUALIFY_SINGLE BIT(4) +#define HOST_FIND_SEARCH_PARENTS BIT(5) +#define HOST_FIND_IPV4_FIRST BIT(6) +#define HOST_FIND_IPV4_ONLY BIT(7) /* Actions applied to specific messages. */ diff --git a/src/src/routers/dnslookup.c b/src/src/routers/dnslookup.c index 5017efbee..6ab08d7ba 100644 --- a/src/src/routers/dnslookup.c +++ b/src/src/routers/dnslookup.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2017 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ #include "../exim.h" @@ -20,6 +20,10 @@ optionlist dnslookup_router_options[] = { (void *)(offsetof(dnslookup_router_options_block, check_srv)) }, { "fail_defer_domains", opt_stringptr, (void *)(offsetof(dnslookup_router_options_block, fail_defer_domains)) }, + { "ipv4_only", opt_stringptr, + (void *)(offsetof(dnslookup_router_options_block, ipv4_only)) }, + { "ipv4_prefer", opt_stringptr, + (void *)(offsetof(dnslookup_router_options_block, ipv4_prefer)) }, { "mx_domains", opt_stringptr, (void *)(offsetof(dnslookup_router_options_block, mx_domains)) }, { "mx_fail_domains", opt_stringptr, @@ -63,16 +67,18 @@ int dnslookup_router_entry(router_instance *rblock, address_item *addr, /* Default private options block for the dnslookup router. */ dnslookup_router_options_block dnslookup_router_option_defaults = { - FALSE, /* check_secondary_mx */ - TRUE, /* qualify_single */ - FALSE, /* search_parents */ - TRUE, /* rewrite_headers */ - NULL, /* widen_domains */ - NULL, /* mx_domains */ - NULL, /* mx_fail_domains */ - NULL, /* srv_fail_domains */ - NULL, /* check_srv */ - NULL /* fail_defer_domains */ + .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, }; @@ -154,7 +160,7 @@ dnslookup_router_entry( host_item h; int rc; int widen_sep = 0; -int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A; +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; @@ -255,6 +261,19 @@ for (;;) } 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 (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 (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 @@ -270,7 +289,7 @@ for (;;) /* 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 + stoubsequently. 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) @@ -279,8 +298,8 @@ for (;;) 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, + 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); diff --git a/src/src/routers/dnslookup.h b/src/src/routers/dnslookup.h index 3979ef231..b7e091587 100644 --- a/src/src/routers/dnslookup.h +++ b/src/src/routers/dnslookup.h @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2015 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* Private structure for the private options. */ @@ -18,6 +18,8 @@ typedef struct { 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. */ diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c index 105fec0fe..9aa2490ec 100644 --- a/src/src/routers/manualroute.c +++ b/src/src/routers/manualroute.c @@ -331,7 +331,7 @@ DEBUG(D_route) debug_printf("expanded list of hosts = \"%s\" options = %s\n", /* Set default lookup type and scan the options */ -lookup_type = lk_default; +lookup_type = LK_DEFAULT; while (*options != 0) { @@ -342,12 +342,16 @@ while (*options != 0) 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 = lk_byname; - else if (Ustrncmp(s, "bydns", n) == 0) lookup_type = lk_bydns; + 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 != NULL; t = t->next) + for (t = transports; t; t = t->next) if (Ustrncmp(t->name, s, n) == 0) { transport = t; @@ -355,7 +359,7 @@ while (*options != 0) break; } - if (t == NULL) + 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); @@ -397,7 +401,7 @@ if (!individual_transport_set) /* 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 != NULL && transport->info->local) +if (transport && transport->info->local) { if (hostlist[0] != 0) { @@ -447,7 +451,7 @@ if (rc != OK) return rc; 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 == NULL) +if (!addr->host_list) { int i; DEBUG(D_route) debug_printf("host_find_failed ignored every host\n"); diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c index c7886923c..251b0b89f 100644 --- a/src/src/routers/queryprogram.c +++ b/src/src/routers/queryprogram.c @@ -520,14 +520,14 @@ s = expand_string(US"${extract{hosts}{$value}}"); if (*s != 0) { - int lookup_type = lk_default; + 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; + 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 " diff --git a/src/src/routers/rf_lookup_hostlist.c b/src/src/routers/rf_lookup_hostlist.c index c826857a7..acf976f67 100644 --- a/src/src/routers/rf_lookup_hostlist.c +++ b/src/src/routers/rf_lookup_hostlist.c @@ -35,7 +35,8 @@ 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 + 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 @@ -90,6 +91,12 @@ for (prev = NULL, h = addr->host_list; h; h = next_h) 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); @@ -97,19 +104,19 @@ for (prev = NULL, h = addr->host_list; h; h = next_h) h->name = string_copyn(h->name, len - 3); rc = host_find_bydns(h, ignore_target_hosts, - HOST_FIND_BY_MX, /* look only for MX records */ - NULL, /* SRV service not relevant */ - NULL, /* failing srv domains not relevant */ - NULL, /* no special mx failing domains */ + 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 */ + 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) + 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, @@ -123,8 +130,14 @@ for (prev = NULL, h = addr->host_list; h; h = next_h) 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, HOST_FIND_BY_A, NULL, + switch (rc = host_find_bydns(h, ignore_target_hosts, whichrrs, NULL, NULL, NULL, &rblock->dnssec, /* domains for request/require */ &canonical_name, &removed)) @@ -133,7 +146,7 @@ for (prev = NULL, h = addr->host_list; h; h = next_h) if (removed) setflag(addr, af_local_host_removed); break; case HOST_FIND_FAILED: - if (lookup_type == lk_default) + if (lookup_type & LK_DEFAULT) { DEBUG(D_route|D_host_lookup) debug_printf("DNS lookup failed: trying getipnodebyname\n"); diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 498cfab5e..424295dd5 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -3522,7 +3522,7 @@ else HDEBUG(D_receive) debug_printf("getting IP address for %s\n", sender_helo_name); - rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A, + rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA, NULL, NULL, NULL, &d, NULL, NULL); if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) for (hh = &h; hh; hh = hh->next) diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 77b3eb818..ac61a405b 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -3992,7 +3992,7 @@ retry_non_continued: /* Find by name if so configured, or if it's an IP address. We don't just copy the IP address, because we need the test-for-local to happen. */ - flags = HOST_FIND_BY_A; + flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA; if (ob->dns_qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; if (ob->dns_search_parents) flags |= HOST_FIND_SEARCH_PARENTS; diff --git a/src/src/verify.c b/src/src/verify.c index eb479d440..a4a4c4943 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1849,7 +1849,7 @@ while (addr_new) additional host items being inserted into the chain. Hence we must save the next host first. */ - flags = HOST_FIND_BY_A; + flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA; if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS; diff --git a/test/confs/1009 b/test/confs/1009 index af6e27642..fbfa7f0c1 100644 --- a/test/confs/1009 +++ b/test/confs/1009 @@ -1,5 +1,7 @@ # Exim test configuration 1009 +OPT= + .include DIR/aux-var/std_conf_prefix @@ -12,10 +14,21 @@ queue_run_in_order begin routers +.ifdef ROUTE_DATA +r0: + driver = manualroute + route_data = ROUTE_DATA OPT + transport = t1 + self = send + +.else + r1: driver = dnslookup transport = t1 self = send + OPT +.endif # ----- Transports ----- diff --git a/test/scripts/1000-Basic-ipv6/1009 b/test/scripts/1000-Basic-ipv6/1009 index 6e5ae7d95..aea8a126e 100644 --- a/test/scripts/1000-Basic-ipv6/1009 +++ b/test/scripts/1000-Basic-ipv6/1009 @@ -11,3 +11,27 @@ exim -bt x@mx46466.test.ex **** exim -bt x@mx46466b.test.ex **** +# +# Reverse the preference order (dnslookup router) +exim -bt -DOPT=ipv4_prefer=y x@mx46.test.ex +**** +# +# Only lookup ipv4 (dnslookup router) +exim -bt -DOPT=ipv4_only=y x@mx46.test.ex +**** +# +# Reverse the preference order (manualroute router, MX) +exim -bt -DROUTE_DATA=mx46.test.ex/MX -DOPT=ipv4_prefer x@mx46.test.ex +**** +# +# Only lookup ipv4 (manualroute router, MX) +exim -bt -DROUTE_DATA=mx46.test.ex/MX -DOPT=ipv4_only x@mx46.test.ex +**** +# +# Reverse the preference order (manualroute router, plain) +exim -bt -DROUTE_DATA=46.test.ex -DOPT=ipv4_prefer x@mx46.test.ex +**** +# +# Only lookup ipv4 (manualroute router, plain) +exim -bt -DROUTE_DATA=46.test.ex -DOPT=ipv4_only x@mx46.test.ex +**** diff --git a/test/src/locate.pl b/test/src/locate.pl index 2eb319cdd..ba74e030b 100644 --- a/test/src/locate.pl +++ b/test/src/locate.pl @@ -10,6 +10,7 @@ my @dirs = grep { /^\// && -d } split(/:/, $ENV{PATH}), qw( /bin /usr/bin /usr/sbin + /usr/lib /usr/libexec /usr/local/bin /usr/local/sbin diff --git a/test/stdout/1009 b/test/stdout/1009 index ccda782f5..673bf0a2f 100644 --- a/test/stdout/1009 +++ b/test/stdout/1009 @@ -30,3 +30,24 @@ x@mx46466b.test.ex host 46b.test.ex [V6NET:ffff:836f:a00:a:800:200a:c033] MX=47 host 46b.test.ex [V4NET.0.0.5] MX=47 host v6.test.ex [V6NET:ffff:836f:a00:a:800:200a:c032] MX=48 +x@mx46.test.ex + router = r1, transport = t1 + host 46.test.ex [V4NET.0.0.4] MX=46 + host 46.test.ex [V6NET:ffff:836f:a00:a:800:200a:c031] MX=46 +x@mx46.test.ex + router = r1, transport = t1 + host 46.test.ex [V4NET.0.0.4] MX=46 +x@mx46.test.ex + router = r0, transport = t1 + host 46.test.ex [V4NET.0.0.4] MX=46 + host 46.test.ex [V6NET:ffff:836f:a00:a:800:200a:c031] MX=46 +x@mx46.test.ex + router = r0, transport = t1 + host 46.test.ex [V4NET.0.0.4] MX=46 +x@mx46.test.ex + router = r0, transport = t1 + host 46.test.ex [V4NET.0.0.4] + host 46.test.ex [V6NET:ffff:836f:a00:a:800:200a:c031] +x@mx46.test.ex + router = r0, transport = t1 + host 46.test.ex [V4NET.0.0.4] -- 2.25.1