/*
Copyright (c) 2003-2006 by Juliusz Chroboczek

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include "polipo.h"

#ifndef NO_STANDARD_RESOLVER
#ifndef NO_FANCY_RESOLVER
int dnsUseGethostbyname = 1;
#else 
const int dnsUseGethostbyname = 3;
#endif
#else
#ifndef NO_FANCY_RESOLVER
const int dnsUseGethostbyname = 0;
#else
#error use no resolver at all?
#endif
#endif

#ifndef NO_FANCY_RESOLVER
AtomPtr dnsNameServer = NULL;
int dnsMaxTimeout = 60;
#endif

#ifndef NO_STANDARD_RESOLVER
int dnsGethostbynameTtl = 1200;
#endif

int dnsNegativeTtl = 120;

#ifdef HAVE_IPv6
int dnsQueryIPv6 = 2;
#else
const int dnsQueryIPv6 = 0;
#endif

typedef struct _DnsQuery {
    unsigned id;
    AtomPtr name;
    ObjectPtr object;
    AtomPtr inet4, inet6;
    time_t ttl4, ttl6;
    time_t time;
    int timeout;
    TimeEventHandlerPtr timeout_handler;
    struct _DnsQuery *next;
} DnsQueryRec, *DnsQueryPtr;

union {
    struct sockaddr sa;
    struct sockaddr_in sin;
#ifdef HAVE_IPv6
    struct sockaddr_in6 sin6;
#endif
} nameserverAddress_storage;

#ifndef NO_FANCY_RESOLVER
static AtomPtr atomLocalhost, atomLocalhostDot;

#define nameserverAddress nameserverAddress_storage.sa

static DnsQueryPtr inFlightDnsQueries;
static DnsQueryPtr inFlightDnsQueriesLast;
#endif

static int really_do_gethostbyname(AtomPtr name, ObjectPtr object);
static int really_do_dns(AtomPtr name, ObjectPtr object);

#ifndef NO_FANCY_RESOLVER
static int stringToLabels(char *buf, int offset, int n, char *string);
static int labelsToString(char *buf, int offset, int n, char *d, 
                          int m, int *j_return);
static int dnsBuildQuery(int id, char *buf, int offset, int n,
                         AtomPtr name, int af);
static int dnsReplyHandler(int abort, FdEventHandlerPtr event);
static int dnsReplyId(char *buf, int offset, int n, int *id_return);
static int dnsDecodeReply(char *buf, int offset, int n,
                          int *id_return,
                          AtomPtr *name_return, AtomPtr *value_return,
                          int *af_return, unsigned *ttl_return);
static int dnsHandler(int status, ConditionHandlerPtr chandler);
static int dnsGethostbynameFallback(int id, AtomPtr message);
static int sendQuery(DnsQueryPtr query);

static int idSeed;
#endif

#ifndef NO_FANCY_RESOLVER
static int
parseResolvConf(char *filename)
{
    FILE *f;
    char buf[512];
    char *p, *q;
    int n;
    AtomPtr nameserver = NULL;

    f = fopen(filename, "r");
    if(f == NULL) {
        do_log_error(L_ERROR, errno, "DNS: couldn't open %s", filename);
        return 0;
    }

    while(1) {
        p = fgets(buf, 512, f);
        if(p == NULL)
            break;

        n = strlen(buf);
        if(buf[n - 1] != '\n') {
            int c;
            do_log(L_WARN, "DNS: overly long line in %s -- skipping.\n",
                   filename);
            do {
                c = fgetc(f);
                if(c == EOF)
                    break;
            } while(c != '\n');
            if(c == EOF)
                break;
        }
        
        while(*p == ' ' || *p == '\t')
            p++;
        if(strcasecmp_n("nameserver", p, 10) != 0)
            continue;
        p += 10;
        while(*p == ' ' || *p == '\t')
            p++;
        q = p;
        while(*q == '.' || *q == ':' || digit(*q) || letter(*q))
            q++;
        if(*q != ' ' && *q != '\t' && *q != '\r' && *q != '\n') {
            do_log(L_WARN, "DNS: couldn't parse line in %s -- skipping.\n",
                   filename);
            continue;
        }
        nameserver = internAtomLowerN(p, q - p);
        break;
    }

    fclose(f);
    if(nameserver) {
        dnsNameServer = nameserver;
        return 1;
    } else {
        return 0;
    }
}
#endif

void
preinitDns()
{
#ifdef HAVE_IPv6
    int fd;
#endif

    assert(sizeof(struct in_addr) == 4);
#ifdef HAVE_IPv6
    assert(sizeof(struct in6_addr) == 16);
#endif

#ifndef NO_STANDARD_RESOLVER
    CONFIG_VARIABLE(dnsGethostbynameTtl, CONFIG_TIME,
                    "TTL for gethostbyname addresses.");
#endif

#ifdef HAVE_IPv6
    fd = socket(PF_INET6, SOCK_STREAM, 0);
    if(fd < 0) {
        if(errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT) {
            dnsQueryIPv6 = 0;
        } else {
            do_log_error(L_WARN, errno, "DNS: couldn't create socket");
        }
    } else {
        close(fd);
    }
#endif

#ifndef NO_FANCY_RESOLVER
    parseResolvConf("/etc/resolv.conf");
    if(dnsNameServer == NULL || dnsNameServer->string[0] == '\0')
        dnsNameServer = internAtom("127.0.0.1");
    CONFIG_VARIABLE(dnsMaxTimeout, CONFIG_TIME,
                    "Max timeout for DNS queries.");
    CONFIG_VARIABLE(dnsNegativeTtl, CONFIG_TIME,
                    "TTL for negative DNS replies with no TTL.");
    CONFIG_VARIABLE(dnsNameServer, CONFIG_ATOM_LOWER,
                    "The name server to use.");
#ifndef NO_STANDARD_RESOLVER
    CONFIG_VARIABLE(dnsUseGethostbyname, CONFIG_TETRASTATE,
                    "Use the system resolver.");
#endif
#endif

#ifdef HAVE_IPv6
    CONFIG_VARIABLE(dnsQueryIPv6, CONFIG_TETRASTATE,
                    "Query for IPv6 addresses.");
#endif
}

void
initDns()
{
#ifndef NO_FANCY_RESOLVER
    int rc;
    struct timeval t;
    struct sockaddr_in *sin = (struct sockaddr_in*)&nameserverAddress;
#ifdef HAVE_IPv6
    struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&nameserverAddress;
#endif

    atomLocalhost = internAtom("localhost");
    atomLocalhostDot = internAtom("localhost.");
    inFlightDnsQueries = NULL;
    inFlightDnsQueriesLast = NULL;

    gettimeofday(&t, NULL);
    idSeed = t.tv_usec & 0xFFFF;
    sin->sin_family = AF_INET;
    sin->sin_port = htons(53);
    rc = inet_aton(dnsNameServer->string, &sin->sin_addr);
#ifdef HAVE_IPv6
    if(rc != 1) {
        sin6->sin6_family = AF_INET6;
        sin6->sin6_port = htons(53);
        rc = inet_pton(AF_INET6, dnsNameServer->string, &sin6->sin6_addr);
    }
#endif
    if(rc != 1) {
        do_log(L_ERROR, "DNS: couldn't parse name server %s.\n",
               dnsNameServer->string);
        exit(1);
    }
#endif
}

int
do_gethostbyname(char *origname,
                 int count,
                 int (*handler)(int, GethostbynameRequestPtr),
                 void *data)
{
    ObjectPtr object;
    int n = strlen(origname);
    AtomPtr name;
    GethostbynameRequestRec request;
    int done, rc;

    memset(&request, 0, sizeof(request));
    request.name = NULL;
    request.addr = NULL;
    request.error_message = NULL;
    request.count = count;
    request.handler = handler;
    request.data = data;

    if(n <= 0 || n > 131) {
        if(n <= 0) {
            request.error_message = internAtom("empty name");
            do_log(L_ERROR, "Empty DNS name.\n");
            done = handler(-EINVAL, &request);
        } else {
            request.error_message = internAtom("name too long");
            do_log(L_ERROR, "DNS name too long.\n");
            done = handler(-ENAMETOOLONG, &request);
        }
        assert(done);
        releaseAtom(request.error_message);
        return 1;
    }

    if(origname[n - 1] == '.')
        n--;

    name = internAtomLowerN(origname, n);

    if(name == NULL) {
        request.error_message = internAtom("couldn't allocate name");
        do_log(L_ERROR, "Couldn't allocate DNS name.\n");
        done = handler(-ENOMEM, &request);
        assert(done);
        releaseAtom(request.error_message);
        return 1;
    }

    request.name = name;
    request.addr = NULL;
    request.error_message = NULL;
    request.count = count;
    request.object = NULL;
    request.handler = handler;
    request.data = data;

    object = findObject(OBJECT_DNS, name->string, name->length);
    if(object == NULL || objectMustRevalidate(object, NULL)) {
        if(object) {
            privatiseObject(object, 0);
            releaseObject(object);
        }
        object = makeObject(OBJECT_DNS, name->string, name->length, 1, 0,
                            NULL, NULL);
        if(object == NULL) {
            request.error_message = internAtom("Couldn't allocate object");
            do_log(L_ERROR, "Couldn't allocate DNS object.\n");
            done = handler(-ENOMEM, &request);
            assert(done);
            releaseAtom(name);
            releaseAtom(request.error_message);
            return 1;
        }
    }

    if((object->flags & (OBJECT_INITIAL | OBJECT_INPROGRESS)) ==
       OBJECT_INITIAL) {
        if(dnsUseGethostbyname >= 3)
            rc = really_do_gethostbyname(name, object);
        else
            rc = really_do_dns(name, object);
        if(rc < 0) {
            assert(!(object->flags & (OBJECT_INITIAL | OBJECT_INPROGRESS)));
            goto fail;
        }
    }

    if(dnsUseGethostbyname >= 3)
        assert(!(object->flags & OBJECT_INITIAL));

#ifndef NO_FANCY_RESOLVER    
    if(object->flags & OBJECT_INITIAL) {
        ConditionHandlerPtr chandler;
        assert(object->flags & OBJECT_INPROGRESS);
        request.object = object;
        chandler = conditionWait(&object->condition, dnsHandler,
                                 sizeof(request), &request);
        if(chandler == NULL) {
            rc = ENOMEM;
            goto fail;
        }
        return 1;
    }
#endif

    if(object->headers && object->headers->length > 0) {
        if(object->headers->string[0] == DNS_A)
            assert(((object->headers->length - 1) % 
                    sizeof(HostAddressRec)) == 0);
        else
            assert(object->headers->string[0] == DNS_CNAME);
        request.addr = retainAtom(object->headers);
    } else if(object->message) {
        request.error_message = retainAtom(object->message);
    }

    releaseObject(object);

    if(request.addr && request.addr->length > 0)
        done = handler(1, &request);
    else
        done = handler(-EDNS_HOST_NOT_FOUND, &request);
    assert(done);

    releaseAtom(request.addr); request.addr = NULL;
    releaseAtom(request.name); request.name = NULL;
    releaseAtom(request.error_message); request.error_message = NULL;
    return 1;

 fail:
    releaseNotifyObject(object);
    done = handler(-errno, &request);
    assert(done);
    releaseAtom(name);
    return 1;
}

static int
dnsDelayedErrorNotifyHandler(TimeEventHandlerPtr event)
{
    int done;
    GethostbynameRequestRec request =
        *(GethostbynameRequestPtr)event->data;
    done = request.handler(-EDNS_HOST_NOT_FOUND, &request);
    assert(done);
    releaseAtom(request.name); request.name = NULL;
    releaseAtom(request.addr); request.addr = NULL;
    releaseAtom(request.error_message); request.error_message = NULL;
    return 1;
}
    
static int
dnsDelayedDoneNotifyHandler(TimeEventHandlerPtr event)
{
    int done;
    GethostbynameRequestRec request = *(GethostbynameRequestPtr)event->data;
    done = request.handler(1, &request);
    assert(done);
    releaseAtom(request.name); request.name = NULL;
    releaseAtom(request.addr); request.addr = NULL;
    releaseAtom(request.error_message); request.error_message = NULL;
    return 1;
}

static int
dnsDelayedNotify(int error, GethostbynameRequestPtr request)
{
    TimeEventHandlerPtr handler;

    if(error)
        handler = scheduleTimeEvent(0,
                                    dnsDelayedErrorNotifyHandler,
                                    sizeof(*request), request);
    else
        handler = scheduleTimeEvent(0,
                                    dnsDelayedDoneNotifyHandler,
                                    sizeof(*request), request);
    if(handler == NULL) {
        do_log(L_ERROR, "Couldn't schedule DNS notification.\n");
        return -1;
    }
    return 1;
}

#ifdef HAVE_IPv6
AtomPtr
rfc2732(AtomPtr name)
{
    char buf[38];
    int rc;
    AtomPtr a = NULL;

    if(name->length < 38 && 
       name->string[0] == '[' && name->string[name->length - 1] == ']') {
        struct in6_addr in6a;
        memcpy(buf, name->string + 1, name->length - 2);
        buf[name->length - 2] = '\0';
        rc = inet_pton(AF_INET6, buf, &in6a);
        if(rc == 1) {
            char s[1 + sizeof(HostAddressRec)];
            memset(s, 0, sizeof(s));
            s[0] = DNS_A;
            s[1] = 6;
            memcpy(s + 2, &in6a, 16);
            a = internAtomN(s, 1 + sizeof(HostAddressRec));
            if(a == NULL)
                return NULL;
        }
    }
    return a;
}

/* Used for sorting host addresses depending on the value of dnsQueryIPv6 */
int
compare_hostaddr(const void *av, const void *bv)
{
    const HostAddressRec *a = av, *b = bv;
    int r;
    if(a->af == 4) {
        if(b->af == 4)
            r = 0;
        else
            r = -1;
    } else {
        if(b->af == 6)
            r = 0;
        else
            r = 1;
    }
    if(dnsQueryIPv6 >= 2)
        return -r;
    else
        return r;
}

