/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS system libraries
 * FILE:        lib/dnsapi/dnsapi/query.c
 * PURPOSE:     DNSAPI functions built on the ADNS library.
 * PROGRAMER:   Art Yerkes
 * UPDATE HISTORY:
 *              12/15/03 -- Created
 */

#include "precomp.h"
#include <winreg.h>
#include <iphlpapi.h>
#include <strsafe.h>

#define NDEBUG
#include <debug.h>

static
BOOL
ParseIpv4Address(
    _In_ PCWSTR AddressString,
    _Out_ PIN_ADDR pAddress)
{
    PCWSTR pTerminator = NULL;
    NTSTATUS Status;

    Status = RtlIpv4StringToAddressW(AddressString,
                                     TRUE,
                                     &pTerminator,
                                     pAddress);
    if (NT_SUCCESS(Status) && pTerminator != NULL && *pTerminator == L'\0')
        return TRUE;

    return FALSE;
}


static
BOOL
ParseIpv6Address(
    _In_ PCWSTR AddressString,
    _Out_ PIN6_ADDR pAddress)
{
    PCWSTR pTerminator = NULL;
    NTSTATUS Status;

    Status = RtlIpv6StringToAddressW(AddressString,
                                     &pTerminator,
                                     pAddress);
    if (NT_SUCCESS(Status) && pTerminator != NULL && *pTerminator == L'\0')
        return TRUE;

    return FALSE;
}


static
PDNS_RECORDW
CreateRecordForIpAddress(
    _In_ PCWSTR Name,
    _In_ WORD Type)
{
    IN_ADDR Ip4Address;
    IN6_ADDR Ip6Address;
    PDNS_RECORDW pRecord = NULL;

    if (Type == DNS_TYPE_A)
    {
        if (ParseIpv4Address(Name, &Ip4Address))
        {
            pRecord = RtlAllocateHeap(RtlGetProcessHeap(),
                                      HEAP_ZERO_MEMORY,
                                      sizeof(DNS_RECORDW));
            if (pRecord == NULL)
                return NULL;

            pRecord->pName = RtlAllocateHeap(RtlGetProcessHeap(),
                                             0,
                                             (wcslen(Name) + 1) * sizeof(WCHAR));
            if (pRecord == NULL)
            {
                RtlFreeHeap(RtlGetProcessHeap(), 0, pRecord);
                return NULL;
            }

            wcscpy(pRecord->pName, Name);
            pRecord->wType = DNS_TYPE_A;
            pRecord->wDataLength = sizeof(DNS_A_DATA);
            pRecord->Flags.S.Section = DnsSectionQuestion;
            pRecord->Flags.S.CharSet = DnsCharSetUnicode;
            pRecord->dwTtl = 7 * 24 * 60 * 60;

            pRecord->Data.A.IpAddress = Ip4Address.S_un.S_addr;

            return pRecord;
        }
    }
    else if (Type == DNS_TYPE_AAAA)
    {
        if (ParseIpv6Address(Name, &Ip6Address))
        {
            pRecord = RtlAllocateHeap(RtlGetProcessHeap(),
                                      HEAP_ZERO_MEMORY,
                                      sizeof(DNS_RECORDW));
            if (pRecord == NULL)
                return NULL;

            pRecord->pName = RtlAllocateHeap(RtlGetProcessHeap(),
                                             0,
                                             (wcslen(Name) + 1) * sizeof(WCHAR));
            if (pRecord == NULL)
            {
                RtlFreeHeap(RtlGetProcessHeap(), 0, pRecord);
                return NULL;
            }

            wcscpy(pRecord->pName, Name);
            pRecord->wType = DNS_TYPE_AAAA;
            pRecord->wDataLength = sizeof(DNS_AAAA_DATA);
            pRecord->Flags.S.Section = DnsSectionQuestion;
            pRecord->Flags.S.CharSet = DnsCharSetUnicode;
            pRecord->dwTtl = 7 * 24 * 60 * 60;

            CopyMemory(&pRecord->Data.AAAA.Ip6Address,
                       &Ip6Address.u.Byte,
                       sizeof(IN6_ADDR));

            return pRecord;
        }
    }

    return NULL;
}


/* DnsQuery ****************************
 * Begin a DNS query, and allow the result to be placed in the application
 * supplied result pointer.  The result can be manipulated with the record
 * functions.
 *
 * Name                 -- The DNS object to be queried.
 * Type                 -- The type of records to be returned.  These are
 *                          listed in windns.h
 * Options              -- Query options.  DNS_QUERY_STANDARD is the base
 *                          state, and every other option takes precedence.
 *                          multiple options can be combined.  Listed in
 *                          windns.h
 * Servers              -- List of alternate servers (optional)
 * QueryResultSet       -- Pointer to the result pointer that will be filled
 *                          when the response is available.
 * Reserved             -- Response as it appears on the wire.  Optional.
 */

