Skip to content
ares_init.c 41 KiB
Newer Older
Gisle Vanem's avatar
Gisle Vanem committed
/* $Id$ */

/* Copyright 1998 by the Massachusetts Institute of Technology.
 * Copyright (C) 2007-2009 by Daniel Stenberg
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 */

#if defined(WIN32) && !defined(WATT32)
#include <iphlpapi.h>
#endif
#include <sys/param.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.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

#ifdef HAVE_ARPA_NAMESER_H
#  include <arpa/nameser.h>
#else
#  include "nameser.h"
#  include <arpa/nameser_compat.h>
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include "ares.h"
#include "inet_net_pton.h"
#include "ares_private.h"
#ifdef WATT32
#undef WIN32  /* Redefined in MingW/MSVC headers */
#endif

static int init_by_options(ares_channel channel, const struct ares_options *options,
Daniel Stenberg's avatar
Daniel Stenberg committed
                           int optmask);
static int init_by_environment(ares_channel channel);
static int init_by_resolv_conf(ares_channel channel);
static int init_by_defaults(ares_channel channel);
static int config_nameserver(struct server_state **servers, int *nservers,
Daniel Stenberg's avatar
Daniel Stenberg committed
                             char *str);
static int set_search(ares_channel channel, const char *str);
static int set_options(ares_channel channel, const char *str);
static const char *try_option(const char *p, const char *q, const char *opt);
static int init_id_key(rc4_key* key,int key_data_len);
#ifndef WIN32
static int sortlist_alloc(struct apattern **sortlist, int *nsort, struct apattern *pat);
static int ip_addr(const char *s, int len, struct in_addr *addr);
static void natural_mask(struct apattern *pat);
static int config_domain(ares_channel channel, char *str);
static int config_lookup(ares_channel channel, const char *str,
                         const char *bindch, const char *filech);
static int config_sortlist(struct apattern **sortlist, int *nsort,
                           const char *str);
static char *try_config(char *s, const char *opt);
#endif
#define ARES_CONFIG_CHECK(x) (x->lookups && x->nsort > -1 && \
                             x->ndots > -1 && x->timeout > -1 && \
                             x->tries > -1)
int ares_init(ares_channel *channelptr)
{
  return ares_init_options(channelptr, NULL, 0);
}