#ifndef NO_STANDARD_RESOLVER
static int
really_do_gethostbyname(AtomPtr name, ObjectPtr object)
{
    struct addrinfo *ai, *entry, hints;
    int rc;
    int error, i;
    char buf[1024];
    AtomPtr a;

    a = rfc2732(name);
    if(a) {
        object->headers = a;
        object->age = current_time.tv_sec;
        object->expires = current_time.tv_sec + 240;
        object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS);
        notifyObject(object);
        return 0;
    }

    memset(&hints, 0, sizeof(hints));
    hints.ai_protocol = IPPROTO_TCP;
    if(dnsQueryIPv6 <= 0)
        hints.ai_family = AF_INET;
    else if(dnsQueryIPv6 >= 3)
        hints.ai_family = AF_INET6;

    rc = getaddrinfo(name->string, NULL, &hints, &ai);

    switch(rc) {
    case 0: error = 0; break;
    case EAI_FAMILY:
#ifdef EAI_ADDRFAMILY
    case EAI_ADDRFAMILY:
#endif
    case EAI_SOCKTYPE:
        error = EAFNOSUPPORT; break;
    case EAI_BADFLAGS: error = EINVAL; break;
    case EAI_SERVICE: error = EDNS_NO_RECOVERY; break;
#ifdef EAI_NONAME
    case EAI_NONAME:
#endif
#ifdef EAI_NODATA
    case EAI_NODATA:
#endif
        error = EDNS_NO_ADDRESS; break;
    case EAI_FAIL: error = EDNS_NO_RECOVERY; break;
    case EAI_AGAIN: error = EDNS_TRY_AGAIN; break;
#ifdef EAI_MEMORY
    case EAI_MEMORY: error = ENOMEM; break;
#endif
    case EAI_SYSTEM: error = errno; break;
    default: error = EUNKNOWN;
    }

    if(error == EDNS_NO_ADDRESS) {
        object->headers = NULL;
        object->age = current_time.tv_sec;
        object->expires = current_time.tv_sec + dnsNegativeTtl;
        object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS);
        notifyObject(object);
        return 0;
    } else if(error) {
        do_log_error(L_ERROR, error, "Getaddrinfo failed");
        object->flags &= ~OBJECT_INPROGRESS;
        abortObject(object, 404,
                    internAtomError(error, "Getaddrinfo failed"));
        notifyObject(object);
        return 0;
    }

    entry = ai;
    buf[0] = DNS_A;
    i = 0;
    while(entry) {
        HostAddressRec host;
        int host_valid = 0;
        if(entry->ai_family == AF_INET && entry->ai_protocol == IPPROTO_TCP) {
            if(dnsQueryIPv6 < 3) {
                host.af = 4;
                memset(host.data, 0, sizeof(host.data));
                memcpy(&host.data,
                       &((struct sockaddr_in*)entry->ai_addr)->sin_addr, 
                       4);
                host_valid = 1;
            }
        } else if(entry->ai_family == AF_INET6 && 
                  entry->ai_protocol == IPPROTO_TCP) {
            if(dnsQueryIPv6 > 0) {
                host.af = 6;
                memset(&host.data, 0, sizeof(host.data));
                memcpy(&host.data,
                       &((struct sockaddr_in6*)entry->ai_addr)->sin6_addr, 
                       16);
                host_valid = 1;
            }
        }
        if(host_valid) {
            if(i >= 1024 / sizeof(HostAddressRec) - 2) {
                do_log(L_ERROR, "Too many addresses for host %s\n", 
                       name->string);
                break;
            }
            memcpy(buf + 1 + i * sizeof(HostAddressRec), 
                   &host, sizeof(HostAddressRec));
            i++;
        }
        entry = entry->ai_next;
    }
    freeaddrinfo(ai);
    if(i == 0) {
        do_log(L_ERROR, "Getaddrinfo returned no useful addresses\n");
        object->flags &= ~OBJECT_INPROGRESS;
        abortObject(object, 404,
                    internAtom("Getaddrinfo returned no useful addresses"));
        notifyObject(object);
        return 0;
    }

    if(1 <= dnsQueryIPv6 && dnsQueryIPv6 <= 2)
        qsort(buf + 1, i, sizeof(HostAddressRec), compare_hostaddr);

    a = internAtomN(buf, 1 + i * sizeof(HostAddressRec));
    if(a == NULL) {
        object->flags &= ~OBJECT_INPROGRESS;
        abortObject(object, 501, internAtom("Couldn't allocate address"));
        notifyObject(object);
        return 0;
    }
    object->headers = a;
    object->age = current_time.tv_sec;
    object->expires = current_time.tv_sec + dnsGethostbynameTtl;
    object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS);
    notifyObject(object);
    return 0;
}
#endif
    
