/*++ 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 #include #include #ifndef WIN32_CHICAGO #include #include // needed by rpcasync.h #include // I_RpcExceptionFilter #include // includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h #else // WIN32_CHICAGO #include #include #endif // WIN32_CHICAGO #include #include #include #ifndef WIN32_CHICAGO #include #include #endif // WIN32_CHICAGO #include // IF_DEBUG() #include // DsGetDcName() #include // DsGetDcNameWithAccount() #include // NAMETYPE_* defines NetpIsRemote(), NIRFLAG_ equates. #include #include #ifndef WIN32_CHICAGO #include // SUPPORTS_* defines #include // SV_TYPE_* defines #include // SERVICE_* defines #include #include // NetpLogon routines #include // Netlogon RPC binding cache init routines #endif // WIN32_CHICAGO #include // NetpKdPrint #include // NetpMemoryFree #ifndef WIN32_CHICAGO #include // NetpApiStatusToNtStatus #include #include // RxNetGetDCName(). #include #include #endif // WIN32_CHICAGO #include // NetpCopyStrToWStr() #if DBG #define NETLOGONDBG 1 #endif // DBG #include // NlPrint() #include // Needed by nlcommon.h #include // 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; } if ( NetStatus == NO_ERROR ) { // // Initialize globals // DsGetDcWsaInitialized = FALSE; // // Initialize the cache of discovered domains. // NetStatus = NetpDcInitializeCache(); if ( NetStatus != NO_ERROR ) { NlPrint((0,"NETAPI32.DLL: Cannot NetpDcinitializeCache\n")); } } 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]; ULONG 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); #ifndef WIN32_CHICAGO length = (ULONG) vsprintf(OutputBuffer, Format, arglist); #else // WIN32_CHICAGO length = (ULONG) vsprintf(OutputBuffer + length - 1, Format, arglist); #endif // WIN32_CHICAGO va_end(arglist); 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 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 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; } // // Start building the destination mailslot name. // NetlogonMailslotName[0] = '\\'; NetlogonMailslotName[1] = '\\'; if ( UnicodeDestinationName != NULL ) { wcscpy(NetlogonMailslotName + 2, UnicodeDestinationName ); } else { NlPrint((NL_CRITICAL, "NETAPI32: NlBrowserSendDatagram internal error 2.\n" )); return STATUS_INTERNAL_ERROR; } 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 ); 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: Open a context for retrieval of the addresses of machines that have registered LDAP.TCP. 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. 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 ); 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 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; iSiteNames[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; iSiteNames[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; iSiteNames[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; iSiteNames[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; iSiteNames[i] ); } *SiteNames = MIDL_user_allocate( Size ); if ( *SiteNames == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; } else { Size = sizeof(LPSTR) * EntryCount; for ( i=0; iSubnetNames[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; iSiteNames[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; iSiteNames[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; iSubnetNames[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; iSiteNames[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