xref: /openbsd-src/usr.bin/dig/lib/dns/masterdump.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
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 <limits.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include <isc/types.h>
24 #include <isc/util.h>
25 
26 #include <dns/fixedname.h>
27 #include <dns/masterdump.h>
28 #include <dns/rdata.h>
29 #include <dns/rdataclass.h>
30 #include <dns/rdataset.h>
31 #include <dns/rdatatype.h>
32 #include <dns/result.h>
33 
34 #define RETERR(x) do { \
35 	isc_result_t _r = (x); \
36 	if (_r != ISC_R_SUCCESS) \
37 		return (_r); \
38 	} while (0)
39 
40 struct dns_master_style {
41 	dns_masterstyle_flags_t flags;		/* DNS_STYLEFLAG_* */
42 	unsigned int ttl_column;
43 	unsigned int class_column;
44 	unsigned int type_column;
45 	unsigned int rdata_column;
46 	unsigned int line_length;
47 	unsigned int tab_width;
48 	unsigned int split_width;
49 };
50 
51 /*%
52  * The maximum length of the newline+indentation that is output
53  * when inserting a line break in an RR.  This effectively puts an
54  * upper limits on the value of "rdata_column", because if it is
55  * very large, the tabs and spaces needed to reach it will not fit.
56  */
57 #define DNS_TOTEXT_LINEBREAK_MAXLEN 100
58 
59 /*%
60  * Context structure for a masterfile dump in progress.
61  */
62 typedef struct dns_totext_ctx {
63 	dns_master_style_t	style;
64 	isc_boolean_t 		class_printed;
65 	char *			linebreak;
66 	char 			linebreak_buf[DNS_TOTEXT_LINEBREAK_MAXLEN];
67 	dns_name_t *		origin;
68 	dns_name_t *		neworigin;
69 	dns_fixedname_t		origin_fixname;
70 	uint32_t 		current_ttl;
71 	isc_boolean_t 		current_ttl_valid;
72 } dns_totext_ctx_t;
73 
74 /*%
75  * A style suitable for dns_rdataset_totext().
76  */
77 const dns_master_style_t
78 dns_master_style_debug = {
79 	DNS_STYLEFLAG_REL_OWNER,
80 	24, 32, 40, 48, 80, 8, UINT_MAX
81 };
82 
83 #define N_SPACES 10
84 static char spaces[N_SPACES+1] = "          ";
85 
86 #define N_TABS 10
87 static char tabs[N_TABS+1] = "\t\t\t\t\t\t\t\t\t\t";
88 
89 /*%
90  * Output tabs and spaces to go from column '*current' to
91  * column 'to', and update '*current' to reflect the new
92  * current column.
93  */
94 static isc_result_t
95 indent(unsigned int *current, unsigned int to, int tabwidth,
96        isc_buffer_t *target)
97 {
98 	isc_region_t r;
99 	unsigned char *p;
100 	unsigned int from;
101 	int ntabs, nspaces, t;
102 
103 	from = *current;
104 
105 	if (to < from + 1)
106 		to = from + 1;
107 
108 	ntabs = to / tabwidth - from / tabwidth;
109 	if (ntabs < 0)
110 		ntabs = 0;
111 
112 	if (ntabs > 0) {
113 		isc_buffer_availableregion(target, &r);
114 		if (r.length < (unsigned) ntabs)
115 			return (ISC_R_NOSPACE);
116 		p = r.base;
117 
118 		t = ntabs;
119 		while (t) {
120 			int n = t;
121 			if (n > N_TABS)
122 				n = N_TABS;
123 			memmove(p, tabs, n);
124 			p += n;
125 			t -= n;
126 		}
127 		isc_buffer_add(target, ntabs);
128 		from = (to / tabwidth) * tabwidth;
129 	}
130 
131 	nspaces = to - from;
132 	INSIST(nspaces >= 0);
133 
134 	isc_buffer_availableregion(target, &r);
135 	if (r.length < (unsigned) nspaces)
136 		return (ISC_R_NOSPACE);
137 	p = r.base;
138 
139 	t = nspaces;
140 	while (t) {
141 		int n = t;
142 		if (n > N_SPACES)
143 			n = N_SPACES;
144 		memmove(p, spaces, n);
145 		p += n;
146 		t -= n;
147 	}
148 	isc_buffer_add(target, nspaces);
149 
150 	*current = to;
151 	return (ISC_R_SUCCESS);
152 }
153 
154 static isc_result_t
155 totext_ctx_init(const dns_master_style_t *style, dns_totext_ctx_t *ctx) {
156 	isc_result_t result;
157 
158 	REQUIRE(style->tab_width != 0);
159 
160 	ctx->style = *style;
161 	ctx->class_printed = ISC_FALSE;
162 
163 	dns_fixedname_init(&ctx->origin_fixname);
164 
165 	/*
166 	 * Set up the line break string if needed.
167 	 */
168 	if ((ctx->style.flags & DNS_STYLEFLAG_MULTILINE) != 0) {
169 		isc_buffer_t buf;
170 		isc_region_t r;
171 		unsigned int col = 0;
172 
173 		isc_buffer_init(&buf, ctx->linebreak_buf,
174 				sizeof(ctx->linebreak_buf));
175 
176 		isc_buffer_availableregion(&buf, &r);
177 		if (r.length < 1)
178 			return (DNS_R_TEXTTOOLONG);
179 		r.base[0] = '\n';
180 		isc_buffer_add(&buf, 1);
181 
182 		if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) {
183 			isc_buffer_availableregion(&buf, &r);
184 			if (r.length < 1)
185 				return (DNS_R_TEXTTOOLONG);
186 			r.base[0] = ';';
187 			isc_buffer_add(&buf, 1);
188 		}
189 
190 		result = indent(&col, ctx->style.rdata_column,
191 				ctx->style.tab_width, &buf);
192 		/*
193 		 * Do not return ISC_R_NOSPACE if the line break string
194 		 * buffer is too small, because that would just make
195 		 * dump_rdataset() retry indefinitely with ever
196 		 * bigger target buffers.  That's a different buffer,
197 		 * so it won't help.  Use DNS_R_TEXTTOOLONG as a substitute.
198 		 */
199 		if (result == ISC_R_NOSPACE)
200 			return (DNS_R_TEXTTOOLONG);
201 		if (result != ISC_R_SUCCESS)
202 			return (result);
203 
204 		isc_buffer_availableregion(&buf, &r);
205 		if (r.length < 1)
206 			return (DNS_R_TEXTTOOLONG);
207 		r.base[0] = '\0';
208 		isc_buffer_add(&buf, 1);
209 		ctx->linebreak = ctx->linebreak_buf;
210 	} else {
211 		ctx->linebreak = NULL;
212 	}
213 
214 	ctx->origin = NULL;
215 	ctx->neworigin = NULL;
216 	ctx->current_ttl = 0;
217 	ctx->current_ttl_valid = ISC_FALSE;
218 
219 	return (ISC_R_SUCCESS);
220 }
221 
222 #define INDENT_TO(col) \
223 	do { \
224 		 if ((result = indent(&column, ctx->style.col, \
225 				      ctx->style.tab_width, target)) \
226 		     != ISC_R_SUCCESS) \
227 			    return (result); \
228 	} while (0)
229 
230 /*
231  * Convert 'rdataset' to master file text format according to 'ctx',
232  * storing the result in 'target'.  If 'owner_name' is NULL, it
233  * is omitted; otherwise 'owner_name' must be valid and have at least
234  * one label.
235  */
236 
237 static isc_result_t
238 rdataset_totext(dns_rdataset_t *rdataset,
239 		dns_name_t *owner_name,
240 		dns_totext_ctx_t *ctx,
241 		isc_boolean_t omit_final_dot,
242 		isc_buffer_t *target)
243 {
244 	isc_result_t result;
245 	unsigned int column;
246 	isc_boolean_t first = ISC_TRUE;
247 	uint32_t current_ttl;
248 	isc_boolean_t current_ttl_valid;
249 	dns_rdatatype_t type;
250 	unsigned int type_start;
251 
252 	rdataset->attributes |= DNS_RDATASETATTR_LOADORDER;
253 	result = dns_rdataset_first(rdataset);
254 
255 	current_ttl = ctx->current_ttl;
256 	current_ttl_valid = ctx->current_ttl_valid;
257 
258 	while (result == ISC_R_SUCCESS) {
259 		column = 0;
260 
261 		/*
262 		 * Comment?
263 		 */
264 		if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0)
265 			RETERR(isc_str_tobuffer(";", target));
266 
267 		/*
268 		 * Owner name.
269 		 */
270 		if (owner_name != NULL &&
271 		    ! ((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0 &&
272 		       !first))
273 		{
274 			unsigned int name_start = target->used;
275 			RETERR(dns_name_totext(owner_name,
276 					       omit_final_dot,
277 					       target));
278 			column += target->used - name_start;
279 		}
280 
281 		/*
282 		 * TTL.
283 		 */
284 		if ((ctx->style.flags & DNS_STYLEFLAG_NO_TTL) == 0 &&
285 		    !((ctx->style.flags & DNS_STYLEFLAG_OMIT_TTL) != 0 &&
286 		      current_ttl_valid &&
287 		      rdataset->ttl == current_ttl))
288 		{
289 			char ttlbuf[64];
290 			isc_region_t r;
291 			unsigned int length;
292 
293 			INDENT_TO(ttl_column);
294 			length = snprintf(ttlbuf, sizeof(ttlbuf), "%u",
295 					  rdataset->ttl);
296 			INSIST(length <= sizeof(ttlbuf));
297 			isc_buffer_availableregion(target, &r);
298 			if (r.length < length)
299 				return (ISC_R_NOSPACE);
300 			memmove(r.base, ttlbuf, length);
301 			isc_buffer_add(target, length);
302 			column += length;
303 
304 			/*
305 			 * If the $TTL directive is not in use, the TTL we
306 			 * just printed becomes the default for subsequent RRs.
307 			 */
308 			if ((ctx->style.flags & DNS_STYLEFLAG_TTL) == 0) {
309 				current_ttl = rdataset->ttl;
310 				current_ttl_valid = ISC_TRUE;
311 			}
312 		}
313 
314 		/*
315 		 * Class.
316 		 */
317 		if ((ctx->style.flags & DNS_STYLEFLAG_NO_CLASS) == 0 &&
318 		    ((ctx->style.flags & DNS_STYLEFLAG_OMIT_CLASS) == 0 ||
319 		     ctx->class_printed == ISC_FALSE))
320 		{
321 			unsigned int class_start;
322 			INDENT_TO(class_column);
323 			class_start = target->used;
324 			result = dns_rdataclass_totext(rdataset->rdclass,
325 						       target);
326 			if (result != ISC_R_SUCCESS)
327 				return (result);
328 			column += (target->used - class_start);
329 		}
330 
331 		/*
332 		 * Type.
333 		 */
334 
335 		type = rdataset->type;
336 
337 		INDENT_TO(type_column);
338 		type_start = target->used;
339 		switch (type) {
340 		case dns_rdatatype_keydata:
341 #define KEYDATA "KEYDATA"
342 			if ((ctx->style.flags & DNS_STYLEFLAG_KEYDATA) != 0) {
343 				if (isc_buffer_availablelength(target) <
344 				    (sizeof(KEYDATA) - 1))
345 					return (ISC_R_NOSPACE);
346 				isc_buffer_putstr(target, KEYDATA);
347 				break;
348 			}
349 			/* FALLTHROUGH */
350 		default:
351 			result = dns_rdatatype_totext(type, target);
352 			if (result != ISC_R_SUCCESS)
353 				return (result);
354 		}
355 		column += (target->used - type_start);
356 
357 		/*
358 		 * Rdata.
359 		 */
360 		INDENT_TO(rdata_column);
361 		{
362 			dns_rdata_t rdata = DNS_RDATA_INIT;
363 			isc_region_t r;
364 
365 			dns_rdataset_current(rdataset, &rdata);
366 
367 			RETERR(dns_rdata_tofmttext(&rdata,
368 						   ctx->origin,
369 						   (unsigned int)
370 						   ctx->style.flags,
371 						   ctx->style.line_length -
372 						       ctx->style.rdata_column,
373 						   ctx->style.split_width,
374 						   ctx->linebreak,
375 						   target));
376 
377 			isc_buffer_availableregion(target, &r);
378 			if (r.length < 1)
379 				return (ISC_R_NOSPACE);
380 			r.base[0] = '\n';
381 			isc_buffer_add(target, 1);
382 		}
383 
384 		first = ISC_FALSE;
385 		result = dns_rdataset_next(rdataset);
386 	}
387 
388 	if (result != ISC_R_NOMORE)
389 		return (result);
390 
391 	/*
392 	 * Update the ctx state to reflect what we just printed.
393 	 * This is done last, only when we are sure we will return
394 	 * success, because this function may be called multiple
395 	 * times with increasing buffer sizes until it succeeds,
396 	 * and failed attempts must not update the state prematurely.
397 	 */
398 	ctx->class_printed = ISC_TRUE;
399 	ctx->current_ttl= current_ttl;
400 	ctx->current_ttl_valid = current_ttl_valid;
401 
402 	return (ISC_R_SUCCESS);
403 }
404 
405 /*
406  * Print the name, type, and class of an empty rdataset,
407  * such as those used to represent the question section
408  * of a DNS message.
409  */
410 static isc_result_t
411 question_totext(dns_rdataset_t *rdataset,
412 		dns_name_t *owner_name,
413 		dns_totext_ctx_t *ctx,
414 		isc_boolean_t omit_final_dot,
415 		isc_buffer_t *target)
416 {
417 	unsigned int column;
418 	isc_result_t result;
419 	isc_region_t r;
420 
421 	result = dns_rdataset_first(rdataset);
422 	REQUIRE(result == ISC_R_NOMORE);
423 
424 	column = 0;
425 
426 	/* Owner name */
427 	{
428 		unsigned int name_start = target->used;
429 		RETERR(dns_name_totext(owner_name,
430 				       omit_final_dot,
431 				       target));
432 		column += target->used - name_start;
433 	}
434 
435 	/* Class */
436 	{
437 		unsigned int class_start;
438 		INDENT_TO(class_column);
439 		class_start = target->used;
440 		result = dns_rdataclass_totext(rdataset->rdclass, target);
441 		if (result != ISC_R_SUCCESS)
442 			return (result);
443 		column += (target->used - class_start);
444 	}
445 
446 	/* Type */
447 	{
448 		unsigned int type_start;
449 		INDENT_TO(type_column);
450 		type_start = target->used;
451 		result = dns_rdatatype_totext(rdataset->type, target);
452 		if (result != ISC_R_SUCCESS)
453 			return (result);
454 		column += (target->used - type_start);
455 	}
456 
457 	isc_buffer_availableregion(target, &r);
458 	if (r.length < 1)
459 		return (ISC_R_NOSPACE);
460 	r.base[0] = '\n';
461 	isc_buffer_add(target, 1);
462 
463 	return (ISC_R_SUCCESS);
464 }
465 
466 isc_result_t
467 dns_rdataset_totext(dns_rdataset_t *rdataset,
468 		    dns_name_t *owner_name,
469 		    isc_boolean_t omit_final_dot,
470 		    isc_boolean_t question,
471 		    isc_buffer_t *target)
472 {
473 	dns_totext_ctx_t ctx;
474 	isc_result_t result;
475 	result = totext_ctx_init(&dns_master_style_debug, &ctx);
476 	if (result != ISC_R_SUCCESS) {
477 		UNEXPECTED_ERROR(__FILE__, __LINE__,
478 				 "could not set master file style");
479 		return (ISC_R_UNEXPECTED);
480 	}
481 
482 	/*
483 	 * The caller might want to give us an empty owner
484 	 * name (e.g. if they are outputting into a master
485 	 * file and this rdataset has the same name as the
486 	 * previous one.)
487 	 */
488 	if (dns_name_countlabels(owner_name) == 0)
489 		owner_name = NULL;
490 
491 	if (question)
492 		return (question_totext(rdataset, owner_name, &ctx,
493 					omit_final_dot, target));
494 	else
495 		return (rdataset_totext(rdataset, owner_name, &ctx,
496 					omit_final_dot, target));
497 }
498 
499 isc_result_t
500 dns_master_rdatasettotext(dns_name_t *owner_name,
501 			  dns_rdataset_t *rdataset,
502 			  const dns_master_style_t *style,
503 			  isc_buffer_t *target)
504 {
505 	dns_totext_ctx_t ctx;
506 	isc_result_t result;
507 	result = totext_ctx_init(style, &ctx);
508 	if (result != ISC_R_SUCCESS) {
509 		UNEXPECTED_ERROR(__FILE__, __LINE__,
510 				 "could not set master file style");
511 		return (ISC_R_UNEXPECTED);
512 	}
513 
514 	return (rdataset_totext(rdataset, owner_name, &ctx,
515 				ISC_FALSE, target));
516 }
517 
518 isc_result_t
519 dns_master_questiontotext(dns_name_t *owner_name,
520 			  dns_rdataset_t *rdataset,
521 			  const dns_master_style_t *style,
522 			  isc_buffer_t *target)
523 {
524 	dns_totext_ctx_t ctx;
525 	isc_result_t result;
526 	result = totext_ctx_init(style, &ctx);
527 	if (result != ISC_R_SUCCESS) {
528 		UNEXPECTED_ERROR(__FILE__, __LINE__,
529 				 "could not set master file style");
530 		return (ISC_R_UNEXPECTED);
531 	}
532 
533 	return (question_totext(rdataset, owner_name, &ctx,
534 				ISC_FALSE, target));
535 }
536 
537 isc_result_t
538 dns_master_stylecreate2(dns_master_style_t **stylep, unsigned int flags,
539 			unsigned int ttl_column, unsigned int class_column,
540 			unsigned int type_column, unsigned int rdata_column,
541 			unsigned int line_length, unsigned int tab_width,
542 			unsigned int split_width)
543 {
544 	dns_master_style_t *style;
545 
546 	REQUIRE(stylep != NULL && *stylep == NULL);
547 	style = malloc(sizeof(*style));
548 	if (style == NULL)
549 		return (ISC_R_NOMEMORY);
550 
551 	style->flags = flags;
552 	style->ttl_column = ttl_column;
553 	style->class_column = class_column;
554 	style->type_column = type_column;
555 	style->rdata_column = rdata_column;
556 	style->line_length = line_length;
557 	style->tab_width = tab_width;
558 	style->split_width = split_width;
559 
560 	*stylep = style;
561 	return (ISC_R_SUCCESS);
562 }
563 
564 void
565 dns_master_styledestroy(dns_master_style_t **stylep) {
566 	dns_master_style_t *style;
567 
568 	REQUIRE(stylep != NULL && *stylep != NULL);
569 	style = *stylep;
570 	*stylep = NULL;
571 	free(style);
572 }
573