//+---------------------------------------------------------------------------- // // Copyright (C) 2001, Microsoft Corporation // // File: DfsXForest.cxx // // Contents: the Dfs xforest info class // // Classes: DfsXForest, DfsDomainNameTable // // History: Nov, 15 2002, Author: udayh // //----------------------------------------------------------------------------- #include "dfsxforest.hxx" #include "lm.h" #include "dfsxforest.tmh" // // This file implements DfsXForest class. This class enumerates all // local domains and cross forest domains, and builds a table of uniqye // domain names. // // Ideally this should be an API such as DsEnumerateDomainTrusts, but // lacking that we are putting this support here. // // Since this is happening so close to RTM, there is minimal code change // in the original path, and all functionality has been implemented in // this new class. This is called from DfsDomainInformation, where // instead of enumerating domains by calling DsEnumerateDomainTrusts we // enumerate domains by building the DfsXForest instance. // // DfsDomainInformation gets the DfsXForest instance, reads all the domains // from the DfsXForest and destroys the DfsXForest. // // // In future, we may either want to get an API for this. If that does // not happen, it may be worthwhile changing the DfsDomainInformation // code to use the DfsXForest instance all the time instead of // building an array of domain names enumerated using this class. // That would be a more performance optimal solution. // // DFSSTATUS DfsXForest::Initialize( DFSSTATUS *pXforestInitStatus ) { DFSSTATUS Status; Status = _DomainTable.Initialize(); if (Status == ERROR_SUCCESS) { Status = DfsAddLocalDomainsToDomainTable(); } if (Status == ERROR_SUCCESS) { DFSSTATUS DfsCrossForestDomainStatus; DfsCrossForestDomainStatus = InitializeForestRootName(); DFS_TRACE_ERROR_NORM(DfsCrossForestDomainStatus, REFERRAL_SERVER, "DfsXForest::InitializeForestRootName, Status 0x%x\n", DfsCrossForestDomainStatus); // // If we cannot get cross forest domain information, // we continue with just the local information. // if (DfsCrossForestDomainStatus == ERROR_SUCCESS) { DfsCrossForestDomainStatus = DfsAddCrossForestDomainsToDomainTable(); DFS_TRACE_ERROR_NORM(DfsCrossForestDomainStatus, REFERRAL_SERVER, "DfsXForest::DfsAddCrossForestDomainsToDomainTable, Status 0x%x\n", DfsCrossForestDomainStatus); } *pXforestInitStatus = DfsCrossForestDomainStatus; } return Status; } // // InitializeForestRootName: Get the Root Forest name and store it // in our private member _ForestRootName. // // DFSSTATUS DfsXForest::InitializeForestRootName() { LSA_OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS NtStatus = 0; DFSSTATUS Status = ERROR_SUCCESS; LSA_HANDLE hPolicy; //attempt to open the policy. RtlInitUnicodeString(&_ForestRootName, NULL); ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));//object attributes are reserved, so initalize to zeroes. NtStatus = LsaOpenPolicy( NULL, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &hPolicy); //recieves the policy handle DFS_TRACE_ERROR_HIGH( NtStatus, REFERRAL_SERVER, "LsaOpenPolicy, NtStatus 0x%x\n", NtStatus ); if (NT_SUCCESS(NtStatus)) { //ask for audit event policy information PPOLICY_DNS_DOMAIN_INFO info; NtStatus = LsaQueryInformationPolicy(hPolicy, PolicyDnsDomainInformation, (PVOID *)&info); DFS_TRACE_ERROR_HIGH( NtStatus, REFERRAL_SERVER, "LsaQueryInformationPolicy, NtStatus 0x%x\n", NtStatus ); if (NT_SUCCESS(NtStatus)) { // // convert LSA_UNICODE_STRING to normal UNICODE_STRING. // not that it matters now, but if things should change, // we will be in trouble. // Save the forest name and we are done. // UNICODE_STRING TempForestName; TempForestName.Buffer = info->DnsForestName.Buffer; TempForestName.Length = info->DnsForestName.Length; TempForestName.MaximumLength = info->DnsForestName.MaximumLength; Status = DfsCreateUnicodeString( &_ForestRootName, &TempForestName); //free policy info structure LsaFreeMemory((PVOID) info); } //Freeing the policy object handle LsaClose(hPolicy); } if (!NT_SUCCESS(NtStatus)) { Status = RtlNtStatusToDosError(NtStatus); } return Status; } // // DfsAddLocalDomainsToDomainTable: enumerate all the local domain // trusts and add this to the domain table. // DFSSTATUS DfsXForest::DfsAddLocalDomainsToDomainTable() { DFSSTATUS Status; ULONG DsDomainCount = 0; PDS_DOMAIN_TRUSTS pDsDomainTrusts = NULL; ULONG Index; Status = DsEnumerateDomainTrusts( NULL, DS_DOMAIN_VALID_FLAGS, &pDsDomainTrusts, &DsDomainCount ); DFS_TRACE_ERROR_NORM( Status, REFERRAL_SERVER, "DsEnumerateDomainTrusts, Status 0x%x\n", Status ); if (Status == ERROR_SUCCESS) { for (Index = 0; (Status == ERROR_SUCCESS) && (Index < DsDomainCount); Index++) { if (pDsDomainTrusts[Index].TrustType == TRUST_TYPE_DOWNLEVEL) continue; if (Status == ERROR_SUCCESS) { if (IsEmptyString(pDsDomainTrusts[Index].NetbiosDomainName) == FALSE) { Status = AddDomainToDomainTable(pDsDomainTrusts[Index].NetbiosDomainName, NULL, TRUE); } } if (Status == ERROR_SUCCESS) { if (IsEmptyString(pDsDomainTrusts[Index].DnsDomainName) == FALSE) { Status = AddDomainToDomainTable(pDsDomainTrusts[Index].DnsDomainName, NULL, FALSE); } } } NetApiBufferFree(pDsDomainTrusts); } return Status; } // // DfsAddForestDomainsToDomainTable: enumerate all the domains in a forest // and add that to our table. // // DFSSTATUS DfsXForest::DfsAddForestDomainsToDomainTable( LSA_HANDLE hPolicy, LPWSTR RootNameString) { DFSSTATUS Status; NTSTATUS NtStatus; ULONG j; LSA_UNICODE_STRING LsaRootName; UNICODE_STRING RootName; PLSA_FOREST_TRUST_INFORMATION pForestTrustInfo = NULL; RtlInitUnicodeString(&RootName, RootNameString); LsaRootName.Length = RootName.Length; LsaRootName.MaximumLength = RootName.MaximumLength; LsaRootName.Buffer = RootName.Buffer; NtStatus = LsaQueryForestTrustInformation( hPolicy, &LsaRootName, &pForestTrustInfo); Status = RtlNtStatusToDosError(NtStatus); if (Status == ERROR_SUCCESS) { for (j = 0; (Status == ERROR_SUCCESS) && (j < pForestTrustInfo->RecordCount); j++ ) { PLSA_FOREST_TRUST_DOMAIN_INFO pDomainInfo; if (pForestTrustInfo->Entries[j]->ForestTrustType == ForestTrustDomainInfo) { pDomainInfo = &pForestTrustInfo->Entries[j]->ForestTrustData.DomainInfo; // // If only the DnsDomainName is empty, add both the dns and // netbios domain names. // The reason is: we cannot resolve netbios domain names // to DCs. We bind to the DNS domain name to resolve netbios // domains, so we add both netbios and dns names in // the add domain to table for netbios names. // if (IsEmptyUnicodeString((PUNICODE_STRING)&pDomainInfo->DnsName) == FALSE) { Status = AddDomainToDomainTable((PUNICODE_STRING)&pDomainInfo->DnsName, NULL, FALSE); if ((Status == ERROR_SUCCESS) && (IsEmptyUnicodeString((PUNICODE_STRING)&pDomainInfo->NetbiosName) == FALSE)) { Status = AddDomainToDomainTable((PUNICODE_STRING)&pDomainInfo->NetbiosName, (PUNICODE_STRING)&pDomainInfo->DnsName, TRUE); } } } } LsaFreeMemory(pForestTrustInfo); } return Status; } // // Magic trust flags: KahrenT indicates this is what we need. // #define DOMAIN_TRUST_FLAGS DS_DOMAIN_DIRECT_INBOUND // // DfsAddCrossForestDomainsToDomainTable: Enumerate each forest that we have // a trust to, and add that to our table. // // DFSSTATUS DfsXForest::DfsAddCrossForestDomainsToDomainTable() { DFSSTATUS Status; NTSTATUS NtStatus; ULONG DsDomainCount = 0; PDS_DOMAIN_TRUSTS pDsDomainTrusts = NULL; PDOMAIN_CONTROLLER_INFO pDomainControllerInfo = NULL; LSA_OBJECT_ATTRIBUTES ObjectAttributes; LSA_HANDLE hPolicy; ULONG Index; //object attributes are reserved, so initalize to zeroes. ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes)); Status = DsGetDcName( NULL, //computer name _ForestRootName.Buffer, NULL, // domain guid NULL, // site name DS_DIRECTORY_SERVICE_REQUIRED | DS_FORCE_REDISCOVERY, &pDomainControllerInfo ); if (Status == ERROR_SUCCESS) { Status = DsEnumerateDomainTrusts( pDomainControllerInfo->DomainControllerName, DOMAIN_TRUST_FLAGS, &pDsDomainTrusts, &DsDomainCount ); if (Status == ERROR_SUCCESS) { LSA_UNICODE_STRING LsaDCName; RtlInitUnicodeString(&LsaDCName, pDomainControllerInfo->DomainControllerName); NtStatus = LsaOpenPolicy(&LsaDCName, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &hPolicy); Status = RtlNtStatusToDosError(NtStatus); if (Status == ERROR_SUCCESS) { for (Index = 0; Index < DsDomainCount; Index++) { PDS_DOMAIN_TRUSTS pDomainTrust; DFSSTATUS ForestTrustStatus; pDomainTrust = &pDsDomainTrusts[Index]; // // If the domain is not in our forest, and it is // not MIT kerberos and it has transitive trust, // we have found a match. Add that forest and its // domains to our list. // if (!(pDomainTrust->Flags & DS_DOMAIN_IN_FOREST) && (pDomainTrust->TrustType != TRUST_TYPE_MIT) && (pDomainTrust->TrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) { // // Ignore status return. // // If any one of the domain enumeration fails, // we dont want to fail the entire domain // table creation. // ForestTrustStatus = DfsAddForestDomainsToDomainTable( hPolicy, pDomainTrust->DnsDomainName); } } LsaClose(hPolicy); } NetApiBufferFree(pDsDomainTrusts); } NetApiBufferFree(pDomainControllerInfo); } return Status; } // //AddDomainToDomainTable: given a domain name add it to our table // DFSSTATUS DfsXForest::AddDomainToDomainTable(LPWSTR DomainName, LPWSTR BindDomainName, BOOLEAN Netbios) { UNICODE_STRING DomainNameUnicode; UNICODE_STRING BindDomainNameUnicode; DFSSTATUS Status; RtlInitUnicodeString(&DomainNameUnicode, DomainName); if (BindDomainName != NULL) { RtlInitUnicodeString(&BindDomainNameUnicode, BindDomainName); } Status = AddDomainToDomainTable( &DomainNameUnicode, BindDomainName ? &BindDomainNameUnicode : NULL, Netbios); return Status; } // //AddDomainToDomainTable: given a domain name add it to our table // DFSSTATUS DfsXForest::AddDomainToDomainTable(PUNICODE_STRING DomainName, PUNICODE_STRING BindDomainName, BOOLEAN Netbios) { DFSSTATUS Status; Status = _DomainTable.AddDomain(DomainName, BindDomainName, Netbios); // // Buffer overflow indicates that we went over the hash table limit. // However, we keep adding so that we have a count of how many we // missed. // if (Status == ERROR_BUFFER_OVERFLOW) { Status = ERROR_SUCCESS; } return Status; } // // AddDomain: Insert the domain name in our hash table. // DFSSTATUS DfsDomainNameTable::AddDomain(PUNICODE_STRING DomainName, PUNICODE_STRING BindDomainName, BOOLEAN Netbios) { PDFS_DOMAIN_NAME_DATA pDomainData = NULL; ULONG DomainDataLength; DFSSTATUS Status = ERROR_SUCCESS; USHORT DomainNameLength = DomainName->Length; USHORT MaxDomainNameLength = DomainNameLength + sizeof(WCHAR); USHORT BindDomainNameLength = 0; USHORT MaxBindDomainNameLength = 0; NTSTATUS NtStatus; ULONG CurrentSize = 0; if (_DomainReferralSize > MAX_REFERRAL_SIZE) { _DomainsSkipped++; return ERROR_BUFFER_OVERFLOW; } CurrentSize = sizeof(DFS_REFERRAL_V3) + sizeof(UNICODE_PATH_SEP) + DomainName->Length + sizeof(UNICODE_NULL); if (BindDomainName != NULL) { BindDomainNameLength = BindDomainName->Length; MaxBindDomainNameLength = BindDomainNameLength + sizeof(WCHAR); } DomainDataLength = sizeof(DFS_DOMAIN_NAME_DATA) + MaxDomainNameLength + MaxBindDomainNameLength; pDomainData = (PDFS_DOMAIN_NAME_DATA) DfsAllocateForDomainTable(DomainDataLength); if (pDomainData != NULL) { RtlZeroMemory(pDomainData, DomainDataLength); pDomainData->Header.RefCount = 1; pDomainData->Header.pvKey = (PVOID)&pDomainData->DomainInfo.DomainName; pDomainData->Header.pData = (PVOID)pDomainData; pDomainData->DomainInfo.Netbios = Netbios; RtlInitUnicodeString(&pDomainData->DomainInfo.DomainName, NULL); pDomainData->DomainInfo.DomainName.Buffer = (LPWSTR)(pDomainData + 1); pDomainData->DomainInfo.DomainName.MaximumLength = MaxDomainNameLength; pDomainData->DomainInfo.DomainName.Length = DomainNameLength; RtlCopyMemory(pDomainData->DomainInfo.DomainName.Buffer, DomainName->Buffer, DomainNameLength); pDomainData->DomainInfo.DomainName.Buffer[DomainNameLength/sizeof(WCHAR)] = 0; if (BindDomainName) { pDomainData->DomainInfo.BindDomainName.Buffer = (LPWSTR)(((ULONG_PTR)(pDomainData + 1)) + MaxDomainNameLength); pDomainData->DomainInfo.BindDomainName.MaximumLength = MaxBindDomainNameLength; pDomainData->DomainInfo.BindDomainName.Length = BindDomainNameLength; RtlCopyMemory(pDomainData->DomainInfo.BindDomainName.Buffer, BindDomainName->Buffer, BindDomainNameLength); pDomainData->DomainInfo.BindDomainName.Buffer[BindDomainNameLength/sizeof(WCHAR)] = 0; pDomainData->DomainInfo.UseBindDomain = TRUE; } } else { Status = ERROR_NOT_ENOUGH_MEMORY; } if (Status == ERROR_SUCCESS) { NtStatus = SHashInsertKey(_pDomainNameTable, pDomainData, &pDomainData->DomainInfo.DomainName, SHASH_FAIL_IFFOUND); if (NtStatus != STATUS_SUCCESS) { DfsDeallocateForDomainTable( pDomainData ); // // if we have collision, we already know this name and // we can ignore this error. // if (NtStatus != STATUS_OBJECT_NAME_COLLISION) { Status = RtlNtStatusToDosError( NtStatus ); } } else { // // we successfully added to the table, bump up our // DomainReferralSize. // InterlockedDecrement(&pDomainData->Header.RefCount); _DomainReferralSize += CurrentSize; } } return Status; } // // InvalidateDomainTable: Run through the hash and throw out all our // entries. // // VOID DfsDomainNameTable::InvalidateDomainTable(VOID) { SHASH_ITERATOR Iter; PDFS_DOMAIN_NAME_DATA pExistingData = NULL; NTSTATUS NtStatus; pExistingData = (PDFS_DOMAIN_NAME_DATA) SHashStartEnumerate(&Iter, _pDomainNameTable); while (pExistingData != NULL) { // // Remove this item. There's nothing we can do if we hit errors // except to keep going. // NtStatus = SHashRemoveKey(_pDomainNameTable, &pExistingData->DomainInfo.DomainName, NULL ); // // ignore status here, since we want to keep going. // pExistingData = (PDFS_DOMAIN_NAME_DATA) SHashNextEnumerate(&Iter, _pDomainNameTable); } SHashFinishEnumerate(&Iter, _pDomainNameTable); } // // GetCount: Run through our hash and get a count of the number of // items. I wish we could ask the hash table for the count. // ULONG DfsDomainNameTable::GetCount(VOID) { SHASH_ITERATOR Iter; PDFS_DOMAIN_NAME_DATA pExistingData = NULL; ULONG nEntries = 0; pExistingData = (PDFS_DOMAIN_NAME_DATA) SHashStartEnumerate(&Iter, _pDomainNameTable); while (pExistingData != NULL) { nEntries++; pExistingData = (PDFS_DOMAIN_NAME_DATA) SHashNextEnumerate(&Iter, _pDomainNameTable); } SHashFinishEnumerate(&Iter, _pDomainNameTable); return nEntries; } // // FinishDomainNameEnumerate // VOID DfsDomainNameTable::FinishDomainNameEnumerate( SHASH_ITERATOR *pIter ) { SHashFinishEnumerate( pIter, _pDomainNameTable); } // Allocate and deallocate the cache data entry PVOID DfsAllocateForDomainTable(ULONG Size ) { PVOID RetValue = NULL; if (Size) { RetValue = (PVOID) new BYTE[Size]; if (RetValue != NULL) { RtlZeroMemory( RetValue, Size ); } } return RetValue; } VOID DfsDeallocateForDomainTable(PVOID pPointer ) { if (pPointer) { delete [] (PBYTE)pPointer; } }