/* SPDX-License-Identifier: MIT OR GPL-3.0-only */
/* resolver.c
 * strophe XMPP client library -- DNS resolver
 *
 * Copyright (C) 2015 Dmitry Podgorny <pasis.ua@gmail.com>
 *
 *  This software is provided AS-IS with no warranty, either express
 *  or implied.
 *
 *  This program is dual licensed under the MIT or GPLv3 licenses.
 */

/** @file
 *  DNS resolver.
 */

#if !defined(_WIN32) && !defined(HAVE_CARES)
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#endif /* _WIN32 && HAVE_CARES */

#ifdef HAVE_CARES
#include <ares.h>
/* for select(2) */
#ifdef _WIN32
#include <winsock2.h>
#else /* _WIN32 */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif /* !_WIN32 */
#endif /* HAVE_CARES */

#include <string.h> /* strncpy */

#include "ostypes.h"
#include "snprintf.h"
#include "util.h" /* xmpp_min */
#include "resolver.h"

#define MESSAGE_HEADER_LEN 12
#define MESSAGE_RESPONSE 1
#define MESSAGE_T_SRV 33
#define MESSAGE_C_IN 1

/*******************************************************************************
 * Forward declarations.
 ******************************************************************************/

#ifdef HAVE_CARES
static int resolver_ares_srv_lookup_buf(xmpp_ctx_t *ctx,
                                        const unsigned char *buf,
                                        size_t len,
                                        resolver_srv_rr_t **srv_rr_list);
static int resolver_ares_srv_lookup(xmpp_ctx_t *ctx,
                                    const char *fulldomain,
                                    resolver_srv_rr_t **srv_rr_list);
#endif /* HAVE_CARES */

#ifndef HAVE_CARES
static int resolver_raw_srv_lookup_buf(xmpp_ctx_t *ctx,
                                       const unsigned char *buf,
                                       size_t len,
                                       resolver_srv_rr_t **srv_rr_list);
#endif /* !HAVE_CARES */

#if defined(_WIN32) && !defined(HAVE_CARES)
static int resolver_win32_srv_lookup(xmpp_ctx_t *ctx,
                                     const char *fulldomain,
                                     resolver_srv_rr_t **srv_rr_list);
static int resolver_win32_srv_query(const char *fulldomain,
                                    unsigned char *buf,
                                    size_t len);
#endif /* _WIN32 && !HAVE_CARES */

/*******************************************************************************
 * Implementation.
 ******************************************************************************/

void resolver_initialize(void)
{
#ifdef HAVE_CARES
    ares_library_init(ARES_LIB_INIT_ALL);
#endif
}

void resolver_shutdown(void)
{
#ifdef HAVE_CARES
    ares_library_cleanup();
#endif
}

resolver_srv_rr_t *resolver_srv_rr_new(xmpp_ctx_t *ctx,
                                       const char *host,
                                       unsigned short port,
                                       unsigned short prio,
                                       unsigned short weight)
{
    resolver_srv_rr_t *rr = strophe_alloc(ctx, sizeof(*rr));
    if (rr) {
        memset(rr, 0, sizeof(*rr));
        rr->port = port;
        rr->priority = prio;
        rr->weight = weight;
        if (host) {
            snprintf(rr->target, sizeof(rr->target), "%s", host);
        }
    }
    return rr;
}

