List:Commits« Previous MessageNext Message »
From:Frazer Clement Date:August 5 2009 2:38pm
Subject:bzr commit into mysql-5.1-telco-7.0 branch (frazer:2961) Bug#46336
View as plain text  
#At file:///home/frazer/bzr/mysql-5.1-telco-7.0/

 2961 Frazer Clement	2009-08-05
      Bug#46336 Backport IPv6 bug fixes to cluster 7.0
      modified:
        configure.in
        include/config-win.h
        scripts/mysql_system_tables_data.sql
        sql/hostname.cc
        sql/mysql_priv.h
        sql/sql_acl.cc
        sql/sql_connect.cc
        vio/viosocket.c

=== modified file 'configure.in'
--- a/configure.in	2009-06-15 07:27:14 +0000
+++ b/configure.in	2009-08-05 14:37:58 +0000
@@ -878,6 +878,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-15 07:27:14 +0000
+++ b/include/config-win.h	2009-08-05 14:37:58 +0000
@@ -21,8 +21,8 @@
 #define BIG_TABLES
 
 /* We have to do this define before including windows.h to get the
-   AWE API functions.  this #define means we target W2K (NT5) and newer */
-#define _WIN32_WINNT     0x0500
+   AWE API functions.  this #define means we target Windows XP and newer */
+#define _WIN32_WINNT     0x0501
 
 #if defined(_MSC_VER) && _MSC_VER >= 1400
 /* Avoid endless warnings about sprintf() etc. being unsafe. */

=== modified file 'scripts/mysql_system_tables_data.sql'
--- a/scripts/mysql_system_tables_data.sql	2008-10-03 15:54:22 +0000
+++ b/scripts/mysql_system_tables_data.sql	2009-08-05 14:37:58 +0000
@@ -24,6 +24,7 @@ set @current_hostname= @@hostname;
 INSERT INTO tmp_user VALUES ('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
 REPLACE INTO tmp_user SELECT @current_hostname,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0 FROM dual WHERE LOWER( @current_hostname) != 'localhost';
 REPLACE INTO tmp_user VALUES ('127.0.0.1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
+REPLACE INTO tmp_user VALUES ('::1','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
 INSERT INTO tmp_user (host,user) VALUES ('localhost','');
 INSERT INTO tmp_user (host,user) SELECT @current_hostname,'' FROM dual WHERE LOWER(@current_hostname ) != 'localhost';
 INSERT INTO user SELECT * FROM tmp_user WHERE @had_user_table=0;

=== modified file 'sql/hostname.cc'
--- a/sql/hostname.cc	2009-05-27 15:21:45 +0000
+++ b/sql/hostname.cc	2009-08-05 14:37:58 +0000
@@ -23,8 +23,8 @@
   @brief
   Get hostname for an IP.
 
-    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"
@@ -43,13 +43,229 @@ extern "C" {					// Because of SCO 3.2V4
 }
 #endif
 
+#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);
+    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.
 
-class host_entry :public hash_filo_element
+  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;
@@ -62,13 +278,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,
-				     (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();
   (void) pthread_mutex_init(&LOCK_hostname,MY_MUTEX_INIT_SLOW);
   return 0;
@@ -76,150 +294,357 @@ bool hostname_cache_init()
 
 void hostname_cache_free()
 {
-  if (hostname_cache)
-  {
-    (void) pthread_mutex_destroy(&LOCK_hostname);
-    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)
   {
-    VOID(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);
+    DBUG_PRINT("info", ("Adding '%s' -> '%s' to the hostname cache...'",
+                        (const char *) ip,
+                        (const char *) hostname_copy));
+  }
+  else
+  {
+    hostname_copy= NULL;
 
-      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);
-      }
-    }
-    VOID(pthread_mutex_unlock(&hostname_cache->lock));
+    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;
+
   VOID(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++;
+
   VOID(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;
+
   VOID(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;
+
   VOID(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)
+{
+  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;
+    }
+
+#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;
+      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 */
+
+  default:
+    DBUG_ASSERT(FALSE);
+    return FALSE;
+  }
+}
+
+static inline bool is_hostname_valid(const char *hostname)
 {
-  char *name= NULL;
+  /*
+    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;
 
-  struct addrinfo hints,*res_lst= NULL,*t_res;
-  int gxi_error;
-  char hostname_buff[NI_MAXHOST];
+  const char *p= hostname + 1;
 
-  host_entry *entry;
+  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");
-  *errors=0;
 
-  /* 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);
-  }
-  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")))
+  /* 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);
   }
+
+  DBUG_PRINT("info", ("IP address: '%s'.", (const char *) ip_string));
+
+  /* Check first if we have host name in the cache. */
   
-  /* Check first if we have name in cache */
   if (!(specialflag & SPECIAL_NO_HOST_CACHE))
   {
     VOID(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;
       VOID(pthread_mutex_unlock(&hostname_cache->lock));
-      DBUG_RETURN(name);
+
+      DBUG_RETURN(FALSE);
     }
+
     VOID(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 == EAI_NONAME)
   {
-    DBUG_PRINT("error",("out of memory"));
-    DBUG_RETURN(0);
+    /*
+      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", ("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);
 
-  /* Don't accept hostnames that starts with digits because they may be
-     false ip:s */
-  if (my_isdigit(&my_charset_latin1,name[0]))
+    DBUG_RETURN(TRUE);
+  }
+
+  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);
   }
-  DBUG_PRINT("info",("resolved: %s",name));
-  
-  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 != 0)
+  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 DNS server is down, as otherwise
@@ -227,38 +652,100 @@ char * ip_to_hostname(struct sockaddr_st
       that attempted to connect during the outage) unable to connect
       indefinitely.
     */
-    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);
+    bool err_status= add_hostname(ip_string, NULL);
 
-    if (res_lst)
-      freeaddrinfo(res_lst);
+    *hostname= NULL;
+    *connect_errors= 0; /* New IP added to the cache. */
 
-    my_free(name, MYF(0));
-    DBUG_RETURN(0);
+    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);
     }
   }
   
