Re: [exim] Potential logic error in retry handling for IPv4+…

Top Page
Delete this message
Reply to this message
Author: Philip Hazel
Date:  
To: exim-users
Subject: Re: [exim] Potential logic error in retry handling for IPv4+IPv6 hosts
On Thu, 1 Dec 2005, Marc Sherman wrote:

> > I'm currently facing a mail delivery problem. The sending MTA is Exim
> > 4.50. The host has no real IPv6 connectivity, but a working IPv6
> > stack, and the DNS resolver returns AAAA records.


OK, I got the message about disabling IPv6 at run time. The patch below,
which I think is still small enough for the list, adds the following
feature:

PH/01 There is a new global option called disable_ipv6, which does exactly what
      its name implies. If set true, even if the Exim binary has IPv6 support,
      no IPv6 activities take place. AAAA records are never looked up for
      host names given in manual routing data or elsewhere. AAAA records that
      are received from the DNS as additional data for MX records are ignored.
      Any IPv6 addresses that are listed in local_interfaces, manualroute route
      data, etc. are also ignored. If IP literals are enabled, the ipliteral
      router declines to handle IPv6 literal addresses.


Please test this patch to see if I have got it right. The patch also
fixes a bug in the ipliteral router, which caused it not to recognize
literal IPv6 domains prefixed by "ipv6:".

Philip

-- 
Philip Hazel            University of Cambridge Computing Service,
ph10@???      Cambridge, England. Phone: +44 1223 334714.




*** exim-4.60/src/globals.h Mon Nov 28 10:57:32 2005
--- globals.h    Mon Dec  5 14:55:51 2005
***************
*** 271,276 ****
--- 271,277 ----
  extern int     demime_ok;              /* Nonzero if message has been demimed */
  extern uschar *demime_reason;          /* Reason for broken MIME container */
  #endif
+ extern BOOL    disable_ipv6;           /* Don't do any IPv6 things */
  extern BOOL    disable_logging;        /* Disables log writing when TRUE */


  #ifdef EXPERIMENTAL_DOMAINKEYS
*** exim-4.60/src/globals.c Mon Nov 28 10:57:32 2005
--- globals.c    Mon Dec  5 14:55:52 2005
***************
*** 482,487 ****
--- 482,488 ----
  int     demime_ok              = 0;
  uschar *demime_reason          = NULL;
  #endif
+ BOOL    disable_ipv6           = FALSE;
  BOOL    disable_logging        = FALSE;


  #ifdef EXPERIMENTAL_DOMAINKEYS
*** exim-4.60/src/readconf.c        Mon Nov 28 10:57:32 2005
--- readconf.c    Mon Dec  5 15:01:12 2005
***************
*** 194,199 ****
--- 194,200 ----
    { "deliver_drop_privilege",   opt_bool,        &deliver_drop_privilege },
    { "deliver_queue_load_max",   opt_fixed,       &deliver_queue_load_max },
    { "delivery_date_remove",     opt_bool,        &delivery_date_remove },
+   { "disable_ipv6",             opt_bool,        &disable_ipv6 },
    { "dns_again_means_nonexist", opt_stringptr,   &dns_again_means_nonexist },
    { "dns_check_names_pattern",  opt_stringptr,   &check_dns_names_pattern },
    { "dns_csa_search_limit",     opt_int,         &dns_csa_search_limit },
***************
*** 2832,2840 ****
      struct hostent *hostdata;


      #if HAVE_IPV6
!     if (dns_ipv4_lookup == NULL ||
           match_isinlist(hostname, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
!            TRUE, NULL) != OK)
        af = AF_INET6;
      #else
      af = AF_INET;
--- 2833,2841 ----
      struct hostent *hostdata;


      #if HAVE_IPV6
!     if (!disable_ipv6 && (dns_ipv4_lookup == NULL ||
           match_isinlist(hostname, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
!            TRUE, NULL) != OK))
        af = AF_INET6;
      #else
      af = AF_INET;
*** exim-4.60/src/host.c    Mon Nov 28 10:57:32 2005
--- host.c    Tue Dec  6 09:48:32 2005
***************
*** 774,783 ****


  while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
    {
    int port = host_address_extract_port(s);            /* Leaves just the IP address */
!   if (string_is_ip_address(s, NULL) == 0)
      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s",
        s, name);


    /* This use of strcpy() is OK because we have checked that s is a valid IP
    address above. The field in the ip_address_item is large enough to hold an
--- 774,788 ----


  while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
    {
+   int ipv; 
    int port = host_address_extract_port(s);            /* Leaves just the IP address */
!   if ((ipv = string_is_ip_address(s, NULL)) == 0)
      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s",
        s, name);
+       
+   /* Skip IPv6 addresses if IPv6 is disabled. */
+ 
+   if (disable_ipv6 && ipv == 6) continue;


    /* This use of strcpy() is OK because we have checked that s is a valid IP
    address above. The field in the ip_address_item is large enough to hold an
***************
*** 1965,1981 ****
      return HOST_FIND_AGAIN;
    }


! /* In an IPv6 world, we need to scan for both kinds of address, so go round the
! loop twice. Note that we have ensured that AF_INET6 is defined even in an IPv4
! world, which makes for slightly tidier code. However, if dns_ipv4_lookup
! matches the domain, we also just do IPv4 lookups here (except when testing
! standalone). */

  #if HAVE_IPV6
    #ifndef STAND_ALONE
