• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #ifdef HAVE_STRINGS_H
20 #  include <strings.h>
21 #endif
22 
23 #include "ares.h"
24 #include "ares_private.h"
25 
26 struct search_query {
27   /* Arguments passed to ares_search */
28   ares_channel channel;
29   char *name;                   /* copied into an allocated buffer */
30   int dnsclass;
31   int type;
32   ares_callback callback;
33   void *arg;
34 
35   int status_as_is;             /* error status from trying as-is */
36   int next_domain;              /* next search domain to try */
37   int trying_as_is;             /* current query is for name as-is */
38   int timeouts;                 /* number of timeouts we saw for this request */
39   int ever_got_nodata;          /* did we ever get ARES_ENODATA along the way? */
40 };
41 
42 static void search_callback(void *arg, int status, int timeouts,
43                             unsigned char *abuf, int alen);
44 static void end_squery(struct search_query *squery, int status,
45                        unsigned char *abuf, int alen);
46 
ares_search(ares_channel channel,const char * name,int dnsclass,int type,ares_callback callback,void * arg)47 void ares_search(ares_channel channel, const char *name, int dnsclass,
48                  int type, ares_callback callback, void *arg)
49 {
50   struct search_query *squery;
51   char *s;
52   const char *p;
53   int status, ndots;
54 
55   /* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
56   if (ares__is_onion_domain(name))
57     {
58       callback(arg, ARES_ENOTFOUND, 0, NULL, 0);
59       return;
60     }
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 = ares__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       ares_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 = ares_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 = ares_strdup(name);
89   if (!squery->name)
90     {
91       ares_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 = ares__cat_domain(name, channel->domains[0], &s);
128       if (status == ARES_SUCCESS)
129         {
130           ares_query(channel, s, dnsclass, type, search_callback, squery);
131           ares_free(s);
132         }
133       else
134       {
135         /* failed, free the malloc()ed memory */
136         ares_free(squery->name);
137         ares_free(squery);
138         callback(arg, status, 0, NULL, 0);
139       }
140     }
141 }
142 
search_callback(void * arg,int status,int timeouts,unsigned char * abuf,int alen)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 = ares__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               ares_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 
end_squery(struct search_query * squery,int status,unsigned char * abuf,int alen)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   ares_free(squery->name);
210   ares_free(squery);
211 }
212 
213 /* Concatenate two domains. */
ares__cat_domain(const char * name,const char * domain,char ** s)214 int ares__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 = ares_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  */
ares__single_domain(ares_channel channel,const char * name,char ** s)233 int ares__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 ((len > 0) && (name[len - 1] == '.'))
248     {
249       *s = ares_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 = ares_malloc(q - p + 1);
277                       if (*s)
278                         {
279                           memcpy(*s, p, q - p);
280                           (*s)[q - p] = 0;
281                         }
282                       ares_free(line);
283                       fclose(fp);
284                       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
285                     }
286                 }
287               ares_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 = ares_strdup(name);
316       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
317     }
318 
319   *s = NULL;
320   return ARES_SUCCESS;
321 }
322