-  freeaddrinfo(res_lst);
-  DBUG_PRINT("error",("Couldn't verify hostname with getaddrinfo"));
+  /* Free the result of getaddrinfo(). */
+
+  freeaddrinfo(addr_info_list);
+
+  /* Add an entry for the IP to the cache. */
+
+  bool err_status;
 
-add_wrong_ip_and_return:
-  my_free(name,MYF(0));
-  add_wrong_ip(in);
-  DBUG_RETURN(0);
+  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;
+  }
+  
+  DBUG_RETURN(err_status);
 }

=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h	2009-06-15 07:27:14 +0000
+++ b/sql/mysql_priv.h	2009-08-05 14:37:58 +0000
@@ -2303,8 +2303,8 @@ uint build_table_shadow_filename(char *b
 #define FRM_ONLY        (1 << 3)
 
 /* from hostname.cc */
-struct in_addr;
-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_acl.cc'
--- a/sql/sql_acl.cc	2009-06-15 05:46:54 +0000
+++ b/sql/sql_acl.cc	2009-08-05 14:37:58 +0000
@@ -1780,24 +1780,83 @@ static bool compare_hostname(const acl_h
 	  (ip && !wild_compare(ip, host->hostname, 0)));
 }
 
+/**
+  Check if the given host name needs to be resolved or not.
+  Host name has to be resolved if it actually contains *name*.
+
+  For example:
+    192.168.1.1               --> FALSE
+    192.168.1.0/255.255.255.0 --> FALSE
+    %                         --> FALSE
+    192.168.1.%               --> FALSE
+    AB%                       --> FALSE
+
+    AAAAFFFF                  --> TRUE (Hostname)
+    AAAA:FFFF:1234:5678       --> FALSE
+    ::1                       --> FALSE
+
+  This function does not check if the given string is a valid host name or
+  not. It assumes that the argument is a valid host name.
+
+  @param hostname   the string to check.
+
+  @return a flag telling if the argument needs to be resolved or not.
+  @retval TRUE the argument is a host name and needs to be resolved.
+  @retval FALSE the argument is either an IP address, or a patter and
+          should not be resolved.
+*/
+
 bool hostname_requires_resolving(const char *hostname)
 {
-  char cur;
   if (!hostname)
     return FALSE;
-  size_t namelen= strlen(hostname);
-  size_t lhlen= strlen(my_localhost);
-  if ((namelen == lhlen) &&
-      !my_strnncoll(system_charset_info, (const uchar *)hostname,  namelen,
-		    (const uchar *)my_localhost, strlen(my_localhost)))
+
+  /* Check if hostname is the localhost. */
+
+  size_t hostname_len= strlen(hostname);
+  size_t localhost_len= strlen(my_localhost);
+
+  if (hostname == my_localhost ||
+      hostname_len == localhost_len &&
+      !my_strnncoll(system_charset_info,
+                    (const uchar *) hostname,  hostname_len,
+		    (const uchar *) my_localhost, strlen(my_localhost)))
+  {
     return FALSE;
-  for (; (cur=*hostname); hostname++)
+  }
+
+  /*
+    If the string contains any of {':', '%', '_', '/'}, it is definitely
+    not a host name:
+      - ':' means that the string is an IPv6 address;
+      - '%' or '_' means that the string is a pattern;
+      - '/' means that the string is an IPv4 network address;
+  */
+
+  for (const char *p= hostname; *p; ++p)
   {
-    if ((cur != '%') && (cur != '_') && (cur != '.') && (cur != '/') &&
-	((cur < '0') || (cur > '9')))
-      return TRUE;
+    switch (*p) {
+      case ':':
+      case '%':
+      case '_':
+      case '/':
+        return FALSE;
+    }
   }
-  return FALSE;
+
+  /*
+    Now we have to tell a host name (ab.cd, 12.ab) from an IPv4 address
+    (12.34.56.78). The assumption is that if the string contains only
+    digits and dots, it is an IPv4 address. Otherwise -- a host name.
+  */
+
+  for (const char *p= hostname; *p; ++p)
+  {
+    if (*p != '.' && !my_isdigit(&my_charset_latin1, *p))
+      return TRUE; /* a "letter" has been found. */
+  }
+
+  return FALSE; /* all characters are either dots or digits. */
 }
 
 