!   if (dns_ipv4_lookup != NULL &&
          match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
!           TRUE, NULL) == OK)
      { af = AF_INET; times = 1; }
    else
    #endif  /* STAND_ALONE */
--- 1970,1986 ----
      return HOST_FIND_AGAIN;
    }


! /* In an IPv6 world, unless IPv6 has been disabled, we need to scan for both
! kinds of address, so go round the loop twice. Note that we have ensured that
! AF_INET6 is defined even in an IPv4 world, which makes for slightly tidier
! code. However, if dns_ipv4_lookup matches the domain, we also just do IPv4
! lookups here (except when testing standalone). */

  #if HAVE_IPV6
    #ifndef STAND_ALONE
!   if (disable_ipv6 || (dns_ipv4_lookup != NULL &&
          match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
!           TRUE, NULL) == OK))
      { af = AF_INET; times = 1; }
    else
    #endif  /* STAND_ALONE */
***************
*** 2249,2266 ****
    return HOST_FOUND;
    }


! /* On an IPv6 system, go round the loop up to three times, looking for A6 and
! AAAA records the first two times. However, unless doing standalone testing, we
! force an IPv4 lookup if the domain matches dns_ipv4_lookup is set. Since A6
! records look like being abandoned, support them only if explicitly configured
! to do so. On an IPv4 system, go round the loop once only, looking only for A
! records. */

  #if HAVE_IPV6
    #ifndef STAND_ALONE
!     if (dns_ipv4_lookup != NULL &&
          match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
!         TRUE, NULL) == OK)
        i = 0;    /* look up A records only */
      else
    #endif        /* STAND_ALONE */
--- 2254,2271 ----
    return HOST_FOUND;
    }


! /* On an IPv6 system, unless IPv6 is disabled, go round the loop up to three
! times, looking for A6 and AAAA records the first two times. However, unless
! doing standalone testing, we force an IPv4 lookup if the domain matches
! dns_ipv4_lookup is set. Since A6 records look like being abandoned, support
! them only if explicitly configured to do so. 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, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
!         TRUE, NULL) == OK))
        i = 0;    /* look up A records only */
      else
    #endif        /* STAND_ALONE */
***************
*** 2893,2902 ****


    if (rr->type != T_A
    #if HAVE_IPV6
!     && rr->type != T_AAAA
!     #ifdef SUPPORT_A6
!     && rr->type != T_A6
!     #endif
    #endif
      ) continue;


--- 2898,2911 ----

    if (rr->type != T_A
    #if HAVE_IPV6
!     && ( disable_ipv6 ||
!          ( 
!          rr->type != T_AAAA
!          #ifdef SUPPORT_A6
!          && rr->type != T_A6
!          #endif
!          )
!        )    
    #endif
      ) continue;


*** exim-4.60/src/routers/ipliteral.c        Mon Nov 28 10:57:32 2005
--- routers/ipliteral.c Tue Dec  6 09:45:10 2005
***************
*** 102,109 ****
  */
  host_item *h;
  uschar *domain = addr->domain;
  int len = Ustrlen(domain);
! int rc;


  addr_new = addr_new;         /* Keep picky compilers happy */
  addr_succeed = addr_succeed;
--- 102,110 ----
  */
  host_item *h;
  uschar *domain = addr->domain;
+ uschar *ip;
  int len = Ustrlen(domain);
! int rc, ipv;


  addr_new = addr_new;         /* Keep picky compilers happy */
  addr_succeed = addr_succeed;
***************
*** 111,124 ****
  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. 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;
domain[len-1] = 0; /* temporarily */

! if (string_is_ip_address(domain+1, NULL) == 0)
    {
    domain[len-1] = ']';
    return DECLINE;
--- 112,131 ----
  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;
domain[len-1] = 0; /* temporarily */

! ip = domain + 1;
! 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))
    {
    domain[len-1] = ']';
    return DECLINE;
***************
*** 128,137 ****
  but if it is set, it should probably work. */


  if (verify_check_this_host(&(rblock->ignore_target_hosts), NULL, domain,
!       domain + 1, NULL) == OK)
    {
    DEBUG(D_route)
!       debug_printf("%s is in ignore_target_hosts\n", domain+1);
    addr->message = US"IP literal host explicitly ignored";
    domain[len-1] = ']';
    return DECLINE;
--- 135,144 ----
  but if it is set, it should probably work. */


  if (verify_check_this_host(&(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";
    domain[len-1] = ']';
    return DECLINE;
***************
*** 142,148 ****
  h = store_get(sizeof(host_item));


h->next = NULL;
! h->address = string_copy(domain+1);
h->port = PORT_NONE;
domain[len-1] = ']'; /* restore */
h->name = string_copy(domain);
--- 149,155 ----
h = store_get(sizeof(host_item));

h->next = NULL;
! h->address = string_copy(ip);
h->port = PORT_NONE;
domain[len-1] = ']'; /* restore */
h->name = string_copy(domain);