1/* MIT License 2 * 3 * Copyright (c) 2023 Brad House 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to deal 7 * in the Software without restriction, including without limitation the rights 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 * copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice (including the next 13 * paragraph) shall be included in all copies or substantial portions of the 14 * Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 * 24 * SPDX-License-Identifier: MIT 25 */ 26#include "ares_setup.h" 27#include "ares.h" 28#include "ares_private.h" 29 30typedef struct { 31 char *name; 32 size_t name_len; 33 size_t idx; 34} ares_nameoffset_t; 35 36static void ares__nameoffset_free(void *arg) 37{ 38 ares_nameoffset_t *off = arg; 39 if (off == NULL) { 40 return; 41 } 42 ares_free(off->name); 43 ares_free(off); 44} 45 46static ares_status_t ares__nameoffset_create(ares__llist_t **list, 47 const char *name, size_t idx) 48{ 49 ares_status_t status; 50 ares_nameoffset_t *off = NULL; 51 52 if (list == NULL || name == NULL || ares_strlen(name) == 0 || 53 ares_strlen(name) > 255) { 54 return ARES_EFORMERR; 55 } 56 57 if (*list == NULL) { 58 *list = ares__llist_create(ares__nameoffset_free); 59 } 60 if (*list == NULL) { 61 status = ARES_ENOMEM; 62 goto fail; 63 } 64 65 off = ares_malloc_zero(sizeof(*off)); 66 if (off == NULL) { 67 return ARES_ENOMEM; 68 } 69 70 off->name = ares_strdup(name); 71 off->name_len = ares_strlen(off->name); 72 off->idx = idx; 73 74 if (ares__llist_insert_last(*list, off) == NULL) { 75 status = ARES_ENOMEM; 76 goto fail; 77 } 78 79 return ARES_SUCCESS; 80 81fail: 82 ares__nameoffset_free(off); 83 return status; 84} 85 86static const ares_nameoffset_t *ares__nameoffset_find(ares__llist_t *list, 87 const char *name) 88{ 89 size_t name_len = ares_strlen(name); 90 ares__llist_node_t *node; 91 const ares_nameoffset_t *longest_match = NULL; 92 93 if (list == NULL || name == NULL || name_len == 0) { 94 return NULL; 95 } 96 97 for (node = ares__llist_node_first(list); node != NULL; 98 node = ares__llist_node_next(node)) { 99 const ares_nameoffset_t *val = ares__llist_node_val(node); 100 size_t prefix_len; 101 102 /* Can't be a match if the stored name is longer */ 103 if (val->name_len > name_len) { 104 continue; 105 } 106 107 /* Can't be the longest match if our existing longest match is longer */ 108 if (longest_match != NULL && longest_match->name_len > val->name_len) { 109 continue; 110 } 111 112 prefix_len = name_len - val->name_len; 113 114 if (strcasecmp(val->name, name + prefix_len) != 0) { 115 continue; 116 } 117 118 /* We need to make sure if `val->name` is "example.com" that name is 119 * is separated by a label, e.g. "myexample.com" is not ok, however 120 * "my.example.com" is, so we look for the preceding "." */ 121 if (prefix_len != 0 && name[prefix_len - 1] != '.') { 122 continue; 123 } 124 125 longest_match = val; 126 } 127 128 return longest_match; 129} 130 131typedef struct { 132 ares__buf_t **label; 133 size_t num; 134} ares_dns_labels_t; 135 136static void ares_dns_labels_free(ares_dns_labels_t *labels) 137{ 138 size_t i; 139 140 if (labels == NULL) { 141 return; 142 } 143 144 for (i = 0; i < labels->num; i++) { 145 ares__buf_destroy(labels->label[i]); 146 labels->label[i] = NULL; 147 } 148 ares_free(labels->label); 149 labels->label = NULL; 150 labels->num = 0; 151} 152 153static ares__buf_t *ares_dns_labels_add(ares_dns_labels_t *labels) 154{ 155 void *temp; 156 157 if (labels == NULL) { 158 return NULL; 159 } 160 161 temp = ares_realloc_zero(labels->label, sizeof(*labels->label) * labels->num, 162 sizeof(*labels->label) * (labels->num + 1)); 163 if (temp == NULL) { 164 return NULL; 165 } 166 167 labels->label = temp; 168 169 labels->label[labels->num] = ares__buf_create(); 170 if (labels->label[labels->num] == NULL) { 171 return NULL; 172 } 173 174 labels->num++; 175 return labels->label[labels->num - 1]; 176} 177 178static const ares__buf_t * 179 ares_dns_labels_get_last(const ares_dns_labels_t *labels) 180{ 181 if (labels == NULL || labels->num == 0) { 182 return NULL; 183 } 184 185 return labels->label[labels->num - 1]; 186} 187 188static void ares_dns_name_labels_del_last(ares_dns_labels_t *labels) 189{ 190 if (labels == NULL || labels->num == 0) { 191 return; 192 } 193 194 ares__buf_destroy(labels->label[labels->num - 1]); 195 labels->label[labels->num - 1] = NULL; 196 labels->num--; 197} 198 199static ares_status_t ares_parse_dns_name_escape(ares__buf_t *namebuf, 200 ares__buf_t *label, 201 ares_bool_t validate_hostname) 202{ 203 ares_status_t status; 204 unsigned char c; 205 206 status = ares__buf_fetch_bytes(namebuf, &c, 1); 207 if (status != ARES_SUCCESS) { 208 return ARES_EBADNAME; 209 } 210 211 /* If next character is a digit, read 2 more digits */ 212 if (isdigit(c)) { 213 size_t i; 214 unsigned int val = 0; 215 216 val = c - '0'; 217 218 for (i = 0; i < 2; i++) { 219 status = ares__buf_fetch_bytes(namebuf, &c, 1); 220 if (status != ARES_SUCCESS) { 221 return ARES_EBADNAME; 222 } 223 224 if (!isdigit(c)) { 225 return ARES_EBADNAME; 226 } 227 val *= 10; 228 val += c - '0'; 229 } 230 231 /* Out of range */ 232 if (val > 255) { 233 return ARES_EBADNAME; 234 } 235 236 if (validate_hostname && !ares__is_hostnamech((unsigned char)val)) { 237 return ARES_EBADNAME; 238 } 239 240 return ares__buf_append_byte(label, (unsigned char)val); 241 } 242 243 /* We can just output the character */ 244 if (validate_hostname && !ares__is_hostnamech(c)) { 245 return ARES_EBADNAME; 246 } 247 248 return ares__buf_append_byte(label, c); 249} 250 251static ares_status_t ares_split_dns_name(ares_dns_labels_t *labels, 252 ares_bool_t validate_hostname, 253 const char *name) 254{ 255 ares_status_t status; 256 ares__buf_t *label = NULL; 257 ares__buf_t *namebuf = NULL; 258 size_t i; 259 size_t total_len = 0; 260 unsigned char c; 261 262 if (name == NULL || labels == NULL) { 263 return ARES_EFORMERR; 264 } 265 266 /* Put name into a buffer for parsing */ 267 namebuf = ares__buf_create(); 268 if (namebuf == NULL) { 269 status = ARES_ENOMEM; 270 goto done; 271 } 272 273 if (*name != '\0') { 274 status = 275 ares__buf_append(namebuf, (const unsigned char *)name, ares_strlen(name)); 276 if (status != ARES_SUCCESS) { 277 goto done; 278 } 279 } 280 281 /* Start with 1 label */ 282 label = ares_dns_labels_add(labels); 283 if (label == NULL) { 284 status = ARES_ENOMEM; 285 goto done; 286 } 287 288 while (ares__buf_fetch_bytes(namebuf, &c, 1) == ARES_SUCCESS) { 289 /* New label */ 290 if (c == '.') { 291 label = ares_dns_labels_add(labels); 292 if (label == NULL) { 293 status = ARES_ENOMEM; 294 goto done; 295 } 296 continue; 297 } 298 299 /* Escape */ 300 if (c == '\\') { 301 status = ares_parse_dns_name_escape(namebuf, label, validate_hostname); 302 if (status != ARES_SUCCESS) { 303 goto done; 304 } 305 continue; 306 } 307 308 /* Output direct character */ 309 if (validate_hostname && !ares__is_hostnamech(c)) { 310 status = ARES_EBADNAME; 311 goto done; 312 } 313 314 status = ares__buf_append_byte(label, c); 315 if (status != ARES_SUCCESS) { 316 goto done; 317 } 318 } 319 320 /* Remove trailing blank label */ 321 if (ares__buf_len(ares_dns_labels_get_last(labels)) == 0) { 322 ares_dns_name_labels_del_last(labels); 323 } 324 325 /* If someone passed in "." there could have been 2 blank labels, check for 326 * that */ 327 if (labels->num == 1 && 328 ares__buf_len(ares_dns_labels_get_last(labels)) == 0) { 329 ares_dns_name_labels_del_last(labels); 330 } 331 332 /* Scan to make sure label lengths are valid */ 333 for (i = 0; i < labels->num; i++) { 334 size_t len = ares__buf_len(labels->label[i]); 335 /* No 0-length labels, and no labels over 63 bytes */ 336 if (len == 0 || len > 63) { 337 status = ARES_EBADNAME; 338 goto done; 339 } 340 total_len += len; 341 } 342 343 /* Can't exceed maximum (unescaped) length */ 344 if (labels->num && total_len + labels->num - 1 > 255) { 345 status = ARES_EBADNAME; 346 goto done; 347 } 348 349 status = ARES_SUCCESS; 350 351done: 352 ares__buf_destroy(namebuf); 353 if (status != ARES_SUCCESS) { 354 ares_dns_labels_free(labels); 355 } 356 return status; 357} 358 359ares_status_t ares__dns_name_write(ares__buf_t *buf, ares__llist_t **list, 360 ares_bool_t validate_hostname, 361 const char *name) 362{ 363 const ares_nameoffset_t *off = NULL; 364 size_t name_len; 365 size_t pos = ares__buf_len(buf); 366 ares_dns_labels_t labels; 367 char name_copy[512]; 368 ares_status_t status; 369 370 if (buf == NULL || name == NULL) { 371 return ARES_EFORMERR; 372 } 373 374 memset(&labels, 0, sizeof(labels)); 375 376 /* NOTE: due to possible escaping, name_copy buffer is > 256 to allow for 377 * this */ 378 name_len = ares_strcpy(name_copy, name, sizeof(name_copy)); 379 380 /* Find longest match */ 381 if (list != NULL) { 382 off = ares__nameoffset_find(*list, name_copy); 383 if (off != NULL && off->name_len != name_len) { 384 /* truncate */ 385 name_len -= (off->name_len + 1); 386 name_copy[name_len] = 0; 387 } 388 } 389 390 /* Output labels */ 391 if (off == NULL || off->name_len != name_len) { 392 size_t i; 393 394 status = ares_split_dns_name(&labels, validate_hostname, name_copy); 395 if (status != ARES_SUCCESS) { 396 goto done; 397 } 398 399 for (i = 0; i < labels.num; i++) { 400 size_t len = 0; 401 const unsigned char *ptr = ares__buf_peek(labels.label[i], &len); 402 403 status = ares__buf_append_byte(buf, (unsigned char)(len & 0xFF)); 404 if (status != ARES_SUCCESS) { 405 goto done; 406 } 407 408 status = ares__buf_append(buf, ptr, len); 409 if (status != ARES_SUCCESS) { 410 goto done; 411 } 412 } 413 414 /* If we are NOT jumping to another label, output terminator */ 415 if (off == NULL) { 416 status = ares__buf_append_byte(buf, 0); 417 if (status != ARES_SUCCESS) { 418 goto done; 419 } 420 } 421 } 422 423 /* Output name compression offset jump */ 424 if (off != NULL) { 425 unsigned short u16 = 426 (unsigned short)0xC000 | (unsigned short)(off->idx & 0x3FFF); 427 status = ares__buf_append_be16(buf, u16); 428 if (status != ARES_SUCCESS) { 429 goto done; 430 } 431 } 432 433 /* Store pointer for future jumps as long as its not an exact match for 434 * a prior entry */ 435 if (list != NULL && (off == NULL || off->name_len != name_len) && 436 name_len > 0) { 437 status = ares__nameoffset_create(list, name /* not truncated copy! */, pos); 438 if (status != ARES_SUCCESS) { 439 goto done; 440 } 441 } 442 443 status = ARES_SUCCESS; 444 445done: 446 ares_dns_labels_free(&labels); 447 return status; 448} 449 450/* Reserved characters for names that need to be escaped */ 451static ares_bool_t is_reservedch(int ch) 452{ 453 switch (ch) { 454 case '"': 455 case '.': 456 case ';': 457 case '\\': 458 case '(': 459 case ')': 460 case '@': 461 case '$': 462 return ARES_TRUE; 463 default: 464 break; 465 } 466 467 return ARES_FALSE; 468} 469 470static ares_status_t ares__fetch_dnsname_into_buf(ares__buf_t *buf, 471 ares__buf_t *dest, size_t len, 472 ares_bool_t is_hostname) 473{ 474 size_t remaining_len; 475 const unsigned char *ptr = ares__buf_peek(buf, &remaining_len); 476 ares_status_t status; 477 size_t i; 478 479 if (buf == NULL || len == 0 || remaining_len < len) { 480 return ARES_EBADRESP; 481 } 482 483 for (i = 0; i < len; i++) { 484 unsigned char c = ptr[i]; 485 486 /* Hostnames have a very specific allowed character set. Anything outside 487 * of that (non-printable and reserved included) are disallowed */ 488 if (is_hostname && !ares__is_hostnamech(c)) { 489 status = ARES_EBADRESP; 490 goto fail; 491 } 492 493 /* NOTE: dest may be NULL if the user is trying to skip the name. validation 494 * still occurs above. */ 495 if (dest == NULL) { 496 continue; 497 } 498 499 /* Non-printable characters need to be output as \DDD */ 500 if (!ares__isprint(c)) { 501 unsigned char escape[4]; 502 503 escape[0] = '\\'; 504 escape[1] = '0' + (c / 100); 505 escape[2] = '0' + ((c % 100) / 10); 506 escape[3] = '0' + (c % 10); 507 508 status = ares__buf_append(dest, escape, sizeof(escape)); 509 if (status != ARES_SUCCESS) { 510 goto fail; 511 } 512 513 continue; 514 } 515 516 /* Reserved characters need to be escaped, otherwise normal */ 517 if (is_reservedch(c)) { 518 status = ares__buf_append_byte(dest, '\\'); 519 if (status != ARES_SUCCESS) { 520 goto fail; 521 } 522 } 523 524 status = ares__buf_append_byte(dest, c); 525 if (status != ARES_SUCCESS) { 526 return status; 527 } 528 } 529 530 return ares__buf_consume(buf, len); 531 532fail: 533 return status; 534} 535 536ares_status_t ares__dns_name_parse(ares__buf_t *buf, char **name, 537 ares_bool_t is_hostname) 538{ 539 size_t save_offset = 0; 540 unsigned char c; 541 ares_status_t status; 542 ares__buf_t *namebuf = NULL; 543 size_t label_start = ares__buf_get_position(buf); 544 545 if (buf == NULL) { 546 return ARES_EFORMERR; 547 } 548 549 if (name != NULL) { 550 namebuf = ares__buf_create(); 551 if (namebuf == NULL) { 552 status = ARES_ENOMEM; 553 goto fail; 554 } 555 } 556 557 /* The compression scheme allows a domain name in a message to be 558 * represented as either: 559 * 560 * - a sequence of labels ending in a zero octet 561 * - a pointer 562 * - a sequence of labels ending with a pointer 563 */ 564 while (1) { 565 /* Keep track of the minimum label starting position to prevent forward 566 * jumping */ 567 if (label_start > ares__buf_get_position(buf)) { 568 label_start = ares__buf_get_position(buf); 569 } 570 571 status = ares__buf_fetch_bytes(buf, &c, 1); 572 if (status != ARES_SUCCESS) { 573 goto fail; 574 } 575 576 /* Pointer/Redirect */ 577 if ((c & 0xc0) == 0xc0) { 578 /* The pointer takes the form of a two octet sequence: 579 * 580 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 581 * | 1 1| OFFSET | 582 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 583 * 584 * The first two bits are ones. This allows a pointer to be distinguished 585 * from a label, since the label must begin with two zero bits because 586 * labels are restricted to 63 octets or less. (The 10 and 01 587 * combinations are reserved for future use.) The OFFSET field specifies 588 * an offset from the start of the message (i.e., the first octet of the 589 * ID field in the domain header). A zero offset specifies the first byte 590 * of the ID field, etc. 591 */ 592 size_t offset = (size_t)((c & 0x3F) << 8); 593 594 /* Fetch second byte of the redirect length */ 595 status = ares__buf_fetch_bytes(buf, &c, 1); 596 if (status != ARES_SUCCESS) { 597 goto fail; 598 } 599 600 offset |= ((size_t)c); 601 602 /* According to RFC 1035 4.1.4: 603 * In this scheme, an entire domain name or a list of labels at 604 * the end of a domain name is replaced with a pointer to a prior 605 * occurrence of the same name. 606 * Note the word "prior", meaning it must go backwards. This was 607 * confirmed via the ISC BIND code that it also prevents forward 608 * pointers. 609 */ 610 if (offset >= label_start) { 611 status = ARES_EBADNAME; 612 goto fail; 613 } 614 615 /* First time we make a jump, save the current position */ 616 if (save_offset == 0) { 617 save_offset = ares__buf_get_position(buf); 618 } 619 620 status = ares__buf_set_position(buf, offset); 621 if (status != ARES_SUCCESS) { 622 status = ARES_EBADNAME; 623 goto fail; 624 } 625 626 continue; 627 } else if ((c & 0xc0) != 0) { 628 /* 10 and 01 are reserved */ 629 status = ARES_EBADNAME; 630 goto fail; 631 } else if (c == 0) { 632 /* termination via zero octet*/ 633 break; 634 } 635 636 /* New label */ 637 638 /* Labels are separated by periods */ 639 if (ares__buf_len(namebuf) != 0 && name != NULL) { 640 status = ares__buf_append_byte(namebuf, '.'); 641 if (status != ARES_SUCCESS) { 642 goto fail; 643 } 644 } 645 646 status = ares__fetch_dnsname_into_buf(buf, namebuf, c, is_hostname); 647 if (status != ARES_SUCCESS) { 648 goto fail; 649 } 650 } 651 652 /* Restore offset read after first redirect/pointer as this is where the DNS 653 * message continues */ 654 if (save_offset) { 655 ares__buf_set_position(buf, save_offset); 656 } 657 658 if (name != NULL) { 659 *name = ares__buf_finish_str(namebuf, NULL); 660 if (*name == NULL) { 661 status = ARES_ENOMEM; 662 goto fail; 663 } 664 } 665 666 return ARES_SUCCESS; 667 668fail: 669 /* We want badname response if we couldn't parse */ 670 if (status == ARES_EBADRESP) { 671 status = ARES_EBADNAME; 672 } 673 674 ares__buf_destroy(namebuf); 675 return status; 676} 677