#else

#ifndef NO_STANDARD_RESOLVER
static int
really_do_gethostbyname(AtomPtr name, ObjectPtr object)
{
    struct hostent *host;
    char *s;
    AtomPtr a;
    int i, j;
    int error;

    host = gethostbyname(name->string);
    if(host == NULL) {
        switch(h_errno) {
        case HOST_NOT_FOUND: error = EDNS_HOST_NOT_FOUND; break;
        case NO_ADDRESS: error = EDNS_NO_ADDRESS; break;
        case NO_RECOVERY: error = EDNS_NO_RECOVERY; break;
        case TRY_AGAIN: error = EDNS_TRY_AGAIN; break;
        default: error = EUNKNOWN; break;
        }
        if(error == EDNS_HOST_NOT_FOUND) {
            object->headers = NULL;
            object->age = current_time.tv_sec;
            object->expires = current_time.tv_sec + dnsNegativeTtl;
            object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS);
            object->flags &= ~OBJECT_INPROGRESS;
            notifyObject(object);
            return 0;
        } else {
            do_log_error(L_ERROR, error, "Gethostbyname failed");
            abortObject(object, 404, 
                        internAtomError(error, "Gethostbyname failed"));
            object->flags &= ~OBJECT_INPROGRESS;
            notifyObject(object);
            return 0;
        }
    }
    if(host->h_addrtype != AF_INET) {
        do_log(L_ERROR, "Address is not AF_INET.\n");
        object->flags &= ~OBJECT_INPROGRESS;
        abortObject(object, 404, internAtom("Address is not AF_INET"));
        notifyObject(object);
        return -1;
    }
    if(host->h_length != sizeof(struct in_addr)) {
        do_log(L_ERROR, "Address size inconsistent.\n");
        object->flags &= ~OBJECT_INPROGRESS;
        abortObject(object, 404, internAtom("Address size inconsistent"));
        notifyObject(object);
        return 0;
    }
    i = 0;
    while(host->h_addr_list[i] != NULL) i++;
    s = malloc(1 + i * sizeof(HostAddressRec));
    if(s == NULL) {
        a = NULL;
    } else {
        memset(s, 0, 1 + i * sizeof(HostAddressRec));
        s[0] = DNS_A;
        for(j = 0; j < i; j++) {
            s[j * sizeof(HostAddressRec) + 1] = 4;
            memcpy(&s[j * sizeof(HostAddressRec) + 2], host->h_addr_list[j],
                   sizeof(struct in_addr));
        }
        a = internAtomN(s, i * sizeof(HostAddressRec) + 1);
        free(s);
    }
    if(!a) {
        object->flags &= ~OBJECT_INPROGRESS;
        abortObject(object, 501, internAtom("Couldn't allocate address"));
        notifyObject(object);
        return 0;
    }
    object->headers = a;
    object->age = current_time.tv_sec;
    object->expires = current_time.tv_sec + dnsGethostbynameTtl;
    object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS);
    notifyObject(object);
    return 0;

}
#endif

