/*++ Copyright (c) 1992 Microsoft Corporation Module Name: dblookup.c Abstract: LSA Database - Lookup Sid and Name routines NOTE: This module should remain as portable code that is independent of the implementation of the LSA Database. As such, it is permitted to use only the exported LSA Database interfaces contained in db.h and NOT the private implementation dependent functions in dbp.h. Author: Scott Birrell (ScottBi) November 27, 1992 Environment: Revision History: --*/ #include #include "dbp.h" #include #include #include #include #include #include #include "lsawmi.h" #include #include #include #include // DnsNameCompare_W ////////////////////////////////////////////////////////////////////////// // // // Lsa Lookup Sid and Name Private Global State Variables // // // ////////////////////////////////////////////////////////////////////////// LARGE_INTEGER LsapDbLookupTimeout; HANDLE LsapDbLookupStartedEvent = NULL; // // This global is set to TRUE when a particular registry key is set // (see the lookup init routine). It means that when a // downlevel client is making a request return the current features of // lookup (search by UPN, transitive trust, etc) all of which are // performed by doing a GC search. By default this feature is // turned off. // BOOLEAN LsapAllowExtendedDownlevelLookup = FALSE; // // This variable, settable in the registry, indicates what events // should be logged. // DWORD LsapLookupLogLevel = 0; // // This global makes LsarLookupSids return SidTypeDeleted for SID's that // otherwise would be returned as SidTypeUnknown. This is to prevent NT4 // wksta's from AV'ing. See WinSERaid bug 11298 for more details. // BOOLEAN LsapReturnSidTypeDeleted = FALSE; // // This global is set to TRUE when a particular registry value is set // (see the lookup init routine). The chaining of isolated names to // externally trusted domains depends on this value being FALSE. // BOOLEAN LsapLookupRestrictIsolatedNameLevel = FALSE; ////////////////////////////////////////////////////////////////////////// // // // Forwards for this module // // // ////////////////////////////////////////////////////////////////////////// NTSTATUS LsapRtlValidateControllerTrustedDomainByHandle( IN LSA_HANDLE DcPolicyHandle, IN PLSAPR_TRUST_INFORMATION TrustInformation ); NTSTATUS LsapDbLookupInitPolicyCache( VOID ); NTSTATUS LsapDbLookupGetServerConnection( IN LSAPR_TRUST_INFORMATION_EX *TrustInfo, IN DWORD Flags, IN LSAP_LOOKUP_LEVEL LookupLevel, IN PLARGE_INTEGER FailedSessionSetupTime, OPTIONAL OUT LPWSTR *ServerName, OUT NL_OS_VERSION *ServerOsVersion, OUT LPWSTR *ServerPrincipalName, OUT PVOID *ClientContext, OUT ULONG *AuthnLevel, OUT LSA_HANDLE *PolicyHandle, OUT PLSAP_BINDING_CACHE_ENTRY * ControllerPolicyEntry, OUT PLARGE_INTEGER SessionSetupTime ); NTSTATUS LsapNullTerminateUnicodeString( IN PUNICODE_STRING String, OUT LPWSTR *pBuffer, OUT BOOLEAN *fFreeBuffer ); // // Flags for LsapDomainHasDomainTrust // // // Lookup domains that we trust that are external to our forest // #define LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_EXTERNAL 0x00000001 // // Lookup domains that are within our forest and that we directly trust // #define LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_INTRA 0x00000002 // // Lookup forest trusts // #define LSAP_LOOKUP_DOMAIN_TRUST_FOREST 0x00000004 NTSTATUS LsapDomainHasDomainTrust( IN ULONG Flags, IN PUNICODE_STRING DomainName, OPTIONAL IN PSID DomainSid, OPTIONAL IN OUT BOOLEAN *fTDLLock, OPTIONAL OUT PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY *TrustEntryOut OPTIONAL ); VOID LsapLookupSamAccountNameToUPN( IN OUT PUNICODE_STRING Name ); VOID LsapLookupUPNToSamAccountName( IN OUT PUNICODE_STRING Name ); BOOL LsapLookupIsUPN( OUT PUNICODE_STRING Name ); VOID LsapLookupCrackName( IN PUNICODE_STRING Prefix, IN PUNICODE_STRING Suffix, OUT PUNICODE_STRING SamAccountName, OUT PUNICODE_STRING DomainName ); ////////////////////////////////////////////////////////////////////////// // // // Lsa Lookup Helper routines // // // ////////////////////////////////////////////////////////////////////////// ULONG LsapLookupGetChainingFlags( IN NL_OS_VERSION ServerOsVersion ) /*++ Routine Description: Based on the OsVersion, this routine determines what flags to pass into LsaIC* to help route the version of the Lsar* routine to call. Arguments: OsVersion -- the version of the secure channel DC Return Values: --*/ { ULONG Flags = 0; if ( ServerOsVersion == NlWin2000 ) { Flags |= LSAIC_WIN2K_TARGET; } else if (ServerOsVersion <= NlNt40) { Flags |= LSAIC_NT4_TARGET; } return Flags; } NTSTATUS LsapDbLookupAddListReferencedDomains( IN OUT PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains, IN PLSAPR_TRUST_INFORMATION TrustInformation, OUT PLONG DomainIndex ) /*++ Routine Description: This function searches a Referenced Domain List for an entry for a given domain and, if no entry exists, adds new entry. If an entry id added, its index into the Referenced Domain List is returned, otherwise the index of the existing entry is returned. If an entry needs to be added and there is insufficient room in the list provided for the new entry, the list will be created or grown as necessary. Arguments: ReferencedDomains - Pointer to a structure in which the list of domains used in the translation is maintained. The entries in this structure are referenced by the structure returned via the Sids parameter. Unlike the Sids parameter, which contains an array entry for each translated name, this structure will only contain one component for each domain utilized in the translation. TrustInformation - Points to Trust Information for the domain being added to the list. On exit, the DomainIndex parameter will be set to the index of the entry on the Referenced Domain List; a negative value will be stored in the error case. DomainIndex - Pointer to location that receives the index of the newly added or existing entry for the domain within the Referenced Domain List. In the error case, a negative value is returned. Return Values: NTSTATUS - Standard Nt Result Code STATUS_SUCCESS - The call completed successfully. STATUS_INSUFFICIENT_RESOURCES - Insufficient system resources, such as memory, to complete the call. --*/ { NTSTATUS Status; ULONG NextIndex; LSAPR_TRUST_INFORMATION OutputTrustInformation; OutputTrustInformation.Name.Buffer = NULL; OutputTrustInformation.Sid = NULL; Status = STATUS_INVALID_PARAMETER; if (ReferencedDomains == NULL) { goto AddListReferencedDomainsError; } Status = STATUS_SUCCESS; // // Search the existing list, trying to match the Domain Sid in the // provided Trust Information with the Domain Sid in a Referenced Domain // List entry. If an entry is found with matching Sid, just return // that entry's index. // if (LsapDbLookupListReferencedDomains( ReferencedDomains, TrustInformation->Sid, DomainIndex )) { goto AddListReferencedDomainsFinish; } // // Check that there is enough room in the List provided for one more // entry. If not, grow the list, copying and freeing the old. // Status = STATUS_SUCCESS; if (ReferencedDomains->Entries >= ReferencedDomains->MaxEntries) { Status = LsapDbLookupGrowListReferencedDomains( ReferencedDomains, ReferencedDomains->MaxEntries + LSAP_DB_REF_DOMAIN_DELTA ); if (!NT_SUCCESS(Status)) { goto AddListReferencedDomainsError; } } // // We now have a Referenced Domain List with room for at least one more // entry. Copy in the Trust Information. // NextIndex = ReferencedDomains->Entries; Status = LsapRpcCopyUnicodeString( NULL, (PUNICODE_STRING) &OutputTrustInformation.Name, (PUNICODE_STRING) &TrustInformation->Name ); if (!NT_SUCCESS(Status)) { goto AddListReferencedDomainsError; } if ( TrustInformation->Sid ) { Status = LsapRpcCopySid( NULL, (PSID) &OutputTrustInformation.Sid, (PSID) TrustInformation->Sid ); if (!NT_SUCCESS(Status)) { goto AddListReferencedDomainsError; } } ReferencedDomains->Domains[NextIndex] = OutputTrustInformation; *DomainIndex = (LONG) NextIndex; ReferencedDomains->Entries++; AddListReferencedDomainsFinish: return(Status); AddListReferencedDomainsError: // // Cleanup buffers allocated for Output Trust Information structure. // if (OutputTrustInformation.Name.Buffer != NULL) { MIDL_user_free( OutputTrustInformation.Name.Buffer ); } if (OutputTrustInformation.Sid != NULL) { MIDL_user_free( OutputTrustInformation.Sid ); } goto AddListReferencedDomainsFinish; } NTSTATUS LsapDbLookupCreateListReferencedDomains( OUT PLSAPR_REFERENCED_DOMAIN_LIST *ReferencedDomains, IN ULONG InitialMaxEntries ) /*++ Routine Description: This function creates an empty Referenced Domain List. The caller is responsible for cleaning up this list when no longer required. Arguments: ReferencedDomains - Receives a pointer to the newly created empty Referenced Domain List. InitialMaxEntries - Initial maximum number of entries desired. Return Values: NTSTATUS - Standard Nt Result Code. STATUS_SUCCESS - The call completed successfully. STATUS_INSUFFICIENT_RESOURCES - Insufficient System Resources such as memory, to complete the call. --*/ { NTSTATUS Status = STATUS_SUCCESS; ULONG DomainsLength; PLSAPR_TRUST_INFORMATION Domains = NULL; PVOID Buffers[2]; ULONG BufferCount; ULONG Index; PLSAPR_REFERENCED_DOMAIN_LIST OutputReferencedDomains = NULL; // // Allocate the Referenced Domain List header. // BufferCount = 0; OutputReferencedDomains = MIDL_user_allocate( sizeof(LSAP_DB_REFERENCED_DOMAIN_LIST) ); if (OutputReferencedDomains == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto CreateListReferencedDomainsError; } Buffers[BufferCount] = OutputReferencedDomains; BufferCount++; // // If a non-zero initial entry count, allocate an array of Trust Information // entries. // if (InitialMaxEntries > 0) { DomainsLength = sizeof(LSA_TRUST_INFORMATION) * InitialMaxEntries; Domains = MIDL_user_allocate( DomainsLength ); Status = STATUS_INSUFFICIENT_RESOURCES; if (Domains == NULL) { goto CreateListReferencedDomainsError; } Status = STATUS_SUCCESS; Buffers[BufferCount] = Domains; BufferCount++; RtlZeroMemory( Domains, DomainsLength ); } // // Initialize the Referenced Domain List Header // OutputReferencedDomains->Entries = 0; OutputReferencedDomains->MaxEntries = InitialMaxEntries; OutputReferencedDomains->Domains = Domains; CreateListReferencedDomainsFinish: *ReferencedDomains = OutputReferencedDomains; return(Status); CreateListReferencedDomainsError: // // Free up buffers allocated by this routine. // for (Index = 0; Index < BufferCount; Index++) { MIDL_user_free(Buffers[Index]); } OutputReferencedDomains = NULL; goto CreateListReferencedDomainsFinish; } NTSTATUS LsapDbLookupGrowListReferencedDomains( IN OUT PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains, IN ULONG MaxEntries ) /*++ Routine Description: This function expands a Referenced Domain List to contain the specified maximum number of entries. The memory for the old Domains array is released. Arguments: ReferencedDomains - Pointer to a Referenced Domain List. This list references an array of zero or more Trust Information entries describing each of the domains referenced by the names. This array will be appended to/reallocated if necessary. MaxEntries - New maximum number of entries. Return Values: NTSTATUS - Standard Nt Result Code STATUS_SUCCESS - The call completed successfully. STATUS_INSUFFICIENT_RESOURCES - Insufficient system resources to complete the call. --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSAPR_TRUST_INFORMATION NewDomainsInfo = NULL; PLSAPR_TRUST_INFORMATION OldDomainsInfo = NULL; ULONG OldDomainsInfoLength, NewDomainsInfoLength; if (ReferencedDomains->MaxEntries < MaxEntries) { NewDomainsInfoLength = MaxEntries * sizeof (LSA_TRUST_INFORMATION); OldDomainsInfoLength = ReferencedDomains->MaxEntries * sizeof (LSA_TRUST_INFORMATION); NewDomainsInfo = MIDL_user_allocate( NewDomainsInfoLength ); Status = STATUS_INSUFFICIENT_RESOURCES; if (NewDomainsInfo == NULL) { goto GrowListReferencedDomainsError; } Status = STATUS_SUCCESS; // // If there was an existing Trust Information Array, copy it // to the newly allocated one and free it. // OldDomainsInfo = ReferencedDomains->Domains; if (OldDomainsInfo != NULL) { RtlCopyMemory( NewDomainsInfo, OldDomainsInfo, OldDomainsInfoLength ); MIDL_user_free( OldDomainsInfo ); } ReferencedDomains->Domains = NewDomainsInfo; ReferencedDomains->MaxEntries = MaxEntries; } GrowListReferencedDomainsFinish: return(Status); GrowListReferencedDomainsError: goto GrowListReferencedDomainsFinish; } BOOLEAN LsapDbLookupListReferencedDomains( IN PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains, IN PLSAPR_SID DomainSid, OUT PLONG DomainIndex ) /*++ Routine Description: This function searches a Referenced Domain List for a given domain and, if found, returns the index of the domain's entry in the list. If the domain is not found an error is returned and a negative value is returned. Arguments: ReferencedDomains - Pointer to a Referenced Domain List. This list references an array of zero or more Trust Information entries describing each of the domains referenced by the names. This array will be appended to/reallocated if necessary. DomainSid - Information containing the Domain's Sid. DomainIndex - Pointer to location that receives the index of the domain in the Referenced Domain List if the domin is found, otherwise a negative value. Return Values: BOOLEAN - TRUE if entry found, else FALSE. --*/ { BOOLEAN BooleanStatus = FALSE; LONG Index; LONG Entries; PLSAPR_TRUST_INFORMATION DomainsInfo; // // Search the Referenced Domain List by Sid or by Name // Entries = (LONG) ReferencedDomains->Entries; DomainsInfo = ReferencedDomains->Domains; *DomainIndex = LSA_UNKNOWN_INDEX; for (Index = 0; Index < (LONG) Entries && DomainSid; Index++) { if ( DomainsInfo[Index].Sid && RtlEqualSid( ( PSID )DomainsInfo[Index].Sid, ( PSID )DomainSid ) ) { BooleanStatus = TRUE; *DomainIndex = Index; break; } } return(BooleanStatus); } NTSTATUS LsapDbLookupMergeDisjointReferencedDomains( IN OPTIONAL PLSAPR_REFERENCED_DOMAIN_LIST FirstReferencedDomainList, IN OPTIONAL PLSAPR_REFERENCED_DOMAIN_LIST SecondReferencedDomainList, OUT PLSAPR_REFERENCED_DOMAIN_LIST *OutputReferencedDomainList, IN ULONG Options ) /*++ Routine Description: This function merges disjoint Referenced Domain Lists, producing a third list. The output list is always produced in non allocate(all_nodes) form. Arguments: FirstReferencedDomainList - Pointer to first mergand. SecondReferencedDomainList - Pointer to second mergand. OutputReferencedDomainList - Receives a pointer to the output list. Options - Specifies optional actions LSAP_DB_USE_FIRST_MERGAND_GRAPH - Specifies that the resulting merged Referenced Domain List may reference the graph of pointers in the first Referenced Domain list. This option is normally selected, since that graph has been allocated as individual nodes. LSAP_DB_USE_SECOND_MERGAND_GRAPH - Specifies that the resulting merged Referenced Domain List may reference the graph of pointers in the first Referenced Domain list. This option is normally not selected, since that graph is usually allocated as all_nodes. Return Values: NTSTATUS - Standard Nt Result Code. --*/ { NTSTATUS Status; ULONG TotalEntries; ULONG FirstReferencedDomainListLength; ULONG SecondReferencedDomainListLength; ULONG FirstEntries, SecondEntries; LSAP_MM_FREE_LIST FreeList; ULONG NextEntry; ULONG MaximumFreeListEntries; ULONG CleanupFreeListOptions = (ULONG) 0; // // Initialize output parmameter // *OutputReferencedDomainList = NULL; // // Calculate Size of output Referenced Domain List. // FirstEntries = (ULONG) 0; if (FirstReferencedDomainList != NULL) { FirstEntries = FirstReferencedDomainList->Entries; } SecondEntries = (ULONG) 0; if (SecondReferencedDomainList != NULL) { SecondEntries = SecondReferencedDomainList->Entries; } TotalEntries = FirstEntries + SecondEntries; // // Allocate a Free List for error cleanup. We need two entries // per Referenced Domain List entry, one for the Domain Name buffer // and one for the Domain Sid. // MaximumFreeListEntries = (ULONG) 0; if (!(Options & LSAP_DB_USE_FIRST_MERGAND_GRAPH)) { MaximumFreeListEntries += 2*FirstEntries; } if (!(Options & LSAP_DB_USE_SECOND_MERGAND_GRAPH)) { MaximumFreeListEntries += 2*SecondEntries; } Status = LsapMmCreateFreeList( &FreeList, MaximumFreeListEntries ); if (!NT_SUCCESS(Status)) { goto MergeDisjointDomainsError; } Status = LsapDbLookupCreateListReferencedDomains( OutputReferencedDomainList, TotalEntries ); if (!NT_SUCCESS(Status)) { goto MergeDisjointDomainsError; } // // Set the number of entries used. We will use all of the entries, // so set this value to the Maximum number of Entries. // ASSERT(OutputReferencedDomainList); (*OutputReferencedDomainList)->Entries = TotalEntries; if ( 0 == TotalEntries ) { // // There is not much to do // // This ASSERT is to understand conditions underwhich we might hit this // scenario. There is likely a coding bug else if we are asking // two empty lists to be merged. // ASSERT( 0 == TotalEntries ); ASSERT( NT_SUCCESS(Status) ); goto MergeDisjointDomainsFinish; } // // Copy in the entries (if any) from the first list. // FirstReferencedDomainListLength = FirstEntries * sizeof(LSA_TRUST_INFORMATION); if (FirstReferencedDomainListLength > (ULONG) 0) { if (Options & LSAP_DB_USE_FIRST_MERGAND_GRAPH) { RtlCopyMemory( (*OutputReferencedDomainList)->Domains, FirstReferencedDomainList->Domains, FirstReferencedDomainListLength ); } else { // // The graph of the first Referenced Domain List must be // copied to separately allocated memory buffers. // Copy each of the Trust Information entries, allocating // individual memory buffers for each Domain Name and Sid. // for (NextEntry = 0; NextEntry < FirstEntries; NextEntry++) { Status = LsapRpcCopyUnicodeString( &FreeList, (PUNICODE_STRING) &((*OutputReferencedDomainList)->Domains[NextEntry].Name), (PUNICODE_STRING) &FirstReferencedDomainList->Domains[NextEntry].Name ); if (!NT_SUCCESS(Status)) { break; } if ( FirstReferencedDomainList->Domains[NextEntry].Sid ) { Status = LsapRpcCopySid( &FreeList, (PSID) &((*OutputReferencedDomainList)->Domains[NextEntry].Sid), (PSID) FirstReferencedDomainList->Domains[NextEntry].Sid ); } else { (*OutputReferencedDomainList)->Domains[NextEntry].Sid = NULL; } if (!NT_SUCCESS(Status)) { break; } } if (!NT_SUCCESS(Status)) { goto MergeDisjointDomainsError; } } } // // Copy in the entries (if any) from the second list. // SecondReferencedDomainListLength = SecondEntries * sizeof(LSA_TRUST_INFORMATION); if (SecondReferencedDomainListLength > (ULONG) 0) { if (Options & LSAP_DB_USE_SECOND_MERGAND_GRAPH) { RtlCopyMemory( (*OutputReferencedDomainList)->Domains + FirstReferencedDomainList->Entries, SecondReferencedDomainList->Domains, SecondReferencedDomainListLength ); } else { // // Copy each of the Trust Information entries, allocating // individual memory buffers for each Domain Name and Sid. // for (NextEntry = 0; NextEntry < SecondEntries; NextEntry++) { Status = LsapRpcCopyUnicodeString( &FreeList, (PUNICODE_STRING) &((*OutputReferencedDomainList)->Domains[FirstEntries +NextEntry].Name), (PUNICODE_STRING) &SecondReferencedDomainList->Domains[NextEntry].Name ); if (!NT_SUCCESS(Status)) { break; } Status = LsapRpcCopySid( &FreeList, (PSID) &((*OutputReferencedDomainList)->Domains[FirstEntries +NextEntry].Sid), (PSID) SecondReferencedDomainList->Domains[NextEntry].Sid ); if (!NT_SUCCESS(Status)) { break; } } if (!NT_SUCCESS(Status)) { goto MergeDisjointDomainsError; } } } MergeDisjointDomainsFinish: // // Delete the Free List, freeing buffers on the list if an error // occurred. // LsapMmCleanupFreeList( &FreeList, CleanupFreeListOptions ); return(Status); MergeDisjointDomainsError: // // Delete the output referenced domain list // if (*OutputReferencedDomainList) { MIDL_user_free( *OutputReferencedDomainList ); *OutputReferencedDomainList = NULL; } // // Specify that buffers on Free List are to be freed. // CleanupFreeListOptions |= LSAP_MM_FREE_BUFFERS; goto MergeDisjointDomainsFinish; } NTSTATUS LsapDbLookupDispatchWorkerThreads( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList ) /*++ Routine Description: This function dispatches sufficient worker threads to handle a Lookup operation. The worker threads can handle work items on any Lookup's Work List, so the number of existing active threads is taken into account. Note that the total number of active Lookup Worker Threads may exceed the guide maximum number of threads, in situations where an active thread terminates during the dispatch cycle. This strategy saves having to recheck the active thread count each time a thread is dispatched. NOTE: This routine expects the specified pointer to a Work List to be valid. A Work List pointer always remains valid until its Primary thread detects completion of the Work List via this routine and then deletes it via LsapDbLookupDeleteWorkList(). Arguments: WorkList - Pointer to a Work List describing a Lookup Sid or Lookup Name operation. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; NTSTATUS DispatchThreadStatus = STATUS_SUCCESS; ULONG AdvisoryChildThreadCount; ULONG DispatchThreadCount; ULONG MaximumDispatchChildThreadCount; ULONG ThreadNumber; HANDLE Thread = NULL; DWORD Ignore; BOOLEAN AcquiredWorkQueueLock = FALSE; // // Acquire the Lookup Work Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupDispatchWorkerThreadsError; } AcquiredWorkQueueLock = TRUE; // // Calculate the number of Worker Threads to dispatch (if any). If // the WorkList has an Advisory Child Thread Count of 0, we will // not dispatch any threads, but instead will perform this Lookup // within this thread. If the WorkList has an Advisory Child Thread // Count > 0, we will dispatch additional threads. The number of // additional child threads dispatched is given by the formula: // // ThreadsToDispatch = // min (MaximumChildThreadCount - ActiveChildThreadCount, // AdvisoryChildThreadCount) // AdvisoryChildThreadCount = WorkList->AdvisoryChildThreadCount; if (AdvisoryChildThreadCount > 0) { MaximumDispatchChildThreadCount = LookupWorkQueue.MaximumChildThreadCount - LookupWorkQueue.ActiveChildThreadCount; if (AdvisoryChildThreadCount <= MaximumDispatchChildThreadCount) { DispatchThreadCount = AdvisoryChildThreadCount; } else { DispatchThreadCount = MaximumDispatchChildThreadCount; } // // Release the Lookup Work Queue Lock // LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; // // Signal the event that indicates that a new Work List is initiated. // Status = NtSetEvent( LsapDbLookupStartedEvent, NULL ); if (!NT_SUCCESS(Status)) { LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LsapDbLookupDispatchWorkList... NtSetEvent failed 0x%lx\n",Status)); goto LookupDispatchWorkerThreadsError; } // // Dispatch the computed number of threads. // for (ThreadNumber = 0; ThreadNumber < DispatchThreadCount; ThreadNumber++) { Thread = CreateThread( NULL, 0L, (LPTHREAD_START_ROUTINE) LsapDbLookupWorkerThreadStart, NULL, 0L, &Ignore ); if (Thread == NULL) { Status = GetLastError(); KdPrint(("LsapDbLookupDispatchWorkerThreads: CreateThread failed 0x%lx\n")); break; } else { CloseHandle( Thread ); } } if (!NT_SUCCESS(Status)) { DispatchThreadStatus = Status; } } // // Unlock the queue so this thread doesn't hog it while doing a lookup // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } // // Do some work in the main thread too. // LsapDbLookupWorkerThread( TRUE); LookupDispatchWorkerThreadsFinish: if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupDispatchWorkerThreadsError: goto LookupDispatchWorkerThreadsFinish; } NTSTATUS LsapDbGetCachedHandleTrustedDomain( IN PLSAPR_TRUST_INFORMATION TrustInformation, IN ACCESS_MASK DesiredAccess, IN OUT LPWSTR *ServerName, IN OUT LPWSTR *ServerPrincipalName, IN OUT PVOID *ClientContext, OUT PLSAP_BINDING_CACHE_ENTRY * ControllerPolicyEntry ) /*++ Routine Description: This routine looks for a cached handle to the LSA on a trusted domain. If one is not present, it will open & cache a new handle. The handle is opened for POLICY_LOOKUP_NAMES. N.B. ServerName, ServerPrincipalName, and ClientContext are IN/OUT parameters -- if a new handle is created, the memory is transferred to the cache and so the values are NULL'ed on return. If a value is found in the cache then the values are also freed (and NULL'ed), so that the interface is consistent (on success, *ServerName, *ServerPrincipalName, *ClientContext are freed). Arguments: TrustInformation - Specifies the Sid and/or Name of the Trusted Domain whose Policy database is to be opened. DesiredAccess -- if a new handle is required, what to ask for ServerName -- in, out; if a new handle is required the server to get one from ServerPrincipalName -- in, out; if a new handle is required this is used to authenticate. ClientContext -- in, out; if a new handle is required, this is used to authenticate. ControllerPolicyEntry - Receives binding cache entry to the LSA Policy Object for the Lsa Policy database located on some DC for the specified Trusted Domain. Return Value: NTSTATUS - Standard Nt Result Code STATUS_SUCCESS - The call completed successfully. STATUS_NO_MORE_ENTRIES - The DC list for the specified domain is null. Result codes from called routines. --*/ { NTSTATUS Status = STATUS_SUCCESS; PUNICODE_STRING DomainName = NULL; PLSAPR_TRUST_INFORMATION OutputTrustInformation = NULL; LSA_HANDLE PolicyHandle = NULL; PLSAP_BINDING_CACHE_ENTRY CacheEntry = NULL; UNICODE_STRING DomainControllerName; *ControllerPolicyEntry = NULL; // // If the caller didn't provide a domain name, look it up now // if ((TrustInformation->Name.Length == 0) || (TrustInformation->Name.Buffer == NULL)) { Status = STATUS_INVALID_PARAMETER; if (TrustInformation->Sid == NULL) { goto Cleanup; } Status = LsapDbLookupSidTrustedDomainList( TrustInformation->Sid, &OutputTrustInformation ); if (!NT_SUCCESS(Status)) { goto Cleanup; } DomainName = (PUNICODE_STRING) &OutputTrustInformation->Name; TrustInformation = OutputTrustInformation; } else { DomainName = (PUNICODE_STRING) &TrustInformation->Name; } // // Look in the cache for a binding handle // CacheEntry = LsapLocateBindingCacheEntry( DomainName, FALSE // don't remove ); if (CacheEntry != NULL) { // // Validate the handle to make sure the DC is still there. // Status = LsapRtlValidateControllerTrustedDomainByHandle( CacheEntry->PolicyHandle, TrustInformation ); if (!NT_SUCCESS(Status)) { LsapReferenceBindingCacheEntry( CacheEntry, TRUE // unlink ); LsapDereferenceBindingCacheEntry( CacheEntry ); LsapDereferenceBindingCacheEntry( CacheEntry ); CacheEntry = NULL; } else { *ControllerPolicyEntry = CacheEntry; goto Cleanup; } } // // There was nothing in the cache, so open a new handle // RtlInitUnicodeString(&DomainControllerName, *ServerName); Status = LsapRtlValidateControllerTrustedDomain( (PLSAPR_UNICODE_STRING)&DomainControllerName, TrustInformation, POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION, *ServerPrincipalName, *ClientContext, &PolicyHandle ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Create a binding cache entry from the handle // // // Note: this routine sets ServerName, ServerPrincipalName and // ClientContext to NULL on success. // Status = LsapCacheBinding( DomainName, &PolicyHandle, ServerName, ServerPrincipalName, ClientContext, ControllerPolicyEntry ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Cleanup: if (PolicyHandle != NULL) { LsaClose(PolicyHandle); } if (NT_SUCCESS(Status)) { // // On success, always free the IN/OUT parameters to provide // a consistent interface // if (*ServerName) { LocalFree(*ServerName); *ServerName = NULL; } if (*ServerPrincipalName) { I_NetLogonFree(*ServerPrincipalName); *ServerPrincipalName = NULL; } if (*ClientContext) { I_NetLogonFree(*ClientContext); *ClientContext = NULL; } // // We should have a cache entry to return // ASSERT(NULL != *ControllerPolicyEntry); } return(Status); } NTSTATUS LsapRtlValidateControllerTrustedDomain( IN PLSAPR_UNICODE_STRING DomainControllerName, IN PLSAPR_TRUST_INFORMATION TrustInformation, IN ACCESS_MASK OriginalDesiredAccess, IN LPWSTR ServerPrincipalName, IN PVOID ClientContext, OUT PLSA_HANDLE ControllerPolicyHandle ) /*++ Routine Description: This function verifies that a specified computer is a DC for a specified Domain and opens the LSA Policy Object with the desired accesses. Arguments: DomainControllerName - Pointer to Unicode String computer name. TrustInformation - Domain Trust Information. Only the Sid is used. OriginalDesiredAccess - Specifies the accesses desired to the target machine's Lsa Policy Database. ServerPrincipalName - RPC server principal name. ClientContext - RPC client context information. PolicyHandle - Receives handle to the Lsa Policy object on the target machine. Return Values: NTSTATUS - Standard Nt Result Code STATUS_OBJECT_NAME_NOT_FOUND - The specified computer is not a Domain Controller for the specified Domain. --*/ { NTSTATUS Status = STATUS_SUCCESS; NTSTATUS Status2 = STATUS_SUCCESS; NTSTATUS SecondaryStatus = STATUS_SUCCESS; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; OBJECT_ATTRIBUTES ObjectAttributes; LSA_HANDLE OutputControllerPolicyHandle = NULL; ACCESS_MASK DesiredAccess; // // Open a handle to the Policy Object on the target DC. // SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; // // Set up the object attributes prior to opening the LSA. // InitializeObjectAttributes( &ObjectAttributes, NULL, 0L, NULL, NULL ); // // The InitializeObjectAttributes macro presently stores NULL for // the SecurityQualityOfService field, so we must manually copy that // structure for now. // ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; // // For authenticated clients, the server adds in the POLICY_LOOKUP_NAMES // access // Retry: if (ClientContext != NULL) { DesiredAccess = OriginalDesiredAccess & ~POLICY_LOOKUP_NAMES; } else { DesiredAccess = OriginalDesiredAccess; } Status = LsaOpenPolicy( (PUNICODE_STRING) DomainControllerName, &ObjectAttributes, DesiredAccess, &OutputControllerPolicyHandle ); if (!NT_SUCCESS(Status)) { goto ValidateControllerTrustedDomainError; } if (ClientContext != NULL) { ULONG RpcErr; RPC_BINDING_HANDLE RpcBindingHandle; // // Setup the the RPC authenticated channel // RpcErr = RpcSsGetContextBinding( OutputControllerPolicyHandle, &RpcBindingHandle ); // // Note: the handle returned by RpcSsGetContextBinding is valid for the lifetime // of OutputControllerPolicyHandle; it does not need to be closed. // Status = I_RpcMapWin32Status(RpcErr); if (!NT_SUCCESS(Status)) { goto ValidateControllerTrustedDomainError; } RpcErr = RpcBindingSetAuthInfo( RpcBindingHandle, ServerPrincipalName, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_NETLOGON, ClientContext, RPC_C_AUTHZ_NONE ); Status = I_RpcMapWin32Status(RpcErr); if (!NT_SUCCESS(Status)) { goto ValidateControllerTrustedDomainError; } // // Perform a validation on the handle to make sure the new auth info // is supported on the called server. // // N.B. The locator will always return a valid DC so this check is not // to validate that the server is a DC, rather it is used to validate // that the connection we've created is valid. This check is only // necessary when setting up secure RPC. // Status = LsapRtlValidateControllerTrustedDomainByHandle( OutputControllerPolicyHandle, TrustInformation ); if (!NT_SUCCESS(Status)) { // // If the server didn't recognize the authn service, try again // without authenticated RPC // if ((Status == RPC_NT_UNKNOWN_AUTHN_SERVICE) || (Status == STATUS_ACCESS_DENIED)) { Status = STATUS_SUCCESS; LsaClose( OutputControllerPolicyHandle ); OutputControllerPolicyHandle = NULL; ClientContext = NULL; goto Retry; } goto ValidateControllerTrustedDomainError; } } Status = STATUS_SUCCESS; ValidateControllerTrustedDomainFinish: // // Return Controller Policy handle or NULL. // *ControllerPolicyHandle = OutputControllerPolicyHandle; return(Status); ValidateControllerTrustedDomainError: // // Close the last Controller's Policy Handle. // if (OutputControllerPolicyHandle != NULL) { SecondaryStatus = LsaClose( OutputControllerPolicyHandle ); OutputControllerPolicyHandle = NULL; } goto ValidateControllerTrustedDomainFinish; } NTSTATUS LsapRtlValidateControllerTrustedDomainByHandle( IN LSA_HANDLE DcPolicyHandle, IN PLSAPR_TRUST_INFORMATION TrustInformation ) /*++ Routine Description: This function validates that the given policy handle refers to a valid domain controller Arguments: DcPolicyHandle - Handle to the policy on the machine to verify TrustInformation - Information regarding this Dc's domain Return Value: STATUS_SUCCESS - Success Otherwise, the handle isn't valid --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSAPR_POLICY_ACCOUNT_DOM_INFO PolicyAccountDomainInfo = NULL; Status = LsaQueryInformationPolicy( DcPolicyHandle, PolicyAccountDomainInformation, ( PVOID * )&PolicyAccountDomainInfo ); // // Now compare the Domain Name and Sid stored for the Controller's // Account Domain with the Domain Name and Sid provided. // PolicyAccountDomainInfo->DomainName is always a NetBIOS name, hence // the use of RtlEqualDomainName for comparison. // if ( NT_SUCCESS( Status ) ) { if ( !RtlEqualDomainName( (PUNICODE_STRING) &TrustInformation->Name, (PUNICODE_STRING) &PolicyAccountDomainInfo->DomainName ) ) { Status = STATUS_OBJECT_NAME_NOT_FOUND; } else if ( TrustInformation->Sid && !RtlEqualSid( ( PSID )TrustInformation->Sid, ( PSID ) PolicyAccountDomainInfo->DomainSid ) ) { Status = STATUS_OBJECT_NAME_NOT_FOUND; } } // // Free up the Policy Account Domain Info. // if ( PolicyAccountDomainInfo != NULL ) { LsaFreeMemory( (PVOID) PolicyAccountDomainInfo ); } return(Status); } NTSTATUS LsapDbLookupAcquireWorkQueueLock( ) /*++ Routine Description: This function acquires the LSA Database Lookup Sids/Names Work Queue Lock. This lock serializes additions or deletions of work lists to/from the queue, and sign-in/sign-out of work items by worker threads. Arguments: None. Return Value: NTSTATUS - Standard Nt Result Code. --*/ { NTSTATUS Status; Status = SafeEnterCriticalSection(&LookupWorkQueue.Lock); return(Status); } VOID LsapDbLookupReleaseWorkQueueLock( ) /*++ Routine Description: This function releases the LSA Database Lookup Sids/Names Work Queue Lock. Arguments: None. Return Value: None. Any error occurring within this routine is an internal error. --*/ { SafeLeaveCriticalSection(&LookupWorkQueue.Lock); } NTSTATUS LsapDbLookupNamesBuildWorkList( IN ULONG LookupOptions, IN ULONG Count, IN BOOLEAN fIncludeIntraforest, IN PLSAPR_UNICODE_STRING Names, IN PLSAPR_UNICODE_STRING PrefixNames, IN PLSAPR_UNICODE_STRING SuffixNames, IN PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains, IN OUT PLSAPR_TRANSLATED_SIDS_EX2 TranslatedSids, IN LSAP_LOOKUP_LEVEL LookupLevel, IN OUT PULONG MappedCount, IN OUT PULONG CompletelyUnmappedCount, OUT PLSAP_DB_LOOKUP_WORK_LIST *WorkList ) /*++ Routine Description: This function constructs a Work List for an LsarLookupNames call. The Work List contains the parameters of the call, and an array of Work Items. Each Work Item specifies either all of the Names to be looked up in a given domain. Qualified names (i.e. those of the form DomainName\UserName) are sorted into different Work Items, one for each DomainName specifed. Unqualified names (i.e. those of the form UserName) are added to every Work Item. Arguments: LookupOptions - LSA_LOOKUP_ISOLATED_AS_LOCAL Count - Specifies the number of names to be translated. fIncludeIntraforest -- if TRUE, trusted domains in our local forest are searched. Names - Pointer to an array of Count Unicode String structures specifying the names to be looked up and mapped to Sids. The strings may be names of User, Group or Alias accounts or domains. PrefixNames - Pointer to an array of Count Unicode String structures containing the Prefix portions of the Names. Names having no Prefix are called Isolated Names. For these, the Unicode String structure is set to contain a zero Length. SuffixNames - Pointer to an array of Count Unicode String structures containing the Suffix portions of the Names. ReferencedDomains - receives a pointer to a structure describing the domains used for the translation. The entries in this structure are referenced by the structure returned via the Sids parameter. Unlike the Sids parameter, which contains an array entry for each translated name, this structure will only contain one component for each domain utilized in the translation. When this information is no longer needed, it must be released by passing the returned pointer to LsaFreeMemory(). TranslatedSids - Pointer to a structure which will (or already) references an array of records describing each translated Sid. The nth entry in this array provides a translation for the nth element in the Names parameter. When this information is no longer needed, it must be released by passing the returned pointer to LsaFreeMemory(). LookupLevel - Specifies the Level of Lookup to be performed on this machine. Values of this field are are follows: LsapLookupWksta - First Level Lookup performed on a workstation normally configured for Windows-Nt. The lookup searches the Well-Known Sids/Names, and the Built-in Domain and Account Domain in the local SAM Database. If not all Sids or Names are identified, performs a "handoff" of a Second level Lookup to the LSA running on a Controller for the workstation's Primary Domain (if any). LsapLookupPDC - Second Level Lookup performed on a Primary Domain Controller. The lookup searches the Account Domain of the SAM Database on the controller. If not all Sids or Names are found, the Trusted Domain List (TDL) is obtained from the LSA's Policy Database and Third Level lookups are performed via "handoff" to each Trusted Domain in the List. LsapLookupTDL - Third Level Lookup performed on a controller for a Trusted Domain. The lookup searches the Account Domain of the SAM Database on the controller only. MappedCount - Pointer to location that contains a count of the Names mapped so far. On exit, this will be updated. MappedCount - Pointer to location containing the number of Names in the Names array that have already been mapped. This number will be updated to reflect additional mapping done by this routine. CompletelyUnmappedCount - Pointer to location containing the count of completely unmapped Names. A Name is completely unmapped if it is unknown and non-composite, or composite but with an unrecognized Domain component. This count is updated on exit, the number of completely unmapped Namess whose Domain Prefices are identified by this routine being subtracted from the input value. WorkList - Receives pointer to completed Work List if successfully built. Return Value: NTSTATUS - Standard Nt Result Code STATUS_NONE_MAPPED - All of the Names specified were composite, but none of their Domains appear in the Trusted Domain List. No Work List has been generated. Note that this is not a fatal error. --*/ { NTSTATUS Status = STATUS_SUCCESS, IgnoreStatus = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_LIST OutputWorkList = NULL; ULONG NameIndex; PLSAP_DB_LOOKUP_WORK_ITEM AnchorWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NextWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM WorkItemToUpdate = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NewWorkItem = NULL; PLSAPR_TRUST_INFORMATION TrustInformation = NULL; LONG DomainIndex = 0; PLSAP_DB_LOOKUP_WORK_ITEM IsolatedNamesWorkItem = NULL; BOOLEAN AcquiredTrustedDomainListReadLock = FALSE; PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY TrustedDomainEntry = NULL; LSAPR_UNICODE_STRING DomainNameBuffer; PLSAPR_UNICODE_STRING DomainName = &DomainNameBuffer; LSAPR_UNICODE_STRING TerminalNameBuffer; PLSAPR_UNICODE_STRING TerminalName = &TerminalNameBuffer; PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY TrustEntry = NULL; LSAPR_TRUST_INFORMATION TrustInfo; LPWSTR ClientNetworkAddress = NULL; // // Get the client's address for logging purposes // ClientNetworkAddress = LsapGetClientNetworkAddress(); // // Create an empty Work List. // Status = LsapDbLookupCreateWorkList(&OutputWorkList); if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } // // Initialize the Work List Header. // OutputWorkList->Status = STATUS_SUCCESS; OutputWorkList->State = InactiveWorkList; OutputWorkList->LookupType = LookupNames; OutputWorkList->Count = Count; OutputWorkList->LookupLevel = LookupLevel; OutputWorkList->ReferencedDomains = ReferencedDomains; OutputWorkList->MappedCount = MappedCount; OutputWorkList->CompletelyUnmappedCount = CompletelyUnmappedCount; OutputWorkList->LookupNamesParams.Names = Names; OutputWorkList->LookupNamesParams.TranslatedSids = TranslatedSids; // // Construct the array of Work Items. Each Work Item will // contain all the Names for a given domain, so we will scan // all of the Names, sorting them into Work Items as we go. // For each Name, follow the steps detailed below. // for (NameIndex = 0; NameIndex < Count; NameIndex++) { // // If this Name's Domain is already marked as known, skip. // if (TranslatedSids->Sids[NameIndex].DomainIndex != LSA_UNKNOWN_INDEX) { continue; } // // Name is completely unknown. See if there is already a Work Item // for its Domain. // AnchorWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) OutputWorkList->AnchorWorkItem; NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) AnchorWorkItem->Links.Flink; WorkItemToUpdate = NULL; NewWorkItem = NULL; // // If this is a qualified name (e.g. "NtDev\ScottBi") proceed // to search for its Domain. // // Also, crack names of the format user@dns.domain.name // LsapLookupCrackName((PUNICODE_STRING)&PrefixNames[ NameIndex ], (PUNICODE_STRING)&SuffixNames[ NameIndex ], (PUNICODE_STRING)TerminalName, (PUNICODE_STRING)DomainName); if (DomainName->Length != 0) { while (NextWorkItem != AnchorWorkItem) { if (LsapCompareDomainNames( (PUNICODE_STRING)&NextWorkItem->TrustInformation.Name, (PUNICODE_STRING) DomainName, NULL) ) { // // A Work Item already exists for the Name's Trusted Domain. // Select that Work Item for update. // WorkItemToUpdate = NextWorkItem; break; } // // Name's domain not found among existing Work Items. Skip to // next Work Item. // NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) NextWorkItem->Links.Flink; } if (WorkItemToUpdate == NULL) { // // No Work Item exists for the Name's Domain. See if the // Name belongs to one of the Trusted Domains. If not, skip // to the next Name. // ULONG Flags = LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_EXTERNAL; if (fIncludeIntraforest) { Flags |= LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_INTRA; } Status = LsapDomainHasDomainTrust(Flags, (PUNICODE_STRING)DomainName, NULL, &AcquiredTrustedDomainListReadLock, &TrustEntry); if (!NT_SUCCESS(Status)) { if (Status == STATUS_NO_SUCH_DOMAIN) { // // No Match! // Status = STATUS_SUCCESS; continue; } break; } RtlZeroMemory( &TrustInfo, sizeof(TrustInfo) ); TrustInfo.Name = TrustEntry->TrustInfoEx.FlatName; TrustInfo.Sid = TrustEntry->TrustInfoEx.Sid; TrustInformation = &TrustInfo; // // Name belongs to a Trusted Domain for which there is // no Work Item. Add the Domain to the Referenced Domain List // and obtain a Domain Index. // Status = LsapDbLookupAddListReferencedDomains( ReferencedDomains, TrustInformation, &DomainIndex ); if (!NT_SUCCESS(Status)) { break; } // // Create a new Work Item for this domain. // Status = LsapDbLookupCreateWorkItem( TrustInformation, DomainIndex, (ULONG) LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY + (ULONG) 1, &NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } // // Add the Work Item to the List. // Status = LsapDbAddWorkItemToWorkList( OutputWorkList, NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } WorkItemToUpdate = NewWorkItem; } // // Add the Name Index to the Work Item. // Status = LsapDbLookupAddIndicesToWorkItem( WorkItemToUpdate, (ULONG) 1, &NameIndex ); if (!NT_SUCCESS(Status)) { break; } // // Store the Domain Index in the Translated Sids array entry for // the Name. // OutputWorkList->LookupNamesParams.TranslatedSids->Sids[NameIndex].DomainIndex = WorkItemToUpdate->DomainIndex; if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } } else { // // This is an Isolated Name. We know that it is not the name // of any Domain on the lookup path, because all of these // have been translated earlier. We will add the name to a // temporary work item and later transfer all the Isolated Names // to every Work Item on the Work List. If we don't already // have a temporary Work Item for the Isolated Names, create one. // // // N.B. Only build this list if we want to perform isolated // name lookup across the directly trusted forest boundary. // if ( (LookupOptions & LSA_LOOKUP_ISOLATED_AS_LOCAL) == 0 && !LsapLookupRestrictIsolatedNameLevel) { UNICODE_STRING Items[2]; // // Event the fact we are looking up this isolated name // Items[0] = *(PUNICODE_STRING)TerminalName; RtlInitUnicodeString(&Items[1], ClientNetworkAddress); LsapTraceEventWithData(EVENT_TRACE_TYPE_INFO, LsaTraceEvent_LookupIsolatedNameInTrustedDomains, 2, Items); if (IsolatedNamesWorkItem == NULL) { // // Create a new Work Item for the Isolated Names. // This is temporary. // Status = LsapDbLookupCreateWorkItem( NULL, DomainIndex, (ULONG) LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY + (ULONG) 1, &IsolatedNamesWorkItem ); if (!NT_SUCCESS(Status)) { break; } } // // Mark this Work Item as having Isolated Names only. // IsolatedNamesWorkItem->Properties = LSAP_DB_LOOKUP_WORK_ITEM_ISOL; // // Add the Name index to the Isolated Names Work Item // Status = LsapDbLookupAddIndicesToWorkItem( IsolatedNamesWorkItem, (ULONG) 1, &NameIndex ); if (!NT_SUCCESS(Status)) { break; } } } } if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } // // If we have any unmapped Isolated Names, we know at this stage that they are // not the Names of Trusted Domains. Therefore, we need to arrange // for them to be looked up in every Trusted Domain. We need to // traverse the list of Trusted Domains and for each Domain, either // create a new Work Item to lookup all of the Names in that Domain, // or add the Names to the existing Work Item generated for that // Domain (because there are some Qualified Names which reference // the Domain). We do this Work Item Generation in 2 stages. First, // we will scan the Work List, adding the Isolated Names to every Work // Item found therein. Second, we will create a Work Item for each // of the Trusted Domains that we don't already have a Work Item. // // // Stage (1) - Scan the Work List, adding the Isolated Names to // every Work Item found therein // if (IsolatedNamesWorkItem != NULL) { // // Stage (1) - Scan the Work List, adding the Isolated Names to // every Work Item found therein // NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) OutputWorkList->AnchorWorkItem->Links.Flink; while (NextWorkItem != OutputWorkList->AnchorWorkItem) { // // Add the Isolated Name indices to this Work Item // Status = LsapDbLookupAddIndicesToWorkItem( NextWorkItem, IsolatedNamesWorkItem->UsedCount, IsolatedNamesWorkItem->Indices ); if (!NT_SUCCESS(Status)) { break; } NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) NextWorkItem->Links.Flink; } if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } // // Stage (2) - Now create Work Items to look up all the Isolated Names // in every Trusted Domain that does not presently have a Work Item. // The Domains for all existing Work Items are already present // on the Referenced Domains List because they all specify at least one // Qualified Name, so we can lookup that list to determine whether a // Work Item exists for a Domain. // for (;;) { // // Grab the lock on the TrustedDomainList. // if (!AcquiredTrustedDomainListReadLock) { Status = LsapDbAcquireReadLockTrustedDomainList(); if (!NT_SUCCESS(Status)) { break; } AcquiredTrustedDomainListReadLock = TRUE; } NewWorkItem = NULL; Status = LsapDbTraverseTrustedDomainList( &TrustedDomainEntry, &TrustInformation ); if (!NT_SUCCESS(Status)) { if (Status == STATUS_NO_MORE_ENTRIES) { Status = STATUS_SUCCESS; } break; } // // Use only trusts to trusted domains // if (!LsapOutboundTrustedDomain(TrustedDomainEntry)) { continue; } // // Found Next Trusted Domain. Check if this domain is already // present on the Referenced Domain List. If so, skip to the // next domain, because we have a Work Item for this domain. // // If the Domain is not present on the Referenced Domain List, // we need to create a Work Item for this Domain and add all // of the unmapped Isolated Names indices to it. // if (LsapDbLookupListReferencedDomains( ReferencedDomains, TrustInformation->Sid, &DomainIndex )) { continue; } // // We don't have a Work Item for this Trusted Domain. Create // one, and add all of the remaining Isolated Names to it. // Mark the Domain Index as unknown. This is picked up // later when the Work Item is processed. If any Names // were translated, the Domain to which the Work Item // relates will be added to the Referenced Domain List. // Status = LsapDbLookupCreateWorkItem( TrustInformation, LSA_UNKNOWN_INDEX, (ULONG) LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY + (ULONG) 1, &NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } // // Mark this Work Item as having Isolated Names only. // NewWorkItem->Properties = LSAP_DB_LOOKUP_WORK_ITEM_ISOL; // // Add the Isolated Name indices to this Work Item // Status = LsapDbLookupAddIndicesToWorkItem( NewWorkItem, IsolatedNamesWorkItem->UsedCount, IsolatedNamesWorkItem->Indices ); if (!NT_SUCCESS(Status)) { break; } // // Add the Work Item to the List. // Status = LsapDbAddWorkItemToWorkList( OutputWorkList, NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } } if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } } // // If the Work List has no Work Items, this means that all of the // Names were composite, but none of the Domain Names specified // could be found on the Trusted Domain List. In this case, // we discard the Work List. // Status = STATUS_NONE_MAPPED; if (OutputWorkList->WorkItemCount == 0) { goto LookupNamesBuildWorkListError; } // // Compute the Advisory Thread Count for this lookup. // Status = LsapDbLookupComputeAdvisoryChildThreadCount( OutputWorkList ); if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } Status = LsapDbLookupInsertWorkList(OutputWorkList); if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } // // Update the Mapped Counts // LsapDbUpdateMappedCountsWorkList( OutputWorkList ); LookupNamesBuildWorkListFinish: // // If necessary, release the Trusted Domain List Read Lock. // if (AcquiredTrustedDomainListReadLock) { LsapDbReleaseLockTrustedDomainList(); AcquiredTrustedDomainListReadLock = FALSE; } // // If we have an Isolated Names Work Item, free it. // if (IsolatedNamesWorkItem != NULL) { MIDL_user_free( IsolatedNamesWorkItem->Indices); IsolatedNamesWorkItem->Indices = NULL; MIDL_user_free( IsolatedNamesWorkItem ); IsolatedNamesWorkItem = NULL; } if (ClientNetworkAddress) { RpcStringFreeW(&ClientNetworkAddress); } *WorkList = OutputWorkList; return(Status); LookupNamesBuildWorkListError: // // Discard the Work List. // if (OutputWorkList != NULL) { IgnoreStatus = LsapDbLookupDeleteWorkList(OutputWorkList); OutputWorkList = NULL; } goto LookupNamesBuildWorkListFinish; } NTSTATUS LsapDbLookupXForestNamesBuildWorkList( IN ULONG Count, IN PLSAPR_UNICODE_STRING Names, IN PLSAPR_UNICODE_STRING PrefixNames, IN PLSAPR_UNICODE_STRING SuffixNames, IN PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains, IN OUT PLSAPR_TRANSLATED_SIDS_EX2 TranslatedSids, IN LSAP_LOOKUP_LEVEL LookupLevel, IN OUT PULONG MappedCount, IN OUT PULONG CompletelyUnmappedCount, OUT PLSAP_DB_LOOKUP_WORK_LIST *WorkList ) /*++ Routine Description: This function constructs a Work List for an LsarLookupNames call. The Work List contains the parameters of the call, and an array of Work Items. Each Work Item specifies either all of the Names to be looked up in a given forest. N.B. The trust information of the WorkList is the DNS name of the target forest, not the DNS name of domain (we don't know what domain the item belongs to yet). Arguments: Count - Specifies the number of names to be translated. Names - Pointer to an array of Count Unicode String structures specifying the names to be looked up and mapped to Sids. The strings may be names of User, Group or Alias accounts or domains. PrefixNames - Pointer to an array of Count Unicode String structures containing the Prefix portions of the Names. Names having no Prefix are called Isolated Names. For these, the Unicode String structure is set to contain a zero Length. SuffixNames - Pointer to an array of Count Unicode String structures containing the Suffix portions of the Names. ReferencedDomains - receives a pointer to a structure describing the domains used for the translation. The entries in this structure are referenced by the structure returned via the Sids parameter. Unlike the Sids parameter, which contains an array entry for each translated name, this structure will only contain one component for each domain utilized in the translation. When this information is no longer needed, it must be released by passing the returned pointer to LsaFreeMemory(). TranslatedSids - Pointer to a structure which will (or already) references an array of records describing each translated Sid. The nth entry in this array provides a translation for the nth element in the Names parameter. When this information is no longer needed, it must be released by passing the returned pointer to LsaFreeMemory(). LookupLevel - Specifies the Level of Lookup to be performed on this machine. Values of this field are are follows: MappedCount - Pointer to location containing the number of Names in the Names array that have already been mapped. This number will be updated to reflect additional mapping done by this routine. CompletelyUnmappedCount - Pointer to location containing the count of completely unmapped Names. A Name is completely unmapped if it is unknown and non-composite, or composite but with an unrecognized Domain component. This count is updated on exit, the number of completely unmapped Namess whose Domain Prefices are identified by this routine being subtracted from the input value. WorkList - Receives pointer to completed Work List if successfully built. Return Value: NTSTATUS - Standard Nt Result Code STATUS_NONE_MAPPED - All of the Names specified were composite, but none of their Domains appear in the Trusted Domain List. No Work List has been generated. Note that this is not a fatal error. --*/ { NTSTATUS Status = STATUS_SUCCESS, IgnoreStatus = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_LIST OutputWorkList = NULL; ULONG NameIndex; PLSAP_DB_LOOKUP_WORK_ITEM AnchorWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NextWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM WorkItemToUpdate = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NewWorkItem = NULL; PLSAPR_TRUST_INFORMATION TrustInformation = NULL; LONG DomainIndex = 0; PLSAPR_UNICODE_STRING DomainName = NULL; PLSAPR_UNICODE_STRING TerminalName = NULL; PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY TrustEntry = NULL; LSAPR_TRUST_INFORMATION TrustInfo; UNICODE_STRING XForestName = {0, 0, NULL}; // // Create an empty Work List. // Status = LsapDbLookupCreateWorkList(&OutputWorkList); if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } // // Initialize the Work List Header. // OutputWorkList->Status = STATUS_SUCCESS; OutputWorkList->State = InactiveWorkList; OutputWorkList->LookupType = LookupNames; OutputWorkList->Count = Count; OutputWorkList->LookupLevel = LookupLevel; OutputWorkList->ReferencedDomains = ReferencedDomains; OutputWorkList->MappedCount = MappedCount; OutputWorkList->CompletelyUnmappedCount = CompletelyUnmappedCount; OutputWorkList->LookupNamesParams.Names = Names; OutputWorkList->LookupNamesParams.TranslatedSids = TranslatedSids; // // Construct the array of Work Items. Each Work Item will // contain all the Names for a given domain, so we will scan // all of the Names, sorting them into Work Items as we go. // For each Name, follow the steps detailed below. // for (NameIndex = 0; NameIndex < Count; NameIndex++) { // // Name is completely unknown. See if there is already a Work Item // for its Forest. // AnchorWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) OutputWorkList->AnchorWorkItem; NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) AnchorWorkItem->Links.Flink; WorkItemToUpdate = NULL; NewWorkItem = NULL; // // If this is a qualified name match by domain name // DomainName = &PrefixNames[ NameIndex ]; TerminalName = &SuffixNames[ NameIndex ]; if (DomainName->Length == 0) { // // This is a UPN -- get the XForest trust if any // Status = LsaIForestTrustFindMatch(RoutingMatchUpn, (PLSA_UNICODE_STRING)TerminalName, &XForestName); if (!NT_SUCCESS(Status)) { // // Perhaps this is an islolated domain name // Status = LsaIForestTrustFindMatch(RoutingMatchDomainName, (PLSA_UNICODE_STRING)TerminalName, &XForestName); } if (!NT_SUCCESS(Status)) { // // Can't find match? Continue // Status = STATUS_SUCCESS; continue; } } else { // // The name has a domain portion -- get the XForest trust if any // Status = LsaIForestTrustFindMatch(RoutingMatchDomainName, (PLSA_UNICODE_STRING)DomainName, &XForestName); if (!NT_SUCCESS(Status)) { // // Can't find match? Continue // Status = STATUS_SUCCESS; continue; } } // // We must have found a match // ASSERT(XForestName.Length > 0); // // See if any entry already exists for us // while (NextWorkItem != AnchorWorkItem) { if (LsapCompareDomainNames( (PUNICODE_STRING) &NextWorkItem->TrustInformation.Name, (PUNICODE_STRING) &XForestName, NULL) ) { // // A Work Item already exists for the Name's Trusted Domain. // Select that Work Item for update. // WorkItemToUpdate = NextWorkItem; break; } // // Name's domain not found among existing Work Items. Skip to // next Work Item. // NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) NextWorkItem->Links.Flink; } if (WorkItemToUpdate == NULL) { RtlZeroMemory( &TrustInfo, sizeof(TrustInfo) ); TrustInfo.Name.Length = XForestName.Length; TrustInfo.Name.MaximumLength = XForestName.MaximumLength; TrustInfo.Name.Buffer = XForestName.Buffer; TrustInfo.Sid = NULL; TrustInformation = &TrustInfo; // // Create a new Work Item for this domain. // Status = LsapDbLookupCreateWorkItem( TrustInformation, LSA_UNKNOWN_INDEX, (ULONG) LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY + (ULONG) 1, &NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } // // Add the Work Item to the List. // Status = LsapDbAddWorkItemToWorkList( OutputWorkList, NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } WorkItemToUpdate = NewWorkItem; // // Mark the item as a xforest item // NewWorkItem->Properties |= LSAP_DB_LOOKUP_WORK_ITEM_XFOREST; } LsaIFree_LSAPR_UNICODE_STRING_BUFFER( (LSAPR_UNICODE_STRING*)&XForestName); XForestName.Buffer = NULL; if (!NT_SUCCESS(Status)) { break; } // // Add the Name Index to the Work Item. // Status = LsapDbLookupAddIndicesToWorkItem( WorkItemToUpdate, (ULONG) 1, &NameIndex ); if (!NT_SUCCESS(Status)) { break; } } if (XForestName.Buffer) { LsaIFree_LSAPR_UNICODE_STRING_BUFFER( (LSAPR_UNICODE_STRING*)&XForestName); XForestName.Buffer = NULL; } if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } // // If the Work List has no Work Items, bail // if (OutputWorkList->WorkItemCount == 0) { Status = STATUS_NONE_MAPPED; goto LookupNamesBuildWorkListError; } // // Compute the Advisory Thread Count for this lookup. // Status = LsapDbLookupComputeAdvisoryChildThreadCount( OutputWorkList ); if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } Status = LsapDbLookupInsertWorkList(OutputWorkList); if (!NT_SUCCESS(Status)) { goto LookupNamesBuildWorkListError; } // // Update the Mapped Counts // LsapDbUpdateMappedCountsWorkList( OutputWorkList ); LookupNamesBuildWorkListFinish: *WorkList = OutputWorkList; return(Status); LookupNamesBuildWorkListError: // // Discard the Work List. // if (OutputWorkList != NULL) { IgnoreStatus = LsapDbLookupDeleteWorkList(OutputWorkList); OutputWorkList = NULL; } goto LookupNamesBuildWorkListFinish; } NTSTATUS LsapDbLookupSidsBuildWorkList( IN ULONG Count, IN PLSAPR_SID *Sids, IN BOOLEAN fIncludeIntraforest, IN PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains, IN PLSAPR_TRANSLATED_NAMES_EX TranslatedNames, IN LSAP_LOOKUP_LEVEL LookupLevel, IN OUT PULONG MappedCount, IN OUT PULONG CompletelyUnmappedCount, OUT PLSAP_DB_LOOKUP_WORK_LIST *WorkList ) /*++ Routine Description: This function constructs a Work List for an LsarLookupSids call. The Work List contains the parameters of the call, and an array of Work Items. Each Work Item specifies all of the Sids belonging to a given Domain and is the minimal unit of work that a worker thread will undertake. Count - Number of Sids in the Sids array, Note that some of these may already have been mapped elsewhere, as specified by the MappedCount parameter. Sids - Pointer to array of pointers to Sids to be translated. Zero or all of the Sids may already have been translated elsewhere. If any of the Sids have been translated, the Names parameter will point to a location containing a non-NULL array of Name translation structures corresponding to the Sids. If the nth Sid has been translated, the nth name translation structure will contain either a non-NULL name or a non-negative offset into the Referenced Domain List. If the nth Sid has not yet been translated, the nth name translation structure will contain a zero-length name string and a negative value for the Referenced Domain List index. fIncludeIntraforest -- if TRUE, trusted domains in our local forest are searched. TrustInformation - Pointer to Trust Information specifying a Domain Sid and Name. ReferencedDomains - Pointer to a Referenced Domain List structure. The structure references an array of zero or more Trust Information entries, one per referenced domain. This array will be appended to or reallocated if necessary. TranslatedNames - Pointer to structure that optionally references a list of name translations for some of the Sids in the Sids array. LookupLevel - Specifies the Level of Lookup to be performed on this machine. Values of this field are are follows: LsapLookupPDC - Second Level Lookup performed on a Primary Domain Controller. The lookup searches the Account Domain of the SAM Database on the controller. If not all Sids or Names are found, the Trusted Domain List (TDL) is obtained from the LSA's Policy Database and Third Level lookups are performed via "handoff" to each Trusted Domain in the List. LsapLookupTDL - Third Level Lookup performed on a controller for a Trusted Domain. The lookup searches the Account Domain of the SAM Database on the controller only. NOTE: LsapLookupWksta is not valid for this parameter. MappedCount - Pointer to location containing the number of Sids in the Sids array that have already been mapped. This number will be updated to reflect additional mapping done by this routine. CompletelyUnmappedCount - Pointer to location containing the count of completely unmapped Sids. A Sid is completely unmapped if it is unknown and also its Domain Prefix Sid is not recognized. This count is updated on exit, the number of completely unmapped Sids whose Domain Prefices are identified by this routine being subtracted from the input value. WorkList - Receives pointer to completed Work List if successfully built. Return Values: NTSTATUS - Standard Nt Result Code. STATUS_NONE_MAPPED - None of the Sids specified belong to any of the Trusted Domains. No Work List has been generated. Note that this is not a fatal error. --*/ { NTSTATUS Status = STATUS_SUCCESS; NTSTATUS IgnoreStatus = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_LIST OutputWorkList = NULL; ULONG SidIndex; PSID DomainSid = NULL; PLSAP_DB_LOOKUP_WORK_ITEM AnchorWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NextWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM WorkItemToUpdate = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NewWorkItem = NULL; PLSAPR_TRUST_INFORMATION TrustInformation = NULL; PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY TrustEntry = NULL; LONG DomainIndex; PSID Sid = NULL; BOOLEAN AcquiredReadLockTrustedDomainList = FALSE; // // Create an empty Work List // Status = LsapDbLookupCreateWorkList(&OutputWorkList); if (!NT_SUCCESS(Status)) { goto LookupSidsBuildWorkListError; } // // Initialize the rest of the Work List Header fields. Some fields // were initialized upon creation to fixed values. The ones set here // depend on parameter values passed into this routine. // OutputWorkList->LookupType = LookupSids; OutputWorkList->Count = Count; OutputWorkList->LookupLevel = LookupLevel; OutputWorkList->ReferencedDomains = ReferencedDomains; OutputWorkList->MappedCount = MappedCount; OutputWorkList->CompletelyUnmappedCount = CompletelyUnmappedCount; OutputWorkList->LookupSidsParams.Sids = Sids; OutputWorkList->LookupSidsParams.TranslatedNames = TranslatedNames; // // Construct the array of Work Items. Each Work Item will // contain all the Sids for a given domain, so we will scan // all of the Sids, sorting them into Work Items as we go. // For each Sid, follow the steps detailed below. // for (SidIndex = 0; SidIndex < Count; SidIndex++) { // // If this Sid's Domain is already marked as known, skip. // if (TranslatedNames->Names[SidIndex].DomainIndex != LSA_UNKNOWN_INDEX) { continue; } // // Sid is completely unknown. Extract its Domain Sid and see if // there is already a Work Item for its Domain. // Sid = Sids[SidIndex]; Status = LsapRtlExtractDomainSid( Sid, &DomainSid ); if (!NT_SUCCESS(Status)) { break; } NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) OutputWorkList->AnchorWorkItem->Links.Flink; AnchorWorkItem = OutputWorkList->AnchorWorkItem; WorkItemToUpdate = NULL; NewWorkItem = NULL; while (NextWorkItem != AnchorWorkItem) { if (RtlEqualSid((PSID) NextWorkItem->TrustInformation.Sid,DomainSid)) { // // A Work Item already exists for the Sid's Trusted Domain. // Select that Work Item for update. // MIDL_user_free(DomainSid); DomainSid = NULL; WorkItemToUpdate = NextWorkItem; break; } // // Sid's domain not found among existing Work Items. Skip to // next Work Item. // NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) NextWorkItem->Links.Flink; } if (WorkItemToUpdate == NULL) { // // No Work Item exists for the Sid's Domain. See if the // Sid belongs to one of the Trusted Domains. If not, skip // to the next Sid. // ULONG Flags = LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_EXTERNAL; if (fIncludeIntraforest) { Flags |= LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_INTRA; } Status = LsapDomainHasDomainTrust(Flags, NULL, DomainSid, &AcquiredReadLockTrustedDomainList, &TrustEntry); MIDL_user_free(DomainSid); DomainSid = NULL; if (Status == STATUS_NO_SUCH_DOMAIN) { Status = STATUS_SUCCESS; continue; } if ( !NT_SUCCESS(Status) ) { break; } // // If the trust is not outbound, don't try to lookup // ASSERT( NULL != TrustEntry ); if ( !FLAG_ON( TrustEntry->TrustInfoEx.TrustDirection, TRUST_DIRECTION_OUTBOUND ) ) { Status = STATUS_SUCCESS; continue; } ASSERT( NULL != TrustEntry->TrustInfoEx.Sid ); TrustInformation = &TrustEntry->ConstructedTrustInfo; // // Sid belongs to a Trusted Domain for which there is // no Work Item. Add the Domain to the Referenced Domain List // and obtain a Domain Index. // Status = LsapDbLookupAddListReferencedDomains( ReferencedDomains, TrustInformation, &DomainIndex ); if (!NT_SUCCESS(Status)) { break; } // // Create a new Work Item for this domain. // Status = LsapDbLookupCreateWorkItem( TrustInformation, DomainIndex, (ULONG) LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY + (ULONG) 1, &NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } // // Add the Work Item to the List. // Status = LsapDbAddWorkItemToWorkList( OutputWorkList, NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } WorkItemToUpdate = NewWorkItem; } if (!NT_SUCCESS(Status)) { break; } // // Add the Sid Index to the Work Item. // Status = LsapDbLookupAddIndicesToWorkItem( WorkItemToUpdate, (ULONG) 1, &SidIndex ); if (!NT_SUCCESS(Status)) { break; } // // Store the Domain Index in the Translated Names array entry for // the Sid. // OutputWorkList->LookupSidsParams.TranslatedNames->Names[SidIndex].DomainIndex = WorkItemToUpdate->DomainIndex; } if (!NT_SUCCESS(Status)) { goto LookupSidsBuildWorkListError; } // // If the Work List has no Work Items, this means that none of the // Sids belong to any of the Trusted Domains. In this case, // we discard the Work List. // Status = STATUS_NONE_MAPPED; if (OutputWorkList->WorkItemCount == 0) { goto LookupSidsBuildWorkListError; } // // Compute the Advisory Thread Count for this lookup. // Status = LsapDbLookupComputeAdvisoryChildThreadCount( OutputWorkList ); if (!NT_SUCCESS(Status)) { goto LookupSidsBuildWorkListError; } // // Insert the Work List at the end of the Work Queue. // Status = LsapDbLookupInsertWorkList(OutputWorkList); if (!NT_SUCCESS(Status)) { goto LookupSidsBuildWorkListError; } // // Update the Mapped Counts // LsapDbUpdateMappedCountsWorkList( OutputWorkList ); *WorkList = OutputWorkList; LookupSidsBuildWorkListFinish: // // If necessary, release the Trusted Domain List Read Lock. // if (DomainSid) { midl_user_free(DomainSid); } if (AcquiredReadLockTrustedDomainList) { LsapDbReleaseLockTrustedDomainList(); AcquiredReadLockTrustedDomainList = FALSE; } return(Status); LookupSidsBuildWorkListError: if ( OutputWorkList != NULL ) { IgnoreStatus = LsapDbLookupDeleteWorkList( OutputWorkList ); OutputWorkList = NULL; } *WorkList = NULL; goto LookupSidsBuildWorkListFinish; } NTSTATUS LsapDbLookupXForestSidsBuildWorkList( IN ULONG Count, IN PLSAPR_SID *Sids, IN PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains, IN PLSAPR_TRANSLATED_NAMES_EX TranslatedNames, IN LSAP_LOOKUP_LEVEL LookupLevel, IN OUT PULONG MappedCount, IN OUT PULONG CompletelyUnmappedCount, OUT PLSAP_DB_LOOKUP_WORK_LIST *WorkList ) /*++ Routine Description: This function constructs a Work List for a Lookup SID request that contains names that need to be resolved at cross forest domains. This routine, hence is only called on DC's in the root of forest. N.B. The trust information is the name of the target trusted forest, not the domain since we don't know the domain at this point. Parameters: Count - Number of Sids in the Sids array, Note that some of these may already have been mapped elsewhere, as specified by the MappedCount parameter. Sids - Pointer to array of pointers to Sids to be translated. Zero or all of the Sids may already have been translated elsewhere. If any of the Sids have been translated, the Names parameter will point to a location containing a non-NULL array of Name translation structures corresponding to the Sids. If the nth Sid has been translated, the nth name translation structure will contain either a non-NULL name or a non-negative offset into the Referenced Domain List. If the nth Sid has not yet been translated, the nth name translation structure will contain a zero-length name string and a negative value for the Referenced Domain List index. TrustInformation - Pointer to Trust Information specifying a Domain Sid and Name. ReferencedDomains - Pointer to a Referenced Domain List structure. The structure references an array of zero or more Trust Information entries, one per referenced domain. This array will be appended to or reallocated if necessary. TranslatedNames - Pointer to structure that optionally references a list of name translations for some of the Sids in the Sids array. LookupLevel - Specifies the Level of Lookup to be performed on this machine. MappedCount - Pointer to location containing the number of Sids in the Sids array that have already been mapped. This number will be updated to reflect additional mapping done by this routine. CompletelyUnmappedCount - Pointer to location containing the count of completely unmapped Sids. A Sid is completely unmapped if it is unknown and also its Domain Prefix Sid is not recognized. This count is updated on exit, the number of completely unmapped Sids whose Domain Prefices are identified by this routine being subtracted from the input value. WorkList - Receives pointer to completed Work List if successfully built. Return Values: NTSTATUS - Standard Nt Result Code. STATUS_NONE_MAPPED - None of the Sids specified belong to any of the Trusted Domains. No Work List has been generated. Note that this is not a fatal error. --*/ { NTSTATUS Status = STATUS_SUCCESS; NTSTATUS IgnoreStatus = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_LIST OutputWorkList = NULL; ULONG SidIndex; PSID DomainSid = NULL; PLSAP_DB_LOOKUP_WORK_ITEM AnchorWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NextWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM WorkItemToUpdate = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NewWorkItem = NULL; PLSAPR_TRUST_INFORMATION TrustInformation = NULL; LSAPR_TRUST_INFORMATION TrustInfo; LONG DomainIndex; PSID Sid = NULL; UNICODE_STRING XForestName = {0, 0, NULL}; // // Create an empty Work List // Status = LsapDbLookupCreateWorkList(&OutputWorkList); if (!NT_SUCCESS(Status)) { goto LookupSidsBuildWorkListError; } // // Initialize the rest of the Work List Header fields. Some fields // were initialized upon creation to fixed values. The ones set here // depend on parameter values passed into this routine. // OutputWorkList->LookupType = LookupSids; OutputWorkList->Count = Count; OutputWorkList->LookupLevel = LookupLevel; OutputWorkList->ReferencedDomains = ReferencedDomains; OutputWorkList->MappedCount = MappedCount; OutputWorkList->CompletelyUnmappedCount = CompletelyUnmappedCount; OutputWorkList->LookupSidsParams.Sids = Sids; OutputWorkList->LookupSidsParams.TranslatedNames = TranslatedNames; // // Construct the array of Work Items. Each Work Item will // contain all the Sids for a given domain, so we will scan // all of the Sids, sorting them into Work Items as we go. // For each Sid, follow the steps detailed below. // for (SidIndex = 0; SidIndex < Count; SidIndex++) { ULONG Length; ULONG DomainSidBuffer[SECURITY_MAX_SID_SIZE/sizeof( ULONG ) + 1 ]; // // Sid is completely unknown. Extract its Domain Sid and see if // there is already a Work Item for its Domain. // Sid = Sids[SidIndex]; // // Extract the domain portion of the SID and see if it matches // one of our cross forest domains. // Length = sizeof(DomainSidBuffer); DomainSid = (PSID)DomainSidBuffer; if (!GetWindowsAccountDomainSid(Sid, DomainSid, &Length)) { continue; } Status = LsaIForestTrustFindMatch(RoutingMatchDomainSid, (PVOID)DomainSid, &XForestName); if (!NT_SUCCESS(Status)) { // // Can't find match? Continue // Status = STATUS_SUCCESS; continue; } NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) OutputWorkList->AnchorWorkItem->Links.Flink; AnchorWorkItem = OutputWorkList->AnchorWorkItem; WorkItemToUpdate = NULL; NewWorkItem = NULL; while (NextWorkItem != AnchorWorkItem) { if ( LsapCompareDomainNames( (PUNICODE_STRING) &NextWorkItem->TrustInformation.Name, (PUNICODE_STRING) &XForestName, NULL) ) { // // A Work Item already exists for the Sid's Trusted Domain. // Select that Work Item for update. // WorkItemToUpdate = NextWorkItem; break; } // // Sid's domain not found among existing Work Items. Skip to // next Work Item. // NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) NextWorkItem->Links.Flink; } if (WorkItemToUpdate == NULL) { // // No Work Item exists for the Sid's Domain. See if the // Sid belongs to one of the Trusted Domains. If not, skip // to the next Sid. // RtlZeroMemory( &TrustInfo, sizeof(TrustInfo) ); TrustInfo.Name.Length = XForestName.Length; TrustInfo.Name.MaximumLength = XForestName.MaximumLength; TrustInfo.Name.Buffer = XForestName.Buffer; TrustInfo.Sid = NULL; TrustInformation = &TrustInfo; // // Create a new Work Item for this domain. // Status = LsapDbLookupCreateWorkItem( TrustInformation, LSA_UNKNOWN_INDEX, (ULONG) LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY + (ULONG) 1, &NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } // // Add the Work Item to the List. // Status = LsapDbAddWorkItemToWorkList( OutputWorkList, NewWorkItem ); if (!NT_SUCCESS(Status)) { break; } WorkItemToUpdate = NewWorkItem; NewWorkItem->Properties |= LSAP_DB_LOOKUP_WORK_ITEM_XFOREST; } LsaIFree_LSAPR_UNICODE_STRING_BUFFER( (LSAPR_UNICODE_STRING*)&XForestName); XForestName.Buffer = NULL; // // Add the Sid Index to the Work Item. // Status = LsapDbLookupAddIndicesToWorkItem( WorkItemToUpdate, (ULONG) 1, &SidIndex ); if (!NT_SUCCESS(Status)) { break; } // // Store the Domain Index in the Translated Names array entry for // the Sid. // OutputWorkList->LookupSidsParams.TranslatedNames->Names[SidIndex].DomainIndex = WorkItemToUpdate->DomainIndex; } if (XForestName.Buffer) { LsaIFree_LSAPR_UNICODE_STRING_BUFFER( (LSAPR_UNICODE_STRING*)&XForestName); XForestName.Buffer = NULL; } if (!NT_SUCCESS(Status)) { goto LookupSidsBuildWorkListError; } // // If the Work List has no Work Items, this means that none of the // Sids belong to any of the Trusted Domains. In this case, // we discard the Work List. // Status = STATUS_NONE_MAPPED; if (OutputWorkList->WorkItemCount == 0) { goto LookupSidsBuildWorkListError; } // // Compute the Advisory Thread Count for this lookup. // Status = LsapDbLookupComputeAdvisoryChildThreadCount( OutputWorkList ); if (!NT_SUCCESS(Status)) { goto LookupSidsBuildWorkListError; } // // Insert the Work List at the end of the Work Queue. // Status = LsapDbLookupInsertWorkList(OutputWorkList); if (!NT_SUCCESS(Status)) { goto LookupSidsBuildWorkListError; } // // Update the Mapped Counts // LsapDbUpdateMappedCountsWorkList( OutputWorkList ); *WorkList = OutputWorkList; LookupSidsBuildWorkListFinish: return(Status); LookupSidsBuildWorkListError: if ( OutputWorkList != NULL ) { IgnoreStatus = LsapDbLookupDeleteWorkList( OutputWorkList ); OutputWorkList = NULL; } *WorkList = NULL; goto LookupSidsBuildWorkListFinish; } NTSTATUS LsapDbLookupCreateWorkList( OUT PLSAP_DB_LOOKUP_WORK_LIST *WorkList ) /*++ Routine Description: This function creates a Lookup Operation Work List and initializes fixed default fields. Arguments: WorkList - Receives Pointer to an empty Work List structure. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; // // Allocate memory for the Work List header. // *WorkList = LsapAllocateLsaHeap( sizeof(LSAP_DB_LOOKUP_WORK_LIST) ); if ( *WorkList == NULL ) { Status = STATUS_INSUFFICIENT_RESOURCES; } else { // // Initialize the fixed fields in the Work List. // Status = LsapDbLookupInitializeWorkList(*WorkList); if (!NT_SUCCESS(Status)) { LsapFreeLsaHeap( (PVOID)*WorkList ); *WorkList = NULL; } } return(Status); } NTSTATUS LsapDbLookupInsertWorkList( IN PLSAP_DB_LOOKUP_WORK_LIST WorkList ) /*++ Routine Description: This function inserts a Lookup Operation Work List in the Work Queue. Arguments: WorkList - Pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; BOOLEAN AcquiredWorkQueueLock = FALSE; // // Acquire the Lookup Work Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupInsertWorkListError; } AcquiredWorkQueueLock = TRUE; // // Mark the Work List as Active. // WorkList->State = ActiveWorkList; // // Link the Work List onto the end of the Work Queue. // WorkList->WorkLists.Flink = (PLIST_ENTRY) LookupWorkQueue.AnchorWorkList; WorkList->WorkLists.Blink = (PLIST_ENTRY) LookupWorkQueue.AnchorWorkList->WorkLists.Blink; WorkList->WorkLists.Flink->Blink = (PLIST_ENTRY) WorkList; WorkList->WorkLists.Blink->Flink = (PLIST_ENTRY) WorkList; // // Update the Currently Assignable Work List and Work Item pointers // if there is none. // if (LookupWorkQueue.CurrentAssignableWorkList == NULL) { LookupWorkQueue.CurrentAssignableWorkList = WorkList; LookupWorkQueue.CurrentAssignableWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) WorkList->AnchorWorkItem->Links.Flink; } // // Diagnostic message indicating work list has been inserted // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA DB: Inserting WorkList: 0x%lx ( Item Count: %ld)\n", WorkList, WorkList->WorkItemCount) ); LookupInsertWorkListFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupInsertWorkListError: goto LookupInsertWorkListFinish; } NTSTATUS LsapDbLookupDeleteWorkList( IN PLSAP_DB_LOOKUP_WORK_LIST WorkList ) /*++ Routine Description: This function Deletes a Lookup Operation Work List from the Work Queue and frees the Work List structure. Arguments: WorkList - Pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_ITEM ThisWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_ITEM NextWorkItem = NULL; BOOLEAN AcquiredWorkQueueLock = FALSE; // // Acquire the Lookup Work Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupDeleteWorkListError; } AcquiredWorkQueueLock = TRUE; // // An internal error exists if we are trying to delete an active Work List. // Only inactive or completed Work Lists can be removed. // ASSERT(WorkList->State != ActiveWorkList); // // If the Work List is on the Work Queue, remove it. // if ((WorkList->WorkLists.Blink != NULL) && (WorkList->WorkLists.Flink != NULL)) { WorkList->WorkLists.Blink->Flink = WorkList->WorkLists.Flink; WorkList->WorkLists.Flink->Blink = WorkList->WorkLists.Blink; } // // Release the Lookup Work Queue Lock. // LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; // // Free up memory allocated for the Work Items on the List. // ThisWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) WorkList->AnchorWorkItem->Links.Blink; while (ThisWorkItem != WorkList->AnchorWorkItem) { NextWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) ThisWorkItem->Links.Blink; if (ThisWorkItem->Indices != NULL) { MIDL_user_free( ThisWorkItem->Indices ); } MIDL_user_free( ThisWorkItem->TrustInformation.Sid ); MIDL_user_free( ThisWorkItem->TrustInformation.Name.Buffer ); MIDL_user_free( ThisWorkItem ); ThisWorkItem = NextWorkItem; } // // Release the handle // if ( WorkList->LookupCompleteEvent ) { NtClose( WorkList->LookupCompleteEvent ); } // // Free up memory allocated for the Work List structure itself. // MIDL_user_free( WorkList ); LookupDeleteWorkListFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupDeleteWorkListError: goto LookupDeleteWorkListFinish; } VOID LsapDbUpdateMappedCountsWorkList( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList ) /*++ Routine Decsription: This function updates the counts of completely Mapped and completely Unmapped Sids or Names in a Work List. A Sid or Name is completely mapped if its Use has been identified, partially mapped if its Domain is known but its terminal name of relative id is not yet known, completely unmapped if its domain is not yet known. Arguments: WorkList - Pointer to Work List to be updated. Return Values: None. --*/ { NTSTATUS Status = STATUS_SUCCESS; BOOLEAN AcquiredWorkQueueLock = FALSE; ULONG OutputMappedCount = (ULONG) 0; ULONG OutputCompletelyUnmappedCount = WorkList->Count; ULONG Index; // // Acquire the Lookup Work Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto UpdateMappedCountsWorkListError; } AcquiredWorkQueueLock = TRUE; if (WorkList->LookupType == LookupSids) { for ( Index = (ULONG) 0; Index < WorkList->Count; Index++ ) { if (WorkList->LookupSidsParams.TranslatedNames->Names[ Index].Use != SidTypeUnknown) { OutputMappedCount++; OutputCompletelyUnmappedCount--; } else if (WorkList->LookupSidsParams.TranslatedNames->Names[ Index].DomainIndex != LSA_UNKNOWN_INDEX) { OutputCompletelyUnmappedCount--; } } } else { for ( Index = (ULONG) 0; Index < WorkList->Count; Index++ ) { if (WorkList->LookupNamesParams.TranslatedSids->Sids[ Index].Use != SidTypeUnknown) { OutputMappedCount++; OutputCompletelyUnmappedCount--; } else if (WorkList->LookupNamesParams.TranslatedSids->Sids[ Index].DomainIndex != LSA_UNKNOWN_INDEX) { OutputCompletelyUnmappedCount--; } } } *WorkList->MappedCount = OutputMappedCount; *WorkList->CompletelyUnmappedCount = OutputCompletelyUnmappedCount; UpdateMappedCountsWorkListFinish: if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return; UpdateMappedCountsWorkListError: goto UpdateMappedCountsWorkListFinish; } NTSTATUS LsapDbLookupSignalCompletionWorkList( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList ) /*++ Routine Description: This function signals the completion or termination of work on a Work List. Arguments: WorkList - Pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; BOOLEAN AcquiredWorkQueueLock = FALSE; // // Verify that all work on the Work List is either complete or // the Work List has been terminated. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupSignalCompletionWorkListError; } AcquiredWorkQueueLock = TRUE; if (NT_SUCCESS(WorkList->Status)) { Status = STATUS_INTERNAL_DB_ERROR; if (WorkList->CompletedWorkItemCount != WorkList->WorkItemCount) { goto LookupSignalCompletionWorkListError; } } // // Signal the event that indicates that a Work List has been processed. // Status = NtSetEvent( WorkList->LookupCompleteEvent, NULL ); if (!NT_SUCCESS(Status)) { LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA DB: LsapDbLookupSignalCompletion.. NtSetEvent failed 0x%lx\n",Status)); goto LookupSignalCompletionWorkListError; } LookupSignalCompletionWorkListFinish: LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA DB: Lookup completion event signalled. (Status: 0x%lx)\n" " WorkList: 0x%lx\n", Status, WorkList) ); // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupSignalCompletionWorkListError: goto LookupSignalCompletionWorkListFinish; } NTSTATUS LsapDbLookupAwaitCompletionWorkList( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList ) /*++ Routine Description: This function awaits the completion or termination of work on a specified Work List. NOTE: This routine expects the specified pointer to a Work List to be valid. A Work List pointer always remains valid until its Primary thread detects completion of the Work List via this routine and then deletes it via LsapDbLookupDeleteWorkList(). For this reason, the Lookup Work Queue lock does not have to be held while this routine executes. Arguments: WorkList - Pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; LSAP_DB_LOOKUP_WORK_LIST_STATE WorkListState; BOOLEAN AcquiredWorkQueueLock = FALSE; // // Loop, waiting for completion events to occur. When one does, // check the status of the specified Work List. // for (;;) { // // Check for completed Work List. Since someone else may be // setting the state, we need to read it while holding the lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { break; } AcquiredWorkQueueLock = TRUE; WorkListState = WorkList->State; LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; if (WorkListState == CompletedWorkList) { break; } // // Wait for Work List completed event to be signalled. // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("Lsa Db: Waiting on worklist completion event\n") ); Status = NtWaitForSingleObject( WorkList->LookupCompleteEvent, TRUE, NULL); LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LsapDb: Wait on worklist completion event Done\n Status: 0x%lx\n", Status) ); if (!NT_SUCCESS(Status)) { break; } } if (!NT_SUCCESS(Status)) { goto LookupAwaitCompletionWorkListError; } LookupAwaitCompletionWorkListFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupAwaitCompletionWorkListError: LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("Lsa Db: LookupAwaitWorklist error. (Status: 0x%lx)\n", Status) ); goto LookupAwaitCompletionWorkListFinish; } NTSTATUS LsapDbAddWorkItemToWorkList( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList, IN PLSAP_DB_LOOKUP_WORK_ITEM WorkItem ) /*++ Routine Decsription: This function adds a Work Item to a Work List. The specified Work Item must exist in non-volatile memory (e.g a heap block). Arguments: WorkList - Pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. WorkItem - Pointer to a Work Item structure describing a list of Sids or Names and a domain in which they are to be looked up. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; BOOLEAN AcquiredWorkQueueLock = FALSE; // // Acquire the Lookup Work Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupAddWorkItemToWorkListError; } AcquiredWorkQueueLock = TRUE; // // Mark the Work Item as assignable. // WorkItem->State = AssignableWorkItem; // // Link the Work Item onto the end of the Work List and increment the // Work Item Count. // WorkItem->Links.Flink = (PLIST_ENTRY) WorkList->AnchorWorkItem; WorkItem->Links.Blink = (PLIST_ENTRY) WorkList->AnchorWorkItem->Links.Blink; WorkItem->Links.Flink->Blink = (PLIST_ENTRY) WorkItem; WorkItem->Links.Blink->Flink = (PLIST_ENTRY) WorkItem; WorkList->WorkItemCount++; LookupAddWorkItemToWorkListFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupAddWorkItemToWorkListError: goto LookupAddWorkItemToWorkListFinish; } VOID LsapDbLookupWorkerThreadStart( ) /*++ Routine Description: This routine initiates a child Worker Thread for a Lookup operation. Arguments: None. Return Value: None. --*/ { // // Start the thread's work processing loop, specifying that this // thread is a child thread rather than the main thread. // LsapDbLookupWorkerThread( FALSE ); } VOID LsapDbLookupWorkerThread( IN BOOLEAN PrimaryThread ) /*++ Routine Description: This function is executed by each worker thread for a Lookup operation. Each worker thread loops, requesting work items from the Lookup Work Queue. Work Items assigned may belong to any current lookup. Arguments: PrimaryThread - TRUE if thread is the main thread of the Lookup operation, FALSE if the thread is a child thread created by the Lookup operation. The main thread of the Lookup operation also processes work items, but is also responsible for collating the results of the Lookup operation. It is not counted in the active thread count and is not returnable to the thread pool. Return Value: None. --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_LIST WorkList = NULL; PLSAP_DB_LOOKUP_WORK_ITEM WorkItem = NULL; BOOLEAN AcquiredWorkQueueLock = FALSE; // // If this thread is a child worker thread, increment count of active // child threads. // if (!PrimaryThread) { Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupWorkerThreadError; } AcquiredWorkQueueLock = TRUE; LookupWorkQueue.ActiveChildThreadCount++; LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } // // Loop while there is work to do. // for (;;) { // // Obtain work packet // Status = LsapDbLookupObtainWorkItem(&WorkList, &WorkItem); if (NT_SUCCESS(Status)) { Status = LsapDbLookupProcessWorkItem(WorkList, WorkItem); if (NT_SUCCESS(Status)) { continue; } // // An error has occurred. Stop this lookup. // Status = LsapDbLookupStopProcessingWorkList(WorkList, Status); // // NOTE: Intentionally ignore the status. // Status = STATUS_SUCCESS; } // // If an error occurred other than there being no more work to do, // quit. // if (Status != STATUS_NO_MORE_ENTRIES) { break; } Status = STATUS_SUCCESS; // // There is no more work to do. If this thread is a child worker // thread, either return thread to pool and wait for more work, or // terminate if enough threads have already been retained. If this // thread is the main thread of a Lookup operation, just return // in order to collate results. // if (!PrimaryThread) { Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { break; } AcquiredWorkQueueLock = TRUE; if (LookupWorkQueue.ActiveChildThreadCount <= LookupWorkQueue.MaximumRetainedChildThreadCount) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; // // Wait forever for more work. // Status = NtWaitForSingleObject( LsapDbLookupStartedEvent, TRUE, NULL); if (NT_SUCCESS(Status)) { continue; } // // An error occurred in the wait routine. Exit the thread. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { break; } AcquiredWorkQueueLock = TRUE; } // // We already have enough active threads or an error has occurred. // Mark this one inactive and terminate it. // LookupWorkQueue.ActiveChildThreadCount--; LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; // // Terminate the thread. // ExitThread((DWORD) Status); } // // We're the Primary Thread of some Lookup operation and there is // no more work to do. Break out so we can return to caller. // break; } LookupWorkerThreadFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return; LookupWorkerThreadError: goto LookupWorkerThreadFinish; } NTSTATUS LsapDbLookupObtainWorkItem( OUT PLSAP_DB_LOOKUP_WORK_LIST *WorkList, OUT PLSAP_DB_LOOKUP_WORK_ITEM *WorkItem ) /*++ Routine Description: This function is called by a worker thread to obtain a Work Item. This Work Item may belong to any current lookup operation. Arguments: WorkList - Receives a pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. WorkItem - Receives a pointer to a Work Item structure describing a list of Sids or Names and a domain in which they are to be looked up. Return Value: NTSTATUS - Standard Nt Result Code STATUS_NO_MORE_ENTRIES - No more work items available. --*/ { NTSTATUS Status; BOOLEAN AcquiredWorkQueueLock = FALSE; *WorkList = NULL; *WorkItem = NULL; // // Acquire the Lookup Work Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupObtainWorkItemError; } AcquiredWorkQueueLock = TRUE; // // Return an error if there are no more Work Items. // Status = STATUS_NO_MORE_ENTRIES; if (LookupWorkQueue.CurrentAssignableWorkList == NULL) { goto LookupObtainWorkItemError; } // // Verify that the Current Assignable Work List does not have // a termination error. This should never happen, because the // pointers should be updated if the Lookup corresponding to the Current // Assignable Work List is terminated. // ASSERT(NT_SUCCESS(LookupWorkQueue.CurrentAssignableWorkList->Status)); // // There are work items available. Check the next one out. // ASSERT(LookupWorkQueue.CurrentAssignableWorkItem->State == AssignableWorkItem); LookupWorkQueue.CurrentAssignableWorkItem->State = AssignedWorkItem; *WorkList = LookupWorkQueue.CurrentAssignableWorkList; *WorkItem = LookupWorkQueue.CurrentAssignableWorkItem; // // Update pointers to next item (if any) in the current Work List // where work is being given out. // Status = LsapDbLookupUpdateAssignableWorkItem(FALSE); if (!NT_SUCCESS(Status)) { goto LookupObtainWorkItemError; } LookupObtainWorkItemFinish: // // If we acquired the Lookup Work Queue Lock, release it. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupObtainWorkItemError: goto LookupObtainWorkItemFinish; } NTSTATUS LsapDbLookupProcessWorkItem( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList, IN OUT PLSAP_DB_LOOKUP_WORK_ITEM WorkItem ) /*++ Routine Description: This function processes a Work Item for a Lookup operation. The Work Item specifies a number of Sids or Names to be looked up in a given domain. Arguments: WorkList - Pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. WorkItem - Pointer to a Work Item structure describing a list of Sids or Names and a domain in which they are to be looked up. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; NTSTATUS SecondaryStatus = STATUS_SUCCESS; ULONG NextLevelCount; ULONG NextLevelMappedCount; PLSAP_BINDING_CACHE_ENTRY ControllerPolicyEntry = NULL; ULONG NextLevelIndex; PLSAPR_REFERENCED_DOMAIN_LIST NextLevelReferencedDomains = NULL; PSID *NextLevelSids = NULL; PUNICODE_STRING NextLevelNames = NULL; PLSAPR_REFERENCED_DOMAIN_LIST OutputReferencedDomains = NULL; PLSAPR_TRANSLATED_SID_EX2 NextLevelTranslatedSids = NULL; PLSA_TRANSLATED_NAME_EX NextLevelTranslatedNames = NULL; ULONG Index; LPWSTR ServerPrincipalName = NULL; LPWSTR ServerName = NULL; PVOID ClientContext = NULL; ULONG AuthnLevel = 0; NL_OS_VERSION ServerOsVersion; LSAP_LOOKUP_LEVEL LookupLevel = WorkList->LookupLevel; PUNICODE_STRING TargetDomainName = NULL; LSAPR_TRUST_INFORMATION_EX TrustInfoEx; BOOLEAN *NextLevelNamesMorphed = NULL; TargetDomainName = (PUNICODE_STRING) &WorkItem->TrustInformation.Name; RtlZeroMemory(&TrustInfoEx, sizeof(TrustInfoEx)); TrustInfoEx.FlatName = WorkItem->TrustInformation.Name; TrustInfoEx.Sid = WorkItem->TrustInformation.Sid; LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA DB: Processing work item. (0x%lx, 0x%lx)\n", WorkList, WorkItem)); ASSERT( (WorkList->LookupLevel == LsapLookupTDL) || (WorkList->LookupLevel == LsapLookupXForestResolve) ); // // Branch according to lookup type. // NextLevelCount = WorkItem->UsedCount; if (WorkList->LookupType == LookupSids) { // // Allocate an array for the Sids to be looked up at a Domain // Controller for the specified Trusted Domain. // NextLevelSids = MIDL_user_allocate( sizeof(PSID) * NextLevelCount ); if (NextLevelSids == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto LookupProcessWorkItemError; } // // Copy in the Sids to be looked up from the Work List. The Work // Item contains their indices relative to the Sid array in the // Work List. // for (NextLevelIndex = 0; NextLevelIndex < NextLevelCount; NextLevelIndex++) { Index = WorkItem->Indices[ NextLevelIndex ]; NextLevelSids[NextLevelIndex] = WorkList->LookupSidsParams.Sids[Index]; } NextLevelMappedCount = (ULONG) 0; // // Lookup the Sids at the DC. // Status = LsaDbLookupSidChainRequest(&TrustInfoEx, NextLevelCount, NextLevelSids, (PLSA_REFERENCED_DOMAIN_LIST *) &NextLevelReferencedDomains, &NextLevelTranslatedNames, WorkList->LookupLevel, &NextLevelMappedCount, NULL ); LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA DB: Sid Lookup.\n" " Item: (0x%lx, 0x%lx)\n" " Count: 0x%lx\n", WorkList, WorkItem, NextLevelCount)); // // If the callout to LsaLookupSids() was unsuccessful, disregard // the error and set the domain name for any Sids having this // domain Sid as prefix sid. // if (!NT_SUCCESS(Status) && Status != STATUS_NONE_MAPPED) { SecondaryStatus = Status; Status = STATUS_SUCCESS; goto LookupProcessWorkItemFinish; } // // The callout to LsaICLookupSids() was successful. Update the // TranslatedNames information in the Work List as appropriate // using the TranslatedNames information returned from the callout. // Status = LsapDbLookupSidsUpdateTranslatedNames( WorkList, WorkItem, NextLevelTranslatedNames, NextLevelReferencedDomains ); if (!NT_SUCCESS(Status)) { goto LookupProcessWorkItemError; } } else if (WorkList->LookupType == LookupNames) { // // Allocate an array of UNICODE_STRING structures for the // names to be looked up at the Domain Controller. // NextLevelNames = MIDL_user_allocate(sizeof(UNICODE_STRING) * NextLevelCount); if (NextLevelNames == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto LookupProcessWorkItemError; } // // Allocate space to remember which names are morphed in place // from a UPN to SamAccountName format. // NextLevelNamesMorphed = MIDL_user_allocate(sizeof(BOOLEAN) * NextLevelCount); if (NextLevelNamesMorphed == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto LookupProcessWorkItemError; } // // Copy in the Names to be looked up from the Work List. The Work // Item contains their indices relative to the Names array in the // Work List. // for (NextLevelIndex = 0; NextLevelIndex < NextLevelCount; NextLevelIndex++) { Index = WorkItem->Indices[ NextLevelIndex ]; NextLevelNames[NextLevelIndex] = *((PUNICODE_STRING) &WorkList->LookupNamesParams.Names[Index]); if ( (WorkList->LookupLevel == LsapLookupTDL) && LsapLookupIsUPN(&NextLevelNames[NextLevelIndex])) { // // We are performing a TDL level lookup. The server side // of the TDL lookup's don't know how to translate UPN's // so morph username@domainname to domainname\username. // Remember which names we morph so that we can morph // them back. // // N.B. A name would only be here if format of the UPN // was username@domainname, where domainname is the name // of the trusted domain. // LsapLookupUPNToSamAccountName(&NextLevelNames[NextLevelIndex]); NextLevelNamesMorphed[NextLevelIndex] = TRUE; } else { NextLevelNamesMorphed[NextLevelIndex] = FALSE; } } NextLevelMappedCount = (ULONG) 0; // // Lookup the Names at the DC. // Status = LsapDbLookupNameChainRequest(&TrustInfoEx, NextLevelCount, NextLevelNames, (PLSA_REFERENCED_DOMAIN_LIST *)&NextLevelReferencedDomains, (PLSA_TRANSLATED_SID_EX2 * )&NextLevelTranslatedSids, WorkList->LookupLevel, &NextLevelMappedCount, NULL ); // // Upmorph any names // for (NextLevelIndex = 0; NextLevelIndex < NextLevelCount; NextLevelIndex++) { if (NextLevelNamesMorphed[NextLevelIndex]) { // // Morph name back to original state // LsapLookupSamAccountNameToUPN(&NextLevelNames[NextLevelIndex]); } } // // If the callout to LsaLookupNames() was unsuccessful, disregard // the error and set the domain name for any Sids having this // domain Sid as prefix sid. // if (!NT_SUCCESS(Status) && Status != STATUS_NONE_MAPPED) { SecondaryStatus = Status; Status = STATUS_SUCCESS; goto LookupProcessWorkItemError; } // // The callout to LsaICLookupNames() was successful. Update the // TranslatedSids information in the Work List as appropriate // using the TranslatedSids information returned from the callout. // Status = LsapDbLookupNamesUpdateTranslatedSids( WorkList, WorkItem, NextLevelTranslatedSids, NextLevelReferencedDomains ); if (!NT_SUCCESS(Status)) { goto LookupProcessWorkItemError; } } else { Status = STATUS_INVALID_PARAMETER; goto LookupProcessWorkItemError; } LookupProcessWorkItemFinish: // // If we are unable to connect to the Trusted Domain via any DC, // suppress the error so that the Lookup can continue to try and // translate other Sids/Names. // // But record what the error was in case no sids are translated if (!NT_SUCCESS(SecondaryStatus)) { NTSTATUS st; st = LsapDbLookupAcquireWorkQueueLock(); ASSERT( NT_SUCCESS( st ) ); if ( NT_SUCCESS( st ) ) { if ( NT_SUCCESS(WorkList->NonFatalStatus) ) { // // Treat any error to open the open domain // as a trust problem // WorkList->NonFatalStatus = STATUS_TRUSTED_DOMAIN_FAILURE; } LsapDbLookupReleaseWorkQueueLock(); } } // // Change the state of the work item to "Completed" // WorkItem->State = CompletedWorkItem; // // Update the Mapped Counts // LsapDbUpdateMappedCountsWorkList( WorkList ); // // Protect WorkList operations // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupProcessWorkItemError; } // // Increment the count of completed Work Items whether or not this // one was completed without error. If the Work List has just been // completed, change its state to "CompletedWorkList" and signal // the Lookup operation completed event. Allow re-entry into // this section if an error is returned. // WorkList->CompletedWorkItemCount++; LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA DB: Process Work Item Completed.\n" " Item: (0x%lx, 0x%lx)\n" " Completed Count: %ld\n", WorkList, WorkItem, WorkList->CompletedWorkItemCount)); if (WorkList->State != CompletedWorkList) { if (WorkList->CompletedWorkItemCount == WorkList->WorkItemCount) { WorkList->State = CompletedWorkList; SecondaryStatus = LsapDbLookupSignalCompletionWorkList( WorkList ); } } // // Done making work list changes // LsapDbLookupReleaseWorkQueueLock(); // // If necessary, free the array of Sids looked up at the next level. // if (NextLevelSids != NULL) { MIDL_user_free( NextLevelSids ); NextLevelSids = NULL; } // // If necessary, free the array of Names looked up at the next level. // if (NextLevelNames != NULL) { MIDL_user_free( NextLevelNames ); NextLevelNames = NULL; } if (NextLevelReferencedDomains != NULL) { MIDL_user_free( NextLevelReferencedDomains ); NextLevelReferencedDomains = NULL; } if (NextLevelTranslatedNames != NULL) { MIDL_user_free( NextLevelTranslatedNames ); NextLevelTranslatedNames = NULL; } if (NextLevelTranslatedSids != NULL) { MIDL_user_free( NextLevelTranslatedSids ); NextLevelTranslatedSids = NULL; } if (NextLevelNamesMorphed != NULL) { MIDL_user_free( NextLevelNamesMorphed ); } return(Status); LookupProcessWorkItemError: goto LookupProcessWorkItemFinish; } NTSTATUS LsapDbLookupSidsUpdateTranslatedNames( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList, IN OUT PLSAP_DB_LOOKUP_WORK_ITEM WorkItem, IN PLSA_TRANSLATED_NAME_EX TranslatedNames, IN PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains ) /*++ Routine Description: This function is called during the processing of a Work Item to update the output TranslatedNames and ReferencedDomains parameters of a Lookup operation's Work List with the results of a callout to LsaICLookupNames. Zero or more Sids may have been translated. Note that, unlike the translation of Names, Sid translation occurs within a single Work Item only. Arguments: WorkList - Pointer to a Work List WorkItem - Pointer to a Work Item. TranslatedNames - Translated Sids information returned from LsaICLookupSids(). Return Values: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; ULONG Index, WorkItemIndex; BOOLEAN AcquiredWorkQueueLock = FALSE; PLSAPR_TRANSLATED_NAME_EX WorkListTranslatedNames = WorkList->LookupSidsParams.TranslatedNames->Names; // // Acquire the Work Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupSidsUpdateTranslatedNamesError; } AcquiredWorkQueueLock = TRUE; for( WorkItemIndex = 0; WorkItemIndex < WorkItem->UsedCount; WorkItemIndex++) { // // If this Sid has been fully translated, copy information to output. // Note that the Sid is partially translated during the building // phase where its Domain is identified. // if (TranslatedNames[WorkItemIndex].Use != SidTypeUnknown) { ULONG LocalDomainIndex; Index = WorkItem->Indices[WorkItemIndex]; if (TranslatedNames[WorkItemIndex].DomainIndex != LSA_UNKNOWN_INDEX) { // // Make sure this is in the referenced domains list // Status = LsapDbLookupAddListReferencedDomains( WorkList->ReferencedDomains, &ReferencedDomains->Domains[TranslatedNames[WorkItemIndex].DomainIndex], (PLONG) &LocalDomainIndex ); if (!NT_SUCCESS(Status)) { break; } } else { LocalDomainIndex = TranslatedNames[WorkItemIndex].DomainIndex; } WorkListTranslatedNames[Index].Use = TranslatedNames[WorkItemIndex].Use; WorkListTranslatedNames[Index].DomainIndex = LocalDomainIndex; Status = LsapRpcCopyUnicodeString( NULL, (PUNICODE_STRING) &WorkListTranslatedNames[Index].Name, (PUNICODE_STRING) &TranslatedNames[WorkItemIndex].Name ); if (!NT_SUCCESS(Status)) { break; } } } if (!NT_SUCCESS(Status)) { goto LookupSidsUpdateTranslatedNamesError; } LookupSidsUpdateTranslatedNamesFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupSidsUpdateTranslatedNamesError: goto LookupSidsUpdateTranslatedNamesFinish; } NTSTATUS LsapDbLookupNamesUpdateTranslatedSids( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList, IN OUT PLSAP_DB_LOOKUP_WORK_ITEM WorkItem, IN PLSAPR_TRANSLATED_SID_EX2 TranslatedSids, IN PLSAPR_REFERENCED_DOMAIN_LIST ReferencedDomains ) /*++ Routine Description: This function is called during the processing of a Work Item to update the output TranslatedSids and ReferencedDomains parameters of a Lookup operation's Work List with the results of a callout to LsaICLookupNames. Zero or more Names may have been translated, and there is the additional complication of multiple translations of Isolated Names as a result of their presence in more than one Work Item. The following rules apply: If the Name is a Qualified Name, it only belongs to the specified Work Item, so it suffices to check that it has been mapped to a Sid. If the Name is an Isolated Name, it belongs to all other Work Items, so it may already have been translated during the processing of some other Work Item. If the Name has previously been translated, the prior translation stands and the present translation is discarded. If the Name has not previously been translated, the Domain for this Work Item is added to the Referenced Domain List and the newly obtained translation is stored in the output TranslatedSids array in the Work List. Arguments: WorkList - Pointer to a Work List WorkItem - Pointer to a Work Item. The DomainIndex field will be updated if the Domain specified by this Work Item is added to the Referenced Domain List by this routine. TranslatedSids - Translated Sids information returned from LsaICLookupNames(). Return Values: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; ULONG WorkItemIndex; ULONG LocalDomainIndex; ULONG Index; PLSAPR_TRANSLATED_SID_EX2 WorkListTranslatedSids = WorkList->LookupNamesParams.TranslatedSids->Sids; BOOLEAN AcquiredWorkQueueLock = FALSE; BOOLEAN AcquiredTrustedDomainLock = FALSE; // // Acquire the trusted domain list lock (so that LsapSidOnFtInfo can be called) // Status = LsapDbAcquireReadLockTrustedDomainList(); if (!NT_SUCCESS(Status)) { goto LookupNamesUpdateTranslatedSidsError; } AcquiredTrustedDomainLock = TRUE; // // Acquire the Work Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupNamesUpdateTranslatedSidsError; } AcquiredWorkQueueLock = TRUE; for( WorkItemIndex = 0; WorkItemIndex < WorkItem->UsedCount; WorkItemIndex++) { // // If this Name has not been translated at all during processing of // this Work Item, skip to the next. // if (LsapDbCompletelyUnmappedSid(&TranslatedSids[WorkItemIndex])) { continue; } // // We partially or fully translated the Name during processing of // this Work Item. If this Name has previously been fully translated // during the processing of another Work Item, discard the new // translation and skip to the next Name. Note that Qualified // Names are always partially translated during the building // of the Work List. Isolated Names are fully translated during // the building phase if they are Domain Names. // Index = WorkItem->Indices[WorkItemIndex]; if ( WorkListTranslatedSids[ Index ].Use != SidTypeUnknown ) { continue; } // // If the SID does not pass the filter test, ignore // if ( (WorkItem->Properties & LSAP_DB_LOOKUP_WORK_ITEM_XFOREST) && TranslatedSids[WorkItemIndex].Sid ) { NTSTATUS Status2; Status2 = LsapSidOnFtInfo((PUNICODE_STRING)&WorkItem->TrustInformation.Name, TranslatedSids[WorkItemIndex].Sid ); if (!NT_SUCCESS(Status2)) { // // This SID did not pass the test // BOOL fSuccess; LPWSTR StringSid = NULL, TargetForest = NULL, AccountName = NULL; LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LsapSidOnFtInfo returned 0x%x\n",Status2)); // // This should be rare -- event log for troubleshooting // purposes // fSuccess = ConvertSidToStringSidW(TranslatedSids[WorkItemIndex].Sid, &StringSid); TargetForest = LocalAlloc(LMEM_ZEROINIT, WorkItem->TrustInformation.Name.Length + sizeof(WCHAR)); if (TargetForest) { RtlCopyMemory(TargetForest, WorkItem->TrustInformation.Name.Buffer, WorkItem->TrustInformation.Name.Length); } AccountName = LocalAlloc(LMEM_ZEROINIT, WorkList->LookupNamesParams.Names[Index].Length + sizeof(WCHAR)); if (AccountName) { RtlCopyMemory(AccountName, WorkList->LookupNamesParams.Names[Index].Buffer, WorkList->LookupNamesParams.Names[Index].Length); } if ( fSuccess && TargetForest && AccountName) { LsapDbLookupReportEvent3( 1, EVENTLOG_WARNING_TYPE, LSAEVENT_LOOKUP_SID_FILTERED, sizeof( ULONG ), &Status2, AccountName, StringSid, TargetForest ); } if (StringSid) { LocalFree(StringSid); } if (TargetForest) { LocalFree(TargetForest); } if (AccountName) { LocalFree(AccountName); } continue; } } // // Name has been translated for the first time during the processing // of this Work Item. If this Work Item does not specify a Domain // Index, we need to add its Domain to the Referenced Domains List. // if (TranslatedSids[WorkItemIndex].DomainIndex != LSA_UNKNOWN_INDEX) { // // Make sure this is in the referenced domains list // Status = LsapDbLookupAddListReferencedDomains( WorkList->ReferencedDomains, &ReferencedDomains->Domains[TranslatedSids[WorkItemIndex].DomainIndex], (PLONG) &LocalDomainIndex ); if (!NT_SUCCESS(Status)) { break; } } else { LocalDomainIndex = TranslatedSids[WorkItemIndex].DomainIndex; } // // Now update the TranslatedSids array in the Work List. WorkListTranslatedSids[Index] = TranslatedSids[WorkItemIndex]; WorkListTranslatedSids[Index].DomainIndex = LocalDomainIndex; Status = LsapRpcCopySid(NULL, &WorkListTranslatedSids[Index].Sid, TranslatedSids[WorkItemIndex].Sid); if (!NT_SUCCESS(Status)) { break;; } } if (!NT_SUCCESS(Status)) { goto LookupNamesUpdateTranslatedSidsError; } LookupNamesUpdateTranslatedSidsFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } if ( AcquiredTrustedDomainLock ) { LsapDbReleaseLockTrustedDomainList(); AcquiredTrustedDomainLock = FALSE; } return(Status); LookupNamesUpdateTranslatedSidsError: goto LookupNamesUpdateTranslatedSidsFinish; } NTSTATUS LsapDbLookupCreateWorkItem( IN PLSAPR_TRUST_INFORMATION TrustInformation, IN LONG DomainIndex, IN ULONG MaximumEntryCount, OUT PLSAP_DB_LOOKUP_WORK_ITEM *WorkItem ) /*++ Routine Description: This function creates a new Work Item for a name Lookup operation. Arguments: TrustInformation - Specifies the Name of the Trusted Domain to which the Work Item relates. The Sid field may be NULL or set to the corresponding Sid. The Trust Information is expected to be in heap or global data. DomainIndex - Specifies the Domain Index of this domain relative to the Referenced Domain List for the Lookup operation specified by the Work List. MaximumEntryCount - Specifies the maximum number of entries that this Work Item will initialiiy be able to contain. WorkItem - Receives a pointer to an empty Work Item structure. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_ITEM OutputWorkItem = NULL; PULONG OutputIndices = NULL; ULONG InitialEntryCount; // // Allocate memory for the Work Item Header. // Status = STATUS_INSUFFICIENT_RESOURCES; OutputWorkItem = MIDL_user_allocate(sizeof(LSAP_DB_LOOKUP_WORK_ITEM)); if (OutputWorkItem == NULL) { goto LookupCreateWorkItemError; } RtlZeroMemory( OutputWorkItem, sizeof(LSAP_DB_LOOKUP_WORK_ITEM) ); // // Initialize the fixed fields in the Work Item. // Status = LsapDbLookupInitializeWorkItem(OutputWorkItem); if (!NT_SUCCESS(Status)) { goto LookupCreateWorkItemError; } // // Initialize other fields from parameters. // // // Copy the trusted domain information into the work item. The // trust information may be NULL if this is the isolated names // work item. // if (TrustInformation != NULL) { if ( TrustInformation->Sid ) { OutputWorkItem->TrustInformation.Sid = MIDL_user_allocate( RtlLengthSid(TrustInformation->Sid) ); if (OutputWorkItem->TrustInformation.Sid == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto LookupCreateWorkItemError; } RtlCopyMemory( OutputWorkItem->TrustInformation.Sid, TrustInformation->Sid, RtlLengthSid(TrustInformation->Sid) ); } OutputWorkItem->TrustInformation.Name.MaximumLength = TrustInformation->Name.Length + sizeof(WCHAR); OutputWorkItem->TrustInformation.Name.Buffer = MIDL_user_allocate(TrustInformation->Name.Length + sizeof(WCHAR)); if (OutputWorkItem->TrustInformation.Name.Buffer == NULL ) { Status = STATUS_INSUFFICIENT_RESOURCES; goto LookupCreateWorkItemError; } RtlCopyUnicodeString( (PUNICODE_STRING) &OutputWorkItem->TrustInformation.Name, (PUNICODE_STRING) &TrustInformation->Name ); } // // Create the Indices array in the Work Item. // InitialEntryCount = (MaximumEntryCount + LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY) & (~LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY); Status = STATUS_INSUFFICIENT_RESOURCES; OutputIndices = MIDL_user_allocate( InitialEntryCount * sizeof(ULONG) ); if (OutputIndices == NULL) { goto LookupCreateWorkItemError; } Status = STATUS_SUCCESS; OutputWorkItem->UsedCount = (ULONG) 0; OutputWorkItem->MaximumCount = InitialEntryCount; OutputWorkItem->Indices = OutputIndices; OutputWorkItem->DomainIndex = DomainIndex; LookupCreateWorkItemFinish: // // Return pointer to newly created Work Item or NULL. // *WorkItem = OutputWorkItem; return(Status); LookupCreateWorkItemError: // // Free memory allocated for Indices array. // if (OutputIndices != NULL) { MIDL_user_free( OutputIndices ); OutputIndices = NULL; } // // Free any memory allocated for the Work Item header. // if (OutputWorkItem != NULL) { if (OutputWorkItem->TrustInformation.Sid != NULL) { MIDL_user_free( OutputWorkItem->TrustInformation.Sid ); } if (OutputWorkItem->TrustInformation.Name.Buffer != NULL) { MIDL_user_free( OutputWorkItem->TrustInformation.Name.Buffer ); } MIDL_user_free(OutputWorkItem); OutputWorkItem = NULL; } goto LookupCreateWorkItemFinish; } NTSTATUS LsapDbLookupAddIndicesToWorkItem( IN OUT PLSAP_DB_LOOKUP_WORK_ITEM WorkItem, IN ULONG Count, IN PULONG Indices ) /*++ Routine Description: This function adds an array of Sid or Name indices to a Work Item. The indices specify Sids or Names within the Sids or Names arrays in the WorkList. If there is sufficient room in the Work Item's existing indices array, the indices will be copied to that array. Otherwise, a larger array will be allocated and the existing one will be freed after copying the existing indices. NOTE: The Work Item must NOT belong to a Work List that is currently on the Work Queue. The Lookup Work Queue Lock will not be taken. Arguments: WorkItem - Pointer to a Work Item structure describing a list of Sids or Names and a domain in which they are to be looked up. Count - Specifies the number of indices to be added. Indices - Specifies the array of indices to be added to the WorkItem. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; PULONG OutputIndices = NULL; ULONG NewMaximumCount; // // Check room available in the work item. If there is enough // room, just copy the indices in. // if (WorkItem->MaximumCount - WorkItem->UsedCount >= Count) { RtlCopyMemory( &WorkItem->Indices[WorkItem->UsedCount], Indices, Count * sizeof(ULONG) ); WorkItem->UsedCount += Count; goto AddIndicesToWorkItemFinish; } // // Allocate array of sufficient size to accommodate the existing // and new indices. Round up number of entries to some granularity // to avoid frequent reallocations. // Status = STATUS_INSUFFICIENT_RESOURCES; NewMaximumCount = ((WorkItem->UsedCount + Count) + LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY) & (~LSAP_DB_LOOKUP_WORK_ITEM_GRANULARITY); OutputIndices = MIDL_user_allocate( NewMaximumCount * sizeof(ULONG) ); if (OutputIndices == NULL) { goto AddIndicesToWorkItemError; } Status = STATUS_SUCCESS; // // Copy in the existing and new indices. // RtlCopyMemory( OutputIndices, WorkItem->Indices, WorkItem->UsedCount * sizeof(ULONG) ); RtlCopyMemory( &OutputIndices[WorkItem->UsedCount], Indices, Count * sizeof(ULONG) ); // // Free the existing indices. Set pointer to the updated indices array // and update the used and maximum counts. // MIDL_user_free( WorkItem->Indices ); WorkItem->Indices = OutputIndices; WorkItem->UsedCount += Count; WorkItem->MaximumCount = NewMaximumCount; AddIndicesToWorkItemFinish: return(Status); AddIndicesToWorkItemError: // // Free any memory allocated for the Output Indices array. // if (OutputIndices != NULL) { MIDL_user_free( OutputIndices ); OutputIndices = NULL; } goto AddIndicesToWorkItemFinish; } NTSTATUS LsapDbLookupComputeAdvisoryChildThreadCount( IN OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList ) /*++ Routine Description: This function computes the advisory thread count for a Lookup operation. This count is an estimate of the optimal number of worker threads (in addition to the main thread) needed to process the Work List. Arguments: WorkList - Pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; ASSERT(WorkList->WorkItemCount != (ULONG) 0); WorkList->AdvisoryChildThreadCount = (WorkList->WorkItemCount - (ULONG) 1); return(Status); } NTSTATUS LsapDbLookupUpdateAssignableWorkItem( IN BOOLEAN MoveToNextWorkList ) /*++ Routine Description: This function updates the next assignable Work Item pointers. Arguments: MoveToNextWorkList - If TRUE, skip the rest of the current Work List. If FALSE, point at the next item in the current Work List. Return Value: NTSTATUS - Standard Nt Result Code. --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_ITEM CandAssignableWorkItem = NULL; PLSAP_DB_LOOKUP_WORK_LIST CandAssignableWorkList = NULL; BOOLEAN AcquiredWorkQueueLock = FALSE; // // Acquire the LookupWork Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupUpdateAssignableWorkItemError; } AcquiredWorkQueueLock = TRUE; // // If there is no Currently Assignable Work List, just exit. // if (LookupWorkQueue.CurrentAssignableWorkList == NULL) { goto LookupUpdateAssignableWorkItemFinish; } // // There is a Currently Assignable Work List. Unless requested to // skip this Work List, examine it. // if (!MoveToNextWorkList) { ASSERT( LookupWorkQueue.CurrentAssignableWorkItem != NULL); // // Select the next Work Item in the list as candidate for the // next Assignable Work Item. If we have not returned to the First // Work Item, selection is complete. // CandAssignableWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) LookupWorkQueue.CurrentAssignableWorkItem->Links.Flink; if (CandAssignableWorkItem != LookupWorkQueue.CurrentAssignableWorkList->AnchorWorkItem) { ASSERT( CandAssignableWorkItem->State == AssignableWorkItem); LookupWorkQueue.CurrentAssignableWorkItem = CandAssignableWorkItem; goto LookupUpdateAssignableWorkItemFinish; } } // // There are no more work items in this Work List or we're to skip the // rest of it. See if there is another Work List. // CandAssignableWorkList = (PLSAP_DB_LOOKUP_WORK_LIST) LookupWorkQueue.CurrentAssignableWorkList->WorkLists.Flink; if (CandAssignableWorkList != LookupWorkQueue.AnchorWorkList) { // // There is another Work List. Select the first Work Item in the // list following the anchor. // CandAssignableWorkItem = (PLSAP_DB_LOOKUP_WORK_ITEM) CandAssignableWorkList->AnchorWorkItem->Links.Flink; // // Verify that the list does not just contain the Anchor Work Item. // Work Lists on the Work Queue should never be empty. // ASSERT (CandAssignableWorkItem != CandAssignableWorkList->AnchorWorkItem); LookupWorkQueue.CurrentAssignableWorkList = CandAssignableWorkList; LookupWorkQueue.CurrentAssignableWorkItem = CandAssignableWorkItem; goto LookupUpdateAssignableWorkItemFinish; } // // All work has been assigned. Set pointers to NULL. // LookupWorkQueue.CurrentAssignableWorkList = NULL; LookupWorkQueue.CurrentAssignableWorkItem = NULL; LookupUpdateAssignableWorkItemFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupUpdateAssignableWorkItemError: goto LookupUpdateAssignableWorkItemFinish; } NTSTATUS LsapDbLookupStopProcessingWorkList( IN PLSAP_DB_LOOKUP_WORK_LIST WorkList, IN NTSTATUS TerminationStatus ) /*++ Routine Description: This function stops further work on a lookup operation at a given level and stores an error code. Arguments: WorkList - Pointer to a Work List structure describing a Lookup Sids or Lookup Names operation. TerminationStatus - Specifies the Nt Result Code to be returned by LsarLookupnames or LsarLookupSids. Return Value: NTSTATUS - Standard Nt Result Code --*/ { NTSTATUS Status = STATUS_SUCCESS; BOOLEAN AcquiredWorkQueueLock = FALSE; // // Acquire the LookupWork Queue Lock. // Status = LsapDbLookupAcquireWorkQueueLock(); if (!NT_SUCCESS(Status)) { goto LookupStopProcessingWorkListError; } AcquiredWorkQueueLock = TRUE; // // Store the termination status in the appropriate WorkList. // WorkList->Status = TerminationStatus; // // If this WorkList happens to be the one in which Work Items are being // given out, we need to prevent any further Work Items from being given // out. Update the next assignable Work Item pointers, specifying that // we should skip to the next Work List (if any). // if (WorkList == LookupWorkQueue.CurrentAssignableWorkList) { Status = LsapDbLookupUpdateAssignableWorkItem(TRUE); } if (!NT_SUCCESS(Status)) { goto LookupStopProcessingWorkListError; } LookupStopProcessingWorkListFinish: // // If necessary, release the Lookup Work Queue Lock. // if (AcquiredWorkQueueLock) { LsapDbLookupReleaseWorkQueueLock(); AcquiredWorkQueueLock = FALSE; } return(Status); LookupStopProcessingWorkListError: goto LookupStopProcessingWorkListFinish; } NTSTATUS LsapRtlExtractDomainSid( IN PSID Sid, OUT PSID *DomainSid ) /*++ Routine Description: This function extracts a Domain Sid from a Sid. Arguments: Sid - Pointer to Sid whose Domain Prefix Sid is to be extracted. DomainSid - Receives pointer to Domain Sid. This Sid will be allocated memory by MIDL_User_allocate() and should be freed via MIDL_user_free when no longer required. Return Value: NTSTATUS - Standard Nt Result Code --*/ { PSID OutputDomainSid; ULONG DomainSidLength = RtlLengthSid(Sid) - sizeof(ULONG); OutputDomainSid = MIDL_user_allocate( DomainSidLength ); if (OutputDomainSid == NULL) { return(STATUS_INSUFFICIENT_RESOURCES); } RtlCopyMemory( OutputDomainSid, Sid, DomainSidLength); (*(RtlSubAuthorityCountSid(OutputDomainSid)))--; *DomainSid = OutputDomainSid; return(STATUS_SUCCESS); } NTSTATUS LsapDbLookupReadRegistrySettings( PVOID Ignored OPTIONAL ) /*++ Routine Description: This routine is called via LsaIRegisterNotification whenever the LSA's registry settings change. Arguments: Ignored -- a callback parameter that is not used. Return Value: STATUS_SUCCCESS; --*/ { DWORD err; HKEY hKey; DWORD dwType; DWORD dwValue; DWORD dwValueSize; err = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa", 0, // reserved KEY_QUERY_VALUE, &hKey ); if ( ERROR_SUCCESS == err ) { dwValueSize = sizeof(dwValue); err = RegQueryValueExW( hKey, L"AllowExtendedDownlevelLookup", NULL, //reserved, &dwType, (PBYTE)&dwValue, &dwValueSize ); if ( (ERROR_SUCCESS == err) && (dwValue != 0) ) { LsapAllowExtendedDownlevelLookup = TRUE; } else { // Reset the value LsapAllowExtendedDownlevelLookup = FALSE; } dwValueSize = sizeof(dwValue); err = RegQueryValueExW( hKey, L"LookupLogLevel", NULL, //reserved, &dwType, (PBYTE)&dwValue, &dwValueSize ); if ( ERROR_SUCCESS == err) { LsapLookupLogLevel = dwValue; } else { // default value LsapLookupLogLevel = 0; } #if DBG if (LsapLookupLogLevel > 0) { LsapGlobalFlag |= LSAP_DIAG_DB_LOOKUP_WORK_LIST; } else { LsapGlobalFlag &= ~LSAP_DIAG_DB_LOOKUP_WORK_LIST; } #endif dwValueSize = sizeof(DWORD); dwValue = 0; err = RegQueryValueExW( hKey, L"LsaLookupReturnSidTypeDeleted", NULL, //reserved, &dwType, (PBYTE)&dwValue, &dwValueSize ); if ( (ERROR_SUCCESS == err) && (dwType == REG_DWORD) && (dwValue != 0) ) { LsapReturnSidTypeDeleted = TRUE; } else { LsapReturnSidTypeDeleted = FALSE; } dwValueSize = sizeof(DWORD); dwValue = 0; err = RegQueryValueExW( hKey, L"LsaLookupRestrictIsolatedNameLevel", NULL, //reserved, &dwType, (PBYTE)&dwValue, &dwValueSize ); if ( (ERROR_SUCCESS == err) && (dwType == REG_DWORD) && (dwValue != 0) ) { LsapLookupRestrictIsolatedNameLevel = TRUE; } else { LsapLookupRestrictIsolatedNameLevel = FALSE; } LsapSidCacheReadParameters(hKey); RegCloseKey( hKey ); } return STATUS_SUCCESS; UNREFERENCED_PARAMETER(Ignored); } NTSTATUS LsapDbLookupInitialize( ) /*++ Routine Description: This function performs initialization of the data structures used by Lookup operations. These structures are as follows: LookupWorkQueue - This is a doubly-linked list of Lookup Work Lists. There is one Work List for each Lookup operation in progress on a DC. Each Lookup Work List contains a doubly-linked list of Lookup Work Items. Each Lookup Work Item specifies a Trusted Domain and array of Sids or Names to be looked up in that domain. Access to this queue is controlled via the Lookup Work Queue Lock. Trusted Domain List - This is a doubly-linked list which contains the Trust Information (i.e. Domain Sid and Domain Name) of each Trusted Domain. The purpose of this list is to enable fast identification of Trusted Domain Sids and Names, without having recourse to open or enumerate Trusted Domain objects. This list is initialized when the system is loaded, and is updated directly when a Trusted Domain object is created or deleted. Arguments: None Return Value: NTSTATUS - Standard Nt Result Code. --*/ { NTSTATUS Status = STATUS_SUCCESS; Status = LsapDbLookupInitPolicyCache(); if (!NT_SUCCESS(Status)) { return Status; } // // Arrange to be notified when the parameter settings change // LsaIRegisterNotification( LsapDbLookupReadRegistrySettings, 0, NOTIFIER_TYPE_NOTIFY_EVENT, NOTIFY_CLASS_REGISTRY_CHANGE, 0, 0, 0 ); Status = LsapDbLookupReadRegistrySettings(NULL); if (!NT_SUCCESS(Status)) { return Status; } // // Perform initialization specific to DC's // if (LsapProductType != NtProductLanManNt) { goto LookupInitializeFinish; } // // Create the Lookup Work List initiated event. // Status = NtCreateEvent( &LsapDbLookupStartedEvent, EVENT_QUERY_STATE | EVENT_MODIFY_STATE | SYNCHRONIZE, NULL, SynchronizationEvent, FALSE ); if (!NT_SUCCESS(Status)) { goto LookupInitializeError; } // // Initialize the Lookup Work Queue // Status = LsapDbLookupInitializeWorkQueue(); if (!NT_SUCCESS(Status)) { goto LookupInitializeError; } LookupInitializeFinish: return(Status); LookupInitializeError: goto LookupInitializeFinish; } NTSTATUS LsapDbLookupInitializeWorkQueue( ) /*++ Routine Description: This function initializes the Lookup Work Queue. It is only called for DC's. Arguments: None Return Value: NTSTATUS - Standard Nt Result Code. --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_LIST AnchorWorkList = NULL; // // Initialize the Work Queue Lock. // Status = SafeInitializeCriticalSection(&LookupWorkQueue.Lock, ( DWORD )LOOKUP_WORK_QUEUE_LOCK_ENUM ); if (!NT_SUCCESS(Status)) { LsapLogError( "LsapDbLookupInitialize: RtlInit..CritSec returned 0x%lx\n", Status ); return Status; } // // Initialize the Work Queue to comprise the Anchor Work List // doubly-linked to itself. // LookupWorkQueue.AnchorWorkList = &LookupWorkQueue.DummyAnchorWorkList; AnchorWorkList = &LookupWorkQueue.DummyAnchorWorkList; // // Set the currently assignable Work List and Work Item pointers to // NULL to indicate that there is no work to to. // LookupWorkQueue.CurrentAssignableWorkList = NULL; LookupWorkQueue.CurrentAssignableWorkItem = NULL; // // Initialize the Anchor Work List. // Status = LsapDbLookupInitializeWorkList(AnchorWorkList); if (!NT_SUCCESS(Status)) { goto LookupInitializeWorkQueueError; } AnchorWorkList->WorkLists.Flink = (PLIST_ENTRY) AnchorWorkList; AnchorWorkList->WorkLists.Blink = (PLIST_ENTRY) AnchorWorkList; // // Set the thread counts. // LookupWorkQueue.ActiveChildThreadCount = (ULONG) 0; LookupWorkQueue.MaximumChildThreadCount = LSAP_DB_LOOKUP_MAX_THREAD_COUNT; LookupWorkQueue.MaximumRetainedChildThreadCount = LSAP_DB_LOOKUP_MAX_RET_THREAD_COUNT; LookupInitializeWorkQueueFinish: return(Status); LookupInitializeWorkQueueError: goto LookupInitializeWorkQueueFinish; } NTSTATUS LsapDbLookupInitializeWorkList( OUT PLSAP_DB_LOOKUP_WORK_LIST WorkList ) /*++ Routine Description: This function initializes an empty Work List structure. The Work List link fields are not set by this function. Arguments: WorkList - Points to Work List structure to be initialized. Return Value: NTSTATUS - Standard Nt Result Code. --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSAP_DB_LOOKUP_WORK_ITEM AnchorWorkItem = NULL; // // Initialize miscellaneous fields in the Work List header. // WorkList->WorkLists.Flink = NULL; WorkList->WorkLists.Blink = NULL; WorkList->WorkItemCount = (ULONG) 0; WorkList->CompletedWorkItemCount = (ULONG) 0; WorkList->State = InactiveWorkList; WorkList->Status = STATUS_SUCCESS; WorkList->NonFatalStatus = STATUS_SUCCESS; // // Init the completion event // Status = NtCreateEvent( &WorkList->LookupCompleteEvent, EVENT_QUERY_STATE | EVENT_MODIFY_STATE | SYNCHRONIZE, NULL, SynchronizationEvent, FALSE ); if (!NT_SUCCESS(Status)) { goto LookupInitializeWorkListError; } // // Initialize the Work List's list of Work Items to comprise the // Anchor Work Item doubly-linked to itself. // WorkList->AnchorWorkItem = &WorkList->DummyAnchorWorkItem; AnchorWorkItem = WorkList->AnchorWorkItem; AnchorWorkItem->Links.Flink = (PLIST_ENTRY) AnchorWorkItem; AnchorWorkItem->Links.Blink = (PLIST_ENTRY) AnchorWorkItem; // // Initialize the Anchor Work Item. // Status = LsapDbLookupInitializeWorkItem(AnchorWorkItem); if (!NT_SUCCESS(Status)) { goto LookupInitializeWorkListError; } LookupInitializeWorkListFinish: return(Status); LookupInitializeWorkListError: goto LookupInitializeWorkListFinish; } NTSTATUS LsapDbLookupInitializeWorkItem( OUT PLSAP_DB_LOOKUP_WORK_ITEM WorkItem ) /*++ Routine Description: This function initializes an empty Work Item structure. The Work Item link fields are not set by this function. Arguments: WorkItem - Points to Work Item structure to be initialized. Return Value: NTSTATUS - Standard Nt Result Code. --*/ { NTSTATUS Status = STATUS_SUCCESS; WorkItem->UsedCount = (ULONG) 0; WorkItem->MaximumCount = (ULONG) 0; WorkItem->State = NonAssignableWorkItem; WorkItem->Properties = (ULONG) 0; return(Status); } NTSTATUS LsapDbLookupLocalDomains( OUT PLSAPR_TRUST_INFORMATION BuiltInDomainTrustInformation, OUT PLSAPR_TRUST_INFORMATION_EX AccountDomainTrustInformation, OUT PLSAPR_TRUST_INFORMATION_EX PrimaryDomainTrustInformation ) /*++ Routine Description: This function returns Trust Information for the Local Domains. Arguments: BuiltInDomainTrustInformation - Pointer to structure that will receive the Name and Sid of the Built-In Domain. Unlike the other two parameters, the Name and Sid buffers for the Built-in Domain are NOT freed after use, because they are global data constants. AccountDomainTrustInformation - Pointer to structure that will receive the Name and Sid of the Accounts Domain. The Name and Sid buffers must be freed after use via MIDL_user_free. PrimaryDomainTrustInformation - Pointer to structure that will receive the Name and Sid of the Accounts Domain. The Name and Sid buffers must be freed after use via MIDL_user_free. --*/ { NTSTATUS Status = STATUS_SUCCESS; ULONG WellKnownSidIndex; PLSAPR_POLICY_ACCOUNT_DOM_INFO PolicyAccountDomainInfo = NULL; PLSAPR_POLICY_DNS_DOMAIN_INFO PolicyDnsDomainInfo = NULL; LPWSTR NameBuffer = NULL; LPWSTR DnsDomainNameBuffer = NULL; BOOLEAN fFreeNameBuffer = FALSE; BOOLEAN fFreeDnsDomainNameBuffer = FALSE; RtlZeroMemory( AccountDomainTrustInformation, sizeof( LSAPR_TRUST_INFORMATION_EX ) ); RtlZeroMemory( PrimaryDomainTrustInformation, sizeof( LSAPR_TRUST_INFORMATION_EX ) ); // // Obtain the Name and Sid of the Built-in Domain // BuiltInDomainTrustInformation->Sid = LsapBuiltInDomainSid; Status = STATUS_INTERNAL_DB_ERROR; if (!LsapDbLookupIndexWellKnownSid( LsapBuiltInDomainSid, (PLSAP_WELL_KNOWN_SID_INDEX) &WellKnownSidIndex )) { goto LookupLocalDomainsError; } Status = STATUS_SUCCESS; // // Obtain the name of the Built In Domain from the table of // Well Known Sids. It suffices to copy the Unicode structures // since we do not need a separate copy of the name buffer. // BuiltInDomainTrustInformation->Name = *((PLSAPR_UNICODE_STRING) LsapDbWellKnownSidName(WellKnownSidIndex)); // // Now obtain the Name and Sid of the Account Domain. // The Sid and Name of the Account Domain are both configurable, and // we need to obtain them from the Policy Object. Now obtain the // Account Domain Sid and Name by querying the appropriate // Policy Information Class. // Status = LsapDbLookupGetDomainInfo((POLICY_ACCOUNT_DOMAIN_INFO **)&PolicyAccountDomainInfo, (POLICY_DNS_DOMAIN_INFO **)&PolicyDnsDomainInfo); if (!NT_SUCCESS(Status)) { goto LookupLocalDomainsError; } // // Set up the Trust Information structure for the Account Domain. // AccountDomainTrustInformation->Sid = PolicyAccountDomainInfo->DomainSid; RtlCopyMemory( &AccountDomainTrustInformation->FlatName, &PolicyAccountDomainInfo->DomainName, sizeof (UNICODE_STRING) ); // // If the account domain is the same as the Dns domain info, return the // dns domain name as the account domain name // if ( PolicyDnsDomainInfo->Sid && PolicyAccountDomainInfo->DomainSid && RtlEqualSid( PolicyDnsDomainInfo->Sid, PolicyAccountDomainInfo->DomainSid ) && RtlEqualUnicodeString( (PUNICODE_STRING)&PolicyDnsDomainInfo->Name, (PUNICODE_STRING)&PolicyAccountDomainInfo->DomainName, TRUE) ) { AccountDomainTrustInformation->DomainName = PolicyDnsDomainInfo->DnsDomainName; AccountDomainTrustInformation->DomainNamesDiffer = TRUE; } else { AccountDomainTrustInformation->DomainName = AccountDomainTrustInformation->FlatName; } // // Now obtain the Name and Sid of the Primary Domain (if any) // The Sid and Name of the Primary Domain are both configurable, and // we need to obtain them from the Policy Object. Now obtain the // Account Domain Sid and Name by querying the appropriate // Policy Information Class. // if ( NT_SUCCESS( Status ) ) { // // Set up the Trust Information structure for the Primary Domain. // PrimaryDomainTrustInformation->Sid = PolicyDnsDomainInfo->Sid; RtlCopyMemory( &PrimaryDomainTrustInformation->FlatName, &PolicyDnsDomainInfo->Name, sizeof (UNICODE_STRING) ); RtlCopyMemory( &PrimaryDomainTrustInformation->DomainName, &PolicyDnsDomainInfo->DnsDomainName, sizeof( UNICODE_STRING ) ); Status = LsapNullTerminateUnicodeString( (PUNICODE_STRING)&PolicyDnsDomainInfo->DnsDomainName, &DnsDomainNameBuffer, &fFreeDnsDomainNameBuffer ); if (NT_SUCCESS(Status)) { Status = LsapNullTerminateUnicodeString( (PUNICODE_STRING)&PolicyDnsDomainInfo->Name, &NameBuffer, &fFreeNameBuffer ); } if (NT_SUCCESS(Status)) { if ( !DnsNameCompare_W( DnsDomainNameBuffer, NameBuffer) ) { PrimaryDomainTrustInformation->DomainNamesDiffer = TRUE; } } if ( fFreeDnsDomainNameBuffer ) { midl_user_free( DnsDomainNameBuffer ); } if ( fFreeNameBuffer ) { midl_user_free( NameBuffer ); } } LookupLocalDomainsFinish: return(Status); LookupLocalDomainsError: goto LookupLocalDomainsFinish; } BOOLEAN LsapIsBuiltinDomain( IN PSID Sid ) { return RtlEqualSid( Sid, LsapBuiltInDomainSid ); } BOOLEAN LsapIsDsDomainByNetbiosName( WCHAR *NetbiosName ) /*++ Routine Description This routine determines if the (domain) netbios name passed in is an accounts domain that is reprssented in the DS (ie is at least an nt5 domain). Parameters: NetbiosName -- a valid string --*/ { NTSTATUS NtStatus; PDSNAME dsname; ULONG len; ASSERT( Sid ); // Ask the DS if it has heard of this name NtStatus = MatchCrossRefByNetbiosName( NetbiosName, NULL, &len ); if ( NT_SUCCESS(NtStatus) ) { // // The domain was found in the ds // return TRUE; } return FALSE; } NTSTATUS LsapGetDomainNameBySid( IN PSID Sid, OUT PUNICODE_STRING DomainName ) /*++ Routine Description Given a sid, this routine will return the netbios name of the domain, if the domain is stored in the ds Parameters: Sid -- domain sid Domain Name -- domain name allocated from MIDL --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; LPWSTR Name = NULL; DSNAME dsname = {0}; ULONG len = 0; ASSERT( Sid ); ASSERT( DomainName ); dsname.structLen = DSNameSizeFromLen(0); len = min( sizeof( NT4SID ), RtlLengthSid( Sid ) ); memcpy( &dsname.Sid, Sid, len ); dsname.SidLen = len; NtStatus = FindNetbiosDomainName( &dsname, NULL, &len ); if ( NT_SUCCESS( NtStatus ) ) { Name = MIDL_user_allocate( len ); if ( !Name ) { NtStatus = STATUS_NO_MEMORY; goto Cleanup; } NtStatus = FindNetbiosDomainName( &dsname, Name, &len ); if ( NT_SUCCESS( NtStatus ) ) { RtlInitUnicodeString( DomainName, Name ); } else { MIDL_user_free( Name ); } } if ( !NT_SUCCESS( NtStatus ) ) { // // Something failed? Assume not a nt5 domain // NtStatus = STATUS_NO_SUCH_DOMAIN; } Cleanup: return NtStatus; } NTSTATUS LsapGetDomainSidByNetbiosName( IN LPWSTR NetbiosName, OUT PSID *Sid ) /*++ Routine Description Given a netbios name, this routine will return the sid of the domain, if it exists. Parameters: NetbiosName -- the name of the domain Sid -- domain sid allocated from MIDL --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; DSNAME *dsname = NULL; ULONG len = 0; ASSERT( NetbiosName ); ASSERT( Sid ); // Init the out param *Sid = NULL; // Check with the DS NtStatus = MatchDomainDnByNetbiosName( NetbiosName, NULL, &len ); if ( NT_SUCCESS( NtStatus ) ) { SafeAllocaAllocate( dsname, len ); if ( dsname == NULL ) { NtStatus = STATUS_NO_MEMORY; goto Cleanup; } NtStatus = MatchDomainDnByNetbiosName( NetbiosName, dsname, &len ); if ( NT_SUCCESS( NtStatus ) && (dsname->SidLen > 0) ) { len = RtlLengthSid( &dsname->Sid ); *Sid = midl_user_allocate( len ); if ( !(*Sid) ) { SafeAllocaFree( dsname ); NtStatus = STATUS_NO_MEMORY; goto Cleanup; } RtlCopySid( len, *Sid, &dsname->Sid ); } SafeAllocaFree( dsname ); } if ( !(*Sid) ) { // // Something failed? Assume not a nt5 domain // NtStatus = STATUS_NO_SUCH_DOMAIN; } Cleanup: return NtStatus; } NTSTATUS LsapGetDomainSidByDnsName( IN LPWSTR DnsName, OUT PSID *Sid ) /*++ Routine Description Given a dns name, this routine will return the sid of the domain, if it exists. Parameters: DnsName -- the name of the domain Sid -- domain sid allocated from MIDL --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; DSNAME *dsname = NULL; ULONG len = 0; ASSERT( DnsName ); ASSERT( Sid ); // Init the out param *Sid = NULL; // Check with the DS NtStatus = MatchDomainDnByDnsName( DnsName, NULL, &len ); if ( NT_SUCCESS( NtStatus ) ) { SafeAllocaAllocate( dsname, len ); if ( dsname == NULL ) { NtStatus = STATUS_NO_MEMORY; goto Cleanup; } NtStatus = MatchDomainDnByDnsName( DnsName, dsname, &len ); if ( NT_SUCCESS( NtStatus ) && (dsname->SidLen > 0) ) { len = RtlLengthSid( &dsname->Sid ); *Sid = midl_user_allocate( len ); if ( !(*Sid) ) { SafeAllocaFree( dsname ); NtStatus = STATUS_NO_MEMORY; goto Cleanup; } RtlCopySid( len, *Sid, &dsname->Sid ); } SafeAllocaFree( dsname ); } if ( !(*Sid) ) { // // Something failed? Assume not a nt5 domain // NtStatus = STATUS_NO_SUCH_DOMAIN; } Cleanup: return NtStatus; } VOID LsapConvertExTrustToOriginal( IN OUT PLSAPR_TRUST_INFORMATION TrustInformation, IN PLSAPR_TRUST_INFORMATION_EX TrustInformationEx ) { RtlZeroMemory( TrustInformation, sizeof(LSAPR_TRUST_INFORMATION) ); RtlCopyMemory( &TrustInformation->Name, &TrustInformationEx->FlatName, sizeof(UNICODE_STRING) ); TrustInformation->Sid = TrustInformationEx->Sid; return; } VOID LsapConvertTrustToEx( IN OUT PLSAPR_TRUST_INFORMATION_EX TrustInformationEx, IN PLSAPR_TRUST_INFORMATION TrustInformation ) { RtlZeroMemory( TrustInformationEx, sizeof(LSAPR_TRUST_INFORMATION_EX) ); RtlCopyMemory( &TrustInformationEx->FlatName, &TrustInformation->Name, sizeof(UNICODE_STRING) ); TrustInformationEx->Sid = TrustInformation->Sid; } NTSTATUS LsapDbOpenPolicyGc ( OUT HANDLE *LsaPolicyHandle ) { NTSTATUS Status = STATUS_SUCCESS; DWORD WinError = ERROR_SUCCESS; PDOMAIN_CONTROLLER_INFO DcInfo = NULL; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING DcName; ASSERT( LsaPolicyHandle ); InitializeObjectAttributes( &ObjectAttributes, NULL, 0L, NULL, NULL ); // // Find a GC in our forest // WinError = DsGetDcName( NULL, // Local computer NULL, // Local domain NULL, // no domain guid NULL, // site doesn't matter (DS_GC_SERVER_REQUIRED | DS_RETURN_DNS_NAME | DS_DIRECTORY_SERVICE_REQUIRED), &DcInfo ); if ( ERROR_SUCCESS != WinError ) { // // No GC? // Status = STATUS_DS_GC_NOT_AVAILABLE; goto Finish; } // // Open it // RtlInitUnicodeString( &DcName, DcInfo->DomainControllerName ); Status = LsaOpenPolicy( &DcName, &ObjectAttributes, POLICY_LOOKUP_NAMES, LsaPolicyHandle ); if ( !NT_SUCCESS( Status ) ) { // // A problem binding means that we can't access the GC for what ever // reason so return that code // Status = STATUS_DS_GC_NOT_AVAILABLE; goto Finish; } Finish: if ( DcInfo ) { NetApiBufferFree( DcInfo ); } return Status; } BOOLEAN LsapRevisionCanHandleNewErrorCodes ( IN ULONG Revision ) { return (Revision != LSA_CLIENT_PRE_NT5); } // // The LSA Lookup Policy Cache // // // During lookup operations the code frequently needs to know the current // Account and DNS domain policy information. Since this information is // largely static, it is suited for caching. // // LsapDbPolicyCache contains the cached information. When the policy changes, // a callback function is called (LsapDbLookupDomainCacheNotify) that NULL's // out this value. The existing global value is freed in one hour. The next // time LsapDbLookupGetDomainInfo is called, new values are placed in the cache. // Note that this scheme does not require any locks. // // // This typedef describes the format of the cache // typedef struct _LSAP_DB_DOMAIN_CACHE_TYPE { PPOLICY_ACCOUNT_DOMAIN_INFO Account; PPOLICY_DNS_DOMAIN_INFO Dns; }LSAP_DB_DOMAIN_CACHE_TYPE, *PLSAP_DB_DOMAIN_CACHE_TYPE; // // This is global cache value, gaurded in code by InterlockedExchangePointer // PLSAP_DB_DOMAIN_CACHE_TYPE LsapDbPolicyCache = NULL; NTSTATUS LsapDbLookupFreeDomainCache( PVOID p ) /*++ Routine Description: This routine frees a cached copy of the LSA Lookup Policy Cache. Arguments: p -- a valid pointer to LSAP_DB_DOMAIN_CACHE_TYPE Return Values: STATUS_SUCCESS --*/ { ASSERT(p); if (p) { PLSAP_DB_DOMAIN_CACHE_TYPE Cache = (PLSAP_DB_DOMAIN_CACHE_TYPE)p; if (Cache->Account) { LsaIFree_LSAPR_POLICY_INFORMATION(PolicyAccountDomainInformation, (PLSAPR_POLICY_INFORMATION) Cache->Account); } if (Cache->Dns) { LsaIFree_LSAPR_POLICY_INFORMATION(PolicyDnsDomainInformation, (PLSAPR_POLICY_INFORMATION) Cache->Dns); } LocalFree(p); } return STATUS_SUCCESS; } NTSTATUS LsapDbLookupBuildDomainCache( OUT PLSAP_DB_DOMAIN_CACHE_TYPE *pCache OPTIONAL ) /*++ Routine Description: This routine queries the LSA to find out the current policy settings (Account and DNS domain) and then places the information in the LSA Lookup Policy cache. Note that the current values are freed after 1 hour. Arguments: pCache -- the value of the new cache created by this routine; caller should not free Return Values: STATUS_SUCCESS, or a resource error --*/ { NTSTATUS Status = STATUS_SUCCESS; PPOLICY_ACCOUNT_DOMAIN_INFO LocalAccountDomainInfo = NULL; PPOLICY_DNS_DOMAIN_INFO LocalDnsDomainInfo = NULL; PLSAP_DB_DOMAIN_CACHE_TYPE NewCache = NULL, OldCache = NULL; NewCache = LocalAlloc(LMEM_ZEROINIT, sizeof(*NewCache)); if (NULL == NewCache) { return STATUS_NO_MEMORY; } Status = LsaIQueryInformationPolicyTrusted(PolicyAccountDomainInformation, (PLSAPR_POLICY_INFORMATION *) &LocalAccountDomainInfo); if (NT_SUCCESS(Status)) { Status = LsaIQueryInformationPolicyTrusted(PolicyDnsDomainInformation, (PLSAPR_POLICY_INFORMATION *) &LocalDnsDomainInfo); } if (NT_SUCCESS(Status)) { ASSERT(NULL != LocalAccountDomainInfo); ASSERT(NULL != LocalDnsDomainInfo); NewCache->Account = LocalAccountDomainInfo; LocalAccountDomainInfo = NULL; NewCache->Dns = LocalDnsDomainInfo; LocalDnsDomainInfo = NULL; // // Return the new cache to caller // if (pCache) { *pCache = NewCache; } // // Carefully move new cache to global pointer // OldCache = InterlockedExchangePointer(&LsapDbPolicyCache, NewCache); // // Don't free the NewCache since it is now in the global space // NewCache = NULL; if (OldCache) { LsaIRegisterNotification(LsapDbLookupFreeDomainCache, OldCache, NOTIFIER_TYPE_INTERVAL, 0, // no class NOTIFIER_FLAG_ONE_SHOT, 60 * 60, // free in one hour NULL); // no handle // // N.B Memory leak of OldCache if failed to register task // } } if (LocalAccountDomainInfo) { LsaIFree_LSAPR_POLICY_INFORMATION(PolicyAccountDomainInformation, (PLSAPR_POLICY_INFORMATION) LocalAccountDomainInfo); } if (LocalDnsDomainInfo) { LsaIFree_LSAPR_POLICY_INFORMATION(PolicyDnsDomainInformation, (PLSAPR_POLICY_INFORMATION) LocalDnsDomainInfo); } if (NewCache) { LocalFree(NewCache); } return Status; } NTSTATUS LsapDbLookupGetDomainInfo( OUT PPOLICY_ACCOUNT_DOMAIN_INFO *AccountDomainInfo OPTIONAL, OUT PPOLICY_DNS_DOMAIN_INFO *DnsDomainInfo OPTIONAL ) /*++ Routine Description: This routine returns to the caller a reference to the global copy of the cached Account or DNS domain policy. Caller must not free. Arguments: p -- a valid pointer to LSAP_DB_DOMAIN_CACHE_TYPE Return Values: STATUS_SUCCESS --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; // // Get a copy of the global cache // PLSAP_DB_DOMAIN_CACHE_TYPE LocalPolicyCache = LsapDbPolicyCache; // // If the cache is empty, fill it // if (NULL == LocalPolicyCache) { // // This will only fail on resource error // NtStatus = LsapDbLookupBuildDomainCache(&LocalPolicyCache); if (!NT_SUCCESS(NtStatus)) { return NtStatus; } } // // We must have valid values // ASSERT(NULL != LocalPolicyCache); ASSERT(NULL != LocalPolicyCache->Account); ASSERT(NULL != LocalPolicyCache->Dns); if (AccountDomainInfo) { *AccountDomainInfo = LocalPolicyCache->Account; } if (DnsDomainInfo) { *DnsDomainInfo = LocalPolicyCache->Dns; } return NtStatus; } NTSTATUS LsapDbLookupDomainCacheNotify( PVOID p ) /*++ Routine Description: This routine is called when a change occurs to the system's policy. This routine rebuilds the LSA policy cache. Arguments: p -- ignored. Return Values: STATUS_SUCCESS, or a resource error --*/ { PLSAP_DB_DOMAIN_CACHE_TYPE OldCache; // // Invalidate the current cache // OldCache = InterlockedExchangePointer(&LsapDbPolicyCache, NULL); if (OldCache) { // // Free the memory in an hour // LsaIRegisterNotification(LsapDbLookupFreeDomainCache, OldCache, NOTIFIER_TYPE_INTERVAL, 0, // no class NOTIFIER_FLAG_ONE_SHOT, 60 * 60, // free in one hour NULL); // no handle // // N.B Memory leak of OldCache if failed to register task // } return STATUS_SUCCESS; UNREFERENCED_PARAMETER(p); } NTSTATUS LsapDbLookupInitPolicyCache( VOID ) /*++ Routine Description: This routine initializes the LSA Lookup Policy Cache. Arguments: None. Return Values: STATUS_SUCCESS, or a resources error --*/ { NTSTATUS Status = STATUS_SUCCESS; HANDLE hEvent = NULL; PVOID fItem = NULL; // // Create event to be used to notify us of changes to the domain // policy // hEvent = CreateEvent(NULL, // use default access control FALSE, // reset automatically FALSE, // start off non-signalled NULL ); if (NULL == hEvent) { return STATUS_INSUFFICIENT_RESOURCES; } // // Set the function to be called on change // fItem = LsaIRegisterNotification(LsapDbLookupDomainCacheNotify, NULL, NOTIFIER_TYPE_HANDLE_WAIT, 0, // no class, 0, 0, hEvent); if (NULL == fItem) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } // // Set the event to be set on change // Status = LsaRegisterPolicyChangeNotification(PolicyNotifyAccountDomainInformation, hEvent); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = LsaRegisterPolicyChangeNotification(PolicyNotifyDnsDomainInformation, hEvent); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = LsapDbLookupBuildDomainCache(NULL); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Success! // fItem = NULL; hEvent = NULL; Cleanup: if (fItem) { LsaICancelNotification(fItem); } if (hEvent) { CloseHandle(hEvent); } return Status; } BOOLEAN LsapDbIsStatusConnectionFailure( NTSTATUS st ) /*++ Routine Description: This routine returns TRUE if the status provided indicates a trust or connection error that prevented the lookup from succeeding. Arguments: None. Return Values: TRUE, FALSE --*/ { switch (st) { case STATUS_TRUSTED_DOMAIN_FAILURE: case STATUS_TRUSTED_RELATIONSHIP_FAILURE: case STATUS_DS_GC_NOT_AVAILABLE: return TRUE; } return FALSE; } NTSTATUS LsapDbLookupAccessCheck( IN LSAPR_HANDLE PolicyHandle ) /*++ Routine Description: This routine performs an access on PolicyHandle to see if the handle has the right to perform a lookup. Arguments: PolicyHanlde -- an RPC context handle Return Values: STATUS_SUCCESS, STATUS_ACCESS_DENIED, other resource errors --*/ { NTSTATUS Status = STATUS_SUCCESS; if (PolicyHandle) { // // Acquire the Lsa Database lock. Verify that the connection handle is // valid, is of the expected type and has all of the desired accesses // granted. Reference he handle. // Status = LsapDbReferenceObject( PolicyHandle, POLICY_LOOKUP_NAMES, PolicyObject, NullObject, LSAP_DB_READ_ONLY_TRANSACTION | LSAP_DB_NO_DS_OP_TRANSACTION ); if (NT_SUCCESS(Status)) { // // We can dereference the original PolicyHandle and release the lock on // the LSA Database; if we need to access the database again, we'll // use the trusted Lsa handle and the appropriate API will take // the lock as required. // Status = LsapDbDereferenceObject( &PolicyHandle, PolicyObject, NullObject, LSAP_DB_READ_ONLY_TRANSACTION | LSAP_DB_NO_DS_OP_TRANSACTION, (SECURITY_DB_DELTA_TYPE) 0, Status ); } } else { // // Only NETLOGON can call without a policy handle // ULONG RpcErr; ULONG AuthnLevel = 0; ULONG AuthnSvc = 0; RpcErr = RpcBindingInqAuthClient( NULL, NULL, // no privileges NULL, // no server principal name &AuthnLevel, &AuthnSvc, NULL // no authorization level ); // // If it as authenticated at level packet integrity or better // and is done with the netlogon package, allow it through // if ((RpcErr == ERROR_SUCCESS) && (AuthnLevel >= RPC_C_AUTHN_LEVEL_PKT_INTEGRITY) && (AuthnSvc == RPC_C_AUTHN_NETLOGON )) { Status = STATUS_SUCCESS; } else { Status = STATUS_ACCESS_DENIED; } } return Status; } NTSTATUS LsapDbLookupGetServerConnection( IN LSAPR_TRUST_INFORMATION_EX *TrustInfo, IN DWORD Flags, IN LSAP_LOOKUP_LEVEL LookupLevel, IN PLARGE_INTEGER FailedSessionSetupTime, OPTIONAL OUT LPWSTR *ServerName, OUT NL_OS_VERSION *ServerOsVersion, OUT LPWSTR *ServerPrincipalName, OUT PVOID *ClientContext, OUT ULONG *AuthnLevel, OUT LSA_HANDLE *PolicyHandle, OUT PLSAP_BINDING_CACHE_ENTRY * CachedPolicyEntry, OUT PLARGE_INTEGER SessionSetupTime ) /*++ Routine Description: This routines returns connection information to a domain controller in the domain specified by TrustInfo, if one can be found. Primarily, this routine uses I_NetLogonGetAuthData to obtain the secure channel DC. Once a DC is found, if the DC is Whistler and we have a client context, then we can exit since that is what is needed by the Whister protocol. Otherwise, the call falls back to obtaining an LSA policy handle. OUT Parameters: -------------- OUT parameters are only allocated on success. ServerName, ServerOsVersion are returned on all successes. ServerPrincipalNames, ClientContext, AuthLevel are returned when the target DC of the sucure channel is .NET or greater. This is to allow an RPC call to use the authentication information directly. Otherwise, if the DC is not Whistler or greater, these OUT parameters are NULL. PolicyHandle is returned when the target DC is a revision less than .NET and the "handle cache" is not in effect. The "handle cache" is used only for secure channels to trusted domains (ie DC to DC communication). Otherwise this parameter is set to NULL on return CachedPolicyEntry is returned when the target DC is a revision less than .NET and the "handle cache" is in effect. Otherwise, this parameter is set to NULL on return. SessionSetupTime is the time NETLOGON established the session time. This is used to know whether to reset the connection, or not. Retry Logic: ----------- Since NETLOGON maintains a cache of security connections to trusted domain DC's, it is possible that the cache becomes out of date. In this case, the ClientContext returned won't work as an authenticated blob against the target DC and RPC calls will fail with STATUS_ACCESS_DENIED or ( STATUS_RPC_SERVER_UNAVAILABLE if the target server is not alive). So, the lookup code will take whatever ClientContext is returned from NETLOGON (via I_NetLogonGetAuthData) and attempt to use it for an RPC call. If the RPC calls fails with access denied or server unavailable then I_NetLogonGetAuthData is called again with the FailedSessionSetupTime. This tells NETLOGON that the client context returned was stale. NETLOGON will then attempt to reset the secure channel to the target domain in order to obtain a new ClientContext for the lookup code to use. Also, when talking to an .NET or greater server, the RPC call to lookup the names is outside this function thus must communicate via the IN parameter FailedSessionSetupTime as to whether the previously given context was good or not. Here are the algorithms. Each one is ultimately what happens when the target domain is the particular OS in the title. So the algorithm under NT4 is what this code does when talking to a NT4 domain, etc. NT4 (or above when signing and sealing is turned off) ------------------------------------------------------ FailedSessionSetupTime = NULL; RetryNetLogon: Call I_NetLogonGetAuthData(FailedSessionSetupTime, &ServerName, &SessionSetupTime) if err return Look in LSA policy handle cache for a match on target domain if found call LsaQueryInformationPolicy to verify handle if !err return success else remove handle from cache and continue endif endif call LsaOpenPolicy(ServerName, POLICY_LOOKUP) if err if (FailedSessionSetupTime == NULL) FailedSessionSetupTime = &SessionSetupTime goto RetryNetLogon; else return failure endif else add handle to cache return success endif Window2000 ---------- FailedSessionSetupTime = NULL RetryNetLogon: Call I_NetLogonGetAuthData(FailedSessionSetupTime, &ServerName, &SessionSetupTime, &ClientContext) if err return Look in LSA policy handle cache for a match on target domain if found call LsaQueryInformationPolicy(ServerName) to verify handle if !err return success else remove handle from cache endif endif // The reasoning for the PolicyAccess is that at one point the process may // go off machine as Anonymous and may not have access to POLICY_LOOKUP so // don't ask for it. PolicyAccess = VIEW_LOCAL_INFORMATION; RetryLsaOpenPolicy: call LsaOpenPolicy(ServerName, PolicyAccess) if server unavailable or access denied // if we've already retried, bail if FailedSessionSetupTime != NULL return failure // retry, indicating the bad context FailedSessionSetupTime = &SessionSetupTime; goto RetryNetLogon else if success Set ClientContext on RPC binding Call LsaQueryInformationPolicy if access denied or authentication service unknown if PolicyAccess == POLICY_LOOKUP // The ClientContext isn't working, retry FailedSessionSetupTime = &SessionSetupTime; goto RetryNetLogon else // The reasoning here is that the target server may not have // understood the NETLOGON authentication package so retry // asking for POLICY_LOOKUP PolicyAccess = POLICY_LOOKUP goto RetryLsaOpenPolicy endif else if success put handle in cache and return else return failure endif else return failure endif .NET ---- Call I_NetLogonGetAuthData(FailedSessionSetupTime, &ServerName, &SessionSetupTime, &ClientContext) if !err Call LsaILookupNames/SidsWithCreds(ServerName, ClientContext) if err is access denied or server unavailable FailedSessionSetupTime = &SessionSetupTime; Call I_NetLogonGetAuthData(FailedSessionSetupTime, &ServerName, &SessionSetupTime, &ClientContext) if !err LsaILookupNames/SidsWithCreds(ServerName, ClientContext) Arguments: TrustInfo -- contains the destination domain; at least one of DomainName or FlatName must be present; Sid is optional. Flags -- None. LookupLevel -- the kind of chaining being performed FailedSessionSetupTime -- the SessionSetupTime of information returned from NETLOGON that didn't work. Server -- out, the destination server name, NULL terminated ServerOsVersion -- out, the OS version ServerPrincipalName, ClientContext, AuthnLevel -- out, goo needed for RpcSetAuthInfo PolicyHandle -- out, a policy to the destination DC. CachedPolicyEntry -- out, a binding handle cache (only for TDL's) SessionSetupTime -- a time stamp of the information returned by NETLOGON Return Values: STATUS_SUCCESS, or fatal resource or network related error. --*/ { NTSTATUS Status = STATUS_SUCCESS; LPWSTR TargetDomain = NULL; PUNICODE_STRING TargetDomainU; ULONG Size; ULONG NlFlags = 0; LPWSTR NetlogonServerName = NULL; PLARGE_INTEGER LocalFailedSessionSetupTime = FailedSessionSetupTime; // // Init the out parameters // *ServerName = NULL; *ServerPrincipalName = NULL; *ClientContext = NULL; *PolicyHandle = NULL; *CachedPolicyEntry = NULL; if (TrustInfo->DomainName.Length > 0) { TargetDomainU = (PUNICODE_STRING) &TrustInfo->DomainName; } else { TargetDomainU = (PUNICODE_STRING) &TrustInfo->FlatName; } ASSERT(TargetDomainU->Length > 0); // // Make the domain name null terminated // Size = TargetDomainU->Length + sizeof(WCHAR); SafeAllocaAllocate( TargetDomain, Size ); if ( TargetDomain == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } RtlZeroMemory(TargetDomain, Size); RtlCopyMemory(TargetDomain, TargetDomainU->Buffer, TargetDomainU->Length); // // Determine the type of secure channel we need // if (LookupLevel == LsapLookupXForestReferral) { NlFlags |= NL_RETURN_CLOSEST_HOP; } else { NlFlags |= NL_DIRECT_TRUST_REQUIRED; } while (TRUE) { // // Try to obtain a secure channel // Status = I_NetLogonGetAuthDataEx(NULL, // local domain TargetDomain, NlFlags, LocalFailedSessionSetupTime, ServerPrincipalName, ClientContext, &NetlogonServerName, ServerOsVersion, AuthnLevel, SessionSetupTime ); if (NT_SUCCESS(Status)) { // // Realloc the ServerName // Size = (wcslen(NetlogonServerName) + 1) * sizeof(WCHAR); *ServerName = LocalAlloc(LMEM_ZEROINIT, Size); if (NULL == *ServerName) { Status = STATUS_NO_MEMORY; goto Cleanup; } wcscpy(*ServerName, NetlogonServerName); I_NetLogonFree( NetlogonServerName ); NetlogonServerName = NULL; } if (!NT_SUCCESS(Status)) { // // This is a fatal error for this batch of names // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: I_NetLogonGetAuthDataEx to %ws failed (0x%x)\n", TargetDomain, Status)); LsapDbLookupReportEvent2( 1, EVENTLOG_WARNING_TYPE, LSAEVENT_LOOKUP_SC_FAILED, sizeof( ULONG ), &Status, TargetDomain, TargetDomain ); goto Cleanup; } ASSERT(NULL != *ServerName); // // We have a destination DC // if ( (*ServerOsVersion < NlWhistler) || ((*ServerOsVersion >= NlWhistler) && (*ClientContext == NULL)) ) { // // Get a policy handle because either // // 1. Secure channel DC is not at least Whistler // 2. Secure channel is configured to not use auth blob (no sign or seal) // LSAPR_TRUST_INFORMATION TrustInformation; UNICODE_STRING DomainControllerName; RtlInitUnicodeString(&DomainControllerName, *ServerName); // // We use the flat name here since part of the validate routine // compares the name against the AccountDomainName of the target // domain controller. // RtlZeroMemory(&TrustInformation, sizeof(TrustInformation)); TrustInformation.Name = TrustInfo->FlatName; if (LookupLevel == LsapLookupTDL) { // // Use the caching scheme; note that this routine will take // ownership of ServerName, ServerPrincipalName, and ClientContext // on success (and will hence set them to NULL) // Status = LsapDbGetCachedHandleTrustedDomain(&TrustInformation, POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION, ServerName, ServerPrincipalName, ClientContext, CachedPolicyEntry); } else { Status = LsapRtlValidateControllerTrustedDomain( (PLSAPR_UNICODE_STRING)&DomainControllerName, &TrustInformation, POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION, *ServerPrincipalName, *ClientContext, PolicyHandle ); } if ( ((STATUS_ACCESS_DENIED == Status) || (RPC_NT_SERVER_UNAVAILABLE == Status)) && (NULL == LocalFailedSessionSetupTime)) { // // Try again, telling NETLOGON this blob is no good // if (*ServerPrincipalName != NULL) { I_NetLogonFree(*ServerPrincipalName); *ServerPrincipalName = NULL; } if (*ServerName) { LocalFree(*ServerName); *ServerName = NULL; } if (*ClientContext) { I_NetLogonFree(*ClientContext); *ClientContext = NULL; } LocalFailedSessionSetupTime = SessionSetupTime; continue; } if (!NT_SUCCESS(Status)) { // // This is a fatal error for this batch of names // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: Failed to open a policy handle to %ws (0x%x)\n", *ServerName, Status)); LsapDbLookupReportEvent2( 1, EVENTLOG_WARNING_TYPE, LSAEVENT_LOOKUP_SC_HANDLE_FAILED, sizeof( ULONG ), &Status, *ServerName, *ServerName ); goto Cleanup; } } // // Exit from the loop // break; } Cleanup: if (!NT_SUCCESS(Status)) { if (*PolicyHandle != NULL) { LsaClose( *PolicyHandle ); *PolicyHandle = NULL; } if (NetlogonServerName != NULL) { I_NetLogonFree(NetlogonServerName); } if (*ServerPrincipalName != NULL) { I_NetLogonFree(*ServerPrincipalName); *ServerPrincipalName = NULL; } if (*ClientContext != NULL) { I_NetLogonFree(*ClientContext); *ClientContext = NULL; } if (*ServerName) { LocalFree(*ServerName); *ServerName = NULL; } if (*CachedPolicyEntry) { LsapDereferenceBindingCacheEntry(*CachedPolicyEntry); *CachedPolicyEntry = NULL; } } else { // // Either a auth info, a policy handle, or a cached policy handle // must be returned // ASSERT( (*PolicyHandle != NULL) || (*ClientContext != NULL) || (*CachedPolicyEntry != NULL)); } SafeAllocaFree( TargetDomain ); return Status; } NTSTATUS LsapDbLookupNameChainRequest( IN LSAPR_TRUST_INFORMATION_EX *TrustInfo, IN ULONG Count, IN PUNICODE_STRING Names, OUT PLSA_REFERENCED_DOMAIN_LIST *ReferencedDomains, OUT PLSA_TRANSLATED_SID_EX2 *Sids, IN LSAP_LOOKUP_LEVEL LookupLevel, OUT PULONG MappedCount, OUT PULONG ServerRevision ) /*++ Routine Description: This routine is the general purpose routine to be used when ever a name request needs to be chained (be resolved off machine). This includes member -> DC DC -> trusted domain DC DC -> trusted forest DC Arguments: TrustInfo -- contains the destination domain; at least one of DomainName or FlatName must be present; Sid is optional. Count -- the number of Names to be resovled Names -- the names to be resolved ReferencedDomains -- out, the resolved domain referenced Sids -- out, the resolved SID's LookupLevel -- the type of chaining requested MappedCount -- out, the number of Names fully resolved ServerRevision -- the LSA lookup revision of the target Return Values: STATUS_SUCCESS STATUS_NONE_MAPPED STATUS_SOME_NOT_MAPPED other fatal resource errors --*/ { NTSTATUS Status = STATUS_SUCCESS; LPWSTR ServerPrincipalName = NULL; LPWSTR ServerName = NULL; PVOID ClientContext = NULL; ULONG AuthnLevel; NL_OS_VERSION ServerOsVersion; LSA_HANDLE ControllerPolicyHandle = NULL; BOOLEAN fLookupCallFailed = FALSE; PUNICODE_STRING DestinationDomain; PLSAP_BINDING_CACHE_ENTRY ControllerPolicyEntry = NULL; LPWSTR TargetServerName = NULL; LARGE_INTEGER SessionSetupTime = {0}; PLARGE_INTEGER FailedSessionSetupTime = NULL; if (TrustInfo->DomainName.Length > 0) { DestinationDomain = (PUNICODE_STRING)&TrustInfo->DomainName; } else { DestinationDomain = (PUNICODE_STRING)&TrustInfo->FlatName; } ASSERT(DestinationDomain->Length > 0); LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: Chaining a name request to %wZ of type %ws\n", DestinationDomain, LsapDbLookupGetLevel(LookupLevel)) ); while (TRUE) { Status = LsapDbLookupGetServerConnection(TrustInfo, 0, LookupLevel, FailedSessionSetupTime, &ServerName, &ServerOsVersion, &ServerPrincipalName, &ClientContext, &AuthnLevel, &ControllerPolicyHandle, &ControllerPolicyEntry, &SessionSetupTime ); if (!NT_SUCCESS(Status)) { // // This is a fatal error for this batch of names // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: Can't get server connection to %wZ (0x%x)\n", DestinationDomain, Status)); goto Cleanup; } // // We have the secure channel // if ( (ServerOsVersion >= NlWhistler) && (ClientContext != NULL) ) { // // Use new version // Status = LsaICLookupNamesWithCreds(ServerName, ServerPrincipalName, AuthnLevel, RPC_C_AUTHN_NETLOGON, ClientContext, RPC_C_AUTHZ_NONE, Count, (PUNICODE_STRING)Names, (PLSA_REFERENCED_DOMAIN_LIST *)ReferencedDomains, (PLSA_TRANSLATED_SID_EX2 * )Sids, LookupLevel, MappedCount); if (((Status == STATUS_ACCESS_DENIED) || (Status == RPC_NT_SERVER_UNAVAILABLE)) && (FailedSessionSetupTime == NULL) ) { // // NETLOGON's auth data has gone stale // if (ServerName != NULL) { LocalFree(ServerName); ServerName = NULL; } if (ServerPrincipalName != NULL) { I_NetLogonFree(ServerPrincipalName); ServerPrincipalName = NULL; } if (ClientContext != NULL) { I_NetLogonFree(ClientContext); ClientContext = NULL; } FailedSessionSetupTime = &SessionSetupTime; ASSERT( NULL == ControllerPolicyHandle); ASSERT( NULL == ControllerPolicyEntry); continue; } if (ServerRevision) { *ServerRevision = LSA_CLIENT_LATEST; } TargetServerName = ServerName; if (!NT_SUCCESS(Status) && Status != STATUS_NONE_MAPPED ) { // // This is a fatal error for this batch of names // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: LsaICLookupNamesWithCreds to %ws failed (0x%x)\n", ServerName, Status)); fLookupCallFailed = TRUE; goto Cleanup; } LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: LsaICLookupNamesWithCreds to %ws succeeded\n", ServerName)); } else { LSA_HANDLE TargetHandle; ULONG LsaICLookupFlags = 0; LsaICLookupFlags = LsapLookupGetChainingFlags(ServerOsVersion); if (ControllerPolicyEntry) { TargetHandle = ControllerPolicyEntry->PolicyHandle; TargetServerName = ControllerPolicyEntry->ServerName; ASSERT( NULL == ControllerPolicyHandle); } else { TargetHandle = ControllerPolicyHandle; TargetServerName = ServerName; } ASSERT(NULL != TargetHandle); Status = LsaICLookupNames( TargetHandle, 0, // no flags necessary Count, (PUNICODE_STRING) Names, (PLSA_REFERENCED_DOMAIN_LIST *) ReferencedDomains, (PLSA_TRANSLATED_SID_EX2 *) Sids, LookupLevel, LsaICLookupFlags, MappedCount, ServerRevision ); if (!NT_SUCCESS(Status) && Status != STATUS_NONE_MAPPED ) { // // This is a fatal error for this batch of names // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: LsaICLookupNames to %ws failed (0x%x)\n", TargetServerName, Status)); fLookupCallFailed = TRUE; goto Cleanup; } LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: LsaICLookupNames to %ws succeeded\n", TargetServerName)); } break; } Cleanup: if (fLookupCallFailed) { ASSERT(NULL != TargetServerName); LsapDbLookupReportEvent2( 1, EVENTLOG_WARNING_TYPE, LSAEVENT_LOOKUP_SC_LOOKUP_FAILED, sizeof( ULONG ), &Status, TargetServerName, TargetServerName ); } if ( !NT_SUCCESS(Status) && (Status != STATUS_NONE_MAPPED) && !LsapDbIsStatusConnectionFailure(Status)) { // // An error occurred not one that our callers will understand. // The specific error has already been logged, so return a general one. // if (LookupLevel == LsapLookupPDC) { Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE; } else { Status = STATUS_TRUSTED_DOMAIN_FAILURE; } } if (ControllerPolicyHandle != NULL) { LsaClose( ControllerPolicyHandle ); } if (ServerName != NULL) { LocalFree(ServerName); } if (ServerPrincipalName != NULL) { I_NetLogonFree(ServerPrincipalName); } if (ClientContext != NULL) { I_NetLogonFree(ClientContext); } if (ControllerPolicyEntry) { LsapDereferenceBindingCacheEntry( ControllerPolicyEntry ); } return Status; } NTSTATUS LsaDbLookupSidChainRequest( IN LSAPR_TRUST_INFORMATION_EX *TrustInfo, IN ULONG Count, IN PSID *Sids, OUT PLSA_REFERENCED_DOMAIN_LIST *ReferencedDomains, OUT PLSA_TRANSLATED_NAME_EX *Names, IN LSAP_LOOKUP_LEVEL LookupLevel, IN OUT PULONG MappedCount, OUT PULONG ServerRevision OPTIONAL ) /*++ Routine Description: This routine is the general purpose routine to be used when ever a SID request needs to be chained (be resolved off machine). This includes member -> DC DC -> trusted domain DC DC -> trusted forest DC Arguments: TrustInfo -- contains the destination domain; at least one of DomainName or FlatName must be present; Sid is optional. Count -- the number of SIDs to be resovled Sids -- the Sids to be resolved ReferencedDomains -- out, the resolved domain referenced Names -- out, the resolved names's LookupLevel -- the type of chaining requested MappedCount -- out, the number of Names fully resolved ServerRevision -- the LSA lookup revision of the target Return Values: STATUS_SUCCESS STATUS_NONE_MAPPED STATUS_SOME_NOT_MAPPED STATUS_TRUSTED_DOMAIN_FAILURE STATUS_TRUSTED_RELATIONSHIP_FAILURE other fatal resource errors --*/ { NTSTATUS Status = STATUS_SUCCESS; LPWSTR ServerName = NULL; NL_OS_VERSION ServerOsVersion; LPWSTR ServerPrincipalName = NULL; PVOID ClientContext = NULL; ULONG AuthnLevel; LSA_HANDLE ControllerPolicyHandle = NULL; BOOLEAN fLookupCallFailed = FALSE; PUNICODE_STRING DestinationDomain; PLSAP_BINDING_CACHE_ENTRY ControllerPolicyEntry = NULL; LPWSTR TargetServerName = NULL; LARGE_INTEGER SessionSetupTime = {0}; PLARGE_INTEGER FailedSessionSetupTime = NULL; if (TrustInfo->DomainName.Length > 0) { DestinationDomain = (PUNICODE_STRING)&TrustInfo->DomainName; } else { DestinationDomain = (PUNICODE_STRING)&TrustInfo->FlatName; } ASSERT(DestinationDomain->Length > 0); LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: Chaining a SID request to %wZ of type %ws\n", DestinationDomain, LsapDbLookupGetLevel(LookupLevel)) ); while (TRUE) { Status = LsapDbLookupGetServerConnection(TrustInfo, 0, LookupLevel, FailedSessionSetupTime, &ServerName, &ServerOsVersion, &ServerPrincipalName, &ClientContext, &AuthnLevel, &ControllerPolicyHandle, &ControllerPolicyEntry, &SessionSetupTime ); if (!NT_SUCCESS(Status)) { // // This is a fatal error for this batch of names // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: Can't get server connection to %wZ failed (0x%x)\n", DestinationDomain, Status)); goto Cleanup; } // // We have the secure channel // if ( (ServerOsVersion >= NlWhistler) && (ClientContext != NULL)) { // // Use new version // Status = LsaICLookupSidsWithCreds(ServerName, ServerPrincipalName, AuthnLevel, RPC_C_AUTHN_NETLOGON, ClientContext, RPC_C_AUTHZ_NONE, Count, (PSID*)Sids, (PLSA_REFERENCED_DOMAIN_LIST *)ReferencedDomains, (PLSA_TRANSLATED_NAME_EX * )Names, LookupLevel, MappedCount); if (((Status == STATUS_ACCESS_DENIED) || (Status == RPC_NT_SERVER_UNAVAILABLE)) && (FailedSessionSetupTime == NULL)) { // // NETLOGON's auth data has gone stale // if (ServerName != NULL) { LocalFree(ServerName); ServerName = NULL; } if (ServerPrincipalName != NULL) { I_NetLogonFree(ServerPrincipalName); ServerPrincipalName = NULL; } if (ClientContext != NULL) { I_NetLogonFree(ClientContext); ClientContext = NULL; } FailedSessionSetupTime = &SessionSetupTime; ASSERT( NULL == ControllerPolicyHandle); ASSERT( NULL == ControllerPolicyEntry); continue; } if (ServerRevision) { *ServerRevision = LSA_CLIENT_LATEST; } TargetServerName = ServerName; if (!NT_SUCCESS(Status) && Status != STATUS_NONE_MAPPED ) { // // This is a fatal error for this batch of names // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: LsaICLookupSidsWithCreds to %ws failed 0x%x\n", ServerName, Status)); fLookupCallFailed = TRUE; goto Cleanup; } else { LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: LsaICLookupSidsWithCreds to %ws succeeded\n", ServerName)); } } else { // // Must use downlevel API // LSA_HANDLE TargetHandle; ULONG LsaICLookupFlags = 0; LsaICLookupFlags = LsapLookupGetChainingFlags(ServerOsVersion); if (ControllerPolicyEntry) { TargetHandle = ControllerPolicyEntry->PolicyHandle; TargetServerName = ControllerPolicyEntry->ServerName; ASSERT( NULL == ControllerPolicyHandle); } else { TargetHandle = ControllerPolicyHandle; TargetServerName = ServerName; } ASSERT(NULL != TargetHandle); Status = LsaICLookupSids( TargetHandle, Count, (PSID*) Sids, (PLSA_REFERENCED_DOMAIN_LIST *) ReferencedDomains, (PLSA_TRANSLATED_NAME_EX *) Names, LookupLevel, LsaICLookupFlags, MappedCount, ServerRevision ); if (!NT_SUCCESS(Status) && Status != STATUS_NONE_MAPPED ) { // // This is a fatal error for this batch of names // LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: LsaICLookupNames to %ws failed 0x%x\n", TargetServerName, Status)); fLookupCallFailed = TRUE; goto Cleanup; } else { LsapDiagPrint( DB_LOOKUP_WORK_LIST, ("LSA: LsaICLookupNames to %ws succeeded\n", TargetServerName)); } } break; } Cleanup: if (fLookupCallFailed) { ASSERT(NULL != TargetServerName); LsapDbLookupReportEvent2( 1, EVENTLOG_WARNING_TYPE, LSAEVENT_LOOKUP_SC_LOOKUP_FAILED, sizeof( ULONG ), &Status, TargetServerName, TargetServerName ); } if ( !NT_SUCCESS(Status) && (Status != STATUS_NONE_MAPPED) && !LsapDbIsStatusConnectionFailure(Status)) { // // An error occurred not one that our callers will understand. // The specific error has already been logged, so return a general one. // if (LookupLevel == LsapLookupPDC) { Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE; } else { Status = STATUS_TRUSTED_DOMAIN_FAILURE; } } if (ServerName != NULL) { LocalFree(ServerName); } if (ServerPrincipalName != NULL) { I_NetLogonFree(ServerPrincipalName); } if (ClientContext != NULL) { I_NetLogonFree(ClientContext); } if (ControllerPolicyHandle != NULL) { LsaClose( ControllerPolicyHandle ); } if (ControllerPolicyEntry) { LsapDereferenceBindingCacheEntry( ControllerPolicyEntry ); } return Status; } NTSTATUS LsapLookupReallocateTranslations( IN OUT PLSA_REFERENCED_DOMAIN_LIST *ReferencedDomains, IN ULONG Count, IN OUT PLSA_TRANSLATED_NAME_EX * Names, OPTIONAL IN OUT PLSA_TRANSLATED_SID_EX2 * Sids OPTIONAL ) /*++ Routine Description: This routine reallocates ReferencedDomains, Names, and Sids, from allocate_all_nodes, to !allocate_all_nodes. This is so that values returned from chaining calls can be returned to the caller. Arguments: ReferencedDomains -- the referenced domains to reallocate, if any Count -- the number of entries in either Names or Sids Names -- the names to reallocate, if any Sids -- the sids to reallocate, if any Return Values: STATUS_SUCCESS, STATUS_NO_MEMORY --*/ { NTSTATUS Status = STATUS_SUCCESS; PLSA_REFERENCED_DOMAIN_LIST LocalReferencedDomains = NULL; PLSA_TRANSLATED_NAME_EX LocalNames = NULL; PLSA_TRANSLATED_SID_EX2 LocalSids = NULL; ULONG Length, i; PVOID Src = NULL, Dst = NULL; // Only one is allowed ASSERT(!((Names && *Names) && (Sids && *Sids))); if (*ReferencedDomains) { LocalReferencedDomains = midl_user_allocate(sizeof(LSA_REFERENCED_DOMAIN_LIST)); if (NULL == LocalReferencedDomains) { goto MemoryFailure; } Length = sizeof(LSA_TRUST_INFORMATION) * (*ReferencedDomains)->Entries; LocalReferencedDomains->Domains = midl_user_allocate(Length); if (NULL == LocalReferencedDomains->Domains) { goto MemoryFailure; } RtlZeroMemory(LocalReferencedDomains->Domains, Length); LocalReferencedDomains->Entries = (*ReferencedDomains)->Entries; for (i = 0; i < LocalReferencedDomains->Entries; i++) { Src= (*ReferencedDomains)->Domains[i].Name.Buffer; if (Src) { Length = (*ReferencedDomains)->Domains[i].Name.Length; Dst = midl_user_allocate(Length); if (NULL == Dst) { goto MemoryFailure; } RtlCopyMemory(Dst, Src, Length); LocalReferencedDomains->Domains[i].Name.Length = (USHORT)Length; LocalReferencedDomains->Domains[i].Name.MaximumLength = (USHORT)Length; LocalReferencedDomains->Domains[i].Name.Buffer = Dst; Dst = NULL; } Src = (*ReferencedDomains)->Domains[i].Sid; if (Src) { Length = GetLengthSid(Src); Dst = midl_user_allocate(Length); if (NULL == Dst) { goto MemoryFailure; } CopySid(Length, Dst, Src); LocalReferencedDomains->Domains[i].Sid = Dst; Dst = NULL; } } } if (Names && *Names) { Length = sizeof(LSA_TRANSLATED_NAME_EX) * Count; LocalNames = midl_user_allocate(Length); if (NULL == LocalNames) { goto MemoryFailure; } RtlZeroMemory(LocalNames, Length); for (i = 0; i < Count; i++) { LocalNames[i] = (*Names)[i]; RtlInitUnicodeString(&LocalNames[i].Name, NULL); Src = (*Names)[i].Name.Buffer; if (Src) { Length = (*Names)[i].Name.Length; Dst = midl_user_allocate(Length); if (NULL == Dst) { goto MemoryFailure; } RtlCopyMemory(Dst, Src, Length); LocalNames[i].Name.Length = (USHORT)Length; LocalNames[i].Name.MaximumLength = (USHORT)Length; LocalNames[i].Name.Buffer = Dst; Dst = NULL; } } } if (Sids && *Sids) { Length = sizeof(LSA_TRANSLATED_SID_EX2) * Count; LocalSids = midl_user_allocate(Length); if (NULL == LocalSids) { goto MemoryFailure; } RtlZeroMemory(LocalSids, Length); for (i = 0; i < Count; i++) { LocalSids[i] = (*Sids)[i]; LocalSids[i].Sid = NULL; Src = (*Sids)[i].Sid; if (Src) { Length = GetLengthSid(Src); Dst = midl_user_allocate(Length); if (NULL == Dst) { goto MemoryFailure; } CopySid(Length, Dst, Src); LocalSids[i].Sid = Dst; Dst = NULL; } } } if (*ReferencedDomains) { midl_user_free(*ReferencedDomains); *ReferencedDomains = LocalReferencedDomains; LocalReferencedDomains = NULL; } if (Names && *Names) { midl_user_free(*Names); *Names = LocalNames; LocalNames = NULL; } if (Sids && *Sids) { midl_user_free(*Sids); *Sids = LocalSids; LocalSids = NULL; } Exit: if (LocalReferencedDomains) { if (LocalReferencedDomains->Domains) { for (i = 0; i < LocalReferencedDomains->Entries; i++) { if (LocalReferencedDomains->Domains[i].Name.Buffer) { midl_user_free(LocalReferencedDomains->Domains[i].Name.Buffer); } if (LocalReferencedDomains->Domains[i].Sid) { midl_user_free(LocalReferencedDomains->Domains[i].Sid); } } midl_user_free(LocalReferencedDomains->Domains); } midl_user_free(LocalReferencedDomains); } if (LocalNames) { for (i = 0; i < Count; i++) { if (LocalNames[i].Name.Buffer) { midl_user_free(LocalNames[i].Name.Buffer); } } midl_user_free(LocalNames); } if (LocalSids) { for (i = 0; i < Count; i++) { if (LocalSids[i].Sid) { midl_user_free(LocalSids[i].Sid); } } midl_user_free(LocalSids); } return Status; MemoryFailure: Status = STATUS_NO_MEMORY; goto Exit; } #if DBG LPWSTR LsapDbLookupGetLevel( IN LSAP_LOOKUP_LEVEL LookupLevel ) // // Simple debug helper routine // { switch (LookupLevel) { case LsapLookupWksta: return L"LsapLookupWksta"; case LsapLookupPDC: return L"LsapLookupPDC"; case LsapLookupTDL: return L"LsapLookupTDL"; case LsapLookupGC: return L"LsapLookupGC"; case LsapLookupXForestReferral: return L"LsapLookupXForestReferral"; case LsapLookupXForestResolve: return L"LsapLookupXForestResolve"; default: return L"Unknown Lookup Level"; } } #endif NTSTATUS LsapDomainHasDomainTrust( IN ULONG Flags, IN PUNICODE_STRING DomainName, OPTIONAL IN PSID DomainSid, OPTIONAL IN OUT BOOLEAN *fTDLLock, OPTIONAL OUT PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY *TrustEntryOut OPTIONAL ) /*++ Routine Description: This routine determines if DomainName/DomainSid refers to a domain that the current DC trusts. This is a worker routine for the LsaLookupNames/Sids API and as such is only interested in trust entries with an associated SID. Therefore, only outbound Windows trusts will be considered. If requested, the TrustEntry for the domain will be returned. Note that the Trusted Domain List lock is required for this. Therefore fTDLock is a required parameter when passing in TrustEntryOut. Arguments: Flags -- LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_EXTERNAL LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_INTRA LSAP_LOOKUP_DOMAIN_TRUST_FOREST DomainName -- The name of the target domain. DomainSid must be NULL if DomainName is non-NULL. DomainSid -- The sid of the target domain. DomainName must be NULL if DomainSid is non-NULL. fTDLLock -- An IN/OUT parameter indicating whether the trusted domain lock is held or not. NULL implies that this routine should grab and release the lock. fTDLLock and TrustEntryOut must either both be present, or both be equal to NULL. TrustEntryOut -- An OUT parameter receiving the TrustEntry of DomainName/DomainSid if found and if the trust satisfies above criteria. fTDLLock and TrustEntryOut must either both be present, or both be equal to NULL. Return Values: STATUS_SUCCESS -- the domain fits the criteria above STATUS_NO_SUCH_DOMAIN -- otherwise --*/ { NTSTATUS Status = STATUS_SUCCESS; BOOLEAN fLock = FALSE; PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY TrustEntry = NULL; UNICODE_STRING DomainNameFound; BOOLEAN fForestTrust = FALSE; BOOLEAN fDomainTrust = FALSE; BOOLEAN fIntraForestDomainTrust = FALSE; // // Only and exactly one IN parameter is permitted // ASSERT( (DomainName != NULL) || (DomainSid != NULL) ); ASSERT( (DomainName == NULL) || (DomainSid == NULL) ); // // Both or none of the these parameters should be sent in // ASSERT( ((fTDLLock == NULL) && (TrustEntryOut == NULL)) || ((fTDLLock != NULL) && (TrustEntryOut != NULL)) ); // // At least one variation must be present currently // ASSERT( 0 != Flags ); // // Init the out parameter // if (TrustEntryOut) { *TrustEntryOut = NULL; } RtlInitUnicodeString(&DomainNameFound, NULL); // // Attempt to find the domain in our trusted domain list // if ( (fTDLLock && (*fTDLLock == FALSE)) || fTDLLock == NULL ) { Status = LsapDbAcquireReadLockTrustedDomainList(); if (!NT_SUCCESS(Status)) { goto Cleanup; } fLock = TRUE; } if (DomainName) { Status = LsapDbLookupNameTrustedDomainListEx( (PLSAPR_UNICODE_STRING) DomainName, &TrustEntry ); } else { Status = LsapDbLookupSidTrustedDomainListEx( DomainSid, &TrustEntry ); } // // Did we find it? // if (!NT_SUCCESS(Status)) { Status = STATUS_NO_SUCH_DOMAIN; goto Cleanup; } // // We only consider trusted domains which have SIDs associated with them. // Only outbound Windows trusts are guaranteed to have SIDs (per CliffV). // if ( !(TrustEntry->TrustInfoEx.TrustDirection & TRUST_DIRECTION_OUTBOUND)) { Status = STATUS_NO_SUCH_DOMAIN; goto Cleanup; } if ( TrustEntry->TrustInfoEx.TrustType != TRUST_TYPE_UPLEVEL && TrustEntry->TrustInfoEx.TrustType != TRUST_TYPE_DOWNLEVEL ) { Status = STATUS_NO_SUCH_DOMAIN; goto Cleanup; } // // All outbound Windows trusts should have a SID // ASSERT( NULL != TrustEntry->TrustInfoEx.Sid ); if ( LsapOutboundTrustedForest(TrustEntry) ) { fForestTrust = TRUE; } if ( LsapOutboundTrustedDomain(TrustEntry) ) { fDomainTrust = TRUE; } // // Only check domain trusts for intra-forest trusts // if ( fDomainTrust ) { Status = LsapGetDomainNameBySid(TrustEntry->TrustInfoEx.Sid, &DomainNameFound); if (NT_SUCCESS(Status)) { fIntraForestDomainTrust = TRUE; } else if (Status != STATUS_NO_SUCH_DOMAIN) { // Unhandled error goto Cleanup; } } // // Logic to determine if the desired type of trust was found // if ( // // Forest trust // ( FLAG_ON(Flags, LSAP_LOOKUP_DOMAIN_TRUST_FOREST) && fForestTrust) // // Direct external trust // || ( FLAG_ON(Flags, LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_EXTERNAL) && fDomainTrust && !fIntraForestDomainTrust) // // Direct, internal trust (intra forest) // || ( FLAG_ON(Flags, LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_INTRA) && fDomainTrust && fIntraForestDomainTrust) ) { // // Success! // Status = STATUS_SUCCESS; if (TrustEntryOut) { *TrustEntryOut = TrustEntry; } } else { Status = STATUS_NO_SUCH_DOMAIN; } Cleanup: if (fLock) { if (fTDLLock == NULL) { LsapDbReleaseLockTrustedDomainList(); } else { *fTDLLock = TRUE; } } if (DomainNameFound.Buffer) { midl_user_free(DomainNameFound.Buffer); } return Status; } NTSTATUS LsapDomainHasForestTrust( IN PUNICODE_STRING DomainName, OPTIONAL IN PSID DomainSid, OPTIONAL IN OUT BOOLEAN *fTDLLock, OPTIONAL OUT PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY *TrustEntryOut OPTIONAL ) // // See LsapDomainHasDomainTrust // { return LsapDomainHasDomainTrust(LSAP_LOOKUP_DOMAIN_TRUST_FOREST, DomainName, DomainSid, fTDLLock, TrustEntryOut); } NTSTATUS LsapDomainHasDirectTrust( IN PUNICODE_STRING DomainName, OPTIONAL IN PSID DomainSid, OPTIONAL IN OUT BOOLEAN *fTDLLock, OPTIONAL OUT PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY *TrustEntryOut OPTIONAL ) { return LsapDomainHasDomainTrust(LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_INTRA| LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_EXTERNAL, DomainName, DomainSid, fTDLLock, TrustEntryOut); } NTSTATUS LsapDomainHasDirectExternalTrust( IN PUNICODE_STRING DomainName, OPTIONAL IN PSID DomainSid, OPTIONAL IN OUT BOOLEAN *fTDLLock, OPTIONAL OUT PLSAP_DB_TRUSTED_DOMAIN_LIST_ENTRY *TrustEntryOut OPTIONAL ) // // See LsapDomainHasDomainTrust // { return LsapDomainHasDomainTrust(LSAP_LOOKUP_DOMAIN_TRUST_DIRECT_EXTERNAL, DomainName, DomainSid, fTDLLock, TrustEntryOut); } NTSTATUS LsapDomainHasTransitiveTrust( IN PUNICODE_STRING DomainName, OPTIONAL IN PSID DomainSid, OPTIONAL OUT LSA_TRUST_INFORMATION *TrustInfo OPTIONAL ) /*++ Routine Description: This routine determines if the domain information (DomainName or DomainSid) belongs to a domain in the current forest. Arguments: DomainName -- the name of the target domain DomainSid -- the sid of the target domain TrustInfo -- the domain SID and netbios name of the domain, if found is returned. The embedded values (the SID and unicode string must be freed by the caller) Return Values: STATUS_SUCCESS -- the domain fits the criteria above STATUS_NO_SUCH_DOMAIN -- otherwise --*/ { NTSTATUS Status = STATUS_SUCCESS; PSID LocalDomainSid = NULL; UNICODE_STRING LocalDomainName; // Precisely one is allowed and expected ASSERT(!(DomainName && DomainSid)); ASSERT(!((DomainName == NULL) && (DomainSid == NULL))); RtlInitUnicodeString(&LocalDomainName, NULL); if (DomainName) { // // Try to match by name // LPWSTR Name; // // Make a NULL terminated string // Name = (WCHAR*)midl_user_allocate(DomainName->Length + sizeof(WCHAR)); if (NULL == Name) { Status = STATUS_NO_MEMORY; goto Exit; } RtlCopyMemory(Name, DomainName->Buffer, DomainName->Length); Name[DomainName->Length / sizeof(WCHAR)] = UNICODE_NULL; // // Try Netbios // Status = LsapGetDomainSidByNetbiosName( Name, &LocalDomainSid ); if ( STATUS_NO_SUCH_DOMAIN == Status ) { // // Try DNS // Status = LsapGetDomainSidByDnsName( Name, &LocalDomainSid ); } // // Get the flat name in all cases // if ( NT_SUCCESS( Status ) ) { Status = LsapGetDomainNameBySid( LocalDomainSid, &LocalDomainName ); } midl_user_free(Name); } else { // // Try to match by SID // Status = LsapGetDomainNameBySid( DomainSid, &LocalDomainName ); if (NT_SUCCESS(Status)) { LocalDomainSid = midl_user_allocate(GetLengthSid(DomainSid)); if (NULL == LocalDomainSid) { Status = STATUS_NO_MEMORY; goto Exit; } if (NULL != LocalDomainSid) { CopySid(GetLengthSid(DomainSid), LocalDomainSid, DomainSid); } } } if (NT_SUCCESS(Status)) { ASSERT(NULL != LocalDomainSid); ASSERT(NULL != LocalDomainName.Buffer); if (TrustInfo) { RtlZeroMemory(TrustInfo, sizeof(*TrustInfo)); TrustInfo->Sid = LocalDomainSid; TrustInfo->Name = LocalDomainName; LocalDomainSid = NULL; RtlInitUnicodeString(&LocalDomainName, NULL); } } Exit: if (LocalDomainSid) { midl_user_free(LocalDomainSid); } if (LocalDomainName.Buffer) { midl_user_free(LocalDomainName.Buffer); } return Status; } // // The character to search for if the name format is assumed to be // a UPN // #define LSAP_LOOKUP_UPN_DELIMITER L'@' // // The character to search for if the name format is assumed to a // SamAccountName format name // #define LSAP_LOOKUP_SAM_ACCOUNT_DELIMITER L'\\' BOOL LsapLookupNameContainsDelimiter( IN PUNICODE_STRING Name, IN WCHAR Delimiter, OUT ULONG *Position OPTIONAL ) /*++ Routine Description: This routine finds Delimiter in Name and returns the position of the delimiter. Name must be at least 3 characters long and the delimiter cannot be in the first or last position. N.B. This routines looks for the left-most delimiter Arguments: Name -- the name to be converted (in place) Delimiter -- LSAP_LOOKUP_SAM_ACCOUNT_DELIMITER LSAP_LOOKUP_UPN_DELIMITER Position -- the array index of Delimiter in Name->Buffer Return Values: TRUE if delimiter found; FALSE otherwise --*/ { ULONG StringLength = Name->Length / sizeof(WCHAR); ULONG DelimiterPosition; ULONG i; // // Find the last Delimiter; return if not found, or in first or last position // DelimiterPosition = 0; for (i = StringLength; i > 0; i--) { if (Name->Buffer[i-1] == Delimiter) { DelimiterPosition = i-1; break; } } if ((DelimiterPosition == 0) || (DelimiterPosition == (StringLength - 1))) { return FALSE; } if (Position) { *Position = DelimiterPosition; } return TRUE; } VOID LsapLookupConvertNameFormat( IN OUT PUNICODE_STRING Name, IN WCHAR OldDelimiter, IN WCHAR NewDelimiter ) /*++ Routine Description: This routine takes a Name and converts the name format between UPN and SamAccountName style. For example: billg@microsoft.com to microsoft.com\billg and microsoft.com\billg to billg@microsoft.com If the expected delimiter is not found in the name, the string is untouched. Arguments: Name -- the name to be converted (in place) OldDelimiter -- LSAP_LOOKUP_SAM_ACCOUNT_DELIMITER LSAP_LOOKUP_UPN_DELIMITER NewDelimiter -- LSAP_LOOKUP_SAM_ACCOUNT_DELIMITER LSAP_LOOKUP_UPN_DELIMITER Return Values: None. --*/ { ULONG StringLength = Name->Length / sizeof(WCHAR); ULONG Delimiter; ULONG i; ULONG Length1, Length2; WCHAR *Buffer = Name->Buffer; ULONG RotationFactor; ULONG LastStartingPoint, MovedCount, CurrentPosition, NextPosition; WCHAR Temp1, Temp2; // // The function's behavior is not defined in this case. // ASSERT(OldDelimiter != NewDelimiter); // // Find the last Delimiter; return if not found, or in first or last position // if (!LsapLookupNameContainsDelimiter(Name, OldDelimiter, &Delimiter)) { return; } // // Make the delimiter part of the first segment // Ie. billg@ microsoft.com // microsoft.com\ billg // Length1 = Delimiter + 1; Length2 = StringLength - Length1; // // Rotate the string // RotationFactor = Length2; MovedCount = 0; CurrentPosition = 0; LastStartingPoint = 0; Temp1 = Buffer[0]; while (MovedCount < StringLength) { NextPosition = CurrentPosition + RotationFactor; NextPosition %= StringLength; Temp2 = Buffer[NextPosition]; Buffer[NextPosition] = Temp1; Temp1 = Temp2; CurrentPosition = NextPosition; if (CurrentPosition == LastStartingPoint) { CurrentPosition++; LastStartingPoint = CurrentPosition; Temp1 = Buffer[CurrentPosition]; } MovedCount++; } // // The string now looks like // microsoft.combillg@ // billgmicrosoft.com\ // // Move down and add new limiter // Temp1 = Buffer[Length2]; for (i = Length2+1; i < StringLength; i++) { Temp2 = Buffer[i]; Buffer[i] = Temp1; Temp1 = Temp2; } Buffer[Length2] = NewDelimiter; // // Final form: // microsoft.com\billg // billg@microsoft.com // return; } VOID LsapLookupCrackName( IN PUNICODE_STRING Prefix, IN PUNICODE_STRING Suffix, OUT PUNICODE_STRING SamAccountName, OUT PUNICODE_STRING DomainName ) /*++ Routine Description: This routine takes Prefix and Suffix, which represent a name of the form Prefix\Suffix, and determines what the domain and username portion are. Arguments: Prefix -- the left side of the \ of the originally requested name Suffix -- the right side of the \ of the originally requested name SamAccountName -- the sam account name embedded in Prefix\Suffix DomainName -- the domain name embedded in Prefix\Suffix Return Values: None. --*/ { ULONG Position; if ( (Prefix->Length == 0) && LsapLookupNameContainsDelimiter(Suffix, LSAP_LOOKUP_UPN_DELIMITER, &Position)) { // // This is an isolated name (no explicit domain portion) that contains // the UPN delimiter -- crack as UPN. // ULONG StringLength = Suffix->Length / sizeof(WCHAR); SamAccountName->Buffer = Suffix->Buffer; SamAccountName->Length = (USHORT)Position * sizeof(WCHAR); SamAccountName->MaximumLength = SamAccountName->Length; DomainName->Buffer = Suffix->Buffer + Position + 1; DomainName->Length = (USHORT) (StringLength - Position - 1) * sizeof(WCHAR); DomainName->MaximumLength = DomainName->Length; } else { // // Simple case, domain is specified as prefix // *SamAccountName = *Suffix; *DomainName = *Prefix; } } VOID LsapLookupSamAccountNameToUPN( IN OUT PUNICODE_STRING Name ) // // Converts, in place, domainname\username to username@domainname // { LsapLookupConvertNameFormat(Name, LSAP_LOOKUP_SAM_ACCOUNT_DELIMITER, LSAP_LOOKUP_UPN_DELIMITER); } VOID LsapLookupUPNToSamAccountName( IN OUT PUNICODE_STRING Name ) // // Converts, in place, username@domainname to domainname\username // { LsapLookupConvertNameFormat(Name, LSAP_LOOKUP_UPN_DELIMITER, LSAP_LOOKUP_SAM_ACCOUNT_DELIMITER); } BOOL LsapLookupIsUPN( OUT PUNICODE_STRING Name ) // // Returns TRUE if Name is syntactically determined to be a UPN // { return LsapLookupNameContainsDelimiter(Name, LSAP_LOOKUP_UPN_DELIMITER, NULL); } ULONG LsapGetDomainLookupScope( IN LSAP_LOOKUP_LEVEL LookupLevel, IN ULONG ClientRevision ) /*++ Routine Description: This routine returns what scope is appropriate when looking up domain names and SID's. Arguments: LookupLevel -- the level requested by the caller fTransitiveTrustSupport -- is the client aware of transitive trust relations. Return Values: A bitmask containing, possibly LSAP_LOOKUP_TRUSTED_DOMAIN_DIRECT LSAP_LOOKUP_TRUSTED_DOMAIN_TRANSITIVE LSAP_LOOKUP_TRUSTED_FOREST LSAP_LOOKUP_TRUSTED_FOREST_ROOT --*/ { ULONG Scope = 0; if ( // // Workstation request // (LookupLevel == LsapLookupPDC) // // Local lookup on DC // || ((LookupLevel == LsapLookupWksta) && (LsapProductType == NtProductLanManNt)) ) { // // Include directly trusted domains // Scope |= LSAP_LOOKUP_TRUSTED_DOMAIN_DIRECT; // // Determine if newer features apply // if ( (ClientRevision > LSA_CLIENT_PRE_NT5) || (LsapSamOpened && !SamIMixedDomain( LsapAccountDomainHandle )) || LsapAllowExtendedDownlevelLookup ) { // // Allow DNS support // Scope |= LSAP_LOOKUP_DNS_SUPPORT; // // Include transitivly trusted domains // Scope |= LSAP_LOOKUP_TRUSTED_DOMAIN_TRANSITIVE; // // Include forest trusts // Scope |= LSAP_LOOKUP_TRUSTED_FOREST; if (LsapDbDcInRootDomain()) { // // If we are in the root domain, also check for // trusts to directly trusted forests for isolated // domain names or domain SID's // Scope |= LSAP_LOOKUP_TRUSTED_FOREST_ROOT; } } } else if ((LookupLevel == LsapLookupXForestResolve) || (LookupLevel == LsapLookupGC) ) { // // Only consider transitively trusted domains // Scope |= LSAP_LOOKUP_TRUSTED_DOMAIN_TRANSITIVE; } return Scope; } NTSTATUS LsapNullTerminateUnicodeString( IN PUNICODE_STRING String, OUT LPWSTR *pBuffer, OUT BOOLEAN *fFreeBuffer ) /*++ Routine Description: This routine accepts a UNICODE_STRING and returns its internal buffer, ensuring that it is NULL terminated. If the buffer is NULL terminated it will be returned in pBuffer. If the buffer isn't NULL terminated it will be reallocated, NULL terminated, and returned in pBuffer. fFreeBuffer will be set to TRUE indicating the caller is responsible for deallocating pBuffer. If an error occurs then pBuffer will be NULL, fFreeBuffer will be FALSE, and no memory will be allocated. Arguments: String - Pointer to a UNICODE_STRING pBuffer - Pointer to a pointer to return the buffer fFreeBuffer - Pointer to a BOOLEAN to indicate whether the caller needs to deallocate pBuffer or not. Return Values: STATUS_SUCCESS - *pBuffer points to a NULL terminated version of String's internal buffer. Check *fFreeBuffer to determine if pBuffer must be freed by the caller. STATUS_NO_MEMORY - The routine failed to NULL terminate String's internal buffer. *pBuffer is NULL and *fFreeBuffer is FALSE. --*/ { BOOLEAN fNullTerminated; NTSTATUS Status = STATUS_SUCCESS; ASSERT(pBuffer); ASSERT(fFreeBuffer); // // Initialize input parameters // *pBuffer = NULL; *fFreeBuffer = FALSE; // // Short circuit for strings that are already NULL terminated. // fNullTerminated = (String->MaximumLength > String->Length && String->Buffer[String->Length / sizeof(WCHAR)] == UNICODE_NULL); if (!fNullTerminated) { // // Allocate enough memory to include a terminating NULL character // *pBuffer = (WCHAR*)midl_user_allocate(String->Length + sizeof(WCHAR)); if ( NULL == *pBuffer ) { Status = STATUS_NO_MEMORY; } else { // // Copy the buffer into pBuffer and NULL terminate it. // *fFreeBuffer = TRUE; RtlCopyMemory(*pBuffer, String->Buffer, String->Length); (*pBuffer)[String->Length / sizeof(WCHAR)] = UNICODE_NULL; } } else { // // String's internal buffer is already NULL terminated, return it. // *pBuffer = String->Buffer; } return Status; } BOOLEAN LsapCompareDomainNames( IN PUNICODE_STRING String, IN PUNICODE_STRING AmbiguousName, IN PUNICODE_STRING FlatName OPTIONAL ) /*++ Routine Description: This routine performs a case insensitive comparison between a string and a domain name. If both the NetBIOS and Dns name forms are known then the caller must pass the NetBIOS domain name as FlatName and the Dns domain name as AmbiguousName. A non-NULL value for FlatName indicates the caller knows both name forms and will result in a more optimal comparison. If the caller has only one name form and it is ambiguous, that is it may be NetBIOS or Dns, then the caller must pass NULL for FlatName. The routine will try both a NetBIOS comparison using RtlEqualDomainName and a Dns comparison using DnsNameCompare_W. If either comparison results in equality the TRUE is returned, otherwise FALSE. This routine provides centralized logic for robust domain name comparison consistent with domain name semantics in Lsa data structures. Lsa trust information structures are interpreted in the following way. LSAPR_TRUST_INFORMATION.Name - Either NetBIOS or Dns The following structures have both a FlatName and DomainName (or Name) field. In this case they are interpreted as follows: LSAPR_TRUST_INFORMATION_EX LSAPR_TRUSTED_DOMAIN_INFORMATION_EX LSAPR_TRUSTED_DOMAIN_INFORMATION_EX2 If the FlatName field is NULL then the other name field is ambiguous. If the FlatName field is non NULL, then the other name field is Dns. NetBIOS comparison is performed using RtlEqualDomainName which enforces the proper OEM character equivelencies. DNS name comparison is performed using DnsNameCompare_W to ensure proper handling of trailing dots and character equivelencies. Arguments: String -- Potentially ambiguous domain name to compare against AmbiguousName, and FlatName if non-NULL. AmbiguousName -- Is treated as an ambiguous name form unless FlatName is also specified. If FlatName is non-NULL then AmbiguousName is treated as a Dns domain name. FlatName -- This parameter is optional. If present it must be the flat name of the domain. Furthermore, passing this parameter indicates that AmbiguousName is in fact a Dns domain name. Return Values: TRUE - String is equivelent to one of FlatDomainName or DnsDomainName FALSE - String is not equivelent to either domain name If any parameter isn't a valid UNICODE_STRING then FALSE is returned. Notes: The number of comparisons required to determine equivelency will depend on the ammount of information passed in by the caller. If both the NetBIOS and Dns domain names are known, pass them both to ensure the minimal number of comparisons. --*/ { NTSTATUS Status; BOOLEAN fEquivalent = FALSE; LPWSTR StringBuffer = NULL; LPWSTR AmbiguousNameBuffer = NULL; BOOLEAN fFreeStringBuffer = FALSE; BOOLEAN fFreeAmbiguousBuffer = FALSE; // // Validate input strings // ASSERT(String); ASSERT(AmbiguousName); ASSERT(NT_SUCCESS(RtlValidateUnicodeString( 0, String ))); ASSERT(NT_SUCCESS(RtlValidateUnicodeString( 0, AmbiguousName ))); // // Ensure the UNICODE_STRING data buffers are NULL terminated before // passing them to DnsNameCompare_W // Status = LsapNullTerminateUnicodeString( String, &StringBuffer, &fFreeStringBuffer ); if (NT_SUCCESS(Status)) { Status = LsapNullTerminateUnicodeString( AmbiguousName, &AmbiguousNameBuffer, &fFreeAmbiguousBuffer ); } if (NT_SUCCESS(Status)) { if ( NULL == FlatName ) { // // AmbiguousName is truly ambiguous, we must perform both // types of comparison between String // fEquivalent = ( RtlEqualDomainName( String, AmbiguousName ) || DnsNameCompare_W( StringBuffer, AmbiguousNameBuffer ) ); } else { ASSERT(NT_SUCCESS(RtlValidateUnicodeString( 0, FlatName ))); // // We are sure of the name forms so lets just use the // appropriate comparison routines on each. // fEquivalent = ( RtlEqualDomainName( String, FlatName ) || DnsNameCompare_W( StringBuffer, AmbiguousNameBuffer ) ); } } if ( fFreeStringBuffer ) { midl_user_free( StringBuffer ); } if ( fFreeAmbiguousBuffer ) { midl_user_free( AmbiguousNameBuffer ); } return fEquivalent; }