/* Find public key in DNS
 * Copyright (C) 2000-2002  D. Hugh Redelmeier.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * RCSID $Id$
 */

#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>	/* ??? for h_errno */
#include <sys/queue.h>

#include <freeswan.h>
#include <ipsec_policy.h>

#include "constants.h"
#include "adns.h"	/* needs <resolv.h> */
#include "defs.h"
#include "log.h"
#include "id.h"
#include "connections.h"
#include "keys.h"	    /* needs connections.h */
#include "dnskey.h"
#include "packet.h"
#include "timer.h"

/* somebody has to decide */
#define MAX_TXT_RDATA	((MAX_KEY_BYTES * 8 / 6) + 40)	/* somewhat arbitrary overkill */

/* ADNS stuff */

int adns_qfd = NULL_FD,	/* file descriptor for sending queries to adns (O_NONBLOCK) */
    adns_afd = NULL_FD;	/* file descriptor for receiving answers from adns */
static pid_t adns_pid = 0;
const char *pluto_adns_option = NULL;	/* path from --pluto_adns */

int adns_restart_count;
#define ADNS_RESTART_MAX 20

void
init_adns(void)
{
    const char *adns_path = pluto_adns_option;
#ifndef USE_LWRES
    static const char adns_name[] = "_pluto_adns";
    const char *helper_bin_dir = getenv("IPSEC_LIBDIR");
#else /* USE_LWRES */
    static const char adns_name[] = "lwdnsq";
    const char *helper_bin_dir = getenv("IPSEC_EXECDIR");
#endif /* USE_LWRES */
    char adns_path_space[4096];	/* plenty long? */
    int qfds[2];
    int afds[2];

    /* find a pathname to the ADNS program */
    if (adns_path == NULL)
    {
	/* pathname was not specified as an option: build it.
	 * First, figure out the directory to be used.
	 */
	ssize_t n;

	if (helper_bin_dir != NULL)
	{
	    n = strlen(helper_bin_dir);
	    if ((size_t)n <= sizeof(adns_path_space) - sizeof(adns_name))
	    {
		strcpy(adns_path_space, helper_bin_dir);
		if (n > 0 && adns_path_space[n -1] != '/')
		    adns_path_space[n++] = '/';
	    }
	}
	else
	{
	    /* The program will be in the same directory as Pluto,
	     * so we use the sympolic link /proc/self/exe to
	     * tell us of the path prefix.
	     */
	    n = readlink("/proc/self/exe", adns_path_space, sizeof(adns_path_space));

	    if (n < 0)
		exit_log_errno((e
		    , "readlink(\"/proc/self/exe\") failed in init_adns()"));

	}

	if ((size_t)n > sizeof(adns_path_space) - sizeof(adns_name))
	    exit_log("path to %s is too long", adns_name);

	while (n > 0 && adns_path_space[n - 1] != '/')
	    n--;

	strcpy(adns_path_space + n, adns_name);
	adns_path = adns_path_space;
    }
    if (access(adns_path, X_OK) < 0)
	exit_log_errno((e, "%s missing or not executable", adns_path));

    if (pipe(qfds) != 0 || pipe(afds) != 0)
	exit_log_errno((e, "pipe(2) failed in init_adns()"));

    adns_pid = fork();
    switch (adns_pid)
    {
    case -1:
	exit_log_errno((e, "fork() failed in init_adns()"));

    case 0:
	/* child */
	{
	    /* Make stdin and stdout our pipes.
	     * Take care to handle case where pipes already use these fds.
	     */
	    if (afds[1] == 0)
		afds[1] = dup(afds[1]);	/* avoid being overwritten */
	    if (qfds[0] != 0)
	    {
		dup2(qfds[0], 0);
		close(qfds[0]);
	    }
	    if (afds[1] != 1)
	    {
		dup2(afds[1], 1);
		close(qfds[1]);
	    }
	    if (afds[0] > 1)
		close(afds[0]);
	    if (afds[1] > 1)
		close(afds[1]);

	    DBG(DBG_DNS, execlp(adns_path, adns_name, "-d", NULL));

	    execlp(adns_path, adns_name, NULL);
	    exit_log_errno((e, "execlp of %s failed", adns_path));
	}

    default:
	/* parent */
	close(qfds[0]);
	adns_qfd = qfds[1];
	adns_afd = afds[0];
	close(afds[1]);
	fcntl(adns_qfd, F_SETFD, FD_CLOEXEC);
	fcntl(adns_afd, F_SETFD, FD_CLOEXEC);
	fcntl(adns_qfd, F_SETFL, O_NONBLOCK);
	break;
    }
}

void
stop_adns(void)
{
    close_any(adns_qfd);
    adns_qfd = NULL_FD;
    close_any(adns_afd);
    adns_afd = NULL_FD;

    if (adns_pid != 0)
    {
	int status;
	pid_t p = waitpid(adns_pid, &status, 0);

	if (p == -1)
	{
	    log_errno((e, "waitpid for ADNS process failed"));
	}
	else if (WIFEXITED(status))
	{
	    if (WEXITSTATUS(status) != 0)
		plog("ADNS process exited with status %d"
		    , (int) WEXITSTATUS(status));
	}
	else if (WIFSIGNALED(status))
	{
	    plog("ADNS process terminated by signal %d", (int)WTERMSIG(status));
	}
	else
	{
	    plog("wait for end of ADNS process returned odd status 0x%x\n"
		, status);
	}
    }
}



/* tricky macro to pass any hot potato */
#define TRY(x)	{ err_t ugh = x; if (ugh != NULL) return ugh; }


/* Process TXT X-IPsec-Server record, accumulating relevant ones
 * in cr->gateways_from_dns, a list sorted by "preference".
 *
 * Format of TXT record body: X-IPsec-Server ( nnn ) = iii kkk
 *  nnn is a 16-bit unsigned integer preference
 *  iii is @FQDN or dotted-decimal IPv4 address or colon-hex IPv6 address
 *  kkk is an optional RSA public signing key in base 64.
 *
 * NOTE: we've got to be very wary of anything we find -- bad guys
 * might have prepared it.
 */

#define our_TXT_attr_string "X-IPsec-Server"
static const char our_TXT_attr[] = our_TXT_attr_string;

static err_t
decode_iii(u_char **pp, struct id *gw_id)
{
    u_char *p = *pp + strspn(*pp, " \t");
    u_char *e = p + strcspn(p, " \t");
    u_char under = *e;

    if (p == e)
	return "TXT " our_TXT_attr_string " badly formed (no gateway specified)";

    *e = '\0';
    if (*p == '@')
    {
	/* gateway specification in this record is @FQDN */
	err_t ugh = atoid(p, gw_id, FALSE);

	if (ugh != NULL)
	    return builddiag("malformed FQDN in TXT " our_TXT_attr_string ": %s"
		, ugh);
    }
    else
    {
	/* gateway specification is numeric */
	ip_address ip;
	err_t ugh = tnatoaddr(p, e-p
	    , strchr(p, ':') == NULL? AF_INET : AF_INET6
	    , &ip);

	if (ugh != NULL)
	    return builddiag("malformed IP address in TXT " our_TXT_attr_string ": %s"
		, ugh);

	if (isanyaddr(&ip))
	    return "gateway address must not be 0.0.0.0 or 0::0";

	iptoid(&ip, gw_id);
    }

    *e = under;
    *pp = e + strspn(e, " \t");

    return NULL;
}

