MySQL Lists are EOL. Please join:

List:Commits« Previous MessageNext Message »
From:Alexander Nozdrin Date:July 28 2009 1:10pm
Subject:bzr commit into mysql-5.4 branch (alik:2804) Bug#45584
View as plain text  
#At file:///mnt/raid/alik/MySQL/bzr/bug38247/azalea-bf-bug45584/ based on revid:alik@stripped

 2804 Alexander Nozdrin	2009-07-28
      Fix for Bug#45584 (Host name cache does not work as a cache).
      
      The problem is described in the bug report.
      
      The solution is the following:
      
        - Make hostname cache key type of (char *);
      
        - Use string representation of normalized IPv6 addresses
          as hostname cache keys when IPv6 is supported.
      
          Use string representation of normalized IPv4 addresses
          as hostname cache keys when IPv6 is not supported.
      
        - Use only the host part of client address for hostname
          cache keys;
      
        - Actually resolve IP addresses to hostnames,
          not to IP strings;
      
        - Minimal supported Windows version has been changed to Windows XP.
          We don't support Windows 2000 for the newer versions any more.
      
          Windows XP has IPv6 support, so declaring it minimal supported
          version removes much of "windows portability hassle".
     @ configure.in
        HAVE_STRUCT_IN6_ADDR will be defined when IPv6 is supported.
     @ sql/mysql_priv.h
        Change the ip_to_hostname() prototype:
          - Make it more descriptive;
          - Provide a way to return error status;
     @ sql/sql_connect.cc
        Handle errors from ip_to_hostname().

    modified:
      configure.in
      include/config-win.h
      sql/hostname.cc
      sql/mysql_priv.h
      sql/sql_connect.cc
=== modified file 'configure.in'
--- a/configure.in	2009-06-17 07:30:19 +0000
+++ b/configure.in	2009-07-28 13:09:58 +0000
@@ -876,6 +876,24 @@ then
 fi
 
 #--------------------------------------------------------------------