static PCHAR
DnsWToC(const WCHAR *WideString)
{
    PCHAR AnsiString;
    int AnsiLen = WideCharToMultiByte(CP_ACP,
                                      0,
                                      WideString,
                                      -1,
                                      NULL,
                                      0,
                                      NULL,
                                      0);
    if (AnsiLen == 0)
        return NULL;
    AnsiString = RtlAllocateHeap(RtlGetProcessHeap(), 0, AnsiLen);
    if (AnsiString == NULL)
    {
        return NULL;
    }
    WideCharToMultiByte(CP_ACP,
                        0,
                        WideString,
                        -1,
                        AnsiString,
                        AnsiLen,
                        NULL,
                        0);

    return AnsiString;
}

static PWCHAR
DnsCToW(const CHAR *NarrowString)
{
    PWCHAR WideString;
    int WideLen = MultiByteToWideChar(CP_ACP,
                                      0,
                                      NarrowString,
                                      -1,
                                      NULL,
                                      0);
    if (WideLen == 0)
        return NULL;
    WideString = RtlAllocateHeap(RtlGetProcessHeap(), 0, WideLen * sizeof(WCHAR));
    if (WideString == NULL)
    {
        return NULL;
    }
    MultiByteToWideChar(CP_ACP,
                        0,
                        NarrowString,
                        -1,
                        WideString,
                        WideLen);

    return WideString;
}

static PCHAR
DnsWToUTF8(const WCHAR *WideString)
{
    PCHAR AnsiString;
    int AnsiLen = WideCharToMultiByte(CP_UTF8,
                                      0,
                                      WideString,
                                      -1,
                                      NULL,
                                      0,
                                      NULL,
                                      0);
    if (AnsiLen == 0)
        return NULL;
    AnsiString = RtlAllocateHeap(RtlGetProcessHeap(), 0, AnsiLen);
    if (AnsiString == NULL)
    {
        return NULL;
    }
    WideCharToMultiByte(CP_UTF8,
                        0,
                        WideString,
                        -1,
                        AnsiString,
                        AnsiLen,
                        NULL,
                        0);

    return AnsiString;
}

static PWCHAR
DnsUTF8ToW(const CHAR *NarrowString)
{
    PWCHAR WideString;
    int WideLen = MultiByteToWideChar(CP_UTF8,
                                      0,
                                      NarrowString,
                                      -1,
                                      NULL,
                                      0);
    if (WideLen == 0)
        return NULL;
    WideString = RtlAllocateHeap(RtlGetProcessHeap(), 0, WideLen * sizeof(WCHAR));
    if (WideString == NULL)
    {
        return NULL;
    }
    MultiByteToWideChar(CP_UTF8,
                        0,
                        NarrowString,
                        -1,
                        WideString,
                        WideLen);

    return WideString;
}