static err_t
process_txt_rr_body(u_char *str
, bool doit	/* should we capture information? */
, enum dns_auth_level dns_auth_level
, struct adns_continuation *const cr)
{
    const struct id *client_id = &cr->id;	/* subject of query */
    u_char *p = str;
    unsigned long pref = 0;
    struct gw_info gi;

    p += strspn(p, " \t");	/* ignore leading whitespace */

    /* is this for us? */
    if (strncasecmp(p, our_TXT_attr, sizeof(our_TXT_attr)-1) != 0)
	return NULL;	/* neither interesting nor bad */

    p += sizeof(our_TXT_attr) - 1;	/* ignore our attribute name */
    p += strspn(p, " \t");	/* ignore leading whitespace */

    /* decode '(' nnn ')' */
    if (*p != '(')
	return "X-IPsec-Server missing '('";

    {
	char *e;

	p++;
	pref = strtoul(p, &e, 0);
	if ((u_char *)e == p)
	    return "malformed X-IPsec-Server priority";

	p = e + strspn(e, " \t");

	if (*p != ')')
	    return "X-IPsec-Server priority missing ')'";

	p++;
	p += strspn(p, " \t");

	if (pref > 0xFFFF)
	    return "X-IPsec-Server priority larger than 0xFFFF";
    }

    /* time for '=' */

    if (*p != '=')
	return "X-IPsec-Server priority missing '='";

    p++;
    p += strspn(p, " \t");

    /* Decode iii (Security Gateway ID). */

    zero(&gi);	/* before first use */

    TRY(decode_iii(&p, &gi.gw_id));	/* will need to unshare_id_content */

    if (!cr->sgw_specified)
    {
	/* we don't know the peer's ID (because we are initiating
	 * and we don't know who to initiate with.
	 * So we're looking for gateway specs with an IP address
	 */
	if (!id_is_ipaddr(&gi.gw_id))
	{
	    DBG(DBG_DNS,
		{
		    char cidb[BUF_LEN];
		    char gwidb[BUF_LEN];

		    idtoa(client_id, cidb, sizeof(cidb));
		    idtoa(&gi.gw_id, gwidb, sizeof(gwidb));
		    DBG_log("TXT %s record for %s: security gateway %s;"
			" ignored because gateway's IP is unspecified"
			, our_TXT_attr, cidb, gwidb);
		});
	    return NULL;	/* we cannot use this record, but it isn't wrong */
	}
    }
    else
    {
	/* We do know the peer's ID (because we are responding)
	 * So we're looking for gateway specs specifying this known ID.
	 */
	const struct id *peer_id = &cr->sgw_id;

	if (!same_id(peer_id, &gi.gw_id))
	{
	    DBG(DBG_DNS,
		{
		    char cidb[BUF_LEN];
		    char gwidb[BUF_LEN];
		    char pidb[BUF_LEN];

		    idtoa(client_id, cidb, sizeof(cidb));
		    idtoa(&gi.gw_id, gwidb, sizeof(gwidb));
		    idtoa(peer_id, pidb, sizeof(pidb));
		    DBG_log("TXT %s record for %s: security gateway %s;"
			" ignored -- looking to confirm %s as gateway"
			, our_TXT_attr, cidb, gwidb, pidb);
		});
	    return NULL;	/* we cannot use this record, but it isn't wrong */
	}
    }

    if (doit)
    {
	/* really accept gateway */
	struct gw_info **gwip;	/* gateway insertion point */

	gi.client_id = *client_id;	/* will need to unshare_id_content */

	/* decode optional kkk: base 64 encoding of key */

	gi.gw_key_present = *p != '\0';
	if (gi.gw_key_present)
	{
	    /* Decode base 64 encoding of key.
	     * Similar code is in process_lwdnsq_key.
	     */
	    u_char kb[RSA_MAX_ENCODING_BYTES];	/* plenty of space for binary form of public key */
	    chunk_t kbc;
	    struct RSA_public_key r;

	    err_t ugh = ttodatav(p, 0, 64, kb, sizeof(kb), &kbc.len
		, diag_space, sizeof(diag_space), TTODATAV_SPACECOUNTS);

	    if (ugh != NULL)
		return builddiag("malformed key data: %s", ugh);

	    if (kbc.len > sizeof(kb))
		return builddiag("key data larger than %lu bytes"
		    , (unsigned long) sizeof(kb));

	    kbc.ptr = kb;
	    ugh = unpack_RSA_public_key(&r, &kbc);
	    if (ugh != NULL)
		return builddiag("invalid key data: %s", ugh);

	    /* now find a key entry to put it in */
	    gi.key = public_key_from_rsa(&r);

	    free_RSA_public_content(&r);

	    unreference_key(&cr->last_info);
	    cr->last_info = reference_key(gi.key);
	}

	/* we're home free!  Allocate everything and add to gateways list. */
	gi.refcnt = 1;
	gi.pref = pref;
	gi.key->dns_auth_level = dns_auth_level;
	gi.key->last_tried_time = gi.key->last_worked_time = NO_TIME;

	/* find insertion point */
	for (gwip = &cr->gateways_from_dns; *gwip != NULL && (*gwip)->pref < pref; gwip = &(*gwip)->next)
	    ;

	DBG(DBG_DNS,
	    {
		char cidb[BUF_LEN];
		char gwidb[BUF_LEN];

		idtoa(client_id, cidb, sizeof(cidb));
		idtoa(&gi.gw_id, gwidb, sizeof(gwidb));
		if (gi.gw_key_present)
		{
		    DBG_log("gateway for %s is %s with key %s"
			, cidb, gwidb, gi.key->u.rsa.keyid);
		}
		else
		{
		    DBG_log("gateway for %s is %s; no key specified"
			, cidb, gwidb);
		}
	    });

	gi.next = *gwip;
	*gwip = clone_thing(gi, "gateway info");
	unshare_id_content(&(*gwip)->gw_id);
	unshare_id_content(&(*gwip)->client_id);
    }

    return NULL;
}

static const char *
rr_typename(int type)
{
    switch (type)
    {
    case T_TXT:
	return "TXT";
    case T_KEY:
	return "KEY";
    default:
	return "???";
    }
}


#ifdef USE_LWRES