#endif

#ifdef NO_STANDARD_RESOLVER
static int
really_do_gethostbyname(AtomPtr name, ObjectPtr object)
{
    abort();
}
#endif

#ifndef NO_FANCY_RESOLVER

static int dnsSocket = -1;
static FdEventHandlerPtr dnsSocketHandler = NULL;

static int
dnsHandler(int status, ConditionHandlerPtr chandler)
{
    GethostbynameRequestRec request = *(GethostbynameRequestPtr)chandler->data;
    ObjectPtr object = request.object;

    assert(!(object->flags & OBJECT_INPROGRESS));

    if(object->headers) {
        request.addr = retainAtom(object->headers);
        dnsDelayedNotify(0, &request);
    } else {
        if(object->message)
            request.error_message = retainAtom(object->message);
        dnsDelayedNotify(1, &request);
    }
    releaseObject(object);
    return 1;
}

static void
removeQuery(DnsQueryPtr query)
{
    DnsQueryPtr previous;
    if(query == inFlightDnsQueries) {
        inFlightDnsQueries = query->next;
        if(inFlightDnsQueries == NULL)
            inFlightDnsQueriesLast = NULL;
    } else {
        previous = inFlightDnsQueries;
        while(previous->next) {
            if(previous->next == query)
                break;
            previous = previous->next;
        }
        assert(previous->next != NULL);
        previous->next = query->next;
        if(previous->next == NULL)
            inFlightDnsQueriesLast = previous;
    }
}

static void
insertQuery(DnsQueryPtr query) 
{
    if(inFlightDnsQueriesLast)
        inFlightDnsQueriesLast->next = query;
    else
        inFlightDnsQueries = query;
    inFlightDnsQueriesLast = query;
}

static DnsQueryPtr
findQuery(int id, AtomPtr name)
{
    DnsQueryPtr query;
    query = inFlightDnsQueries;
    while(query) {
        if(query->id == id && (name == NULL || query->name == name))
            return query;
        query = query->next;
    }
    return NULL;
}

static int
dnsTimeoutHandler(TimeEventHandlerPtr event)
{
    DnsQueryPtr query = *(DnsQueryPtr*)event->data;
    ObjectPtr object = query->object;
    int rc;

    query->timeout = MAX(10, query->timeout * 2);

    if(query->timeout > dnsMaxTimeout) {
        abortObject(object, 501, internAtom("Timeout"));
        goto fail;
    } else {
        rc = sendQuery(query);
        if(rc < 0) {
            if(rc != -EWOULDBLOCK && rc != -EAGAIN && rc != -ENOBUFS) {
                abortObject(object, 501,
                            internAtomError(-rc,
                                            "Couldn't send DNS query"));
                goto fail;
            }
            /* else let it timeout */
        }
        query->timeout_handler =
            scheduleTimeEvent(query->timeout, dnsTimeoutHandler,
                              sizeof(query), &query);
        if(query->timeout_handler == NULL) {
            do_log(L_ERROR, "Couldn't schedule DNS timeout handler.\n");
            abortObject(object, 501,
                        internAtom("Couldn't schedule DNS timeout handler"));
            goto fail;
        }
        return 1;
    }

 fail:
    removeQuery(query);
    object->flags &= ~OBJECT_INPROGRESS;
    if(query->inet4) releaseAtom(query->inet4);
    if(query->inet6) releaseAtom(query->inet6);
    free(query);
    releaseNotifyObject(object);
    return 1;
}

static int
establishDnsSocket()
{
    int rc;
#ifdef HAVE_IPv6
    int inet6 = (nameserverAddress.sa_family == AF_INET6);
    int pf = inet6 ? PF_INET6 : PF_INET;
    int sa_size = 
        inet6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
#else
    int pf = PF_INET;
    int sa_size = sizeof(struct sockaddr_in);
#endif

    if(dnsSocket < 0) {
        assert(!dnsSocketHandler);
        dnsSocket = socket(pf, SOCK_DGRAM, 0);
        if(dnsSocket < 0) {
            do_log_error(L_ERROR, errno, "Couldn't create DNS socket");
            return -errno;
        }

        rc = connect(dnsSocket, &nameserverAddress, sa_size);
        if(rc < 0) {
            close(dnsSocket);
            dnsSocket = -1;
            do_log_error(L_ERROR, errno, "Couldn't create DNS \"connection\"");
            return -errno;
        }
    }

    if(!dnsSocketHandler) {
        dnsSocketHandler = 
            registerFdEvent(dnsSocket, POLLIN, dnsReplyHandler, 0, NULL);
        if(dnsSocketHandler == NULL) {
            do_log(L_ERROR, "Couldn't register DNS socket handler.\n");
            close(dnsSocket);
            dnsSocket = -1;
            return -ENOMEM;
        }
    }

    return 1;
}

