/*++

Copyright (c) 1987-1991  Microsoft Corporation

Module Name:

    getdcnam.c

Abstract:

    NetGetDCName API

Author:

    Ported from Lan Man 2.0

Environment:

    User mode only.
    Contains NT-specific code.
    Requires ANSI C extensions: slash-slash comments, long external names.

Revision History:

    09-Feb-1989 (PaulC)
        Created file, to hold NetGetDCName.

    18-Apr-1989 (Ericpe)
        Implemented NetGetDCName.

    30-May-1989 (DannyGl)
        Reduced DosReadMailslot timeout.

    07-Jul-1989 (NealF)
        Use I_NetNameCanonicalize

    27-Jul-1989 (WilliamW)
        Use WIN3 manifest for WIN3.0 compatibility

    03-Jan-1990 (WilliamW)
        canonicalize domain and use I_NetCompareName

    08-Jun-1991 (CliffV)
        Ported to NT

    23-Jul-1991 JohnRo
        Implement downlevel NetGetDCName.

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#ifndef WIN32_CHICAGO
#include <rpc.h>
#include <ntrpcp.h>   // needed by rpcasync.h
#include <rpcasync.h> // I_RpcExceptionFilter
#include <logon_c.h>// includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h
#else // WIN32_CHICAGO
#include <windef.h>
#include <lmcons.h>
#endif // WIN32_CHICAGO
#include <stdio.h>

#include <winbase.h>
#include <winsock2.h>

#ifndef WIN32_CHICAGO
#include <accessp.h>
#include <align.h>
#endif // WIN32_CHICAGO
#include <debuglib.h>   // IF_DEBUG()
#include <dsgetdc.h>    // DsGetDcName()
#include <dsgetdcp.h>   // DsGetDcNameWithAccount()
#include <icanon.h>     // NAMETYPE_* defines NetpIsRemote(), NIRFLAG_ equates.
#include <lmapibuf.h>
#include <lmerr.h>
#ifndef WIN32_CHICAGO
#include <lmremutl.h>   // SUPPORTS_* defines
#include <lmserver.h>   // SV_TYPE_* defines
#include <lmsvc.h>      // SERVICE_* defines
#include <lmwksta.h>
#include <logonp.h>     // NetpLogon routines
#include <nlbind.h>     // Netlogon RPC binding cache init routines
#endif // WIN32_CHICAGO
#include <netdebug.h>   // NetpKdPrint
#include <netlib.h>     // NetpMemoryFree
#ifndef WIN32_CHICAGO
#include <netlibnt.h>   // NetpApiStatusToNtStatus
#include <netrpc.h>
#include <rxdomain.h>   // RxNetGetDCName().
#include <string.h>
#include <stdlib.h>
#endif // WIN32_CHICAGO
#include <tstring.h>    // NetpCopyStrToWStr()

#if DBG
#define NETLOGONDBG 1
#endif // DBG
#include <nldebug.h>    // NlPrint()
#include <ntddbrow.h>   // Needed by nlcommon.h
#include <nlcommon.h>   // Definitions shared with netlogon



//
// Only dynamically initialize winsock.
//
CRITICAL_SECTION GlobalDCNameCritSect;
BOOLEAN DsGetDcWsaInitialized;

#define LOCKDOMAINSEM() EnterCriticalSection( &GlobalDCNameCritSect )
#define UNLOCKDOMAINSEM() LeaveCriticalSection( &GlobalDCNameCritSect )

// end global dll data

#ifdef WIN32_CHICAGO // from net\inc\logonp.h
NET_API_STATUS
NetpLogonWriteMailslot(
    IN LPWSTR MailslotName,
    IN LPVOID Buffer,
    IN DWORD BufferSize
    );
NTSTATUS
NetpApiStatusToNtStatus(
    NET_API_STATUS  NetStatus
    );
#endif // WIN32_CHICAGO


NET_API_STATUS
DCNameInitialize(
    VOID
    )
/*++

Routine Description:

    Perform per-process initialization.

Arguments:

    None.

Return Value:

    Status of the operation.

--*/
{
    NET_API_STATUS NetStatus = NO_ERROR;


#ifndef NETTEST_UTILITY
#ifndef WIN32_CHICAGO
    //
    // Initialize the RPC binding cache.
    //

    NetStatus = NlBindingAttachDll();

    if ( NetStatus != NO_ERROR ) {
        return NetStatus;
    }
#endif // WIN32_CHICAGO
#endif // NETTEST_UTILITY

    //
    // Initialize the DLL critsects.
    //

    try {
        InitializeCriticalSection( &GlobalDCNameCritSect );
    } except( EXCEPTION_EXECUTE_HANDLER ) {
        NlPrint((0,"NETAPI32.DLL: Cannot initialize GlobalDCNameCritSect\n"));
        NetStatus = ERROR_NOT_ENOUGH_MEMORY;
    }

    //
    // Cleanup on error and return
    //

    if ( NetStatus != NO_ERROR ) {
#ifndef NETTEST_UTILITY
#ifndef WIN32_CHICAGO

        //
        // Shutdown the RPC binding cache.
        //
        NlBindingDetachDll();

#endif // WIN32_CHICAGO
#endif // NETTEST_UTILITY
        return NetStatus;
    }

    //
    // Initialize globals
    //

    DsGetDcWsaInitialized = FALSE;

    //
    // Initialize the cache of discovered domains.
    //

    NetStatus = NetpDcInitializeCache();

    //
    // Cleanup on error
    //

    if ( NetStatus != NO_ERROR ) {
        NlPrint((0,"NETAPI32.DLL: Cannot NetpDcinitializeCache\n"));
#ifndef NETTEST_UTILITY
#ifndef WIN32_CHICAGO

        //
        // Shutdown the RPC binding cache.
        //
        NlBindingDetachDll();

#endif // WIN32_CHICAGO
#endif // NETTEST_UTILITY
        DeleteCriticalSection( &GlobalDCNameCritSect );
    }

    return NetStatus;
}


VOID
DCNameClose(
    VOID
    )
/*++

Routine Description:

    Perform per-process cleanup.

Arguments:

    None.

Return Value:

    None.

--*/
{

#ifndef NETTEST_UTILITY
#ifndef WIN32_CHICAGO
    //
    // Shutdown the RPC binding cache.
    //

    NlBindingDetachDll();
#endif // WIN32_CHICAGO
#endif // NETTEST_UTILITY

    //
    // If we initialized winsock,
    //  cleanup.
    //

    LOCKDOMAINSEM();
    if ( DsGetDcWsaInitialized ) {
        WSACleanup();
        DsGetDcWsaInitialized = FALSE;
    }
    UNLOCKDOMAINSEM();

    //
    // Delete the critsect that protects the DCName cache
    //

    DeleteCriticalSection( &GlobalDCNameCritSect );

    //
    // Free the cache of discovered DCs.
    //

    NetpDcUninitializeCache();

}


#if NETLOGONDBG
#define MAX_PRINTF_LEN 1024        // Arbitrary.


VOID
NlPrintRoutine(
    IN DWORD DebugFlag,
    IN LPSTR Format,
    ...
    )
/*++

Routine Description:

    Local version of NlPrintRoutine for those instances where we're not
    compiled directly into the netlogon service.

Arguments:

Return Value:

--*/
{
#ifdef NETTEST_UTILITY
    extern BOOL ReallyVerbose;
#endif // NETTEST_UTILITY

#ifndef NETTEST_UTILITY
    // NetlibpTrace |= NETLIB_DEBUG_LOGON; // ?? Force verbosity
#ifndef WIN32_CHICAGO
    IF_DEBUG( LOGON ) {
#endif // WIN32_CHICAGO
#endif // NETTEST_UTILITY
        va_list arglist;
        char OutputBuffer[MAX_PRINTF_LEN];
        int length = 0;
        static BeginningOfLine = TRUE;

        //
        // Handle the beginning of a new line.
        //
        //

        if ( BeginningOfLine ) {
#ifdef NETTEST_UTILITY
            if ( ReallyVerbose ) {
                 printf( "        " );
            }
#endif // NETTEST_UTILITY
        }

        va_start(arglist, Format);
        length = _vsnprintf(OutputBuffer, MAX_PRINTF_LEN - 1, Format, arglist);
        va_end(arglist);

        //
        // Ensure buffer is always NULL terminated
        //
        OutputBuffer[MAX_PRINTF_LEN-1] = '\0';
        BeginningOfLine = (length > 0 && OutputBuffer[length-1] == '\n' );

        // Output buffer may contain percent signs (like "%SystemRoot%"), so
        // print it without parsing it.
#ifndef WIN32_CHICAGO
#ifdef NETTEST_UTILITY
        if ( ReallyVerbose ) {
             printf( "%s", (PCH) OutputBuffer);
        }
#else  NETTEST_UTILITY
        (void) DbgPrint( "%s", (PCH) OutputBuffer);
#endif // NETTEST_UTILITY
#else // WIN32_CHICAGO
        OutputDebugString( OutputBuffer);
#endif // WIN32_CHICAGO

#ifndef NETTEST_UTILITY
#ifndef WIN32_CHICAGO
    }
#endif // WIN32_CHICAGO
#endif // NETTEST_UTILITY

}

#endif // DBG

#ifndef WIN32_CHICAGO

#if NETLOGONDBG
//
// Have my own version of RtlAssert so debug versions of netlogon really assert on
// free builds.
//
VOID
NlAssertFailed(
    IN PVOID FailedAssertion,
    IN PVOID FileName,
    IN ULONG LineNumber,
    IN PCHAR Message OPTIONAL
    )
{

#ifdef NETTEST_UTILITY
    NlPrint((0, "\n*** Assertion failed: %s%s\n***   Source File: %s, line %ld\n\n",
                  Message ? Message : "",
                  FailedAssertion,
                  FileName,
                  LineNumber
                ));
#else  NETTEST_UTILITY
    RtlAssert( FailedAssertion, FileName, LineNumber, Message );
#endif  NETTEST_UTILITY
}
#endif // DBG
#endif //WIN32_CHICAGO

#if NETLOGONDBG

VOID
NlpDumpBuffer(
    IN DWORD DebugFlag,
    PVOID Buffer,
    DWORD BufferSize
    )
/*++

Routine Description:

    Dumps the buffer content on to the debugger output.

Arguments:

    DebugFlag: Debug flag to pass on to NlPrintRoutine

    Buffer: buffer pointer.

    BufferSize: size of the buffer.

Return Value:

    none

--*/
{
#define NUM_CHARS 16

    DWORD i, limit;
    CHAR TextBuffer[NUM_CHARS + 1];
    LPBYTE BufferPtr = Buffer;

    //
    // Hex dump of the bytes
    //
    limit = ((BufferSize - 1) / NUM_CHARS + 1) * NUM_CHARS;

    for (i = 0; i < limit; i++) {

        if (i < BufferSize) {

            NlPrint((0,"%02x ", BufferPtr[i]));

            if (BufferPtr[i] < 31 ) {
                TextBuffer[i % NUM_CHARS] = '.';
            } else if (BufferPtr[i] == '\0') {
                TextBuffer[i % NUM_CHARS] = ' ';
            } else {
                TextBuffer[i % NUM_CHARS] = (CHAR) BufferPtr[i];
            }

        } else {

            NlPrint((0,"   "));
            TextBuffer[i % NUM_CHARS] = ' ';

        }

        if ((i + 1) % NUM_CHARS == 0) {
            TextBuffer[NUM_CHARS] = 0;
            NlPrint((0,"  %s\n", TextBuffer));
        }

    }

    UNREFERENCED_PARAMETER( DebugFlag );
}
#endif // DBG


NTSTATUS
NlBrowserSendDatagram(
    IN PVOID ContextDomainInfo,
    IN ULONG IpAddress,
    IN LPWSTR UnicodeDestinationName,
    IN DGRECEIVER_NAME_TYPE NameType,
    IN LPWSTR TransportName,
    IN LPSTR OemMailslotName,
    IN PVOID Buffer,
    IN ULONG BufferSize,
    IN BOOL SendSynchronously,
    IN OUT PBOOL FlushNameOnOneIpTransport OPTIONAL
    )
