Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

13664 lines
421 KiB

//depot/Lab02_N/DS/netapi/svcdlls/logonsrv/idl/netpdc.c#6 - integrate change 5756 (text)
/*++
Copyright (c) 1987-1992 Microsoft Corporation
Module Name:
NetpDc.c
Abstract:
Routines shared by logonsrv\server and logonsrv\common
Author:
Cliff Van Dyke (cliffv) 20-July-1996
Environment:
User mode only.
Contains NT-specific code.
Requires ANSI C extensions: slash-slash comments, long external names.
Revision History:
--*/
//
// Common include files.
//
#ifndef _NETLOGON_SERVER
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntsam.h> // Needed by netlogon.h
#include <ntlsa.h>
#include <rpc.h> // RPC_STATUS
#include <windef.h>
#include <winbase.h>
#include <winsock2.h>
#include <lmcons.h> // General net defines
#include <dsgetdc.h> // DsGetDcName()
#include <dsgetdcp.h>
#include <align.h> // ROUND_UP_COUNT()
#include <config.h> // NetConfig
#include <lmsname.h> // SERVICE_TCPIP
#include <lmerr.h> // System Error Log definitions
#include <icanon.h> // NetpNameValidate()
#include <lmapibuf.h> // NetapipBufferAllocate
#include <lmaccess.h> // UF_*
#include <names.h> // NetpIsDomainNameValid()
#include <netlib.h> // NetpMemoryAllcate(
#include <netlibnt.h> // NetpApiStatusToNtStatus();
#include <netlogon.h> // Definition of mailslot messages
#include <ntddbrow.h> // Needed by nlcommon.h
#include <ntrpcp.h>
#include <logonp.h> // NetpLogon.. routines
#include <tstring.h> // NetpCopyStrToWStr()
#include <time.h> // time() function from C runtime
#if DBG
#define NETLOGONDBG 1
#endif // DBG
#include <nldebug.h> // NlPrint()
#include <nlbind.h> // Definitions shared with netlogon
#include <nlcommon.h> // Definitions shared with netlogon
#ifdef WIN32_CHICAGO
#include "ntcalls.h"
BOOLEAN CodePage = TRUE; // Alway be DBCS
BOOLEAN *NlsMbOemCodePageTag = &CodePage;
#endif // WIN32_CHICAGO
#include <iniparm.h>
#endif // _NETLOGON_SERVER
#include <svcguid.h> // SVCID_INET_HOSTADDRBYNAME
#define OLD_DNS_RECORD 1 // Needed for dnsapi.h
#include <dnsapi.h> // DnsNameCompare_W
#include <dnssrv.h> // NetpSrv...
#include <winldap.h> // ldap_...
//
// Include nlcommon.h again allocating the actual variables
// this time around.
//
#define NLCOMMON_ALLOCATE
#include "nlcommon.h"
#undef NLCOMMON_ALLOCATE
//
// Context describing the SRV records for a DNS name.
//
typedef struct _DSGETDC_CONTEXT {
//
// Original parameters passed by the caller.
//
LPSTR QueriedDnsName;
LPWSTR QueriedSiteName;
GUID QueriedDomainGuid;
LPSTR QueriedDnsForestName;
DWORD QueriedInternalFlags;
// NL_DNS_NAME_TYPE QueriedNlDnsNameType;
//
// Type of this DNS name being queried.
//
NL_DNS_NAME_TYPE NlDnsNameType;
//
// Context of the current DNS name.
//
HANDLE SrvContextHandle;
//
// Flags
//
ULONG QueriedFlags; // Flags passed to DsGetDcOpen
BOOLEAN FirstTime; // This is the first DnsGetDcNext call
} DSGETDC_CONTEXT, *PDSGETDC_CONTEXT;
//
// List of previously cached responses.
//
CRITICAL_SECTION NlDcCritSect;
LIST_ENTRY NlDcDomainList;
ULONG NlDcDomainCount;
#define NL_DC_MAX_DOMAINS 2000 // Avoid infinite domains.
GUID NlDcZeroGuid;
DWORD NlDcDnsFailureTime;
//
// Determine if the passed in DWORD has precisely one bit set.
//
#define JUST_ONE_BIT( _x ) (((_x) != 0 ) && ( ( (~(_x) + 1) & (_x) ) == (_x) ))
// If the caller passes ANY of these flags,
// only an NT 5.0 (or newer) DC should respond.
#define DS_NT50_REQUIRED (DS_DIRECTORY_SERVICE_REQUIRED | \
DS_GC_SERVER_REQUIRED | \
DS_IP_REQUIRED | \
DS_RETURN_DNS_NAME | \
DS_KDC_REQUIRED | \
DS_TIMESERV_REQUIRED | \
DS_IS_DNS_NAME )
// If the caller passes ANY of these flags,
// an NT 5.0 (or newer) DC should respond.
#define DS_NT50_WANTED (DS_NT50_REQUIRED | \
DS_DIRECTORY_SERVICE_PREFERRED )
//
// Define an exception filter to improve debuggin capabilities.
//
#ifdef _NETLOGON_SERVER
#define NL_EXCEPTION NlExceptionFilter(GetExceptionInformation())
LONG
NlExceptionFilter( EXCEPTION_POINTERS * pException)
{
if( pException->ExceptionRecord->ExceptionCode == STATUS_POSSIBLE_DEADLOCK ) {
return EXCEPTION_CONTINUE_SEARCH;
}
#if DBG
DbgPrint("[Netlogon] exception in DsGetDcName.\n" );
DbgBreakPoint();
#endif // DBG
return EXCEPTION_EXECUTE_HANDLER;
UNREFERENCED_PARAMETER( pException );
}
#endif // _NETLOGON_SERVER
/*++
Routine Description:
This macro clears all of the negative cache fields for a particular DC entry.
Arguments:
_DcEntry -- Address of the entry to flush
Return Value:
None
--*/
#ifdef _NETLOGON_SERVER
#define NlFlushNegativeCacheEntry( _DcEntry ) \
(_DcEntry)->NegativeCacheTime = 0; \
(_DcEntry)->ExpBackoffPeriod = 0; \
(_DcEntry)->BackgroundRetryInitTime.QuadPart = 0; \
(_DcEntry)->PermanentNegativeCache = FALSE;
#else // _NETLOGON_SERVER
#define NlFlushNegativeCacheEntry( _DcEntry )
#endif // _NETLOGON_SERVER
#if NETLOGONDBG
LPSTR
NlMailslotOpcode(
IN WORD Opcode
)
/*++
Routine Description:
Return string describing mailslot message.
Arguments:
Opcode: Opcode of message
Return Value:
String corresponding to opcode
--*/
{
switch ( Opcode ) {
case LOGON_REQUEST:
return "UAS Logon";
case LOGON_RESPONSE:
return "UAS Logon Response <old>";
case LOGON_CENTRAL_QUERY:
return "CENTRAL_QUERY";
case LOGON_DISTRIB_QUERY:
return "DISTRIB_QUERY";
case LOGON_CENTRAL_RESPONSE:
return "CENTRAL_RESPONSE";
case LOGON_DISTRIB_RESPONSE:
return "DISTRIB_RESPONSE";
case LOGON_RESPONSE2:
return "Uas Logon Response";
case LOGON_PRIMARY_QUERY:
return "Primary Query";
case LOGON_START_PRIMARY:
return "Start Primary";
case LOGON_FAIL_PRIMARY:
return "Fail Primary";
case LOGON_UAS_CHANGE:
return "Uas Change";
case LOGON_NO_USER:
return "Uas No User <old>";
case LOGON_PRIMARY_RESPONSE:
return "Primary Response";
case LOGON_RELOGON_RESPONSE:
return "RELOGON_RESPONSE";
case LOGON_WKSTINFO_RESPONSE:
return "WKSTINFO_RESPONSE";
case LOGON_PAUSE_RESPONSE:
return "Uas Pause Response";
case LOGON_USER_UNKNOWN:
return "Uas No User";
case LOGON_UPDATE_ACCOUNT:
return "UPDATE_ACCOUNT";
case LOGON_SAM_LOGON_REQUEST:
return "Sam Logon";
case LOGON_SAM_LOGON_RESPONSE:
return "Sam Logon Response";
case LOGON_SAM_PAUSE_RESPONSE:
return "Sam Pause Response";
case LOGON_SAM_USER_UNKNOWN:
return "Sam User Unknown";
case LOGON_SAM_LOGON_RESPONSE_EX:
return "Sam Logon Response Ex";
case LOGON_SAM_PAUSE_RESPONSE_EX:
return "Sam Pause Response Ex";
case LOGON_SAM_USER_UNKNOWN_EX:
return "Sam User Unknown Ex";
default:
return "<Unknown>";
}
}
LPSTR
NlDgrNameType(
IN DGRECEIVER_NAME_TYPE NameType
)
/*++
Routine Description:
Return string describing datagram receiver name type.
Arguments:
NameType: Name type of interest.
Return Value:
String corresponding to name type
--*/
{
switch ( NameType ) {
case ComputerName:
return "00";
case PrimaryDomain:
return "00";
case LogonDomain:
return "LogonDomain";
case OtherDomain:
return "OtherDomain";
case DomainAnnouncement:
return "__MSBROWSE__";
case MasterBrowser:
return "1D";
case BrowserElection:
return "1E";
case BrowserServer:
return "20";
case DomainName:
return "1C";
case PrimaryDomainBrowser:
return "1B";
case AlternateComputerName:
return "Alternate";
default:
return "<Unknown>";
}
}
#endif // NETLOGONDBG
BOOLEAN
NlReadDwordHklmRegValue(
IN LPCSTR SubKey,
IN LPCSTR ValueName,
OUT PDWORD ValueRead
)
/*++
Routine Description:
Reads a DWORD from the specified registry location.
Arguments:
SubKey - Subkey of the value to read.
ValueName - The name of the value to read.
ValueRead - Returns the value read from the registry.
Return Status:
TRUE - We've successfully read the data.
FALSE - We've not been able to read the data successfully.
--*/
{
LONG RegStatus;
HKEY KeyHandle = NULL;
DWORD ValueType;
DWORD Value;
DWORD ValueSize;
//
// Open the key
//
RegStatus = RegOpenKeyExA(
HKEY_LOCAL_MACHINE,
SubKey,
0, //Reserved
KEY_QUERY_VALUE,
&KeyHandle );
if ( RegStatus != ERROR_SUCCESS ) {
if ( RegStatus != ERROR_FILE_NOT_FOUND ) {
NlPrint(( NL_CRITICAL,
"NlReadDwordHklmRegValue: Cannot open registy key 'HKLM\\%s' %ld.\n",
SubKey,
RegStatus ));
}
return FALSE;
}
//
// Get the value
//
ValueSize = sizeof(Value);
RegStatus = RegQueryValueExA(
KeyHandle,
ValueName,
0,
&ValueType,
(LPBYTE)&Value,
&ValueSize );
RegCloseKey( KeyHandle );
if ( RegStatus != ERROR_SUCCESS ) {
if ( RegStatus != ERROR_FILE_NOT_FOUND ) {
NlPrint(( NL_CRITICAL,
"NlReadDwordHklmRegValue: Cannot query value of 'HKLM\\%s\\%s' %ld.\n",
SubKey,
ValueName,
RegStatus ));
}
return FALSE;
}
if ( ValueType != REG_DWORD ) {
NlPrint(( NL_CRITICAL,
"NlReadDwordHklmRegValue: value of 'HKLM\\%s\\%s'is not a REG_DWORD %ld.\n",
SubKey,
ValueName,
ValueType ));
return FALSE;
}
if ( ValueSize != sizeof(Value) ) {
NlPrint(( NL_CRITICAL,
"NlReadDwordHklmRegValue: value size of 'HKLM\\%s\\%s'is not 4 %ld.\n",
SubKey,
ValueName,
ValueSize ));
return FALSE;
}
//
// We've successfully read the data
//
*ValueRead = Value;
return TRUE;
}
BOOLEAN
NlReadDwordNetlogonRegValue(
IN LPCSTR ValueName,
OUT PDWORD Value
)
/*++
Routine Description:
This is common code (i.e. not netlogon specific) that reads
a DWORD from Netlogon specific locations in registry. It
first reads the value from the Netlogon Group Policy section.
If the value is not specified in teh GP section, this routine
reads the value from the Netlogon PArameters section.
Arguments:
ValueName - The name of the value to read.
Value - Returns the value read from the registry.
Return Status:
TRUE - We've successfully read the data.
FALSE - We've not been able to read the data successfully.
--*/
{
BOOLEAN Result = FALSE;
DWORD LocalValue = 0;
//
// The value given in Netlogon GP section takes precedence.
//
Result = NlReadDwordHklmRegValue( NL_GPPARAM_KEY, // GP section
ValueName,
&LocalValue );
//
// If the value is not specified in the netlogon GP section,
// see if it is in the Netlogon Parameters section.
//
if ( !Result ) {
Result = NlReadDwordHklmRegValue( NL_PARAM_KEY, // Netlogon Parameters section
ValueName,
&LocalValue );
}
if ( Result ) {
*Value = LocalValue;
return TRUE;
}
return FALSE;
}
VOID
NetpIpAddressToStr(
ULONG IpAddress,
CHAR IpAddressString[NL_IP_ADDRESS_LENGTH+1]
)
/*++
Routine Description:
Convert an IP address to a string.
Arguments:
IpAddress - IP Address to convert
IpAddressString - resultant string.
Return Value:
None.
--*/
{
struct in_addr InetAddr;
char * InetAddrString;
//
// Convert the address to ascii
//
InetAddr.s_addr = IpAddress;
InetAddrString = inet_ntoa( InetAddr );
//
// Copy the string our to the caller.
//
if ( InetAddrString == NULL ||
strlen(InetAddrString) > NL_IP_ADDRESS_LENGTH ) {
*IpAddressString = L'\0';
} else {
strcpy( IpAddressString, InetAddrString );
}
return;
}
VOID
NetpIpAddressToWStr(
ULONG IpAddress,
WCHAR IpAddressString[NL_IP_ADDRESS_LENGTH+1]
)
/*++
Routine Description:
Convert an IP address to a string.
Arguments:
IpAddress - IP Address to convert
IpAddressString - resultant string.
Return Value:
None.
--*/
{
CHAR IpAddressStr[NL_IP_ADDRESS_LENGTH+1];
NetpIpAddressToStr( IpAddress, IpAddressStr );
NetpCopyStrToWStr( IpAddressString, IpAddressStr );
}
NET_API_STATUS
NetpSockAddrToStr(
PSOCKADDR SockAddr,
ULONG SockAddrSize,
CHAR SockAddrString[NL_SOCK_ADDRESS_LENGTH+1]
)
/*++
Routine Description:
Convert an socket address to a string.
Arguments:
SockAddr - Socket Address to convert
SockAddrSize - Size (in bytes) of SockAddr
SockAddrString - resultant string.
Return Value:
NO_ERROR: if translation was successful
--*/
{
int WsaError;
ULONG AddressLength;
#ifdef WIN32_CHICAGO
LPSTR pTemp;
#endif // WIN32_CHICAGO
//
// Convert the address to text.
//
AddressLength = NL_SOCK_ADDRESS_LENGTH+1;
#ifndef WIN32_CHICAGO // Needs Winsock2
WsaError = WSAAddressToStringA( SockAddr,
SockAddrSize,
NULL,
SockAddrString,
&AddressLength );
if ( WsaError != 0 ) {
*SockAddrString = '\0';
WsaError = WSAGetLastError();
NlPrint(( NL_CRITICAL,
"NetpSockAddrToStr: Cannot convert socket address %ld\n",
WsaError ));
return WsaError;
}
#else // WIN32_CHICAGO
// cast the PSOCKADDR to a sockaddr_in and access sin_addr
pTemp = inet_ntoa(((SOCKADDR_IN *) SockAddr)->sin_addr);
if ( (pTemp != NULL) && (strlen(pTemp) <= NL_SOCK_ADDRESS_LENGTH) ) {
strcpy(SockAddrString, pTemp);
} else {
*SockAddrString = '\0';
return ERROR_INTERNAL_ERROR;
}
#endif // WIN32_CHICAGO
return NO_ERROR;
}
NET_API_STATUS
NetpSockAddrToWStr(
PSOCKADDR SockAddr,
ULONG SockAddrSize,
WCHAR SockAddrString[NL_SOCK_ADDRESS_LENGTH+1]
)
/*++
Routine Description:
Convert an socket address to a string.
Arguments:
SockAddr - Socket Address to convert
SockAddrSize - Size (in bytes) of SockAddr
SockAddrString - resultant string.
Return Value:
TRUE if translation was successful
--*/
{
int WsaError;
ULONG AddressLength;
#ifdef WIN32_CHICAGO
CHAR OemSockAddrString[NL_SOCK_ADDRESS_LENGTH+1];
OEM_STRING OemString;
UNICODE_STRING UnicodeString;
#endif // WIN32_CHICAGO
//
// Convert the address to text.
//
AddressLength = NL_SOCK_ADDRESS_LENGTH+1;
#ifndef WIN32_CHICAGO
WsaError = WSAAddressToStringW( SockAddr,
SockAddrSize,
NULL,
SockAddrString,
&AddressLength );
if ( WsaError != 0 ) {
*SockAddrString = '\0';
WsaError = WSAGetLastError();
NlPrint(( NL_CRITICAL,
"NetpSockAddrToWStr: Cannot convert socket address %ld\n",
WsaError ));
return WsaError;
}
#else // WIN32_CHICAGO
// cast the PSOCKADDR to a sockaddr_in and access sin_addr
WsaError = NetpSockAddrToStr( SockAddr,
SockAddrSize,
OemSockAddrString);
RtlInitString(&OemString, OemSockAddrString);
UnicodeString.MaximumLength = ((USHORT)AddressLength) * sizeof(WCHAR);
UnicodeString.Buffer = SockAddrString;
RtlOemStringToUnicodeString(&UnicodeString, &OemString, FALSE);
#endif // WIN32_CHICAGO
return WsaError;
}
LPWSTR
NetpAllocWStrFromUtf8Str(
IN LPSTR Utf8String
)
/*++
Routine Description:
Convert a UTF8 (zero terminated) string to the corresponding UNICODE
string.
Arguments:
Utf8String - Specifies the UTF8 zero terminated string to convert.
Return Value:
NULL - There was some error in the conversion.
Otherwise, it returns a pointer to the zero terminated UNICODE string in
an allocated buffer. The buffer must be freed using NetApiBufferFree.
--*/
{
return NetpAllocWStrFromUtf8StrEx( Utf8String, -1 );
}
ULONG
NetpUtf8ToUnicodeLen(
IN LPSTR Utf8String
)
/*++
Routine Description:
Returns the number of UNICODE characters that will result if the
specified UTF8 (zero terminated) string is converted to UNICODE.
The resultant character count does not include the trailing zero terminator.
Arguments:
Utf8String - Specifies the UTF8 zero terminated string to convert.
Return Value:
Number of characters.
--*/
{
ULONG UnicodeLen;
//
// Determine the length of the Unicode string.
//
#ifndef WIN32_CHICAGO
// No support for UTF8/7 char on Win95. Use the entry points
// exported in wldap32.dll
UnicodeLen = MultiByteToWideChar(
CP_UTF8,
0, // All characters can be mapped.
Utf8String,
-1, // NULL terminated.
NULL,
0 );
if ( UnicodeLen == 0 ) {
return 0;
}
return UnicodeLen - 1;
#else // WIN32_CHICAGO
UnicodeLen = LdapUTF8ToUnicode(
Utf8String,
strlen(Utf8String),
NULL,
0 );
return UnicodeLen ;
#endif // WIN32_CHICAGO
}
VOID
NetpCopyUtf8StrToWStr(
OUT LPWSTR UnicodeString,
IN LPSTR Utf8String
)
/*++
Routine Description:
Convert a UTF8 (zero terminated) string to the corresponding UNICODE
string.
Arguments:
UnicodeString - Specifies the buffer the UTF8 string is to be copied to.
Utf8String - Specifies the UTF8 zero terminated string to convert.
Return Value:
None.
--*/
{
int UnicodeStringLen;
//
// Translate the string to Unicode.
//
#ifndef WIN32_CHICAGO
// No support for UTF8/7 char on Win95. Use the entry points
// exported in wldap32.dll
UnicodeStringLen = MultiByteToWideChar(
CP_UTF8,
0, // All characters can be mapped.
Utf8String,
-1, // NULL terminated.
UnicodeString,
0x7FFFFFFF );
#else // WIN32_CHICAGO
UnicodeStringLen = LdapUTF8ToUnicode(
Utf8String,
strlen(Utf8String),
UnicodeString,
0x7FFFFFFF );
#endif // WIN32_CHICAGO
if ( UnicodeStringLen == 0 ) {
*UnicodeString = L'\0';
}
}
NET_API_STATUS
NetpAllocWStrFromUtf8StrAsRequired(
IN LPSTR Utf8String,
IN ULONG Utf8StringLength,
IN ULONG UnicodeStringBufferSize,
OUT LPWSTR UnicodeStringBuffer OPTIONAL,
OUT LPWSTR *AllocatedUnicodeString OPTIONAL
)
/*++
Routine Description:
Convert a UTF8 (zero terminated) string to the corresponding UNICODE
string. Allocate memory as required.
Arguments:
Utf8String - Specifies the UTF8 zero terminated string to convert.
Utf8StringLength - Length in bytes of Utf8String excluding the NULL
terminator. (-1 for zero terminated)
UnicodeStringBuffer -- Buffer to copy the covnverted string to. If NULL,
the function will allocate the needed memory and return it in
AllocatedUnicodeString.
UnicodeStringBufferSize - Size in wide charactes of UnicodeStringBuffer.
If this size is less than what's needed to store the resulting
NULL terminated unicode string, the function will allocate the
needed memory and return it in AllocatedUnicodeString.
AllocatedUnicodeString - If the passed in buffer for the resulting
unicode string isn't large enough, the function will allocate
the needed memory and the pointer to the allocated memory will
be returned in this parameter. If NULL and the passed in buffer
isn't large enough to store the resulting NULl terminated string,
the function returns ERROR_INSUFFICIENT_BUFFER. The allocated buffer
must be freed using NetApiBufferFree.
Return Value:
NO_ERROR - The strinf has been successfully converted.
ERROR_INVALID_PARAMETER - The paramer combination is invalid.
ERROR_INSUFFICIENT_BUFFER - The passed in buffer isn't large enough
and the caller doesn't want this fi=unction to allocate needed
memory (i.e. AllocatedUnicodeString is NULL).
ERROR_NOT_ENOUGH_MEMORY - Couldn't allocate the needed memory.
--*/
{
NET_API_STATUS NetStatus = NO_ERROR;
LPWSTR UnicodeString = NULL;
int UnicodeStringLen = 0;
//
// Sanity check the parameters
//
if ( (UnicodeStringBuffer == NULL || UnicodeStringBufferSize == 0) &&
AllocatedUnicodeString == NULL ) {
return ERROR_INVALID_PARAMETER;
}
//
// Initilization
//
if ( AllocatedUnicodeString != NULL ) {
*AllocatedUnicodeString = NULL;
}
//
// Determine the length of the Unicode string.
//
#ifndef WIN32_CHICAGO
// No support for UTF8/7 char on Win95. Use the entry points
// exported in wldap32.dll
UnicodeStringLen = MultiByteToWideChar(
CP_UTF8,
0, // All characters can be mapped.
Utf8String,
Utf8StringLength,
UnicodeString,
0 );
#else // WIN32_CHICAGO
UnicodeStringLen = LdapUTF8ToUnicode(
Utf8String,
Utf8StringLength,
UnicodeString,
0 );
#endif // WIN32_CHICAGO
if ( UnicodeStringLen == 0 ) {
return ERROR_INVALID_PARAMETER;
}
//
// Allocate a buffer for the Unicode string,
// if the passed buffer isn't large enough
//
if ( UnicodeStringBuffer == NULL ||
((ULONG)UnicodeStringLen+1 > UnicodeStringBufferSize) ) {
//
// If the caller doesn't want us to allocate the
// space needed, tell him his buffer isn't large enough
//
if ( AllocatedUnicodeString == NULL ) {
return ERROR_INSUFFICIENT_BUFFER;
}
NetStatus = NetApiBufferAllocate( (UnicodeStringLen+1)*sizeof(WCHAR),
AllocatedUnicodeString );
if ( NetStatus != NO_ERROR ) {
return NetStatus;
}
UnicodeString = *AllocatedUnicodeString;
} else {
UnicodeString = UnicodeStringBuffer;
}
//
// Translate the string to Unicode.
//
#ifndef WIN32_CHICAGO
// No support for UTF8/7 char on Win95. Use the entry points
// exported in wldap32.dll
UnicodeStringLen = MultiByteToWideChar(
CP_UTF8,
0, // All characters can be mapped.
Utf8String,
Utf8StringLength,
UnicodeString,
UnicodeStringLen );
#else // WIN32_CHICAGO
UnicodeStringLen = LdapUTF8ToUnicode(
Utf8String,
Utf8StringLength,
UnicodeString,
UnicodeStringLen );
#endif // WIN32_CHICAGO
if ( UnicodeStringLen == 0 ) {
//
// If we have allocated the memory, free it
//
if ( AllocatedUnicodeString != NULL &&
*AllocatedUnicodeString != NULL ) {
NetApiBufferFree( *AllocatedUnicodeString );
*AllocatedUnicodeString = NULL;
}
return ERROR_INVALID_PARAMETER;
}
UnicodeString[UnicodeStringLen] = L'\0';
return NO_ERROR;
}
LPWSTR
NetpAllocWStrFromUtf8StrEx(
IN LPSTR Utf8String,
IN ULONG Length
)
/*++
Routine Description:
Convert a UTF8 (zero terminated) string to the corresponding UNICODE
string.
Arguments:
Utf8String - Specifies the UTF8 zero terminated string to convert.
Length - Length in bytes of Utf8String. (-1 for zero terminated).
Return Value:
NULL - There was some error in the conversion.
Otherwise, it returns a pointer to the zero terminated UNICODE string in
an allocated buffer. The buffer must be freed using NetApiBufferFree.
--*/
{
NET_API_STATUS NetStatus;
LPWSTR UnicodeString = NULL;
NetStatus = NetpAllocWStrFromUtf8StrAsRequired( Utf8String,
Length,
0,
NULL,
&UnicodeString );
if ( NetStatus == NO_ERROR ) {
return UnicodeString;
} else {
return NULL;
}
}
LPSTR
NetpCreateUtf8StrFromWStr(
IN LPCWSTR UnicodeString,
IN LPSTR TargetDestination OPTIONAL,
IN int TargetDestinationBufferSize
)
/*++
Routine Description:
Convert a Unicode (zero terminated) string to the corresponding
zero terminated UTF8 string.
Arguments:
UnicodeString - Specifies the Unicode zero terminated string to convert.
TargetDestination - Specifies the address in the preallocated buffer to
which to copy the converted string. If NULL, memory is allocated
by this routine.
TargetDestinationBufferSize - The size of the preallocated destination
buffer in bytes. If TargetDestination isn't NULL, TargetDestinationBufferSize
will be used to make sure that the routine does not write beyond the
preallocated buffer limit.
Return Value:
NULL - There was some error in the conversion.
Otherwise: if TargetDestination is NULL, it returns a pointer to the
zero terminated UTF8 string in an allocated buffer. The buffer must be
freed using NetpMemoryFree. If TargetDestination isn't NULL, it
returns a pointer whose value is equal to TargetDestination.
--*/
{
LPSTR Utf8String = NULL;
int Utf8StringLen;
//
// Determine the length of the Unicode string.
//
#ifndef WIN32_CHICAGO
// No support for UTF8/7 char on Win95. Use the entry points
// exported in wldap32.dll
Utf8StringLen = WideCharToMultiByte(
CP_UTF8,
0, // All characters can be mapped.
UnicodeString,
-1, // Zero terminated
Utf8String,
0,
NULL,
NULL );
#else // WIN32_CHICAGO
Utf8StringLen = LdapUnicodeToUTF8(
UnicodeString,
wcslen(UnicodeString),
Utf8String,
0
);
#endif // WIN32_CHICAGO
if ( Utf8StringLen == 0 ||
(TargetDestination != NULL && (Utf8StringLen+1 > TargetDestinationBufferSize)) ) {
return NULL;
}
//
// Allocate a buffer for the UTF8 string as needed.
//
if ( TargetDestination == NULL ) {
Utf8String = NetpMemoryAllocate( Utf8StringLen+1 );
} else {
Utf8String = TargetDestination;
}
if ( Utf8String == NULL ) {
return NULL;
}
//
// Translate the string to Unicode.
//
#ifndef WIN32_CHICAGO
// No support for UTF8/7 char on Win95. Use the entry points
// exported in wldap32.dll
Utf8StringLen = WideCharToMultiByte(
CP_UTF8,
0, // All characters can be mapped.
UnicodeString,
-1, // Zero terminated
Utf8String,
Utf8StringLen,
NULL,
NULL );
#else // WIN32_CHICAGO
Utf8StringLen = LdapUnicodeToUTF8(
UnicodeString,
wcslen(UnicodeString),
Utf8String,
Utf8StringLen
);
#endif // WIN32_CHICAGO
if ( Utf8StringLen == 0 ) {
if ( TargetDestination == NULL ) {
NetpMemoryFree( Utf8String );
}
return NULL;
}
Utf8String[Utf8StringLen] = '\0';
return Utf8String;
}
LPSTR
NetpAllocUtf8StrFromWStr(
IN LPCWSTR UnicodeString
)
/*++
Routine Description:
Convert a Unicode (zero terminated) string to the corresponding UTF8
string.
Arguments:
UnicodeString - Specifies the Unicode zero terminated string to convert.
Return Value:
NULL - There was some error in the conversion.
Otherwise, it returns a pointer to the zero terminated UTF8 string in
an allocated buffer. The buffer must be freed using NetApiBufferFree.
--*/
{
return NetpCreateUtf8StrFromWStr( UnicodeString, NULL, 0 );
}
LPSTR
NetpAllocUtf8StrFromUnicodeString(
IN PUNICODE_STRING UnicodeString
)
/*++
Routine Description:
Convert a Unicode string to the corresponding UTF8
string.
Arguments:
UnicodeString - Specifies the Unicode string to convert.
Return Value:
NULL - There was some error in the conversion.
Otherwise, it returns a pointer to the zero terminated UTF8 string in
an allocated buffer. The buffer must be freed using NetApiBufferFree.
--*/
{
LPSTR Utf8String = NULL;
int Utf8StringLen;
//
// Sanity check.
//
if ( UnicodeString == NULL || UnicodeString->Buffer == NULL ) {
return NULL;
}
//
// Determine the length of the Unicode string.
//
Utf8StringLen = WideCharToMultiByte(
CP_UTF8,
0, // All characters can be mapped.
UnicodeString->Buffer,
UnicodeString->Length/sizeof(WCHAR),
Utf8String,
0,
NULL,
NULL );
if ( Utf8StringLen == 0 ) {
return NULL;
}
//
// Allocate a buffer for the Unicode string.
//
Utf8String = NetpMemoryAllocate( Utf8StringLen+1 );
if ( Utf8String == NULL ) {
return NULL;
}
//
// Translate the string to Unicode.
//
Utf8StringLen = WideCharToMultiByte(
CP_UTF8,
0, // All characters can be mapped.
UnicodeString->Buffer,
UnicodeString->Length/sizeof(WCHAR),
Utf8String,
Utf8StringLen,
NULL,
NULL );
if ( Utf8StringLen == 0 ) {
NetpMemoryFree( Utf8String );
return NULL;
}
Utf8String[Utf8StringLen] = '\0';
return Utf8String;
}
BOOLEAN
NlpCompareUtf8(
IN LPCSTR Utf8String1,
IN ULONG Utf8String1Size,
IN LPCSTR Utf8String2,
IN ULONG Utf8String2Size
)
/*++
Routine Description:
Compare if two UTF8 strings are equal. The comparison is case insensitive.
Arguments:
Utf8String1 - First string of Utf8 characters to compare.
Utf8String1Size - Size (in bytes) of Utf8String1
Utf8String2 - Second string of Utf8 characters to compare.
Utf8String2Size - Size (in bytes) of Utf8String2
Return Value:
TRUE - if the strings are equal
--*/
{
WCHAR UnicodeString1[NL_MAX_DNS_LABEL_LENGTH];
WCHAR UnicodeString2[NL_MAX_DNS_LABEL_LENGTH];
int UnicodeString1Len;
int UnicodeString2Len;
//
// If the strings are bit for bit identical
// return so.
//
if ( Utf8String1Size == Utf8String2Size &&
RtlEqualMemory( Utf8String1, Utf8String2, Utf8String1Size ) ) {
return TRUE;
}
//
// Convert the strings to UNICODE
//
#ifndef WIN32_CHICAGO
// No support for UTF8/7 char on Win95. Use the entry points
// exported in wldap32.dll
UnicodeString1Len = MultiByteToWideChar(
CP_UTF8,
0, // All characters can be mapped.
Utf8String1,
Utf8String1Size, // Zero terminated
UnicodeString1,
sizeof(UnicodeString1)/sizeof(WCHAR) );
#else // WIN32_CHICAGO
UnicodeString1Len = LdapUTF8ToUnicode(
Utf8String1,
Utf8String1Size, // Zero terminated
UnicodeString1,
sizeof(UnicodeString1)/sizeof(WCHAR) );
#endif// WIN32_CHICAGO
if ( UnicodeString1Len == 0 ) {
return FALSE;
}
#ifndef WIN32_CHICAGO
// No support for UTF8/7 char on Win95. Use the entry points
// exported in wldap32.dll
UnicodeString2Len = MultiByteToWideChar(
CP_UTF8,
0, // All characters can be mapped.
Utf8String2,
Utf8String2Size, // Zero terminated
UnicodeString2,
sizeof(UnicodeString2)/sizeof(WCHAR) );
#else // WIN32_CHICAGO
UnicodeString2Len = LdapUTF8ToUnicode(
Utf8String2,
Utf8String2Size, // Zero terminated
UnicodeString2,
sizeof(UnicodeString2)/sizeof(WCHAR) );
#endif// WIN32_CHICAGO
if ( UnicodeString2Len == 0 ) {
return FALSE;
}
//
// Compare the Unicode strings
//
return CompareStringW( LOCALE_SYSTEM_DEFAULT,
NORM_IGNORECASE,
UnicodeString1,
UnicodeString1Len,
UnicodeString2,
UnicodeString2Len ) == 2;
}
NET_API_STATUS
NlpUnicodeToCutf8(
IN LPBYTE MessageBuffer,
IN LPCWSTR OrigUnicodeString,
IN BOOLEAN IgnoreDots,
IN OUT LPBYTE *Utf8String,
IN OUT PULONG Utf8StringSize,
IN OUT PULONG CompressCount,
IN OUT LPWORD CompressOffset,
IN OUT CHAR **CompressUtf8String
)
/*++
Routine Description:
Same as NlpUtf8ToCutf8 except the input string is in Unicode.
Arguments:
Same as NlpUtf8ToCutf8 except the input string is in Unicode.
Return Value:
Same as NlpUtf8ToCutf8 except the input string is in Unicode.
--*/
{
NET_API_STATUS NetStatus;
LPSTR LocalUtf8String;
//
// Convert the string to Utf8.
//
//
// Default to an empty string.
//
if ( !ARGUMENT_PRESENT(OrigUnicodeString) || *OrigUnicodeString == '\0' ) {
LocalUtf8String = NULL;
} else {
LocalUtf8String = NetpAllocUtf8StrFromWStr( OrigUnicodeString );
if ( LocalUtf8String == NULL ) {
return ERROR_NOT_ENOUGH_MEMORY;
}
}
//
// Pack it.
//
NetStatus = NlpUtf8ToCutf8( MessageBuffer,
LocalUtf8String,
IgnoreDots,
Utf8String,
Utf8StringSize,
CompressCount,
CompressOffset,
CompressUtf8String );
NetApiBufferFree( LocalUtf8String );
return NetStatus;
}
NET_API_STATUS
NlpUtf8ToCutf8(
IN LPBYTE MessageBuffer,
IN LPCSTR OrigUtf8String,
IN BOOLEAN IgnoreDots,
IN OUT LPBYTE *Utf8String,
IN OUT PULONG Utf8StringSize,
IN OUT PULONG CompressCount,
IN OUT LPWORD CompressOffset,
IN OUT CHAR **CompressUtf8String
)
/*++
Routine Description:
Convert the passed in OrigUtf8String into a counted UTF-8 string. The
resultant string is actually a series of counted strings in RFC 1035 DNS
format. Each label (up to 63 bytes terminated in a '.') is preceeded
by a byte count byte. The final byte count byte is a zero byte.
This routine also support RFC 1035 compression. In that format, the
terminating 'byte count' byte might have the high two bits set. In that
case, that byte and the byte following it represent an "offset" to the
actual remainder of the string. This routine inputs an array of strings
that will be matched for compression purposes.
RFC 1035 limits the character set to A-Z, a-z, 0-9, - and .. This routine
returns RFC compatible results if the input is limited to that character set.
The author expects DNS to be extended to include other characters and
to encode those characters using UTF-8.
Arguments:
Buffer - Pointer to the beginning of the buffer that all strings are
being packed into.
OrigUtf8String - Zero terminated Utf8 string to be converted.
IgnoreDots - TRUE if .'s are to be treated as any other character.
Utf8String - Address of pointer to buffer to copy counted Utf8 string as described above.
Return a pointer to the byte immediately beyond the copied string.
Utf8StringSize - On input, specifies the size of the Utf8String buffer.
Returns the size (in bytes) of the space remaining in the buffer.
CompressCount - Specifies the number of strings that are candidates for
compressing the input string.
Upon successful completion, this count is incremented by one and
the newly packed string
CompressOffset - Array of CompressCount offsets that represent the offset
of the compression string. This offset will be returned at the end of
Utf8String if the string can indeed be compressed.
This offset is in host-order and should not include any
NL_DNS_COMPRESS_WORD_MASK.
CompressUtf8String - Array of CompressCount strings that are already packed
in the current message.
Return Value:
NO_ERROR - String was coverted successfully.
ERROR_INVALID_DOMAINNAME - The passed in unicode string contains one
or more labels longer than 63 bytes (in UTF-8) or short than 1 byte.
ERROR_INSUFFICIENT_BUFFER - The resultant UTF-8 string was longer than
255 bytes.
--*/
{
NET_API_STATUS NetStatus;
ULONG CharCount;
char *Period;
char *Current;
LPBYTE *AllocatedLabelPointer = NULL;
LPBYTE *LabelPointer;
ULONG LabelCount = 0;
LPBYTE *CompressLabelPointer;
ULONG CompressLabelCount;
ULONG Index;
//
// Default to an empty string.
//
if ( !ARGUMENT_PRESENT(OrigUtf8String) || *OrigUtf8String == '\0' ) {
if ( *Utf8StringSize < 1 ) {
return ERROR_INSUFFICIENT_BUFFER;
}
**Utf8String = '\0';
*Utf8StringSize -= 1;
*Utf8String += 1;
return NO_ERROR;
}
//
// Copy the zero terminated utf8 string to the buffer.
// (Leave room for the initial character count.)
//
CharCount = strlen( OrigUtf8String ) + 1;
if ( (*Utf8StringSize) < CharCount + 1 ) {
return ERROR_INSUFFICIENT_BUFFER;
}
RtlCopyMemory( (*Utf8String)+1, OrigUtf8String, CharCount );
//
// Allocate a temporary array to keep track of the compression.
// (At most every second character can be a .)
// (Allocate two arrays with a single call to LocalAlloc.)
//
AllocatedLabelPointer =
LocalAlloc( 0, sizeof(LPBYTE) * (CharCount / 2) +
sizeof(LPBYTE) * (NL_MAX_DNS_LENGTH/2) );
if ( AllocatedLabelPointer == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
LabelPointer = AllocatedLabelPointer;
CompressLabelPointer = &AllocatedLabelPointer[CharCount/2];
//
// Convert the string to a counted string.
// Simply replace '.'s with character counts
//
Current = (*Utf8String)+1;
while ( *Current != '\0' ) {
ULONG LabelSize;
//
// Find the end of the current label.
// use strchr not lstrchr to avoid DBCS semantics.
//
Period = strchr( Current, '.' );
//
// Special case ignoring dots.
//
// We can't totally ignore dots since we want to take advantage of
// RFC 1035 compression. But we have to overcome syntax limitations
// imposed by the compression.
//
if ( IgnoreDots ) {
//
// When ignoring dots, two adjacent dots are legal.
// But they confuse RFC 1035 compression. So, put the second dot
// into the following label
//
if ( Period == Current ) {
Period = strchr( Current+1, '.' );
}
//
// If the last character is a dot,
// include it in the last label.
//
if ( Period != NULL && *(Period+1) == '\0' ) {
Period++;
}
}
if ( Period == NULL ) {
Period = strchr( Current, '\0' );
}
//
// Compute the length of the label.
//
LabelSize = (ULONG)(Period - Current);
if ( LabelSize > NL_MAX_DNS_LABEL_LENGTH || LabelSize < 1 ) {
//
// Enforce this even for IgnoreDots. This is a restriction of
// RFC 1035 compression.
//
NetStatus = ERROR_INVALID_DOMAINNAME;
goto Cleanup;
}
//
// Save a pointer to the current label;
//
LabelPointer[LabelCount] = Current - 1;
LabelCount ++;
//
// Save the size of the current label and move to the next label.
//
*(Current-1) = (char) LabelSize;
Current += LabelSize;
if ( *Current == '\0' ) {
break;
}
if ( *Current == '.' && *(Current+1) == '\0' ) {
// And ignore trailing .'s
*Current = '\0';
CharCount --;
break;
}
Current += 1;
}
LabelPointer[LabelCount] = Current;
NlAssert( ((ULONG)(Current - (*Utf8String))) == CharCount );
//
// Loop through the compression strings seeing if we can compress this string.
//
if ( CompressCount != NULL ) {
for ( Index=0; Index<*CompressCount; Index++ ) {
LPBYTE CurrentCompressString = CompressUtf8String[Index];
LONG LabelIndex;
LONG CompressLabelIndex;
//
// If we're already compressed as much as we can be,
// exit.
//
if ( LabelCount == 0 ) {
break;
}
//
// Compute label pointers for the next compress string.
//
Current = CurrentCompressString;
CompressLabelCount = 0;
while ( *Current != '\0' &&
((*Current) & NL_DNS_COMPRESS_BYTE_MASK) != NL_DNS_COMPRESS_BYTE_MASK ) {
CompressLabelPointer[CompressLabelCount] = Current;
CompressLabelCount++;
Current += *Current + 1;
}
CompressLabelPointer[CompressLabelCount] = Current;
//
// Skip this string if there are no labels
//
if ( CompressLabelCount == 0 ) {
continue;
}
//
// Skip this string if it is compressed to a different degree than
// we are now.
//
// If we compress with this string, upon decompressesion we'll
// append to our string anything that is appended to this string.
// So, we have to make sure the postfixes match.
//
if ( *CompressLabelPointer[CompressLabelCount] != *LabelPointer[LabelCount] ) {
continue;
}
// Compare both bytes if there really was compression.
if ( ((*LabelPointer[LabelCount]) & NL_DNS_COMPRESS_BYTE_MASK) == NL_DNS_COMPRESS_BYTE_MASK &&
*(CompressLabelPointer[CompressLabelCount]+1) != *(LabelPointer[LabelCount]+1) ) {
continue;
}
//
// Walk backward through the labels comparing them.
// While they continue to match,
// keep lobbing bytes off the end of our return string.
//
LabelIndex = LabelCount-1;
CompressLabelIndex = CompressLabelCount-1;
while ( LabelIndex >= 0 &&
CompressLabelIndex >= 0 &&
NlpCompareUtf8( LabelPointer[LabelIndex]+1,
*(LabelPointer[LabelIndex]),
CompressLabelPointer[CompressLabelIndex]+1,
*(CompressLabelPointer[CompressLabelIndex]) )) {
//
// Put the offset onto the end of the current buffer.
//
SmbPutUshort( LabelPointer[LabelIndex],
htons((WORD)(NL_DNS_COMPRESS_WORD_MASK |
(CompressOffset[Index] +
CompressLabelPointer[CompressLabelIndex] -
CurrentCompressString))) );
//
// Adjust the total number of bytes returned.
//
CharCount = (ULONG)(LabelPointer[LabelIndex] - (*Utf8String)) + sizeof(WORD) - 1;
//
// Indicate we've ditched yet another label from the string.
//
LabelCount --;
//
// Adjust Index to next label.
//
LabelIndex --;
CompressLabelIndex --;
}
}
//
// Save a pointer to this string so the next caller can compress
// into it.
//
CompressUtf8String[*CompressCount] = *Utf8String;
CompressOffset[*CompressCount] = (USHORT)((*Utf8String) - MessageBuffer);
*CompressCount += 1;
}
//
// Return the character count.
// (Include the leading label length byte.)
*Utf8StringSize -= CharCount+1;
*Utf8String += CharCount+1;
NetStatus = NO_ERROR;
//
// Done
//
Cleanup:
if ( AllocatedLabelPointer != NULL ) {
LocalFree( AllocatedLabelPointer );
}
return NetStatus;
}
BOOL
NlEqualDnsNameU(
IN PUNICODE_STRING Name1,
IN PUNICODE_STRING Name2
)
/*++
Routine Description:
This routine compares two DNS names for equality.
Case is ignored. A single trailing . is ignored.
Null is compared equal to a zero length string.
Arguments:
Name1 - First DNS name to compare
Name2 - Second DNS name to compare
Return Value:
TRUE: DNS names are equal.
--*/
{
BOOL Result = FALSE;
LPWSTR String1 = NULL;
LPWSTR String2 = NULL;
//
// Sanity check
//
if ( Name1 == NULL ) {
return (Name2 == NULL);
} else if ( Name2 == NULL ) {
return FALSE;
}
//
// Do the work
//
String1 = LocalAlloc( 0, Name1->Length + sizeof(WCHAR) );
if ( String1 == NULL ) {
goto Cleanup;
}
String2 = LocalAlloc( 0, Name2->Length + sizeof(WCHAR) );
if ( String2 == NULL ) {
goto Cleanup;
}
RtlCopyMemory( String1, Name1->Buffer, Name1->Length );
String1[ Name1->Length/sizeof(WCHAR) ] = L'\0';
RtlCopyMemory( String2, Name2->Buffer, Name2->Length );
String2[ Name2->Length/sizeof(WCHAR) ] = L'\0';
Result = NlEqualDnsName( (LPCWSTR) String1, (LPCWSTR) String2 );
Cleanup:
if ( String1 != NULL ) {
LocalFree( String1 );
}
if ( String2 != NULL ) {
LocalFree( String2 );
}
return Result;
}
BOOL
NlEqualDnsName(
IN LPCWSTR Name1,
IN LPCWSTR Name2
)
/*++
Routine Description:
This routine compares two DNS names for equality.
Case is ignored. A single trailing . is ignored.
Null is compared equal to a zero length string.
Arguments:
Name1 - First DNS name to compare
Name2 - Second DNS name to compare
Return Value:
TRUE: DNS names are equal.
--*/
{
if ( Name1 == NULL ) {
return (Name2 == NULL);
} else if ( Name2 == NULL ) {
return FALSE;
}
return DnsNameCompare_W( (LPWSTR) Name1, (LPWSTR) Name2 );
}
BOOL
NlEqualDnsNameUtf8(
IN LPCSTR Name1,
IN LPCSTR Name2
)
/*++
Routine Description:
This routine compares two DNS names for equality.
Case is ignored. A single trailing . is ignored.
Null is compared equal to a zero length string.
Arguments:
Name1 - First DNS name to compare
Name2 - Second DNS name to compare
Return Value:
TRUE: DNS names are equal.
--*/
{
if ( Name1 == NULL ) {
return (Name2 == NULL);
} else if ( Name2 == NULL ) {
return FALSE;
}
return DnsNameCompare_UTF8( (LPSTR)Name1, (LPSTR)Name2 );
}
BOOL
NetpDcValidDnsDomain(
IN LPCWSTR DnsDomainName
)
/*++
Routine Description:
Returns whether the specified string is a valid DNS Domain name.
Arguments:
DnsDomainName - DNS domain name to validate.
Return Value:
TRUE - The specified name is syntactically a DNS Domain name.
FALSE - The specified name in not syntactically a DNS Domain name.
--*/
{
DNS_STATUS DnsStatus;
DnsStatus = DnsValidateDnsName_W( DnsDomainName );
if ( DnsStatus == ERROR_SUCCESS ||
DnsStatus == DNS_ERROR_NON_RFC_NAME ) {
return TRUE;
}
return FALSE;
}
ULONG
NetpDcElapsedTime(
IN ULONG StartTime
)
/*++
Routine Description:
Returns the time (in milliseconds) that has elapsed is StartTime.
Arguments:
StartTime - A time stamp from GetTickCount()
Return Value:
Returns the time (in milliseconds) that has elapsed is StartTime.
--*/
{
ULONG CurrentTime;
//
// If time has has wrapped,
// account for it.
//
CurrentTime = GetTickCount();
if ( CurrentTime >= StartTime ) {
return CurrentTime - StartTime;
} else {
return (0xFFFFFFFF-StartTime) + CurrentTime;
}
}
BOOL
NetpLogonGetCutf8String(
IN PVOID Message,
IN DWORD MessageSize,
IN OUT PCHAR *Where,
OUT LPSTR *Data
)
/*++
Routine Description:
Get a counted UTF-8 string (potentially compressed) from a message.
Return the uncompressed string as a . seperated zero-terminated string.
A trailing . is returned on the name since all packed strings are assumed
to be absolute names.
Arguments:
Message - Points to a buffer containing the message.
MessageSize - The number of bytes in the message buffer.
Where - Indirectly points to the current location in the buffer. The
data at the current location is validated (i.e., checked to ensure
its length is within the bounds of the message buffer and not too
long). If the data is valid, this current location is updated
to point to the byte following the data in the message buffer.
Data - Points to a location to return the DNS name.
A zero length string is returned as a NULL buffer.
The buffer returned should be freed via NetpMemoryFree.
Return Value:
TRUE - the data is valid.
FALSE - the data is invalid (e.g., DataSize is too big for the buffer.
--*/
{
CHAR DnsName[NL_MAX_DNS_LENGTH+1];
ULONG DnsNameLength = 0;
ULONG InitialOffset;
BYTE LabelSize;
LPBYTE LocalWhere;
BYTE PointerBytes[2];
WORD Pointer;
BOOLEAN WhereUpdated = FALSE;
BOOLEAN FirstLabel = TRUE;
LocalWhere = *Where;
InitialOffset = (ULONG)(*Where - ((LPBYTE)Message));
//
// Loop getting counted strings from the message.
//
for (;;) {
//
// Get the length of the current label from the buffer.
//
if ( !NetpLogonGetBytes( Message, MessageSize, &LocalWhere, 1, &LabelSize ) ) {
NlPrint(( NL_CRITICAL, "NetpLogonGetCutf8String: Can't get label size.\n" ));
return FALSE;
}
//
// If this is the end of the string,
// process it.
//
if ( LabelSize == 0 ) {
//
// If this is, then we've not updated the callers 'Where',
// do it now.
//
if ( !WhereUpdated ) {
WhereUpdated = TRUE;
*Where = LocalWhere;
}
//
// If the string is empty,
// return the empty string to the caller.
//
if ( DnsNameLength == 0 ) {
*Data = NULL;
return TRUE;
}
//
// Copy the DNS name to an allocated buffer.
//
DnsName[DnsNameLength] = '\0';
DnsNameLength++;
*Data = NetpMemoryAllocate( DnsNameLength );
if ( *Data == NULL ) {
NlPrint(( NL_CRITICAL, "NetpLogonGetCutf8String: Can't allocate buffer.\n" ));
return FALSE;
}
RtlCopyMemory( *Data, DnsName, DnsNameLength );
return TRUE;
//
// If this is a pointer,
// get rest of pointer.
//
} else if ( LabelSize & NL_DNS_COMPRESS_BYTE_MASK ) {
//
// Get the second byte of the pointer.
//
if ( !NetpLogonGetBytes( Message, MessageSize, &LocalWhere, 1, &PointerBytes[1] ) ) {
NlPrint(( NL_CRITICAL, "NetpLogonGetCutf8String: Can't get pointer byte.\n" ));
return FALSE;
}
//
// Convert the pointer to host order.
//
PointerBytes[0] = LabelSize;
Pointer = ntohs( *((LPWORD)PointerBytes) ) & ~NL_DNS_COMPRESS_WORD_MASK;
//
// Ensure the pointer points to before the beginning of this string.
// This ensures we terminate.
//
if ( Pointer >= InitialOffset ) {
NlPrint(( NL_CRITICAL,
"NetpLogonGetCutf8String: Pointer offset too large 0x%lx 0x%lx.\n",
Pointer,
InitialOffset ));
return FALSE;
}
//
// If we've not updated the callers 'Where',
// do it now.
//
if ( !WhereUpdated ) {
WhereUpdated = TRUE;
*Where = LocalWhere;
}
//
// Prepare the start processing the pointed to string.
//
InitialOffset = Pointer;
LocalWhere = ((LPBYTE)Message) + Pointer;
//
// If this is simply a counted label,
// process it.
//
} else {
//
// If this isn't the first label,
// add a '.' after the previous label.
//
if ( !FirstLabel ) {
DnsName[DnsNameLength] = '.';
DnsNameLength++;
} else {
FirstLabel = FALSE;
}
//
// Ensure the current label fits in the local buffer.
//
if ( DnsNameLength + LabelSize + 2 >= sizeof(DnsName) ) {
NlPrint(( NL_CRITICAL,
"NetpLogonGetCutf8String: Label to long %ld %ld.\n",
DnsNameLength,
LabelSize ));
return FALSE;
}
//
// Copy the label into the local buffer.
// (Leave an extra byte for a trailing '\0' and '.')
//
if ( !NetpLogonGetBytes(
Message,
MessageSize,
&LocalWhere,
LabelSize,
&DnsName[DnsNameLength] )) {
NlPrint(( NL_CRITICAL, "NetpLogonGetCutf8String: Can't get label.\n" ));
return FALSE;
}
DnsNameLength += LabelSize;
}
}
}
NET_API_STATUS
NetpDcBuildPing(
IN BOOL PdcOnly,
IN ULONG RequestCount,
IN LPCWSTR UnicodeComputerName,
IN LPCWSTR UnicodeUserName OPTIONAL,
IN LPCSTR ResponseMailslotName,
IN ULONG AllowableAccountControlBits,
IN PSID RequestedDomainSid OPTIONAL,
IN ULONG NtVersion,
OUT PVOID *Message,
OUT PULONG MessageSize
)
/*++
Routine Description:
Build the message to ping a DC to see if it exists.
Arguments:
PdcOnly - True if only the PDC should respond.
RequestCount - Retry count of this operation.
UnicodeComputerName - Netbios computer name of the machine to respond to.
UnicodeUserName - Account name of the user being pinged.
If NULL, DC will always respond affirmatively.
ResponseMailslotName - Name of the mailslot DC is to respond to.
AllowableAccountControlBits - Mask of allowable account types for UnicodeUserName.
RequestedDomainSid - Sid of the domain the message is destined to.
NtVersion - Version of the message.
0: For backward compatibility.
NETLOGON_NT_VERSION_5: for NT 5.0 message.
NETLOGON_NT_VERSION_5EX: for extended NT 5.0 message
Message - Returns the message to be sent to the DC in question.
Buffer must be free using NetpMemoryFree().
MessageSize - Returns the size (in bytes) of the returned message
Return Value:
NO_ERROR - Operation completed successfully;
ERROR_NOT_ENOUGH_MEMORY - The message could not be allocated.
--*/
{
NET_API_STATUS NetStatus;
LPSTR Where;
PNETLOGON_SAM_LOGON_REQUEST SamLogonRequest = NULL;
LPSTR OemComputerName = NULL;
//
// If only the PDC should respond,
// build a primary query packet.
//
if ( PdcOnly ) {
PNETLOGON_LOGON_QUERY LogonQuery;
//
// Allocate memory for the primary query message.
//
SamLogonRequest = NetpMemoryAllocate( sizeof(NETLOGON_LOGON_QUERY) );
if( SamLogonRequest == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
LogonQuery = (PNETLOGON_LOGON_QUERY)SamLogonRequest;
//
// Translate to get an Oem computer name.
//
#ifndef WIN32_CHICAGO
OemComputerName = NetpLogonUnicodeToOem( (LPWSTR)UnicodeComputerName );
#else
OemComputerName = MyNetpLogonUnicodeToOem( (LPWSTR)UnicodeComputerName );
#endif
if ( OemComputerName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Build the query message.
//
LogonQuery->Opcode = LOGON_PRIMARY_QUERY;
Where = LogonQuery->ComputerName;
NetpLogonPutOemString(
OemComputerName,
sizeof(LogonQuery->ComputerName),
&Where );
NetpLogonPutOemString(
(LPSTR) ResponseMailslotName,
sizeof(LogonQuery->MailslotName),
&Where );
NetpLogonPutUnicodeString(
(LPWSTR) UnicodeComputerName,
sizeof( LogonQuery->UnicodeComputerName ),
&Where );
// Join common code to add NT 5 specific data.
//
// If any DC can respond,
// build a logon query packet.
//
} else {
ULONG DomainSidSize;
//
// Allocate memory for the logon request message.
//
#ifndef WIN32_CHICAGO
if ( RequestedDomainSid != NULL ) {
DomainSidSize = RtlLengthSid( RequestedDomainSid );
} else {
DomainSidSize = 0;
}
#else // WIN32_CHICAGO
DomainSidSize = 0;
#endif // WIN32_CHICAGO
SamLogonRequest = NetpMemoryAllocate(
sizeof(NETLOGON_SAM_LOGON_REQUEST) +
DomainSidSize +
sizeof(DWORD) // for SID alignment on 4 byte boundary
);
if( SamLogonRequest == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Build the query message.
//
SamLogonRequest->Opcode = LOGON_SAM_LOGON_REQUEST;
SamLogonRequest->RequestCount = (WORD) RequestCount;
Where = (PCHAR) &SamLogonRequest->UnicodeComputerName;
NetpLogonPutUnicodeString(
(LPWSTR) UnicodeComputerName,
sizeof(SamLogonRequest->UnicodeComputerName),
&Where );
NetpLogonPutUnicodeString(
(LPWSTR) UnicodeUserName,
sizeof(SamLogonRequest->UnicodeUserName),
&Where );
NetpLogonPutOemString(
(LPSTR) ResponseMailslotName,
sizeof(SamLogonRequest->MailslotName),
&Where );
NetpLogonPutBytes(
&AllowableAccountControlBits,
sizeof(SamLogonRequest->AllowableAccountControlBits),
&Where );
//
// Place domain SID in the message.
//
NetpLogonPutBytes( &DomainSidSize, sizeof(DomainSidSize), &Where );
NetpLogonPutDomainSID( RequestedDomainSid, DomainSidSize, &Where );
}
NetpLogonPutNtToken( &Where, NtVersion );
//
// Return the message to the caller.
//
*Message = SamLogonRequest;
*MessageSize = (ULONG)(Where - (PCHAR)SamLogonRequest);
SamLogonRequest = NULL;
NetStatus = NO_ERROR;
//
// Free locally used resources.
//
Cleanup:
if ( OemComputerName != NULL ) {
NetpMemoryFree( OemComputerName );
}
if ( SamLogonRequest != NULL ) {
NetpMemoryFree( SamLogonRequest );
}
return NetStatus;
}
NET_API_STATUS
NetpDcPackFilterBinary(
IN LPCSTR Name,
IN LPBYTE Buffer,
IN ULONG BufferSize,
IN LPSTR *FilterBuffer,
IN PULONG FilterSize
)
/*++
Routine Description:
Pack a binary blob into an LDAP filter.
Arguments:
Name - Name of the string.
Buffer - Pointer to bytes to pack pack.
If NULL, this routine successfully returns after doing nothing.
BufferSize - Number of bytes in Buffer.
FilterBuffer - Specifies a pointer to the address of the buffer.
This buffer is reallocated as needed to extend the string.
If the buffer does not exist, it is allocated.
Buffer must be free using NetpMemoryFree().
FilterSize - Specifies/Returns the length of FilterBuffer.
Return Value:
NO_ERROR - Operation completed successfully;
ERROR_NOT_ENOUGH_MEMORY - The message could not be allocated.
--*/
{
NET_API_STATUS NetStatus;
ULONG NewSize;
LPSTR NewBuffer;
ULONG NameSize;
LPBYTE Where;
LPSTR FilterElement = NULL;
ULONG FilterElementSize;
#define LDAP_BINARY_EQUAL "="
#define LDAP_BINARY_EQUAL_SIZE (sizeof(LDAP_BINARY_EQUAL)-1)
#define LDAP_BINARY_TEMP_SIZE 1024
//
// If there's nothing to pack,
// Pack nothing.
//
if ( Buffer == NULL || BufferSize == 0 ) {
return NO_ERROR;
}
//
// Allocate a buffer for storage local to this procedure.
// (Don't put in on the stack since we don't want to commit a huge stack.)
//
FilterElement = LocalAlloc( 0, LDAP_BINARY_TEMP_SIZE );
if ( FilterElement == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Build an escaped version of the buffer.
//
NetStatus = ldap_escape_filter_elementA (
Buffer,
BufferSize,
FilterElement,
LDAP_BINARY_TEMP_SIZE );
if ( NetStatus != NO_ERROR ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Compute the size of the new buffer.
//
if ( *FilterBuffer == NULL ) {
*FilterSize = 4; // (&)\0
}
NameSize = strlen( Name );
FilterElementSize = strlen( FilterElement );
NewSize = *FilterSize +
1 + // (
NameSize +
LDAP_BINARY_EQUAL_SIZE +
FilterElementSize +
1; // )
//
// Allocate a new buffer
//
NewBuffer = NetpMemoryAllocate( NewSize );
if ( NewBuffer == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Copy the existing buffer into the newly allocated space.
// (Initialize the buffer if this is the first allocation).
//
if ( *FilterBuffer == NULL ) {
strcpy( NewBuffer, "(&" );
} else {
RtlCopyMemory( NewBuffer, *FilterBuffer, *FilterSize );
NetpMemoryFree( *FilterBuffer );
*FilterBuffer = NULL;
}
//
// Append the new information
//
Where = NewBuffer + *FilterSize - 2;
strcpy( Where, "(");
Where ++;
RtlCopyMemory( Where, Name, NameSize );
Where += NameSize;
RtlCopyMemory( Where, LDAP_BINARY_EQUAL, LDAP_BINARY_EQUAL_SIZE );
Where += LDAP_BINARY_EQUAL_SIZE;
RtlCopyMemory( Where, FilterElement, FilterElementSize );
Where += FilterElementSize;
strcpy( Where, "))");
Where += 2;
//
// Tell the caller about the new filter.
//
*FilterBuffer = NewBuffer;
*FilterSize = NewSize;
NetStatus = NO_ERROR;
//
// Free locally used resources.
//
Cleanup:
if ( FilterElement != NULL ) {
LocalFree( FilterElement );
}
return NetStatus;
}
NET_API_STATUS
NetpDcPackFilterString(
IN LPCSTR Name,
IN LPCWSTR UnicodeString OPTIONAL,
IN LPSTR *FilterBuffer,
IN PULONG FilterSize
)
/*++
Routine Description:
Pack a Unicode String into the LDAP filter.
The actual packed string is the UTF-8 representation since that takes
less space on the wire.
Arguments:
Name - Name of the string.
UnicodeString - String to pack.
If NULL, this routine successfully returns after doing nothing.
FilterBuffer - Specifies a pointer to the address of the buffer.
This buffer is reallocated as needed to extend the string.
If the buffer does not exist, it is allocated.
Buffer must be free using NetpMemoryFree().
FilterSize - Specifies/Returns the length of FilterBuffer.
Return Value:
NO_ERROR - Operation completed successfully;
ERROR_NOT_ENOUGH_MEMORY - The message could not be allocated.
--*/
{
NET_API_STATUS NetStatus;
LPSTR Utf8String = NULL;
ULONG Utf8StringSize;
//
// If there's nothing to pack,
// Pack nothing.
//
if ( UnicodeString == NULL || *UnicodeString == L'\0') {
return NO_ERROR;
}
//
// Convert to utf8.
//
Utf8String = NetpAllocUtf8StrFromWStr( UnicodeString );
if ( Utf8String == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
Utf8StringSize = strlen( Utf8String );
//
// Pack the UTF-8 string as binary.
// LDAP filters have a limited character set (UTF-8 doesn't).
// The LDAP API will put the UTF-8 string on the wire bit-for-bit
// indentical to the Utf8String (even though the filter buffer
// will contain jibberish).
//
NetStatus = NetpDcPackFilterBinary( Name,
Utf8String,
Utf8StringSize,
FilterBuffer,
FilterSize );
//
// Free locally used resources.
//
Cleanup:
if ( Utf8String != NULL ) {
NetpMemoryFree( Utf8String );
}
return NetStatus;
}
NET_API_STATUS
NetpDcBuildLdapFilter(
IN LPCWSTR UnicodeComputerName,
IN LPCWSTR UnicodeUserName OPTIONAL,
IN ULONG AllowableAccountControlBits,
IN PSID RequestedDomainSid OPTIONAL,
IN LPCWSTR RequestedDnsDomainName OPTIONAL,
IN GUID *RequestedDomainGuid OPTIONAL,
IN ULONG NtVersion,
OUT LPSTR *Message
)
/*++
Routine Description:
Build the LDAP filter to ping a DC to see if it exists.
Arguments:
UnicodeComputerName - Netbios computer name of the machine to respond to.
UnicodeUserName - Account name of the user being pinged.
If NULL, DC will always respond affirmatively.
AllowableAccountControlBits - Mask of allowable account types for UnicodeUserName.
RequestedDomainSid - Sid of the domain the message is destined to.
RequestedDnsDomainName - DNS Host Name. Host name of the domain the message
is destined to.
RequestedDomainGuid - Domain GUID of the domain this message is
destined to.
NtVersion - Version of the message.
0: For backward compatibility.
NETLOGON_NT_VERSION_5: for NT 5.0 message.
NETLOGON_NT_VERSION_5EX: for extended NT 5.0 message
Message - Returns the message to be sent to the DC in question.
Buffer must be free using NetpMemoryFree().
Return Value:
NO_ERROR - Operation completed successfully;
ERROR_NOT_ENOUGH_MEMORY - The message could not be allocated.
--*/
{
NET_API_STATUS NetStatus;
LPSTR FilterBuffer = NULL;
ULONG FilterSize = 0;
//
// Pack the text strings into the filter.
//
NetStatus = NetpDcPackFilterString(
NL_FILTER_DNS_DOMAIN_NAME,
RequestedDnsDomainName,
&FilterBuffer,
&FilterSize );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
NetStatus = NetpDcPackFilterString(
NL_FILTER_HOST_NAME,
UnicodeComputerName,
&FilterBuffer,
&FilterSize );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
NetStatus = NetpDcPackFilterString(
NL_FILTER_USER_NAME,
UnicodeUserName,
&FilterBuffer,
&FilterSize );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Pack the binary blobs into the filter
//
if ( AllowableAccountControlBits != 0 ) {
NetStatus = NetpDcPackFilterBinary(
NL_FILTER_ALLOWABLE_ACCOUNT_CONTROL,
(LPBYTE)&AllowableAccountControlBits,
sizeof(AllowableAccountControlBits),
&FilterBuffer,
&FilterSize );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
}
if ( RequestedDomainSid != NULL ) {
NetStatus = NetpDcPackFilterBinary(
NL_FILTER_DOMAIN_SID,
RequestedDomainSid,
#ifndef WIN32_CHICAGO
RtlLengthSid( RequestedDomainSid ),
#else // WIN32_CHICAGO
0,
#endif // WIN32_CHICAGO
&FilterBuffer,
&FilterSize );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
}
if ( RequestedDomainGuid != NULL ) {
NetStatus = NetpDcPackFilterBinary(
NL_FILTER_DOMAIN_GUID,
(LPBYTE)RequestedDomainGuid,
sizeof(GUID),
&FilterBuffer,
&FilterSize );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
}
if ( NtVersion != NETLOGON_NT_VERSION_5 ) {
NetStatus = NetpDcPackFilterBinary(
NL_FILTER_NT_VERSION,
(LPBYTE)&NtVersion,
sizeof(NtVersion),
&FilterBuffer,
&FilterSize );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
}
//
// Return the filter to the caller.
//
NlAssert( FilterBuffer != NULL );
if ( FilterBuffer == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
*Message = FilterBuffer;
NetStatus = NO_ERROR;
//
// Free locally used resources.
//
Cleanup:
if ( NetStatus != NO_ERROR ) {
if ( FilterBuffer != NULL ) {
NetpMemoryFree( FilterBuffer );
}
}
return NetStatus;
}
PNL_DC_CACHE_ENTRY
NetpDcAllocateCacheEntry(
IN LPWSTR ServerName OPTIONAL,
IN LPSTR OemPrimaryDcName OPTIONAL,
IN LPWSTR UserName OPTIONAL,
IN LPWSTR DomainName OPTIONAL,
IN GUID *DomainGuid,
IN LPSTR DnsForestName OPTIONAL,
IN LPSTR DnsDomainName OPTIONAL,
IN LPSTR DnsHostName OPTIONAL,
IN LPSTR Utf8NetbiosDomainName OPTIONAL,
IN LPSTR Utf8NetbiosComputerName OPTIONAL,
IN LPSTR Utf8UserName OPTIONAL,
IN LPSTR Utf8DcSiteName OPTIONAL,
IN LPSTR Utf8ClientSiteName OPTIONAL,
IN ULONG Flags
)
/*++
Routine Description:
Allocate a cache entry and fill it in.
Arguments:
Various fields to fill into an allocated cache entry.
Return Value:
Pointer to a newly allocated cache entry
The cache entry should be freed by calling NetpDcDerefCacheEntry
NULL: The entry could not be allocated.
--*/
{
NET_API_STATUS NetStatus;
ULONG ServerNameSize = 0;
ULONG UserNameSize = 0;
ULONG DomainNameSize = 0;
ULONG DnsForestNameSize = 0;
ULONG DnsDomainNameSize = 0;
ULONG DnsHostNameSize = 0;
ULONG DcSiteNameSize = 0;
ULONG ClientSiteNameSize = 0;
ULONG CacheEntrySize;
PCHAR Where;
PNL_DC_CACHE_ENTRY NlDcCacheEntry;
//
// Determine the size of the cache entry to return.
//
// Sizeof the server name.
if ( Utf8NetbiosComputerName != NULL && Utf8NetbiosComputerName[0] != '\0' ) {
ServerNameSize = (NetpUtf8ToUnicodeLen( Utf8NetbiosComputerName ) + 1) * sizeof(WCHAR);
} else if ( ServerName != NULL && ServerName[0] != '\0') {
ServerNameSize = (wcslen(ServerName) + 1) * sizeof(WCHAR);
} else if ( OemPrimaryDcName != NULL ) {
ServerNameSize = (strlen(OemPrimaryDcName) + 1) * sizeof(WCHAR);
}
// Sizeof the user name.
if ( Utf8UserName != NULL && Utf8UserName[0] != '\0' ) {
UserNameSize = NetpUtf8ToUnicodeLen( Utf8UserName ) * sizeof(WCHAR) + sizeof(WCHAR);
} else if ( UserName != NULL && UserName[0] != '\0') {
UserNameSize = (wcslen(UserName) + 1) * sizeof(WCHAR);
}
// Sizeof the netbios domain name.
if ( Utf8NetbiosDomainName != NULL && Utf8NetbiosDomainName[0] != '\0' ) {
DomainNameSize = NetpUtf8ToUnicodeLen( Utf8NetbiosDomainName ) * sizeof(WCHAR) + sizeof(WCHAR);
} else if ( DomainName != NULL && DomainName[0] != '\0') {
DomainNameSize = (wcslen(DomainName) + 1) * sizeof(WCHAR);
}
// Sizeof the Dns Tree name.
if ( DnsForestName != NULL ) {
DnsForestNameSize = NetpUtf8ToUnicodeLen( DnsForestName ) * sizeof(WCHAR) + sizeof(WCHAR);
}
// Sizeof the Dns Domain name.
if ( DnsDomainName != NULL ) {
DnsDomainNameSize = NetpUtf8ToUnicodeLen( DnsDomainName ) * sizeof(WCHAR) + sizeof(WCHAR);
}
// Sizeof the Dns Host name.
if ( DnsHostName != NULL ) {
DnsHostNameSize = NetpUtf8ToUnicodeLen( DnsHostName ) * sizeof(WCHAR) + sizeof(WCHAR);
}
// Sizeof the Dc Site name.
if ( Utf8DcSiteName != NULL ) {
DcSiteNameSize = NetpUtf8ToUnicodeLen( Utf8DcSiteName ) * sizeof(WCHAR) + sizeof(WCHAR);
}
// Sizeof the Client Site name.
if ( Utf8ClientSiteName != NULL ) {
ClientSiteNameSize = NetpUtf8ToUnicodeLen( Utf8ClientSiteName ) * sizeof(WCHAR) + sizeof(WCHAR);
}
//
// Allocate the buffer to return.
//
CacheEntrySize = sizeof( NL_DC_CACHE_ENTRY ) +
ServerNameSize +
UserNameSize +
DomainNameSize +
DnsForestNameSize +
DnsDomainNameSize +
DnsHostNameSize +
DcSiteNameSize +
ClientSiteNameSize;
NlDcCacheEntry = NetpMemoryAllocate( CacheEntrySize );
if ( NlDcCacheEntry == NULL ) {
NlPrint(( NL_CRITICAL,
"NetpDcAllocateCacheEntry: %ws: not enough memory.\n" ));
return NULL;
}
RtlZeroMemory( NlDcCacheEntry, CacheEntrySize );
Where = (LPBYTE) (NlDcCacheEntry + 1 );
// Local reference.
NlDcCacheEntry->ReferenceCount = 1;
//
// Copy the collected information out to the caller.
//
NlDcCacheEntry->DomainGuid = *DomainGuid;
NlDcCacheEntry->ReturnFlags = Flags & DS_PING_FLAGS;
// Copy the server name (removing any \\)
if ( Utf8NetbiosComputerName != NULL && Utf8NetbiosComputerName[0] != '\0' ) {
NlDcCacheEntry->UnicodeNetbiosDcName = (LPWSTR) Where;
if ( Utf8NetbiosComputerName[0] == '\\' && Utf8NetbiosComputerName[1] == '\\' ) {
NetpCopyUtf8StrToWStr( (LPWSTR)Where, Utf8NetbiosComputerName+2 );
} else {
NetpCopyUtf8StrToWStr( (LPWSTR)Where, Utf8NetbiosComputerName );
}
} else if ( ServerName != NULL && ServerName[0] != '\0') {
NlDcCacheEntry->UnicodeNetbiosDcName = (LPWSTR) Where;
if ( ServerName[0] == L'\\' && ServerName[1] == L'\\' ) {
wcscpy( (LPWSTR)Where, ServerName+2 );
} else {
wcscpy( (LPWSTR)Where, ServerName );
}
} else if ( OemPrimaryDcName != NULL ) {
NlDcCacheEntry->UnicodeNetbiosDcName = (LPWSTR) Where;
if ( OemPrimaryDcName[0] == '\\' && OemPrimaryDcName[1] == '\\') {
NetpCopyStrToWStr( (LPWSTR)Where, OemPrimaryDcName+2 );
} else {
NetpCopyStrToWStr( (LPWSTR)Where, OemPrimaryDcName );
}
}
Where += ServerNameSize;
// Copy the user name,
if ( Utf8UserName != NULL && Utf8UserName[0] != '\0' ) {
NlDcCacheEntry->UnicodeUserName = (LPWSTR) Where;
NetpCopyUtf8StrToWStr( (LPWSTR)Where, Utf8UserName );
} else if ( UserName != NULL && UserName[0] != '\0') {
NlDcCacheEntry->UnicodeUserName = (LPWSTR) Where;
wcscpy( (LPWSTR)Where, UserName );
}
Where += UserNameSize;
// Copy the domain name.
if ( Utf8NetbiosDomainName != NULL && Utf8NetbiosDomainName[0] != '\0' ) {
NlDcCacheEntry->UnicodeNetbiosDomainName = (LPWSTR) Where;
NetpCopyUtf8StrToWStr( (LPWSTR)Where, Utf8NetbiosDomainName );
} else if ( DomainName != NULL && DomainName[0] != '\0') {
NlDcCacheEntry->UnicodeNetbiosDomainName = (LPWSTR) Where;
wcscpy( (LPWSTR)Where, DomainName );
}
Where += DomainNameSize;
// Copy the DnsForestName
if ( DnsForestName != NULL ) {
NlDcCacheEntry->UnicodeDnsForestName = (LPWSTR) Where;
NetpCopyUtf8StrToWStr( (LPWSTR)Where, DnsForestName );
}
Where += DnsForestNameSize;
// Copy the DnsDomainName
if ( DnsDomainName != NULL ) {
NlDcCacheEntry->UnicodeDnsDomainName = (LPWSTR) Where;
NetpCopyUtf8StrToWStr( (LPWSTR)Where, DnsDomainName );
}
Where += DnsDomainNameSize;
// Copy the DnsHostName
if ( DnsHostName != NULL ) {
NlDcCacheEntry->UnicodeDnsHostName = (LPWSTR) Where;
NetpCopyUtf8StrToWStr( (LPWSTR)Where, DnsHostName );
}
Where += DnsHostNameSize;
// Copy the DcSiteName
if ( Utf8DcSiteName != NULL ) {
NlDcCacheEntry->UnicodeDcSiteName = (LPWSTR) Where;
NetpCopyUtf8StrToWStr( (LPWSTR)Where, Utf8DcSiteName );
}
Where += DcSiteNameSize;
// Copy the ClientSiteName
if ( Utf8ClientSiteName != NULL ) {
NlDcCacheEntry->UnicodeClientSiteName = (LPWSTR) Where;
NetpCopyUtf8StrToWStr( (LPWSTR)Where, Utf8ClientSiteName );
}
Where += ClientSiteNameSize;
//
// Save the time when we created the entry
//
NlDcCacheEntry->CreationTime = GetTickCount();
return NlDcCacheEntry;
}
NET_API_STATUS
NetpDcParsePingResponse(
IN LPCWSTR DisplayDomainName,
IN PVOID Message,
IN ULONG MessageSize,
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry
)
/*++
Routine Description:
Parse the response message for a ping.
Arguments:
DisplayDomainName - Domain name to display on debugger if problems occur
Message - The message returned from a DC in question.
MessageSize - Specifies the size (in bytes) of the message
NlDcCacheEntry - On success, returns a pointer to the cache entry
describing the found DC. This entry must be dereferenced using
NetpDcDerefCacheEntry.
Return Value:
NO_ERROR - Operation completed successfully;
ERROR_INVALID_DATA - The message could not be recognized as a valid
response message.
ERROR_NOT_ENOUGH_MEMORY - The message could not be allocated.
--*/
{
NET_API_STATUS NetStatus;
PNETLOGON_SAM_LOGON_RESPONSE_EX SamLogonResponseEx;
PNETLOGON_SAM_LOGON_RESPONSE SamLogonResponse;
PNETLOGON_PRIMARY PrimaryResponse;
DWORD SamLogonResponseSize;
LPWSTR ServerName = NULL;
LPSTR OemPrimaryDcName = NULL;
LPWSTR UserName = NULL;
LPWSTR DomainName = NULL;
GUID DomainGuid = {0};
GUID SiteGuid = {0};
USHORT LocalOpcode;
LPSTR DnsForestName = NULL;
LPSTR DnsDomainName = NULL;
LPSTR DnsHostName = NULL;
LPSTR Utf8NetbiosDomainName = NULL;
LPSTR Utf8NetbiosComputerName = NULL;
LPSTR Utf8UserName = NULL;
LPSTR Utf8DcSiteName = NULL;
LPSTR Utf8ClientSiteName = NULL;
ULONG LocalDcIpAddress;
SOCKET_ADDRESS DcSocketAddress = {0,0};
SOCKADDR_IN DcSockAddrIn;
ULONG Flags;
LPBYTE Where;
DWORD Version;
DWORD VersionFlags;
//
// Initialization.
//
SamLogonResponse = (PNETLOGON_SAM_LOGON_RESPONSE) Message;
SamLogonResponseSize = MessageSize;
*NlDcCacheEntry = NULL;
//
// Get the version of the responder.
//
Version = NetpLogonGetMessageVersion( SamLogonResponse,
&SamLogonResponseSize,
&VersionFlags );
//
// Process the message as a function of the opcode.
//
LocalOpcode = SamLogonResponse->Opcode;
switch ( LocalOpcode ) {
case LOGON_SAM_LOGON_RESPONSE:
case LOGON_SAM_USER_UNKNOWN:
case LOGON_SAM_PAUSE_RESPONSE:
//
// Ensure the version is expected.
//
if ( Version != LMNT_MESSAGE ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Version bad. %ld\n",
DisplayDomainName,
Version ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the Netbios name of the server that responded.
//
Where = (PCHAR) &SamLogonResponse->UnicodeLogonServer;
if ( !NetpLogonGetUnicodeString(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponse->UnicodeLogonServer),
&ServerName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws server name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Ensure this is a UNC name.
//
if ( ServerName[0] != '\0' &&
(ServerName[0] != '\\' || ServerName[1] != '\\' )) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws server name not UNC.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the name of the account the response is for.
//
if ( !NetpLogonGetUnicodeString(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponse->UnicodeUserName ),
&UserName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: user name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the name of the domain the response is from.
//
if ( !NetpLogonGetUnicodeString(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponse->UnicodeDomainName ),
&DomainName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: domain name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the NT 5 specific responses.
//
if ( VersionFlags & NETLOGON_NT_VERSION_5) {
//
// Pick up the GUID of the domain the response is from.
//
if ( !NetpLogonGetGuid(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&DomainGuid ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: domain guid bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the GUID of the site the responding DC is in.
//
if ( !NetpLogonGetGuid(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&SiteGuid ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws site guid bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the DNS domain name of the tree the responder is in.
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&DnsForestName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws DNS forest bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the DNS domain name the responding DC is in.
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&DnsDomainName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: DNS domain bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the DNS host name of the responding DC.
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&DnsHostName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: DNS host bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the IP Address of the responding DC.
//
if ( !NetpLogonGetBytes(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponse->DcIpAddress ),
&LocalDcIpAddress) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: IP Address bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Convert the IP address to a sockaddr
//
// One should find it mildly humorous that on the host we represent the
// IP address in net order and that on the net we represent it in host order.
// I'm chuckling as I write this.
//
if ( LocalDcIpAddress != 0 ) {
DcSockAddrIn.sin_family = AF_INET;
DcSockAddrIn.sin_port = 0;
DcSockAddrIn.sin_addr.S_un.S_addr = htonl(LocalDcIpAddress);
DcSocketAddress.lpSockaddr = (LPSOCKADDR) &DcSockAddrIn;
DcSocketAddress.iSockaddrLength = sizeof(SOCKADDR_IN);
}
//
// Pick up the flags desribing the responding DC.
//
if ( !NetpLogonGetBytes(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponse->Flags ),
&Flags) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Flags bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// If not version 5,
// indicate version 5 specific fields are not present.
//
} else {
RtlZeroMemory( &DomainGuid, sizeof(DomainGuid) );
Flags = 0;
}
break;
case LOGON_SAM_LOGON_RESPONSE_EX:
case LOGON_SAM_USER_UNKNOWN_EX:
case LOGON_SAM_PAUSE_RESPONSE_EX:
//
// Map the opcode for easier use by the client.
//
switch ( LocalOpcode ) {
case LOGON_SAM_LOGON_RESPONSE_EX:
LocalOpcode = LOGON_SAM_LOGON_RESPONSE; break;
case LOGON_SAM_USER_UNKNOWN_EX:
LocalOpcode = LOGON_SAM_USER_UNKNOWN; break;
case LOGON_SAM_PAUSE_RESPONSE_EX:
LocalOpcode = LOGON_SAM_PAUSE_RESPONSE; break;
}
SamLogonResponseEx = (PNETLOGON_SAM_LOGON_RESPONSE_EX) SamLogonResponse;
//
// Ensure the version is expected.
//
if ( Version != LMNT_MESSAGE ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Version bad. %ld\n",
DisplayDomainName,
Version ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the flags desribing the responding DC.
//
Where = (PCHAR) &SamLogonResponseEx->Flags;
if ( !NetpLogonGetBytes(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponseEx->Flags ),
&Flags) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Flags bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the GUID of the domain the response is from.
//
if ( !NetpLogonGetGuid(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&DomainGuid ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: domain guid bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the DNS domain name of the tree the responder is in.
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&DnsForestName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws DNS forest bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the DNS domain name the responding DC is in.
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&DnsDomainName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: DNS domain bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the DNS host name of the responding DC.
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&DnsHostName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: DNS host bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the Netbios domain name
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&Utf8NetbiosDomainName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Netbios Domain name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Ensure the Netbios domain name length is valid
//
if ( Utf8NetbiosDomainName != NULL &&
NetpUtf8ToUnicodeLen(Utf8NetbiosDomainName) > DNLEN ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Netbios Domain name '%s' bad.\n",
DisplayDomainName,
Utf8NetbiosDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the Netbios Computer name
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&Utf8NetbiosComputerName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Netbios Computer name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Ensure the Netbios computer name length is valid
//
if ( Utf8NetbiosComputerName != NULL &&
NetpUtf8ToUnicodeLen(Utf8NetbiosComputerName) > CNLEN ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Netbios Computer name '%s' bad.\n",
DisplayDomainName,
Utf8NetbiosComputerName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the user name
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&Utf8UserName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: User name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the DC site name
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&Utf8DcSiteName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: DC site name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the client site name
//
if ( !NetpLogonGetCutf8String(
SamLogonResponse,
SamLogonResponseSize,
&Where,
&Utf8ClientSiteName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: Client site name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// If this message contains the IP address of the DC,
// grab it.
//
if ( VersionFlags & NETLOGON_NT_VERSION_5EX_WITH_IP ) {
CHAR LocalSockAddrSize;
//
// Grab the size of the SockAddr
//
if ( !NetpLogonGetBytes(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponseEx->DcSockAddrSize ),
&LocalSockAddrSize ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: SockAddr size bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
if ( LocalSockAddrSize > sizeof(DcSockAddrIn) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: SockAddr size too big %ld %ld.\n",
DisplayDomainName,
LocalSockAddrSize,
sizeof(DcSockAddrIn)));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Grab the SockAddr itself.
//
if ( !NetpLogonGetBytes(
SamLogonResponse,
SamLogonResponseSize,
&Where,
LocalSockAddrSize,
&DcSockAddrIn ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: SockAddr size bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Build a SocketAddress to point to the SockAddr
//
DcSocketAddress.lpSockaddr = (LPSOCKADDR) &DcSockAddrIn;
DcSocketAddress.iSockaddrLength = LocalSockAddrSize;
}
break;
//
// Process a response to a primary query.
//
case LOGON_PRIMARY_RESPONSE:
PrimaryResponse = (PNETLOGON_PRIMARY)SamLogonResponse;
Where = PrimaryResponse->PrimaryDCName;
//
// Pick up the Netbios name of the server that responded.
//
if ( !NetpLogonGetOemString(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(PrimaryResponse->PrimaryDCName),
&OemPrimaryDcName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws:OEM server name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// PDC for the specified domain is an NT PDC.
// Get the UNICODE machine name from the message.
//
if ( Version == LMNT_MESSAGE ) {
//
// Pick up the Netbios name of the server that responded.
//
if ( !NetpLogonGetUnicodeString(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(PrimaryResponse->UnicodePrimaryDCName),
&ServerName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: server name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// Pick up the Netbios domain name of the domain the response is from.
//
if ( !NetpLogonGetUnicodeString(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(PrimaryResponse->UnicodeDomainName),
&DomainName ) ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: domain name bad.\n",
DisplayDomainName ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
}
//
// Ensure caller knows this is a PDC.
//
RtlZeroMemory( &DomainGuid, sizeof(DomainGuid) );
Flags = DS_PDC_FLAG | DS_WRITABLE_FLAG;
break;
//
// Unknown response opcode.
//
default:
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: opcode bad. %ld\n",
DisplayDomainName,
LocalOpcode ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// ASSERT: DC has been found.
//
//
// Allocate and initialize a cache entry.
//
*NlDcCacheEntry = NetpDcAllocateCacheEntry(
ServerName,
OemPrimaryDcName,
UserName,
DomainName,
&DomainGuid,
DnsForestName,
DnsDomainName,
DnsHostName,
Utf8NetbiosDomainName,
Utf8NetbiosComputerName,
Utf8UserName,
Utf8DcSiteName,
Utf8ClientSiteName,
Flags );
if ( *NlDcCacheEntry == NULL ) {
NlPrint(( NL_CRITICAL,
"NetpDcParsePingResponse: %ws: not enough memory.\n",
DisplayDomainName ));
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
(*NlDcCacheEntry)->Opcode = LocalOpcode;
(*NlDcCacheEntry)->VersionFlags = VersionFlags;
//
// Fill the DC's SockAddr into the cache entry
//
if ( DcSocketAddress.iSockaddrLength != 0 ) {
NlAssert( DcSocketAddress.iSockaddrLength <= sizeof( (*NlDcCacheEntry)->SockAddrIn) );
RtlCopyMemory( &(*NlDcCacheEntry)->SockAddrIn,
DcSocketAddress.lpSockaddr,
DcSocketAddress.iSockaddrLength );
(*NlDcCacheEntry)->SockAddr.lpSockaddr = (LPSOCKADDR)
&(*NlDcCacheEntry)->SockAddrIn;
(*NlDcCacheEntry)->SockAddr.iSockaddrLength = DcSocketAddress.iSockaddrLength;
}
NetStatus = NO_ERROR;
Cleanup:
//
// On failure,
// delete any strings we may have allocated for return to the caller.
//
if ( NetStatus != NO_ERROR ) {
if ( *NlDcCacheEntry != NULL ) {
NetpMemoryFree( *NlDcCacheEntry );
*NlDcCacheEntry = NULL;
}
}
//
// Delete any buffers allocated locally.
//
if ( DnsForestName != NULL ) {
NetpMemoryFree( DnsForestName );
}
if ( DnsDomainName != NULL ) {
NetpMemoryFree( DnsDomainName );
}
if ( DnsHostName != NULL ) {
NetpMemoryFree( DnsHostName );
}
if ( Utf8NetbiosDomainName != NULL ) {
NetpMemoryFree( Utf8NetbiosDomainName );
}
if ( Utf8NetbiosComputerName != NULL ) {
NetpMemoryFree( Utf8NetbiosComputerName );
}
if ( Utf8UserName != NULL ) {
NetpMemoryFree( Utf8UserName );
}
if ( Utf8DcSiteName != NULL ) {
NetpMemoryFree( Utf8DcSiteName );
}
if ( Utf8ClientSiteName != NULL ) {
NetpMemoryFree( Utf8ClientSiteName );
}
return NetStatus;;
}
NET_API_STATUS
NetpDcFlagsToNameType(
IN ULONG Flags,
OUT PNL_DNS_NAME_TYPE NlDnsNameType
)
/*++
Routine Description:
Given the flags specified to DsGetDcName, return the type of the DNS
name to query to discover that type of DC.
Arguments:
Flags - Passes additional information to be used to process the request.
Flags can be a combination values bitwise or'ed together.
NlDnsNameType - Returns the type of DNS name to query.
Return Value:
NO_ERROR - Operation completed successfully;
ERROR_INVALID_FLAGS - The flags parameter has conflicting bits set.
--*/
{
ULONG LocalFlags;
//
// If more than one of this bits is set,
// that's invalid.
//
LocalFlags = Flags & (DS_KDC_REQUIRED|DS_PDC_REQUIRED|DS_GC_SERVER_REQUIRED);
if ( LocalFlags != 0 && !JUST_ONE_BIT( LocalFlags ) ) {
return ERROR_INVALID_FLAGS;
}
//
// Select the cache entry type based on the requested DC type.
//
if ( Flags & DS_PDC_REQUIRED ) {
*NlDnsNameType = NlDnsPdc;
} else if ( Flags & DS_ONLY_LDAP_NEEDED ) {
if ( Flags & DS_GC_SERVER_REQUIRED ) {
*NlDnsNameType = NlDnsGenericGc;
} else {
*NlDnsNameType = NlDnsLdap;
}
} else if ( Flags & DS_GC_SERVER_REQUIRED ) {
*NlDnsNameType = NlDnsGc;
} else if ( Flags & DS_KDC_REQUIRED ) {
*NlDnsNameType = NlDnsKdc;
} else {
*NlDnsNameType = NlDnsDc;
}
return NO_ERROR;
}
BOOL
NetpAppendUtf8Str(
IN OUT LPSTR To,
IN LPCSTR From,
IN ULONG ResultingStringLengthMax
)
/*++
Routine Description:
This routine appends a UTF8 string to a UTF8 string making sure
that it doesn't write beyond the buffer limit.
Arguments:
To - The string to append to.
From - The string to append.
ResultingStringLengthMax - Maximum allowed length of the resulting string
in bytes not counting the terminating null character.
Return Value:
TRUE: The string is successfully appended.
Otherwise, returns FALSE.
--*/
{
ULONG ToLen;
ULONG FromLen;
if ( To == NULL || From == NULL || ResultingStringLengthMax == 0 ) {
return FALSE;
}
ToLen = strlen(To);
FromLen = strlen(From);
if ( ToLen+FromLen > ResultingStringLengthMax ) {
return FALSE;
}
RtlCopyMemory( &To[ToLen], From, FromLen+1 );
return TRUE;
}
NET_API_STATUS
NetpDcBuildDnsName(
IN NL_DNS_NAME_TYPE NlDnsNameType,
IN GUID *DomainGuid OPTIONAL,
IN LPCWSTR SiteName OPTIONAL,
IN LPCSTR DnsDomainName,
OUT char DnsName[NL_MAX_DNS_LENGTH+1]
)
/*++
Routine Description:
This routine returns the textual DNS name for a particular domain and
name type.
Arguments:
NlDnsNameType - The specific type of name.
DomainGuid - Guid to append to DNS name.
For NlDnsDcByGuid, this is the GUID of the domain being located.
For NlDnsDsaCname, this is the GUID of the DSA being located.
SiteName - Name of the site to append to DNS name.
If NlDnsNameType is any of the *AtSite values,
this is the name of the site the DC is in.
DnsDomainName - Specifies the DNS domain for the name.
For NlDnsDcByGuid or any of the GC names,
this is the DNS domain name of the domain at the root of the tree of
domains.
For all others, this is the DNS domain for the DC.
DnsName - Textual representation of the DNS name.
The returned name is an absolute name (e.g., ends in a .)
Return Value:
NO_ERROR: The name was returned;
ERROR_INVALID_DOMAINNAME: Domain's name is too long. Additional labels
cannot be concatenated.
--*/
{
char *FinalString;
ULONG DnsNameLength;
//
// All SRV record names names are prefixed by ldap.tcp (or kdc.tcp or gc.tcp),
// A records and CNAME records are not.
//
*DnsName = '\0';
if ( NlDnsSrvRecord( NlDnsNameType ) ) {
//
// Output the name of the service.
//
if ( NlDnsNameType == NlDnsGenericGc ||
NlDnsNameType == NlDnsGenericGcAtSite ) {
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_GC_SRV, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
} else if ( NlDnsKpwdRecord( NlDnsNameType )) {
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_KPWD_SRV, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
} else if ( NlDnsKdcRecord( NlDnsNameType ) ) {
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_KDC_SRV, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
} else {
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_LDAP_SRV, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
}
//
// Output the name of the transport.
//
if ( NlDcDnsNameTypeDesc[NlDnsNameType].IsTcp ) {
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_TCP, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
} else {
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_UDP, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
}
}
//
// If this is a site specific name,
// append the site name and the .sites. constant.
//
if ( NlDcDnsNameTypeDesc[NlDnsNameType].IsSiteSpecific ) {
if ( NULL == NetpCreateUtf8StrFromWStr( SiteName,
&DnsName[strlen(DnsName)],
NL_MAX_DNS_LENGTH+1-strlen(DnsName)) ) {
return ERROR_INVALID_DOMAINNAME;
}
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_AT_SITE, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
}
//
// Add the first label (or two) of the DNS name as a function of the name type.
//
switch (NlDnsNameType) {
case NlDnsLdap:
case NlDnsLdapAtSite:
case NlDnsRfc1510Kdc:
case NlDnsRfc1510KdcAtSite:
case NlDnsGenericGc:
case NlDnsGenericGcAtSite:
case NlDnsRfc1510UdpKdc:
case NlDnsRfc1510Kpwd:
case NlDnsRfc1510UdpKpwd:
break;
case NlDnsPdc:
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_PDC, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
break;
case NlDnsGc:
case NlDnsGcAtSite:
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_GC, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
break;
case NlDnsDc:
case NlDnsDcAtSite:
case NlDnsKdc:
case NlDnsKdcAtSite:
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_DC, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
break;
case NlDnsDcByGuid: {
RPC_STATUS RpcStatus;
char *StringGuid;
RpcStatus = UuidToStringA( DomainGuid, &StringGuid );
if ( RpcStatus != RPC_S_OK ) {
NlPrint(( NL_CRITICAL,
"NetpDcBuildDnsName: not enough memory.\n" ));
return ERROR_NOT_ENOUGH_MEMORY;
}
if ( !NetpAppendUtf8Str(DnsName, StringGuid, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
RpcStringFreeA( &StringGuid );
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_DC_BY_GUID, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
break;
}
case NlDnsLdapIpAddress:
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_DC_IP_ADDRESS, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
break;
case NlDnsGcIpAddress:
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_GC_IP_ADDRESS, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
break;
case NlDnsDsaCname:
{
RPC_STATUS RpcStatus;
char *StringGuid;
RpcStatus = UuidToStringA( DomainGuid, &StringGuid );
if ( RpcStatus != RPC_S_OK ) {
NlPrint(( NL_CRITICAL,
"NetpDcBuildDnsName: not enough memory.\n" ));
return ERROR_NOT_ENOUGH_MEMORY;
}
if ( !NetpAppendUtf8Str(DnsName, StringGuid, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
RpcStringFreeA( &StringGuid );
if ( !NetpAppendUtf8Str(DnsName, NL_DNS_DSA_IP_ADDRESS, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
break;
}
default:
return ERROR_INVALID_PARAMETER;
}
//
// Add it to the correct DNS domain.
// Ensuring it isn't too long.
//
if ( !NetpAppendUtf8Str(DnsName, DnsDomainName, NL_MAX_DNS_LENGTH) ) {
return ERROR_INVALID_DOMAINNAME;
}
DnsNameLength = strlen(DnsName);
//
// Ensure it is an absolute name.
//
if ( DnsName[DnsNameLength-1] != '.' ) {
if ( DnsNameLength+1 > NL_MAX_DNS_LENGTH ) {
return ERROR_INVALID_DOMAINNAME;
}
DnsName[DnsNameLength] = '.';
DnsName[DnsNameLength+1] = '\0';
}
return NO_ERROR;
}
VOID
NetpDcDerefCacheEntry(
IN PNL_DC_CACHE_ENTRY NlDcCacheEntry
)
/*++
Routine Description:
Decrement the reference count on a cache entry. If the count reaches zero,
delete the entry.
The count will only reach zero if the entry is already removed from the
global linked list.
Arguments:
NlDcCacheEntry - Cache entry to dereference.
Return Value:
None.
--*/
{
ULONG LocalReferenceCount;
EnterCriticalSection(&NlDcCritSect);
LocalReferenceCount = -- NlDcCacheEntry->ReferenceCount;
LeaveCriticalSection(&NlDcCritSect);
if ( LocalReferenceCount == 0 ) {
NetpMemoryFree(NlDcCacheEntry);
}
}
BOOL
NetpDcMatchResponse(
IN PNL_GETDC_CONTEXT Context,
IN PNL_DC_CACHE_ENTRY NlDcCacheEntry,
IN BOOL BeVerbose,
OUT PBOOL UsedNetbios
)
/*++
Routine Description:
This routine determines if the characteristics specified as input
parameters match the characteristics of the DC requested by the caller.
This routine is used to determine if a received ping response is suitable
to the original caller. This routine is also used to determine if a cache entry
is suitable to the original caller.
Arguments:
Context - Context describing the GetDc operation.
NlDcCacheEntry - Reponse to compare with.
BeVerbose - TRUE if problems are to be logged
UsedNetbios - Returns TRUE if the netbios domain name was used to do the
successful comparison.
Return Value:
TRUE - The parameters describe a suitable DC
--*/
{
BOOLEAN LocalUsedNetbios = FALSE;
//
// Initialization
//
*UsedNetbios = FALSE;
#ifdef notdef
// Only use GUID to be rename safe. Not to prevent discovery of a re-installed
// domain.
//
// Ensure the DomainGuid returned matches the one expected.
//
if ( Context->QueriedDomainGuid != NULL &&
!IsEqualGUID( &NlDcCacheEntry->DomainGuid, &NlDcZeroGuid) &&
!IsEqualGUID( &NlDcCacheEntry->DomainGuid, Context->QueriedDomainGuid) ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: Domain Guid isn't queried Guid\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName ));
}
return FALSE;
}
#endif // notdef
//
// Either the Netbios DC name or DNS DC name must have been returned.
//
if ( NlDcCacheEntry->UnicodeNetbiosDcName == NULL && NlDcCacheEntry->UnicodeDnsHostName == NULL ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: Neither Netbios nor DNS DC name available\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName ));
}
return FALSE;
}
//
// If we ping a DC, check that the responding DC name is the one requested.
//
// Process the special case when we ping a DC and the DC name can be both DNS and Netbios
//
if ( (Context->QueriedInternalFlags & (DS_PING_DNS_HOST|DS_PING_NETBIOS_HOST)) ==
(DS_PING_DNS_HOST|DS_PING_NETBIOS_HOST)) {
BOOL NameMatched = FALSE;
//
// Check if the DNS name matches
//
if ( NlDcCacheEntry->UnicodeDnsHostName != NULL &&
Context->QueriedDcName != NULL &&
NlEqualDnsName(NlDcCacheEntry->UnicodeDnsHostName,
Context->QueriedDcName) ) {
NameMatched = TRUE;
}
//
// If DNS name doesn't match, check if Netbios name does
//
if ( !NameMatched &&
NlDcCacheEntry->UnicodeNetbiosDcName != NULL &&
Context->QueriedDcName != NULL &&
(NlNameCompare(NlDcCacheEntry->UnicodeNetbiosDcName,
(LPWSTR)Context->QueriedDcName,
NAMETYPE_COMPUTER) == 0) ) {
NameMatched = TRUE;
}
//
// If neither name matches, fail
//
if ( !NameMatched ) {
if ( BeVerbose ) {
NlPrint(( NL_CRITICAL,
"NetpDcMatchResponse: Ping response with unmatched host name %ws %ws %ws\n",
Context->QueriedDcName,
NlDcCacheEntry->UnicodeDnsHostName,
NlDcCacheEntry->UnicodeNetbiosDcName ));
}
return FALSE;
}
//
// If the pinged DC name is exactly DNS,
// check that the returned DNS host name is same
//
} else if ( Context->QueriedInternalFlags & DS_PING_DNS_HOST ) {
if ( (NlDcCacheEntry->UnicodeDnsHostName == NULL) ||
(Context->QueriedDcName == NULL) ||
!NlEqualDnsName(NlDcCacheEntry->UnicodeDnsHostName, Context->QueriedDcName) ) {
if ( BeVerbose ) {
NlPrint(( NL_CRITICAL,
"NetpDcMatchResponse: Ping response with unmatched DNS host name %ws %ws\n",
Context->QueriedDcName,
NlDcCacheEntry->UnicodeDnsHostName ));
}
return FALSE;
}
//
// If the pinged DC name is exactly Netbios,
// check that the returned Netbios host name is same
//
} else if ( Context->QueriedInternalFlags & DS_PING_NETBIOS_HOST ) {
if ( (NlDcCacheEntry->UnicodeNetbiosDcName == NULL) ||
(Context->QueriedDcName == NULL) ||
NlNameCompare(NlDcCacheEntry->UnicodeNetbiosDcName,
(LPWSTR)Context->QueriedDcName,
NAMETYPE_COMPUTER) != 0 ) {
if ( BeVerbose ) {
NlPrint(( NL_CRITICAL,
"NetpDcMatchResponse: Ping response with unmatched Netbios host name %ws %ws\n",
Context->QueriedDcName,
NlDcCacheEntry->UnicodeNetbiosDcName ));
}
return FALSE;
}
}
//
// If asking for a GC,
// ensure the Tree name of the responding DC matches the one
// we're asking for.
//
if ( NlDnsGcName( Context->QueriedNlDnsNameType ) ) {
if (NlDcCacheEntry->UnicodeDnsForestName == NULL ||
Context->QueriedDnsDomainName == NULL ||
!NlEqualDnsName( NlDcCacheEntry->UnicodeDnsForestName, Context->QueriedDnsDomainName ) ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: asking for GC and tree name doesn't match request %ws %ws\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
NlDcCacheEntry->UnicodeDnsForestName,
Context->QueriedDnsDomainName ));
}
return FALSE;
}
//
// Ensure the domain name returned matches the one expected.
//
} else {
BOOLEAN NetbiosSame;
BOOLEAN DnsSame;
//
// If neither of the domain names compared,
// the domain names don't match
//
NetbiosSame =
( NlDcCacheEntry->UnicodeNetbiosDomainName != NULL &&
Context->QueriedNetbiosDomainName != NULL &&
NlNameCompare( (LPWSTR)NlDcCacheEntry->UnicodeNetbiosDomainName, (LPWSTR)Context->QueriedNetbiosDomainName, NAMETYPE_DOMAIN ) == 0 );
DnsSame =
NlDcCacheEntry->UnicodeDnsDomainName != NULL &&
Context->QueriedDnsDomainName != NULL &&
NlEqualDnsName( NlDcCacheEntry->UnicodeDnsDomainName, Context->QueriedDnsDomainName );
if ( !NetbiosSame && !DnsSame ) {
//
// Lanman PDC's don't return the domain name.
// (So don't complain about lack of domain name if this is a PDC query.)
//
if ( Context->DcQueryType != NlDcQueryPdc ||
NlDcCacheEntry->UnicodeNetbiosDomainName != NULL ||
NlDcCacheEntry->UnicodeDnsDomainName != NULL ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: Neither Netbios %ws nor DNS %ws domain matches queried name %ws %ws\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
NlDcCacheEntry->UnicodeNetbiosDomainName,
NlDcCacheEntry->UnicodeDnsDomainName,
Context->QueriedNetbiosDomainName,
Context->QueriedDnsDomainName ));
}
//
// Finally check if the domain GUID matches which
// may be the case if the domain has been renamed
//
if ( Context->QueriedDomainGuid != NULL &&
!IsEqualGUID( &NlDcCacheEntry->DomainGuid, &NlDcZeroGuid) &&
IsEqualGUID( &NlDcCacheEntry->DomainGuid, Context->QueriedDomainGuid) ) {
if ( BeVerbose ) {
NlPrint(( NL_CRITICAL,
"NetpDcMatchResponse: %ws: But Domain Guid matches\n",
NlDcCacheEntry->UnicodeNetbiosDcName ));
}
} else {
return FALSE;
}
} else {
// Lanman PDCs always used netbios.
LocalUsedNetbios = TRUE;
}
}
//
// If only the domain name matched,
// tell the caller.
//
if ( NetbiosSame && !DnsSame ) {
LocalUsedNetbios = TRUE;
}
}
//
// Ensure the queried account name is the correct.
//
if ( Context->QueriedAccountName != NULL ) {
//
// If this is an NT 4 PDC responding to a PDC query,
// ignore the fact we queried for an account.
// We can't query both "PDC" and "account" at the same time
// from NT 4.
//
if ( NlDcCacheEntry->Opcode == LOGON_PRIMARY_RESPONSE &&
(NlDcCacheEntry->ReturnFlags & DS_DS_FLAG) == 0 &&
(NlDcCacheEntry->ReturnFlags & DS_PDC_FLAG) != 0 ) {
if ( BeVerbose ) {
NlPrint((NL_SESSION_SETUP,
"NetpDcMatchResponse: %ws: %ws: response for wrong account '%ws' s.b. '%ws' (but message from NT 4 PDC so use it).\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->UnicodeUserName,
Context->QueriedAccountName ));
}
} else if ( NlDcCacheEntry->UnicodeUserName == NULL ||
_wcsicmp( (LPWSTR) Context->QueriedAccountName, NlDcCacheEntry->UnicodeUserName ) != 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: response for wrong account '%ws' s.b. '%ws'\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->UnicodeUserName,
Context->QueriedAccountName ));
}
return FALSE;
}
}
//
// Ensure the responding DC is still playing the correct role.
//
switch ( Context->DcQueryType ) {
case NlDcQueryLdap:
case NlDcQueryGenericDc:
// All DCs are suitable
break;
case NlDcQueryPdc:
if ( (NlDcCacheEntry->ReturnFlags & DS_PDC_FLAG) == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: Responder is not the PDC. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
break;
case NlDcQueryGc:
if ( (NlDcCacheEntry->ReturnFlags & (DS_GC_FLAG|DS_DS_FLAG)) != (DS_GC_FLAG|DS_DS_FLAG) ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: Responder is not a GC server. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
break;
case NlDcQueryGenericGc:
if ( (NlDcCacheEntry->ReturnFlags & DS_GC_FLAG) == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: Responder is not a GC server. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
break;
case NlDcQueryKdc:
// Handle KDCs below.
break;
default:
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: invalid query type 0x%lx 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
Context->DcQueryType,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
//
// If we are not doing an NDNC discovery (i.e. we are discovering a real
// domain DC), disregard a response from an LDAP server servicing this
// name as NDNC
//
if ( NlDnsNonNdncName( Context->QueriedNlDnsNameType ) &&
(NlDcCacheEntry->ReturnFlags & DS_NDNC_FLAG) != 0 ) {
if ( BeVerbose ) {
NlPrint(( NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: response not from real domain DC. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
//
// If we need a DS server, ensure the responding DC is one.
//
if ( (Context->QueriedFlags & DS_DIRECTORY_SERVICE_REQUIRED) &&
(NlDcCacheEntry->ReturnFlags & DS_DS_FLAG) == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: response not from DS server. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
//
// If we need machine running the timeserv, ensure the responding DC is one.
//
if ( (Context->QueriedFlags & (DS_TIMESERV_REQUIRED|DS_GOOD_TIMESERV_PREFERRED)) &&
(NlDcCacheEntry->ReturnFlags & DS_TIMESERV_FLAG) == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: response not from a Time Server. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
//
// If we need machine running that is writable, ensure the responding DC is.
//
if ( (Context->QueriedFlags & DS_WRITABLE_REQUIRED) &&
(NlDcCacheEntry->ReturnFlags & DS_WRITABLE_FLAG) == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: Responder is not a writable server. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
//
// If we need an LDAP server, ensure the responding server is
//
if ( (Context->QueriedFlags & DS_ONLY_LDAP_NEEDED) &&
(NlDcCacheEntry->ReturnFlags & DS_LDAP_FLAG) == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: Responder is not a LDAP server. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
//
// If the caller wants only netbios names,
// ensure one is available.
//
if ( Context->QueriedFlags & DS_RETURN_FLAT_NAME ) {
if ( NlDcCacheEntry->UnicodeNetbiosDcName == NULL || NlDcCacheEntry->UnicodeNetbiosDomainName == NULL ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: Netbios server or domain name needed and missing.\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName ));
}
return FALSE;
}
}
//
// If the caller wants only dns names,
// ensure one is available.
//
if ( Context->QueriedFlags & DS_RETURN_DNS_NAME ) {
if ( NlDcCacheEntry->UnicodeDnsHostName == NULL || NlDcCacheEntry->UnicodeDnsDomainName == NULL ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: Dns server or domain name needed and missing.\n",
Context->QueriedDisplayDomainName ));
}
return FALSE;
}
}
//
// If the caller explicitly specified a sitename,
// ensure the DC is in the specified site.
//
if ( Context->DoingExplicitSite ) {
if ( NlDcCacheEntry->UnicodeDcSiteName == NULL ||
_wcsicmp( NlDcCacheEntry->UnicodeDcSiteName,
Context->QueriedSiteName ) != 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: Responder is in site '%ws' but site '%ws' asked for.\n",
Context->QueriedDisplayDomainName,
NlDcCacheEntry->UnicodeDcSiteName,
Context->QueriedSiteName ));
}
return FALSE;
}
}
//
// If we should ignored responses from ourself,
// do so now.
//
if ( (Context->QueriedFlags & DS_AVOID_SELF) != 0 &&
NlDcCacheEntry->UnicodeNetbiosDcName != NULL ) {
//
// If response is from this computer,
// ignore it.
//
if ( NlNameCompare( NlDcCacheEntry->UnicodeNetbiosDcName,
(LPWSTR)Context->OurNetbiosComputerName,
NAMETYPE_COMPUTER ) == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_SESSION_SETUP,
"NetpDcMatchResponse: %ws: response is from ourself and caller asked for AVOID_SELF.\n",
Context->QueriedDisplayDomainName ));
}
return FALSE;
}
}
//
// If we need machine running the KDC, ensure the responding DC is one.
//
if ( (Context->QueriedFlags & DS_KDC_REQUIRED) &&
(NlDcCacheEntry->ReturnFlags & DS_KDC_FLAG) == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: response not from KDC. 0x%lx\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName,
NlDcCacheEntry->ReturnFlags ));
}
return FALSE;
}
//
// If we need a DC running IP, ensure the responding DC has an IP address.
//
// Do this check after the KDC check. Kerberos always asks for IP_REQUIRED.
// We don't want this check to discard the entry for non-KDCs.
//
if ( (Context->QueriedFlags & DS_IP_REQUIRED) &&
NlDcCacheEntry->SockAddr.iSockaddrLength == 0 ) {
if ( BeVerbose ) {
NlPrint((NL_CRITICAL,
"NetpDcMatchResponse: %ws: %ws: response not from IP transport\n",
NlDcCacheEntry->UnicodeNetbiosDcName,
Context->QueriedDisplayDomainName ));
}
return FALSE;
}
//
// FINAL TEST!!!!
//
// Only do this test if the cache entry meets all of the other criteria.
//
// If we prefer a DS server and this DC is not one,
// just save this entry and continue looking.
// If we find no DS server, we'll use this entry as a last resort.
//
if ( (Context->QueriedFlags & DS_DIRECTORY_SERVICE_PREFERRED) &&
(NlDcCacheEntry->ReturnFlags & DS_DS_FLAG) == 0 ) {
//
// Ditch the previously saved cache entry if the new DC is closer.
//
if ( Context->ImperfectCacheEntry != NULL &&
(Context->ImperfectCacheEntry->ReturnFlags & DS_CLOSEST_FLAG) == 0 &&
(NlDcCacheEntry->ReturnFlags & DS_CLOSEST_FLAG) != 0 ) {
NetpDcDerefCacheEntry( Context->ImperfectCacheEntry );
Context->ImperfectCacheEntry = NULL;
}
//
// Only save the first entry
//
if ( Context->ImperfectCacheEntry == NULL ) {
if ( BeVerbose ) {
NlPrint((NL_SESSION_SETUP,
"NetpDcMatchResponse: %ws: non-DS server responded when DS preferred\n",
Context->QueriedDisplayDomainName ));
}
//
// Reference the entry
//
NlDcCacheEntry->ReferenceCount ++;
Context->ImperfectCacheEntry = NlDcCacheEntry;
Context->ImperfectUsedNetbios = LocalUsedNetbios;
}
//
// Tell the caller that the match failed.
// The caller will use the above stored entry at his discretion.
return FALSE;
}
//
// FINAL TEST!!!!
//
// Only do this test if the cache entry meets all of the other criteria.
//
// If we prefer a "good" time server and this DC is not one,
// just save this entry and continue looking.
// If we find no "good" time server server, we'll use this entry as a last resort.
//
if ( (Context->QueriedFlags & DS_GOOD_TIMESERV_PREFERRED) &&
(NlDcCacheEntry->ReturnFlags & DS_GOOD_TIMESERV_FLAG) == 0 ) {
//
// Ditch the previously saved cache entry if the new DC is closer.
//
if ( Context->ImperfectCacheEntry != NULL &&
(Context->ImperfectCacheEntry->ReturnFlags & DS_CLOSEST_FLAG) == 0 &&
(NlDcCacheEntry->ReturnFlags & DS_CLOSEST_FLAG) != 0 ) {
NetpDcDerefCacheEntry( Context->ImperfectCacheEntry );
Context->ImperfectCacheEntry = NULL;
}
//
// Only save the first entry
//
if ( Context->ImperfectCacheEntry == NULL ) {
if ( BeVerbose ) {
NlPrint((NL_SESSION_SETUP,
"NetpDcMatchResponse: %ws: non-good time server responded when one preferred\n",
Context->QueriedDisplayDomainName ));
}
//
// Reference the entry
//
NlDcCacheEntry->ReferenceCount ++;
Context->ImperfectCacheEntry = NlDcCacheEntry;
Context->ImperfectUsedNetbios = LocalUsedNetbios;
}
//
// Tell the caller that the match failed.
// The caller will use the above stored entry at his discretion.
return FALSE;
}
//
// All tests passed.
//
*UsedNetbios = LocalUsedNetbios;
return TRUE;
}
PNL_DC_CACHE_ENTRY
NetpDcFindCacheEntry(
IN PNL_GETDC_CONTEXT Context,
OUT PBOOL UsedNetbios,
OUT PBOOL ForcePing
)
/*++
Routine Description:
This routine finds a cache entry that matches the caller's query.
Arguments:
Context - Context describing the GetDc operation.
UsedNetbios - Returns TRUE if the netbios domain name was used to do the
successful comparison.
ForcePing - TRUE if the returned cache entry has to be pinged before it is used
Return Value:
On success, returns a pointer to the cache entry describing the found DC.
This entry must be dereferenced using NetpDcDerefCacheEntry.
NULL - no matching cache entry could be found.
--*/
{
PNL_DC_CACHE_ENTRY NlDcCacheEntry;
BOOL LocalUsedNetbios;
LONG QueryType; // Must be a signed number
//
// Check if there is a cache entry for this query type.
//
*ForcePing = FALSE;
EnterCriticalSection(&NlDcCritSect);
NlDcCacheEntry = Context->NlDcDomainEntry->Dc[Context->DcQueryType].NlDcCacheEntry;
if ( NlDcCacheEntry != NULL ) {
//
// Ensure the cache entry matches all the criteria.
//
if ( NetpDcMatchResponse(
Context,
NlDcCacheEntry,
FALSE,
&LocalUsedNetbios) ) {
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Cache entry %ld exists and was used.\n",
Context->NlDcDomainEntry->UnicodeNetbiosDomainName,
Context->NlDcDomainEntry->UnicodeDnsDomainName,
Context->DcQueryType ));
goto Cleanup;
} else {
BOOL Matched;
//
// If the only thing different is the account name,
// don't ditch the cache entry just for that reason.
//
if ( Context->QueriedAccountName != NULL ) {
LPCWSTR QueriedAccountName;
ULONG QueriedAllowableAccountControlBits;
QueriedAccountName = Context->QueriedAccountName;
Context->QueriedAccountName = NULL;
QueriedAllowableAccountControlBits = Context->QueriedAllowableAccountControlBits;
Context->QueriedAllowableAccountControlBits = 0;
Matched = NetpDcMatchResponse(
Context,
NlDcCacheEntry,
FALSE,
&LocalUsedNetbios);
Context->QueriedAccountName = QueriedAccountName;
Context->QueriedAllowableAccountControlBits = QueriedAllowableAccountControlBits;
if ( Matched ) {
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Cache entry %ld exists and was used (but account wrong).\n",
Context->NlDcDomainEntry->UnicodeNetbiosDomainName,
Context->NlDcDomainEntry->UnicodeDnsDomainName,
Context->DcQueryType ));
*ForcePing = TRUE;
goto Cleanup;
}
}
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Cache entry %ld exists and was NOT used.\n",
Context->NlDcDomainEntry->UnicodeNetbiosDomainName,
Context->NlDcDomainEntry->UnicodeDnsDomainName,
Context->DcQueryType ));
}
}
//
// Try to find a less specific cache entry that happens to match the criteria.
//
// For example, if I've previously cached an entry for a generic DC and it
// happens to be a PDC. If I later try to find a PDC, use the one I've already
// found.
//
for ( QueryType = Context->DcQueryType-1; QueryType>=0; QueryType-- ) {
//
// Do not return a GC entry if this is a non-GC (PDC) discovery.
// Accordingly, do not return a non-GC entry if this is a GC discovery.
// We want to ensure this match to return the correct closeness bit.
//
if ( QueryType == NlDcQueryGc || QueryType == NlDcQueryGenericGc ) {
if ( !NlDnsGcName( Context->QueriedNlDnsNameType ) ) {
continue;
}
} else {
if ( NlDnsGcName( Context->QueriedNlDnsNameType ) ) {
continue;
}
}
//
// If the cache entry matches all the criteria,
// use it.
//
NlDcCacheEntry = Context->NlDcDomainEntry->Dc[QueryType].NlDcCacheEntry;
if ( NlDcCacheEntry != NULL &&
NetpDcMatchResponse(
Context,
NlDcCacheEntry,
FALSE,
&LocalUsedNetbios) ) {
//
// I considered saving this cache entry as the preferred cache
// entry for this query type (by copying the pointer and
// incrementing the reference count). That'd ensure I'd
// consistently get this entry for this query type. But it'd
// also mean that I'd get this old entry once the original
// entry had been forced from the cache.
//
// Context->NlDcDomainEntry->Dc[Context->DcQueryType].NlDcCacheEntry = NlDcCacheEntry;
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Cache entry %ld used for %ld query.\n",
Context->NlDcDomainEntry->UnicodeNetbiosDomainName,
Context->NlDcDomainEntry->UnicodeDnsDomainName,
QueryType,
Context->DcQueryType ));
goto Cleanup;
}
}
//
// Entry isn't in the cache.
//
NlDcCacheEntry = NULL;
Cleanup:
if ( NlDcCacheEntry != NULL ) {
//
// Reference this entry.
//
NlDcCacheEntry->ReferenceCount++;
*UsedNetbios = LocalUsedNetbios;
}
LeaveCriticalSection(&NlDcCritSect);
return NlDcCacheEntry;
}
PNL_DC_DOMAIN_ENTRY
NetpDcFindDomainEntry(
IN GUID *DomainGuid OPTIONAL,
IN LPCWSTR NetbiosDomainName OPTIONAL,
IN LPCWSTR DnsDomainName OPTIONAL,
IN PNL_DC_DOMAIN_ENTRY NlDcDomainEntryToAvoid OPTIONAL,
IN BOOL RequireExactMatch
)
/*++
Routine Description:
This routine finds a domain entry that matches the caller's query.
At least one search parameter must be specified.
If the exact match is required by the caller, the routine will
ensure that for every search parameter specified, the returned
domain entry has that parameter set. Otherwise, the routine will
return the best entry that matches the caller's query where the
GUID match will take precedence followed by the DNS domain name
match followed by the Netbios domain name match.
Arguments:
DomainGuid - Specifies the GUID of the domain to find.
NetbiosDomainName - Specifies the Netbios name of the domain to find.
DnsDomainName - Specifies the Dns name of the domain to find.
NlDcDomainEntryToAvoid - Specifies that this domain entry is not
to be returned even if it matches the description.
RequireExactMatch - Specifies whether all parameters specified
must be matched in the returned domain entry.
Return Value:
On success, returns a pointer to the domain cache entry describing a domain.
This entry must be dereference using NetpDcDerefDomainEntry.
NULL - no matching cache entry could be found.
--*/
{
PLIST_ENTRY DomainEntry;
PNL_DC_DOMAIN_ENTRY NlDcDomainEntry = NULL;
PNL_DC_DOMAIN_ENTRY BestEntry = NULL;
ULONG ThisEntryQuality = 0;
ULONG BestEntryQuality = 0;
ULONG BestQualityPossible = 0;
//
// Compute the best quality of the match the caller
// can get given the parameters specified
//
//
// Netbios domain name match is the lowest priority,
// so assign least significant bit to this match
//
if ( NetbiosDomainName != NULL ) {
BestQualityPossible += 1;
}
//
// DNS domain name match is the next in priority,
// so assign the next significant bit to this match
//
if ( DnsDomainName != NULL ) {
BestQualityPossible += 2;
}
//
// Finally, the GUID match is the highest in priority,
// so assign the highest significant bit to this match
//
if ( DomainGuid != NULL ) {
BestQualityPossible += 4;
}
//
// Ensure there is at least one search parameter specified
//
if ( BestQualityPossible == 0 ) {
NlPrint(( NL_CRITICAL, "NetpDcFindDomainEntry: No search parameter is specified\n" ));
return NULL;
}
//
// Loop trying to find the best cache entry matching caller's query.
//
EnterCriticalSection(&NlDcCritSect);
for ( DomainEntry = NlDcDomainList.Flink ;
DomainEntry != &NlDcDomainList;
DomainEntry = DomainEntry->Flink ) {
NlDcDomainEntry = CONTAINING_RECORD( DomainEntry, NL_DC_DOMAIN_ENTRY, Next);
ThisEntryQuality = 0;
//
// If this is the entry we're to avoid, skip it.
//
if ( NlDcDomainEntry == NlDcDomainEntryToAvoid ) {
continue;
}
//
// Check the Netbios domain name match
//
if ( NetbiosDomainName != NULL &&
NlDcDomainEntry->UnicodeNetbiosDomainName[0] != L'\0' &&
NlNameCompare(NlDcDomainEntry->UnicodeNetbiosDomainName,
(LPWSTR)NetbiosDomainName,
NAMETYPE_DOMAIN) == 0 ) {
//
// Netbios domain name match is least important -- set
// the least significant bit in the match quality of this entry
//
ThisEntryQuality += 1;
}
//
// Check the DNS domain name match
//
if ( DnsDomainName != NULL &&
NlDcDomainEntry->UnicodeDnsDomainName != NULL &&
NlEqualDnsName(NlDcDomainEntry->UnicodeDnsDomainName, DnsDomainName) ) {
//
// DNS domain name match is next in importance -- set
// the next significant bit in the match quality of this entry
//
ThisEntryQuality += 2;
}
//
// Check the DomainGuid match
//
if ( DomainGuid != NULL &&
!IsEqualGUID( &NlDcDomainEntry->DomainGuid, &NlDcZeroGuid) &&
IsEqualGUID( &NlDcDomainEntry->DomainGuid, DomainGuid) ) {
//
// GUID match is most important -- set the highest bit in
// the match quality of this entry
//
ThisEntryQuality += 4;
}
//
// Check whether this entry is the best match so far
//
if ( ThisEntryQuality > BestEntryQuality ) {
BestEntryQuality = ThisEntryQuality;
BestEntry = NlDcDomainEntry;
}
//
// If this is as best as it can be, no need to check
// the remaining entries
//
if ( BestEntryQuality == BestQualityPossible ) {
break;
}
}
//
// If the caller requires exact match,
// ensure the best entry we've got is the one
//
if ( RequireExactMatch && BestEntryQuality < BestQualityPossible ) {
BestEntry = NULL;
}
//
// If we've got the entry that satisfies the caller,
// reference it and return it
//
if ( BestEntry != NULL ) {
BestEntry->ReferenceCount ++;
}
LeaveCriticalSection(&NlDcCritSect);
if ( BestEntry != NULL ) {
NlPrint(( NL_DNS_MORE,
"NetpDcFindDomainEntry: %ws %ws: Found%sdomain cache entry with quality %ld/%ld\n",
BestEntry->UnicodeNetbiosDomainName,
BestEntry->UnicodeDnsDomainName,
(RequireExactMatch ? " exact " : " "),
BestEntryQuality,
BestQualityPossible ));
} else {
NlPrint(( NL_DNS_MORE,
"NetpDcFindDomainEntry: %ws %ws: Failed to find%sdomain cache entry with quality %ld/%ld\n",
NetbiosDomainName,
DnsDomainName,
(RequireExactMatch ? " exact " : " "),
BestEntryQuality,
BestQualityPossible ));
}
return BestEntry;
}
VOID
NetpDcDerefDomainEntry(
IN PNL_DC_DOMAIN_ENTRY NlDcDomainEntry
)
/*++
Routine Description:
Decrement the reference count on a cache entry. If the count reaches zero,
delete the entry.
The count will only reach zero if the entry is already removed from the
global linked list.
Arguments:
NlDcDomainEntry - Cache entry to dereference.
Return Value:
None.
--*/
{
ULONG LocalReferenceCount;
EnterCriticalSection(&NlDcCritSect);
LocalReferenceCount = -- NlDcDomainEntry->ReferenceCount;
if ( LocalReferenceCount == 0 ) {
ULONG QueryType;
//
// Remove our reference to all of the cache entries for this domain.
//
for ( QueryType = 0; QueryType < NlDcQueryTypeCount; QueryType ++ ) {
if ( NlDcDomainEntry->Dc[QueryType].NlDcCacheEntry != NULL ) {
NetpDcDerefCacheEntry( NlDcDomainEntry->Dc[QueryType].NlDcCacheEntry );
}
}
//
// Free DnsName
//
if ( NlDcDomainEntry->UnicodeDnsDomainName != NULL ) {
NetpMemoryFree( NlDcDomainEntry->UnicodeDnsDomainName );
}
//
// Free the entry itself.
//
NetpMemoryFree(NlDcDomainEntry);
}
LeaveCriticalSection(&NlDcCritSect);
}
VOID
NetpDcDeleteDomainEntry(
IN PNL_DC_DOMAIN_ENTRY NlDcDomainEntry
)
/*++
Routine Description:
Remove a cache entry from the global list.
Enter with NlDcCritSect locked.
Arguments:
NlDcDomainEntry - Cache entry to remove.
Return Value:
None.
--*/
{
//
// Remove it.
//
RemoveEntryList( &NlDcDomainEntry->Next );
NlDcDomainCount --;
//
// Ensure any current references know it has been deleted.
//
NlDcDomainEntry->DeletedEntry = TRUE;
//
// Decrement the reference indicating it is on the list.
//
NetpDcDerefDomainEntry( NlDcDomainEntry );
}
BOOL
NetpDcUpdateDomainEntry(
IN PNL_DC_DOMAIN_ENTRY NlDcDomainEntry,
IN GUID *DomainGuid OPTIONAL,
IN LPCWSTR NetbiosDomainName OPTIONAL,
IN LPCWSTR DnsDomainName OPTIONAL
)
/*++
Routine Description:
This routine updates the domain entry to contain the passed in domain
name information.
Arguments:
NlDcDomainEntry - Domain entry to update.
DomainGuid - Specifies the GUID of the domain.
NetbiosDomainName - Specifies the Netbios name of the domain.
DnsDomainName - Specifies the Dns name of the domain.
Return Value:
TRUE - all names updated as requested.
FALSE - some names could not be updated.
--*/
{
BOOL NamesChanged = FALSE;
PNL_DC_DOMAIN_ENTRY DuplicateDomainEntry;
//
// If this entry has been deleted,
// don't bother updating it.
//
EnterCriticalSection(&NlDcCritSect);
if ( NlDcDomainEntry->DeletedEntry ) {
LeaveCriticalSection(&NlDcCritSect);
return TRUE;
}
//
// Fill in the Netbios domain name if it is not already filled in.
//
if ( NetbiosDomainName != NULL &&
( NlDcDomainEntry->UnicodeNetbiosDomainName[0] == L'\0' ||
NlNameCompare( NlDcDomainEntry->UnicodeNetbiosDomainName, (LPWSTR)NetbiosDomainName, NAMETYPE_DOMAIN ) != 0 ) ) {
//
// Be safe: ensure the name fits into our buffer
//
if ( wcslen(NetbiosDomainName) > DNLEN ) {
NlPrint(( NL_CRITICAL,
"NetpDcUpdateDomainEntry: Netbios domain name '%ws' too long\n",
NetbiosDomainName ));
LeaveCriticalSection(&NlDcCritSect);
return FALSE;
} else {
wcscpy( NlDcDomainEntry->UnicodeNetbiosDomainName, NetbiosDomainName );
}
NamesChanged = TRUE;
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Set netbios domain name\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName ));
}
//
// Fill in the DNS domain name if it is not already filled in.
//
if ( DnsDomainName != NULL &&
( NlDcDomainEntry->UnicodeDnsDomainName == NULL ||
!NlEqualDnsName( NlDcDomainEntry->UnicodeDnsDomainName, DnsDomainName ) ) ) {
if ( NlDcDomainEntry->UnicodeDnsDomainName != NULL ) {
NetApiBufferFree( NlDcDomainEntry->UnicodeDnsDomainName );
}
NlDcDomainEntry->UnicodeDnsDomainName = NetpAllocWStrFromWStr( (LPWSTR) DnsDomainName );
if ( NlDcDomainEntry->UnicodeDnsDomainName == NULL ) {
LeaveCriticalSection(&NlDcCritSect);
return FALSE;
}
NamesChanged = TRUE;
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Set DNS domain name\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName ));
}
//
// Fill in the GUID if its not already filled in.
//
if ( DomainGuid != NULL &&
IsEqualGUID( &NlDcDomainEntry->DomainGuid, DomainGuid) ) {
NamesChanged = TRUE;
NlDcDomainEntry->DomainGuid = *DomainGuid;
}
//
// If the names have changed,
// perhaps this domain cache entry now duplicates another entry.
//
// Find any duplicate entries and merge them into this one.
//
// Require exact match for the duplicate entries as we want to
// preserve entries which have some parameters different which
// is a case for renamed domain that may have 2 cache entries
// corresponding to the active and alias names.
//
if ( NamesChanged ) {
while ( (DuplicateDomainEntry = NetpDcFindDomainEntry(
&NlDcDomainEntry->DomainGuid,
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName,
NlDcDomainEntry,
TRUE )) != NULL ) {
ULONG QueryType;
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: is now a duplicate of %ws %ws\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName,
DuplicateDomainEntry->UnicodeNetbiosDomainName,
DuplicateDomainEntry->UnicodeDnsDomainName ));
//
// Move any cache entries from the duplicate to the new.
// ?? We could theoretically keep the 'better' of the two entries.
//
for ( QueryType = 0; QueryType < NlDcQueryTypeCount; QueryType ++ ) {
if ( NlDcDomainEntry->Dc[QueryType].NlDcCacheEntry == NULL &&
DuplicateDomainEntry->Dc[QueryType].NlDcCacheEntry != NULL ) {
NlDcDomainEntry->Dc[QueryType].NlDcCacheEntry =
DuplicateDomainEntry->Dc[QueryType].NlDcCacheEntry;
NlFlushNegativeCacheEntry( &NlDcDomainEntry->Dc[QueryType] );
DuplicateDomainEntry->Dc[QueryType].NlDcCacheEntry = NULL;
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: grab entry %ld from %ws %ws\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName,
QueryType,
DuplicateDomainEntry->UnicodeNetbiosDomainName,
DuplicateDomainEntry->UnicodeDnsDomainName ));
}
}
//
// Delete the duplicate entry.
// (There may be an outstanding reference to this entry.)
//
NetpDcDeleteDomainEntry( DuplicateDomainEntry );
// Remove our reference.
NetpDcDerefDomainEntry( DuplicateDomainEntry );
}
}
LeaveCriticalSection(&NlDcCritSect);
return TRUE;
}
VOID
NetpDcInsertCacheEntry(
IN PNL_GETDC_CONTEXT Context,
IN PNL_DC_CACHE_ENTRY NlDcCacheEntry
)
/*++
Routine Description:
This routine inserts a cache entry onto the domain entry.
Arguments:
Context - Context describing the GetDc operation.
NlDcCacheEntry - Cache entry to use.
Return Value:
None.
--*/
{
PNL_DC_CACHE_ENTRY *CacheEntryPtr;
PNL_DC_DOMAIN_ENTRY NlDcDomainEntry = Context->NlDcDomainEntry;
//
// Avoid caching local responses. For local discoveries, we always
// check whether the local DC currently satisfies the query.
// If it doesn't, we may stumble upon an outdated cache entry if
// we cache local responses.
//
if ( NlDcCacheEntry->CacheEntryFlags & NL_DC_CACHE_LOCAL ) {
return;
}
//
// If the caller explicitly asked for a particular site,
// and that site isn't the closest site.
// Avoid polluting the cache with this entry.
//
if ( Context->DoingExplicitSite &&
(NlDcCacheEntry->ReturnFlags & DS_CLOSEST_FLAG) == 0 ) {
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: don't cache this entry since an explicit site '%ws' was asked for.\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName,
Context->QueriedSiteName ));
return;
}
//
// If there is no cache entry for this query type,
// or this cache entry is better than the old one,
// or the new cache entry was found via a 'force' rediscovery,
// or the new cache entry is for the same DC as the old entry,
// use the new cache entry.
//
EnterCriticalSection(&NlDcCritSect);
CacheEntryPtr = &NlDcDomainEntry->Dc[Context->DcQueryType].NlDcCacheEntry;
if ( *CacheEntryPtr == NULL ||
NlDcCacheEntry->DcQuality >= (*CacheEntryPtr)->DcQuality ||
(Context->QueriedFlags & DS_FORCE_REDISCOVERY) != 0 ||
(NlDcCacheEntry->UnicodeNetbiosDcName != NULL &&
(*CacheEntryPtr)->UnicodeNetbiosDcName != NULL &&
NlNameCompare( NlDcCacheEntry->UnicodeNetbiosDcName,
(*CacheEntryPtr)->UnicodeNetbiosDcName,
NAMETYPE_COMPUTER ) == 0 ) ||
(NlDcCacheEntry->UnicodeDnsHostName != NULL &&
(*CacheEntryPtr)->UnicodeDnsHostName != NULL &&
NlEqualDnsName( NlDcCacheEntry->UnicodeDnsHostName,
(*CacheEntryPtr)->UnicodeDnsHostName ) )
) {
//
// Delink any existing cache entry.
//
if ( *CacheEntryPtr != NULL ) {
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Ditch existing cache entry %ld (Quality: %ld)\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName,
Context->DcQueryType,
(*CacheEntryPtr)->DcQuality ));
NetpDcDerefCacheEntry( *CacheEntryPtr );
*CacheEntryPtr = NULL;
}
//
// Link the cache entry onto the domain entry and increment the reference count
// to account for the new reference.
//
*CacheEntryPtr = NlDcCacheEntry;
NlDcCacheEntry->ReferenceCount ++;
//
// Indicate that the cache entry has been inserted
//
NlDcCacheEntry->CacheEntryFlags |= NL_DC_CACHE_ENTRY_INSERTED;
//
// Flush the negative cache.
//
NlFlushNegativeCacheEntry( &NlDcDomainEntry->Dc[Context->DcQueryType] );
//
// Update the domain entry to contain more information about the domain.
//
// If this is a GC discovery entry and the discovered forest name is different
// from the domain name of the GC, update the domain entry using the forest
// name only.
//
if ( NlDnsGcName( Context->QueriedNlDnsNameType ) &&
(NlDcCacheEntry->UnicodeDnsForestName == NULL ||
NlDcCacheEntry->UnicodeDnsDomainName == NULL ||
!NlEqualDnsName( NlDcCacheEntry->UnicodeDnsForestName,
NlDcCacheEntry->UnicodeDnsDomainName )) ) {
(VOID) NetpDcUpdateDomainEntry( NlDcDomainEntry,
NULL,
NULL,
NlDcCacheEntry->UnicodeDnsForestName );
} else {
(VOID) NetpDcUpdateDomainEntry( NlDcDomainEntry,
&NlDcCacheEntry->DomainGuid,
NlDcCacheEntry->UnicodeNetbiosDomainName,
NlDcCacheEntry->UnicodeDnsDomainName );
}
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Add cache entry %ld (Quality: %ld)\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName,
Context->DcQueryType,
NlDcCacheEntry->DcQuality ));
} else {
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Existing cache entry for type %ld is better %ld %ld\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName,
Context->DcQueryType,
NlDcCacheEntry->DcQuality,
(*CacheEntryPtr)->DcQuality ));
}
LeaveCriticalSection(&NlDcCritSect);
return;
}
PNL_DC_DOMAIN_ENTRY
NetpDcCreateDomainEntry(
IN GUID *DomainGuid OPTIONAL,
IN LPCWSTR NetbiosDomainName OPTIONAL,
IN LPCWSTR DnsDomainName OPTIONAL
)
/*++
Routine Description:
This routine finds an existing domain cache entry that matches the
caller's query or creates one.
Arguments:
DomainGuid - Specifies the GUID of the domain to find.
NetbiosDomainName - Specifies the Netbios name of the domain to find.
DnsDomainName - Specifies the Dns name of the domain to find.
Return Value:
On success, returns a pointer to the domain cache entry describing a domain.
This entry must be dereference using NetpDcDerefDomainEntry.
NULL - Entry could not be allocated.
--*/
{
PLIST_ENTRY DomainEntry;
PNL_DC_DOMAIN_ENTRY NlDcDomainEntry;
//
// If there is an existing entry, use it.
//
// Don't require exact match for the existing
// entry as NetpDcGetName may not know the right
// Netbios and DNS names for the domain.
//
EnterCriticalSection(&NlDcCritSect);
NlDcDomainEntry = NetpDcFindDomainEntry( DomainGuid,
NetbiosDomainName,
DnsDomainName,
NULL,
FALSE ); // Exact match not required
if ( NlDcDomainEntry != NULL ) {
//
// Put the referenced entry at the front of the list.
//
RemoveEntryList( &NlDcDomainEntry->Next );
InsertHeadList( &NlDcDomainList, &NlDcDomainEntry->Next );
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Found existing domain cache entry\n",
NlDcDomainEntry->UnicodeNetbiosDomainName,
NlDcDomainEntry->UnicodeDnsDomainName ));
//
// Set the domain information in the domain entry.
//
// One might be tempted to put the domain name into the domain entry at this
// time. That'd be bogus since that caller doesn't know whether the passed
// in netbios and DNS name is really correct. For instance, in some instances,
// both passed in names are the netbios domain name.
//
if ( !NetpDcUpdateDomainEntry( NlDcDomainEntry,
DomainGuid,
NULL,
NULL ) ) {
// Remove our reference.
NetpDcDerefDomainEntry( NlDcDomainEntry );
NlDcDomainEntry = NULL;
}
//
// Otherwise allocate a new entry.
//
} else {
//
// Allocate a new entry.
//
NlDcDomainEntry = NetpMemoryAllocate( sizeof( NL_DC_DOMAIN_ENTRY ) );
if ( NlDcDomainEntry == NULL ) {
LeaveCriticalSection(&NlDcCritSect);
return NULL;
}
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Create new domain cache entry\n",
NetbiosDomainName,
DnsDomainName ));
//
// Initialize the entry.
//
RtlZeroMemory( NlDcDomainEntry, sizeof(NL_DC_DOMAIN_ENTRY) );
// One for our reference. One for being in global list.
NlDcDomainEntry->ReferenceCount = 2;
//
// Link a newly allocated entry into the global list.
InsertHeadList( &NlDcDomainList, &NlDcDomainEntry->Next );
NlDcDomainCount ++;
//
// If we already have enough entries,
// delete the LRU one.
//
if ( NlDcDomainCount > NL_DC_MAX_DOMAINS ) {
PNL_DC_DOMAIN_ENTRY TempNlDcDomainEntry =
CONTAINING_RECORD( NlDcDomainList.Blink, NL_DC_DOMAIN_ENTRY, Next);
if ( TempNlDcDomainEntry != NlDcDomainEntry ) {
NlAssert( TempNlDcDomainEntry->ReferenceCount == 1 );
NetpDcDeleteDomainEntry( TempNlDcDomainEntry );
NlPrint(( NL_CRITICAL,
"NetpDcGreateDomainEntry: LRU'ed out a domain entry.\n" ));
}
}
//
// Set the domain information in the domain entry.
//
//
// Since we allocated the entry, we can put the potentially bogus names
// on it. All entries need a name. Then if no DCs are found,
// this entry can act as a negative cache entry.
//
if ( !NetpDcUpdateDomainEntry( NlDcDomainEntry,
DomainGuid,
NetbiosDomainName,
DnsDomainName ) ) {
// Remove from the global linked list.
NetpDcDeleteDomainEntry( NlDcDomainEntry );
// Remove our reference.
NetpDcDerefDomainEntry( NlDcDomainEntry );
NlDcDomainEntry = NULL;
}
}
LeaveCriticalSection(&NlDcCritSect);
return NlDcDomainEntry;
}
ULONG
NetpDcGetPingWaitTime(
IN PNL_GETDC_CONTEXT Context
)
/*++
Routine Description:
This routine determines the wait time for a ping response
for a new DC that has not yet been pinged. The wait time
depends on the total number of DCs which have already been
pinged as follows:
For the first 5 DCs (including this new one) the wait time is the maximum timeout per ping
For the next 5 DCs (including this new one) the wait time is the median timeout per ping
For the rest of DCs (including this new one) the wait time is the minimum timeout per ping
The rational behind this distribution is that we want to reduce the network
traffic and reduce chances for network flooding (that is harmful for DCs)
in case all DCs are slow to respond due to high load. Thus, the first 10 DCs
have higher chances to be discovered before we impose greater network traffic
by pinging the rest of DCs. If the first 10 DCs happen to be slow we have to
reduce the wait timeout to a minimum as we want to cover a reasonable number
of DCs in the time left.
Arguments:
Context - Context describing the GetDc operation. The DcsPinged
field should be equal to the current total number of DCs on
the list to be pinged.
Return Value:
Wait time in milliseconds
--*/
{
//
// If there are at most 4 DCs already pinged ...
//
if ( Context->DcsPinged < 5 ) {
return NL_DC_MAX_PING_TIMEOUT; // 0.4 sec
//
// If there are 5 or more but less than 10 DCs pinged ...
//
} else if ( Context->DcsPinged < 10 ) {
return NL_DC_MED_PING_TIMEOUT; // 0.2 sec
//
// If there are already 10 or more DCs pinged ...
//
} else {
return NL_DC_MIN_PING_TIMEOUT; // 0.1 sec
}
}
NET_API_STATUS
NetpDcProcessAddressList(
IN PNL_GETDC_CONTEXT Context,
IN LPWSTR DnsHostName OPTIONAL,
IN PSOCKET_ADDRESS SockAddressList,
IN ULONG SockAddressCount,
IN BOOLEAN SiteSpecificAddress,
OUT PNL_DC_ADDRESS *FirstAddressInserted OPTIONAL
)
/*++
Routine Description:
This routine adds IP addresses to the list of addresses to ping
ensuring that all addresses are unique in the resulted list.
Arguments:
Context - Context describing the GetDc operation.
DnsHostName - Server name whose address list is being processed.
SockAddressList - List of socket addresses.
SockAddressCount - The number of socket addresses in SockAddressList.
SiteSpecificAddress - If TRUE, indicates that the addresses were
retrieved as a result of site specific DNS lookups.
FirstAddressInserted - Returns a pointer to the first entry inserted
into the returned list.
Return Value:
NO_ERROR - The operation was successful.
ERROR_NOT_ENOUGH_MEMORY - Not enough memory to complete the operation.
--*/
{
NET_API_STATUS NetStatus;
PNL_DC_ADDRESS DcAddress = NULL;
ULONG AddressIndex;
PLIST_ENTRY ListEntry;
WORD SavedPort;
//
// Initialization
//
if ( FirstAddressInserted != NULL ) {
*FirstAddressInserted = NULL;
}
//
// Loop through the socket address list keeping only the new ones
//
for ( AddressIndex = 0; AddressIndex < SockAddressCount; AddressIndex++ ) {
//
// Ignore addresses that are too big.
//
if ( SockAddressList[AddressIndex].iSockaddrLength >
sizeof(DcAddress->SockAddrIn) ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: bogus address length. %ld %ld\n",
Context->QueriedDisplayDomainName,
SockAddressList[AddressIndex].iSockaddrLength,
sizeof(DcAddress->SockAddrIn) ));
continue;
}
//
// Force the port number to be zero.
//
if ( SockAddressList[AddressIndex].lpSockaddr->sa_family == AF_INET ) {
((SOCKADDR_IN *)(SockAddressList[AddressIndex].lpSockaddr))->sin_port = 0;
}
//
// If this address is already on the list,
// update the new address.
//
DcAddress = NULL ;
for ( ListEntry = Context->DcAddressList.Flink ;
ListEntry != &Context->DcAddressList ;
ListEntry = ListEntry->Flink) {
DcAddress = CONTAINING_RECORD( ListEntry, NL_DC_ADDRESS, Next );
if ( SockAddressList[AddressIndex].iSockaddrLength ==
DcAddress->SockAddress.iSockaddrLength &&
RtlEqualMemory( SockAddressList[AddressIndex].lpSockaddr,
DcAddress->SockAddress.lpSockaddr,
DcAddress->SockAddress.iSockaddrLength ) ) {
break;
}
DcAddress = NULL ;
}
//
// Update the site specific bit
//
if ( DcAddress != NULL ) {
if ( SiteSpecificAddress ) {
DcAddress->AddressFlags |= NL_DC_ADDRESS_SITE_SPECIFIC;
}
continue;
}
//
// Allocate structure describing the new address.
//
DcAddress = LocalAlloc( LMEM_ZEROINIT, sizeof(NL_DC_ADDRESS) );
if ( DcAddress == NULL ) {
return ERROR_NOT_ENOUGH_MEMORY;
}
//
// Fill it in and link it at the end of the list.
//
DcAddress->SockAddress.iSockaddrLength =
SockAddressList[AddressIndex].iSockaddrLength;
DcAddress->SockAddress.lpSockaddr =
(LPSOCKADDR) &DcAddress->SockAddrIn;
RtlCopyMemory( DcAddress->SockAddress.lpSockaddr,
SockAddressList[AddressIndex].lpSockaddr,
SockAddressList[AddressIndex].iSockaddrLength );
if ( DnsHostName != NULL ) {
DcAddress->DnsHostName = NetpAllocWStrFromWStr( DnsHostName );
if ( DcAddress->DnsHostName == NULL ) {
LocalFree( DcAddress );
return ERROR_NOT_ENOUGH_MEMORY;
}
}
//
// Convert the address to text.
//
// The DC only supports UDP on port 389. So ignore the
// port number returned from DNS.
//
SavedPort = DcAddress->SockAddrIn.sin_port;
DcAddress->SockAddrIn.sin_port = 0;
NetStatus = NetpSockAddrToStr(
DcAddress->SockAddress.lpSockaddr,
DcAddress->SockAddress.iSockaddrLength,
DcAddress->SockAddrString );
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameSiteIp: %ws: Cannot NetpSockAddrToStr. %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
if ( DcAddress->DnsHostName != NULL ) {
NetApiBufferFree( DcAddress->DnsHostName );
}
LocalFree( DcAddress );
return NetStatus;
}
DcAddress->SockAddrIn.sin_port = SavedPort;
DcAddress->LdapHandle = NULL;
DcAddress->AddressFlags = 0;
if ( SiteSpecificAddress ) {
DcAddress->AddressFlags |= NL_DC_ADDRESS_SITE_SPECIFIC;
}
DcAddress->AddressPingWait = NetpDcGetPingWaitTime( Context );
InsertTailList( &Context->DcAddressList, &DcAddress->Next );
Context->DcAddressCount++;
if ( FirstAddressInserted != NULL && *FirstAddressInserted == NULL ) {
*FirstAddressInserted = DcAddress;
}
}
return NO_ERROR;
}
#ifndef WIN32_CHICAGO
NET_API_STATUS
I_DsGetDcCache(
IN LPCWSTR NetbiosDomainName OPTIONAL,
IN LPCWSTR DnsDomainName OPTIONAL,
OUT PBOOLEAN InNt4Domain,
OUT LPDWORD InNt4DomainTime
)
/*++
Routine Description:
This routine finds a domain entry that matches the caller's query.
Arguments:
NetbiosDomainName - Specifies the Netbios name of the domain to find.
DnsDomainName - Specifies the Dns name of the domain to find.
At least one of the above parameters should be non-NULL.
InNt4Domain - Returns true if the domain is an NT 4.0 domain.
InNt4DomainTime - Returns the GetTickCount time of when the domain was
detected to be an NT 4.0 domain.
Return Value:
NO_ERROR: Information is returned about the domain.
ERROR_NO_SUCH_DOMAIN: cached information is not available for this domain.
--*/
{
PNL_DC_DOMAIN_ENTRY NlDcDomainEntry;
NlDcDomainEntry = NetpDcFindDomainEntry(
NULL,
NetbiosDomainName,
DnsDomainName,
NULL,
FALSE ); // Exact match not required
if ( NlDcDomainEntry == NULL ) {
return ERROR_NO_SUCH_DOMAIN;
}
*InNt4Domain = NlDcDomainEntry->InNt4Domain;
*InNt4DomainTime = NlDcDomainEntry->InNt4DomainTime;
NetpDcDerefDomainEntry( NlDcDomainEntry );
return NO_ERROR;
}
#endif // WIN32_CHICAGO
NET_API_STATUS
NetpDcCheckSiteCovered(
IN PNL_GETDC_CONTEXT Context,
IN LPWSTR DnsDcName OPTIONAL,
OUT PBOOLEAN DcClose
)
/*++
Routine Description:
This routine determines whether the site passed in the context
structure is covered by the passed DC. It does so by looking up
SRV records registered for the name type specified in the passed
context and the specified site. If there is a record that belongs
to the specified DC, the site is covered by the DC. If no DC is
specified, the routine determines if the site is covered by any
DC in the domain specified in the passed context.
Arguments:
Context - Context describing the GetDc operation.
DnsDcName - DNS DC name.
DcClose - On success, indicates whether the DC is close or not.
Return Value:
NO_ERROR: The NlDcCacheEntry was returned;
ERROR_DNS_NOT_CONFIGURED: IP or DNS is not available on this computer.
ERROR_INTERNAL_ERROR: Unhandled situation detected.
ERROR_NOT_ENOUGH_MEMORY: Not enough memory is available to process
this request.
Various Winsock errors.
--*/
{
NET_API_STATUS NetStatus = NO_ERROR;
BOOLEAN IsClose = FALSE;
HANDLE DsGetDcHandle = NULL;
PSOCKET_ADDRESS SockAddressList = NULL;
ULONG SockAddressCount = 0;
LPSTR Utf8DnsDomainName = NULL;
LPSTR Utf8DnsDcName = NULL;
LPSTR DnsHostName = NULL;
//
// Check that the site name is availbale
//
if ( Context->QueriedSiteName == NULL ) {
goto Cleanup;
}
//
// Convert the DNS name to Utf8
//
Utf8DnsDomainName = NetpAllocUtf8StrFromWStr( Context->QueriedDnsDomainName );
if ( Utf8DnsDomainName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
if ( DnsDcName != NULL ) {
Utf8DnsDcName = NetpAllocUtf8StrFromWStr( DnsDcName );
if ( Utf8DnsDcName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
}
//
// Get a context for the DNS name queries.
//
NetStatus = NetpDcGetDcOpen( Utf8DnsDomainName,
DS_ONLY_DO_SITE_NAME, // Do site specific names only
Context->QueriedSiteName,
Context->QueriedDomainGuid,
// No need to pass the forest name since it's used only
// for the "by guid" name which is not site specific
NULL,
// Force fresh DNS lookups
(Context->QueriedFlags | DS_FORCE_REDISCOVERY) & DS_OPEN_VALID_FLAGS,
&DsGetDcHandle );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Loop getting addresses to query.
//
for ( ;; ) {
//
// Free any memory from a previous iteration.
//
if ( SockAddressList != NULL ) {
LocalFree( SockAddressList );
SockAddressList = NULL;
}
//
// Get the next set of IP addresses from DNS
//
NetStatus = NetpDcGetDcNext( DsGetDcHandle,
&SockAddressCount,
&SockAddressList,
&DnsHostName,
NULL ); // don't need SRV record count
//
// Process the exeptional conditions
//
if ( NetStatus == NO_ERROR && SockAddressCount > 0 ) {
//
// If the DC is passed, check if this is its record.
//
if ( Utf8DnsDcName == NULL ||
NlEqualDnsNameUtf8(Utf8DnsDcName, DnsHostName) ) {
IsClose = TRUE;
break;
}
//
// If we're done, break out of the loop.
//
} else if ( NetStatus == ERROR_NO_MORE_ITEMS ) {
break;
//
// If DNS isn't available, blow this request away.
//
} else if ( NetStatus == ERROR_TIMEOUT ||
NetStatus == DNS_ERROR_RCODE_SERVER_FAILURE ) { // Server failed
break;
//
// If IP or DNS is not configured, tell the caller.
//
} else if ( NetStatus == DNS_ERROR_NO_TCPIP || // TCP/IP not configured
NetStatus == DNS_ERROR_NO_DNS_SERVERS ) { // DNS not configured
NlPrint(( NL_CRITICAL,
"NetpDcCheckSiteCovered: %ws: IP Not configured from DnsQuery.\n",
Context->QueriedDisplayDomainName ));
NetStatus = ERROR_DNS_NOT_CONFIGURED;
goto Cleanup;
//
// We don't handle any other error.
//
} else {
NlPrint(( NL_CRITICAL,
"NetpDcCheckSiteCovered: %ws: Unknown error from DnsQuery. %ld 0x%lx\n",
Context->QueriedDisplayDomainName,
NetStatus,
NetStatus ));
goto Cleanup;
}
}
Cleanup:
if ( SockAddressList != NULL) {
LocalFree( SockAddressList );
}
if ( DsGetDcHandle != NULL ) {
NetpDcGetDcClose( DsGetDcHandle );
}
if ( Utf8DnsDomainName != NULL ) {
NetpMemoryFree( Utf8DnsDomainName );
}
if ( Utf8DnsDcName != NULL ) {
NetpMemoryFree( Utf8DnsDcName );
}
if ( NetStatus == NO_ERROR ) {
*DcClose = IsClose;
}
return NetStatus;
}
NET_API_STATUS
NetpDcHandlePingResponse(
IN PNL_GETDC_CONTEXT Context,
IN PVOID ResponseBuffer,
IN ULONG ResponseSize,
IN ULONG PassedCacheEntryFlags,
IN PNL_DC_ADDRESS ResponseDcAddress OPTIONAL,
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry,
OUT PBOOL UsedNetbios
)
/*++
Routine Description:
The response is parsed and a cache entry is created for the response.
The cache entry is returned to the caller.
Arguments:
Context - Context describing the GetDc operation.
ResponseBuffer - Specifies the message returned by the DC in question.
ResponseSize - Specifies the size (in bytes) of the message
ResponseDcAddress - If specified, gives the DC address of the DC that responded.
This address will be used rather than the one in the ResponseBuffer.
NULL indicates that Netbios was used to discover the DC.
PassedCacheEntryFlags - Passes flags indicating over which mechanism
the response was received: either mailslot or ldap.
NlDcCacheEntry - On success, returns a pointer to the cache entry
describing the found DC. This entry must be dereferenced using
NetpDcDerefCacheEntry.
UsedNetbios - Returns TRUE if the netbios domain name was used to match
the returned cache entry.
Return Value:
NO_ERROR: The NlDcCacheEntry was returned;
ERROR_SEM_TIMEOUT: (Silly, but for consistency) Specifies that the message
doesn't match the criteria in Context
ERROR_INVALID_DATA - The message could not be recognized as a valid
response message.
ERROR_NOT_ENOUGH_MEMORY - The message could not be allocated.
ERROR_SERVICE_NOT_ACTIVE - The netlogon service is paused on the server.
Returned only for server pings.
--*/
{
NET_API_STATUS NetStatus;
DWORD StartTime;
DWORD ElapsedTime;
DWORD LocalTimeout;
PNL_DC_CACHE_ENTRY LocalNlDcCacheEntry = NULL;
BOOL LocalUsedNetbios;
PDNS_RECORD DnsRecords = NULL;
LPBYTE Where;
//
// Initialization
//
*NlDcCacheEntry = NULL;
//
// Verbosity
//
#if NETLOGONDBG
NlPrint(( NL_MAILSLOT_TEXT,
"%ws: Received '%s' response.\n",
Context->QueriedDisplayDomainName,
NlMailslotOpcode(((PNETLOGON_LOGON_QUERY)ResponseBuffer)->Opcode)));
NlpDumpBuffer(NL_MAILSLOT_TEXT, ResponseBuffer, ResponseSize);
#endif // NETLOGONDBG
//
// Parse the response
//
NetStatus = NetpDcParsePingResponse(
Context->QueriedDisplayDomainName,
ResponseBuffer,
ResponseSize,
&LocalNlDcCacheEntry );
if ( NetStatus != NO_ERROR ) {
NlPrint((NL_CRITICAL,
"NetpDcHandlePingResponse: %ws: cannot NetpDcParsePingResponse: %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
goto Cleanup;
}
//
// Set the passed cache entry flags
//
LocalNlDcCacheEntry->CacheEntryFlags |= PassedCacheEntryFlags;
//
// If our caller knows the DC's SockAddr,
// override the one that came back from the DC.
//
if ( ResponseDcAddress != NULL ) {
LocalNlDcCacheEntry->SockAddr.iSockaddrLength = ResponseDcAddress->SockAddress.iSockaddrLength;
LocalNlDcCacheEntry->SockAddr.lpSockaddr = (LPSOCKADDR)
&LocalNlDcCacheEntry->SockAddrIn;
RtlCopyMemory( LocalNlDcCacheEntry->SockAddr.lpSockaddr,
ResponseDcAddress->SockAddress.lpSockaddr,
ResponseDcAddress->SockAddress.iSockaddrLength );
}
//
// If a NT5 DC didn't return the close bit, perhaps
// our statically configured site (if any) is covered
// by the DC. Check it. But do this check only if
// the discovered domain is in our forest to avoid
// site name collisions between different forests.
//
#ifdef _NETLOGON_SERVER
if ( (LocalNlDcCacheEntry->ReturnFlags & DS_DS_FLAG) != 0 &&
(LocalNlDcCacheEntry->ReturnFlags & DS_CLOSEST_FLAG) == 0 &&
// site is configured on DC by definition
(!NlGlobalMemberWorkstation || NlGlobalParameters.SiteNameConfigured) &&
NlEqualDnsName(LocalNlDcCacheEntry->UnicodeDnsForestName, Context->QueriedDnsForestName) ) {
BOOLEAN ClosenessDetermined = FALSE;
//
// If we are querying for our configured site,
// check whether DC's site is the queried site or
// whether we got this DC as the result of site specific query
//
if ( Context->QueriedInternalFlags & DS_SITENAME_DEFAULTED ) {
//
// If the DC site name is the same as ours, the DC is obviously close
//
if ( LocalNlDcCacheEntry->UnicodeDcSiteName != NULL &&
_wcsicmp(Context->QueriedSiteName, LocalNlDcCacheEntry->UnicodeDcSiteName) == 0 ) {
NlPrint(( NL_MISC, "NetpDchandlePingResponse: %ws DC marked as close (same queried site)\n",
Context->QueriedDisplayDomainName ));
LocalNlDcCacheEntry->ReturnFlags |= DS_CLOSEST_FLAG;
ClosenessDetermined = TRUE;
//
// Otherwise, if this is actual DC discovery (not just a DC ping),
// we might already queried DNS for our site specific records:
// the address structure indicates that.
//
} else if ( (Context->QueriedInternalFlags & (DS_PING_DNS_HOST|DS_PING_NETBIOS_HOST)) == 0 &&
ResponseDcAddress != NULL ) {
if ( (ResponseDcAddress->AddressFlags & NL_DC_ADDRESS_SITE_SPECIFIC) != 0 ) {
NlPrint(( NL_MISC, "NetpDchandlePingResponse: %ws DC marked as close (via address)\n",
Context->QueriedDisplayDomainName ));
LocalNlDcCacheEntry->ReturnFlags |= DS_CLOSEST_FLAG;
}
ClosenessDetermined = TRUE;
}
//
// Otherwise, get our configured site from the global
// and check whether DC's site is our configured site
//
} else {
EnterCriticalSection( &NlGlobalSiteCritSect );
if ( NlGlobalUnicodeSiteName != NULL &&
LocalNlDcCacheEntry->UnicodeDcSiteName != NULL &&
_wcsicmp(NlGlobalUnicodeSiteName, LocalNlDcCacheEntry->UnicodeDcSiteName) == 0 ) {
NlPrint(( NL_MISC, "NetpDchandlePingResponse: %ws DC marked as close (same global site)\n",
Context->QueriedDisplayDomainName ));
LocalNlDcCacheEntry->ReturnFlags |= DS_CLOSEST_FLAG;
ClosenessDetermined = TRUE;
}
LeaveCriticalSection( &NlGlobalSiteCritSect );
}
//
// If we haven't detemined the closeness given the info we've got,
// we have to ask DNS
//
if ( !ClosenessDetermined ) {
BOOLEAN DcClose = FALSE;
NetStatus = NetpDcCheckSiteCovered( Context,
LocalNlDcCacheEntry->UnicodeDnsHostName,
&DcClose );
//
// Fail on hard error
//
if ( NetStatus == ERROR_NOT_ENOUGH_MEMORY ||
NetStatus == ERROR_INTERNAL_ERROR ) {
goto Cleanup;
}
//
// If the DC is a close to our static site, mark it as such
//
if ( NetStatus == NO_ERROR && DcClose ) {
NlPrint(( NL_MISC, "NetpDchandlePingResponse: %ws DC marked as close (via DNS)\n",
Context->QueriedDisplayDomainName ));
LocalNlDcCacheEntry->ReturnFlags |= DS_CLOSEST_FLAG;
}
}
}
//
// Decide whether we want to re-attempt to find a close DC in 15 minutes.
//
if ( (LocalNlDcCacheEntry->ReturnFlags & DS_DS_FLAG) != 0 &&
(LocalNlDcCacheEntry->ReturnFlags & DS_CLOSEST_FLAG) == 0 ) {
//
// Mark the cache to expire if any of the following is true:
//
// The DC returned our site name. Apparently our IP address
// maps to a site in a forest of the discovered DC but all
// DCs covering that site are currently down.
//
// There were site specific DNS records. It is possible that
// all DCs that registered those records are currently down
// but will come back up later. But do this case only if the
// domain is from our forest to avoid site name collisions
// between different forests.
//
// Our site name is statically configured. It is possible
// that our site got configured before a DC was installed
// into that site, so re-try to discover that DC later.
// But do this case only if the domain is from our forest
// because there is no reason to assume that there is some
// correlation in configuration between different forests.
//
//
// Check the first case separately (for performance reasons)
//
if ( LocalNlDcCacheEntry->UnicodeClientSiteName != NULL ) {
LocalNlDcCacheEntry->CacheEntryFlags |= NL_DC_CACHE_NONCLOSE_EXPIRE;
//
// If the first case didn't happen, try the other two
//
} else {
if ( ((Context->ContextFlags & NL_GETDC_SITE_SPECIFIC_DNS_AVAIL) != 0 ||
// site is configured on DC by definition
(!NlGlobalMemberWorkstation || NlGlobalParameters.SiteNameConfigured)) &&
NlEqualDnsName(LocalNlDcCacheEntry->UnicodeDnsForestName, Context->QueriedDnsForestName) ) {
LocalNlDcCacheEntry->CacheEntryFlags |= NL_DC_CACHE_NONCLOSE_EXPIRE;
}
}
}
#endif // _NETLOGON_SERVER
if ( LocalNlDcCacheEntry->SockAddrIn.sin_family == AF_INET ) {
// Force the port number to be zero.
LocalNlDcCacheEntry->SockAddrIn.sin_port = 0;
LocalNlDcCacheEntry->DcQuality += 2; // IP is a good quality
}
//
// Ensure the opcode is expected.
// (Ignore responses from paused DCs, too.)
//
switch ( LocalNlDcCacheEntry->Opcode ) {
case LOGON_SAM_USER_UNKNOWN:
//
// If we asked for a specific account,
// then this is a negative answer.
//
if ( Context->QueriedAccountName != NULL ) {
NlPrint((NL_CRITICAL,
"NetpDcHandlePingResponse: %ws: %ws: response says specified account not found.\n",
Context->QueriedDisplayDomainName,
Context->QueriedAccountName ));
Context->NoSuchUserResponse = TRUE;
NetStatus = ERROR_NO_SUCH_USER;
goto Cleanup;
}
/* DROP THROUGH */
case LOGON_SAM_LOGON_RESPONSE:
case LOGON_PRIMARY_RESPONSE:
break;
case LOGON_SAM_PAUSE_RESPONSE:
NlPrint((NL_CRITICAL,
"NetpDcHandlePingResponse: %ws: Netlogon is paused on the server. 0x%lx\n",
Context->QueriedDisplayDomainName,
LocalNlDcCacheEntry->Opcode ));
//
// If we are pinging a server and the netlogon service is paused
// on the server, tell the caller about this.
//
if ( Context->QueriedInternalFlags & (DS_PING_DNS_HOST|DS_PING_NETBIOS_HOST) ) {
NetStatus = ERROR_SERVICE_NOT_ACTIVE;
goto Cleanup;
}
NetStatus = ERROR_SEM_TIMEOUT;
goto Cleanup;
default:
NlPrint((NL_CRITICAL,
"NetpDcHandlePingResponse: %ws: response opcode not valid. 0x%lx\n",
Context->QueriedDisplayDomainName,
LocalNlDcCacheEntry->Opcode ));
NetStatus = ERROR_INVALID_DATA;
goto Cleanup;
}
//
// If we got any response from a DC that doesn't support the DS,
// note that fact for later.
//
// Check more than just DS_DS_FLAG since this might be an NT 5 DC where the
// DS simply isn't started yet or an AD/UNIX server
//
if ( (LocalNlDcCacheEntry->VersionFlags & (NETLOGON_NT_VERSION_5|
NETLOGON_NT_VERSION_5EX|
NETLOGON_NT_VERSION_5EX_WITH_IP)) == 0 ) {
Context->NonDsResponse = TRUE;
} else {
Context->DsResponse = TRUE;
}
//
// If we got any response from any DC,
// don't cache the fact that we couldn't find a DC.
// We may just be looking for the wrong type of DC.
//
Context->AvoidNegativeCache = TRUE;
//
// Ensure the response matches the original caller's requirements.
//
if ( !NetpDcMatchResponse( Context,
LocalNlDcCacheEntry,
TRUE,
&LocalUsedNetbios ) ) {
NetStatus = ERROR_SEM_TIMEOUT;
goto Cleanup;
}
//
// If we are doing a DC discovery (not just pings) and
// this is not a local call and
// we are going to return the DNS DC name,
// ensure that it can be resolved in DNS
//
// Note that we don't need to do this for pings as the
// caller will make the right choice w.r.t. which
// name (DNS or Netbios) to pick up given all the needed
// info in the internal structure we return to the caller.
//
// Note that we only ensure that the name can be resolved in DNS,
// we don't ensure that the IP adresses DNS returnes contain the one
// we used to ping the DC.
if ( (Context->QueriedInternalFlags & DS_DOING_DC_DISCOVERY) != 0 && // doing DC discovery
(PassedCacheEntryFlags & NL_DC_CACHE_LOCAL) == 0 && // not a local call
((Context->QueriedFlags & DS_RETURN_DNS_NAME) != 0 || // caller requires DNS name
// caller doesn't require Netbios name and DNS domain name matched the query
((Context->QueriedFlags & DS_RETURN_FLAT_NAME) == 0 && !LocalUsedNetbios)) ) {
//
// If the DC returned a DNS host name and
// we didn't yet resolve the DC name in DNS (i.e. we used Netbios) or
// we've got a DNS name that is different from the one the DC returned,
// resolve the DNS name the DC returned
//
if ( LocalNlDcCacheEntry->UnicodeDnsHostName != NULL && // we have a DNS name
(ResponseDcAddress == NULL || // name not yet resolved in DNS
ResponseDcAddress->DnsHostName == NULL || // name not yet resolved in DNS
!NlEqualDnsName(LocalNlDcCacheEntry->UnicodeDnsHostName, // already resolved but names are different
ResponseDcAddress->DnsHostName)) ) {
NetStatus = DnsQuery_W( LocalNlDcCacheEntry->UnicodeDnsHostName,
DNS_TYPE_A,
(Context->QueriedFlags & DS_FORCE_REDISCOVERY) ?
DNS_QUERY_BYPASS_CACHE : 0,
NULL, // No list of DNS servers
&DnsRecords,
NULL );
if ( NetStatus != NO_ERROR || DnsRecords == NULL ) {
NlPrint(( NL_CRITICAL,
"NetpDcHandlePingResponse: %ws: Failed DNS resolution for %ws (%ws): 0x%lx\n",
Context->QueriedDisplayDomainName,
LocalNlDcCacheEntry->UnicodeDnsHostName,
(ResponseDcAddress != NULL) ?
ResponseDcAddress->DnsHostName :
L"none",
NetStatus ));
NetStatus = ERROR_SEM_TIMEOUT;
goto Cleanup;
} else {
NlPrint(( NL_MISC,
"NetpDcHandlePingResponse: %ws: Successful DNS resolution for %ws (%ws)\n",
Context->QueriedDisplayDomainName,
LocalNlDcCacheEntry->UnicodeDnsHostName,
(ResponseDcAddress != NULL) ?
ResponseDcAddress->DnsHostName :
L"none" ));
}
}
}
//
// Compute the quality of this cache entry
//
// Some qualities are more important than others
//
if (LocalNlDcCacheEntry->ReturnFlags & DS_DS_FLAG) {
LocalNlDcCacheEntry->DcQuality += 1;
}
if (LocalNlDcCacheEntry->ReturnFlags & DS_GOOD_TIMESERV_FLAG) {
LocalNlDcCacheEntry->DcQuality += 1;
}
if (LocalNlDcCacheEntry->ReturnFlags & DS_KDC_FLAG) {
LocalNlDcCacheEntry->DcQuality += 5;
}
if (LocalNlDcCacheEntry->ReturnFlags & DS_TIMESERV_FLAG) {
LocalNlDcCacheEntry->DcQuality += 5;
}
if (LocalNlDcCacheEntry->ReturnFlags & DS_CLOSEST_FLAG) {
LocalNlDcCacheEntry->DcQuality += 10;
}
//
// We found it!
//
// Return the cache entry to the caller.
NetStatus = NO_ERROR;
*NlDcCacheEntry = LocalNlDcCacheEntry;
*UsedNetbios = LocalUsedNetbios;
LocalNlDcCacheEntry = NULL;
Cleanup:
if ( LocalNlDcCacheEntry != NULL ) {
NetpDcDerefCacheEntry( LocalNlDcCacheEntry );
}
if ( DnsRecords != NULL ) {
DnsRecordListFree( DnsRecords, DnsFreeRecordListDeep );
}
return NetStatus;
}
NET_API_STATUS
NetpDcGetPingResponse(
IN PNL_GETDC_CONTEXT Context,
IN ULONG Timeout,
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry,
OUT PBOOL UsedNetbios
)
/*++
Routine Description:
This routine reads a ping response from the specified mailslot.
The response is parsed and a cache entry is created for the response.
The cache entry is returned to the caller.
Arguments:
Context - Context describing the GetDc operation.
Timeout - Maximum time (in milliseconds) to wait for the response.
NlDcCacheEntry - On success, returns a pointer to the cache entry
describing the found DC. This entry must be dereference using
NetpDcDerefCacheEntry.
UsedNetbios - Returns TRUE if the netbios domain name was used to match
the returned cache entry.
Return Value:
NO_ERROR: The NlDcCacheEntry was returned;
ERROR_SEM_TIMEOUT: No response was available within Timeout milliseconds
ERROR_INVALID_DATA: We pinged a DC with a particular IP address and that DC
returned responce info that was in conflict with the requested info.
ERROR_SERVICE_NOT_ACTIVE - The netlogon service is paused on the pinged
server. Returned only for DC pings.
--*/
{
NET_API_STATUS NetStatus;
DWORD StartTime;
DWORD ElapsedTime;
DWORD BeginElapsedTime;
DWORD LocalTimeout;
LPBYTE Response;
DWORD ResponseSize;
PLIST_ENTRY ListEntry;
PNL_DC_ADDRESS DcAddress;
PNL_DC_ADDRESS UsedDcAddress;
PNL_DC_ADDRESS ResponseDcAddress;
int LdapError;
LDAP_TIMEVAL LdapTimeout;
PLDAPMessage LdapMessage = NULL;
PLDAPMessage CurrentEntry;
PLDAP_BERVAL *Berval = NULL;
ULONG LocalCacheEntryFlags = 0;
//
// Initialization
//
*NlDcCacheEntry = NULL;
StartTime = GetTickCount();
//
// Some timeouts are computed.
// Prevent timeouts from being ridiculously small.
// However, if this is DC pinging, allow 0 timeout
// for just checking if a response is available.
//
if ( Timeout < NL_DC_MIN_PING_TIMEOUT &&
(Context->QueriedInternalFlags & (DS_PING_DNS_HOST|DS_PING_NETBIOS_HOST)) == 0 ) {
Timeout = NL_DC_MIN_PING_TIMEOUT;
}
//
// Loop ignoring bogus responses.
//
for (;;) {
//
// Flag that we don't yet have a response.
//
Response = NULL;
ResponseDcAddress = NULL;
BeginElapsedTime = NetpDcElapsedTime( StartTime );
UsedDcAddress = NULL;
//
// Loop through the list of DCs we've started LDAP calls to.
//
for ( ListEntry = Context->DcAddressList.Flink ;
ListEntry != &Context->DcAddressList ;
ListEntry = ListEntry->Flink) {
//
// Cleanup from previous iteration.
if ( Berval != NULL ) {
ldap_value_free_len( Berval );
Berval = NULL;
}
if ( LdapMessage != NULL ) {
ldap_msgfree( LdapMessage );
LdapMessage = NULL;
}
//
// Skip this entry if no LDAP search has been started.
//
DcAddress = CONTAINING_RECORD( ListEntry, NL_DC_ADDRESS, Next );
if ( DcAddress->LdapHandle == NULL ) {
continue; // Continue with the next host
}
//
// Poll to see if a result is available for ANY of the searches
// I've done to this host.
//
LdapTimeout.tv_sec = 0;
LdapTimeout.tv_usec = 0;
LdapError = ldap_result(
DcAddress->LdapHandle,
LDAP_RES_ANY,
TRUE, // Return all of search
&LdapTimeout, // poll
&LdapMessage );
//
// If the request timed out, continue with the next host.
// We get this timeout if the response hasn't yet come back from the DC
//
if ( LdapError == 0 ) {
continue; // Continue with the next host
//
// Otherwise, check error conditions
//
} else if ( LdapError == -1 ) {
#if NETLOGONDBG
NlPrint(( NL_CRITICAL,
"NetpDcGetPingResponse: %ws: Cannot ldap_result ip address %s: %ld %s\n",
Context->QueriedDisplayDomainName,
DcAddress->SockAddrString,
DcAddress->LdapHandle->ld_errno,
ldap_err2stringA(DcAddress->LdapHandle->ld_errno) ));
#endif // NETLOGONDBG
//
// LDAP_TIMEOUT means the IP address exists but there is no LDAP server at that address.
// Don't ever try this machine again.
//
// All other status codes are unknown. Keep trying this machine since we don't know
// if this is a client side or server side failure.
//
if ( DcAddress->LdapHandle->ld_errno == LDAP_TIMEOUT ) {
if ( (DcAddress->AddressFlags & NL_DC_ADDRESS_NEVER_TRY_AGAIN) == 0 ) {
DcAddress->AddressFlags |= NL_DC_ADDRESS_NEVER_TRY_AGAIN;
Context->DcAddressCount--;
}
}
//
// ldap_result returned the answer. No need calling ldap_result again.
//
ldap_unbind( DcAddress->LdapHandle );
DcAddress->LdapHandle = NULL;
continue; // Continue with the next host
}
//
// Get the first entry returned. (There should only be one.)
//
CurrentEntry = ldap_first_entry( DcAddress->LdapHandle, LdapMessage );
if ( CurrentEntry == NULL ) {
//
// This means the server doesn't support the NETLOGON attribute.
// That's probably because the netlogon service is stopped.
//
NlPrint(( NL_MAILSLOT_TEXT,
"NetpDcGetPingResponse: %ws: Netlogon service stopped on DC at %s\n",
Context->QueriedDisplayDomainName,
DcAddress->SockAddrString ));
//
// Don't ever try this machine again.
//
if ( (DcAddress->AddressFlags & NL_DC_ADDRESS_NEVER_TRY_AGAIN) == 0 ) {
DcAddress->AddressFlags |= NL_DC_ADDRESS_NEVER_TRY_AGAIN;
Context->DcAddressCount--;
}
//
// ldap_result returned the answer. No need calling ldap_result again.
//
ldap_unbind( DcAddress->LdapHandle );
DcAddress->LdapHandle = NULL;
continue; // Continue with the next host
}
//
// Get the Netlogon attribute returned. (There should only be one.)
//
Berval = ldap_get_values_lenA( DcAddress->LdapHandle,
CurrentEntry,
NETLOGON_LDAP_ATTRIBUTE );
if ( Berval == NULL ) {
if ( DcAddress->LdapHandle->ld_errno != 0 ) {
#if NETLOGONDBG
NlPrint(( NL_CRITICAL,
"NetpDcGetPingResponse: %ws: Cannot ldap_get_values_len ip address %s: %ld %s\n",
Context->QueriedDisplayDomainName,
DcAddress->SockAddrString,
DcAddress->LdapHandle->ld_errno,
ldap_err2stringA(DcAddress->LdapHandle->ld_errno) ));
#endif // NETLOGONDBG
// ?? Some should be fatal
//
// If the DC returned that there isn't a NETLOGON attribute,
// then it really isn't a DC.
if ( DcAddress->LdapHandle->ld_errno == LDAP_NO_SUCH_ATTRIBUTE ) {
if ( (DcAddress->AddressFlags & NL_DC_ADDRESS_NEVER_TRY_AGAIN) == 0 ) {
DcAddress->AddressFlags |= NL_DC_ADDRESS_NEVER_TRY_AGAIN;
Context->DcAddressCount--;
}
}
}
//
// ldap_result returned the answer. No need calling ldap_result again.
//
ldap_unbind( DcAddress->LdapHandle );
DcAddress->LdapHandle = NULL;
continue; // Continue with the next host
}
if ( Berval[0] == NULL ) {
//
// ldap_result returned the answer. No need calling ldap_result again.
//
ldap_unbind( DcAddress->LdapHandle );
DcAddress->LdapHandle = NULL;
continue; // Continue with the next host
}
//
// Check to see if we have SockAddress
//
if ( DcAddress->SockAddress.iSockaddrLength != 0 ) {
ResponseDcAddress = DcAddress;
}
Response = Berval[0]->bv_val;
ResponseSize = Berval[0]->bv_len;
UsedDcAddress = DcAddress;
LocalCacheEntryFlags = NL_DC_CACHE_LDAP;
//
// ldap_result returned the answer. No need calling ldap_result again.
//
ldap_unbind( DcAddress->LdapHandle );
DcAddress->LdapHandle = NULL;
break;
}
//
// If we don't yet have a response,
// try mailslots.
if ( Response == NULL && Context->ResponseMailslotHandle != NULL ) {
//
// Set the mailslot read to return after the appropriate time.
// ?? This is now common code. I could set it when I create the mailslot.
//
if ( !SetMailslotInfo(
Context->ResponseMailslotHandle,
0 ) ) { // zero timeout
NetStatus = GetLastError();
NlPrint((NL_CRITICAL,
"NetpDcGetPingResponse: %ws: cannot change temp mailslot timeout %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
goto Cleanup;
}
//
// Read the response from the response mailslot
//
if ( !ReadFile( Context->ResponseMailslotHandle,
Context->ResponseBuffer,
Context->ResponseBufferSize,
&ResponseSize,
NULL )
#ifdef WIN32_CHICAGO
|| (ResponseSize == 0)
#endif // WIN32_CHICAGO
) {
NetStatus = GetLastError();
#ifdef WIN32_CHICAGO
if ((NetStatus == NO_ERROR) && (ResponseSize == 0))
{
NetStatus = ERROR_SEM_TIMEOUT;
}
#endif // WIN32_CHICAGO
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
NlPrint((NL_CRITICAL,
"NetpDcGetPingResponse: %ws: cannot read temp mailslot timeout %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
goto Cleanup;
}
/* Just drop through with no response */
} else {
Response = (LPBYTE) Context->ResponseBuffer;
LocalCacheEntryFlags = NL_DC_CACHE_MAILSLOT;
}
}
//
// See if this response meets our needs
//
if ( Response != NULL ) {
NetStatus = NetpDcHandlePingResponse(
Context,
Response,
ResponseSize,
LocalCacheEntryFlags,
ResponseDcAddress,
NlDcCacheEntry,
UsedNetbios );
//
// We are done if we have any response for a ping
//
if ( Context->QueriedInternalFlags &
(DS_PING_DNS_HOST | DS_PING_NETBIOS_HOST) ) {
//
// If the response conflicts with the request,
// tell that the caller
//
if ( NetStatus == ERROR_SEM_TIMEOUT ) {
NetStatus = ERROR_INVALID_DATA;
}
goto Cleanup;
}
//
// For a DC discovery, take an appropriate action depending
// on the response we got from the DC
//
switch ( NetStatus ) {
case ERROR_INVALID_DATA: // Response is garbled
break; // Continue processing more responses
case ERROR_SEM_TIMEOUT: // Doesn't match the request
case ERROR_NO_SUCH_USER: // User doesn't exist on this DC
if ( UsedDcAddress != NULL) {
NlPrint((NL_MAILSLOT_TEXT,
"NetpDcGetPingResponse: %ws: marked DC as NeverTryAgain %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
if ( (UsedDcAddress->AddressFlags & NL_DC_ADDRESS_NEVER_TRY_AGAIN) == 0 ) {
UsedDcAddress->AddressFlags |= NL_DC_ADDRESS_NEVER_TRY_AGAIN;
Context->DcAddressCount--;
}
}
break; // Continue processing more responses
default:
goto Cleanup;
}
}
//
// If we still have no response,
// sleep a while waiting for one.
//
// (It's too bad I have to poll. But there's no way to create a
// wait on any of the above to come back. Perhaps, if there is exactly
// one object to wait on ...) ??
//
if ( Response == NULL ) {
ElapsedTime = NetpDcElapsedTime( StartTime );
#ifndef NETTEST_UTILITY
if ( ElapsedTime != BeginElapsedTime &&
ElapsedTime-BeginElapsedTime > 25 ) {
NlPrint((NL_CRITICAL,
"NetpDcGetPingResponse: it took %ld msecs to poll\n",
ElapsedTime-BeginElapsedTime ));
}
#endif // NETTEST_UTILITY
if ( ElapsedTime >= Timeout) {
NetStatus = ERROR_SEM_TIMEOUT;
goto Cleanup;
}
LocalTimeout = Timeout - ElapsedTime;
#ifdef notdef
NlPrint((NL_CRITICAL,
"NetpDcGetPingResponse: timeout is %ld %ld %ld %ld\n",
StartTime,
ElapsedTime,
Timeout,
LocalTimeout ));
#endif // notdef
//
// Since I'm polling, don't wait too long.
//
Sleep( min( LocalTimeout, NL_DC_MIN_PING_TIMEOUT ) );
}
}
/* NOT REACHED */
Cleanup:
if ( Berval != NULL ) {
ldap_value_free_len( Berval );
}
if ( LdapMessage != NULL ) {
ldap_msgfree( LdapMessage );
}
return NetStatus;
}
VOID
NetpDcFreeAddressList(
IN PNL_GETDC_CONTEXT Context
)
/*++
Routine Description:
This routine frees the address list associated with the current context.
Arguments:
Context - Context describing the GetDc operation.
Return Value:
None.
--*/
{
PNL_DC_ADDRESS DcAddress;
PLIST_ENTRY ListEntry;
//
// Loop deleting existing addresses.
//
while ( !IsListEmpty( &Context->DcAddressList ) ) {
ListEntry = RemoveHeadList( &Context->DcAddressList );
DcAddress = CONTAINING_RECORD( ListEntry, NL_DC_ADDRESS, Next );
//
// Free this DcAddress
//
if ( DcAddress->LdapHandle != NULL ) {
ldap_unbind( DcAddress->LdapHandle );
}
if ( DcAddress->DnsHostName != NULL ) {
NetApiBufferFree( DcAddress->DnsHostName );
}
LocalFree( DcAddress );
}
Context->DcAddressCount = 0;
}
NET_API_STATUS
NetpDcPingListIp(
IN PNL_GETDC_CONTEXT Context,
IN PNL_DC_ADDRESS FirstAddress OPTIONAL,
IN BOOL WaitForResponce,
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry OPTIONAL,
OUT PBOOL UsedNetbios OPTIONAL,
OUT PULONG DcPingCount
)
/*++
Routine Description:
This routine determines the name/address of a DC with the specified
characteristics using an IP-only algorithm.
Arguments:
Context - Context describing the GetDc operation.
FirstAddress - If specified, this must be one of the entries in
Context->DcAddressList. Only this entry and entries following it in
the list will be pinged.
WaitForResponce - TRUE if need to wait for ping responces (by calling
NetpDcGetPingResponse after each ping). TRUE is used when this is
synchronous DC discovery as with DsGetDcName. If FALSE, the pings
are sent asynchronously.
NlDcCacheEntry - On success, returns a pointer to the cache entry
describing the found DC. This entry must be dereferenced using
NetpDcDerefCacheEntry. Optional if WaitForResponce is FALSE.
UsedNetbios - Returns TRUE if the netbios domain name was used to match
the returned cache entry. Optional if WaitForResponce is FALSE.
DcPingCount - Returns the number of DC's pinged.
If WaitForResponce is TRUE, use DcPingCount only if the return status is
ERROR_SEM_TIMEOUT.
Return Value:
NO_ERROR: The NlDcCacheEntry was returned if WaitForResponce was TRUE.
If WaitForResponce was FALSE, pings have been attempted to all addresses
specified in Context->DcAddressList, but there is no guarantee that all
the pings were successfully sent. The caller should check the value of
DcPingCount to determine the number of successfully pinged DCs.
ERROR_NO_SUCH_DOMAIN: The specified domain does not exist.
(Definitive status that we need not try again.)
ERROR_SEM_TIMEOUT: No DC responded to the request.
(Non-definitive status that we should try again.)
ERROR_INTERNAL_ERROR: Unhandled situation detected.
--*/
{
NET_API_STATUS NetStatus;
NTSTATUS Status;
ULONG AddressIndex;
PLIST_ENTRY ListEntry;
PNL_DC_ADDRESS DcAddress;
char *AttributeList[2];
int LdapMessageId;
NlAssert( NlDcCacheEntry != NULL ||
(NlDcCacheEntry == NULL && !WaitForResponce) );
NlAssert( UsedNetbios != NULL ||
(UsedNetbios == NULL && !WaitForResponce) );
//
// Loop through the list pinging each entry.
//
*DcPingCount = 0;
if ( FirstAddress == NULL ) {
ListEntry = Context->DcAddressList.Flink;
} else {
ListEntry = &FirstAddress->Next;
}
for ( ;
ListEntry != &Context->DcAddressList ;
ListEntry = ListEntry->Flink) {
DcAddress = CONTAINING_RECORD( ListEntry, NL_DC_ADDRESS, Next );
//
// If we're certain this DC won't work,
// skip it.
//
if ( DcAddress->AddressFlags & NL_DC_ADDRESS_NEVER_TRY_AGAIN ) {
continue;
}
//
// Send the ping.
//
//
// Open a connection to the server unless we already have one
//
if ( DcAddress->LdapHandle == NULL ) {
//
// Get an LDAP handle to the server.
//
DcAddress->LdapHandle = cldap_openA( DcAddress->SockAddrString, 0 );
if ( DcAddress->LdapHandle == NULL ) {
NetStatus = GetLastError();
NlPrint(( NL_CRITICAL,
"NetpDcPingListIp: %ws: Cannot LdapOpen ip address %s: %ld\n",
Context->QueriedDisplayDomainName,
DcAddress->SockAddrString,
NetStatus ));
// Some statuses should be fatal ??
continue;
}
}
//
// Ping the server using UDP LDAP.
//
// Get the Netlogon parameters of the server.
//
NlPrint(( NL_MAILSLOT,
"NetpDcPingListIp: %ws: Sent UDP ping to %s\n",
Context->QueriedDisplayDomainName,
DcAddress->SockAddrString ));
AttributeList[0] = NETLOGON_LDAP_ATTRIBUTE;
AttributeList[1] = NULL;
LdapMessageId = ldap_searchA(
DcAddress->LdapHandle,
NULL, // DN
LDAP_SCOPE_BASE,
Context->LdapFilter,
AttributeList,
FALSE ); // Attributes and values
if ( LdapMessageId == -1 ) {
NlPrint(( NL_CRITICAL,
"NetpDcPingListIp: %ws: Cannot LdapOpen ip address %s: %ld %s\n",
Context->QueriedDisplayDomainName,
DcAddress->SockAddrString,
DcAddress->LdapHandle->ld_errno,
ldap_err2stringA(DcAddress->LdapHandle->ld_errno) ));
// Some statuses should be fatal ??
continue;
}
//
// Count the number of DCs we've pinged.
//
(*DcPingCount) ++;
if ( WaitForResponce ) {
//
// Get the response from the ping.
//
NlAssert( DcAddress->AddressPingWait != 0 );
NetStatus = NetpDcGetPingResponse(
Context,
DcAddress->AddressPingWait,
NlDcCacheEntry,
UsedNetbios );
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcPingListIp: %ws: Cannot NetpDcGetPingResponse. %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
}
}
if ( WaitForResponce ) {
NetStatus = ERROR_SEM_TIMEOUT;
} else {
NetStatus = NO_ERROR;
}
Cleanup:
return NetStatus;
}
NET_API_STATUS
NetpDcPingIp(
IN PNL_GETDC_CONTEXT Context,
OUT PULONG DcPingCount
)
/*++
Routine Description:
This routine sends a ping to a DC with the specified
characteristics using an IP-only algorithm.
Arguments:
Context - Context describing the GetDc operation.
DcPingCount - Returns the number of DC's pinged.
Return Value:
NO_ERROR: Pings have been attempted to all addresses
specified in Context->DcAddressList, but there is no guarantee that all
the pings were successfully sent. The caller should check the value of
DcPingCount to determine the number of successfully pinged DCs.
ERROR_NO_SUCH_DOMAIN: The specified domain does not exist.
(Definitive status that we need not try again.)
ERROR_SEM_TIMEOUT: No DC responded to the request.
(Non-definitive status that we should try again.)
ERROR_INTERNAL_ERROR: Unhandled situation detected.
--*/
{
return NetpDcPingListIp( Context,
NULL,
FALSE,
NULL,
NULL,
DcPingCount );
}
NET_API_STATUS
NetpDcGetDcOpen(
IN LPCSTR DnsName,
IN ULONG OptionFlags,
IN LPCWSTR SiteName OPTIONAL,
IN GUID *DomainGuid OPTIONAL,
IN LPCSTR DnsForestName OPTIONAL,
IN ULONG Flags,
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 - UTF-8 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.
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.
Flags - Passes additional information to be used to process the request.
Flags 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 NetpDcGetDcClose.
Return Value:
Status of the operation.
NO_ERROR: GetDcContext was returned successfully.
--*/
{
NET_API_STATUS NetStatus = NO_ERROR;
PDSGETDC_CONTEXT GetDcContext = NULL;
ULONG Size;
//
// Verify the DC flags
//
if ( Flags & ~DS_OPEN_VALID_FLAGS ) {
NetStatus = ERROR_INVALID_FLAGS;
goto Cleanup;
}
//
// Verify the option flags
//
if ( OptionFlags & ~DS_OPEN_VALID_OPTION_FLAGS ) {
NetStatus = ERROR_INVALID_FLAGS;
goto Cleanup;
}
//
// Allocate a context
//
GetDcContext = LocalAlloc( LMEM_ZEROINIT, sizeof(DSGETDC_CONTEXT) );
if ( GetDcContext == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// If the name has a well known prefix,
// strip off the prefix and convert it to a flag bit.
//
// An LDAP client gets a name of this from in a GC referral. By converting
// that name to this form, I will find a site specific GC.
//
if ( Flags == 0 ) {
if ( _strnicmp( DnsName, NL_DNS_GC, sizeof(NL_DNS_GC)-1) == 0 ) {
DnsName += sizeof(NL_DNS_GC)-1;
Flags |= DS_GC_SERVER_REQUIRED;
} else if ( _strnicmp( DnsName, NL_DNS_PDC, sizeof(NL_DNS_PDC)-1) == 0 ) {
DnsName += sizeof(NL_DNS_PDC)-1;
Flags |= DS_PDC_REQUIRED;
}
}
//
// Fill in the DNS name
//
Size = (strlen(DnsName) + 1) * sizeof(char);
GetDcContext->QueriedDnsName = LocalAlloc( 0, Size );
if ( GetDcContext->QueriedDnsName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
RtlCopyMemory( GetDcContext->QueriedDnsName, DnsName, Size );
//
// Fill in the forest name if specified
//
if ( ARGUMENT_PRESENT(DnsForestName) ) {
Size = (strlen(DnsForestName) + 1) * sizeof(char);
GetDcContext->QueriedDnsForestName = LocalAlloc( 0, Size );
if ( GetDcContext->QueriedDnsForestName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
RtlCopyMemory( GetDcContext->QueriedDnsForestName, DnsForestName, Size );
}
//
// Fill in the site name if specified
//
if ( ARGUMENT_PRESENT(SiteName) ) {
Size = (wcslen(SiteName) + 1) * sizeof(WCHAR);
GetDcContext->QueriedSiteName = LocalAlloc( 0, Size );
if ( GetDcContext->QueriedSiteName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
RtlCopyMemory( GetDcContext->QueriedSiteName, SiteName, Size );
}
//
// Fill in flags
//
GetDcContext->QueriedInternalFlags = OptionFlags;
//
// Fill in domain GUID if specified
//
if ( ARGUMENT_PRESENT( DomainGuid ) ) {
GetDcContext->QueriedDomainGuid = *DomainGuid;
}
//
// Compute the initial DNS name type to query.
//
GetDcContext->FirstTime = TRUE;
GetDcContext->QueriedFlags = Flags;
NetStatus = NetpDcFlagsToNameType( Flags, &GetDcContext->NlDnsNameType );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
//
// If a site name was specified by the caller,
// and this name type supports a type specific query,
// start with the type specific query.
//
if ( GetDcContext->QueriedSiteName != NULL ) {
if ( NlDcDnsNameTypeDesc[GetDcContext->NlDnsNameType].SiteSpecificDnsNameType != NlDnsInvalid ) {
GetDcContext->NlDnsNameType = NlDcDnsNameTypeDesc[GetDcContext->NlDnsNameType].SiteSpecificDnsNameType;
}
}
//
// Return the context to the caller.
//
*RetGetDcContext = GetDcContext;
NetStatus = NO_ERROR;
//
// Cleanup
//
Cleanup:
if ( NetStatus != NO_ERROR ) {
if ( GetDcContext != NULL ) {
NetpDcGetDcClose( GetDcContext );
}
}
return NetStatus;
}
NET_API_STATUS
NetpDcGetDcNext(
IN HANDLE GetDcContextHandle,
OUT PULONG SockAddressCount OPTIONAL,
OUT LPSOCKET_ADDRESS *SockAddresses OPTIONAL,
OUT LPSTR *DnsHostName OPTIONAL,
OUT PULONG InitSrvRecordCount OPTIONAL
)
/*++
Routine Description:
Returns the next logical SRV record for the name opened by NetpDcGetDcOpen.
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.
This buffer need not be freed. The buffer is valid until the call to
NetpDcGetDcClose or the next call to NetpDcGetDcNext.
InitSrvRecordCount - This parameter returns the number of SRV records
returned by DNS query on the initial invocation of this routine
on a given discovery. If this is not the initial invocation (but
a subsequent call to get the next DC address), this parameter is
not used. Note that this parameter may be set even if this routine
fails (to query A records).
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 NetpDcGetDcNext 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 NetpDcGetDcNext 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 NetpDcGetDcNext
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;
PDSGETDC_CONTEXT GetDcContext = (PDSGETDC_CONTEXT) GetDcContextHandle;
PDNS_RECORD *DnsArray;
PDNS_RECORD SrvDnsRecord;
PDNS_RECORD DnsARecords = NULL;
CHAR DnsName[NL_MAX_DNS_LENGTH+1];
BOOLEAN NotifySiteChange = FALSE;
GUID *CurrentGuid;
LPCSTR CurrentDnsRecordName;
ULONG Index;
//
// Loop trying the various DNS record names.
//
for (;;) {
//
// If we aren't still processing a set of SRV records from the previous call,
// move on to the next name.
//
if ( GetDcContext->SrvContextHandle == NULL ) {
CurrentDnsRecordName = GetDcContext->QueriedDnsName;
//
// If this isn't the first call,
// compute the next DNS name to query.
//
if ( !GetDcContext->FirstTime ) {
//
// If we just completed the site specific records,
// and we've been asked to tell the caller when that's done,
// remember to tell the caller.
//
// Don't actually notify the caller until right before we're going to hit
// the wire.
//
if ( NlDcDnsNameTypeDesc[GetDcContext->NlDnsNameType].IsSiteSpecific &&
(GetDcContext->QueriedInternalFlags & DS_NOTIFY_AFTER_SITE_RECORDS) != 0 ) {
NotifySiteChange = TRUE;
}
//
// Compute the next name type to query.
//
GetDcContext->NlDnsNameType = NlDcDnsNameTypeDesc[GetDcContext->NlDnsNameType].NextDnsNameType;
if ( GetDcContext->NlDnsNameType == NlDnsInvalid ) {
//
// No more names to process.
//
NetStatus = ERROR_NO_MORE_ITEMS;
goto Cleanup;
}
//
// If the current name type is not a site specific name type,
// and we've been asked to do only site specific names.
// we're done.
//
if ( !NlDcDnsNameTypeDesc[GetDcContext->NlDnsNameType].IsSiteSpecific &&
(GetDcContext->QueriedInternalFlags & DS_ONLY_DO_SITE_NAME) != 0 ) {
NetStatus = ERROR_NO_MORE_ITEMS;
goto Cleanup;
}
//
// If this is the "by guid" name but we don't have a guid or forest name,
// go to the next name.
//
if ( NlDnsDcGuid( GetDcContext->NlDnsNameType ) ) {
//
// If no domain GUID was specified,
// go to the next name.
//
if ( IsEqualGUID( &GetDcContext->QueriedDomainGuid, &NlDcZeroGuid) ) {
continue;
}
//
// Otherwise try to find the domain by GUID
// This name is registered at the tree name.
//
if ( GetDcContext->QueriedDnsForestName == NULL ) {
continue;
}
CurrentDnsRecordName = GetDcContext->QueriedDnsForestName;
}
}
GetDcContext->FirstTime = FALSE;
//
// If we are to notify the caller when we're done with the site specific records,
// do so now.
//
if ( NotifySiteChange ) {
//
// We've already decided what SRV records to look up next.
// Flag that we've done so.
//
GetDcContext->FirstTime = TRUE;
NetStatus = ERROR_FILEMARK_DETECTED;
goto Cleanup;
}
//
// Build the DNS name to query.
//
NetStatus = NetpDcBuildDnsName(
GetDcContext->NlDnsNameType,
&GetDcContext->QueriedDomainGuid,
GetDcContext->QueriedSiteName,
CurrentDnsRecordName,
DnsName );
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetDcNext: %s: %ld: Cannot NetpDcBuildDnsName. %ld\n",
CurrentDnsRecordName,
GetDcContext->NlDnsNameType,
NetStatus ));
goto Cleanup;
}
//
// Get the SRV records from DNS.
//
NetStatus = NetpSrvOpen( DnsName,
(GetDcContext->QueriedFlags & DS_FORCE_REDISCOVERY) != 0 ?
DNS_QUERY_BYPASS_CACHE :
0,
&GetDcContext->SrvContextHandle );
if ( NetStatus != NO_ERROR ) {
//
// If the specified record cannot be found in DNS,
// try the next name type.
//
if ( NlDcNoDnsRecord( NetStatus ) ) {
continue;
}
NlPrint(( NL_CRITICAL,
"NetpDcGetDcNext: %s: Cannot Query DNS. %ld 0x%lx\n",
DnsName,
NetStatus,
NetStatus ));
goto Cleanup;
}
//
// If asked, return the number of SRV records found
//
if ( InitSrvRecordCount != NULL ) {
*InitSrvRecordCount = NetpSrvGetRecordCount( GetDcContext->SrvContextHandle );
}
}
//
// If we've got more SRV records to process for this DnsName,
// get the next SRV record.
//
NetStatus = NetpSrvNext( GetDcContext->SrvContextHandle,
SockAddressCount,
SockAddresses,
DnsHostName );
if ( NetStatus == NO_ERROR ) {
goto Cleanup;
//
// If we're done with this set of SRV records mark so for next time.
//
} else if ( NetStatus == ERROR_NO_MORE_ITEMS ) {
NetpSrvClose( GetDcContext->SrvContextHandle );
GetDcContext->SrvContextHandle = NULL;
continue;
//
// All other statuses are simply returned to our caller.
//
} else {
NlPrint(( NL_CRITICAL,
"NetpDcGetDcNext: %s: %ld: Cannot NetpSrvNext. %ld 0x%lx\n",
GetDcContext->QueriedDnsName,
GetDcContext->NlDnsNameType,
NetStatus,
NetStatus ));
goto Cleanup;
}
}
ASSERT( FALSE );
Cleanup:
if ( NlDcNoDnsRecord( NetStatus ) ) {
NetStatus = DNS_ERROR_RCODE_NAME_ERROR;
}
return NetStatus;
}
VOID
NetpDcGetDcClose(
IN HANDLE GetDcContextHandle
)
/*++
Routine Description:
Free the context allocated by NetpDcGetDcOpen
Arguments:
GetDcContextHandle - An opaque context describing the SRV records.
Return Value:
Status of the operation.
NO_ERROR: GetDcContext was returned successfully.
--*/
{
PDSGETDC_CONTEXT GetDcContext = (PDSGETDC_CONTEXT) GetDcContextHandle;
if ( GetDcContext != NULL ) {
//
// Free allocated names
//
if ( GetDcContext->QueriedDnsName != NULL ) {
LocalFree( GetDcContext->QueriedDnsName );
}
if ( GetDcContext->QueriedSiteName != NULL ) {
LocalFree( GetDcContext->QueriedSiteName );
}
if ( GetDcContext->QueriedDnsForestName != NULL ) {
LocalFree( GetDcContext->QueriedDnsForestName );
}
//
// Free the SRV context
//
if ( GetDcContext->SrvContextHandle != NULL ) {
NetpSrvClose( GetDcContext->SrvContextHandle );
}
//
// Free the context itself
//
LocalFree( GetDcContext );
}
}
NET_API_STATUS
NetpDcGetNameSiteIp(
IN PNL_GETDC_CONTEXT Context,
IN ULONG InternalFlags,
IN LPCWSTR SiteName OPTIONAL,
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry,
OUT PBOOL UsedNetbios
)
/*++
Routine Description:
This routine determines the name/address of a DC with the specified
characteristics using an IP-only algorithm.
Arguments:
Context - Context describing the GetDc operation.
InternalFlags - Flags affecting the operation of the routine.
DS_ONLY_DO_SITE_NAME - Non-site names should be ignored.
SiteName - Specifies the 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.
NlDcCacheEntry - On success, returns a pointer to the cache entry
describing the found DC.
This entry must be dereferenced using NetpDcDerefCacheEntry.
UsedNetbios - Returns TRUE if the netbios domain name was used to match
the returned cache entry.
Return Value:
NO_ERROR: The NlDcCacheEntry was returned;
ERROR_NO_SUCH_DOMAIN: The specified domain does not exist.
(Definitive status that we need not try again.)
ERROR_SEM_TIMEOUT: No DC responded to the request.
(Non-definitive status that we should try again.)
ERROR_DNS_NOT_CONFIGURED: IP or DNS is not available on this computer.
ERROR_INTERNAL_ERROR: Unhandled situation detected.
ERROR_INVALID_DOMAINNAME: Domain's name is too long. Additional labels
cannot be concatenated.
ERROR_NOT_ENOUGH_MEMORY: Not enough memory is available to process
this request.
Various Winsock errors.
--*/
{
NET_API_STATUS NetStatus;
HANDLE DsGetDcHandle = NULL;
PSOCKET_ADDRESS SockAddressList = NULL;
ULONG SockAddressCount;
PNL_DC_ADDRESS FirstDcToQuery;
PNL_DC_ADDRESS DcAddress;
ULONG DcPingCount = 0;
ULONG LocalMaxLdapServersPinged = 0xffffffff;
BOOLEAN SiteSpecificRecords = FALSE;
BOOLEAN DnsRecordFound = FALSE;
LPSTR Utf8DnsDomainName = NULL;
LPSTR Utf8DnsForestName = NULL;
LPSTR Utf8DnsHostName = NULL;
LPWSTR UnicodeDnsHostName = NULL;
//
// Ping the list of DCs found on the previous call.
//
NetStatus = NetpDcPingListIp(
Context,
NULL,
TRUE, // Wait for ping responce
NlDcCacheEntry,
UsedNetbios,
&DcPingCount );
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: Cannot NetpDcPingListIp. %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
//
// Convert the DNS name to Utf8
//
Utf8DnsDomainName = NetpAllocUtf8StrFromWStr( Context->QueriedDnsDomainName );
if ( Utf8DnsDomainName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
if ( Context->QueriedDnsForestName != NULL ) {
Utf8DnsForestName = NetpAllocUtf8StrFromWStr( Context->QueriedDnsForestName );
if ( Utf8DnsForestName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
}
//
// Determine if we are doing site specific discovery
//
if ( SiteName != NULL &&
NlDcDnsNameTypeDesc[Context->QueriedNlDnsNameType].SiteSpecificDnsNameType != NlDnsInvalid ) {
SiteSpecificRecords = TRUE;
}
//
// Get a context for the DNS name queries.
//
NetStatus = NetpDcGetDcOpen( Utf8DnsDomainName,
DS_NOTIFY_AFTER_SITE_RECORDS | InternalFlags,
SiteName,
Context->QueriedDomainGuid,
Utf8DnsForestName,
Context->QueriedFlags & DS_OPEN_VALID_FLAGS,
&DsGetDcHandle );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Determine the maximum number of DCs we can ping
//
#ifdef _NETLOGON_SERVER
//
// In netlogon, the value is kept in global parameters
//
LocalMaxLdapServersPinged = NlGlobalParameters.MaxLdapServersPinged;
#else
//
// If we are not running in netlogon, we need to read
// the value directly from the registry
//
if ( !NlReadDwordNetlogonRegValue("MaxLdapServersPinged",
&LocalMaxLdapServersPinged) ) {
//
// If it's not set in registry, use the default
//
LocalMaxLdapServersPinged = DEFAULT_MAXLDAPSERVERSPINGED;
} else {
//
// Ensure that the value set in registry is in the valid range
//
if ( LocalMaxLdapServersPinged < MIN_MAXLDAPSERVERSPINGED ||
LocalMaxLdapServersPinged > MAX_MAXLDAPSERVERSPINGED ) {
LocalMaxLdapServersPinged = DEFAULT_MAXLDAPSERVERSPINGED;
}
}
#endif // _NETLOGON_SERVER
//
// Loop getting new addresses to query.
//
// Note that on the second invocation of this routine
// from the loop in NetpDcGetName we will get new addresses
// only if DNS got updated between the two invocations which
// is unlikely, but we'll try anyway.
//
// Note also that DcsPinged isn't nulified before the below
// loop as it is incremented for new DCs/addresses only.
//
Context->SiteSpecificFailedAQueryCount = 0;
for ( ;; ) {
//
// Free any memory from a previous iteration.
//
FirstDcToQuery = NULL;
if ( SockAddressList != NULL ) {
LocalFree( SockAddressList );
SockAddressList = NULL;
}
//
// Get the next set of IP addresses from DNS
//
NetStatus = NetpDcGetDcNext( DsGetDcHandle,
&SockAddressCount,
&SockAddressList,
&Utf8DnsHostName,
SiteSpecificRecords ?
&Context->SiteSpecificSrvRecordCount :
NULL );
//
// Process the exeptional conditions
//
if ( NetStatus == NO_ERROR ) {
//
// Since a SRV record was found, the only reason to not find the DC is if
// the DC is down. That isn't a permanent condition.
//
Context->AvoidPermanentNegativeCache = TRUE;
//
// Indicate that DNS is up and running.
//
Context->ResponseFromDnsServer = TRUE;
//
// Indicate whether site specific records are available
//
if ( SiteSpecificRecords ) {
Context->ContextFlags |= NL_GETDC_SITE_SPECIFIC_DNS_AVAIL;
}
/* Drop out */
//
// If the A record cannot be found for the SRV record in DNS,
// try the other name type.
//
} else if ( NetStatus == DNS_ERROR_RCODE_NAME_ERROR) {
//
// Since a SRV record was found, the only reason to not find the DC is if
// the DC is down. That isn't a permanent condition.
//
Context->AvoidPermanentNegativeCache = TRUE;
//
// Indicate that DNS is up and running.
//
Context->ResponseFromDnsServer = TRUE;
//
// Increment the number of times site specific
// DNS A query failed
//
if ( SiteSpecificRecords ) {
Context->SiteSpecificFailedAQueryCount ++;
}
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: cannot find A record.\n",
Context->QueriedDisplayDomainName ));
continue;
//
// If we've processed all of the site specific SRV records and are about to move on,
// wait a little longer for the site specific DCs to respond.
//
} else if ( NetStatus == ERROR_FILEMARK_DETECTED ) {
//
// Only do this if there actually were site specific SRV records.
//
if ( DcPingCount ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: site specific SRV records done.\n",
Context->QueriedDisplayDomainName ));
//
// Get the response from the ping.
//
NetStatus = NetpDcGetPingResponse(
Context,
NL_DC_MED_PING_TIMEOUT, // wait for median time
NlDcCacheEntry,
UsedNetbios );
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: Cannot NetpDcGetPingResponse. %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
}
//
// Indicate that all subsequent addresses are retrived as a result
// of non-site-specific DNS record lookup.
//
SiteSpecificRecords = FALSE;
continue;
//
// If we're done,
// break out of the loop.
//
} else if ( NetStatus == ERROR_NO_MORE_ITEMS ) {
//
// Indicate that DNS is up and running.
//
Context->ResponseFromDnsServer = TRUE;
break;
//
// If DNS isn't available,
// blow this request away.
//
} else if ( NetStatus == ERROR_TIMEOUT ||
NetStatus == DNS_ERROR_RCODE_SERVER_FAILURE ) { // Server failed
//
// DNS servers being down isn't a permanent condition.
//
Context->AvoidPermanentNegativeCache = TRUE;
break;
//
// If IP or DNS is not configured,
// tell the caller.
//
} else if ( NetStatus == DNS_ERROR_NO_TCPIP || // TCP/IP not configured
NetStatus == DNS_ERROR_NO_DNS_SERVERS ) { // DNS not configured
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: IP Not configured from DnsQuery.\n",
Context->QueriedDisplayDomainName ));
NetStatus = ERROR_DNS_NOT_CONFIGURED;
goto Cleanup;
//
// We don't handle any other error.
//
} else {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: Unknown error from DnsQuery. %ld 0x%lx\n",
Context->QueriedDisplayDomainName,
NetStatus,
NetStatus ));
goto Cleanup;
}
DnsRecordFound = TRUE;
//
// Add new addresses to the list
//
if ( UnicodeDnsHostName != NULL ) {
NetApiBufferFree( UnicodeDnsHostName );
UnicodeDnsHostName = NULL;
}
UnicodeDnsHostName = NetpAllocWStrFromUtf8Str( Utf8DnsHostName );
if ( UnicodeDnsHostName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
NetStatus = NetpDcProcessAddressList( Context,
UnicodeDnsHostName,
SockAddressList,
SockAddressCount,
SiteSpecificRecords,
&FirstDcToQuery );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Only process this list if new entries were added.
//
if ( FirstDcToQuery != NULL ) {
ULONG LocalDcPingCount = 0;
//
// Ping the new list of DCs.
//
NetStatus = NetpDcPingListIp(
Context,
FirstDcToQuery,
TRUE, // Wait for ping responce
NlDcCacheEntry,
UsedNetbios,
&LocalDcPingCount );
//
// If we sent a ping to at least one address for this DC,
// count this DC in the number of DCs pinged
//
if ( LocalDcPingCount > 0 ) {
Context->DcsPinged ++;
}
//
// Check error conditions
//
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: Cannot NetpDcPingListIp. %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
//
// Update the number of pings we sent
//
DcPingCount += LocalDcPingCount;
//
// Stop getting new addresses if we have reached
// the limit on the number of DCs we can ping
//
if ( Context->DcsPinged >= LocalMaxLdapServersPinged ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameSiteIp: %ws: Reached the DC limit %lu %lu\n",
Context->QueriedDisplayDomainName,
Context->DcsPinged,
LocalMaxLdapServersPinged ));
break;
}
}
}
//
// If no DNS records could be found,
// this is a definitive failure.
//
if ( !DnsRecordFound ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: No data returned from DnsQuery.\n",
Context->QueriedDisplayDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// If we could not send a ping to any of the DCs,
// or if there are no more DCs to ping,
// this is a definitive failure.
//
if ( DcPingCount == 0 || Context->DcAddressCount == 0 ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameIp: %ws: Couldn't ping any DCs.\n",
Context->QueriedDisplayDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
NetStatus = ERROR_SEM_TIMEOUT;
Cleanup:
if ( SockAddressList != NULL) {
LocalFree( SockAddressList );
}
if ( DsGetDcHandle != NULL ) {
NetpDcGetDcClose( DsGetDcHandle );
}
if ( Utf8DnsDomainName != NULL ) {
NetpMemoryFree( Utf8DnsDomainName );
}
if ( Utf8DnsForestName != NULL ) {
NetpMemoryFree( Utf8DnsForestName );
}
if ( UnicodeDnsHostName != NULL ) {
NetApiBufferFree( UnicodeDnsHostName );
}
//
// Note that Utf8DnsHostName should not be freed
// as it wasn't allocated
//
return NetStatus;
}
NET_API_STATUS
NetpDcGetNameIp(
IN PNL_GETDC_CONTEXT Context,
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry,
OUT PBOOL UsedNetbios
)
/*++
Routine Description:
This routine determines the name/address of a DC with the specified
characteristics using an IP-only algorithm.
This routine handles the case where the site of the DC found isn't the
'closest' site to the client. In that case, the DC found will indicate
which site is the closest site. This routine will try to find a DC in that
closest site.
Arguments:
Context - Context describing the GetDc operation.
NlDcCacheEntry - On success, returns a pointer to the cache entry
describing the found DC.
This entry must be dereferenced using NetpDcDerefCacheEntry.
UsedNetbios - Returns TRUE if the netbios domain name was used to match
the returned cache entry.
Return Value:
NO_ERROR: The NlDcCacheEntry was returned;
ERROR_NO_SUCH_DOMAIN: The specified domain does not exist.
(Definitive status that we need not try again.)
ERROR_SEM_TIMEOUT: No DC responded to the request.
(Non-definitive status that we should try again.)
ERROR_DNS_NOT_CONFIGURED: IP or DNS is not available on this computer.
ERROR_INTERNAL_ERROR: Unhandled situation detected.
ERROR_INVALID_DOMAINNAME: Domain's name is too long. Additional labels
cannot be concatenated.
ERROR_NOT_ENOUGH_MEMORY: Not enough memory is available to process
this request.
Various Winsock errors.
--*/
{
NET_API_STATUS NetStatus;
PNL_DC_CACHE_ENTRY ClosestNlDcCacheEntry;
BOOL ClosestUsedNetbios;
//
// Try the operation as it was passed to us.
//
NetStatus = NetpDcGetNameSiteIp( Context,
Context->DoingExplicitSite ?
DS_ONLY_DO_SITE_NAME :
0, // No flags this time
Context->QueriedSiteName,
NlDcCacheEntry,
UsedNetbios );
if ( NetStatus != NO_ERROR ) {
return NetStatus;
}
//
// If the queried DC type doesn't have site specific DCs,
// return the found DC to the caller.
//
if ( NlDcDnsNameTypeDesc[Context->QueriedNlDnsNameType].SiteSpecificDnsNameType == NlDnsInvalid ) {
return NO_ERROR;
}
//
// If the caller explicitly specified a sitename,
// return the found DC to the caller.
//
if ( Context->DoingExplicitSite ) {
return NO_ERROR;
}
//
// If the responding DC is in the closest site,
// or the DC doesn't know the closest site,
// or we've already tried the closest site (this case shouldn't happen),
// return the found DC to the caller.
//
if ( ((*NlDcCacheEntry)->ReturnFlags & DS_CLOSEST_FLAG ) != 0 ||
(*NlDcCacheEntry)->UnicodeClientSiteName == NULL ||
(Context->QueriedSiteName != NULL &&
_wcsicmp( (*NlDcCacheEntry)->UnicodeClientSiteName,
Context->QueriedSiteName ) == 0 ) ) {
return NO_ERROR;
}
//
// Free up any existing addresses that have been pinged.
//
// We're starting the operation over again.
NetpDcFreeAddressList( Context );
//
// Try the operation again just trying to find a DC in the right site.
//
NlPrint(( NL_MISC,
"NetpDcGetNameIp: %ws Trying to find a DC in a closer site: %ws\n",
Context->QueriedDisplayDomainName,
(*NlDcCacheEntry)->UnicodeClientSiteName ));
NetStatus = NetpDcGetNameSiteIp( Context,
DS_ONLY_DO_SITE_NAME,
(*NlDcCacheEntry)->UnicodeClientSiteName,
&ClosestNlDcCacheEntry,
&ClosestUsedNetbios );
if ( NetStatus != NO_ERROR ) {
return NO_ERROR;
}
//
// If we found a closer DC,
// ditch the first entry and use the new one.
//
NetpDcDerefCacheEntry( *NlDcCacheEntry );
*NlDcCacheEntry = ClosestNlDcCacheEntry;
*UsedNetbios = ClosestUsedNetbios;
return NO_ERROR;
}
NET_API_STATUS
NetpDcGetNameNetbios(
PNL_GETDC_CONTEXT Context,
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry,
OUT PBOOL UsedNetbios
)
/*++
Routine Description:
This routine determines the name/address of a DC with the specified
characteristics using a Netbios algorithm.
Arguments:
Context - Context describing the GetDc operation.
NlDcCacheEntry - On success, returns a pointer to the cache entry
describing the found DC.
This entry must be dereferenced using NetpDcDerefCacheEntry.
UsedNetbios - Returns TRUE if the netbios domain name was used to match
the returned cache entry.
Return Value:
NO_ERROR: The NlDcCacheEntry was returned;
ERROR_NO_SUCH_DOMAIN: The specified domain does not exist.
(Definitive status that we need not try again.)
ERROR_SEM_TIMEOUT: No DC responded to the request.
(Non-definitive status that we should try again.)
ERROR_INTERNAL_ERROR: Unhandled situation detected.
ERROR_INVALID_DOMAINNAME: Domain's name is too long. Additional labels
cannot be concatenated.
ERROR_NOT_ENOUGH_MEMORY: Not enough memory is available to process
this request.
--*/
{
NET_API_STATUS NetStatus;
NTSTATUS Status;
BOOL Flush1cName = FALSE;
BOOL Flush1bName = FALSE;
//
// Avoid querying for GCs.
//
// GCs don't have their own Netbios name. I could simply send to the 1C name, but
// 1) It would be wasteful to send to all of the DCs when only some are GCs.
// 2) WINS only registers a max of 25 DC addresses per name.
//
if ( NlDnsGcName(Context->QueriedNlDnsNameType) ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameNetbios: %ws: Cannot query for GC using netbios.\n",
Context->QueriedDisplayDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// Flush Netbios cache if this is forced rediscovery
//
if ( Context->QueriedFlags & DS_FORCE_REDISCOVERY ) {
Flush1bName = TRUE;
Flush1cName = TRUE;
}
//
// If there is an alternate ping message,
// send it first.
//
if ( Context->AlternatePingMessageSize != 0 ) {
//
// If only the PDC should responsd,
// send the alternate ping to DomainName[1B].
//
// If any DC can respond,
// broadcast it to DomainName[1C] groupname.
//
//
// If this is a request for a PDC with an account,
// the "primary" message is a normal "primary query" sent for
// backward compatibility with NT 4 and earlier. This message
// is a "logon user" that NT 5 will understand.
//
// (More specifically, NT 4 understands this query but we'll discard
// the response since the response from NT 4 doesn't flag the response
// as being from the PDC.)
//
//
// If this is a request for a writable DC,
// this request is the datagram send of the "logon user" message to DomainName[1C].
//
#if NETLOGONDBG
NlPrint((NL_MAILSLOT,
"Sent '%s' message to %ws[%s] on all transports.\n",
NlMailslotOpcode(((PNETLOGON_LOGON_QUERY)(Context->AlternatePingMessage))->Opcode),
(LPWSTR) Context->QueriedNetbiosDomainName,
NlDgrNameType( Context->DcQueryType == NlDcQueryPdc ?
PrimaryDomainBrowser : // 0x1B name
DomainName))); // 0x1C name
#endif // NETLOGONDBG
Status = NlBrowserSendDatagram(
Context->SendDatagramContext,
(Context->QueriedFlags & DS_IP_REQUIRED ) ? ALL_IP_TRANSPORTS : 0,
(LPWSTR) Context->QueriedNetbiosDomainName,
Context->DcQueryType == NlDcQueryPdc ?
PrimaryDomainBrowser : // 0x1B name
DomainName, // 0x1C name
NULL, // All transports
NETLOGON_LM_MAILSLOT_A,
Context->AlternatePingMessage,
Context->AlternatePingMessageSize,
TRUE, // send synchronously
Context->DcQueryType == NlDcQueryPdc ?
&Flush1bName :
&Flush1cName );
if ( !NT_SUCCESS(Status)) {
NetStatus = NetpNtStatusToApiStatus( Status );
NlPrint(( NL_CRITICAL,
"NetpDcGetNameNetbios: %ws: Cannot NlBrowserSendDatagram. (ALT) %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
if ( NlDcUseGenericStatus(NetStatus) ) {
NetStatus = ERROR_NO_SUCH_DOMAIN;
}
goto Cleanup;
}
//
// Get the response from the ping.
//
NetStatus = NetpDcGetPingResponse(
Context,
NL_DC_MIN_PING_TIMEOUT,
NlDcCacheEntry,
UsedNetbios );
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameNetbios: %ws: Cannot NetpDcGetPingResponse. %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
}
//
// If this is a PDC query,
// Broadcast to DomainName[1B] unique name
// registered only by the PDC. (Currently, only NT 3.5 (and newer) PDCs register
// this name and accept incoming mailslot messages on the name.)
//
if ( Context->DcQueryType == NlDcQueryPdc ||
(Context->QueriedFlags & DS_WRITABLE_REQUIRED) != 0 ) {
#if NETLOGONDBG
NlPrint((NL_MAILSLOT,
"Sent '%s' message to %ws[%s] on all transports.\n",
NlMailslotOpcode(((PNETLOGON_LOGON_QUERY)(Context->PingMessage))->Opcode),
(LPWSTR) Context->QueriedNetbiosDomainName,
NlDgrNameType(PrimaryDomainBrowser))); // 0x1B name
#endif // NETLOGONDBG
Status = NlBrowserSendDatagram(
Context->SendDatagramContext,
(Context->QueriedFlags & DS_IP_REQUIRED ) ? ALL_IP_TRANSPORTS : 0,
(LPWSTR) Context->QueriedNetbiosDomainName,
PrimaryDomainBrowser, // 0x1B name
NULL, // All transports
NETLOGON_LM_MAILSLOT_A,
Context->PingMessage,
Context->PingMessageSize,
TRUE, // send synchronously
&Flush1bName );
if ( !NT_SUCCESS(Status)) {
NetStatus = NetpNtStatusToApiStatus( Status );
NlPrint(( NL_CRITICAL,
"NetpDcGetNameNetbios: %ws: Cannot NlBrowserSendDatagram. (1B) %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
if ( NlDcUseGenericStatus(NetStatus) ) {
NetStatus = ERROR_NO_SUCH_DOMAIN;
}
goto Cleanup;
}
//
// Get the response from the ping.
//
NetStatus = NetpDcGetPingResponse(
Context,
NL_DC_MIN_PING_TIMEOUT,
NlDcCacheEntry,
UsedNetbios );
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameNetbios: %ws: Cannot NetpDcGetPingResponse. %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
}
//
// If this is the second or third iteration,
// or if this isn't a PDC query,
// broadcast to DomainName[1C] groupname
// registered only by DCs. (Currently, only NT DCs register
// this name.)
//
// If this is a request for a writable DC,
// this request is the datagram send of the "primary query" message to DomainName[1C].
//
if ( Context->TryCount != 0 ||
(Context->DcQueryType != NlDcQueryPdc &&
(Context->QueriedFlags & DS_WRITABLE_REQUIRED) == 0 )) {
#if NETLOGONDBG
NlPrint((NL_MAILSLOT,
"Sent '%s' message to %ws[%s] on all transports.\n",
NlMailslotOpcode(((PNETLOGON_LOGON_QUERY)(Context->PingMessage))->Opcode),
(LPWSTR) Context->QueriedNetbiosDomainName,
NlDgrNameType(DomainName))); // 0x1C name
#endif // NETLOGONDBG
Status = NlBrowserSendDatagram(
Context->SendDatagramContext,
(Context->QueriedFlags & DS_IP_REQUIRED ) ? ALL_IP_TRANSPORTS : 0,
(LPWSTR) Context->QueriedNetbiosDomainName,
DomainName, // 0x1C name
NULL, // All transports
NETLOGON_LM_MAILSLOT_A,
Context->PingMessage,
Context->PingMessageSize,
TRUE, // send synchronously
&Flush1cName );
if ( !NT_SUCCESS(Status)) {
NetStatus = NetpNtStatusToApiStatus( Status );
NlPrint(( NL_CRITICAL,
"NetpDcGetNameNetbios: %ws: Cannot NlBrowserSendDatagram. (1C) %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
if ( NlDcUseGenericStatus(NetStatus) ) {
NetStatus = ERROR_NO_SUCH_DOMAIN;
}
goto Cleanup;
}
//
// Get the response from the ping.
//
NetStatus = NetpDcGetPingResponse(
Context,
NL_DC_MIN_PING_TIMEOUT,
NlDcCacheEntry,
UsedNetbios );
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetNameNetbios: %ws: Cannot NetpDcGetPingResponse. %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
}
NetStatus = ERROR_SEM_TIMEOUT;
Cleanup:
return NetStatus;
}
DWORD
NetpDcInitializeContext(
IN PVOID SendDatagramContext OPTIONAL,
IN LPCWSTR ComputerName OPTIONAL,
IN LPCWSTR AccountName OPTIONAL,
IN ULONG AllowableAccountControlBits,
IN LPCWSTR NetbiosDomainName OPTIONAL,
IN LPCWSTR DnsDomainName OPTIONAL,
IN LPCWSTR DnsForestName OPTIONAL,
IN PSID RequestedDomainSid OPTIONAL,
IN GUID *DomainGuid OPTIONAL,
IN LPCWSTR SiteName OPTIONAL,
IN LPCWSTR DcNameToPing OPTIONAL,
IN PSOCKET_ADDRESS DcSocketAddressList OPTIONAL,
IN ULONG DcSocketAddressCount,
IN ULONG Flags,
IN ULONG InternalFlags,
IN ULONG InitializationType,
IN OUT PNL_GETDC_CONTEXT Context
)
/*++
Routine Description:
This routine initializes the Context data struct describing the GetDc operation.
Arguments:
SendDatagramContext - Specifies context to pass a NlBrowserSendDatagram
ComputerName - Specifies the NETBIOS name of this computer.
If NULL, the name will be dynamically determined.
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 USER_MACHINE_ACCOUNT_MASK.
Invalid bits are ignored. If more than one bit is specified, the
account can be of any of the specified types.
NetbiosDomainName - The Netbios name of the domain to query.
(e.g., microsoft). Either NetbiosDomainName or DnsDomainName or both
must be specified.
DnsDomainName - The DNS-style name of the domain to query.
(e.g., microsoft.com)
DnsForestName - The DNS-style name of the tree the queried domain is in.
RequestedDomainSid - Sid of the domain the message is destined to.
If NULL, no domain sid will be sent in the ping request.
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.
DcNameToPing - The name of the DC to ping. If set, Context is the
ping context, not a discovery one.
DcSocketAddressList - A list of socket addresses to ping. Ignored if only
Context flags need to be initialized.
DcSocketAddressCount - The number of socket addresses in DcSocketAddressList.
Ignored if only Context flags need to be initialized.
Flags - Passes additional information to be used to process the request.
Flags can be a combination values bitwise or'ed together.
InternalFlags - Internal Flags used to pass additional information
DoFlagInitialization - TRUE if only Contex flags need to be initialize.
If FALSE, this must be the ping part initialization only.
Context - the Context data struct describing the GetDc operation.
Return Value:
NO_ERROR: The initialization was successful
ERROR_INVALID_FLAGS - The flags parameter has conflicting or superfluous
bits set.
ERROR_INVALID_PARAMETER - One of the parameters is invalid.
ERROR_NOT_ENOUGH_MEMORY: Not enough memory is available to process
this request.
Various Winsock errors.
--*/
{
NET_API_STATUS NetStatus = NO_ERROR;
LPWSTR LocalComputerName = NULL;
CHAR ResponseMailslotName[MAX_PATH+1];
ULONG ExtraVersionBits = 0;
PNL_DC_ADDRESS DcAddress = NULL;
//
// Do flag initialization
//
if ( InitializationType & NL_GETDC_CONTEXT_INITIALIZE_FLAGS ) {
//
// Treat zero length domain name as NULL.
//
if ( DnsDomainName != NULL && *DnsDomainName == L'\0' ) {
DnsDomainName = NULL;
}
if ( DnsForestName != NULL && *DnsForestName == L'\0' ) {
DnsForestName = NULL;
}
if ( NetbiosDomainName != NULL && *NetbiosDomainName == L'\0' ) {
NetbiosDomainName = NULL;
}
if ( SiteName != NULL && *SiteName == L'\0' ) {
SiteName = NULL;
}
if ( DcNameToPing != NULL && *DcNameToPing == L'\0' ) {
DcNameToPing = NULL;
}
//
// Initialization
//
RtlZeroMemory( Context, sizeof(*Context) );
Context->FreeOurNetbiosComputerName = FALSE;
Context->QueriedAccountName = AccountName;
Context->QueriedAllowableAccountControlBits = AllowableAccountControlBits;
Context->QueriedNetbiosDomainName = NetbiosDomainName;
Context->QueriedDnsDomainName = DnsDomainName;
Context->QueriedDnsForestName = DnsForestName;
Context->QueriedDcName = DcNameToPing;
if ( DnsDomainName != NULL ) {
Context->QueriedDisplayDomainName = DnsDomainName;
} else {
Context->QueriedDisplayDomainName = NetbiosDomainName;
}
Context->QueriedDomainGuid = DomainGuid;
Context->QueriedFlags = Flags;
Context->QueriedInternalFlags = InternalFlags;
Context->SendDatagramContext = SendDatagramContext;
Context->ImperfectCacheEntry = NULL;
InitializeListHead( &Context->DcAddressList );
Context->DcAddressCount = 0;
Context->QueriedSiteName = SiteName;
Context->DoingExplicitSite = Context->QueriedSiteName != NULL &&
(Context->QueriedInternalFlags & DS_SITENAME_DEFAULTED) == 0;
Context->ResponseBuffer = NULL;
Context->ResponseMailslotHandle = NULL;
//
// Don't pass confusing bits
//
if ( Context->QueriedAccountName == NULL ) {
Context->QueriedAllowableAccountControlBits = 0;
}
//
// Validate the passed in flags
//
if ( (Context->QueriedFlags & ~DSGETDC_VALID_FLAGS) != 0 ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: %ws: invalid flags %lx\n",
Context->QueriedDisplayDomainName,
Flags ));
NetStatus = ERROR_INVALID_FLAGS;
goto Cleanup;
}
if ( Context->QueriedFlags & DS_GC_SERVER_REQUIRED ) {
//
// The DC ignores pings with superfluous info.
// So catch the caller here.
//
if ( Context->QueriedAccountName != NULL ||
Context->QueriedAllowableAccountControlBits != 0 ||
RequestedDomainSid != NULL ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: %ws: GC queried and invalid parameters specified.\n",
Context->QueriedDisplayDomainName ));
NetStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
}
//
// The Only LDAP bit is mutually exclusive with almost everything.
//
// Don't error out. Rather, treat the only LDAP bit as an advisory
// that these other bits should affect the decision of which DC to find.
//
if ( Context->QueriedFlags & DS_ONLY_LDAP_NEEDED ) {
Context->QueriedFlags &= ~(
DS_DIRECTORY_SERVICE_REQUIRED |
DS_DIRECTORY_SERVICE_PREFERRED |
DS_TIMESERV_REQUIRED |
DS_GOOD_TIMESERV_PREFERRED |
DS_PDC_REQUIRED |
DS_KDC_REQUIRED );
}
//
// Convert the flags to the type of DNS name to lookup.
//
NetStatus = NetpDcFlagsToNameType( Context->QueriedFlags, &Context->QueriedNlDnsNameType );
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: %ws: cannot convert flags to nametype %ld\n",
Context->QueriedDisplayDomainName,
NetStatus ));
goto Cleanup;
}
Context->DcQueryType = NlDcDnsNameTypeDesc[Context->QueriedNlDnsNameType].DcQueryType;
//
// The Good Time Service preferred bit is mutually exclusive with almost everything.
//
if ( Context->QueriedFlags & DS_GOOD_TIMESERV_PREFERRED ) {
if ( Context->QueriedFlags & (
DS_DIRECTORY_SERVICE_REQUIRED |
DS_DIRECTORY_SERVICE_PREFERRED |
DS_GC_SERVER_REQUIRED |
DS_PDC_REQUIRED |
DS_KDC_REQUIRED |
DS_WRITABLE_REQUIRED )) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: %ws: flags not compatible with 'Good Time' %lx\n",
Context->QueriedDisplayDomainName,
Flags ));
NetStatus = ERROR_INVALID_FLAGS;
goto Cleanup;
}
}
//
// If the caller needs the PDC,
// ditch the DS preferred flag (there is only one PDC),
// ditch the writable flag (the PDC is always writable)
//
if ( (Context->QueriedFlags & DS_PDC_REQUIRED ) != 0 ) {
Context->QueriedFlags &= ~(DS_DIRECTORY_SERVICE_PREFERRED|DS_WRITABLE_REQUIRED);
}
//
// If the caller says that an NT 5.0 DC is both preferred and required,
// ditch the preferred bit.
//
if ( (Context->QueriedFlags & DS_NT50_REQUIRED ) != 0 &&
(Context->QueriedFlags & DS_DIRECTORY_SERVICE_PREFERRED) != 0 ) {
Context->QueriedFlags &= ~DS_DIRECTORY_SERVICE_PREFERRED;
}
//
// Ensure we have a computername.
//
if ( ComputerName == NULL ) {
#ifndef WIN32_CHICAGO
//
// On a cluster, use the physical netbios name since this name is
// used to receive returned mailslot packets.
//
NetStatus = NetpGetComputerNameEx ( &LocalComputerName, TRUE );
#else
NetStatus = NetpGetComputerName ( &LocalComputerName);
#endif // WIN32_CHICAGO
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
ComputerName = LocalComputerName;
Context->FreeOurNetbiosComputerName = TRUE;
}
Context->OurNetbiosComputerName = ComputerName;
//
// Get the domain entry describing this domain.
//
Context->NlDcDomainEntry = NetpDcCreateDomainEntry(
Context->QueriedDomainGuid,
Context->QueriedNetbiosDomainName,
Context->QueriedDnsDomainName );
if ( Context->NlDcDomainEntry == NULL ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: not enough memory for DomainEntry.\n" ));
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
}
//
// Do the ping initialization part
//
if ( InitializationType & NL_GETDC_CONTEXT_INITIALIZE_PING ) {
if ( Context->QueriedFlags & DS_GC_SERVER_REQUIRED ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_GC;
}
if ( Context->QueriedFlags & DS_IP_REQUIRED ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_IP;
}
if ( Context->QueriedFlags & DS_PDC_REQUIRED ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_PDC;
}
//
// See if we are to neutralize NT4 emulation
//
#ifdef _NETLOGON_SERVER
//
// In netlogon, the boolean is kept in global parameters
//
if ( NlGlobalParameters.NeutralizeNt4Emulator ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_AVOID_NT4EMUL;
}
#else
//
// If we are not running in netlogon, we need to read
// the boolean directly from the registry
//
{
DWORD LocalNeutralizeNt4Emulator = 0;
NT_PRODUCT_TYPE NtProductType;
if ( !RtlGetNtProductType( &NtProductType ) ) {
NtProductType = NtProductWinNt;
}
//
// On DC, we always neutrilize NT4 emulation
//
if ( NtProductType == NtProductLanManNt ) {
LocalNeutralizeNt4Emulator = 1;
//
// On wksta, read the registry
//
} else {
NlReadDwordNetlogonRegValue( "NeutralizeNt4Emulator",
&LocalNeutralizeNt4Emulator );
}
if ( LocalNeutralizeNt4Emulator ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_AVOID_NT4EMUL;
}
}
#endif // _NETLOGON_SERVER
//
// If we're querying by netbios name,
// initialize for doing the query.
//
if ( Context->QueriedNetbiosDomainName != NULL ) {
//
// Allocate the response buffer
//
// (This buffer could be allocated on the stack ofNetpDcGetPingResponse()
// except the buffer is large and we want to avoid stack overflows.)
// (DWORD align it.)
//
Context->ResponseBuffer = LocalAlloc( 0,
( MAX_RANDOM_MAILSLOT_RESPONSE/sizeof(DWORD) ) * sizeof(DWORD)
);
if ( Context->ResponseBuffer == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
Context->ResponseBufferSize =
( MAX_RANDOM_MAILSLOT_RESPONSE/sizeof(DWORD) ) * sizeof(DWORD);
//
// Open a mailslot to get ping responses on.
//
//
// We need to "Randomize" the mailslot name so that this api can have
// more than one invocation at a time. If we don't, the fact that
// mailslots must have unique names will prevent the second invocation
// of this api from functioning until the first has ended and deleted
// the mailslot. NetpLogonCreateRandomMailslot does this for us
// and creates the mailslot in the process.
//
NetStatus = NetpLogonCreateRandomMailslot( ResponseMailslotName,
&Context->ResponseMailslotHandle );
if (NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: %ws: cannot create temp mailslot %ld\n",
Context->QueriedDisplayDomainName,
NetStatus));
goto Cleanup;
}
//
// Build the ping message.
//
// If the account name is specified, don't generate a "primary query"
// message since it doesn't have an account name in it.
//
NetStatus = NetpDcBuildPing(
(Context->DcQueryType == NlDcQueryPdc ||
(Context->QueriedFlags & DS_WRITABLE_REQUIRED) != 0),
0, // RequestCount,
Context->OurNetbiosComputerName, // Netbios name of this computer
Context->QueriedAccountName,
ResponseMailslotName,
Context->QueriedAllowableAccountControlBits,
RequestedDomainSid,
// We really need the IP address, so don't ask for simple 5EX version
NETLOGON_NT_VERSION_5|NETLOGON_NT_VERSION_5EX_WITH_IP|ExtraVersionBits,
&Context->PingMessage,
&Context->PingMessageSize );
if (NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: %ws: cannot build ping message %ld\n",
Context->QueriedDisplayDomainName,
NetStatus));
goto Cleanup;
}
//
// Build the alternate ping message if we do a DC discovery.
//
// For writable DCs and PDCs, the ping message built above is the "primary query" message
// (used to find a PDC in pre-NT 5.0 domains) and the message build below is the
// "logon user" message.
//
// If the account name is specified by the original caller,
// the "logon user" message allows us to prefer DCs that have the account.
//
// If a writable DC is requested,
// sending this message to NT 5 DCs allows us to return ANY NT 5 DC.
//
if ( ((Context->QueriedInternalFlags & DS_DOING_DC_DISCOVERY) != 0) &&
(((Context->QueriedFlags & DS_WRITABLE_REQUIRED) != 0) ||
(Context->QueriedAccountName != NULL && Context->DcQueryType == NlDcQueryPdc)) ) {
NetStatus = NetpDcBuildPing(
FALSE,
0, // RequestCount,
Context->OurNetbiosComputerName, // Netbios name of this computer
Context->QueriedAccountName,
ResponseMailslotName,
Context->QueriedAllowableAccountControlBits,
RequestedDomainSid,
// We really need the IP address, so don't ask for simple 5EX version
NETLOGON_NT_VERSION_5|NETLOGON_NT_VERSION_5EX_WITH_IP|ExtraVersionBits,
&Context->AlternatePingMessage,
&Context->AlternatePingMessageSize );
if (NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: %ws: cannot build alternate ping message %ld\n",
Context->QueriedDisplayDomainName,
NetStatus));
goto Cleanup;
}
}
}
//
// Build the LDAP filter.
//
NetStatus = NetpDcBuildLdapFilter(
Context->OurNetbiosComputerName, // Netbios name of this computer
Context->QueriedAccountName,
Context->QueriedAllowableAccountControlBits,
RequestedDomainSid,
Context->QueriedDnsDomainName,
Context->QueriedDomainGuid,
// Don't ask for 5EX_WITH_IP version since the server doesn't know the right IP address over LDAP
NETLOGON_NT_VERSION_5|NETLOGON_NT_VERSION_5EX|ExtraVersionBits,
&Context->LdapFilter );
if (NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeContext: %ws: cannot build ldap filter %ld\n",
Context->QueriedDisplayDomainName,
NetStatus));
goto Cleanup;
}
//
// Add the socket address to the address list.
//
if ( DcSocketAddressCount > 0 ) {
NetStatus = NetpDcProcessAddressList( Context,
(LPWSTR) DcNameToPing,
DcSocketAddressList,
DcSocketAddressCount,
FALSE, // Don't know if site specific
NULL );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
}
}
Cleanup:
return NetStatus;
}
VOID
NetpDcUninitializeContext(
IN OUT PNL_GETDC_CONTEXT Context
)
/*++
Routine Description:
This routine cleans up the Context data struct describing the GetDc operation.
Arguments:
Context - the Context data struct describing the GetDc operation.
Return Value:
None.
--*/
{
if ( Context->FreeOurNetbiosComputerName && Context->OurNetbiosComputerName != NULL ) {
NetApiBufferFree((LPWSTR) Context->OurNetbiosComputerName);
}
if ( Context->ResponseBuffer != NULL ) {
LocalFree( Context->ResponseBuffer );
}
if ( Context->ResponseMailslotHandle != NULL ) {
CloseHandle(Context->ResponseMailslotHandle);
}
if ( Context->PingMessage != NULL ) {
NetpMemoryFree( Context->PingMessage );
}
if ( Context->AlternatePingMessage != NULL ) {
NetpMemoryFree( Context->AlternatePingMessage );
}
if ( Context->LdapFilter != NULL ) {
NetpMemoryFree( Context->LdapFilter );
}
if ( Context->NlDcDomainEntry != NULL ) {
NetpDcDerefDomainEntry( Context->NlDcDomainEntry );
}
if ( Context->ImperfectCacheEntry != NULL ) {
NetpDcDerefCacheEntry( Context->ImperfectCacheEntry );
}
NetpDcFreeAddressList( Context );
}
NET_API_STATUS
NlPingDcNameWithContext (
IN PNL_GETDC_CONTEXT Context,
IN ULONG NumberOfPings,
IN BOOLEAN WaitForResponse,
IN ULONG Timeout,
OUT PBOOL UsedNetbios OPTIONAL,
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry OPTIONAL
)
/*++
Routine Description:
Ping the specified DC using the appropriate ping mechanism
and optionally wait for the ping responses. Several pings
will be attempted up to the specified limit.
Arguments:
Context - Desribes the DC to ping.
NumberOfPings - Total number of pings to send.
WaitForResponse -
If TRUE, this API will send up to NumberOfPings pings and wait for a
response from the DC. The API will return the success code depending
on whether or not the DC responds successfully.
If FALSE, pings will be sent and no responses will be collected.
The API will return the success code depending on whether or not
all of the requeted pings were successfully sent.
Timeout - Total ammount of time in milliseconds to wait for ping responses.
Ignored if WaitForResponse is FALSE.
UsedNetbios - Returns TRUE if the netbios domain name was used to match
the returned cache entry. Ignored if WaitForResponse is FALSE.
NlDcCacheEntry - Returns the data structure describing response received
from the DC. Should be freed by calling NetpMemoryFree. Ignored if
WaitForResponse is FALSE.
Return Value:
NO_ERROR - Success.
ERROR_NO_LOGON_SERVERS - No DC could be found
ERROR_NO_SUCH_USER - The DC doesn't have the user account specified in the
ping Context.
ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper
domain controller of the specified domain.
ERROR_SERVICE_NOT_ACTIVE - The netlogon service is paused on the server.
--*/
{
NTSTATUS Status;
NET_API_STATUS NetStatus;
ULONG RetryCount;
PNL_DC_CACHE_ENTRY NlLocalDcCacheEntry = NULL;
BOOL LocalUsedNetbios;
ULONG IpPingCount;
ULONG TotalPingsSent = 0;
//
// If we have no mechanism to send the pings, error out.
//
if ( (Context->QueriedInternalFlags &
(DS_PING_USING_LDAP | DS_PING_USING_MAILSLOT)) == 0 ) {
return ERROR_NO_LOGON_SERVERS;
}
//
// Ping repeatedely the DC
//
for ( RetryCount=0; RetryCount<NumberOfPings; RetryCount++ ) {
//
// Send the ldap ping
//
if ( Context->QueriedInternalFlags & DS_PING_USING_LDAP ) {
NetStatus = NetpDcPingIp( Context, &IpPingCount );
//
// If we cannot send any ldap ping, do not error out. Rather, indicate
// to avoid the ldap mechanism and try the mailslot one only.
//
if ( NetStatus != NO_ERROR || IpPingCount == 0 ) {
NlPrint((NL_CRITICAL,
"NlPingDcNameWithContext: cannot send %ld ldap pings: PingsSent = %ld, Error = 0x%lx\n",
Context->DcAddressCount,
IpPingCount,
NetStatus ));
Context->QueriedInternalFlags &= ~DS_PING_USING_LDAP;
} else {
TotalPingsSent += IpPingCount;
NlPrint((NL_MISC,
"NlPingDcNameWithContext: Sent %ld/%ld ldap pings to %ws\n",
IpPingCount,
Context->DcAddressCount,
Context->QueriedDcName ));
}
}
//
// Send the mailslot ping
//
if ( Context->QueriedInternalFlags & DS_PING_USING_MAILSLOT ) {
#if NETLOGONDBG
NlPrint((NL_MAILSLOT,
"NlPingDcNameWithContext: Sent '%s' message to %ws[%s] on %ws.\n",
NlMailslotOpcode(((PNETLOGON_LOGON_QUERY)(Context->PingMessage))->Opcode),
Context->QueriedDcName,
NlDgrNameType(ComputerName),
NULL ));
#endif // NETLOGONDBG
// Skip over \\ in unc server name
Status = NlBrowserSendDatagram(
Context->SendDatagramContext,
(Context->QueriedFlags & DS_IP_REQUIRED ) ? ALL_IP_TRANSPORTS : 0,
(LPWSTR) Context->QueriedDcName,
ComputerName,
NULL, // All transports
NETLOGON_LM_MAILSLOT_A,
Context->PingMessage,
Context->PingMessageSize,
TRUE, // send synchronously
NULL ); // Don't flush Netbios cache
//
// If we cannot write the maislot, do not error out. Rather, indicate
// to avoid the mailslot mechanism and try the ldap one only.
//
if ( !NT_SUCCESS(Status) ) {
NlPrint((NL_CRITICAL,
"NlPingDcNameWithContext: cannot write netlogon mailslot: 0x%lx\n",
Status));
Context->QueriedInternalFlags &= ~DS_PING_USING_MAILSLOT;
} else {
TotalPingsSent ++;
}
}
//
// If we didn't send any ping, error out. Otherwise, try to get a
// response. It is possible that we will not do any more pings
// if either of the ping mechanisms is to be avoided, but we want
// to give all of the time left to those pings which have been sent.
//
if ( TotalPingsSent == 0 ) {
NetStatus = ERROR_NO_LOGON_SERVERS;
goto Cleanup;
}
//
// Get the response from the ping.
//
if ( WaitForResponse ) {
if ( NlLocalDcCacheEntry != NULL ) {
NetpMemoryFree( NlLocalDcCacheEntry );
NlLocalDcCacheEntry = NULL;
}
NetStatus = NetpDcGetPingResponse(
Context,
Timeout/NumberOfPings,
&NlLocalDcCacheEntry,
&LocalUsedNetbios );
//
// If no error, we've successfully found the DC.
//
if ( NetStatus == NO_ERROR ) {
if ( NlLocalDcCacheEntry->CacheEntryFlags & NL_DC_CACHE_LDAP ) {
NlPrint((NL_MISC,
"NlPingDcNameWithContext: %ws responded over IP.\n",
Context->QueriedDcName ));
}
if ( NlLocalDcCacheEntry->CacheEntryFlags & NL_DC_CACHE_MAILSLOT ) {
NlPrint((NL_MISC,
"NlPingDcNameWithContext: %ws responded on a mailslot.\n",
Context->QueriedDcName ));
}
goto Cleanup;
//
// If we've timed out, retry
//
} else if ( NetStatus == ERROR_SEM_TIMEOUT ) {
NlPrint((NL_MISC,
"NlPingDcNameWithContext: Ping response timeout for %ws.\n",
Context->QueriedDcName ));
continue;
//
// If the DC we've successfully pinged and got response from
// returns responce info that is in conflict with the requested
// info, error out.
//
} else if ( NetStatus == ERROR_INVALID_DATA ) {
NlPrint((NL_CRITICAL,
"NlPingDcNameWithContext: Invalid response returned from %ws.\n",
Context->QueriedDcName ));
NetStatus = ERROR_DOMAIN_TRUST_INCONSISTENT;
goto Cleanup;
//
// Tell the caller that the netlogon service is paused
// on the server.
//
} else if ( NetStatus == ERROR_SERVICE_NOT_ACTIVE ) {
NlPrint((NL_CRITICAL,
"NlPingDcNameWithContext: Netlogon is paused on %ws.\n",
Context->QueriedDcName ));
goto Cleanup;
//
// Check if there is no such account
//
} else if ( NetStatus == ERROR_NO_SUCH_USER ) {
NlPrint((NL_CRITICAL,
"NlPingDcNameWithContext: No such user %ws on %ws.\n",
Context->QueriedAccountName,
Context->QueriedDcName ));
goto Cleanup;
} else {
NlPrint((NL_CRITICAL,
"NlPingDcNameWithContext: Wrong return code from NetpDcGetPingResponse: 0x%lx\n",
NetStatus));
NetStatus = ERROR_NO_LOGON_SERVERS;
goto Cleanup;
}
}
}
if ( WaitForResponse ) {
NlPrint(( NL_CRITICAL,
"NlPingDcNameWithContext: Can't ping the DC %ws.\n", Context->QueriedDcName ));
NetStatus = ERROR_NO_LOGON_SERVERS;
} else {
//
// If we are requested to only send the pings and
// we couldn't send all of the requested pings,
//
if ( TotalPingsSent < NumberOfPings ) {
NetStatus = ERROR_NO_LOGON_SERVERS;
} else {
NetStatus = NO_ERROR;
}
}
Cleanup:
//
// Return the DC info to the caller.
//
if ( NlLocalDcCacheEntry != NULL ) {
if ( NetStatus == NO_ERROR && WaitForResponse && NlDcCacheEntry != NULL ) {
*NlDcCacheEntry = NlLocalDcCacheEntry;
} else {
NetpMemoryFree( NlLocalDcCacheEntry );
}
}
if ( NetStatus == NO_ERROR && WaitForResponse && UsedNetbios != NULL ) {
*UsedNetbios = LocalUsedNetbios;
}
return NetStatus;
}
DWORD
NetpGetGcUsingNetbios(
IN PNL_GETDC_CONTEXT Context,
IN DWORD OrigTimeout,
IN DWORD OrigRetryCount,
OUT PNL_DC_CACHE_ENTRY *DomainControllerCacheEntry
)
/*++
Routine Description:
This routine tries to find a GC using a Netbios domain name.
Arguments:
Context - Context describing the initial attempt to find a DC.
DomainControllerCacheEntry -
Return a pointer to a private PNL_DC_CACHE_ENTRY
structure describing the domain controller selected. The returned
structure must be dereferenced using NetpDcDerefCacheEntry.
Return Value:
The status code that is to be returned by the caller.
--*/
{
NET_API_STATUS NetStatus;
ULONG ElapsedTime;
ULONG TimeToWait;
ULONG LocalFlags;
ULONG LocalInternalFlags;
PDOMAIN_CONTROLLER_INFOW DcDomainControllerInfo = NULL;
PNL_DC_CACHE_ENTRY GcDomainControllerCacheEntry = NULL;
NlPrint(( NL_MAILSLOT,
"%ws: Try to find a GC using netbios domain name.\n",
Context->QueriedNetbiosDomainName ));
//
// Reduce the timeout to be the time we haven't already spent.
// (But allow a minimum of 2 seconds)
//
ElapsedTime = NetpDcElapsedTime( Context->StartTime );
if ( ElapsedTime < OrigTimeout ) {
TimeToWait = max((OrigTimeout - ElapsedTime), NL_DC_MIN_ITERATION_TIMEOUT);
} else {
TimeToWait = NL_DC_MIN_ITERATION_TIMEOUT;
}
#ifdef notdef
NlPrint((NL_CRITICAL,
"NetpGetGcUsingNetbios: timeout is %ld %ld %ld %ld\n",
Context->StartTime,
ElapsedTime,
OrigTimeout,
TimeToWait ));
#endif // notdef
//
// Compute the flags to use to find a DC
//
// Only keep the 'force' bit from the ones passed by the caller.
// Any other bit could only serve to confuse finding a GC.
//
LocalFlags = (Context->QueriedFlags & DS_FORCE_REDISCOVERY);
// Prefer a DS to ensure we get back a forest name if we can.
LocalFlags |= DS_DIRECTORY_SERVICE_PREFERRED;
//
// Compute the internal flags used to find a DC
//
// Keep only the internal flags that still apply to this call.
//
LocalInternalFlags = (Context->QueriedInternalFlags & DS_IS_PRIMARY_DOMAIN);
// Tell NetpDcGetName not to cache failures.
LocalInternalFlags |= DS_DONT_CACHE_FAILURE;
// Since we're only using the data in the ping response and we're not
// actually using the returned DC, don't require a close DC.
LocalInternalFlags |= DS_CLOSE_DC_NOT_NEEDED;
// Ensure the named domain is really the root domain
//
// It wouldn't be fatal to allow this. However, we cannot support it
// for DNS names. So we don't want folks to stumble upon this working for
// Netbios domain names.
//
// However, if the caller didn't pass the domain name, don't require the
// root domain. The caller just wants to find a GC and doesn't know the
// forest name. This will be the case for Win9x clients who passed NULL
// and we don't know the forest name on Win9x (so we couldn't get the
// forest name in DsIGetDcName).
if ( (Context->QueriedInternalFlags & DS_CALLER_PASSED_NULL_DOMAIN) == 0 ) {
LocalInternalFlags |= DS_REQUIRE_ROOT_DOMAIN;
}
//
// Simply try to find a DC in the named domain.
//
// Don't try to find a DC in the named site. Their might not be one.
// We only know that there'll be a GC in the named site. Luckily,
// Netbios isn't very site aware in the first place.
//
// Even if the DC found happens to be a close GC, don't use it.
// That'd unfairly load the GCs that happen to be in the root domain.
// We should spread the load to all the GCs in the site.
//
NetStatus = NetpDcGetName(
Context->SendDatagramContext,
Context->OurNetbiosComputerName,
NULL, // No AccountName
0, // No AccountControlBits
Context->QueriedNetbiosDomainName,
NULL, // We've already shown that the DNS name doesn't work
NULL, // We don't know the forest name
NULL, // RequestedDomainSid,
Context->QueriedDomainGuid,
NULL, // There might not be a DC in the named site.
LocalFlags,
LocalInternalFlags,
TimeToWait,
OrigRetryCount == 0 ?
1 :
OrigRetryCount,
&DcDomainControllerInfo,
NULL );
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"%ws: Cannot even find a DC much less a GC.\n",
Context->QueriedNetbiosDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// Make sure we know know the name of the forest.
//
if ( DcDomainControllerInfo->DnsForestName == NULL ) {
NlPrint(( NL_CRITICAL,
"%ws: DC %ws doesn't know the forest name so can't find a GC.\n",
Context->QueriedNetbiosDomainName,
DcDomainControllerInfo->DomainControllerName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
NlPrint(( NL_MAILSLOT,
"%ws: DC %ws says the forest name is %ws.\n",
Context->QueriedNetbiosDomainName,
DcDomainControllerInfo->DomainControllerName,
DcDomainControllerInfo->DnsForestName ));
//
// Compute the flags used for finding a GC given a forest name
//
//
// If the caller wasn't explicit about the format of the returned name,
// be consistant with the original request.
//
LocalFlags = Context->QueriedFlags;
if ( (LocalFlags & (DS_RETURN_FLAT_NAME|DS_RETURN_DNS_NAME)) == 0 ) {
//
// If the caller specified only a Netbios domain name,
// or if we didn't know whether the name was a DNS or netbios name,
// then we should return a netbios name to the caller.
//
// (In the later case, we can infer that the name is a netbios name
// since UsedNetbios is true.)
//
if ( Context->QueriedDnsDomainName == NULL ||
(Context->QueriedInternalFlags & DS_NAME_FORMAT_AMBIGUOUS) != 0 ) {
LocalFlags |= DS_RETURN_FLAT_NAME;
}
}
LocalFlags |= DS_AVOID_SELF; // Already tried self
//
// Tell netlogon not to cache this failed attempt.
// The main routine will do it.
//
LocalInternalFlags = Context->QueriedInternalFlags;
LocalInternalFlags |= DS_DONT_CACHE_FAILURE;
//
// Try to find a GC in the returned forest name
//
NetStatus = NetpDcGetName(
Context->SendDatagramContext,
Context->OurNetbiosComputerName,
NULL, // No AccountName
0, // No AccountControlBits
NULL, // Do not specify the Netbios name for a GC search
DcDomainControllerInfo->DnsForestName,
DcDomainControllerInfo->DnsForestName,
NULL, // RequestedDomainSid,
Context->QueriedDomainGuid,
Context->QueriedSiteName,
LocalFlags,
LocalInternalFlags,
TimeToWait,
OrigRetryCount == 0 ?
1 :
OrigRetryCount,
NULL,
&GcDomainControllerCacheEntry );
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"%ws: Cannot find a GC in forest %ws.\n",
Context->QueriedNetbiosDomainName,
DcDomainControllerInfo->DnsForestName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
Cleanup:
//
// Return the found GC to the caller.
//
if ( NetStatus == NO_ERROR ) {
*DomainControllerCacheEntry = GcDomainControllerCacheEntry;
} else if ( GcDomainControllerCacheEntry != NULL ) {
NetpDcDerefCacheEntry( GcDomainControllerCacheEntry );
}
if ( DcDomainControllerInfo != NULL ) {
NetApiBufferFree( DcDomainControllerInfo );
}
return NetStatus;
}
DWORD
NetpGetBetterDc(
IN PNL_GETDC_CONTEXT Context,
IN DWORD OrigTimeout,
IN DWORD OrigRetryCount,
IN OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry
)
/*++
Routine Description:
This routine decides whether a better DC can be found. This routine
is only found if we used netbios to find the current DC. It allows
us to overcome some of the weaknesses of netbios.
If the found DC isn't in the closest site, we attempt to find one
in the closest site using DNS.
Arguments:
Context - Context describing the initial attempt to find a DC.
NlDcCacheEntry - Passes in a pointer to a private
PNL_DC_CACHE_ENTRY structure describing the original found DC.
This structure may be dereferenced by this routine.
If DomainControllerInfo is NULL, then NlDcCacheEntry returns a
pointer to a private PNL_DC_CACHE_ENTRY
structure describing the domain controller selected. The returned
structure must be dereferenced using NetpDcDerefCacheEntry. This
may be the original structure or a newly allocated one.
Return Value:
The status code that is to be returned by the caller.
--*/
{
NET_API_STATUS NetStatus;
ULONG ElapsedTime;
ULONG TimeToWait;
ULONG LocalFlags = Context->QueriedFlags;
ULONG LocalInternalFlags = Context->QueriedInternalFlags;
LPWSTR LocalSiteName;
PNL_DC_CACHE_ENTRY LocalDomainControllerCacheEntry;
//
// If the DC we've got has a DNS domain name,
// and that DC told us what site we're in,
// and that DC isn't in the closest site.
// try to get a DC in that closest site.
//
if ( (*NlDcCacheEntry)->UnicodeDnsDomainName != NULL &&
(*NlDcCacheEntry)->UnicodeClientSiteName != NULL &&
((*NlDcCacheEntry)->ReturnFlags & DS_CLOSEST_FLAG ) == 0 &&
(Context->QueriedInternalFlags & DS_CLOSE_DC_NOT_NEEDED) == 0 ) {
NlPrint(( NL_DNS,
"%ws %ws: Try to find a close DC using DNS\n",
(*NlDcCacheEntry)->UnicodeDnsDomainName,
(*NlDcCacheEntry)->UnicodeClientSiteName ));
//
// Otherwise, the original passed in DC is just fine.
//
} else {
return NO_ERROR;
}
//
// Reduce the timeout to be the time we haven't already spent.
// (But allow a minimum of 2 seconds)
//
ElapsedTime = NetpDcElapsedTime( Context->StartTime );
if ( ElapsedTime < OrigTimeout ) {
TimeToWait = max((OrigTimeout - ElapsedTime), NL_DC_MIN_ITERATION_TIMEOUT);
} else {
TimeToWait = NL_DC_MIN_ITERATION_TIMEOUT;
}
#ifdef notdef
NlPrint((NL_CRITICAL,
"NetpGetBetterDc: timeout is %ld %ld %ld %ld\n",
Context->StartTime,
ElapsedTime,
OrigTimeout,
TimeToWait ));
#endif // notdef
LocalFlags |= DS_AVOID_SELF; // Already tried self
//
// Adjust the InternalFlags to match the new request.
//
LocalSiteName = (LPWSTR) Context->QueriedSiteName;
if ( LocalInternalFlags & DS_SITENAME_DEFAULTED ) {
LocalInternalFlags &= ~DS_SITENAME_DEFAULTED;
LocalSiteName = NULL;
}
//
// This routine is called only if Netbios was used to
// discover the previously found DC. So, unless the
// caller required the DNS info be returned, we will
// return the Netbios format to the caller so ensure
// the Netbios format match for the better DC.
//
if ( (Context->QueriedFlags & DS_RETURN_DNS_NAME) == 0 ) {
LocalFlags |= DS_RETURN_FLAT_NAME;
}
//
// If the caller wasn't explicit about the format of the returned name,
// be consistant with the original request.
//
//
// Tell netlogon not to cache this failed retry attempt.
//
LocalInternalFlags |= DS_DONT_CACHE_FAILURE;
//
// Go get the DC using DNS and an explicit site name
// Request only the appropriate structure to be returned
//
NetStatus = NetpDcGetName(
Context->SendDatagramContext,
Context->OurNetbiosComputerName,
Context->QueriedAccountName,
Context->QueriedAllowableAccountControlBits,
NULL, // No Netbios domain name (Done that. Been there.)
(*NlDcCacheEntry)->UnicodeDnsDomainName,
Context->QueriedDnsForestName != NULL ?
Context->QueriedDnsForestName :
(*NlDcCacheEntry)->UnicodeDnsForestName,
NULL, // RequestedDomainSid,
Context->QueriedDomainGuid != NULL ?
Context->QueriedDomainGuid :
(IsEqualGUID( &(*NlDcCacheEntry)->DomainGuid, &NlDcZeroGuid) ?
NULL :
&(*NlDcCacheEntry)->DomainGuid ),
LocalSiteName != NULL ?
LocalSiteName :
(*NlDcCacheEntry)->UnicodeClientSiteName,
LocalFlags,
LocalInternalFlags,
TimeToWait,
OrigRetryCount == 0 ?
1 :
OrigRetryCount,
NULL,
&LocalDomainControllerCacheEntry );
if ( NetStatus == NO_ERROR ) {
NetpDcDerefCacheEntry( *NlDcCacheEntry );
*NlDcCacheEntry = LocalDomainControllerCacheEntry;
}
return NO_ERROR;
}
DWORD
NetpDcGetName(
IN PVOID SendDatagramContext OPTIONAL,
IN LPCWSTR ComputerName OPTIONAL,
IN LPCWSTR AccountName OPTIONAL,
IN ULONG AllowableAccountControlBits,
IN LPCWSTR NetbiosDomainName OPTIONAL,
IN LPCWSTR DnsDomainName OPTIONAL,
IN LPCWSTR DnsForestName OPTIONAL,
IN PSID RequestedDomainSid OPTIONAL,
IN GUID *DomainGuid OPTIONAL,
IN LPCWSTR SiteName OPTIONAL,
IN ULONG Flags,
IN ULONG InternalFlags,
IN DWORD Timeout,
IN DWORD RetryCount,
OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo OPTIONAL,
OUT PNL_DC_CACHE_ENTRY *DomainControllerCacheEntry OPTIONAL
)
/*++
Routine Description:
NetpDcGetName is a worker function for DsGetDcName. It has the following
characteristics. It is synchronous. It executes in the caller (it does
not RPC to Netlogon). It implements a cache of responses. The cache must
previously have been initialized via NetpDcInitializeCache. The cache should
be free upon process cleanup (or DLL unload) using NetpDcUninitializeCache.
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.
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:
SendDatagramContext - Specifies context to pass a NlBrowserSendDatagram
ComputerName - Specifies the NETBIOS name of this computer.
If NULL, the name will be dynamically determined.
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 USER_MACHINE_ACCOUNT_MASK.
Invalid bits are ignored. If more than one bit is specified, the
account can be of any of the specified types.
NetbiosDomainName - The Netbios name of the domain to query.
(e.g., microsoft). Either NetbiosDomainName or DnsDomainName or both
must be specified.
DnsDomainName - The DNS-style name of the domain to query.
(e.g., microsoft.com)
DnsForestName - The DNS-style name of the tree the queried domain is in.
RequestedDomainSid - Sid of the domain the message is destined to.
If NULL, no domain sid will be sent in the ping request.
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.
InternalFlags - Internal Flags used to pass additional information
Timeout - Maximum time (in milliseconds) caller is willing to wait on
this operation.
RetryCount - Number of times the "ping" will be sent within the Timeout period
DomainControllerInfo - Returns a pointer to a DOMAIN_CONTROLLER_INFO
structure describing the domain controller selected. The returned
structure must be deallocated using NetApiBufferFree.
DomainControllerCacheEntry - Returns a pointer to an internal structure describing
the domain controller selected. The structure is private and is not returned
to an external caller. Either DomainControllerInfo or DomainControllerCacheEntry
should be set on input. The returned structure must be dereferenced using
NetpDcDerefCacheEntry.
Return Value:
NO_ERROR: The NlDcCacheEntry was returned;
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.
ERROR_INVALID_DOMAINNAME: Domain's name is too long. Additional labels
cannot be concatenated.
ERROR_NOT_ENOUGH_MEMORY: Not enough memory is available to process
this request.
Various Winsock errors.
--*/
{
NET_API_STATUS NetStatus;
NL_GETDC_CONTEXT Context;
PNL_DC_CACHE_ENTRY NlDcCacheEntry = NULL;
BOOL UseIp = TRUE;
BOOL UseNetbios = TRUE;
BOOLEAN AtleastOneTimeout = FALSE;
PVOID PingResponseMessage = NULL;
DWORD PingResponseMessageSize;
PLIST_ENTRY ListEntry;
BOOLEAN OnlyTryOnce = FALSE;
BOOL UsedNetbios;
ULONG ExtraVersionBits = 0;
ULONG ElapsedTime;
ULONG IterationWaitTime;
ULONG TimeToWait;
LPWSTR LocalQueriedlNetbiosDomainName = NULL;
#ifdef _NETLOGON_SERVER
//
// Prevent any outer exception handler from obscuring bugs in this code.
//
try {
#endif // _NETLOGON_SERVER
//
// Treat zero length domain name as NULL.
//
if ( DnsDomainName != NULL && *DnsDomainName == L'\0' ) {
DnsDomainName = NULL;
}
if ( DnsForestName != NULL && *DnsForestName == L'\0' ) {
DnsForestName = NULL;
}
if ( NetbiosDomainName != NULL && *NetbiosDomainName == L'\0' ) {
NetbiosDomainName = NULL;
}
//
// Initialization
//
NetStatus = NetpDcInitializeContext(
SendDatagramContext,
ComputerName,
AccountName,
AllowableAccountControlBits,
NetbiosDomainName,
DnsDomainName,
DnsForestName,
RequestedDomainSid,
DomainGuid,
SiteName,
NULL, // Not a ping request
NULL, // No socket addresses
0, // 0 socket addresses
Flags,
InternalFlags | DS_DOING_DC_DISCOVERY, // This is a DC discovery
NL_GETDC_CONTEXT_INITIALIZE_FLAGS, // Flag initialization only
&Context );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Ask Netlogon if this machine satisfies these requirements
// It's better to use the local machine than going out on the net and trying to
// discover one.
//
if ( Context.QueriedFlags & DS_GC_SERVER_REQUIRED ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_GC;
}
if ( Context.QueriedFlags & DS_IP_REQUIRED ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_IP;
}
if ( Context.QueriedFlags & DS_PDC_REQUIRED ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_PDC;
}
#ifdef _NETLOGON_SERVER
if ( NlGlobalParameters.NeutralizeNt4Emulator ) {
ExtraVersionBits |= NETLOGON_NT_VERSION_AVOID_NT4EMUL;
}
if ( (Context.QueriedFlags & DS_AVOID_SELF) == 0 ) {
LPSTR Utf8DnsDomainName = NULL;
if ( DnsDomainName != NULL ) {
Utf8DnsDomainName = NetpAllocUtf8StrFromWStr( DnsDomainName );
if ( Utf8DnsDomainName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
}
NetStatus = NlGetLocalPingResponse(
L"<Local>",
FALSE, // not an LDAP ping
NetbiosDomainName,
Utf8DnsDomainName,
DomainGuid,
RequestedDomainSid,
Context.DcQueryType == NlDcQueryPdc,
Context.OurNetbiosComputerName, // Netbios name of this computer
Context.QueriedAccountName,
Context.QueriedAllowableAccountControlBits,
LMNT_MESSAGE,
NETLOGON_NT_VERSION_5|NETLOGON_NT_VERSION_5EX|NETLOGON_NT_VERSION_5EX_WITH_IP|NETLOGON_NT_VERSION_LOCAL|ExtraVersionBits,
NULL, // No incoming socket address
&PingResponseMessage,
&PingResponseMessageSize );
if ( Utf8DnsDomainName != NULL ) {
NetApiBufferFree( Utf8DnsDomainName );
}
if ( NetStatus != ERROR_NO_SUCH_DOMAIN ) {
if (NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws: cannot get local ping response %ld\n",
Context.QueriedDisplayDomainName,
NetStatus));
goto Cleanup;
}
//
// See if this response meets our needs
//
NetStatus = NetpDcHandlePingResponse(
&Context,
PingResponseMessage,
PingResponseMessageSize,
NL_DC_CACHE_LOCAL, // local response
NULL,
&NlDcCacheEntry,
&UsedNetbios );
switch ( NetStatus ) {
case ERROR_SEM_TIMEOUT: // Doesn't match the request
case ERROR_INVALID_DATA: // Response is garbled
case ERROR_NO_SUCH_USER: // User doesn't exist on this DC
break;
default:
goto Cleanup;
}
}
}
//
// If this is primary DC discovery, first cache the DC info written by
// the join process, if any. We will pick up this cached info later.
// We do this even if the current DC discovery is more specific than
// the generic DC discovery used by the join process. Indeed, the join
// DC may turn out to be more specific and may satisfy the current request.
//
// This is a potentially lengthy operation since the DC will be pinged.
//
EnterCriticalSection(&NlDcCritSect);
if ( !NlGlobalJoinLogicDone &&
(Context.QueriedInternalFlags & DS_IS_PRIMARY_DOMAIN) != 0 ) {
NlGlobalJoinLogicDone = TRUE;
if ( NlCacheJoinDomainControllerInfo() == NO_ERROR ) {
//
// It is bogus to force rediscovery on the first attempt for
// a particular type. For example, forcing discovery would
// avoid the cache thereby mising the join DC we just cached.
//
Context.QueriedFlags &= ~DS_FORCE_REDISCOVERY;
}
}
LeaveCriticalSection(&NlDcCritSect);
#endif // _NETLOGON_SERVER
//
// If discovery isn't being forced,
// do any optimizations that will speed getting the results to the caller.
//
if ( (Context.QueriedFlags & DS_FORCE_REDISCOVERY) == 0 ) {
ULONG CacheEntryElapsedTime;
DWORD NegativeCacheElapsedTime = 0xFFFFFFFF;
ULONG CacheEntryRefreshPeriod = 0xFFFFFFFF; // Infinity
BOOL SimilarQueryFailed = FALSE;
BOOL ForcePing;
//
// If there is a cache entry for this operation,
// Use it.
//
NlDcCacheEntry = NetpDcFindCacheEntry( &Context, &UsedNetbios, &ForcePing );
if ( NlDcCacheEntry != NULL ) {
CacheEntryElapsedTime = NetpDcElapsedTime(NlDcCacheEntry->CreationTime);
}
//
// If the cached DC is not close,
// check if it's time to re-discover a close one.
//
if ( NlDcCacheEntry != NULL &&
(NlDcCacheEntry->CacheEntryFlags & NL_DC_CACHE_NONCLOSE_EXPIRE) != 0 &&
CacheEntryElapsedTime > NL_DC_CLOSE_SITE_TIMEOUT ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws cache not for closest site and it is too old. %ld\n",
Context.QueriedDisplayDomainName,
NetpDcElapsedTime( NlDcCacheEntry->CreationTime ) ));
NetpDcDerefCacheEntry( NlDcCacheEntry );
NlDcCacheEntry = NULL;
}
//
// Determine the appropriate cache entry refresh interval.
// Notice that a cache entry will never expire if we are
// not running in netlogon's process.
//
#ifdef _NETLOGON_SERVER
//
// Get the value as configured in seconds
//
if ( Context.QueriedFlags & DS_BACKGROUND_ONLY ) {
CacheEntryRefreshPeriod = NlGlobalParameters.BackgroundSuccessfulRefreshPeriod;
} else {
CacheEntryRefreshPeriod = NlGlobalParameters.NonBackgroundSuccessfulRefreshPeriod;
}
// If the value converted into milliseconds fits into a ULONG, use it
if ( CacheEntryRefreshPeriod <= MAXULONG/1000 ) {
CacheEntryRefreshPeriod *= 1000; // convert to milliseconds
// Otherwise, use the max ULONG
} else {
CacheEntryRefreshPeriod = MAXULONG; // infinity
}
#endif // _NETLOGON_SERVER
//
// If this cache entry is too old,
// ping the DC to see if it still plays the same role.
//
if ( NlDcCacheEntry != NULL &&
(ForcePing ||
CacheEntryElapsedTime > CacheEntryRefreshPeriod) ) {
if ( ForcePing ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws cache doesn't have right account name.\n",
Context.QueriedDisplayDomainName ));
} else {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws cache is too old. %ld\n",
Context.QueriedDisplayDomainName,
NetpDcElapsedTime( NlDcCacheEntry->CreationTime ) ));
}
//
// Indicate which mechanism should be used to ping the DC
//
if ( NlDcCacheEntry->CacheEntryFlags & NL_DC_CACHE_LDAP ) {
//
// Add the cached DC address to the list of quried addresses
//
if ( NlDcCacheEntry->SockAddr.iSockaddrLength != 0 ) {
NetStatus = NetpDcProcessAddressList( &Context,
NlDcCacheEntry->UnicodeDnsHostName,
&NlDcCacheEntry->SockAddr,
1,
FALSE, // Don't know if site specific
NULL );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Prefer DNS name for ldap pings
//
if ( NlDcCacheEntry->UnicodeDnsHostName != NULL ) {
Context.QueriedDcName = NlDcCacheEntry->UnicodeDnsHostName;
Context.QueriedInternalFlags |= DS_PING_DNS_HOST;
} else if ( NlDcCacheEntry->UnicodeNetbiosDcName != NULL ) {
Context.QueriedDcName = NlDcCacheEntry->UnicodeNetbiosDcName;
Context.QueriedInternalFlags |= DS_PING_NETBIOS_HOST;
}
Context.QueriedInternalFlags |= DS_PING_USING_LDAP;
} else {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws cache says use ldap but has no address\n",
Context.QueriedDisplayDomainName ));
}
} else if ( NlDcCacheEntry->CacheEntryFlags & NL_DC_CACHE_MAILSLOT ) {
//
// We must have Netbios name for mailslot pings
//
if ( NlDcCacheEntry->UnicodeNetbiosDcName != NULL &&
NlDcCacheEntry->UnicodeNetbiosDomainName != NULL ) {
Context.QueriedDcName = NlDcCacheEntry->UnicodeNetbiosDcName;
//
// If we don't have the Netbios domain name in Context,
// use the one from the cache entry
//
if ( Context.QueriedNetbiosDomainName == NULL ) {
LocalQueriedlNetbiosDomainName =
NetpAllocWStrFromWStr( NlDcCacheEntry->UnicodeNetbiosDomainName );
if ( LocalQueriedlNetbiosDomainName == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
Context.QueriedNetbiosDomainName = LocalQueriedlNetbiosDomainName;
}
Context.QueriedInternalFlags |= DS_PING_NETBIOS_HOST;
Context.QueriedInternalFlags |= DS_PING_USING_MAILSLOT;
} else {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws cache says use maislot but has no Netbios name\n",
Context.QueriedDisplayDomainName ));
}
}
//
// Ping the DC using the specified mechanism
//
if ( Context.QueriedInternalFlags & (DS_PING_DNS_HOST|DS_PING_NETBIOS_HOST) ) {
ULONG PingStartTime;
ULONG PingElapsedTime;
PNL_DC_CACHE_ENTRY PingedNlDcCacheEntry = NULL;
//
// Do the ping part initialization of Context.
// Some of the arguments passed below are ignored by the API.
// Instead, the corresponding fields of Context initialized
// in the flag part of the context initialization are used.
//
NetStatus = NetpDcInitializeContext(
SendDatagramContext,
ComputerName,
AccountName,
Context.QueriedAllowableAccountControlBits,
NetbiosDomainName, // Ignored for ping initialization
DnsDomainName, // Ignored for ping initialization
DnsForestName, // Ignored for ping initialization
RequestedDomainSid,
DomainGuid, // Ignored for ping initialization
SiteName, // Ignored for ping initialization
NULL, // Quieried DC name has been just set
NULL, // Socket address has been just set
0, // No socket addresses passed here
Flags, // Ignored for ping initialization
InternalFlags, // Ignored for ping initialization
NL_GETDC_CONTEXT_INITIALIZE_PING, // Ping initialization
&Context );
if ( NetStatus != NO_ERROR ) {
NetpDcDerefCacheEntry( NlDcCacheEntry );
NlDcCacheEntry = NULL;
goto Cleanup;
}
//
// Ping the cached DC name
//
// We send one ping and wait for maximum time
// that we give to a ping response. If the DC
// is slow to respond here, it will be given the
// second chance since we will leave its address
// at the front of the list. (Here we assume that
// the ping is LDAP. If it's mailslot, the 0.4 second
// timeout should be large enough for the datagram
// response time; however, the DC will not be prefered
// later if it's slow to respond here)
//
PingStartTime = GetTickCount();
NetStatus = NlPingDcNameWithContext(
&Context,
1, // Send 1 ping
TRUE, // Wait for response
NL_DC_MAX_PING_TIMEOUT, // Give maximum timeout per ping
&UsedNetbios,
&PingedNlDcCacheEntry );
//
// Clear all ping related bits to not confuse the DC
// discovery if it happens. But leave the Netbios domain
// name and the DC address on the list to validate the
// DC's response if it happens to arrive after the 0.5
// second timeout.
//
Context.QueriedDcName = NULL;
Context.QueriedInternalFlags &= ~( DS_PING_DNS_HOST |
DS_PING_NETBIOS_HOST |
DS_PING_USING_LDAP |
DS_PING_USING_MAILSLOT );
//
// On success, update the cache entry. The new cache entry
// returned from the pinged DC may contain new information
// such as a new client site name. So we want to use it
// instead of the currently cached one.
//
NetpDcDerefCacheEntry( NlDcCacheEntry );
NlDcCacheEntry = NULL;
if ( NetStatus == NO_ERROR ) {
NlDcCacheEntry = PingedNlDcCacheEntry;
}
//
// Update the timeout.
//
PingElapsedTime = NetpDcElapsedTime( PingStartTime );
if ( Timeout > PingElapsedTime ) {
Timeout -= PingElapsedTime;
}
//
// If we have no ping mechanism for this cache entry, ditch it
//
} else {
NetpDcDerefCacheEntry( NlDcCacheEntry );
NlDcCacheEntry = NULL;
}
}
if ( NlDcCacheEntry != NULL ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws using cached information\n",
Context.QueriedDisplayDomainName ));
NetStatus = NO_ERROR;
goto Cleanup;
}
#ifdef _NETLOGON_SERVER
//
// Since there is no cache entry,
// check if we've attempted to find a DC recently.
//
EnterCriticalSection(&NlDcCritSect);
if ( Context.NlDcDomainEntry->Dc[Context.DcQueryType].NegativeCacheTime != 0 ) {
NegativeCacheElapsedTime = NetpDcElapsedTime( Context.NlDcDomainEntry->Dc[Context.DcQueryType].NegativeCacheTime );
//
// If this couldn't be discovered in the last 45 seconds,
//
if ( NegativeCacheElapsedTime < (NlGlobalParameters.NegativeCachePeriod*1000) ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws similar query failed recently %ld\n",
Context.QueriedDisplayDomainName,
NegativeCacheElapsedTime ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
Context.AvoidNegativeCache = TRUE;
LeaveCriticalSection(&NlDcCritSect);
goto Cleanup;
}
//
// The negative cache timeout hasn't elapsed yet.
// But indicate that similar query failed in the past
// to make a decision as to whether we want to retry
// the DC discovery for background callers below.
//
SimilarQueryFailed = TRUE;
}
//
// If the caller wants an NT5 (or newer) DC but we know
// this domain is NT4, adjust the negative cache elapsed
// time to be the time passed since the latest failure for
// similar query.
//
if ( (Context.QueriedFlags & DS_NT50_WANTED) != 0 &&
Context.NlDcDomainEntry->InNt4Domain ) {
DWORD InNt4DomainElapsedTime;
InNt4DomainElapsedTime = NetpDcElapsedTime( Context.NlDcDomainEntry->InNt4DomainTime );
if ( InNt4DomainElapsedTime < NegativeCacheElapsedTime ) {
NegativeCacheElapsedTime = InNt4DomainElapsedTime;
}
//
// Indicate that similar query failed in the past
//
SimilarQueryFailed = TRUE;
}
//
// If the caller only wants a DC for a background task
// and we know a similar query failed in the past,
// see if it is time to try again.
//
if ( (Context.QueriedFlags & DS_BACKGROUND_ONLY) != 0 && SimilarQueryFailed ) {
if ( NegativeCacheElapsedTime < (Context.NlDcDomainEntry->Dc[Context.DcQueryType].ExpBackoffPeriod*1000) ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws similar background query failed recently %ld %ld\n",
Context.QueriedDisplayDomainName,
NegativeCacheElapsedTime,
(Context.NlDcDomainEntry->Dc[Context.DcQueryType].ExpBackoffPeriod*1000) ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
Context.AvoidNegativeCache = TRUE;
LeaveCriticalSection(&NlDcCritSect);
goto Cleanup;
}
//
// If we've already spent all the time we're willing to spend on
// background tasks, blow this one away.
//
if ( NlGlobalParameters.BackgroundRetryQuitTime != 0 &&
NlTimeHasElapsedEx(
&Context.NlDcDomainEntry->Dc[Context.DcQueryType].BackgroundRetryInitTime,
&NlGlobalParameters.BackgroundRetryQuitTime_100ns,
NULL ) ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws avoiding all future background queries\n",
Context.QueriedDisplayDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
Context.AvoidNegativeCache = TRUE;
LeaveCriticalSection(&NlDcCritSect);
goto Cleanup;
}
//
// If the negative cache entry has been marked permanent,
// blow this one away.
//
if ( Context.NlDcDomainEntry->Dc[Context.DcQueryType].PermanentNegativeCache ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws is permanently negative cached.\n",
Context.QueriedDisplayDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
Context.AvoidNegativeCache = TRUE;
LeaveCriticalSection(&NlDcCritSect);
goto Cleanup;
}
//
// We're going to try again.
// Adjust the exponential backoff period.
//
Context.NlDcDomainEntry->Dc[Context.DcQueryType].ExpBackoffPeriod *= 2;
if ( Context.NlDcDomainEntry->Dc[Context.DcQueryType].ExpBackoffPeriod >
NlGlobalParameters.BackgroundRetryMaximumPeriod ) {
Context.NlDcDomainEntry->Dc[Context.DcQueryType].ExpBackoffPeriod =
NlGlobalParameters.BackgroundRetryMaximumPeriod;
}
}
LeaveCriticalSection(&NlDcCritSect);
#endif // _NETLOGON_SERVER
//
// If a good time server is preferred,
// and we already have a suitable cache entry,
// only try once to find a good time server.
//
if ( (Context.QueriedFlags & DS_GOOD_TIMESERV_PREFERRED) != 0 &&
Context.ImperfectCacheEntry != NULL ) {
//
// Don't cache the fact that we couldn't find a DC.
//
Context.AvoidNegativeCache = TRUE;
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Only try once to find good timeserver.\n",
Context.QueriedDisplayDomainName ));
OnlyTryOnce = TRUE;
}
//
// If an NT 5.0 DC is wanted,
// handle the case where we know we're in an NT 4.0 domain.
//
if ((Context.QueriedFlags & DS_NT50_WANTED) != 0 ) {
EnterCriticalSection(&NlDcCritSect);
if ( Context.NlDcDomainEntry->InNt4Domain ) {
//
// If we recently found that this was an NT 4.0 domain,
// fail the call immediately.
//
if ( NetpDcElapsedTime(Context.NlDcDomainEntry->InNt4DomainTime) <= NL_NT4_AVOIDANCE_TIME ) {
//
// If the caller only prefers an NT 5.0 machine,
// let him find an NT 4.0 DC normally.
//
if ( Context.QueriedFlags & DS_DIRECTORY_SERVICE_PREFERRED ) {
//
// If we had an NT 4 DC cached,
// Use it now.
//
if ( Context.ImperfectCacheEntry != NULL ) {
LeaveCriticalSection(&NlDcCritSect);
NlDcCacheEntry = Context.ImperfectCacheEntry;
UsedNetbios = Context.ImperfectUsedNetbios;
Context.ImperfectCacheEntry = NULL;
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Avoid finding NT 5.0 DC in NT 4.0 domain (Use previously cached entry.)\n",
Context.QueriedDisplayDomainName ));
NetStatus = NO_ERROR;
goto Cleanup;
}
Context.QueriedFlags &= ~DS_DIRECTORY_SERVICE_PREFERRED;
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Avoid finding NT 5.0 DC in NT 4.0 domain (Ditch preferred)\n",
Context.QueriedDisplayDomainName ));
//
// If the caller needs an NT 5.0 DC,
// fail the call immediately.
//
} else {
//
// Don't cache the fact that we couldn't find a DC.
// The InNt4Domain cache is more sophisticated than the
// simple negative cache.
//
Context.AvoidNegativeCache = TRUE;
LeaveCriticalSection(&NlDcCritSect);
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Avoid finding NT 5.0 DC in NT 4.0 domain\n",
Context.QueriedDisplayDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// If it's been a while since we found out,
// we'll retry the operation (but only once).
//
// This minimizes the cost, but still allows us to find an NT 5 DC
// if it was just temporarily down.
//
} else {
//
// Don't cache the fact that we couldn't find a DC.
// The InNt4Domain cache is more sophisticated than the
// simple negative cache.
//
Context.AvoidNegativeCache = TRUE;
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Only try once to find NT 5.0 DC in NT 4.0 domain.\n",
Context.QueriedDisplayDomainName ));
OnlyTryOnce = TRUE;
}
}
LeaveCriticalSection(&NlDcCritSect);
}
}
//
// If we did not initialize the ping part of Context earlier
// to do host pings, do it here.
//
if ( Context.LdapFilter == NULL && Context.PingMessage == NULL ) {
NetStatus = NetpDcInitializeContext(
SendDatagramContext,
ComputerName,
AccountName,
Context.QueriedAllowableAccountControlBits,
NetbiosDomainName,
DnsDomainName,
DnsForestName,
RequestedDomainSid,
DomainGuid,
SiteName,
NULL, // Not a ping request
NULL, // No socket addresses
0, // 0 socket addresses
Flags,
InternalFlags,
NL_GETDC_CONTEXT_INITIALIZE_PING, // Ping part initialization
&Context );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
}
//
// Loop until we've made several attempts to find the DC
//
Context.StartTime = GetTickCount();
for ( Context.TryCount = 0;
Context.TryCount < RetryCount;
Context.TryCount ++ ) {
//
// If a DNS domain name is known,
// use DNS to find a DC.
//
if ( Context.QueriedDnsDomainName == NULL ) {
UseIp = FALSE;
} else if ( UseIp ) {
//
// Try using DNS/IP to find the DC.
//
NetStatus = NetpDcGetNameIp(
&Context,
&NlDcCacheEntry,
&UsedNetbios );
//
// If we found it,
// return it.
//
if ( NetStatus == NO_ERROR ) {
goto Cleanup;
//
// If DNS isn't configured,
// mark that we don't want to try DNS again.
// (Drop through to Netbios.)
//
} else if ( NetStatus == ERROR_DNS_NOT_CONFIGURED ) {
UseIp = FALSE;
//
// If DNS has the name registered,
// but the DCs haven't yet responded,
// indicate we need to keep on waiting.
// (Drop through to Netbios.)
//
} else if ( NetStatus == ERROR_SEM_TIMEOUT ) {
AtleastOneTimeout = TRUE;
//
// If DNS doesn't have the name registered,
// indicate we don't need to try DNS again.
// (Drop through to Netbios.)
//
} else if ( NetStatus == ERROR_NO_SUCH_DOMAIN ) {
UseIp = FALSE;
//
// All other problems are DNS detected errors to return to
// the caller.
//
} else {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws: cannot find DC via IP/DNS %ld\n",
Context.QueriedDisplayDomainName,
NetStatus));
goto Cleanup;
}
}
//
// If a Netbios domain name is known,
// use Netbios to find a DC.
//
if ( Context.QueriedNetbiosDomainName == NULL ) {
UseNetbios = FALSE;
} else if ( UseNetbios ) {
NetStatus = NetpDcGetNameNetbios(
&Context,
&NlDcCacheEntry,
&UsedNetbios );
//
// If we found it,
// return it.
//
if ( NetStatus == NO_ERROR ) {
goto Cleanup;
//
// If Netbios sent the datagram successfully,
// but the DCs haven't yet responded,
// indicate we need to keep on waiting.
// (Drop through to next iteration.)
//
} else if ( NetStatus == ERROR_SEM_TIMEOUT ) {
AtleastOneTimeout = TRUE;
//
// If Netbios couldn't send the datagram,
// indicate we don't need to try Netbios again.
// (Drop through to next iteration.)
//
} else if ( NetStatus == ERROR_NO_SUCH_DOMAIN ) {
UseNetbios = FALSE;
//
// All other problems are DNS detected errors to return to
// the caller.
//
} else {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws: cannot find DC via Netbios %ld\n",
Context.QueriedDisplayDomainName,
NetStatus));
goto Cleanup;
}
}
//
// If there are no more mechanisms to try,
// we're done.
//
if ( !UseIp && !UseNetbios ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws: IP and Netbios are both done.\n",
Context.QueriedDisplayDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// If no datagrams were sent successfully,
// we're done.
//
if ( !AtleastOneTimeout ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws: no datagrams were sent\n",
Context.QueriedDisplayDomainName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// If we should only try once,
// we've done that first try.
//
if ( OnlyTryOnce ) {
//
// Wait a short amount of time to ensure the response has a chance
// to reach us.
//
NetStatus = NetpDcGetPingResponse(
&Context,
NL_NT4_ONE_TRY_TIME,
&NlDcCacheEntry,
&UsedNetbios );
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws: Cannot NetpDcGetPingResponse. %ld\n",
Context.QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
//
// So we couldn't get an NT5 DC (or a good time server).
//
// If the caller requires an NT 5.0 DC,
// we're done so error out early.
//
if ( (Context.QueriedFlags & (DS_DIRECTORY_SERVICE_PREFERRED|DS_GOOD_TIMESERV_PREFERRED)) == 0 ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Only try once done.\n",
Context.QueriedDisplayDomainName ));
break;
}
//
// If an NT 4 DC has already been found,
// (or a non-good time server has already been found),
// use it since the caller didn't require NT5 DC
// (or a good time server).
//
if ( Context.ImperfectCacheEntry != NULL ) {
if ( Context.QueriedFlags & DS_DIRECTORY_SERVICE_PREFERRED ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Tried once to find NT 5.0 DC (Using found NT 4.0 DC).\n",
Context.QueriedDisplayDomainName ));
} else if (Context.QueriedFlags & DS_GOOD_TIMESERV_PREFERRED) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Tried once to find good timeserver (Using previously found DC).\n",
Context.QueriedDisplayDomainName ));
}
//
// Drop through to handle this in the cleanup section
//
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// Here we don't have an imperfect cache entry and the caller
// doesn't require an NT5 DC. Let him continue to find an
// NT 4.0 DC normally.
//
Context.QueriedFlags &= ~DS_DIRECTORY_SERVICE_PREFERRED;
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Only try once reset.\n",
Context.QueriedDisplayDomainName ));
OnlyTryOnce = FALSE;
}
//
// Wait up to 1/RetryCount'th of the total available time for responses to come back.
// The caller will either resort to a less preferable candidate or
// will repeat the pings. In either case, we'd rather this candidate won.
//
// Always wait a short amount of time here. Consider the case that DNS
// took 20 seconds to find that there was no DNS server. We still want
// to give Netbios a decent amount of time to find a DC.
//
ElapsedTime = NetpDcElapsedTime( Context.StartTime );
#ifdef notdef
NlPrint((NL_CRITICAL,
"NetpDcGetName: timeout is %ld %ld %ld %ld\n",
Context.StartTime,
ElapsedTime,
Timeout,
Context.TryCount ));
#endif // notdef
IterationWaitTime = (Timeout*(Context.TryCount+1))/RetryCount;
if ( IterationWaitTime > ElapsedTime &&
(IterationWaitTime - ElapsedTime) > NL_DC_MIN_ITERATION_TIMEOUT ) {
TimeToWait = IterationWaitTime - ElapsedTime;
} else {
TimeToWait = NL_DC_MIN_ITERATION_TIMEOUT;
}
NetStatus = NetpDcGetPingResponse(
&Context,
TimeToWait,
&NlDcCacheEntry,
&UsedNetbios );
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws: Cannot NetpDcGetPingResponse. %ld\n",
Context.QueriedDisplayDomainName,
NetStatus ));
}
goto Cleanup;
}
//
// If at least one NT 4.0 DC is available in the domain,
// and no NT 5.0 DCs (of any type) are available,
// and we asked for an NT 5.0 DC,
// early out now since our caller is impatient.
//
// Don't be tempted to leave out the last test. If we're not
// explicitly asking for an NT 5.0 DC, we might not ping any NT 5.0 DCs
// even though they exist in the domain.
//
//
if ( Context.NonDsResponse &&
!Context.DsResponse &&
(Context.QueriedFlags & DS_NT50_REQUIRED) != 0 ) {
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// If we've waited long enough for a perfect DC,
// drop out an use the imperfect one.
//
if ( Context.ImperfectCacheEntry != NULL ) {
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
}
//
// Tried two times and still can't find one.
//
NetStatus = ERROR_NO_SUCH_DOMAIN;
Cleanup:
////////////////////////////////
// //
// First, hanle failure cases //
// //
////////////////////////////////
//
// If the problem is simply that the DCs don't have a user account for the named user,
// change the status code.
//
if ( NetStatus == ERROR_NO_SUCH_DOMAIN && Context.NoSuchUserResponse ) {
NetStatus = ERROR_NO_SUCH_USER;
}
//
// If no DC has been found so far,
// handle it.
//
if ( NetStatus == ERROR_NO_SUCH_DOMAIN ) {
//
// If there is a cache entry that might not be perfect,
// see if it is satisfactory.
//
if ( Context.ImperfectCacheEntry != NULL ) {
//
// Handle NT 4 DC found when DS preferred.
//
if ( Context.QueriedFlags & DS_DIRECTORY_SERVICE_PREFERRED ) {
//
// If we actually attempted to discover a NT5 DC and
// found only NT4 DC, reset the InNt4DomainTime stamp
//
if ( Context.NonDsResponse && !Context.DsResponse ) {
EnterCriticalSection(&NlDcCritSect);
Context.NlDcDomainEntry->InNt4Domain = TRUE;
Context.NlDcDomainEntry->InNt4DomainTime = GetTickCount();
LeaveCriticalSection(&NlDcCritSect);
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Domain is a _new_ NT 4.0 domain.\n",
Context.QueriedDisplayDomainName ));
} else {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Domain is still NT 4.0 domain.\n",
Context.QueriedDisplayDomainName ));
}
NlDcCacheEntry = Context.ImperfectCacheEntry;
UsedNetbios = Context.ImperfectUsedNetbios;
Context.ImperfectCacheEntry = NULL;
NetStatus = NO_ERROR;
//
// Handle regular timeserv found when good timeserv preferred.
//
} else if (Context.QueriedFlags & DS_GOOD_TIMESERV_PREFERRED) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Domain has no good timeserv.\n",
Context.QueriedDisplayDomainName ));
NlDcCacheEntry = Context.ImperfectCacheEntry;
UsedNetbios = Context.ImperfectUsedNetbios;
Context.ImperfectCacheEntry = NULL;
NetStatus = NO_ERROR;
}
}
}
//
// If this is a failed attempt to find a GC using a netbios name,
// try to find a DC then using the forest name returned from that DC.
//
// If the DNS name is different than the netbios name,
// that DNS has already been given a chance.
//
if ( NetStatus == ERROR_NO_SUCH_DOMAIN &&
NlDnsGcName(Context.QueriedNlDnsNameType) &&
Context.QueriedNetbiosDomainName != NULL &&
( Context.QueriedDnsDomainName == NULL ||
_wcsicmp( Context.QueriedDnsDomainName,
Context.QueriedNetbiosDomainName ) == 0 ) ) {
NetStatus = NetpGetGcUsingNetbios(
&Context,
Timeout,
RetryCount,
&NlDcCacheEntry );
//
// If this was successful, we certainly used DNS
//
if ( NetStatus == NO_ERROR ) {
UsedNetbios = FALSE;
}
}
//
// If still no such dc could be found,
// update the cache appropriately.
//
if ( NetStatus == ERROR_NO_SUCH_DOMAIN ) {
//
// If at least one NT 4.0 DC is available in the domain,
// and no NT 5.0 DCs (of any type) are available,
// and we asked for an NT 5.0 DC,
// flag that this is an NT 4.0 domain.
//
// Don't be tempted to leave out the last test. If we're not
// explicitly asking for an NT 5.0 DC, we might not ping any NT 5.0 DCs
// even though they exist in the domain.
//
//
if ( Context.NonDsResponse &&
!Context.DsResponse &&
(Context.QueriedFlags & DS_NT50_REQUIRED) != 0 ) {
EnterCriticalSection(&NlDcCritSect);
Context.NlDcDomainEntry->InNt4Domain = TRUE;
Context.NlDcDomainEntry->InNt4DomainTime = GetTickCount();
LeaveCriticalSection(&NlDcCritSect);
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Domain is an NT 4.0 domain.\n",
Context.QueriedDisplayDomainName ));
}
//
// If this call isn't a retry of a successful query.
// update the cache to reflect this failure.
//
if ( (Context.QueriedInternalFlags & DS_DONT_CACHE_FAILURE) == 0 ) {
//
// If this was a forced attempt to find a DC,
// delete any existing cache entry.
//
// There's no use keeping this entry around.
//
if ( Context.QueriedFlags & DS_FORCE_REDISCOVERY ) {
EnterCriticalSection( &NlDcCritSect );
if ( Context.NlDcDomainEntry->Dc[Context.DcQueryType].NlDcCacheEntry != NULL ) {
NlPrint(( NL_DNS_MORE,
"Cache: %ws %ws: Ditch cache entry %ld since force couldn't find DC\n",
Context.NlDcDomainEntry->UnicodeNetbiosDomainName,
Context.NlDcDomainEntry->UnicodeDnsDomainName,
Context.DcQueryType ));
NetpDcDerefCacheEntry( Context.NlDcDomainEntry->Dc[Context.DcQueryType].NlDcCacheEntry );
Context.NlDcDomainEntry->Dc[Context.DcQueryType].NlDcCacheEntry = NULL;
}
LeaveCriticalSection( &NlDcCritSect );
}
#ifdef _NETLOGON_SERVER
//
// Cache the fact that we couldn't find a DC.
//
if ( !Context.AvoidNegativeCache ) {
EnterCriticalSection( &NlDcCritSect );
Context.NlDcDomainEntry->Dc[Context.DcQueryType].NegativeCacheTime =
GetTickCount();
LeaveCriticalSection( &NlDcCritSect );
}
#endif // _NETLOGON_SERVER
}
}
//
// Initialize the first background failure time if:
//
// This is failed attempt and we don't have a reason
// not to cache it
//
// OR
//
// This is failed attempt and the caller wanted a NT5
// DC but this is a NT4 domain
//
#ifdef _NETLOGON_SERVER
if ( (NetStatus == ERROR_NO_SUCH_DOMAIN &&
(Context.QueriedInternalFlags & DS_DONT_CACHE_FAILURE) == 0 &&
!Context.AvoidNegativeCache)
|| // OR
(NetStatus == ERROR_NO_SUCH_DOMAIN &&
(Context.QueriedFlags & DS_NT50_WANTED) != 0 &&
Context.NlDcDomainEntry != NULL &&
Context.NlDcDomainEntry->InNt4Domain) ) {
EnterCriticalSection( &NlDcCritSect );
//
// If this is the first failure,
// cache the time of the first failure.
//
if ( Context.NlDcDomainEntry->Dc[Context.DcQueryType].BackgroundRetryInitTime.QuadPart == 0 ) {
NlQuerySystemTime ( &Context.NlDcDomainEntry->Dc[Context.DcQueryType].BackgroundRetryInitTime );
Context.NlDcDomainEntry->Dc[Context.DcQueryType].ExpBackoffPeriod =
NlGlobalParameters.BackgroundRetryInitialPeriod;
}
//
// If this is a trusted domain (e.g., we're sure that the DNS name specified is a DNS name),
// and we got a response from a DNS server (implying net connectivity),
// and we didn't find a reason to avoid the permanent cache (e.g., found a SRV record),
// then we think we'll never be able to find a DC in this domain.
//
// (Notice the implication that the DNS server got the SRV entries before
// this machine got the trusted domain list entry.)
if ( (Context.QueriedInternalFlags & DS_IS_TRUSTED_DNS_DOMAIN) != 0 &&
Context.ResponseFromDnsServer &&
!Context.AvoidPermanentNegativeCache ) {
Context.NlDcDomainEntry->Dc[Context.DcQueryType].PermanentNegativeCache = TRUE;
NlPrint(( NL_DNS,
"Cache: %ws %ws: Cache entry %ld marked permanently negative.\n",
Context.NlDcDomainEntry->UnicodeNetbiosDomainName,
Context.NlDcDomainEntry->UnicodeDnsDomainName,
Context.DcQueryType ));
}
LeaveCriticalSection( &NlDcCritSect );
}
#endif // _NETLOGON_SERVER
////////////////////////////////
// //
// Now, hanle success cases //
// //
////////////////////////////////
//
// Update the cache. See if we really want to use this entry.
//
if ( NetStatus == NO_ERROR ) {
//
// If this entry hasn't been inserted, we haven't
// yet used it to set the site name as appropriate.
//
if ( (NlDcCacheEntry->CacheEntryFlags & NL_DC_CACHE_ENTRY_INSERTED) == 0 ) {
#ifdef _NETLOGON_SERVER
//
// If the domain being queried is the domain this machine is
// a member of,
// save the name of the site for the next call.
//
// Avoid setting the site name to NULL if the DC is NT4 DC since
// NT4 is not site aware. If the site name is NULL but the DC
// is NT5 DC, set the site to NULL to indicate that this machine
// is not in a site.
//
if ( ( NlDcCacheEntry->UnicodeClientSiteName != NULL ||
NlDcCacheEntry->ReturnFlags & DS_DS_FLAG ) &&
(Context.QueriedInternalFlags & DS_IS_PRIMARY_DOMAIN) != 0 ) {
NlSetDynamicSiteName( NlDcCacheEntry->UnicodeClientSiteName );
}
#endif // _NETLOGON_SERVER
//
// Insert the cache entry into the cache.
//
NetpDcInsertCacheEntry( &Context, NlDcCacheEntry );
}
//
// If we successfully found an NT 5.0 DC,
// flag that this is not an NT 4.0 domain.
//
EnterCriticalSection(&NlDcCritSect);
if ( (Context.QueriedFlags & DS_NT50_REQUIRED) != 0 &&
Context.NlDcDomainEntry->InNt4Domain ) {
Context.NlDcDomainEntry->InNt4Domain = FALSE;
Context.NlDcDomainEntry->InNt4DomainTime = 0;
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Domain is an NT 5.0 domain.\n",
Context.QueriedDisplayDomainName ));
}
LeaveCriticalSection(&NlDcCritSect);
//
// If the caller requires that the DC be in the root domain,
// and this one isn't,
// fail.
//
if ( (Context.QueriedInternalFlags & DS_REQUIRE_ROOT_DOMAIN) != 0 &&
NlDcCacheEntry->UnicodeDnsDomainName != NULL &&
NlDcCacheEntry->UnicodeDnsForestName != NULL &&
!NlEqualDnsName( NlDcCacheEntry->UnicodeDnsDomainName,
NlDcCacheEntry->UnicodeDnsForestName ) ) {
NlPrint(( NL_MISC,
"NetpDcGetName: %ws: Domain isn't the root domain %ws %ws.\n",
Context.QueriedDisplayDomainName,
NlDcCacheEntry->UnicodeDnsDomainName,
NlDcCacheEntry->UnicodeDnsForestName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
}
}
//
// If we used netbios to find a DC,
// see if failling back to DNS would get a better DC.
//
if ( NetStatus == NO_ERROR && UsedNetbios ) {
NetStatus = NetpGetBetterDc( &Context,
Timeout,
RetryCount,
&NlDcCacheEntry );
}
//
// Prepare the returned data.
//
// Convert cache entry into controller info if requested
//
if ( NetStatus == NO_ERROR && DomainControllerInfo != NULL ) {
WCHAR IpAddressString[NL_SOCK_ADDRESS_LENGTH+1];
WCHAR IpAddressStringSize;
ULONG DomainControllerInfoSize;
ULONG DnsHostNameSize;
ULONG NetbiosDcNameSize=0;
ULONG DnsDomainNameSize;
ULONG NetbiosDomainNameSize = 0;
ULONG DcSiteNameSize = 0;
ULONG ClientSiteNameSize = 0;
ULONG DnsForestNameSize = 0;
ULONG ReturnFlags = NlDcCacheEntry->ReturnFlags;
BOOL LocalUsedNetbios = UsedNetbios;
LPBYTE Where;
//
// If the user requested DNS names, then we need to send
// back dns names
//
if (( Flags & DS_RETURN_DNS_NAME) == DS_RETURN_DNS_NAME) {
LocalUsedNetbios = FALSE;
}
//
// Compute the size of the controller info entry.
//
DomainControllerInfoSize = sizeof(DOMAIN_CONTROLLER_INFOW);
if ( NlDcCacheEntry->UnicodeDnsHostName != NULL &&
(Context.QueriedFlags & DS_RETURN_FLAT_NAME) == 0 &&
!LocalUsedNetbios ) {
DnsHostNameSize = (wcslen(NlDcCacheEntry->UnicodeDnsHostName) + 1) * sizeof(WCHAR);
// DomainControllerName
DomainControllerInfoSize += DnsHostNameSize + 2 * sizeof(WCHAR);
} else if ( NlDcCacheEntry->UnicodeNetbiosDcName != NULL ) {
NetbiosDcNameSize = (wcslen(NlDcCacheEntry->UnicodeNetbiosDcName) + 1) * sizeof(WCHAR);
// DomainControllerName
DomainControllerInfoSize += NetbiosDcNameSize + 2 * sizeof(WCHAR);
} else {
// This can't ever happen. (But better to fail than to AV.)
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
if ( NlDcCacheEntry->SockAddr.iSockaddrLength != 0 ) {
NetStatus = NetpSockAddrToWStr(
NlDcCacheEntry->SockAddr.lpSockaddr,
NlDcCacheEntry->SockAddr.iSockaddrLength,
IpAddressString );
if ( NetStatus != NO_ERROR ) {
NlPrint(( NL_CRITICAL,
"NetpDcGetName: %ws: Cannot NetpSockAddrToWStr. 0x%lx\n",
Context.QueriedDisplayDomainName,
NetStatus ));
goto Cleanup;
}
IpAddressStringSize = (wcslen(IpAddressString) + 1) * sizeof(WCHAR);
// DomainControllerAddress
DomainControllerInfoSize += IpAddressStringSize + 2 * sizeof(WCHAR);
} else if ( NlDcCacheEntry->UnicodeNetbiosDcName != NULL ) {
if ( NetbiosDcNameSize == 0 ) {
NetbiosDcNameSize = (wcslen(NlDcCacheEntry->UnicodeNetbiosDcName) + 1) * sizeof(WCHAR);
}
// DomainControllerAddress
DomainControllerInfoSize += NetbiosDcNameSize + 2 * sizeof(WCHAR);
} else {
// This can't ever happen. (But better to fail than to AV.)
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
if ( NlDcCacheEntry->UnicodeDnsDomainName != NULL &&
(Context.QueriedFlags & DS_RETURN_FLAT_NAME) == 0 &&
!LocalUsedNetbios ) {
DnsDomainNameSize = (wcslen(NlDcCacheEntry->UnicodeDnsDomainName) + 1) * sizeof(WCHAR);
// DomainName
DomainControllerInfoSize += DnsDomainNameSize;
} else if ( NlDcCacheEntry->UnicodeNetbiosDomainName != NULL ) {
NetbiosDomainNameSize = (wcslen(NlDcCacheEntry->UnicodeNetbiosDomainName) + 1) * sizeof(WCHAR);
// DomainName
DomainControllerInfoSize += NetbiosDomainNameSize;
} else if ( LocalUsedNetbios &&
Context.QueriedNetbiosDomainName != NULL ) {
// Lanman PDC or SAMBA Domain Master brower.
NetbiosDomainNameSize = (wcslen(Context.QueriedNetbiosDomainName) + 1) * sizeof(WCHAR);
// DomainName
DomainControllerInfoSize += NetbiosDomainNameSize;
} else {
// This can't ever happen. (But better to fail than to AV.)
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
if ( NlDcCacheEntry->UnicodeDnsForestName != NULL ) {
DnsForestNameSize = (wcslen(NlDcCacheEntry->UnicodeDnsForestName) + 1) * sizeof(WCHAR);
// TreeName
DomainControllerInfoSize += DnsForestNameSize;
}
if ( NlDcCacheEntry->UnicodeDcSiteName != NULL ) {
DcSiteNameSize = (wcslen(NlDcCacheEntry->UnicodeDcSiteName) + 1) * sizeof(WCHAR);
// DcSiteName
DomainControllerInfoSize += DcSiteNameSize;
}
if ( NlDcCacheEntry->UnicodeClientSiteName != NULL ) {
ClientSiteNameSize = (wcslen(NlDcCacheEntry->UnicodeClientSiteName) + 1) * sizeof(WCHAR);
// ClientSiteName
DomainControllerInfoSize += ClientSiteNameSize;
}
//
// Allocate the controller info entry.
//
NetStatus = NetApiBufferAllocate(
DomainControllerInfoSize,
DomainControllerInfo );
if ( NetStatus == NO_ERROR ) {
Where = (LPBYTE)((*DomainControllerInfo) + 1);
//
// Copy information into the allocated buffer.
//
(*DomainControllerInfo)->DomainControllerName = (LPWSTR)Where;
*((LPWSTR)Where)++ = L'\\';
*((LPWSTR)Where)++ = L'\\';
if (NlDcCacheEntry->UnicodeDnsHostName != NULL &&
(Context.QueriedFlags & DS_RETURN_FLAT_NAME) == 0 &&
!LocalUsedNetbios ) {
RtlCopyMemory( Where,
NlDcCacheEntry->UnicodeDnsHostName,
DnsHostNameSize );
Where += DnsHostNameSize;
ReturnFlags |= DS_DNS_CONTROLLER_FLAG;
} else {
RtlCopyMemory( Where,
NlDcCacheEntry->UnicodeNetbiosDcName,
NetbiosDcNameSize );
Where += NetbiosDcNameSize;
}
(*DomainControllerInfo)->DomainControllerAddress = (LPWSTR)Where;
*((LPWSTR)Where)++ = L'\\';
*((LPWSTR)Where)++ = L'\\';
if ( NlDcCacheEntry->SockAddr.iSockaddrLength != 0 ) {
RtlCopyMemory( Where,
IpAddressString,
IpAddressStringSize );
Where += IpAddressStringSize;
(*DomainControllerInfo)->DomainControllerAddressType = DS_INET_ADDRESS;
} else {
RtlCopyMemory( Where,
NlDcCacheEntry->UnicodeNetbiosDcName,
NetbiosDcNameSize );
Where += NetbiosDcNameSize;
(*DomainControllerInfo)->DomainControllerAddressType = DS_NETBIOS_ADDRESS;
}
(*DomainControllerInfo)->DomainGuid = NlDcCacheEntry->DomainGuid;
(*DomainControllerInfo)->DomainName = (LPWSTR)Where;
if (NlDcCacheEntry->UnicodeDnsDomainName != NULL &&
(Context.QueriedFlags & DS_RETURN_FLAT_NAME) == 0 &&
!LocalUsedNetbios ) {
RtlCopyMemory( Where,
NlDcCacheEntry->UnicodeDnsDomainName,
DnsDomainNameSize );
Where += DnsDomainNameSize;
ReturnFlags |= DS_DNS_DOMAIN_FLAG;
} else if ( NlDcCacheEntry->UnicodeNetbiosDomainName != NULL ) {
RtlCopyMemory( Where,
NlDcCacheEntry->UnicodeNetbiosDomainName,
NetbiosDomainNameSize );
Where += NetbiosDomainNameSize;
} else if ( LocalUsedNetbios &&
Context.QueriedNetbiosDomainName != NULL ) {
RtlCopyMemory( Where,
Context.QueriedNetbiosDomainName,
NetbiosDomainNameSize );
Where += NetbiosDomainNameSize;
} else {
NetStatus = ERROR_INTERNAL_ERROR;
goto Cleanup;
}
if (NlDcCacheEntry->UnicodeDnsForestName != NULL ) {
(*DomainControllerInfo)->DnsForestName = (LPWSTR)Where;
RtlCopyMemory( Where,
NlDcCacheEntry->UnicodeDnsForestName,
DnsForestNameSize );
Where += DnsForestNameSize;
ReturnFlags |= DS_DNS_FOREST_FLAG;
} else {
(*DomainControllerInfo)->DnsForestName = NULL;
}
if (NlDcCacheEntry->UnicodeDcSiteName != NULL ) {
(*DomainControllerInfo)->DcSiteName = (LPWSTR)Where;
RtlCopyMemory( Where,
NlDcCacheEntry->UnicodeDcSiteName,
DcSiteNameSize );
Where += DcSiteNameSize;
} else {
(*DomainControllerInfo)->DcSiteName = NULL;
}
if (NlDcCacheEntry->UnicodeClientSiteName != NULL ) {
(*DomainControllerInfo)->ClientSiteName = (LPWSTR)Where;
RtlCopyMemory( Where,
NlDcCacheEntry->UnicodeClientSiteName,
ClientSiteNameSize );
Where += ClientSiteNameSize;
} else {
(*DomainControllerInfo)->ClientSiteName = NULL;
}
(*DomainControllerInfo)->Flags = ReturnFlags;
}
}
//
// Print the debug info pertaing to load balancing
//
// We are logging successful discoveries on workstations
// which have the following characteristics:
//
// * DNS based discovery
// * Forced discovery
// * Primary domain discovery
// * With default site name
// * With no account
// * Generic discovery
//
#ifdef _NETLOGON_SERVER
IF_NL_DEBUG( MISC ) {
if ( NlGlobalMemberWorkstation &&
(NetStatus == NO_ERROR) &&
(NlDcCacheEntry->CacheEntryFlags & NL_DC_CACHE_LDAP) && !UsedNetbios &&
(Context.QueriedFlags & DS_FORCE_REDISCOVERY) &&
(Context.QueriedInternalFlags & DS_IS_PRIMARY_DOMAIN) &&
(Context.QueriedInternalFlags & DS_SITENAME_DEFAULTED) &&
(Context.QueriedAccountName == NULL) &&
(Context.DcQueryType == NlDcQueryGenericDc || Context.DcQueryType == NlDcQueryKdc) ) {
LPSTR FlagsBuffer = LocalAlloc( LMEM_ZEROINIT, 200 );
if ( FlagsBuffer != NULL ) {
DsFlagsToString( Flags, FlagsBuffer );
NlPrint(( NL_MISC,
"LoadBalanceDebug (Flags: %s): DC=%ws, SrvCount=%lu, FailedAQueryCount=%lu, DcsPinged=%lu, LoopIndex=%lu\n",
FlagsBuffer,
NlDcCacheEntry->UnicodeNetbiosDcName,
Context.SiteSpecificSrvRecordCount,
Context.SiteSpecificFailedAQueryCount,
Context.DcsPinged,
Context.TryCount ));
LocalFree( FlagsBuffer );
}
}
}
#endif // _NETLOGON_SERVER
//
// Return the cache entry if requested
//
if ( NetStatus == NO_ERROR && DomainControllerCacheEntry != NULL ) {
*DomainControllerCacheEntry = NlDcCacheEntry;
NlDcCacheEntry = NULL;
}
//
// Free local resources
//
NetpDcUninitializeContext( &Context );
if ( PingResponseMessage != NULL ) {
NetpMemoryFree( PingResponseMessage );
}
if ( NlDcCacheEntry != NULL ) {
NetpDcDerefCacheEntry( NlDcCacheEntry );
}
if ( LocalQueriedlNetbiosDomainName != NULL ) {
NetApiBufferFree( LocalQueriedlNetbiosDomainName );
}
#ifdef _NETLOGON_SERVER
} except( NL_EXCEPTION) {
NetStatus = NetpNtStatusToApiStatus(GetExceptionCode());
}
#endif // _NETLOGON_SERVER
return NetStatus;
}
DWORD
DsIGetDcName(
IN LPCWSTR ComputerName OPTIONAL,
IN LPCWSTR AccountName OPTIONAL,
IN ULONG AllowableAccountControlBits,
IN LPCWSTR DomainName OPTIONAL,
IN LPCWSTR DnsForestName OPTIONAL,
IN GUID *DomainGuid OPTIONAL,
IN LPCWSTR SiteName OPTIONAL,
IN ULONG Flags,
IN ULONG InternalFlags,
IN PVOID SendDatagramContext OPTIONAL,
IN DWORD Timeout,
IN LPWSTR NetbiosPrimaryDomainName OPTIONAL,
IN LPWSTR DnsPrimaryDomainName OPTIONAL,
IN GUID *PrimaryDomainGuid OPTIONAL,
IN LPWSTR DnsTrustedDomainName OPTIONAL,
IN LPWSTR NetbiosTrustedDomainName OPTIONAL,
OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
)
/*++
Routine Description:
Same as DsGetDcNameW.
This is the internal routine called by DsGetDcNameW (in the context of
the original caller) or DsrGetDcName (in the context of the Netlogon
service) to actaully implement DsGetDcNameW.
Arguments:
Same as DsGetDcNameW except for the following additional parameters:
ComputerName - Specifies the NETBIOS name of this computer.
If NULL, the name will be dynamically determined.
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.
DnsForestName - The DNS-style name of the tree the queried domain is in.
SendDatagramContext - Specifies context to pass a NlBrowserSendDatagram
Timeout - Maximum time (in milliseconds) caller is willing to wait on
this operation.
InternalFlags - Internal Flags used to pass additional information
NetbiosPrimaryDomainName - Netbios name of the domain this machine belongs
to.
DnsPrimaryDomainName - DNS name of the domain this machine belongs to.
PrimaryDomainGuid - GUID of the primary domain.
DnsTrustedName - DNS name of the queried domain as was found in the
trust list.
If not specified, the specified domain isn't a trusted domain.
NetbiosTrustedDomainName - Netbios name of the queried domain as was found in the
trust list.
If not specified, the specified domain isn't a trusted domain.
Return Value:
Same as DsGetDcNameW.
--*/
{
NET_API_STATUS NetStatus;
LPCWSTR NetbiosDomainName = NULL;
LPCWSTR DnsDomainName = NULL;
ULONG SamAllowableAccountControlBits;
ULONG FormatCount = 0;
BOOLEAN CallerSpecifiedDomain = FALSE;
#ifdef WIN32_CHICAGO
LPSTR pDomainName = NULL;
#endif // WIN32_CHICAGO
//
// Ensure caller didn't specify both name type flags.
//
if ( ((Flags & (DS_IS_FLAT_NAME|DS_IS_DNS_NAME)) ==
(DS_IS_FLAT_NAME|DS_IS_DNS_NAME) ) ||
((Flags & (DS_RETURN_FLAT_NAME|DS_RETURN_DNS_NAME)) ==
(DS_RETURN_FLAT_NAME|DS_RETURN_DNS_NAME)) ) {
NetStatus = ERROR_INVALID_FLAGS;
goto Cleanup;
}
//
// If the caller specified, DS_RETURN_DNS_NAME, we should really
// set DS_IP_REQUIRED.
if ((Flags & DS_RETURN_DNS_NAME) == DS_RETURN_DNS_NAME) {
Flags |= DS_IP_REQUIRED;
}
//
// If the caller didn't specify a domain name,
// use our domain name.
//
if ( DomainName == NULL || *DomainName == L'\0' ) {
#ifndef WIN32_CHICAGO
//
// If the caller wants a GC,
// the domain to use is the tree name.
//
// If we don't yet know our tree name,
// it is better to try with our primary name than to not try at all
//
//
if ( (Flags & DS_GC_SERVER_REQUIRED) != 0 &&
DnsForestName != NULL ) {
NetbiosDomainName = NULL;
DnsDomainName = DnsForestName;
//
// Otherwise, the domain to use is the primary domain name.
// Do this even if the primary domain name is a workgroup
// name because there might be a DC in this workgroup/domain
// which the caller is trying to discover.
//
} else {
NetbiosDomainName = NetbiosPrimaryDomainName;
DnsDomainName = DnsPrimaryDomainName;
InternalFlags |= DS_IS_PRIMARY_DOMAIN | DS_IS_TRUSTED_DNS_DOMAIN;
}
#else // WIN32_CHICAGO
#define NETWORK_PROVIDER_KEY ("System\\CurrentControlSet\\Services\\Msnp32\\NetworkProvider")
#define AUTH_AGENT_VALUE ("AuthenticatingAgent")
do {
HKEY hRegKey = NULL;
DWORD dwError = 0, dwSize = 0, dwType;
if (ERROR_SUCCESS != (dwError = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
NETWORK_PROVIDER_KEY,
0,
KEY_ALL_ACCESS,
&hRegKey ) ) )
{
break;
}
// get domain name size
if ( ERROR_SUCCESS != ( dwError = RegQueryValueEx (
hRegKey,
AUTH_AGENT_VALUE,
NULL,
&dwType,
NULL,
&dwSize )))
{
break;
}
if (dwSize == 0 )
{
break;
}
pDomainName = (LPSTR) NetpMemoryAllocate((dwSize+1 ) * sizeof(WCHAR));
if (pDomainName == NULL)
{
break;
}
// get domainname
if ( ERROR_SUCCESS != ( dwError = RegQueryValueEx (
hRegKey,
AUTH_AGENT_VALUE,
NULL,
&dwType,
(PUCHAR) pDomainName,
&dwSize )))
{
break;
}
NetbiosDomainName = (LPCWSTR)NetpAllocWStrFromOemStr(pDomainName);
} while (FALSE);
if (NetbiosDomainName == NULL)
{
NetStatus = ERROR_INVALID_DOMAINNAME;
goto Cleanup;
}
//
// Indicate that the caller passed NULL as the domain name
//
InternalFlags |= DS_CALLER_PASSED_NULL_DOMAIN;
#endif // WIN32_CHICAGO
//
// If the caller did specify a domain name,
// validate it.
//
} else {
CallerSpecifiedDomain = TRUE;
//
// If the specified domain name is a syntactically valid DNS name,
// use it as a DNS name.
//
// Don't even try to check if caller claims it is a NETBIOS name.
//
if ( (Flags & DS_IS_FLAT_NAME) == 0 &&
NetpDcValidDnsDomain( DomainName )) {
DnsDomainName = DomainName;
FormatCount ++;
//
// If the primary domain name specified is not a
// workgroup name and
// If the caller specified the DNS primary domain name,
// we don't need to guess the Netbios domain name.
//
if ( (InternalFlags & DS_PRIMARY_NAME_IS_WORKGROUP) == 0 &&
DnsPrimaryDomainName != NULL &&
NlEqualDnsName( DnsPrimaryDomainName, DomainName ) ) {
//
// If the DNS name is specified on input,
// don't fall back to the Netbios name for discovery.
// There is no benefit to trying the Netbios name.
// Also, when using the netbios name, we don't know when all
// of the DCs have responded negatively. So, we can't early
// out.
//
if ( NetbiosPrimaryDomainName != NULL &&
NlEqualDnsName( DnsPrimaryDomainName, NetbiosPrimaryDomainName ) ) {
// Unless, of course the netbios and DNS domain names are spelled the same.
NetbiosDomainName = NetbiosPrimaryDomainName;
}
InternalFlags |= DS_IS_PRIMARY_DOMAIN | DS_IS_TRUSTED_DNS_DOMAIN;
Flags |= DS_IS_DNS_NAME;
//
// But return the DNS name unless the caller has explicitly
// asked for the Netbios name.
//
if ( (Flags & DS_RETURN_FLAT_NAME) == 0 ) {
Flags |= DS_RETURN_DNS_NAME;
}
//
// If the caller specified the DNS trusted domain name,
// we don't need to guess the Netbios domain name.
//
} else if ( DnsTrustedDomainName != NULL &&
NlEqualDnsName( DnsTrustedDomainName, DomainName ) ) {
//
// If the DNS name is specified on input,
// don't fall back to the Netbios name for discovery.
// There is no benefit to trying the Netbios name.
// Also, when using the netbios name, we don't know when all
// of the DCs have responded negatively. So, we can't early
// out.
//
if ( NetbiosTrustedDomainName != NULL &&
NlEqualDnsName( DnsTrustedDomainName, NetbiosTrustedDomainName ) ) {
// Unless, of course the netbios and DNS domain names are spelled the same.
NetbiosDomainName = NetbiosTrustedDomainName;
}
InternalFlags |= DS_IS_TRUSTED_DNS_DOMAIN;
Flags |= DS_IS_DNS_NAME;
//
// But return the DNS name unless the caller has explicitly
// asked for the Netbios name.
//
if ( (Flags & DS_RETURN_FLAT_NAME) == 0 ) {
Flags |= DS_RETURN_DNS_NAME;
}
}
}
//
// If the specified domain name is a syntactically valid Netbios name,
// use it as a Netbios name.
// (Don't even try to check if caller claims it is a DNS name)
//
if ( (Flags & DS_IS_DNS_NAME) == 0 &&
NetpIsDomainNameValid( (LPWSTR) DomainName )) {
NetbiosDomainName = DomainName;
FormatCount ++;
//
// If the primary domain name specified is not a
// workgroup name and
// If the caller specified the Netbios primary domain name,
// we don't need to guess the DNS domain name.
//
if ( (InternalFlags & DS_PRIMARY_NAME_IS_WORKGROUP) == 0 &&
NetbiosPrimaryDomainName != NULL &&
NlNameCompare( (LPWSTR) DomainName,
NetbiosPrimaryDomainName,
NAMETYPE_DOMAIN ) == 0 ) {
//
// Use both the DNS name and the Netbios name to do the discovery
// (Use the DNS name since it is rename safe.)
//
DnsDomainName = DnsPrimaryDomainName;
InternalFlags |= DS_IS_PRIMARY_DOMAIN | DS_IS_TRUSTED_DNS_DOMAIN;
Flags |= DS_IS_FLAT_NAME;
//
// But return the netbios name unless the caller has explicitly
// asked for the DNS name.
//
if ( (Flags & DS_RETURN_DNS_NAME) == 0 ) {
Flags |= DS_RETURN_FLAT_NAME;
}
//
// If the caller specified a DNS trust domain name and
// the Netbios trust domain name corresponds to the queried
// domain name, use the DNS trust domain.
//
// Note that we use DNS trust domain name only if it's not
// NULL. We do this because we may not know the DNS trust
// domain name if the trust domain got upgraded (because
// we don't update TDOs on the trusting side). In such case,
// if we were to reset the DNS name (already set above) of the
// domain we are searching to NULL, we would do a dis-service
// as we would skip legitimate DNS based discovery in case the
// upgraded DNS domain name is same as the Netbios domain name.
// Note that by not setting DNS domain name to NULL we risk doing
// unnecessary DNS discovery for cases when the trust domain is
// indeed NT4.0. However, most NT4.0 domain names are single labeled,
// so the code below that disallowes single labeled DNS domain names
// will likely catch this case and reset the DNS domain name to NULL.
//
} else if ( DnsTrustedDomainName != NULL &&
NetbiosTrustedDomainName != NULL &&
NlNameCompare( (LPWSTR) DomainName,
NetbiosTrustedDomainName,
NAMETYPE_DOMAIN ) == 0 ) {
//
// Use both the DNS name and the Netbios name to do the discovery
// (Use the DNS name since it is rename safe.)
//
DnsDomainName = DnsTrustedDomainName;
InternalFlags |= DS_IS_TRUSTED_DNS_DOMAIN;
Flags |= DS_IS_FLAT_NAME;
//
// But return the netbios name unless the caller has explicitly
// asked for the DNS name.
//
if ( (Flags & DS_RETURN_DNS_NAME) == 0 ) {
Flags |= DS_RETURN_FLAT_NAME;
}
}
}
}
//
// Disallow single labeled DNS domain names if the caller
// specified one unless the domain is proven to exist
// (i.e. trusted) or we are forced to allow such names.
//
// Note that we don't really trust the caller here:
// We won't allow single labeled DNS domain name even if
// the caller claims it's a DNS name (via DS_IS_DNS_NAME)
//
if ( DnsDomainName != NULL &&
CallerSpecifiedDomain &&
(InternalFlags & DS_IS_TRUSTED_DNS_DOMAIN) == 0 ) {
BOOLEAN SingleLabel = FALSE;
LPWSTR Period = NULL;
LPWSTR SecondPeriod = NULL;
Period = wcsstr( DnsDomainName, L"." );
//
// If there is no period in the name,
// the name is single labeled
//
if ( Period == NULL ) {
SingleLabel = TRUE;
//
// If there is a period, the name is
// single labeled if this is the only
// period and it's either the first
// or the last character in the name
//
} else {
SecondPeriod = wcsstr( Period+1, L"." );
if ( SecondPeriod == NULL ) {
if ( Period == DnsDomainName ||
*(Period+1) == UNICODE_NULL ) {
SingleLabel = TRUE;
}
}
}
//
// If this is single label DNS name,
// disallow it unless we are forced
// otherwise via the registry config
//
if ( SingleLabel ) {
DWORD LocalAllowSingleLabelDnsDomain = 0;
//
// In netlogon, the boolean is kept in global parameters
//
#ifdef _NETLOGON_SERVER
if ( !NlGlobalParameters.AllowSingleLabelDnsDomain ) {
NlPrint(( NL_MISC, "DsIGetDcName: Ignore single label DNS domain name %ws\n", DnsDomainName ));
DnsDomainName = NULL;
}
//
// If we are not running in netlogon, we need to read
// the boolean directly from the registry
//
#else
NlReadDwordNetlogonRegValue( "AllowSingleLabelDnsDomain",
&LocalAllowSingleLabelDnsDomain );
if ( !LocalAllowSingleLabelDnsDomain ) {
NlPrint(( NL_MISC, "DsIGetDcName: Ignore single label DNS domain name %ws\n", DnsDomainName ));
DnsDomainName = NULL;
}
#endif // _NETLOGON_SERVER
}
}
//
// If the name is neither a netbios or DNS name,
// give up.
//
if ( NetbiosDomainName == NULL && DnsDomainName == NULL ) {
NetStatus = ERROR_INVALID_DOMAINNAME;
goto Cleanup;
}
//
// If this is the primary domain,
// and the caller didn't specify a GUID,
// use the GUID of the primary domain.
//
if ( (InternalFlags & DS_IS_PRIMARY_DOMAIN) != 0 &&
DomainGuid == NULL ) {
DomainGuid = PrimaryDomainGuid;
}
//
// If the format is ambiguous,
// pass that info on down.
//
if ( FormatCount > 1 ) {
InternalFlags |= DS_NAME_FORMAT_AMBIGUOUS;
}
//
// Map the AllowableAccountControlBits to the SAM representation.
//
SamAllowableAccountControlBits = 0;
if ( AllowableAccountControlBits & UF_TEMP_DUPLICATE_ACCOUNT ) {
SamAllowableAccountControlBits |= USER_TEMP_DUPLICATE_ACCOUNT;
}
if ( AllowableAccountControlBits & UF_NORMAL_ACCOUNT ) {
SamAllowableAccountControlBits |= USER_NORMAL_ACCOUNT;
}
if (AllowableAccountControlBits & UF_INTERDOMAIN_TRUST_ACCOUNT){
SamAllowableAccountControlBits |= USER_INTERDOMAIN_TRUST_ACCOUNT;
}
if (AllowableAccountControlBits & UF_WORKSTATION_TRUST_ACCOUNT){
SamAllowableAccountControlBits |= USER_WORKSTATION_TRUST_ACCOUNT;
}
if ( AllowableAccountControlBits & UF_SERVER_TRUST_ACCOUNT ) {
SamAllowableAccountControlBits |= USER_SERVER_TRUST_ACCOUNT;
}
//
// Try finding a DC with this information.
//
NetStatus = NetpDcGetName(
SendDatagramContext,
ComputerName,
AccountName,
SamAllowableAccountControlBits,
NetbiosDomainName,
DnsDomainName,
DnsForestName,
NULL, // No Domain Sid
DomainGuid,
SiteName,
Flags,
InternalFlags,
Timeout,
MAX_DC_RETRIES,
DomainControllerInfo,
NULL );
Cleanup:
#ifdef WIN32_CHICAGO
if (pDomainName)
{
NetpMemoryFree(pDomainName);
pDomainName = NULL;
if ( NetbiosDomainName != NULL ) {
NetpMemoryFree((LPWSTR)NetbiosDomainName);
}
}
#endif // WIN32_CHICAGO
return NetStatus;
}
#ifndef WIN32_CHICAGO
NET_API_STATUS
NlParseSubnetString(
IN LPCWSTR SubnetName,
OUT PULONG SubnetAddress,
OUT PULONG SubnetMask,
OUT LPBYTE SubnetBitCount
)
/*++
Routine Description:
Convert the subnet name to address and bit count.
Arguments:
SubnetName - Subnet string
SubnetAddress - Returns the subnet number in Network byte order.
SubnetMask - Returns the subnet mask in network byte order
SubnetBitCount - Returns the number of leftmost significant bits in the
SubnetAddress
Return Value:
NO_ERROR: success
ERROR_INVALID_NAME: Syntax of SubnetName is bad.
WSANOTINITIALISED: WSA needs to be initialized before making this call
--*/
{
LPWSTR SlashPointer;
WCHAR *End;
ULONG LocalBitCount;
WCHAR LocalSubnetName[NL_SOCK_ADDRESS_LENGTH*2];
WCHAR CanonicalSubnetName[NL_SOCK_ADDRESS_LENGTH+1];
ULONG CanonicalSubnetNameLen;
WCHAR CanonicalBitCount[3];
static ULONG BitMask[] = {
0x00000000,
0x00000080,
0x000000C0,
0x000000E0,
0x000000F0,
0x000000F8,
0x000000FC,
0x000000FE,
0x000000FF,
0x000080FF,
0x0000C0FF,
0x0000E0FF,
0x0000F0FF,
0x0000F8FF,
0x0000FCFF,
0x0000FEFF,
0x0000FFFF,
0x0080FFFF,
0x00C0FFFF,
0x00E0FFFF,
0x00F0FFFF,
0x00F8FFFF,
0x00FCFFFF,
0x00FEFFFF,
0x00FFFFFF,
0x80FFFFFF,
0xC0FFFFFF,
0xE0FFFFFF,
0xF0FFFFFF,
0xF8FFFFFF,
0xFCFFFFFF,
0xFEFFFFFF,
0xFFFFFFFF };
INT WsaStatus;
SOCKADDR_IN SockAddrIn;
INT SockAddrSize;
//
// Copy the string to where we can munge it.
//
if ( wcslen(SubnetName) + 1 > sizeof(LocalSubnetName)/sizeof(WCHAR) ) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: String too long\n", SubnetName ));
return ERROR_INVALID_NAME;
}
wcscpy( LocalSubnetName, SubnetName );
//
// Find the subnet bit count.
//
SlashPointer = wcschr( LocalSubnetName, L'/' );
if ( SlashPointer == NULL ) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: Bit Count missing %ld\n", SubnetName ));
return ERROR_INVALID_NAME;
}
//
// Zero terminate the address portion of the subnet name.
//
*SlashPointer = L'\0';
//
// Get the BitCount portion.
//
LocalBitCount = wcstoul( SlashPointer+1, &End, 10 );
if ( LocalBitCount == 0 || LocalBitCount > 32 ) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: Bit Count bad %ld\n", SubnetName, LocalBitCount ));
return ERROR_INVALID_NAME;
}
if ( *End != L'\0' ) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: Bit Count not at string end\n", SubnetName ));
return ERROR_INVALID_NAME;
}
ultow( LocalBitCount, CanonicalBitCount, 10 );
if ( wcscmp( SlashPointer+1, CanonicalBitCount ) != 0 ) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: not canonical %ws\n", SlashPointer+1, CanonicalBitCount ));
return ERROR_INVALID_NAME;
}
*SubnetBitCount = (BYTE)LocalBitCount;
//
// Convert the address portion to binary.
//
SockAddrSize = sizeof(SockAddrIn);
WsaStatus = WSAStringToAddressW( (LPWSTR)LocalSubnetName,
AF_INET,
NULL,
(PSOCKADDR)&SockAddrIn,
&SockAddrSize );
if ( WsaStatus != 0 ) {
WsaStatus = WSAGetLastError();
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: Wsa Error %ld\n", SubnetName, WsaStatus ));
if ( WsaStatus == WSAEFAULT ||
WsaStatus == WSAEINVAL ) {
return ERROR_INVALID_NAME;
}
return WsaStatus;
}
if ( SockAddrIn.sin_family != AF_INET ) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: Not AF_INET\n", SubnetName ));
return ERROR_INVALID_NAME;
}
*SubnetAddress = SockAddrIn.sin_addr.S_un.S_addr;
*SubnetMask = BitMask[*SubnetBitCount];
//
// Require that the passed in string be in canonical form.
// (e.g., no leading zeros). Since this text string is used as the
// name of an object in the DS, we don't want two different text strings
// to represent the same subnet.
//
CanonicalSubnetNameLen = sizeof(CanonicalSubnetName)/sizeof(WCHAR);
WsaStatus = WSAAddressToStringW( (PSOCKADDR)&SockAddrIn,
SockAddrSize,
NULL,
CanonicalSubnetName,
&CanonicalSubnetNameLen );
if ( WsaStatus != 0 ) {
WsaStatus = WSAGetLastError();
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: Wsa Error %ld\n", SubnetName, WsaStatus ));
if ( WsaStatus == WSAEFAULT ||
WsaStatus == WSAEINVAL ) {
return ERROR_INVALID_NAME;
}
return WsaStatus;
}
if ( wcscmp( LocalSubnetName, CanonicalSubnetName ) != 0 ) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: not canonical %ws\n", SubnetName, CanonicalSubnetName ));
return ERROR_INVALID_NAME;
}
//
// Ensure there are no bits set that aren't included in the subnet mask.
//
if ( (*SubnetAddress) & ~(*SubnetMask)) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: bits not in subnet mask %8.8lX %8.8lX\n", SubnetName, *SubnetAddress, *SubnetMask ));
return ERROR_INVALID_NAME;
}
//
// Ensure the subnet mask isn't 0 since
// RFC950 says "the value of all zeros and all ones should not be assigned
// to physical subnets" since all zeros implies "this network" and all ones
// implies "all hosts"
if ( *SubnetAddress == 0 ||
*SubnetAddress == *SubnetMask ) {
NlPrint(( NL_CRITICAL,
"NlParseSubnetString: %ws: all zero or all one subnet address %8.8lX %8.8lX\n", SubnetName, *SubnetAddress, *SubnetMask ));
return ERROR_INVALID_NAME;
}
return NO_ERROR;
}
#endif // WIN32_CHICAGO
#ifdef _NETLOGON_SERVER
VOID
NetpDcFlushNegativeCache(
VOID
)
/*++
Routine Description:
Flush any failures to discover a DC.
Arguments:
None.
Return Value:
None.
--*/
{
PLIST_ENTRY DomainEntry;
PNL_DC_DOMAIN_ENTRY NlDcDomainEntry;
ULONG QueryType;
//
// Loop through each cache entry
//
EnterCriticalSection(&NlDcCritSect);
for ( DomainEntry = NlDcDomainList.Flink ;
DomainEntry != &NlDcDomainList;
DomainEntry = DomainEntry->Flink ) {
NlDcDomainEntry = CONTAINING_RECORD( DomainEntry, NL_DC_DOMAIN_ENTRY, Next);
//
// Clear the failure time for each query type.
//
for ( QueryType = 0; QueryType < NlDcQueryTypeCount; QueryType ++ ) {
NlFlushNegativeCacheEntry( &NlDcDomainEntry->Dc[QueryType] );
}
}
LeaveCriticalSection(&NlDcCritSect);
return;
}
#endif // _NETLOGON_SERVER
NET_API_STATUS
NetpDcInitializeCache(
VOID
)
/*++
Routine Description:
Initialize the cache of discovered DCs.
Arguments:
None.
Return Value:
NO_ERROR - Operation completed successfully;
ERROR_NOT_ENOUGH_MEMORY - The cache could not be allocated.
--*/
{
NET_API_STATUS NetStatus = NO_ERROR;
try {
InitializeCriticalSection( &NlDcCritSect );
} except( EXCEPTION_EXECUTE_HANDLER ) {
NlPrint(( NL_CRITICAL,
"NetpDcInitializeCache: Cannot initialize NlDcCritSect\n" ));
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
}
InitializeListHead( &NlDcDomainList );
NlDcDomainCount = 0;
RtlZeroMemory( &NlDcZeroGuid, sizeof(NlDcZeroGuid) );
NlDcDnsFailureTime = 0;
return NetStatus;
}
VOID
NetpDcUninitializeCache(
VOID
)
/*++
Routine Description:
Uninitialize the cache of discovered DCs.
Arguments:
None.
Return Value:
None.
--*/
{
//
// Delete existing domain entries.
//
EnterCriticalSection( &NlDcCritSect );
while (!IsListEmpty(&NlDcDomainList)) {
PNL_DC_DOMAIN_ENTRY NlDcDomainEntry =
CONTAINING_RECORD( NlDcDomainList.Flink, NL_DC_DOMAIN_ENTRY, Next);
NlAssert( NlDcDomainEntry->ReferenceCount == 1 );
NetpDcDeleteDomainEntry( NlDcDomainEntry );
}
LeaveCriticalSection( &NlDcCritSect );
DeleteCriticalSection( &NlDcCritSect );
}