mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 04:46:15 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			992 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			992 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
 | 
						|
   Copyright (c) 2011, 2014, SkySQL Ab.
 | 
						|
 | 
						|
   This program is free software; you can redistribute it and/or modify
 | 
						|
   it under the terms of the GNU General Public License as published by
 | 
						|
   the Free Software Foundation; version 2 of the License.
 | 
						|
 | 
						|
   This program is distributed in the hope that it will be useful,
 | 
						|
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
   GNU General Public License for more details.
 | 
						|
 | 
						|
   You should have received a copy of the GNU General Public License
 | 
						|
   along with this program; if not, write to the Free Software
 | 
						|
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA */
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  @file
 | 
						|
 | 
						|
  @brief
 | 
						|
  Get hostname for an IP address.
 | 
						|
 | 
						|
  Hostnames are checked with reverse name lookup and checked that they
 | 
						|
  doesn't resemble an IP address.
 | 
						|
*/
 | 
						|
#include "mariadb.h"
 | 
						|
#include "sql_priv.h"
 | 
						|
#include "unireg.h"                             // SPECIAL_NO_HOST_CACHE
 | 
						|
#include "hostname.h"
 | 
						|
#ifndef _WIN32
 | 
						|
#include <netdb.h>        // getservbyname, servent
 | 
						|
#endif
 | 
						|
#include "hash_filo.h"
 | 
						|
#include <m_ctype.h>
 | 
						|
#include "log.h"                                // sql_print_warning,
 | 
						|
                                                // sql_print_information
 | 
						|
#include "violite.h"                            // vio_getnameinfo,
 | 
						|
                                                // vio_get_normalized_ip_string
 | 
						|
#ifdef	__cplusplus
 | 
						|