static void resolver_srv_list_sort(resolver_srv_rr_t **srv_rr_list)
{
    resolver_srv_rr_t *rr_head;
    resolver_srv_rr_t *rr_current;
    resolver_srv_rr_t *rr_next;
    resolver_srv_rr_t *rr_prev;
    int swap;

    rr_head = *srv_rr_list;

    if ((rr_head == NULL) || (rr_head->next == NULL)) {
        /* Empty or single record list */
        return;
    }

    do {
        rr_prev = NULL;
        rr_current = rr_head;
        rr_next = rr_head->next;
        swap = 0;
        while (rr_next != NULL) {
            /*
             * RFC2052: A client MUST attempt to contact the target host
             * with the lowest-numbered priority it can reach.
             * RFC2052: When selecting a target host among the
             * those that have the same priority, the chance of trying
             * this one first SHOULD be proportional to its weight.
             */
            if ((rr_current->priority > rr_next->priority) ||
                (rr_current->priority == rr_next->priority &&
                 rr_current->weight < rr_next->weight)) {
                /* Swap node */
                swap = 1;
                if (rr_prev != NULL) {
                    rr_prev->next = rr_next;
                } else {
                    /* Swap head node */
                    rr_head = rr_next;
                }
                rr_current->next = rr_next->next;
                rr_next->next = rr_current;

                rr_prev = rr_next;
                rr_next = rr_current->next;
            } else {
                /* Next node */
                rr_prev = rr_current;
                rr_current = rr_next;
                rr_next = rr_next->next;
            }
        }
    } while (swap != 0);

    *srv_rr_list = rr_head;
}

int resolver_srv_lookup_buf(xmpp_ctx_t *ctx,
                            const unsigned char *buf,
                            size_t len,
                            resolver_srv_rr_t **srv_rr_list)
{
    int set;

#ifdef HAVE_CARES
    set = resolver_ares_srv_lookup_buf(ctx, buf, len, srv_rr_list);
#else
    set = resolver_raw_srv_lookup_buf(ctx, buf, len, srv_rr_list);
    if (set != XMPP_DOMAIN_FOUND && *srv_rr_list != NULL) {
        resolver_srv_free(ctx, *srv_rr_list);
        *srv_rr_list = NULL;
    }
#endif
    resolver_srv_list_sort(srv_rr_list);

    return set;
}

int resolver_srv_lookup(xmpp_ctx_t *ctx,
                        const char *service,
                        const char *proto,
                        const char *domain,
                        resolver_srv_rr_t **srv_rr_list)
{
#define RESOLVER_BUF_MAX 65536
    unsigned char *buf;
    char fulldomain[2048];
    int len;
    int set = XMPP_DOMAIN_NOT_FOUND;

    (void)buf;
    (void)len;

    strophe_snprintf(fulldomain, sizeof(fulldomain), "_%s._%s.%s", service,
                     proto, domain);

    *srv_rr_list = NULL;

#ifdef HAVE_CARES

    set = resolver_ares_srv_lookup(ctx, fulldomain, srv_rr_list);

#else /* HAVE_CARES */

#ifdef _WIN32
    set = resolver_win32_srv_lookup(ctx, fulldomain, srv_rr_list);
    if (set == XMPP_DOMAIN_FOUND)
        return set;
#endif /* _WIN32 */

    buf = strophe_alloc(ctx, RESOLVER_BUF_MAX);
    if (buf == NULL)
        return XMPP_DOMAIN_NOT_FOUND;

#ifdef _WIN32
    len = resolver_win32_srv_query(fulldomain, buf, RESOLVER_BUF_MAX);
#else  /* _WIN32 */
    len = res_query(fulldomain, MESSAGE_C_IN, MESSAGE_T_SRV, buf,
                    RESOLVER_BUF_MAX);
#endif /* _WIN32 */

    if (len > 0)
        set = resolver_srv_lookup_buf(ctx, buf, (size_t)len, srv_rr_list);

    strophe_free(ctx, buf);

#endif /* HAVE_CARES */

    return set;
}

void resolver_srv_free(xmpp_ctx_t *ctx, resolver_srv_rr_t *srv_rr_list)
{
    resolver_srv_rr_t *rr;

    while (srv_rr_list != NULL) {
        rr = srv_rr_list->next;
        strophe_free(ctx, srv_rr_list);
        srv_rr_list = rr;
    }
}

#ifndef HAVE_CARES
/*******************************************************************************
 * Resolver raw implementation.
 *
 * This code is common for both unix and win32.
 ******************************************************************************/

