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