Skip to content

File host_lookup.c

File List > connection > host_lookup.c

Go to the documentation of this file

/*
 *
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * host_lookup.c
 */

#ident "$Id$"

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <unordered_map>

#if defined (WINDOWS)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <io.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>
#endif

#include "host_lookup.h"
#include "porting.h"
#include "system_parameter.h"
#include "environment_variable.h"
#include "message_catalog.h"
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"

#define LINE_BUF_SIZE                (512)
#define HOSTNAME_LEN                 (256)
#define MAX_NUM_HOSTS                (256)
#define IPADDR_LEN                   (17)
#define MTIME_DIGITS                 (10)
#define IPv4_ADDR_LEN                (4)
#define NUM_IPADDR_DOT               (3)
#define MAX_NUM_IPADDR_PER_HOST      (1)

#define NUM_DIGIT(VAL)              (size_t)(log10 (VAL) + 1)

#if !defined (MALLOC)
#define MALLOC(SIZE)            malloc(SIZE)
#define FREE_MEM(PTR)           \
        do {                    \
          if (PTR) {            \
            free(PTR);          \
            PTR = 0;            \
          }                     \
        } while (0)
#endif

#define CUBRID_HOSTS_CONF            "cubrid_hosts.conf"
#define MAX_FQDN_LEN                 (255)
#define MAX_LABEL_LEN                (63)

#define ADD_ENTRY_SUCCESS            0  // Successfully added an entry
#define DUPLICATE_ENTRY              1  // Entry is a duplicate
#define ERROR_ALLOC                 -1  // Memory allocation error or general failure

#define UHOST_CONF_VALID_CHECK       1
#define UHOST_CONF_LOAD              2

typedef enum
{
  HOSTNAME_TO_IPADDR = 0,
  IPADDR_TO_HOSTNAME = 1,
} LOOKUP_TYPE;

typedef enum
{
  INSERT_IPADDR = 0,
  INSERT_HOSTNAME = 1,
} HOSTENT_INSERT_TYPE;

typedef enum
{
  USE_GLIBC_HOSTS = 0,
  USE_USER_DEFINED_HOSTS = 1,
} HOST_LOOKUP_TYPE;

typedef enum
{
  LOAD_FAIL = -1,
  LOAD_INIT,
  LOAD_SUCCESS,
} HOSTS_CONF_LOAD_STATUS;

static struct hostent *hostent_Cache[MAX_NUM_HOSTS];


// *INDENT-OFF*
static std::unordered_map <std::string, int> user_host_Map;
// *INDENT-ON*

static struct hostent *hostent_alloc (const char *ipaddr, const char *hostname);
static int load_hosts_file (void);
static struct hostent *host_lookup_internal (const char *hostname, struct sockaddr *saddr, LOOKUP_TYPE lookup_type);
static void strcpy_ucase (char *dst, size_t len, const char *src);
static bool is_valid_ipv4 (const char *ip_addr);
static int is_valid_label (const char *label);
static int is_valid_fqdn (const char *fqdn);
static int add_user_host_map (const char *ipaddr, const char *hostname, int cache_idx);
static bool handle_uhost_conf (int action_type);

/*
 * hostent_alloc () - Allocate memory hostent structure.
 * 
 * return        : The hostent pointer.
 * ipaddr (in)   : Elements of hostent.
 * hostname (in) : Elements of hostent.
 */
static struct hostent *
hostent_alloc (const char *ipaddr, const char *hostname)
{
  struct hostent *hp = NULL;
  char addr_trans_bi_buf[sizeof (struct in_addr)];

  if ((hp = (struct hostent *) MALLOC (sizeof (struct hostent))) == NULL)
    {
      goto return_phase;
    }

  hp->h_addrtype = AF_INET;
  hp->h_length = IPv4_ADDR_LEN;

  if (inet_pton (AF_INET, ipaddr, addr_trans_bi_buf) < 1)
    {
      FREE_MEM (hp);
      goto return_phase;
    }

  hp->h_name = strdup (hostname);
  hp->h_aliases = NULL;

  if ((hp->h_addr_list = (char **) MALLOC (sizeof (char *) * MAX_NUM_IPADDR_PER_HOST)) == NULL)
    {
      FREE_MEM (hp->h_name);
      FREE_MEM (hp);
      goto return_phase;
    }

  if ((hp->h_addr_list[0] = (char *) MALLOC (sizeof (char) * IPv4_ADDR_LEN)) == NULL)
    {
      FREE_MEM (hp->h_addr_list);
      FREE_MEM (hp->h_name);
      FREE_MEM (hp);
      goto return_phase;
    }

  memcpy (hp->h_addr, addr_trans_bi_buf, IPv4_ADDR_LEN);

  return hp;

return_phase:

  return NULL;
}

