/* MIT License
 *
 * Copyright (c) 1998 Massachusetts Institute of Technology
 * Copyright (c) 2007 Daniel Stenberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * SPDX-License-Identifier: MIT
 */

#include "ares_private.h"

#ifdef HAVE_SYS_PARAM_H
#  include <sys/param.h>
#endif

#ifdef HAVE_NETINET_IN_H
#  include <netinet/in.h>
#endif

#ifdef HAVE_NETDB_H
#  include <netdb.h>
#endif

#ifdef HAVE_ARPA_INET_H
#  include <arpa/inet.h>
#endif

#include "ares_nameser.h"

#if defined(ANDROID) || defined(__ANDROID__)
#  include <sys/system_properties.h>
#  include "ares_android.h"
/* From the Bionic sources */
#  define DNS_PROP_NAME_PREFIX "net.dns"
#  define MAX_DNS_PROPERTIES   8
#endif

#if defined(CARES_USE_LIBRESOLV)
#  include <resolv.h>
#endif

#if defined(USE_WINSOCK) && defined(HAVE_IPHLPAPI_H)
#  include <iphlpapi.h>
#endif

#include "ares_inet_net_pton.h"
#include "event/ares_event.h"

int ares_init(ares_channel_t **channelptr)
{
  return ares_init_options(channelptr, NULL, 0);
}

static int ares_query_timeout_cmp_cb(const void *arg1, const void *arg2)
{
  const ares_query_t *q1 = arg1;
  const ares_query_t *q2 = arg2;

  if (q1->timeout.sec > q2->timeout.sec) {
    return 1;
  }
  if (q1->timeout.sec < q2->timeout.sec) {
    return -1;
  }

  if (q1->timeout.usec > q2->timeout.usec) {
    return 1;
  }
  if (q1->timeout.usec < q2->timeout.usec) {
    return -1;
  }

  return 0;
}

static int server_sort_cb(const void *data1, const void *data2)
{
  const ares_server_t *s1 = data1;
  const ares_server_t *s2 = data2;

  if (s1->consec_failures < s2->consec_failures) {
    return -1;
  }
  if (s1->consec_failures > s2->consec_failures) {
    return 1;
  }
  if (s1->idx < s2->idx) {
    return -1;
  }
  if (s1->idx > s2->idx) {
    return 1;
  }
  return 0;
}

static void server_destroy_cb(void *data)
{
  if (data == NULL) {
    return; /* LCOV_EXCL_LINE: DefensiveCoding */
  }
  ares_destroy_server(data);
}

static ares_status_t init_by_defaults(ares_channel_t *channel)
{
  char         *hostname = NULL;
  ares_status_t rc       = ARES_SUCCESS;
#ifdef HAVE_GETHOSTNAME
  const char *dot;
#endif
  struct ares_addr addr;
  ares_llist_t    *sconfig = NULL;

  /* Enable EDNS by default */
  if (!(channel->optmask & ARES_OPT_FLAGS)) {
    channel->flags = ARES_FLAG_EDNS;
  }
  if (channel->ednspsz == 0) {
    channel->ednspsz = EDNSPACKETSZ;
  }

  if (channel->timeout == 0) {
    channel->timeout = DEFAULT_TIMEOUT;
  }

  if (channel->tries == 0) {
    channel->tries = DEFAULT_TRIES;
  }

  if (ares_slist_len(channel->servers) == 0) {
    /* Add a default local named server to the channel unless configured not
     * to (in which case return an error).
     */
    if (channel->flags & ARES_FLAG_NO_DFLT_SVR) {
      rc = ARES_ENOSERVER;
      goto error;
    }

    addr.family            = AF_INET;
    addr.addr.addr4.s_addr = htonl(INADDR_LOOPBACK);

    rc = ares_sconfig_append(channel, &sconfig, &addr, 0, 0, NULL);
    if (rc != ARES_SUCCESS) {
      goto error; /* LCOV_EXCL_LINE: OutOfMemory */
    }

    rc = ares_servers_update(channel, sconfig, ARES_FALSE);
    ares_llist_destroy(sconfig);

    if (rc != ARES_SUCCESS) {
      goto error;
    }
  }

  if (channel->ndomains == 0) {
    /* Derive a default domain search list from the kernel hostname,
     * or set it to empty if the hostname isn't helpful.
     */
#ifndef HAVE_GETHOSTNAME
    channel->ndomains = 0; /* default to none */
#else
    size_t len        = 256;
    channel->ndomains = 0; /* default to none */

    hostname = ares_malloc(len);
    if (!hostname) {
      rc = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
      goto error;       /* LCOV_EXCL_LINE: OutOfMemory */
    }

    if (gethostname(hostname, (GETHOSTNAME_TYPE_ARG2)len) != 0) {
      /* Lets not treat a gethostname failure as critical, since we
       * are ok if gethostname doesn't even exist */
      *hostname = '\0';
    }

    dot = strchr(hostname, '.');
    if (dot) {
      /* a dot was found */
      channel->domains = ares_malloc(sizeof(char *));
      if (!channel->domains) {
        rc = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
        goto error;       /* LCOV_EXCL_LINE: OutOfMemory */
      }
      channel->domains[0] = ares_strdup(dot + 1);
      if (!channel->domains[0]) {
        rc = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
        goto error;       /* LCOV_EXCL_LINE: OutOfMemory */
      }
      channel->ndomains = 1;
    }
#endif
  }

  if (channel->nsort == 0) {
    channel->sortlist = NULL;
  }

  if (!channel->lookups) {
    channel->lookups = ares_strdup("fb");
    if (!channel->lookups) {
      rc = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
    }
  }

  /* Set default fields for server failover behavior */
  if (!(channel->optmask & ARES_OPT_SERVER_FAILOVER)) {
    channel->server_retry_chance = DEFAULT_SERVER_RETRY_CHANCE;
    channel->server_retry_delay  = DEFAULT_SERVER_RETRY_DELAY;
  }

error:
  if (hostname) {
    ares_free(hostname);
  }

  return rc;
}

