Routing: dnslookup and manualroute routers: ipv4_only, ipv4_prefer options. Bug...
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 28 Jan 2018 12:42:01 +0000 (12:42 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 28 Jan 2018 19:44:57 +0000 (19:44 +0000)
16 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/host.c
src/src/macros.h
src/src/routers/dnslookup.c
src/src/routers/dnslookup.h
src/src/routers/manualroute.c
src/src/routers/queryprogram.c
src/src/routers/rf_lookup_hostlist.c
src/src/smtp_in.c
src/src/transports/smtp.c
src/src/verify.c
test/confs/1009
test/scripts/1000-Basic-ipv6/1009
test/src/locate.pl
test/stdout/1009

index c908009a4b18a6ace4c6bcfb7d63ead809ac652b..7ad6f0275dde96911b6be03512e56aba6e973ef1 100644 (file)
@@ -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:
index 560e15ef50b5aa80b210d639eab9d8260aec50f0..2a72852bb984cb393861c710018ce73c6e6c5b85 100644 (file)
@@ -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
 ------------
index 1f0d91959b9f1982e2cfedeba4328c96aadb338e..72130f55a08f8818f460e258108c4eb71382d89f 100644 (file)
@@ -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;
index b97e9931551cfb533cc5e675831478c3aab93c90..6658fa70bb0e53b8ad9e686403dd117b5c7aaab8 100644 (file)
@@ -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. */
 
index 5017efbee8793a552263166c42f3b6924e292029..6ab08d7baa0a043aaab9e1576460b2ac14dc22cd 100644 (file)
@@ -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);
 
index 3979ef23149e4dc1574e5abe001cc5b8d428770e..b7e0915877da21ed3b8f7cc51abef3644357c9b3 100644 (file)
@@ -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. */
index 105fec0fec30b94bacd0963115571bbfa60a3edc..9aa2490ecece84d79c1bfcdef1f352d64c6b8fd0 100644 (file)
@@ -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");
index c7886923c7cb423f46bc184f3b4e3d6cf3037efa..251b0b89f2d8cab2bb5a24e18eff106b131805f4 100644 (file)
@@ -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 "
index c826857a725b2d34a20202fb36d5cdd01711a3db..acf976f672e5c5dfc921a4516f7e5801cab4ed9a 100644 (file)
@@ -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");
index 498cfab5e2d6513d6915d372c24140fec53178f3..424295dd58994eb24d2bbc62542ea688d6735e58 100644 (file)
@@ -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)
index 77b3eb8181f20e239b3a81e2754b6db782302602..ac61a405b3d2ccaac24e175da642c0272a14eea8 100644 (file)
@@ -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;
 
index eb479d44005f9b796637e85c1e50d6645f2b54ac..a4a4c49432e355a5694770837543982220844d12 100644 (file)
@@ -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;
 
index af6e27642a1d49b1037722443ea44b0143867221..fbfa7f0c108bd49f896daccd48f08ccd15a0f56a 100644 (file)
@@ -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 -----
index 6e5ae7d95d33a56a9579137349b4948f4ea317a3..aea8a126efb2002ae274767dddf4f5af516372ae 100644 (file)
@@ -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
+****
index 2eb319cdddce2e0528f274a82737dac6e5037058..ba74e030b60fdc0fb2ed1b760362aaffe8b4acec 100644 (file)
@@ -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
index ccda782f582f974eee086adaae23b4c9eae84632..673bf0a2f11638f03387caee29795e8e84c4f622 100644 (file)
@@ -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]