# ifdef USE_KEYRR
static err_t
process_lwdnsq_key(u_char *str
, enum dns_auth_level dns_auth_level
, struct adns_continuation *const cr)
{
    /* fields of KEY record.  See RFC 2535 3.1 KEY RDATA format. */
    unsigned long flags	/* 16 bits */
	, protocol	/* 8 bits */
	, algorithm;	/* 8 bits */

    char *rest = str
	, *p
	, *endofnumber;

    /* flags */
    p = strsep(&rest, " \t");
    if (p == NULL)
	return "lwdnsq KEY: missing flags";

    flags = strtoul(p, &endofnumber, 10);
    if (*endofnumber != '\0')
	return "lwdnsq KEY: malformed flags";

    /* protocol */
    p = strsep(&rest, " \t");
    if (p == NULL)
	return "lwdnsq KEY: missing protocol";

    protocol = strtoul(p, &endofnumber, 10);
    if (*endofnumber != '\0')
	return "lwdnsq KEY: malformed protocol";

    /* algorithm */
    p = strsep(&rest, " \t");
    if (p == NULL)
	return "lwdnsq KEY: missing algorithm";

    algorithm = strtoul(p, &endofnumber, 10);
    if (*endofnumber != '\0')
	return "lwdnsq KEY: malformed algorithm";

    /* is this key interesting? */
    if (protocol == 4	/* IPSEC (RFC 2535 3.1.3) */
    && algorithm == 1	/* RSA/MD5 (RFC 2535 3.2) */
    && (flags & 0x8000ul) == 0	/* use for authentication (3.1.2) */
    && (flags & 0x2CF0ul) == 0)	/* must be zero */
    {
	/* Decode base 64 encoding of key.
	 * Similar code is in process_txt_rr_body.
	 */
	u_char kb[RSA_MAX_ENCODING_BYTES];	/* plenty of space for binary form of public key */
	chunk_t kbc;
	err_t ugh = ttodatav(rest, 0, 64, kb, sizeof(kb), &kbc.len
	    , diag_space, sizeof(diag_space), TTODATAV_IGNORESPACE);

	if (ugh != NULL)
	    return builddiag("malformed key data: %s", ugh);

	if (kbc.len > sizeof(kb))
	    return builddiag("key data larger than %lu bytes"
		, (unsigned long) sizeof(kb));

	kbc.ptr = kb;
	TRY(add_public_key(&cr->id, dns_auth_level, PUBKEY_ALG_RSA, &kbc
	    , &cr->keys_from_dns));

	/* keep a reference to last one */
	unreference_key(&cr->last_info);
	cr->last_info = reference_key(cr->keys_from_dns->key);
    }
    return NULL;
}
# endif /* USE_KEYRR */

#else  /* ! USE_LWRES */

/* structure of Query Reply (RFC 1035 4.1.1):
 *
 *  +---------------------+
 *  |        Header       |
 *  +---------------------+
 *  |       Question      | the question for the name server
 *  +---------------------+
 *  |        Answer       | RRs answering the question
 *  +---------------------+
 *  |      Authority      | RRs pointing toward an authority
 *  +---------------------+
 *  |      Additional     | RRs holding additional information
 *  +---------------------+
 */

/* Header section format (as modified by RFC 2535 6.1):
 *                                  1  1  1  1  1  1
 *    0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
 *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *  |                      ID                       |
 *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *  |QR|   Opcode  |AA|TC|RD|RA| Z|AD|CD|   RCODE   |
 *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *  |                    QDCOUNT                    |
 *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *  |                    ANCOUNT                    |
 *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *  |                    NSCOUNT                    |
 *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *  |                    ARCOUNT                    |
 *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 */
struct qr_header {
    u_int16_t	id;	/* 16-bit identifier to match query */

    u_int16_t	stuff;	/* packed crud: */

#define QRS_QR	0x8000	/* QR: on if this is a response */

#define QRS_OPCODE_SHIFT	11  /* OPCODE field */
#define QRS_OPCODE_MASK	0xF
#define QRSO_QUERY	0   /* standard query */
#define QRSO_IQUERY	1   /* inverse query */
#define QRSO_STATUS	2   /* server status request query */

#define QRS_AA 0x0400	/* AA: on if Authoritative Answer */
#define QRS_TC 0x0200	/* TC: on if truncation happened */
#define QRS_RD 0x0100	/* RD: on if recursion desired */
#define QRS_RA 0x0080	/* RA: on if recursion available */
#define QRS_Z  0x0040	/* Z: reserved; must be zero */
#define QRS_AD 0x0020	/* AD: on if authentic data (RFC 2535) */
#define QRS_CD 0x0010	/* AD: on if checking disabled (RFC 2535) */

#define QRS_RCODE_SHIFT	0 /* RCODE field: response code */
#define QRS_RCODE_MASK	0xF
#define QRSR_OK	    0


    u_int16_t qdcount;	    /* number of entries in question section */
    u_int16_t ancount;	    /* number of resource records in answer section */
    u_int16_t nscount;	    /* number of name server resource records in authority section */
    u_int16_t arcount;	    /* number of resource records in additional records section */
};

static field_desc qr_header_fields[] = {
    { ft_nat, 16/BITS_PER_BYTE, "ID", NULL },
    { ft_nat, 16/BITS_PER_BYTE, "stuff", NULL },
    { ft_nat, 16/BITS_PER_BYTE, "QD Count", NULL },
    { ft_nat, 16/BITS_PER_BYTE, "Answer Count", NULL },
    { ft_nat, 16/BITS_PER_BYTE, "Authority Count", NULL },
    { ft_nat, 16/BITS_PER_BYTE, "Additional Count", NULL },
    { ft_end, 0, NULL, NULL }
};

static struct_desc qr_header_desc = {
    "Query Response Header",
    qr_header_fields,
    sizeof(struct qr_header)
};

/* Messages for codes in RCODE (see RFC 1035 4.1.1) */
static const err_t rcode_text[QRS_RCODE_MASK + 1] = {
    NULL,   /* not an error */
    "Format error - The name server was unable to interpret the query",
    "Server failure - The name server was unable to process this query"
	" due to a problem with the name server",
    "Name Error - Meaningful only for responses from an authoritative name"
	" server, this code signifies that the domain name referenced in"
	" the query does not exist",
    "Not Implemented - The name server does not support the requested"
	" kind of query",
    "Refused - The name server refuses to perform the specified operation"
	" for policy reasons",
    /* the rest are reserved for future use */
    };

/* throw away a possibly compressed domain name */

static err_t
eat_name(pb_stream *pbs)
{
    u_char name_buf[NS_MAXDNAME + 2];
    u_char *ip = pbs->cur;
    unsigned oi = 0;
    unsigned jump_count = 0;

    for (;;)
    {
	u_int8_t b;

	if (ip >= pbs->roof)
	    return "ran out of message while skipping domain name";

	b = *ip++;
	if (jump_count == 0)
	    pbs->cur = ip;

	if (b == 0)
	    break;

	switch (b & 0xC0)
	{
	    case 0x00:
		/* we grab the next b characters */
		if (oi + b > NS_MAXDNAME)
		    return "domain name too long";

		if (pbs->roof - ip <= b)
		    return "domain name falls off end of message";

		if (oi != 0)
		    name_buf[oi++] = '.';

		memcpy(name_buf + oi, ip, b);
		oi += b;
		ip += b;
		if (jump_count == 0)
		    pbs->cur = ip;
		break;

	    case 0xC0:
		{
		    unsigned ix;

		    if (ip >= pbs->roof)
			return "ran out of message in middle of compressed domain name";

		    ix = ((b & ~0xC0u) << 8) | *ip++;
		    if (jump_count == 0)
			pbs->cur = ip;

		    if (ix >= pbs_room(pbs))
			return "impossible compressed domain name";

		    /* Avoid infinite loop.
		     * There can be no more jumps than there are bytes
		     * in the packet.  Not a tight limit, but good enough.
		     */
		    jump_count++;
		    if (jump_count > pbs_room(pbs))
			return "loop in compressed domain name";

		    ip = pbs->start + ix;
		}
		break;

	    default:
		return "invalid code in label";
	}
    }

    name_buf[oi++] = '\0';

    DBG(DBG_DNS, DBG_log("skipping name %s", name_buf));

    return NULL;
}