=== modified file 'sql/sql_connect.cc'
--- a/sql/sql_connect.cc	2009-05-27 15:21:45 +0000
+++ b/sql/sql_connect.cc	2009-08-05 14:37:58 +0000
@@ -665,8 +665,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)
       {

=== modified file 'vio/viosocket.c'
--- a/vio/viosocket.c	2009-06-15 07:27:14 +0000
+++ b/vio/viosocket.c	2009-08-05 14:37:58 +0000
@@ -312,56 +312,100 @@ my_socket vio_fd(Vio* vio)
 }
 
 
-my_bool vio_peer_addr(Vio * vio, char *buf, uint16 *port, size_t buflen)
+my_bool vio_peer_addr(Vio * vio, char *ip_buffer, uint16 *port, 
+                      size_t ip_buffer_size)
 {
   DBUG_ENTER("vio_peer_addr");
-  DBUG_PRINT("enter", ("sd: " MY_SOCKET_FORMAT,
+  DBUG_PRINT("enter", ("Client socket fd: " MY_SOCKET_FORMAT,
                        MY_SOCKET_FORMAT_VALUE(vio->sd)));
   if (vio->localhost)
   {
-    strmov(buf,"127.0.0.1");
+    strmov(ip_buffer,"127.0.0.1");
     *port= 0;
   }
   else
   {
-    int error;
-    char port_buf[NI_MAXSERV];
-    size_socket addrLen = sizeof(vio->remote);
-    if (my_getpeername(vio->sd, (struct sockaddr *) (&vio->remote),
-                       &addrLen) != 0)
-    {
-      DBUG_PRINT("exit", ("getpeername gave error: %d", socket_errno));
-      DBUG_RETURN(1);
-    }
-    vio->addrLen= (int)addrLen;
+    int err_code;
+    char port_buffer[NI_MAXSERV];
     
-    if ((error= getnameinfo((struct sockaddr *)(&vio->remote), 
-                            addrLen,
-                            buf, buflen,
-                            port_buf, NI_MAXSERV, NI_NUMERICHOST|NI_NUMERICSERV)))
-    {
-      DBUG_PRINT("exit", ("getnameinfo gave error: %s", 
-                          gai_strerror(error)));
-      DBUG_RETURN(1);
+    struct sockaddr *addr= (struct sockaddr *) (&vio->remote);
+    size_socket addr_size = sizeof (vio->remote);
+
+    struct sockaddr *resolving_addr= addr;
+    uint resolving_addr_size= addr_size;
+
+#ifdef HAVE_STRUCT_IN6_ADDR
+    struct sockaddr_storage addr_storage;
+#endif
+
+    /* Get sockaddr by socked fd (fill vio->remote) */
+
+    err_code= my_getpeername(vio->sd, addr, &addr_size);
+
+    if (err_code)
+    {
+      DBUG_PRINT("exit", ("getpeername() gave error: %d", socket_errno));
+      DBUG_RETURN(TRUE);
     }
-    
-    *port= (uint16)strtol(port_buf, (char **)NULL, 10);
+
+    vio->addrLen= (int) addr_size;
 
     /*
-      A lot of users do not have IPv6 loopback resolving to localhost
-      correctly setup. Should this exist? No. If we do not do it though
-      we will be getting a lot of support questions from users who
-      have bad setups. This code should be removed by say... 2012.
-        -Brian
+      Convert IPv6 address to IPv4 if it is IPv4-mapped or IPv4-compatible.
     */
-    if (!memcmp(buf, "::ffff:127.0.0.1", sizeof("::ffff:127.0.0.1")))
-      strmov(buf, "127.0.0.1");
+
+#ifdef HAVE_STRUCT_IN6_ADDR
+    if (addr->sa_family == AF_INET6)
+    {
+      const struct sockaddr_in6 *addr6= (const struct sockaddr_in6 *) addr;
+      const struct in6_addr *ip6= &(addr6->sin6_addr);
+      const uint32 *ip6_int32= (uint32 *) ip6->s6_addr;
+
+      memset(&addr_storage, 0, sizeof (addr_storage));
+
+      if (IN6_IS_ADDR_V4MAPPED(ip6) || IN6_IS_ADDR_V4COMPAT(ip6))
+      {
+        struct sockaddr_in *ip4= (struct sockaddr_in *) &addr_storage;
+        ip4->sin_family= AF_INET;
+        ip4->sin_port= 0;
+
+        /*
+          In an IPv4 mapped or compatible address, the last 32 bits represent
+          the IPv4 address. The byte orders for IPv6 and IPv4 addresses are
+          the same, so a simple copy is possible.
+        */
+        ip4->sin_addr.s_addr= ip6_int32[3];
+
+        resolving_addr= (struct sockaddr *) ip4;
+        resolving_addr_size= sizeof (addr_storage);
+      }
+    }
+#endif /* HAVE_STRUCT_IN6_ADDR */
+
+    /* Get IP address & port number. */
+
+    err_code= getnameinfo(resolving_addr, resolving_addr_size,
+                          ip_buffer, ip_buffer_size,
+                          port_buffer, NI_MAXSERV,
+                          NI_NUMERICHOST | NI_NUMERICSERV);
+
+    if (err_code)
+    {
+      DBUG_PRINT("exit", ("getnameinfo() gave error: %s",
+                          gai_strerror(err_code)));
+      DBUG_RETURN(TRUE);
+    }
+
+    *port= (uint16) strtol(port_buffer, (char **) NULL, 10);
   }
-  DBUG_PRINT("exit", ("addr: %s", buf));
-  DBUG_RETURN(0);
-}
 
 
+  DBUG_PRINT("exit", ("Client IP address: %s; port: %d",
+                      (const char *) ip_buffer,
+                      (int) *port));
+  DBUG_RETURN(FALSE);
+}
+
 /* Return 0 if there is data to be read */
 
 my_bool vio_poll_read(Vio *vio,uint timeout)

Thread
bzr commit into mysql-5.1-telco-7.0 branch (frazer:2961) Bug#46336Frazer Clement5 Aug