static int
sendQuery(DnsQueryPtr query)
{
    char buf[512];
    int buflen;
    int rc;
    int af[2];
    int i;

    if(dnsSocket < 0)
        return -1;

    if(dnsQueryIPv6 <= 0) {
        af[0] = 4; af[1] = 0;
    } else if(dnsQueryIPv6 <= 2) {
        af[0] = 4; af[1] = 6;
    } else {
        af[0] = 6; af[1] = 0;
    }

    for(i = 0; i < 2; i++) {
        if(af[i] == 0)
            continue;
        if(af[i] == 4 && query->inet4)
            continue;
        else if(af[i] == 6 && query->inet6)
            continue;

        buflen = dnsBuildQuery(query->id, buf, 0, 512, query->name, af[i]);
        if(buflen <= 0) {
            do_log(L_ERROR, "Couldn't build DNS query.\n");
            return buflen;
        }

        rc = send(dnsSocket, buf, buflen, 0);
        if(rc < buflen) {
            if(rc >= 0) {
                do_log(L_ERROR, "Couldn't send DNS query: partial send.\n");
                return -EAGAIN;
            } else {
                do_log_error(L_ERROR, errno, "Couldn't send DNS query");
                return -errno;
            }
        }
    }
    return 1;
}

static int
really_do_dns(AtomPtr name, ObjectPtr object)
{
    int rc;
    DnsQueryPtr query;
    AtomPtr message = NULL;
    int id;
    AtomPtr a = NULL;

    if(a == NULL) {
        if(name == atomLocalhost || name == atomLocalhostDot) {
            char s[1 + sizeof(HostAddressRec)];
            memset(s, 0, sizeof(s));
            s[0] = DNS_A;
            s[1] = 4;
            s[2] = 127;
            s[3] = 0;
            s[4] = 0;
            s[5] = 1;
            a = internAtomN(s, 1 + sizeof(HostAddressRec));
            if(a == NULL) {
                abortObject(object, 501,
                            internAtom("Couldn't allocate address"));
                notifyObject(object);
                errno = ENOMEM;
                return -1;
            }
        }
    }

    if(a == NULL) {
        struct in_addr ina;
        rc = inet_aton(name->string, &ina);
        if(rc == 1) {
            char s[1 + sizeof(HostAddressRec)];
            memset(s, 0, sizeof(s));
            s[0] = DNS_A;
            s[1] = 4;
            memcpy(s + 2, &ina, 4);
            a = internAtomN(s, 1 + sizeof(HostAddressRec));
            if(a == NULL) {
                abortObject(object, 501,
                            internAtom("Couldn't allocate address"));
                notifyObject(object);
                errno = ENOMEM;
                return -1;
            }
        }
    }
#ifdef HAVE_IPv6
    if(a == NULL)
        a = rfc2732(name);
#endif

    if(a) {
        object->headers = a;
        object->age = current_time.tv_sec;
        object->expires = current_time.tv_sec + 240;
        object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS);
        notifyObject(object);
        return 0;
    }

    rc = establishDnsSocket();
    if(rc < 0) {
        do_log_error(L_ERROR, -rc, "Couldn't establish DNS socket.\n");
        message = internAtomError(-rc, "Couldn't establish DNS socket");
        goto fallback;
    }

    /* The id is used to speed up detecting replies to queries that
       are no longer current -- see dnsReplyHandler. */
    id = (idSeed++) & 0xFFFF;

    query = malloc(sizeof(DnsQueryRec));
    if(query == NULL) {
        do_log(L_ERROR, "Couldn't allocate DNS query.\n");
        message = internAtom("Couldn't allocate DNS query");
        goto fallback;
    }
    query->id = id;
    query->inet4 = NULL;
    query->inet6 = NULL;
    query->name = name;
    query->time = current_time.tv_sec;
    query->object = retainObject(object);
    query->timeout = 4;
    query->timeout_handler = NULL;
    query->next = NULL;

    query->timeout_handler = 
        scheduleTimeEvent(query->timeout, dnsTimeoutHandler,
                          sizeof(query), &query);
    if(query->timeout_handler == NULL) {
        do_log(L_ERROR, "Couldn't schedule DNS timeout handler.\n");
        message = internAtom("Couldn't schedule DNS timeout handler");
        goto free_fallback;
    }
    insertQuery(query);

    object->flags |= OBJECT_INPROGRESS;
    rc = sendQuery(query);
    if(rc < 0) {
        if(rc != -EWOULDBLOCK && rc != -EAGAIN && rc != -ENOBUFS) {
            object->flags &= ~OBJECT_INPROGRESS;
            message = internAtomError(-rc, "Couldn't send DNS query");
            goto remove_fallback;
        }
        /* else let it timeout */
    }
    releaseAtom(message);
    return 1;

 remove_fallback:
    removeQuery(query);
 free_fallback:
    releaseObject(query->object);
    cancelTimeEvent(query->timeout_handler);
    free(query);
 fallback:
    if(dnsUseGethostbyname >= 1) {
        releaseAtom(message);
        do_log(L_WARN, "Falling back on gethostbyname.\n");
        return really_do_gethostbyname(name, object);
    } else {
        abortObject(object, 501, message);
        notifyObject(object);
        return 1;
    }
}