static err_t
eat_name_helpfully(pb_stream *pbs, const char *context)
{
    err_t ugh = eat_name(pbs);

    return ugh == NULL? ugh
	: builddiag("malformed name within DNS record of %s: %s", context, ugh);
}

/* non-variable part of 4.1.2 Question Section entry:
 *                                 1  1  1  1  1  1
 *   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                                               |
 * /                     QNAME                     /
 * /                                               /
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                     QTYPE                     |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                     QCLASS                    |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 */

struct qs_fixed {
    u_int16_t qtype;
    u_int16_t qclass;
};

static field_desc qs_fixed_fields[] = {
    { ft_loose_enum, 16/BITS_PER_BYTE, "QTYPE", &rr_qtype_names },
    { ft_loose_enum, 16/BITS_PER_BYTE, "QCLASS", &rr_class_names },
    { ft_end, 0, NULL, NULL }
};

static struct_desc qs_fixed_desc = {
    "Question Section entry fixed part",
    qs_fixed_fields,
    sizeof(struct qs_fixed)
};

/* 4.1.3. Resource record format:
 *                                 1  1  1  1  1  1
 *   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                                               |
 * /                                               /
 * /                      NAME                     /
 * |                                               |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                      TYPE                     |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                     CLASS                     |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                      TTL                      |
 * |                                               |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * |                   RDLENGTH                    |
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
 * /                     RDATA                     /
 * /                                               /
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 */

struct rr_fixed {
    u_int16_t type;
    u_int16_t class;
    u_int32_t ttl;	/* actually signed */
    u_int16_t rdlength;
};


static field_desc rr_fixed_fields[] = {
    { ft_loose_enum, 16/BITS_PER_BYTE, "type", &rr_type_names },
    { ft_loose_enum, 16/BITS_PER_BYTE, "class", &rr_class_names },
    { ft_nat, 32/BITS_PER_BYTE, "TTL", NULL },
    { ft_nat, 16/BITS_PER_BYTE, "RD length", NULL },
    { ft_end, 0, NULL, NULL }
};

static struct_desc rr_fixed_desc = {
    "Resource Record fixed part",
    rr_fixed_fields,
    /* note: following is tricky: avoids padding problems */
    offsetof(struct rr_fixed, rdlength) + sizeof(u_int16_t)
};

/* RFC 1035 3.3.14: TXT RRs have text in the RDATA field.
 * It is in the form of a sequence of <character-string>s as described in 3.3.
 * unpack_txt_rdata() deals with this peculiar representation.
 */

/* RFC 2535 3.1 KEY RDATA format:
 *
 *                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |             flags             |    protocol   |   algorithm   |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                                                               /
 * /                          public key                           /
 * /                                                               /
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
 */

struct key_rdata {
    u_int16_t flags;
    u_int8_t protocol;
    u_int8_t algorithm;
};

static field_desc key_rdata_fields[] = {
    { ft_nat, 16/BITS_PER_BYTE, "flags", NULL },
    { ft_nat, 8/BITS_PER_BYTE, "protocol", NULL },
    { ft_nat, 8/BITS_PER_BYTE, "algorithm", NULL },
    { ft_end, 0, NULL, NULL }
};

static struct_desc key_rdata_desc = {
    "KEY RR RData fixed part",
    key_rdata_fields,
    sizeof(struct key_rdata)
};

/* RFC 2535 4.1 SIG RDATA format:
 *
 *                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |        type covered           |  algorithm    |     labels    |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                         original TTL                          |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                      signature expiration                     |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                      signature inception                      |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |            key  tag           |                               |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+         signer's name         +
 * |                                                               /
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-/
 * /                                                               /
 * /                            signature                          /
 * /                                                               /
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 */

struct sig_rdata {
    u_int16_t type_covered;
    u_int8_t algorithm;
    u_int8_t labels;
    u_int32_t original_ttl;
    u_int32_t sig_expiration;
    u_int32_t sig_inception;
    u_int16_t key_tag;
};

static field_desc sig_rdata_fields[] = {
    { ft_nat, 16/BITS_PER_BYTE, "type_covered", NULL},
    { ft_nat, 8/BITS_PER_BYTE, "algorithm", NULL},
    { ft_nat, 8/BITS_PER_BYTE, "labels", NULL},
    { ft_nat, 32/BITS_PER_BYTE, "original ttl", NULL},
    { ft_nat, 32/BITS_PER_BYTE, "sig expiration", NULL},
    { ft_nat, 32/BITS_PER_BYTE, "sig inception", NULL},
    { ft_nat, 16/BITS_PER_BYTE, "key tag", NULL},
    { ft_end, 0, NULL, NULL }
};

static struct_desc sig_rdata_desc = {
    "SIG RR RData fixed part",
    sig_rdata_fields,
    sizeof(struct sig_rdata)
};

/* handle a KEY Resource Record. */

#ifdef USE_KEYRR
static err_t
process_key_rr(u_char *ptr, size_t len
, bool doit	/* should we capture information? */
, enum dns_auth_level dns_auth_level
, struct adns_continuation *const cr)
{
    pb_stream pbs;
    struct key_rdata kr;

    if (len < sizeof(struct key_rdata))
	return "KEY Resource Record's RD Length is too small";

    init_pbs(&pbs, ptr, len, "KEY RR");

    if (!in_struct(&kr, &key_rdata_desc, &pbs, NULL))
	return "failed to get fixed part of KEY Resource Record RDATA";

    if (kr.protocol == 4	/* IPSEC (RFC 2535 3.1.3) */
    && kr.algorithm == 1	/* RSA/MD5 (RFC 2535 3.2) */
    && (kr.flags & 0x8000) == 0	/* use for authentication (3.1.2) */
    && (kr.flags & 0x2CF0) == 0)	/* must be zero */
    {
	/* we have what seems to be a tasty key */

	if (doit)
	{
	    chunk_t k;

	    setchunk(k, pbs.cur, pbs_left(&pbs));
	    TRY(add_public_key(&cr->id, dns_auth_level, PUBKEY_ALG_RSA, &k
		, &cr->keys_from_dns));
	}
    }
    return NULL;
}
#endif /* USE_KEYRR */


/* unpack TXT rr RDATA into C string.
 * A sequence of <character-string>s as described in RFC 1035 3.3.
 * We concatenate them.
 */
static err_t
unpack_txt_rdata(u_char *d, size_t dlen, const u_char *s, size_t slen)
{
    size_t i = 0
	, o = 0;

    while (i < slen)
    {
	size_t cl = s[i++];

	if (i + cl > slen)
	    return "TXT rr RDATA representation malformed";

	if (o + cl >= dlen)
	    return "TXT rr RDATA too large";

	memcpy(d + o, s + i, cl);
	i += cl;
	o += cl;
    }
    d[o] = '\0';
    if (strlen(d) != o)
	return "TXT rr RDATA contains a NUL";

    return NULL;
}

static err_t
process_txt_rr(u_char *rdata, size_t rdlen
, bool doit	/* should we capture information? */
, enum dns_auth_level dns_auth_level
, struct adns_continuation *const cr)
{
    u_char str[RSA_MAX_ENCODING_BYTES * 8 / 6 + 20];	/* space for unpacked RDATA */

    TRY(unpack_txt_rdata(str, sizeof(str), rdata, rdlen));
    return process_txt_rr_body(str, doit, dns_auth_level, cr);
}

