• 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_private.h"
29 
30 #ifdef HAVE_STRINGS_H
31 #  include <strings.h>
32 #endif
33 
34 struct search_query {
35   /* Arguments passed to ares_search_dnsrec() */
36   ares_channel_t      *channel;
37   ares_callback_dnsrec callback;
38   void                *arg;
39 
40   /* Duplicate of DNS record passed to ares_search_dnsrec() */
41   ares_dns_record_t   *dnsrec;
42 
43   /* Search order for names */
44   char               **names;
45   size_t               names_cnt;
46 
47   /* State tracking progress through the search query */
48   size_t               next_name_idx; /* next name index being attempted */
49   size_t      timeouts;        /* number of timeouts we saw for this request */
50   ares_bool_t ever_got_nodata; /* did we ever get ARES_ENODATA along the way? */
51 };
52 
squery_free(struct search_query * squery)53 static void squery_free(struct search_query *squery)
54 {
55   if (squery == NULL) {
56     return; /* LCOV_EXCL_LINE: DefensiveCoding */
57   }
58   ares_strsplit_free(squery->names, squery->names_cnt);
59   ares_dns_record_destroy(squery->dnsrec);
60   ares_free(squery);
61 }
62 
63 /* End a search query by invoking the user callback and freeing the
64  * search_query structure.
65  */
end_squery(struct search_query * squery,ares_status_t status,const ares_dns_record_t * dnsrec)66 static void end_squery(struct search_query *squery, ares_status_t status,
67                        const ares_dns_record_t *dnsrec)
68 {
69   squery->callback(squery->arg, status, squery->timeouts, dnsrec);
70   squery_free(squery);
71 }
72 
73 static void search_callback(void *arg, ares_status_t status, size_t timeouts,
74                             const ares_dns_record_t *dnsrec);
75 
ares_search_next(ares_channel_t * channel,struct search_query * squery,ares_bool_t * skip_cleanup)76 static ares_status_t ares_search_next(ares_channel_t      *channel,
77                                       struct search_query *squery,
78                                       ares_bool_t         *skip_cleanup)
79 {
80   ares_status_t status;
81 
82   *skip_cleanup = ARES_FALSE;
83 
84   /* Misuse check */
85   if (squery->next_name_idx >= squery->names_cnt) {
86     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
87   }
88 
89   status = ares_dns_record_query_set_name(
90     squery->dnsrec, 0, squery->names[squery->next_name_idx++]);
91   if (status != ARES_SUCCESS) {
92     return status;
93   }
94 
95   status = ares_send_nolock(channel, NULL, 0, squery->dnsrec, search_callback,
96                             squery, NULL);
97 
98   if (status != ARES_EFORMERR) {
99     *skip_cleanup = ARES_TRUE;
100   }
101 
102   return status;
103 }
104 
search_callback(void * arg,ares_status_t status,size_t timeouts,const ares_dns_record_t * dnsrec)105 static void search_callback(void *arg, ares_status_t status, size_t timeouts,
106                             const ares_dns_record_t *dnsrec)
107 {
108   struct search_query *squery  = (struct search_query *)arg;
109   ares_channel_t      *channel = squery->channel;
110 
111   ares_status_t        mystatus;
112   ares_bool_t          skip_cleanup = ARES_FALSE;
113 
114   squery->timeouts += timeouts;
115 
116   if (dnsrec) {
117     ares_dns_rcode_t rcode = ares_dns_record_get_rcode(dnsrec);
118     size_t ancount = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER);
119     mystatus       = ares_dns_query_reply_tostatus(rcode, ancount);
120   } else {
121     mystatus = status;
122   }
123 
124   switch (mystatus) {
125     case ARES_ENODATA:
126     case ARES_ENOTFOUND:
127       break;
128     case ARES_ESERVFAIL:
129     case ARES_EREFUSED:
130       /* Issue #852, systemd-resolved may return SERVFAIL or REFUSED on a
131        * single label domain name. */
132       if (ares_name_label_cnt(squery->names[squery->next_name_idx - 1]) != 1) {
133         end_squery(squery, mystatus, dnsrec);
134         return;
135       }
136       break;
137     default:
138       end_squery(squery, mystatus, dnsrec);
139       return;
140   }
141 
142   /* If we ever get ARES_ENODATA along the way, record that; if the search
143    * should run to the very end and we got at least one ARES_ENODATA,
144    * then callers like ares_gethostbyname() may want to try a T_A search
145    * even if the last domain we queried for T_AAAA resource records
146    * returned ARES_ENOTFOUND.
147    */
148   if (mystatus == ARES_ENODATA) {
149     squery->ever_got_nodata = ARES_TRUE;
150   }
151 
152   if (squery->next_name_idx < squery->names_cnt) {
153     mystatus = ares_search_next(channel, squery, &skip_cleanup);
154     if (mystatus != ARES_SUCCESS && !skip_cleanup) {
155       end_squery(squery, mystatus, NULL);
156     }
157     return;
158   }
159 
160   /* We have no more domains to search, return an appropriate response. */
161   if (mystatus == ARES_ENOTFOUND && squery->ever_got_nodata) {
162     end_squery(squery, ARES_ENODATA, NULL);
163     return;
164   }
165 
166   end_squery(squery, mystatus, NULL);
167 }
168 
169 /* Determine if the domain should be looked up as-is, or if it is eligible
170  * for search by appending domains */
ares_search_eligible(const ares_channel_t * channel,const char * name)171 static ares_bool_t ares_search_eligible(const ares_channel_t *channel,
172                                         const char           *name)
173 {
174   size_t len = ares_strlen(name);
175 
176   /* Name ends in '.', cannot search */
177   if (len && name[len - 1] == '.') {
178     return ARES_FALSE;
179   }
180 
181   if (channel->flags & ARES_FLAG_NOSEARCH) {
182     return ARES_FALSE;
183   }
184 
185   return ARES_TRUE;
186 }
187 
ares_name_label_cnt(const char * name)188 size_t ares_name_label_cnt(const char *name)
189 {
190   const char *p;
191   size_t      ndots = 0;
192 
193   if (name == NULL) {
194     return 0;
195   }
196 
197   for (p = name; p != NULL && *p != 0; p++) {
198     if (*p == '.') {
199       ndots++;
200     }
201   }
202 
203   /* Label count is 1 greater than ndots */
204   return ndots + 1;
205 }
206 
ares_search_name_list(const ares_channel_t * channel,const char * name,char *** names,size_t * names_len)207 ares_status_t ares_search_name_list(const ares_channel_t *channel,
208                                     const char *name, char ***names,
209                                     size_t *names_len)
210 {
211   ares_status_t status;
212   char        **list     = NULL;
213   size_t        list_len = 0;
214   char         *alias    = NULL;
215   size_t        ndots    = 0;
216   size_t        idx      = 0;
217   size_t        i;
218 
219   /* Perform HOSTALIASES resolution */
220   status = ares_lookup_hostaliases(channel, name, &alias);
221   if (status == ARES_SUCCESS) {
222     /* If hostalias succeeds, there is no searching, it is used as-is */
223     list_len = 1;
224     list     = ares_malloc_zero(sizeof(*list) * list_len);
225     if (list == NULL) {
226       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
227       goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
228     }
229     list[0] = alias;
230     alias   = NULL;
231     goto done;
232   } else if (status != ARES_ENOTFOUND) {
233     goto done;
234   }
235 
236   /* See if searching is eligible at all, if not, look up as-is only */
237   if (!ares_search_eligible(channel, name)) {
238     list_len = 1;
239     list     = ares_malloc_zero(sizeof(*list) * list_len);
240     if (list == NULL) {
241       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
242       goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
243     }
244     list[0] = ares_strdup(name);
245     if (list[0] == NULL) {
246       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
247     } else {
248       status = ARES_SUCCESS;
249     }
250     goto done;
251   }
252 
253   /* Count the number of dots in name, 1 less than label count */
254   ndots = ares_name_label_cnt(name);
255   if (ndots > 0) {
256     ndots--;
257   }
258 
259   /* Allocate an entry for each search domain, plus one for as-is */
260   list_len = channel->ndomains + 1;
261   list     = ares_malloc_zero(sizeof(*list) * list_len);
262   if (list == NULL) {
263     status = ARES_ENOMEM;
264     goto done;
265   }
266 
267   /* Set status here, its possible there are no search domains at all, so
268    * status may be ARES_ENOTFOUND from ares_lookup_hostaliases(). */
269   status = ARES_SUCCESS;
270 
271   /* Try as-is first */
272   if (ndots >= channel->ndots) {
273     list[idx] = ares_strdup(name);
274     if (list[idx] == NULL) {
275       status = ARES_ENOMEM;
276       goto done;
277     }
278     idx++;
279   }
280 
281   /* Append each search suffix to the name */
282   for (i = 0; i < channel->ndomains; i++) {
283     status = ares_cat_domain(name, channel->domains[i], &list[idx]);
284     if (status != ARES_SUCCESS) {
285       goto done;
286     }
287     idx++;
288   }
289 
290   /* Try as-is last */
291   if (ndots < channel->ndots) {
292     list[idx] = ares_strdup(name);
293     if (list[idx] == NULL) {
294       status = ARES_ENOMEM;
295       goto done;
296     }
297     idx++;
298   }
299 
300 
301 done:
302   if (status == ARES_SUCCESS) {
303     *names     = list;
304     *names_len = list_len;
305   } else {
306     ares_strsplit_free(list, list_len);
307   }
308 
309   ares_free(alias);
310   return status;
311 }
312 
ares_search_int(ares_channel_t * channel,const ares_dns_record_t * dnsrec,ares_callback_dnsrec callback,void * arg)313 static ares_status_t ares_search_int(ares_channel_t          *channel,
314                                      const ares_dns_record_t *dnsrec,
315                                      ares_callback_dnsrec callback, void *arg)
316 {
317   struct search_query *squery = NULL;
318   const char          *name;
319   ares_status_t        status       = ARES_SUCCESS;
320   ares_bool_t          skip_cleanup = ARES_FALSE;
321 
322   /* Extract the name for the search. Note that searches are only supported for
323    * DNS records containing a single query.
324    */
325   if (ares_dns_record_query_cnt(dnsrec) != 1) {
326     status = ARES_EBADQUERY;
327     goto fail;
328   }
329 
330   status = ares_dns_record_query_get(dnsrec, 0, &name, NULL, NULL);
331   if (status != ARES_SUCCESS) {
332     goto fail;
333   }
334 
335   /* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
336   if (ares_is_onion_domain(name)) {
337     status = ARES_ENOTFOUND;
338     goto fail;
339   }
340 
341   /* Allocate a search_query structure to hold the state necessary for
342    * doing multiple lookups.
343    */
344   squery = ares_malloc_zero(sizeof(*squery));
345   if (squery == NULL) {
346     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
347     goto fail;            /* LCOV_EXCL_LINE: OutOfMemory */
348   }
349 
350   squery->channel = channel;
351 
352   /* Duplicate DNS record since, name will need to be rewritten */
353   squery->dnsrec = ares_dns_record_duplicate(dnsrec);
354   if (squery->dnsrec == NULL) {
355     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
356     goto fail;            /* LCOV_EXCL_LINE: OutOfMemory */
357   }
358 
359   squery->callback        = callback;
360   squery->arg             = arg;
361   squery->timeouts        = 0;
362   squery->ever_got_nodata = ARES_FALSE;
363 
364   status =
365     ares_search_name_list(channel, name, &squery->names, &squery->names_cnt);
366   if (status != ARES_SUCCESS) {
367     goto fail;
368   }
369 
370   status = ares_search_next(channel, squery, &skip_cleanup);
371   if (status != ARES_SUCCESS) {
372     goto fail;
373   }
374 
375   return status;
376 
377 fail:
378   if (!skip_cleanup) {
379     squery_free(squery);
380     callback(arg, status, 0, NULL);
381   }
382   return status;
383 }
384 
385 /* Callback argument structure passed to ares_dnsrec_convert_cb(). */
386 typedef struct {
387   ares_callback callback;
388   void         *arg;
389 } dnsrec_convert_arg_t;
390 
391 /*! Function to create callback arg for converting from ares_callback_dnsrec
392  *  to ares_calback */
ares_dnsrec_convert_arg(ares_callback callback,void * arg)393 void *ares_dnsrec_convert_arg(ares_callback callback, void *arg)
394 {
395   dnsrec_convert_arg_t *carg = ares_malloc_zero(sizeof(*carg));
396   if (carg == NULL) {
397     return NULL;
398   }
399   carg->callback = callback;
400   carg->arg      = arg;
401   return carg;
402 }
403 
404 /*! Callback function used to convert from the ares_callback_dnsrec prototype to
405  *  the ares_callback prototype, by writing the result and passing that to
406  *  the inner callback.
407  */
ares_dnsrec_convert_cb(void * arg,ares_status_t status,size_t timeouts,const ares_dns_record_t * dnsrec)408 void ares_dnsrec_convert_cb(void *arg, ares_status_t status, size_t timeouts,
409                             const ares_dns_record_t *dnsrec)
410 {
411   dnsrec_convert_arg_t *carg = arg;
412   unsigned char        *abuf = NULL;
413   size_t                alen = 0;
414 
415   if (dnsrec != NULL) {
416     ares_status_t mystatus = ares_dns_write(dnsrec, &abuf, &alen);
417     if (mystatus != ARES_SUCCESS) {
418       status = mystatus;
419     }
420   }
421 
422   carg->callback(carg->arg, (int)status, (int)timeouts, abuf, (int)alen);
423 
424   ares_free(abuf);
425   ares_free(carg);
426 }
427 
428 /* Search for a DNS name with given class and type. Wrapper around
429  * ares_search_int() where the DNS record to search is first constructed.
430  */
ares_search(ares_channel_t * channel,const char * name,int dnsclass,int type,ares_callback callback,void * arg)431 void ares_search(ares_channel_t *channel, const char *name, int dnsclass,
432                  int type, ares_callback callback, void *arg)
433 {
434   ares_status_t      status;
435   ares_dns_record_t *dnsrec = NULL;
436   size_t             max_udp_size;
437   ares_dns_flags_t   rd_flag;
438   void              *carg = NULL;
439   if (channel == NULL || name == NULL) {
440     return;
441   }
442 
443   /* For now, ares_search_int() uses the ares_callback prototype. We need to
444    * wrap the callback passed to this function in ares_dnsrec_convert_cb, to
445    * convert from ares_callback_dnsrec to ares_callback. Allocate the convert
446    * arg structure here.
447    */
448   carg = ares_dnsrec_convert_arg(callback, arg);
449   if (carg == NULL) {
450     callback(arg, ARES_ENOMEM, 0, NULL, 0);
451     return;
452   }
453 
454   rd_flag      = !(channel->flags & ARES_FLAG_NORECURSE) ? ARES_FLAG_RD : 0;
455   max_udp_size = (channel->flags & ARES_FLAG_EDNS) ? channel->ednspsz : 0;
456   status       = ares_dns_record_create_query(
457     &dnsrec, name, (ares_dns_class_t)dnsclass, (ares_dns_rec_type_t)type, 0,
458     rd_flag, max_udp_size);
459   if (status != ARES_SUCCESS) {
460     callback(arg, (int)status, 0, NULL, 0);
461     ares_free(carg);
462     return;
463   }
464 
465   ares_channel_lock(channel);
466   ares_search_int(channel, dnsrec, ares_dnsrec_convert_cb, carg);
467   ares_channel_unlock(channel);
468 
469   ares_dns_record_destroy(dnsrec);
470 }
471 
472 /* Search for a DNS record. Wrapper around ares_search_int(). */
ares_search_dnsrec(ares_channel_t * channel,const ares_dns_record_t * dnsrec,ares_callback_dnsrec callback,void * arg)473 ares_status_t ares_search_dnsrec(ares_channel_t          *channel,
474                                  const ares_dns_record_t *dnsrec,
475                                  ares_callback_dnsrec callback, void *arg)
476 {
477   ares_status_t status;
478 
479   if (channel == NULL || dnsrec == NULL || callback == NULL) {
480     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
481   }
482 
483   ares_channel_lock(channel);
484   status = ares_search_int(channel, dnsrec, callback, arg);
485   ares_channel_unlock(channel);
486 
487   return status;
488 }
489 
490 /* Concatenate two domains. */
ares_cat_domain(const char * name,const char * domain,char ** s)491 ares_status_t ares_cat_domain(const char *name, const char *domain, char **s)
492 {
493   size_t nlen = ares_strlen(name);
494   size_t dlen = ares_strlen(domain);
495 
496   *s = ares_malloc(nlen + 1 + dlen + 1);
497   if (!*s) {
498     return ARES_ENOMEM;
499   }
500   memcpy(*s, name, nlen);
501   (*s)[nlen] = '.';
502   if (ares_streq(domain, ".")) {
503     /* Avoid appending the root domain to the separator, which would set *s to
504        an ill-formed value (ending in two consecutive dots). */
505     dlen = 0;
506   }
507   memcpy(*s + nlen + 1, domain, dlen);
508   (*s)[nlen + 1 + dlen] = 0;
509   return ARES_SUCCESS;
510 }
511 
ares_lookup_hostaliases(const ares_channel_t * channel,const char * name,char ** alias)512 ares_status_t ares_lookup_hostaliases(const ares_channel_t *channel,
513                                       const char *name, char **alias)
514 {
515   ares_status_t status      = ARES_SUCCESS;
516   const char   *hostaliases = NULL;
517   ares_buf_t   *buf         = NULL;
518   ares_array_t *lines       = NULL;
519   size_t        num;
520   size_t        i;
521 
522   if (channel == NULL || name == NULL || alias == NULL) {
523     return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
524   }
525 
526   *alias = NULL;
527 
528   /* Configuration says to not perform alias lookup */
529   if (channel->flags & ARES_FLAG_NOALIASES) {
530     return ARES_ENOTFOUND;
531   }
532 
533   /* If a domain has a '.', its not allowed to perform an alias lookup */
534   if (strchr(name, '.') != NULL) {
535     return ARES_ENOTFOUND;
536   }
537 
538   hostaliases = getenv("HOSTALIASES");
539   if (hostaliases == NULL) {
540     status = ARES_ENOTFOUND;
541     goto done;
542   }
543 
544   buf = ares_buf_create();
545   if (buf == NULL) {
546     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
547     goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
548   }
549 
550   status = ares_buf_load_file(hostaliases, buf);
551   if (status != ARES_SUCCESS) {
552     goto done;
553   }
554 
555   /* The HOSTALIASES file is structured as one alias per line.  The first
556    * field in the line is the simple hostname with no periods, followed by
557    * whitespace, then the full domain name, e.g.:
558    *
559    * c-ares  www.c-ares.org
560    * curl    www.curl.se
561    */
562 
563   status = ares_buf_split(buf, (const unsigned char *)"\n", 1,
564                           ARES_BUF_SPLIT_TRIM, 0, &lines);
565   if (status != ARES_SUCCESS) {
566     goto done;
567   }
568 
569   num = ares_array_len(lines);
570   for (i = 0; i < num; i++) {
571     ares_buf_t **bufptr       = ares_array_at(lines, i);
572     ares_buf_t  *line         = *bufptr;
573     char         hostname[64] = "";
574     char         fqdn[256]    = "";
575 
576     /* Pull off hostname */
577     ares_buf_tag(line);
578     ares_buf_consume_nonwhitespace(line);
579     if (ares_buf_tag_fetch_string(line, hostname, sizeof(hostname)) !=
580         ARES_SUCCESS) {
581       continue;
582     }
583 
584     /* Match hostname */
585     if (!ares_strcaseeq(hostname, name)) {
586       continue;
587     }
588 
589     /* consume whitespace */
590     ares_buf_consume_whitespace(line, ARES_TRUE);
591 
592     /* pull off fqdn */
593     ares_buf_tag(line);
594     ares_buf_consume_nonwhitespace(line);
595     if (ares_buf_tag_fetch_string(line, fqdn, sizeof(fqdn)) != ARES_SUCCESS ||
596         ares_strlen(fqdn) == 0) {
597       continue;
598     }
599 
600     /* Validate characterset */
601     if (!ares_is_hostname(fqdn)) {
602       continue;
603     }
604 
605     *alias = ares_strdup(fqdn);
606     if (*alias == NULL) {
607       status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
608       goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
609     }
610 
611     /* Good! */
612     status = ARES_SUCCESS;
613     goto done;
614   }
615 
616   status = ARES_ENOTFOUND;
617 
618 done:
619   ares_buf_destroy(buf);
620   ares_array_destroy(lines);
621 
622   return status;
623 }
624