Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

4976 lines
129 KiB

/*++
Copyright (c) 1987-1992 Microsoft Corporation
Module Name:
trustutl.c
Abstract:
Utilities manange of trusted domain list.
Author:
30-Jan-92 (cliffv)
Environment:
User mode only.
Contains NT-specific code.
Requires ANSI C extensions: slash-slash comments, long external names.
Revision History:
--*/
//
// Common include files.
//
#include <logonsrv.h> // Include files common to entire service
//
// Include files specific to this .c file
//
#include <ntlsa.h>
#include <alertmsg.h> // ALERT_* defines
#include <align.h>
#include <config.h> // net config helpers.
#include <confname.h> // SECTION_ equates, NETLOGON_KEYWORD_ equates.
#include <lmerr.h>
#include <stdlib.h> // C library functions (rand, etc)
#include <tstring.h>
#include <lmapibuf.h>
#include <lmuse.h> // NetUseDel
#include <names.h> // NetpIsUserNameValid
#include <nlbind.h> // Netlogon RPC binding cache routines
PCLIENT_SESSION
NlFindNamedClientSession(
IN PUNICODE_STRING DomainName
)
/*++
Routine Description:
Find the specified entry in the Trust List.
Arguments:
DomainName - The name of the domain to find.
Return Value:
Returns a pointer to the found entry.
The found entry is returned referenced and must be dereferenced using
NlUnrefClientSession.
If there is no such entry, return NULL.
--*/
{
PCLIENT_SESSION ClientSession = NULL;
//
// On DC, look up the domain in the trusted domain list.
//
if ( NlGlobalRole == RoleBackup || NlGlobalRole == RolePrimary ) {
PLIST_ENTRY ListEntry;
//
// Lookup the ClientSession with the TrustList locked and reference
// the found entry before dropping the lock.
//
LOCK_TRUST_LIST();
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
ListEntry = ListEntry->Flink) {
ClientSession =
CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
if ( RtlEqualDomainName( &ClientSession->CsDomainName,
DomainName ) ) {
NlRefClientSession( ClientSession );
break;
}
ClientSession = NULL;
}
UNLOCK_TRUST_LIST();
}
//
// On a workstation or BDC, refer to the Primary domain.
//
if ( (NlGlobalRole == RoleBackup && ClientSession == NULL) ||
NlGlobalRole == RoleMemberWorkstation ) {
if ( RtlEqualDomainName( &NlGlobalUnicodeDomainNameString,
DomainName ) ) {
ClientSession = NlGlobalClientSession;
NlRefClientSession( ClientSession );
} else {
ClientSession = NULL;
}
}
return ClientSession;
}
PCLIENT_SESSION
NlAllocateClientSession(
IN PUNICODE_STRING DomainName,
IN PSID DomainId,
IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType
)
/*++
Routine Description:
Allocate a ClientSession structure and initialize it.
The allocated entry is returned referenced and must be dereferenced using
NlUnrefClientSession.
Arguments:
DomainName - Specifies the DomainName of the entry.
DomainId - Specifies the DomainId of the Domain.
SecureChannelType -- Type of secure channel this ClientSession structure
will represent.
Return Value:
--*/
{
PCLIENT_SESSION ClientSession;
ULONG ClientSessionSize;
ULONG SidSize;
PCHAR Where;
//
// Determine the size of the ClientSession structure.
//
SidSize = RtlLengthSid( DomainId );
if ( DomainName->Length > DNLEN * sizeof(WCHAR) ) {
NlPrint((NL_CRITICAL,
"NlAllocateClientSession given "
"too long domain name %wZ\n", DomainName ));
return NULL;
}
ClientSessionSize = sizeof(CLIENT_SESSION) +
SidSize +
DomainName->Length + sizeof(WCHAR);
//
// Allocate the Client Session Entry
//
ClientSession = NetpMemoryAllocate( ClientSessionSize );
if (ClientSession == NULL) {
return NULL;
}
RtlZeroMemory( ClientSession, ClientSessionSize );
//
// Initialize misc. fields.
//
ClientSession->CsDomainId = NULL;
*ClientSession->CsUncServerName = L'\0';
ClientSession->CsTransportName = NULL;
ClientSession->CsSecureChannelType = SecureChannelType;
ClientSession->CsState = CS_IDLE;
ClientSession->CsReferenceCount = 1;
ClientSession->CsConnectionStatus = STATUS_NO_LOGON_SERVERS;
ClientSession->CsDiscoveryRetryCount = 0;
ClientSession->CsDiscoveryFlags = 0;
ClientSession->CsTimeoutCount = 0;
ClientSession->CsApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
InitializeListHead( &ClientSession->CsNext );
//
// Build the account name as a function of the SecureChannelType.
//
switch (SecureChannelType) {
case WorkstationSecureChannel:
case ServerSecureChannel:
wcscpy( ClientSession->CsAccountName, NlGlobalUnicodeComputerName );
wcscat( ClientSession->CsAccountName, SSI_ACCOUNT_NAME_POSTFIX);
break;
case TrustedDomainSecureChannel:
wcscpy( ClientSession->CsAccountName, NlGlobalUnicodeDomainName );
wcscat( ClientSession->CsAccountName, SSI_ACCOUNT_NAME_POSTFIX);
break;
default:
NetpMemoryFree( ClientSession );
return NULL;
}
//
// Create the writer semaphore.
//
ClientSession->CsWriterSemaphore = CreateSemaphore(
NULL, // No special security
1, // Initially not locked
1, // At most 1 unlocker
NULL ); // No name
if ( ClientSession->CsWriterSemaphore == NULL ) {
NetpMemoryFree( ClientSession );
return NULL;
}
//
// Create the Discovery event.
//
ClientSession->CsDiscoveryEvent = CreateEvent(
NULL, // No special security
TRUE, // Manual Reset
FALSE, // No discovery initially happening
NULL ); // No name
if ( ClientSession->CsDiscoveryEvent == NULL ) {
CloseHandle( ClientSession->CsWriterSemaphore );
NetpMemoryFree( ClientSession );
return NULL;
}
//
// Copy the DomainId and DomainName to the buffer.
//
Where = (PCHAR)(ClientSession + 1);
NlAssert( Where == ROUND_UP_POINTER( Where, ALIGN_DWORD) );
ClientSession->CsDomainId = (PSID) Where;
NetpLogonPutBytes( DomainId, SidSize, &Where );
NlAssert( Where == ROUND_UP_POINTER( Where, ALIGN_WCHAR) );
ClientSession->CsDomainName.Buffer = (LPWSTR) Where;
ClientSession->CsDomainName.Length = DomainName->Length;
ClientSession->CsDomainName.MaximumLength = (USHORT)
(DomainName->Length + sizeof(WCHAR));
NetpLogonPutBytes( DomainName->Buffer, DomainName->Length, &Where );
*(Where++) = '\0';
*(Where++) = '\0';
return ClientSession;
}
VOID
NlFreeClientSession(
IN PCLIENT_SESSION ClientSession
)
/*++
Routine Description:
Free the specified Trust list entry.
This routine is called with the Trust List locked.
Arguments:
ClientSession - Specifies a pointer to the trust list entry to delete.
Return Value:
--*/
{
//
// If someone has an outstanding pointer to this entry,
// delay the deletion for now.
//
if ( ClientSession->CsReferenceCount > 0 ) {
ClientSession->CsFlags |= CS_DELETE_ON_UNREF;
return;
}
//
// If this is a trusted domain secure channel,
// Delink the entry from the sequential list.
//
if (ClientSession->CsSecureChannelType == TrustedDomainSecureChannel ) {
RemoveEntryList( &ClientSession->CsNext );
NlGlobalTrustListLength --;
}
//
// Close the discovery event if it exists.
//
if ( ClientSession->CsDiscoveryEvent != NULL ) {
CloseHandle( ClientSession->CsDiscoveryEvent );
}
//
// Close the write synchronization handles.
//
(VOID) CloseHandle( ClientSession->CsWriterSemaphore );
//
// If there is an rpc binding handle to this server,
// unbind it.
if ( ClientSession->CsFlags & CS_BINDING_CACHED ) {
//
// Indicate the handle is no longer bound
//
NlGlobalBindingHandleCount --;
NlPrint((NL_SESSION_SETUP,
"NlFreeClientSession: %wZ: Unbind from server " FORMAT_LPWSTR ".\n",
&ClientSession->CsDomainName,
ClientSession->CsUncServerName ));
(VOID) NlBindingRemoveServerFromCache( ClientSession->CsUncServerName );
}
//
// Delete the entry
//
NetpMemoryFree( ClientSession );
}
VOID
NlRefClientSession(
IN PCLIENT_SESSION ClientSession
)
/*++
Routine Description:
Mark the specified client session as referenced.
On Entry,
The trust list must be locked.
Arguments:
ClientSession - Specifies a pointer to the trust list entry.
Return Value:
None.
--*/
{
//
// Simply increment the reference count.
//
ClientSession->CsReferenceCount ++;
}
VOID
NlUnrefClientSession(
IN PCLIENT_SESSION ClientSession
)
/*++
Routine Description:
Mark the specified client session as unreferenced.
On Entry,
The trust list entry must be referenced by the caller.
The caller must not be a writer of the trust list entry.
The trust list may be locked. But this routine will lock it again to
handle those cases where it isn't already locked.
Arguments:
ClientSession - Specifies a pointer to the trust list entry.
Return Value:
--*/
{
LOCK_TRUST_LIST();
//
// Dereference the entry.
//
NlAssert( ClientSession->CsReferenceCount > 0 );
ClientSession->CsReferenceCount --;
//
// If we're the last referencer and
// someone wanted to delete the entry while we had it referenced,
// finish the deletion.
//
if ( ClientSession->CsReferenceCount == 0 &&
(ClientSession->CsFlags & CS_DELETE_ON_UNREF) ) {
NlFreeClientSession( ClientSession );
}
UNLOCK_TRUST_LIST();
}
BOOL
NlTimeoutSetWriterClientSession(
IN PCLIENT_SESSION ClientSession,
IN DWORD Timeout
)
/*++
Routine Description:
Become a writer of the specified client session but fail the operation if
we have to wait more than Timeout milliseconds.
A writer can "write" many of the fields in the client session structure.
See the comments in ssiinit.h for details.
On Entry,
The trust list must NOT be locked.
The trust list entry must be referenced by the caller.
The caller must NOT be a writer of the trust list entry.
Actually, the trust list can be locked if the caller passes in a short
timeout (for instance, zero milliseconds.) Specifying a longer timeout
violates the locking order.
Arguments:
ClientSession - Specifies a pointer to the trust list entry.
Timeout - Maximum time (in milliseconds) to wait for a previous writer.
Return Value:
TRUE - The caller is now the writer of the client session.
FALSE - The operation has timed out.
--*/
{
DWORD WaitStatus;
NlAssert( ClientSession->CsReferenceCount > 0 );
//
// Wait for other writers to finish.
//
WaitStatus = WaitForSingleObject( ClientSession->CsWriterSemaphore, Timeout );
if ( WaitStatus != 0 ) {
NlPrint(( NL_CRITICAL,
"NlTimeoutSetWriterClientSession timed out: %ld\n",
WaitStatus ));
return FALSE;
}
//
// Become a writer.
//
LOCK_TRUST_LIST();
ClientSession->CsFlags |= CS_WRITER;
UNLOCK_TRUST_LIST();
return TRUE;
}
VOID
NlResetWriterClientSession(
IN PCLIENT_SESSION ClientSession
)
/*++
Routine Description:
Stop being a writer of the specified client session.
On Entry,
The trust list must NOT be locked.
The trust list entry must be referenced by the caller.
The caller must be a writer of the trust list entry.
Arguments:
ClientSession - Specifies a pointer to the trust list entry.
Return Value:
--*/
{
NlAssert( ClientSession->CsReferenceCount > 0 );
NlAssert( ClientSession->CsFlags & CS_WRITER );
//
// Stop being a writer.
//
LOCK_TRUST_LIST();
ClientSession->CsFlags &= ~CS_WRITER;
UNLOCK_TRUST_LIST();
//
// Allow writers to try again.
//
if ( !ReleaseSemaphore( ClientSession->CsWriterSemaphore, 1, NULL ) ) {
NlPrint((NL_CRITICAL,
"ReleaseSemaphore CsWriterSemaphore returned %ld\n",
GetLastError() ));
}
}
VOID
NlSetStatusClientSession(
IN PCLIENT_SESSION ClientSession,
IN NTSTATUS CsConnectionStatus
)
/*++
Routine Description:
Set the connection state for this client session.
On Entry,
The trust list must NOT be locked.
The trust list entry must be referenced by the caller.
The caller must be a writer of the trust list entry.
Arguments:
ClientSession - Specifies a pointer to the trust list entry.
CsConnectionStatus - the status of the connection.
Return Value:
--*/
{
BOOLEAN UnbindFromServer = FALSE;
WCHAR UncServerName[UNCLEN+1];
NlAssert( ClientSession->CsReferenceCount > 0 );
NlAssert( ClientSession->CsFlags & CS_WRITER );
NlPrint((NL_SESSION_SETUP,
"NlSetStatusClientSession: %wZ: Set connection status to %lx\n",
&ClientSession->CsDomainName,
CsConnectionStatus ));
EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
ClientSession->CsConnectionStatus = CsConnectionStatus;
if ( NT_SUCCESS(CsConnectionStatus) ) {
ClientSession->CsState = CS_AUTHENTICATED;
//
// Handle setting the connection status to an error condition.
//
} else {
//
// If there is an rpc binding handle to this server,
// unbind it.
LOCK_TRUST_LIST();
if ( ClientSession->CsFlags & CS_BINDING_CACHED ) {
//
// Indicate the handle is no longer bound
//
ClientSession->CsFlags &= ~CS_BINDING_CACHED;
NlGlobalBindingHandleCount --;
//
// Capture the ServerName
//
wcscpy( UncServerName, ClientSession->CsUncServerName );
UnbindFromServer = TRUE;
}
UNLOCK_TRUST_LIST();
//
// If this is a BDC that just lost it's PDC,
// Indicate we don't know who the PDC is anymore.
//
if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) {
NlSetPrimaryName( NULL );
}
//
// Indicate discovery is needed (And can be done at any time.)
//
ClientSession->CsState = CS_IDLE;
*ClientSession->CsUncServerName = L'\0';
ClientSession->CsTransportName = NULL;
ClientSession->CsTimeoutCount = 0;
ClientSession->CsLastAuthenticationTry.QuadPart = 0;
//
// Don't be tempted to clear CsAuthenticationSeed and CsSessionKey here.
// Even though the secure channel is gone, NlFinishApiClientSession may
// have dropped it. The caller of NlFinishApiClientSession will use
// the above two fields after the session is dropped in an attempt to
// complete the final call on the secure channel.
//
}
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
//
// Now that I have as many resources unlocked as possible,
// Unbind from this server.
//
if ( UnbindFromServer ) {
NlPrint((NL_SESSION_SETUP,
"NlSetStatusClientSession: %wZ: Unbind from server " FORMAT_LPWSTR ".\n",
&ClientSession->CsDomainName,
UncServerName ));
(VOID) NlBindingRemoveServerFromCache( UncServerName );
}
}
NTSTATUS
NlInitTrustList(
VOID
)
/*++
Routine Description:
Initialize the in-memory trust list to match LSA's version.
Arguments:
None.
Return Value:
Status of the operation.
--*/
{
NTSTATUS Status;
LSA_ENUMERATION_HANDLE EnumerationContext = 0;
LSAPR_TRUSTED_ENUM_BUFFER LsaTrustList = {0, NULL};
ULONG LsaTrustListLength = 0;
ULONG LsaTrustListIndex = 0;
BOOL LsaAllDone = FALSE;
//
// Mark each entry in the trust list for deletion
//
//
// Loop through the LSA's list of domains
//
// For each entry found,
// If the entry already exits in the trust list,
// remove the mark for deletion.
// else
// allocate a new entry.
//
for (;; LsaTrustListIndex ++ ) {
PUNICODE_STRING DomainName;
PSID DomainId;
//
// Get more trusted domain names from the LSA.
//
if ( LsaTrustListIndex >= LsaTrustListLength ) {
//
// If we've already gotten everything from LSA,
// go delete entries that should be deleted.
//
if ( LsaAllDone ) {
break;
}
//
// Free any previous buffer returned from LSA.
//
if ( LsaTrustList.Information != NULL ) {
LsaIFree_LSAPR_TRUSTED_ENUM_BUFFER( &LsaTrustList );
LsaTrustList.Information = NULL;
}
//
// Do the actual enumeration
//
Status = LsarEnumerateTrustedDomains(
NlGlobalPolicyHandle,
&EnumerationContext,
&LsaTrustList,
1024);
LsaTrustListLength = LsaTrustList.EntriesRead;
// If Lsa says he's returned all of the information,
// remember not to ask Lsa for more.
//
if ( Status == STATUS_NO_MORE_ENTRIES ) {
LsaAllDone = TRUE;
break;
//
// If Lsa says there is more information, just ensure he returned
// something to us on this call.
//
} else if ( NT_SUCCESS(Status) ) {
if ( LsaTrustListLength == 0 ) {
Status = STATUS_BUFFER_TOO_SMALL;
goto Cleanup;
}
//
// All other status' are errors.
//
} else {
goto Cleanup;
}
LsaTrustListIndex = 0;
}
//
// At this point LsaTrustList[LsaTrustListIndex] is the next entry
// returned from the LSA.
//
DomainName =
(PUNICODE_STRING)
&(LsaTrustList.Information[LsaTrustListIndex].Name);
DomainId =
(PSID)LsaTrustList.Information[LsaTrustListIndex].Sid;
NlPrint((NL_SESSION_SETUP, "NlInitTrustList: %wZ in LSA\n",
DomainName ));
if ( DomainName->Length > DNLEN * sizeof(WCHAR) ) {
NlPrint((NL_CRITICAL,
"LsarEnumerateTrustedDomains returned "
"too long domain name %wZ\n", DomainName ));
continue;
}
if ( RtlEqualDomainName( &NlGlobalUnicodeDomainNameString,
DomainName ) ) {
NlPrint((NL_SESSION_SETUP, "NlInitTrustList: %wZ "
"ignoring trust relationship to our own domain\n",
DomainName ));
continue;
}
//
// Update the in-memory trust list to match the LSA.
//
Status = NlUpdateTrustListBySid ( DomainId, DomainName );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
}
//
// Trust list successfully updated.
//
Status = STATUS_SUCCESS;
Cleanup:
if ( LsaTrustList.Information != NULL ) {
LsaIFree_LSAPR_TRUSTED_ENUM_BUFFER( &LsaTrustList );
}
return Status;
}
VOID
NlPickTrustedDcForEntireTrustList(
VOID
)
/*++
Routine Description:
For each domain in the trust list where the DC has not been
available for at least 45 seconds, try to select a new DC.
Arguments:
None.
Return Value:
Status of the operation.
--*/
{
PLIST_ENTRY ListEntry;
PCLIENT_SESSION ClientSession;
LOCK_TRUST_LIST();
//
// Mark each entry to indicate we need to pick a DC.
//
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
ListEntry = ListEntry->Flink) {
ClientSession = CONTAINING_RECORD( ListEntry,
CLIENT_SESSION,
CsNext );
ClientSession->CsFlags |= CS_PICK_DC;
}
//
// Loop thru the trust list finding secure channels needing the DC
// to be picked.
//
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
) {
ClientSession = CONTAINING_RECORD( ListEntry,
CLIENT_SESSION,
CsNext );
//
// If we've already done this entry,
// skip this entry.
//
if ( (ClientSession->CsFlags & CS_PICK_DC) == 0 ) {
ListEntry = ListEntry->Flink;
continue;
}
ClientSession->CsFlags &= ~CS_PICK_DC;
//
// If the DC is already picked,
// skip this entry.
//
if ( ClientSession->CsState != CS_IDLE ) {
ListEntry = ListEntry->Flink;
continue;
}
//
// Reference this entry while picking the DC.
//
NlRefClientSession( ClientSession );
UNLOCK_TRUST_LIST();
//
// Check if we've tried to authenticate recently.
// (Don't call NlTimeToReauthenticate with the trust list locked.
// It locks NlGlobalDcDiscoveryCritSect. That's the wrong locking
// order.)
//
if ( NlTimeToReauthenticate( ClientSession ) ) {
//
// Try to pick the DC for the session.
//
if ( NlTimeoutSetWriterClientSession( ClientSession, 10*1000 ) ) {
(VOID) NlDiscoverDc( ClientSession, DT_DeadDomain );
NlResetWriterClientSession( ClientSession );
}
}
//
// Since we dropped the trust list lock,
// we'll start the search from the front of the list.
//
NlUnrefClientSession( ClientSession );
LOCK_TRUST_LIST();
ListEntry = NlGlobalTrustList.Flink ;
}
UNLOCK_TRUST_LIST();
//
// On a BDC,
// ensure we know who the PDC is.
//
// In NT 3.1, we relied on the fact that the PDC sent us pulses every 5
// minutes. For NT 3.5, the PDC backs off after 3 such failed attempts and
// will only send a pulse every 2 hours. So, we'll take on the
// responsibility
//
if ( NlGlobalRole == RoleBackup &&
NlGlobalClientSession->CsState == CS_IDLE ) {
//
// Check if we've tried to authenticate recently.
// (Don't call NlTimeToReauthenticate with the trust list locked.
// It locks NlGlobalDcDiscoveryCritSect. That's the wrong locking
// order.)
//
NlRefClientSession( NlGlobalClientSession );
if ( NlTimeToReauthenticate( NlGlobalClientSession ) ) {
//
// Try to pick the DC for the session.
//
if ( NlTimeoutSetWriterClientSession( NlGlobalClientSession, 10*1000 ) ) {
(VOID) NlDiscoverDc( NlGlobalClientSession, DT_DeadDomain );
NlResetWriterClientSession( NlGlobalClientSession );
}
}
NlUnrefClientSession( NlGlobalClientSession );
}
}
BOOL
NlReadSamLogonResponse (
IN HANDLE ResponseMailslotHandle,
IN LPWSTR AccountName,
OUT LPDWORD Opcode,
OUT LPWSTR *UncLogonServer
)
/*++
Routine Description:
Read a response from to a SamLogonRequest.
Arguments:
ResponseMailslotHandle - Handle of mailslot to read.
AccountName - Name of the account the response is for.
Opcode - Returns the opcode from the message. This will be one of
LOGON_SAM_LOGON_RESPONSE or LOGON_SAM_USER_UNKNOWN.
UncLogonServer - Returns the UNC name of the logon server that responded.
This buffer is only returned if a valid message was received.
The buffer returned should be freed via NetpMemoryFree.
Return Value:
TRUE: a valid message was received.
FALSE: a valid message was not received.
--*/
{
CHAR ResponseBuffer[MAX_RANDOM_MAILSLOT_RESPONSE];
PNETLOGON_SAM_LOGON_RESPONSE SamLogonResponse;
DWORD SamLogonResponseSize;
LPWSTR LocalServerName;
LPWSTR LocalUserName;
PCHAR Where;
DWORD Version;
DWORD VersionFlags;
//
// Loop ignoring responses which are garbled.
//
for ( ;; ) {
//
// Read the response from the response mailslot
// (This mailslot is set up with a 5 second timeout).
//
if ( !ReadFile( ResponseMailslotHandle,
ResponseBuffer,
sizeof(ResponseBuffer),
&SamLogonResponseSize,
NULL ) ) {
IF_DEBUG( MAILSLOT ) {
NET_API_STATUS NetStatus;
NetStatus = GetLastError();
if ( NetStatus != ERROR_SEM_TIMEOUT ) {
NlPrint((NL_CRITICAL,
"NlReadSamLogonResponse: "
"cannot read response mailslot: %ld\n",
NetStatus ));
}
}
return FALSE;
}
SamLogonResponse = (PNETLOGON_SAM_LOGON_RESPONSE) ResponseBuffer;
NlPrint((NL_MAILSLOT_TEXT, "NlReadSamLogonResponse opcode 0x%x\n",
SamLogonResponse->Opcode ));
NlpDumpBuffer(NL_MAILSLOT_TEXT, SamLogonResponse, SamLogonResponseSize);
//
// Ensure the opcode is expected.
// (Ignore responses from paused DCs, too.)
//
if ( SamLogonResponse->Opcode != LOGON_SAM_LOGON_RESPONSE &&
SamLogonResponse->Opcode != LOGON_SAM_USER_UNKNOWN ) {
NlPrint((NL_CRITICAL,
"NlReadSamLogonResponse: response opcode not valid. 0x%lx\n",
SamLogonResponse->Opcode ));
continue;
}
//
// Ensure the version is expected.
//
Version = NetpLogonGetMessageVersion( SamLogonResponse,
&SamLogonResponseSize,
&VersionFlags );
if ( Version != LMNT_MESSAGE ) {
NlPrint((NL_CRITICAL,"NlReadSamLogonResponse: version not valid 0x%lx 0x%lx.\n",
Version, VersionFlags ));
continue;
}
//
// Pick up the name of the server that responded.
//
Where = (PCHAR) &SamLogonResponse->UnicodeLogonServer;
if ( !NetpLogonGetUnicodeString(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponse->UnicodeLogonServer),
&LocalServerName ) ) {
NlPrint((NL_CRITICAL,
"NlReadSamLogonResponse: "
"server name not formatted right\n"));
continue;
}
//
// Ensure this is a UNC name.
//
if ( LocalServerName[0] != '\\' || LocalServerName[1] != '\\' ) {
NlPrint((NL_CRITICAL,
"NlReadSamLogonResponse: server name isn't UNC name\n"));
continue;
}
//
// Pick up the name of the account the response is for.
//
if ( !NetpLogonGetUnicodeString(
SamLogonResponse,
SamLogonResponseSize,
&Where,
sizeof(SamLogonResponse->UnicodeUserName ),
&LocalUserName ) ) {
NlPrint((NL_CRITICAL,
"NlReadSamLogonResponse: User name not formatted right\n"));
continue;
}
//
// If the response is for the correct account,
// break out of the loop.
//
if ( NlNameCompare( AccountName, LocalUserName, NAMETYPE_USER) == 0 ) {
break;
}
NlPrint((NL_CRITICAL,
"NlReadSamLogonResponse: User name " FORMAT_LPWSTR
" s.b. " FORMAT_LPWSTR ".\n",
LocalUserName,
AccountName ));
}
//
// Return the info to the caller.
//
*Opcode = SamLogonResponse->Opcode;
*UncLogonServer = NetpMemoryAllocate(
(wcslen(LocalServerName) + 1) * sizeof(WCHAR) );
if ( *UncLogonServer == NULL ) {
NlPrint((NL_CRITICAL, "NlReadSamLogonResponse: Not enough memory\n"));
return FALSE;
}
wcscpy( (*UncLogonServer), LocalServerName );
return TRUE;
}
VOID
NlSaveTrustedDomainList (
IN LPWSTR TrustedDomainList
)
/*++
Routine Description:
Save the list of trusted domains to the registry.
Arguments:
TrustedDomainList - Specifies a list of trusted domains in MULTI_SZ format.
Return Value:
None.
--*/
{
NET_API_STATUS NetStatus;
LPNET_CONFIG_HANDLE SectionHandle;
LPWSTR LocalTrustedDomainList;
//
// Open the NetLogon configuration section.
//
NetStatus = NetpOpenConfigData(
&SectionHandle,
NULL, // no server name.
SERVICE_NETLOGON,
FALSE ); // we write access
if ( NetStatus != NO_ERROR ) {
NlPrint((NL_CRITICAL,
"NlSaveTrustedDomainList: NetpOpenConfigData failed: %ld\n",
NetStatus ));
} else {
//
// Convert an empty list to a recognizable form
//
if ( TrustedDomainList == NULL ) {
LocalTrustedDomainList = L"\0";
} else {
LocalTrustedDomainList = TrustedDomainList;
}
//
// Write the domain list to the registry
//
NetStatus = NetpSetConfigTStrArray(
SectionHandle,
NETLOGON_KEYWORD_TRUSTEDDOMAINLIST,
LocalTrustedDomainList );
if ( NetStatus != NO_ERROR ) {
NlPrint((NL_CRITICAL,
"NlSaveTrustedDomainList: NetpSetConfigTStrArray failed: %ld\n",
NetStatus ));
}
(VOID) NetpCloseConfigData( SectionHandle );
}
return;
}
NET_API_STATUS
NlReadRegTrustedDomainList (
IN LPWSTR NewDomainName OPTIONAL,
IN BOOL DeleteName,
OUT LPWSTR *TrustedDomainList,
OUT PBOOL TrustedDomainListKnown
)
/*++
Routine Description:
Read the list of trusted domains from the registry.
Arguments:
NewDomainName - New DomainName of this domain. When this machine joins a domain,
NCPA caches the trusted domain list where we can find it. That ensures the
trusted domain list is available upon reboot even before we dial via RAS. Winlogon
can therefore get the trusted domain list from us under those circumstances.
DeleteName - TRUE if the name is to be deleted upon successful completion.
TrustedDomainList - Returns a list of trusted domains in MULTI_SZ format. Buffer
must be freed using NetApiBufferFree.
TrustedDomainListKnown - Returns true if we know the trusted domain list.
Return Value:
None.
--*/
{
NET_API_STATUS NetStatus;
LPNET_CONFIG_HANDLE SectionHandle = NULL;
WCHAR ValueName[sizeof(NETLOGON_KEYWORD_TRUSTEDDOMAINLIST)/sizeof(WCHAR)+1+DNLEN+1];
//
// Open the NetLogon configuration section.
//
*TrustedDomainListKnown = FALSE;
*TrustedDomainList = NULL;
NetStatus = NetpOpenConfigData(
&SectionHandle,
NULL, // no server name.
SERVICE_NETLOGON,
!DeleteName ); // Get Write access if deleting.
if ( NetStatus != NO_ERROR ) {
NlPrint((NL_CRITICAL,
"NlReadRegTrustedDomainList: NetpOpenConfigData failed: %ld\n",
NetStatus ));
}
//
// Get the "TrustedDomainList" configured parameter
//
if ( NewDomainName == NULL ) {
*ValueName = L'\0';
} else {
wcscpy( ValueName, NewDomainName );
wcscat( ValueName, L"_" );
}
wcscat( ValueName, NETLOGON_KEYWORD_TRUSTEDDOMAINLIST );
NetStatus = NetpGetConfigTStrArray (
SectionHandle,
ValueName,
TrustedDomainList ); // Must be freed by NetApiBufferFree().
//
// Handle the default
//
if (NetStatus == NERR_CfgParamNotFound) {
*TrustedDomainList = NULL;
} else if (NetStatus != NO_ERROR) {
NlPrint((NL_CRITICAL,
"NlReadRegTrustedDomainList: NetpGetConfigTStrArray failed: %ld\n",
NetStatus ));
goto Cleanup;
} else {
*TrustedDomainListKnown = TRUE;
}
if ( DeleteName && *TrustedDomainListKnown) {
NET_API_STATUS TempNetStatus;
TempNetStatus = NetpDeleteConfigKeyword ( SectionHandle, ValueName );
if ( TempNetStatus != NO_ERROR ) {
NlPrint((NL_CRITICAL,
"NlReadRegTrustedDomainList: NetpDeleteConfigKeyword failed: %ld\n",
TempNetStatus ));
}
}
NetStatus = NO_ERROR;
Cleanup:
if ( SectionHandle != NULL ) {
(VOID) NetpCloseConfigData( SectionHandle );
}
return NetStatus;
}
NTSTATUS
NlGetTrustedDomainList (
IN LPWSTR UncDcName,
OUT LPWSTR *TrustedDomainList
)
/*++
Routine Description:
Get the list of trusted domains from the specified DC.
Arguments:
UncDcName - Specifies the name of a DC in the domain.
TrustedDomainList - Returns a list of trusted domains in MULTI_SZ format.
This list should be freed via NetpMemoryFree
Return Value:
STATUS_SUCCESS - if the trust list was successfully returned
--*/
{
NTSTATUS Status;
LSA_HANDLE LsaHandle = NULL;
UNICODE_STRING UncDcNameString;
OBJECT_ATTRIBUTES ObjectAttributes;
LSA_ENUMERATION_HANDLE EnumerationContext;
BOOLEAN AllDone = FALSE;
LPWSTR CurrentBuffer = NULL;
DWORD CurrentSize = 0;
PLSA_TRUST_INFORMATION TrustList = NULL;
//
// Open the policy database on the DC
//
RtlInitUnicodeString( &UncDcNameString, UncDcName );
InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
Status = LsaOpenPolicy( &UncDcNameString,
&ObjectAttributes,
POLICY_VIEW_LOCAL_INFORMATION,
&LsaHandle );
if ( !NT_SUCCESS(Status) ) {
NlPrint((NL_CRITICAL,
"NlGetTrustedDomainList: " FORMAT_LPWSTR
": LsaOpenPolicy failed: %lx\n",
UncDcName,
Status ));
LsaHandle = NULL;
goto Cleanup;
}
//
// Allocate the buffer in case there are no domains.
//
CurrentBuffer = NetpMemoryAllocate( sizeof(WCHAR) );
if ( CurrentBuffer == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
*CurrentBuffer = L'\0';
//
// Loop getting a list of trusted domains
//
EnumerationContext = 0;
do {
ULONG CountReturned;
ULONG i;
DWORD Size;
LPWSTR NewBuffer;
LPWSTR CurrentLoc;
//
// Free any buffers from a previous iteration.
//
if ( TrustList != NULL ) {
(VOID) LsaFreeMemory( TrustList );
}
//
// Get more trusted domains names
//
Status = LsaEnumerateTrustedDomains(
LsaHandle,
&EnumerationContext,
(PVOID *) &TrustList,
0xFFFFFFFF,
&CountReturned );
if ( Status == STATUS_NO_MORE_ENTRIES ) {
AllDone = TRUE;
Status = STATUS_SUCCESS;
}
if ( !NT_SUCCESS(Status) && Status != STATUS_NO_MORE_ENTRIES ) {
NlPrint((NL_CRITICAL,
"NlGetTrustedDomainList: " FORMAT_LPWSTR
": LsaEnumerateTrustedDomains failed: %lx\n",
UncDcName,
Status ));
TrustList = NULL;
goto Cleanup;
}
if ( CountReturned == 0 ) {
continue;
}
//
// Determine the size of names returned on this call.
//
Size = 0;
for ( i=0; i<CountReturned; i++ ) {
Size += TrustList[i].Name.Length + sizeof(WCHAR);
}
//
// Reallocate the buffer.
//
NewBuffer = NetpMemoryReallocate( CurrentBuffer, Size + CurrentSize + sizeof(WCHAR) );
if ( NewBuffer == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
CurrentBuffer = NewBuffer;
CurrentLoc = &NewBuffer[CurrentSize/sizeof(WCHAR)];
CurrentSize += Size;
//
// Handle each trusted domain.
//
for ( i=0; i<CountReturned; i++ ) {
LPWSTR CurrentDomainName;
//
// Copy the new domain name into the buffer
//
CurrentDomainName = CurrentLoc;
RtlCopyMemory( CurrentLoc,
TrustList[i].Name.Buffer,
TrustList[i].Name.Length );
CurrentLoc += TrustList[i].Name.Length / sizeof(WCHAR);
*(CurrentLoc++) = L'\0';
*CurrentLoc = L'\0'; // Place double terminator each time
//
// Ensure the SID of the trusted domain isn't the domain sid of this
// machine.
//
if ( RtlEqualSid( TrustList[i].Sid, NlGlobalDBInfoArray[SAM_DB].DBId )) {
LPWSTR AlertStrings[3];
//
// alert admin.
//
AlertStrings[0] = NlGlobalUnicodeComputerName;
AlertStrings[1] = CurrentDomainName;
AlertStrings[2] = NULL;
RaiseAlert( ALERT_NetLogonSidConflict,
AlertStrings );
//
// Save the info in the eventlog
//
NlpWriteEventlog(
ALERT_NetLogonSidConflict,
EVENTLOG_ERROR_TYPE,
TrustList[i].Sid,
RtlLengthSid( TrustList[i].Sid ),
AlertStrings,
2 );
}
}
} while ( !AllDone );
//
// Save the collected information to the registry
//
NlSaveTrustedDomainList ( CurrentBuffer );
//
// Remember the list in globals
//
NlSetTrustedDomainList ( CurrentBuffer, TRUE );
Status = STATUS_SUCCESS;
//
// Free any locally used resources.
//
Cleanup:
if ( LsaHandle != NULL ) {
(VOID) LsaClose( LsaHandle );
}
if ( TrustList != NULL ) {
(VOID) LsaFreeMemory( TrustList );
}
if ( !NT_SUCCESS(Status) ) {
if ( CurrentBuffer != NULL ) {
NetpMemoryFree( CurrentBuffer );
CurrentBuffer = NULL;
}
}
*TrustedDomainList = CurrentBuffer;
return Status;
}
NTSTATUS
NlSetTrustedDomainList (
IN LPWSTR TrustedDomainList,
IN BOOL TrustedDomainListKnown
)
/*++
Routine Description:
Set the domain list in globals
Arguments:
TrustedDomainList - Specifies a list of trusted domains in MULTI_SZ format.
TrustedDomainListKnown - TRUE if TrustedDomainList has been retrieved
from a DC in the domain.
Return Value:
Status of the operation.
Upon failure, the previous list remains intact.
--*/
{
PTRUSTED_DOMAIN LocalTrustedDomainList;
DWORD LocalTrustedDomainCount;
DWORD LocalTrustedDomainSize;
DWORD i;
PTRUSTED_DOMAIN OldList;
LPWSTR CurrentEntry;
//
// If the new list is zero length,
// don't bother allocating anything.
//
if ( TrustedDomainList == NULL ) {
LocalTrustedDomainList = NULL;
LocalTrustedDomainCount = 0;
//
// Otherwise, build a buffer of the trusted domain list
//
} else {
//
// Allocate a buffer for the new list
//
LocalTrustedDomainCount = NetpTStrArrayEntryCount( TrustedDomainList );
LocalTrustedDomainList = NetpMemoryAllocate(
LocalTrustedDomainCount *
sizeof(TRUSTED_DOMAIN) );
if ( LocalTrustedDomainList == NULL && LocalTrustedDomainCount != 0 ) {
return STATUS_NO_MEMORY;
}
//
// Copy the names to the new structure upper casing them and
// converting to OEM.
//
NlPrint((NL_LOGON, "NlSetTrustedDomainList: New trusted domain list:\n" ));
CurrentEntry = TrustedDomainList;
for ( i=0; i<LocalTrustedDomainCount; i++ ) {
NTSTATUS Status;
UNICODE_STRING UnicodeString;
OEM_STRING OemString;
NlPrint((NL_LOGON, " " FORMAT_LPWSTR "\n", CurrentEntry ));
//
// Convert the input string to OEM
//
RtlInitUnicodeString( &UnicodeString, CurrentEntry );
OemString.Buffer = LocalTrustedDomainList[i].DomainName;
OemString.MaximumLength = sizeof(LocalTrustedDomainList[i].DomainName);
Status = RtlUpcaseUnicodeStringToOemString(
&OemString,
&UnicodeString,
FALSE ); // Don't Allocate dest
if ( !NT_SUCCESS( Status ) ) {
NlPrint(( NL_CRITICAL,
"Can't convert to OEM: " FORMAT_LPWSTR ": %lX\n",
CurrentEntry,
Status ));
NetpMemoryFree( LocalTrustedDomainList );
return Status;
}
CurrentEntry += (UnicodeString.Length / sizeof(WCHAR)) + 1;
}
}
//
// Swap in the new list
LOCK_TRUST_LIST();
OldList = NlGlobalTrustedDomainList;
NlGlobalTrustedDomainList = LocalTrustedDomainList;
NlGlobalTrustedDomainCount = LocalTrustedDomainCount;
NlGlobalTrustedDomainListKnown = TrustedDomainListKnown;
NtQuerySystemTime( &NlGlobalTrustedDomainListTime );
UNLOCK_TRUST_LIST();
//
// Free the old list.
//
if ( OldList != NULL ) {
NetpMemoryFree( OldList );
}
return STATUS_SUCCESS;
}
BOOLEAN
NlIsDomainTrusted (
IN PUNICODE_STRING DomainName
)
/*++
Routine Description:
Determine if the specified domain is trusted.
If the trusted domain list has not been obtained from the DC,
indicate the domain is trusted. This causes the caller fall back to
the prior behaviour of indicating that the DC cannot be contacted.
This status code is special cased in MSV1_0 to indicate that cached
credentials should be tried. This ensures that a newly upgraded RAS
client continues to use cached credentials until it dials in the first
time.
Arguments:
DomainName - Name of the domain to query.
Return Value:
TRUE - if the domain name specified is a trusted domain.
--*/
{
NTSTATUS Status;
DWORD i;
OEM_STRING OemString;
CHAR OemBuffer[DNLEN+1];
OEM_STRING CurrentDomainName;
//
// If the no domain name was specified,
// indicate the domain is not trusted.
//
if ( DomainName == NULL || DomainName->Length == 0 ) {
return FALSE;
}
//
// Convert the input string to OEM
//
OemString.MaximumLength = sizeof(OemBuffer);
OemString.Buffer = OemBuffer;
Status = RtlUpcaseUnicodeStringToOemString( &OemString,
DomainName,
FALSE ); // Don't Allocate dest
if ( !NT_SUCCESS( Status ) ) {
return FALSE;
}
//
// Consider the Primary Domain to be a trusted domain, too
//
RtlInitString( &CurrentDomainName, NlGlobalAnsiDomainName );
if ( RtlEqualString( &OemString, &CurrentDomainName, FALSE ) ) {
return TRUE;
}
//
// If we have no trusted domain list,
// fall back to previous behavior.
//
if ( !NlGlobalTrustedDomainListKnown ) {
return TRUE;
}
//
// Compare the input trusted domain name to each element in the list
//
LOCK_TRUST_LIST();
for ( i=0; i<NlGlobalTrustedDomainCount; i++ ) {
RtlInitString( &CurrentDomainName,
NlGlobalTrustedDomainList[i].DomainName );
//
// Simply compare the bytes (both are already uppercased)
//
if ( RtlEqualString( &OemString, &CurrentDomainName, FALSE ) ) {
UNLOCK_TRUST_LIST();
return TRUE;
}
}
UNLOCK_TRUST_LIST();
//
// All other domains aren't trusted.
//
return FALSE;
}
//
// Define the Actions to NlDcDiscoveryMachine.
//
typedef enum {
StartDiscovery,
DcFoundMessage,
DcNotFoundMessage,
DcTimerExpired
} DISCOVERY_ACTION;
//
// number of broadcastings to get DC before reporting DC not found
// error.
//
#define MAX_DC_RETRIES 3
NTSTATUS
NlDcDiscoveryMachine(
IN OUT PCLIENT_SESSION ClientSession,
IN DISCOVERY_ACTION Action,
IN LPWSTR UncDcName OPTIONAL,
IN LPWSTR TransportName OPTIONAL,
IN LPSTR ResponseMailslotName OPTIONAL,
IN DISCOVERY_TYPE DiscoveryType
)
/*++
Routine Description:
State machine to get the name of a DC in a domain.
Arguments:
ClientSession -- Client session structure whose DC is to be picked.
The Client Session structure must be referenced.
Action -- The event which just occurred.
UncDcName -- If the Action is DcFoundMessage, this is the name of the newly
found domain controller.
TransportName -- If the Action is DcFoundMessage, this is the name of the
transport the domain controller can be reached on.
ResponseMailslotName -- If action is StartDiscovery or DcTimerExpired,
this name is the name of the mailslot that the response is sent to.
DiscoveryType -- Indicate synchronous, Asynchronous, or rediscovery of a
"Dead domain".
Return Value:
STATUS_SUCCESS - if DC was found.
STATUS_PENDING - if discovery is still in progress and the caller should
call again in DISCOVERY_PERIOD with the DcTimerExpired action.
STATUS_NO_LOGON_SERVERS - if DC was not found.
STATUS_NO_TRUST_SAM_ACCOUNT - if DC was found but it does not have
an account for this machine.
--*/
{
NTSTATUS Status;
PNETLOGON_SAM_LOGON_REQUEST SamLogonRequest = NULL;
NlAssert( ClientSession->CsReferenceCount > 0 );
EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
//
// Handle a new request to start discovery and timer expiration.
//
switch (Action) {
case StartDiscovery:
case DcTimerExpired: {
DWORD DomainSidSize;
ULONG AllowableAccountControlBits;
WCHAR NetlogonMailslotName[DNLEN+NETLOGON_NT_MAILSLOT_LEN+5];
PCHAR Where;
//
// If discovery is currently going on,
// ignore this new request.
// If discovery isn't currently going on,
// ignore a timer expiration.
//
if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_IN_PROGRESS) &&
Action == StartDiscovery ){
Status = STATUS_SUCCESS;
goto Ignore;
} else if (
(ClientSession->CsDiscoveryFlags & CS_DISCOVERY_IN_PROGRESS) == 0 &&
Action == DcTimerExpired ){
if ( ClientSession->CsState == CS_IDLE ) {
Status = ClientSession->CsConnectionStatus;
} else {
Status = STATUS_SUCCESS;
}
goto Ignore;
}
//
// Increment/set the retry count
//
if ( Action == StartDiscovery ) {
ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_IN_PROGRESS;
ClientSession->CsDiscoveryRetryCount = 0;
NlAssert( ClientSession->CsDiscoveryEvent != NULL );
if ( !ResetEvent( ClientSession->CsDiscoveryEvent ) ) {
NlPrint(( NL_CRITICAL,
"NlDcDiscoveryMachine: %ws: ResetEvent failed %ld\n",
ClientSession->CsDomainName.Buffer,
GetLastError() ));
}
NlPrint(( NL_SESSION_SETUP,
"NlDcDiscoveryMachine: %ws: Start Discovery\n",
ClientSession->CsDomainName.Buffer ));
} else {
ClientSession->CsDiscoveryRetryCount ++;
if ( ClientSession->CsDiscoveryRetryCount == MAX_DC_RETRIES ) {
NlPrint(( NL_CRITICAL,
"NlDcDiscoveryMachine: %ws: Discovery failed\n",
ClientSession->CsDomainName.Buffer ));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
NlPrint(( NL_SESSION_SETUP,
"NlDcDiscoveryMachine: %ws: Discovery retry %ld\n",
ClientSession->CsDomainName.Buffer,
ClientSession->CsDiscoveryRetryCount ));
}
//
// Determine the Account type we're looking for.
//
if ( ClientSession->CsSecureChannelType == WorkstationSecureChannel ) {
AllowableAccountControlBits = USER_WORKSTATION_TRUST_ACCOUNT;
} else if ( ClientSession->CsSecureChannelType ==
TrustedDomainSecureChannel ) {
AllowableAccountControlBits = USER_INTERDOMAIN_TRUST_ACCOUNT;
} else if ( ClientSession->CsSecureChannelType ==
ServerSecureChannel ) {
AllowableAccountControlBits = USER_SERVER_TRUST_ACCOUNT;
} else {
NlPrint(( NL_CRITICAL,
"NlDcDiscoveryMachine: %ws: "
"invalid SecureChannelType retry %ld\n",
ClientSession->CsDomainName.Buffer,
ClientSession->CsSecureChannelType ));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
//
// Initialization memory for the logon request message.
//
DomainSidSize = RtlLengthSid( ClientSession->CsDomainId );
SamLogonRequest = NetpMemoryAllocate(
sizeof(NETLOGON_SAM_LOGON_REQUEST) +
DomainSidSize +
sizeof(DWORD) // for SID alignment on 4 byte boundary
);
if( SamLogonRequest == NULL ) {
NlPrint(( NL_CRITICAL, "NlDcDiscoveryMachine can't allocate memory\n"));
// This isn't the real status, but callers handle this status
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
//
// Build the query message.
//
SamLogonRequest->Opcode = LOGON_SAM_LOGON_REQUEST;
SamLogonRequest->RequestCount =
(USHORT) ClientSession->CsDiscoveryRetryCount;
Where = (PCHAR) &SamLogonRequest->UnicodeComputerName;
NetpLogonPutUnicodeString(
NlGlobalUnicodeComputerName,
sizeof(SamLogonRequest->UnicodeComputerName),
&Where );
NetpLogonPutUnicodeString(
ClientSession->CsAccountName,
sizeof(SamLogonRequest->UnicodeUserName),
&Where );
NetpLogonPutOemString(
ResponseMailslotName,
sizeof(SamLogonRequest->MailslotName),
&Where );
NetpLogonPutBytes(
&AllowableAccountControlBits,
sizeof(SamLogonRequest->AllowableAccountControlBits),
&Where );
//
// place domain SID in the message.
//
NetpLogonPutBytes( &DomainSidSize, sizeof(DomainSidSize), &Where );
NetpLogonPutDomainSID( ClientSession->CsDomainId, DomainSidSize, &Where );
NetpLogonPutNtToken( &Where );
//
// Broadcast the message to each Netlogon service in the domain.
//
// We are sending to the DomainName* name which will be received by
// all NT DCs including those DCs on a WAN.
//
// When doing the discover of the PDC for this domain, send to
// DomainName** which only sends to Domain<1B> which is registered
// only by the PDC.
//
NetlogonMailslotName[0] = '\\';
NetlogonMailslotName[1] = '\\';
wcscpy(NetlogonMailslotName+2, ClientSession->CsDomainName.Buffer );
wcscat(NetlogonMailslotName, L"*" );
if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) {
wcscat(NetlogonMailslotName, L"*" );
}
wcscat(NetlogonMailslotName, NETLOGON_NT_MAILSLOT_W );
Status = NlpWriteMailslot(
NetlogonMailslotName,
SamLogonRequest,
Where - (PCHAR)(SamLogonRequest) );
if ( !NT_SUCCESS(Status) ) {
NlPrint(( NL_CRITICAL,
"NlDcDiscoveryMachine: %ws: "
"cannot write netlogon mailslot 0x%lx\n",
ClientSession->CsDomainName.Buffer,
Status));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
//
// If this is an asynchronous call and this is the first call,
// start the periodic timer.
//
if ( DiscoveryType == DT_Asynchronous && Action == StartDiscovery ) {
if ( NlGlobalDcDiscoveryCount == 0 ) {
NlGlobalDcDiscoveryTimer.Period =
DISCOVERY_PERIOD + NlGlobalExpectedDialupDelayParameter*1000/MAX_DC_RETRIES;
(VOID) NtQuerySystemTime( &NlGlobalDcDiscoveryTimer.StartTime );
//
// If netlogon is exitting,
// the main thread is already gone.
//
if ( NlGlobalTerminate ) {
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
//
// Tell the main thread that I've changed a timer.
//
if ( !SetEvent( NlGlobalTimerEvent ) ) {
NlPrint(( NL_CRITICAL,
"NlDcDiscoveryMachine: %ws: SetEvent2 failed %ld\n",
ClientSession->CsDomainName.Buffer,
GetLastError() ));
}
}
NlGlobalDcDiscoveryCount ++;
ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_ASYNCHRONOUS;
//
// Don't let the session go away during discovery.
//
LOCK_TRUST_LIST();
NlRefClientSession( ClientSession );
UNLOCK_TRUST_LIST();
//
// If this is merely an attempt to revive a "dead" domain,
// we just send the single mailslot message above and exit discovery.
// If any DC responds, we'll pick up the response even though
// discovery isn't in progress.
//
} else if ( DiscoveryType == DT_DeadDomain ) {
Status = ClientSession->CsConnectionStatus;
goto Cleanup;
}
Status = STATUS_PENDING;
goto Ignore;
}
//
// Handle when a DC claims to be the DC for the requested domain.
//
case DcFoundMessage:
//
// If we already know the name of a DC,
// ignore this new name.
//
// When we implement doing discovery while a session is already up,
// we need to handle the case where someone has the ClientSession
// write locked. In that case, we should probably just hang the new
// DCname somewhere off the ClientSession structure and swap in the
// new DCname when the writer drops the write lock. ??
//
if ( ClientSession->CsState != CS_IDLE ) {
NlPrint(( NL_SESSION_SETUP,
"NlDcDiscoveryMachine: %ws: DC %ws ignored."
" DC previously found.\n",
ClientSession->CsDomainName.Buffer,
UncDcName ));
Status = STATUS_SUCCESS;
goto Ignore;
}
//
// Install the new DC name in the Client session
//
wcsncpy( ClientSession->CsUncServerName, UncDcName, UNCLEN );
ClientSession->CsUncServerName[UNCLEN] = L'\0';
//
// Save the transport this discovery came in on.
//
if ( TransportName == NULL ) {
NlPrint(( NL_SESSION_SETUP,
"NlDcDiscoveryMachine: %ws: Found DC %ws\n",
ClientSession->CsDomainName.Buffer,
UncDcName ));
} else {
NlPrint(( NL_SESSION_SETUP,
"NlDcDiscoveryMachine: %ws: Found DC %ws on transport %ws\n",
ClientSession->CsDomainName.Buffer,
UncDcName,
TransportName ));
ClientSession->CsTransportName =
NlTransportLookupTransportName( TransportName );
if ( ClientSession->CsTransportName == NULL ) {
NlPrint(( NL_CRITICAL,
"NlDcDiscoveryMachine: " FORMAT_LPWSTR ": Transport not found\n",
TransportName ));
}
}
//
// If this is a BDC discovering it's PDC,
// save the PDC name.
// Start the replicator and let it figure if it needs to be running.
//
if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) {
NlSetPrimaryName( ClientSession->CsUncServerName+2 );
(VOID) NlStartReplicatorThread( 0 );
}
Status = STATUS_SUCCESS;
goto Cleanup;
case DcNotFoundMessage:
//
// If we already know the name of a DC,
// ignore this new name.
//
if ( ClientSession->CsState != CS_IDLE ) {
NlPrint(( NL_SESSION_SETUP,
"NlDcDiscoveryMachine: %ws: DC %ws ignored."
" DC previously found.\n",
ClientSession->CsDomainName.Buffer,
UncDcName ));
Status = STATUS_SUCCESS;
goto Ignore;
}
//
// If discovery isn't currently going on,
// ignore this extraneous message.
//
if ((ClientSession->CsDiscoveryFlags & CS_DISCOVERY_IN_PROGRESS) == 0 ){
NlPrint(( NL_SESSION_SETUP,
"NlDcDiscoveryMachine: %ws: DC %ws ignored."
" Discovery not in progress.\n",
ClientSession->CsDomainName.Buffer,
UncDcName ));
Status = ClientSession->CsConnectionStatus;
goto Ignore;
}
NlPrint(( NL_CRITICAL,
"NlDcDiscoveryMachine: %ws: "
"Received No Such Account message\n",
ClientSession->CsDomainName.Buffer));
Status = STATUS_NO_TRUST_SAM_ACCOUNT;
goto Cleanup;
}
//
// We never reach here.
//
NlAssert(FALSE);
//
// Handle discovery being completed.
//
Cleanup:
//
// On success,
// Indicate that the session setup is allowed to happen immediately.
//
// Leave CsConnectionStatus with a "failure" status code until the
// secure channel is set up. Other, routines simply return
// CsConnectionStatus as the state of the secure channel.
//
if ( NT_SUCCESS(Status) ) {
ClientSession->CsLastAuthenticationTry.QuadPart = 0;
ClientSession->CsState = CS_DC_PICKED;
//
// On failure,
// Indicate that we've recently made the attempt to find a DC.
//
} else {
NtQuerySystemTime( &ClientSession->CsLastAuthenticationTry );
ClientSession->CsState = CS_IDLE;
ClientSession->CsConnectionStatus = Status;
}
NtQuerySystemTime( &ClientSession->CsLastDiscoveryTime );
//
// Tell the initiator that discover has completed.
//
ClientSession->CsDiscoveryFlags &= ~CS_DISCOVERY_IN_PROGRESS;
NlAssert( ClientSession->CsDiscoveryEvent != NULL );
if ( !SetEvent( ClientSession->CsDiscoveryEvent ) ) {
NlPrint(( NL_CRITICAL,
"NlDcDiscoveryMachine: %ws: SetEvent failed %ld\n",
ClientSession->CsDomainName.Buffer,
GetLastError() ));
}
//
// If this was an async discovery,
// turn the timer off.
//
if ( ClientSession->CsDiscoveryFlags & CS_DISCOVERY_ASYNCHRONOUS ) {
ClientSession->CsDiscoveryFlags &= ~CS_DISCOVERY_ASYNCHRONOUS;
NlGlobalDcDiscoveryCount--;
if ( NlGlobalDcDiscoveryCount == 0 ) {
NlGlobalDcDiscoveryTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
}
//
// We no longer care about the Client session
//
LOCK_TRUST_LIST();
NlUnrefClientSession( ClientSession );
UNLOCK_TRUST_LIST();
}
//
// Cleanup locally used resources.
//
Ignore:
//
// free log request message.
//
if( SamLogonRequest != NULL ) {
NetpMemoryFree( SamLogonRequest );
}
//
// Unlock the crit sect and return.
//
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
return Status;
}
NTSTATUS
NlDiscoverDc (
IN OUT PCLIENT_SESSION ClientSession,
IN DISCOVERY_TYPE DiscoveryType
)
/*++
Routine Description:
Get the name of a DC in a domain.
On Entry,
The trust list must NOT be locked.
The trust list entry must be referenced by the caller.
The caller must be a writer of the trust list entry.
Arguments:
ClientSession -- Client session structure whose DC is to be picked.
The Client Session structure must be marked for write.
DiscoveryType -- Indicate synchronous, Asynchronous, or rediscovery of a
"Dead domain".
Return Value:
STATUS_SUCCESS - if DC was found.
STATUS_PENDING - Operation is still in progress
STATUS_NO_LOGON_SERVERS - if DC was not found.
STATUS_NO_TRUST_SAM_ACCOUNT - if DC was found but it does not have
an account for this machine.
--*/
{
NTSTATUS Status;
HANDLE ResponseMailslotHandle = NULL;
CHAR ResponseMailslotName[PATHLEN+1];
NlAssert( ClientSession->CsReferenceCount > 0 );
NlAssert( ClientSession->CsFlags & CS_WRITER );
//
// If this is a BDC discovering its own PDC,
// and we've already discovered the PDC
// (via NetGetDcName or the PDC has spontaneously told us its name),
// just use that name.
//
// If we're our own PDC,
// we must have just been demoted to a BDC and haven't found PDC yet,
// in that case rediscover.
//
if ( ClientSession->CsSecureChannelType == ServerSecureChannel &&
*NlGlobalUnicodePrimaryName != L'\0' &&
NlNameCompare( NlGlobalUnicodePrimaryName,
NlGlobalUnicodeComputerName,
NAMETYPE_COMPUTER) != 0 ) {
//
// Just set the PDC name in the Client Session structure.
//
EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
wcscpy( ClientSession->CsUncServerName, NlGlobalUncPrimaryName );
ClientSession->CsLastAuthenticationTry.QuadPart = 0;
NtQuerySystemTime( &ClientSession->CsLastDiscoveryTime );
ClientSession->CsState = CS_DC_PICKED;
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
Status = STATUS_SUCCESS;
goto Cleanup;
}
//
// If this is a workstation,
// Create a mailslot for the DC's to respond to.
//
if ( NlGlobalRole == RoleMemberWorkstation ) {
NET_API_STATUS NetStatus;
NlAssert( DiscoveryType == DT_Synchronous );
NetStatus = NetpLogonCreateRandomMailslot( ResponseMailslotName,
&ResponseMailslotHandle);
if ( NetStatus != NERR_Success ) {
NlPrint((NL_CRITICAL,
"NlDiscoverDc: cannot create temp mailslot %ld\n",
NetStatus ));
Status = NetpApiStatusToNtStatus( NetStatus );
goto Cleanup;
}
//
// If the mailslot timeout shouldn't be the default 5 seconds,
// set it to the right value.
//
if ( NlGlobalExpectedDialupDelayParameter != 0 ) {
if ( !SetMailslotInfo(
ResponseMailslotHandle,
DISCOVERY_PERIOD + NlGlobalExpectedDialupDelayParameter*1000/MAX_DC_RETRIES ) ) {
NetStatus = GetLastError();
NlPrint((NL_CRITICAL,
"NlDiscoverDc: cannot change temp mailslot timeout %ld\n",
NetStatus ));
Status = NetpApiStatusToNtStatus( NetStatus );
goto Cleanup;
}
}
} else {
lstrcpyA( ResponseMailslotName, NETLOGON_NT_MAILSLOT_A );
}
//
// Start discovery.
//
Status = NlDcDiscoveryMachine( ClientSession,
StartDiscovery,
NULL,
NULL,
ResponseMailslotName,
DiscoveryType );
if ( !NT_SUCCESS(Status) || DiscoveryType != DT_Synchronous ) {
goto Cleanup;
}
//
// If the discovery machine asked us to call back every DISCOVERY_PERIOD,
// loop doing exactly that.
//
if ( Status == STATUS_PENDING ) {
//
// Loop waiting.
//
for (;;) {
DWORD WaitStatus;
//
// On non-workstations,
// the main loop gets the mailslot responses.
// (So just do the timeout here).
//
if ( NlGlobalRole != RoleMemberWorkstation ) {
//
// Wait for DISOVERY_PERIOD.
//
WaitStatus =
WaitForSingleObject( ClientSession->CsDiscoveryEvent,
DISCOVERY_PERIOD + NlGlobalExpectedDialupDelayParameter*1000/MAX_DC_RETRIES );
if ( WaitStatus == 0 ) {
break;
} else if ( WaitStatus != WAIT_TIMEOUT ) {
NlPrint((NL_CRITICAL,
"NlDiscoverDc: wait error: %ld\n",
WaitStatus ));
Status = NetpApiStatusToNtStatus( WaitStatus );
goto Cleanup;
}
// Drop through to indicate timer expiration
//
// Workstations do the mailslot read directly.
//
} else {
CHAR ResponseBuffer[MAX_RANDOM_MAILSLOT_RESPONSE];
PNETLOGON_SAM_LOGON_RESPONSE SamLogonResponse;
DWORD SamLogonResponseSize;
//
// Read the response from the response mailslot
// (This mailslot is set up with a 5 second timeout).
//
if ( ReadFile( ResponseMailslotHandle,
ResponseBuffer,
sizeof(ResponseBuffer),
&SamLogonResponseSize,
NULL ) ) {
DWORD Version;
DWORD VersionFlags;
SamLogonResponse =
(PNETLOGON_SAM_LOGON_RESPONSE) ResponseBuffer;
//
// get message version.
//
Version = NetpLogonGetMessageVersion(
SamLogonResponse,
&SamLogonResponseSize,
&VersionFlags );
//
// Handle the incoming message.
//
Status = NlDcDiscoveryHandler ( SamLogonResponse,
SamLogonResponseSize,
NULL, // Transport name
Version );
if ( Status != STATUS_PENDING ) {
goto Cleanup;
}
//
// Ignore badly formed responses.
//
continue;
} else {
WaitStatus = GetLastError();
if ( WaitStatus != ERROR_SEM_TIMEOUT ) {
NlPrint((NL_CRITICAL,
"NlDiscoverDc: "
"cannot read response mailslot: %ld\n",
WaitStatus ));
Status = NetpApiStatusToNtStatus( WaitStatus );
goto Cleanup;
}
}
}
//
// If we reach here,
// DISCOVERY_PERIOD has expired.
//
Status = NlDcDiscoveryMachine( ClientSession,
DcTimerExpired,
NULL,
NULL,
ResponseMailslotName,
DiscoveryType );
if ( Status != STATUS_PENDING ) {
goto Cleanup;
}
}
//
// If someone else started the discovery,
// just wait for that discovery to finish.
//
} else {
NlWaitForSingleObject( "Client Session waiting for discovery",
ClientSession->CsDiscoveryEvent );
}
//
// Return the status to the caller.
//
if ( ClientSession->CsState == CS_IDLE ) {
Status = ClientSession->CsConnectionStatus;
} else {
Status = STATUS_SUCCESS;
}
Cleanup:
if ( ResponseMailslotHandle != NULL ) {
CloseHandle(ResponseMailslotHandle);
}
//
// If this is a workstation,
// get the trusted domain list from the discovered DC.
//
if ( NlGlobalRole == RoleMemberWorkstation && NT_SUCCESS(Status) ) {
NTSTATUS TempStatus;
LPWSTR TrustedDomainList;
TempStatus = NlGetTrustedDomainList (
ClientSession->CsUncServerName,
&TrustedDomainList );
if ( NT_SUCCESS( TempStatus ) ) {
NetpMemoryFree( TrustedDomainList );
}
}
return Status;
}
NTSTATUS
NlUpdateTrustListBySid (
IN PSID DomainId,
IN PUNICODE_STRING DomainName OPTIONAL
)
/*++
Routine Description:
Update a single in-memory trust list entry to match the LSA.
Do async discovery on a domain.
Arguments:
DomainId -- Domain Id of the domain to do the discovery for.
DomainName -- Specifies the DomainName of the domain. If this parameter
isn't specified, the LSA is queried for the name. If this parameter
is specified, the LSA is guaranteed to contain this domain.
Return Value:
Status of the operation.
--*/
{
NTSTATUS Status;
PLIST_ENTRY ListEntry;
PCLIENT_SESSION ClientSession = NULL;
PUNICODE_STRING LocalDomainName;
LSAPR_HANDLE TrustedDomainHandle = NULL;
PLSAPR_TRUSTED_DOMAIN_INFO TrustedDomainName = NULL;
//
// If the domain name was passed in,
// there is no need to query the LSA for the name.
//
if ( DomainName != NULL ) {
LocalDomainName = DomainName;
//
// Determine if the TrustedDomain object exists in the LSA.
//
} else {
Status = LsarOpenTrustedDomain(
NlGlobalPolicyHandle,
DomainId,
TRUSTED_QUERY_DOMAIN_NAME,
&TrustedDomainHandle );
if ( NT_SUCCESS(Status) ) {
Status = LsarQueryInfoTrustedDomain(
TrustedDomainHandle,
TrustedDomainNameInformation,
&TrustedDomainName );
if ( !NT_SUCCESS(Status) ) {
NlPrint(( NL_CRITICAL,
"NlUpdateTrustListBySid: "
"cannot LsarQueryInfoTrustedDomain: %lx\n",
Status));
TrustedDomainName = NULL;
goto Cleanup;
}
LocalDomainName =
(PUNICODE_STRING)&TrustedDomainName->TrustedDomainNameInfo.Name;
} else {
LocalDomainName = NULL;
}
}
//
// Ensure the SID of the trusted domain isn't the domain sid of this
// machine.
//
if ( RtlEqualSid( DomainId, NlGlobalPrimaryDomainId )) {
LPWSTR AlertStrings[3];
WCHAR AlertDomainName[DNLEN+1];
//
// alert admin.
//
if ( LocalDomainName == NULL ||
LocalDomainName->Length > sizeof(AlertDomainName) ) {
AlertDomainName[0] = L'\0';
} else {
RtlCopyMemory( AlertDomainName, LocalDomainName->Buffer, LocalDomainName->Length );
AlertDomainName[ LocalDomainName->Length / sizeof(WCHAR) ] = L'\0';
}
AlertStrings[0] = NlGlobalUnicodeDomainName;
AlertStrings[1] = AlertDomainName;
AlertStrings[2] = NULL;
RaiseAlert( ALERT_NetLogonSidConflict,
AlertStrings );
//
// Save the info in the eventlog
//
NlpWriteEventlog(
ALERT_NetLogonSidConflict,
EVENTLOG_ERROR_TYPE,
DomainId,
RtlLengthSid( DomainId ),
AlertStrings,
2 );
}
//
// Loop through the trust list finding the right entry.
//
LOCK_TRUST_LIST();
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
ListEntry = ListEntry->Flink) {
ClientSession = CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
if ( RtlEqualSid( ClientSession->CsDomainId, DomainId ) ) {
break;
}
ClientSession = NULL;
}
//
// At this point,
// LocalDomainName is NULL if the trust relationship doesn't exist in LSA
// ClientSession is NULL if the trust relationship doesn't exist in memory
//
//
// If the Trust exists in neither place,
// ignore this request.
//
if ( LocalDomainName == NULL && ClientSession == NULL ) {
UNLOCK_TRUST_LIST();
Status = STATUS_SUCCESS;
goto Cleanup;
//
// If the trust exists in the LSA but not in memory,
// add the trust entry.
//
} else if ( LocalDomainName != NULL && ClientSession == NULL ) {
ClientSession = NlAllocateClientSession(
LocalDomainName,
DomainId,
TrustedDomainSecureChannel );
if (ClientSession == NULL) {
UNLOCK_TRUST_LIST();
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
//
// Link this entry onto the tail of the TrustList.
//
InsertTailList( &NlGlobalTrustList, &ClientSession->CsNext );
NlGlobalTrustListLength ++;
NlPrint((NL_SESSION_SETUP,
"NlUpdateTrustListBySid: " FORMAT_LPWSTR
": Added to local trust list\n",
ClientSession->CsDomainName.Buffer ));
//
// If the trust exists in memory but not in the LSA,
// delete the entry.
//
} else if ( LocalDomainName == NULL && ClientSession != NULL ) {
NlPrint((NL_SESSION_SETUP,
"NlUpdateTrustListBySid: " FORMAT_LPWSTR
": Deleted from local trust list\n",
ClientSession->CsDomainName.Buffer ));
NlFreeClientSession( ClientSession );
ClientSession = NULL;
//
// If the trust exists in both places,
// undo any pending deletion.
//
} else if ( LocalDomainName != NULL && ClientSession != NULL ) {
ClientSession->CsFlags &= ~CS_DELETE_ON_UNREF;
NlRefClientSession( ClientSession );
NlPrint((NL_SESSION_SETUP,
"NlUpdateTrustListBySid: " FORMAT_LPWSTR
": Already in trust list\n",
ClientSession->CsDomainName.Buffer ));
}
UNLOCK_TRUST_LIST();
//
// If we haven't discovered a DC for this domain,
// and we haven't tried discovery recently,
// start the discovery asynchronously
//
if ( ClientSession != NULL &&
ClientSession->CsState == CS_IDLE &&
NlTimeToReauthenticate( ClientSession ) ) {
//
// Only wait for 45 seconds. This routine is called by the netlogon main
// thread. Another thread may have the ClientSession locked and need the
// main thread to finish a discovery.
//
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
Status = STATUS_SUCCESS;
goto Cleanup;
}
Status = NlDiscoverDc ( ClientSession, DT_Asynchronous );
NlResetWriterClientSession( ClientSession );
if ( Status == STATUS_PENDING ) {
Status = STATUS_SUCCESS;
}
goto Cleanup;
}
Status = STATUS_SUCCESS;
//
// Cleanup locally used resources.
//
Cleanup:
if ( TrustedDomainName != NULL ) {
LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO(
TrustedDomainNameInformation,
TrustedDomainName );
}
if ( TrustedDomainHandle != NULL ) {
NTSTATUS LocalStatus;
LocalStatus = LsarClose( &TrustedDomainHandle );
NlAssert( NT_SUCCESS( LocalStatus ));
}
if ( ClientSession != NULL ) {
NlUnrefClientSession( ClientSession );
}
return Status;
}
VOID
NlDcDiscoveryExpired (
IN BOOLEAN Exitting
)
/*++
Routine Description:
Handle expiration of the DC discovery timer.
Arguments:
NONE
Return Value:
Exitting: TRUE if the netlogon service is exitting
--*/
{
PLIST_ENTRY ListEntry;
PCLIENT_SESSION ClientSession;
NlAssert( NlGlobalRole != RoleMemberWorkstation );
LOCK_TRUST_LIST();
//
// Mark each entry to indicate we've not yet handled the timer expiration
//
if ( !Exitting ) {
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
ListEntry = ListEntry->Flink) {
ClientSession = CONTAINING_RECORD( ListEntry,
CLIENT_SESSION,
CsNext );
ClientSession->CsFlags |= CS_HANDLE_TIMER;
}
}
//
// Loop thru the trust list handling timer expiration.
//
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
) {
ClientSession = CONTAINING_RECORD( ListEntry,
CLIENT_SESSION,
CsNext );
//
// If we've already done this entry,
// skip this entry.
//
if ( !Exitting ) {
if ( (ClientSession->CsFlags & CS_HANDLE_TIMER) == 0 ) {
ListEntry = ListEntry->Flink;
continue;
}
ClientSession->CsFlags &= ~CS_HANDLE_TIMER;
}
//
// If async discovery isn't going on,
// skip this entry.
//
if ((ClientSession->CsDiscoveryFlags & CS_DISCOVERY_ASYNCHRONOUS) == 0){
ListEntry = ListEntry->Flink;
continue;
}
//
// Call the discovery machine with the trust list unlocked.
//
UNLOCK_TRUST_LIST();
(VOID) NlDcDiscoveryMachine( ClientSession,
DcTimerExpired,
NULL,
NULL,
NETLOGON_NT_MAILSLOT_A,
TRUE );
//
// Since we dropped the trust list lock,
// we'll start the search from the front of the list.
//
LOCK_TRUST_LIST();
ListEntry = NlGlobalTrustList.Flink ;
}
UNLOCK_TRUST_LIST();
//
// Complete the asynchronous discover on the Global client session.
//
if ( NlGlobalClientSession != NULL &&
NlGlobalClientSession->CsDiscoveryFlags & CS_DISCOVERY_ASYNCHRONOUS ) {
(VOID) NlDcDiscoveryMachine( NlGlobalClientSession,
DcTimerExpired,
NULL,
NULL,
NETLOGON_NT_MAILSLOT_A,
TRUE );
}
}
NTSTATUS
NlDcDiscoveryHandler (
IN PNETLOGON_SAM_LOGON_RESPONSE Message,
IN DWORD MessageSize,
IN LPWSTR TransportName,
IN DWORD Version
)
/*++
Routine Description:
Handle a mailslot response to a DC Discovery request.
Arguments:
Message -- The response message
MessageSize -- The size of the message in bytes.
TransportName -- Name of the transport the messages arrived on.
Version -- version info of the message.
Return Value:
STATUS_SUCCESS - if DC was found.
STATUS_PENDING - if discovery is still in progress and the caller should
call again in DISCOVERY_PERIOD with the DcTimerExpired action.
STATUS_NO_LOGON_SERVERS - if DC was not found.
STATUS_NO_TRUST_SAM_ACCOUNT - if DC was found but it does not have
an account for this machine.
--*/
{
NTSTATUS Status;
LPWSTR LocalServerName;
LPWSTR LocalUserName;
LPWSTR LocalDomainName;
PCHAR Where;
PCLIENT_SESSION ClientSession = NULL;
UNICODE_STRING DomainNameString;
if ( Version != LMNT_MESSAGE ) {
NlPrint((NL_CRITICAL,
"NlDcDiscoveryHandler: version not valid.\n"));
Status = STATUS_PENDING;
goto Cleanup;
}
//
// Ignore messages from paused DCs.
//
if ( Message->Opcode != LOGON_SAM_LOGON_RESPONSE &&
Message->Opcode != LOGON_SAM_USER_UNKNOWN ) {
Status = STATUS_PENDING;
goto Cleanup;
}
//
// Pick up the name of the server that responded.
//
Where = (PCHAR) &Message->UnicodeLogonServer;
if ( !NetpLogonGetUnicodeString(
Message,
MessageSize,
&Where,
sizeof(Message->UnicodeLogonServer),
&LocalServerName ) ) {
NlPrint((NL_CRITICAL,
"NlDcDiscoveryHandler: server name not formatted right\n"));
Status = STATUS_PENDING;
goto Cleanup;
}
//
// Pick up the name of the account the response is for.
//
if ( !NetpLogonGetUnicodeString(
Message,
MessageSize,
&Where,
sizeof(Message->UnicodeUserName ),
&LocalUserName ) ) {
NlPrint((NL_CRITICAL,
"NlDcDiscoveryHandler: User name not formatted right\n"));
Status = STATUS_PENDING;
goto Cleanup;
}
//
// If the domain name is not in the message,
// ignore the message.
//
if( Where >= ((PCHAR)Message + MessageSize) ) {
NlPrint((NL_CRITICAL,
"NlDcDiscoveryHandler: "
"Response from %ws doesn't contain domain name\n",
LocalServerName ));
if ( NlGlobalRole == RoleMemberWorkstation ) {
LocalDomainName = NlGlobalUnicodeDomainName;
NlPrint((NL_SESSION_SETUP,
"NlDcDiscoveryHandler: "
"Workstation: Assuming %ws is in domain %ws\n",
LocalServerName,
LocalDomainName ));
} else {
Status = STATUS_PENDING;
goto Cleanup;
}
//
// Pick up the name of the domain the response is for.
//
} else {
if ( !NetpLogonGetUnicodeString(
Message,
MessageSize,
&Where,
sizeof(Message->UnicodeDomainName ),
&LocalDomainName ) ) {
NlPrint((NL_CRITICAL,
"NlDcDiscoveryHandler: "
" Domain name from %ws not formatted right\n",
LocalServerName ));
Status = STATUS_PENDING;
goto Cleanup;
}
}
//
// On the PDC or BDC,
// find the Client session for the domain.
// On workstations,
// find the primary domain client session.
//
RtlInitUnicodeString( &DomainNameString, LocalDomainName );
ClientSession = NlFindNamedClientSession( &DomainNameString );
if ( ClientSession == NULL ) {
NlPrint((NL_SESSION_SETUP,
"NlDcDiscoveryHandler: "
" Domain name %ws from %ws has no client session.\n",
LocalDomainName,
LocalServerName ));
Status = STATUS_PENDING;
goto Cleanup;
}
//
// Ensure the response is for the correct account.
//
if ( NlNameCompare( ClientSession->CsAccountName,
LocalUserName,
NAMETYPE_USER) != 0 ) {
NlPrint((NL_CRITICAL,
"NlDcDiscoveryHandler: "
" Domain name %ws from %ws has invalid account name %ws.\n",
LocalDomainName,
LocalServerName,
LocalUserName ));
Status = STATUS_PENDING;
goto Cleanup;
}
//
// Finally, tell the DC discovery machine what happened.
//
#ifdef DONT_REQUIRE_ACCOUNT
IF_DEBUG( DONT_REQUIRE_ACCOUNT ) {
Message->Opcode = LOGON_SAM_LOGON_RESPONSE;
}
#endif // DONT_REQUIRE_ACCOUNT
Status = NlDcDiscoveryMachine(
ClientSession,
(Message->Opcode == LOGON_SAM_LOGON_RESPONSE) ?
DcFoundMessage :
DcNotFoundMessage,
LocalServerName,
TransportName,
NULL,
FALSE );
//
// Free any locally used resources.
//
Cleanup:
if ( ClientSession != NULL ) {
NlUnrefClientSession( ClientSession );
}
return Status;
}
NTSTATUS
NlCaptureServerClientSession (
IN PCLIENT_SESSION ClientSession,
OUT WCHAR UncServerName[UNCLEN+1]
)
/*++
Routine Description:
Captures a copy of the UNC server name for the client session.
On Entry,
The trust list must NOT be locked.
The trust list entry must be referenced by the caller.
The caller must NOT be a writer of the trust list entry.
Arguments:
ClientSession - Specifies a pointer to the trust list entry to use.
UncServerName - Returns the UNC name of the server for this client session.
If there is none, an empty string is returned.
Return Value:
STATUS_SUCCESS - Server name was successfully copied.
Otherwise - Status of the secure channel
--*/
{
NTSTATUS Status;
NlAssert( ClientSession->CsReferenceCount > 0 );
EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
if ( ClientSession->CsState == CS_IDLE ) {
Status = ClientSession->CsConnectionStatus;
*UncServerName = L'\0';
} else {
Status = STATUS_SUCCESS;
wcscpy( UncServerName, ClientSession->CsUncServerName );
}
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
return Status;
}
PCLIENT_SESSION
NlPickDomainWithAccount (
IN LPWSTR AccountName,
IN ULONG AllowableAccountControlBits
)
/*++
Routine Description:
Get the name of a trusted domain that defines a particular account.
Arguments:
AccountName - Name of our user account to find.
AllowableAccountControlBits - A mask of allowable SAM account types that
are allowed to satisfy this request.
Return Value:
Pointer to referenced ClientSession structure describing the secure channel
to the domain containing the account.
The returned ClientSession is referenced and should be unreferenced
using NlUnrefClientSession.
NULL - DC was not found.
--*/
{
NTSTATUS Status;
NET_API_STATUS NetStatus;
PCLIENT_SESSION ClientSession;
DWORD i;
PLIST_ENTRY ListEntry;
DWORD ResponsesPending;
NETLOGON_SAM_LOGON_REQUEST SamLogonRequest;
PCHAR Where;
HANDLE ResponseMailslotHandle = NULL;
CHAR ResponseMailslotName[PATHLEN+1];
DWORD Opcode;
DWORD DomainSidSize;
//
// Define a local list of trusted domains.
//
ULONG LocalTrustListLength;
ULONG Index;
struct _LOCAL_TRUST_LIST {
//
// TRUE if ALL processing is finished on this trusted domain.
//
BOOLEAN Done;
//
// TRUE if at least one discovery has been done on this trusted domain.
//
BOOLEAN DiscoveryDone;
//
// TRUE if discovery is in progress on this trusted domain.
//
BOOLEAN DoingDiscovery;
//
// Number of times we need to repeat the current domain discovery
// or finduser datagram for this current domain.
//
DWORD RetriesLeft;
//
// Pointer to referenced ClientSession structure for the domain.
//
PCLIENT_SESSION ClientSession;
//
// Server name for the domain.
//
WCHAR UncServerName[UNCLEN+1];
//
// Second Server name for the domain.
//
WCHAR UncServerName2[UNCLEN+1];
} *LocalTrustList = NULL;
//
// Be verbose.
//
NlPrint((NL_LOGON,
"NlPickDomainWithAccount: %ws: Algorithm entered.\n",
AccountName ));
//
// Don't allow bogus user names.
//
// NlReadSamLogonResponse uses NlNameCompare to ensure the response message
// is for this user. Since NlNameCompare canonicalizes both names, it will
// reject invalid syntax. That causes NlReadSamLogonResponse to ignore ALL
// response messages, thus causing multiple retries before failing. We'd
// rather fail here.
//
if ( !NetpIsUserNameValid( AccountName ) ){
NlPrint((NL_CRITICAL,
"NlPickDomainWithAccount: Username " FORMAT_LPWSTR
" is invalid syntax.\n",
AccountName ));
return NULL;
}
//
// Allocate a local list of trusted domains.
//
LOCK_TRUST_LIST();
LocalTrustListLength = NlGlobalTrustListLength;
LocalTrustList = (struct _LOCAL_TRUST_LIST *) NetpMemoryAllocate(
LocalTrustListLength * sizeof(struct _LOCAL_TRUST_LIST));
if ( LocalTrustList == NULL ) {
UNLOCK_TRUST_LIST();
ClientSession = NULL;
goto Cleanup;
}
//
// Build a local list of trusted domains we know DCs for.
//
Index = 0;
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
ListEntry = ListEntry->Flink) {
ClientSession = CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
//
// Add this Client Session to the list.
//
NlRefClientSession( ClientSession );
LocalTrustList[Index].ClientSession = ClientSession;
Index++;
}
UNLOCK_TRUST_LIST();
//
// Capture the name of the server for each client session.
//
for ( Index = 0; Index < LocalTrustListLength; Index ++ ) {
(VOID) NlCaptureServerClientSession(
LocalTrustList[Index].ClientSession,
LocalTrustList[Index].UncServerName );
*LocalTrustList[Index].UncServerName2 = L'\0';
//
// We're not done yet.
//
LocalTrustList[Index].Done = FALSE;
//
// If there is no DC discovered for this domain,
// don't try very hard to discover one.
// (Indeed, just one discovery datagram is all we need.)
//
if ( *LocalTrustList[Index].UncServerName == L'\0' ) {
LocalTrustList[Index].RetriesLeft = 1;
LocalTrustList[Index].DoingDiscovery = TRUE;
LocalTrustList[Index].DiscoveryDone = TRUE;
//
// If we know the DC for this domain,
// try sending to the current DC before discovering a new one.
//
} else {
LocalTrustList[Index].RetriesLeft = 3;
LocalTrustList[Index].DoingDiscovery = FALSE;
LocalTrustList[Index].DiscoveryDone = FALSE;
}
}
//
// Create a mailslot for the DC's to respond to.
//
if (NetStatus = NetpLogonCreateRandomMailslot( ResponseMailslotName,
&ResponseMailslotHandle)){
NlPrint((NL_CRITICAL,
"NlPickDomainWithAccount: cannot create temp mailslot %ld\n",
NetStatus ));
ClientSession = NULL;
goto Cleanup;
}
//
// Build the query message.
//
SamLogonRequest.Opcode = LOGON_SAM_LOGON_REQUEST;
SamLogonRequest.RequestCount = 0;
Where = (PCHAR) &SamLogonRequest.UnicodeComputerName;
NetpLogonPutUnicodeString(
NlGlobalUnicodeComputerName,
sizeof(SamLogonRequest.UnicodeComputerName),
&Where );
NetpLogonPutUnicodeString(
AccountName,
sizeof(SamLogonRequest.UnicodeUserName),
&Where );
NetpLogonPutOemString(
ResponseMailslotName,
sizeof(SamLogonRequest.MailslotName),
&Where );
NetpLogonPutBytes(
&AllowableAccountControlBits,
sizeof(SamLogonRequest.AllowableAccountControlBits),
&Where );
//
// place domain NULL SID in the message.
//
DomainSidSize = 0;
NetpLogonPutBytes( &DomainSidSize, sizeof(DomainSidSize), &Where );
NetpLogonPutNtToken( &Where );
//
// Try multiple times to get a response from each DC.
//
for (;; ) {
//
// Send the mailslot message to each domain that has not yet responded.
//
ResponsesPending = 0;
for ( Index = 0; Index < LocalTrustListLength; Index ++ ) {
//
// If this domain has already responded, ignore it.
//
if ( LocalTrustList[Index].Done ) {
continue;
}
//
// If we don't currently know the DC name for this domain,
// capture any that's been discovered since we started the algorithm.
//
ClientSession = LocalTrustList[Index].ClientSession;
if ( *LocalTrustList[Index].UncServerName == L'\0' ) {
(VOID) NlCaptureServerClientSession(
LocalTrustList[Index].ClientSession,
LocalTrustList[Index].UncServerName );
//
// Handle the case where we've now discovered a DC.
//
if ( *LocalTrustList[Index].UncServerName != L'\0' ) {
NlPrint((NL_LOGON,
"NlPickDomainWithAccount: %ws: Noticed domain %wZ has discovered a new DC %ws.\n",
AccountName,
&ClientSession->CsDomainName,
LocalTrustList[Index].UncServerName ));
//
// If we did the discovery,
//
if ( LocalTrustList[Index].DoingDiscovery ) {
LocalTrustList[Index].DoingDiscovery = FALSE;
LocalTrustList[Index].RetriesLeft = 3;
}
}
}
//
// If we're done retrying what we were doing,
// try something else.
//
if ( LocalTrustList[Index].RetriesLeft == 0 ) {
if ( LocalTrustList[Index].DiscoveryDone ) {
LocalTrustList[Index].Done = TRUE;
NlPrint((NL_LOGON,
"NlPickDomainWithAccount: %ws: Can't find DC for domain %wZ (ignore this domain).\n",
AccountName,
&ClientSession->CsDomainName ));
continue;
} else {
//
// Save the previous DC name since it might just be
// very slow in responding. We'll want to be able
// to recognize responses from the previous DC.
//
wcscpy( LocalTrustList[Index].UncServerName2,
LocalTrustList[Index].UncServerName );
*LocalTrustList[Index].UncServerName = L'\0';
LocalTrustList[Index].DoingDiscovery = TRUE;
LocalTrustList[Index].RetriesLeft = 3;
LocalTrustList[Index].DiscoveryDone = TRUE;
}
}
//
// Indicate we're trying something.
//
LocalTrustList[Index].RetriesLeft --;
ResponsesPending ++;
//
// If its time to discover a DC in the domain,
// do it.
//
if ( LocalTrustList[Index].DoingDiscovery ) {
//
// Discover a new server
//
if ( NlTimeoutSetWriterClientSession( ClientSession, 10*1000 ) ) {
//
// Only tear down an existing secure channel once.
//
if ( LocalTrustList[Index].RetriesLeft == 3 ) {
NlSetStatusClientSession( ClientSession,
STATUS_NO_LOGON_SERVERS );
}
//
// We can't afford to wait so only send a single
// discovery datagram.
//
// Since the discovery is asysnchronous,
// it is very unlikely that we'll actually
// be able to query the DC on this pass. As such, this
// entire iteration of the loop is typically
// dedicated to discovery.
//
(VOID) NlDiscoverDc( ClientSession, DT_DeadDomain );
NlResetWriterClientSession( ClientSession );
}
}
//
// Send the message to a DC for the domain.
//
if ( *LocalTrustList[Index].UncServerName != L'\0' ) {
CHAR OemServerName[CNLEN+1];
// Skip over \\ in unc server name
NetpCopyWStrToStr( OemServerName,
LocalTrustList[Index].UncServerName+2 );
Status = NlBrowserSendDatagram(
OemServerName,
ClientSession->CsTransportName,
NETLOGON_NT_MAILSLOT_A,
&SamLogonRequest,
Where - (PCHAR)(&SamLogonRequest) );
if ( !NT_SUCCESS(Status) ) {
NlPrint((NL_CRITICAL,
"NlPickDomainWithAccount: "
" cannot write netlogon mailslot: 0x%lx\n",
Status));
ClientSession = NULL;
goto Cleanup;
}
}
}
//
// If all of the domains are done,
// leave the loop.
//
if ( ResponsesPending == 0 ) {
break;
}
//
// See if any DC responds.
//
while ( ResponsesPending > 0 ) {
LPWSTR UncLogonServer;
//
// If we timed out,
// break out of the loop.
//
if ( !NlReadSamLogonResponse( ResponseMailslotHandle,
AccountName,
&Opcode,
&UncLogonServer ) ) {
break;
}
//
// Find out which DC responded
//
// ?? Optimize by converting to uppercase OEM outside of loop
for ( Index = 0; Index < LocalTrustListLength; Index ++ ) {
ClientSession = LocalTrustList[Index].ClientSession;
if ( (*LocalTrustList[Index].UncServerName != L'\0' &&
NlNameCompare( LocalTrustList[Index].UncServerName+2,
UncLogonServer+2,
NAMETYPE_COMPUTER ) == 0 ) ||
(*LocalTrustList[Index].UncServerName2 != L'\0' &&
NlNameCompare( LocalTrustList[Index].UncServerName2+2,
UncLogonServer+2,
NAMETYPE_COMPUTER ) == 0 ) ) {
break;
}
}
NetpMemoryFree( UncLogonServer );
//
// If the response wasn't for one of the DCs we sent to,
// ignore the response.
//
if ( Index >= LocalTrustListLength ) {
NlPrint((NL_CRITICAL,
"NlPickDomainWithAccount: Server %ws responded though we didn't query it for account %ws.",
UncLogonServer,
AccountName ));
continue;
}
//
// If the DC recognizes our account,
// we've successfully found the DC.
//
if ( Opcode == LOGON_SAM_LOGON_RESPONSE ) {
NlPrint((NL_LOGON,
"NlPickDomainWithAccount: "
"%wZ has account " FORMAT_LPWSTR "\n",
&ClientSession->CsDomainName,
AccountName ));
goto Cleanup;
}
//
// If this DC has already responded once,
// ignore the response,
//
if ( LocalTrustList[Index].Done ) {
continue;
}
//
// Mark another DC as having responded negatively.
//
NlPrint((NL_CRITICAL,
"NlPickDomainWithAccount: "
"%wZ responded negatively for account "
FORMAT_LPWSTR " 0x%x\n",
&ClientSession->CsDomainName,
AccountName,
Opcode ));
LocalTrustList[Index].Done = TRUE;
ResponsesPending --;
}
}
//
// No DC has the specified account.
//
ClientSession = NULL;
//
// Cleanup locally used resources.
//
Cleanup:
if ( ResponseMailslotHandle != NULL ) {
CloseHandle(ResponseMailslotHandle);
}
//
// Unreference each client session structure and free the local trust list.
// (Keep the returned ClientSession referenced).
//
if ( LocalTrustList != NULL ) {
for (i=0; i<LocalTrustListLength; i++ ) {
if ( ClientSession != LocalTrustList[i].ClientSession ) {
NlUnrefClientSession( LocalTrustList[i].ClientSession );
}
}
NetpMemoryFree(LocalTrustList);
}
return ClientSession;
}
NTSTATUS
NlStartApiClientSession(
IN PCLIENT_SESSION ClientSession,
IN BOOLEAN QuickApiCall
)
/*++
Routine Description:
Enable the timer for timing out an API call on the secure channel.
On Entry,
The trust list must NOT be locked.
The caller must be a writer of the trust list entry.
Arguments:
ClientSession - Structure used to define the session.
QuickApiCall - True if this API call MUST finish in less than 45 seconds
and will in reality finish in less than 15 seconds unless something
is terribly wrong.
Return Value:
Status of the RPC binding to the server
--*/
{
NTSTATUS Status;
BOOLEAN BindingHandleCached;
LARGE_INTEGER TimeNow;
//
// Save the current time.
// Start the timer on the API call.
//
LOCK_TRUST_LIST();
NtQuerySystemTime( &TimeNow );
ClientSession->CsApiTimer.StartTime = TimeNow;
ClientSession->CsApiTimer.Period =
QuickApiCall ? NlGlobalShortApiCallPeriod : LONG_API_CALL_PERIOD;
//
// If the global timer isn't running,
// start it and tell the main thread that I've changed a timer.
//
if ( NlGlobalBindingHandleCount == 0 ) {
if ( NlGlobalApiTimer.Period != NlGlobalShortApiCallPeriod ) {
NlGlobalApiTimer.Period = NlGlobalShortApiCallPeriod;
NlGlobalApiTimer.StartTime = TimeNow;
if ( !SetEvent( NlGlobalTimerEvent ) ) {
NlPrint(( NL_CRITICAL,
"NlStartApiClientSession: %ws: SetEvent failed %ld\n",
ClientSession->CsDomainName.Buffer,
GetLastError() ));
}
}
}
//
// Remember if the binding handle is cached, then mark it as cached.
//
BindingHandleCached = (ClientSession->CsFlags & CS_BINDING_CACHED) != 0;
ClientSession->CsFlags |= CS_BINDING_CACHED;
//
// Count the number of concurrent binding handles cached
//
if ( !BindingHandleCached ) {
NlGlobalBindingHandleCount ++;
}
UNLOCK_TRUST_LIST();
//
// If the binding handle isn't already cached,
// cache it now.
//
if ( !BindingHandleCached ) {
NlPrint((NL_SESSION_MORE,
"NlStartApiClientSession: %wZ: Bind to server " FORMAT_LPWSTR ".\n",
&ClientSession->CsDomainName,
ClientSession->CsUncServerName ));
NlAssert( ClientSession->CsState != CS_IDLE );
Status = NlBindingAddServerToCache ( ClientSession->CsUncServerName );
if ( !NT_SUCCESS(Status) ) {
LOCK_TRUST_LIST();
ClientSession->CsFlags &= ~CS_BINDING_CACHED;
NlGlobalBindingHandleCount --;
UNLOCK_TRUST_LIST();
}
} else {
Status = STATUS_SUCCESS;
}
return Status;
}
BOOLEAN
NlFinishApiClientSession(
IN PCLIENT_SESSION ClientSession,
IN BOOLEAN OkToKillSession
)
/*++
Routine Description:
Disable the timer for timing out the API call.
Also, determine if it is time to pick a new DC since the current DC is
reponding so poorly. The decision is made from the number of
timeouts that happened during the last reauthentication time. If
timeoutcount is more than the limit, it sets the connection status
to CS_IDLE so that new DC will be picked up and new session will be
established.
On Entry,
The trust list must NOT be locked.
The caller must be a writer of the trust list entry.
Arguments:
ClientSession - Structure used to define the session.
OkToKillSession - TRUE if it's OK to actually drop the secure channel.
Otherwise, this routine will simply return FALSE upon timeout and
depend on the caller to drop the secure channel.
Return Value:
TRUE - API finished normally
FALSE - API timed out AND the ClientSession structure was torn down.
The caller shouldn't use the ClientSession structure without first
setting up another session. FALSE will only be return for a "quick"
API call.
FALSE does not imply that the API call failed. It should only be used
as an indication that the secure channel was torn down.
--*/
{
BOOLEAN SessionOk = TRUE;
TIMER ApiTimer;
//
// Grab a copy of the ApiTimer.
//
// Only a copy is needed and we don't want to keep the trust list locked
// while locking NlGlobalDcDiscoveryCritSect (wrong locking order) nor while
// freeing the session.
//
LOCK_TRUST_LIST();
ApiTimer = ClientSession->CsApiTimer;
//
// Turn off the timer for this API call.
//
ClientSession->CsApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
UNLOCK_TRUST_LIST();
//
// If this was a "quick" API call,
// and the API took too long,
// increment the count of times it timed out.
//
if ( ApiTimer.Period == NlGlobalShortApiCallPeriod ) {
if( NlTimeHasElapsed(
ApiTimer.StartTime,
( ClientSession->CsSecureChannelType ==
WorkstationSecureChannel ?
MAX_WKSTA_API_TIMEOUT :
MAX_DC_API_TIMEOUT) + NlGlobalExpectedDialupDelayParameter*1000 ) ) {
//
// API timeout.
//
ClientSession->CsTimeoutCount++;
NlPrint((NL_CRITICAL,
"NlFinishApiClientSession: "
"timeout call to " FORMAT_LPWSTR ". Count: %lu \n",
ClientSession->CsUncServerName,
ClientSession->CsTimeoutCount));
}
//
// did we hit the limit ?
//
if( ClientSession->CsTimeoutCount >=
(DWORD)( ClientSession->CsSecureChannelType ==
WorkstationSecureChannel ?
MAX_WKSTA_TIMEOUT_COUNT :
MAX_DC_TIMEOUT_COUNT ) ) {
BOOL IsTimeHasElapsed;
//
// block CsLastAuthenticationTry access
//
EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
IsTimeHasElapsed =
NlTimeHasElapsed(
ClientSession->CsLastAuthenticationTry,
( ClientSession->CsSecureChannelType ==
WorkstationSecureChannel ?
MAX_WKSTA_REAUTHENTICATION_WAIT :
MAX_DC_REAUTHENTICATION_WAIT) );
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
if( IsTimeHasElapsed ) {
NlPrint((NL_CRITICAL,
"NlFinishApiClientSession: "
"dropping the session to " FORMAT_LPWSTR "\n",
ClientSession->CsUncServerName ));
//
// timeoutcount limit exceeded and it is time to reauth.
//
SessionOk = FALSE;
//
// Only drop the secure channel if the caller requested it.
//
if ( OkToKillSession ) {
NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
//
// Start asynchronous DC discovery if this is not a workstation.
//
if ( NlGlobalRole != RoleMemberWorkstation ) {
(VOID) NlDiscoverDc( ClientSession, DT_Asynchronous );
}
}
}
}
}
return SessionOk;
}
BOOLEAN
NlTimeoutOneApiClientSession (
PCLIENT_SESSION ClientSession
)
/*++
Routine Description:
Timeout any API calls active specified client session structure
Arguments:
ClientSession: Pointer to client session to time out
Enter with global trust list locked.
Return Value:
TRUE - iff this routine temporarily dropped the global trust list lock.
--*/
{
#define SHARE_TO_KILL L"\\IPC$"
#define SHARE_TO_KILL_LENGTH 5
NET_API_STATUS NetStatus;
WCHAR ShareToKill[UNCLEN+SHARE_TO_KILL_LENGTH+1];
WCHAR UncServerName[UNCLEN+1];
BOOLEAN TrustListUnlocked = FALSE;
//
// Ignore non-existent sessions.
//
if ( ClientSession == NULL ) {
return FALSE;
}
//
// If an API call is in progress and has taken too long,
// Timeout the API call.
//
if ( NlTimeHasElapsed( ClientSession->CsApiTimer.StartTime,
ClientSession->CsApiTimer.Period ) ) {
//
// Save the server name but drop all our locks.
//
NlRefClientSession( ClientSession );
UNLOCK_TRUST_LIST();
(VOID) NlCaptureServerClientSession( ClientSession, ShareToKill );
NlUnrefClientSession( ClientSession );
TrustListUnlocked = TRUE;
//
// Now that we've unlocked the trust list,
// Drop the session to the server we've identified.
//
wcscat( ShareToKill, SHARE_TO_KILL );
NlPrint(( NL_CRITICAL,
"NlTimeoutApiClientSession: Start NetUseDel on "
FORMAT_LPWSTR "\n",
ShareToKill ));
IF_DEBUG( INHIBIT_CANCEL ) {
NlPrint(( NL_INHIBIT_CANCEL,
"NlimeoutApiClientSession: NetUseDel bypassed due to "
"INHIBIT_CANCEL Dbflag on " FORMAT_LPWSTR "\n",
ShareToKill ));
} else {
NetStatus = NetUseDel( NULL, ShareToKill, USE_LOTS_OF_FORCE );
}
NlPrint(( NL_CRITICAL,
"NlTimeoutApiClientSession: Completed NetUseDel on "
FORMAT_LPWSTR " (%ld)\n",
ShareToKill,
NetStatus ));
//
// If we have an RPC binding handle cached,
// and it has outlived its usefulness,
// purge it from the cache.
//
} else if ( (ClientSession->CsFlags & CS_BINDING_CACHED) != 0 &&
NlTimeHasElapsed( ClientSession->CsApiTimer.StartTime,
BINDING_CACHE_PERIOD ) ) {
//
// We must be a writer of the Client Session to unbind the RPC binding
// handle.
//
// Don't wait to become the writer because:
// A) We've violated the locking order by trying to become the writer
// with the trust list locked.
// B) The writer might be doing a long API call like replication and
// we're not willing to wait.
//
NlRefClientSession( ClientSession );
if ( NlTimeoutSetWriterClientSession( ClientSession, 0 ) ) {
//
// Indicate the handle is no longer cached.
//
ClientSession->CsFlags &= ~CS_BINDING_CACHED;
NlGlobalBindingHandleCount --;
//
// Save the server name but drop all our locks.
//
UNLOCK_TRUST_LIST();
(VOID) NlCaptureServerClientSession( ClientSession, UncServerName );
TrustListUnlocked = TRUE;
//
// Unbind this server.
//
NlPrint((NL_SESSION_MORE,
"NlTimeoutApiClientSession: %wZ: Unbind from server " FORMAT_LPWSTR ".\n",
&ClientSession->CsDomainName,
UncServerName ));
(VOID) NlBindingRemoveServerFromCache( UncServerName );
//
// Done being writer of the client session.
//
NlResetWriterClientSession( ClientSession );
}
NlUnrefClientSession( ClientSession );
}
if ( TrustListUnlocked ) {
LOCK_TRUST_LIST();
}
return TrustListUnlocked;
}
VOID
NlTimeoutApiClientSession (
VOID
)
/*++
Routine Description:
Timeout any API calls active on any of the client session structures
Arguments:
NONE.
Return Value:
NONE.
--*/
{
PCLIENT_SESSION ClientSession;
PLIST_ENTRY ListEntry;
//
// If there are no API calls outstanding,
// just reset the global timer.
//
NlPrint(( NL_SESSION_MORE, "NlTimeoutApiClientSession Called\n"));
LOCK_TRUST_LIST();
if ( NlGlobalBindingHandleCount == 0 ) {
NlGlobalApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
//
// If there are API calls outstanding,
// Loop through the trust list making a list of Servers to kill
//
} else {
//
// Mark each trust list entry indicating it needs to be handled
//
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
ListEntry = ListEntry->Flink) {
ClientSession = CONTAINING_RECORD( ListEntry,
CLIENT_SESSION,
CsNext );
ClientSession->CsFlags |= CS_HANDLE_API_TIMER;
}
//
// Loop thru the trust list handling API timeout
//
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
) {
ClientSession = CONTAINING_RECORD( ListEntry,
CLIENT_SESSION,
CsNext );
//
// If we've already done this entry,
// skip this entry.
//
if ( (ClientSession->CsFlags & CS_HANDLE_API_TIMER) == 0 ) {
ListEntry = ListEntry->Flink;
continue;
}
ClientSession->CsFlags &= ~CS_HANDLE_API_TIMER;
//
// Handle timing out the API call and the RPC binding handle.
//
// If the routine had to drop the TrustList crit sect,
// start at the very beginning of the list.
if ( NlTimeoutOneApiClientSession ( ClientSession ) ) {
ListEntry = NlGlobalTrustList.Flink;
} else {
ListEntry = ListEntry->Flink;
}
}
//
// Do the global client session, too.
//
(VOID) NlTimeoutOneApiClientSession ( NlGlobalClientSession );
}
UNLOCK_TRUST_LIST();
return;
}
NTSTATUS
NetrEnumerateTrustedDomains (
IN LPWSTR ServerName OPTIONAL,
OUT PDOMAIN_NAME_BUFFER DomainNameBuffer
)
/*++
Routine Description:
This API returns the names of the domains trusted by the domain ServerName is a member of.
The returned list does not include the domain ServerName is directly a member of.
Netlogon implements this API by calling LsaEnumerateTrustedDomains on a DC in the
domain ServerName is a member of. However, Netlogon returns cached information if
it has been less than 5 minutes since the last call was made or if no DC is available.
Netlogon's cache of Trusted domain names is maintained in the registry across reboots.
As such, the list is available upon boot even if no DC is available.
Arguments:
ServerName - name of remote server (null for local). ServerName must be an NT workstation
or NT non-DC server.
DomainNameBuffer->DomainNames - Returns an allocated buffer containing the list of trusted domains in
MULTI-SZ format (i.e., each string is terminated by a zero character, the next string
immediately follows, the sequence is terminated by zero length domain name). The
buffer should be freed using NetApiBufferFree.
DomainNameBuffer->DomainNameByteCount - Number of bytes returned in DomainNames
Return Value:
ERROR_SUCCESS - Success.
STATUS_NOT_SUPPORTED - This machine is not an NT workstation or NT non-DC server.
STATUS_NO_LOGON_SERVERS - No DC could be found and no cached information is available.
STATUS_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
broken and no cached information is available.
STATUS_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
broken or the password is broken and no cached information is available.
--*/
{
NTSTATUS Status;
PCLIENT_SESSION ClientSession = NlGlobalClientSession;
ULONG DiscoveryDone = FALSE;
LPWSTR TrustedDomainList = NULL;
BOOL TrustedDomainListKnown;
UNICODE_STRING UncDcNameString;
WCHAR UncDcName[UNCLEN+1];
NlPrint((NL_MISC,
"NetrEnumerateTrustedDomains: Called.\n" ));
if ( NlGlobalRole == RoleMemberWorkstation ) {
//
// Don't give up unless we've done discovery.
//
do {
//
// If we don't currently know the name of the server,
// discover one.
//
if ( ClientSession->CsState == CS_IDLE ) {
//
// If we've tried to authenticate recently,
// don't bother trying again.
//
if ( !NlTimeToReauthenticate( ClientSession ) ) {
Status = ClientSession->CsConnectionStatus;
goto Cleanup;
}
//
// Discover a DC
//
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
NlPrint((NL_CRITICAL, "NetrEnumerateTrustedDomains: Can't become writer of client session.\n" ));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
// Check again now that we're the writer
if ( ClientSession->CsState == CS_IDLE ) {
Status = NlDiscoverDc( ClientSession, DT_Synchronous );
if ( !NT_SUCCESS(Status) ) {
NlResetWriterClientSession( ClientSession );
NlPrint((NL_CRITICAL,
"NetrEnumerateTrustedDomains: Discovery failed %lx\n",
Status ));
goto Cleanup;
}
NlPrint((NL_MISC,
"NetrEnumerateTrustedDomains: Discovery succeeded\n" ));
DiscoveryDone = TRUE;
}
NlResetWriterClientSession( ClientSession );
}
//
// Capture a copy of the DC the session is to.
//
Status = NlCaptureServerClientSession( ClientSession, UncDcName );
if ( !NT_SUCCESS(Status) ) {
Status = STATUS_NO_LOGON_SERVERS;
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
NlPrint((NL_CRITICAL, "NetrEnumerateTrustedDomainsGetAnyDcName: Can't become writer of client session.\n" ));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
NlSetStatusClientSession( ClientSession, Status );
NlResetWriterClientSession( ClientSession );
continue;
}
//
// If we don't have DCs in our cache or
// if it has been more than 5 minutes since we've refreshed our cache,
// get a new list from our primary domain.
//
if ( !NlGlobalTrustedDomainListKnown ||
NlTimeHasElapsed( NlGlobalTrustedDomainListTime, 5 * 60 * 1000 ) ) {
NlPrint((NL_MISC,
"NetrEnumerateTrustedDomains: Domain List collected from %ws\n", UncDcName ));
Status = NlGetTrustedDomainList (
UncDcName,
&TrustedDomainList );
if ( !NT_SUCCESS(Status) ) {
Status = STATUS_NO_LOGON_SERVERS;
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
NlPrint((NL_CRITICAL, "NetrEnumerateTrustedDomainsGetAnyDcName: Can't become writer of client session.\n" ));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
NlSetStatusClientSession( ClientSession, Status );
NlResetWriterClientSession( ClientSession );
continue;
}
continue;
//
// Otherwise just use the cached information.
//
} else {
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
} while ( !NT_SUCCESS(Status) && !DiscoveryDone );
//
// Free any locally used resources.
//
Cleanup:
//
// Don't divulge too much to the caller.
//
if ( Status == STATUS_ACCESS_DENIED ) {
Status = STATUS_NO_TRUST_SAM_ACCOUNT;
}
//
// If we simply can't access a DC,
// return the cached information.
//
if ( !NT_SUCCESS(Status) ) {
NET_API_STATUS NetStatus;
NlPrint((NL_MISC,
"NetrEnumerateTrustedDomains: Domain List returned from cache.\n" ));
NetStatus = NlReadRegTrustedDomainList (
NULL,
FALSE, // Don't delete registry key
&TrustedDomainList,
&TrustedDomainListKnown );
// Leave 'Status' alone if we can't read from the cache.
if (NetStatus == NO_ERROR ) {
if ( TrustedDomainListKnown ) {
Status = STATUS_SUCCESS;
}
} else {
NlPrint((NL_CRITICAL,
"NetrEnumerateTrustedDomains: Can't get Domain List from cache: 0x%lX\n",
NetStatus ));
}
}
#ifdef notdef // We're using NlGlobalClientSession
if ( ClientSession != NULL ) {
NlUnrefClientSession( ClientSession );
}
#endif // notdef // We're using NlGlobalClientSession
//
// Return the DCName to the caller.
//
if ( NT_SUCCESS(Status) ) {
DomainNameBuffer->DomainNameByteCount = NetpTStrArraySize( TrustedDomainList );
DomainNameBuffer->DomainNames = (LPBYTE) TrustedDomainList;
} else {
if ( TrustedDomainList != NULL ) {
NetApiBufferFree( TrustedDomainList );
}
DomainNameBuffer->DomainNameByteCount = 0;
DomainNameBuffer->DomainNames = NULL;
}
NlPrint((NL_MISC,
"NetrEnumerateTrustedDomains: returns: 0x%lX\n",
Status ));
return Status;
} else {
//
// NlGlobalRole != RoleMemberWorksation
//
#ifndef notdef
Status = NlGetTrustedDomainList (
NULL, // we want to query our own trusted domain list
&TrustedDomainList );
//
// Return the DCName to the caller.
//
if ( NT_SUCCESS(Status) ) {
DomainNameBuffer->DomainNameByteCount = NetpTStrArraySize( TrustedDomainList );
DomainNameBuffer->DomainNames = (LPBYTE) TrustedDomainList;
} else {
if ( TrustedDomainList != NULL ) {
NetApiBufferFree( TrustedDomainList );
}
DomainNameBuffer->DomainNameByteCount = 0;
DomainNameBuffer->DomainNames = NULL;
}
NlPrint((NL_MISC,
"NetrEnumerateTrustedDomains: returns: 0x%lX\n",
Status ));
return Status;
#else
//
// BUGBUG: this code does not work because the list of
// client sessions does not accurately reflect the list of
// trusted domains. See bug 34234
//
PLIST_ENTRY ListEntry;
ULONG BufferLength;
LPWSTR CurrentLoc;
//
// Loop through the client sessions add first calculate the
// size required
//
BufferLength = sizeof(WCHAR);
LOCK_TRUST_LIST();
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
ListEntry = ListEntry->Flink) {
ClientSession =
CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
BufferLength += ClientSession->CsDomainName.Length + sizeof(WCHAR);
}
TrustedDomainList = (LPWSTR) NetpMemoryAllocate( BufferLength );
if (TrustedDomainList == NULL) {
Status = STATUS_NO_MEMORY;
} else {
Status = STATUS_SUCCESS;
*TrustedDomainList = L'\0';
CurrentLoc = TrustedDomainList;
//
// Now add all the trusted domains onto the string we
// allocated
//
for ( ListEntry = NlGlobalTrustList.Flink ;
ListEntry != &NlGlobalTrustList ;
ListEntry = ListEntry->Flink) {
ClientSession =
CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
RtlCopyMemory(
CurrentLoc,
ClientSession->CsDomainName.Buffer,
ClientSession->CsDomainName.Length
);
CurrentLoc += ClientSession->CsDomainName.Length / sizeof(WCHAR);
*(CurrentLoc++) = L'\0';
*CurrentLoc = L'\0'; // Place double terminator each time
}
}
UNLOCK_TRUST_LIST();
//
// Return the list of domains to the caller.
//
if ( NT_SUCCESS(Status) ) {
DomainNameBuffer->DomainNameByteCount = NetpTStrArraySize( TrustedDomainList );
DomainNameBuffer->DomainNames = (LPBYTE) TrustedDomainList;
} else {
if ( TrustedDomainList != NULL ) {
NetApiBufferFree( TrustedDomainList );
}
DomainNameBuffer->DomainNameByteCount = 0;
DomainNameBuffer->DomainNames = NULL;
}
return Status;
#endif
}
UNREFERENCED_PARAMETER( ServerName );
}