static err_t
process_answer_section(pb_stream *pbs
, bool doit	/* should we capture information? */
, enum dns_auth_level *dns_auth_level
, u_int16_t ancount	/* number of RRs in the answer section */
, struct adns_continuation *const cr)
{
    const int type = cr->query.type;	/* type of RR of interest */
    unsigned c;

    DBG(DBG_DNS, DBG_log("*Answer Section:"));

    for (c = 0; c != ancount; c++)
    {
	struct rr_fixed rrf;
	size_t tail;

	/* ??? do we need to match the name? */

	TRY(eat_name_helpfully(pbs, "Answer Section"));

	if (!in_struct(&rrf, &rr_fixed_desc, pbs, NULL))
	    return "failed to get fixed part of Answer Section Resource Record";

	if (rrf.rdlength > pbs_left(pbs))
	    return "RD Length extends beyond end of message";

	/* ??? should we care about ttl? */

	tail = rrf.rdlength;

	if (rrf.type == type && rrf.class == C_IN)
	{
	    err_t ugh = NULL;

	    switch (type)
	    {
#ifdef USE_KEYRR
	    case T_KEY:
		ugh = process_key_rr(pbs->cur, tail, doit, *dns_auth_level, cr);
		break;
#endif /* USE_KEYRR */
	    case T_TXT:
		ugh = process_txt_rr(pbs->cur, tail, doit, *dns_auth_level, cr);
		break;
	    case T_SIG:
		/* Check if SIG RR authenticates what we are learning.
		 * The RRset covered by a SIG must have the same owner,
		 * class, and type.
		 * For us, the class is always C_IN, so that matches.
		 * We decode the SIG RR's fixed part to check
		 * that the type_covered field matches our query type
		 * (this may be redundant).
		 * We don't check the owner (apparently this is the
		 * name on the record) -- we assume that it matches
		 * or we would not have been given this SIG in the
		 * Answer Section.
		 *
		 * We only look on first pass, and only if we've something
		 * to learn.  This cuts down on useless decoding.
		 */
		if (!doit && *dns_auth_level == DAL_UNSIGNED)
		{
		    struct sig_rdata sr;

		    if (!in_struct(&sr, &sig_rdata_desc, pbs, NULL))
			ugh = "failed to get fixed part of SIG Resource Record RDATA";
		    else if (sr.type_covered == type)
			*dns_auth_level = DAL_SIGNED;
		}
		break;
	    default:
		ugh = builddiag("unexpected RR type %d", type);
		break;
	    }
	    if (ugh != NULL)
		return ugh;
	}
	in_raw(NULL, tail, pbs, "RR RDATA");
    }

    return doit
	&& cr->gateways_from_dns == NULL
#ifdef USE_KEYRR
	&& cr->keys_from_dns == NULL
#endif /* USE_KEYRR */
	? builddiag("no suitable %s record found in DNS", rr_typename(type))
	: NULL;
}

/* process DNS answer -- TXT or KEY query */

static err_t
process_dns_answer(struct adns_continuation *const cr
, u_char ans[], int anslen)
{
    const int type = cr->query.type;	/* type of record being sought */
    int r;	/* all-purpose return value holder */
    u_int16_t c;	/* number of current RR in current answer section */
    pb_stream pbs;
    u_int8_t *ans_start;	/* saved position of answer section */
    struct qr_header qr_header;
    enum dns_auth_level dns_auth_level;

    init_pbs(&pbs, ans, anslen, "Query Response Message");

    /* decode and check header */

    if (!in_struct(&qr_header, &qr_header_desc, &pbs, NULL))
	return "malformed header";

    /* ID: nothing to do with us */

    /* stuff -- lots of things */
    if ((qr_header.stuff & QRS_QR) == 0)
	return "not a response?!?";

    if (((qr_header.stuff >> QRS_OPCODE_SHIFT) & QRS_OPCODE_MASK) != QRSO_QUERY)
	return "unexpected opcode";

    /* I don't think we care about AA */

    if (qr_header.stuff & QRS_TC)
	return "response truncated";

    /* I don't think we care about RD, RA, or CD */

    /* AD means "authentic data" */
    dns_auth_level = qr_header.stuff & QRS_AD? DAL_UNSIGNED : DAL_NOTSEC;

    if (qr_header.stuff & QRS_Z)
	return "Z bit is not zero";

    r = (qr_header.stuff >> QRS_RCODE_SHIFT) & QRS_RCODE_MASK;
    if (r != 0)
	return r < (int)elemsof(rcode_text)? rcode_text[r] : "unknown rcode";

    if (qr_header.ancount == 0)
	return builddiag("no %s RR found by DNS", rr_typename(type));

    /* end of header checking */

    /* Question Section processing */

    /* 4.1.2. Question section format:
     *                                 1  1  1  1  1  1
     *   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
     * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
     * |                                               |
     * /                     QNAME                     /
     * /                                               /
     * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
     * |                     QTYPE                     |
     * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
     * |                     QCLASS                    |
     * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
     */

    DBG(DBG_DNS, DBG_log("*Question Section:"));

    for (c = 0; c != qr_header.qdcount; c++)
    {
	struct qs_fixed qsf;

	TRY(eat_name_helpfully(&pbs, "Question Section"));

	if (!in_struct(&qsf, &qs_fixed_desc, &pbs, NULL))
	    return "failed to get fixed part of Question Section";

	if (qsf.qtype != type)
	    return "unexpected QTYPE in Question Section";

	if (qsf.qclass != C_IN)
	    return "unexpected QCLASS in Question Section";
    }

    /* rest of sections are made up of Resource Records */

    /* Answer Section processing -- error checking, noting T_SIG */

    ans_start = pbs.cur;	/* remember start of answer section */

    TRY(process_answer_section(&pbs, FALSE, &dns_auth_level
	, qr_header.ancount, cr));

    /* Authority Section processing (just sanity checking) */

    DBG(DBG_DNS, DBG_log("*Authority Section:"));

    for (c = 0; c != qr_header.nscount; c++)
    {
	struct rr_fixed rrf;
	size_t tail;

	TRY(eat_name_helpfully(&pbs, "Authority Section"));

	if (!in_struct(&rrf, &rr_fixed_desc, &pbs, NULL))
	    return "failed to get fixed part of Authority Section Resource Record";

	if (rrf.rdlength > pbs_left(&pbs))
	    return "RD Length extends beyond end of message";

	/* ??? should we care about ttl? */

	tail = rrf.rdlength;

	in_raw(NULL, tail, &pbs, "RR RDATA");
    }

    /* Additional Section processing (just sanity checking) */

    DBG(DBG_DNS, DBG_log("*Additional Section:"));

    for (c = 0; c != qr_header.arcount; c++)
    {
	struct rr_fixed rrf;
	size_t tail;

	TRY(eat_name_helpfully(&pbs, "Additional Section"));

	if (!in_struct(&rrf, &rr_fixed_desc, &pbs, NULL))
	    return "failed to get fixed part of Additional Section Resource Record";

	if (rrf.rdlength > pbs_left(&pbs))
	    return "RD Length extends beyond end of message";

	/* ??? should we care about ttl? */

	tail = rrf.rdlength;

	in_raw(NULL, tail, &pbs, "RR RDATA");
    }

    /* done all sections */

    /* ??? is padding legal, or can we complain if more left in record? */

    /* process Answer Section again -- accept contents */

    pbs.cur = ans_start;	/* go back to start of answer section */

    return process_answer_section(&pbs, TRUE, &dns_auth_level
	, qr_header.ancount, cr);
}

