Skip to content
Snippets Groups Projects
ares_search.c 7.06 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* 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.
     */
    
    static const char rcsid[] = "$Id$";
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <ctype.h>
    
    #ifdef WIN32
    #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,
    			    int alen);
    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 == '.')
    	ndots++;
        }
    
      /* 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);
    	}
          else
    	callback(arg, status, NULL, 0);
        }
    }
    
    static void search_callback(void *arg, int status, unsigned char *abuf,
    			    int alen)
    {
      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);
    	}
          else
    	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)
    {
      int nlen = strlen(name), 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)
    {
      int 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;
    }