struct message_header {
    uint16_t id;
    uint8_t octet2;
    uint8_t octet3;
    uint16_t qdcount;
    uint16_t ancount;
    uint16_t nscount;
    uint16_t arcount;
};

/* the same as ntohs(), but receives pointer to the value */
static uint16_t xmpp_ntohs_ptr(const void *ptr)
{
    const uint8_t *p = (const uint8_t *)ptr;

    return (uint16_t)((p[0] << 8U) + p[1]);
}

static uint8_t message_header_qr(const struct message_header *header)
{
    return (header->octet2 >> 7) & 1;
}

static uint8_t message_header_rcode(const struct message_header *header)
{
    return header->octet3 & 0x0f;
}

/*
 * Append a label or a dot to the target name with buffer overflow checks.
 * Returns length of the non-truncated resulting string, may be bigger than
 * name_max.
 */
static size_t message_name_append_safe(char *name,
                                       size_t name_len,
                                       size_t name_max,
                                       const char *tail,
                                       size_t tail_len)
{
    size_t copy_len;

    copy_len = name_max > name_len ? name_max - name_len : 0;
    copy_len = xmpp_min(tail_len, copy_len);
    if (copy_len > 0)
        memcpy(&name[name_len], tail, copy_len);

    return name_len + tail_len;
}

/* Returns length of the compressed name. This is NOT the same as strlen(). */
static unsigned message_name_get(const unsigned char *buf,
                                 size_t buf_len,
                                 unsigned buf_offset,
                                 char *name,
                                 size_t name_max)
{
    size_t name_len = 0;
    unsigned i = buf_offset;
    unsigned pointer;
    unsigned rc;
    unsigned char label_len;

    while (1) {
        if (i >= buf_len)
            return 0;
        label_len = buf[i++];
        if (label_len == 0)
            break;

        /* Label */
        if ((label_len & 0xc0) == 0) {
            if (i + label_len - 1 >= buf_len)
                return 0;
            if (name != NULL) {
                name_len = message_name_append_safe(name, name_len, name_max,
                                                    (char *)&buf[i], label_len);
                name_len =
                    message_name_append_safe(name, name_len, name_max, ".", 1);
            }
            i += label_len;

            /* Pointer */
        } else if ((label_len & 0xc0) == 0xc0) {
            if (i >= buf_len)
                return 0;
            pointer = (label_len & 0x3f) << 8 | buf[i++];
            /* Prevent infinite looping */
            if (pointer >= buf_offset)
                return 0;
            if (name != NULL && name_len >= name_max && name_max > 0) {
                /* We have filled the name buffer. Don't pass it recursively. */
                name[name_max - 1] = '\0';
                name = NULL;
                name_max = 0;
            }
            rc = message_name_get(
                buf, buf_len, pointer, name != NULL ? &name[name_len] : NULL,
                name_max > name_len ? name_max - name_len : 0);
            if (rc == 0)
                return 0;
            /* Pointer is always the last. */
            break;

            /* The 10 and 01 combinations are reserved for future use. */
        } else {
            return 0;
        }
    }
    if (label_len == 0) {
        if (name_len == 0)
            name_len = 1;
        /*
         * At this point name_len is length of the resulting name,
         * including '\0'. This value can be exported to allocate buffer
         * of precise size.
         */
        if (name != NULL && name_max > 0) {
            /*
             * Overwrite leading '.' with a '\0'. If the resulting name is
             * bigger than name_max it is truncated.
             */
            name[xmpp_min(name_len, name_max) - 1] = '\0';
        }
    }

    return i - buf_offset;
}

static unsigned
message_name_len(const unsigned char *buf, size_t buf_len, unsigned buf_offset)
{
    return message_name_get(buf, buf_len, buf_offset, NULL, SIZE_MAX);
}

