diff options
author | Alexander Nozdrin <alik@sun.com> | 2009-11-25 13:53:23 +0300 |
---|---|---|
committer | Alexander Nozdrin <alik@sun.com> | 2009-11-25 13:53:23 +0300 |
commit | 7eb84da890698c3109795742c6f34d4580567b17 (patch) | |
tree | e44eeb900770af5a8a6c8712d985329fd1d7ba44 /sql | |
parent | 027a2fff1573550baae84ff97b962169a4961518 (diff) | |
download | mariadb-git-7eb84da890698c3109795742c6f34d4580567b17.tar.gz |
Backport of WL#798 (MySQL IPv6 support) from 6.0.
The following 6.0 revisions were analyzed:
- sp1r-brian@zim.(none)-20071228102738-21894
- sp1r-brian@zim.(none)-20071228121841-56447
- sp1r-brian@zim.(none)-20071228205403-56423
- sp1r-brian@zim.(none)-20071228221139-55341
- sp1r-brian@zim.(none)-20071228233443-55352
- sp1r-brian@zim.(none)-20071229094527-61763
- sp1r-brian@zim.(none)-20071230203739-61746
- sp1r-brian@zim.(none)-20080102213805-61741
- sp1r-brian@zim.(none)-20080103201041-61746
- sp1r-brian@zim.(none)-20080104234927-59875
- sp1r-brian@zim.(none)-20080105005827-59874
- sp1r-brian@zim.(none)-20080105012020-59865
- sp1r-brian@zim.(none)-20080106003858-59857
- sp1r-brian@zim.(none)-20080123195552-31680
- sp1r-brian@zim.(none)-20080124201848-29999
- sp1r-brian@zim.(none)-20080129075144-36991
- sp1r-cbell/Chuck@mysql_cab_desk.-20080215041806-21954
- sp1r-vvaintroub@wva.-20080212124547-06272
- sp1r-dfischer/mysqldev@mysql.com/production.mysql.com-20071223184258-15140
- sp1r-brian@zim.(none)-20080206122216-35774
- sp1r-baker@bk-internal.mysql.com-20080209005622-35947
- sp1r-baker@bk-internal.mysql.com-20080224215608-24613
- sp1r-baker@bk-internal.mysql.com-20080307170710-63543
- sp1r-baker@bk-internal.mysql.com-20080312233205-13069
- sp1r-Reggie@core.-20080402175211-28643
- kpettersson@mysql.com-20080901101150-4ne74r8v0492vv42
- alik@sun.com-20090805173811-9fzt0ymcp9tsvn7k
- alik@sun.com-20090805173937-fcv1fdveodq5x9gb
- alik@sun.com-20090805175009-g1od16i3t1xkw2qr
- kostja@sun.com-20090805200643-j9i4fy7ii8ijho5c
- alik@sun.com-20090807195303-j4fb5m4l1dgdahwo
- alik@sun.com-20090808114848-3rkzr9kifrijzaqy
- alik@sun.com-20090810041739-ugmx34h34uid8mox
- alik@sun.com-20090810105306-rf43rfyzzblsy5e7
- alik@sun.com-20090810123113-ccdjwai68b5woqdm
- alik@sun.com-20090811080423-gb7pibec1znaydzy
- alik@sun.com-20090811082130-5uckar1vx3kdsw7g
- alik@sun.com-20090812202051-uqkfwwxxcjvo5ean
The following bugfixes are also backported within this patch:
- Bug#34292: netdb.h missing in hostname.cc
- Bug#39153: Failing to lookup a host name can lead to crash
in current IPv6 implementation
- Bug#38247: Server does not resolve connecting ip's
- Bug#43006: main.skip_name_resolve fails on Windows in PB2
- Bug#45606: ACL requires IPv4-mapped addresses to be used
- Bug#45584: Host name cache does not work as a cache
Diffstat (limited to 'sql')
-rw-r--r-- | sql/client_settings.h | 4 | ||||
-rw-r--r-- | sql/hostname.cc | 593 | ||||
-rw-r--r-- | sql/mysql_priv.h | 12 | ||||
-rw-r--r-- | sql/mysqld.cc | 114 | ||||
-rw-r--r-- | sql/sql_acl.cc | 81 | ||||
-rw-r--r-- | sql/sql_class.h | 1 | ||||
-rw-r--r-- | sql/sql_connect.cc | 43 |
7 files changed, 615 insertions, 233 deletions
diff --git a/sql/client_settings.h b/sql/client_settings.h index fd50bfdbb88..7d103d5904d 100644 --- a/sql/client_settings.h +++ b/sql/client_settings.h @@ -30,10 +30,6 @@ #define set_sigpipe(mysql) #define reset_sigpipe(mysql) #define read_user_name(A) {} -#define mysql_rpl_query_type(A,B) MYSQL_RPL_ADMIN -#define mysql_master_send_query(A, B, C) 1 -#define mysql_slave_send_query(A, B, C) 1 -#define mysql_rpl_probe(mysql) 0 #undef HAVE_SMEM #undef _CUSTOMCONFIG_ diff --git a/sql/hostname.cc b/sql/hostname.cc index b498c548e61..cf35e60061f 100644 --- a/sql/hostname.cc +++ b/sql/hostname.cc @@ -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" @@ -34,24 +34,54 @@ extern "C" { // Because of SCO 3.2V4.2 #ifdef HAVE_SYS_UN_H #include <sys/un.h> #endif -#include <netdb.h> #include <sys/utsname.h> #endif // __WIN__ #ifdef __cplusplus } #endif +/* + HOST_ENTRY_KEY_SIZE -- size of IP address string in the hash cache. +*/ + +#define HOST_ENTRY_KEY_SIZE INET6_ADDRSTRLEN + +/** + 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. -class host_entry :public hash_filo_element + 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 in_addr *) 0)->s_addr)]; - 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_key[HOST_ENTRY_KEY_SIZE]; + + /** + Number of errors during handshake phase from the IP address. + */ + uint connect_errors; + + /** + One of the host names for the IP address. May be NULL. + */ + const char *hostname; }; static hash_filo *hostname_cache; -static pthread_mutex_t LOCK_hostname; void hostname_cache_refresh() { @@ -60,219 +90,468 @@ 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 in_addr),NULL, - (my_hash_free_key) free, - &my_charset_bin))) + Host_entry tmp; + uint key_offset= (uint) ((char*) (&tmp.ip_key) - (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; } 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 prepare_hostname_cache_key(const char *ip_string, + char *ip_key) +{ + int ip_string_length= strlen(ip_string); + DBUG_ASSERT(ip_string_length < HOST_ENTRY_KEY_SIZE); -static void add_hostname(struct in_addr *in,const char *name) + memset(ip_key, 0, HOST_ENTRY_KEY_SIZE); + memcpy_fixed(ip_key, ip_string, ip_string_length); +} + +static inline Host_entry *hostname_cache_search(const char *ip_key) { - if (!(specialflag & SPECIAL_NO_HOST_CACHE)) + return (Host_entry *) hostname_cache->search((uchar *) ip_key, 0); +} + +static bool add_hostname_impl(const char *ip_key, const char *hostname) +{ + if (hostname_cache_search(ip_key)) + return FALSE; + + size_t hostname_size= hostname ? strlen(hostname) + 1 : 0; + + Host_entry *entry= (Host_entry *) malloc(sizeof (Host_entry) + hostname_size); + + if (!entry) + return TRUE; + + char *hostname_copy; + + memcpy_fixed(&entry->ip_key, ip_key, HOST_ENTRY_KEY_SIZE); + + if (hostname_size) { - pthread_mutex_lock(&hostname_cache->lock); - host_entry *entry; - if (!(entry=(host_entry*) hostname_cache->search((uchar*) &in->s_addr,0))) - { - uint length=name ? (uint) strlen(name) : 0; + hostname_copy= (char *) (entry + 1); + memcpy(hostname_copy, hostname, hostname_size); - if ((entry=(host_entry*) malloc(sizeof(host_entry)+length+1))) - { - char *new_name; - memcpy_fixed(&entry->ip, &in->s_addr, sizeof(in->s_addr)); - 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_key, + (const char *) hostname_copy)); } -} + else + { + hostname_copy= NULL; + + DBUG_PRINT("info", ("Adding '%s' -> NULL to the hostname cache...'", + (const char *) ip_key)); + } + + entry->hostname= hostname_copy; + entry->connect_errors= 0; + return hostname_cache->add(entry); +} -inline void add_wrong_ip(struct in_addr *in) +static bool add_hostname(const char *ip_key, 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_key, hostname); + + pthread_mutex_unlock(&hostname_cache->lock); + + return err_status; } -void inc_host_errors(struct in_addr *in) +void inc_host_errors(const char *ip_string) { + if (!ip_string) + return; + + char ip_key[HOST_ENTRY_KEY_SIZE]; + prepare_hostname_cache_key(ip_string, ip_key); + pthread_mutex_lock(&hostname_cache->lock); - host_entry *entry; - if ((entry=(host_entry*) hostname_cache->search((uchar*) &in->s_addr,0))) - entry->errors++; + + Host_entry *entry= hostname_cache_search(ip_key); + + if (entry) + entry->connect_errors++; + pthread_mutex_unlock(&hostname_cache->lock); } -void reset_host_errors(struct in_addr *in) + +void reset_host_errors(const char *ip_string) { + if (!ip_string) + return; + + char ip_key[HOST_ENTRY_KEY_SIZE]; + prepare_hostname_cache_key(ip_string, ip_key); + pthread_mutex_lock(&hostname_cache->lock); - host_entry *entry; - if ((entry=(host_entry*) hostname_cache->search((uchar*) &in->s_addr,0))) - entry->errors=0; + + Host_entry *entry= hostname_cache_search(ip_key); + + if (entry) + entry->connect_errors= 0; + pthread_mutex_unlock(&hostname_cache->lock); } -/* Deal with systems that don't defined INADDR_LOOPBACK */ -#ifndef INADDR_LOOPBACK -#define INADDR_LOOPBACK 0x7f000001UL -#endif -char * ip_to_hostname(struct in_addr *in, 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 ntohl(ip4->s_addr) == INADDR_LOOPBACK; + } + +#ifdef HAVE_IPV6 + case AF_INET6: + { + /* Check for IPv6 ::1. */ + struct in6_addr *ip6= &((struct sockaddr_in6 *) ip)->sin6_addr; + return IN6_IS_ADDR_LOOPBACK(ip6); + } +#endif /* HAVE_IPV6 */ + + default: + return FALSE; + } +} + +static inline bool is_hostname_valid(const char *hostname) { - uint i; - host_entry *entry; + /* + 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_storage IP address (sockaddr). Must be set. + @param [in] ip_string IP address (string). 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, + const char *ip_string, + char **hostname, uint *connect_errors) +{ + const struct sockaddr *ip= (const sockaddr *) ip_storage; + int err_code; + bool err_status; + DBUG_ENTER("ip_to_hostname"); - *errors=0; + DBUG_PRINT("info", ("IP address: '%s'; family: %d.", + (const char *) ip_string, + (int) ip->sa_family)); - /* We always treat the loopback address as "localhost". */ - if (in->s_addr == htonl(INADDR_LOOPBACK)) // is expanded inline by gcc - DBUG_RETURN((char *)my_localhost); + /* Check if we have loopback address (127.0.0.1 or ::1). */ + + if (is_ip_loopback(ip)) + { + DBUG_PRINT("info", ("Loopback address detected.")); + + *connect_errors= 0; /* Do not count connect errors from localhost. */ + *hostname= (char *) my_localhost; + + DBUG_RETURN(FALSE); + } + + /* Prepare host name cache key. */ + + char ip_key[HOST_ENTRY_KEY_SIZE]; + prepare_hostname_cache_key(ip_string, ip_key); + + /* Check first if we have host name in the cache. */ - /* Check first if we have name in cache */ if (!(specialflag & SPECIAL_NO_HOST_CACHE)) { pthread_mutex_lock(&hostname_cache->lock); - if ((entry=(host_entry*) hostname_cache->search((uchar*) &in->s_addr,0))) + + Host_entry *entry= hostname_cache_search(ip_key); + + if (entry) { - char *name; - if (!entry->hostname) - name=0; // Don't allow connection - else - name=my_strdup(entry->hostname,MYF(0)); - *errors= entry->errors; + *connect_errors= entry->connect_errors; + *hostname= NULL; + + if (entry->hostname) + *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_key, + (const char *) (*hostname? *hostname : "null"), + (int) *connect_errors)); + pthread_mutex_unlock(&hostname_cache->lock); - DBUG_RETURN(name); + + DBUG_RETURN(FALSE); } + pthread_mutex_unlock(&hostname_cache->lock); } - struct hostent *hp, *check; - char *name; - LINT_INIT(check); -#if defined(HAVE_GETHOSTBYADDR_R) && defined(HAVE_SOLARIS_STYLE_GETHOST) - char buff[GETHOSTBYADDR_BUFF_SIZE],buff2[GETHOSTBYNAME_BUFF_SIZE]; - int tmp_errno; - struct hostent tmp_hostent, tmp_hostent2; -#ifdef HAVE_purify - bzero(buff,sizeof(buff)); // Bug in purify -#endif - if (!(hp=gethostbyaddr_r((char*) in,sizeof(*in), - AF_INET, - &tmp_hostent,buff,sizeof(buff),&tmp_errno))) - { - DBUG_PRINT("error",("gethostbyaddr_r returned %d",tmp_errno)); - return 0; - } - if (!(check=my_gethostbyname_r(hp->h_name,&tmp_hostent2,buff2,sizeof(buff2), - &tmp_errno))) + /* + 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_key)); + + err_code= vio_getnameinfo(ip, hostname_buffer, NI_MAXHOST, NULL, 0, + NI_NAMEREQD); + + if (err_code == EAI_NONAME) { - DBUG_PRINT("error",("gethostbyname_r returned %d",tmp_errno)); /* - Don't cache responses when the DSN 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. + There is no reverse address mapping for the IP address. A host name + can not be resolved. */ - if (tmp_errno == HOST_NOT_FOUND || tmp_errno == NO_DATA) - add_wrong_ip(in); - my_gethostbyname_r_free(); - DBUG_RETURN(0); - } - if (!hp->h_name[0]) - { - DBUG_PRINT("error",("Got an empty hostname")); - add_wrong_ip(in); - my_gethostbyname_r_free(); - DBUG_RETURN(0); // Don't allow empty hostnames - } - if (!(name=my_strdup(hp->h_name,MYF(0)))) - { - my_gethostbyname_r_free(); - DBUG_RETURN(0); // out of memory + + DBUG_PRINT("error", ("IP address '%s' could not be resolved: " + "no reverse address mapping.", + (const char *) ip_key)); + + sql_print_warning("IP address '%s' could not be resolved: " + "no reverse address mapping.", + (const char *) ip_key); + + err_status= add_hostname(ip_key, NULL); + + *hostname= NULL; + *connect_errors= 0; /* New IP added to the cache. */ + + DBUG_RETURN(err_status); } - my_gethostbyname_r_free(); -#else - pthread_mutex_lock(&LOCK_hostname); - if (!(hp=gethostbyaddr((char*) in,sizeof(*in), AF_INET))) + else if (err_code) { - pthread_mutex_unlock(&LOCK_hostname); - DBUG_PRINT("error",("gethostbyaddr returned %d",errno)); + DBUG_PRINT("error", ("IP address '%s' could not be resolved: " + "getnameinfo() returned %d.", + (const char *) ip_key, + (int) err_code)); + + sql_print_warning("IP address '%s' could not be resolved: " + "getnameinfo() returned error (code: %d).", + (const char *) ip_key, + (int) err_code); - if (errno == HOST_NOT_FOUND || errno == NO_DATA) - goto add_wrong_ip_and_return; - /* Failure, don't cache responce */ - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } - if (!hp->h_name[0]) // Don't allow empty hostnames + + DBUG_PRINT("info", ("IP '%s' resolved to '%s'.", + (const char *) ip_key, + (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 example, 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)) { - pthread_mutex_unlock(&LOCK_hostname); - DBUG_PRINT("error",("Got an empty hostname")); - 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_key, + (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_key, + (const char *) hostname_buffer); + + err_status= add_hostname(ip_key, NULL); + + *hostname= NULL; + *connect_errors= 0; /* New IP added to the cache. */ + + DBUG_RETURN(err_status); } - if (!(name=my_strdup(hp->h_name,MYF(0)))) + + /* 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_family= AF_UNSPEC; + + 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 == EAI_NONAME) { - pthread_mutex_unlock(&LOCK_hostname); - DBUG_RETURN(0); // out of memory + /* + 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. + */ + + err_status= add_hostname(ip_key, NULL); + + *hostname= NULL; + *connect_errors= 0; /* New IP added to the cache. */ + + DBUG_RETURN(err_status); } - check=gethostbyname(name); - pthread_mutex_unlock(&LOCK_hostname); - if (!check) + else if (err_code) { - DBUG_PRINT("error",("gethostbyname returned %d",errno)); - my_free(name,MYF(0)); - DBUG_RETURN(0); + DBUG_PRINT("error", ("getaddrinfo() failed with error code %d.", err_code)); + DBUG_RETURN(TRUE); } -#endif - /* Don't accept hostnames that starts with digits because they may be - false ip:s */ - if (my_isdigit(&my_charset_latin1,name[0])) + /* 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) { - char *pos; - for (pos= name+1 ; my_isdigit(&my_charset_latin1,*pos); pos++) ; - if (*pos == '.') + char ip_buffer[HOST_ENTRY_KEY_SIZE]; + + { + err_status= + vio_get_normalized_ip_string(addr_info->ai_addr, addr_info->ai_addrlen, + ip_buffer, sizeof (ip_buffer)); + DBUG_ASSERT(!err_status); + } + + DBUG_PRINT("info", (" - '%s'", (const char *) ip_buffer)); + + if (strcmp(ip_key, ip_buffer) == 0) { - DBUG_PRINT("error",("mysqld doesn't accept hostnames that starts with a number followed by a '.'")); - my_free(name,MYF(0)); - goto add_wrong_ip_and_return; + /* 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; } } - /* Check that 'gethostbyname' returned the used ip */ - for (i=0; check->h_addr_list[i]; i++) + /* Log resolved IP-addresses if no match was found. */ + + if (!*hostname) { - if (*(uint32*)(check->h_addr_list)[i] == in->s_addr) + sql_print_information("Hostname '%s' does not resolve to '%s'.", + (const char *) hostname_buffer, + (const char *) ip_key); + 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) { - add_hostname(in,name); - DBUG_RETURN(name); + char ip_buffer[HOST_ENTRY_KEY_SIZE]; + + err_status= + vio_get_normalized_ip_string(addr_info->ai_addr, addr_info->ai_addrlen, + ip_buffer, sizeof (ip_buffer)); + DBUG_ASSERT(!err_status); + + sql_print_information(" - %s\n", (const char *) ip_buffer); } } - DBUG_PRINT("error",("Couldn't verify hostname with gethostbyname")); - my_free(name,MYF(0)); -add_wrong_ip_and_return: - add_wrong_ip(in); - DBUG_RETURN(0); + /* Free the result of getaddrinfo(). */ + + freeaddrinfo(addr_info_list); + + /* Add an entry for the IP to the cache. */ + + if (*hostname) + { + err_status= add_hostname(ip_key, *hostname); + *connect_errors= 0; + } + else + { + DBUG_PRINT("error",("Couldn't verify hostname with getaddrinfo().")); + + err_status= add_hostname(ip_key, NULL); + *hostname= NULL; + *connect_errors= 0; + } + + DBUG_RETURN(err_status); } diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index c6454679e28..552b834b379 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -53,6 +53,9 @@ #include "sql_array.h" #include "sql_plugin.h" #include "scheduler.h" +#ifndef __WIN__ +#include <netdb.h> +#endif class Parser_state; @@ -2235,10 +2238,11 @@ uint build_table_shadow_filename(char *buff, size_t bufflen, #define FRM_ONLY (1 << 3) /* from hostname.cc */ -struct in_addr; -char * ip_to_hostname(struct in_addr *in,uint *errors); -void inc_host_errors(struct in_addr *in); -void reset_host_errors(struct in_addr *in); +bool ip_to_hostname(struct sockaddr_storage *ip_storage, + const char *ip_string, + char **hostname, uint *connect_errors); +void inc_host_errors(const char *ip_string); +void reset_host_errors(const char *ip_string); bool hostname_cache_init(); void hostname_cache_free(); void hostname_cache_refresh(void); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 1cab245b317..5f00fa0ec64 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -106,7 +106,6 @@ extern "C" { // Because of SCO 3.2V4.2 #ifdef HAVE_SYS_UN_H # include <sys/un.h> #endif -#include <netdb.h> #ifdef HAVE_SELECT_H # include <select.h> #endif @@ -176,7 +175,7 @@ static void registerwithneb(); static void getvolumename(); static void getvolumeID(BYTE *volumeName); #endif /* __NETWARE__ */ - + #ifdef _AIX41 int initgroups(const char *,unsigned int); @@ -390,7 +389,6 @@ static my_bool opt_short_log_format= 0; static uint kill_cached_threads, wake_thread; static ulong killed_threads, thread_created; static ulong max_used_connections; -static ulong my_bind_addr; /**< the address we bind to */ static volatile ulong cached_thread_count= 0; static const char *sql_mode_str= "OFF"; /* Text representation for OPTIMIZER_SWITCH_DEFAULT */ @@ -1635,17 +1633,18 @@ static void set_root(const char *path) #endif } + static void network_init(void) { - struct sockaddr_in IPaddr; #ifdef HAVE_SYS_UN_H struct sockaddr_un UNIXaddr; #endif - int arg=1; + int arg; int ret; uint waited; uint this_wait; uint retry; + char port_buf[NI_MAXSERV]; DBUG_ENTER("network_init"); LINT_INIT(ret); @@ -1656,26 +1655,65 @@ static void network_init(void) if (mysqld_port != 0 && !opt_disable_networking && !opt_bootstrap) { + struct addrinfo *ai, *a; + struct addrinfo hints; + int error; DBUG_PRINT("general",("IP Socket is %d",mysqld_port)); - ip_sock = socket(AF_INET, SOCK_STREAM, 0); + + bzero(&hints, sizeof (hints)); + hints.ai_flags= AI_PASSIVE; + hints.ai_socktype= SOCK_STREAM; + hints.ai_family= AF_UNSPEC; + + my_snprintf(port_buf, NI_MAXSERV, "%d", mysqld_port); + error= getaddrinfo(my_bind_addr_str, port_buf, &hints, &ai); + if (error != 0) + { + DBUG_PRINT("error",("Got error: %d from getaddrinfo()", error)); + sql_perror(ER(ER_IPSOCK_ERROR)); /* purecov: tested */ + unireg_abort(1); /* purecov: tested */ + } + + for (a= ai; a != NULL; a= a->ai_next) + { + ip_sock= socket(a->ai_family, a->ai_socktype, a->ai_protocol); + if (ip_sock != INVALID_SOCKET) + break; + } + if (ip_sock == INVALID_SOCKET) { DBUG_PRINT("error",("Got error: %d from socket()",socket_errno)); sql_perror(ER(ER_IPSOCK_ERROR)); /* purecov: tested */ unireg_abort(1); /* purecov: tested */ } - bzero((char*) &IPaddr, sizeof(IPaddr)); - IPaddr.sin_family = AF_INET; - IPaddr.sin_addr.s_addr = my_bind_addr; - IPaddr.sin_port = (unsigned short) htons((unsigned short) mysqld_port); #ifndef __WIN__ /* We should not use SO_REUSEADDR on windows as this would enable a user to open two mysqld servers with the same TCP/IP port. */ + arg= 1; (void) setsockopt(ip_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&arg,sizeof(arg)); #endif /* __WIN__ */ + +#ifdef IPV6_V6ONLY + /* + For interoperability with older clients, IPv6 socket should + listen on both IPv6 and IPv4 wildcard addresses. + Turn off IPV6_V6ONLY option. + + NOTE: this will work starting from Windows Vista only. + On Windows XP dual stack is not available, so it will not + listen on the corresponding IPv4-address. + */ + if (a->ai_family == AF_INET6) + { + arg= 0; + (void) setsockopt(ip_sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, + sizeof(arg)); + } +#endif /* Sometimes the port is not released fast enough when stopping and restarting the server. This happens quite often with the test suite @@ -1686,8 +1724,7 @@ static void network_init(void) */ for (waited= 0, retry= 1; ; retry++, waited+= this_wait) { - if (((ret= bind(ip_sock, my_reinterpret_cast(struct sockaddr *) (&IPaddr), - sizeof(IPaddr))) >= 0) || + if (((ret= bind(ip_sock, a->ai_addr, a->ai_addrlen)) >= 0 ) || (socket_errno != SOCKET_EADDRINUSE) || (waited >= mysqld_port_timeout)) break; @@ -1695,6 +1732,7 @@ static void network_init(void) this_wait= retry * retry / 3 + 1; sleep(this_wait); } + freeaddrinfo(ai); if (ret < 0) { DBUG_PRINT("error",("Got error: %d from bind",socket_errno)); @@ -1716,7 +1754,6 @@ static void network_init(void) if (Service.IsNT() && mysqld_unix_port[0] && !opt_bootstrap && opt_enable_named_pipe) { - strxnmov(pipe_name, sizeof(pipe_name)-1, "\\\\.\\pipe\\", mysqld_unix_port, NullS); bzero((char*) &saPipeSecurity, sizeof(saPipeSecurity)); @@ -1782,6 +1819,7 @@ static void network_init(void) UNIXaddr.sun_family = AF_UNIX; strmov(UNIXaddr.sun_path, mysqld_unix_port); (void) unlink(mysqld_unix_port); + arg= 1; (void) setsockopt(unix_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&arg, sizeof(arg)); umask(0); @@ -4850,7 +4888,7 @@ static bool read_init_file(char *file_name) When we enter this function, LOCK_thread_count is hold! */ - + void handle_connection_in_main_thread(THD *thd) { safe_mutex_assert_owner(&LOCK_thread_count); @@ -4933,7 +4971,6 @@ void create_thread_to_handle_connection(THD *thd) static void create_new_thread(THD *thd) { - NET *net=&thd->net; DBUG_ENTER("create_new_thread"); /* @@ -5003,6 +5040,7 @@ inline void kill_broken_server() /* Handle new connections and spawn new process to handle them */ #ifndef EMBEDDED_LIBRARY + void handle_connections_sockets() { my_socket sock,new_sock; @@ -5010,7 +5048,7 @@ void handle_connections_sockets() uint max_used_connection= (uint) (max(ip_sock,unix_sock)+1); fd_set readFDs,clientFDs; THD *thd; - struct sockaddr_in cAddr; + struct sockaddr_storage cAddr; int ip_flags=0,socket_flags=0,flags; st_vio *vio_tmp; DBUG_ENTER("handle_connections_sockets"); @@ -5084,9 +5122,9 @@ void handle_connections_sockets() #endif /* NO_FCNTL_NONBLOCK */ for (uint retry=0; retry < MAX_ACCEPT_RETRY; retry++) { - size_socket length=sizeof(struct sockaddr_in); - new_sock = accept(sock, my_reinterpret_cast(struct sockaddr *) (&cAddr), - &length); + size_socket length= sizeof(struct sockaddr_storage); + new_sock= accept(sock, (struct sockaddr *)(&cAddr), + &length); #ifdef __NETWARE__ // TODO: temporary fix, waiting for TCP/IP fix - DEFECT000303149 if ((new_sock == INVALID_SOCKET) && (socket_errno == EINVAL)) @@ -5157,9 +5195,10 @@ void handle_connections_sockets() { size_socket dummyLen; - struct sockaddr dummy; - dummyLen = sizeof(struct sockaddr); - if (getsockname(new_sock,&dummy, &dummyLen) < 0) + struct sockaddr_storage dummy; + dummyLen = sizeof(dummy); + if ( getsockname(new_sock,(struct sockaddr *)&dummy, + (SOCKET_SIZE_TYPE *)&dummyLen) < 0 ) { sql_perror("Error on new connection socket"); (void) shutdown(new_sock, SHUT_RDWR); @@ -5505,7 +5544,7 @@ errorconn: NullS); sql_perror(buff); } - if (handle_client_file_map) + if (handle_client_file_map) CloseHandle(handle_client_file_map); if (handle_client_map) UnmapViewOfFile(handle_client_map); @@ -7786,7 +7825,6 @@ static int mysql_init_variables(void) strmov(server_version, MYSQL_SERVER_VERSION); myisam_recover_options_str= sql_mode_str= "OFF"; myisam_stats_method_str= "nulls_unequal"; - my_bind_addr = htonl(INADDR_ANY); threads.empty(); thread_cache.empty(); key_caches.empty(); @@ -8226,27 +8264,25 @@ mysqld_get_one_option(int optid, my_use_symdir=0; break; case (int) OPT_BIND_ADDRESS: - if ((my_bind_addr= (ulong) inet_addr(argument)) == INADDR_NONE) { - struct hostent *ent; - if (argument[0]) - ent=gethostbyname(argument); - else + struct addrinfo *res_lst, hints; + + bzero(&hints, sizeof(struct addrinfo)); + hints.ai_socktype= SOCK_STREAM; + hints.ai_protocol= IPPROTO_TCP; + + if (getaddrinfo(argument, NULL, &hints, &res_lst) != 0) { - char myhostname[255]; - if (gethostname(myhostname,sizeof(myhostname)) < 0) - { - sql_perror("Can't start server: cannot get my own hostname!"); - return 1; - } - ent=gethostbyname(myhostname); + sql_print_error("Can't start server: cannot resolve hostname!"); + return 1; } - if (!ent) + + if (res_lst->ai_next) { - sql_perror("Can't start server: cannot resolve hostname!"); + sql_print_error("Can't start server: bind-address refers to multiple interfaces!"); return 1; } - my_bind_addr = (ulong) ((in_addr*)ent->h_addr_list[0])->s_addr; + freeaddrinfo(res_lst); } break; case (int) OPT_PID_FILE: diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 641423605aa..155e7298e90 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -1785,24 +1785,83 @@ static bool compare_hostname(const acl_host_and_ip *host, const char *hostname, (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. */ } diff --git a/sql/sql_class.h b/sql/sql_class.h index 03a92c1a685..551a2372632 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1238,7 +1238,6 @@ public: HASH user_vars; // hash for user variables String packet; // dynamic buffer for network I/O String convert_buffer; // buffer for charset conversions - struct sockaddr_in remote; // client socket address struct rand_struct rand; // used for authentication struct system_variables variables; // Changeable local variables struct system_status_var status_var; // Per thread statistic vars diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index f5f962a02a3..8ecafce6f93 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -377,7 +377,8 @@ check_user(THD *thd, enum enum_server_command command, if (send_old_password_request(thd) || my_net_read(net) != SCRAMBLE_LENGTH_323 + 1) { - inc_host_errors(&thd->remote.sin_addr); + inc_host_errors(thd->main_security_ctx.ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); DBUG_RETURN(1); } @@ -498,9 +499,9 @@ check_user(THD *thd, enum enum_server_command command, thd->main_security_ctx.host_or_ip, passwd_len ? ER(ER_YES) : ER(ER_NO)); /* - log access denied messages to the error log when log-warnings = 2 + Log access denied messages to the error log when log-warnings = 2 so that the overhead of the general query log is not required to track - failed connections + failed connections. */ if (global_system_variables.log_warnings > 1) { @@ -666,9 +667,9 @@ static int check_connection(THD *thd) if (!thd->main_security_ctx.host) // If TCP/IP connection { - char ip[30]; + char ip[NI_MAXHOST]; - if (vio_peer_addr(net->vio, ip, &thd->peer_port)) + if (vio_peer_addr(net->vio, ip, &thd->peer_port, NI_MAXHOST)) { my_error(ER_BAD_HOST_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); return 1; @@ -676,12 +677,15 @@ static int check_connection(THD *thd) if (!(thd->main_security_ctx.ip= my_strdup(ip,MYF(MY_WME)))) return 1; /* The error is set by my_strdup(). */ thd->main_security_ctx.host_or_ip= thd->main_security_ctx.ip; - vio_in_addr(net->vio,&thd->remote.sin_addr); if (!(specialflag & SPECIAL_NO_RESOLVE)) { - vio_in_addr(net->vio,&thd->remote.sin_addr); - thd->main_security_ctx.host= - ip_to_hostname(&thd->remote.sin_addr, &connect_errors); + if (ip_to_hostname(&net->vio->remote, thd->main_security_ctx.ip, + &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) { @@ -714,7 +718,7 @@ static int check_connection(THD *thd) thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host; thd->main_security_ctx.ip= 0; /* Reset sin_addr */ - bzero((char*) &thd->remote, sizeof(thd->remote)); + bzero((char*) &net->vio->remote, sizeof(net->vio->remote)); } vio_keepalive(net->vio, TRUE); @@ -769,7 +773,8 @@ static int check_connection(THD *thd) (pkt_len= my_net_read(net)) == packet_error || pkt_len < MIN_HANDSHAKE_SIZE) { - inc_host_errors(&thd->remote.sin_addr); + inc_host_errors(thd->main_security_ctx.ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); return 1; @@ -779,7 +784,7 @@ static int check_connection(THD *thd) #include "_cust_sql_parse.h" #endif if (connect_errors) - reset_host_errors(&thd->remote.sin_addr); + reset_host_errors(thd->main_security_ctx.ip); if (thd->packet.alloc(thd->variables.net_buffer_length)) return 1; /* The error is set by alloc(). */ @@ -813,7 +818,7 @@ static int check_connection(THD *thd) /* Do the SSL layering. */ if (!ssl_acceptor_fd) { - inc_host_errors(&thd->remote.sin_addr); + inc_host_errors(thd->main_security_ctx.ip); my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); return 1; } @@ -821,7 +826,8 @@ static int check_connection(THD *thd) if (sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout)) { DBUG_PRINT("error", ("Failed to accept new SSL connection")); - inc_host_errors(&thd->remote.sin_addr); + inc_host_errors(thd->main_security_ctx.ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); return 1; } @@ -831,7 +837,8 @@ static int check_connection(THD *thd) { DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)", pkt_len)); - inc_host_errors(&thd->remote.sin_addr); + inc_host_errors(thd->main_security_ctx.ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); return 1; } @@ -840,7 +847,8 @@ static int check_connection(THD *thd) if (end >= (char*) net->read_pos+ pkt_len +2) { - inc_host_errors(&thd->remote.sin_addr); + inc_host_errors(thd->main_security_ctx.ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); return 1; } @@ -878,7 +886,8 @@ static int check_connection(THD *thd) if (passwd + passwd_len + db_len > (char *)net->read_pos + pkt_len) { - inc_host_errors(&thd->remote.sin_addr); + inc_host_errors(thd->main_security_ctx.ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); return 1; } |