+# Check for IPv6 support
+#--------------------------------------------------------------------
+
+AC_CHECK_HEADERS(netinet/in6.h)
+
+AC_CHECK_TYPES([struct in6_addr], [], [], 
+[[#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN6_H
+#include <netinet/in6.h>
+#endif]])
+
+#--------------------------------------------------------------------
 # Check for TCP wrapper support
 #--------------------------------------------------------------------
 

=== modified file 'include/config-win.h'
--- a/include/config-win.h	2009-06-17 07:30:19 +0000
+++ b/include/config-win.h	2009-07-28 13:09:58 +0000
@@ -17,11 +17,11 @@
 
 #define BIG_TABLES
 
-/* 
+/*
   Minimal version of Windows we should be able to run on.
-  Currently Windows 2000
+  Currently Windows XP.
 */
-#define _WIN32_WINNT     0x0500
+#define _WIN32_WINNT     0x0501
 
 
 #if defined(_MSC_VER) && _MSC_VER >= 1400

=== modified file 'sql/hostname.cc'
--- a/sql/hostname.cc	2009-01-27 02:08:48 +0000
+++ b/sql/hostname.cc	2009-07-28 13:09:58 +0000
@@ -18,10 +18,10 @@
   @file
 
   @brief
-  Get hostname for an IP.
+  Get hostname for an IP address.
 
-    Hostnames are checked with reverse name lookup and
-    checked that they doesn't resemble an ip.
+  Hostnames are checked with reverse name lookup and checked that they
+  doesn't resemble an IP address.
 */
 
 #include "mysql_priv.h"
@@ -40,12 +40,228 @@ extern "C" {					// Because of SCO 3.2V4
 }
 #endif
 
-class host_entry :public hash_filo_element
+#ifdef __WIN__
+#define HAVE_STRUCT_IN6_ADDR
+#endif /* __WIN__ */
+
+/*
+  HOST_ENTRY_KEY_SIZE -- size of IP address string in the hash cache. The
+  system constant NI_MAXHOST could be used here. However, it means at least
+  1024 bytes per IP, which seems to be quite big.
+
+  Since IP address string is created by our function get_ip_string(), we
+  can reduce the space.  get_ip_string() returns a hexadecimal
+  respresentation (1111:2222:...:8888) in case of IPv6 and the standard
+  representation (111.222.333.444) in case of IPv4, which means 39 bytes at
+  most. So, we need 40 bytes for storing IP address string including
+  trailing zero.
+*/
+
+#define HOST_ENTRY_KEY_SIZE 40
+
+/************************************************************************/
+
+/*
+  When this code was written there were issues with winsock in pusbuild,
+  this constant is in this place for this reason.
+*/
+
+#ifdef EAI_NODATA
+    const int MY_NONAME_ERR_CODE= EAI_NODATA;
+#else
+    const int MY_NONAME_ERR_CODE= EAI_NONAME;
+#endif
+
+/************************************************************************/
+
+/**
+  Get the string representation for IP address. IPv6 and IPv4 addresses are
+  supported. The function is needed because getnameinfo() is known to be
+  buggy in some circumstances. Actually, this is a basic replacement for
+  getnameinfo() called with the NI_NUMERICHOST flag. Only the hostname part is
+  dumped (the port part is omitted).
+*/
+
+static void get_ip_string(const struct sockaddr *ip,
+                          char *ip_str, int ip_str_size)
+{
+  switch (ip->sa_family) {
+  case AF_INET:
+  {
+    struct in_addr *ip4= &((struct sockaddr_in *) ip)->sin_addr;
+    uint32 ip4_int32= ntohl(ip4->s_addr); // XXX
+    uint8 *ip4_int8= (uint8 *) &ip4_int32;
+
+    int n= my_snprintf(ip_str, ip_str_size, "%d.%d.%d.%d",
+                       ip4_int8[0], ip4_int8[1], ip4_int8[2], ip4_int8[3]);
+
+    DBUG_ASSERT(n < ip_str_size);
+    break;
+  }
+
+#ifdef HAVE_STRUCT_IN6_ADDR
+  case AF_INET6:
+  {
+    struct in6_addr *ip6= &((struct sockaddr_in6 *) ip)->sin6_addr;
+    uint16 *ip6_int16= (uint16 *) ip6->s6_addr;
+
+    int n= my_snprintf(ip_str, ip_str_size,
+                       "%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X",
+                       ntohs(ip6_int16[0]), ntohs(ip6_int16[1]),
+                       ntohs(ip6_int16[2]), ntohs(ip6_int16[3]),
+                       ntohs(ip6_int16[4]), ntohs(ip6_int16[5]),
+                       ntohs(ip6_int16[6]), ntohs(ip6_int16[7]));
+
+    DBUG_ASSERT(n < ip_str_size);
+    break;
+  }
+#endif /* HAVE_STRUCT_IN6_ADDR */
+
+  default:
+    DBUG_ASSERT(FALSE);
+  }
+}
+
+/**
+  Get key value for IP address. Key value is a string representation of
+  normalized IP address.
+
+  When IPv4 and IPv6 are both used in the network stack, or in the network
+  path between a client and a server, a client can have different apparent
+  IP addresses, based on the exact route taken.
+
+  This function normalize the client IP representation, so it's suitable to
+  use as a key for searches.
+
+  Transformations are implemented as follows:
+  - IPv4 a.b.c.d --> IPv6 mapped IPv4 ::ffff:a.b.c.d
+  - IPv6 compat IPv4 ::a.b.c.d --> IPv6 mapped IPv4 ::ffff:a.b.c.d
+  - IPv6 --> IPv6
+
+  If IPv6 is not available at compile-time, IPv4 form is used.
+
+  @param [in]   ip      IP address
+  @param [out]  ip_key  Key for the given IP value
+
+  @note According to RFC3493 the only specified member of the in6_addr
+  structure is s6_addr.
+
+  @note It is possible to call hostname_cache_get_key() with zeroed IP
+  address (ip->sa_family == 0). In this case hostname_cache_get_key()
+  returns TRUE (error status).
+
+  @return Error status.
+  @retval FALSE Success
+  @retval TRUE Error
+*/
+
+static bool hostname_cache_get_key(const struct sockaddr *ip, char *ip_key)
+{
+  const struct sockaddr *ip_to_generate_key= ip;
+
+  if (ip->sa_family == 0)
+    return TRUE; /* IP address is not set. */
+
+#ifdef HAVE_STRUCT_IN6_ADDR
+  /* Prepare normalized IP address. */
+
+  struct sockaddr_storage norm_ip;
+  struct in6_addr *norm_ip6= &((sockaddr_in6 *) &norm_ip)->sin6_addr;
+  uint32 *norm_ip6_int32= (uint32 *) norm_ip6->s6_addr;
+
+  memset(&norm_ip, 0, sizeof (sockaddr_storage));
+
+  switch (ip->sa_family) {
+  case AF_INET:
+  {
+    struct in_addr *ip4= &((struct sockaddr_in *) ip)->sin_addr;
+
+    norm_ip6_int32[0]= 0;
+    norm_ip6_int32[1]= 0;
+    norm_ip6_int32[2]= htonl(0xffff);
+    norm_ip6_int32[3]= ip4->s_addr; /* in net byte order */
+
+    DBUG_ASSERT(IN6_IS_ADDR_V4MAPPED(norm_ip6));
+    break;
+  }
+
+  case AF_INET6:
+  {
+    struct in6_addr *ip6= &((struct sockaddr_in6 *) ip)->sin6_addr;
+    uint32 *ip6_int32= (uint32 *) ip6->s6_addr;
+
+    if (IN6_IS_ADDR_V4COMPAT(ip6))
+    {
+      norm_ip6_int32[0]= 0;
+      norm_ip6_int32[1]= 0;
+      norm_ip6_int32[2]= htonl(0xffff);
+      norm_ip6_int32[3]= ip6_int32[3]; /* in net byte order */
+      DBUG_ASSERT(IN6_IS_ADDR_V4MAPPED(norm_ip6));
+    }
+    else
+    {
+      /* All in net byte order: just copy 16 bytes. */
+      memcpy(norm_ip6_int32, ip6_int32, 4 * sizeof (uint32));
+    }
+
+    break;
+  }
+
+  default:
+    DBUG_ASSERT(FALSE);
+    break;
+  }
+
+  norm_ip.ss_family= AF_INET6;
+  ip_to_generate_key= (sockaddr *) &norm_ip;
+#endif /* HAVE_STRUCT_IN6_ADDR */
+
+  /*
+    Zero all bytes of the key, because it's not just 0-terminated string.
+    All bytes are taken into account during hash search.
+  */
+
+  memset(ip_key, 0, HOST_ENTRY_KEY_SIZE);
+
+  /* Get numeric representation of the normalized IP address. */
+  get_ip_string(ip_to_generate_key, ip_key, HOST_ENTRY_KEY_SIZE);
+
+  return FALSE;
+}
+
+/**
+  An entry in the hostname hash table cache.
+
+  Host name cache does two things:
+    - caches host names to save DNS look ups;
+    - counts connect errors from IP.
+
+  Host name can be NULL (that means DNS look up failed), but connect errors
+  still are counted.
+*/
+
+class Host_entry :public hash_filo_element
 {
 public:
-  char	 ip[sizeof(struct sockaddr_storage)];
-  uint	 errors;
-  char	 *hostname;
+  /**
+    Client IP address. This is the key used with the hash table.
+
+    The client IP address is always expressed in IPv6, even when the
+    network IPv6 stack is not present.
+
+    This IP address is never used to connect to a socket.
+  */
+  char ip[HOST_ENTRY_KEY_SIZE];
+
+  /**
+    Number of errors during handshake phase from the IP address.
+  */
+  uint connect_errors;
+
+  /**
+    One of host names for the IP address. May be NULL.
+  */
+  const char *hostname;
 };
 
 static hash_filo *hostname_cache;
@@ -57,13 +273,15 @@ void hostname_cache_refresh()
 
 bool hostname_cache_init()
 {
-  host_entry tmp;
-  uint offset= (uint) ((char*) (&tmp.ip) - (char*) &tmp);
-  if (!(hostname_cache=new hash_filo(HOST_CACHE_SIZE, offset,
-				     sizeof(struct sockaddr_storage),NULL,
-				     (my_hash_free_key) free,
-				     &my_charset_bin)))
+  Host_entry tmp;
+  uint key_offset= (uint) ((char*) (&tmp.ip) - (char*) &tmp);
+
+  if (!(hostname_cache= new hash_filo(HOST_CACHE_SIZE,
+                                      key_offset, HOST_ENTRY_KEY_SIZE,
+                                      NULL, (my_hash_free_key) free,
+                                      &my_charset_bin)))
     return 1;
+
   hostname_cache->clear();
 
   return 0;
@@ -71,190 +289,461 @@ bool hostname_cache_init()
 
 void hostname_cache_free()
 {
-  if (hostname_cache)
-  {
-    delete hostname_cache;
-    hostname_cache= 0;
-  }
+  delete hostname_cache;
+  hostname_cache= NULL;
 }
 
-static void add_hostname(struct sockaddr_storage *in, const char *name)
+static inline Host_entry *hostname_cache_search(const char *ip)
 {
-  if (!(specialflag & SPECIAL_NO_HOST_CACHE))
+  return (Host_entry *) hostname_cache->search((uchar *) ip, 0);
+}
+
+static bool add_hostname_impl(const char *ip, const char *hostname)
+{
+  if (hostname_cache_search(ip))
+    return FALSE;
+
+  uint hostname_length= hostname ? (uint) strlen(hostname) : 0;
+
+  Host_entry *entry= (Host_entry *) malloc(sizeof (Host_entry) +
+                                           hostname_length + 1);
+
+  if (!entry)
+    return TRUE;
+
+  char *hostname_copy;
+
+  memcpy_fixed(&entry->ip, ip, HOST_ENTRY_KEY_SIZE);
+
+  if (hostname_length)
   {
-    pthread_mutex_lock(&hostname_cache->lock);
-    host_entry *entry;
-    if (!(entry=(host_entry*) hostname_cache->search((uchar*) in, 0)))
-    {
-      uint length=name ? (uint) strlen(name) : 0;
+    hostname_copy= (char *) (entry + 1);
+    memcpy(hostname_copy, hostname, hostname_length + 1);
 
-      if ((entry=(host_entry*) malloc(sizeof(host_entry)+length+1)))
-      {
-	char *new_name;
-	memcpy_fixed(&entry->ip, in, sizeof(struct sockaddr_storage));
-	if (length)
-	  memcpy(new_name= (char *) (entry+1), name, length+1);
-	else
-	  new_name=0;
-	entry->hostname=new_name;
-	entry->errors=0;
-	(void) hostname_cache->add(entry);
-      }
-    }
-    pthread_mutex_unlock(&hostname_cache->lock);
+    DBUG_PRINT("info", ("Adding '%s' -> '%s' to the hostname cache...'",
+                        (const char *) ip,
+                        (const char *) hostname_copy));
   }
+  else
+  {
+    hostname_copy= NULL;
+
+    DBUG_PRINT("info", ("Adding '%s' -> NULL to the hostname cache...'",
+                        (const char *) ip));
+  }
+
+  entry->hostname= hostname_copy;
+  entry->connect_errors= 0;
+
+  return hostname_cache->add(entry);
 }
 
-inline void add_wrong_ip(struct sockaddr_storage *in)
+static bool add_hostname(const char *ip, const char *hostname)
 {
-  add_hostname(in, NullS);
+  if (specialflag & SPECIAL_NO_HOST_CACHE)
+    return FALSE;
+
+  pthread_mutex_lock(&hostname_cache->lock);
+
+  bool err_status= add_hostname_impl(ip, hostname);
+
+  pthread_mutex_unlock(&hostname_cache->lock);
+
+  return err_status;
 }
 
-void inc_host_errors(struct sockaddr_storage *in)
+void inc_host_errors(struct sockaddr_storage *ip)
 {
+  char key[HOST_ENTRY_KEY_SIZE];
+
+  if (hostname_cache_get_key((struct sockaddr *) ip, key))
+    return;
+
   pthread_mutex_lock(&hostname_cache->lock);
-  host_entry *entry;
 
-  if ((entry=(host_entry*) hostname_cache->search((uchar*)in, 0)))
-    entry->errors++;
+  Host_entry *entry= hostname_cache_search(key);
+
+  if (entry)
+    entry->connect_errors++;
 
   pthread_mutex_unlock(&hostname_cache->lock);
 }
 
 
-void reset_host_errors(struct sockaddr_storage *in)
+void reset_host_errors(struct sockaddr_storage *ip)
 {
+  char key[HOST_ENTRY_KEY_SIZE];
+
+  if (hostname_cache_get_key((struct sockaddr *) ip, key))
+    return;
+
   pthread_mutex_lock(&hostname_cache->lock);
-  host_entry *entry;
 
-  if ((entry=(host_entry*) hostname_cache->search((uchar*)in, 0)))
-    entry->errors=0;
+  Host_entry *entry= hostname_cache_search(key);
+
+  if (entry)
+    entry->connect_errors= 0;
 
   pthread_mutex_unlock(&hostname_cache->lock);
 }
 
 
-char *ip_to_hostname(struct sockaddr_storage *in, int addrLen, uint *errors)
+static inline bool is_ip_loopback(const struct sockaddr *ip)
 {
-  char *name;
+  switch (ip->sa_family) {
+  case AF_INET:
+    {
+      /* Check for IPv4 127.0.0.1. */
+      struct in_addr *ip4= &((struct sockaddr_in *) ip)->sin_addr;
+      return ip4->s_addr == INADDR_LOOPBACK;
+    }
 
-  struct addrinfo hints,*res_lst,*t_res;
-  int gxi_error;
-  char hostname_buff[NI_MAXHOST];
+#ifdef HAVE_STRUCT_IN6_ADDR
+  case AF_INET6:
+    {
+      /*
+        Check if we have loopback here:
+          - IPv6 loopback             (::1)
+          - IPv4-compatible 127.0.0.1 (0:0:0:0:0:0000:7f00:0001)
+          - IPv4-mapped 127.0.0.1     (0:0:0:0:0:ffff:7f00:0001)
+      */
+      struct in6_addr *ip6= &((struct sockaddr_in6 *) ip)->sin6_addr;
 
-  host_entry *entry;
-  DBUG_ENTER("ip_to_hostname");
-  *errors=0;
+      if (IN6_IS_ADDR_V4COMPAT(ip6) || IN6_IS_ADDR_V4MAPPED(ip6))
+      {
+        uint32 *ip6_int32= (uint32 *) ip6->s6_addr;
+        return ntohl(ip6_int32[3]) == INADDR_LOOPBACK;
+      }
+      else
+      {
+        return IN6_IS_ADDR_LOOPBACK(ip6);
+      }
+    }
+#endif /* HAVE_STRUCT_IN6_ADDR */
 
-  /* Historical comparison for 127.0.0.1 */
-  gxi_error= getnameinfo((struct sockaddr *)in, addrLen,
-                         hostname_buff, NI_MAXHOST,
-                         NULL, 0, NI_NUMERICHOST);
-  if (gxi_error)
-  {
-    DBUG_PRINT("error",("getnameinfo returned %d", gxi_error));
-    DBUG_RETURN(0);
+  default:
+    DBUG_ASSERT(FALSE);
+    return FALSE;
   }
-  DBUG_PRINT("info",("resolved: %s", hostname_buff));
+}
 
-  /* The next three compares are to solve historical solutions with localhost */
-  if (!memcmp(hostname_buff, "127.0.0.1", sizeof("127.0.0.1")))
-  {
-    DBUG_RETURN((char *)my_localhost);
-  }
-  if (!memcmp(hostname_buff, "::ffff:127.0.0.1", sizeof("::ffff:127.0.0.1")))
+static inline bool is_hostname_valid(const char *hostname)
+{
+  /*
+    A hostname is invalid if it starts with a number followed by a dot
+    (IPv4 address).
+  */
+
+  if (!my_isdigit(&my_charset_latin1, hostname[0]))
+    return TRUE;
+
+  const char *p= hostname + 1;
+
+  while (my_isdigit(&my_charset_latin1, *p))
+    ++p;
+
+  return *p != '.';
+}
+
+/**
+  Resolve IP-address to host name.
+
+  This function does the following things:
+    - resolves IP-address;
+    - employs Forward Confirmed Reverse DNS technique to validate IP-address;
+    - returns host name if IP-address is validated;
+    - set value to out-variable connect_errors -- this variable represents the
+      number of connection errors from the specified IP-address.
+
+  NOTE: connect_errors are counted (are supported) only for the clients
+  where IP-address can be resolved and FCrDNS check is passed.
+
+  @param [in]  IP address. Must be set.
+  @param [out] hostname
+  @param [out] connect_errors
+
+  @return Error status
+  @retval FALSE Success
+  @retval TRUE Error
+
+  The function does not set/report MySQL server error in case of failure.
+  It's caller's responsibility to handle failures of this function
+  properly.
+*/
+
+bool ip_to_hostname(struct sockaddr_storage *ip_storage,
+                    char **hostname, uint *connect_errors)
+{
+  const struct sockaddr *ip= (const sockaddr *) ip_storage;
+  char ip_string[HOST_ENTRY_KEY_SIZE];
+  int err_code;
+
+  DBUG_ENTER("ip_to_hostname");
+
+  /* IP address must be set properly. */
+
+  DBUG_ASSERT(ip_storage->ss_family == AF_INET ||
+              ip_storage->ss_family == AF_INET6);
+
+  /* Check if we have loopback address (127.0.0.1 or ::1). */
+
+  if (is_ip_loopback(ip))
   {
-    DBUG_RETURN((char *)my_localhost);
+    DBUG_PRINT("info", ("Loopback address detected."));
+
+    *connect_errors= 0; /* Do not count connect errors from localhost. */
+    *hostname= (char *) my_localhost;
+
+    DBUG_RETURN(FALSE);
   }
-  if (!memcmp(hostname_buff, "::1", sizeof("::1")))
+
+  /* Get hostname cache key for the IP address. */
+
   {
-    DBUG_RETURN((char *)my_localhost);
+    bool err_status= hostname_cache_get_key(ip, ip_string);
+    DBUG_ASSERT(!err_status);
   }
 
-  /* Check first if we have name in cache */
+  DBUG_PRINT("info", ("IP address: '%s'.", (const char *) ip_string));
+
+  /* Check first if we have host name in the cache. */
+
   if (!(specialflag & SPECIAL_NO_HOST_CACHE))
   {
     pthread_mutex_lock(&hostname_cache->lock);
-    if ((entry= (host_entry*)hostname_cache->search((uchar *)&in, 0)))
+
+    Host_entry *entry= hostname_cache_search(ip_string);
+
+    if (entry)
     {
+      *connect_errors= entry->connect_errors;
+      *hostname= NULL;
+
       if (entry->hostname)
-        name= my_strdup(entry->hostname, MYF(0));
-      else
-        name= NULL;
+        *hostname= my_strdup(entry->hostname, MYF(0));
+
+      DBUG_PRINT("info",("IP (%s) has been found in the cache. "
+                         "Hostname: '%s'; connect_errors: %d",
+                         (const char *) ip_string,
+                         (const char *) (*hostname? *hostname : "null"),
+                         (int) *connect_errors));
 
-      DBUG_PRINT("info",("cached data %s", name ? name : "null" ));
-      *errors= entry->errors;
       pthread_mutex_unlock(&hostname_cache->lock);
-      DBUG_RETURN(name);
+
+      DBUG_RETURN(FALSE);
     }
+
     pthread_mutex_unlock(&hostname_cache->lock);
   }
 
-  if (!(name= my_strdup(hostname_buff, MYF(0))))
+  /*
+    Resolve host name. Return an error if a host name can not be resolved
+    (instead of returning the numeric form of the host name).
+  */
+
+  char hostname_buffer[NI_MAXHOST];
+
+  DBUG_PRINT("info", ("Resolving '%s'...", (const char *) ip_string));
+
+  err_code= getnameinfo(ip, sizeof (sockaddr_storage),
+                        hostname_buffer, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
+
+  if (err_code == MY_NONAME_ERR_CODE)
+  {
+    /*
+      There is no reverse address mapping for the IP address. A host name
+      can not be resolved.
+    */
+
+    DBUG_PRINT("error", ("IP address '%s' could not be resolved: "
+                         "no reverse address mapping.",
+                         (const char *) ip_string));
+
+    sql_print_warning("IP address '%s' could not be resolved: "
+                      "no reverse address mapping.",
+                      (const char *) ip_string);
+
+    bool err_status= add_hostname(ip_string, NULL);
+
+    *hostname= NULL;
+    *connect_errors= 0; /* New IP added to the cache. */
+
+    DBUG_RETURN(err_status);
+  }
+  else if (err_code)
   {
-    DBUG_PRINT("error",("out of memory"));
-    DBUG_RETURN(0);
+    DBUG_PRINT("error", ("IP address '%s' could not be resolved: "
+                         "getnameinfo() returned %d.",
+                         (const char *) ip_string,
+                         (int) err_code));
+
+    sql_print_warning("IP address '%s' could not be resolved: "
+                      "getnameinfo() returned error (code: %d).",
+                      (const char *) ip_string,
+                      (int) err_code);
+
+    DBUG_RETURN(TRUE);
   }
 
-  /* Don't accept hostnames that starts with digits because they may be
-    false ip:s */
-  if (my_isdigit(&my_charset_latin1, name[0]))
+  DBUG_PRINT("info", ("IP '%s' resolved to '%s'.",
+                      (const char *) ip_string,
+                      (const char *) hostname_buffer));
+
+  /*
+    Validate hostname: the server does not accept host names, which
+    resemble IP addresses.
+
+    The thing is that theoretically, a host name can be in a form of IPv4
+    address (123.example.org, or 1.2 or even 1.2.3.4). We have to deny such
+    host names because ACL-systems is not designed to work with them.
+
+    For exmaple, it is possible to specify a host name mask (like
+    192.168.1.%) for an ACL rule. Then, if IPv4-like hostnames are allowed,
+    there is a security hole: instead of allowing access for
+    192.168.1.0/255 network (which was assumed by the user), the access
+    will be allowed for host names like 192.168.1.example.org.
+  */
+
+  if (!is_hostname_valid(hostname_buffer))
   {
-    char *pos;
-    for (pos= name+1 ; my_isdigit(&my_charset_latin1, *pos); pos++) ;
-    if (*pos == '.')
-    {
-      DBUG_PRINT("error",("mysqld doesn't accept hostnames that starts with a number followed by a '.'"));
-      goto add_wrong_ip_and_return;
-    }
+    DBUG_PRINT("error", ("IP address '%s' has been resolved "
+                         "to the host name '%s', which resembles "
+                         "IPv4-address itself.",
+                         (const char *) ip_string,
+                         (const char *) hostname_buffer));
+
+    sql_print_warning("IP address '%s' has been resolved "
+                      "to the host name '%s', which resembles "
+                      "IPv4-address itself.",
+                      (const char *) ip_string,
+                      (const char *) hostname_buffer);
+
+    bool err_status= add_hostname(ip_string, NULL);
+
+    *hostname= NULL;
+    *connect_errors= 0; /* New IP added to the cache. */
+
+    DBUG_RETURN(err_status);
   }
 
-  bzero(&hints, sizeof (struct addrinfo));
+  /* Get IP-addresses for the resolved host name (FCrDNS technique). */
+
+  struct addrinfo hints;
+  struct addrinfo *addr_info_list;
+
+  memset(&hints, 0, sizeof (struct addrinfo));
   hints.ai_flags= AI_PASSIVE;
-  hints.ai_socktype= SOCK_STREAM;  
+  hints.ai_socktype= SOCK_STREAM;
   hints.ai_family= AF_UNSPEC;
 
-  gxi_error= getaddrinfo(hostname_buff, NULL, &hints, &res_lst);
-  if (gxi_error)
+  DBUG_PRINT("info", ("Getting IP addresses for hostname '%s'...",
+                      (const char *) hostname_buffer));
+
+  err_code= getaddrinfo(hostname_buffer, NULL, &hints, &addr_info_list);
+
+  if (err_code == MY_NONAME_ERR_CODE)
   {
     /*
-      Don't cache responses when the DSN server is down, as otherwise
+      Don't cache responses when the DNS server is down, as otherwise
       transient DNS failure may leave any number of clients (those
       that attempted to connect during the outage) unable to connect
       indefinitely.
     */
-    /* 
-      When this code was written there were issues with winsock in pusbuild, 
-      this define is in this place for this reason.
-    */
-    DBUG_PRINT("error",("getaddrinfo returned %d", gxi_error));
-#ifdef EAI_NODATA
-    if (gxi_error == EAI_NODATA )
-#else
-    if (gxi_error == EAI_NONAME )
-#endif
-      add_wrong_ip(in);
 
-    my_free(name,MYF(0));
-    DBUG_RETURN(0);
+    bool err_status= add_hostname(ip_string, NULL);
+
+    *hostname= NULL;
+    *connect_errors= 0; /* New IP added to the cache. */
+
+    DBUG_RETURN(err_status);
+  }
+  else if (err_code)
+  {
+    DBUG_PRINT("error", ("getaddrinfo() failed with error code %d.", err_code));
+    DBUG_RETURN(TRUE);
   }
 
-  /* Check that 'getaddrinfo' returned the used ip */
-  for (t_res= res_lst; t_res; t_res=t_res->ai_next)
+  /* Check that getaddrinfo() returned the used IP (FCrDNS technique). */
+
+  DBUG_PRINT("info", ("The following IP addresses found for '%s':",
+                      (const char *) hostname_buffer));
+
+  for (struct addrinfo *addr_info= addr_info_list;
+       addr_info; addr_info= addr_info->ai_next)
+
   {
-    if (!memcmp(&(t_res->ai_addr), in,
-                sizeof(struct sockaddr_storage) ) )
+    struct sockaddr *resolved_ip= addr_info->ai_addr;
+    char resolved_ip_key[HOST_ENTRY_KEY_SIZE];
+
     {
-      add_hostname(in, name);
-      freeaddrinfo(res_lst);
-      DBUG_RETURN(name);
+      bool err_status= hostname_cache_get_key(resolved_ip, resolved_ip_key);
+      DBUG_ASSERT(!err_status);
     }
+
+    DBUG_PRINT("info", ("  - '%s'", (const char *) resolved_ip_key));
+
+    if (strcmp(ip_string, resolved_ip_key) == 0)
+    {
+      /* Copy host name string to be stored in the cache. */
+
+      *hostname= my_strdup(hostname_buffer, MYF(0));
+
+      if (!*hostname)
+      {
+        DBUG_PRINT("error", ("Out of memory."));
+
+        freeaddrinfo(addr_info_list);
+        DBUG_RETURN(TRUE);
+      }
+
+      break;
+    }
+  }
+
+  /* Log resolved IP-addresses if no match was found. */
+
+  if (!*hostname)
+  {
+    sql_print_information("Hostname '%s' does not resolve to '%s'.",
+                          (const char *) hostname_buffer,
+                          (const char *) ip_string);
+    sql_print_information("Hostname '%s' has the following IP addresses:",
+                          (const char *) hostname_buffer);
+
+    for (struct addrinfo *addr_info= addr_info_list;
+         addr_info; addr_info= addr_info->ai_next)
+
+    {
+      struct sockaddr *resolved_ip= addr_info->ai_addr;
+      char resolved_ip_key[HOST_ENTRY_KEY_SIZE];
+
+      hostname_cache_get_key(resolved_ip, resolved_ip_key);
+
+      sql_print_information(" - %s\n", (const char *) resolved_ip_key);
+    }
+  }
+
+  /* Free the result of getaddrinfo(). */
+
+  freeaddrinfo(addr_info_list);
+
+  /* Add an entry for the IP to the cache. */
+
+  bool err_status;
+
+  if (*hostname)
+  {
+    err_status= add_hostname(ip_string, *hostname);
+    *connect_errors= 0;
+  }
+  else
+  {
+    DBUG_PRINT("error",("Couldn't verify hostname with getaddrinfo()."));
+
+    err_status= add_hostname(ip_string, NULL);
+    *hostname= NULL;
+    *connect_errors= 0;
   }
-  freeaddrinfo(res_lst);
-  DBUG_PRINT("error",("Couldn't verify hostname with getaddrinfo"));
 
-add_wrong_ip_and_return:
-  my_free(name,MYF(0));
-  add_wrong_ip(in);
-  DBUG_RETURN(0);
+  DBUG_RETURN(err_status);
 }

=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h	2009-06-17 07:30:19 +0000
+++ b/sql/mysql_priv.h	2009-07-28 13:09:58 +0000
@@ -2277,7 +2277,8 @@ uint build_table_shadow_filename(char *b
 #define FRM_ONLY        (1 << 3)
 
 /* from hostname.cc */
-char *ip_to_hostname(struct sockaddr_storage *in, int addrLen, uint *errors);
+bool ip_to_hostname(struct sockaddr_storage *ip_storage,
+                    char **hostname, uint *connect_errors);
 void inc_host_errors(struct sockaddr_storage *in);
 void reset_host_errors(struct sockaddr_storage *in);
 bool hostname_cache_init();

=== modified file 'sql/sql_connect.cc'
--- a/sql/sql_connect.cc	2009-06-09 14:36:52 +0000
+++ b/sql/sql_connect.cc	2009-07-28 13:09:58 +0000
@@ -679,8 +679,13 @@ static int check_connection(THD *thd)
     thd->main_security_ctx.host_or_ip= thd->main_security_ctx.ip;
     if (!(specialflag & SPECIAL_NO_RESOLVE))
     {
-      thd->main_security_ctx.host=
-        ip_to_hostname(&net->vio->remote, net->vio->addrLen, &connect_errors);
+      if (ip_to_hostname(&net->vio->remote,
+                         &thd->main_security_ctx.host, &connect_errors))
+      {
+        my_error(ER_BAD_HOST_ERROR, MYF(0), ip);
+        return 1;
+      }
+
       /* Cut very long hostnames to avoid possible overflows */
       if (thd->main_security_ctx.host)
       {


Attachment: [text/bzr-bundle] bzr/alik@sun.com-20090728130958-k1d0ueph89kzy0k2.bundle
Thread
bzr commit into mysql-5.4 branch (alik:2804) Bug#45584Alexander Nozdrin28 Jul
  • Re: bzr commit into mysql-5.4 branch (alik:2804) Bug#45584Frazer Clement28 Jul
  • Re: bzr commit into mysql-5.4 branch (alik:2804) Bug#45584Frazer Clement29 Jul
  • Re: bzr commit into mysql-5.4 branch (alik:2804) Bug#45584Davi Arnaut1 Aug