DNS_STATUS WINAPI
DnsQuery_CodePage(UINT CodePage,
           LPCSTR Name,
           WORD Type,
           DWORD Options,
           PVOID Extra,
           PDNS_RECORD *QueryResultSet,
           PVOID *Reserved)
{
    UINT i;
    PWCHAR Buffer;
    DNS_STATUS Status;
    PDNS_RECORD QueryResultWide;
    PDNS_RECORD ConvertedRecord = 0, LastRecord = 0;

    if (Name == NULL)
        return ERROR_INVALID_PARAMETER;
    if (QueryResultSet == NULL)
        return ERROR_INVALID_PARAMETER;

    switch (CodePage)
    {
    case CP_ACP:
        Buffer = DnsCToW(Name);
        break;

    case CP_UTF8:
        Buffer = DnsUTF8ToW(Name);
        break;

    default:
        return ERROR_INVALID_PARAMETER;
    }

    Status = DnsQuery_W(Buffer, Type, Options, Extra, &QueryResultWide, Reserved);

    while (Status == ERROR_SUCCESS && QueryResultWide)
    {
        switch (QueryResultWide->wType)
        {
        case DNS_TYPE_A:
        case DNS_TYPE_WKS:
        case DNS_TYPE_CNAME:
        case DNS_TYPE_PTR:
        case DNS_TYPE_NS:
        case DNS_TYPE_MB:
        case DNS_TYPE_MD:
        case DNS_TYPE_MF:
        case DNS_TYPE_MG:
        case DNS_TYPE_MR:
            ConvertedRecord = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(DNS_RECORD));
            break;

        case DNS_TYPE_MINFO:
            ConvertedRecord = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(DNS_TXT_DATA) + QueryResultWide->Data.TXT.dwStringCount);
            break;

        case DNS_TYPE_NULL:
            ConvertedRecord = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(DNS_NULL_DATA) + QueryResultWide->Data.Null.dwByteCount);
            break;
        }
        if (ConvertedRecord == NULL)
        {
            /* The name */
            RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
            /* The result*/
            DnsIntFreeRecordList(QueryResultWide);
            QueryResultSet = NULL;
            return ERROR_OUTOFMEMORY;
        }

        if (CodePage == CP_ACP)
        {
            ConvertedRecord->pName = DnsWToC((PWCHAR)QueryResultWide->pName);
            ConvertedRecord->Flags.S.CharSet = DnsCharSetAnsi;
        }
        else
        {
            ConvertedRecord->pName = DnsWToUTF8((PWCHAR)QueryResultWide->pName);
            ConvertedRecord->Flags.S.CharSet = DnsCharSetUtf8;
        }

        ConvertedRecord->wType = QueryResultWide->wType;

        switch (QueryResultWide->wType)
        {
        case DNS_TYPE_A:
        case DNS_TYPE_WKS:
            ConvertedRecord->wDataLength = QueryResultWide->wDataLength;
            memcpy(&ConvertedRecord->Data, &QueryResultWide->Data, QueryResultWide->wDataLength);
            break;

        case DNS_TYPE_CNAME:
        case DNS_TYPE_PTR:
        case DNS_TYPE_NS:
        case DNS_TYPE_MB:
        case DNS_TYPE_MD:
        case DNS_TYPE_MF:
        case DNS_TYPE_MG:
        case DNS_TYPE_MR:
            ConvertedRecord->wDataLength = sizeof(DNS_PTR_DATA);
            if (CodePage == CP_ACP)
                ConvertedRecord->Data.PTR.pNameHost = DnsWToC((PWCHAR)QueryResultWide->Data.PTR.pNameHost);
            else
                ConvertedRecord->Data.PTR.pNameHost = DnsWToUTF8((PWCHAR)QueryResultWide->Data.PTR.pNameHost);
            break;

        case DNS_TYPE_MINFO:
            ConvertedRecord->wDataLength = sizeof(DNS_MINFO_DATA);
            if (CodePage == CP_ACP)
            {
                ConvertedRecord->Data.MINFO.pNameMailbox = DnsWToC((PWCHAR)QueryResultWide->Data.MINFO.pNameMailbox);
                ConvertedRecord->Data.MINFO.pNameErrorsMailbox = DnsWToC((PWCHAR)QueryResultWide->Data.MINFO.pNameErrorsMailbox);
            }
            else
            {
                ConvertedRecord->Data.MINFO.pNameMailbox = DnsWToUTF8((PWCHAR)QueryResultWide->Data.MINFO.pNameMailbox);
                ConvertedRecord->Data.MINFO.pNameErrorsMailbox = DnsWToUTF8((PWCHAR)QueryResultWide->Data.MINFO.pNameErrorsMailbox);
            }
            break;

        case DNS_TYPE_MX:
            ConvertedRecord->wDataLength = sizeof(DNS_MX_DATA);
            if (CodePage == CP_ACP)
                ConvertedRecord->Data.MX.pNameExchange = DnsWToC((PWCHAR)QueryResultWide->Data.MX.pNameExchange);
            else
                ConvertedRecord->Data.MX.pNameExchange = DnsWToUTF8((PWCHAR)QueryResultWide->Data.MX.pNameExchange);
            ConvertedRecord->Data.MX.wPreference = QueryResultWide->Data.MX.wPreference;
            break;

        case DNS_TYPE_HINFO:
            ConvertedRecord->wDataLength = sizeof(DNS_TXT_DATA) + (sizeof(PCHAR) * QueryResultWide->Data.TXT.dwStringCount);
            ConvertedRecord->Data.TXT.dwStringCount = QueryResultWide->Data.TXT.dwStringCount;

            if (CodePage == CP_ACP)
                for (i = 0; i < ConvertedRecord->Data.TXT.dwStringCount; i++)
                    ConvertedRecord->Data.TXT.pStringArray[i] = DnsWToC((PWCHAR)QueryResultWide->Data.TXT.pStringArray[i]);
            else
                for (i = 0; i < ConvertedRecord->Data.TXT.dwStringCount; i++)
                    ConvertedRecord->Data.TXT.pStringArray[i] = DnsWToUTF8((PWCHAR)QueryResultWide->Data.TXT.pStringArray[i]);

            break;

        case DNS_TYPE_NULL:
            ConvertedRecord->wDataLength = sizeof(DNS_NULL_DATA) + QueryResultWide->Data.Null.dwByteCount;
            ConvertedRecord->Data.Null.dwByteCount = QueryResultWide->Data.Null.dwByteCount;
            memcpy(&ConvertedRecord->Data.Null.Data, &QueryResultWide->Data.Null.Data, QueryResultWide->Data.Null.dwByteCount);
            break;
        }

        if (LastRecord)
        {
            LastRecord->pNext = ConvertedRecord;
            LastRecord = LastRecord->pNext;
        }
        else
        {
            LastRecord = *QueryResultSet = ConvertedRecord;
        }

        QueryResultWide = QueryResultWide->pNext;
    }

    if (LastRecord)
        LastRecord->pNext = 0;

    /* The name */
    RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
    /* The result*/
    if (QueryResultWide) DnsIntFreeRecordList(QueryResultWide);

    return Status;
}