/*
 * host_lookup_internal () - Look up the hostname or ip address in the hostent_Cache.
 * 
 * return         : The hostent pointer.
 * hostname(in)   : The host name to look up IP address.
 * saddr(in)      : The IP address to look up host name.
 * lookup_type(in): The macro to choose look up mode.
 *  
 *  Note: If lookup_type is HOSTNAME_TO_IPADDR, saddr is NULL. hostname is NULL otherwise.
 */
static struct hostent *
host_lookup_internal (const char *hostname, struct sockaddr *saddr, LOOKUP_TYPE lookup_type)
{
  static int hosts_conf_file_Load = LOAD_INIT;
  static pthread_mutex_t load_hosts_file_lock = PTHREAD_MUTEX_INITIALIZER;

  char addr_trans_ch_buf[IPADDR_LEN];
  struct sockaddr_in *addr_trans = NULL;
  char hostname_u[HOSTNAME_LEN];

  if (hosts_conf_file_Load == LOAD_INIT)
    {
      pthread_mutex_lock (&load_hosts_file_lock);
      if (hosts_conf_file_Load == LOAD_INIT)
    {
      hosts_conf_file_Load = load_hosts_file ();
    }
      pthread_mutex_unlock (&load_hosts_file_lock);
    }
  if (hosts_conf_file_Load == LOAD_FAIL)
    {
      return NULL;
    }

  addr_trans = (struct sockaddr_in *) saddr;

  assert (((hostname != NULL) && (saddr == NULL)) || ((hostname == NULL) && (saddr != NULL)));

  if (lookup_type == IPADDR_TO_HOSTNAME)
    {
      if (inet_ntop (AF_INET, &addr_trans->sin_addr, addr_trans_ch_buf, sizeof (addr_trans_ch_buf)) == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_GENERIC_ERROR, 0);
      return NULL;
    }
    }

  strcpy_ucase (hostname_u, (size_t) HOSTNAME_LEN, hostname);

  /* Look up in the user_host_Map */
  /* The case which is looking up the IP addr and checking the hostname or IP addr in the hash map */
  if ((lookup_type == HOSTNAME_TO_IPADDR) && (user_host_Map.find (hostname_u) != user_host_Map.end ()))
    {
      return hostent_Cache[user_host_Map.find (hostname_u)->second];
    }
  else if ((lookup_type == IPADDR_TO_HOSTNAME) && (user_host_Map.find (addr_trans_ch_buf) != user_host_Map.end ()))
    {
      return hostent_Cache[user_host_Map.find (addr_trans_ch_buf)->second];
    }

  /*Hostname and IP addr cannot be found */
  return NULL;
}

/*
 * gethostbyname_uhost () - Do same job with gethostbyname (), using by the 'user' defined 'cubrid_hosts.conf' file or glibc.
 * 
 * return   : the hostent pointer.
 *
 *  Note: The parameters are same with glibc parameters.
 */
struct hostent *
gethostbyname_uhost (const char *name)
{
  static struct hostent *hp = NULL;

  if (prm_get_bool_value (PRM_ID_USE_USER_HOSTS) == USE_GLIBC_HOSTS)
    {
      // TODO : Not thread-safe function
      hp = gethostbyname (name);
    }
  else
    {
      hp = host_lookup_internal (name, NULL, HOSTNAME_TO_IPADDR);
    }

  return hp;
}

/*
 * gethostbyname_r_uhost () - Do same job with gethostbyname_r (), using by the 'user' defined 'cubrid_hosts.conf' file or glibc.
 * 
 *  Note: The parameters are same with glibc parameters.
 */
#ifdef HAVE_GETHOSTBYNAME_R
#if defined (HAVE_GETHOSTBYNAME_R_GLIBC)
int
gethostbyname_r_uhost (const char *name,
               struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop)
#elif defined (HAVE_GETHOSTBYNAME_R_SOLARIS)
struct hostent *
gethostbyname_r_uhost (const char *name, struct hostent *ret, char *buf, size_t buflen, int *h_errnop)
#elif defined (HAVE_GETHOSTBYNAME_R_HOSTENT_DATA)
int
gethostbyname_r_uhost (const char *name, struct hostent *ret, struct hostent_data *ht_data)
#else
#error "HAVE_GETHOSTBYNAME_R"
#endif /* HAVE_GETHOSTBYNAME_R_GLIBC */
#else /* WINDOWS */
int
gethostbyname_r_uhost (const char *name,
               struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop)
