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