extern "C" {					// Because of SCO 3.2V4.2
 | 
						|
#endif
 | 
						|
#if !defined( _WIN32)
 | 
						|
#ifdef HAVE_SYS_UN_H
 | 
						|
#include <sys/un.h>
 | 
						|
#endif
 | 
						|
#include <sys/utsname.h>
 | 
						|
#endif // _WIN32
 | 
						|
#ifdef	__cplusplus
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
Host_errors::Host_errors()
 | 
						|
: m_connect(0),
 | 
						|
  m_host_blocked(0),
 | 
						|
  m_nameinfo_transient(0),
 | 
						|
  m_nameinfo_permanent(0),
 | 
						|
  m_format(0),
 | 
						|
  m_addrinfo_transient(0),
 | 
						|
  m_addrinfo_permanent(0),
 | 
						|
  m_FCrDNS(0),
 | 
						|
  m_host_acl(0),
 | 
						|
  m_no_auth_plugin(0),
 | 
						|
  m_auth_plugin(0),
 | 
						|
  m_handshake(0),
 | 
						|
  m_proxy_user(0),
 | 
						|
  m_proxy_user_acl(0),
 | 
						|
  m_authentication(0),
 | 
						|
  m_ssl(0),
 | 
						|
  m_max_user_connection(0),
 | 
						|
  m_max_user_connection_per_hour(0),
 | 
						|
  m_default_database(0),
 | 
						|
  m_init_connect(0),
 | 
						|
  m_local(0)
 | 
						|
{}
 | 
						|
 | 
						|
Host_errors::~Host_errors() = default;
 | 
						|
 | 
						|
void Host_errors::reset()
 | 
						|
{
 | 
						|
  m_connect= 0;
 | 
						|
  m_host_blocked= 0;
 | 
						|
  m_nameinfo_transient= 0;
 | 
						|
  m_nameinfo_permanent= 0;
 | 
						|
  m_format= 0;
 | 
						|
  m_addrinfo_transient= 0;
 | 
						|
  m_addrinfo_permanent= 0;
 | 
						|
  m_FCrDNS= 0;
 | 
						|
  m_host_acl= 0;
 | 
						|
  m_no_auth_plugin= 0;
 | 
						|
  m_auth_plugin= 0;
 | 
						|
  m_handshake= 0;
 | 
						|
  m_proxy_user= 0;
 | 
						|
  m_proxy_user_acl= 0;
 | 
						|
  m_authentication= 0;
 | 
						|
  m_ssl= 0;
 | 
						|
  m_max_user_connection= 0;
 | 
						|
  m_max_user_connection_per_hour= 0;
 | 
						|
  m_default_database= 0;
 | 
						|
  m_init_connect= 0;
 | 
						|
  m_local= 0;
 | 
						|
}
 | 
						|
 | 
						|
void Host_errors::aggregate(const Host_errors *errors)
 | 
						|
{
 | 
						|
  m_connect+= errors->m_connect;
 | 
						|
  m_host_blocked+= errors->m_host_blocked;
 | 
						|
  m_nameinfo_transient+= errors->m_nameinfo_transient;
 | 
						|
  m_nameinfo_permanent+= errors->m_nameinfo_permanent;
 | 
						|
  m_format+= errors->m_format;
 | 
						|
  m_addrinfo_transient+= errors->m_addrinfo_transient;
 | 
						|
  m_addrinfo_permanent+= errors->m_addrinfo_permanent;
 | 
						|
  m_FCrDNS+= errors->m_FCrDNS;
 | 
						|
  m_host_acl+= errors->m_host_acl;
 | 
						|
  m_no_auth_plugin+= errors->m_no_auth_plugin;
 | 
						|
  m_auth_plugin+= errors->m_auth_plugin;
 | 
						|
  m_handshake+= errors->m_handshake;
 | 
						|
  m_proxy_user+= errors->m_proxy_user;
 | 
						|
  m_proxy_user_acl+= errors->m_proxy_user_acl;
 | 
						|
  m_authentication+= errors->m_authentication;
 | 
						|
  m_ssl+= errors->m_ssl;
 | 
						|
  m_max_user_connection+= errors->m_max_user_connection;
 | 
						|
  m_max_user_connection_per_hour+= errors->m_max_user_connection_per_hour;
 | 
						|
  m_default_database+= errors->m_default_database;
 | 
						|
  m_init_connect+= errors->m_init_connect;
 | 
						|
  m_local+= errors->m_local;
 | 
						|
}
 | 
						|
 | 
						|
static Hash_filo<Host_entry> *hostname_cache;
 | 
						|
ulong host_cache_size;
 | 
						|
 | 
						|
void hostname_cache_refresh()
 | 
						|
{
 | 
						|
  hostname_cache->clear();
 | 
						|
}
 | 
						|
 | 
						|
uint hostname_cache_size()
 | 
						|
{
 | 
						|
  return hostname_cache->size();
 | 
						|
}
 | 
						|
 | 
						|
void hostname_cache_resize(uint size)
 | 
						|
{
 | 
						|
  hostname_cache->resize(size);
 | 
						|
}
 | 
						|
 | 
						|
bool hostname_cache_init()
 | 
						|
{
 | 
						|
  Host_entry tmp;
 | 
						|
  uint key_offset= (uint) ((char*) (&tmp.ip_key) - (char*) &tmp);
 | 
						|
 | 
						|
  if (!(hostname_cache= new Hash_filo<Host_entry>(key_memory_host_cache_hostname,
 | 
						|
                             host_cache_size, key_offset, HOST_ENTRY_KEY_SIZE,
 | 
						|
                             NULL, my_free, &my_charset_bin)))
 | 
						|
    return 1;
 | 
						|
 | 
						|
  hostname_cache->clear();
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
void hostname_cache_free()
 | 
						|
{
 | 
						|
  delete hostname_cache;
 | 
						|
  hostname_cache= NULL;
 | 
						|
}
 | 
						|
 | 
						|
void hostname_cache_lock()
 | 
						|
{
 | 
						|
  mysql_mutex_lock(&hostname_cache->lock);
 | 
						|
}
 | 
						|
 | 
						|
void hostname_cache_unlock()
 | 
						|
{
 | 
						|
  mysql_mutex_unlock(&hostname_cache->lock);
 | 
						|
}
 | 
						|
 | 
						|
static void prepare_hostname_cache_key(const char *ip_string,
 | 
						|
                                       char *ip_key)
 | 
						|
{
 | 
						|
  size_t ip_string_length= strlen(ip_string);
 | 
						|
  DBUG_ASSERT(ip_string_length < HOST_ENTRY_KEY_SIZE);
 | 
						|
 | 
						|
  memset(ip_key, 0, HOST_ENTRY_KEY_SIZE);
 | 
						|
  memcpy(ip_key, ip_string, ip_string_length);
 | 
						|
}
 | 
						|
 | 
						|
Host_entry *hostname_cache_first()
 | 
						|
{ return hostname_cache->first(); }
 | 
						|
 | 
						|
static inline Host_entry *hostname_cache_search(const char *ip_key)
 | 
						|
{
 | 
						|
  return hostname_cache->search((uchar *) ip_key, HOST_ENTRY_KEY_SIZE);
 | 
						|
}
 | 
						|
 | 
						|
static void add_hostname_impl(const char *ip_key, const char *hostname,
 | 
						|
                              bool validated, Host_errors *errors,
 | 
						|
                              ulonglong now)
 | 
						|
{
 | 
						|
  Host_entry *entry;
 | 
						|
  bool need_add= false;
 | 
						|
 | 
						|
  entry= hostname_cache_search(ip_key);
 | 
						|
 | 
						|
  if (likely(entry == NULL))
 | 
						|
  {
 | 
						|
    entry= (Host_entry *) my_malloc(key_memory_host_cache_hostname,
 | 
						|
                                    sizeof (Host_entry), 0);
 | 
						|
    if (entry == NULL)
 | 
						|
      return;
 | 
						|
 | 
						|
    need_add= true;
 | 
						|
    memcpy(&entry->ip_key, ip_key, HOST_ENTRY_KEY_SIZE);
 | 
						|
    entry->m_errors.reset();
 | 
						|
    entry->m_hostname_length= 0;
 | 
						|
    entry->m_host_validated= false;
 | 
						|
    entry->m_first_seen= now;
 | 
						|
    entry->m_last_seen= now;
 | 
						|
    entry->m_first_error_seen= 0;
 | 
						|
    entry->m_last_error_seen= 0;
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    entry->m_last_seen= now;
 | 
						|
  }
 | 
						|
 | 
						|
  if (validated)
 | 
						|
  {
 | 
						|
    if (hostname != NULL)
 | 
						|
    {
 | 
						|
      size_t len= strlen(hostname);
 | 
						|
      if (len > sizeof(entry->m_hostname) - 1)
 | 
						|
        len= sizeof(entry->m_hostname) - 1;
 | 
						|
      memcpy(entry->m_hostname, hostname, len);
 | 
						|
      entry->m_hostname[len]= '\0';
 | 
						|
      entry->m_hostname_length= (uint)len;
 | 
						|
 | 
						|
      DBUG_PRINT("info",
 | 
						|
                 ("Adding/Updating '%s' -> '%s' (validated) to the hostname cache...'",
 | 
						|
                 (const char *) ip_key,
 | 
						|
                 (const char *) entry->m_hostname));
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      entry->m_hostname_length= 0;
 | 
						|
      DBUG_PRINT("info",
 | 
						|
                 ("Adding/Updating '%s' -> NULL (validated) to the hostname cache...'",
 | 
						|
                 (const char *) ip_key));
 | 
						|
    }
 | 
						|
    entry->m_host_validated= true;
 | 
						|
    /*
 | 
						|
      New errors that are considered 'blocking',
 | 
						|
      that will eventually cause the IP to be black listed and blocked.
 | 
						|
    */
 | 
						|
    errors->sum_connect_errors();
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    entry->m_hostname_length= 0;
 | 
						|
    entry->m_host_validated= false;
 | 
						|
    /* Do not count new blocking errors during DNS failures. */
 | 
						|
    errors->clear_connect_errors();
 | 
						|
    DBUG_PRINT("info",
 | 
						|
               ("Adding/Updating '%s' -> NULL (not validated) to the hostname cache...'",
 | 
						|
               (const char *) ip_key));
 | 
						|
  }
 | 
						|
 | 
						|
  if (errors->has_error())
 | 
						|
    entry->set_error_timestamps(now);
 | 
						|
 | 
						|
  entry->m_errors.aggregate(errors);
 | 
						|
 | 
						|
  if (need_add)
 | 
						|
    hostname_cache->add(entry);
 | 
						|
 | 
						|
  return;
 | 
						|
}
 | 
						|
 | 
						|
static void add_hostname(const char *ip_key, const char *hostname,
 | 
						|
                         bool validated, Host_errors *errors)
 | 
						|
{
 | 
						|
  if (specialflag & SPECIAL_NO_HOST_CACHE)
 | 
						|
    return;
 | 
						|
 | 
						|
  ulonglong now= my_hrtime().val;
 | 
						|
 | 
						|
  mysql_mutex_lock(&hostname_cache->lock);
 | 
						|
 | 
						|
  add_hostname_impl(ip_key, hostname, validated, errors, now);
 | 
						|
 | 
						|
  mysql_mutex_unlock(&hostname_cache->lock);
 | 
						|
 | 
						|
  return;
 | 
						|
}
 | 
						|
 | 
						|
void inc_host_errors(const char *ip_string, Host_errors *errors)
 | 
						|
{
 | 
						|
  if (!ip_string)
 | 
						|
    return;
 | 
						|
 | 
						|
  ulonglong now= my_hrtime().val;
 | 
						|
  char ip_key[HOST_ENTRY_KEY_SIZE];
 | 
						|
  prepare_hostname_cache_key(ip_string, ip_key);
 | 
						|
 | 
						|
  mysql_mutex_lock(&hostname_cache->lock);
 | 
						|
 | 
						|
  Host_entry *entry= hostname_cache_search(ip_key);
 | 
						|
 | 
						|
  if (entry)
 | 
						|
  {
 | 
						|
    if (entry->m_host_validated)
 | 
						|
      errors->sum_connect_errors();
 | 
						|
    else
 | 
						|
      errors->clear_connect_errors();
 | 
						|
 | 
						|
    entry->m_errors.aggregate(errors);
 | 
						|
    entry->set_error_timestamps(now);
 | 
						|
  }
 | 
						|
 | 
						|
  mysql_mutex_unlock(&hostname_cache->lock);
 | 
						|
}
 | 
						|
 | 
						|
void reset_host_connect_errors(const char *ip_string)
 | 
						|
{
 | 
						|
  if (!ip_string)
 | 
						|
    return;
 | 
						|
 | 
						|
  char ip_key[HOST_ENTRY_KEY_SIZE];
 | 
						|
  prepare_hostname_cache_key(ip_string, ip_key);
 | 
						|
 | 
						|
  mysql_mutex_lock(&hostname_cache->lock);
 | 
						|
 | 
						|
  Host_entry *entry= hostname_cache_search(ip_key);
 | 
						|
 | 
						|
  if (entry)
 | 
						|
    entry->m_errors.clear_connect_errors();
 | 
						|
 | 
						|
  mysql_mutex_unlock(&hostname_cache->lock);
 | 
						|
}
 | 
						|
 | 
						|
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)
 | 
						|
{
 | 
						|
  /*
 | 
						|
    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.
 | 
						|
    - update the host_cache statistics
 | 
						|
 | 
						|
  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 0 Success
 | 
						|
  @retval RC_BLOCKED_HOST The host is blocked.
 | 
						|
 | 
						|
  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.
 | 
						|
*/
 | 
						|
 | 
						|
int ip_to_hostname(struct sockaddr_storage *ip_storage,
 | 
						|
                   const char *ip_string,
 | 
						|
                   const char **hostname,
 | 
						|
                   uint *connect_errors)
 | 
						|
{
 | 
						|
  const struct sockaddr *ip= (const sockaddr *) ip_storage;
 | 
						|
  int err_code;
 | 
						|
  bool err_status __attribute__((unused));
 | 
						|
  Host_errors errors;
 | 
						|
 | 
						|
  DBUG_ENTER("ip_to_hostname");
 | 
						|
  DBUG_PRINT("info", ("IP address: '%s'; family: %d.",
 | 
						|
                      (const char *) ip_string,
 | 
						|
                      (int) ip->sa_family));
 | 
						|
 | 
						|
  /* Default output values, for most cases. */
 | 
						|
  *hostname= NULL;
 | 
						|
  *connect_errors= 0;
 | 
						|
 | 
						|
  /* Check if we have loopback address (127.0.0.1 or ::1). */
 | 
						|
 | 
						|
  if (is_ip_loopback(ip))
 | 
						|
  {
 | 
						|
    DBUG_PRINT("info", ("Loopback address detected."));
 | 
						|
 | 
						|
    /* Do not count connect errors from localhost. */
 | 
						|
    *hostname= my_localhost;
 | 
						|
 | 
						|
    DBUG_RETURN(0);
 | 
						|
  }
 | 
						|
 | 
						|
  /* 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. */
 | 
						|
 | 
						|
  if (!(specialflag & SPECIAL_NO_HOST_CACHE))
 | 
						|
  {
 | 
						|
    ulonglong now= my_hrtime().val;
 | 
						|
 | 
						|
    mysql_mutex_lock(&hostname_cache->lock);
 | 
						|
 | 
						|
    Host_entry *entry= hostname_cache_search(ip_key);
 | 
						|
 | 
						|
    if (entry)
 | 
						|
    {
 | 
						|
      entry->m_last_seen= now;
 | 
						|
      *connect_errors= entry->m_errors.m_connect;
 | 
						|
 | 
						|
      if (unlikely(entry->m_errors.m_connect >= max_connect_errors))
 | 
						|
      {
 | 
						|
        entry->m_errors.m_host_blocked++;
 | 
						|
        entry->set_error_timestamps(now);
 | 
						|
        mysql_mutex_unlock(&hostname_cache->lock);
 | 
						|
        DBUG_RETURN(RC_BLOCKED_HOST);
 | 
						|
      }
 | 
						|
 | 
						|
      /*
 | 
						|
        If there is an IP -> HOSTNAME association in the cache,
 | 
						|
        but for a hostname that was not validated,
 | 
						|
        do not return that hostname: perform the network validation again.
 | 
						|
      */
 | 
						|
      if (entry->m_host_validated)
 | 
						|
      {
 | 
						|
        if (entry->m_hostname_length)
 | 
						|
          *hostname= my_strdup(key_memory_host_cache_hostname,
 | 
						|
                               entry->m_hostname, MYF(0));
 | 
						|
 | 
						|
        DBUG_PRINT("info",("IP (%s) has been found in the cache. "
 | 
						|
                           "Hostname: '%s'",
 | 
						|
                           (const char *) ip_key,
 | 
						|
                           (const char *) (*hostname? *hostname : "null")
 | 
						|
                          ));
 | 
						|
 | 
						|
        mysql_mutex_unlock(&hostname_cache->lock);
 | 
						|
 | 
						|
        DBUG_RETURN(0);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    mysql_mutex_unlock(&hostname_cache->lock);
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    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);
 | 
						|
 | 
						|
  /*
 | 
						|
  ===========================================================================
 | 
						|
  DEBUG code only (begin)
 | 
						|
  Simulate various output from vio_getnameinfo().
 | 
						|
  ===========================================================================
 | 
						|
  */
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getnameinfo_error_noname",
 | 
						|
                  {
 | 
						|
                    safe_strcpy(hostname_buffer, sizeof(hostname_buffer),
 | 
						|
                                "<garbage>");
 | 
						|
                    err_code= EAI_NONAME;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getnameinfo_error_again",
 | 
						|
                  {
 | 
						|
                    safe_strcpy(hostname_buffer, sizeof(hostname_buffer),
 | 
						|
                                "<garbage>");
 | 
						|
                    err_code= EAI_AGAIN;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getnameinfo_fake_ipv4",
 | 
						|
                  {
 | 
						|
                    safe_strcpy(hostname_buffer, sizeof(hostname_buffer),
 | 
						|
                                "santa.claus.ipv4.example.com");
 | 
						|
                    err_code= 0;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getnameinfo_fake_ipv6",
 | 
						|
                  {
 | 
						|
                    safe_strcpy(hostname_buffer, sizeof(hostname_buffer),
 | 
						|
                                "santa.claus.ipv6.example.com");
 | 
						|
                    err_code= 0;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getnameinfo_format_ipv4",
 | 
						|
                  {
 | 
						|
                    safe_strcpy(hostname_buffer, sizeof(hostname_buffer),
 | 
						|
                                "12.12.12.12");
 | 
						|
                    err_code= 0;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getnameinfo_format_ipv6",
 | 
						|
                  {
 | 
						|
                    safe_strcpy(hostname_buffer, sizeof(hostname_buffer),
 | 
						|
                                "12:DEAD:BEEF:0");
 | 
						|
                    err_code= 0;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getnameinfo_fake_long_host",
 | 
						|
                  {
 | 
						|
                    strcpy(hostname_buffer, "host5678901_345678902_345678903_345678904_345678905_345678906_345678907_345678908_345678909_345678910_345678911_345678912_345678913_345678914_345678915_345678916_345678917_345678918_345678919_345678920_345678921_345678922_345678923_345678924_345678925_345");
 | 
						|
                    err_code= 0;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  /*
 | 
						|
  ===========================================================================
 | 
						|
  DEBUG code only (end)
 | 
						|
  ===========================================================================
 | 
						|
  */
 | 
						|
 | 
						|
  if (err_code)
 | 
						|
  {
 | 
						|
    // NOTE: gai_strerror() returns a string ending by a dot.
 | 
						|
 | 
						|
    DBUG_PRINT("error", ("IP address '%s' could not be resolved: %s",
 | 
						|
                         (const char *) ip_key,
 | 
						|
                         (const char *) gai_strerror(err_code)));
 | 
						|
 | 
						|
    sql_print_warning("IP address '%s' could not be resolved: %s",
 | 
						|
                      (const char *) ip_key,
 | 
						|
                      (const char *) gai_strerror(err_code));
 | 
						|
 | 
						|
    bool validated;
 | 
						|
    if (vio_is_no_name_error(err_code))
 | 
						|
    {
 | 
						|
      /*
 | 
						|
        The no-name error means that there is no reverse address mapping
 | 
						|
        for the IP address. A host name can not be resolved.
 | 
						|
      */
 | 
						|
      errors.m_nameinfo_permanent= 1;
 | 
						|
      validated= true;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      /*
 | 
						|
        If it is not the no-name error, we should not cache the hostname
 | 
						|
        (or rather its absence), because the failure might be transient.
 | 
						|
        Only the ip error statistics are cached.
 | 
						|
      */
 | 
						|
      errors.m_nameinfo_transient= 1;
 | 
						|
      validated= false;
 | 
						|
    }
 | 
						|
    add_hostname(ip_key, NULL, validated, &errors);
 | 
						|
 | 
						|
    DBUG_RETURN(0);
 | 
						|
  }
 | 
						|
 | 
						|
  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))
 | 
						|
  {
 | 
						|
    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);
 | 
						|
 | 
						|
    errors.m_format= 1;
 | 
						|
    add_hostname(ip_key, hostname_buffer, false, &errors);
 | 
						|
 | 
						|
    DBUG_RETURN(false);
 | 
						|
  }
 | 
						|
 | 
						|
  /* Get IP-addresses for the resolved host name (FCrDNS technique). */
 | 
						|
 | 
						|
  struct addrinfo hints;
 | 
						|
  struct addrinfo *addr_info_list;
 | 
						|
  /*
 | 
						|
    Makes fault injection with DBUG_EXECUTE_IF easier.
 | 
						|
    Invoking free_addr_info(NULL) crashes on some platforms.
 | 
						|
  */
 | 
						|
  bool free_addr_info_list= false;
 | 
						|
 | 
						|
  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 == 0)
 | 
						|
    free_addr_info_list= true;
 | 
						|
 | 
						|
  /*
 | 
						|
  ===========================================================================
 | 
						|
  DEBUG code only (begin)
 | 
						|
  Simulate various output from getaddrinfo().
 | 
						|
  ===========================================================================
 | 
						|
  */
 | 
						|
  DBUG_EXECUTE_IF("getaddrinfo_error_noname",
 | 
						|
                  {
 | 
						|
                    if (free_addr_info_list)
 | 
						|
                      freeaddrinfo(addr_info_list);
 | 
						|
 | 
						|
                    addr_info_list= NULL;
 | 
						|
                    err_code= EAI_NONAME;
 | 
						|
                    free_addr_info_list= false;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getaddrinfo_error_again",
 | 
						|
                  {
 | 
						|
                    if (free_addr_info_list)
 | 
						|
                      freeaddrinfo(addr_info_list);
 | 
						|
 | 
						|
                    addr_info_list= NULL;
 | 
						|
                    err_code= EAI_AGAIN;
 | 
						|
                    free_addr_info_list= false;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getaddrinfo_fake_bad_ipv4",
 | 
						|
                  {
 | 
						|
                    if (free_addr_info_list)
 | 
						|
                      freeaddrinfo(addr_info_list);
 | 
						|
 | 
						|
                    struct sockaddr_in *debug_addr;
 | 
						|
                    /*
 | 
						|
                      Not thread safe, which is ok.
 | 
						|
                      Only one connection at a time is tested with
 | 
						|
                      fault injection.
 | 
						|
                    */
 | 
						|
                    static struct sockaddr_in debug_sock_addr[2];
 | 
						|
                    static struct addrinfo debug_addr_info[2];
 | 
						|
                    /* Simulating ipv4 192.0.2.126 */
 | 
						|
                    debug_addr= & debug_sock_addr[0];
 | 
						|
                    debug_addr->sin_family= AF_INET;
 | 
						|
                    inet_pton(AF_INET,"192.0.2.126", &debug_addr->sin_addr);
 | 
						|
 | 
						|
                    /* Simulating ipv4 192.0.2.127 */
 | 
						|
                    debug_addr= & debug_sock_addr[1];
 | 
						|
                    debug_addr->sin_family= AF_INET;
 | 
						|
                    inet_pton(AF_INET,"192.0.2.127", &debug_addr->sin_addr);
 | 
						|
 | 
						|
                    debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0];
 | 
						|
                    debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in);
 | 
						|
                    debug_addr_info[0].ai_next= & debug_addr_info[1];
 | 
						|
 | 
						|
                    debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1];
 | 
						|
                    debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in);
 | 
						|
                    debug_addr_info[1].ai_next= NULL;
 | 
						|
 | 
						|
                    addr_info_list= & debug_addr_info[0];
 | 
						|
                    err_code= 0;
 | 
						|
                    free_addr_info_list= false;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getaddrinfo_fake_good_ipv4",
 | 
						|
                  {
 | 
						|
                    if (free_addr_info_list)
 | 
						|
                      freeaddrinfo(addr_info_list);
 | 
						|
 | 
						|
                    struct sockaddr_in *debug_addr;
 | 
						|
                    static struct sockaddr_in debug_sock_addr[2];
 | 
						|
                    static struct addrinfo debug_addr_info[2];
 | 
						|
                    /* Simulating ipv4 192.0.2.5 */
 | 
						|
                    debug_addr= & debug_sock_addr[0];
 | 
						|
                    debug_addr->sin_family= AF_INET;
 | 
						|
                    inet_pton(AF_INET,"192.0.2.5", &debug_addr->sin_addr);
 | 
						|
 | 
						|
                    /* Simulating ipv4 192.0.2.4 */
 | 
						|
                    debug_addr= & debug_sock_addr[1];
 | 
						|
                    debug_addr->sin_family= AF_INET;
 | 
						|
                    inet_pton(AF_INET,"192.0.2.4", &debug_addr->sin_addr);
 | 
						|
 | 
						|
                    debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0];
 | 
						|
                    debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in);
 | 
						|
                    debug_addr_info[0].ai_next= & debug_addr_info[1];
 | 
						|
 | 
						|
                    debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1];
 | 
						|
                    debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in);
 | 
						|
                    debug_addr_info[1].ai_next= NULL;
 | 
						|
 | 
						|
                    addr_info_list= & debug_addr_info[0];
 | 
						|
                    err_code= 0;
 | 
						|
                    free_addr_info_list= false;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
#ifdef HAVE_IPV6
 | 
						|
  DBUG_EXECUTE_IF("getaddrinfo_fake_bad_ipv6",
 | 
						|
                  {
 | 
						|
                    if (free_addr_info_list)
 | 
						|
                      freeaddrinfo(addr_info_list);
 | 
						|
 | 
						|
                    struct sockaddr_in6 *debug_addr;
 | 
						|
                    struct in6_addr *ip6;
 | 
						|
                    /*
 | 
						|
                      Not thread safe, which is ok.
 | 
						|
                      Only one connection at a time is tested with
 | 
						|
                      fault injection.
 | 
						|
                    */
 | 
						|
                    static struct sockaddr_in6 debug_sock_addr[2];
 | 
						|
                    static struct addrinfo debug_addr_info[2];
 | 
						|
                    /* Simulating ipv6 2001:DB8::6:7E */
 | 
						|
                    debug_addr= & debug_sock_addr[0];
 | 
						|
                    debug_addr->sin6_family= AF_INET6;
 | 
						|
                    ip6= & debug_addr->sin6_addr;
 | 
						|
                    inet_pton(AF_INET6,"2001:DB8::6:7E",ip6);
 | 
						|
 | 
						|
                    /* Simulating ipv6 2001:DB8::6:7F */
 | 
						|
                    debug_addr= & debug_sock_addr[1];
 | 
						|
                    debug_addr->sin6_family= AF_INET6;
 | 
						|
                    ip6= & debug_addr->sin6_addr;
 | 
						|
                    inet_pton(AF_INET6,"2001:DB8::6:7F",ip6);
 | 
						|
 | 
						|
                    debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0];
 | 
						|
                    debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in6);
 | 
						|
                    debug_addr_info[0].ai_next= & debug_addr_info[1];
 | 
						|
 | 
						|
                    debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1];
 | 
						|
                    debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in6);
 | 
						|
                    debug_addr_info[1].ai_next= NULL;
 | 
						|
 | 
						|
                    addr_info_list= & debug_addr_info[0];
 | 
						|
                    err_code= 0;
 | 
						|
                    free_addr_info_list= false;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("getaddrinfo_fake_good_ipv6",
 | 
						|
                  {
 | 
						|
                    if (free_addr_info_list)
 | 
						|
                      freeaddrinfo(addr_info_list);
 | 
						|
 | 
						|
                    struct sockaddr_in6 *debug_addr;
 | 
						|
                    struct in6_addr *ip6;
 | 
						|
                    /*
 | 
						|
                      Not thread safe, which is ok.
 | 
						|
                      Only one connection at a time is tested with
 | 
						|
                      fault injection.
 | 
						|
                    */
 | 
						|
                    static struct sockaddr_in6 debug_sock_addr[2];
 | 
						|
                    static struct addrinfo debug_addr_info[2];
 | 
						|
                    /* Simulating ipv6 2001:DB8::6:7 */
 | 
						|
                    debug_addr= & debug_sock_addr[0];
 | 
						|
                    debug_addr->sin6_family= AF_INET6;
 | 
						|
                    ip6= & debug_addr->sin6_addr;
 | 
						|
                    ip6->s6_addr[ 0] = 0x20;
 | 
						|
                    ip6->s6_addr[ 1] = 0x01;
 | 
						|
                    ip6->s6_addr[ 2] = 0x0d;
 | 
						|
                    ip6->s6_addr[ 3] = 0xb8;
 | 
						|
                    ip6->s6_addr[ 4] = 0x00;
 | 
						|
                    ip6->s6_addr[ 5] = 0x00;
 | 
						|
                    ip6->s6_addr[ 6] = 0x00;
 | 
						|
                    ip6->s6_addr[ 7] = 0x00;
 | 
						|
                    ip6->s6_addr[ 8] = 0x00;
 | 
						|
                    ip6->s6_addr[ 9] = 0x00;
 | 
						|
                    ip6->s6_addr[10] = 0x00;
 | 
						|
                    ip6->s6_addr[11] = 0x00;
 | 
						|
                    ip6->s6_addr[12] = 0x00;
 | 
						|
                    ip6->s6_addr[13] = 0x06;
 | 
						|
                    ip6->s6_addr[14] = 0x00;
 | 
						|
                    ip6->s6_addr[15] = 0x07;
 | 
						|
 | 
						|
                    /* Simulating ipv6 2001:DB8::6:6 */
 | 
						|
                    debug_addr= & debug_sock_addr[1];
 | 
						|
                    debug_addr->sin6_family= AF_INET6;
 | 
						|
                    ip6= & debug_addr->sin6_addr;
 | 
						|
                    ip6->s6_addr[ 0] = 0x20;
 | 
						|
                    ip6->s6_addr[ 1] = 0x01;
 | 
						|
                    ip6->s6_addr[ 2] = 0x0d;
 | 
						|
                    ip6->s6_addr[ 3] = 0xb8;
 | 
						|
                    ip6->s6_addr[ 4] = 0x00;
 | 
						|
                    ip6->s6_addr[ 5] = 0x00;
 | 
						|
                    ip6->s6_addr[ 6] = 0x00;
 | 
						|
                    ip6->s6_addr[ 7] = 0x00;
 | 
						|
                    ip6->s6_addr[ 8] = 0x00;
 | 
						|
                    ip6->s6_addr[ 9] = 0x00;
 | 
						|
                    ip6->s6_addr[10] = 0x00;
 | 
						|
                    ip6->s6_addr[11] = 0x00;
 | 
						|
                    ip6->s6_addr[12] = 0x00;
 | 
						|
                    ip6->s6_addr[13] = 0x06;
 | 
						|
                    ip6->s6_addr[14] = 0x00;
 | 
						|
                    ip6->s6_addr[15] = 0x06;
 | 
						|
 | 
						|
                    debug_addr_info[0].ai_addr= (struct sockaddr*) & debug_sock_addr[0];
 | 
						|
                    debug_addr_info[0].ai_addrlen= sizeof (struct sockaddr_in6);
 | 
						|
                    debug_addr_info[0].ai_next= & debug_addr_info[1];
 | 
						|
 | 
						|
                    debug_addr_info[1].ai_addr= (struct sockaddr*) & debug_sock_addr[1];
 | 
						|
                    debug_addr_info[1].ai_addrlen= sizeof (struct sockaddr_in6);
 | 
						|
                    debug_addr_info[1].ai_next= NULL;
 | 
						|
 | 
						|
                    addr_info_list= & debug_addr_info[0];
 | 
						|
                    err_code= 0;
 | 
						|
                    free_addr_info_list= false;
 | 
						|
                  }
 | 
						|
                  );
 | 
						|
#endif /* HAVE_IPV6 */
 | 
						|
 | 
						|
  /*
 | 
						|
  ===========================================================================
 | 
						|
  DEBUG code only (end)
 | 
						|
  ===========================================================================
 | 
						|
  */
 | 
						|
 | 
						|
  if (err_code != 0)
 | 
						|
  {
 | 
						|
    sql_print_warning("Host name '%s' could not be resolved: %s",
 | 
						|
                      (const char *) hostname_buffer,
 | 
						|
                      (const char *) gai_strerror(err_code));
 | 
						|
 | 
						|
    bool validated;
 | 
						|
 | 
						|
    if (err_code == EAI_NONAME)
 | 
						|
    {
 | 
						|
      errors.m_addrinfo_permanent= 1;
 | 
						|
      validated= true;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      /*
 | 
						|
        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.
 | 
						|
        Only cache error statistics.
 | 
						|
      */
 | 
						|
      errors.m_addrinfo_transient= 1;
 | 
						|
      validated= false;
 | 
						|
    }
 | 
						|
    add_hostname(ip_key, NULL, validated, &errors);
 | 
						|
 | 
						|
    DBUG_RETURN(false);
 | 
						|
  }
 | 
						|
 | 
						|
  /* 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 ip_buffer[HOST_ENTRY_KEY_SIZE];
 | 
						|
 | 
						|
    {
 | 
						|
      err_status=
 | 
						|
        vio_get_normalized_ip_string(addr_info->ai_addr, (int)addr_info->ai_addrlen,
 | 
						|
                                     ip_buffer, sizeof (ip_buffer));
 | 
						|
      DBUG_ASSERT(!err_status);
 | 
						|
    }
 | 
						|
 | 
						|
    DBUG_PRINT("info", ("  - '%s'", (const char *) ip_buffer));
 | 
						|
 | 
						|
    if (strcasecmp(ip_key, ip_buffer) == 0)
 | 
						|
    {
 | 
						|
      /* Copy host name string to be stored in the cache. */
 | 
						|
 | 
						|
      *hostname= my_strdup(key_memory_host_cache_hostname,
 | 
						|
                           hostname_buffer, MYF(0));
 | 
						|
 | 
						|
      if (!*hostname)
 | 
						|
      {
 | 
						|
        DBUG_PRINT("error", ("Out of memory."));
 | 
						|
 | 
						|
        if (free_addr_info_list)
 | 
						|
          freeaddrinfo(addr_info_list);
 | 
						|
        DBUG_RETURN(true);
 | 
						|
      }
 | 
						|
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* Log resolved IP-addresses if no match was found. */
 | 
						|
 | 
						|
  if (!*hostname)
 | 
						|
  {
 | 
						|
    errors.m_FCrDNS= 1;
 | 
						|
 | 
						|
    sql_print_warning("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)
 | 
						|
    {
 | 
						|
      char ip_buffer[HOST_ENTRY_KEY_SIZE];
 | 
						|
 | 
						|
      err_status=
 | 
						|
        vio_get_normalized_ip_string(addr_info->ai_addr, (int)addr_info->ai_addrlen,
 | 
						|
                                     ip_buffer, sizeof (ip_buffer));
 | 
						|
      DBUG_ASSERT(!err_status);
 | 
						|
 | 
						|
      sql_print_information(" - %s", (const char *) ip_buffer);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* Add an entry for the IP to the cache. */
 | 
						|
  add_hostname(ip_key, *hostname, true, &errors);
 | 
						|
 | 
						|
  /* Free the result of getaddrinfo(). */
 | 
						|
  if (free_addr_info_list)
 | 
						|
    freeaddrinfo(addr_info_list);
 | 
						|
 | 
						|
  DBUG_RETURN(false);
 | 
						|
}
 |