1 /* $NetBSD: dns_rr.c,v 1.4 2025/02/25 19:15:44 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dns_rr 3 6 /* SUMMARY 7 /* resource record memory and list management 8 /* SYNOPSIS 9 /* #include <dns.h> 10 /* 11 /* DNS_RR *dns_rr_create(qname, rname, type, class, ttl, preference, 12 /* weight, port, data, data_len) 13 /* const char *qname; 14 /* const char *rname; 15 /* unsigned short type; 16 /* unsigned short class; 17 /* unsigned int ttl; 18 /* unsigned preference; 19 /* unsigned weight; 20 /* unsigned port; 21 /* const char *data; 22 /* size_t data_len; 23 /* 24 /* void dns_rr_free(list) 25 /* DNS_RR *list; 26 /* 27 /* DNS_RR *dns_rr_copy(record) 28 /* DNS_RR *record; 29 /* 30 /* DNS_RR *dns_rr_append(list, record) 31 /* DNS_RR *list; 32 /* DNS_RR *record; 33 /* 34 /* DNS_RR *dns_rr_sort(list, compar) 35 /* DNS_RR *list 36 /* int (*compar)(DNS_RR *, DNS_RR *); 37 /* 38 /* int dns_rr_compare_pref_ipv6(DNS_RR *a, DNS_RR *b) 39 /* DNS_RR *list 40 /* DNS_RR *list 41 /* 42 /* int dns_rr_compare_pref_ipv4(DNS_RR *a, DNS_RR *b) 43 /* DNS_RR *list 44 /* DNS_RR *list 45 /* 46 /* int dns_rr_compare_pref_any(DNS_RR *a, DNS_RR *b) 47 /* DNS_RR *list 48 /* DNS_RR *list 49 /* 50 /* DNS_RR *dns_rr_shuffle(list) 51 /* DNS_RR *list; 52 /* 53 /* DNS_RR *dns_rr_remove(list, record) 54 /* DNS_RR *list; 55 /* DNS_RR *record; 56 /* 57 /* DNS_RR *dns_rr_detach(list, record) 58 /* DNS_RR *list; 59 /* DNS_RR *record; 60 /* 61 /* DNS_RR *dns_srv_rr_sort(list) 62 /* DNS_RR *list; 63 /* 64 /* int var_dns_rr_list_limit; 65 /* AUXILIARY FUNCTIONS 66 /* DNS_RR *dns_rr_create_nopref(qname, rname, type, class, ttl, 67 /* data, data_len) 68 /* const char *qname; 69 /* const char *rname; 70 /* unsigned short type; 71 /* unsigned short class; 72 /* unsigned int ttl; 73 /* const char *data; 74 /* size_t data_len; 75 /* 76 /* DNS_RR *dns_rr_create_noport(qname, rname, type, class, ttl, 77 /* preference, data, data_len) 78 /* const char *qname; 79 /* const char *rname; 80 /* unsigned short type; 81 /* unsigned short class; 82 /* unsigned int ttl; 83 /* unsigned preference; 84 /* const char *data; 85 /* size_t data_len; 86 /* DESCRIPTION 87 /* The routines in this module maintain memory for DNS resource record 88 /* information, and maintain lists of DNS resource records. 89 /* 90 /* dns_rr_create() creates and initializes one resource record. 91 /* The \fIqname\fR field specifies the query name. 92 /* The \fIrname\fR field specifies the reply name. 93 /* \fIpreference\fR is used for MX and SRV records; \fIweight\fR 94 /* and \fIport\fR are used for SRV records; \fIdata\fR is a null 95 /* pointer or specifies optional resource-specific data; 96 /* \fIdata_len\fR is the amount of resource-specific data. 97 /* 98 /* dns_rr_create_nopref() and dns_rr_create_noport() are convenience 99 /* wrappers around dns_rr_create() that take fewer arguments. 100 /* 101 /* dns_rr_free() releases the resource used by of zero or more 102 /* resource records. 103 /* 104 /* dns_rr_copy() makes a copy of a resource record. 105 /* 106 /* dns_rr_append() appends an input resource record list to 107 /* an output list. Null arguments are explicitly allowed. 108 /* When the result would be longer than var_dns_rr_list_limit 109 /* (default: 100), dns_rr_append() logs a warning, flags the 110 /* output list as truncated, and discards the excess elements. 111 /* Once an output list is flagged as truncated (test with 112 /* DNS_RR_IS_TRUNCATED()), the caller is expected to stop 113 /* trying to append records to that list. Note: the 'truncated' 114 /* flag is transitive, i.e. when appending a input list that 115 /* was flagged as truncated to an output list, the output list 116 /* will also be flagged as truncated. 117 /* 118 /* dns_rr_sort() sorts a list of resource records into ascending 119 /* order according to a user-specified criterion. The result is the 120 /* sorted list. 121 /* 122 /* dns_rr_compare_pref_XXX() are dns_rr_sort() helpers to sort 123 /* records by their MX preference and by their address family. 124 /* 125 /* dns_rr_shuffle() randomly permutes a list of resource records. 126 /* 127 /* dns_rr_remove() disconnects the specified record from the 128 /* specified list and destroys it. 129 /* The updated list is the result value. 130 /* The record MUST be a list member. 131 /* 132 /* dns_rr_detach() disconnects the specified record from the 133 /* specified list. The updated list is the result value. 134 /* The record MUST be a list member. 135 /* 136 /* dns_srv_rr_sort() sorts a list of SRV records according to 137 /* their priority and weight as described in RFC 2782. 138 /* LICENSE 139 /* .ad 140 /* .fi 141 /* The Secure Mailer license must be distributed with this software. 142 /* AUTHOR(S) 143 /* Wietse Venema 144 /* IBM T.J. Watson Research 145 /* P.O. Box 704 146 /* Yorktown Heights, NY 10598, USA 147 /* 148 /* Wietse Venema 149 /* Google, Inc. 150 /* 111 8th Avenue 151 /* New York, NY 10011, USA 152 /* 153 /* SRV Support by 154 /* Tomas Korbar 155 /* Red Hat, Inc. 156 /*--*/ 157 158 /* System library. */ 159 160 #include <sys_defs.h> 161 #include <string.h> 162 #include <stdlib.h> 163 164 /* Utility library. */ 165 166 #include <msg.h> 167 #include <mymalloc.h> 168 #include <myrand.h> 169 170 /* DNS library. */ 171 172 #include "dns.h" 173 174 /* 175 * A generous safety limit for the number of DNS resource records that the 176 * Postfix DNS client library will admit into a list. The default value 100 177 * is 20x the default limit on the number address records that the Postfix 178 * SMTP client is willing to consider. 179 * 180 * Mutable, to make code testable. 181 */ 182 int var_dns_rr_list_limit = 100; 183 184 /* dns_rr_create - fill in resource record structure */ 185 186 DNS_RR *dns_rr_create(const char *qname, const char *rname, 187 ushort type, ushort class, 188 unsigned int ttl, unsigned pref, 189 unsigned weight, unsigned port, 190 const char *data, size_t data_len) 191 { 192 DNS_RR *rr; 193 194 /* 195 * Note: if this function is changed, update dns_rr_copy(). 196 */ 197 rr = (DNS_RR *) mymalloc(sizeof(*rr)); 198 rr->qname = mystrdup(qname); 199 rr->rname = mystrdup(rname); 200 rr->type = type; 201 rr->class = class; 202 rr->ttl = ttl; 203 rr->dnssec_valid = 0; 204 rr->pref = pref; 205 rr->weight = weight; 206 rr->port = port; 207 if (data_len != 0) { 208 rr->data = mymalloc(data_len); 209 memcpy(rr->data, data, data_len); 210 } else { 211 rr->data = 0; 212 } 213 rr->data_len = data_len; 214 rr->next = 0; 215 rr->flags = 0; 216 return (rr); 217 } 218 219 /* dns_rr_free - destroy resource record structure */ 220 221 void dns_rr_free(DNS_RR *rr) 222 { 223 if (rr) { 224 if (rr->next) 225 dns_rr_free(rr->next); 226 myfree(rr->qname); 227 myfree(rr->rname); 228 if (rr->data) 229 myfree(rr->data); 230 myfree((void *) rr); 231 } 232 } 233 234 /* dns_rr_copy - copy resource record */ 235 236 DNS_RR *dns_rr_copy(DNS_RR *src) 237 { 238 DNS_RR *dst; 239 240 /* 241 * Note: struct copy, because dns_rr_create() would not copy all fields. 242 */ 243 dst = (DNS_RR *) mymalloc(sizeof(*dst)); 244 *dst = *src; 245 dst->qname = mystrdup(src->qname); 246 dst->rname = mystrdup(src->rname); 247 if (dst->data) 248 dst->data = mymemdup(src->data, src->data_len); 249 dst->next = 0; 250 return (dst); 251 } 252 253 /* dns_rr_append_with_limit - append resource record to limited list */ 254 255 static void dns_rr_append_with_limit(DNS_RR *list, DNS_RR *rr, int limit) 256 { 257 258 /* 259 * Pre: list != 0, all lists are concatenated with dns_rr_append(). 260 * 261 * Post: all elements have the DNS_RR_FLAG_TRUNCATED flag value set, or all 262 * elements have it cleared, so that there is no need to update code in 263 * legacy stable releases that deletes or reorders elements. 264 */ 265 if (limit <= 1) { 266 if (list->next || rr) { 267 msg_warn("DNS record count limit (%d) exceeded -- dropping" 268 " excess record(s) after qname=%s qtype=%s", 269 var_dns_rr_list_limit, list->qname, 270 dns_strtype(list->type)); 271 list->flags |= DNS_RR_FLAG_TRUNCATED; 272 dns_rr_free(list->next); 273 dns_rr_free(rr); 274 list->next = 0; 275 } 276 } else { 277 if (list->next == 0 && rr) { 278 list->next = rr; 279 rr = 0; 280 } 281 if (list->next) { 282 dns_rr_append_with_limit(list->next, rr, limit - 1); 283 list->flags |= list->next->flags; 284 } 285 } 286 } 287 288 /* dns_rr_append - append resource record(s) to list, or discard */ 289 290 DNS_RR *dns_rr_append(DNS_RR *list, DNS_RR *rr) 291 { 292 293 /* 294 * Note: rr is not length checked; when multiple lists are concatenated, 295 * the output length may be a small multiple of var_dns_rr_list_limit. 296 */ 297 if (rr == 0) 298 return (list); 299 if (list == 0) 300 return (rr); 301 if (!DNS_RR_IS_TRUNCATED(list)) { 302 dns_rr_append_with_limit(list, rr, var_dns_rr_list_limit); 303 } else { 304 dns_rr_free(rr); 305 } 306 return (list); 307 } 308 309 /* dns_rr_compare_pref_ipv6 - compare records by preference, ipv6 preferred */ 310 311 int dns_rr_compare_pref_ipv6(DNS_RR *a, DNS_RR *b) 312 { 313 if (a->pref != b->pref) 314 return (a->pref - b->pref); 315 #ifdef HAS_IPV6 316 if (a->type == b->type) /* 200412 */ 317 return 0; 318 if (a->type == T_AAAA) 319 return (-1); 320 if (b->type == T_AAAA) 321 return (+1); 322 #endif 323 return 0; 324 } 325 326 /* dns_rr_compare_pref_ipv4 - compare records by preference, ipv4 preferred */ 327 328 int dns_rr_compare_pref_ipv4(DNS_RR *a, DNS_RR *b) 329 { 330 if (a->pref != b->pref) 331 return (a->pref - b->pref); 332 #ifdef HAS_IPV6 333 if (a->type == b->type) 334 return 0; 335 if (a->type == T_AAAA) 336 return (+1); 337 if (b->type == T_AAAA) 338 return (-1); 339 #endif 340 return 0; 341 } 342 343 /* dns_rr_compare_pref_any - compare records by preference, protocol-neutral */ 344 345 int dns_rr_compare_pref_any(DNS_RR *a, DNS_RR *b) 346 { 347 if (a->pref != b->pref) 348 return (a->pref - b->pref); 349 return 0; 350 } 351 352 /* dns_rr_compare_pref - binary compatibility helper after name change */ 353 354 int dns_rr_compare_pref(DNS_RR *a, DNS_RR *b) 355 { 356 return (dns_rr_compare_pref_ipv6(a, b)); 357 } 358 359 /* dns_rr_sort_callback - glue function */ 360 361 static int (*dns_rr_sort_user) (DNS_RR *, DNS_RR *); 362 363 static int dns_rr_sort_callback(const void *a, const void *b) 364 { 365 DNS_RR *aa = *(DNS_RR **) a; 366 DNS_RR *bb = *(DNS_RR **) b; 367 368 return (dns_rr_sort_user(aa, bb)); 369 } 370 371 /* dns_rr_sort - sort resource record list */ 372 373 DNS_RR *dns_rr_sort(DNS_RR *list, int (*compar) (DNS_RR *, DNS_RR *)) 374 { 375 int (*saved_user) (DNS_RR *, DNS_RR *); 376 DNS_RR **rr_array; 377 DNS_RR *rr; 378 int len; 379 int i; 380 381 /* 382 * Avoid mymalloc() panic. 383 */ 384 if (list == 0) 385 return (list); 386 387 /* 388 * Save state and initialize. 389 */ 390 saved_user = dns_rr_sort_user; 391 dns_rr_sort_user = compar; 392 393 /* 394 * Build linear array with pointers to each list element. 395 */ 396 for (len = 0, rr = list; rr != 0; len++, rr = rr->next) 397 /* void */ ; 398 rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array)); 399 for (len = 0, rr = list; rr != 0; len++, rr = rr->next) 400 rr_array[len] = rr; 401 402 /* 403 * Sort by user-specified criterion. 404 */ 405 qsort((void *) rr_array, len, sizeof(*rr_array), dns_rr_sort_callback); 406 407 /* 408 * Fix the links. 409 */ 410 for (i = 0; i < len - 1; i++) 411 rr_array[i]->next = rr_array[i + 1]; 412 rr_array[i]->next = 0; 413 list = rr_array[0]; 414 415 /* 416 * Cleanup. 417 */ 418 myfree((void *) rr_array); 419 dns_rr_sort_user = saved_user; 420 return (list); 421 } 422 423 /* dns_rr_shuffle - shuffle resource record list */ 424 425 DNS_RR *dns_rr_shuffle(DNS_RR *list) 426 { 427 DNS_RR **rr_array; 428 DNS_RR *rr; 429 int len; 430 int i; 431 int r; 432 433 /* 434 * Avoid mymalloc() panic. 435 */ 436 if (list == 0) 437 return (list); 438 439 /* 440 * Build linear array with pointers to each list element. 441 */ 442 for (len = 0, rr = list; rr != 0; len++, rr = rr->next) 443 /* void */ ; 444 rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array)); 445 for (len = 0, rr = list; rr != 0; len++, rr = rr->next) 446 rr_array[len] = rr; 447 448 /* 449 * Shuffle resource records. Every element has an equal chance of landing 450 * in slot 0. After that every remaining element has an equal chance of 451 * landing in slot 1, ... This is exactly n! states for n! permutations. 452 */ 453 for (i = 0; i < len - 1; i++) { 454 r = i + (myrand() % (len - i)); /* Victor&Son */ 455 rr = rr_array[i]; 456 rr_array[i] = rr_array[r]; 457 rr_array[r] = rr; 458 } 459 460 /* 461 * Fix the links. 462 */ 463 for (i = 0; i < len - 1; i++) 464 rr_array[i]->next = rr_array[i + 1]; 465 rr_array[i]->next = 0; 466 list = rr_array[0]; 467 468 /* 469 * Cleanup. 470 */ 471 myfree((void *) rr_array); 472 return (list); 473 } 474 475 /* dns_rr_remove - remove record from list, return new list */ 476 477 DNS_RR *dns_rr_remove(DNS_RR *list, DNS_RR *record) 478 { 479 list = dns_rr_detach(list, record); 480 dns_rr_free(record); 481 return (list); 482 } 483 484 /* dns_rr_detach - detach record from list, return new list */ 485 486 DNS_RR *dns_rr_detach(DNS_RR *list, DNS_RR *record) 487 { 488 if (list == 0) 489 msg_panic("dns_rr_detach: record not found"); 490 491 if (list == record) { 492 list = record->next; 493 record->next = 0; 494 } else { 495 list->next = dns_rr_detach(list->next, record); 496 } 497 return (list); 498 } 499 500 /* weight_order - sort equal-priority records by weight */ 501 502 static void weight_order(DNS_RR **array, int count) 503 { 504 int unordered_weights; 505 int i; 506 507 /* 508 * Compute the sum of record weights. If weights are not supplied then 509 * this function would be a noop. In fact this would be a noop when all 510 * weights have the same value, whether that weight is zero or not. There 511 * is no need to give special treatment to zero weights. 512 */ 513 for (unordered_weights = 0, i = 0; i < count; i++) 514 unordered_weights += array[i]->weight; 515 if (unordered_weights == 0) 516 return; 517 518 /* 519 * The record ordering code below differs from RFC 2782 when the input 520 * contains a mix of zero and non-zero weights: the code below does not 521 * give special treatment to zero weights. Instead, it treats a zero 522 * weight just like any other small weight. Fewer special cases make for 523 * code that is simpler and more robust. 524 */ 525 for (i = 0; i < count - 1; i++) { 526 int running_sum; 527 int threshold; 528 int k; 529 DNS_RR *temp; 530 531 /* 532 * Choose a random threshold [0..unordered_weights] inclusive. 533 */ 534 threshold = myrand() % (unordered_weights + 1); 535 536 /* 537 * Move the first record with running_sum >= threshold to the ordered 538 * list, and update unordered_weights. 539 */ 540 for (running_sum = 0, k = i; k < count; k++) { 541 running_sum += array[k]->weight; 542 if (running_sum >= threshold) { 543 unordered_weights -= array[k]->weight; 544 temp = array[i]; 545 array[i] = array[k]; 546 array[k] = temp; 547 break; 548 } 549 } 550 } 551 } 552 553 /* dns_srv_rr_sort - sort resource record list */ 554 555 DNS_RR *dns_srv_rr_sort(DNS_RR *list) 556 { 557 int (*saved_user) (DNS_RR *, DNS_RR *); 558 DNS_RR **rr_array; 559 DNS_RR *rr; 560 int len; 561 int i; 562 int r; 563 int cur_pref; 564 int left_bound; /* inclusive */ 565 int right_bound; /* non-inclusive */ 566 567 /* 568 * Avoid mymalloc() panic, or rr_array[0] fence-post error. 569 */ 570 if (list == 0) 571 return (list); 572 573 /* 574 * Save state and initialize. 575 */ 576 saved_user = dns_rr_sort_user; 577 dns_rr_sort_user = dns_rr_compare_pref_any; 578 579 /* 580 * Build linear array with pointers to each list element. 581 */ 582 for (len = 0, rr = list; rr != 0; len++, rr = rr->next) 583 /* void */ ; 584 rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array)); 585 for (len = 0, rr = list; rr != 0; len++, rr = rr->next) 586 rr_array[len] = rr; 587 588 /* 589 * Shuffle resource records. Every element has an equal chance of landing 590 * in slot 0. After that every remaining element has an equal chance of 591 * landing in slot 1, ... This is exactly n! states for n! permutations. 592 */ 593 for (i = 0; i < len - 1; i++) { 594 r = i + (myrand() % (len - i)); /* Victor&Son */ 595 rr = rr_array[i]; 596 rr_array[i] = rr_array[r]; 597 rr_array[r] = rr; 598 } 599 600 /* First order the records by preference. */ 601 qsort((void *) rr_array, len, sizeof(*rr_array), dns_rr_sort_callback); 602 603 /* 604 * Walk through records and sort the records in every same-preference 605 * partition according to their weight. Note that left_bound is 606 * inclusive, and that right-bound is non-inclusive. 607 */ 608 left_bound = 0; 609 cur_pref = rr_array[left_bound]->pref; /* assumes len > 0 */ 610 611 for (right_bound = 1; /* see below */ ; right_bound++) { 612 if (right_bound == len || rr_array[right_bound]->pref != cur_pref) { 613 if (right_bound - left_bound > 1) 614 weight_order(rr_array + left_bound, right_bound - left_bound); 615 if (right_bound == len) 616 break; 617 left_bound = right_bound; 618 cur_pref = rr_array[left_bound]->pref; 619 } 620 } 621 622 /* 623 * Fix the links. 624 */ 625 for (i = 0; i < len - 1; i++) 626 rr_array[i]->next = rr_array[i + 1]; 627 rr_array[i]->next = 0; 628 list = rr_array[0]; 629 630 /* 631 * Cleanup. 632 */ 633 myfree((void *) rr_array); 634 dns_rr_sort_user = saved_user; 635 return (list); 636 } 637