#define BUF_OVERFLOW_CHECK(ptr, len)                  \
    do {                                              \
        if ((ptr) >= (len)) {                         \
            if (*srv_rr_list != NULL)                 \
                resolver_srv_free(ctx, *srv_rr_list); \
            *srv_rr_list = NULL;                      \
            return XMPP_DOMAIN_NOT_FOUND;             \
        }                                             \
    } while (0)

static int resolver_raw_srv_lookup_buf(xmpp_ctx_t *ctx,
                                       const unsigned char *buf,
                                       size_t len,
                                       resolver_srv_rr_t **srv_rr_list)
{
    unsigned i;
    unsigned j;
    unsigned name_len;
    unsigned rdlength;
    uint16_t type;
    uint16_t class;
    struct message_header header;
    resolver_srv_rr_t *rr;

    *srv_rr_list = NULL;

    if (len < MESSAGE_HEADER_LEN)
        return XMPP_DOMAIN_NOT_FOUND;

    header.id = xmpp_ntohs_ptr(&buf[0]);
    header.octet2 = buf[2];
    header.octet3 = buf[3];
    header.qdcount = xmpp_ntohs_ptr(&buf[4]);
    header.ancount = xmpp_ntohs_ptr(&buf[6]);
    header.nscount = xmpp_ntohs_ptr(&buf[8]);
    header.arcount = xmpp_ntohs_ptr(&buf[10]);
    if (message_header_qr(&header) != MESSAGE_RESPONSE ||
        message_header_rcode(&header) != 0) {
        return XMPP_DOMAIN_NOT_FOUND;
    }
    j = MESSAGE_HEADER_LEN;

    /* skip question section */
    for (i = 0; i < header.qdcount; ++i) {
        BUF_OVERFLOW_CHECK(j, len);
        name_len = message_name_len(buf, len, j);
        /* error in name format */
        if (name_len == 0)
            return XMPP_DOMAIN_NOT_FOUND;
        j += name_len + 4;
    }

    for (i = 0; i < header.ancount; ++i) {
        BUF_OVERFLOW_CHECK(j, len);
        name_len = message_name_len(buf, len, j);
        /* error in name format */
        if (name_len == 0)
            return XMPP_DOMAIN_NOT_FOUND;
        j += name_len;
        BUF_OVERFLOW_CHECK(j + 16, len);
        type = xmpp_ntohs_ptr(&buf[j]);
        class = xmpp_ntohs_ptr(&buf[j + 2]);
        rdlength = xmpp_ntohs_ptr(&buf[j + 8]);
        j += 10;
        if (type == MESSAGE_T_SRV && class == MESSAGE_C_IN) {
            rr = resolver_srv_rr_new(ctx, NULL, 0, 0, 0);
            if (rr) {
                rr->next = *srv_rr_list;
                rr->priority = xmpp_ntohs_ptr(&buf[j]);
                rr->weight = xmpp_ntohs_ptr(&buf[j + 2]);
                rr->port = xmpp_ntohs_ptr(&buf[j + 4]);
                name_len = message_name_get(buf, len, j + 6, rr->target,
                                            sizeof(rr->target));
                if (name_len > 0)
                    *srv_rr_list = rr;
                else
                    strophe_free(ctx, rr); /* skip broken record */
            }
        }
        j += rdlength;
    }

    return *srv_rr_list != NULL ? XMPP_DOMAIN_FOUND : XMPP_DOMAIN_NOT_FOUND;
}

#endif /* !HAVE_CARES */

#ifdef HAVE_CARES
/*******************************************************************************
 * Resolver implementation using c-ares library.
 ******************************************************************************/

struct resolver_ares_ctx {
    xmpp_ctx_t *ctx;
    int result;
    resolver_srv_rr_t *srv_rr_list;
};

