xref: /titanic_41/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dns.c (revision 24db46411fd54f70c35b94bb952eb7ba040e43b4)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdio.h>
29 #include <string.h>
30 #include <limits.h>
31 #include <sys/types.h>
32 #include <sys/errno.h>
33 #include <sys/tiuser.h>
34 #include <arpa/nameser.h>
35 #include <arpa/inet.h>
36 #include <netinet/in.h>
37 #include "snoop.h"
38 
39 /* The string used to indent detail lines */
40 #define	DNS_INDENT	"    "
41 /*
42  * From RFC1035, the maximum size of a character-string is limited by the
43  * one octet length field.  We add one character to that to make sure the
44  * result is terminated.
45  */
46 #define	MAX_CHAR_STRING_SIZE	UCHAR_MAX + 1
47 
48 /* private functions */
49 static char *dns_opcode_string(uint_t opcode);
50 static char *dns_rcode_string(uint_t rcode);
51 static char *dns_type_string(uint_t type, int detail);
52 static char *dns_class_string(uint_t cls, int detail);
53 static size_t skip_question(const uchar_t *header, const uchar_t *data,
54     const uchar_t *data_end);
55 static size_t print_question(char *line, const uchar_t *header,
56     const uchar_t *data, const uchar_t *data_end, int detail);
57 static size_t print_answer(char *line, const uchar_t *header,
58     const uchar_t *data, const uchar_t *data_end, int detail);
59 static char *binary_string(char data);
60 static void print_ip(int af, char *line, const uchar_t *data, uint16_t len);
61 static const uchar_t *get_char_string(const uchar_t *data, char *charbuf,
62     uint16_t datalen);
63 static size_t print_char_string(char *line, const uchar_t *data, uint16_t len);
64 static const uchar_t *get_domain_name(const uchar_t *header,
65     const uchar_t *data, const uchar_t *data_end, char *namebuf, char *namend);
66 static size_t print_domain_name(char *line, const uchar_t *header,
67     const uchar_t *data, const uchar_t *data_end);
68 
69 void
70 interpret_dns(int flags, int proto, const uchar_t *data, int len)
71 {
72 	typedef HEADER dns_header;
73 	dns_header header;
74 	char *line;
75 	ushort_t id, qdcount, ancount, nscount, arcount;
76 	ushort_t count;
77 	const uchar_t *rrp;	/* Resource Record Pointer. */
78 	const uchar_t *data_end;
79 
80 	if (proto == IPPROTO_TCP) {
81 		/* not supported now */
82 		return;
83 	}
84 
85 	/* We need at least the header in order to parse a packet. */
86 	if (sizeof (dns_header) > len) {
87 		return;
88 	}
89 	data_end = data + len;
90 	/*
91 	 * Copy the header into a local structure for aligned access to
92 	 * each field.
93 	 */
94 	(void) memcpy(&header, data, sizeof (header));
95 	id = ntohs(header.id);
96 	qdcount = ntohs(header.qdcount);
97 	ancount = ntohs(header.ancount);
98 	nscount = ntohs(header.nscount);
99 	arcount = ntohs(header.arcount);
100 
101 	if (flags & F_SUM) {
102 		line = get_sum_line();
103 		line += sprintf(line, "DNS %c ", header.qr ? 'R' : 'C');
104 
105 		if (header.qr) {
106 			/* answer */
107 			if (header.rcode == 0) {
108 				/* reply is OK */
109 				rrp = data + sizeof (dns_header);
110 				while (qdcount--) {
111 					if (rrp >= data_end) {
112 						return;
113 					}
114 					rrp += skip_question(data,
115 					    rrp, data_end);
116 				}
117 				/* the answers follow the questions */
118 				if (ancount > 0) {
119 					(void) print_answer(line,
120 					    data, rrp, data_end, FALSE);
121 				}
122 			} else {
123 				(void) sprintf(line, " Error: %d(%s)",
124 				    header.rcode,
125 				    dns_rcode_string(header.rcode));
126 			}
127 		} else {
128 			/* question */
129 			rrp = data + sizeof (dns_header);
130 			if (rrp >= data_end) {
131 				return;
132 			}
133 			(void) print_question(line, data, rrp, data_end,
134 			    FALSE);
135 		}
136 	}
137 	if (flags & F_DTAIL) {
138 		show_header("DNS:  ", "DNS Header", sizeof (dns_header));
139 		show_space();
140 		if (header.qr) {
141 			/* answer */
142 			(void) snprintf(get_line(0, 0), get_line_remain(),
143 			    "Response ID = %d", id);
144 			(void) snprintf(get_line(0, 0), get_line_remain(),
145 			    "%s%s%s",
146 			    header.aa ? "AA (Authoritative Answer) " : "",
147 			    header.tc ? "TC (TrunCation) " : "",
148 			    header.ra ? "RA (Recursion Available) ": "");
149 			(void) snprintf(get_line(0, 0), get_line_remain(),
150 			    "Response Code: %d (%s)",
151 			    header.rcode, dns_rcode_string(header.rcode));
152 			(void) snprintf(get_line(0, 0), get_line_remain(),
153 			    "Reply to %d question(s)", qdcount);
154 		} else {
155 			/* question */
156 			(void) snprintf(get_line(0, 0), get_line_remain(),
157 			    "Query ID = %d", id);
158 			(void) snprintf(get_line(0, 0), get_line_remain(),
159 			    "Opcode: %s", dns_opcode_string(header.opcode));
160 			(void) snprintf(get_line(0, 0), get_line_remain(),
161 			    "%s%s",
162 			    header.tc ? "TC (TrunCation) " : "",
163 			    header.rd ? "RD (Recursion Desired) " : "");
164 			(void) snprintf(get_line(0, 0), get_line_remain(),
165 			    "%d question(s)", qdcount);
166 		}
167 		rrp = data + sizeof (dns_header);
168 		count = 0;
169 		while (qdcount--) {
170 			if (rrp >= data_end) {
171 				return;
172 			}
173 			count++;
174 			rrp += print_question(get_line(0, 0),
175 			    data, rrp, data_end, TRUE);
176 			show_space();
177 		}
178 		/* Only answers should hold answers, but just in case */
179 		if (header.qr || ancount > 0) {
180 			(void) snprintf(get_line(0, 0), get_line_remain(),
181 			    "%d answer(s)", ancount);
182 			count = 0;
183 			while (ancount--) {
184 				if (rrp >= data_end) {
185 					return;
186 				}
187 				count++;
188 				rrp += print_answer(get_line(0, 0),
189 				    data, rrp, data_end, TRUE);
190 				show_space();
191 			}
192 		}
193 		/* Likewise only answers should hold NS records */
194 		if (header.qr || nscount > 0) {
195 			(void) snprintf(get_line(0, 0), get_line_remain(),
196 			    "%d name server resource(s)", nscount);
197 			count = 0;
198 			while (nscount--) {
199 				if (rrp >= data_end) {
200 					return;
201 				}
202 				count++;
203 				rrp += print_answer(get_line(0, 0), data,
204 				    rrp, data_end, TRUE);
205 				show_space();
206 			}
207 		}
208 		/* Additional section may hold an EDNS0 record. */
209 		if (header.qr || arcount > 0) {
210 			(void) snprintf(get_line(0, 0), get_line_remain(),
211 			    "%d additional record(s)", arcount);
212 			count = 0;
213 			while (arcount-- && rrp < data_end) {
214 				count++;
215 				rrp += print_answer(get_line(0, 0), data,
216 				    rrp, data_end, TRUE);
217 				show_space();
218 			}
219 		}
220 	}
221 }
222 
223 
224 static char *
225 dns_opcode_string(uint_t opcode)
226 {
227 	static char buffer[64];
228 	switch (opcode) {
229 	case ns_o_query:	return ("Query");
230 	case ns_o_iquery:	return ("Inverse Query");
231 	case ns_o_status:	return ("Status");
232 	default:
233 		(void) snprintf(buffer, sizeof (buffer), "Unknown (%u)",
234 		    opcode);
235 		return (buffer);
236 	}
237 }
238 
239 static char *
240 dns_rcode_string(uint_t rcode)
241 {
242 	static char buffer[64];
243 	switch (rcode) {
244 	case ns_r_noerror:	return ("OK");
245 	case ns_r_formerr:	return ("Format Error");
246 	case ns_r_servfail:	return ("Server Fail");
247 	case ns_r_nxdomain:	return ("Name Error");
248 	case ns_r_notimpl:	return ("Unimplemented");
249 	case ns_r_refused:	return ("Refused");
250 	case ns_r_badvers:	return ("Bad Version"); /* EDNS rcode */
251 	default:
252 		(void) snprintf(buffer, sizeof (buffer), "Unknown (%u)", rcode);
253 		return (buffer);
254 	}
255 }
256 
257 static char *
258 dns_type_string(uint_t type, int detail)
259 {
260 	static char buffer[64];
261 	switch (type) {
262 	case ns_t_a:	return (detail ? "Address" : "Addr");
263 	case ns_t_ns:	return (detail ? "Authoritative Name Server" : "NS");
264 	case ns_t_cname:	return (detail ? "Canonical Name" : "CNAME");
265 	case ns_t_soa:	return (detail ? "Start Of a zone Authority" : "SOA");
266 	case ns_t_mb:	return (detail ? "Mailbox domain name" : "MB");
267 	case ns_t_mg:	return (detail ? "Mailbox Group member" : "MG");
268 	case ns_t_mr:	return (detail ? "Mail Rename domain name" : "MR");
269 	case ns_t_null:	return ("NULL");
270 	case ns_t_wks:	return (detail ? "Well Known Service" : "WKS");
271 	case ns_t_ptr:	return (detail ? "Domain Name Pointer" : "PTR");
272 	case ns_t_hinfo:	return (detail ? "Host Information": "HINFO");
273 	case ns_t_minfo:
274 		return (detail ? "Mailbox or maillist Info" : "MINFO");
275 	case ns_t_mx:	return (detail ? "Mail Exchange" : "MX");
276 	case ns_t_txt:	return (detail ? "Text strings" : "TXT");
277 	case ns_t_aaaa:	return (detail ? "IPv6 Address" : "AAAA");
278 	case ns_t_opt:	return (detail ? "EDNS0 option" : "OPT");
279 	case ns_t_axfr:	return (detail ? "Transfer of entire zone" : "AXFR");
280 	case ns_t_mailb:
281 		return (detail ? "Mailbox related records" : "MAILB");
282 	case ns_t_maila:	return (detail ? "Mail agent RRs" : "MAILA");
283 	case ns_t_any:	return (detail ? "All records" : "*");
284 	default:
285 		(void) snprintf(buffer, sizeof (buffer), "Unknown (%u)", type);
286 		return (buffer);
287 	}
288 }
289 
290 static char *
291 dns_class_string(uint_t cls, int detail)
292 {
293 	static char buffer[64];
294 	switch (cls) {
295 	case ns_c_in:		return (detail ? "Internet" : "Internet");
296 	case ns_c_chaos: 	return (detail ? "CHAOS" : "CH");
297 	case ns_c_hs:		return (detail ? "Hesiod" : "HS");
298 	case ns_c_any:		return (detail ? "* (Any class)" : "*");
299 	default:
300 		(void) snprintf(buffer, sizeof (buffer), "Unknown (%u)", cls);
301 		return (buffer);
302 	}
303 }
304 
305 static size_t
306 skip_question(const uchar_t *header, const uchar_t *data,
307     const uchar_t *data_end)
308 {
309 	const uchar_t *data_bak = data;
310 	char dummy_buffer[NS_MAXDNAME];
311 
312 	data = get_domain_name(header, data, data_end, dummy_buffer,
313 	    dummy_buffer + sizeof (dummy_buffer));
314 	/* Skip the 32 bits of class and type that follow the domain name */
315 	data += sizeof (uint32_t);
316 	return (data - data_bak);
317 }
318 
319 static size_t
320 print_question(char *line, const uchar_t *header, const uchar_t *data,
321     const uchar_t *data_end, int detail)
322 {
323 	const uchar_t *data_bak = data;
324 	uint16_t type;
325 	uint16_t cls;
326 
327 	if (detail) {
328 		line += snprintf(line, get_line_remain(),
329 		    DNS_INDENT "Domain Name: ");
330 	}
331 	data += print_domain_name(line, header, data, data_end);
332 
333 	/*
334 	 * Make sure we don't run off the end of the packet by reading the
335 	 * type and class.
336 	 *
337 	 * The pointer subtraction on the left side of the following
338 	 * expression has a signed result of type ptrdiff_t, and the right
339 	 * side has an unsigned result of type size_t.  We therefore need
340 	 * to cast the right side of the expression to be of the same
341 	 * signed type to keep the result of the pointer arithmetic to be
342 	 * automatically cast to an unsigned value.  We do a similar cast
343 	 * in other similar expressions throughout this file.
344 	 */
345 	if ((data_end - data) < (ptrdiff_t)(2 * sizeof (uint16_t)))
346 		return (data_end - data_bak);
347 
348 	GETINT16(type, data);
349 	GETINT16(cls, data);
350 
351 	if (detail) {
352 		(void) snprintf(get_line(0, 0), get_line_remain(),
353 		    DNS_INDENT "Class: %u (%s)",
354 		    cls, dns_class_string(cls, detail));
355 		(void) snprintf(get_line(0, 0), get_line_remain(),
356 		    DNS_INDENT "Type:  %u (%s)", type,
357 		    dns_type_string(type, detail));
358 	} else {
359 		(void) sprintf(line + strlen(line), " %s %s \?",
360 		    dns_class_string(cls, detail),
361 		    dns_type_string(type, detail));
362 	}
363 	return (data - data_bak);
364 }
365 
366 /*
367  * print_answer() is used to display the contents of a single resource
368  * record (RR) from either the answer, name server or additional
369  * section of the DNS packet.
370  *
371  * Input:
372  *	*line: snoops output buffer.
373  *	*header: start of the DNS packet, required for names and rcode.
374  *	*data: location within header from where the RR starts.
375  * 	*data_end: where DNS data ends.
376  * 	detail: simple or verbose output.
377  *
378  * Returns:
379  *	Pointer to next RR or data_end.
380  *
381  * Most RRs have the same top level format as defined in RFC 1035:
382  *
383  *                                     1  1  1  1  1  1
384  *       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
385  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
386  *    |                                               |
387  *    /                      NAME                     /
388  *    |                                               |
389  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
390  *    |                      TYPE                     |
391  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
392  *    |                     CLASS                     |
393  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
394  *    |                      TTL                      |
395  *    |                                               |
396  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
397  *    |                   RDLENGTH                    |
398  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
399  *    /                     RDATA                     /
400  *    /                                               /
401  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
402  *
403  * However RFC 2671 introduced an exception to this rule
404  * with the "Extension Mechanisms for DNS" (EDNS0).
405  * When the type is 41 the remaining resource record format
406  * is:
407  *
408  *                                     1  1  1  1  1  1
409  *       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
410  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
411  *    |                    TYPE = 41                  |
412  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
413  *    |           Sender's UDP payload size           |
414  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
415  *    |    Extended-rcode     |        Version        |
416  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
417  *    |                      Zero                     |
418  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
419  *    |                   RDLENGTH                    |
420  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
421  *    /                     RDATA                     /
422  *    /                                               /
423  *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
424  *
425  */
426 static size_t
427 print_answer(char *line, const uchar_t *header, const uchar_t *data,
428     const uchar_t *data_end, int detail)
429 {
430 	const uchar_t *data_bak = data;
431 	const uchar_t *data_next;
432 	uint16_t type;
433 	uint16_t cls;
434 	int32_t ttl;
435 	uint16_t rdlen;
436 	uint32_t serial, refresh, retry, expire, minimum;
437 	uint8_t protocol;
438 	int linepos;
439 	uint16_t preference;
440 	/* declarations for EDNS follow */
441 	uint16_t size;	/* Sender's UDP payload size */
442 	uint8_t xrcode;	/* Extended-rcode */
443 	uint8_t ver;	/* Version */
444 	uint16_t rcode;	/* Extracted from the DNS header */
445 	union {		/* DNS header overlay used for extraction */
446 		HEADER		*head;
447 		const uchar_t	*raw;
448 	} headptr;
449 
450 	if (detail) {
451 		line += snprintf(line, get_line_remain(),
452 		    DNS_INDENT "Domain Name: ");
453 	}
454 	data += print_domain_name(line, header, data, data_end);
455 
456 	/*
457 	 * Next, get the record type, being careful to make sure we
458 	 * don't run off the end of the packet.
459 	 */
460 	if ((data_end - data) < (ptrdiff_t)(sizeof (type))) {
461 		return (data_end - data_bak);
462 	}
463 
464 	GETINT16(type, data);
465 
466 	if (type == ns_t_opt) {
467 		/*
468 		 * Make sure we won't run off the end reading size,
469 		 * xrcode, version, zero and rdlen.
470 		 */
471 		if ((data_end - data) <
472 		    ((ptrdiff_t)(sizeof (size)
473 			+ sizeof (xrcode)
474 			+ sizeof (ver)
475 			+ sizeof (cls)	/* zero */
476 			+ sizeof (rdlen)))) {
477 			return (data_end - data_bak);
478 		}
479 
480 		GETINT16(size, data);
481 		GETINT8(xrcode, data);
482 		/*
483 		 * The extended rcode represents the top half of the
484 		 * rcode which must be added to the rcode in the header.
485 		 */
486 		rcode = 0xff & (xrcode << 4);
487 		headptr.raw = header;		/* Overlay the header... */
488 		rcode += headptr.head->rcode;	/* And pluck out the rcode. */
489 
490 		GETINT8(ver, data);
491 		GETINT16(cls, data); /* zero */
492 		GETINT16(rdlen, data);
493 
494 		if (detail) {
495 			(void) snprintf(get_line(0, 0), get_line_remain(),
496 			    DNS_INDENT "Type:  %u (%s)", type,
497 			    dns_type_string(type, detail));
498 			(void) snprintf(get_line(0, 0), get_line_remain(),
499 			    DNS_INDENT "UDP payload size: %u (0x%.4x)",
500 			    size, size);
501 			(void) snprintf(get_line(0, 0), get_line_remain(),
502 			    DNS_INDENT "Extended rcode: %u "
503 			    "(translates to %u (%s))",
504 			    xrcode, rcode, dns_rcode_string(rcode));
505 			(void) snprintf(get_line(0, 0), get_line_remain(),
506 			    DNS_INDENT "EDNS0 Version: %u", ver);
507 			(void) snprintf(get_line(0, 0), get_line_remain(),
508 			    DNS_INDENT "zero: %u", cls);
509 			(void) snprintf(get_line(0, 0), get_line_remain(),
510 			    DNS_INDENT "Data length: %u", rdlen);
511 		} else {
512 			line += strlen(line);
513 			line += sprintf(line, " %s UDP %u rc %d ver %u len %u",
514 			    dns_type_string(type, detail), size, rcode, ver,
515 			    rdlen);
516 		}
517 
518 		/*
519 		 * Make sure that rdlen is within data boundary.
520 		 */
521 		if (rdlen > data_end - data)
522 			return (data_end - data_bak);
523 
524 		/* Future OPT decode code goes here. */
525 
526 		data += rdlen;
527 		return (data - data_bak);
528 	}
529 
530 	/*
531 	 * Make sure we don't run off the end of the packet by reading the
532 	 * class, ttl, and length.
533 	 */
534 	if ((data_end - data) <
535 	    ((ptrdiff_t)(sizeof (cls)
536 		+ sizeof (ttl)
537 		+ sizeof (rdlen)))) {
538 		return (data_end - data_bak);
539 	}
540 
541 	GETINT16(cls, data);
542 
543 	if (detail) {
544 		(void) snprintf(get_line(0, 0), get_line_remain(),
545 		    DNS_INDENT "Class: %d (%s)", cls,
546 		    dns_class_string(cls, detail));
547 		(void) snprintf(get_line(0, 0), get_line_remain(),
548 		    DNS_INDENT "Type:  %d (%s)", type,
549 		    dns_type_string(type, detail));
550 	} else {
551 		line += strlen(line);
552 		line += sprintf(line, " %s %s ",
553 		    dns_class_string(cls, detail),
554 		    dns_type_string(type, detail));
555 	}
556 
557 	GETINT32(ttl, data);
558 	if (detail) {
559 		(void) snprintf(get_line(0, 0), get_line_remain(),
560 		    DNS_INDENT "TTL (Time To Live): %d", ttl);
561 	}
562 
563 	GETINT16(rdlen, data);
564 	if (detail) {
565 		line = get_line(0, 0);
566 		line += snprintf(line, get_line_remain(), DNS_INDENT "%s: ",
567 		    dns_type_string(type, detail));
568 	}
569 
570 	if (rdlen > data_end - data)
571 		return (data_end - data_bak);
572 
573 	switch (type) {
574 	case ns_t_a:
575 		print_ip(AF_INET, line, data, rdlen);
576 		break;
577 	case ns_t_aaaa:
578 		print_ip(AF_INET6, line, data, rdlen);
579 		break;
580 	case ns_t_hinfo:
581 		line += sprintf(line, "CPU: ");
582 		data_next = data + print_char_string(line, data, rdlen);
583 		if (data_next >= data_end)
584 			break;
585 		line += strlen(line);
586 		line += sprintf(line, "OS: ");
587 		(void) print_char_string(line, data_next,
588 		    rdlen - (data_next - data));
589 		break;
590 	case ns_t_ns:
591 	case ns_t_cname:
592 	case ns_t_mb:
593 	case ns_t_mg:
594 	case ns_t_mr:
595 	case ns_t_ptr:
596 		(void) print_domain_name(line, header, data, data_end);
597 		break;
598 	case ns_t_mx:
599 		data_next = data;
600 		if (rdlen < sizeof (uint16_t))
601 			break;
602 		GETINT16(preference, data_next);
603 		if (detail) {
604 			(void) print_domain_name(line, header, data_next,
605 			    data_end);
606 			(void) snprintf(get_line(0, 0), get_line_remain(),
607 			    DNS_INDENT "Preference: %u", preference);
608 		} else {
609 			(void) print_domain_name(line, header, data_next,
610 			    data_end);
611 		}
612 		break;
613 	case ns_t_soa:
614 		if (!detail)
615 			break;
616 		line = get_line(0, 0);
617 		line += snprintf(line, get_line_remain(),
618 		    DNS_INDENT "MNAME (Server name): ");
619 		data_next = data + print_domain_name(line, header, data,
620 		    data_end);
621 		if (data_next >= data_end)
622 			break;
623 		line = get_line(0, 0);
624 		line += snprintf(line, get_line_remain(),
625 		    DNS_INDENT "RNAME (Resposible mailbox): ");
626 		data_next = data_next +
627 		    print_domain_name(line, header, data_next, data_end);
628 		if ((data_end - data_next) < (ptrdiff_t)(5 * sizeof (uint32_t)))
629 			break;
630 		GETINT32(serial, data_next);
631 		GETINT32(refresh, data_next);
632 		GETINT32(retry, data_next);
633 		GETINT32(expire, data_next);
634 		GETINT32(minimum, data_next);
635 		(void) snprintf(get_line(0, 0), get_line_remain(),
636 		    DNS_INDENT "Serial: %u", serial);
637 		(void) snprintf(get_line(0, 0), get_line_remain(),
638 		    DNS_INDENT "Refresh: %u  Retry: %u  "
639 		    "Expire: %u Minimum: %u",
640 		    refresh, retry, expire, minimum);
641 		break;
642 	case ns_t_wks:
643 		print_ip(AF_INET, line, data, rdlen);
644 		if (!detail)
645 			break;
646 		data_next = data + sizeof (in_addr_t);
647 		if (data_next >= data_end)
648 			break;
649 		GETINT8(protocol, data_next);
650 		line = get_line(0, 0);
651 		line += snprintf(line, get_line_remain(),
652 		    DNS_INDENT "Protocol: %u ", protocol);
653 		switch (protocol) {
654 		case IPPROTO_UDP:
655 			(void) snprintf(line, get_line_remain(), "(UDP)");
656 			break;
657 		case IPPROTO_TCP:
658 			(void) snprintf(line, get_line_remain(), "(TCP)");
659 			break;
660 		}
661 		(void) snprintf(get_line(0, 0), get_line_remain(),
662 		    DNS_INDENT "Service bitmap:");
663 		(void) snprintf(line, get_line_remain(),
664 		    DNS_INDENT "0       8       16      24");
665 		linepos = 4;
666 		while (data_next < data + rdlen) {
667 			if (linepos == 4) {
668 				line = get_line(0, 0);
669 				line += snprintf(line, get_line_remain(),
670 				    DNS_INDENT);
671 				linepos = 0;
672 			}
673 			line += snprintf(line, get_line_remain(), "%s",
674 			    binary_string(*data_next));
675 			linepos++;
676 			data_next++;
677 		}
678 		break;
679 	case ns_t_minfo:
680 		if (!detail)
681 			break;
682 		line = get_line(0, 0);
683 		line += snprintf(line, get_line_remain(),
684 		    DNS_INDENT "RMAILBX (Resposible mailbox): ");
685 		data_next = data + print_domain_name(line, header, data,
686 		    data_end);
687 		line = get_line(0, 0);
688 		line += snprintf(line, get_line_remain(),
689 		    DNS_INDENT "EMAILBX (mailbox to receive err message): ");
690 		data_next = data_next + print_domain_name(line, header,
691 		    data_next, data_end);
692 		break;
693 	}
694 	data += rdlen;
695 	return (data - data_bak);
696 }
697 
698 static char *
699 binary_string(char data)
700 {
701 	static char bstring[8 + 1];
702 	char *ptr;
703 	int i;
704 	ptr = bstring;
705 	for (i = 0; i < 8; i++) {
706 		*ptr++ = (data & 0x80) ? '1' : '0';
707 		data = data << 1;
708 	}
709 	*ptr = (char)0;
710 	return (bstring);
711 }
712 
713 static void
714 print_ip(int af, char *line, const uchar_t *data, uint16_t len)
715 {
716 	in6_addr_t	addr6;
717 	in_addr_t	addr4;
718 	void		*addr;
719 
720 	switch (af) {
721 	case AF_INET:
722 		if (len != sizeof (in_addr_t))
723 			return;
724 		addr = memcpy(&addr4, data, sizeof (addr4));
725 		break;
726 	case AF_INET6:
727 		if (len != sizeof (in6_addr_t))
728 			return;
729 		addr = memcpy(&addr6, data, sizeof (addr6));
730 		break;
731 	}
732 
733 	(void) inet_ntop(af, addr, line, INET6_ADDRSTRLEN);
734 }
735 
736 /*
737  * charbuf is assumed to be of size MAX_CHAR_STRING_SIZE.
738  */
739 static const uchar_t *
740 get_char_string(const uchar_t *data, char *charbuf, uint16_t datalen)
741 {
742 	int len;
743 	char *name = charbuf;
744 	int i = 0;
745 
746 	/*
747 	 * From RFC1035, a character-string is a single length octet followed
748 	 * by that number of characters.
749 	 */
750 	if (datalen > 1) {
751 		len = *data;
752 		data++;
753 		if (len > 0 && len < MAX_CHAR_STRING_SIZE) {
754 			for (i = 0; i < len; i++, data++)
755 				name[i] = *data;
756 		}
757 	}
758 	name[i] = '\0';
759 	return (data);
760 }
761 
762 static size_t
763 print_char_string(char *line, const uchar_t *data, uint16_t len)
764 {
765 	char charbuf[MAX_CHAR_STRING_SIZE];
766 	const uchar_t *data_bak = data;
767 
768 	data = get_char_string(data, charbuf, len);
769 	(void) sprintf(line, "%s", charbuf);
770 	return (data - data_bak);
771 }
772 
773 /*
774  * header: the entire message header, this is where we start to
775  *	   count the offset of the compression scheme
776  * data:   the start of the domain name
777  * namebuf: user supplied buffer
778  * return: the next byte after what we have parsed
779  */
780 static const uchar_t *
781 get_domain_name(const uchar_t *header, const uchar_t *data,
782     const uchar_t *data_end, char *namebuf, char *namend)
783 {
784 	uint8_t len;
785 	char *name = namebuf;
786 
787 	/*
788 	 * From RFC1035, a domain name is a sequence of labels, where each
789 	 * label consists of a length octet followed by that number of
790 	 * octets.  The domain name terminates with the zero length octet
791 	 * for the null label of the root.
792 	 */
793 
794 	while (name < (namend - 1)) {
795 		if ((data_end - data) < (ptrdiff_t)(sizeof (uint8_t))) {
796 			/* The length octet is off the end of the packet. */
797 			break;
798 		}
799 		GETINT8(len, data);
800 		if (len == 0) {
801 			/*
802 			 * Domain names end with a length byte of zero,
803 			 * which represents the null label of the root.
804 			 */
805 			break;
806 		}
807 		/*
808 		 * test if we are using the compression scheme
809 		 */
810 		if ((len & 0xc0) == 0xc0) {
811 			uint16_t offset;
812 			const uchar_t *label_ptr;
813 
814 			/*
815 			 * From RFC1035, message compression allows a
816 			 * domain name or a list of labels at the end of a
817 			 * domain name to be replaced with a pointer to a
818 			 * prior occurance of the same name.  In this
819 			 * scheme, the pointer is a two octet sequence
820 			 * where the most significant two bits are set, and
821 			 * the remaining 14 bits are the offset from the
822 			 * start of the message of the next label.
823 			 */
824 			data--;
825 			if ((data_end - data) <
826 			    (ptrdiff_t)(sizeof (uint16_t))) {
827 				/*
828 				 * The offset octets aren't entirely
829 				 * contained within this pakcet.
830 				 */
831 				data = data_end;
832 				break;
833 			}
834 			GETINT16(offset, data);
835 			label_ptr = header + (offset & 0x3fff);
836 			/*
837 			 * We must verify that the offset is valid by
838 			 * checking that it is less than the current data
839 			 * pointer and that it isn't off the end of the
840 			 * packet.
841 			 */
842 			if (label_ptr > data || label_ptr >= data_end)
843 				break;
844 			(void) get_domain_name(header, label_ptr, data_end,
845 			    name, namend);
846 			return (data);
847 		} else {
848 			if (len > (data_end - data)) {
849 				/*
850 				 * The label isn't entirely contained
851 				 * within the packet.  Don't read it.  The
852 				 * caller checks that the data pointer is
853 				 * not beyond the end after we've
854 				 * incremented it.
855 				 */
856 				data = data_end;
857 				break;
858 			}
859 			while (len > 0 && name < (namend - 2)) {
860 				*name = *data;
861 				name++;
862 				data++;
863 				len--;
864 			}
865 			*name = '.';
866 			name++;
867 		}
868 	}
869 	*name = '\0';
870 	return (data);
871 }
872 
873 static size_t
874 print_domain_name(char *line, const uchar_t *header, const uchar_t *data,
875     const uchar_t *data_end)
876 {
877 	char name[NS_MAXDNAME];
878 	const uchar_t *new_data;
879 
880 	new_data = get_domain_name(header, data, data_end, name,
881 	    name + sizeof (name));
882 
883 	(void) sprintf(line, "%s", name);
884 	return (new_data - data);
885 }
886