? rjk.diff
Index: Makefile.in
===================================================================
RCS file: /cvsroot/strace/strace/Makefile.in,v
retrieving revision 1.7
diff -u -r1.7 Makefile.in
--- Makefile.in	2001/02/16 20:14:40	1.7
+++ Makefile.in	2001/10/20 18:24:38
@@ -54,7 +54,7 @@
 OBJ		= strace.o version.o syscall.o util.o desc.o file.o ipc.o \
 			io.o ioctl.o mem.o net.o process.o bjm.o \
 			resource.o signal.o sock.o system.o term.o time.o \
-			proc.o stream.o
+			proc.o stream.o dns.o
 
 all: strace
 
@@ -108,3 +108,4 @@
 system.o: system.c defs.h config.h
 time.o: time.c defs.h config.h
 util.o: util.c defs.h config.h
+dns.o: dns.h defs.h config.h
Index: defs.h
===================================================================
RCS file: /cvsroot/strace/strace/defs.h,v
retrieving revision 1.26
diff -u -r1.26 defs.h
--- defs.h	2001/10/09 23:47:38	1.26
+++ defs.h	2001/10/20 18:24:39
@@ -297,6 +297,7 @@
 #define QUAL_FAULT	0040	/* report events with this fault */
 #define QUAL_READ	0100	/* dump data read on this file descriptor */
 #define QUAL_WRITE	0200	/* dump data written to this file descriptor */
+#define QUAL_DNS	0400	/* parse data read/written as UDP DNS packets */
 
 #define entering(tcp)	(!((tcp)->flags & TCB_INSYSCALL))
 #define exiting(tcp)	((tcp)->flags & TCB_INSYSCALL)
@@ -377,6 +378,7 @@
 extern int upeek P((int, long, long *));
 extern void dumpiov P((struct tcb *, int, long));
 extern void dumpstr P((struct tcb *, long, int));
+extern void dumpdns P((struct tcb *, long, int));
 extern void string_quote P((char *str));
 extern void printstr P((struct tcb *, long, int));
 extern void printnum P((struct tcb *, long, char *));
Index: strace.1
===================================================================
RCS file: /cvsroot/strace/strace/strace.1,v
retrieving revision 1.7
diff -u -r1.7 strace.1
--- strace.1	1999/12/23 14:20:15	1.7
+++ strace.1	2001/10/20 18:24:40
@@ -324,6 +324,7 @@
 .BR verbose ,
 .BR raw ,
 .BR signal ,
+.BR dns ,
 .BR read ,
 or
 .B write
@@ -416,6 +417,13 @@
 (or
 .BR signal=!io )
 causes SIGIO signals not to be traced.
+.TP
+.BI "\-e dns=" set
+Decode data read from or written to the file descriptors listed
+assuming that they are UDP DNS packets.  For example, if you know that
+the process you are tracing uses file descriptor 3 for DNS requests,
+use
+.BR "\-e dns=3" .
 .TP
 .BI "\-e read=" set
 Perform a full hexadecimal and ASCII dump of all the data read from
Index: strace.c
===================================================================
RCS file: /cvsroot/strace/strace/strace.c,v
retrieving revision 1.24
diff -u -r1.24 strace.c
--- strace.c	2001/10/09 23:47:38	1.24
+++ strace.c	2001/10/20 18:24:43
@@ -146,7 +146,7 @@
 -x -- print non-ascii strings in hex, -xx -- print all strings in hex\n\
 -a column -- alignment COLUMN for printing syscall results (default %d)\n\
 -e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
-   options: trace, abbrev, verbose, raw, signal, read, or write\n\
+   options: trace, abbrev, verbose, raw, signal, dns, read, or write\n\
 -o file -- send trace output to FILE instead of stderr\n\
 -O overhead -- set overhead for tracing syscalls to OVERHEAD usecs\n\
 -p pid -- trace process with process id PID, may be repeated\n\
Index: syscall.c
===================================================================
RCS file: /cvsroot/strace/strace/syscall.c,v
retrieving revision 1.33
diff -u -r1.33 syscall.c
--- syscall.c	2001/10/09 23:47:38	1.33
+++ syscall.c	2001/10/20 18:24:46
@@ -250,6 +250,7 @@
 	{ QUAL_WRITE,	"write",	lookup_desc,	"descriptor"	},
 	{ QUAL_WRITE,	"writes",	lookup_desc,	"descriptor"	},
 	{ QUAL_WRITE,	"w",		lookup_desc,	"descriptor"	},
+	{ QUAL_DNS,	"dns",		lookup_desc,	"descriptor"	},
 	{ 0,		NULL,		NULL,		NULL		},
 };
 
@@ -406,6 +407,8 @@
 #endif
 		if (qual_flags[tcp->u_arg[0]] & QUAL_READ)
 			dumpstr(tcp, tcp->u_arg[1], tcp->u_rval);
+		if (qual_flags[tcp->u_arg[0]] & QUAL_DNS)
+			dumpdns(tcp,tcp->u_arg[1], tcp->u_rval);
 		break;
 	case SYS_write:
 #ifdef SYS_send
@@ -416,6 +419,8 @@
 #endif
 		if (qual_flags[tcp->u_arg[0]] & QUAL_WRITE)
 			dumpstr(tcp, tcp->u_arg[1], tcp->u_arg[2]);
+		if (qual_flags[tcp->u_arg[0]] & QUAL_DNS)
+			dumpdns(tcp,tcp->u_arg[1], tcp->u_rval);
 		break;
 #ifdef SYS_readv
         case SYS_readv:
