summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/doc-docbook/spec.xfpt33
-rw-r--r--doc/doc-txt/NewStuff3
-rw-r--r--src/src/host.c85
-rw-r--r--src/src/macros.h20
-rw-r--r--src/src/routers/dnslookup.c49
-rw-r--r--src/src/routers/dnslookup.h4
-rw-r--r--src/src/routers/manualroute.c18
-rw-r--r--src/src/routers/queryprogram.c6
-rw-r--r--src/src/routers/rf_lookup_hostlist.c33
-rw-r--r--src/src/smtp_in.c2
-rw-r--r--src/src/transports/smtp.c2
-rw-r--r--src/src/verify.c2
-rw-r--r--test/confs/100913
-rw-r--r--test/scripts/1000-Basic-ipv6/100924
-rw-r--r--test/src/locate.pl1
-rw-r--r--test/stdout/100921
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]