xref: /openbsd/usr.bin/dig/lib/dns/rdataset.c (revision 3cab2bb3)
1 /*
2  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * Permission to use, copy, modify, and/or distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
9  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
11  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
13  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14  * PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 /*! \file */
18 
19 #include <stdint.h>
20 #include <stdlib.h>
21 
22 #include <isc/buffer.h>
23 #include <isc/util.h>
24 
25 #include <dns/name.h>
26 #include <dns/rdata.h>
27 #include <dns/rdataset.h>
28 #include <dns/compress.h>
29 
30 void
31 dns_rdataset_init(dns_rdataset_t *rdataset) {
32 
33 	/*
34 	 * Make 'rdataset' a valid, disassociated rdataset.
35 	 */
36 
37 	REQUIRE(rdataset != NULL);
38 
39 	rdataset->methods = NULL;
40 	ISC_LINK_INIT(rdataset, link);
41 	rdataset->rdclass = 0;
42 	rdataset->type = 0;
43 	rdataset->ttl = 0;
44 	rdataset->trust = 0;
45 	rdataset->covers = 0;
46 	rdataset->attributes = 0;
47 	rdataset->count = UINT32_MAX;
48 	rdataset->private1 = NULL;
49 	rdataset->private2 = NULL;
50 	rdataset->private3 = NULL;
51 	rdataset->privateuint4 = 0;
52 	rdataset->private5 = NULL;
53 	rdataset->private6 = NULL;
54 	rdataset->private7 = NULL;
55 	rdataset->resign = 0;
56 }
57 
58 void
59 dns_rdataset_disassociate(dns_rdataset_t *rdataset) {
60 
61 	/*
62 	 * Disassociate 'rdataset' from its rdata, allowing it to be reused.
63 	 */
64 
65 	REQUIRE(rdataset->methods != NULL);
66 
67 	(rdataset->methods->disassociate)(rdataset);
68 	rdataset->methods = NULL;
69 	ISC_LINK_INIT(rdataset, link);
70 	rdataset->rdclass = 0;
71 	rdataset->type = 0;
72 	rdataset->ttl = 0;
73 	rdataset->trust = 0;
74 	rdataset->covers = 0;
75 	rdataset->attributes = 0;
76 	rdataset->count = UINT32_MAX;
77 	rdataset->private1 = NULL;
78 	rdataset->private2 = NULL;
79 	rdataset->private3 = NULL;
80 	rdataset->privateuint4 = 0;
81 	rdataset->private5 = NULL;
82 	rdataset->private6 = NULL;
83 }
84 
85 isc_boolean_t
86 dns_rdataset_isassociated(dns_rdataset_t *rdataset) {
87 	/*
88 	 * Is 'rdataset' associated?
89 	 */
90 
91 	if (rdataset->methods != NULL)
92 		return (ISC_TRUE);
93 
94 	return (ISC_FALSE);
95 }
96 
97 static void
98 question_disassociate(dns_rdataset_t *rdataset) {
99 	UNUSED(rdataset);
100 }
101 
102 static isc_result_t
103 question_cursor(dns_rdataset_t *rdataset) {
104 	UNUSED(rdataset);
105 
106 	return (ISC_R_NOMORE);
107 }
108 
109 static void
110 question_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
111 	/*
112 	 * This routine should never be called.
113 	 */
114 	UNUSED(rdataset);
115 	UNUSED(rdata);
116 
117 	REQUIRE(0);
118 }
119 
120 static void
121 question_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
122 	*target = *source;
123 }
124 
125 static unsigned int
126 question_count(dns_rdataset_t *rdataset) {
127 	/*
128 	 * This routine should never be called.
129 	 */
130 	UNUSED(rdataset);
131 	REQUIRE(0);
132 
133 	return (0);
134 }
135 
136 static dns_rdatasetmethods_t question_methods = {
137 	question_disassociate,
138 	question_cursor,
139 	question_cursor,
140 	question_current,
141 	question_clone,
142 	question_count,
143 	NULL,
144 	NULL,
145 	NULL,
146 	NULL,
147 	NULL,
148 	NULL,
149 	NULL,
150 	NULL,
151 	NULL,
152 	NULL
153 };
154 
155 void
156 dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass,
157 			  dns_rdatatype_t type)
158 {
159 
160 	/*
161 	 * Make 'rdataset' a valid, associated, question rdataset, with a
162 	 * question class of 'rdclass' and type 'type'.
163 	 */
164 
165 	REQUIRE(rdataset->methods == NULL);
166 
167 	rdataset->methods = &question_methods;
168 	rdataset->rdclass = rdclass;
169 	rdataset->type = type;
170 	rdataset->attributes |= DNS_RDATASETATTR_QUESTION;
171 }
172 
173 void
174 dns_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
175 
176 	/*
177 	 * Make 'target' refer to the same rdataset as 'source'.
178 	 */
179 
180 	REQUIRE(source->methods != NULL);
181 	REQUIRE(target->methods == NULL);
182 
183 	(source->methods->clone)(source, target);
184 }
185 
186 isc_result_t
187 dns_rdataset_first(dns_rdataset_t *rdataset) {
188 
189 	/*
190 	 * Move the rdata cursor to the first rdata in the rdataset (if any).
191 	 */
192 
193 	REQUIRE(rdataset->methods != NULL);
194 
195 	return ((rdataset->methods->first)(rdataset));
196 }
197 
198 isc_result_t
199 dns_rdataset_next(dns_rdataset_t *rdataset) {
200 
201 	/*
202 	 * Move the rdata cursor to the next rdata in the rdataset (if any).
203 	 */
204 
205 	REQUIRE(rdataset->methods != NULL);
206 
207 	return ((rdataset->methods->next)(rdataset));
208 }
209 
210 void
211 dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
212 
213 	/*
214 	 * Make 'rdata' refer to the current rdata.
215 	 */
216 
217 	REQUIRE(rdataset->methods != NULL);
218 
219 	(rdataset->methods->current)(rdataset, rdata);
220 }
221 
222 #define MAX_SHUFFLE	32
223 #define WANT_FIXED(r)	(((r)->attributes & DNS_RDATASETATTR_FIXEDORDER) != 0)
224 #define WANT_RANDOM(r)	(((r)->attributes & DNS_RDATASETATTR_RANDOMIZE) != 0)
225 
226 struct towire_sort {
227 	int key;
228 	dns_rdata_t *rdata;
229 };
230 
231 static int
232 towire_compare(const void *av, const void *bv) {
233 	const struct towire_sort *a = (const struct towire_sort *) av;
234 	const struct towire_sort *b = (const struct towire_sort *) bv;
235 	return (a->key - b->key);
236 }
237 
238 static isc_result_t
239 towiresorted(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
240 	     dns_compress_t *cctx, isc_buffer_t *target,
241 	     dns_rdatasetorderfunc_t order, const void *order_arg,
242 	     isc_boolean_t partial, unsigned int *countp)
243 {
244 	dns_rdata_t rdata = DNS_RDATA_INIT;
245 	isc_region_t r;
246 	isc_result_t result;
247 	unsigned int i, count = 0, added, choice;
248 	isc_buffer_t savedbuffer, rdlen, rrbuffer;
249 	unsigned int headlen;
250 	isc_boolean_t question = ISC_FALSE;
251 	isc_boolean_t shuffle = ISC_FALSE;
252 	dns_rdata_t *in = NULL, in_fixed[MAX_SHUFFLE];
253 	struct towire_sort *out = NULL, out_fixed[MAX_SHUFFLE];
254 
255 	/*
256 	 * Convert 'rdataset' to wire format, compressing names as specified
257 	 * in cctx, and storing the result in 'target'.
258 	 */
259 
260 	REQUIRE(rdataset->methods != NULL);
261 	REQUIRE(countp != NULL);
262 	REQUIRE((order == NULL) == (order_arg == NULL));
263 	REQUIRE(cctx != NULL);
264 
265 	if ((rdataset->attributes & DNS_RDATASETATTR_QUESTION) != 0) {
266 		question = ISC_TRUE;
267 		count = 1;
268 		result = dns_rdataset_first(rdataset);
269 		INSIST(result == ISC_R_NOMORE);
270 	} else {
271 		count = (rdataset->methods->count)(rdataset);
272 		result = dns_rdataset_first(rdataset);
273 		if (result == ISC_R_NOMORE)
274 			return (ISC_R_SUCCESS);
275 		if (result != ISC_R_SUCCESS)
276 			return (result);
277 	}
278 
279 	/*
280 	 * Do we want to shuffle this answer?
281 	 */
282 	if (!question && count > 1 &&
283 	    (!WANT_FIXED(rdataset) || order != NULL) &&
284 	    rdataset->type != dns_rdatatype_rrsig)
285 		shuffle = ISC_TRUE;
286 
287 	if (shuffle && count > MAX_SHUFFLE) {
288 		in = reallocarray(NULL, count, sizeof(*in));
289 		out = reallocarray(NULL, count, sizeof(*out));
290 		if (in == NULL || out == NULL)
291 			shuffle = ISC_FALSE;
292 	} else {
293 		in = in_fixed;
294 		out = out_fixed;
295 	}
296 
297 	if (shuffle) {
298 		/*
299 		 * First we get handles to all of the rdata.
300 		 */
301 		i = 0;
302 		do {
303 			INSIST(i < count);
304 			dns_rdata_init(&in[i]);
305 			dns_rdataset_current(rdataset, &in[i]);
306 			i++;
307 			result = dns_rdataset_next(rdataset);
308 		} while (result == ISC_R_SUCCESS);
309 		if (result != ISC_R_NOMORE)
310 			goto cleanup;
311 		INSIST(i == count);
312 
313 		/*
314 		 * Now we shuffle.
315 		 */
316 		if (WANT_FIXED(rdataset)) {
317 			/*
318 			 * 'Fixed' order.
319 			 */
320 			INSIST(order != NULL);
321 			for (i = 0; i < count; i++) {
322 				out[i].key = (*order)(&in[i], order_arg);
323 				out[i].rdata = &in[i];
324 			}
325 		} else if (WANT_RANDOM(rdataset)) {
326 			/*
327 			 * 'Random' order.
328 			 */
329 			for (i = 0; i < count; i++) {
330 				choice = i + arc4random_uniform(count - i);
331 				rdata = in[i];
332 				in[i] = in[choice];
333 				in[choice] = rdata;
334 				if (order != NULL)
335 					out[i].key = (*order)(&in[i],
336 							      order_arg);
337 				else
338 					out[i].key = 0; /* Unused */
339 				out[i].rdata = &in[i];
340 			}
341 		} else {
342 			/*
343 			 * "Cyclic" order.
344 			 */
345 			uint32_t val;
346 			unsigned int j;
347 
348 			val = rdataset->count;
349 			if (val == UINT32_MAX)
350 				val = arc4random();
351 			j = val % count;
352 			for (i = 0; i < count; i++) {
353 				if (order != NULL)
354 					out[i].key = (*order)(&in[j],
355 							      order_arg);
356 				else
357 					out[i].key = 0; /* Unused */
358 				out[i].rdata = &in[j];
359 				j++;
360 				if (j == count)
361 					j = 0; /* Wrap around. */
362 			}
363 		}
364 
365 		/*
366 		 * Sorted order.
367 		 */
368 		if (order != NULL)
369 			qsort(out, count, sizeof(out[0]), towire_compare);
370 	}
371 
372 	savedbuffer = *target;
373 	i = 0;
374 	added = 0;
375 
376 	do {
377 		/*
378 		 * Copy out the name, type, class, ttl.
379 		 */
380 
381 		rrbuffer = *target;
382 		dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);
383 		result = dns_name_towire(owner_name, cctx, target);
384 		if (result != ISC_R_SUCCESS)
385 			goto rollback;
386 		headlen = sizeof(dns_rdataclass_t) + sizeof(dns_rdatatype_t);
387 		if (!question)
388 			headlen += sizeof(dns_ttl_t)
389 				+ 2;  /* XXX 2 for rdata len */
390 		isc_buffer_availableregion(target, &r);
391 		if (r.length < headlen) {
392 			result = ISC_R_NOSPACE;
393 			goto rollback;
394 		}
395 		isc_buffer_putuint16(target, rdataset->type);
396 		isc_buffer_putuint16(target, rdataset->rdclass);
397 		if (!question) {
398 			isc_buffer_putuint32(target, rdataset->ttl);
399 
400 			/*
401 			 * Save space for rdlen.
402 			 */
403 			rdlen = *target;
404 			isc_buffer_add(target, 2);
405 
406 			/*
407 			 * Copy out the rdata
408 			 */
409 			if (shuffle)
410 				rdata = *(out[i].rdata);
411 			else {
412 				dns_rdata_reset(&rdata);
413 				dns_rdataset_current(rdataset, &rdata);
414 			}
415 			result = dns_rdata_towire(&rdata, cctx, target);
416 			if (result != ISC_R_SUCCESS)
417 				goto rollback;
418 			INSIST((target->used >= rdlen.used + 2) &&
419 			       (target->used - rdlen.used - 2 < 65536));
420 			isc_buffer_putuint16(&rdlen,
421 					     (uint16_t)(target->used -
422 							    rdlen.used - 2));
423 			added++;
424 		}
425 
426 		if (shuffle) {
427 			i++;
428 			if (i == count)
429 				result = ISC_R_NOMORE;
430 			else
431 				result = ISC_R_SUCCESS;
432 		} else {
433 			result = dns_rdataset_next(rdataset);
434 		}
435 	} while (result == ISC_R_SUCCESS);
436 
437 	if (result != ISC_R_NOMORE)
438 		goto rollback;
439 
440 	*countp += count;
441 
442 	result = ISC_R_SUCCESS;
443 	goto cleanup;
444 
445  rollback:
446 	if (partial && result == ISC_R_NOSPACE) {
447 		INSIST(rrbuffer.used < 65536);
448 		dns_compress_rollback(cctx, (uint16_t)rrbuffer.used);
449 		*countp += added;
450 		*target = rrbuffer;
451 		goto cleanup;
452 	}
453 	INSIST(savedbuffer.used < 65536);
454 	dns_compress_rollback(cctx, (uint16_t)savedbuffer.used);
455 	*countp = 0;
456 	*target = savedbuffer;
457 
458  cleanup:
459 	if (out != NULL && out != out_fixed)
460 		free(out);
461 	if (in != NULL && in != in_fixed)
462 		free(in);
463 	return (result);
464 }
465 
466 isc_result_t
467 dns_rdataset_towiresorted(dns_rdataset_t *rdataset,
468 			  const dns_name_t *owner_name,
469 			  dns_compress_t *cctx,
470 			  isc_buffer_t *target,
471 			  dns_rdatasetorderfunc_t order,
472 			  const void *order_arg,
473 			  unsigned int *countp)
474 {
475 	return (towiresorted(rdataset, owner_name, cctx, target,
476 			     order, order_arg, ISC_FALSE, countp));
477 }
478 
479 isc_result_t
480 dns_rdataset_towire(dns_rdataset_t *rdataset,
481 		    dns_name_t *owner_name,
482 		    dns_compress_t *cctx,
483 		    isc_buffer_t *target,
484 		    unsigned int *countp)
485 {
486 	return (towiresorted(rdataset, owner_name, cctx, target,
487 			     NULL, NULL, ISC_FALSE, countp));
488 }
489