static int resolver_ares_srv_lookup_buf(xmpp_ctx_t *ctx,
                                        const unsigned char *buf,
                                        size_t len,
                                        resolver_srv_rr_t **srv_rr_list)
{
    struct ares_srv_reply *srv;
    struct ares_srv_reply *item;
    resolver_srv_rr_t *rr;
    int rc;

    *srv_rr_list = NULL;

    rc = ares_parse_srv_reply(buf, len, &srv);
    if (rc != ARES_SUCCESS)
        return XMPP_DOMAIN_NOT_FOUND;

    item = srv;
    while (item != NULL) {
        rr = strophe_alloc(ctx, sizeof(*rr));
        if (rr == NULL)
            break;
        rr->next = *srv_rr_list;
        rr->priority = item->priority;
        rr->weight = item->weight;
        rr->port = item->port;
        strncpy(rr->target, item->host, sizeof(rr->target) - 1);
        rr->target[sizeof(rr->target) - 1] = '\0';
        *srv_rr_list = rr;
        item = item->next;
    }
    ares_free_data(srv);

    return *srv_rr_list == NULL ? XMPP_DOMAIN_NOT_FOUND : XMPP_DOMAIN_FOUND;
}

static void ares_srv_lookup_callback(
    void *arg, int status, int timeouts, unsigned char *buf, int len)
{
    struct resolver_ares_ctx *actx = arg;

    (void)timeouts;

    if (status != ARES_SUCCESS)
        actx->result = XMPP_DOMAIN_NOT_FOUND;
    else
        actx->result = resolver_ares_srv_lookup_buf(actx->ctx, buf, len,
                                                    &actx->srv_rr_list);
}

static int resolver_ares_srv_lookup(xmpp_ctx_t *ctx,
                                    const char *fulldomain,
                                    resolver_srv_rr_t **srv_rr_list)
{
    struct resolver_ares_ctx actx;
    ares_channel chan;
    struct timeval tv;
    struct timeval *tvp;
    fd_set rfds;
    fd_set wfds;
    int nfds;
    int rc;

    actx.ctx = ctx;
    actx.result = XMPP_DOMAIN_NOT_FOUND;
    actx.srv_rr_list = NULL;

    rc = ares_init(&chan);
    if (rc == ARES_SUCCESS) {
        ares_query(chan, fulldomain, MESSAGE_C_IN, MESSAGE_T_SRV,
                   ares_srv_lookup_callback, &actx);
        while (1) {
            FD_ZERO(&rfds);
            FD_ZERO(&wfds);
            nfds = ares_fds(chan, &rfds, &wfds);
            if (nfds == 0)
                break;
            tvp = ares_timeout(chan, NULL, &tv);
            select(nfds, &rfds, &wfds, NULL, tvp);
            ares_process(chan, &rfds, &wfds);
        }
        ares_destroy(chan);
    }

    *srv_rr_list = actx.srv_rr_list;
    return actx.result;
}

#endif /* HAVE_CARES */

#if defined(_WIN32) && !defined(HAVE_CARES)
/*******************************************************************************
 * Next part was copied from sock.c and contains old win32 code.
 *
 * The idea is to get raw response from a name server and pass it to
 * resolver_srv_lookup_buf(). In fact, resolver_win32_srv_query() replaces
 * the call of res_query().
 * Dnsapi code is moved to a separated function resolver_srv_win32_lookup() and
 * changed to meet new API.
 *
 * XXX If the code is compiled it should work like before.
 ******************************************************************************/

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windns.h>
#include <iphlpapi.h>

struct dnsquery_header {
    unsigned short id;
    unsigned char qr;
    unsigned char opcode;
    unsigned char aa;
    unsigned char tc;
    unsigned char rd;
    unsigned char ra;
    unsigned char z;
    unsigned char rcode;
    unsigned short qdcount;
    unsigned short ancount;
    unsigned short nscount;
    unsigned short arcount;
};

struct dnsquery_question {
    char qname[1024];
    unsigned short qtype;
    unsigned short qclass;
};

