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
40struct 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 */
62typedef 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 */
77const dns_master_style_t
78dns_master_style_debug = {
79	DNS_STYLEFLAG_REL_OWNER,
80	24, 32, 40, 48, 80, 8, UINT_MAX
81};
82
83#define N_SPACES 10
84static char spaces[N_SPACES+1] = "          ";
85
86#define N_TABS 10
87static 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 */
94static isc_result_t
95indent(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
154static isc_result_t
155totext_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
237static isc_result_t
238rdataset_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 */
409static isc_result_t
410question_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
465isc_result_t
466dns_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
498isc_result_t
499dns_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
517isc_result_t
518dns_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
536isc_result_t
537dns_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
563void
564dns_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