#endif /* ! USE_LWRES */


/****************************************************************/

static err_t
build_dns_name(u_char name_buf[NS_MAXDNAME + 2]
, unsigned long serial USED_BY_DEBUG
, const struct id *id
, const char *typename USED_BY_DEBUG
, const char *gwname   USED_BY_DEBUG)
{
    /* note: all end in "." to suppress relative searches */
    id = resolve_myid(id);
    switch (id->kind)
    {
    case ID_IPV4_ADDR:
    {
	/* XXX: this is really ugly and only temporary until addrtot can
	 *      generate the correct format
	 */
	const unsigned char *b;
	size_t bl USED_BY_DEBUG = addrbytesptr(&id->ip_addr, &b);

	passert(bl == 4);
	snprintf(name_buf, NS_MAXDNAME + 2, "%d.%d.%d.%d.in-addr.arpa."
	    , b[3], b[2], b[1], b[0]);
	break;
    }

    case ID_IPV6_ADDR:
    {
	/* ??? is this correct? */
	const unsigned char *b;
	size_t bl;
	u_char *op = name_buf;
	static const char suffix[] = "IP6.INT.";

	for (bl = addrbytesptr(&id->ip_addr, &b); bl-- != 0; )
	{
	    if (op + 4 + sizeof(suffix) >= name_buf + NS_MAXDNAME + 1)
		return "IPv6 reverse name too long";
	    op += sprintf(op, "%x.%x.", b[bl] & 0xF, b[bl] >> 4);
	}
	strcpy(op, suffix);
	break;
    }

    case ID_FQDN:
	/* strip trailing "." characters, then add one */
	{
	    size_t il = id->name.len;

	    while (il > 0 && id->name.ptr[il - 1] == '.')
		il--;
	    if (il > NS_MAXDNAME)
		return "FQDN too long for domain name";

	    memcpy(name_buf, id->name.ptr, il);
	    strcpy(name_buf + il, ".");
	}
	break;

    default:
	return "can only query DNS for key for ID that is a FQDN, IPV4_ADDR, or IPV6_ADDR";
    }

    DBG(DBG_CONTROL | DBG_DNS, DBG_log("DNS query %lu for %s for %s (gw: %s)"
	, serial, typename, name_buf, gwname));
    return NULL;
}

void
gw_addref(struct gw_info *gw)
{
    if (gw != NULL)
    {
	DBG(DBG_DNS, DBG_log("gw_addref: %p refcnt: %d++", gw, gw->refcnt))
	gw->refcnt++;
    }
}

void
gw_delref(struct gw_info **gwp)
{
    struct gw_info *gw = *gwp;

    if (gw != NULL)
    {
	DBG(DBG_DNS, DBG_log("gw_delref: %p refcnt: %d--", gw, gw->refcnt));

	passert(gw->refcnt != 0);
	gw->refcnt--;
	if (gw->refcnt == 0)
	{
	    free_id_content(&gw->client_id);
	    free_id_content(&gw->gw_id);
	    if (gw->gw_key_present)
		unreference_key(&gw->key);
	    gw_delref(&gw->next);
	    pfree(gw);	/* trickery could make this a tail-call */
	}
	*gwp = NULL;
    }
}

static int adns_in_flight = 0;	/* queries outstanding */

/* Start an asynchronous DNS query.
 *
 * For KEY record, the result will be a list in cr->keys_from_dns.
 * For TXT records, the result will be a list in cr->gateways_from_dns.
 *
 * If sgw_id is null, only consider TXT records that specify an
 * IP address for the gatway: we need this in the initiation case.
 *
 * If sgw_id is non-null, only consider TXT records that specify
 * this id as the security gatway; this is useful to the Responder
 * for confirming claims of gateways.
 *
 * Continuation cr gives information for continuing when the result shows up.
 *
 * Two kinds of errors must be handled: synchronous (immediate)
 * and asynchronous.  Synchronous errors are indicated by the returned
 * value of start_adns_query; in this case, the continuation will
 * have been freed and the continuation routine will not be called.
 * Asynchronous errors are indicated by the ugh parameter passed to the
 * continuation routine.
 *
 * After the continuation routine has completed, handle_adns_answer
 * will free the continuation.  The continuation routine should have
 * freed any axiliary resources.
 *
 * Note: in the synchronous error case, start_adns_query will have
 * freed the continuation; this means that the caller will have to
 * be very careful to release any auxiliary resources that were in
 * the continuation record without using the continuation record.
 *
 * Either there will be an error result passed to the continuation routine,
 * or the results will be in cr->keys_from_dns or cr->gateways_from_dns.
 * The result variables must by left NULL by the continutation routine.
 * The continuation routine is responsible for establishing and
 * disestablishing any logging context (whack_log_fd, cur_*).
 */

static struct adns_continuation *continuations = NULL;	/* newest of queue */
static struct adns_continuation *next_query = NULL;	/* oldest not sent */

static struct adns_continuation *
continuation_for_qtid(unsigned long qtid)
{
    struct adns_continuation *cr = NULL;

    if (qtid != 0)
	for (cr = continuations; cr != NULL && cr->qtid != qtid; cr = cr->previous)
	    ;
    return cr;
}

static void
release_adns_continuation(struct adns_continuation *cr)
{
    passert(cr != next_query);
    gw_delref(&cr->gateways_from_dns);
#ifdef USE_KEYRR
    free_public_keys(&cr->keys_from_dns);
#endif /* USE_KEYRR */
    unshare_id_content(&cr->id);
    unshare_id_content(&cr->sgw_id);

    /* unlink from doubly-linked list */
    if (cr->next == NULL)
    {
	passert(continuations == cr);
	continuations = cr->previous;
    }
    else
    {
	passert(cr->next->previous == cr);
	cr->next->previous = cr->previous;
    }

    if (cr->previous != NULL)
    {
	passert(cr->previous->next == cr);
	cr->previous->next = cr->next;
    }

    pfree(cr);
}

err_t
start_adns_query(const struct id *id	/* domain to query */
, const struct id *sgw_id	/* if non-null, any accepted gw_info must match */
, int type	/* T_TXT or T_KEY, selecting rr type of interest */
, cont_fn_t cont_fn
, struct adns_continuation *cr)
{
    static unsigned long qtid = 1;	/* query transaction id; NOTE: static */
    const char *typename = rr_typename(type);
    char gwidb[BUF_LEN];

    if(adns_pid == 0
       && adns_restart_count < ADNS_RESTART_MAX)
    {
	plog("ADNS helper was not running. Restarting attempt %d",adns_restart_count);
	init_adns();
    }


    /* Splice this in at head of doubly-linked list of continuations.
     * Note: this must be done before any release_adns_continuation().
     */
    cr->next = NULL;
    cr->previous = continuations;
    if (continuations != NULL)
    {
	passert(continuations->next == NULL);
	continuations->next = cr;
    }
    continuations = cr;

    cr->qtid = qtid++;
    cr->type = type;
    cr->cont_fn = cont_fn;
    cr->id = *id;
    unshare_id_content(&cr->id);
    cr->sgw_specified = sgw_id != NULL;
    cr->sgw_id = cr->sgw_specified? *sgw_id : empty_id;
    unshare_id_content(&cr->sgw_id);
    cr->gateways_from_dns = NULL;
#ifdef USE_KEYRR
    cr->keys_from_dns = NULL;
#endif /* USE_KEYRR */

#ifdef DEBUG
    cr->debugging = cur_debugging;
#else
    cr->debugging = LEMPTY;
#endif

    idtoa(&cr->sgw_id, gwidb, sizeof(gwidb));

    zero(&cr->query);

    {
	err_t ugh = build_dns_name(cr->query.name_buf, cr->qtid
	    , id, typename, gwidb);

	if (ugh != NULL)
	{
	    release_adns_continuation(cr);
	    return ugh;
	}
    }

    if (next_query == NULL)
	next_query = cr;

    unsent_ADNS_queries = TRUE;

    return NULL;
}

