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 */
49static char *dns_opcode_string(uint_t opcode);
50static char *dns_rcode_string(uint_t rcode);
51static char *dns_type_string(uint_t type, int detail);
52static char *dns_class_string(uint_t cls, int detail);
53static size_t skip_question(const uchar_t *header, const uchar_t *data,
54    const uchar_t *data_end);
55static size_t print_question(char *line, const uchar_t *header,
56    const uchar_t *data, const uchar_t *data_end, int detail);
57static size_t print_answer(char *line, const uchar_t *header,
58    const uchar_t *data, const uchar_t *data_end, int detail);
59static char *binary_string(char data);
60static void print_ip(int af, char *line, const uchar_t *data, uint16_t len);
61static const uchar_t *get_char_string(const uchar_t *data, char *charbuf,
62    uint16_t datalen);
63static size_t print_char_string(char *line, const uchar_t *data, uint16_t len);
64static const uchar_t *get_domain_name(const uchar_t *header,
65    const uchar_t *data, const uchar_t *data_end, char *namebuf, char *namend);
66static size_t print_domain_name(char *line, const uchar_t *header,
67    const uchar_t *data, const uchar_t *data_end);
68
69void
70interpret_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
238static char *
239dns_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
253static char *
254dns_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
271static char *
272dns_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
304static char *
305dns_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
319static size_t
320skip_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
333static size_t
334print_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 */
449static size_t
450print_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
730static char *
731binary_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
745static void
746print_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 */
771static const uchar_t *
772get_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
794static size_t
795print_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 */
812static const uchar_t *
813get_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
905static size_t
906print_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