static void netbuf_add_16bitnum(unsigned char *buf,
                                int buflen,
                                int *offset,
                                unsigned short num)
{
    unsigned char *start = buf + *offset;
    unsigned char *p = start;

    /* assuming big endian */
    *p++ = (num >> 8) & 0xff;
    *p++ = (num)&0xff;

    *offset += 2;
}

static void
netbuf_add_domain_name(unsigned char *buf, int buflen, int *offset, char *name)
{
    unsigned char *start = buf + *offset;
    unsigned char *p = start;
    unsigned char *wordstart, *wordend;

    wordstart = (unsigned char *)name;

    while (*wordstart) {
        int len;
        wordend = wordstart;
        while (*wordend && *wordend != '.') {
            wordend++;
        }

        len = (int)(wordend - wordstart);

        if (len > 0x3F) {
            len = 0x3F;
        }

        *p++ = len;

        while (wordstart != wordend) {
            *p++ = *wordstart++;
        }

        if (*wordstart == '.') {
            wordstart++;
        }
    }

    *p++ = '\0';

    *offset += (int)(p - start);
}

static void netbuf_add_dnsquery_header(unsigned char *buf,
                                       int buflen,
                                       int *offset,
                                       struct dnsquery_header *header)
{
    unsigned char *p;

    netbuf_add_16bitnum(buf, buflen, offset, header->id);

    p = buf + *offset;
    *p++ = ((header->qr & 0x01) << 7) | ((header->opcode & 0x0F) << 3) |
           ((header->aa & 0x01) << 2) | ((header->tc & 0x01) << 1) |
           ((header->rd & 0x01));
    *p++ = ((header->ra & 0x01) << 7) | ((header->z & 0x07) << 4) |
           ((header->rcode & 0x0F));
    *offset += 2;

    netbuf_add_16bitnum(buf, buflen, offset, header->qdcount);
    netbuf_add_16bitnum(buf, buflen, offset, header->ancount);
    netbuf_add_16bitnum(buf, buflen, offset, header->nscount);
    netbuf_add_16bitnum(buf, buflen, offset, header->arcount);
}

static void netbuf_add_dnsquery_question(unsigned char *buf,
                                         int buflen,
                                         int *offset,
                                         struct dnsquery_question *question)
{
    netbuf_add_domain_name(buf, buflen, offset, question->qname);
    netbuf_add_16bitnum(buf, buflen, offset, question->qtype);
    netbuf_add_16bitnum(buf, buflen, offset, question->qclass);
}

static int resolver_win32_srv_lookup(xmpp_ctx_t *ctx,
                                     const char *fulldomain,
                                     resolver_srv_rr_t **srv_rr_list)
{
    resolver_srv_rr_t *rr;
    HINSTANCE hdnsapi = NULL;

    DNS_STATUS(WINAPI * pDnsQuery_A)
    (PCSTR, WORD, DWORD, PIP4_ARRAY, DNS_RECORDA **, PVOID *);
    void(WINAPI * pDnsRecordListFree)(DNS_RECORDA *, DNS_FREE_TYPE);

    if (hdnsapi = LoadLibrary("dnsapi.dll")) {
        pDnsQuery_A = (void *)GetProcAddress(hdnsapi, "DnsQuery_A");
        pDnsRecordListFree =
            (void *)GetProcAddress(hdnsapi, "DnsRecordListFree");

        if (pDnsQuery_A && pDnsRecordListFree) {
            DNS_RECORDA *dnsrecords = NULL;
            DNS_STATUS error;

            error = pDnsQuery_A(fulldomain, DNS_TYPE_SRV, DNS_QUERY_STANDARD,
                                NULL, &dnsrecords, NULL);

            if (error == 0) {
                DNS_RECORDA *current = dnsrecords;

                while (current) {
                    if (current->wType == DNS_TYPE_SRV) {
                        rr = strophe_alloc(ctx, sizeof(*rr));
                        if (rr == NULL)
                            break;
                        rr->next = *srv_rr_list;
                        rr->port = current->Data.Srv.wPort;
                        rr->priority = current->Data.Srv.wPriority;
                        rr->weight = current->Data.Srv.wWeight;
                        strophe_snprintf(rr->target, sizeof(rr->target), "%s",
                                         current->Data.Srv.pNameTarget);
                        *srv_rr_list = rr;
                    }
                    current = current->pNext;
                }
            }

            pDnsRecordListFree(dnsrecords, DnsFreeRecordList);
        }

        FreeLibrary(hdnsapi);
    }
    resolver_srv_list_sort(srv_rr_list);

    return *srv_rr_list != NULL ? XMPP_DOMAIN_FOUND : XMPP_DOMAIN_NOT_FOUND;
}

