Blob Blame Raw
#include "dns_parser.h"

#include <node.h>
#include <v8.h>

#include <ares.h>
#include <ares_dns.h>

#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <arpa/nameser.h>
#include <arpa/inet.h>

#ifdef __OpenBSD__
# ifndef ns_t_a
#  include <nameser.h>
# endif
#endif  // __OpenBSD__

namespace dns_parser {

static v8::Persistent<v8::String> question_symbol, answer_symbol, authority_symbol, additional_symbol;
static v8::Persistent<v8::String> type_symbol, ntype_symbol, class_symbol, nclass_symbol, ttl_symbol, rdata_symbol, text_symbol, data_symbol;
static v8::Persistent<v8::String> priority_symbol, weight_symbol, port_symbol, name_symbol, exchange_symbol, target_symbol;
static v8::Persistent<v8::String> mname_symbol, rname_symbol, serial_symbol, refresh_symbol, retry_symbol, expire_symbol, minimum_symbol;

static v8::Persistent<v8::String> space_symbol;

typedef int (*ParseRRData)(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local<v8::Object> &r);

struct RR_TYPE {
	const char *s;
	int n;
	ParseRRData parse_in; /* only parse class IN */
	v8::Persistent<v8::String> symbol;
};

struct RR_CLASS {
	const char *s;
	int n;
	v8::Persistent<v8::String> symbol;
};

static int parse_char_string(const unsigned char *&rr_data, int &rr_len, v8::Local<v8::String> &s) {
	if (rr_len <= 0) return ARES_EBADRESP;

	int len = (unsigned int) rr_data[0];
	rr_data++; rr_len--;
	if (len > rr_len) return ARES_EBADRESP;

	s = v8::String::New((const char*) rr_data, len);
	rr_data += len; rr_len -= len;

	return ARES_SUCCESS;
}

static int ParseRRData_A(const unsigned char *, int , const unsigned char *rr_data, int rr_len, v8::Local<v8::Object> &r) {
	if (rr_len == sizeof(struct in_addr)) {
		struct in_addr a;
		memcpy(&a, rr_data, sizeof(a));

		char ip[INET_ADDRSTRLEN];
		ip[0] = '\0';
		::inet_ntop(AF_INET, &a, ip, INET_ADDRSTRLEN);

		v8::Local<v8::String> address = v8::String::New(ip);
		r->Set(text_symbol, address);
		return ARES_SUCCESS;
	}
	return ARES_EBADRESP;
}

static int ParseRRData_AAAA(const unsigned char *, int , const unsigned char *rr_data, int rr_len, v8::Local<v8::Object> &r) {
	if (rr_len == sizeof(struct in6_addr)) {
		struct in6_addr a;
		memcpy(&a, rr_data, sizeof(a));

		char ip[INET6_ADDRSTRLEN];
		ip[0] = '\0';
		::inet_ntop(AF_INET6, &a, ip, INET6_ADDRSTRLEN);

		v8::Local<v8::String> address = v8::String::New(ip);
		r->Set(text_symbol, address);
		return ARES_SUCCESS;
	}
	return ARES_EBADRESP;
}

static int ParseRRData_MX(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local<v8::Object> &r) {
	int status;

	if (rr_len < 2) return ARES_EBADRESP;

	uint16_t priority;
	memcpy(&priority, rr_data, sizeof(priority));
	priority = ntohs(priority);
	rr_data += sizeof(priority); rr_len -= 2;

	char *hostname;
	long len;
	status = ares_expand_name(rr_data, abuf, alen, &hostname, &len);
	if (status != ARES_SUCCESS) return status;
	v8::Local<v8::String> v8_hostname = v8::String::New(hostname);
	free(hostname);
	if (len != rr_len) return ARES_EBADRESP;

	v8::Local<v8::Integer> v8_priority = v8::Integer::New(priority);
	r->Set(priority_symbol, v8_priority);
	r->Set(exchange_symbol, v8_hostname);

	v8::Local<v8::String> text = v8::String::Concat(v8_priority->ToString(), space_symbol);
	text = v8::String::Concat(text, v8_hostname);
	r->Set(text_symbol, text);
	return ARES_SUCCESS;
}

static int ParseRRData_NS(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local<v8::Object> &r) {
	int status;

	char *hostname;
	long len;
	status = ares_expand_name(rr_data, abuf, alen, &hostname, &len);
	if (status != ARES_SUCCESS) return status;
	v8::Local<v8::String> v8_hostname = v8::String::New(hostname);
	free(hostname);
	if (len != rr_len) return ARES_EBADRESP;

	r->Set(text_symbol, v8_hostname);

	return ARES_SUCCESS;
}

static int ParseRRData_SOA(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local<v8::Object> &r) {
	int status;
	long len;

	char *mname; /* primary NS */
	status = ares_expand_name(rr_data, abuf, alen, &mname, &len);
	if (status != ARES_SUCCESS) return status;
	v8::Local<v8::String> v8_mname = v8::String::New(mname);
	free(mname);
	if (len > rr_len) return ARES_EBADRESP;
	rr_data += len; rr_len -= len;

	char *rname; /* owner mailbox */
	status = ares_expand_name(rr_data, abuf, alen, &rname, &len);
	if (status != ARES_SUCCESS) return status;
	v8::Local<v8::String> v8_rname = v8::String::New(rname);
	free(rname);
	if (len > rr_len) return ARES_EBADRESP;
	rr_data += len; rr_len -= len;

	if (rr_len != 20) return ARES_EBADRESP;

	uint32_t serial, refresh, retry, expire, minimum;
	memcpy(&serial, rr_data, sizeof(uint32_t)); serial = ntohl(serial); rr_data += sizeof(uint32_t);
	memcpy(&refresh, rr_data, sizeof(uint32_t)); refresh = ntohl(refresh); rr_data += sizeof(uint32_t);
	memcpy(&retry, rr_data, sizeof(uint32_t)); retry = ntohl(retry); rr_data += sizeof(uint32_t);
	memcpy(&expire, rr_data, sizeof(uint32_t)); expire = ntohl(expire); rr_data += sizeof(uint32_t);
	memcpy(&minimum, rr_data, sizeof(uint32_t)); minimum = ntohl(minimum);

	v8::Local<v8::Integer>
		v8_serial = v8::Integer:: New(serial),
		v8_refresh = v8::Integer:: New(refresh),
		v8_retry = v8::Integer:: New(retry),
		v8_expire = v8::Integer:: New(expire),
		v8_minimum = v8::Integer:: New(minimum);

	v8::Local<v8::String> text = v8::String::Concat(v8_mname, space_symbol);
	text = v8::String::Concat(text, v8_rname); text = v8::String::Concat(text, space_symbol);
	text = v8::String::Concat(text, v8_serial->ToString()); text = v8::String::Concat(text, space_symbol);
	text = v8::String::Concat(text, v8_refresh->ToString()); text = v8::String::Concat(text, space_symbol);
	text = v8::String::Concat(text, v8_retry->ToString()); text = v8::String::Concat(text, space_symbol);
	text = v8::String::Concat(text, v8_expire->ToString()); text = v8::String::Concat(text, space_symbol);
	text = v8::String::Concat(text, v8_minimum->ToString());
	r->Set(text_symbol, text);

	r->Set(mname_symbol, v8_mname);
	r->Set(rname_symbol, v8_rname);
	r->Set(serial_symbol, v8_serial);
	r->Set(refresh_symbol, v8_refresh);
	r->Set(retry_symbol, v8_retry);
	r->Set(expire_symbol, v8_expire);
	r->Set(minimum_symbol, v8_minimum);

	return ARES_SUCCESS;
}

static int ParseRRData_TXT(const unsigned char *, int , const unsigned char *rr_data, int rr_len, v8::Local<v8::Object> &r) {
	int status, i;

	if (0 == rr_len) return ARES_EBADRESP;

	v8::Local<v8::Array> list = v8::Array::New();
	v8::Local<v8::String> s, text = v8::String::New(""), quote = v8::String::NewSymbol("\"");
	for (i = 0; 0 < rr_len; i++) {
		status = parse_char_string(rr_data, rr_len, s);
		if (ARES_SUCCESS != status) return status;

		list->Set(v8::Integer::New(i), s);

		if (i != 0) text = v8::String::Concat(text, space_symbol);
		text = v8::String::Concat(text, quote);
		/* TODO: escaping */
		text = v8::String::Concat(text, s);
		text = v8::String::Concat(text, quote);
	}
	
	r->Set(data_symbol, list);
	r->Set(text_symbol, text);

	return ARES_SUCCESS;
}

static int ParseRRData_SRV(const unsigned char *abuf, int alen, const unsigned char *rr_data, int rr_len, v8::Local<v8::Object> &r) {
	int status;

	if (rr_len < 6) return ARES_EBADRESP;

	uint16_t priority, weight, port;
	memcpy(&priority, rr_data, sizeof(uint16_t)); priority = ntohl(priority); rr_data += sizeof(uint16_t); rr_len -= sizeof(uint16_t);
	memcpy(&weight, rr_data, sizeof(uint16_t)); weight = ntohl(weight); rr_data += sizeof(uint16_t); rr_len -= sizeof(uint16_t);
	memcpy(&port, rr_data, sizeof(uint16_t)); port = ntohl(port); rr_data += sizeof(uint16_t); rr_len -= sizeof(uint16_t);

	char *target;
	long len;
	status = ares_expand_name(rr_data, abuf, alen, &target, &len);
	if (status != ARES_SUCCESS) return status;
	v8::Local<v8::String> v8_target = v8::String::New(target);
	free(target);
	if (len != rr_len) return ARES_EBADRESP;

	v8::Local<v8::Integer>
		v8_priority = v8::Integer:: New(priority),
		v8_weight = v8::Integer:: New(weight),
		v8_port = v8::Integer:: New(port);

	r->Set(priority_symbol, v8_priority);
	r->Set(weight_symbol, v8_weight);
	r->Set(port_symbol, v8_port);
	r->Set(target_symbol, v8_target);

	v8::Local<v8::String> text = v8::String::Concat(v8_priority->ToString(), space_symbol);
	text = v8::String::Concat(text, v8_weight->ToString()); text = v8::String::Concat(text, space_symbol);
	text = v8::String::Concat(text, v8_port->ToString()); text = v8::String::Concat(text, space_symbol);
	text = v8::String::Concat(text, v8_target);
	r->Set(text_symbol, text);

	return ARES_SUCCESS;
}

/* indented ones are deprecated/experimental */
static RR_TYPE rr_types[] = {
	{ "A", 1, ParseRRData_A, v8::Persistent<v8::String>() },
	{ "NS", 2, ParseRRData_NS, v8::Persistent<v8::String>() },
		{ "MD", 3, 0, v8::Persistent<v8::String>() },
		{ "MF", 4, 0, v8::Persistent<v8::String>() },
	{ "CNAME", 5, ParseRRData_NS, v8::Persistent<v8::String>() }, /* same RRDATA as NS */
	{ "SOA", 6, ParseRRData_SOA, v8::Persistent<v8::String>() },
		{ "MB", 7, 0, v8::Persistent<v8::String>() },
		{ "MG", 8, 0, v8::Persistent<v8::String>() },
		{ "MR", 9, 0, v8::Persistent<v8::String>() },
		{ "NULL", 10, 0, v8::Persistent<v8::String>() },
		{ "WKS", 11, 0, v8::Persistent<v8::String>() },
	{ "PTR", 12, ParseRRData_NS, v8::Persistent<v8::String>() }, /* same RRDATA as NS */
		{ "HINFO", 13, 0, v8::Persistent<v8::String>() },
		{ "MINFO", 14, 0, v8::Persistent<v8::String>() },
	{ "MX", 15, ParseRRData_MX, v8::Persistent<v8::String>() },
	{ "TXT", 16, ParseRRData_TXT, v8::Persistent<v8::String>() },
	{ "RP", 17, 0, v8::Persistent<v8::String>() },
	{ "AFSDB", 18, 0, v8::Persistent<v8::String>() },
	{ "SIG", 24, 0, v8::Persistent<v8::String>() },
	{ "KEY", 25, 0, v8::Persistent<v8::String>() },
	{ "AAAA", 28, ParseRRData_AAAA, v8::Persistent<v8::String>() },
	{ "LOC", 29, 0, v8::Persistent<v8::String>() },
	{ "SRV", 33, ParseRRData_SRV, v8::Persistent<v8::String>() },
	{ "NAPTR", 35, 0, v8::Persistent<v8::String>() },
	{ "KX", 36, 0, v8::Persistent<v8::String>() },
	{ "CERT", 37, 0, v8::Persistent<v8::String>() },
	{ "DNAME", 39, 0, v8::Persistent<v8::String>() },
	{ "OPT", 41, 0, v8::Persistent<v8::String>() },
	{ "APL", 42, 0, v8::Persistent<v8::String>() },
	{ "DS", 43, 0, v8::Persistent<v8::String>() },
	{ "SSHFP", 44, 0, v8::Persistent<v8::String>() },
	{ "IPSECKEY", 45, 0, v8::Persistent<v8::String>() },
	{ "RRSIG", 46, 0, v8::Persistent<v8::String>() },
	{ "NSEC", 47, 0, v8::Persistent<v8::String>() },
	{ "DNSKEY", 48, 0, v8::Persistent<v8::String>() },
	{ "DHCID", 49, 0, v8::Persistent<v8::String>() },
	{ "NSEC3", 50, 0, v8::Persistent<v8::String>() },
	{ "NSEC3PARAM", 51, 0, v8::Persistent<v8::String>() },
	{ "HIP", 55, 0, v8::Persistent<v8::String>() },
	{ "SPF", 99, 0, v8::Persistent<v8::String>() },
	{ "TKEY", 249, 0, v8::Persistent<v8::String>() },
	{ "TSIG", 250, 0, v8::Persistent<v8::String>() },
	{ "IXFR", 251, 0, v8::Persistent<v8::String>() },
	{ "AXFR", 252, 0, v8::Persistent<v8::String>() },
		{ "MAILB", 253, 0, v8::Persistent<v8::String>() },
		{ "MAILA", 254, 0, v8::Persistent<v8::String>() },
	{ "ANY", 255, 0, v8::Persistent<v8::String>() }, /* aka "*" */
	{ "TA", 32768, 0, v8::Persistent<v8::String>() },
	{ "DLV", 32769, 0, v8::Persistent<v8::String>() }
};

static RR_CLASS rr_classes[] = {
	{ "IN", 1, v8::Persistent<v8::String>() }, /* the Internet */
		{ "CS", 2, v8::Persistent<v8::String>() }, /* the CSNET class (Obsolete - used only for examples in some obsolete RFCs) */
	{ "CH", 3, v8::Persistent<v8::String>() }, /* the CHAOS class */
	{ "HS", 4, v8::Persistent<v8::String>() }, /* Hesiod [Dyer 87] */
	{ "ANY", 255, v8::Persistent<v8::String>() } /* same value as in rr_type, so no name conflict */
};

void Initialize(v8::Handle<v8::Object> target) {
	v8::HandleScope scope;

	using namespace v8;

	int r = ares_library_init(ARES_LIB_INIT_ALL);
	if (0 != r) {
		// TODO
		// ThrowException(Exception::Error(String::New(ares_strerror(r))));
		assert(r == 0);
	}

	question_symbol = NODE_PSYMBOL("question");
	answer_symbol = NODE_PSYMBOL("answer");
	authority_symbol = NODE_PSYMBOL("authority");
	additional_symbol = NODE_PSYMBOL("additional");

	type_symbol = NODE_PSYMBOL("type");
	ntype_symbol = NODE_PSYMBOL("ntype");
	class_symbol = NODE_PSYMBOL("class");
	nclass_symbol = NODE_PSYMBOL("nclass");
	ttl_symbol = NODE_PSYMBOL("ttl");
	rdata_symbol = NODE_PSYMBOL("rdata");
	text_symbol = NODE_PSYMBOL("text");
	data_symbol = NODE_PSYMBOL("data");

	priority_symbol = NODE_PSYMBOL("priority");
	weight_symbol = NODE_PSYMBOL("weight");
	port_symbol = NODE_PSYMBOL("port");
	name_symbol = NODE_PSYMBOL("name");
	exchange_symbol = NODE_PSYMBOL("exchange");
	target_symbol = NODE_PSYMBOL("target");

	mname_symbol = NODE_PSYMBOL("mname");
	rname_symbol = NODE_PSYMBOL("rname");
	serial_symbol = NODE_PSYMBOL("serial");
	refresh_symbol = NODE_PSYMBOL("refresh");
	retry_symbol = NODE_PSYMBOL("retry");
	expire_symbol = NODE_PSYMBOL("expire");
	minimum_symbol = NODE_PSYMBOL("minimum");

	space_symbol = NODE_PSYMBOL(" ");;

	v8::Local<v8::Object> types = v8::Object::New();
	for (unsigned int i = 0; i < sizeof(rr_types)/sizeof(rr_types[0]); i++) {
		rr_types[i].symbol = NODE_PSYMBOL(rr_types[i].s);
		target->Set(rr_types[i].symbol, v8::Integer::New(rr_types[i].n));
	}
	for (unsigned int i = 0; i < sizeof(rr_classes)/sizeof(rr_classes[0]); i++) {
		rr_classes[i].symbol = NODE_PSYMBOL(rr_classes[i].s);
		target->Set(rr_classes[i].symbol, v8::Integer::New(rr_classes[i].n));
	}
}

static inline const char *ares_errno_string(int errorno) {
#define ERRNO_CASE(e)  case ARES_##e: return #e;
	switch (errorno) {
	ERRNO_CASE(SUCCESS)
	ERRNO_CASE(ENODATA)
	ERRNO_CASE(EFORMERR)
	ERRNO_CASE(ESERVFAIL)
	ERRNO_CASE(ENOTFOUND)
	ERRNO_CASE(ENOTIMP)
	ERRNO_CASE(EREFUSED)
	ERRNO_CASE(EBADQUERY)
	ERRNO_CASE(EBADNAME)
	ERRNO_CASE(EBADFAMILY)
	ERRNO_CASE(EBADRESP)
	ERRNO_CASE(ECONNREFUSED)
	ERRNO_CASE(ETIMEOUT)
	ERRNO_CASE(EOF)
	ERRNO_CASE(EFILE)
	ERRNO_CASE(ENOMEM)
	ERRNO_CASE(EDESTRUCTION)
	ERRNO_CASE(EBADSTR)
	ERRNO_CASE(EBADFLAGS)
	ERRNO_CASE(ENONAME)
	ERRNO_CASE(EBADHINTS)
	ERRNO_CASE(ENOTINITIALIZED)
	ERRNO_CASE(ELOADIPHLPAPI)
	ERRNO_CASE(EADDRGETNETWORKPARAMS)
	ERRNO_CASE(ECANCELLED)
	default:
		assert(0 && "Unhandled c-ares errno");
		return "(UNKNOWN)";
	}
}

v8::Local<v8::Value> AresException(int status) {
	v8::Local<v8::String> code = v8::String::NewSymbol(ares_errno_string(status));
	v8::Local<v8::String> message = v8::String::NewSymbol(ares_strerror(status));

	v8::Local<v8::String> cons1 = v8::String::Concat(code, v8::String::NewSymbol(", "));
	v8::Local<v8::String> cons2 = v8::String::Concat(cons1, message);

	v8::Local<v8::Value> e = v8::Exception::Error(cons2);

	v8::Local<v8::Object> obj = e->ToObject();
	obj->Set(v8::String::NewSymbol("errno"), v8::Integer::New(status));

	return e;
}

static void set_type(v8::Local<v8::Object> &r, int rr_type) {
	r->Set(ntype_symbol, v8::Integer::New(rr_type));
	for (unsigned int i = 0; i < sizeof(rr_types)/sizeof(rr_types[0]); i++) {
		if (rr_type == rr_types[i].n) {
			r->Set(type_symbol, rr_types[i].symbol);
			break;
		}
	}
}
static void set_class(v8::Local<v8::Object> &r, int rr_class) {
	r->Set(nclass_symbol, v8::Integer::New(rr_class));
	for (unsigned int i = 0; i < sizeof(rr_classes)/sizeof(rr_classes[0]); i++) {
		if (rr_class == rr_classes[i].n) {
			r->Set(class_symbol, rr_classes[i].symbol);
			break;
		}
	}
}

static int parse_read_questions(const unsigned char *abuf, int alen, const unsigned char * &aptr, int count, v8::Local<v8::Array> list) {
	int status;
	long len;

	/* skip each question */
	for (int i = 0; i < count; i++) {
		int rr_type, rr_class;
		char *rr_name;
		v8::Local<v8::Object> r = v8::Object::New();

		/* Decode the RR up to the data field. */
		status = ares_expand_name(aptr, abuf, alen, &rr_name, &len);
		if (status != ARES_SUCCESS) return status;
		aptr += len;
		if (aptr + QFIXEDSZ > abuf + alen) {
			free(rr_name);
			return ARES_EBADRESP;
		}

		rr_type = DNS_RR_TYPE(aptr);
		rr_class = DNS_RR_CLASS(aptr);
		aptr += QFIXEDSZ;

		if (aptr > abuf + alen) {
			free(rr_name);
			return ARES_EBADRESP;
		}

		r->Set(name_symbol, v8::String::New(rr_name));
		set_type(r, rr_type);
		set_class(r, rr_class);
		free(rr_name);

		list->Set(v8::Integer::New(i), r);
	}

	return ARES_SUCCESS;
}

static int parse_read_answers(const unsigned char *abuf, int alen, const unsigned char * &aptr, int count, v8::Local<v8::Array> list) {
	int status;
	long len;

	/* Examine each resource record (RR) in turn. */
	for (int i = 0; i < count; i++) {
		int rr_type, rr_class, rr_len, rr_ttl;
		char *rr_name;
		const unsigned char *rr_data;
		v8::Local<v8::Object> r = v8::Object::New();

		/* Decode the RR up to the data field. */
		status = ares_expand_name(aptr, abuf, alen, &rr_name, &len);
		if (status != ARES_SUCCESS) break;
		aptr += len;
		if (aptr + RRFIXEDSZ > abuf + alen) {
			free(rr_name);
			return ARES_EBADRESP;
		}

		rr_type = DNS_RR_TYPE(aptr);
		rr_class = DNS_RR_CLASS(aptr);
		rr_ttl = DNS_RR_TTL(aptr);
		rr_len = DNS_RR_LEN(aptr);
		aptr += RRFIXEDSZ;
		rr_data = aptr;
		aptr += rr_len;

		if (aptr > abuf + alen) {
			free(rr_name);
			return ARES_EBADRESP;
		}

		r->Set(name_symbol, v8::String::New(rr_name));
		set_class(r, rr_class);
		r->Set(ttl_symbol, v8::Integer::New(rr_ttl));
		free(rr_name);

		r->Set(ntype_symbol, v8::Integer::New(rr_type));
		for (unsigned int t = 0; t < sizeof(rr_types)/sizeof(rr_types[0]); t++) {
			if (rr_type == rr_types[t].n) {
				r->Set(type_symbol, rr_types[t].symbol);
				if (C_IN == rr_class && rr_types[t].parse_in) {
					status = rr_types[t].parse_in(abuf, alen, rr_data, rr_len, r);
					if (status != ARES_SUCCESS) return status;
				}
				break;
			}
		}

		r->Set(rdata_symbol, v8::String::New((const char*) rr_data, rr_len));

		list->Set(v8::Integer::New(i), r);
	}

	return ARES_SUCCESS;
}

v8::Local<v8::Value> parse_dns_response(const unsigned char *abuf, int alen, int *pstatus) {
	unsigned int qdcount, ancount, nscount, arcount;
	int status;
	const unsigned char *aptr;

	if (pstatus) *pstatus = ARES_SUCCESS;

	v8::Local<v8::Array> qd = v8::Array::New(), an = v8::Array::New(), ns = v8::Array::New(), ar = v8::Array::New();
	v8::Local<v8::Object> msg = v8::Object::New();

	/* Give up if abuf doesn't have room for a header. */
	if (alen < HFIXEDSZ) {
		status = ARES_EBADRESP;
		goto error;
	}

	/* Fetch the question and answer count from the header. */
	qdcount = DNS_HEADER_QDCOUNT(abuf);
	ancount = DNS_HEADER_ANCOUNT(abuf);
	nscount = DNS_HEADER_NSCOUNT(abuf);
	arcount = DNS_HEADER_ARCOUNT(abuf);
	if (qdcount != 1)  {
		status = ARES_EBADRESP;
		goto error;
	}

	aptr = abuf + HFIXEDSZ;

	status = parse_read_questions(abuf, alen, aptr, qdcount, qd);
	if (status != ARES_SUCCESS) goto error;

	status = parse_read_answers(abuf, alen, aptr, ancount, an);
	if (status != ARES_SUCCESS) goto error;

	status = parse_read_answers(abuf, alen, aptr, nscount, ns);
	if (status != ARES_SUCCESS) goto error;

	status = parse_read_answers(abuf, alen, aptr, arcount, ar);
	if (status != ARES_SUCCESS) goto error;

	msg->Set(question_symbol, qd);
	msg->Set(answer_symbol, an);
	msg->Set(authority_symbol, ns);
	msg->Set(additional_symbol, ar);

	return msg;

error:
	if (pstatus) *pstatus = status;
	return AresException(status);
}

}