Index: util.c
===================================================================
RCS file: /cvsroot/strace/strace/util.c,v
retrieving revision 1.27
diff -u -r1.27 util.c
--- util.c	2001/10/16 10:20:22	1.27
+++ util.c	2001/10/20 18:24:48
@@ -34,6 +34,8 @@
  */
 
 #include "defs.h"
+#include <arpa/inet.h>
+#include "dns.h"
 
 #include <sys/user.h>
 #include <sys/param.h>
@@ -553,6 +555,38 @@
 				*s++ = ' ';
 		}
 		tprintf("%s |\n", outstr);
+	}
+}
+
+void
+dumpdns(tcp, addr, len)
+struct tcb *tcp;
+long addr;
+int len;
+{
+	static int strsize = -1;
+	static unsigned char *str;
+	struct dns_packet *p;
+	enum dns_error e;
+	extern FILE *outf;
+
+	if (strsize < len) {
+		if (str)
+			free(str);
+		if ((str = malloc(len)) == NULL) {
+			fprintf(stderr, "dumpdns: no memory\n");
+			return;
+		}
+		strsize = len;
+	}
+
+	if (umoven(tcp, addr, len, (char *) str) < 0)
+		return;
+	if(!(e = dns_unpick(&p, str, len))) {
+		fprintf(outf, ";;; DNS packet decode -------------------------------------------------------\n");
+		dns_print(outf, p);
+		fprintf(outf, ";;; End of DNS packet decode ------------------------------------------------\n");
+		dns_free(p);
 	}
 }
 