static int
dnsReplyHandler(int abort, FdEventHandlerPtr event)
{
    int fd = event->fd;
    char buf[2048];
    int len, rc;
    ObjectPtr object;
    unsigned ttl = 0;
    AtomPtr name, value, message = NULL;
    int id;
    int af;
    DnsQueryPtr query;
    AtomPtr cname = NULL;

    if(abort) {
        dnsSocketHandler = NULL;
        rc = establishDnsSocket();
        if(rc < 0) {
            do_log(L_ERROR, "Couldn't reestablish DNS socket.\n");
            /* At this point, we should abort all in-flight
               DNS requests.  Oh, well, they'll timeout anyway. */
        }
        return 1;
    }

    len = recv(fd, buf, 2048, 0);
    if(len <= 0) {
        if(errno == EINTR || errno == EAGAIN) return 0;
        /* This is where we get ECONNREFUSED for an ICMP port unreachable */
        do_log_error(L_ERROR, errno, "DNS: recv failed");
        dnsGethostbynameFallback(-1, message);
        return 0;
    }

    /* This could be a late reply to a query that timed out and was
       resent, a reply to a query that timed out, or a reply to an
       AAAA query when we already got a CNAME reply to the associated
       A.  We filter such replies straight away, without trying to
       parse them. */
    rc = dnsReplyId(buf, 0, len, &id);
    if(rc < 0) {
        do_log(L_WARN, "Short DNS reply.\n");
        return 0;
    }
    if(!findQuery(id, NULL)) {
        return 0;
    }

    rc = dnsDecodeReply(buf, 0, len, &id, &name, &value, &af, &ttl);
    if(rc < 0) {
        assert(value == NULL);
        /* We only want to fallback on gethostbyname if we received a
           reply that we could not understand.  What about truncated
           replies? */
        if(rc < 0) {
            do_log_error(L_WARN, -rc, "DNS");
            if(dnsUseGethostbyname >= 2 ||
               (dnsUseGethostbyname && 
                (rc != -EDNS_HOST_NOT_FOUND && rc != -EDNS_NO_RECOVERY &&
                 rc != -EDNS_FORMAT))) {
                dnsGethostbynameFallback(id, message);
                return 0;
            } else {
                message = internAtomError(-rc, NULL);
            }
        } else {
            assert(name != NULL && id >= 0 && af >= 0);
        }
    }

    query = findQuery(id, name);
    if(query == NULL) {
        /* Duplicate id ? */
        releaseAtom(value);
        releaseAtom(name);
        return 0;
    }

    /* We're going to use the information in this reply.  If it was an
       error, construct an empty atom to distinguish it from information
       we're still waiting for. */
    if(value == NULL)
        value = internAtom("");

 again:
    if(af == 4) {
        if(query->inet4 == NULL) {
            query->inet4 = value;
            query->ttl4 = current_time.tv_sec + ttl;
        } else
            releaseAtom(value);
    } else if(af == 6) {
        if(query->inet6 == NULL) {
            query->inet6 = value;
            query->ttl6 = current_time.tv_sec + ttl;
        } else
            releaseAtom(value);
    } else if(af == 0) {
        if(query->inet4 || query->inet6) {
            do_log(L_WARN, "Host %s has both %s and CNAME -- "
                   "ignoring CNAME.\n", query->name->string,
                   query->inet4 ? "A" : "AAAA");
            releaseAtom(value);
            value = internAtom("");
            af = query->inet4 ? 4 : 6;
            goto again;
        } else {
            cname = value;
        }
    }

    if(rc >= 0 && !cname &&
       ((dnsQueryIPv6 < 3 && query->inet4 == NULL) ||
        (dnsQueryIPv6 > 0 && query->inet6 == NULL)))
        return 0;

    /* This query is complete */

    cancelTimeEvent(query->timeout_handler);
    object = query->object;

    if(object->flags & OBJECT_INITIAL) {
        assert(!object->headers);
        if(cname) {
            assert(query->inet4 == NULL && query->inet6 == NULL);
            object->headers = cname;
            object->expires = current_time.tv_sec + ttl;
        } else if((!query->inet4 || query->inet4->length == 0) &&
                  (!query->inet6 || query->inet6->length == 0)) {
            releaseAtom(query->inet4);
            releaseAtom(query->inet6);
            object->expires = current_time.tv_sec + dnsNegativeTtl;
            abortObject(object, 500, retainAtom(message));
        } else if(!query->inet4 || query->inet4->length == 0) {
            object->headers = query->inet6;
            object->expires = query->ttl6;
            releaseAtom(query->inet4);
        } else if(!query->inet6 || query->inet6->length == 0) {
            object->headers = query->inet4;
            object->expires = query->ttl4;
            releaseAtom(query->inet6);
        } else {
            /* need to merge results */
            char buf[1024];
            if(query->inet4->length + query->inet6->length > 1024) {
                releaseAtom(query->inet4);
                releaseAtom(query->inet6);
                abortObject(object, 500, internAtom("DNS reply too long"));
            } else {
                if(dnsQueryIPv6 <= 1) {
                    memcpy(buf, query->inet4->string, query->inet4->length);
                    memcpy(buf + query->inet4->length,
                           query->inet6->string + 1, query->inet6->length - 1);
                } else {
                    memcpy(buf, query->inet6->string, query->inet6->length);
                    memcpy(buf + query->inet6->length,
                           query->inet4->string + 1, query->inet4->length - 1);
                }
                object->headers =
                    internAtomN(buf, 
                                query->inet4->length + 
                                query->inet6->length - 1);
                if(object->headers == NULL)
                    abortObject(object, 500, 
                                internAtom("Couldn't allocate DNS atom"));
            }
            object->expires = MIN(query->ttl4, query->ttl6);
        }
        object->age = current_time.tv_sec;
        object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS);
    } else {
        do_log(L_WARN, "DNS object ex nihilo for %s.\n", query->name->string);
    }
    
    removeQuery(query);
    free(query);

    releaseAtom(name);
    releaseAtom(message);
    releaseNotifyObject(object);
    return 0;
}

static int
dnsGethostbynameFallback(int id, AtomPtr message)
{
    DnsQueryPtr query, previous;
    ObjectPtr object;

    if(inFlightDnsQueries == NULL) {
        releaseAtom(message);
        return 1;
    }

    query = NULL;
    if(id < 0 || inFlightDnsQueries->id == id) {
        previous = NULL;
        query = inFlightDnsQueries;
    } else {
        previous = inFlightDnsQueries;
        while(previous->next) {
            if(previous->next->id == id) {
                query = previous->next;
                break;
            }
            previous = previous->next;
        }
        if(!query) {
            previous = NULL;
            query = inFlightDnsQueries;
        }
    }

    if(previous == NULL) {
        inFlightDnsQueries = query->next;
        if(inFlightDnsQueries == NULL)
            inFlightDnsQueriesLast = NULL;
    } else {
        previous->next = query->next;
        if(query->next == NULL)
            inFlightDnsQueriesLast = NULL;
    }

    object = makeObject(OBJECT_DNS, query->name->string, query->name->length,
                        1, 0, NULL, NULL);
    if(!object) {
        do_log(L_ERROR, "Couldn't make DNS object.\n");
        releaseAtom(query->name);
        releaseAtom(message);
        releaseObject(query->object);
        cancelTimeEvent(query->timeout_handler);
        free(query);
        return -1;
    }
    if(dnsUseGethostbyname >= 1) {
        releaseAtom(message);
        do_log(L_WARN, "Falling back to using system resolver.\n");
        really_do_gethostbyname(retainAtom(query->name), object);
    } else {
        releaseAtom(object->message);
        object->message = message;
        object->flags &= ~OBJECT_INPROGRESS;
        releaseNotifyObject(object);
    }
    cancelTimeEvent(query->timeout_handler);
    releaseAtom(query->name);
    if(query->inet4) releaseAtom(query->inet4);
    if(query->inet6) releaseAtom(query->inet6);
    releaseObject(query->object);
    free(query);
    return 1;
}

