Skip to content
Snippets Groups Projects
ares_search.c 8.01 KiB
Newer Older
  • Learn to ignore specific revisions
  • Gisle Vanem's avatar
    Gisle Vanem committed
    /* $Id$ */
    
    Gisle Vanem's avatar
    Gisle Vanem committed
    
    
    /* Copyright 1998 by the Massachusetts Institute of Technology.
     *
     * 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.
     */
    
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <ctype.h>
    
    
    #if defined(WIN32) && !defined(WATT32)
    
    #include "nameser.h"
    #endif
    
    #include "ares.h"
    #include "ares_private.h"
    
    struct search_query {
      /* Arguments passed to ares_search */
      ares_channel channel;
    
      char *name;                   /* copied into an allocated buffer */
    
      int dnsclass;
      int type;
      ares_callback callback;
      void *arg;
    
    
      int status_as_is;             /* error status from trying as-is */
      int next_domain;              /* next search domain to try */
      int trying_as_is;             /* current query is for name as-is */
    
    };
    
    static void search_callback(void *arg, int status, unsigned char *abuf,
    
    static void end_squery(struct search_query *squery, int status,
    
                           unsigned char *abuf, int alen);
    
    static int cat_domain(const char *name, const char *domain, char **s);
    static int single_domain(ares_channel channel, const char *name, char **s);
    
    void ares_search(ares_channel channel, const char *name, int dnsclass,
    
                     int type, ares_callback callback, void *arg)
    
    {
      struct search_query *squery;
      char *s;
      const char *p;
      int status, ndots;
    
      /* If name only yields one domain to search, then we don't have
       * to keep extra state, so just do an ares_query().
       */
      status = single_domain(channel, name, &s);
      if (status != ARES_SUCCESS)
        {
          callback(arg, status, NULL, 0);
          return;
        }
      if (s)
        {
          ares_query(channel, s, dnsclass, type, callback, arg);
          free(s);
          return;
        }
    
      /* Allocate a search_query structure to hold the state necessary for
       * doing multiple lookups.
       */
      squery = malloc(sizeof(struct search_query));
      if (!squery)
        {
          callback(arg, ARES_ENOMEM, NULL, 0);
          return;
        }
      squery->channel = channel;
      squery->name = strdup(name);
      if (!squery->name)
        {
          free(squery);
          callback(arg, ARES_ENOMEM, NULL, 0);
          return;
        }
      squery->dnsclass = dnsclass;
      squery->type = type;
      squery->status_as_is = -1;
      squery->callback = callback;
      squery->arg = arg;
    
      /* Count the number of dots in name. */
      ndots = 0;
      for (p = name; *p; p++)
        {
          if (*p == '.')
    
        }
    
      /* If ndots is at least the channel ndots threshold (usually 1),
       * then we try the name as-is first.  Otherwise, we try the name
       * as-is last.
       */
      if (ndots >= channel->ndots)
        {
          /* Try the name as-is first. */
          squery->next_domain = 0;
          squery->trying_as_is = 1;
          ares_query(channel, name, dnsclass, type, search_callback, squery);
        }
      else
        {
          /* Try the name as-is last; start with the first search domain. */
          squery->next_domain = 1;
          squery->trying_as_is = 0;
          status = cat_domain(name, channel->domains[0], &s);
          if (status == ARES_SUCCESS)
    
            {
              ares_query(channel, s, dnsclass, type, search_callback, squery);
              free(s);
            }
    
          {
            /* failed, free the malloc()ed memory */
            free(squery->name);
            free(squery);
    
            callback(arg, status, NULL, 0);
    
        }
    }
    
    static void search_callback(void *arg, int status, unsigned char *abuf,
    
    {
      struct search_query *squery = (struct search_query *) arg;
      ares_channel channel = squery->channel;
      char *s;
    
      /* Stop searching unless we got a non-fatal error. */
      if (status != ARES_ENODATA && status != ARES_ESERVFAIL
          && status != ARES_ENOTFOUND)
        end_squery(squery, status, abuf, alen);
      else
        {
          /* Save the status if we were trying as-is. */
          if (squery->trying_as_is)
    
            squery->status_as_is = status;
    
          if (squery->next_domain < channel->ndomains)
    
            {
              /* Try the next domain. */
              status = cat_domain(squery->name,
                                  channel->domains[squery->next_domain], &s);
              if (status != ARES_SUCCESS)
                end_squery(squery, status, NULL, 0);
              else
                {
                  squery->trying_as_is = 0;
                  squery->next_domain++;
                  ares_query(channel, s, squery->dnsclass, squery->type,
                             search_callback, squery);
                  free(s);
                }
            }
    
          else if (squery->status_as_is == -1)
    
            {
              /* Try the name as-is at the end. */
              squery->trying_as_is = 1;
              ares_query(channel, squery->name, squery->dnsclass, squery->type,
                         search_callback, squery);
            }
    
            end_squery(squery, squery->status_as_is, NULL, 0);
    
        }
    }
    
    static void end_squery(struct search_query *squery, int status,
    
                           unsigned char *abuf, int alen)
    
    {
      squery->callback(squery->arg, status, abuf, alen);
      free(squery->name);
      free(squery);
    }
    
    /* Concatenate two domains. */
    static int cat_domain(const char *name, const char *domain, char **s)
    {
    
      size_t nlen = strlen(name);
      size_t dlen = strlen(domain);
    
    
      *s = malloc(nlen + 1 + dlen + 1);
      if (!*s)
        return ARES_ENOMEM;
      memcpy(*s, name, nlen);
      (*s)[nlen] = '.';
      memcpy(*s + nlen + 1, domain, dlen);
      (*s)[nlen + 1 + dlen] = 0;
      return ARES_SUCCESS;
    }
    
    /* Determine if this name only yields one query.  If it does, set *s to
     * the string we should query, in an allocated buffer.  If not, set *s
     * to NULL.
     */
    static int single_domain(ares_channel channel, const char *name, char **s)
    {
    
      size_t len = strlen(name);
    
      const char *hostaliases;
      FILE *fp;
      char *line = NULL;
      int linesize, status;
      const char *p, *q;
    
      /* If the name contains a trailing dot, then the single query is the name
       * sans the trailing dot.
       */
      if (name[len - 1] == '.')
        {
          *s = strdup(name);
          return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
        }
    
      if (!(channel->flags & ARES_FLAG_NOALIASES) && !strchr(name, '.'))
        {
          /* The name might be a host alias. */
          hostaliases = getenv("HOSTALIASES");
          if (hostaliases)
    
            {
              fp = fopen(hostaliases, "r");
              if (fp)
                {
                  while ((status = ares__read_line(fp, &line, &linesize))
                         == ARES_SUCCESS)
                    {
                      if (strncasecmp(line, name, len) != 0 ||
                          !isspace((unsigned char)line[len]))
                        continue;
                      p = line + len;
                      while (isspace((unsigned char)*p))
                        p++;
                      if (*p)
                        {
                          q = p + 1;
                          while (*q && !isspace((unsigned char)*q))
                            q++;
                          *s = malloc(q - p + 1);
                          if (*s)
                            {
                              memcpy(*s, p, q - p);
                              (*s)[q - p] = 0;
                            }
                          free(line);
                          fclose(fp);
                          return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
                        }
                    }
                  free(line);
                  fclose(fp);
                  if (status != ARES_SUCCESS)
                    return status;
                }
            }
    
        }
    
      if (channel->flags & ARES_FLAG_NOSEARCH || channel->ndomains == 0)
        {
          /* No domain search to do; just try the name as-is. */
          *s = strdup(name);
          return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
        }
    
      *s = NULL;
      return ARES_SUCCESS;
    }