Home | History | Annotate | Download | only in c-ares
      1 
      2 /* Copyright 1998 by the Massachusetts Institute of Technology.
      3  *
      4  * Permission to use, copy, modify, and distribute this
      5  * software and its documentation for any purpose and without
      6  * fee is hereby granted, provided that the above copyright
      7  * notice appear in all copies and that both that copyright
      8  * notice and this permission notice appear in supporting
      9  * documentation, and that the name of M.I.T. not be used in
     10  * advertising or publicity pertaining to distribution of the
     11  * software without specific, written prior permission.
     12  * M.I.T. makes no representations about the suitability of
     13  * this software for any purpose.  It is provided "as is"
     14  * without express or implied warranty.
     15  */
     16 
     17 #include "ares_setup.h"
     18 
     19 #include <stdio.h>
     20 #include <stdlib.h>
     21 #include <string.h>
     22 #include <ctype.h>
     23 
     24 #ifdef HAVE_STRINGS_H
     25 #  include <strings.h>
     26 #endif
     27 
     28 #include "ares.h"
     29 #include "ares_private.h"
     30 
     31 struct search_query {
     32   /* Arguments passed to ares_search */
     33   ares_channel channel;
     34   char *name;                   /* copied into an allocated buffer */
     35   int dnsclass;
     36   int type;
     37   ares_callback callback;
     38   void *arg;
     39 
     40   int status_as_is;             /* error status from trying as-is */
     41   int next_domain;              /* next search domain to try */
     42   int trying_as_is;             /* current query is for name as-is */
     43   int timeouts;                 /* number of timeouts we saw for this request */
     44   int ever_got_nodata;          /* did we ever get ARES_ENODATA along the way? */
     45 };
     46 
     47 static void search_callback(void *arg, int status, int timeouts,
     48                             unsigned char *abuf, int alen);
     49 static void end_squery(struct search_query *squery, int status,
     50                        unsigned char *abuf, int alen);
     51 static int cat_domain(const char *name, const char *domain, char **s);
     52 static int single_domain(ares_channel channel, const char *name, char **s);
     53 
     54 void ares_search(ares_channel channel, const char *name, int dnsclass,
     55                  int type, ares_callback callback, void *arg)
     56 {
     57   struct search_query *squery;
     58   char *s;
     59   const char *p;
     60   int status, ndots;
     61 
     62   /* If name only yields one domain to search, then we don't have
     63    * to keep extra state, so just do an ares_query().
     64    */
     65   status = single_domain(channel, name, &s);
     66   if (status != ARES_SUCCESS)
     67     {
     68       callback(arg, status, 0, NULL, 0);
     69       return;
     70     }
     71   if (s)
     72     {
     73       ares_query(channel, s, dnsclass, type, callback, arg);
     74       free(s);
     75       return;
     76     }
     77 
     78   /* Allocate a search_query structure to hold the state necessary for
     79    * doing multiple lookups.
     80    */
     81   squery = malloc(sizeof(struct search_query));
     82   if (!squery)
     83     {
     84       callback(arg, ARES_ENOMEM, 0, NULL, 0);
     85       return;
     86     }
     87   squery->channel = channel;
     88   squery->name = strdup(name);
     89   if (!squery->name)
     90     {
     91       free(squery);
     92       callback(arg, ARES_ENOMEM, 0, NULL, 0);
     93       return;
     94     }
     95   squery->dnsclass = dnsclass;
     96   squery->type = type;
     97   squery->status_as_is = -1;
     98   squery->callback = callback;
     99   squery->arg = arg;
    100   squery->timeouts = 0;
    101   squery->ever_got_nodata = 0;
    102 
    103   /* Count the number of dots in name. */
    104   ndots = 0;
    105   for (p = name; *p; p++)
    106     {
    107       if (*p == '.')
    108         ndots++;
    109     }
    110 
    111   /* If ndots is at least the channel ndots threshold (usually 1),
    112    * then we try the name as-is first.  Otherwise, we try the name
    113    * as-is last.
    114    */
    115   if (ndots >= channel->ndots)
    116     {
    117       /* Try the name as-is first. */
    118       squery->next_domain = 0;
    119       squery->trying_as_is = 1;
    120       ares_query(channel, name, dnsclass, type, search_callback, squery);
    121     }
    122   else
    123     {
    124       /* Try the name as-is last; start with the first search domain. */
    125       squery->next_domain = 1;
    126       squery->trying_as_is = 0;
    127       status = cat_domain(name, channel->domains[0], &s);
    128       if (status == ARES_SUCCESS)
    129         {
    130           ares_query(channel, s, dnsclass, type, search_callback, squery);
    131           free(s);
    132         }
    133       else
    134       {
    135         /* failed, free the malloc()ed memory */
    136         free(squery->name);
    137         free(squery);
    138         callback(arg, status, 0, NULL, 0);
    139       }
    140     }
    141 }
    142 
    143 static void search_callback(void *arg, int status, int timeouts,
    144                             unsigned char *abuf, int alen)
    145 {
    146   struct search_query *squery = (struct search_query *) arg;
    147   ares_channel channel = squery->channel;
    148   char *s;
    149 
    150   squery->timeouts += timeouts;
    151 
    152   /* Stop searching unless we got a non-fatal error. */
    153   if (status != ARES_ENODATA && status != ARES_ESERVFAIL
    154       && status != ARES_ENOTFOUND)
    155     end_squery(squery, status, abuf, alen);
    156   else
    157     {
    158       /* Save the status if we were trying as-is. */
    159       if (squery->trying_as_is)
    160         squery->status_as_is = status;
    161 
    162       /*
    163        * If we ever get ARES_ENODATA along the way, record that; if the search
    164        * should run to the very end and we got at least one ARES_ENODATA,
    165        * then callers like ares_gethostbyname() may want to try a T_A search
    166        * even if the last domain we queried for T_AAAA resource records
    167        * returned ARES_ENOTFOUND.
    168        */
    169       if (status == ARES_ENODATA)
    170         squery->ever_got_nodata = 1;
    171 
    172       if (squery->next_domain < channel->ndomains)
    173         {
    174           /* Try the next domain. */
    175           status = cat_domain(squery->name,
    176                               channel->domains[squery->next_domain], &s);
    177           if (status != ARES_SUCCESS)
    178             end_squery(squery, status, NULL, 0);
    179           else
    180             {
    181               squery->trying_as_is = 0;
    182               squery->next_domain++;
    183               ares_query(channel, s, squery->dnsclass, squery->type,
    184                          search_callback, squery);
    185               free(s);
    186             }
    187         }
    188       else if (squery->status_as_is == -1)
    189         {
    190           /* Try the name as-is at the end. */
    191           squery->trying_as_is = 1;
    192           ares_query(channel, squery->name, squery->dnsclass, squery->type,
    193                      search_callback, squery);
    194         }
    195       else {
    196         if (squery->status_as_is == ARES_ENOTFOUND && squery->ever_got_nodata) {
    197           end_squery(squery, ARES_ENODATA, NULL, 0);
    198         }
    199         else
    200           end_squery(squery, squery->status_as_is, NULL, 0);
    201       }
    202     }
    203 }
    204 
    205 static void end_squery(struct search_query *squery, int status,
    206                        unsigned char *abuf, int alen)
    207 {
    208   squery->callback(squery->arg, status, squery->timeouts, abuf, alen);
    209   free(squery->name);
    210   free(squery);
    211 }
    212 
    213 /* Concatenate two domains. */
    214 static int cat_domain(const char *name, const char *domain, char **s)
    215 {
    216   size_t nlen = strlen(name);
    217   size_t dlen = strlen(domain);
    218 
    219   *s = malloc(nlen + 1 + dlen + 1);
    220   if (!*s)
    221     return ARES_ENOMEM;
    222   memcpy(*s, name, nlen);
    223   (*s)[nlen] = '.';
    224   memcpy(*s + nlen + 1, domain, dlen);
    225   (*s)[nlen + 1 + dlen] = 0;
    226   return ARES_SUCCESS;
    227 }
    228 
    229 /* Determine if this name only yields one query.  If it does, set *s to
    230  * the string we should query, in an allocated buffer.  If not, set *s
    231  * to NULL.
    232  */
    233 static int single_domain(ares_channel channel, const char *name, char **s)
    234 {
    235   size_t len = strlen(name);
    236   const char *hostaliases;
    237   FILE *fp;
    238   char *line = NULL;
    239   int status;
    240   size_t linesize;
    241   const char *p, *q;
    242   int error;
    243 
    244   /* If the name contains a trailing dot, then the single query is the name
    245    * sans the trailing dot.
    246    */
    247   if (name[len - 1] == '.')
    248     {
    249       *s = strdup(name);
    250       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
    251     }
    252 
    253   if (!(channel->flags & ARES_FLAG_NOALIASES) && !strchr(name, '.'))
    254     {
    255       /* The name might be a host alias. */
    256       hostaliases = getenv("HOSTALIASES");
    257       if (hostaliases)
    258         {
    259           fp = fopen(hostaliases, "r");
    260           if (fp)
    261             {
    262               while ((status = ares__read_line(fp, &line, &linesize))
    263                      == ARES_SUCCESS)
    264                 {
    265                   if (strncasecmp(line, name, len) != 0 ||
    266                       !ISSPACE(line[len]))
    267                     continue;
    268                   p = line + len;
    269                   while (ISSPACE(*p))
    270                     p++;
    271                   if (*p)
    272                     {
    273                       q = p + 1;
    274                       while (*q && !ISSPACE(*q))
    275                         q++;
    276                       *s = malloc(q - p + 1);
    277                       if (*s)
    278                         {
    279                           memcpy(*s, p, q - p);
    280                           (*s)[q - p] = 0;
    281                         }
    282                       free(line);
    283                       fclose(fp);
    284                       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
    285                     }
    286                 }
    287               free(line);
    288               fclose(fp);
    289               if (status != ARES_SUCCESS && status != ARES_EOF)
    290                 return status;
    291             }
    292           else
    293             {
    294               error = ERRNO;
    295               switch(error)
    296                 {
    297                 case ENOENT:
    298                 case ESRCH:
    299                   break;
    300                 default:
    301                   DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n",
    302                                  error, strerror(error)));
    303                   DEBUGF(fprintf(stderr, "Error opening file: %s\n",
    304                                  hostaliases));
    305                   *s = NULL;
    306                   return ARES_EFILE;
    307                 }
    308             }
    309         }
    310     }
    311 
    312   if (channel->flags & ARES_FLAG_NOSEARCH || channel->ndomains == 0)
    313     {
    314       /* No domain search to do; just try the name as-is. */
    315       *s = strdup(name);
    316       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
    317     }
    318 
    319   *s = NULL;
    320   return ARES_SUCCESS;
    321 }
    322