• 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 static int cat_domain(const char *name, const char *domain, char **s);
47 STATIC_TESTABLE int single_domain(ares_channel channel, const char *name, char **s);
48 
ares_search(ares_channel channel,const char * name,int dnsclass,int type,ares_callback callback,void * arg)49 void ares_search(ares_channel channel, const char *name, int dnsclass,
50                  int type, ares_callback callback, void *arg)
51 {
52   struct search_query *squery;
53   char *s;
54   const char *p;
55   int status, ndots;
56 
57   /* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
58   if (ares__is_onion_domain(name))
59     {
60       callback(arg, ARES_ENOTFOUND, 0, NULL, 0);
61       return;
62     }
63 
64   /* If name only yields one domain to search, then we don't have
65    * to keep extra state, so just do an ares_query().
66    */
67   status = single_domain(channel, name, &s);
68   if (status != ARES_SUCCESS)
69     {
70       callback(arg, status, 0, NULL, 0);
71       return;
72     }
73   if (s)
74     {
75       ares_query(channel, s, dnsclass, type, callback, arg);
76       ares_free(s);
77       return;
78     }
79 
80   /* Allocate a search_query structure to hold the state necessary for
81    * doing multiple lookups.
82    */
83   squery = ares_malloc(sizeof(struct search_query));
84   if (!squery)
85     {
86       callback(arg, ARES_ENOMEM, 0, NULL, 0);
87       return;
88     }
89   squery->channel = channel;
90   squery->name = ares_strdup(name);
91   if (!squery->name)
92     {
93       ares_free(squery);
94       callback(arg, ARES_ENOMEM, 0, NULL, 0);
95       return;
96     }
97   squery->dnsclass = dnsclass;
98   squery->type = type;
99   squery->status_as_is = -1;
100   squery->callback = callback;
101   squery->arg = arg;
102   squery->timeouts = 0;
103   squery->ever_got_nodata = 0;
104 
105   /* Count the number of dots in name. */
106   ndots = 0;
107   for (p = name; *p; p++)
108     {
109       if (*p == '.')
110         ndots++;
111     }
112 
113   /* If ndots is at least the channel ndots threshold (usually 1),
114    * then we try the name as-is first.  Otherwise, we try the name
115    * as-is last.
116    */
117   if (ndots >= channel->ndots)
118     {
119       /* Try the name as-is first. */
120       squery->next_domain = 0;
121       squery->trying_as_is = 1;
122       ares_query(channel, name, dnsclass, type, search_callback, squery);
123     }
124   else
125     {
126       /* Try the name as-is last; start with the first search domain. */
127       squery->next_domain = 1;
128       squery->trying_as_is = 0;
129       status = cat_domain(name, channel->domains[0], &s);
130       if (status == ARES_SUCCESS)
131         {
132           ares_query(channel, s, dnsclass, type, search_callback, squery);
133           ares_free(s);
134         }
135       else
136       {
137         /* failed, free the malloc()ed memory */
138         ares_free(squery->name);
139         ares_free(squery);
140         callback(arg, status, 0, NULL, 0);
141       }
142     }
143 }
144 
search_callback(void * arg,int status,int timeouts,unsigned char * abuf,int alen)145 static void search_callback(void *arg, int status, int timeouts,
146                             unsigned char *abuf, int alen)
147 {
148   struct search_query *squery = (struct search_query *) arg;
149   ares_channel channel = squery->channel;
150   char *s;
151 
152   squery->timeouts += timeouts;
153 
154   /* Stop searching unless we got a non-fatal error. */
155   if (status != ARES_ENODATA && status != ARES_ESERVFAIL
156       && status != ARES_ENOTFOUND)
157     end_squery(squery, status, abuf, alen);
158   else
159     {
160       /* Save the status if we were trying as-is. */
161       if (squery->trying_as_is)
162         squery->status_as_is = status;
163 
164       /*
165        * If we ever get ARES_ENODATA along the way, record that; if the search
166        * should run to the very end and we got at least one ARES_ENODATA,
167        * then callers like ares_gethostbyname() may want to try a T_A search
168        * even if the last domain we queried for T_AAAA resource records
169        * returned ARES_ENOTFOUND.
170        */
171       if (status == ARES_ENODATA)
172         squery->ever_got_nodata = 1;
173 
174       if (squery->next_domain < channel->ndomains)
175         {
176           /* Try the next domain. */
177           status = cat_domain(squery->name,
178                               channel->domains[squery->next_domain], &s);
179           if (status != ARES_SUCCESS)
180             end_squery(squery, status, NULL, 0);
181           else
182             {
183               squery->trying_as_is = 0;
184               squery->next_domain++;
185               ares_query(channel, s, squery->dnsclass, squery->type,
186                          search_callback, squery);
187               ares_free(s);
188             }
189         }
190       else if (squery->status_as_is == -1)
191         {
192           /* Try the name as-is at the end. */
193           squery->trying_as_is = 1;
194           ares_query(channel, squery->name, squery->dnsclass, squery->type,
195                      search_callback, squery);
196         }
197       else {
198         if (squery->status_as_is == ARES_ENOTFOUND && squery->ever_got_nodata) {
199           end_squery(squery, ARES_ENODATA, NULL, 0);
200         }
201         else
202           end_squery(squery, squery->status_as_is, NULL, 0);
203       }
204     }
205 }
206 
end_squery(struct search_query * squery,int status,unsigned char * abuf,int alen)207 static void end_squery(struct search_query *squery, int status,
208                        unsigned char *abuf, int alen)
209 {
210   squery->callback(squery->arg, status, squery->timeouts, abuf, alen);
211   ares_free(squery->name);
212   ares_free(squery);
213 }
214 
215 /* Concatenate two domains. */
cat_domain(const char * name,const char * domain,char ** s)216 static int cat_domain(const char *name, const char *domain, char **s)
217 {
218   size_t nlen = strlen(name);
219   size_t dlen = strlen(domain);
220 
221   *s = ares_malloc(nlen + 1 + dlen + 1);
222   if (!*s)
223     return ARES_ENOMEM;
224   memcpy(*s, name, nlen);
225   (*s)[nlen] = '.';
226   memcpy(*s + nlen + 1, domain, dlen);
227   (*s)[nlen + 1 + dlen] = 0;
228   return ARES_SUCCESS;
229 }
230 
231 /* Determine if this name only yields one query.  If it does, set *s to
232  * the string we should query, in an allocated buffer.  If not, set *s
233  * to NULL.
234  */
single_domain(ares_channel channel,const char * name,char ** s)235 STATIC_TESTABLE int single_domain(ares_channel channel, const char *name, char **s)
236 {
237   size_t len = strlen(name);
238   const char *hostaliases;
239   FILE *fp;
240   char *line = NULL;
241   int status;
242   size_t linesize;
243   const char *p, *q;
244   int error;
245 
246   /* If the name contains a trailing dot, then the single query is the name
247    * sans the trailing dot.
248    */
249   if ((len > 0) && (name[len - 1] == '.'))
250     {
251       *s = ares_strdup(name);
252       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
253     }
254 
255   if (!(channel->flags & ARES_FLAG_NOALIASES) && !strchr(name, '.'))
256     {
257       /* The name might be a host alias. */
258       hostaliases = getenv("HOSTALIASES");
259       if (hostaliases)
260         {
261           fp = fopen(hostaliases, "r");
262           if (fp)
263             {
264               while ((status = ares__read_line(fp, &line, &linesize))
265                      == ARES_SUCCESS)
266                 {
267                   if (strncasecmp(line, name, len) != 0 ||
268                       !ISSPACE(line[len]))
269                     continue;
270                   p = line + len;
271                   while (ISSPACE(*p))
272                     p++;
273                   if (*p)
274                     {
275                       q = p + 1;
276                       while (*q && !ISSPACE(*q))
277                         q++;
278                       *s = ares_malloc(q - p + 1);
279                       if (*s)
280                         {
281                           memcpy(*s, p, q - p);
282                           (*s)[q - p] = 0;
283                         }
284                       ares_free(line);
285                       fclose(fp);
286                       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
287                     }
288                 }
289               ares_free(line);
290               fclose(fp);
291               if (status != ARES_SUCCESS && status != ARES_EOF)
292                 return status;
293             }
294           else
295             {
296               error = ERRNO;
297               switch(error)
298                 {
299                 case ENOENT:
300                 case ESRCH:
301                   break;
302                 default:
303                   DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n",
304                                  error, strerror(error)));
305                   DEBUGF(fprintf(stderr, "Error opening file: %s\n",
306                                  hostaliases));
307                   *s = NULL;
308                   return ARES_EFILE;
309                 }
310             }
311         }
312     }
313 
314   if (channel->flags & ARES_FLAG_NOSEARCH || channel->ndomains == 0)
315     {
316       /* No domain search to do; just try the name as-is. */
317       *s = ares_strdup(name);
318       return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
319     }
320 
321   *s = NULL;
322   return ARES_SUCCESS;
323 }
324