DNS_STATUS WINAPI
DnsQuery_A(LPCSTR Name,
           WORD Type,
           DWORD Options,
           PVOID Extra,
           PDNS_RECORD *QueryResultSet,
           PVOID *Reserved)
{
    return DnsQuery_CodePage(CP_ACP, Name, Type, Options, Extra, QueryResultSet, Reserved);
}

DNS_STATUS WINAPI
DnsQuery_UTF8(LPCSTR Name,
              WORD Type,
              DWORD Options,
              PVOID Extra,
              PDNS_RECORD *QueryResultSet,
              PVOID *Reserved)
{
    return DnsQuery_CodePage(CP_UTF8, Name, Type, Options, Extra, QueryResultSet, Reserved);
}

DNS_STATUS
WINAPI
DnsQuery_W(LPCWSTR Name,
           WORD Type,
           DWORD Options,
           PVOID Extra,
           PDNS_RECORD *QueryResultSet,
           PVOID *Reserved)
{
    DWORD dwRecords = 0;
    PDNS_RECORDW pRecord = NULL;
    size_t NameLen, i;
    DNS_STATUS Status = ERROR_SUCCESS;

    DPRINT("DnsQuery_W()\n");

    if ((Name == NULL) ||
        (QueryResultSet == NULL))
        return ERROR_INVALID_PARAMETER;

    *QueryResultSet = NULL;

    /* Create an A or AAAA record for an IP4 or IP6 address */
    pRecord = CreateRecordForIpAddress(Name,
                                       Type);
    if (pRecord != NULL)
    {
        *QueryResultSet = (PDNS_RECORD)pRecord;
        return ERROR_SUCCESS;
    }

    /*
     * Check allowed characters
     * According to RFC a-z,A-Z,0-9,-,_, but can't start or end with - or _
     */
    NameLen = wcslen(Name);
    if (Name[0] == L'-' || Name[0] == L'_' || Name[NameLen - 1] == L'-' ||
        Name[NameLen - 1] == L'_' || wcsstr(Name, L"..") != NULL)
    {
        return ERROR_INVALID_NAME;
    }

    i = 0;
    while (i < NameLen)
    {
        if (!((Name[i] >= L'a' && Name[i] <= L'z') ||
              (Name[i] >= L'A' && Name[i] <= L'Z') ||
              (Name[i] >= L'0' && Name[i] <= L'9') ||
              Name[i] == L'-' || Name[i] == L'_' || Name[i] == L'.'))
        {
            return DNS_ERROR_INVALID_NAME_CHAR;
        }

        i++;
    }

    RpcTryExcept
    {
        Status = R_ResolverQuery(NULL,
                                 Name,
                                 Type,
                                 Options,
                                 &dwRecords,
                                 (DNS_RECORDW **)QueryResultSet);
        DPRINT("R_ResolverQuery() returned %lu\n", Status);
    }
    RpcExcept(EXCEPTION_EXECUTE_HANDLER)
    {
        Status = RpcExceptionCode();
        DPRINT("Exception returned %lu\n", Status);
    }
    RpcEndExcept;

    return Status;
}

WCHAR
*xstrsave(const WCHAR *str)
{
    WCHAR *p;
    size_t len = 0;

    /* FIXME: how much instead of MAX_PATH? */
    StringCbLengthW(str, MAX_PATH, &len);
    len+=sizeof(WCHAR);

    p = RtlAllocateHeap(RtlGetProcessHeap(), 0, len);

    if (p)
        StringCbCopyW(p, len, str);

    return p;
}

CHAR
*xstrsaveA(const CHAR *str)
{
    CHAR *p;
    size_t len = 0;

    /* FIXME: how much instead of MAX_PATH? */
    StringCbLengthA(str, MAX_PATH, &len);
    len++;

    p = RtlAllocateHeap(RtlGetProcessHeap(), 0, len);

    if (p)
        StringCbCopyA(p, len, str);

    return p;
}


