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