#endif              /* HAVE_GETHOSTBYNAME_R */
{
#if defined (WINDOWS)
  int ret_val = 0;
  return 0;
#endif

  struct hostent *hp_buf = NULL;
#ifdef HAVE_GETHOSTBYNAME_R
#if defined (HAVE_GETHOSTBYNAME_R_GLIBC)
  int ret_val = 0;
#elif defined (HAVE_GETHOSTBYNAME_R_SOLARIS)
  struct hostent *ret_val = NULL;
#elif defined (HAVE_GETHOSTBYNAME_R_HOSTENT_DATA)
  int ret_val = 0;
#else
#error "HAVE_GETHOSTBYNAME_R"
#endif
#endif

  if (prm_get_bool_value (PRM_ID_USE_USER_HOSTS) == USE_GLIBC_HOSTS)
    {
#ifdef HAVE_GETHOSTBYNAME_R
#if defined (HAVE_GETHOSTBYNAME_R_GLIBC)
      ret_val = gethostbyname_r (name, ret, buf, buflen, result, h_errnop);
#elif defined (HAVE_GETHOSTBYNAME_R_SOLARIS)
      ret_val = gethostbyname_r (name, ret, buf, buflen, h_errnop);
#elif defined (HAVE_GETHOSTBYNAME_R_HOSTENT_DATA)
      ret_val = gethostbyname_r (name, ret, &ht_data);
#else
#error "HAVE_GETHOSTBYNAME_R"
#endif
#endif
      goto return_phase;
    }
  else
    {
      hp_buf = host_lookup_internal (name, NULL, HOSTNAME_TO_IPADDR);
    }

#ifdef HAVE_GETHOSTBYNAME_R
#if defined (HAVE_GETHOSTBYNAME_R_GLIBC)
  if (hp_buf == NULL)
    {
      ret_val = EINVAL;

      goto return_phase;
    }

  memcpy ((void *) ret, (void *) hp_buf, sizeof (struct hostent));
  *result = hp_buf;

  ret_val = 0;
#elif defined (HAVE_GETHOSTBYNAME_R_SOLARIS)
  if (hp_buf == NULL)
    {
      ret_val = NULL;

      goto return_phase;
    }

  memcpy ((void *) ret, (void *) hp_buf, sizeof (struct hostent));

  ret_val = ret;
#elif defined (HAVE_GETHOSTBYNAME_R_HOSTENT_DATA)
  if (hp_buf == NULL)
    {
      ret_val = -1;

      goto return_phase;
    }

  memcpy ((void *) ret, (void *) hp_buf, sizeof (struct hostent));

  ret_val = 0;
#else
#error "HAVE_GETHOSTBYNAME_R"
#endif /* HAVE_GETHOSTBYNAME_R_GLIBC */
#endif /* HAVE_GETHOSTBYNAME_R */

return_phase:
  return ret_val;
}

/*
 * getnameinfo_uhost () - Do same job with getnameinfo (), using by the 'user' defined 'cubrid_hosts.conf' file or glibc.
 * 
 * return   : 0, if successful, Error otherwise.
 *
 *  Note: The parameters are same with glibc parameters.
 */
int
getnameinfo_uhost (struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *serv, size_t servlen,
           int flags)
{
  int ret;
  struct hostent *hp = NULL;

  if (prm_get_bool_value (PRM_ID_USE_USER_HOSTS) == USE_GLIBC_HOSTS)
    {
      ret = getnameinfo (addr, addrlen, host, hostlen, serv, servlen, flags);
    }
  else
    {
      if ((hp = host_lookup_internal (NULL, addr, IPADDR_TO_HOSTNAME)) != NULL)
    {
      strncpy (host, hp->h_name, hostlen);
      host[hostlen] = '\0';

      ret = 0;
    }
      else
    {
      ret = EAI_NONAME;
    }
    }
  return ret;
}

/*
 * getaddrinfo_uhost () - Do same job with getaddrinfo (), using by the 'user' defined 'cubrid_hosts.conf' file or glibc.
 * 
 * return   : 0, if successful, Error otherwise.
 *
 *  Note: The parameters are same with glibc parameters.
 */