/* This function is far from perfect but it works enough */
IP4_ADDRESS
CheckForCurrentHostname(CONST CHAR * Name, PFIXED_INFO network_info)
{
    PCHAR TempName;
    DWORD AdapterAddressesSize, Status;
    IP4_ADDRESS ret = 0, Address;
    PIP_ADAPTER_ADDRESSES Addresses = NULL, pip;
    BOOL Found = FALSE;

    if (network_info->DomainName[0])
    {
        size_t StringLength;
        size_t TempSize = 2;
        StringCchLengthA(network_info->HostName, sizeof(network_info->HostName), &StringLength);
        TempSize += StringLength;
        StringCchLengthA(network_info->DomainName, sizeof(network_info->DomainName), &StringLength);
        TempSize += StringLength;
        TempName = RtlAllocateHeap(RtlGetProcessHeap(), 0, TempSize);
        StringCchCopyA(TempName, TempSize, network_info->HostName);
        StringCchCatA(TempName, TempSize, ".");
        StringCchCatA(TempName, TempSize, network_info->DomainName);
    }
    else
    {
        TempName = RtlAllocateHeap(RtlGetProcessHeap(), 0, 1);
        TempName[0] = 0;
    }
    Found = !stricmp(Name, network_info->HostName) || !stricmp(Name, TempName);
    RtlFreeHeap(RtlGetProcessHeap(), 0, TempName);
    if (!Found)
    {
        return 0;
    }
    /* get adapter info */
    AdapterAddressesSize = 0;
    GetAdaptersAddresses(AF_INET,
                         GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_DNS_SERVER |
                         GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST,
                         NULL,
                         Addresses,
                         &AdapterAddressesSize);
    if (!AdapterAddressesSize)
    {
        return 0;
    }
    Addresses = RtlAllocateHeap(RtlGetProcessHeap(), 0, AdapterAddressesSize);
    Status = GetAdaptersAddresses(AF_INET,
                                  GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_DNS_SERVER |
                                  GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST,
                                  NULL,
                                  Addresses,
                                  &AdapterAddressesSize);
    if (Status)
    {
        RtlFreeHeap(RtlGetProcessHeap(), 0, Addresses);
        return 0;
    }
    for (pip = Addresses; pip != NULL; pip = pip->Next) {
        Address = ((LPSOCKADDR_IN)pip->FirstUnicastAddress->Address.lpSockaddr)->sin_addr.S_un.S_addr;
        if (Address != ntohl(INADDR_LOOPBACK))
            break;
    }
    if (Address && Address != ntohl(INADDR_LOOPBACK))
    {
        ret = Address;
    }
    RtlFreeHeap(RtlGetProcessHeap(), 0, Addresses);
    return ret;
}


