Home | History | Annotate | Line # | Download | only in dns
      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