static int
stringToLabels(char *buf, int offset, int n, char *string)
{
    int i = offset;
    int j = 0, k = 0;
    while(1) {
        while(string[k] != '.' && string[k] != '\0')
            k++;
        if(k >= j + 256) return -1;
        buf[i] = (unsigned char)(k - j); i++; if(i >= n) return -1;
        while(j < k) {
            buf[i] = string[j]; i++; j++; if(i >= n) return -1;
        }
        if(string[j] == '\0') {
            buf[i] = '\0';
            i++; if(i >= n) return -1;
            break;
        }
        j++; k++;
    }

    return i;
}

#ifdef UNALIGNED_ACCESS
#define DO_NTOHS(_d, _s) _d = ntohs(*(short*)(_s));
#define DO_NTOHL(_d, _s) _d = ntohl(*(unsigned*)(_s))
#define DO_HTONS(_d, _s) *(short*)(_d) = htons(_s);
#define DO_HTONL(_d, _s) *(unsigned*)(_d) = htonl(_s)
#else
#define DO_NTOHS(_d, _s) \
    do { short _dd; \
         memcpy(&(_dd), (_s), sizeof(short)); \
         _d = ntohs(_dd); } while(0)
#define DO_NTOHL(_d, _s) \
    do { unsigned _dd; \
         memcpy(&(_dd), (_s), sizeof(unsigned)); \
         _d = ntohl(_dd); } while(0)
#define DO_HTONS(_d, _s) \
    do { unsigned short _dd; \
         _dd = htons(_s); \
         memcpy((_d), &(_dd), sizeof(unsigned short)); } while(0);
#define DO_HTONL(_d, _s) \
    do { unsigned _dd; \
         _dd = htonl(_s); \
         memcpy((_d), &(_dd), sizeof(unsigned)); } while(0);
#endif

static int
labelsToString(char *buf, int offset, int n, char *d, int m, int *j_return)
{
    int i = offset, j, k;
    int ll;

    j = 0;
    while(1) {
        if(i >= n) return -1;
        ll = *(unsigned char*)&buf[i]; i++;
        if(ll == 0) {
            break;
        }
        if((ll & (3 << 6)) == (3 << 6)) {
            /* RFC 1035, 4.1.4 */
            int o;
            if(i >= n) return -1;
            o = (ll & ~(3 << 6)) << 8 | *(unsigned char*)&buf[i];
            i++;
            labelsToString(buf, o, n, &d[j], m - j, &k);
            j += k;
            break;
        } else if((ll & (3 << 6)) == 0) {
            for(k = 0; k < ll; k++) {
                if(i >= n || j >= m) return -1;
                d[j++] = buf[i++];
            }
            if(i >= n) return -1;
            if(buf[i] != '\0') {
                if(j >= m) return -1;
                d[j++] = '.';
            }
        } else {
            return -1;
        }
    }
    *j_return = j;
    return i;
}

static int
dnsBuildQuery(int id, char *buf, int offset, int n, AtomPtr name, int af)
{
    int i = offset;
    int type;
    switch(af) {
    case 4: type = 1; break;
    case 6: type = 28; break;
    default: return EINVAL;
    }

    if(i + 12 >= n) return -1;
    DO_HTONS(&buf[i], id); i += 2;
    DO_HTONS(&buf[i], 1<<8); i += 2;
    DO_HTONS(&buf[i], 1); i += 2;
    DO_HTONS(&buf[i], 0); i += 2;
    DO_HTONS(&buf[i], 0); i += 2;
    DO_HTONS(&buf[i], 0); i += 2;

    i = stringToLabels(buf, i, n, name->string);
    if(i < 0) return -ENAMETOOLONG;
    
    if(i + 4 >= n) return -ENAMETOOLONG;
    DO_HTONS(&buf[i], type); i += 2;
    DO_HTONS(&buf[i], 1); i += 2;
    return i;
}

static int
dnsReplyId(char *buf, int offset, int n, int *id_return)
{
    if(n - offset < 12)
        return -1;
    *id_return = ntohs(*(short*)&buf[offset]);
    return 1;
}

