• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* MIT License
2  *
3  * Copyright (c) 1998 Massachusetts Institute of Technology
4  * Copyright (c) The c-ares project and its contributors
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the next
14  * paragraph) shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  *
25  * SPDX-License-Identifier: MIT
26  */
27 
28 #include "ares_setup.h"
29 
30 #ifdef HAVE_STRINGS_H
31 #  include <strings.h>
32 #endif
33 
34 #include "ares.h"
35 #include "ares_private.h"
36 
37 struct search_query {
38   /* Arguments passed to ares_search */
39   ares_channel_t *channel;
40   char           *name; /* copied into an allocated buffer */
41   int             dnsclass;
42   int             type;
43   ares_callback   callback;
44   void           *arg;
45   char          **domains; /* duplicate for ares_reinit() safety */
46   size_t          ndomains;
47 
48   int             status_as_is; /* error status from trying as-is */
49   size_t          next_domain;  /* next search domain to try */
50   ares_bool_t     trying_as_is; /* current query is for name as-is */
51   size_t          timeouts;     /* number of timeouts we saw for this request */
52   ares_bool_t ever_got_nodata; /* did we ever get ARES_ENODATA along the way? */
53 };
54 
55 static void search_callback(void *arg, int status, int timeouts,
56                             unsigned char *abuf, int alen);
57 static void end_squery(struct search_query *squery, ares_status_t status,
58                        unsigned char *abuf, size_t alen);
59 
ares_search_int(ares_channel_t * channel,const char * name,int dnsclass,int type,ares_callback callback,void * arg)60 static void ares_search_int(ares_channel_t *channel, const char *name,
61                             int dnsclass, int type, ares_callback callback,
62                             void *arg)
63 {
64   struct search_query *squery;
65   char                *s;
66   const char          *p;
67   ares_status_t        status;
68   size_t               ndots;
69 
70   /* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
71   if (ares__is_onion_domain(name)) {
72     callback(arg, ARES_ENOTFOUND, 0, NULL, 0);
73     return;
74   }
75 
76   /* If name only yields one domain to search, then we don't have
77    * to keep extra state, so just do an ares_query().
78    */
79   status = ares__single_domain(channel, name, &s);
80   if (status != ARES_SUCCESS) {
81     callback(arg, (int)status, 0, NULL, 0);
82     return;
83   }
84   if (s) {
85     ares_query(channel, s, dnsclass, type, callback, arg);
86     ares_free(s);
87     return;
88   }
89 
90   /* Allocate a search_query structure to hold the state necessary for
91    * doing multiple lookups.
92    */
93   squery = ares_malloc_zero(sizeof(*squery));
94   if (!squery) {
95     callback(arg, ARES_ENOMEM, 0, NULL, 0);
96     return;
97   }
98   squery->channel = channel;
99   squery->name    = ares_strdup(name);
100   if (!squery->name) {
101     ares_free(squery);
102     callback(arg, ARES_ENOMEM, 0, NULL, 0);
103     return;
104   }
105 
106   /* Duplicate domains for safety during ares_reinit() */
107   if (channel->ndomains) {
108     squery->domains =
109       ares__strsplit_duplicate(channel->domains, channel->ndomains);
110     if (squery->domains == NULL) {
111       ares_free(squery->name);
112       ares_free(squery);
113       callback(arg, ARES_ENOMEM, 0, NULL, 0);
114       return;
115     }
116     squery->ndomains = channel->ndomains;
117   }
118 
119   squery->dnsclass        = dnsclass;
120   squery->type            = type;
121   squery->status_as_is    = -1;
122   squery->callback        = callback;
123   squery->arg             = arg;
124   squery->timeouts        = 0;
125   squery->ever_got_nodata = ARES_FALSE;
126 
127   /* Count the number of dots in name. */
128   ndots = 0;
129   for (p = name; *p; p++) {
130     if (*p == '.') {
131       ndots++;
132     }
133   }
134 
135   /* If ndots is at least the channel ndots threshold (usually 1),
136    * then we try the name as-is first.  Otherwise, we try the name
137    * as-is last.
138    */
139   if (ndots >= channel->ndots || squery->ndomains == 0) {
140     /* Try the name as-is first. */
141     squery->next_domain  = 0;
142     squery->trying_as_is = ARES_TRUE;
143     ares_query(channel, name, dnsclass, type, search_callback, squery);
144   } else {
145     /* Try the name as-is last; start with the first search domain. */
146     squery->next_domain  = 1;
147     squery->trying_as_is = ARES_FALSE;
148     status               = ares__cat_domain(name, squery->domains[0], &s);
149     if (status == ARES_SUCCESS) {
150       ares_query(channel, s, dnsclass, type, search_callback, squery);
151       ares_free(s);
152     } else {
153       /* failed, free the malloc()ed memory */
154       ares_free(squery->name);
155       ares_free(squery);
156       callback(arg, (int)status, 0, NULL, 0);
157     }
158   }
159 }
160 
ares_search(ares_channel_t * channel,const char * name,int dnsclass,int type,ares_callback callback,void * arg)161 void ares_search(ares_channel_t *channel, const char *name, int dnsclass,
162                  int type, ares_callback callback, void *arg)
163 {
164   if (channel == NULL) {
165     return;
166   }
167   ares__channel_lock(channel);
168   ares_search_int(channel, name, dnsclass, type, callback, arg);
169   ares__channel_unlock(channel);
170 }
171 
search_callback(void * arg,int status,int timeouts,unsigned char * abuf,int alen)172 static void search_callback(void *arg, int status, int timeouts,
173                             unsigned char *abuf, int alen)
174 {
175   struct search_query *squery  = (struct search_query *)arg;
176   ares_channel_t      *channel = squery->channel;
177   char                *s;
178 
179   squery->timeouts += (size_t)timeouts;
180 
181   /* Stop searching unless we got a non-fatal error. */
182   if (status != ARES_ENODATA && status != ARES_ESERVFAIL &&
183       status != ARES_ENOTFOUND) {
184     end_squery(squery, (ares_status_t)status, abuf, (size_t)alen);
185   } else {
186     /* Save the status if we were trying as-is. */
187     if (squery->trying_as_is) {
188       squery->status_as_is = status;
189     }
190 
191     /*
192      * If we ever get ARES_ENODATA along the way, record that; if the search
193      * should run to the very end and we got at least one ARES_ENODATA,
194      * then callers like ares_gethostbyname() may want to try a T_A search
195      * even if the last domain we queried for T_AAAA resource records
196      * returned ARES_ENOTFOUND.
197      */
198     if (status == ARES_ENODATA) {
199       squery->ever_got_nodata = ARES_TRUE;
200     }
201 
202     if (squery->next_domain < squery->ndomains) {
203       ares_status_t mystatus;
204       /* Try the next domain. */
205       mystatus = ares__cat_domain(squery->name,
206                                   squery->domains[squery->next_domain], &s);
207       if (mystatus != ARES_SUCCESS) {
208         end_squery(squery, mystatus, NULL, 0);
209       } else {
210         squery->trying_as_is = ARES_FALSE;
211         squery->next_domain++;
212         ares_query(channel, s, squery->dnsclass, squery->type, search_callback,
213                    squery);
214         ares_free(s);
215       }
216     } else if (squery->status_as_is == -1) {
217       /* Try the name as-is at the end. */
218       squery->trying_as_is = ARES_TRUE;
219       ares_query(channel, squery->name, squery->dnsclass, squery->type,
220                  search_callback, squery);
221     } else {
222       if (squery->status_as_is == ARES_ENOTFOUND && squery->ever_got_nodata) {
223         end_squery(squery, ARES_ENODATA, NULL, 0);
224       } else {
225         end_squery(squery, (ares_status_t)squery->status_as_is, NULL, 0);
226       }
227     }
228   }
229 }
230 
end_squery(struct search_query * squery,ares_status_t status,unsigned char * abuf,size_t alen)231 static void end_squery(struct search_query *squery, ares_status_t status,
232                        unsigned char *abuf, size_t alen)
233 {
234   squery->callback(squery->arg, (int)status, (int)squery->timeouts, abuf,
235                    (int)alen);
236   ares__strsplit_free(squery->domains, squery->ndomains);
237   ares_free(squery->name);
238   ares_free(squery);
239 }
240 
241 /* Concatenate two domains. */
ares__cat_domain(const char * name,const char * domain,char ** s)242 ares_status_t ares__cat_domain(const char *name, const char *domain, char **s)
243 {
244   size_t nlen = ares_strlen(name);
245   size_t dlen = ares_strlen(domain);
246 
247   *s = ares_malloc(nlen + 1 + dlen + 1);
248   if (!*s) {
249     return ARES_ENOMEM;
250   }
251   memcpy(*s, name, nlen);
252   (*s)[nlen] = '.';
253   if (strcmp(domain, ".") == 0) {
254     /* Avoid appending the root domain to the separator, which would set *s to
255        an ill-formed value (ending in two consecutive dots). */
256     dlen = 0;
257   }
258   memcpy(*s + nlen + 1, domain, dlen);
259   (*s)[nlen + 1 + dlen] = 0;
260   return ARES_SUCCESS;
261 }
262 
263 /* Determine if this name only yields one query.  If it does, set *s to
264  * the string we should query, in an allocated buffer.  If not, set *s
265  * to NULL.
266  */
ares__single_domain(const ares_channel_t * channel,const char * name,char ** s)267 ares_status_t ares__single_domain(const ares_channel_t *channel,
268                                   const char *name, char **s)
269 {
270   size_t        len = ares_strlen(name);
271   const char   *hostaliases;
272   FILE         *fp;
273   char         *line = NULL;
274   ares_status_t status;
275   size_t        linesize;
276   const char   *p;
277   const char   *q;
278   int           error;
279 
280   /* If the name contains a trailing dot, then the single query is the name
281    * sans the trailing dot.
282    */
283   if ((len > 0) && (name[len - 1] == '.')) {
284     *s = ares_strdup(name);
285     return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
286   }
287 
288   if (!(channel->flags & ARES_FLAG_NOALIASES) && !strchr(name, '.')) {
289     /* The name might be a host alias. */
290     hostaliases = getenv("HOSTALIASES");
291     if (hostaliases) {
292       fp = fopen(hostaliases, "r");
293       if (fp) {
294         while ((status = ares__read_line(fp, &line, &linesize)) ==
295                ARES_SUCCESS) {
296           if (strncasecmp(line, name, len) != 0 || !ISSPACE(line[len])) {
297             continue;
298           }
299           p = line + len;
300           while (ISSPACE(*p)) {
301             p++;
302           }
303           if (*p) {
304             q = p + 1;
305             while (*q && !ISSPACE(*q)) {
306               q++;
307             }
308             *s = ares_malloc((size_t)(q - p + 1));
309             if (*s) {
310               memcpy(*s, p, (size_t)(q - p));
311               (*s)[q - p] = 0;
312             }
313             ares_free(line);
314             fclose(fp);
315             return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
316           }
317         }
318         ares_free(line);
319         fclose(fp);
320         if (status != ARES_SUCCESS && status != ARES_EOF) {
321           return status;
322         }
323       } else {
324         error = ERRNO;
325         switch (error) {
326           case ENOENT:
327           case ESRCH:
328             break;
329           default:
330             DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
331                            strerror(error)));
332             DEBUGF(fprintf(stderr, "Error opening file: %s\n", hostaliases));
333             *s = NULL;
334             return ARES_EFILE;
335         }
336       }
337     }
338   }
339 
340   if (channel->flags & ARES_FLAG_NOSEARCH || channel->ndomains == 0) {
341     /* No domain search to do; just try the name as-is. */
342     *s = ares_strdup(name);
343     return (*s) ? ARES_SUCCESS : ARES_ENOMEM;
344   }
345 
346   *s = NULL;
347   return ARES_SUCCESS;
348 }
349