int
getaddrinfo_uhost (char *node, char *service, struct addrinfo *hints, struct addrinfo **res)
{
  int ret = 0;
  struct hostent *hp = NULL;
  struct addrinfo *addrp = NULL;

  if (prm_get_bool_value (PRM_ID_USE_USER_HOSTS) == USE_GLIBC_HOSTS)
    {
      ret = getaddrinfo (node, service, hints, res);
      goto return_phase;
    }
  else
    {
      hp = host_lookup_internal (node, NULL, HOSTNAME_TO_IPADDR);
    }

  if (hp == NULL)
    {
      ret = EAI_NONAME;
      goto return_phase;
    }

  if ((addrp = (struct addrinfo *) MALLOC (sizeof (struct addrinfo))) == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (struct addrinfo));
      ret = EAI_MEMORY;
      goto return_phase;
    }

  memset (addrp, 0, sizeof (addrinfo));
  if ((addrp->ai_canonname = strdup (hp->h_name)) == NULL)
    {
      freeaddrinfo_uhost (addrp);
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (struct sockaddr));
      ret = EAI_MEMORY;
      goto return_phase;
    }

  if (hints != NULL)
    {
      addrp->ai_family = hints->ai_family;
    }
  else
    {
      addrp->ai_family = AF_UNSPEC;
    }

  *res = addrp;

return_phase:

  return ret;
}

void
freeaddrinfo_uhost (struct addrinfo *res)
{
  if (prm_get_bool_value (PRM_ID_USE_USER_HOSTS) == USE_GLIBC_HOSTS)
    {
      return (freeaddrinfo (res));
    }

  if (res)
    {
      FREE_MEM (res->ai_canonname);
      FREE_MEM (res);
    }
  return;
}

static void
strcpy_ucase (char *dst, size_t len, const char *src)
{
  size_t i;

  if (dst == NULL || src == NULL || len == 0)
    {
      return;
    }

  for (i = 0; i < len - 1; i++)
    {
      dst[i] = (char) toupper (src[i]);
    }
  dst[i] = '\0';

  return;
}


/*
* is_valid_ip () - Check the ipv4 IP address format.
* 
* return   : true if IP address is valid format, false otherwise
*/
static bool
is_valid_ipv4 (const char *ip_addr)
{
  struct sockaddr_in sa;

  if (ip_addr == NULL || *ip_addr == '\0')
    {
      return false;
    }

  return inet_pton (AF_INET, ip_addr, &(sa.sin_addr)) == 1;
}


/*
* is_valid_hostname () - Check the host name is valid format.
*
* return   : true if host name is valid format, false otherwise
*/
static int
is_valid_label (const char *label)
{
  int length = 0;

  if (label == NULL || *label == '\0')
    {
      return 0;
    }

  length = strlen (label);
  if (length > MAX_LABEL_LEN)
    {
      return 0;
    }

  if (!isalpha (label[0]) || !isalnum (label[length - 1]))
    {
      return 0;
    }

  for (int i = 0; i < length; i++)
    {
      if (!isalnum (label[i]) && label[i] != '-')
    {
      return 0;
    }
    }

  return 1;
}

static int
is_valid_fqdn (const char *fqdn)
{
  char label[MAX_LABEL_LEN + 1];
  int label_len = 0;
  int length = 0;

  if (fqdn == NULL || *fqdn == '\0')
    {
      return 0;
    }

  length = strlen (fqdn);

  if (length > MAX_FQDN_LEN)
    {
      return 0;
    }

  for (int i = 0; i < length; i++)
    {
      if (fqdn[i] == '.')
    {
      label[label_len] = '\0';
      if (!is_valid_label (label))
        {
          return 0;
        }
      label_len = 0;
    }
      else
    {
      if (label_len >= MAX_LABEL_LEN)
        {
          return 0;
        }
      label[label_len++] = fqdn[i];
    }
    }

  label[label_len] = '\0';
  return is_valid_label (label);
}

bool
validate_uhost_conf (void)
{
  return handle_uhost_conf (UHOST_CONF_VALID_CHECK);
}

/*
* load_hosts_file () - Load the cubrid_host.conf and be ready for using user_host_Map and hostent_Cache.
* 
* return   : The result of cubrid_host.conf loading.
*/
static int
load_hosts_file (void)
{
  return handle_uhost_conf (UHOST_CONF_LOAD) ? LOAD_SUCCESS : LOAD_FAIL;
}