int ares_init_options(ares_channel_t           **channelptr,
                      const struct ares_options *options, int optmask)
{
  ares_channel_t *channel;
  ares_status_t   status = ARES_SUCCESS;

  if (ares_library_initialized() != ARES_SUCCESS) {
    return ARES_ENOTINITIALIZED; /* LCOV_EXCL_LINE: n/a on non-WinSock */
  }

  channel = ares_malloc_zero(sizeof(*channel));
  if (!channel) {
    *channelptr = NULL;
    return ARES_ENOMEM;
  }

  /* We are in a good state */
  channel->sys_up = ARES_TRUE;

  /* One option where zero is valid, so set default value here */
  channel->ndots = 1;

  status = ares_channel_threading_init(channel);
  if (status != ARES_SUCCESS) {
    goto done;
  }

  /* Generate random key */
  channel->rand_state = ares_init_rand_state();
  if (channel->rand_state == NULL) {
    status = ARES_ENOMEM;
    DEBUGF(fprintf(stderr, "Error: init_id_key failed: %s\n",
                   ares_strerror(status)));
    goto done;
  }

  /* Initialize Server List */
  channel->servers =
    ares_slist_create(channel->rand_state, server_sort_cb, server_destroy_cb);
  if (channel->servers == NULL) {
    status = ARES_ENOMEM;
    goto done;
  }

  /* Initialize our lists of queries */
  channel->all_queries = ares_llist_create(NULL);
  if (channel->all_queries == NULL) {
    status = ARES_ENOMEM;
    goto done;
  }

  channel->queries_by_qid = ares_htable_szvp_create(NULL);
  if (channel->queries_by_qid == NULL) {
    status = ARES_ENOMEM;
    goto done;
  }

  channel->queries_by_timeout =
    ares_slist_create(channel->rand_state, ares_query_timeout_cmp_cb, NULL);
  if (channel->queries_by_timeout == NULL) {
    status = ARES_ENOMEM;
    goto done;
  }

  channel->connnode_by_socket = ares_htable_asvp_create(NULL);
  if (channel->connnode_by_socket == NULL) {
    status = ARES_ENOMEM;
    goto done;
  }

  /* Initialize configuration by each of the four sources, from highest
   * precedence to lowest.
   */

  status = ares_init_by_options(channel, options, optmask);
  if (status != ARES_SUCCESS) {
    DEBUGF(fprintf(stderr, "Error: init_by_options failed: %s\n",
                   ares_strerror(status)));
    /* If we fail to apply user-specified options, fail the whole init process
     */
    goto done;
  }

  /* Go ahead and let it initialize the query cache even if the ttl is 0 and
   * completely unused.  This reduces the number of different code paths that
   * might be followed even if there is a minor performance hit. */
  status = ares_qcache_create(channel->rand_state, channel->qcache_max_ttl,
                              &channel->qcache);
  if (status != ARES_SUCCESS) {
    goto done; /* LCOV_EXCL_LINE: OutOfMemory */
  }

  if (status == ARES_SUCCESS) {
    status = ares_init_by_sysconfig(channel);
    if (status != ARES_SUCCESS) {
      DEBUGF(fprintf(stderr, "Error: init_by_sysconfig failed: %s\n",
                     ares_strerror(status)));
    }
  }

  /*
   * No matter what failed or succeeded, seed defaults to provide
   * useful behavior for things that we missed.
   */
  status = init_by_defaults(channel);
  if (status != ARES_SUCCESS) {
    DEBUGF(fprintf(stderr, "Error: init_by_defaults failed: %s\n",
                   ares_strerror(status)));
    goto done;
  }

  ares_set_socket_functions_def(channel);

  /* Initialize the event thread */
  if (channel->optmask & ARES_OPT_EVENT_THREAD) {
    ares_event_thread_t *e = NULL;

    status = ares_event_thread_init(channel);
    if (status != ARES_SUCCESS) {
      goto done; /* LCOV_EXCL_LINE: UntestablePath */
    }

    /* Initialize monitor for configuration changes.  In some rare cases,
     * ARES_ENOTIMP may occur (OpenWatcom), ignore this. */
    e      = channel->sock_state_cb_data;
    status = ares_event_configchg_init(&e->configchg, e);
    if (status != ARES_SUCCESS && status != ARES_ENOTIMP) {
      goto done; /* LCOV_EXCL_LINE: UntestablePath */
    }
    status = ARES_SUCCESS;
  }

done:
  if (status != ARES_SUCCESS) {
    ares_destroy(channel);
    return (int)status;
  }

  *channelptr = channel;
  return ARES_SUCCESS;
}

