#At file:///home/frazer/bzr/mysql-5.1-telco-7.0/
2954 Frazer Clement 2009-07-29
Bug#46336 Backport IPv6 bug fixes to cluster-7.0
This patch contains the fixes to the mysql-5.4 bugs mentioned in
the bug report.
Passes MTR testcases and manual testing including replication.
Appears to resolve issues with skip_name_resolve.
Appears to resolve issues with V4MAPPED addresses in ACLs
Extra fix information available in bug report.
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-07-29 12:08:36 +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-07-29 12:08:36 +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-07-29 12:08:36 +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-07-29 12:08:36 +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-07-29 12:08:36 +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-07-29 12:08:36 +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-07-29 12:08:36 +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-07-29 12:08:36 +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:2954) Bug#46336 | Frazer Clement | 29 Jul |