DNS_STATUS
WINAPI
Query_Main(LPCWSTR Name,
           WORD Type,
           DWORD Options,
           PDNS_RECORD *QueryResultSet)
{
    adns_state astate;
    int quflags = (Options & DNS_QUERY_NO_RECURSION) == 0 ? adns_qf_search : 0;
    int adns_error;
    adns_answer *answer;
    LPSTR CurrentName;
    unsigned CNameLoop;
    PFIXED_INFO network_info;
    ULONG network_info_blen = 0;
    DWORD network_info_result;
    PIP_ADDR_STRING pip;
    IP4_ADDRESS Address;
    struct in_addr addr;
    PCHAR HostWithDomainName;
    PCHAR AnsiName;
    size_t NameLen = 0;

    if (Name == NULL)
        return ERROR_INVALID_PARAMETER;
    if (QueryResultSet == NULL)
        return ERROR_INVALID_PARAMETER;

    *QueryResultSet = NULL;

    switch (Type)
    {
    case DNS_TYPE_A:
        /* FIXME: how much instead of MAX_PATH? */
        NameLen = WideCharToMultiByte(CP_ACP,
                                      0,
                                      Name,
                                      -1,
                                      NULL,
                                      0,
                                      NULL,
                                      0);
        AnsiName = RtlAllocateHeap(RtlGetProcessHeap(), 0, NameLen);
        if (NULL == AnsiName)
        {
            return ERROR_OUTOFMEMORY;
        }
        WideCharToMultiByte(CP_ACP,
                            0,
                            Name,
                            -1,
                            AnsiName,
                            NameLen,
                            NULL,
                            0);
        NameLen--;

        network_info_result = GetNetworkParams(NULL, &network_info_blen);
        network_info = (PFIXED_INFO)RtlAllocateHeap(RtlGetProcessHeap(), 0, (size_t)network_info_blen);
        if (NULL == network_info)
        {
            return ERROR_OUTOFMEMORY;
        }

        network_info_result = GetNetworkParams(network_info, &network_info_blen);
        if (network_info_result != ERROR_SUCCESS)
        {
            RtlFreeHeap(RtlGetProcessHeap(), 0, network_info);
            return network_info_result;
        }

        if ((Address = CheckForCurrentHostname(NameLen != 0 ? AnsiName : network_info->HostName, network_info)) != 0)
        {
            size_t TempLen = 2, StringLength = 0;
            RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
            StringCchLengthA(network_info->HostName, sizeof(network_info->HostName), &StringLength);
            TempLen += StringLength;
            StringCchLengthA(network_info->DomainName, sizeof(network_info->DomainName), &StringLength);
            TempLen += StringLength;
            HostWithDomainName = (PCHAR)RtlAllocateHeap(RtlGetProcessHeap(), 0, TempLen);
            StringCchCopyA(HostWithDomainName, TempLen, network_info->HostName);
            if (network_info->DomainName[0])
            {
                StringCchCatA(HostWithDomainName, TempLen, ".");
                StringCchCatA(HostWithDomainName, TempLen, network_info->DomainName);
            }
            RtlFreeHeap(RtlGetProcessHeap(), 0, network_info);
            *QueryResultSet = (PDNS_RECORD)RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(DNS_RECORD));

            if (NULL == *QueryResultSet)
            {
                RtlFreeHeap(RtlGetProcessHeap(), 0, HostWithDomainName);
                return ERROR_OUTOFMEMORY;
            }

            (*QueryResultSet)->pNext = NULL;
            (*QueryResultSet)->wType = Type;
            (*QueryResultSet)->wDataLength = sizeof(DNS_A_DATA);
            (*QueryResultSet)->Flags.S.Section = DnsSectionAnswer;
            (*QueryResultSet)->Flags.S.CharSet = DnsCharSetUnicode;
            (*QueryResultSet)->Data.A.IpAddress = Address;

            (*QueryResultSet)->pName = (LPSTR)DnsCToW(HostWithDomainName);

            RtlFreeHeap(RtlGetProcessHeap(), 0, HostWithDomainName);
            return (*QueryResultSet)->pName ? ERROR_SUCCESS : ERROR_OUTOFMEMORY;
        }

        if ((Options & DNS_QUERY_NO_WIRE_QUERY) != 0)
        {
            RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
            RtlFreeHeap(RtlGetProcessHeap(), 0, network_info);
            return ERROR_FILE_NOT_FOUND;
        }

        adns_error = adns_init(&astate, adns_if_noenv | adns_if_noerrprint | adns_if_noserverwarn, 0);
        if (adns_error != adns_s_ok)
        {
            RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
            RtlFreeHeap(RtlGetProcessHeap(), 0, network_info);
            return DnsIntTranslateAdnsToDNS_STATUS(adns_error);
        }
        for (pip = &(network_info->DnsServerList); pip; pip = pip->Next)
        {
            addr.s_addr = inet_addr(pip->IpAddress.String);
            if ((addr.s_addr != INADDR_ANY) && (addr.s_addr != INADDR_NONE))
                adns_addserver(astate, addr);
        }
        if (network_info->DomainName[0])
        {
            adns_ccf_search(astate, "LOCALDOMAIN", -1, network_info->DomainName);
        }
        RtlFreeHeap(RtlGetProcessHeap(), 0, network_info);

        if (!adns_numservers(astate))
        {
            /* There are no servers to query so bail out */
            adns_finish(astate);
            RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
            return ERROR_FILE_NOT_FOUND;
        }

        /*
        * adns doesn't resolve chained CNAME records (a CNAME which points to
        * another CNAME pointing to another... pointing to an A record), according
        * to a mailing list thread the authors believe that chained CNAME records
        * are invalid and the DNS entries should be fixed. That's a nice academic
        * standpoint, but there certainly are chained CNAME records out there,
        * even some fairly major ones (at the time of this writing
        * download.mozilla.org is a chained CNAME). Everyone else seems to resolve
        * these fine, so we should too. So we loop here to try to resolve CNAME
        * chains ourselves. Of course, there must be a limit to protect against
        * CNAME loops.
        */

#define CNAME_LOOP_MAX 16

        CurrentName = AnsiName;

        for (CNameLoop = 0; CNameLoop < CNAME_LOOP_MAX; CNameLoop++)
        {
            adns_error = adns_synchronous(astate, CurrentName, adns_r_addr, quflags, &answer);

            if (adns_error != adns_s_ok)
            {
                adns_finish(astate);

                if (CurrentName != AnsiName)
                    RtlFreeHeap(RtlGetProcessHeap(), 0, CurrentName);

                RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
                return DnsIntTranslateAdnsToDNS_STATUS(adns_error);
            }

            if (answer && answer->rrs.addr)
            {
                if (CurrentName != AnsiName)
                    RtlFreeHeap(RtlGetProcessHeap(), 0, CurrentName);

                RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
                *QueryResultSet = (PDNS_RECORD)RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(DNS_RECORD));

                if (NULL == *QueryResultSet)
                {
                    adns_finish(astate);
                    return ERROR_OUTOFMEMORY;
                }

                (*QueryResultSet)->pNext = NULL;
                (*QueryResultSet)->wType = Type;
                (*QueryResultSet)->wDataLength = sizeof(DNS_A_DATA);
                (*QueryResultSet)->Flags.S.Section = DnsSectionAnswer;
                (*QueryResultSet)->Flags.S.CharSet = DnsCharSetUnicode;
                (*QueryResultSet)->Data.A.IpAddress = answer->rrs.addr->addr.inet.sin_addr.s_addr;

                adns_finish(astate);

                (*QueryResultSet)->pName = (LPSTR)xstrsave(Name);

                return (*QueryResultSet)->pName ? ERROR_SUCCESS : ERROR_OUTOFMEMORY;
            }

            if (NULL == answer || adns_s_prohibitedcname != answer->status || NULL == answer->cname)
            {
                adns_finish(astate);

                if (CurrentName != AnsiName)
                    RtlFreeHeap(RtlGetProcessHeap(), 0, CurrentName);

                RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
                return ERROR_FILE_NOT_FOUND;
            }

            if (CurrentName != AnsiName)
                RtlFreeHeap(RtlGetProcessHeap(), 0, CurrentName);

            CurrentName = (LPSTR)xstrsaveA(answer->cname);

            if (!CurrentName)
            {
                RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
                adns_finish(astate);
                return ERROR_OUTOFMEMORY;
            }
        }

        adns_finish(astate);
        RtlFreeHeap(RtlGetProcessHeap(), 0, AnsiName);
        RtlFreeHeap(RtlGetProcessHeap(), 0, CurrentName);
        return ERROR_FILE_NOT_FOUND;

    default:
        return ERROR_OUTOFMEMORY; /* XXX arty: find a better error code. */
    }
}