/*++

Routine Description:

    Send the specified mailslot message to the specified mailslot on the
    specified server on the specified transport..

Arguments:

    DomainInfo - Hosted domain sending the datagram

    IpAddress - IpAddress of the machine to send the pind to.
        If zero, UnicodeDestinationName must be specified.
        If ALL_IP_TRANSPORTS, UnicodeDestination must be specified but the datagram
            will only be sent on IP transports.

    UnicodeDestinationName -- Name of the server to send to.

    NameType -- Type of name represented by UnicodeDestinationName.

    TransportName -- Name of the transport to send on.
        Use NULL to send on all transports.

    OemMailslotName -- Name of the mailslot to send to.

    Buffer -- Specifies a pointer to the mailslot message to send.

    BufferSize -- Size in bytes of the mailslot message

    SendSynchronously -- Ignored in this implementation

    FlushNameOnOneIpTransport -- Ignored in this implementation

Return Value:

    Status of the operation.

--*/
{
    NTSTATUS Status;
    NET_API_STATUS NetStatus;
    WCHAR NetlogonMailslotName[MAX_PATH+1];

    //
    // Sanity check.
    //

    if ( ContextDomainInfo != NULL ||
         TransportName != NULL ) {

        NlPrint((NL_CRITICAL, "NETAPI32: NlBrowserSendDatagram internal error.\n" ));
        return STATUS_INTERNAL_ERROR;
    }

    if ( UnicodeDestinationName == NULL || OemMailslotName == NULL ) {
        NlPrint((NL_CRITICAL, "NETAPI32: NlBrowserSendDatagram internal error 2.\n" ));
        return STATUS_INTERNAL_ERROR;
    }

    //
    // Ensure our buffer is big enough.
    //  OemMailslotName passed to this routine is NETLOGON_LM_MAILSLOT_A,
    //  so each character of OemMailslotName will translate into one
    //  unicode character.
    //

    if ( (2 + wcslen(UnicodeDestinationName) + 2 + strlen(OemMailslotName)) > MAX_PATH ) {
        NlPrint((NL_CRITICAL, "NETAPI32: NlBrowserSendDatagram internal error 3.\n" ));
        return STATUS_INTERNAL_ERROR;
    }

    //
    // Start building the destination mailslot name.
    //

    NetlogonMailslotName[0] = '\\';
    NetlogonMailslotName[1] = '\\';

    wcscpy(NetlogonMailslotName + 2, UnicodeDestinationName );

    switch ( NameType ) {
    case PrimaryDomain:              // Primary domain (signature 0), group
        break;
    case DomainName:                 // DC Domain name (domain name, signature 1c)
        wcscat( NetlogonMailslotName, L"*" );
        break;
    case PrimaryDomainBrowser:       // PDC Browser name (domain name, signature 1b), unique
        wcscat( NetlogonMailslotName, L"**" );
        break;
    default:
        return STATUS_INVALID_PARAMETER;
    }

    NetpCopyStrToWStr( &NetlogonMailslotName[wcslen(NetlogonMailslotName)],
                       OemMailslotName );

    NetStatus = NetpLogonWriteMailslot(
                    NetlogonMailslotName,
                    Buffer,
                    BufferSize );

#ifndef WIN32_CHICAGO
    NlPrint((NL_MAILSLOT,
             "Sent out '%s' message to %ws on all transports.\n",
             NlMailslotOpcode(((PNETLOGON_LOGON_QUERY)Buffer)->Opcode),
             NetlogonMailslotName));
#endif // WIN32_CHICAGO

#if NETLOGONDBG
    NlpDumpBuffer( NL_MAILSLOT_TEXT, Buffer, BufferSize );
#endif // NETLOGONDBG
    if ( NetStatus != NERR_Success ) {

        Status = NetpApiStatusToNtStatus( NetStatus );

        NlPrint(( NL_CRITICAL,
                "NetpDcSendPing: cannot write netlogon mailslot: %ws 0x%lx %ld\n",
                UnicodeDestinationName,
                IpAddress,
                NetStatus));

    } else {
        Status = STATUS_SUCCESS;
    }

    NlPrint(( NL_MISC, "NlBrowserSendDatagram : returned 0x%lx\n", NetStatus));
    return Status;
}

NET_API_STATUS
DsWsaInitialize(
    VOID
    )
/*++

Routine Description:

    Initialize winsock.

Arguments:

    None.

Return Value:

    Status of the operation.

--*/
{
    NET_API_STATUS NetStatus;

    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    LOCKDOMAINSEM();

    if ( !DsGetDcWsaInitialized ) {
        //
        // Initialize winsock.
        //

        wVersionRequested = MAKEWORD( 1, 1 );

        NetStatus = WSAStartup( wVersionRequested, &wsaData );
        if ( NetStatus != 0 ) {
            UNLOCKDOMAINSEM();
            NlPrint((NL_CRITICAL, "NETAPI32.DLL: Cannot initialize winsock %ld.\n", NetStatus ));
            return NetStatus;
        }

        if ( LOBYTE( wsaData.wVersion ) != 1 ||
             HIBYTE( wsaData.wVersion ) != 1 ) {
            WSACleanup();
            UNLOCKDOMAINSEM();
            NlPrint((NL_CRITICAL, "NETAPI32.DLL: Wrong winsock version %ld.\n", wsaData.wVersion ));
            return WSANOTINITIALISED;
        }

        DsGetDcWsaInitialized = TRUE;
    }

    UNLOCKDOMAINSEM();
    return NO_ERROR;
}

DWORD
DsWsaGetDcName(
    IN LPCWSTR ComputerName OPTIONAL,
    IN LPCWSTR AccountName OPTIONAL,
    IN ULONG AllowableAccountControlBits,
    IN LPCWSTR DomainName OPTIONAL,
    IN GUID *DomainGuid OPTIONAL,
    IN LPCWSTR SiteName OPTIONAL,
    IN ULONG Flags,
    IN ULONG InternalFlags,
    IN DWORD Timeout,
    IN LPWSTR NetbiosPrimaryDomainName OPTIONAL,
    IN LPWSTR DnsPrimaryDomainName OPTIONAL,
    IN GUID *PrimaryDomainGuid OPTIONAL,
    OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
)
/*++

Routine Description:

    Wrapper for DsIGetDcName that ensures WSA has been initialized.
                        i
Arguments:

    (See DsIGetDcName).


Return Value:

    (See DsIGetDcName).

--*/
{
    NET_API_STATUS NetStatus;


    //
    // Pass the call through to DsIGetDcName
    //

    NetStatus = DsIGetDcName(
                    ComputerName,
                    AccountName,
                    AllowableAccountControlBits,
                    DomainName,
                    NULL,   // Tree name is not known
                    DomainGuid,
                    SiteName,
                    Flags,
                    InternalFlags,
                    NULL,   // No send datagram context
                    Timeout,
                    NetbiosPrimaryDomainName,
                    DnsPrimaryDomainName,
                    PrimaryDomainGuid,
                    NULL,
                    NULL,
                    DomainControllerInfo );

    //
    // If Winsock has not yet been initialized,
    //  initialize it.
    //

    if ( NetStatus == WSANOTINITIALISED ) {

        //
        // Initialize WSA.
        //

        NetStatus = DsWsaInitialize();

        if ( NetStatus != NO_ERROR ) {
            goto Cleanup;
        }

        //
        // Repeat the call.
        //

        NetStatus = DsIGetDcName(
                        ComputerName,
                        AccountName,
                        AllowableAccountControlBits,
                        DomainName,
                        NULL,   // Tree name is not known
                        DomainGuid,
                        SiteName,
                        Flags,
                        InternalFlags,
                        NULL,   // No send datagram context
                        Timeout,
                        NetbiosPrimaryDomainName,
                        DnsPrimaryDomainName,
                        PrimaryDomainGuid,
                        NULL,
                        NULL,
                        DomainControllerInfo );
    }

    //
    // Free locally used resouces.
    //

Cleanup:
    return NetStatus;

}

DWORD
DsLocalGetDcName(
    IN LPCWSTR ComputerName OPTIONAL,
    IN LPCWSTR AccountName OPTIONAL,
    IN ULONG AllowableAccountControlBits,
    IN LPCWSTR DomainName OPTIONAL,
    IN GUID *DomainGuid OPTIONAL,
    IN LPCWSTR SiteName OPTIONAL,
    IN ULONG Flags,
    IN ULONG InternalFlags,
    IN DWORD Timeout,
    IN LPWSTR NetbiosPrimaryDomainName OPTIONAL,
    IN LPWSTR DnsPrimaryDomainName OPTIONAL,
    IN LPWSTR DnsPrimaryForestName OPTIONAL,
    IN GUID *PrimaryDomainGuid OPTIONAL,
    OUT PBOOLEAN Local,
    OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
)
/*++

Routine Description:

    Wrapper for DsWsaGetDcName that ensures this is a local call.

Arguments:

    (See DsIGetDcName).

    Local - Returns TRUE if the call is local and this routine performed
        the requested operation.


Return Value:

    (See DsIGetDcName).

--*/
{
    NET_API_STATUS NetStatus;

    DWORD LocalOrRemote;

    //
    // If no computername was specified,
    //  mark this as a local call.
    //

    *Local = TRUE;

#ifndef WIN32_CHICAGO
    if ( ComputerName == NULL || ComputerName[0] == '\0' ) {

        LocalOrRemote = ISLOCAL;

    //
    // Check if the ComputerName specifies this machine.
    //

    } else {

        NetStatus = NetpIsRemote(
                (LPWSTR) ComputerName,    // uncanon server name
                &LocalOrRemote,
                NULL,
                0,
                0 );

        if (NetStatus != NERR_Success) {
            goto Cleanup;
        }

    }
#else // WIN32_CHICAGO
        LocalOrRemote = ISLOCAL;
#endif // WIN32_CHICAGO

    //
    // If the call is local,
    //  just do it.
    //

    if ( LocalOrRemote == ISLOCAL ) {

        //
        // Initialize WSA.
        //  (Ignore errors since WSA isn't always needed.)
        //

        (VOID) DsWsaInitialize();

        NetStatus = DsIGetDcName(
                        NULL,   // Don't use computer name.  This has to be netbios name
                        AccountName,
                        AllowableAccountControlBits,
                        DomainName,
                        DnsPrimaryForestName,
                        DomainGuid,
                        SiteName,
                        Flags,
                        InternalFlags,
                        NULL,   // No send datagram context
                        Timeout,
                        NetbiosPrimaryDomainName,
                        DnsPrimaryDomainName,
                        PrimaryDomainGuid,
                        NULL,
                        NULL,
                        DomainControllerInfo );

    } else {
        *Local = FALSE;
        NetStatus = NO_ERROR;
    }

    //
    // Cleanup all locally used resources
    //
#ifndef WIN32_CHICAGO
Cleanup:
#endif // WIN32_CHICAGO
    return NetStatus;
}

DWORD
WINAPI
DsGetDcOpenW(
    IN LPCWSTR DnsName,
    IN ULONG OptionFlags,
    IN LPCWSTR SiteName OPTIONAL,
    IN GUID *DomainGuid OPTIONAL,
    IN LPCWSTR DnsForestName OPTIONAL,
    IN ULONG DcFlags,
    OUT PHANDLE RetGetDcContext
    )
/*++

Routine Description:

    The DsGetDcOpen/DsGetDcNext/DsGetDcClose API set is designed
    for use by applications that have specific needs for DC
    discovery which are not satisfied by the DsGetDcName API. Normally,
    applications should call DsGetDcName if they need to discover a DC.
    However, an application may have application specific selection criteria in choosing
    among DCs available in the domain. The DsGetDcOpen/DsGetDcNext/DsGetDcClose API set
    can be used in such cases to successivly retrieve the names of the DCs that are available in
    the domain and apply the application specific selection criteria for each DC returned.
    Note that it is this API set that DsGetDcName itself uses internally to retrives the DC names and apply
    its own selection criteria.

    An application should first call DsGetDcOpen
    to initialize the context needed to perform the task. Then the application should call
    DsGetDcNext in a loop getting DC names in order described below until the API indicates that there
    are no more DCs available in the domain that satisfy the caller's requirements. Finally,
    the application should call DsGetDcClose to free the resources used to perform the task.
    Note that the task is achieved by quering DNS for records registered by DCs; DCs themselves
    are not contacted by this API set.

    The order in which DCs are returned by successive calls to DsGetDcNext is as follows.
    DCs that cover the site of the machine where the API is executed are returned first.
    (A DC is said to cover a site if the DC is configured to reside in that site or if the
    DC resides in a site that is nearest to the site in question in terms of the configured
    inter-site link cost). After all DCs that cover the site of the machine are returned,
    DCs in other sites (if any) are optionally returned. If there are several DCs in each of the two site
    related groups (the group of DCs covering and the group of DCs not covering the machine site),
    DCs are returned within the group in order of their configured priorities and weights which are specified
    in DNS. DCs that have lower numeric priority are returned within a group first. If withing a site related group
    there is a subgroup of several DCs with the same priority, DCs are returned in a weighted random order
    where DCs with higher weight have more probability to be returned first. The sites, priorities, and weights
    are configured by the domain administrator to achieve effective performance and load balancing
    among multiple DCs available in teh domain, so applications calling DsGetDcOpen/DsGetDcNext/DsGetDcClose API set
    take advantage of this configuration while performing application specific DC discovery.

    Here is an example of usage:

       DsGetDcNameFlags |= DS_PDC_REQUIRED;
       DsGetDcNameFlags |= DS_ONLY_LDAP_NEEDED;
            DsGetDcNameFlags |= DS_KDC_REQUIRED;
            DsGetDcNameFlags |= DS_GC_SERVER_REQUIRED;
            DsGetDcNameFlags |= DS_FORCE_REDISCOVERY;
            DsGetDcNameFlags |= DS_WRITABLE_REQUIRED;

            DsGetDcOpenFlags |= DS_ONLY_DO_SITE_NAME;


    < TO BE CONTINUED >




    Open a context for retrieval of the addresses of machines that have
    registered LDAP.TCP.<xxx> SRV records.

Arguments:

    DnsName - Unicode DNS name of the LDAP server to lookup

    OptionFlags - Flags affecting the operation of the routine.

        DS_ONLY_DO_SITE_NAME - Non-site names should be ignored.

        DS_NOTIFY_AFTER_SITE_RECORDS - Return ERROR_FILEMARK_DETECTED
            after all site specific records have been processed.

    SiteName - Name of site the client is in.

    DomainGuid -  Specifies the GUID of the domain specified by DnsName.
        This value is used to handle the case of domain renames.  If this
        value is specified and DomainName has been renamed, DsGetDcName will
        attempt to locate a DC in the domain having this specified DomainGuid.

    DnsForestName - Specifies the name of the domain at the root of the tree
        containing DnsName.  This value is used in conjunction with DomainGuid
        for finding DnsName if the domain has been renamed.

    DcFlags - Passes additional information to be used to process the request.
        DcFlags can be a combination values bitwise or'ed together.

        Any of the following flags are allowed and have the same meaning as
        for DsGetDcName:

        DS_PDC_REQUIRED
        DS_GC_SERVER_REQUIRED
        DS_WRITABLE_REQUIRED
        DS_FORCE_REDISCOVERY - Avoids DNS cache

        If no flags are specified, no special DC role is required.

    RetGetDcContext - Returns an opaque context.
        This context must be freed using DsGetDcClose.

Return Value:

    Status of the operation.

    NO_ERROR: GetDcContext was returned successfully.

--*/