static void *ares_reinit_thread(void *arg)
{
  ares_channel_t *channel = arg;
  ares_status_t   status;

  /* ares_init_by_sysconfig() will lock when applying the config, but not
   * when retrieving. */
  status = ares_init_by_sysconfig(channel);
  if (status != ARES_SUCCESS) {
    DEBUGF(fprintf(stderr, "Error: init_by_sysconfig failed: %s\n",
                   ares_strerror(status)));
  }

  ares_channel_lock(channel);

  /* Flush cached queries on reinit */
  if (status == ARES_SUCCESS && channel->qcache) {
    ares_qcache_flush(channel->qcache);
  }

  channel->reinit_pending = ARES_FALSE;
  ares_channel_unlock(channel);

  return NULL;
}

ares_status_t ares_reinit(ares_channel_t *channel)
{
  ares_status_t status = ARES_SUCCESS;

  if (channel == NULL) {
    return ARES_EFORMERR;
  }

  ares_channel_lock(channel);

  /* If a reinit is already in process, lets not do it again. Or if we are
   * shutting down, skip. */
  if (!channel->sys_up || channel->reinit_pending) {
    ares_channel_unlock(channel);
    return ARES_SUCCESS;
  }
  channel->reinit_pending = ARES_TRUE;
  ares_channel_unlock(channel);

  if (ares_threadsafety()) {
    /* clean up the prior reinit process's thread.  We know the thread isn't
     * running since reinit_pending was false */
    if (channel->reinit_thread != NULL) {
      void *rv;
      ares_thread_join(channel->reinit_thread, &rv);
      channel->reinit_thread = NULL;
    }

    /* Spawn a new thread */
    status =
      ares_thread_create(&channel->reinit_thread, ares_reinit_thread, channel);
    if (status != ARES_SUCCESS) {
      /* LCOV_EXCL_START: UntestablePath */
      ares_channel_lock(channel);
      channel->reinit_pending = ARES_FALSE;
      ares_channel_unlock(channel);
      /* LCOV_EXCL_STOP */
    }
  } else {
    /* Threading support not available, call directly */
    ares_reinit_thread(channel);
  }

  return status;
}

/* ares_dup() duplicates a channel handle with all its options and returns a
   new channel handle */