void
DnsIntFreeRecordList(PDNS_RECORD ToDelete)
{
    UINT i;
    PDNS_RECORD next = 0;

    while(ToDelete)
    {
        if(ToDelete->pName)
            RtlFreeHeap(RtlGetProcessHeap(), 0, ToDelete->pName);

        switch(ToDelete->wType)
        {
            case DNS_TYPE_CNAME:
            case DNS_TYPE_PTR:
            case DNS_TYPE_NS:
            case DNS_TYPE_MB:
            case DNS_TYPE_MD:
            case DNS_TYPE_MF:
            case DNS_TYPE_MG:
            case DNS_TYPE_MR:
                RtlFreeHeap(RtlGetProcessHeap(), 0, ToDelete->Data.PTR.pNameHost);
                break;

            case DNS_TYPE_MINFO:
            case DNS_TYPE_MX:
                RtlFreeHeap(RtlGetProcessHeap(), 0, ToDelete->Data.MX.pNameExchange);
                break;

            case DNS_TYPE_HINFO:
                for(i = 0; i < ToDelete->Data.TXT.dwStringCount; i++)
                    RtlFreeHeap(RtlGetProcessHeap(), 0, ToDelete->Data.TXT.pStringArray[i]);

                RtlFreeHeap(RtlGetProcessHeap(), 0, ToDelete->Data.TXT.pStringArray);
                break;
        }

        next = ToDelete->pNext;
        RtlFreeHeap(RtlGetProcessHeap(), 0, ToDelete);
        ToDelete = next;
    }
}

BOOL
WINAPI
DnsFlushResolverCache(VOID)
{
    DNS_STATUS Status = ERROR_SUCCESS;

    DPRINT("DnsFlushResolverCache()\n");

    RpcTryExcept
    {
        Status = R_ResolverFlushCache(NULL);
        DPRINT("R_ResolverFlushCache() returned %lu\n", Status);
    }
    RpcExcept(EXCEPTION_EXECUTE_HANDLER)
    {
        Status = RpcExceptionCode();
        DPRINT("Exception returned %lu\n", Status);
    }
    RpcEndExcept;

    return (Status == ERROR_SUCCESS);
}