static bool
handle_uhost_conf (int action_type)
{
  FILE *file = NULL;
  char line_buf[LINE_BUF_SIZE + 1];
  char *hosts_conf_dir;
  char host_conf_file_full_path[PATH_MAX];
  char ipaddr[IPADDR_LEN];
  char hostname[HOSTNAME_LEN];
  char *save_ptr_strtok;
  int line_number = 0;
  bool is_empty_hostinfo = true;
  int cache_idx = 0;
  bool has_invalid_entries = false;

  hosts_conf_dir = envvar_confdir_file (host_conf_file_full_path, PATH_MAX, CUBRID_HOSTS_CONF);

  file = fopen (hosts_conf_dir, "r");
  if (!file)
    {
      fprintf (stderr,
           msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_PARAMETERS, PRM_ERR_FILE_NOT_FOUND),
           host_conf_file_full_path);
      return false;
    }

  while (fgets (line_buf, sizeof (line_buf), file))
    {
      char *hash_pos;

      line_number++;

      hash_pos = strchr (line_buf, '#');
      if (hash_pos != NULL)
    {
      *hash_pos = '\0';
    }

      trim (line_buf);

      if (line_buf[0] == '\0')
    {
      continue;
    }

      is_empty_hostinfo = false;

      char *token = strtok_r (line_buf, " \t\n", &save_ptr_strtok);
      if (token != NULL)
    {
      strncpy (ipaddr, token, IPADDR_LEN - 1);
      ipaddr[IPADDR_LEN - 1] = '\0';

      token = strtok_r (NULL, "\n", &save_ptr_strtok);
      if (token != NULL)
        {
          trim (token);
          strcpy_ucase (hostname, (size_t) HOSTNAME_LEN, token);
        }
      else
        {
          // No hostname case
          if (action_type == UHOST_CONF_VALID_CHECK)
        {
          if (!is_valid_ipv4 (ipaddr))
            {
              fprintf (stderr,
                   msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_PARAMETERS,
                           PRM_ERR_INVALID_HOSTNAME), ipaddr, line_number, hosts_conf_dir);
            }
          else
            {
              fprintf (stderr,
                   msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_PARAMETERS,
                           PRM_ERR_INVALID_HOSTNAME), "No Hostname", line_number, hosts_conf_dir);
            }
          has_invalid_entries = true;
          goto end;
        }
          continue;
        }

      // Validate IP address
      if (!is_valid_ipv4 (ipaddr))
        {
          if (action_type == UHOST_CONF_VALID_CHECK)
        {
          fprintf (stderr,
               msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_PARAMETERS,
                       PRM_ERR_INVALID_HOSTNAME), ipaddr, line_number, hosts_conf_dir);
          has_invalid_entries = true;
          goto end;
        }
          continue;
        }

      // Validate hostname
      if (!is_valid_fqdn (hostname))
        {
          if (action_type == UHOST_CONF_VALID_CHECK)
        {
          fprintf (stderr,
               msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_PARAMETERS,
                       PRM_ERR_INVALID_HOSTNAME), hostname, line_number, hosts_conf_dir);
          has_invalid_entries = true;
          goto end;
        }
          continue;
        }

      if (action_type == UHOST_CONF_LOAD)
        {
          // Add valid entries to user_host_Map
          if (hostname[0] && ipaddr[0])
        {
          int result = add_user_host_map (ipaddr, hostname, cache_idx);

          if (result == ADD_ENTRY_SUCCESS)
            {
              cache_idx++;
            }
          else if (result == DUPLICATE_ENTRY)
            {
              continue;
            }
          else if (result == ERROR_ALLOC)
            {
              cache_idx = 0;
              goto end;
            }
        }
        }
    }
    }

  // Check if the host info is empty
  if (action_type == UHOST_CONF_VALID_CHECK && is_empty_hostinfo == true)
    {
      fprintf (stderr,
           msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_PARAMETERS, PRM_ERR_EMPTY_HOSTS_CONF), hosts_conf_dir);
      has_invalid_entries = true;
    }

end:
  if (file)
    {
      fclose (file);
    }

  if (action_type == UHOST_CONF_VALID_CHECK && has_invalid_entries)
    {
      return false;
    }

  if (action_type == UHOST_CONF_LOAD && (cache_idx == 0))
    {
      return false;
    }

  return true;
}

static int
add_user_host_map (const char *ipaddr, const char *hostname, int cache_idx)
{
  /*not duplicated hostname, IP address or not duplicated hostname and duplicated IP address */
  if (user_host_Map.find (hostname) != user_host_Map.end () || user_host_Map.find (ipaddr) != user_host_Map.end ())
    {
      return DUPLICATE_ENTRY;
    }

  user_host_Map[hostname] = cache_idx;
  user_host_Map[ipaddr] = cache_idx;

  if ((hostent_Cache[cache_idx] = hostent_alloc (ipaddr, hostname)) == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_GENERIC_ERROR, 0);
      user_host_Map.clear ();
      return ERROR_ALLOC;
    }

  return ADD_ENTRY_SUCCESS;
}