/* send remaining ADNS queries (until pipe full or none left)
 *
 * This is a co-routine, so it uses static variables to
 * preserve state across calls.
 */
bool unsent_ADNS_queries = FALSE;

void
send_unsent_ADNS_queries(void)
{
    static const unsigned char *buf_end = NULL;	/* NOTE STATIC */
    static const unsigned char *buf_cur = NULL;	/* NOTE STATIC */

    if (adns_qfd == NULL_FD)
	return;	/* nothing useful to do */

    for (;;)
    {
	if (buf_cur != buf_end)
	{
	    static int try = 0;	/* NOTE STATIC */
	    size_t n = buf_end - buf_cur;
	    ssize_t r = write(adns_qfd, buf_cur, n);

	    if (r == -1)
	    {
		switch (errno)
		{
		case EINTR:
		    continue;	/* try again now */
		case EAGAIN:
		    DBG(DBG_DNS, DBG_log("EAGAIN writing to ADNS"));
		    break;	/* try again later */
		default:
		    try++;
		    log_errno((e, "error %d writing DNS query", try));
		    break;	/* try again later */
		}
		unsent_ADNS_queries = TRUE;
		break;	/* done! */
	    }
	    else
	    {
		passert(r >= 0);
		try = 0;
		buf_cur += r;
	    }
	}
	else
	{
	    if (next_query == NULL)
	    {
		unsent_ADNS_queries = FALSE;
		break;	/* done! */
	    }

#ifdef USE_LWRES
	    next_query->used = FALSE;
	    {
		/* NOTE STATIC: */
		static unsigned char qbuf[LWDNSQ_CMDBUF_LEN + 1];	/* room for NUL */

		snprintf(qbuf, sizeof(qbuf), "%s %lu %s\n"
		    , rr_typename(next_query->type)
		    , next_query->qtid
		    , next_query->query.name_buf);
		DBG(DBG_DNS, DBG_log("lwdnsq query: %.*s", (int)(strlen(qbuf) - 1), qbuf));
		buf_cur = qbuf;
		buf_end = qbuf + strlen(qbuf);
	    }
#else /* !USE_LWRES */
	    next_query->query.debugging = next_query->debugging;
	    next_query->query.serial = next_query->qtid;
	    next_query->query.len = sizeof(next_query->query);
	    next_query->query.qmagic = ADNS_Q_MAGIC;
	    next_query->query.type = next_query->type;
	    buf_cur = (const void *)&next_query->query;
	    buf_end = buf_cur + sizeof(next_query->query);
#endif /* !USE_LWRES */
	    next_query = next_query->next;
	    adns_in_flight++;
	}
    }
}

#ifdef USE_LWRES
/* Process a line of lwdnsq answer.
 * Returns with error message iff lwdnsq result is malformed.
 * Most errors will be in DNS data and will be handled by cr->cont_fn.
 */
static err_t
process_lwdnsq_answer(char *ts)
{
    err_t ugh = NULL;
    char *rest;
    char *p;
    char *endofnumber;
    struct adns_continuation *cr = NULL;
    unsigned long qtid;
    time_t anstime;	/* time of answer */
    char *atype;	/* type of answer */
    long ttl;	/* ttl of answer; int, but long for conversion */
    bool AuthenticatedData = FALSE;
    static char scratch_null_str[] = "";	/* cannot be const, but isn't written */

    /* query transaction id */
    rest = ts;
    p = strsep(&rest, " \t");
    if (p == NULL)
	return "lwdnsq: answer missing query transaction ID";

    qtid = strtoul(p, &endofnumber, 10);
    if (*endofnumber != '\0')
	return "lwdnsq: malformed query transaction ID";

    cr = continuation_for_qtid(qtid);
    if (qtid != 0 && cr == NULL)
	return "lwdnsq: unrecognized qtid";     /* can't happen! */

    /* time */
    p = strsep(&rest, " \t");
    if (p == NULL)
	return "lwdnsq: missing time";

    anstime = strtoul(p, &endofnumber, 10);
    if (*endofnumber != '\0')
	return "lwdnsq: malformed time";

    /* TTL */
    p = strsep(&rest, " \t");
    if (p == NULL)
	return "lwdnsq: missing TTL";

    ttl = strtol(p, &endofnumber, 10);
    if (*endofnumber != '\0')
	return "lwdnsq: malformed TTL";

    /* type */
    atype = strsep(&rest, " \t");
    if (atype == NULL)
	return "lwdnsq: missing type";

    /* if rest is NULL, make it "", otherwise eat whitespace after type */
    rest = rest == NULL? scratch_null_str : rest + strspn(rest, " \t");

    if (strncasecmp(atype, "AD-", 3) == 0)
    {
	AuthenticatedData = TRUE;
	atype += 3;
    }

    /* deal with each type */

    if (cr == NULL)
    {
	/* we don't actually know which this applies to */
	return builddiag("lwdnsq: 0 qtid invalid with %s", atype);
    }
    else if (strcaseeq(atype, "START"))
    {
	/* ignore */
    }
    else if (strcaseeq(atype, "DONE"))
    {
	if (!cr->used)
	{
	    /* "no results returned by lwdnsq" should not happen */
	    cr->cont_fn(cr
		, cr->gateways_from_dns == NULL
#ifdef USE_KEYRR
		  && cr->keys_from_dns == NULL
#endif /* USE_KEYRR */
		    ? "no results returned by lwdnsq" : NULL);
	    cr->used = TRUE;
	}
	reset_globals();
	release_adns_continuation(cr);
	adns_in_flight--;
    }
    else if (strcaseeq(atype, "RETRY"))
    {
	if (!cr->used)
	{
	    cr->cont_fn(cr, rest);
	    cr->used = TRUE;
	}
    }
    else if (strcaseeq(atype, "FATAL"))
    {
	if (!cr->used)
	{
	    cr->cont_fn(cr, rest);
	    cr->used = TRUE;
	}
    }
    else if (strcaseeq(atype, "DNSSEC"))
    {
	/* ignore */
    }
    else if (strcaseeq(atype, "NAME"))
    {
	/* ignore */
    }
    else if (strcaseeq(atype, "TXT"))
    {
	char *end = rest + strlen(rest);
	err_t txt_ugh;

	if (*rest == '"' && end[-1] == '"')
	{
	    /* strip those pesky quotes */
	    rest++;
	    *--end = '\0';
	}

	txt_ugh = process_txt_rr_body(rest
	    , TRUE
	    , AuthenticatedData? DAL_SIGNED : DAL_NOTSEC
	    , cr);

	if (txt_ugh != NULL)
	{
	    DBG(DBG_DNS,
		DBG_log("error processing TXT resource record (%s) while processing: %s"
			, txt_ugh, rest));
	    cr->cont_fn(cr, txt_ugh);
	    cr->used = TRUE;
	}
    }
    else if (strcaseeq(atype, "SIG"))
    {
	/* record the SIG records for posterity */
	if (cr->last_info != NULL)
	{
	    pfreeany(cr->last_info->dns_sig);
	    cr->last_info->dns_sig = clone_str(rest, "sigrecord");
	}
    }
    else if (strcaseeq(atype, "A"))
    {
	/* ignore */
    }
    else if (strcaseeq(atype, "AAAA"))
    {
	/* ignore */
    }
    else if (strcaseeq(atype, "CNAME"))
    {
	/* ignore */
    }
    else if (strcaseeq(atype, "CNAMEFROM"))
    {
	/* ignore */
    }
    else if (strcaseeq(atype, "PTR"))
    {
	/* ignore */
    }
#ifdef USE_KEYRR
    else if (strcaseeq(atype, "KEY"))
    {
	err_t key_ugh = process_lwdnsq_key(rest
	    , AuthenticatedData? DAL_SIGNED : DAL_NOTSEC
	    , cr);

	if (key_ugh != NULL)
	{
	    DBG(DBG_DNS,
		DBG_log("error processing KEY resource record (%s) while processing: %s"
			, key_ugh, rest));
	    cr->cont_fn(cr, key_ugh);
	    cr->used = TRUE;
	}
    }
#endif /* USE_KEYRR */
    else
    {
	ugh = "lwdnsq: unrecognized type";
    }
    return ugh;
}
#endif /* USE_LWRES */