static int
dnsDecodeReply(char *buf, int offset, int n, int *id_return,
               AtomPtr *name_return, AtomPtr *value_return,
               int *af_return, unsigned *ttl_return)
{
    int i = offset, j, m;
    int id = -1, b23, qdcount, ancount, nscount, arcount, rdlength;
    int class, type;
    unsigned int ttl;
    char b[2048];
    int af = -1;
    AtomPtr name = NULL, value;
    char addresses[1024];
    int addr_index = 0;
    int error = EDNS_NO_ADDRESS;
    unsigned final_ttl = 7 * 24 * 3600;
    int dnserror;

    if(n - i < 12) {
        error = EDNS_INVALID;
        goto fail;
    }

    DO_NTOHS(id, &buf[i]); i += 2;
    DO_NTOHS(b23, &buf[i]); i += 2;
    DO_NTOHS(qdcount, &buf[i]); i += 2;
    DO_NTOHS(ancount, &buf[i]); i += 2;
    DO_NTOHS(nscount, &buf[i]); i += 2;
    DO_NTOHS(arcount, &buf[i]); i += 2;

    do_log(D_DNS, 
           "DNS id %d, b23 0x%x, qdcount %d, ancount %d, "
           "nscount %d, arcount %d\n",
           id, b23, qdcount, ancount, nscount, arcount);

    if((b23 & (0xF870)) != 0x8000) {
        do_log(L_ERROR, "Incorrect DNS reply (b23 = 0x%x).\n", b23);
        error = EDNS_INVALID;
        goto fail;
    }

    dnserror = b23 & 0xF;

    if(b23 & 0x200) {
        do_log(L_WARN, "Truncated DNS reply (b23 = 0x%x).\n", b23);
    }

    if(dnserror || qdcount != 1) {
        if(!dnserror)
            do_log(L_ERROR, 
                   "Unexpected number %d of DNS questions.\n", qdcount);
        if(dnserror == 1)
            error = EDNS_FORMAT;
        else if(dnserror == 2)
            error = EDNS_NO_RECOVERY;
        else if(dnserror == 3)
            error = EDNS_HOST_NOT_FOUND;
        else if(dnserror == 4 || dnserror == 5)
            error = EDNS_REFUSED;
        else if(dnserror == 0)
            error = EDNS_INVALID;
        else
            error = EUNKNOWN;
        goto fail;
    }

    /* We do this early, so that we can return the address family to
       the caller in case of error. */
    i = labelsToString(buf, i, n, b, 2048, &m);
    if(i < 0) {
        error = EDNS_FORMAT;
        goto fail;
    }
    DO_NTOHS(type, &buf[i]); i += 2;
    DO_NTOHS(class, &buf[i]); i += 2;

    if(type == 1)
        af = 4;
    else if(type == 28)
        af = 6;
    else {
        error = EDNS_FORMAT;
        goto fail;
    }

    do_log(D_DNS, "DNS q: ");
    do_log_n(D_DNS, b, m);
    do_log(D_DNS, " (%d, %d)\n", type, class);
    name = internAtomLowerN(b, m);
    if(name == NULL) {
        error = ENOMEM;
        goto fail;
    }

    if(class != 1) {
        error = EDNS_FORMAT;
        goto fail;
    }

#define PARSE_ANSWER(kind, label) \
do { \
    i = labelsToString(buf, i, 1024, b, 2048, &m); \
    if(i < 0) goto label; \
    DO_NTOHS(type, &buf[i]); i += 2; if(i > 1024) goto label; \
    DO_NTOHS(class, &buf[i]); i += 2; if(i > 1024) goto label; \
    DO_NTOHL(ttl, &buf[i]); i += 4; if(i > 1024) goto label; \
    DO_NTOHS(rdlength, &buf[i]); i += 2; if(i > 1024) goto label; \
    do_log(D_DNS, "DNS " kind ": "); \
    do_log_n(D_DNS, b, m); \
    do_log(D_DNS, " (%d, %d): %d bytes, ttl %u\n", \
           type, class, rdlength, ttl); \
   } while(0)


    for(j = 0; j < ancount; j++) {
        PARSE_ANSWER("an", fail);
        if(strcasecmp_n(name->string, b, m) == 0) {
            if(class != 1) {
                do_log(D_DNS, "DNS: %s: unknown class %d.\n", 
                       name->string, class);
                error = EDNS_UNSUPPORTED;
                goto cont;
            }
            if(type == 1 || type == 28) {
                if((type == 1 && rdlength != 4) ||
                   (type == 28 && rdlength != 16)) {
                    do_log(L_ERROR, 
                           "DNS: %s: unexpected length %d of %s record.\n",
                           name->string, rdlength, type == 1 ? "A" : "AAAA");
                    error = EDNS_INVALID;
                    if(rdlength <= 0 || rdlength >= 32)
                        goto fail;
                    goto cont;
                }
                if(af == 0) {
                    do_log(L_WARN, "DNS: %s: host has both A and CNAME -- "
                           "ignoring CNAME.\n", name->string);
                    addr_index = 0;
                    af = -1;
                }
                if(type == 1) {
                    if(af < 0)
                        af = 4;
                    else if(af == 6) {
                        do_log(L_WARN, "Unexpected AAAA reply.\n");
                        goto cont;
                    }
                } else {
                    if(af < 0)
                        af = 6;
                    else if(af == 4) {
                        do_log(L_WARN, "Unexpected A reply.\n");
                        goto cont;
                    }
                }

                if(addr_index == 0) {
                    addresses[0] = DNS_A;
                    addr_index++;
                } else {
                    if(addr_index > 1000) {
                        error = EDNS_INVALID;
                        goto fail;
                    }
                }
                assert(addresses[0] == DNS_A);
                if(final_ttl > ttl)
                    final_ttl = ttl;
                memset(&addresses[addr_index], 0, sizeof(HostAddressRec));
                if(type == 1) {
                    addresses[addr_index] = 4;
                    memcpy(addresses + addr_index + 1, buf + i, 4);
                } else {
                    addresses[addr_index] = 6;
                    memcpy(addresses + addr_index + 1, buf + i, 16);
                }
                addr_index += sizeof(HostAddressRec);
            } else if(type == 5) {
                int j, k;
                if(af != 0 && addr_index > 0) {
                    do_log(L_WARN, "DNS: host has both CNAME and A -- "
                           "ignoring CNAME.\n");
                    goto cont;
                }
                af = 0;

                if(addr_index != 0) {
                    /* Only warn if the CNAMEs are not identical */
                    char tmp[512]; int jj, kk;
                    assert(addresses[0] == DNS_CNAME);
                    jj = labelsToString(buf, i, n,
                                        tmp, 512, &kk);
                    if(jj < 0 ||
                       kk != strlen(addresses + 1) ||
                       memcmp(addresses + 1, tmp, kk) != 0) {
                        do_log(L_WARN, "DNS: "
                               "%s: host has multiple CNAMEs -- "
                               "ignoring subsequent.\n", name->string);

                    }
                    goto cont;
                }
                addresses[0] = DNS_CNAME;
                addr_index++;
                j = labelsToString(buf, i, n,
                                   addresses + 1, 1020, &k);
                if(j < 0) {
                    addr_index = 0;
                    error = ENAMETOOLONG;
                    continue;
                }
                addr_index = k + 1;
            } else {
                error = EDNS_NO_ADDRESS;
                i += rdlength;
                continue;
            }

        }
    cont:
        i += rdlength;
    }

#if (LOGGING & D_DNS)
    for(j = 0; j < nscount; j++) {
        PARSE_ANSWER("ns", nofail);
        i += rdlength;
    }

    for(j = 0; j < arcount; j++) {
        PARSE_ANSWER("ar", nofail);
        i += rdlength;
    }

 nofail:
#endif

#undef PARSE_ANSWER

    do_log(D_DNS, "DNS: %d bytes\n", addr_index);
    if(af < 0)
        goto fail;

    value = internAtomN(addresses, addr_index);
    if(value == NULL) {
        error = ENOMEM;
        goto fail;
    }

    assert(af >= 0);
    *id_return = id;
    *name_return = name;
    *value_return = value;
    *af_return = af;
    *ttl_return = final_ttl;
    return 1;

 fail:
    *id_return = id;
    *name_return = name;
    *value_return = NULL;
    *af_return = af;
    return -error;
}

#else

static int
really_do_dns(AtomPtr name, ObjectPtr object)
{
    abort();
}

#endif