static int
resolver_win32_srv_query(const char *fulldomain, unsigned char *buf, size_t len)
{
    int set = 0;
    int insize = 0;

    /* if dnsapi didn't work/isn't there, try querying the dns server manually
     */
    if (!set) {
        struct dnsquery_header header;
        struct dnsquery_question question;
        int offset = 0;
        int addrlen;
        sock_t sock;
        struct sockaddr_in dnsaddr;
        char dnsserverips[16][256];
        int numdnsservers = 0;
        int j;

        /* Try getting the DNS server ips from GetNetworkParams() in iphlpapi
         * first */
        if (!numdnsservers) {
            HINSTANCE hiphlpapi = NULL;
            DWORD(WINAPI * pGetNetworkParams)(PFIXED_INFO, PULONG);

            if (hiphlpapi = LoadLibrary("Iphlpapi.dll")) {
                pGetNetworkParams =
                    (void *)GetProcAddress(hiphlpapi, "GetNetworkParams");

                if (pGetNetworkParams) {
                    FIXED_INFO *fi;
                    ULONG len;
                    DWORD error;
                    char buffer[65535];

                    len = 65535;
                    fi = (FIXED_INFO *)buffer;

                    if ((error = pGetNetworkParams(fi, &len)) ==
                        ERROR_SUCCESS) {
                        IP_ADDR_STRING *pias = &(fi->DnsServerList);

                        while (pias && numdnsservers < 16) {
                            strcpy(dnsserverips[numdnsservers++],
                                   pias->IpAddress.String);
                            pias = pias->Next;
                        }
                    }
                }
            }
            FreeLibrary(hiphlpapi);
        }

        /* Next, try getting the DNS server ips from the registry */
        if (!numdnsservers) {
            HKEY search;
            LONG error;

            error = RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", 0,
                KEY_READ, &search);

            if (error != ERROR_SUCCESS) {
                error = RegOpenKeyEx(
                    HKEY_LOCAL_MACHINE,
                    "SYSTEM\\CurrentControlSet\\Services\\VxD\\MSTCP", 0,
                    KEY_READ, &search);
            }

            if (error == ERROR_SUCCESS) {
                char name[512];
                DWORD len = 512;

                error = RegQueryValueEx(search, "NameServer", NULL, NULL,
                                        (LPBYTE)name, &len);

                if (error != ERROR_SUCCESS) {
                    error = RegQueryValueEx(search, "DhcpNameServer", NULL,
                                            NULL, (LPBYTE)name, &len);
                }

                if (error == ERROR_SUCCESS) {
                    char *parse = "0123456789.", *start, *end;
                    start = name;
                    end = name;
                    name[len] = '\0';

                    while (*start && numdnsservers < 16) {
                        while (strchr(parse, *end)) {
                            end++;
                        }

                        strncpy(dnsserverips[numdnsservers++], start,
                                end - start);

                        while (*end && !strchr(parse, *end)) {
                            end++;
                        }

                        start = end;
                    }
                }
            }

            RegCloseKey(search);
        }

        if (!numdnsservers) {
            HKEY searchlist;
            LONG error;

            error = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                                 "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\P"
                                 "arameters\\Interfaces",
                                 0, KEY_READ, &searchlist);

            if (error == ERROR_SUCCESS) {
                unsigned int i;
                DWORD numinterfaces = 0;

                RegQueryInfoKey(searchlist, NULL, NULL, NULL, &numinterfaces,
                                NULL, NULL, NULL, NULL, NULL, NULL, NULL);

                for (i = 0; i < numinterfaces; i++) {
                    char name[512];
                    DWORD len = 512;
                    HKEY searchentry;

                    RegEnumKeyEx(searchlist, i, (LPTSTR)name, &len, NULL, NULL,
                                 NULL, NULL);

                    if (RegOpenKeyEx(searchlist, name, 0, KEY_READ,
                                     &searchentry) == ERROR_SUCCESS) {
                        if (RegQueryValueEx(searchentry, "DhcpNameServer", NULL,
                                            NULL, (LPBYTE)name,
                                            &len) == ERROR_SUCCESS) {
                            char *parse = "0123456789.", *start, *end;
                            start = name;
                            end = name;
                            name[len] = '\0';

                            while (*start && numdnsservers < 16) {
                                while (strchr(parse, *end)) {
                                    end++;
                                }

                                strncpy(dnsserverips[numdnsservers++], start,
                                        end - start);

                                while (*end && !strchr(parse, *end)) {
                                    end++;
                                }

                                start = end;
                            }
                        } else if (RegQueryValueEx(searchentry, "NameServer",
                                                   NULL, NULL, (LPBYTE)name,
                                                   &len) == ERROR_SUCCESS) {
                            char *parse = "0123456789.", *start, *end;
                            start = name;
                            end = name;
                            name[len] = '\0';

                            while (*start && numdnsservers < 16) {
                                while (strchr(parse, *end)) {
                                    end++;
                                }

                                strncpy(dnsserverips[numdnsservers++], start,
                                        end - start);

                                while (*end && !strchr(parse, *end)) {
                                    end++;
                                }

                                start = end;
                            }
                        }
                        RegCloseKey(searchentry);
                    }
                }
                RegCloseKey(searchlist);
            }
        }

        /* If we have a DNS server, use it */
        if (numdnsservers) {
            ULONG nonblocking = 1;
            int i;

            memset(&header, 0, sizeof(header));
            header.id = 12345; /* FIXME: Get a better id here */
            header.rd = 1;
            header.qdcount = 1;

            netbuf_add_dnsquery_header(buf, (int)len, &offset, &header);

            memset(&question, 0, sizeof(question));
            strncpy(question.qname, fulldomain, 1024);
            question.qtype = MESSAGE_T_SRV; /* SRV */
            question.qclass = MESSAGE_C_IN; /* INTERNET! */

            netbuf_add_dnsquery_question(buf, (int)len, &offset, &question);

            insize = 0;
            for (i = 0; i < numdnsservers && insize <= 0; i++) {
                sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
                ioctlsocket(sock, FIONBIO, &nonblocking);

                memset(&dnsaddr, 0, sizeof(dnsaddr));

                dnsaddr.sin_family = AF_INET;
                dnsaddr.sin_port = htons(53);
                dnsaddr.sin_addr.s_addr = inet_addr(dnsserverips[i]);

                addrlen = sizeof(dnsaddr);
                sendto(sock, (char *)buf, offset, 0,
                       (struct sockaddr *)&dnsaddr, addrlen);
                for (j = 0; j < 50; j++) {
                    insize = recvfrom(sock, (char *)buf, (int)len, 0,
                                      (struct sockaddr *)&dnsaddr, &addrlen);
                    if (insize == SOCKET_ERROR) {
                        if (sock_error() == WSAEWOULDBLOCK) {
                            Sleep(100);
                        } else {
                            break;
                        }
                    } else {
                        break;
                    }
                }

                closesocket(sock);
            }
            set = insize > 0;
        }
    }

    return set ? insize : -1;
}

#endif /* _WIN32 && !HAVE_CARES */