int ares_init_options(ares_channel *channelptr, struct ares_options *options,
Daniel Stenberg's avatar
Daniel Stenberg committed
                      int optmask)
{
  ares_channel channel;
  int i;
  int status = ARES_SUCCESS;
  struct server_state *server;
#ifdef CURLDEBUG
  const char *env = getenv("CARES_MEMDEBUG");

  if (env)
    curl_memdebug(env);
  env = getenv("CARES_MEMLIMIT");
  if (env)
    curl_memlimit(atoi(env));
#endif

  channel = malloc(sizeof(struct ares_channeldata));
  if (!channel) {
    *channelptr = NULL;
    return ARES_ENOMEM;
  /* Set everything to distinguished values so we know they haven't
   * been set yet.
   */
  channel->flags = -1;
  channel->timeout = -1;
  channel->tries = -1;
  channel->ndots = -1;
  channel->udp_port = -1;
  channel->tcp_port = -1;
  channel->socket_send_buffer_size = -1;
  channel->socket_receive_buffer_size = -1;
  channel->nservers = -1;
  channel->ndomains = -1;
  channel->nsort = -1;
  channel->lookups = NULL;
  channel->domains = NULL;
  channel->sortlist = NULL;
  channel->servers = NULL;
  channel->sock_state_cb_data = NULL;
  channel->sock_create_cb = NULL;
  channel->sock_create_cb_data = NULL;
  channel->last_timeout_processed = (time_t)now.tv_sec;

  /* Initialize our lists of queries */
  ares__init_list_head(&(channel->all_queries));
  for (i = 0; i < ARES_QID_TABLE_SIZE; i++)
    {
      ares__init_list_head(&(channel->queries_by_qid[i]));
    }
  for (i = 0; i < ARES_TIMEOUT_TABLE_SIZE; i++)
    {
      ares__init_list_head(&(channel->queries_by_timeout[i]));
    }

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

  if (status == ARES_SUCCESS) {
Daniel Stenberg's avatar
Daniel Stenberg committed
    status = init_by_options(channel, options, optmask);
    if (status != ARES_SUCCESS)
      DEBUGF(fprintf(stderr, "Error: init_by_options failed: %s\n",
                     ares_strerror(status)));
  }
  if (status == ARES_SUCCESS) {
    status = init_by_environment(channel);
    if (status != ARES_SUCCESS)
      DEBUGF(fprintf(stderr, "Error: init_by_environment failed: %s\n",
                     ares_strerror(status)));
  }
  if (status == ARES_SUCCESS) {
    status = init_by_resolv_conf(channel);
    if (status != ARES_SUCCESS)
      DEBUGF(fprintf(stderr, "Error: init_by_resolv_conf failed: %s\n",
                     ares_strerror(status)));
  }
  if (status == ARES_SUCCESS) {
    status = init_by_defaults(channel);
    if (status != ARES_SUCCESS)
      DEBUGF(fprintf(stderr, "Error: init_by_defaults failed: %s\n",
                     ares_strerror(status)));
  }

  /* Generate random key */

  if (status == ARES_SUCCESS) {
    status = init_id_key(&channel->id_key, ARES_ID_KEY_LEN);
    if (status == ARES_SUCCESS)
      channel->next_id = ares__generate_new_id(&channel->id_key);
    else
      DEBUGF(fprintf(stderr, "Error: init_id_key failed: %s\n",
                     ares_strerror(status)));
  }

  if (status != ARES_SUCCESS)
    {
      /* Something failed; clean up memory we may have allocated. */
      if (channel->servers)
Daniel Stenberg's avatar
Daniel Stenberg committed
        free(channel->servers);
Daniel Stenberg's avatar
Daniel Stenberg committed
        {
          for (i = 0; i < channel->ndomains; i++)
            free(channel->domains[i]);
          free(channel->domains);
        }
Daniel Stenberg's avatar
Daniel Stenberg committed
        free(channel->sortlist);
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(channel->lookups)
        free(channel->lookups);
      free(channel);
      return status;
    }

  /* Trim to one server if ARES_FLAG_PRIMARY is set. */
  if ((channel->flags & ARES_FLAG_PRIMARY) && channel->nservers > 1)
    channel->nservers = 1;

  /* Initialize server states. */
  for (i = 0; i < channel->nservers; i++)
    {
      server = &channel->servers[i];
      server->udp_socket = ARES_SOCKET_BAD;
      server->tcp_socket = ARES_SOCKET_BAD;
      server->tcp_connection_generation = ++channel->tcp_connection_generation;
      server->tcp_lenbuf_pos = 0;
      server->tcp_buffer = NULL;
      server->qhead = NULL;
      server->qtail = NULL;
      ares__init_list_head(&(server->queries_to_server));
      server->channel = channel;
    }

  *channelptr = channel;
  return ARES_SUCCESS;
}

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

  *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_save_options(src, &opts, &optmask);
  if(rc)
    return rc;

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

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

  if(rc)
    return rc;

  /* Now clone the options that ares_save_options() doesn't support. */
  (*dest)->sock_create_cb      = src->sock_create_cb;
  (*dest)->sock_create_cb_data = src->sock_create_cb_data;
/* Save options from initialized channel */
int ares_save_options(ares_channel channel, struct ares_options *options,
                      int *optmask)
{
  int i;

  /* Zero everything out */
  memset(options, 0, sizeof(struct ares_options));

  if (!ARES_CONFIG_CHECK(channel))
    return ARES_ENODATA;

  /* Traditionally the optmask wasn't saved in the channel struct so it was
     recreated here. ROTATE is the first option that has no struct field of
     its own in the public config struct */
  (*optmask) = (ARES_OPT_FLAGS|ARES_OPT_TRIES|ARES_OPT_NDOTS|
                ARES_OPT_UDP_PORT|ARES_OPT_TCP_PORT|ARES_OPT_SOCK_STATE_CB|
                ARES_OPT_SERVERS|ARES_OPT_DOMAINS|ARES_OPT_LOOKUPS|
                ARES_OPT_SORTLIST|ARES_OPT_TIMEOUTMS) |
    (channel->optmask & ARES_OPT_ROTATE);

  /* Copy easy stuff */
  options->flags   = channel->flags;

  /* We return full millisecond resolution but that's only because we don't
     set the ARES_OPT_TIMEOUT anymore, only the new ARES_OPT_TIMEOUTMS */
  options->timeout = channel->timeout;
  options->tries   = channel->tries;
  options->ndots   = channel->ndots;
  options->udp_port = (unsigned short)channel->udp_port;
  options->tcp_port = (unsigned short)channel->tcp_port;
  options->sock_state_cb     = channel->sock_state_cb;
  options->sock_state_cb_data = channel->sock_state_cb_data;

  /* Copy servers */
  if (channel->nservers) {
    options->servers =
      malloc(channel->nservers * sizeof(struct server_state));
    if (!options->servers && channel->nservers != 0)
      return ARES_ENOMEM;
    for (i = 0; i < channel->nservers; i++)
      options->servers[i] = channel->servers[i].addr;
  }
  options->nservers = channel->nservers;

  /* copy domains */
  if (channel->ndomains) {
    options->domains = malloc(channel->ndomains * sizeof(char *));
    if (!options->domains)

    for (i = 0; i < channel->ndomains; i++)
    {
      options->ndomains = i;
      options->domains[i] = strdup(channel->domains[i]);
      if (!options->domains[i])
        return ARES_ENOMEM;
    }
  }
  options->ndomains = channel->ndomains;

  /* copy lookups */
  if (channel->lookups) {
    options->lookups = strdup(channel->lookups);
    if (!options->lookups && channel->lookups)
      return ARES_ENOMEM;
  }
  if (channel->nsort) {
    options->sortlist = malloc(channel->nsort * sizeof(struct apattern));
    if (!options->sortlist)
      return ARES_ENOMEM;
    for (i = 0; i < channel->nsort; i++)
    {
      memcpy(&(options->sortlist[i]), &(channel->sortlist[i]),
             sizeof(struct apattern));
    }
  }
  options->nsort = channel->nsort;

  return ARES_SUCCESS;
}

static int init_by_options(ares_channel channel,
                           const struct ares_options *options,
Daniel Stenberg's avatar
Daniel Stenberg committed
                           int optmask)
{
  int i;

  /* Easy stuff. */
  if ((optmask & ARES_OPT_FLAGS) && channel->flags == -1)
    channel->flags = options->flags;
  if ((optmask & ARES_OPT_TIMEOUTMS) && channel->timeout == -1)
    channel->timeout = options->timeout;
  else if ((optmask & ARES_OPT_TIMEOUT) && channel->timeout == -1)
    channel->timeout = options->timeout * 1000;
  if ((optmask & ARES_OPT_TRIES) && channel->tries == -1)
    channel->tries = options->tries;
  if ((optmask & ARES_OPT_NDOTS) && channel->ndots == -1)
    channel->ndots = options->ndots;
  if ((optmask & ARES_OPT_ROTATE) && channel->rotate == -1)
  if ((optmask & ARES_OPT_UDP_PORT) && channel->udp_port == -1)
    channel->udp_port = options->udp_port;
  if ((optmask & ARES_OPT_TCP_PORT) && channel->tcp_port == -1)
    channel->tcp_port = options->tcp_port;
  if ((optmask & ARES_OPT_SOCK_STATE_CB) && channel->sock_state_cb == NULL)
    {
      channel->sock_state_cb = options->sock_state_cb;
      channel->sock_state_cb_data = options->sock_state_cb_data;
    }
  if ((optmask & ARES_OPT_SOCK_SNDBUF)
      && channel->socket_send_buffer_size == -1)
    channel->socket_send_buffer_size = options->socket_send_buffer_size;
  if ((optmask & ARES_OPT_SOCK_RCVBUF)
      && channel->socket_receive_buffer_size == -1)
    channel->socket_receive_buffer_size = options->socket_receive_buffer_size;

  /* Copy the servers, if given. */
  if ((optmask & ARES_OPT_SERVERS) && channel->nservers == -1)
    {
      /* Avoid zero size allocations at any cost */
      if (options->nservers > 0)
        {
          channel->servers =
            malloc(options->nservers * sizeof(struct server_state));
          if (!channel->servers)
            return ARES_ENOMEM;
          for (i = 0; i < options->nservers; i++)
            channel->servers[i].addr = options->servers[i];
        }
      channel->nservers = options->nservers;
    }

  /* Copy the domains, if given.  Keep channel->ndomains consistent so
   * we can clean up in case of error.
   */
  if ((optmask & ARES_OPT_DOMAINS) && channel->ndomains == -1)
    {
      /* Avoid zero size allocations at any cost */
      if (options->ndomains > 0)
      {
        channel->domains = malloc(options->ndomains * sizeof(char *));
        if (!channel->domains)
          return ARES_ENOMEM;
        for (i = 0; i < options->ndomains; i++)
          {
            channel->ndomains = i;
            channel->domains[i] = strdup(options->domains[i]);
            if (!channel->domains[i])
              return ARES_ENOMEM;
          }
      }
      channel->ndomains = options->ndomains;
    }

  /* Set lookups, if given. */
  if ((optmask & ARES_OPT_LOOKUPS) && !channel->lookups)
    {
      channel->lookups = strdup(options->lookups);
      if (!channel->lookups)
Daniel Stenberg's avatar
Daniel Stenberg committed
        return ARES_ENOMEM;
  /* copy sortlist */
  if ((optmask & ARES_OPT_SORTLIST) && channel->nsort == -1)
    {
      channel->sortlist = malloc(options->nsort * sizeof(struct apattern));
      if (!channel->sortlist)
        return ARES_ENOMEM;
      for (i = 0; i < options->nsort; i++)
        {
Daniel Stenberg's avatar
Daniel Stenberg committed
          memcpy(&(channel->sortlist[i]), &(options->sortlist[i]),
                 sizeof(struct apattern));
  return ARES_SUCCESS;
}

static int init_by_environment(ares_channel channel)
{
  const char *localdomain, *res_options;
  int status;

  localdomain = getenv("LOCALDOMAIN");
  if (localdomain && channel->ndomains == -1)
    {
      status = set_search(channel, localdomain);
      if (status != ARES_SUCCESS)
Daniel Stenberg's avatar
Daniel Stenberg committed
        return status;
    }

  res_options = getenv("RES_OPTIONS");
  if (res_options)
    {
      status = set_options(channel, res_options);
      if (status != ARES_SUCCESS)
Daniel Stenberg's avatar
Daniel Stenberg committed
        return status;
#ifdef WIN32
/*
 * Warning: returns a dynamically allocated buffer, the user MUST
 * use free() if the function returns 1
 */
static int get_res_nt(HKEY hKey, const char *subkey, char **obuf)
  DWORD size = 0;
  result = RegQueryValueEx(hKey, subkey, 0, NULL, NULL, &size);
  if ((result != ERROR_SUCCESS && result != ERROR_MORE_DATA) || !size)
    return 0;
  *obuf = malloc(size+1);
  if (!*obuf)
    return 0;
  if (RegQueryValueEx(hKey, subkey, 0, NULL,
                      (LPBYTE)*obuf, &size) != ERROR_SUCCESS)
  {
    free(*obuf);
    return 0;
  }
  if (size == 1)
  {
    free(*obuf);
    return 0;
  }
  return 1;
static int get_res_interfaces_nt(HKEY hKey, const char *subkey, char **obuf)
  char enumbuf[39]; /* GUIDs are 38 chars + 1 for NULL */
  DWORD enum_size = 39;
  while (RegEnumKeyEx(hKey, idx++, enumbuf, &enum_size, 0,
                      NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS)
  {
    enum_size = 39;
    if (RegOpenKeyEx(hKey, enumbuf, 0, KEY_QUERY_VALUE, &hVal) !=
        ERROR_SUCCESS)
      continue;
    rc = get_res_nt(hVal, subkey, obuf);

static int get_iphlpapi_dns_info (char *ret_buf, size_t ret_size)
{
  FIXED_INFO    *fi   = alloca (sizeof(*fi));
  DWORD          size = sizeof (*fi);
Gisle Vanem's avatar
Gisle Vanem committed
  typedef DWORD (WINAPI* get_net_param_func) (FIXED_INFO*, DWORD*);
  get_net_param_func fpGetNetworkParams;  /* available only on Win-98/2000+ */
  HMODULE        handle;
  IP_ADDR_STRING *ipAddr;
  int            i, count = 0;
  int            debug  = 0;
  size_t         ip_size = sizeof("255.255.255.255,")-1;
  size_t         left = ret_size;
  char          *ret = ret_buf;

  if (!fi)
     return (0);

  handle = LoadLibrary ("iphlpapi.dll");
  if (!handle)
     return (0);

  fpGetNetworkParams = (get_net_param_func) GetProcAddress (handle, "GetNetworkParams");
  if (!fpGetNetworkParams)
  res = (*fpGetNetworkParams) (fi, &size);
  if ((res != ERROR_BUFFER_OVERFLOW) && (res != ERROR_SUCCESS))
     goto quit;

  fi = alloca (size);
  if (!fi || (*fpGetNetworkParams) (fi, &size) != ERROR_SUCCESS)
     goto quit;

  if (debug)
  {
    printf ("Host Name: %s\n", fi->HostName);
    printf ("Domain Name: %s\n", fi->DomainName);
    printf ("DNS Servers:\n"
            "    %s (primary)\n", fi->DnsServerList.IpAddress.String);
  }
  if (strlen(fi->DnsServerList.IpAddress.String) > 0 &&
      inet_addr(fi->DnsServerList.IpAddress.String) != INADDR_NONE &&
      left > ip_size)
  {
    ret += sprintf (ret, "%s,", fi->DnsServerList.IpAddress.String);
    left -= ret - ret_buf;
    count++;
  }

  for (i = 0, ipAddr = fi->DnsServerList.Next; ipAddr && left > ip_size;
       ipAddr = ipAddr->Next, i++)
  {
    if (inet_addr(ipAddr->IpAddress.String) != INADDR_NONE)
    {
       ret += sprintf (ret, "%s,", ipAddr->IpAddress.String);
       left -= ret - ret_buf;
       count++;
    }
    if (debug)
       printf ("    %s (secondary %d)\n", ipAddr->IpAddress.String, i+1);
  }

quit:
  if (handle)
     FreeLibrary (handle);

  if (debug && left <= ip_size)
     printf ("Too many nameservers. Truncating to %d addressess", count);
  if (ret > ret_buf)
     ret[-1] = '\0';
  return (count);
}
#endif

static int init_by_resolv_conf(ares_channel channel)
{
Gisle Vanem's avatar
Gisle Vanem committed
  int status = -1, nservers = 0, nsort = 0;
  struct server_state *servers = NULL;
  struct apattern *sortlist = NULL;

#ifdef WIN32

    /*
  NameServer info via IPHLPAPI (IP helper API):
    GetNetworkParams() should be the trusted source for this.
    Available in Win-98/2000 and later. If that fail, fall-back to
    registry information.

  NameServer Registry:

   On Windows 9X, the DNS server can be found in:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\MSTCP\NameServer

Daniel Stenberg's avatar
Daniel Stenberg committed
        On Windows NT/2000/XP/2003:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\NameServer
Daniel Stenberg's avatar
Daniel Stenberg committed
        or
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DhcpNameServer
Daniel Stenberg's avatar
Daniel Stenberg committed
        or
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\{AdapterID}\
NameServer
Daniel Stenberg's avatar
Daniel Stenberg committed
        or
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\{AdapterID}\
DhcpNameServer
   */

  HKEY mykey;
  HKEY subkey;
  DWORD data_type;
  DWORD bytes;
  DWORD result;
  char  buf[256];
  if (channel->nservers > -1)  /* don't override ARES_OPT_SERVER */
     return ARES_SUCCESS;

  if (get_iphlpapi_dns_info(buf,sizeof(buf)) > 0)
  {
    status = config_nameserver(&servers, &nservers, buf);
    if (status == ARES_SUCCESS)
Gisle Vanem's avatar
Gisle Vanem committed
  if (IS_NT())
  {
    if (RegOpenKeyEx(
          HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0,
          KEY_READ, &mykey
          ) == ERROR_SUCCESS)
    {
      RegOpenKeyEx(mykey, "Interfaces", 0,
                   KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS, &subkey);
      if (get_res_nt(mykey, NAMESERVER, &line))
      {
        status = config_nameserver(&servers, &nservers, line);
        free(line);
      }
      else if (get_res_nt(mykey, DHCPNAMESERVER, &line))
      {
        status = config_nameserver(&servers, &nservers, line);
        free(line);
      }
      /* Try the interfaces */
      else if (get_res_interfaces_nt(subkey, NAMESERVER, &line))
        status = config_nameserver(&servers, &nservers, line);
        free(line);
      }
      else if (get_res_interfaces_nt(subkey, DHCPNAMESERVER, &line))
      {
        status = config_nameserver(&servers, &nservers, line);
        free(line);
      }
      RegCloseKey(subkey);
      RegCloseKey(mykey);
    }
  }
  else
  {
    if (RegOpenKeyEx(
          HKEY_LOCAL_MACHINE, WIN_NS_9X, 0,
          KEY_READ, &mykey
          ) == ERROR_SUCCESS)
    {
      if ((result = RegQueryValueEx(
             mykey, NAMESERVER, NULL, &data_type,
             NULL, &bytes
            ) == ERROR_SUCCESS ||
          line = malloc(bytes+1);
          if (RegQueryValueEx(mykey, NAMESERVER, NULL, &data_type,
                              (unsigned char *)line, &bytes) ==
              ERROR_SUCCESS)
          {
            status = config_nameserver(&servers, &nservers, line);
          }
          free(line);
  if (status == ARES_SUCCESS)
  else
    /* Catch the case when all the above checks fail (which happens when there
       is no network card or the cable is unplugged) */
    status = ARES_EFILE;

  /* Under RISC OS, name servers are listed in the
     system variable Inet$Resolvers, space separated. */

  line = getenv("Inet$Resolvers");
  if (line) {
    char *resolvers = strdup(line), *pos, *space;

    if (!resolvers)
      return ARES_ENOMEM;

    pos = resolvers;
    do {
      space = strchr(pos, ' ');
      if (space)
      status = config_nameserver(&servers, &nservers, pos);
      if (status != ARES_SUCCESS)
        break;
      pos = space + 1;
    } while (space);

    if (status == ARES_SUCCESS)
      status = ARES_EOF;
#elif defined(WATT32)
  int i;

  sock_init();
  for (i = 0; def_nameservers[i]; i++)
      ;
  if (i == 0)
    return ARES_SUCCESS; /* use localhost DNS server */

  nservers = i;
  servers = calloc(sizeof(*servers), i);
  if (!servers)
     return ARES_ENOMEM;

  for (i = 0; def_nameservers[i]; i++)
      servers[i].addr.s_addr = htonl(def_nameservers[i]);
  status = ARES_EOF;

    /* Don't read resolv.conf and friends if we don't have to */
    if (ARES_CONFIG_CHECK(channel))
        return ARES_SUCCESS;

    if (fp) {
      while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS)
      {
        if ((p = try_config(line, "domain")) && channel->ndomains == -1)
          status = config_domain(channel, p);
        else if ((p = try_config(line, "lookup")) && !channel->lookups)
          status = config_lookup(channel, p, "bind", "file");
        else if ((p = try_config(line, "search")) && channel->ndomains == -1)
          status = set_search(channel, p);
        else if ((p = try_config(line, "nameserver")) && channel->nservers == -1)
          status = config_nameserver(&servers, &nservers, p);
        else if ((p = try_config(line, "sortlist")) && channel->nsort == -1)
          status = config_sortlist(&sortlist, &nsort, p);
        else if ((p = try_config(line, "options")))
          status = set_options(channel, p);
        else
          status = ARES_SUCCESS;
        if (status != ARES_SUCCESS)
          break;
      }
      fclose(fp);
    }
    else {
        DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n",
        DEBUGF(fprintf(stderr, "Error opening file: %s\n", PATH_RESOLV_CONF));
        status = ARES_EFILE;
      }
    if ((status == ARES_EOF) && (!channel->lookups)) {
      /* Many systems (Solaris, Linux, BSD's) use nsswitch.conf */
      fp = fopen("/etc/nsswitch.conf", "r");
      if (fp) {
        while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS)
        {
          if ((p = try_config(line, "hosts:")) && !channel->lookups)
            status = config_lookup(channel, p, "dns", "files");
        }
        fclose(fp);
      }
          status = ARES_EOF;
          break;
        default:
          DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n",
          DEBUGF(fprintf(stderr, "Error opening file: %s\n", "/etc/nsswitch.conf"));
          status = ARES_EFILE;
        }
      }
    if ((status == ARES_EOF) && (!channel->lookups)) {
      /* Linux / GNU libc 2.x and possibly others have host.conf */
      fp = fopen("/etc/host.conf", "r");
      if (fp) {
        while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS)
        {
          if ((p = try_config(line, "order")) && !channel->lookups)
            status = config_lookup(channel, p, "bind", "hosts");
        }
        fclose(fp);
      }
          status = ARES_EOF;
          break;
        default:
          DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n",
          DEBUGF(fprintf(stderr, "Error opening file: %s\n", "/etc/host.conf"));
          status = ARES_EFILE;
        }
      }
    if ((status == ARES_EOF) && (!channel->lookups)) {
      /* Tru64 uses /etc/svc.conf */
      fp = fopen("/etc/svc.conf", "r");
      if (fp) {
        while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS)
        {
          if ((p = try_config(line, "hosts=")) && !channel->lookups)
            status = config_lookup(channel, p, "bind", "local");
        }
        fclose(fp);
      }
          status = ARES_EOF;
          break;
        default:
          DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n",
          DEBUGF(fprintf(stderr, "Error opening file: %s\n", "/etc/svc.conf"));
          status = ARES_EFILE;
        }
      }

#endif

  /* Handle errors. */
  if (status != ARES_EOF)
    {
      if (servers != NULL)
        free(servers);
      if (sortlist != NULL)
        free(sortlist);
      return status;
    }

  /* If we got any name server entries, fill them in. */
  if (servers)
    {
      channel->servers = servers;
      channel->nservers = nservers;
    }

  /* If we got any sortlist entries, fill them in. */
  if (sortlist)
    {
      channel->sortlist = sortlist;
      channel->nsort = nsort;
    }

  return ARES_SUCCESS;
}

static int init_by_defaults(ares_channel channel)
{
  char *hostname = NULL;
  int rc = ARES_SUCCESS;

  if (channel->flags == -1)
    channel->flags = 0;
  if (channel->timeout == -1)
    channel->timeout = DEFAULT_TIMEOUT;
  if (channel->tries == -1)
    channel->tries = DEFAULT_TRIES;
  if (channel->ndots == -1)
    channel->ndots = 1;
  if (channel->rotate == -1)
    channel->rotate = 0;
  if (channel->udp_port == -1)
    channel->udp_port = htons(NAMESERVER_PORT);
  if (channel->tcp_port == -1)
    channel->tcp_port = htons(NAMESERVER_PORT);

  if (channel->nservers == -1) {
    /* If nobody specified servers, try a local named. */
    channel->servers = malloc(sizeof(struct server_state));
    if (!channel->servers) {
      rc = ARES_ENOMEM;
      goto error;
    channel->servers[0].addr.s_addr = htonl(INADDR_LOOPBACK);
    channel->nservers = 1;
  }
#ifdef ENAMETOOLONG
#define toolong(x) (x == -1) && ((ENAMETOOLONG == errno) || (EINVAL == errno))