int ares_dup(ares_channel_t **dest, const ares_channel_t *src)
{
  struct ares_options opts;
  ares_status_t       rc;
  int                 optmask;

  if (dest == NULL || src == NULL) {
    return ARES_EFORMERR;
  }

  *dest = NULL; /* in case of failure return NULL explicitly */

  /* First get the options supported by the old ares_save_options() function,
     which is most of them */
  rc = (ares_status_t)ares_save_options(src, &opts, &optmask);
  if (rc != ARES_SUCCESS) {
    ares_destroy_options(&opts);
    goto done;
  }

  /* Then create the new channel with those options */
  rc = (ares_status_t)ares_init_options(dest, &opts, optmask);

  /* destroy the options copy to not leak any memory */
  ares_destroy_options(&opts);

  if (rc != ARES_SUCCESS) {
    goto done;
  }

  ares_channel_lock(src);
  /* Now clone the options that ares_save_options() doesn't support, but are
   * user-provided */
  (*dest)->sock_create_cb            = src->sock_create_cb;
  (*dest)->sock_create_cb_data       = src->sock_create_cb_data;
  (*dest)->sock_config_cb            = src->sock_config_cb;
  (*dest)->sock_config_cb_data       = src->sock_config_cb_data;
  memcpy(&(*dest)->sock_funcs, &(src->sock_funcs), sizeof((*dest)->sock_funcs));
  (*dest)->sock_func_cb_data         = src->sock_func_cb_data;
  (*dest)->legacy_sock_funcs         = src->legacy_sock_funcs;
  (*dest)->legacy_sock_funcs_cb_data = src->legacy_sock_funcs_cb_data;
  (*dest)->server_state_cb           = src->server_state_cb;
  (*dest)->server_state_cb_data      = src->server_state_cb_data;

  ares_strcpy((*dest)->local_dev_name, src->local_dev_name,
              sizeof((*dest)->local_dev_name));
  (*dest)->local_ip4 = src->local_ip4;
  memcpy((*dest)->local_ip6, src->local_ip6, sizeof(src->local_ip6));
  ares_channel_unlock(src);

  /* Servers are a bit unique as ares_init_options() only allows ipv4 servers
   * and not a port per server, but there are other user specified ways, that
   * too will toggle the optmask ARES_OPT_SERVERS to let us know.  If that's
   * the case, pull them in.
   *
   * We don't want to clone system-configuration servers though.
   *
   * We must use the "csv" format to get things like link-local address support
   */

  if (optmask & ARES_OPT_SERVERS) {
    char *csv = ares_get_servers_csv(src);
    if (csv == NULL) {
      /* LCOV_EXCL_START: OutOfMemory */
      ares_destroy(*dest);
      *dest = NULL;
      rc    = ARES_ENOMEM;
      goto done;
      /* LCOV_EXCL_STOP */
    }

    rc = (ares_status_t)ares_set_servers_ports_csv(*dest, csv);
    ares_free_string(csv);
    if (rc != ARES_SUCCESS) {
      /* LCOV_EXCL_START: OutOfMemory */
      ares_destroy(*dest);
      *dest = NULL;
      goto done;
      /* LCOV_EXCL_STOP */
    }
  }

  rc = ARES_SUCCESS;
done:
  return (int)rc; /* everything went fine */
}

void ares_set_local_ip4(ares_channel_t *channel, unsigned int local_ip)
{
  if (channel == NULL) {
    return;
  }
  ares_channel_lock(channel);
  channel->local_ip4 = local_ip;
  ares_channel_unlock(channel);
}

/* local_ip6 should be 16 bytes in length */
void ares_set_local_ip6(ares_channel_t *channel, const unsigned char *local_ip6)
{
  if (channel == NULL) {
    return;
  }
  ares_channel_lock(channel);
  memcpy(&channel->local_ip6, local_ip6, sizeof(channel->local_ip6));
  ares_channel_unlock(channel);
}

/* local_dev_name should be null terminated. */
void ares_set_local_dev(ares_channel_t *channel, const char *local_dev_name)
{
  if (channel == NULL) {
    return;
  }

  ares_channel_lock(channel);
  ares_strcpy(channel->local_dev_name, local_dev_name,
              sizeof(channel->local_dev_name));
  channel->local_dev_name[sizeof(channel->local_dev_name) - 1] = 0;
  ares_channel_unlock(channel);
}

int ares_set_sortlist(ares_channel_t *channel, const char *sortstr)
{
  size_t           nsort    = 0;
  struct apattern *sortlist = NULL;
  ares_status_t    status;

  if (!channel) {
    return ARES_ENODATA;
  }
  ares_channel_lock(channel);

  status = ares_parse_sortlist(&sortlist, &nsort, sortstr);
  if (status == ARES_SUCCESS && sortlist) {
    if (channel->sortlist) {
      ares_free(channel->sortlist);
    }
    channel->sortlist = sortlist;
    channel->nsort    = nsort;

    /* Save sortlist as if it was passed in as an option */
    channel->optmask |= ARES_OPT_SORTLIST;
  }
  ares_channel_unlock(channel);
  return (int)status;
}
