xref: /openbsd-src/usr.bin/dig/lib/dns/masterdump.c (revision 4008b4f70d16d90cdc0c5877e9c3c42ccdc5be48)
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 	int 		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 	int 		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
indent(unsigned int * current,unsigned int to,int tabwidth,isc_buffer_t * target)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
totext_ctx_init(const dns_master_style_t * style,dns_totext_ctx_t * ctx)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 = 0;
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 = 0;
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
rdataset_totext(dns_rdataset_t * rdataset,dns_name_t * owner_name,dns_totext_ctx_t * ctx,int omit_final_dot,isc_buffer_t * target)238 rdataset_totext(dns_rdataset_t *rdataset,
239 		dns_name_t *owner_name,
240 		dns_totext_ctx_t *ctx,
241 		int omit_final_dot,
242 		isc_buffer_t *target)
243 {
244 	isc_result_t result;
245 	unsigned int column;
246 	int first = 1;
247 	uint32_t current_ttl;
248 	int current_ttl_valid;
249 	dns_rdatatype_t type;
250 	unsigned int type_start;
251 
252 	result = dns_rdataset_first(rdataset);
253 
254 	current_ttl = ctx->current_ttl;
255 	current_ttl_valid = ctx->current_ttl_valid;
256 
257 	while (result == ISC_R_SUCCESS) {
258 		column = 0;
259 
260 		/*
261 		 * Comment?
262 		 */
263 		if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0)
264 			RETERR(isc_str_tobuffer(";", target));
265 
266 		/*
267 		 * Owner name.
268 		 */
269 		if (owner_name != NULL &&
270 		    ! ((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0 &&
271 		       !first))
272 		{
273 			unsigned int name_start = target->used;
274 			RETERR(dns_name_totext(owner_name,
275 					       omit_final_dot,
276 					       target));
277 			column += target->used - name_start;
278 		}
279 
280 		/*
281 		 * TTL.
282 		 */
283 		if ((ctx->style.flags & DNS_STYLEFLAG_NO_TTL) == 0 &&
284 		    !((ctx->style.flags & DNS_STYLEFLAG_OMIT_TTL) != 0 &&
285 		      current_ttl_valid &&
286 		      rdataset->ttl == current_ttl))
287 		{
288 			char ttlbuf[64];
289 			isc_region_t r;
290 			unsigned int length;
291 
292 			INDENT_TO(ttl_column);
293 			length = snprintf(ttlbuf, sizeof(ttlbuf), "%u",
294 					  rdataset->ttl);
295 			INSIST(length <= sizeof(ttlbuf));
296 			isc_buffer_availableregion(target, &r);
297 			if (r.length < length)
298 				return (ISC_R_NOSPACE);
299 			memmove(r.base, ttlbuf, length);
300 			isc_buffer_add(target, length);
301 			column += length;
302 
303 			/*
304 			 * If the $TTL directive is not in use, the TTL we
305 			 * just printed becomes the default for subsequent RRs.
306 			 */
307 			if ((ctx->style.flags & DNS_STYLEFLAG_TTL) == 0) {
308 				current_ttl = rdataset->ttl;
309 				current_ttl_valid = 1;
310 			}
311 		}
312 
313 		/*
314 		 * Class.
315 		 */
316 		if ((ctx->style.flags & DNS_STYLEFLAG_NO_CLASS) == 0 &&
317 		    ((ctx->style.flags & DNS_STYLEFLAG_OMIT_CLASS) == 0 ||
318 		     !ctx->class_printed))
319 		{
320 			unsigned int class_start;
321 			INDENT_TO(class_column);
322 			class_start = target->used;
323 			result = dns_rdataclass_totext(rdataset->rdclass,
324 						       target);
325 			if (result != ISC_R_SUCCESS)
326 				return (result);
327 			column += (target->used - class_start);
328 		}
329 
330 		/*
331 		 * Type.
332 		 */
333 
334 		type = rdataset->type;
335 
336 		INDENT_TO(type_column);
337 		type_start = target->used;
338 		switch (type) {
339 		case dns_rdatatype_keydata:
340 #define KEYDATA "KEYDATA"
341 			if ((ctx->style.flags & DNS_STYLEFLAG_KEYDATA) != 0) {
342 				if (isc_buffer_availablelength(target) <
343 				    (sizeof(KEYDATA) - 1))
344 					return (ISC_R_NOSPACE);
345 				isc_buffer_putstr(target, KEYDATA);
346 				break;
347 			}
348 			/* FALLTHROUGH */
349 		default:
350 			result = dns_rdatatype_totext(type, target);
351 			if (result != ISC_R_SUCCESS)
352 				return (result);
353 		}
354 		column += (target->used - type_start);
355 
356 		/*
357 		 * Rdata.
358 		 */
359 		INDENT_TO(rdata_column);
360 		{
361 			dns_rdata_t rdata = DNS_RDATA_INIT;
362 			isc_region_t r;
363 
364 			dns_rdataset_current(rdataset, &rdata);
365 
366 			RETERR(dns_rdata_tofmttext(&rdata,
367 						   ctx->origin,
368 						   (unsigned int)
369 						   ctx->style.flags,
370 						   ctx->style.line_length -
371 						       ctx->style.rdata_column,
372 						   ctx->style.split_width,
373 						   ctx->linebreak,
374 						   target));
375 
376 			isc_buffer_availableregion(target, &r);
377 			if (r.length < 1)
378 				return (ISC_R_NOSPACE);
379 			r.base[0] = '\n';
380 			isc_buffer_add(target, 1);
381 		}
382 
383 		first = 0;
384 		result = dns_rdataset_next(rdataset);
385 	}
386 
387 	if (result != ISC_R_NOMORE)
388 		return (result);
389 
390 	/*
391 	 * Update the ctx state to reflect what we just printed.
392 	 * This is done last, only when we are sure we will return
393 	 * success, because this function may be called multiple
394 	 * times with increasing buffer sizes until it succeeds,
395 	 * and failed attempts must not update the state prematurely.
396 	 */
397 	ctx->class_printed = 1;
398 	ctx->current_ttl= current_ttl;
399 	ctx->current_ttl_valid = current_ttl_valid;
400 
401 	return (ISC_R_SUCCESS);
402 }
403 
404 /*
405  * Print the name, type, and class of an empty rdataset,
406  * such as those used to represent the question section
407  * of a DNS message.
408  */
409 static isc_result_t
question_totext(dns_rdataset_t * rdataset,dns_name_t * owner_name,dns_totext_ctx_t * ctx,int omit_final_dot,isc_buffer_t * target)410 question_totext(dns_rdataset_t *rdataset,
411 		dns_name_t *owner_name,
412 		dns_totext_ctx_t *ctx,
413 		int omit_final_dot,
414 		isc_buffer_t *target)
415 {
416 	unsigned int column;
417 	isc_result_t result;
418 	isc_region_t r;
419 
420 	result = dns_rdataset_first(rdataset);
421 	REQUIRE(result == ISC_R_NOMORE);
422 
423 	column = 0;
424 
425 	/* Owner name */
426 	{
427 		unsigned int name_start = target->used;
428 		RETERR(dns_name_totext(owner_name,
429 				       omit_final_dot,
430 				       target));
431 		column += target->used - name_start;
432 	}
433 
434 	/* Class */
435 	{
436 		unsigned int class_start;
437 		INDENT_TO(class_column);
438 		class_start = target->used;
439 		result = dns_rdataclass_totext(rdataset->rdclass, target);
440 		if (result != ISC_R_SUCCESS)
441 			return (result);
442 		column += (target->used - class_start);
443 	}
444 
445 	/* Type */
446 	{
447 		unsigned int type_start;
448 		INDENT_TO(type_column);
449 		type_start = target->used;
450 		result = dns_rdatatype_totext(rdataset->type, target);
451 		if (result != ISC_R_SUCCESS)
452 			return (result);
453 		column += (target->used - type_start);
454 	}
455 
456 	isc_buffer_availableregion(target, &r);
457 	if (r.length < 1)
458 		return (ISC_R_NOSPACE);
459 	r.base[0] = '\n';
460 	isc_buffer_add(target, 1);
461 
462 	return (ISC_R_SUCCESS);
463 }
464 
465 isc_result_t
dns_rdataset_totext(dns_rdataset_t * rdataset,dns_name_t * owner_name,int omit_final_dot,int question,isc_buffer_t * target)466 dns_rdataset_totext(dns_rdataset_t *rdataset,
467 		    dns_name_t *owner_name,
468 		    int omit_final_dot,
469 		    int question,
470 		    isc_buffer_t *target)
471 {
472 	dns_totext_ctx_t ctx;
473 	isc_result_t result;
474 	result = totext_ctx_init(&dns_master_style_debug, &ctx);
475 	if (result != ISC_R_SUCCESS) {
476 		UNEXPECTED_ERROR(__FILE__, __LINE__,
477 				 "could not set master file style");
478 		return (ISC_R_UNEXPECTED);
479 	}
480 
481 	/*
482 	 * The caller might want to give us an empty owner
483 	 * name (e.g. if they are outputting into a master
484 	 * file and this rdataset has the same name as the
485 	 * previous one.)
486 	 */
487 	if (dns_name_countlabels(owner_name) == 0)
488 		owner_name = NULL;
489 
490 	if (question)
491 		return (question_totext(rdataset, owner_name, &ctx,
492 					omit_final_dot, target));
493 	else
494 		return (rdataset_totext(rdataset, owner_name, &ctx,
495 					omit_final_dot, target));
496 }
497 
498 isc_result_t
dns_master_rdatasettotext(dns_name_t * owner_name,dns_rdataset_t * rdataset,const dns_master_style_t * style,isc_buffer_t * target)499 dns_master_rdatasettotext(dns_name_t *owner_name,
500 			  dns_rdataset_t *rdataset,
501 			  const dns_master_style_t *style,
502 			  isc_buffer_t *target)
503 {
504 	dns_totext_ctx_t ctx;
505 	isc_result_t result;
506 	result = totext_ctx_init(style, &ctx);
507 	if (result != ISC_R_SUCCESS) {
508 		UNEXPECTED_ERROR(__FILE__, __LINE__,
509 				 "could not set master file style");
510 		return (ISC_R_UNEXPECTED);
511 	}
512 
513 	return (rdataset_totext(rdataset, owner_name, &ctx,
514 				0, target));
515 }
516 
517 isc_result_t
dns_master_questiontotext(dns_name_t * owner_name,dns_rdataset_t * rdataset,const dns_master_style_t * style,isc_buffer_t * target)518 dns_master_questiontotext(dns_name_t *owner_name,
519 			  dns_rdataset_t *rdataset,
520 			  const dns_master_style_t *style,
521 			  isc_buffer_t *target)
522 {
523 	dns_totext_ctx_t ctx;
524 	isc_result_t result;
525 	result = totext_ctx_init(style, &ctx);
526 	if (result != ISC_R_SUCCESS) {
527 		UNEXPECTED_ERROR(__FILE__, __LINE__,
528 				 "could not set master file style");
529 		return (ISC_R_UNEXPECTED);
530 	}
531 
532 	return (question_totext(rdataset, owner_name, &ctx,
533 				0, target));
534 }
535 
536 isc_result_t
dns_master_stylecreate2(dns_master_style_t ** stylep,unsigned int flags,unsigned int ttl_column,unsigned int class_column,unsigned int type_column,unsigned int rdata_column,unsigned int line_length,unsigned int tab_width,unsigned int split_width)537 dns_master_stylecreate2(dns_master_style_t **stylep, unsigned int flags,
538 			unsigned int ttl_column, unsigned int class_column,
539 			unsigned int type_column, unsigned int rdata_column,
540 			unsigned int line_length, unsigned int tab_width,
541 			unsigned int split_width)
542 {
543 	dns_master_style_t *style;
544 
545 	REQUIRE(stylep != NULL && *stylep == NULL);
546 	style = malloc(sizeof(*style));
547 	if (style == NULL)
548 		return (ISC_R_NOMEMORY);
549 
550 	style->flags = flags;
551 	style->ttl_column = ttl_column;
552 	style->class_column = class_column;
553 	style->type_column = type_column;
554 	style->rdata_column = rdata_column;
555 	style->line_length = line_length;
556 	style->tab_width = tab_width;
557 	style->split_width = split_width;
558 
559 	*stylep = style;
560 	return (ISC_R_SUCCESS);
561 }
562 
563 void
dns_master_styledestroy(dns_master_style_t ** stylep)564 dns_master_styledestroy(dns_master_style_t **stylep) {
565 	dns_master_style_t *style;
566 
567 	REQUIRE(stylep != NULL && *stylep != NULL);
568 	style = *stylep;
569 	*stylep = NULL;
570 	free(style);
571 }
572