Home | History | Annotate | Download | only in snoop
      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