BOOL
WINAPI
DnsFlushResolverCacheEntry_A(
    _In_ LPCSTR pszEntry)
{
    DNS_STATUS Status = ERROR_SUCCESS;
    LPWSTR pszUnicodeEntry;

    DPRINT1("DnsFlushResolverCacheEntry_A(%s)\n", pszEntry);

    if (pszEntry == NULL)
        return FALSE;

    pszUnicodeEntry = DnsCToW(pszEntry);
    if (pszUnicodeEntry == NULL)
        return FALSE;

    RpcTryExcept
    {
        Status = R_ResolverFlushCacheEntry(NULL, pszUnicodeEntry, DNS_TYPE_ANY);
        DPRINT("R_ResolverFlushCacheEntry() returned %lu\n", Status);
    }
    RpcExcept(EXCEPTION_EXECUTE_HANDLER)
    {
        Status = RpcExceptionCode();
        DPRINT("Exception returned %lu\n", Status);
    }
    RpcEndExcept;

    RtlFreeHeap(RtlGetProcessHeap(), 0, pszUnicodeEntry);

    return (Status == ERROR_SUCCESS);
}


BOOL
WINAPI
DnsFlushResolverCacheEntry_UTF8(
    _In_ LPCSTR pszEntry)
{
    DNS_STATUS Status = ERROR_SUCCESS;
    LPWSTR pszUnicodeEntry;

    DPRINT1("DnsFlushResolverCacheEntry_UTF8(%s)\n", pszEntry);

    if (pszEntry == NULL)
        return FALSE;

    pszUnicodeEntry = DnsCToW(pszEntry);
    if (pszUnicodeEntry == NULL)
        return FALSE;

    RpcTryExcept
    {
        Status = R_ResolverFlushCacheEntry(NULL, pszUnicodeEntry, DNS_TYPE_ANY);
        DPRINT("R_ResolverFlushCacheEntry() returned %lu\n", Status);
    }
    RpcExcept(EXCEPTION_EXECUTE_HANDLER)
    {
        Status = RpcExceptionCode();
        DPRINT("Exception returned %lu\n", Status);
    }
    RpcEndExcept;

    RtlFreeHeap(RtlGetProcessHeap(), 0, pszUnicodeEntry);

    return (Status == ERROR_SUCCESS);
}


BOOL
WINAPI
DnsFlushResolverCacheEntry_W(
    _In_ LPCWSTR pszEntry)
{
    DNS_STATUS Status = ERROR_SUCCESS;

    DPRINT1("DnsFlushResolverCacheEntry_W(%S)\n", pszEntry);

    if (pszEntry == NULL)
        return FALSE;

    RpcTryExcept
    {
        Status = R_ResolverFlushCacheEntry(NULL, pszEntry, DNS_TYPE_ANY);
        DPRINT("R_ResolverFlushCacheEntry() returned %lu\n", Status);
    }
    RpcExcept(EXCEPTION_EXECUTE_HANDLER)
    {
        Status = RpcExceptionCode();
        DPRINT("Exception returned %lu\n", Status);
    }
    RpcEndExcept;

    return (Status == ERROR_SUCCESS);
}


BOOL
WINAPI
DnsGetCacheDataTable(
    _Out_ PDNS_CACHE_ENTRY *DnsCache)
{
    DNS_STATUS Status = ERROR_SUCCESS;
    PDNS_CACHE_ENTRY CacheEntries = NULL;

    if (DnsCache == NULL)
        return FALSE;

    RpcTryExcept
    {
        Status = CRrReadCache(NULL,
                              &CacheEntries);
        DPRINT("CRrReadCache() returned %lu\n", Status);
    }
    RpcExcept(EXCEPTION_EXECUTE_HANDLER)
    {
        Status = RpcExceptionCode();
        DPRINT1("Exception returned %lu\n", Status);
    }
    RpcEndExcept;

    if (Status != ERROR_SUCCESS)
        return FALSE;

    if (CacheEntries == NULL)
        return FALSE;

    *DnsCache = CacheEntries;

    return TRUE;
}

DWORD
WINAPI
GetCurrentTimeInSeconds(VOID)
{
    FILETIME Time;
    FILETIME Adjustment;
    ULARGE_INTEGER lTime, lAdj;
    SYSTEMTIME st = {1970, 1, 0, 1, 0, 0, 0};

    SystemTimeToFileTime(&st, &Adjustment);
    memcpy(&lAdj, &Adjustment, sizeof(lAdj));
    GetSystemTimeAsFileTime(&Time);
    memcpy(&lTime, &Time, sizeof(lTime));
    lTime.QuadPart -= lAdj.QuadPart;
    return (DWORD)(lTime.QuadPart/10000000ULL);
}