static void
recover_adns_die(void)
{
    struct adns_continuation *cr = NULL;
    
    adns_pid = 0;
    if(adns_restart_count < ADNS_RESTART_MAX) {
	adns_restart_count++;

	/* next DNS query will restart it */

	/* we have to walk the list of the outstanding requests,
	 * and redo them!
	 */

	cr = continuations;

	/* find the head of the list */
	if(continuations != NULL) {
	    for (; cr->previous != NULL; cr = cr->previous);
	}
	    
	next_query = cr;

	if(next_query != NULL) {
	    unsent_ADNS_queries = TRUE;
	}
    }
}

void reset_adns_restart_count(void)
{
    adns_restart_count=0;
}

void
handle_adns_answer(void)
{
  /* These are retained across calls to handle_adns_answer. */
    static size_t buflen = 0;	/* bytes in answer buffer */
#ifndef USE_LWRES
    static struct adns_answer buf;
#else /* USE_LWRES */
    static char buf[LWDNSQ_RESULT_LEN_MAX];
    static char buf_copy[LWDNSQ_RESULT_LEN_MAX];
#endif /* USE_LWRES */

    ssize_t n;

    passert(buflen < sizeof(buf));
    n = read(adns_afd, (unsigned char *)&buf + buflen, sizeof(buf) - buflen);

    if (n < 0)
    {
	if (errno != EINTR)
	{
	    log_errno((e, "error reading answer from adns"));
	    /* ??? how can we recover? */
	}
	n = 0;	/* now n reflects amount read */
    }
    else if (n == 0)
    {
	/* EOF */
	if (adns_in_flight != 0)
	{
	    plog("EOF from ADNS with %d queries outstanding (restarts %d)"
		 , adns_in_flight, adns_restart_count);
	    recover_adns_die();
	}
	if (buflen != 0)
	{
	    plog("EOF from ADNS with %lu bytes of a partial answer outstanding"
		 "(restarts %d)"
		 , (unsigned long)buflen
		 ,  adns_restart_count);
	    recover_adns_die();
	}
	stop_adns();
	return;
    }
    else
    {
	passert(adns_in_flight > 0);
    }

    buflen += n;
#ifndef USE_LWRES
    while (buflen >= offsetof(struct adns_answer, ans) && buflen >= buf.len)
    {
	/* we've got a tasty answer -- process it */
	err_t ugh;
	struct adns_continuation *cr = continuation_for_qtid(buf.serial);	/* assume it works */
	const char *typename = rr_typename(cr->query.type);
	const char *name_buf = cr->query.name_buf;

#ifdef USE_KEYRR
	passert(cr->keys_from_dns == NULL);
#endif /* USE_KEYRR */
	passert(cr->gateways_from_dns == NULL);
	adns_in_flight--;
	if (buf.result == -1)
	{
	    /* newer resolvers support statp->res_h_errno as well as h_errno.
	     * That might be better, but older resolvers don't.
	     * See resolver(3), if you have it.
	     * The undocumented(!) h_errno values are defined in
	     * /usr/include/netdb.h.
	     */
	    switch (buf.h_errno_val)
	    {
	    case NO_DATA:
		ugh = builddiag("no %s record for %s", typename, name_buf);
		break;
	    case HOST_NOT_FOUND:
		ugh = builddiag("no host %s for %s record", name_buf, typename);
		break;
	    default:
		ugh = builddiag("failure querying DNS for %s of %s: %s"
		    , typename, name_buf, hstrerror(buf.h_errno_val));
		break;
	    }
	}
	else if (buf.result > (int) sizeof(buf.ans))
	{
	    ugh = builddiag("(INTERNAL ERROR) answer too long (%ld) for buffer"
		, (long)buf.result);
	}
	else
	{
	    ugh = process_dns_answer(cr, buf.ans, buf.result);
	    if (ugh != NULL)
		ugh = builddiag("failure processing %s record of DNS answer for %s: %s"
		    , typename, name_buf, ugh);
	}
	DBG(DBG_RAW | DBG_CRYPT | DBG_PARSING | DBG_CONTROL | DBG_DNS,
	    DBG_log(BLANK_FORMAT);
	    if (ugh == NULL)
		DBG_log("asynch DNS answer %lu for %s of %s"
		    , cr->query.serial, typename, name_buf);
	    else
		DBG_log("asynch DNS answer %lu %s", cr->query.serial, ugh);
	    );

	passert(GLOBALS_ARE_RESET());
	cr->cont_fn(cr, ugh);
	reset_globals();
	release_adns_continuation(cr);

	/* shift out answer that we've consumed */
	buflen -= buf.len;
	memmove((unsigned char *)&buf, (unsigned char *)&buf + buf.len, buflen);
    }
#else /* USE_LWRES */
    for (;;)
    {
	err_t ugh;
	char *nlp = memchr(buf, '\n', buflen);

	if (nlp == NULL)
	    break;

	/* we've got a line */
	*nlp++ = '\0';

	DBG(DBG_RAW | DBG_CRYPT | DBG_PARSING | DBG_CONTROL | DBG_DNS
	    , DBG_log("lwdns: %s", buf));

	/* process lwdnsq_answer may modify buf, so make a copy. */
	buf_copy[0]='\0';
	strncat(buf_copy, buf, sizeof(buf_copy));

	ugh = process_lwdnsq_answer(buf_copy);
	if (ugh != NULL)
	    plog("failure processing lwdnsq output: %s; record: %s"
		 , ugh, buf);

	passert(GLOBALS_ARE_RESET());
	reset_globals();

	/* shift out answer that we've consumed */
	buflen -= nlp - buf;
	memmove(buf, nlp, buflen);
    }
#endif /* USE_LWRES */
}
