ares_search.c (17895B)
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 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 */ 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 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 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 */ 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 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 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 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 */ 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 */ 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 */ 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(). */ 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. */ 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 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 }