{
    NET_API_STATUS NetStatus = NO_ERROR;
    LPSTR DnsNameUtf8 = NULL;
    LPSTR DnsForestNameUtf8 = NULL;

    //
    // Validate the input
    //

    if ( DnsName == NULL || *DnsName == UNICODE_NULL ) {
        NetStatus = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    if ( RetGetDcContext == NULL ) {
        NetStatus = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    //
    // Convert DnsName and DnsForestName to UTF-8
    //

    DnsNameUtf8 = NetpAllocUtf8StrFromWStr( DnsName );
    if ( DnsNameUtf8 == NULL ) {
        NetStatus = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }

    if ( DnsForestName != NULL ) {
        DnsForestNameUtf8 = NetpAllocUtf8StrFromWStr( DnsForestName );
        if ( DnsForestNameUtf8 == NULL ) {
            NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    NetStatus = NetpDcGetDcOpen( DnsNameUtf8,
                              OptionFlags,
                              SiteName,
                              DomainGuid,
                              DnsForestNameUtf8,
                              DcFlags,
                              RetGetDcContext );
Cleanup:

    if ( DnsNameUtf8 != NULL ) {
        NetpMemoryFree( DnsNameUtf8 );
    }

    if ( DnsForestNameUtf8 != NULL ) {
        NetpMemoryFree( DnsForestNameUtf8 );
    }

    return NetStatus;
}

DWORD
WINAPI
DsGetDcOpenA(
    IN LPCSTR DnsName,
    IN ULONG OptionFlags,
    IN LPCSTR SiteName OPTIONAL,
    IN GUID *DomainGuid OPTIONAL,
    IN LPCSTR DnsForestName OPTIONAL,
    IN ULONG DcFlags,
    OUT PHANDLE RetGetDcContext
    )
/*++

Routine Description:

    Open a context for retrieval of the addresses of machines that have
    registered LDAP.TCP.<xxx> SRV records.

Arguments:

    DnsName - Unicode DNS name of the LDAP server to lookup

    OptionFlags - Flags affecting the operation of the routine.

        DS_ONLY_DO_SITE_NAME - Non-site names should be ignored.

        DS_NOTIFY_AFTER_SITE_RECORDS - Return ERROR_FILEMARK_DETECTED
            after all site specific records have been processed.

    SiteName - Name of site the client is in.

    DomainGuid -  Specifies the GUID of the domain specified by DnsName.
        This value is used to handle the case of domain renames.  If this
        value is specified and DomainName has been renamed, DsGetDcName will
        attempt to locate a DC in the domain having this specified DomainGuid.

    DnsForestName - Specifies the name of the domain at the root of the tree
        containing DnsName.  This value is used in conjunction with DomainGuid
        for finding DnsName if the domain has been renamed.

    DcFlags - Passes additional information to be used to process the request.
        DcFlags can be a combination values bitwise or'ed together.

        Any of the following flags are allowed and have the same meaning as
        for DsGetDcName:

        DS_PDC_REQUIRED
        DS_GC_SERVER_REQUIRED
        DS_WRITABLE_REQUIRED
        DS_FORCE_REDISCOVERY - Avoids DNS cache

        If no flags are specified, no special DC role is required.

    RetGetDcContext - Returns an opaque context.
        This context must be freed using DsGetDcClose.

Return Value:

    Status of the operation.

    NO_ERROR: GetDcContext was returned successfully.

--*/

{
    NET_API_STATUS NetStatus = NO_ERROR;
    LPWSTR DnsNameW = NULL;
    LPWSTR SiteNameW = NULL;
    LPWSTR DnsForestNameW = NULL;

    //
    // Convert DnsName and DnsForestName to UTF-8
    //

    if ( DnsName != NULL && *DnsName != '\0' ) {
        DnsNameW = NetpAllocWStrFromAStr( DnsName );
        if ( DnsNameW == NULL ) {
            NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    if ( SiteName != NULL && *SiteName != '\0') {
        SiteNameW = NetpAllocWStrFromAStr( SiteName );
        if ( SiteNameW == NULL ) {
            NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    if ( DnsForestName != NULL && *DnsForestName != '\0' ) {
        DnsForestNameW = NetpAllocWStrFromAStr( DnsForestName );
        if ( DnsForestNameW == NULL ) {
            NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    NetStatus = DsGetDcOpenW( DnsNameW,
                              OptionFlags,
                              SiteNameW,
                              DomainGuid,
                              DnsForestNameW,
                              DcFlags,
                              RetGetDcContext );
Cleanup:

    if ( DnsNameW != NULL ) {
        NetApiBufferFree( DnsNameW );
    }

    if ( SiteNameW != NULL ) {
        NetApiBufferFree( SiteNameW );
    }

    if ( DnsForestNameW != NULL ) {
        NetApiBufferFree( DnsForestNameW );
    }

    return NetStatus;
}


DWORD
WINAPI
DsGetDcNextW(
    IN HANDLE GetDcContextHandle,
    OUT PULONG SockAddressCount OPTIONAL,
    OUT LPSOCKET_ADDRESS *SockAddresses OPTIONAL,
    OUT LPWSTR *DnsHostName OPTIONAL
    )
/*++

Routine Description:

    Returns the next logical SRV record for the name opened by DsGetDcOpen.
    The returned record takes into account the weights and priorities specified
    in the SRV records.

Arguments:

    GetDcContextHandle - An opaque context describing the SRV records.

    SockAddressCount - Returns the number of Addresses in SockAddresses.
        If NULL, addresses will not be looked up.

    SockAddresses - Returns an array SOCKET_ADDRESS structures for the server.
        All returned addresses will be of family AF_INET or AF_INET6.
        The returned sin_port field contains port from the SRV record.
            A Port of 0 indicate no port is available from DNS.
        This buffer should be freed using LocalFree().

    DnsHostName - Returns a pointer to the DnsHostName in the SRV record.
        A NULL is returned if no host name is known. Must be freed by
        calling NetApiBufferFree.

Return Value:

    NO_ERROR: Addresses were returned

    ERROR_NO_MORE_ITEMS: No more addresses are available.

    ERROR_FILEMARK_DETECTED: Caller has specified the DS_NOTIFY_AFTER_SITE_RECORDS flag
        and DsGetDcNext has processed all of the site specific SRV records.  The caller
        should take any action based on no site specific DCs being available, then
        should call DsGetDcNext to continue on to other DCs.

    Any other errors returned are those detected while trying to find the A
        records associated with the host of the SRV record.  The caller can
        note the error (perhaps so the caller can return this status to
        his caller if no usefull server is found) then call DsGetDcNext
        again to get the next SRV record.  The caller can inspect this error
        and return immediately if the caller deems the error serious.

    The following interesting errors might be returned:

    DNS_ERROR_RCODE_NAME_ERROR: No A records are available for this SRV record.

    ERROR_TIMEOUT: DNS server didn't respond in a reasonable time

--*/
{
    NET_API_STATUS NetStatus = NO_ERROR;
    LPSTR DnsHostNameUtf8 = NULL;
    LPWSTR LocalDnsHostName = NULL;
    ULONG LocalSockAddressCount = 0;
    LPSOCKET_ADDRESS LocalSockAddresses = NULL;

    //
    // Call the internal version
    //

    NetStatus = NetpDcGetDcNext( GetDcContextHandle,
                              SockAddressCount != NULL ?
                                &LocalSockAddressCount :
                                NULL,
                              SockAddresses != NULL ?
                                &LocalSockAddresses :
                                NULL,
                              DnsHostName != NULL ?
                                &DnsHostNameUtf8 :
                                NULL,
                              NULL ); // don't need SRV record count

    if ( NetStatus != NO_ERROR ) {
        goto Cleanup;
    }

    //
    // Convert the host name to Unicode, if needed
    //

    if ( DnsHostName != NULL ) {
        LocalDnsHostName = NetpAllocWStrFromUtf8Str( DnsHostNameUtf8 );
        if ( LocalDnsHostName == NULL ) {
            NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

Cleanup:

    //
    // Return the data on success or clean up on error
    //  (No need to free DnsHostNameUtf8 as it isn't allocated)
    //

    if ( NetStatus == NO_ERROR ) {

        if ( SockAddressCount != NULL ) {
            *SockAddressCount = LocalSockAddressCount;
        }
        if ( SockAddresses != NULL ) {
            *SockAddresses = LocalSockAddresses;
        }
        if ( DnsHostName != NULL ) {
            *DnsHostName = LocalDnsHostName;
        }

    } else {

        if ( LocalSockAddresses != NULL ) {
            LocalFree( LocalSockAddresses );
        }
        if ( LocalDnsHostName != NULL ) {
            NetApiBufferFree( LocalDnsHostName );
        }
    }

    return NetStatus;
}

DWORD
WINAPI
DsGetDcNextA(
    IN HANDLE GetDcContextHandle,
    OUT PULONG SockAddressCount OPTIONAL,
    OUT LPSOCKET_ADDRESS *SockAddresses OPTIONAL,
    OUT LPSTR *DnsHostName OPTIONAL
    )
/*++

Routine Description:

    Returns the next logical SRV record for the name opened by DsGetDcOpen.
    The returned record takes into account the weights and priorities specified
    in the SRV records.

Arguments:

    GetDcContextHandle - An opaque context describing the SRV records.

    SockAddressCount - Returns the number of Addresses in SockAddresses.
        If NULL, addresses will not be looked up.

    SockAddresses - Returns an array SOCKET_ADDRESS structures for the server.
        All returned addresses will be of family AF_INET or AF_INET6.
        The returned sin_port field contains port from the SRV record.
            A Port of 0 indicate no port is available from DNS.
        This buffer should be freed using LocalFree().

    DnsHostName - Returns a pointer to the DnsHostName in the SRV record.
        A NULL is returned if no host name is known. Must be freed by
        calling NetApiBufferFree.

Return Value:

    NO_ERROR: Addresses were returned

    ERROR_NO_MORE_ITEMS: No more addresses are available.

    ERROR_FILEMARK_DETECTED: Caller has specified the DS_NOTIFY_AFTER_SITE_RECORDS flag
        and DsGetDcNext has processed all of the site specific SRV records.  The caller
        should take any action based on no site specific DCs being available, then
        should call DsGetDcNext to continue on to other DCs.

    Any other errors returned are those detected while trying to find the A
        records associated with the host of the SRV record.  The caller can
        note the error (perhaps so the caller can return this status to
        his caller if no usefull server is found) then call DsGetDcNext
        again to get the next SRV record.  The caller can inspect this error
        and return immediately if the caller deems the error serious.

    The following interesting errors might be returned:

    DNS_ERROR_RCODE_NAME_ERROR: No A records are available for this SRV record.

    ERROR_TIMEOUT: DNS server didn't respond in a reasonable time

--*/
{
    NET_API_STATUS NetStatus = NO_ERROR;
    LPWSTR DnsHostNameW = NULL;
    LPSTR LocalDnsHostName = NULL;
    ULONG LocalSockAddressCount = 0;
    LPSOCKET_ADDRESS LocalSockAddresses = NULL;

    //
    // Call the Unicode version
    //

    NetStatus = DsGetDcNextW( GetDcContextHandle,
                              SockAddressCount != NULL ?
                                &LocalSockAddressCount :
                                NULL,
                              SockAddresses != NULL ?
                                &LocalSockAddresses :
                                NULL,
                              DnsHostName != NULL ?
                                &DnsHostNameW :
                                NULL );

    if ( NetStatus != NO_ERROR ) {
        goto Cleanup;
    }

    //
    // Convert the host name to Unicode, if needed
    //

    if ( DnsHostName != NULL ) {
        LocalDnsHostName = NetpAllocAStrFromWStr( DnsHostNameW );
        if ( LocalDnsHostName == NULL ) {
            NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

Cleanup:

    if ( DnsHostNameW != NULL ) {
        NetApiBufferFree( DnsHostNameW );
    }

    //
    // Return the data on success or clean up on error
    //

    if ( NetStatus == NO_ERROR ) {

        if ( SockAddressCount != NULL ) {
            *SockAddressCount = LocalSockAddressCount;
        }
        if ( SockAddresses != NULL ) {
            *SockAddresses = LocalSockAddresses;
        }
        if ( DnsHostName != NULL ) {
            *DnsHostName = LocalDnsHostName;
        }

    } else {

        if ( LocalSockAddresses != NULL ) {
            LocalFree( LocalSockAddresses );
        }
        if ( LocalDnsHostName != NULL ) {
            NetApiBufferFree( LocalDnsHostName );
        }
    }

    return NetStatus;
}

VOID
WINAPI
DsGetDcCloseW(
    IN HANDLE GetDcContextHandle
    )
/*++

Routine Description:

    Free the context allocated by DsGetDcOpen

Arguments:

    GetDcContextHandle - An opaque context describing the SRV records.

Return Value:

    None

--*/

{
    //
    // Just call the internal version
    //
    NetpDcGetDcClose( GetDcContextHandle );
}

DWORD
WINAPI
DsGetDcNameA(
    IN LPCSTR ComputerName OPTIONAL,
    IN LPCSTR DomainName OPTIONAL,
    IN GUID *DomainGuid OPTIONAL,
    IN LPCSTR SiteName OPTIONAL,
    IN ULONG Flags,
    OUT PDOMAIN_CONTROLLER_INFOA *DomainControllerInfo
)
/*++

Routine Description:

    Same as DsGetDcNameW except it takes and returns ASCII.

Arguments:

    Same as DsGetDcNameW except it takes and returns ASCII.

Return Value:

    Same as DsGetDcNameW except it takes and returns ASCII.

--*/
{
    return DsGetDcNameWithAccountA( ComputerName,
                         NULL,
                         0,
                         DomainName,
                         DomainGuid,
                         SiteName,
                         Flags,
                         DomainControllerInfo );
}

DWORD
WINAPI
DsGetDcNameWithAccountA(
    IN LPCSTR ComputerName OPTIONAL,
    IN LPCSTR AccountName OPTIONAL,
    IN ULONG AllowableAccountControlBits,
    IN LPCSTR DomainName OPTIONAL,
    IN GUID *DomainGuid OPTIONAL,
    IN LPCSTR SiteName OPTIONAL,
    IN ULONG Flags,
    OUT PDOMAIN_CONTROLLER_INFOA *DomainControllerInfo
)
/*++

Routine Description:

    Same as DsGetDcNameW except it takes and returns ASCII.

Arguments:

    Same as DsGetDcNameW except it takes and returns ASCII.

Return Value:

    Same as DsGetDcNameW except it takes and returns ASCII.

--*/
{
    DWORD WinStatus;
    LPWSTR UnicodeComputerName = NULL;
    LPWSTR UnicodeAccountName = NULL;
    LPWSTR UnicodeDomainName = NULL;
    LPWSTR UnicodeSiteName = NULL;

    LPSTR AnsiDomainControllerName = NULL;
    ULONG AnsiDomainControllerNameSize = 0;
    LPSTR AnsiDomainControllerAddress = NULL;
    ULONG AnsiDomainControllerAddressSize = 0;
    LPSTR AnsiDomainName = NULL;
    ULONG AnsiDomainNameSize = 0;
    LPSTR AnsiForestName = NULL;
    ULONG AnsiForestNameSize = 0;
    LPSTR AnsiDcSiteName = NULL;
    ULONG AnsiDcSiteNameSize = 0;
    LPSTR AnsiClientSiteName = NULL;
    ULONG AnsiClientSiteNameSize = 0;
    CHAR szBuf[] = "";

    LPBYTE Where;

    PDOMAIN_CONTROLLER_INFOW DomainControllerInfoW = NULL;

    //
    // Convert input parameters to Unicode.
    //

    *DomainControllerInfo = NULL;

    if ( ComputerName != NULL ) {
        UnicodeComputerName = NetpAllocWStrFromAStr( ComputerName );

        if ( UnicodeComputerName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    if ( AccountName != NULL ) {
        UnicodeAccountName = NetpAllocWStrFromAStr( AccountName );

        if ( UnicodeAccountName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    if ( DomainName != NULL ) {
        UnicodeDomainName = NetpAllocWStrFromAStr( DomainName );

        if ( UnicodeDomainName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    if ( SiteName != NULL ) {
        UnicodeSiteName = NetpAllocWStrFromAStr( SiteName );

        if ( UnicodeSiteName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    //
    // Call the Unicode version of the routine.
    //

    WinStatus = DsGetDcNameWithAccountW(
                    UnicodeComputerName,
                    UnicodeAccountName,
                    AllowableAccountControlBits,
                    UnicodeDomainName,
                    DomainGuid,
                    UnicodeSiteName,
                    Flags,
                    &DomainControllerInfoW );

    if ( WinStatus != NO_ERROR ) {
        goto Cleanup;
    }

    //
    // Convert the output structure to Ansi character set.
    //

    if ( DomainControllerInfoW->DomainControllerName != NULL ) {
        AnsiDomainControllerName = NetpAllocAStrFromWStr( DomainControllerInfoW->DomainControllerName );

        if ( AnsiDomainControllerName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        AnsiDomainControllerNameSize = lstrlenA( AnsiDomainControllerName ) + 1;
    }

    if ( DomainControllerInfoW->DomainControllerAddress != NULL ) {
        AnsiDomainControllerAddress = NetpAllocAStrFromWStr( DomainControllerInfoW->DomainControllerAddress );

        if ( AnsiDomainControllerAddress == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        AnsiDomainControllerAddressSize = lstrlenA( AnsiDomainControllerAddress ) + 1;
    }

    if ( DomainControllerInfoW->DomainName != NULL ) {
        AnsiDomainName = NetpAllocAStrFromWStr( DomainControllerInfoW->DomainName );

        if ( AnsiDomainName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        AnsiDomainNameSize = lstrlenA( AnsiDomainName ) + 1;
    }

    if ( DomainControllerInfoW->DnsForestName != NULL ) {
        AnsiForestName = NetpAllocAStrFromWStr( DomainControllerInfoW->DnsForestName );

        if ( AnsiForestName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        AnsiForestNameSize = lstrlenA( AnsiForestName ) + 1;
    }

    if ( DomainControllerInfoW->DcSiteName != NULL ) {
        AnsiDcSiteName = NetpAllocAStrFromWStr( DomainControllerInfoW->DcSiteName );

        if ( AnsiDcSiteName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        AnsiDcSiteNameSize = lstrlenA( AnsiDcSiteName ) + 1;
    }

    if ( DomainControllerInfoW->ClientSiteName != NULL ) {
        AnsiClientSiteName = NetpAllocAStrFromWStr( DomainControllerInfoW->ClientSiteName );

        if ( AnsiClientSiteName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        AnsiClientSiteNameSize = lstrlenA( AnsiClientSiteName ) + 1;
    }

    //
    // Allocate the Ansi version of the structure.
    //

    WinStatus = NetApiBufferAllocate(
                    sizeof(DOMAIN_CONTROLLER_INFOA) +
                        AnsiDomainControllerNameSize +
                        AnsiDomainControllerAddressSize +
                        AnsiDomainNameSize +
                        AnsiForestNameSize +
                        AnsiDcSiteNameSize +
                        AnsiClientSiteNameSize,
                    DomainControllerInfo );

    if ( WinStatus != NO_ERROR ) {
        goto Cleanup;
    }

    Where = (LPBYTE)((*DomainControllerInfo) + 1);

    //
    // Copy information into the allocated buffer.
    //

    *(*DomainControllerInfo) = *(PDOMAIN_CONTROLLER_INFOA)DomainControllerInfoW;

    if ( AnsiDomainControllerName != NULL ) {
        (*DomainControllerInfo)->DomainControllerName = Where;
        RtlCopyMemory( Where,
                       AnsiDomainControllerName,
                       AnsiDomainControllerNameSize );
        Where += AnsiDomainControllerNameSize;
    }

    if ( AnsiDomainControllerAddress != NULL ) {
        (*DomainControllerInfo)->DomainControllerAddress = Where;
        RtlCopyMemory( Where,
                       AnsiDomainControllerAddress,
                       AnsiDomainControllerAddressSize );
        Where += AnsiDomainControllerAddressSize;
    }

    if ( AnsiDomainName != NULL ) {
        (*DomainControllerInfo)->DomainName = Where;
        RtlCopyMemory( Where,
                       AnsiDomainName,
                       AnsiDomainNameSize );
        Where += AnsiDomainNameSize;
    }

    if ( AnsiForestName != NULL ) {
        (*DomainControllerInfo)->DnsForestName = Where;
        RtlCopyMemory( Where,
                       AnsiForestName,
                       AnsiForestNameSize );
        Where += AnsiForestNameSize;
    }

    if ( AnsiDcSiteName != NULL ) {
        (*DomainControllerInfo)->DcSiteName = Where;
        RtlCopyMemory( Where,
                       AnsiDcSiteName,
                       AnsiDcSiteNameSize );
        Where += AnsiDcSiteNameSize;
    }

    if ( AnsiClientSiteName != NULL ) {
        (*DomainControllerInfo)->ClientSiteName = Where;
        RtlCopyMemory( Where,
                       AnsiClientSiteName,
                       AnsiClientSiteNameSize );
        Where += AnsiClientSiteNameSize;
    }

#ifdef WIN32_CHICAGO
    NlPrint((NL_MISC, "DomainControllerName: \t\t\"%s\"\n", AnsiDomainControllerName  ? AnsiDomainControllerName : szBuf));
    NlPrint((NL_MISC, "DomainControllerAddress:\t\t\"%s\"\n", AnsiDomainControllerAddress ? AnsiDomainControllerAddress : szBuf ));
    NlPrint((NL_MISC, "DomainControllerAddressType: \t%d\n", DomainControllerInfoW->DomainControllerAddressType ));
    NlPrint((NL_MISC, "DomainGuid : \t\n"));
    NlPrint((NL_MISC, "DomainName: \t\t\t\"%s\"\n", AnsiDomainName ? AnsiDomainName : szBuf));
    NlPrint((NL_MISC, "DnsForestName: \t\t\t\"%s\"\n", AnsiForestName ? AnsiForestName : szBuf));
    NlPrint((NL_MISC, "Flags: \t\t\t\t 0x%x\n", DomainControllerInfoW->Flags));
    NlPrint((NL_MISC, "DcSiteName: \t\t\t\"%s\"\n", AnsiDcSiteName ? AnsiDcSiteName : szBuf));
    NlPrint((NL_MISC, "ClientSiteName: \t\t\t\"%s\"\n", AnsiClientSiteName ? AnsiClientSiteName : szBuf));
#endif // WIN32_CHICAGO

    WinStatus = NO_ERROR;

    //
    // Clean up locally used resources.
    //

Cleanup:
    if ( UnicodeComputerName != NULL ) {
        NetApiBufferFree( UnicodeComputerName );
    }
    if ( UnicodeAccountName != NULL ) {
        NetApiBufferFree( UnicodeAccountName );
    }
    if ( UnicodeDomainName != NULL ) {
        NetApiBufferFree( UnicodeDomainName );
    }
    if ( UnicodeSiteName != NULL ) {
        NetApiBufferFree( UnicodeSiteName );
    }
    if ( DomainControllerInfoW != NULL ) {
        NetApiBufferFree( DomainControllerInfoW );
    }
    if ( AnsiDomainControllerName != NULL ) {
        NetApiBufferFree( AnsiDomainControllerName );
    }
    if ( AnsiDomainControllerAddress != NULL ) {
        NetApiBufferFree( AnsiDomainControllerAddress );
    }
    if ( AnsiDomainName != NULL ) {
        NetApiBufferFree( AnsiDomainName );
    }
    if ( AnsiForestName != NULL ) {
        NetApiBufferFree( AnsiForestName );
    }
    if ( AnsiDcSiteName != NULL ) {
        NetApiBufferFree( AnsiDcSiteName );
    }
    if ( AnsiClientSiteName != NULL ) {
        NetApiBufferFree( AnsiClientSiteName );
    }
    if ( WinStatus != NO_ERROR ) {
        if ( *DomainControllerInfo != NULL ) {
            NetApiBufferFree( *DomainControllerInfo );
            *DomainControllerInfo = NULL;
        }
    }

    return WinStatus;

}

#ifndef WIN32_CHICAGO

NTSTATUS
NlWaitForEvent(
    LPWSTR EventName,
    ULONG Timeout
    )

/*++

Routine Description:

    Wait up to Timeout seconds for EventName to be triggered.

Arguments:

    EventName - Name of event to wait on

    Timeout - Timeout for event (in seconds).

Return Status:

    STATUS_SUCCESS - Indicates NETLOGON successfully initialized.
    STATUS_NETLOGON_NOT_STARTED - Timeout occurred.

--*/

{
    NTSTATUS Status;

    HANDLE EventHandle;
    OBJECT_ATTRIBUTES EventAttributes;
    UNICODE_STRING EventNameString;
    LARGE_INTEGER LocalTimeout;


    //
    // Create an event for us to wait on.
    //

    RtlInitUnicodeString( &EventNameString, EventName);
    InitializeObjectAttributes( &EventAttributes, &EventNameString, 0, 0, NULL);

    Status = NtCreateEvent(
                   &EventHandle,
                   SYNCHRONIZE,
                   &EventAttributes,
                   NotificationEvent,
                   (BOOLEAN) FALSE      // The event is initially not signaled
                   );

    if ( !NT_SUCCESS(Status)) {

        //
        // If the event already exists, the server beat us to creating it.
        // Just open it.
        //

        if( Status == STATUS_OBJECT_NAME_EXISTS ||
            Status == STATUS_OBJECT_NAME_COLLISION ) {

            Status = NtOpenEvent( &EventHandle,
                                  SYNCHRONIZE,
                                  &EventAttributes );

        }
        if ( !NT_SUCCESS(Status)) {
            NlPrint((0,"[NETAPI32] OpenEvent failed %lx\n", Status ));
            return Status;
        }
    }


    //
    // Wait for NETLOGON to initialize.  Wait a maximum of Timeout seconds.
    //

    LocalTimeout.QuadPart = ((LONGLONG)(Timeout)) * (-10000000);
    Status = NtWaitForSingleObject( EventHandle, (BOOLEAN)FALSE, &LocalTimeout);
    (VOID) NtClose( EventHandle );

    if ( !NT_SUCCESS(Status) || Status == STATUS_TIMEOUT ) {
        if ( Status == STATUS_TIMEOUT ) {
            Status = STATUS_NETLOGON_NOT_STARTED;   // Map to an error condition
        }
        return Status;
    }

    return STATUS_SUCCESS;
}


NTSTATUS
NlWaitForNetlogon(
    ULONG Timeout
    )

/*++

Routine Description:

    Wait up to Timeout seconds for the netlogon service to start.

Arguments:

    Timeout - Timeout for event (in seconds).

Return Status:

    STATUS_SUCCESS - Indicates NETLOGON successfully initialized.
    STATUS_NETLOGON_NOT_STARTED - Timeout occurred.

--*/

{
    NTSTATUS Status;
    NET_API_STATUS NetStatus;
    SC_HANDLE ScManagerHandle = NULL;
    SC_HANDLE ServiceHandle = NULL;
    SERVICE_STATUS ServiceStatus;
    LPQUERY_SERVICE_CONFIG ServiceConfig;
    LPQUERY_SERVICE_CONFIG AllocServiceConfig = NULL;
    QUERY_SERVICE_CONFIG DummyServiceConfig;
    DWORD ServiceConfigSize;

    //
    // If the netlogon service is currently running,
    //  skip the rest of the tests.
    //

    Status = NlWaitForEvent( L"\\NETLOGON_SERVICE_STARTED", 0 );

    if ( NT_SUCCESS(Status) ) {
        return Status;
    }

    //
    // If we're in setup,
    //  don't bother waiting for netlogon to start.
    //

    if ( NlDoingSetup() ) {
        return STATUS_NETLOGON_NOT_STARTED;
    }

    //
    // Open a handle to the Netlogon Service.
    //

    ScManagerHandle = OpenSCManager(
                          NULL,
                          NULL,
                          SC_MANAGER_CONNECT );

    if (ScManagerHandle == NULL) {
        NlPrint((0, "[NETAPI32] NlWaitForNetlogon: OpenSCManager failed: "
                      "%lu\n", GetLastError()));
        Status = STATUS_NETLOGON_NOT_STARTED;
        goto Cleanup;
    }

    ServiceHandle = OpenService(
                        ScManagerHandle,
                        SERVICE_NETLOGON,
                        SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG );

    if ( ServiceHandle == NULL ) {
        NlPrint((0, "[NETAPI32] NlWaitForNetlogon: OpenService failed: "
                      "%lu\n", GetLastError()));
        Status = STATUS_NETLOGON_NOT_STARTED;
        goto Cleanup;
    }


    //
    // If the Netlogon service isn't configured to be automatically started
    //  by the service controller, don't bother waiting for it to start.
    //
    // ?? Pass "DummyServiceConfig" and "sizeof(..)" since QueryService config
    //  won't allow a null pointer, yet.

    if ( QueryServiceConfig(
            ServiceHandle,
            &DummyServiceConfig,
            sizeof(DummyServiceConfig),
            &ServiceConfigSize )) {

        ServiceConfig = &DummyServiceConfig;

    } else {

        NetStatus = GetLastError();
        if ( NetStatus != ERROR_INSUFFICIENT_BUFFER ) {
            NlPrint((0, "[NETAPI32] NlWaitForNetlogon: QueryServiceConfig failed: "
                      "%lu\n", NetStatus));
            Status = STATUS_NETLOGON_NOT_STARTED;
            goto Cleanup;
        }

        AllocServiceConfig = LocalAlloc( 0, ServiceConfigSize );
        ServiceConfig = AllocServiceConfig;

        if ( AllocServiceConfig == NULL ) {
            Status = STATUS_NO_MEMORY;
            goto Cleanup;
        }

        if ( !QueryServiceConfig(
                ServiceHandle,
                ServiceConfig,
                ServiceConfigSize,
                &ServiceConfigSize )) {

            NlPrint((0, "[NETAPI32] NlWaitForNetlogon: QueryServiceConfig "
                      "failed again: %lu\n", GetLastError()));
            Status = STATUS_NETLOGON_NOT_STARTED;
            goto Cleanup;
        }
    }

    if ( ServiceConfig->dwStartType != SERVICE_AUTO_START ) {
        NlPrint((0, "[NETAPI32] NlWaitForNetlogon: Netlogon start type invalid:"
                          "%lu\n", ServiceConfig->dwStartType ));
        Status = STATUS_NETLOGON_NOT_STARTED;
        goto Cleanup;
    }



    //
    // Loop waiting for the netlogon service to start.
    //  (Convert Timeout to a number of 10 second iterations)
    //

    Timeout = (Timeout+9)/10;
    for (;;) {


        //
        // Query the status of the Netlogon service.
        //

        if (! QueryServiceStatus( ServiceHandle, &ServiceStatus )) {

            NlPrint((0, "[NETAPI32] NlWaitForNetlogon: QueryServiceStatus failed: "
                          "%lu\n", GetLastError() ));
            Status = STATUS_NETLOGON_NOT_STARTED;
            goto Cleanup;
        }

        //
        // Return or continue waiting depending on the state of
        //  the netlogon service.
        //

        switch( ServiceStatus.dwCurrentState) {
        case SERVICE_RUNNING:
            Status = STATUS_SUCCESS;
            goto Cleanup;

        case SERVICE_STOPPED:

            //
            // If Netlogon failed to start,
            //  error out now.  The caller has waited long enough to start.
            //
            if ( ServiceStatus.dwWin32ExitCode != ERROR_SERVICE_NEVER_STARTED ){
#if NETLOGONDBG
                NlPrint((0, "[NETAPI32] NlWaitForNetlogon: "
                          "Netlogon service couldn't start: %lu %lx\n",
                          ServiceStatus.dwWin32ExitCode,
                          ServiceStatus.dwWin32ExitCode ));
                if ( ServiceStatus.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR ) {
                    NlPrint((0, "         Service specific error code: %lu %lx\n",
                              ServiceStatus.dwServiceSpecificExitCode,
                              ServiceStatus.dwServiceSpecificExitCode ));
                }
#endif // DBG
                Status = STATUS_NETLOGON_NOT_STARTED;
                goto Cleanup;
            }

            //
            // If Netlogon has never been started on this boot,
            //  continue waiting for it to start.
            //

            break;

        //
        // If Netlogon is trying to start up now,
        //  continue waiting for it to start.
        //
        case SERVICE_START_PENDING:
            break;

        //
        // Any other state is bogus.
        //
        default:
            NlPrint((0, "[NETAPI32] NlWaitForNetlogon: "
                      "Invalid service state: %lu\n",
                      ServiceStatus.dwCurrentState ));
            Status = STATUS_NETLOGON_NOT_STARTED;
            goto Cleanup;

        }


        //
        // Wait ten seconds for the netlogon service to start.
        //  If it has successfully started, just return now.
        //

        Status = NlWaitForEvent( L"\\NETLOGON_SERVICE_STARTED", 10 );

        if ( Status != STATUS_NETLOGON_NOT_STARTED ) {
            goto Cleanup;
        }

        //
        // If we've waited long enough for netlogon to start,
        //  time out now.
        //

        if ( (--Timeout) == 0 ) {
            Status = STATUS_NETLOGON_NOT_STARTED;
            goto Cleanup;
        }


    }

    /* NOT REACHED */

Cleanup:
    if ( ScManagerHandle != NULL ) {
        (VOID) CloseServiceHandle(ScManagerHandle);
    }
    if ( ServiceHandle != NULL ) {
        (VOID) CloseServiceHandle(ServiceHandle);
    }
    if ( AllocServiceConfig != NULL ) {
        LocalFree( AllocServiceConfig );
    }
    return Status;
}
#endif // WIN32_CHICAGO



DWORD
WINAPI
#ifdef NETTEST_UTILITY
NettestDsGetDcNameW(
#else // NETTEST_UTILITY
DsGetDcNameW(
#endif // NETTEST_UTILITY
        IN LPCWSTR ComputerName OPTIONAL,
        IN LPCWSTR DomainName OPTIONAL,
        IN GUID *DomainGuid OPTIONAL,
        IN LPCWSTR SiteName OPTIONAL,
        IN ULONG Flags,
        OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
)
/*++

Routine Description:

    The DsGetDcName API returns the name of a DC in a specified domain.
    The domain may be trusted (directly or indirectly) by the caller or
    may be untrusted.  DC selection criteria are supplied to the API to
    indicate preference for a DC with particular characteristics.

    The DsGetDcName API is available in an ANSI and UNICODE versions.
    This is the Unicode version.

    The DsGetDcName API does not require any particular access to the
    specified domain.  DsGetDcName does not ensure the returned domain
    controller is currently available by default.  Rather, the caller
    should attempt to use the returned domain controller.  If the domain
    controller is indeed not available, the caller should repeat the
    DsGetDcName call specifying the DS_FORCE_REDISCOVERY flag.

    The DsGetDcName API is remoted to the Netlogon service on the machine
    specified by ComputerName.

Arguments:

    ComputerName - Specifies the name of the server to remote this API to.
        Typically, this parameter should be specified as NULL.

    DomainName - The name of the domain to query.  This name can either be a
        DNS-style name (e.g., microsoft.com) or a flat-style name
        (e.g., microsoft).

    DomainGuid - Specifies the Domain GUID of the domain being queried.
        This value is used to handle the case of domain renames.  If this
        value is specified and DomainName has been renamed, DsGetDcName will
        attempt to locate a DC in the domain having this specified DomainGuid.

    SiteName - Specifies the site name of the site the returned DC should be
        "close" to.  The parameter should typically be the site name of the
        site the client is in.  If not specified, the site name defaults to
        the site of ComputerName.

    Flags - Passes additional information to be used to process the request.
        Flags can be a combination values bitwise or'ed together.

    DomainControllerInfo - Returns a pointer to a DOMAIN_CONTROLLER_INFO
        structure describing the domain controller selected.  The returned
        structure must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

    ERROR_INVALID_DOMAINNAME - The format of the specified domain name is
        invalid.

    ERROR_NO_SUCH_DOMAIN: No DC is available for the specified domain or
        the domain does not exist.

    ERROR_NO_SUCH_USER: A DC responded that the specified user account
        doesn't exist

    ERROR_INVALID_FLAGS - The flags parameter has conflicting or superfluous
        bits set.

    ERROR_INTERNAL_ERROR: Unhandled situation detected.

    Various Winsock errors.

--*/
{
    NET_API_STATUS NetStatus;

    return DsGetDcNameWithAccountW(
                    ComputerName,
                    NULL,   // no AccountName,
                    0,      // no AllowableAccountControlBits,
                    DomainName,
                    DomainGuid,
                    SiteName,
                    Flags,
                    DomainControllerInfo );
}



DWORD
WINAPI
DsGetDcNameWithAccountW(
        IN LPCWSTR ComputerName OPTIONAL,
        IN LPCWSTR AccountName OPTIONAL,
        IN ULONG AllowableAccountControlBits,
        IN LPCWSTR DomainName OPTIONAL,
        IN GUID *DomainGuid OPTIONAL,
        IN LPCWSTR SiteName OPTIONAL,
        IN ULONG Flags,
        OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
)
/*++

Routine Description:

    The DsGetDcName API returns the name of a DC in a specified domain.
    The domain may be trusted (directly or indirectly) by the caller or
    may be untrusted.  DC selection criteria are supplied to the API to
    indicate preference for a DC with particular characteristics.

    The DsGetDcName API is available in an ANSI and UNICODE versions.
    This is the Unicode version.

    The DsGetDcName API does not require any particular access to the
    specified domain.  DsGetDcName does not ensure the returned domain
    controller is currently available by default.  Rather, the caller
    should attempt to use the returned domain controller.  If the domain
    controller is indeed not available, the caller should repeat the
    DsGetDcName call specifying the DS_FORCE_REDISCOVERY flag.

    The DsGetDcName API is remoted to the Netlogon service on the machine
    specified by ComputerName.

Arguments:

    ComputerName - Specifies the name of the server to remote this API to.
        Typically, this parameter should be specified as NULL.

    AccountName - Account name to pass on the ping request.
        If NULL, no account name will be sent.

    AllowableAccountControlBits - Mask of allowable account types for AccountName.
        Valid bits are those specified by UF_MACHINE_ACCOUNT_MASK.
        Invalid bits are ignored.  If more than one bit is specified, the
        account can be of any of the specified types.

    DomainName - The name of the domain to query.  This name can either be a
        DNS-style name (e.g., microsoft.com) or a flat-style name
        (e.g., microsoft).

    DomainGuid - Specifies the Domain GUID of the domain being queried.
        This value is used to handle the case of domain renames.  If this
        value is specified and DomainName has been renamed, DsGetDcName will
        attempt to locate a DC in the domain having this specified DomainGuid.

    SiteName - Specifies the site name of the site the returned DC should be
        "close" to.  The parameter should typically be the site name of the
        site the client is in.  If not specified, the site name defaults to
        the site of ComputerName.

    Flags - Passes additional information to be used to process the request.
        Flags can be a combination values bitwise or'ed together.

    DomainControllerInfo - Returns a pointer to a DOMAIN_CONTROLLER_INFO
        structure describing the domain controller selected.  The returned
        structure must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

    ERROR_INVALID_DOMAINNAME - The format of the specified domain name is
        invalid.

    ERROR_NO_SUCH_DOMAIN: No DC is available for the specified domain or
        the domain does not exist.

    ERROR_NO_SUCH_USER: A DC responded that the specified user account
        doesn't exist

    ERROR_INVALID_FLAGS - The flags parameter has conflicting or superfluous
        bits set.

    ERROR_INTERNAL_ERROR: Unhandled situation detected.

    Various Winsock errors.

--*/
{
    NET_API_STATUS NetStatus;

    GUID *PrimaryDomainGuid = NULL;
    LPWSTR NetbiosPrimaryDomainName = NULL;
    LPWSTR DnsPrimaryDomainName = NULL;
    LPWSTR DnsPrimaryForestName = NULL;
    BOOLEAN IsWorkgroupName;
    BOOLEAN Local;


    //
    // Determine the PrimaryDomainName
    //

#ifdef WIN32_CHICAGO
    NetStatus = NetpGetDomainNameExEx( &NetbiosPrimaryDomainName,
                                       &DnsPrimaryDomainName,
                                       &IsWorkgroupName );
#else // WIN32_CHICAGO
    NetStatus = NetpGetDomainNameExExEx( &NetbiosPrimaryDomainName,
                                         &DnsPrimaryDomainName,
                                         &DnsPrimaryForestName,
                                         &PrimaryDomainGuid,
                                         &IsWorkgroupName );
#endif // WIN32_CHICAGO

    if ( NetStatus != NERR_Success ) {
        NlPrint(( 0, "DsGetDcNameW: cannot call NetpGetDomainName: %ld\n",
                          NetStatus));
        goto Cleanup;
    }
#ifdef WIN32_CHICAGO
    IsWorkgroupName = TRUE;
#endif // WIN32_CHICAGO
#ifdef NETTEST_UTILITY
    IsWorkgroupName = TRUE;
#endif // NETTEST_UTILITY

    //
    // Sanity check the domain name since LSA doesn't and I'll AV below
    //

    if ( NetbiosPrimaryDomainName != NULL &&
         wcslen( NetbiosPrimaryDomainName) > DNLEN ) {
        NlPrint(( 0, "DsGetDcNameW: Workgroup name is too long: %ld\n",
                     wcslen( NetbiosPrimaryDomainName ) ));
        NetStatus = ERROR_INVALID_DOMAINNAME;
        goto Cleanup;
    }



    //
    // If this machine is a member of a workgroup (not a domain),
    //  and the API isn't remoted,
    //  do the algorithm here.
    //  Netlogon isn't running on this machine.
    //

    if ( IsWorkgroupName ) {
        DWORD Timeout = NL_DC_MAX_TIMEOUT;   // 15 seconds
        DWORD DialUpDelayInSeconds;

        //
        // Read the dial up delay from the registry. Add it to the
        // maximum DC discovery timeout to account for dial up
        // connections. If the value cannot be read, ignore it.
        //

        if ( NlReadDwordNetlogonRegValue("ExpectedDialupDelay",
                                         &DialUpDelayInSeconds) ) {
            NlPrint(( 0, "DsGetDcNameWithAccountW: Read dial up delay of %ld seconds\n",
                      DialUpDelayInSeconds ));
            Timeout += DialUpDelayInSeconds * 1000;
        }

        NetStatus = DsLocalGetDcName(
                        ComputerName,
                        AccountName,
                        AllowableAccountControlBits,
                        DomainName,
                        DomainGuid,
                        SiteName,
                        Flags,
                        DS_PRIMARY_NAME_IS_WORKGROUP,
                        Timeout,
                        NetbiosPrimaryDomainName,
                        DnsPrimaryDomainName,
                        DnsPrimaryForestName,
                        PrimaryDomainGuid,
                        &Local,
                        DomainControllerInfo );

        //
        // If the call was performed locally,
        //   We're all done regardless of the status.
        //

        if ( Local ) {
            goto Cleanup;
        }

    }

#ifndef NETTEST_UTILITY
#ifndef WIN32_CHICAGO
    //
    // Remote the API to the Netlogon service.
    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {
        //
        // Call RPC version of the API.
        //

        *DomainControllerInfo = NULL;  // Force RPC to allocate

        NetStatus = DsrGetDcNameEx2(
                            (LPWSTR) ComputerName,
                            (LPWSTR) AccountName,
                            AllowableAccountControlBits,
                            (LPWSTR) DomainName,
                            DomainGuid,
                            (LPWSTR) SiteName,
                            Flags,
                            DomainControllerInfo );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    //
    // If the netlogon service isn't running,
    //  and it's in the process of starting,
    //  wait for it to start.
    //

    if ( NetStatus == RPC_S_UNKNOWN_IF ) {
        NTSTATUS TempStatus;

        TempStatus = NlWaitForNetlogon( 90 );


        if ( NT_SUCCESS(TempStatus) ) {

            RpcTryExcept {
                //
                // Call RPC version of the API.
                //

                *DomainControllerInfo = NULL;  // Force RPC to allocate

                NetStatus = DsrGetDcNameEx2(
                                    (LPWSTR) ComputerName,
                                    (LPWSTR) AccountName,
                                    AllowableAccountControlBits,
                                    (LPWSTR) DomainName,
                                    DomainGuid,
                                    (LPWSTR) SiteName,
                                    Flags,
                                    DomainControllerInfo );

            } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

                NetStatus = RpcExceptionCode();

            } RpcEndExcept;
        }
    }

    //
    // If Netlogon isn't running on the local machine,
    //  or Netlogon is the NT 4.0 version of netlogon (the upgrade case),
    //  try doing the call in-process.
    //

    if ( NetStatus == RPC_S_UNKNOWN_IF ||
         NetStatus == RPC_S_PROCNUM_OUT_OF_RANGE ) {
        NET_API_STATUS TempStatus;
        DWORD Timeout = NL_DC_MAX_TIMEOUT;   // 15 seconds
        DWORD DialUpDelayInSeconds;

        //
        // Read the dial up delay from the registry. Add it to the
        // maximum DC discovery timeout to account for dial up
        // connections. If the value cannot be read, ignore it.
        //

        if ( NlReadDwordNetlogonRegValue("ExpectedDialupDelay",
                                         &DialUpDelayInSeconds) ) {
            NlPrint(( 0, "DsGetDcNameWithAccountW: Read dial up delay of %ld seconds\n",
                      DialUpDelayInSeconds ));
            Timeout += DialUpDelayInSeconds * 1000;
        }

        TempStatus = DsLocalGetDcName(
                        ComputerName,
                        AccountName,
                        AllowableAccountControlBits,
                        DomainName,
                        DomainGuid,
                        SiteName,
                        Flags,
                        IsWorkgroupName ?
                            DS_PRIMARY_NAME_IS_WORKGROUP : 0,
                        Timeout,
                        NetbiosPrimaryDomainName,
                        DnsPrimaryDomainName,
                        DnsPrimaryForestName,
                        PrimaryDomainGuid,
                        &Local,
                        DomainControllerInfo );

        //
        // If the call was performed locally,
        //   We're all done regardless of the status.
        //

        if ( Local ) {
            NetStatus = TempStatus;
            goto Cleanup;
        }

    }
#endif // WIN32_CHICAGO
#endif // NETTEST_UTILITY

    IF_DEBUG( LOGON ) {
#ifndef WIN32_CHICAGO
        NetpKdPrint(("DsrGetDcName rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
#else // WIN32_CHICAGO
        NlPrint((0, "DsrGetDcName rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
#endif // WIN32_CHICAGO
    }

Cleanup:

    //
    // Cleanup all locally used resources
    //

    if ( NetbiosPrimaryDomainName != NULL ) {
        NetApiBufferFree( NetbiosPrimaryDomainName );
    }

    if ( DnsPrimaryDomainName != NULL ) {
        NetApiBufferFree( DnsPrimaryDomainName );
    }

    if ( DnsPrimaryForestName != NULL ) {
        NetApiBufferFree( DnsPrimaryForestName );
    }

    if ( PrimaryDomainGuid != NULL ) {
        NetApiBufferFree( PrimaryDomainGuid );
    }
#ifdef WIN32_CHICAGO
        NlPrint((NL_MISC, "DsGetDcNameWithAccountW rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
#endif // WIN32_CHICAGO

    return NetStatus;
}

#ifndef NETTEST_UTILITY
#ifndef WIN32_CHICAGO

NET_API_STATUS NET_API_FUNCTION
NetGetDCName (
    IN  LPCWSTR   ServerName OPTIONAL,
    IN  LPCWSTR   DomainName OPTIONAL,
    OUT LPBYTE  *Buffer
    )

/*++

Routine Description:

    Get the name of the primary domain controller for a domain.

Arguments:

    ServerName - name of remote server (null for local)

    DomainName - name of domain (null for primary)

    Buffer - Returns a pointer to an allcated buffer containing the
        servername of the PDC of the domain.  The server name is prefixed
        by \\.  The buffer should be deallocated using NetApiBufferFree.

Return Value:

        NERR_Success - Success.  Buffer contains PDC name prefixed by \\.
        NERR_DCNotFound     No DC found for this domain.
        ERROR_INVALID_NAME  Badly formed domain name

--*/
{
    NET_API_STATUS NetStatus = 0;
    PDOMAIN_CONTROLLER_INFOW DomainControllerInfo = NULL;


    //
    // API SECURITY - Anyone can call anytime.  No code required.
    //

    //
    // Check if API is to be remoted, and handle downlevel case if so.
    //

    if ( (ServerName != NULL) && ( ServerName[0] != '\0') ) {
        WCHAR UncCanonServerName[UNCLEN+1];
        DWORD LocalOrRemote;

        NetStatus = NetpIsRemote(
                (LPWSTR) ServerName,    // uncanon server name
                & LocalOrRemote,
                UncCanonServerName,     // output: canon
                UNCLEN + 1,
                NIRFLAG_MAPLOCAL        // flags: map null to local name
                );
        if (NetStatus != NERR_Success) {
            goto Cleanup;
        }
        if (LocalOrRemote == ISREMOTE) {


            //
            // Do the RPC call with an exception handler since RPC will raise an
            // exception if anything fails. It is up to us to figure out what
            // to do once the exception is raised.
            //

            NET_REMOTE_TRY_RPC

                //
                // Call RPC version of the API.
                //
                *Buffer = NULL;

                NetStatus = NetrGetDCName(
                                    (LPWSTR) ServerName,
                                    (LPWSTR) DomainName,
                                    (LPWSTR *)Buffer );

            NET_REMOTE_RPC_FAILED(
                    "NetGetDCName",
                    UncCanonServerName,
                    NetStatus,
                    NET_REMOTE_FLAG_NORMAL,
                    SERVICE_NETLOGON )

                //
                // We should probaly check if it's really a downlevel machine
                //

                NetStatus = RxNetGetDCName(
                        UncCanonServerName,
                        (LPWSTR) DomainName,
                        (LPBYTE *) Buffer  // may be allocated
                        );


            NET_REMOTE_END

            goto Cleanup;

        }

        //
        // Must be explicit reference to local machine.  Fall through and
        // handle it.
        //

    }

    //
    // Simply call DsGetDcName to find the PDC.
    //
    // NT 3.x cached the response for the primary domain.  The was Lanman
    // legacy to avoid the expensive discovery.  However, NT discovery is
    // cheaper than the API calls Lanman used to verify the cached information.
    //
    //

    NetStatus = DsGetDcNameW(
                    NULL,       // Not remoted
                    DomainName,
                    NULL,       // No Domain GUID
                    NULL,       // No Site GUID
                    DS_FORCE_REDISCOVERY |
                        DS_PDC_REQUIRED |
                        DS_IS_FLAT_NAME |
                        DS_RETURN_FLAT_NAME,
                    &DomainControllerInfo );

    //
    // Map the status codes to be compatible.
    //

    if ( NetStatus != NO_ERROR ) {
        if ( NlDcUseGenericStatus(NetStatus) ) {
            NetStatus = NERR_DCNotFound;
        }
        goto Cleanup;
    }

    //
    // Allocate a buffer to return to the caller and fill it in
    //

    NetStatus = NetapipBufferAllocate(
                      (wcslen(DomainControllerInfo->DomainControllerName) + 1) * sizeof(WCHAR),
                      (LPVOID *) Buffer );

    if ( NetStatus != NO_ERROR ) {
        IF_DEBUG( LOGON ) {
            NetpKdPrint(( "NetGetDCName: cannot allocate response buffer.\n"));
        }
        goto Cleanup;
    }

    wcscpy((LPWSTR)*Buffer, DomainControllerInfo->DomainControllerName );

Cleanup:

    //
    // Cleanup all locally used resources
    //

    if ( DomainControllerInfo != NULL ) {
        NetApiBufferFree( DomainControllerInfo );
    }

    return NetStatus;
}


NET_API_STATUS NET_API_FUNCTION
NetGetAnyDCName (
    IN  LPCWSTR   ServerName OPTIONAL,
    IN  LPCWSTR   DomainName OPTIONAL,
    OUT LPBYTE  *Buffer
    )

/*++

Routine Description:

    Get the name of the any domain controller for a domain that is directly trusted
    by ServerName.


    If ServerName is a standalone Windows NT Workstation or standalone Windows NT Server,
        no DomainName is valid.

    If ServerName is a Windows NT Workstation that is a member of a domain or a
        Windows NT Server member server,
        the DomainName must the the domain ServerName is a member of.

    If ServerName is a Windows NT Server domain controller,
        the DomainName must be one of the domains trusted by the
        domain the server is a controller for.

    The domain controller found is guaranteed to have been up at one point during
    this API call.

Arguments:

    ServerName - name of remote server (null for local)

    DomainName - name of domain (null for primary domain)

    Buffer - Returns a pointer to an allcated buffer containing the
        servername of a DC of the domain.  The server name is prefixed
        by \\.  The buffer should be deallocated using NetApiBufferFree.

Return Value:

    ERROR_SUCCESS - Success.  Buffer contains DC name prefixed by \\.

    ERROR_NO_LOGON_SERVERS - No DC could be found

    ERROR_NO_SUCH_DOMAIN - The specified domain is not a trusted domain.

    ERROR_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
        broken.

    ERROR_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
        broken or the password is broken.

    ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper
        domain controller of the specified domain.

--*/
{
    NET_API_STATUS          NetStatus;


    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {

        *Buffer = NULL;  // Force RPC to allocate

        //
        // Call RPC version of the API.
        //

        NetStatus = NetrGetAnyDCName(
                            (LPWSTR) ServerName,
                            (LPWSTR) DomainName,
                            (LPWSTR *) Buffer );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    IF_DEBUG( LOGON ) {
        NetpKdPrint(("NetGetAnyDCName rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
    }

    return NetStatus;
}

DWORD
WINAPI
DsValidateSubnetNameA(
    IN LPCSTR SubnetName
)
/*++

Routine Description:

    Routine to validate a subnet name of the form xxx.xxx.xxx.xxx/yy

Arguments:

    SubnetName - Name of the subnet to validate.

Return Value:

    NO_ERROR: Subnet name is valid

    ERROR_INVALID_NAME: Subnet name is not valid

--*/
{
    DWORD WinStatus;
    LPWSTR UnicodeSubnetName = NULL;

    //
    // Convert to unicode.
    //

    if ( SubnetName == NULL ) {
        return ERROR_INVALID_NAME;
    }

    UnicodeSubnetName = NetpAllocWStrFromAStr( SubnetName );

    if ( UnicodeSubnetName == NULL ) {
        WinStatus = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }

    //
    // Validate the name
    //

    WinStatus = DsValidateSubnetNameW( UnicodeSubnetName );

    //
    // Clean up locally used resources.
    //

Cleanup:
    if ( UnicodeSubnetName != NULL ) {
        NetApiBufferFree( UnicodeSubnetName );
    }

    return WinStatus;

}

DWORD
WINAPI
DsValidateSubnetNameW(
    IN LPCWSTR SubnetName
)
/*++

Routine Description:

    Routine to validate a subnet name of the form xxx.xxx.xxx.xxx/yy

Arguments:

    SubnetName - Name of the subnet to validate.

Return Value:

    NO_ERROR: Subnet name is valid

    ERROR_INVALID_NAME: Subnet name is not valid

--*/
{
    DWORD WinStatus;
    ULONG SubnetAddress;
    ULONG SubnetMask;
    BYTE SubnetBitCount;


    //
    // Caller the worker routine to do the real work
    //

    WinStatus = NlParseSubnetString( SubnetName,
                                     &SubnetAddress,
                                     &SubnetMask,
                                     &SubnetBitCount );

    //
    // If Winsock has not yet been initialized,
    //  initialize it.
    //

    if ( WinStatus == WSANOTINITIALISED ) {

        //
        // Initialize WSA.
        //

        WinStatus = DsWsaInitialize();

        if ( WinStatus != NO_ERROR ) {
            goto Cleanup;
        }

        //
        // Repeat the call.
        //


        WinStatus = NlParseSubnetString( SubnetName,
                                         &SubnetAddress,
                                         &SubnetMask,
                                         &SubnetBitCount );

    }

    //
    // Free locally used resouces.
    //

Cleanup:
    return WinStatus;

}

DWORD
WINAPI
DsGetSiteNameA(
    IN LPCSTR ComputerName OPTIONAL,
    OUT LPSTR *SiteName
    )
/*++

Routine Description:

    Same as DsGetSiteNameW except it takes and returns ASCII.

Arguments:

    Same as DsGetSiteNameW except it takes and returns ASCII.

Return Value:

    Same as DsGetSiteNameW except it takes and returns ASCII.

--*/
{
    DWORD WinStatus;
    LPWSTR UnicodeComputerName = NULL;
    LPWSTR UnicodeSiteName = NULL;


    //
    // Convert input parameters to Unicode.
    //

    *SiteName = NULL;

    if ( ComputerName != NULL ) {
        UnicodeComputerName = NetpAllocWStrFromAStr( ComputerName );

        if ( UnicodeComputerName == NULL ) {
            WinStatus = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }
    }

    //
    // Call the Unicode version of the routine.
    //

    WinStatus = DsGetSiteNameW(
                    UnicodeComputerName,
                    &UnicodeSiteName );

    if ( WinStatus != NO_ERROR ) {
        goto Cleanup;
    }

    //
    // Convert the output structure to ANSI character set.
    //

    *SiteName = NetpAllocAStrFromWStr( UnicodeSiteName );

    if ( *SiteName == NULL ) {
        WinStatus = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }

    WinStatus = NO_ERROR;

    //
    // Clean up locally used resources.
    //

Cleanup:
    if ( UnicodeComputerName != NULL ) {
        NetApiBufferFree( UnicodeComputerName );
    }
    if ( UnicodeSiteName != NULL ) {
        NetApiBufferFree( UnicodeSiteName );
    }

    return WinStatus;

}

DWORD
WINAPI
DsGetSiteNameW(
    IN LPCWSTR ComputerName OPTIONAL,
    OUT LPWSTR *SiteName
    )
/*++

Routine Description:

    The DsGetSiteName API returns the name site a computer is in.

    For a DC, the SiteName is the site the DC is configured to be in.

    For a member workstation or member server, this is the name of the site
    the workstation is in as configured in the domain the machine is a member of.

    A standalone workstation or standalone server will always return
    ERROR_NO_SITENAME.

Arguments:

    ComputerName - Specifies the name of the server to remote this API to.
        Typically, this parameter should be specified as NULL.

    SiteName - Returns the site name of the site this machine is in.
        The returned buffer must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

    ERROR_NO_SITENAME - The machine is not in a site.

--*/
{
    NET_API_STATUS NetStatus;

    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {

        *SiteName = NULL;  // Force RPC to allocate

        //
        // Call RPC version of the API.
        //

        NetStatus = DsrGetSiteName(
                            (LPWSTR) ComputerName,
                            SiteName );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    //
    // If Netlogon isn't running on the local machine,
    //  or Netlogon is the NT 4.0 version of netlogon (the upgrade case),
    //  Simply indicate that there is no site.
    //

    if ( NetStatus == RPC_S_UNKNOWN_IF ||
         NetStatus == RPC_S_PROCNUM_OUT_OF_RANGE ) {

        NetStatus = ERROR_NO_SITENAME;

    }

    IF_DEBUG( LOGON ) {
        NetpKdPrint(("DsrGetSiteName rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
    }

    return NetStatus;
}

DWORD
WINAPI
DsAddressToSiteNamesA(
    IN LPCSTR ComputerName OPTIONAL,
    IN DWORD EntryCount,
    IN PSOCKET_ADDRESS SocketAddresses,
    OUT LPSTR **SiteNames
    )
/*++

Routine Description:

    The DsAddressToSiteNames API returns the site names that correspond to
    the specified addresses.

Arguments:

    ComputerName - Specifies the name of the domain controller to remote this API to.

    EntryCount - Number of addresses to convert.

    SocketAddresses - Specifies an EntryCount element array of addresses
        to convert.  Each address must be of type AF_INET.

    SiteNames - Returns an array of pointers to site names.  EntryCount entries
        are returned.  An entry will be returned as NULL if the corresponding
        address does not map to any site or if the address is malformed.

        The returned buffer must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

--*/
{
    NET_API_STATUS NetStatus;
    PNL_SITE_NAME_ARRAY SiteNameArray;
    LPWSTR UnicodeComputerName = NULL;

    //
    // Initialization
    //
    *SiteNames = NULL;
    if ( EntryCount == 0 ) {
        return ERROR_INVALID_PARAMETER;
    }


    //
    // Convert input parameters to Unicode.
    //

    if ( ComputerName != NULL ) {
        UnicodeComputerName = NetpAllocWStrFromAStr( ComputerName );

        if ( UnicodeComputerName == NULL ) {
            return ERROR_NOT_ENOUGH_MEMORY;
        }
    }



    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {

        SiteNameArray  = NULL;  // Force RPC to allocate

        //
        // Call RPC version of the API.
        //

        NetStatus = DsrAddressToSiteNamesW(
                            UnicodeComputerName,
                            EntryCount,
                            (PNL_SOCKET_ADDRESS)SocketAddresses,
                            &SiteNameArray );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    //
    // Convert the site names to what the caller expects.
    //

    if ( NetStatus == NO_ERROR && SiteNameArray != NULL ) {

        //
        // Sanity check
        //

        if ( EntryCount != SiteNameArray->EntryCount ) {
            NetStatus = ERROR_INVALID_PARAMETER;


        } else {
            ULONG Size;
            ULONG i;

            //
            // Allocate a buffer to return to the caller
            //

            Size = sizeof(LPSTR) * EntryCount;
            for ( i=0; i<EntryCount; i++) {
                Size += RtlUnicodeStringToAnsiSize( &SiteNameArray->SiteNames[i] );
            }

            *SiteNames = MIDL_user_allocate( Size );

            if ( *SiteNames == NULL ) {
                NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            } else {
                LPBYTE Where;

                //
                // Loop copying names to the caller.
                //

                Where = ((LPBYTE)(*SiteNames)) + sizeof(LPSTR) * EntryCount;
                for ( i=0; i<EntryCount; i++) {

                    //
                    // If no name was returned,
                    //  pass a NULL back to the caller.
                    //

                    if ( SiteNameArray->SiteNames[i].Length == 0 ) {
                        (*SiteNames)[i] = NULL;

                    //
                    // Copy the site name into the return buffer.
                    //
                    } else {
                        ANSI_STRING TempString;
                        NTSTATUS Status;

                        (*SiteNames)[i] = (LPSTR) Where;
                        TempString.Buffer = Where;
                        TempString.MaximumLength = (USHORT) RtlUnicodeStringToAnsiSize( &SiteNameArray->SiteNames[i] );
                        Status = RtlUnicodeStringToAnsiString(
                                        &TempString,
                                        &SiteNameArray->SiteNames[i],
                                        FALSE );

                        if ( !NT_SUCCESS(Status) ) {
                            MIDL_user_free( *SiteNames );
                            *SiteNames = NULL;
                            NetStatus = NetpNtStatusToApiStatus( Status );
                            break;
                        }

                        Where += TempString.MaximumLength;

                    }
                }

            }
        }

        MIDL_user_free( SiteNameArray );
    }

    IF_DEBUG( LOGON ) {
        NetpKdPrint(("DsAddressToSiteNames rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
    }

    if ( UnicodeComputerName != NULL ) {
        NetApiBufferFree( UnicodeComputerName );
    }

    return NetStatus;
}

DWORD
WINAPI
DsAddressToSiteNamesW(
    IN LPCWSTR ComputerName OPTIONAL,
    IN DWORD EntryCount,
    IN PSOCKET_ADDRESS SocketAddresses,
    OUT LPWSTR **SiteNames
    )
/*++

Routine Description:

    The DsAddressToSiteNames API returns the site names that correspond to
    the specified addresses.

Arguments:

    ComputerName - Specifies the name of the domain controller to remote this API to.

    EntryCount - Number of addresses to convert.

    SocketAddresses - Specifies an EntryCount element array of addresses
        to convert.  Each address must be of type AF_INET.

    SiteNames - Returns an array of pointers to site names.  EntryCount entries
        are returned.  An entry will be returned as NULL if the corresponding
        address does not map to any site or if the address is malformed.

        The returned buffer must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

--*/
{
    NET_API_STATUS NetStatus;
    PNL_SITE_NAME_ARRAY SiteNameArray;

    //
    // Initialization
    //
    *SiteNames = NULL;
    if ( EntryCount == 0 ) {
        return ERROR_INVALID_PARAMETER;
    }



    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {

        SiteNameArray  = NULL;  // Force RPC to allocate

        //
        // Call RPC version of the API.
        //

        NetStatus = DsrAddressToSiteNamesW(
                            (LPWSTR) ComputerName,
                            EntryCount,
                            (PNL_SOCKET_ADDRESS)SocketAddresses,
                            &SiteNameArray );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    //
    // Convert the site names to what the caller expects.
    //

    if ( NetStatus == NO_ERROR && SiteNameArray != NULL ) {

        //
        // Sanity check
        //

        if ( EntryCount != SiteNameArray->EntryCount ) {
            NetStatus = ERROR_INVALID_PARAMETER;


        } else {
            ULONG Size;
            ULONG i;

            //
            // Allocate a buffer to return to the caller
            //

            Size = sizeof(LPWSTR) * EntryCount;
            for ( i=0; i<EntryCount; i++) {
                Size += SiteNameArray->SiteNames[i].Length + sizeof(WCHAR);
            }

            *SiteNames = MIDL_user_allocate( Size );

            if ( *SiteNames == NULL ) {
                NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            } else {
                LPBYTE Where;

                //
                // Loop copying names to the caller.
                //

                Where = ((LPBYTE)(*SiteNames)) + sizeof(LPWSTR) * EntryCount;
                for ( i=0; i<EntryCount; i++) {

                    //
                    // If no name was returned,
                    //  pass a NULL back to the caller.
                    //

                    if ( SiteNameArray->SiteNames[i].Length == 0 ) {
                        (*SiteNames)[i] = NULL;

                    //
                    // Copy the site name into the return buffer.
                    //
                    } else {

                        (*SiteNames)[i] = (LPWSTR) Where;
                        RtlCopyMemory( Where,
                                       SiteNameArray->SiteNames[i].Buffer,
                                       SiteNameArray->SiteNames[i].Length );
                        Where += SiteNameArray->SiteNames[i].Length;
                        *(LPWSTR)Where = L'\0';
                        Where += sizeof(WCHAR);

                    }
                }

            }
        }

        MIDL_user_free( SiteNameArray );
    }

    IF_DEBUG( LOGON ) {
        NetpKdPrint(("DsAddressToSiteNames rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
    }

    return NetStatus;
}

DWORD
WINAPI
DsAddressToSiteNamesExA(
    IN LPCSTR ComputerName OPTIONAL,
    IN DWORD EntryCount,
    IN PSOCKET_ADDRESS SocketAddresses,
    OUT LPSTR **SiteNames,
    OUT LPSTR **SubnetNames
    )
/*++

Routine Description:

    The DsAddressToSiteNamesEx API returns the site names and subnet names
    that correspond to the specified addresses.

Arguments:

    ComputerName - Specifies the name of the domain controller to remote this API to.

    EntryCount - Number of addresses to convert.

    SocketAddresses - Specifies an EntryCount element array of addresses
        to convert.  Each address must be of type AF_INET.

    SiteNames - Returns an array of pointers to site names.  EntryCount entries
        are returned.  An entry will be returned as NULL if the corresponding
        address does not map to any site or if the address is malformed.

        The returned buffer must be deallocated using NetApiBufferFree.

    SubnetNames - Returns an array of pointers to subnet names which were used
        to perform the address to site name mappings.  EntryCount entries
        are returned.  An entry will be returned as NULL if the corresponding address
        to site mapping was not determined or if no subnet was used to perform the
        corresponding address to site mapping which is the case when there is exactly
        one site in the enterprise with no subnet objects mapped to it.

        The returned buffer must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

--*/
{
    NET_API_STATUS NetStatus;
    PNL_SITE_NAME_EX_ARRAY SiteNameArray;
    LPWSTR UnicodeComputerName = NULL;

    //
    // Initialization
    //
    *SiteNames = NULL;
    *SubnetNames = NULL;
    if ( EntryCount == 0 ) {
        return ERROR_INVALID_PARAMETER;
    }


    //
    // Convert input parameters to Unicode.
    //

    if ( ComputerName != NULL ) {
        UnicodeComputerName = NetpAllocWStrFromAStr( ComputerName );

        if ( UnicodeComputerName == NULL ) {
            return ERROR_NOT_ENOUGH_MEMORY;
        }
    }



    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {

        SiteNameArray  = NULL;  // Force RPC to allocate

        //
        // Call RPC version of the API.
        //

        NetStatus = DsrAddressToSiteNamesExW(
                            UnicodeComputerName,
                            EntryCount,
                            (PNL_SOCKET_ADDRESS)SocketAddresses,
                            &SiteNameArray );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    //
    // Convert the site names to what the caller expects.
    //

    if ( NetStatus == NO_ERROR && SiteNameArray != NULL ) {

        //
        // Sanity check
        //

        if ( EntryCount != SiteNameArray->EntryCount ) {
            NetStatus = ERROR_INVALID_PARAMETER;


        } else {
            ULONG Size;
            ULONG i;

            //
            // Allocate a buffer to return to the caller
            //

            Size = sizeof(LPSTR) * EntryCount;
            for ( i=0; i<EntryCount; i++) {
                Size += RtlUnicodeStringToAnsiSize( &SiteNameArray->SiteNames[i] );
            }

            *SiteNames = MIDL_user_allocate( Size );

            if ( *SiteNames == NULL ) {
                NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            } else {

                Size = sizeof(LPSTR) * EntryCount;
                for ( i=0; i<EntryCount; i++) {
                    Size += RtlUnicodeStringToAnsiSize( &SiteNameArray->SubnetNames[i] );
                }

                *SubnetNames = MIDL_user_allocate( Size );

                if ( *SubnetNames == NULL ) {
                    MIDL_user_free( *SiteNames );
                    *SiteNames = NULL;
                    NetStatus = ERROR_NOT_ENOUGH_MEMORY;
                } else {
                    LPBYTE Where;
                    LPBYTE Where2;

                    //
                    // Loop copying names to the caller.
                    //

                    Where = ((LPBYTE)(*SiteNames)) + sizeof(LPSTR) * EntryCount;
                    Where2 = ((LPBYTE)(*SubnetNames)) + sizeof(LPSTR) * EntryCount;
                    for ( i=0; i<EntryCount; i++) {

                        //
                        // If no name was returned,
                        //  pass a NULL back to the caller.
                        //

                        if ( SiteNameArray->SiteNames[i].Length == 0 ) {
                            (*SiteNames)[i] = NULL;

                        //
                        // Copy the site name into the return buffer.
                        //
                        } else {
                            ANSI_STRING TempString;
                            NTSTATUS Status;

                            (*SiteNames)[i] = (LPSTR) Where;
                            TempString.Buffer = Where;
                            TempString.MaximumLength = (USHORT) RtlUnicodeStringToAnsiSize( &SiteNameArray->SiteNames[i] );
                            Status = RtlUnicodeStringToAnsiString(
                                            &TempString,
                                            &SiteNameArray->SiteNames[i],
                                            FALSE );

                            if ( !NT_SUCCESS(Status) ) {
                                MIDL_user_free( *SiteNames );
                                *SiteNames = NULL;
                                MIDL_user_free( *SubnetNames );
                                *SubnetNames = NULL;
                                NetStatus = NetpNtStatusToApiStatus( Status );
                                break;
                            }

                            Where += TempString.MaximumLength;

                        }

                        //
                        // If no name was returned,
                        //  pass a NULL back to the caller.
                        //

                        if ( SiteNameArray->SubnetNames[i].Length == 0 ) {
                            (*SubnetNames)[i] = NULL;

                        //
                        // Copy the Subnet name into the return buffer.
                        //
                        } else {
                            ANSI_STRING TempString;
                            NTSTATUS Status;

                            (*SubnetNames)[i] = (LPSTR) Where2;
                            TempString.Buffer = Where2;
                            TempString.MaximumLength = (USHORT) RtlUnicodeStringToAnsiSize( &SiteNameArray->SubnetNames[i] );
                            Status = RtlUnicodeStringToAnsiString(
                                            &TempString,
                                            &SiteNameArray->SubnetNames[i],
                                            FALSE );

                            if ( !NT_SUCCESS(Status) ) {
                                MIDL_user_free( *SubnetNames );
                                *SubnetNames = NULL;
                                MIDL_user_free( *SubnetNames );
                                *SubnetNames = NULL;
                                NetStatus = NetpNtStatusToApiStatus( Status );
                                break;
                            }

                            Where2 += TempString.MaximumLength;

                        }
                    }
                }

            }
        }

        MIDL_user_free( SiteNameArray );
    }

    IF_DEBUG( LOGON ) {
        NetpKdPrint(("DsAddressToSiteNames rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
    }

    return NetStatus;
}

DWORD
WINAPI
DsAddressToSiteNamesExW(
    IN LPCWSTR ComputerName OPTIONAL,
    IN DWORD EntryCount,
    IN PSOCKET_ADDRESS SocketAddresses,
    OUT LPWSTR **SiteNames,
    OUT LPWSTR **SubnetNames
    )
/*++

Routine Description:

    The DsAddressToSiteNames API returns the site names that correspond to
    the specified addresses.

Arguments:

    ComputerName - Specifies the name of the domain controller to remote this API to.

    EntryCount - Number of addresses to convert.

    SocketAddresses - Specifies an EntryCount element array of addresses
        to convert.  Each address must be of type AF_INET.

    SiteNames - Returns an array of pointers to site names.  EntryCount entries
        are returned.  An entry will be returned as NULL if the corresponding
        address does not map to any site or if the address is malformed.

        The returned buffer must be deallocated using NetApiBufferFree.

    SubnetNames - Returns an array of pointers to subnet names which were used
        to perform the address to site name mappings.  EntryCount entries
        are returned.  An entry will be returned as NULL if the corresponding address
        to site mapping was not determined or if no subnet was used to perform the
        corresponding address to site mapping which is the case when there is exactly
        one site in the enterprise with no subnet objects mapped to it.

        The returned buffer must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

--*/
{
    NET_API_STATUS NetStatus;
    PNL_SITE_NAME_EX_ARRAY SiteNameArray;

    //
    // Initialization
    //
    *SiteNames = NULL;
    *SubnetNames = NULL;
    if ( EntryCount == 0 ) {
        return ERROR_INVALID_PARAMETER;
    }



    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {

        SiteNameArray  = NULL;  // Force RPC to allocate

        //
        // Call RPC version of the API.
        //

        NetStatus = DsrAddressToSiteNamesExW(
                            (LPWSTR) ComputerName,
                            EntryCount,
                            (PNL_SOCKET_ADDRESS)SocketAddresses,
                            &SiteNameArray );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    //
    // Convert the site names to what the caller expects.
    //

    if ( NetStatus == NO_ERROR && SiteNameArray != NULL ) {

        //
        // Sanity check
        //

        if ( EntryCount != SiteNameArray->EntryCount ) {
            NetStatus = ERROR_INVALID_PARAMETER;


        } else {
            ULONG Size;
            ULONG i;

            //
            // Allocate a buffer to return to the caller
            //

            Size = sizeof(LPWSTR) * EntryCount;
            for ( i=0; i<EntryCount; i++) {
                Size += SiteNameArray->SiteNames[i].Length + sizeof(WCHAR);
            }

            *SiteNames = MIDL_user_allocate( Size );

            if ( *SiteNames == NULL ) {
                NetStatus = ERROR_NOT_ENOUGH_MEMORY;
            } else {

                //
                // Allocate a buffer to return to the caller
                //

                Size = sizeof(LPWSTR) * EntryCount;
                for ( i=0; i<EntryCount; i++) {
                    Size += SiteNameArray->SubnetNames[i].Length + sizeof(WCHAR);
                }

                *SubnetNames = MIDL_user_allocate( Size );

                if ( *SubnetNames == NULL ) {
                    NetStatus = ERROR_NOT_ENOUGH_MEMORY;
                } else {
                    LPBYTE Where;
                    LPBYTE Where2;

                    //
                    // Loop copying names to the caller.
                    //

                    Where = ((LPBYTE)(*SiteNames)) + sizeof(LPWSTR) * EntryCount;
                    Where2 = ((LPBYTE)(*SubnetNames)) + sizeof(LPWSTR) * EntryCount;
                    for ( i=0; i<EntryCount; i++) {

                        //
                        // If no name was returned,
                        //  pass a NULL back to the caller.
                        //

                        if ( SiteNameArray->SiteNames[i].Length == 0 ) {
                            (*SiteNames)[i] = NULL;

                        //
                        // Copy the site name into the return buffer.
                        //
                        } else {

                            (*SiteNames)[i] = (LPWSTR) Where;
                            RtlCopyMemory( Where,
                                           SiteNameArray->SiteNames[i].Buffer,
                                           SiteNameArray->SiteNames[i].Length );
                            Where += SiteNameArray->SiteNames[i].Length;
                            *(LPWSTR)Where = L'\0';
                            Where += sizeof(WCHAR);

                        }

                        //
                        // If no name was returned,
                        //  pass a NULL back to the caller.
                        //

                        if ( SiteNameArray->SubnetNames[i].Length == 0 ) {
                            (*SubnetNames)[i] = NULL;

                        //
                        // Copy the Subnet name into the return buffer.
                        //
                        } else {

                            (*SubnetNames)[i] = (LPWSTR) Where2;
                            RtlCopyMemory( Where2,
                                           SiteNameArray->SubnetNames[i].Buffer,
                                           SiteNameArray->SubnetNames[i].Length );
                            Where2 += SiteNameArray->SubnetNames[i].Length;
                            *(LPWSTR)Where2 = L'\0';
                            Where2 += sizeof(WCHAR);

                        }
                    }
                }

            }
        }

        MIDL_user_free( SiteNameArray );
    }

    IF_DEBUG( LOGON ) {
        NetpKdPrint(("DsAddressToSiteNames rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
    }

    return NetStatus;
}

DWORD
WINAPI
DsGetDcSiteCoverageA(
    IN LPCSTR ComputerName OPTIONAL,
    OUT PULONG EntryCount,
    OUT LPSTR **SiteNames
    )
/*++

Routine Description:

    This API returns the site names of all sites covered by a DC.

Arguments:

    ComputerName - Specifies the name of the domain controller to remote this API to.

    EntryCount - Returns the number of sites covered by DC.

    SiteNames - Returns an array of pointers to site names.
        The returned buffer must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

--*/
{
    NET_API_STATUS NetStatus;
    PNL_SITE_NAME_ARRAY SiteNameArray;
    LPWSTR UnicodeComputerName = NULL;

    //
    // Initialization
    //

    *SiteNames = NULL;

    //
    // Convert input parameters to Unicode.
    //

    if ( ComputerName != NULL ) {
        UnicodeComputerName = NetpAllocWStrFromAStr( (LPSTR)ComputerName );

        if ( UnicodeComputerName == NULL ) {
            return ERROR_NOT_ENOUGH_MEMORY;
        }
    }


    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {

        SiteNameArray = NULL;  // Force RPC to allocate

        //
        // Call RPC version of the API.
        //

        NetStatus = DsrGetDcSiteCoverageW(
                            UnicodeComputerName,
                            &SiteNameArray );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    //
    // Convert the site names to what the caller expects.
    //

    if ( NetStatus == NO_ERROR && SiteNameArray != NULL ) {

        ULONG Size;
        ULONG i;

        //
        // Set the size of the array
        //

        *EntryCount = SiteNameArray->EntryCount;

        //
        // Allocate a buffer to return to the caller
        //

        Size = sizeof(LPSTR) * SiteNameArray->EntryCount;
        for ( i=0; i<*EntryCount; i++ ) {
            Size += RtlUnicodeStringToAnsiSize( &SiteNameArray->SiteNames[i] );
        }

        *SiteNames = MIDL_user_allocate( Size );

        if ( *SiteNames == NULL ) {
            NetStatus = ERROR_NOT_ENOUGH_MEMORY;
        } else {

            LPBYTE Where;

            //
            // Loop copying names to the caller.
            //

            Where = ((LPBYTE)(*SiteNames)) + sizeof(LPSTR) * SiteNameArray->EntryCount;
            for ( i=0; i<*EntryCount; i++) {

                //
                // Copy the site name into the return buffer.
                //
                ANSI_STRING TempString;
                NTSTATUS Status;

                (*SiteNames)[i] = (LPSTR) Where;
                TempString.Buffer = Where;
                TempString.MaximumLength = (USHORT) RtlUnicodeStringToAnsiSize( &SiteNameArray->SiteNames[i] );
                Status = RtlUnicodeStringToAnsiString(
                                &TempString,
                                &SiteNameArray->SiteNames[i],
                                FALSE );

                if ( !NT_SUCCESS(Status) ) {
                    MIDL_user_free( *SiteNames );
                    *SiteNames = NULL;
                    NetStatus = NetpNtStatusToApiStatus( Status );
                    break;
                }

                Where += TempString.MaximumLength;

            }

        }

        MIDL_user_free( SiteNameArray );
    }

    IF_DEBUG( LOGON ) {
        NetpKdPrint(("DsGetDcSiteCoverage rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
    }

    if ( UnicodeComputerName != NULL ) {
        NetApiBufferFree( UnicodeComputerName );
    }

    return NetStatus;
}

DWORD
WINAPI
DsGetDcSiteCoverageW(
    IN LPCWSTR ComputerName OPTIONAL,
    OUT PULONG EntryCount,
    OUT LPWSTR **SiteNames
    )
/*++

Routine Description:

    This API returns the site names of all sites covered by a DC.

Arguments:

    ComputerName - Specifies the name of the domain controller to remote this API to.

    EntryCount - Returns the number of sites covered by DC.

    SiteNames - Returns an array of pointers to site names.
        The returned buffer must be deallocated using NetApiBufferFree.

Return Value:

    NO_ERROR - Operation completed successfully;

    ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to complete the
        operation.

--*/
{
    NET_API_STATUS NetStatus;
    PNL_SITE_NAME_ARRAY SiteNameArray;

    //
    // Initialization
    //

    *SiteNames = NULL;

    //
    // Do the RPC call with an exception handler since RPC will raise an
    // exception if anything fails. It is up to us to figure out what
    // to do once the exception is raised.
    //

    RpcTryExcept {

        SiteNameArray = NULL;  // Force RPC to allocate

        //
        // Call RPC version of the API.
        //

        NetStatus = DsrGetDcSiteCoverageW(
                            (LPWSTR) ComputerName,
                            &SiteNameArray );

    } RpcExcept( I_RpcExceptionFilter(RpcExceptionCode()) ) {

        NetStatus = RpcExceptionCode();

    } RpcEndExcept;

    //
    // Convert the site names to what the caller expects.
    //

    if ( NetStatus == NO_ERROR && SiteNameArray != NULL ) {

        ULONG Size;
        ULONG i;

        //
        // Set the size of the array
        //

        *EntryCount = SiteNameArray->EntryCount;

        //
        // Allocate a buffer to return to the caller
        //

        Size = sizeof(LPWSTR) * SiteNameArray->EntryCount;
        for ( i=0; i<*EntryCount; i++) {
            Size += SiteNameArray->SiteNames[i].Length + sizeof(WCHAR);
        }

        *SiteNames = MIDL_user_allocate( Size );

        if ( *SiteNames == NULL ) {
            NetStatus = ERROR_NOT_ENOUGH_MEMORY;
        } else {

            LPBYTE Where;

            //
            // Loop copying names to the caller.
            //

            Where = ((LPBYTE)(*SiteNames)) + sizeof(LPWSTR) * SiteNameArray->EntryCount;
            for ( i=0; i<*EntryCount; i++) {

                //
                // Copy the site name into the return buffer.
                //

                (*SiteNames)[i] = (LPWSTR) Where;
                RtlCopyMemory( Where,
                               SiteNameArray->SiteNames[i].Buffer,
                               SiteNameArray->SiteNames[i].Length );
                Where += SiteNameArray->SiteNames[i].Length;
                *(LPWSTR)Where = L'\0';
                Where += sizeof(WCHAR);

            }

        }

        MIDL_user_free( SiteNameArray );
    }

    IF_DEBUG( LOGON ) {
        NetpKdPrint(("DsGetDcSiteCoverage rc = %lu 0x%lx\n",
                     NetStatus, NetStatus));
    }

    return NetStatus;
}

#endif // WIN32_CHICAGO
#endif // NETTEST_UTILITY