--- /dev/null	Tue Jul  3 18:30:45 2001
+++ dns.h	Sat Oct 20 18:45:58 2001
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2001 Richard Kettlewell <rjk@terraraq.org.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $Header: /cvs/dns/dns.h,v 1.7 2001/10/20 17:45:58 richard Exp $
+ */
+
+/* These types and functions provide for decoding DNS packets into a
+ * more managable form.
+ *
+ * It is assumed that the local character set is (a superset of)
+ * ASCII, i.e., no attempt is made to translate the ASCII found in the
+ * DNS packets into the local character set.  Sorry, EBCDIC users.
+ *
+ * You need to include some other header files before this one, in
+ * particular you must have declarations for size_t and FILE.
+ */
+
+/* grotty portability stuff */
+#ifdef __cplusplus
+/* C++ needs some magic of its own, but always supports const and
+ * function prototypes. */
+# define DNS_BEGIN_DECLS extern "C" {
+# define DNS_END_DECLS };
+# define DNS_PROTO(args) args
+# define DNS_USE_PROTOS 1
+#else
+# define DNS_BEGIN_DECLS
+# define DNS_END_DECLS
+# ifdef __STDC__
+/* Standard C => __STDC__ defined, it might still be defined for
+ * non-Standard C BUT we assume that implementors only do this if they
+ * have at least a reasonable approximation */
+#  define DNS_PROTO(args) args
+#  define DNS_USE_PROTOS 1
+# else
+/* if even __STDC__ isn't defined then we assume that prototypes and
+ * const might not be available.  Perhaps we shouldn't rely on enums
+ * either... */
+#  define DNS_PROTO(args) args
+#  define const
+#  define DNS_USE_PROTOS 0
+# endif
+#endif
+
+DNS_BEGIN_DECLS
+
+/* record types */
+enum dns_type {
+	dnst_a = 1,
+	dnst_ns = 2,
+	dnst_md = 3,
+	dnst_mf = 4,
+	dnst_cname = 5,
+	dnst_soa = 6,
+	dnst_mb = 7,
+	dnst_mg = 8,
+	dnst_mr = 9,
+	dnst_null = 10,
+	dnst_wks = 11,
+	dnst_ptr = 12,
+	dnst_hinfo = 13,
+	dnst_minfo = 14,
+	dnst_mx = 15,
+	dnst_txt = 16,
+	dnst_axfr = 252,
+	dnst_maila = 253,
+	dnst_mailb = 254,
+	dnst_any = 255,
+};
+
+/* record classes */
+enum dns_class {
+	dnsc_in = 1,
+	dnsc_cs = 2,
+	dnsc_ch = 3,
+	dnsc_hs = 4,
+};
+
+/* structure representing an unpacked DNS packet */
+struct dns_packet {
+	unsigned dns_id;	/* ID field */
+	int dns_qr;		/* 0 for a query, 1 for a response */
+	int dns_opcode;		/* */
+	int dns_aa;		/* authoritative answer (in responses) */
+	int dns_tc;		/* truncated */
+	int dns_rd;		/* recursion desired */
+	int dns_ra;		/* recursion available (in responses) */
+	int dns_z;		/* (supposed to be 0) */
+	int dns_rcode;		/* response code */
+	unsigned dns_qdcount;	/* number of entries in question section */
+	struct dns_record *dns_qdrecords; /* ...and the entries */
+	unsigned dns_ancount;	/* number of entries in answer section */
+	struct dns_record *dns_anrecords; /* ...and the entries */
+	unsigned dns_nscount;	/* number of entries in authority section */
+	struct dns_record *dns_nsrecords; /* ...and the entries */
+	unsigned dns_arcount;	/* number of entries in additional section */
+	struct dns_record *dns_arrecords; /* ...and the entries */
+	size_t dns_extra;	/* set if there is extra rubbish on
+				 * the end of the packet */
+};
+
+/* structure representing a single record */
+struct dns_record {
+	struct dns_record *dnsr_next; /* next record */
+	char *dnsr_name;	/* name */
+	enum dns_type dnsr_type; /* type */
+	enum dns_class dnsr_class; /* class */
+	unsigned long dnsr_ttl;	/* TTL in seconds */
+	unsigned dnsr_rdlength;	/* length of rdata section */
+	const char *dnsr_rdata;	/* raw rdata */
+	union {
+		/* only the well-known types are implemented, please
+		 * do contribute more */
+		struct in_addr dnsd_a;
+		char *dnsd_cname;
+#define dnsd_ns dnsd_cname
+#define dnsd_ptr dnsd_cname
+#define dnsd_mb dnsd_cname
+#define dnsd_md dnsd_cname
+#define dnsd_mf dnsd_cname
+#define dnsd_mr dnsd_cname
+		char *dnsd_txt;
+		struct {
+			char *dnsm_rmailbx;
+			char *dnsm_emailbx;
+		} dnsd_minfo;
+		struct {
+			char *dnsh_cpu;
+			char *dnsh_os;
+		} dnsd_hinfo;
+		struct {
+			unsigned dnsm_pref;
+			char *dnsm_exchange;
+		} dnsd_mx;
+		struct {
+			char *dnss_mname;
+			char *dnss_rname;
+			unsigned long dnss_serial;
+			unsigned long dnss_refresh;
+			unsigned long dnss_retry;
+			unsigned long dnss_expire;
+			unsigned long dnss_minimum;
+		} dnsd_soa;
+		struct {
+			char *dnsn_text;
+			size_t dnsn_len;
+		} dnsd_null;
+	} dnsr_u;
+};
+
+/* possible errors unpicking a packet */
+enum dns_error {
+	dns_ok,			/* worked */
+	dns_trunc,		/* truncated */
+	dns_malloc,		/* malloc failed */
+	dns_loop,		/* compression loop */
+	dns_nametoolong,	/* name > 255 octets */
+	dns_badptr,		/* bad pointer */
+	dns_badrdata,		/* rdata doesn't match rdlength */
+};
+
+/* unpick the DNS packet occupting NBYTES octets starting at P.  On
+ * success return dns_ok and assign *DP.  On error return an error
+ * code and do not modify *DP. */
+enum dns_error dns_unpick DNS_PROTO((struct dns_packet **dp,
+				     const unsigned char *p, size_t nbytes));
+
+/* free the memory used by decoded DNS packet at D */
+void dns_free DNS_PROTO((struct dns_packet *d));
+
+/* output a human-readable description of the decoded DNS packet at P
+ * to stream FP.  Return 0 on success and nonzero on error. */
+int dns_print DNS_PROTO((FILE *fp, const struct dns_packet *p));
+
+/* return a human-readable version of error code E.  If the error code
+ * is not one of the known ones then a numeric version will be written
+ * into BUFFER, not modifying more than BUFSIZE bytes. */
+const char *dns_strerror DNS_PROTO((enum dns_error e,
+				    char buffer[], size_t bufsize));
+
+DNS_END_DECLS
+
+/*
+Local Variables:
+mode:c
+c-basic-offset:8
+End:
+*/
--- /dev/null	Tue Jul  3 18:30:45 2001
+++ dns.c	Sat Oct 20 18:48:08 2001
@@ -0,0 +1,940 @@
+/*
+ * Copyright (c) 2001 Richard Kettlewell <rjk@terraraq.org.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $Header: /cvs/dns/dns.c,v 1.10 2001/10/20 17:46:08 richard Exp $
+ */
+
+/* DNS packet unpicking.  See RFC1035 for details of DNS packet
+ * format.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include "dns.h"
+
+/* width of name column in output */
+#define NAMEFIELD 36
+
+/* maximum number of pointer to follow, before deciding the packet is
+ * bogus */
+#define MAXPTRFOLLOW 64
+
+/* entry in a name/value lookup table.  name=0 terminates. */
+struct lookup {
+	int value;
+	const char *name;
+};
+
+/* forward declarations of internal functions */
+static void dns_free_records DNS_PROTO((struct dns_record *r));
+static unsigned long dns_get32 DNS_PROTO((const unsigned char *p));
+static unsigned dns_get16 DNS_PROTO((const unsigned char *p));
+static enum dns_error dns_name DNS_PROTO((char **namep,
+					  const unsigned char *p,
+					  size_t nbytes,
+					  size_t *offsetp));
+static enum dns_error dns_string DNS_PROTO((char **stringp,
+					    const unsigned char *p,
+					    size_t nbytes,
+					    size_t *offsetp));
+static enum dns_error dns_question DNS_PROTO((struct dns_record **rp,
+				   const unsigned char *p, size_t nbytes,
+				   size_t *offsetp));
+static enum dns_error dns_answer DNS_PROTO((struct dns_record **rp,
+					    const unsigned char *p,
+					    size_t nbytes,
+					    size_t *offsetp));
+static void dns_free_records DNS_PROTO((struct dns_record *r));
+static const char *dns_lookupname DNS_PROTO((const struct lookup *l, int n,
+					     char buffer[], size_t bufsize));
+static const char *dns_opcodename DNS_PROTO((int n,
+				  char buffer[], size_t bufsize));
+static const char *dns_rcodename DNS_PROTO((int n,
+				 char buffer[], size_t bufsize));
+static const char *dns_classname DNS_PROTO((int n,
+					    char buffer[], size_t bufsize));
+static const char *dns_typename DNS_PROTO((int n,
+					   char buffer[], size_t bufsize));
+static int dns_padding DNS_PROTO((const char *s));
+static int dns_printqs DNS_PROTO((FILE *fp, const struct dns_record *r));
+static int dns_printstring DNS_PROTO((FILE *fp, const char *p));
+static int dns_printas DNS_PROTO((FILE *fp, const struct dns_record *r));
+
+/* retrieve the 32-bit big-endian value at P */
+static unsigned long
+#if DNS_USE_PROTOS
+dns_get32(const unsigned char *p)
+#else
+dns_get32(p)
+const unsigned char *p;
+#endif
+{
+	return p[0] * 0x1000000 + p[1] * 0x10000 + p[2] * 0x100 + p[3];
+}
+
+/* retrieve the 16-bit big-endian value at P */
+static unsigned
+#if DNS_USE_PROTOS
+dns_get16(const unsigned char *p)
+#else
+dns_get16(p)
+const unsigned char *p;
+#endif
+{
+	return p[0] * 0x100 + p[1];
+}
+
+/* extract a DNS name.  See RFC1035 4.1.4 for details of the
+ * compression scheme the DNS uses. */
+static enum dns_error
+#if DNS_USE_PROTOS
+dns_name(char **namep,
+	 const unsigned char *p, size_t nbytes,
+	 size_t *offsetp)
+#else
+dns_name(namep, p, nbytes, offsetp)
+char **namep;
+const unsigned char *p;
+size_t nbytes;
+size_t *offsetp;
+#endif
+{
+	size_t offset = *offsetp;
+
+	/* names may be no longer than 255 octets.  If every character
+	 * needs quoting in the \DDD form then this gives 255*4=1020.
+	 * 1024 is a bit rounder l-)
+	 *
+	 * Actually it can never be quite that high, due to the need
+	 * for an unquoted "." every 64 characters.  But still. */
+	char name[1024];
+	int n = 0;
+	int length;
+	int ptr;
+	int ptrfollow = 0;
+
+	if(offset >= nbytes)
+		return dns_trunc;
+	while(p[offset]) {
+		switch(p[offset] & 0xC0) {
+		case 0x00:
+			/* plain label */
+			length = p[offset++];
+			/* reject if we are truncated in the middle of
+			 * the name */
+			if(offset + length > nbytes)
+				return dns_trunc;
+			/* add a dot if this is not the first label */
+			if(n != 0) {
+				/* reject if the dot would make the
+				 * name too long */
+				if(n >= (sizeof name) - 1)
+					return dns_nametoolong;
+				name[n++] = '.';
+			}
+			/* reject if this label would make the name
+			 * too long */
+			while(length > 0) {
+				int c = p[offset++];
+				char quoted[16];
+				size_t l;
+
+				if(isgraph(c)) {
+					if(c != '@'
+					   && c != '.'
+					   && c != '\\'
+					   && c != '('
+					   && c != ')'
+					   && c != ';')
+						sprintf(quoted, "%c", c);
+					else
+						sprintf(quoted, "\\%c", c);
+				} else {
+					sprintf(quoted, "\\%03o", (unsigned)c);
+				}
+				l = strlen(quoted);
+				if(n + l > (sizeof name) - 1)
+					return dns_nametoolong;
+				strcpy(name + n, quoted);
+				n += l;
+				length--;
+			}
+			break;
+		case 0xC0:
+			/* pointer */
+			if(offset + 2 > nbytes)
+				return dns_trunc;
+			if(ptrfollow == 0)
+				*offsetp = offset + 2;
+			++ptrfollow;
+			/* don't loop forever on a bogus packet */
+			if(ptrfollow > MAXPTRFOLLOW)
+				return dns_loop;
+			ptr = dns_get16(p + offset) & 0x3FFF;
+			/* reject pointers into the header or beyond
+			 * the end of the packet */
+			if(ptr < 12 || ptr >= nbytes)
+				return dns_badptr;
+			offset = ptr;
+			break;
+		default:
+			/* reserved for future usage */
+			return 0;
+		}
+	}
+	if(ptrfollow == 0)
+		*offsetp = offset + 1; /* account for terminating 0 */
+	name[n] = 0;
+	if(!(*namep = malloc(strlen(name) + 1)))
+		return dns_malloc;
+	strcpy(*namep, name);
+	return dns_ok;
+}
+
+/* extract a character string. */
+static enum dns_error
+#if DNS_USE_PROTOS
+dns_string(char **stringp,
+	   const unsigned char *p, size_t nbytes,
+	   size_t *offsetp)
+#else
+dns_string(stringp, p, nbytes, offsetp)
+char **stringp;
+const unsigned char *p;
+size_t nbytes;
+size_t *offsetp;
+#endif
+{
+	int length;
+
+	if(*offsetp >= nbytes)
+		return dns_trunc;
+	length = p[(*offsetp)++];
+	if(*offsetp + length > nbytes)
+		return dns_trunc;
+	if(!(*stringp = malloc(length + 1)))
+		return dns_malloc;
+	memcpy(*stringp, p + *offsetp, length);
+	(*stringp)[length] = 0;
+	*offsetp += length;
+	return dns_ok;
+}
+
+/* extract a question-section record */
+static enum dns_error
+#if DNS_USE_PROTOS
+dns_question(struct dns_record **rp,
+	     const unsigned char *p, size_t nbytes,
+	     size_t *offsetp)
+#else
+dns_question(rp, p, nbytes, offsetp)
+struct dns_record **rp;
+const unsigned char *p;
+size_t nbytes;
+size_t *offsetp;
+#endif
+{
+	struct dns_record *r;
+	char *name;
+	enum dns_error e;
+
+	/* get the name */
+	if((e = dns_name(&name, p, nbytes, offsetp)))
+		return e;
+	/* make sure we're not truncated here */
+	if(*offsetp + 4 > nbytes) {
+		free(name);
+		return dns_trunc;
+	}
+	if(!(r = malloc(sizeof *r))) {
+		free(name);
+		return dns_malloc;
+	}
+	memset(r, 0, sizeof *r);
+	r->dnsr_name = name;
+	r->dnsr_type = dns_get16(p + *offsetp);
+	r->dnsr_class = dns_get16(p + *offsetp + 2);
+	*offsetp += 4;
+	*rp = r;
+	return dns_ok;
+}
+
+/* extract an answer-section record */
+static enum dns_error
+#if DNS_USE_PROTOS
+dns_answer(struct dns_record **rp,
+	   const unsigned char *p, size_t nbytes,
+	   size_t *offsetp)
+#else
+dns_answer(rp, p, nbytes, offsetp)
+struct dns_record **rp;
+const unsigned char *p;
+size_t nbytes;
+size_t *offsetp;
+#endif
+{
+	struct dns_record *r;
+	enum dns_error e;
+	size_t expected_offset;
+
+	/* we start out just like a question, so re-use the code */
+	if((e = dns_question(&r, p, nbytes, offsetp)))
+		return e;
+	if(*offsetp + 6 > nbytes)
+		goto truncated;
+	r->dnsr_ttl = dns_get32(p + *offsetp);
+	r->dnsr_rdlength = dns_get16(p + *offsetp + 4);
+	*offsetp += 6;
+	if(*offsetp + r->dnsr_rdlength > nbytes)
+		goto truncated;
+	r->dnsr_rdata = p + *offsetp;
+	expected_offset = *offsetp + r->dnsr_rdlength;
+	/* type-specific decoding */
+	switch(r->dnsr_type) {
+	case dnst_a:
+		if(*offsetp + 4 > nbytes)
+			goto truncated;
+		memcpy(&r->dnsr_u.dnsd_a, r->dnsr_rdata, 4);
+		*offsetp += 4;
+		break;
+	case dnst_ns:
+	case dnst_cname:
+	case dnst_ptr:
+	case dnst_mb:
+	case dnst_md:
+	case dnst_mf:
+	case dnst_mg:
+	case dnst_mr:
+		if((e = dns_name(&r->dnsr_u.dnsd_cname,
+				 p, nbytes, offsetp)))
+			goto error;
+		break;
+	case dnst_txt:
+		if((e = dns_string(&r->dnsr_u.dnsd_txt,
+				   p, nbytes, offsetp)))
+			goto error;
+		break;
+	case dnst_hinfo:
+		if((e = dns_string(&r->dnsr_u.dnsd_hinfo.dnsh_cpu,
+				   p, nbytes, offsetp)))
+			goto error;
+		if((e = dns_string(&r->dnsr_u.dnsd_hinfo.dnsh_os,
+				   p, nbytes, offsetp)))
+			goto error;
+		break;
+	case dnst_mx:
+		if(*offsetp + 2 > nbytes)
+			goto truncated;
+		r->dnsr_u.dnsd_mx.dnsm_pref = dns_get16(p + *offsetp);
+		*offsetp += 2;
+		if((e = dns_name(&r->dnsr_u.dnsd_mx.dnsm_exchange,
+				 p, nbytes, offsetp)))
+			goto error;
+		break;
+	case dnst_soa:
+		if((e = dns_name(&r->dnsr_u.dnsd_soa.dnss_mname,
+				 p, nbytes, offsetp)))
+			goto error;
+		if((e = dns_name(&r->dnsr_u.dnsd_soa.dnss_rname,
+				 p, nbytes, offsetp)))
+			goto error;
+		if(*offsetp + 20 > nbytes)
+			goto truncated;
+		r->dnsr_u.dnsd_soa.dnss_serial = dns_get32(p + *offsetp);
+		r->dnsr_u.dnsd_soa.dnss_refresh = dns_get32(p + *offsetp + 4);
+		r->dnsr_u.dnsd_soa.dnss_retry = dns_get32(p + *offsetp + 8);
+		r->dnsr_u.dnsd_soa.dnss_expire = dns_get32(p + *offsetp + 12);
+		r->dnsr_u.dnsd_soa.dnss_minimum = dns_get32(p + *offsetp + 16);
+		*offsetp += 20;
+		break;
+	case dnst_minfo:
+		if((e = dns_name(&r->dnsr_u.dnsd_minfo.dnsm_rmailbx,
+				 p, nbytes, offsetp)))
+			goto error;
+		if((e = dns_name(&r->dnsr_u.dnsd_minfo.dnsm_emailbx,
+				 p, nbytes, offsetp)))
+			goto error;
+		break;
+	case dnst_null:
+		if(!(r->dnsr_u.dnsd_null.dnsn_text = malloc(r->dnsr_rdlength))) {
+			e = dns_malloc;
+			goto error;
+		}
+		r->dnsr_u.dnsd_null.dnsn_len = r->dnsr_rdlength;
+		memcpy(r->dnsr_u.dnsd_null.dnsn_text, p, r->dnsr_rdlength);
+		*offsetp += r->dnsr_rdlength;
+		break;
+	default:
+		/* not handled */
+		break;
+	}
+	if(*offsetp != expected_offset) {
+		e = dns_badrdata;
+		goto error;
+	}
+	*rp = r;
+	return dns_ok;
+truncated:
+	e = dns_trunc;
+error:
+	dns_free_records(r);
+	return e;
+}
+
+enum dns_error
+#if DNS_USE_PROTOS
+dns_unpick(struct dns_packet **dp,
+	   const unsigned char *p, size_t nbytes)
+#else
+dns_unpick(dp, p, nbytes)
+struct dns_packet **dp;
+const unsigned char *p;
+size_t nbytes;
+#endif
+{
+	struct dns_packet *d;
+	size_t offset;
+	int n;
+	struct dns_record **rr;
+	enum dns_error e;
+
+	/* don't even try to process ridiculously short packets */
+	if(nbytes < 12) {
+		*dp = 0;
+		return dns_trunc;
+	}
+	if(!(d = malloc(sizeof *d)))
+		return dns_malloc;
+	memset(d, 0, sizeof *d);
+	/* decode the header */
+	d->dns_id = dns_get16(p + 0);
+	d->dns_qr = (p[2] & 0x80) ? 1 : 0;
+	d->dns_opcode = (p[2] >> 3) & 0x0f;
+	d->dns_aa = (p[2] & 0x04) ? 1 : 0;
+	d->dns_tc = (p[2] & 0x02) ? 1 : 0;
+	d->dns_rd = (p[2] & 0x01) ? 1 : 0;
+	d->dns_ra = (p[3] & 0x80) ? 1 : 0;
+	d->dns_z = (p[3] >> 4) & 0x07;
+	d->dns_rcode = p[3] & 0x0F;
+	d->dns_qdcount = dns_get16(p + 4);
+	d->dns_ancount = dns_get16(p + 6);
+	d->dns_nscount = dns_get16(p + 8);
+	d->dns_arcount = dns_get16(p + 10);
+	d->dns_extra = 0;
+
+	/* decode the question section */
+	offset = 12;
+	rr = &d->dns_qdrecords;
+	for(n = 0; n < d->dns_qdcount; ++n) {
+		if((e = dns_question(rr, p, nbytes, &offset)))
+			goto error;
+		rr = &(*rr)->dnsr_next;
+	}
+
+	/* decode the other three sections */
+	rr = &d->dns_anrecords;
+	for(n = 0; n < d->dns_ancount; ++n) {
+		if((e = dns_answer(rr, p, nbytes, &offset)))
+			goto error;
+		rr = &(*rr)->dnsr_next;
+	}
+	rr = &d->dns_nsrecords;
+	for(n = 0; n < d->dns_nscount; ++n) {
+		if((e = dns_answer(rr, p, nbytes, &offset)))
+			goto error;
+		rr = &(*rr)->dnsr_next;
+	}
+	rr = &d->dns_arrecords;
+	for(n = 0; n < d->dns_arcount; ++n) {
+		if((e = dns_answer(rr, p, nbytes, &offset)))
+			goto error;
+		rr = &(*rr)->dnsr_next;
+	}
+	d->dns_extra = nbytes - offset;
+	*dp = d;
+	return dns_ok;
+error:
+	if(e == dns_trunc)
+		*dp = d;
+	else {
+		*dp = 0;
+		dns_free(d);
+	}
+	return e;
+}
+
+void
+#if DNS_USE_PROTOS
+dns_free(struct dns_packet *d)
+#else
+dns_free(d)
+struct dns_packet *d;
+#endif
+{
+	if(d) {
+		dns_free_records(d->dns_qdrecords);
+		dns_free_records(d->dns_anrecords);
+		dns_free_records(d->dns_nsrecords);
+		dns_free_records(d->dns_arrecords);
+		free(d);
+	}
+}
+
+/* free DNS records starting at R, and following the list */
+static void
+#if DNS_USE_PROTOS
+dns_free_records(struct dns_record *r)
+#else
+dns_free_records(r)
+struct dns_record *r;
+#endif
+{
+	if(r) {
+		dns_free_records(r->dnsr_next);
+		switch(r->dnsr_type) {
+		case dnst_cname:
+		case dnst_ns:
+		case dnst_ptr:
+		case dnst_mb:
+		case dnst_md:
+		case dnst_mf:
+		case dnst_mg:
+		case dnst_mr:
+			free(r->dnsr_u.dnsd_cname);
+			break;
+		case dnst_txt:
+			free(r->dnsr_u.dnsd_txt);
+			break;
+		case dnst_hinfo:
+			free(r->dnsr_u.dnsd_hinfo.dnsh_cpu);
+			free(r->dnsr_u.dnsd_hinfo.dnsh_os);
+			break;
+		case dnst_mx:
+			free(r->dnsr_u.dnsd_mx.dnsm_exchange);
+			break;
+		case dnst_soa:
+			free(r->dnsr_u.dnsd_soa.dnss_mname);
+			free(r->dnsr_u.dnsd_soa.dnss_rname);
+			break;
+		case dnst_minfo:
+			free(r->dnsr_u.dnsd_minfo.dnsm_rmailbx);
+			free(r->dnsr_u.dnsd_minfo.dnsm_emailbx);
+			break;
+		case dnst_null:
+			free(r->dnsr_u.dnsd_null.dnsn_text);
+			break;
+		default:
+			break;
+		}
+		free(r);
+	}
+}
+
+/* look up value N in name/value lookup table L and return the
+ * corresponding name.  If there is none, write the number into BUFFER
+ * (size BUFSIZE) and return that. */
+static const char *
+#if DNS_USE_PROTOS
+dns_lookupname(const struct lookup *l, int n,
+	       char buffer[], size_t bufsize)
+#else
+dns_lookupname(l, n, buffer, bufisze)
+const struct lookup *l;
+int n;
+char buffer[];
+size_t bufsize;
+#endif
+{
+	while(l->name != 0 && l->value != n)
+		++l;
+	if(l->name)
+		return l->name;
+	snprintf(buffer, bufsize, "[%d]", n);
+	return buffer;
+}
+
+/* return the name of opcode N.  Use BUFFER/BUFSIZE if there is no
+ * name for it. */
+static const char *
+#if DNS_USE_PROTOS
+dns_opcodename(int n,
+	       char buffer[], size_t bufsize)
+#else
+dns_opcodename(n, buffer, bufsize)
+int n;
+char buffer[];
+size_t bufsize;
+#endif
+{
+	static const struct lookup opcodes[] = {
+		{ 0, "QUERY" },
+		{ 1, "IQUERY" },
+		{ 2, "STATUS" },
+		{ -1, 0 },
+	};
+	return dns_lookupname(opcodes, n, buffer, bufsize);
+}
+
+/* return the name of response code N.  Use BUFFER/BUFSIZE if there is
+ * no name for it. */
+static const char *
+#if DNS_USE_PROTOS
+dns_rcodename(int n,
+	      char buffer[], size_t bufsize)
+#else
+dns_rcodename(n, buffer, bufsize)
+int n;
+char buffer[];
+size_t bufsize;
+#endif
+{
+	static const struct lookup rcodes[] = {
+		{ 0, "no error" },
+		{ 1, "format error" },
+		{ 2, "server failure" },
+		{ 3, "name error" },
+		{ 4, "not implemented" },
+		{ 5, "refused" },
+		{ -1, 0 },
+	};
+	return dns_lookupname(rcodes, n, buffer, bufsize);
+}
+
+/* return the name of class N.  Use BUFFER/BUFSIZE if there is no name
+ * for it. */
+static const char *
+#if DNS_USE_PROTOS
+dns_classname(int n,
+	      char buffer[], size_t bufsize)
+#else
+dns_classname(n, buffer, bufsize)
+int n;
+char buffer[];
+size_t bufsize;
+#endif
+{
+	static const struct lookup classes[] = {
+		{ dnsc_in, "IN" },
+		{ dnsc_cs, "CS" },
+		{ dnsc_ch, "CH" },
+		{ dnsc_hs, "HS" },
+		{ -1, 0 },
+	};
+	return dns_lookupname(classes, n, buffer, bufsize);
+}
+
+/* return the name of type N.  Use BUFFER/BUFSIZE if there is no name
+ * for it. */
+static const char *
+#if DNS_USE_PROTOS
+dns_typename(int n,
+	     char buffer[], size_t bufsize)
+#else
+dns_typename(n, buffer, bufsize)
+int n;
+char buffer[];
+size_t bufsize;
+#endif
+{
+	static const struct lookup types[] = {
+		{ dnst_a, "A" },
+		{ dnst_ns, "NS" },
+		{ dnst_md, "MD" },
+		{ dnst_mf, "MF" },
+		{ dnst_cname, "CNAME" },
+		{ dnst_soa, "SOA" },
+		{ dnst_mb, "MB" },
+		{ dnst_mg, "MG" },
+		{ dnst_mr, "MR" },
+		{ dnst_null, "NULL" },
+		{ dnst_wks, "WKS" },
+		{ dnst_ptr, "PTR" },
+		{ dnst_hinfo, "HINFO" },
+		{ dnst_minfo, "MINFO" },
+		{ dnst_mx, "MX" },
+		{ dnst_txt, "TXT" },
+		{ dnst_axfr, "AXFR" },
+		{ dnst_maila, "MAILA" },
+		{ dnst_mailb, "MAILB" },
+		{ dnst_any, "ANY" },
+		{ -1, 0 },
+	};
+	return dns_lookupname(types, n, buffer, bufsize);
+}
+
+/* return the number of spaces of padding to be used for name S. */
+static int
+#if DNS_USE_PROTOS
+dns_padding(const char *s)
+#else
+dns_padding(s)
+const char *s;
+#endif
+{
+	size_t l = strlen(s);
+	if(l >= NAMEFIELD - 1)
+		return 1;
+	else
+		return (NAMEFIELD - 1) - l;
+}
+
+/* display the questions starting at R and following the list.  Return
+ * 0 on success or non-0 on error. */
+static int
+#if DNS_USE_PROTOS
+dns_printqs(FILE *fp, const struct dns_record *r)
+#else
+dns_printqs(fp, r)
+FILE *fp;
+const struct dns_record *r;
+#endif
+{
+	for(; r; r = r->dnsr_next) {
+		char cbuf[64], tbuf[64];
+
+		if(fprintf(fp, "%s.%*s%6s %-2s %-6s\n",
+			   r->dnsr_name,
+			   dns_padding(r->dnsr_name), "",
+			   "",
+			   dns_classname(r->dnsr_class, cbuf, sizeof cbuf),
+			   dns_typename(r->dnsr_type, tbuf, sizeof tbuf)) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+/* display a string e.g. for a TXT record.  Return 0 on success or
+ * non-0 on error. */
+static int
+#if DNS_USE_PROTOS
+dns_printstring(FILE *fp, const char *p)
+#else
+dns_printstring(fp, p)
+FILE *fp;
+const char *p;
+#endif
+{
+	if(fputc('"', fp) < 0)
+		return -1;
+	for(; *p; ++p) {
+		int c = (unsigned char)*p;
+		if(isprint(c)) {
+			if(c == '\\' || c == '"') {
+				if(fputc('\\', fp) < 0)
+					return -1;
+			}
+			if(fputc(c, fp) < 0)
+				return -1;
+		} else {
+			if(fprintf(fp, "\\%03o", (unsigned)c) < 0)
+				return -1;
+		}
+	}
+	if(fputc('"', fp) < 0)
+		return -1;
+	return 0;
+}
+
+/* display the answers starting at R and following the list.  Return 0
+ * on success or non-0 on error. */
+static int
+#if DNS_USE_PROTOS
+dns_printas(FILE *fp, const struct dns_record *r)
+#else
+dns_printas(fp, r)
+FILE *fp;
+const struct dns_record *r;
+#endif
+{
+	for(; r; r = r->dnsr_next) {
+		char cbuf[64], tbuf[64];
+
+		if(fprintf(fp, "%s.%*s%6lu %-2s %-6s ",
+			   r->dnsr_name,
+			   dns_padding(r->dnsr_name), "",
+			   r->dnsr_ttl,
+			   dns_classname(r->dnsr_class, cbuf, sizeof cbuf),
+			   dns_typename(r->dnsr_type, tbuf, sizeof tbuf)) < 0)
+			return -1;
+		switch(r->dnsr_type) {
+		case dnst_a:
+			if(fprintf(fp, "%s",
+				   inet_ntoa(r->dnsr_u.dnsd_a)) < 0)
+				return -1;
+			break;
+		case dnst_cname:
+		case dnst_ns:
+		case dnst_ptr:
+		case dnst_mb:
+		case dnst_md:
+		case dnst_mf:
+		case dnst_mg:
+		case dnst_mr:
+			if(fprintf(fp, "%s.", r->dnsr_u.dnsd_cname) < 0)
+				return -1;
+			break;
+		case dnst_txt:
+			if(dns_printstring(fp, r->dnsr_u.dnsd_txt))
+				return -1;
+			break;
+		case dnst_hinfo:
+			if(dns_printstring(fp, r->dnsr_u.dnsd_hinfo.dnsh_cpu)
+			   || fputc(' ', fp) < 0
+			   || dns_printstring(fp, r->dnsr_u.dnsd_hinfo.dnsh_os))
+				return -1;
+			break;
+		case dnst_mx:
+			if(fprintf(fp, "%u %s.",
+				   r->dnsr_u.dnsd_mx.dnsm_pref,
+				   r->dnsr_u.dnsd_mx.dnsm_exchange) < 0)
+				return -1;
+			break;
+		case dnst_soa:
+			if(fprintf(fp, "%s. %s. ( %lu %lu %lu %lu %lu )",
+				   r->dnsr_u.dnsd_soa.dnss_mname,
+				   r->dnsr_u.dnsd_soa.dnss_rname,
+				   r->dnsr_u.dnsd_soa.dnss_serial,
+				   r->dnsr_u.dnsd_soa.dnss_refresh,
+				   r->dnsr_u.dnsd_soa.dnss_retry,
+				   r->dnsr_u.dnsd_soa.dnss_expire,
+				   r->dnsr_u.dnsd_soa.dnss_minimum) < 0)
+				return -1;
+			break;
+		case dnst_minfo:
+			if(fprintf(fp, "%s. %s.",
+				   r->dnsr_u.dnsd_minfo.dnsm_rmailbx,
+				   r->dnsr_u.dnsd_minfo.dnsm_emailbx) < 0)
+				return -1;
+			break;
+		default:
+			if(fprintf(fp, "; ... %u bytes rdata",
+				   r->dnsr_rdlength) < 0)
+				return -1;
+			break;
+		}
+		if(fprintf(fp, "\n") < 0)
+			return -1;
+	}
+	return 0;
+}
+
+int
+#if DNS_USE_PROTOS
+dns_print(FILE *fp, const struct dns_packet *p)
+#else
+dns_print(fp, p)
+FILE *fp;
+const struct dns_packet *p;
+#endif
+{
+	char obuf[64];
+	char rbuf[64];
+	
+	if(fprintf(fp, "; id=%u %s opcode=%s rd=%d\n",
+		   p->dns_id,
+		   p->dns_qr ? "response" : "query",
+		   dns_opcodename(p->dns_opcode,
+				  obuf, sizeof obuf),
+		   p->dns_rd) < 0)
+		return -1;
+	if(p->dns_qr) {
+		if(fprintf(fp, "; aa=%d tc=%d ra=%d rcode=%s\n",
+			   p->dns_aa, p->dns_tc, p->dns_ra,
+			   dns_rcodename(p->dns_rcode,
+					 rbuf, sizeof rbuf)) < 0)
+			return -1;
+	}
+	if(p->dns_qdcount) {
+		if(fprintf(fp, "; %u records in question section\n",
+			   p->dns_qdcount) < 0)
+			return -1;
+		if(dns_printqs(fp, p->dns_qdrecords))
+			return -1;
+	}
+	if(p->dns_ancount) {
+		if(fprintf(fp, "; %u records in answer section\n",
+			   p->dns_ancount) < 0)
+			return -1;
+		if(dns_printas(fp, p->dns_anrecords))
+			return -1;
+	}
+	if(p->dns_ancount) {
+		if(fprintf(fp, "; %u records in authority section\n",
+			   p->dns_nscount) < 0)
+			return -1;
+		if(dns_printas(fp, p->dns_nsrecords))
+			return -1;
+	}
+	if(p->dns_ancount) {
+		if(fprintf(fp, "; %u records in additional section\n",
+			   p->dns_arcount) < 0)
+			return -1;
+		if(dns_printas(fp, p->dns_arrecords))
+			return -1;
+	}
+	if(p->dns_tc)
+		if(fprintf(fp, "; ...truncated...\n") < 0)
+			return -1;
+	if(p->dns_extra)
+		if(fprintf(fp, "; %lu additional bytes\n",
+			   (unsigned long)p->dns_extra) < 0)
+			return -1;
+	return 0;
+}
+
+const char *
+#if DNS_USE_PROTOS
+dns_strerror(enum dns_error e,
+	     char buffer[], size_t bufsize)
+#else
+dns_strerror(e, buffer, bufsize)
+enum dns_error e;	
+char buffer[];
+size_t bufsize;
+#endif
+{
+	static const struct lookup errors[] = {
+		{ dns_ok, "OK" },
+		{ dns_trunc, "truncated" },
+		{ dns_malloc, "malloc failed" },
+		{ dns_loop, "compressed name loop" },
+		{ dns_nametoolong, "name too long" },
+		{ dns_badptr, "invalid pointer" },
+		{ dns_badrdata, "rdata doesn't match rdlength" },
+		{ -1, 0 },
+	};
+	return dns_lookupname(errors, e, buffer, bufsize);
+}
+
+/*
+Local Variables:
+mode:c
+c-basic-offset:8
+End:
+*/
