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