2318 lines
56 KiB
C++
2318 lines
56 KiB
C++
|
|
||
|
//+-----------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation 1992 - 1996
|
||
|
//
|
||
|
// File: refer.cxx
|
||
|
//
|
||
|
// Contents: Routines for interdomain referrals
|
||
|
//
|
||
|
//
|
||
|
// History: 26-Nov-1996 MikeSw Created
|
||
|
//
|
||
|
// Notes: The list of domains could be kept as a splay tree for faster
|
||
|
// searches & inserts.
|
||
|
//
|
||
|
//------------------------------------------------------------------------
|
||
|
|
||
|
#include "kdcsvr.hxx"
|
||
|
#include <lsarpc.h>
|
||
|
extern "C"
|
||
|
{
|
||
|
#include <dns.h> // DNS_MAX_NAME_LENGTH
|
||
|
#include <ntdsa.h> // CrackSingleName
|
||
|
}
|
||
|
|
||
|
|
||
|
LIST_ENTRY KdcDomainList;
|
||
|
RTL_CRITICAL_SECTION KdcDomainListLock;
|
||
|
BOOLEAN KdcDomainListInitialized = FALSE;
|
||
|
|
||
|
LIST_ENTRY KdcReferralCache;
|
||
|
RTL_CRITICAL_SECTION KdcReferralCacheLock;
|
||
|
BOOLEAN KdcReferralCacheInitialized = FALSE;
|
||
|
UNICODE_STRING KdcForestRootDomainName = {0};
|
||
|
|
||
|
#define KdcLockDomainList() (RtlEnterCriticalSection(&KdcDomainListLock))
|
||
|
#define KdcUnlockDomainList() (RtlLeaveCriticalSection(&KdcDomainListLock))
|
||
|
|
||
|
#define KdcLockReferralCache() (RtlEnterCriticalSection(&KdcReferralCacheLock))
|
||
|
#define KdcUnlockReferralCache() (RtlLeaveCriticalSection(&KdcReferralCacheLock))
|
||
|
|
||
|
#define KdcReferenceDomainInfo(_x_) \
|
||
|
InterlockedIncrement(&(_x_)->References)
|
||
|
|
||
|
#define KdcReferenceReferralCacheEntry(_x_) \
|
||
|
InterlockedIncrement(&(_x_)->References)
|
||
|
|
||
|
// temp #defines
|
||
|
#define NEW_KDCEVENT_TRUSTLIST_LOOP 0xC000000C
|
||
|
static TCHAR THIS_FILE[]=TEXT(__FILE__);
|
||
|
#define FILENO FILENO_REFER
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcDereferenceReferralCacheEntry
|
||
|
//
|
||
|
// Synopsis: Derefernce a domain info structure. If the reference
|
||
|
// count goes to zero the structure is freed.
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
VOID
|
||
|
KdcDereferenceReferralCacheEntry(
|
||
|
IN PREFERRAL_CACHE_ENTRY CacheEntry
|
||
|
)
|
||
|
{
|
||
|
if (InterlockedDecrement(&CacheEntry->References) == 0)
|
||
|
{
|
||
|
KdcLockReferralCache();
|
||
|
CacheEntry->ListEntry.Blink->Flink = CacheEntry->ListEntry.Flink;
|
||
|
CacheEntry->ListEntry.Flink->Blink = CacheEntry->ListEntry.Blink;
|
||
|
KdcUnlockReferralCache();
|
||
|
|
||
|
KerbFreeString(&CacheEntry->RealmName);
|
||
|
MIDL_user_free(CacheEntry);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcDereferenceReferralCacheEntry
|
||
|
//
|
||
|
// Synopsis: Derefernce a domain info structure. If the reference
|
||
|
// count goes to zero the structure is freed.
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
KERBERR
|
||
|
KdcAddReferralCacheEntry(
|
||
|
IN PUNICODE_STRING RealmName,
|
||
|
IN ULONG CacheFlags
|
||
|
)
|
||
|
{
|
||
|
|
||
|
PREFERRAL_CACHE_ENTRY CacheEntry = NULL;
|
||
|
KERBERR KerbErr;
|
||
|
TimeStamp CurrentTime;
|
||
|
|
||
|
|
||
|
CacheEntry = (PREFERRAL_CACHE_ENTRY) MIDL_user_allocate(sizeof(REFERRAL_CACHE_ENTRY));
|
||
|
if (NULL == CacheEntry)
|
||
|
{
|
||
|
// We're low on memory, non-fatal
|
||
|
return KRB_ERR_GENERIC;
|
||
|
}
|
||
|
|
||
|
KerbErr = KerbDuplicateString(
|
||
|
&(CacheEntry->RealmName),
|
||
|
RealmName
|
||
|
);
|
||
|
|
||
|
if (!KERB_SUCCESS(KerbErr))
|
||
|
{
|
||
|
MIDL_user_free(CacheEntry);
|
||
|
return KerbErr;
|
||
|
}
|
||
|
|
||
|
CacheEntry->CacheFlags = CacheFlags;
|
||
|
|
||
|
// Set cache timeout == 10 minutes
|
||
|
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
|
||
|
CacheEntry->EndTime.QuadPart = CurrentTime.QuadPart + (LONGLONG) 60*10*10000000;
|
||
|
|
||
|
|
||
|
InterlockedIncrement(&CacheEntry->References);
|
||
|
|
||
|
|
||
|
KdcLockReferralCache();
|
||
|
InsertHeadList(
|
||
|
&KdcReferralCache,
|
||
|
&(CacheEntry->ListEntry)
|
||
|
);
|
||
|
|
||
|
KdcUnlockReferralCache();
|
||
|
|
||
|
DebugLog((DEB_TRACE, "Added referal cache entry - %wZ State: %x\n",
|
||
|
RealmName, CacheFlags));
|
||
|
|
||
|
|
||
|
return KerbErr;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcLookupReferralCacheEntry
|
||
|
//
|
||
|
// Synopsis: Derefernce a domain info structure. If the reference
|
||
|
// count goes to zero the structure is freed.
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
KERBERR
|
||
|
KdcLocateReferralCacheEntry(
|
||
|
IN PUNICODE_STRING RealmName,
|
||
|
IN ULONG NewFlags,
|
||
|
OUT PULONG CacheState
|
||
|
)
|
||
|
{
|
||
|
|
||
|
KERBERR KerbErr = KDC_ERR_NONE;
|
||
|
PLIST_ENTRY ListEntry;
|
||
|
PREFERRAL_CACHE_ENTRY CacheEntry = NULL;
|
||
|
BOOLEAN ListLocked = FALSE;
|
||
|
BOOLEAN Found = FALSE;
|
||
|
|
||
|
*CacheState = KDC_NO_ENTRY;
|
||
|
KdcLockReferralCache();
|
||
|
ListLocked = TRUE;
|
||
|
|
||
|
//
|
||
|
// Go through the binding cache looking for the correct entry
|
||
|
//
|
||
|
|
||
|
for (ListEntry = KdcReferralCache.Flink ;
|
||
|
ListEntry != KdcReferralCache.Blink ;
|
||
|
ListEntry = ListEntry->Flink )
|
||
|
{
|
||
|
CacheEntry = CONTAINING_RECORD(ListEntry, REFERRAL_CACHE_ENTRY, ListEntry.Flink);
|
||
|
|
||
|
if (RtlEqualUnicodeString(
|
||
|
&CacheEntry->RealmName,
|
||
|
RealmName,
|
||
|
TRUE // case insensitive
|
||
|
))
|
||
|
{
|
||
|
TimeStamp CurrentTime;
|
||
|
GetSystemTimeAsFileTime((PFILETIME) &CurrentTime );
|
||
|
|
||
|
//
|
||
|
// Update the flags & time on this cache entry
|
||
|
//
|
||
|
if (NewFlags != KDC_NO_ENTRY)
|
||
|
{
|
||
|
CacheEntry->CacheFlags = NewFlags;
|
||
|
CacheEntry->EndTime.QuadPart = CurrentTime.QuadPart + (LONGLONG) 10*60*10000000;
|
||
|
Found = TRUE;
|
||
|
}
|
||
|
else // just a lookup
|
||
|
{
|
||
|
if (KdcGetTime(CacheEntry->EndTime) < KdcGetTime(CurrentTime))
|
||
|
{
|
||
|
DebugLog((DEB_TRACE, "Time: Purging KDC Referral cache entry (%x : refcount %x) for %wZ \n",
|
||
|
CacheEntry,CacheEntry->References, RealmName));
|
||
|
KdcDereferenceReferralCacheEntry(CacheEntry);
|
||
|
|
||
|
}
|
||
|
else // got our flags
|
||
|
{
|
||
|
*CacheState = CacheEntry->CacheFlags;
|
||
|
DebugLog((DEB_TRACE, "Found entry for %wZ, flags - %x\n",
|
||
|
RealmName, *CacheState));
|
||
|
|
||
|
Found = TRUE;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If it wasn't found, but if we asked for any new flags
|
||
|
// we want a new cache entry
|
||
|
if (!Found && (NewFlags != KDC_NO_ENTRY))
|
||
|
{
|
||
|
DebugLog((DEB_TRACE, "Adding referral cache entry - %wZ State: %x\n",
|
||
|
RealmName, NewFlags));
|
||
|
|
||
|
KerbErr = KdcAddReferralCacheEntry(
|
||
|
RealmName,
|
||
|
NewFlags
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
if (ListLocked)
|
||
|
{
|
||
|
KdcUnlockReferralCache();
|
||
|
}
|
||
|
|
||
|
return KerbErr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcFreeDomainInfo
|
||
|
//
|
||
|
// Synopsis:
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
VOID
|
||
|
KdcFreeDomainInfo(
|
||
|
IN PKDC_DOMAIN_INFO DomainInfo
|
||
|
)
|
||
|
{
|
||
|
if (ARGUMENT_PRESENT(DomainInfo))
|
||
|
{
|
||
|
KerbFreeString(&DomainInfo->NetbiosName);
|
||
|
KerbFreeString(&DomainInfo->DnsName);
|
||
|
if (NULL != DomainInfo->Sid)
|
||
|
{
|
||
|
MIDL_user_free(DomainInfo->Sid);
|
||
|
}
|
||
|
MIDL_user_free(DomainInfo);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcDereferenceDomainInfo
|
||
|
//
|
||
|
// Synopsis: Derefernce a domain info structure. If the reference
|
||
|
// count goes to zero the structure is freed.
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
VOID
|
||
|
KdcDereferenceDomainInfo(
|
||
|
IN PKDC_DOMAIN_INFO DomainInfo
|
||
|
)
|
||
|
{
|
||
|
if (InterlockedDecrement(&DomainInfo->References) == 0)
|
||
|
{
|
||
|
KdcFreeDomainInfo(DomainInfo);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcCheckForInterdomainReferral
|
||
|
//
|
||
|
// Synopsis: This function makes a determination that an interdomain referral
|
||
|
// that we're processing is destined for an external forest. This
|
||
|
// is important because we won't have any referral information about
|
||
|
// the destination forest, so we've got to target the root of our
|
||
|
// enterprise instead.
|
||
|
//
|
||
|
// TBD: This function currently uses the CrackSingleName API (with
|
||
|
// composed KRBTGT name to verify we're going for an xforest trust.
|
||
|
// We should cache both positive and negative results so that we can
|
||
|
// eliminate that call.
|
||
|
//
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments: ReferralTarget - Receives ticket info for target domain
|
||
|
// ReferralRealm - Receives realm name of referral realm, if present
|
||
|
// DestinationDomain - Target domain name
|
||
|
// ExactMatch - The target domain has to be trusted by this domain
|
||
|
//
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
KERBERR
|
||
|
KdcCheckForCrossForestReferral(
|
||
|
OUT PKDC_TICKET_INFO ReferralTarget,
|
||
|
OUT OPTIONAL PUNICODE_STRING ReferralRealm,
|
||
|
OUT PKERB_EXT_ERROR pExtendedError,
|
||
|
IN PUNICODE_STRING DestinationDomain,
|
||
|
IN ULONG NameFlags
|
||
|
)
|
||
|
{
|
||
|
|
||
|
KERBERR KerbErr = KDC_ERR_NONE;
|
||
|
NTSTATUS Status;
|
||
|
LPWSTR KrbtgtSpn = NULL;
|
||
|
UNICODE_STRING ServiceName = {0 , 0, NULL };
|
||
|
WCHAR CrackedDnsDomain [DNS_MAX_NAME_LENGTH + 1];
|
||
|
ULONG CrackedDomainLength = (DNS_MAX_NAME_LENGTH+1) * sizeof(WCHAR);
|
||
|
WCHAR CrackedName[UNLEN+DNS_MAX_NAME_LENGTH + 2];
|
||
|
ULONG CrackedNameLength = ((UNLEN+DNS_MAX_NAME_LENGTH + 2) * sizeof(WCHAR));
|
||
|
ULONG CrackError = 0, CacheFlags = 0;
|
||
|
|
||
|
|
||
|
//
|
||
|
// Is it in the realm list of recent rejectees or
|
||
|
// positive hits?
|
||
|
//
|
||
|
KerbErr = KdcLocateReferralCacheEntry(
|
||
|
DestinationDomain,
|
||
|
0, // no new flags
|
||
|
&CacheFlags
|
||
|
);
|
||
|
|
||
|
if (CacheFlags == KDC_NO_ENTRY)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Compose an SPN related to our KRBTGT account
|
||
|
//
|
||
|
KerbErr = KerbBuildUnicodeSpn(
|
||
|
DestinationDomain,
|
||
|
SecData.KdcServiceName(),
|
||
|
&ServiceName
|
||
|
);
|
||
|
|
||
|
if (!KERB_SUCCESS(KerbErr))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
KrbtgtSpn = KerbBuildNullTerminatedString(&ServiceName);
|
||
|
if (NULL == KrbtgtSpn)
|
||
|
{
|
||
|
KerbErr = KRB_ERR_GENERIC;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Look it up
|
||
|
//
|
||
|
Status = CrackSingleName(
|
||
|
DS_SERVICE_PRINCIPAL_NAME, // we know its an SPN
|
||
|
DS_NAME_FLAG_TRUST_REFERRAL | DS_NAME_FLAG_GCVERIFY,
|
||
|
KrbtgtSpn,
|
||
|
DS_UNIQUE_ID_NAME,
|
||
|
&CrackedDomainLength,
|
||
|
CrackedDnsDomain,
|
||
|
&CrackedNameLength,
|
||
|
CrackedName,
|
||
|
&CrackError
|
||
|
);
|
||
|
|
||
|
// Any error, or CrackError other than xforest result
|
||
|
// means we don't know where this referral is headed.
|
||
|
// TBD: Recovery?
|
||
|
if (!NT_SUCCESS(Status) || (CrackError != DS_NAME_ERROR_TRUST_REFERRAL))
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,
|
||
|
"KDC presented w/ a unknown Xrealm TGT (%wZ)\n",
|
||
|
DestinationDomain));
|
||
|
|
||
|
// Add a negative entry
|
||
|
KdcLocateReferralCacheEntry(
|
||
|
DestinationDomain,
|
||
|
KDC_UNTRUSTED_REALM,
|
||
|
&CacheFlags
|
||
|
);
|
||
|
|
||
|
KerbErr = KDC_ERR_PATH_NOT_ACCEPTED;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
KdcLocateReferralCacheEntry(
|
||
|
DestinationDomain,
|
||
|
KDC_TRUSTED_REALM,
|
||
|
&CacheFlags
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
else if (CacheFlags == KDC_UNTRUSTED_REALM)
|
||
|
{
|
||
|
|
||
|
DebugLog((DEB_ERROR,
|
||
|
"Checking for X Forest on Untrusted Realm %wZ\n",
|
||
|
DestinationDomain
|
||
|
));
|
||
|
|
||
|
KerbErr = KDC_ERR_PATH_NOT_ACCEPTED;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now, we're pretty sure we're going to hit this other forest,
|
||
|
// somewhere. For SPNs, we've got to find the root domain of our forest
|
||
|
// to finish off the x realm transaction. For UPNs, just
|
||
|
// return the cracked domain.
|
||
|
//
|
||
|
if ((NameFlags & KDC_NAME_SERVER) != 0)
|
||
|
{
|
||
|
UNICODE_STRING ForestRoot = {0};
|
||
|
|
||
|
|
||
|
Status = SecData.GetKdcForestRoot(&ForestRoot);
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
|
||
|
KerbErr = KdcFindReferralTarget(
|
||
|
ReferralTarget,
|
||
|
ReferralRealm,
|
||
|
pExtendedError,
|
||
|
&ForestRoot,
|
||
|
FALSE, // we'll accept closest
|
||
|
FALSE // Outbound.
|
||
|
);
|
||
|
|
||
|
if (!KERB_SUCCESS(KerbErr))
|
||
|
{
|
||
|
DebugLog((DEB_ERROR, "Couldn't find referral info for root of forest \n"));
|
||
|
goto Cleanup;
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// swap w/ our dns domain for referral realm, unless we're
|
||
|
// processing a UPN
|
||
|
//
|
||
|
KerbFreeString(ReferralRealm);
|
||
|
KerbFreeString(&ForestRoot);
|
||
|
}
|
||
|
|
||
|
KerbDuplicateString(
|
||
|
ReferralRealm,
|
||
|
DestinationDomain
|
||
|
);
|
||
|
|
||
|
|
||
|
Cleanup:
|
||
|
|
||
|
|
||
|
KerbFreeString(&ServiceName);
|
||
|
|
||
|
if (KrbtgtSpn != NULL)
|
||
|
{
|
||
|
MIDL_user_free(KrbtgtSpn);
|
||
|
}
|
||
|
|
||
|
|
||
|
return ( KerbErr );
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcFindReferralTarget
|
||
|
//
|
||
|
// Synopsis: Takes a domain name as a parameter and returns information
|
||
|
// in the closest available domain. For heirarchical links,
|
||
|
// this would be a parent or child. If a cross link is available,
|
||
|
// this might be the other side of a cross link. For inter-
|
||
|
// organization links, this might be a whole different tree
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments: ReferralTarget - Receives ticket info for target domain
|
||
|
// ReferralRealm - Receives realm name of referral realm, if present
|
||
|
// DestinationDomain - Target domain name
|
||
|
// ExactMatch - The target domain has to be trusted by this domain
|
||
|
//
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
KERBERR
|
||
|
KdcFindReferralTarget(
|
||
|
OUT PKDC_TICKET_INFO ReferralTarget,
|
||
|
OUT OPTIONAL PUNICODE_STRING ReferralRealm,
|
||
|
OUT PKERB_EXT_ERROR pExtendedError,
|
||
|
IN PUNICODE_STRING DestinationDomain,
|
||
|
IN BOOLEAN ExactMatch,
|
||
|
IN BOOLEAN InboundWanted
|
||
|
)
|
||
|
{
|
||
|
KERBERR KerbErr = KDC_ERR_NONE;
|
||
|
PKDC_DOMAIN_INFO DomainInfo = NULL;
|
||
|
PKDC_DOMAIN_INFO ClosestRoute = NULL;
|
||
|
UNICODE_STRING TempRealmName;
|
||
|
BOOLEAN fListLocked = FALSE;
|
||
|
|
||
|
TRACE(KDC, KdcFindReferralTarget, DEB_FUNCTION);
|
||
|
|
||
|
RtlInitUnicodeString(
|
||
|
ReferralRealm,
|
||
|
NULL
|
||
|
);
|
||
|
D_DebugLog((DEB_TRACE,"Generating referral for target %wZ\n",DestinationDomain));
|
||
|
|
||
|
if (InboundWanted)
|
||
|
{
|
||
|
KdcLockDomainList();
|
||
|
|
||
|
KerbErr = KdcLookupDomainName(
|
||
|
&DomainInfo,
|
||
|
DestinationDomain,
|
||
|
&KdcDomainList
|
||
|
);
|
||
|
|
||
|
if (!KERB_SUCCESS(KerbErr) || ((DomainInfo->Flags & KDC_TRUST_INBOUND) == 0))
|
||
|
{
|
||
|
DebugLog((DEB_WARN,"Failed to find inbound referral target %wZ\n",DestinationDomain));
|
||
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_UNABLE_TO_REFER, FILENO, __LINE__);
|
||
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
||
|
fListLocked = TRUE;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set the closest route to be this domain & add a reference for
|
||
|
// the extra pointer
|
||
|
//
|
||
|
|
||
|
KdcReferenceDomainInfo(DomainInfo);
|
||
|
ClosestRoute = DomainInfo;
|
||
|
KdcUnlockDomainList();
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// Check the list of domains for the target
|
||
|
//
|
||
|
|
||
|
KdcLockDomainList();
|
||
|
|
||
|
KerbErr = KdcLookupDomainRoute(
|
||
|
&DomainInfo,
|
||
|
&ClosestRoute,
|
||
|
DestinationDomain,
|
||
|
&KdcDomainList
|
||
|
);
|
||
|
|
||
|
KdcUnlockDomainList();
|
||
|
|
||
|
if (!KERB_SUCCESS(KerbErr))
|
||
|
{
|
||
|
DebugLog((DEB_WARN,"Failed to find referral target %wZ\n",DestinationDomain));
|
||
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_UNABLE_TO_REFER, FILENO, __LINE__);
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check to see if we needed & got an exact match
|
||
|
//
|
||
|
|
||
|
if (ExactMatch &&
|
||
|
(DomainInfo != ClosestRoute))
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"Needed exact match and got a transitively-trusted domain.\n" ));
|
||
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_UNABLE_TO_REFER, FILENO, __LINE__);
|
||
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Return the referral realm, if present
|
||
|
//
|
||
|
|
||
|
if (ARGUMENT_PRESENT(ReferralRealm))
|
||
|
{
|
||
|
if (!NT_SUCCESS(KerbDuplicateString(
|
||
|
ReferralRealm,
|
||
|
&DomainInfo->DnsName
|
||
|
)))
|
||
|
{
|
||
|
KerbErr = KRB_ERR_GENERIC;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now get the ticket info for the domain
|
||
|
//
|
||
|
|
||
|
KerbErr = KdcGetTicketInfoForDomain(
|
||
|
ReferralTarget,
|
||
|
pExtendedError,
|
||
|
ClosestRoute,
|
||
|
InboundWanted ? Inbound : Outbound
|
||
|
);
|
||
|
|
||
|
if (!KERB_SUCCESS(KerbErr))
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"Failed to get ticket info for domain %wZ: 0x%x. %ws, line %d\n",
|
||
|
DestinationDomain, KerbErr , __FILE__, __LINE__ ));
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
Cleanup:
|
||
|
if (DomainInfo != NULL)
|
||
|
{
|
||
|
KdcDereferenceDomainInfo(DomainInfo);
|
||
|
}
|
||
|
if (ClosestRoute != NULL)
|
||
|
{
|
||
|
KdcDereferenceDomainInfo(ClosestRoute);
|
||
|
}
|
||
|
if (fListLocked)
|
||
|
{
|
||
|
KdcUnlockDomainList();
|
||
|
}
|
||
|
if (!KERB_SUCCESS(KerbErr) && ARGUMENT_PRESENT(ReferralRealm))
|
||
|
{
|
||
|
KerbFreeString(ReferralRealm);
|
||
|
}
|
||
|
//
|
||
|
// Remap the error
|
||
|
//
|
||
|
|
||
|
if (KerbErr == KDC_ERR_S_PRINCIPAL_UNKNOWN)
|
||
|
{
|
||
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
return(KerbErr);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcGetTicketInfoForDomain
|
||
|
//
|
||
|
// Synopsis: Retrieves the ticket information for a domain
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
KERBERR
|
||
|
KdcGetTicketInfoForDomain(
|
||
|
OUT PKDC_TICKET_INFO TicketInfo,
|
||
|
OUT PKERB_EXT_ERROR pExtendedError,
|
||
|
IN PKDC_DOMAIN_INFO DomainInfo,
|
||
|
IN KDC_DOMAIN_INFO_DIRECTION Direction
|
||
|
)
|
||
|
{
|
||
|
PLSAPR_TRUSTED_DOMAIN_INFO TrustInfo = NULL;
|
||
|
PLSAPR_AUTH_INFORMATION AuthInfo = NULL;
|
||
|
PLSAPR_AUTH_INFORMATION OldAuthInfo = NULL;
|
||
|
KERBERR KerbErr = KDC_ERR_NONE;
|
||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
UNICODE_STRING Password;
|
||
|
ULONG PasswordLength = 0;
|
||
|
LARGE_INTEGER CurrentTime;
|
||
|
ULONG cbSid;
|
||
|
|
||
|
|
||
|
TRACE(KDC, KdcGetTicketInfoForDomain, DEB_FUNCTION);
|
||
|
|
||
|
//
|
||
|
// Get information about the domain. Note that we use the dns name
|
||
|
// field. For NT5 domains in the enterprise this will contain the
|
||
|
// real DNS name. For non- tree domains it will contain the name from
|
||
|
// the trusted domain object, so this call should always succeed.
|
||
|
//
|
||
|
|
||
|
Status = LsarQueryTrustedDomainInfoByName(
|
||
|
GlobalPolicyHandle,
|
||
|
(PLSAPR_UNICODE_STRING) &DomainInfo->DnsName,
|
||
|
TrustedDomainAuthInformation,
|
||
|
&TrustInfo
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
//
|
||
|
// If the domain didn't exist, we have a problem because our
|
||
|
// cache is out of date. Or, we're loooking for our domain.. this
|
||
|
// is always going to return STATUS_OBJECT_NAME_NOT_FOUND
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// WAS BUG: reload the cache -- this is handled in the call to
|
||
|
// LSAIKerberosRegisterTrustNotification(), which will then
|
||
|
// reload the cache using KdcTrustChangeCallback(). As long
|
||
|
// as this callback is solid (?), we should never fail the
|
||
|
// above. If needed, we can revisit. -TS
|
||
|
//
|
||
|
|
||
|
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"Domain %wZ in cache but object doesn't exist. %ws, line %d\n",
|
||
|
&DomainInfo->DnsName, THIS_FILE, __LINE__ ));
|
||
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"Failed to query domain info for %wZ: 0x%x. %ws, line %d\n",
|
||
|
&DomainInfo->DnsName, Status, THIS_FILE, __LINE__ ));
|
||
|
KerbErr = KRB_ERR_GENERIC;
|
||
|
}
|
||
|
|
||
|
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Note: Kerberos direction is opposite normal direction
|
||
|
//
|
||
|
|
||
|
if (Direction == Outbound)
|
||
|
{
|
||
|
AuthInfo = TrustInfo->TrustedAuthInfo.IncomingAuthenticationInformation;
|
||
|
OldAuthInfo = TrustInfo->TrustedAuthInfo.IncomingPreviousAuthenticationInformation;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AuthInfo = TrustInfo->TrustedAuthInfo.OutgoingAuthenticationInformation;
|
||
|
OldAuthInfo = TrustInfo->TrustedAuthInfo.OutgoingPreviousAuthenticationInformation;
|
||
|
}
|
||
|
|
||
|
if (AuthInfo == NULL)
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"No auth info for this trust: %wZ. %ws, line %d\n",
|
||
|
&DomainInfo->DnsName, THIS_FILE, __LINE__ ));
|
||
|
FILL_EXT_ERROR(pExtendedError, STATUS_TRUSTED_DOMAIN_FAILURE, FILENO, __LINE__);
|
||
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check the last update time. If the new auth info is too new, we want
|
||
|
// to keep using the old one.
|
||
|
//
|
||
|
if (OldAuthInfo != NULL)
|
||
|
{
|
||
|
|
||
|
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
|
||
|
if (CurrentTime.QuadPart - AuthInfo->LastUpdateTime.QuadPart < SecData.KdcDomainPasswordReplSkew().QuadPart)
|
||
|
{
|
||
|
PLSAPR_AUTH_INFORMATION TempAuthInfo;
|
||
|
|
||
|
//
|
||
|
// Swap current & old auth info to encrypt tickets with old password
|
||
|
//
|
||
|
|
||
|
TempAuthInfo = AuthInfo;
|
||
|
AuthInfo = OldAuthInfo;
|
||
|
OldAuthInfo = TempAuthInfo;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// So now that we have the auth info we need to build a ticket info
|
||
|
//
|
||
|
|
||
|
Password.Length = Password.MaximumLength = (USHORT) AuthInfo->AuthInfoLength;
|
||
|
Password.Buffer = (LPWSTR) AuthInfo->AuthInfo;
|
||
|
|
||
|
Status = KdcBuildPasswordList(
|
||
|
&Password,
|
||
|
&DomainInfo->DnsName,
|
||
|
SecData.KdcDnsRealmName(),
|
||
|
DomainTrustAccount,
|
||
|
NULL, // no stored creds
|
||
|
0, // no stored creds
|
||
|
FALSE, // don't marshall
|
||
|
DomainInfo->Type != TRUST_TYPE_MIT, // don;t include builtin crypt types for mit trusts,
|
||
|
(AuthInfo->AuthType & TRUST_AUTH_TYPE_NT4OWF) ? KERB_PRIMARY_CRED_OWF_ONLY : 0,
|
||
|
Direction,
|
||
|
&TicketInfo->Passwords,
|
||
|
&PasswordLength
|
||
|
);
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
|
||
|
KerbErr = KRB_ERR_GENERIC;
|
||
|
goto Cleanup;
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Build the old password list as well
|
||
|
//
|
||
|
|
||
|
if (OldAuthInfo != NULL)
|
||
|
{
|
||
|
|
||
|
|
||
|
Password.Length = Password.MaximumLength = (USHORT) OldAuthInfo->AuthInfoLength;
|
||
|
Password.Buffer = (LPWSTR) OldAuthInfo->AuthInfo;
|
||
|
|
||
|
Status = KdcBuildPasswordList(
|
||
|
&Password,
|
||
|
&DomainInfo->DnsName,
|
||
|
SecData.KdcDnsRealmName(),
|
||
|
DomainTrustAccount,
|
||
|
NULL,
|
||
|
0,
|
||
|
FALSE, // don't marshall
|
||
|
DomainInfo->Type != TRUST_TYPE_MIT, // don;t include builtin crypt types for mit trusts,
|
||
|
(OldAuthInfo->AuthType & TRUST_AUTH_TYPE_NT4OWF) ? KERB_PRIMARY_CRED_OWF_ONLY : 0,
|
||
|
Direction,
|
||
|
&TicketInfo->OldPasswords,
|
||
|
&PasswordLength
|
||
|
);
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
|
||
|
KerbErr = KRB_ERR_GENERIC;
|
||
|
goto Cleanup;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if (!NT_SUCCESS(KerbDuplicateString(
|
||
|
&TicketInfo->AccountName,
|
||
|
&DomainInfo->DnsName
|
||
|
)))
|
||
|
{
|
||
|
KerbErr = KRB_ERR_GENERIC;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// BUG 73479: need to get ticket options
|
||
|
//
|
||
|
|
||
|
TicketInfo->fTicketOpts = AUTH_REQ_PER_USER_FLAGS |
|
||
|
AUTH_REQ_ALLOW_NOADDRESS |
|
||
|
AUTH_REQ_ALLOW_ENC_TKT_IN_SKEY |
|
||
|
AUTH_REQ_ALLOW_VALIDATE |
|
||
|
AUTH_REQ_OK_AS_DELEGATE;
|
||
|
|
||
|
if ((DomainInfo->Attributes & TRUST_ATTRIBUTE_NON_TRANSITIVE) == 0)
|
||
|
{
|
||
|
TicketInfo->fTicketOpts |= AUTH_REQ_TRANSITIVE_TRUST;
|
||
|
}
|
||
|
TicketInfo->PasswordExpires = tsInfinity;
|
||
|
TicketInfo->UserAccountControl = USER_INTERDOMAIN_TRUST_ACCOUNT;
|
||
|
|
||
|
if (DomainInfo->Sid)
|
||
|
{
|
||
|
cbSid = RtlLengthSid(DomainInfo->Sid);
|
||
|
TicketInfo->TrustSid = (PSID) MIDL_user_allocate(cbSid);
|
||
|
if (TicketInfo->TrustSid == NULL)
|
||
|
{
|
||
|
KerbErr = KRB_ERR_GENERIC;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
Status = RtlCopySid (
|
||
|
cbSid,
|
||
|
TicketInfo->TrustSid,
|
||
|
DomainInfo->Sid
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
KerbErr = KRB_ERR_GENERIC;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TicketInfo->TrustAttributes = DomainInfo->Attributes;
|
||
|
|
||
|
//
|
||
|
// Add trusted forest UNICODE STRING onto ticket info
|
||
|
// if its Xforest
|
||
|
//
|
||
|
if ((DomainInfo->Attributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE ) != 0)
|
||
|
{
|
||
|
TicketInfo->TrustAttributes = TRUST_ATTRIBUTE_FOREST_TRANSITIVE;
|
||
|
|
||
|
KerbErr = KerbDuplicateString(
|
||
|
&(TicketInfo->TrustedForest),
|
||
|
&DomainInfo->DnsName
|
||
|
);
|
||
|
|
||
|
|
||
|
|
||
|
if (!KERB_SUCCESS(KerbErr))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Cleanup:
|
||
|
|
||
|
|
||
|
if (TrustInfo != NULL)
|
||
|
{
|
||
|
LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO(
|
||
|
TrustedDomainAuthInformation,
|
||
|
TrustInfo
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
if (!KERB_SUCCESS(KerbErr))
|
||
|
{
|
||
|
FreeTicketInfo(TicketInfo);
|
||
|
}
|
||
|
return(KerbErr);
|
||
|
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcLookupDomainName
|
||
|
//
|
||
|
// Synopsis: Looks up a domain name in the list of domains and returns
|
||
|
// the domain info
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
KERBERR
|
||
|
KdcLookupDomainName(
|
||
|
OUT PKDC_DOMAIN_INFO * DomainInfo,
|
||
|
IN PUNICODE_STRING DomainName,
|
||
|
IN PLIST_ENTRY DomainList
|
||
|
)
|
||
|
{
|
||
|
PLIST_ENTRY ListEntry;
|
||
|
PKDC_DOMAIN_INFO Domain;
|
||
|
|
||
|
TRACE(KDC, KdcLookupDomainName, DEB_FUNCTION);
|
||
|
|
||
|
for (ListEntry = DomainList->Flink;
|
||
|
ListEntry != DomainList ;
|
||
|
ListEntry = ListEntry->Flink )
|
||
|
{
|
||
|
Domain = (PKDC_DOMAIN_INFO) CONTAINING_RECORD(ListEntry, KDC_DOMAIN_INFO, Next);
|
||
|
if (KerbCompareUnicodeRealmNames(
|
||
|
DomainName,
|
||
|
&Domain->DnsName
|
||
|
) || // case insensitive
|
||
|
RtlEqualUnicodeString(
|
||
|
DomainName,
|
||
|
&Domain->NetbiosName,
|
||
|
TRUE)) // case insensitive
|
||
|
{
|
||
|
|
||
|
KdcReferenceDomainInfo(Domain);
|
||
|
*DomainInfo = Domain;
|
||
|
return(KDC_ERR_NONE);
|
||
|
}
|
||
|
}
|
||
|
return(KDC_ERR_S_PRINCIPAL_UNKNOWN);
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcLookupDomainRoute
|
||
|
//
|
||
|
// Synopsis: Looks up a domain name in the list of domains and returns
|
||
|
// the domain info for the closest domain.
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
KERBERR
|
||
|
KdcLookupDomainRoute(
|
||
|
OUT PKDC_DOMAIN_INFO * DomainInfo,
|
||
|
OUT PKDC_DOMAIN_INFO * ClosestRoute,
|
||
|
IN PUNICODE_STRING DomainName,
|
||
|
IN PLIST_ENTRY DomainList
|
||
|
)
|
||
|
{
|
||
|
KERBERR KerbErr;
|
||
|
PKDC_DOMAIN_INFO Domain;
|
||
|
|
||
|
TRACE(KDC, KdcLookupDomainRoute, DEB_FUNCTION);
|
||
|
|
||
|
|
||
|
KerbErr = KdcLookupDomainName(
|
||
|
&Domain,
|
||
|
DomainName,
|
||
|
DomainList
|
||
|
);
|
||
|
|
||
|
if (KERB_SUCCESS(KerbErr))
|
||
|
{
|
||
|
if (Domain->ClosestRoute != NULL)
|
||
|
{
|
||
|
*DomainInfo = Domain;
|
||
|
|
||
|
// If the closest route is this domain, then cheat and send back
|
||
|
// the closest domain as the domain requested.
|
||
|
|
||
|
if (KerbCompareUnicodeRealmNames(&(Domain->ClosestRoute->DnsName), SecData.KdcDnsRealmName()))
|
||
|
|
||
|
{
|
||
|
*ClosestRoute = Domain;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*ClosestRoute = Domain->ClosestRoute;
|
||
|
}
|
||
|
KdcReferenceDomainInfo(*ClosestRoute);
|
||
|
return(KDC_ERR_NONE);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
KdcDereferenceDomainInfo(Domain);
|
||
|
DebugLog((DEB_WARN,"Asked for referral to %wZ domain, in organization but unreachable\n",
|
||
|
DomainName ));
|
||
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
return(KerbErr);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcLookupDomainByDnsName
|
||
|
//
|
||
|
// Synopsis: Looks up a domain name in the list of domains and returns
|
||
|
// the domain info
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
PKDC_DOMAIN_INFO
|
||
|
KdcLookupDomainByDnsName(
|
||
|
IN PUNICODE_STRING DnsDomainName,
|
||
|
IN PLIST_ENTRY DomainList
|
||
|
)
|
||
|
{
|
||
|
PLIST_ENTRY ListEntry;
|
||
|
PKDC_DOMAIN_INFO Domain;
|
||
|
|
||
|
TRACE(KDC, KdcLookupDomainName, DEB_FUNCTION);
|
||
|
|
||
|
for (ListEntry = DomainList->Flink;
|
||
|
ListEntry != DomainList ;
|
||
|
ListEntry = ListEntry->Flink )
|
||
|
{
|
||
|
Domain = (PKDC_DOMAIN_INFO) CONTAINING_RECORD(ListEntry, KDC_DOMAIN_INFO, Next);
|
||
|
if (KerbCompareUnicodeRealmNames(
|
||
|
DnsDomainName,
|
||
|
&Domain->DnsName
|
||
|
))
|
||
|
{
|
||
|
|
||
|
return(Domain);
|
||
|
}
|
||
|
}
|
||
|
return(NULL);
|
||
|
}
|
||
|
|
||
|
#if DBG
|
||
|
|
||
|
VOID
|
||
|
DebugDumpDomainList(
|
||
|
IN PLIST_ENTRY DomainList
|
||
|
)
|
||
|
{
|
||
|
PLIST_ENTRY ListEntry;
|
||
|
PKDC_DOMAIN_INFO Domain;
|
||
|
|
||
|
TRACE(KDC, KdcLookupDomainName, DEB_FUNCTION);
|
||
|
|
||
|
for (ListEntry = DomainList->Flink;
|
||
|
ListEntry != DomainList ;
|
||
|
ListEntry = ListEntry->Flink )
|
||
|
{
|
||
|
Domain = (PKDC_DOMAIN_INFO) CONTAINING_RECORD(ListEntry, KDC_DOMAIN_INFO, Next);
|
||
|
|
||
|
DebugLog((DEB_TRACE,"Domain %wZ:\n",&Domain->DnsName));
|
||
|
if (Domain->ClosestRoute == NULL)
|
||
|
{
|
||
|
D_DebugLog((DEB_TRACE,"\t no closest route\n"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
D_DebugLog((DEB_TRACE,"\t closest route = %wZ\n",&Domain->ClosestRoute->DnsName));
|
||
|
}
|
||
|
|
||
|
if (Domain->Parent == NULL)
|
||
|
{
|
||
|
D_DebugLog((DEB_TRACE,"\t no parent\n"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
D_DebugLog((DEB_TRACE,"\t parent = %wZ\n",&Domain->Parent->DnsName));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif // DBG
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcRecurseAddTreeTrust
|
||
|
//
|
||
|
// Synopsis: Recursively adds a tree trust - adds it and then all its
|
||
|
// children.
|
||
|
//
|
||
|
// Effects: Adds children depth-first
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS
|
||
|
KdcRecurseAddTreeTrust(
|
||
|
IN PLIST_ENTRY DomainList,
|
||
|
IN PLSAPR_TREE_TRUST_INFO TreeTrust,
|
||
|
IN OPTIONAL PKDC_DOMAIN_INFO DomainInfo
|
||
|
)
|
||
|
{
|
||
|
PKDC_DOMAIN_INFO NewDomainInfo = NULL;
|
||
|
BOOLEAN Linked = FALSE;
|
||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
ULONG Index;
|
||
|
|
||
|
//
|
||
|
// Create new root trust
|
||
|
//
|
||
|
|
||
|
NewDomainInfo = (PKDC_DOMAIN_INFO) MIDL_user_allocate(sizeof(KDC_DOMAIN_INFO));
|
||
|
if (NewDomainInfo == NULL)
|
||
|
{
|
||
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
RtlZeroMemory(
|
||
|
NewDomainInfo,
|
||
|
sizeof(KDC_DOMAIN_INFO)
|
||
|
);
|
||
|
|
||
|
Status = KerbDuplicateString(
|
||
|
&NewDomainInfo->DnsName,
|
||
|
(PUNICODE_STRING) &TreeTrust->DnsDomainName
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Uppercase the domain name here, as everything in the forest
|
||
|
// is uplevel.
|
||
|
//
|
||
|
|
||
|
Status = RtlUpcaseUnicodeString(
|
||
|
&NewDomainInfo->DnsName,
|
||
|
&NewDomainInfo->DnsName,
|
||
|
FALSE // don't allocate
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
Status = KerbDuplicateString(
|
||
|
&NewDomainInfo->NetbiosName,
|
||
|
(PUNICODE_STRING)&TreeTrust->FlatName
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
NewDomainInfo->Parent = DomainInfo;
|
||
|
|
||
|
//
|
||
|
// Insert into list
|
||
|
//
|
||
|
|
||
|
NewDomainInfo->References = 1;
|
||
|
|
||
|
InsertTailList(
|
||
|
DomainList,
|
||
|
&NewDomainInfo->Next
|
||
|
);
|
||
|
Linked = TRUE;
|
||
|
|
||
|
|
||
|
//
|
||
|
// Now recursively add all children
|
||
|
//
|
||
|
|
||
|
for (Index = 0; Index < TreeTrust->Children ; Index++ )
|
||
|
{
|
||
|
Status = KdcRecurseAddTreeTrust(
|
||
|
DomainList,
|
||
|
&TreeTrust->ChildDomains[Index],
|
||
|
NewDomainInfo
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
Cleanup:
|
||
|
if (!Linked && (NewDomainInfo != NULL))
|
||
|
{
|
||
|
KdcFreeDomainInfo(NewDomainInfo);
|
||
|
}
|
||
|
return(Status);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcInsertDomainTrustIntoTree
|
||
|
//
|
||
|
// Synopsis: Adds trust information to the tree of domains. For domains
|
||
|
// which are in the tree, trust direction
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
NTSTATUS
|
||
|
KdcInsertDomainTrustIntoForest(
|
||
|
IN OUT PLIST_ENTRY DomainList,
|
||
|
IN PLSAPR_TRUSTED_DOMAIN_INFORMATION_EX NewTrust
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
PKDC_DOMAIN_INFO DomainInfo = NULL;
|
||
|
PKDC_DOMAIN_INFO NewDomainInfo = NULL;
|
||
|
ULONG cbSid;
|
||
|
|
||
|
TRACE(KDC, KdcInsertDomainTrustIntoForest, DEB_FUNCTION);
|
||
|
|
||
|
|
||
|
D_DebugLog((DEB_T_DOMAIN, "Inserting trusted domain into domain list: %wZ\n",&NewTrust->Name));
|
||
|
|
||
|
//
|
||
|
// Check to see if the domain is already in the tree
|
||
|
//
|
||
|
|
||
|
DomainInfo = KdcLookupDomainByDnsName(
|
||
|
(PUNICODE_STRING) &NewTrust->Name,
|
||
|
DomainList
|
||
|
);
|
||
|
if (DomainInfo == NULL)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Allocate and fill in a new domain structure for this domain.
|
||
|
// It is not part of the tree so the GUID will be zero.
|
||
|
//
|
||
|
|
||
|
NewDomainInfo = (PKDC_DOMAIN_INFO) MIDL_user_allocate(sizeof(KDC_DOMAIN_INFO));
|
||
|
if (NewDomainInfo == NULL)
|
||
|
{
|
||
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
|
||
|
RtlZeroMemory(
|
||
|
NewDomainInfo,
|
||
|
sizeof(KDC_DOMAIN_INFO)
|
||
|
);
|
||
|
|
||
|
//
|
||
|
// Copy in the names of the domain
|
||
|
//
|
||
|
|
||
|
Status = KerbDuplicateString(
|
||
|
&NewDomainInfo->DnsName,
|
||
|
(PUNICODE_STRING) &NewTrust->Name
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If the trust is uplevel, then uppercase
|
||
|
//
|
||
|
|
||
|
if (NewTrust->TrustType == TRUST_TYPE_UPLEVEL)
|
||
|
{
|
||
|
Status = RtlUpcaseUnicodeString(
|
||
|
&NewDomainInfo->DnsName,
|
||
|
&NewDomainInfo->DnsName,
|
||
|
FALSE // don't allocate
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Status = KerbDuplicateString(
|
||
|
&NewDomainInfo->NetbiosName,
|
||
|
(PUNICODE_STRING) &NewTrust->FlatName
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
NewDomainInfo->References = 1;
|
||
|
|
||
|
InsertTailList(DomainList, &NewDomainInfo->Next);
|
||
|
DomainInfo = NewDomainInfo;
|
||
|
NewDomainInfo = NULL;
|
||
|
|
||
|
}
|
||
|
|
||
|
DomainInfo->Attributes = NewTrust->TrustAttributes;
|
||
|
DomainInfo->Type = NewTrust->TrustType;
|
||
|
|
||
|
//
|
||
|
// If this is not an inbound-only trust, the closest route to get here
|
||
|
// is to go directly here.
|
||
|
//
|
||
|
|
||
|
if ((NewTrust->TrustDirection & TRUST_DIRECTION_INBOUND) != 0)
|
||
|
{
|
||
|
DomainInfo->ClosestRoute = DomainInfo;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Note the confusion of inbound and outbound. For Kerberos inbound is
|
||
|
// the opposite of for trust objects.
|
||
|
//
|
||
|
|
||
|
if ((NewTrust->TrustDirection & TRUST_DIRECTION_OUTBOUND) != 0)
|
||
|
{
|
||
|
DomainInfo->Flags |= KDC_TRUST_INBOUND;
|
||
|
if ((((DomainInfo->Attributes & TRUST_ATTRIBUTE_FILTER_SIDS) != 0) &&
|
||
|
((DomainInfo->Type & TRUST_TYPE_UPLEVEL) != 0)) ||
|
||
|
((DomainInfo->Attributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0))
|
||
|
{
|
||
|
if (NewTrust->Sid != NULL)
|
||
|
{
|
||
|
cbSid = RtlLengthSid(NewTrust->Sid);
|
||
|
DomainInfo->Sid = (PSID) MIDL_user_allocate(cbSid);
|
||
|
if (DomainInfo->Sid == NULL)
|
||
|
{
|
||
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
Status = RtlCopySid (
|
||
|
cbSid,
|
||
|
DomainInfo->Sid,
|
||
|
NewTrust->Sid
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
|
||
|
if ((DomainInfo->Attributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0)
|
||
|
{
|
||
|
SecData.SetCrossForestEnabled(TRUE);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Cleanup:
|
||
|
|
||
|
if (NewDomainInfo != NULL)
|
||
|
{
|
||
|
KdcFreeDomainInfo(NewDomainInfo);
|
||
|
}
|
||
|
|
||
|
return(Status);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcComputeShortestDomainPaths
|
||
|
//
|
||
|
// Synopsis: Compute the shortest path for each domain in the tree
|
||
|
// by traversing up until either the local domain or
|
||
|
// a parent of it is located, and then traverse down.
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
NTSTATUS
|
||
|
KdcComputeShortestDomainPaths(
|
||
|
IN PLIST_ENTRY DomainList
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
PKDC_DOMAIN_INFO * ParentList = NULL;
|
||
|
ULONG CountOfParents = 0, Index;
|
||
|
PKDC_DOMAIN_INFO LocalDomain;
|
||
|
PKDC_DOMAIN_INFO WorkingDomain;
|
||
|
PKDC_DOMAIN_INFO ParentDomain;
|
||
|
PLIST_ENTRY ListEntry;
|
||
|
BOOLEAN FoundParent;
|
||
|
ULONG TouchedIndex = 1;
|
||
|
|
||
|
TRACE(KDC, KdcComputeShortestDomainPaths, DEB_FUNCTION);
|
||
|
|
||
|
//
|
||
|
// If the tree is empty, then there are no shortest paths to compute.
|
||
|
//
|
||
|
|
||
|
if (IsListEmpty(DomainList))
|
||
|
{
|
||
|
return(STATUS_SUCCESS);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Calculate the number of parents & grandparents of the local domain.
|
||
|
//
|
||
|
|
||
|
LocalDomain = KdcLookupDomainByDnsName(
|
||
|
SecData.KdcDnsRealmName(),
|
||
|
DomainList
|
||
|
);
|
||
|
|
||
|
if (LocalDomain == NULL)
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"No forest info for local domain - no transitive trust. %ws, line %d\n", THIS_FILE, __LINE__));
|
||
|
return(STATUS_SUCCESS);
|
||
|
}
|
||
|
LocalDomain->ClosestRoute = LocalDomain;
|
||
|
|
||
|
WorkingDomain = LocalDomain->Parent;
|
||
|
while (WorkingDomain != NULL)
|
||
|
{
|
||
|
//
|
||
|
// Stop if we've come to this domain before.
|
||
|
//
|
||
|
ReportServiceEvent(
|
||
|
EVENTLOG_ERROR_TYPE,
|
||
|
NEW_KDCEVENT_TRUSTLIST_LOOP,
|
||
|
sizeof(NTSTATUS),
|
||
|
&Status,
|
||
|
0,
|
||
|
NULL
|
||
|
);
|
||
|
|
||
|
if (WorkingDomain->Touched == TouchedIndex)
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"Loop in trust list! %ws, line %d\n", THIS_FILE, __LINE__));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
WorkingDomain->Touched = TouchedIndex;
|
||
|
CountOfParents++;
|
||
|
WorkingDomain = WorkingDomain->Parent;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we had any parents, build an array of all our parents.
|
||
|
//
|
||
|
|
||
|
if (CountOfParents != 0)
|
||
|
{
|
||
|
ParentList = (PKDC_DOMAIN_INFO *) MIDL_user_allocate(CountOfParents * sizeof(PKDC_DOMAIN_INFO));
|
||
|
if (ParentList == NULL)
|
||
|
{
|
||
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Store each parent in the list.
|
||
|
//
|
||
|
Index = 0;
|
||
|
TouchedIndex++;
|
||
|
WorkingDomain = LocalDomain->Parent;
|
||
|
while (WorkingDomain != NULL)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Stop if we've come to this domain before.
|
||
|
//
|
||
|
|
||
|
if (WorkingDomain->Touched == TouchedIndex)
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"Loop in trust list! %ws, line %d\n", THIS_FILE, __LINE__));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Skip domains that have no domain info. They have probably been
|
||
|
// deleted.
|
||
|
//
|
||
|
WorkingDomain->Touched = TouchedIndex;
|
||
|
|
||
|
ParentList[Index++] = WorkingDomain;
|
||
|
WorkingDomain = WorkingDomain->Parent;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now loop through every domain in the tree. For each domain, if it
|
||
|
// is not trusted, check it against the list of parents. If it is a
|
||
|
// parent, walk down the list until a trusted domain is found.
|
||
|
//
|
||
|
|
||
|
|
||
|
for (ListEntry = DomainList->Flink;
|
||
|
ListEntry != DomainList;
|
||
|
ListEntry = ListEntry->Flink )
|
||
|
{
|
||
|
WorkingDomain = (PKDC_DOMAIN_INFO) CONTAINING_RECORD(ListEntry, KDC_DOMAIN_INFO, Next);
|
||
|
|
||
|
ParentDomain = WorkingDomain;
|
||
|
|
||
|
//
|
||
|
// Walk up from this domain until we find a common ancestor with
|
||
|
// the local domain
|
||
|
//
|
||
|
TouchedIndex++;
|
||
|
while (ParentDomain != NULL)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Stop if we've come to this domain before.
|
||
|
//
|
||
|
|
||
|
if (ParentDomain->Touched == TouchedIndex)
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"Loop in trust list! %ws, line %d\n", THIS_FILE, __LINE__));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Skip domains that have no domain info. They have probably been
|
||
|
// deleted.
|
||
|
//
|
||
|
|
||
|
ParentDomain->Touched = TouchedIndex;
|
||
|
|
||
|
|
||
|
//
|
||
|
// If the parent has a closest route, use it
|
||
|
//
|
||
|
|
||
|
if (ParentDomain->ClosestRoute != NULL)
|
||
|
{
|
||
|
WorkingDomain->ClosestRoute = ParentDomain->ClosestRoute;
|
||
|
D_DebugLog((DEB_T_DOMAIN, "Shortest route for domain %wZ is %wZ\n",
|
||
|
&WorkingDomain->DnsName,
|
||
|
&WorkingDomain->ClosestRoute->DnsName
|
||
|
));
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Look through the list of parents for this domain to see if it
|
||
|
// is trusted
|
||
|
//
|
||
|
|
||
|
Index = CountOfParents;
|
||
|
FoundParent = FALSE;
|
||
|
while (Index > 0)
|
||
|
{
|
||
|
Index--;
|
||
|
if (ParentList[Index] == ParentDomain)
|
||
|
{
|
||
|
//
|
||
|
// We found a domain that is a parent of
|
||
|
// ours
|
||
|
//
|
||
|
|
||
|
FoundParent = TRUE;
|
||
|
|
||
|
}
|
||
|
if (FoundParent && (ParentList[Index]->ClosestRoute != NULL))
|
||
|
{
|
||
|
WorkingDomain->ClosestRoute = ParentList[Index]->ClosestRoute;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (WorkingDomain->ClosestRoute != NULL)
|
||
|
{
|
||
|
D_DebugLog((DEB_T_DOMAIN, "Shortest route for domain %wZ is %wZ\n",
|
||
|
&WorkingDomain->DnsName,
|
||
|
&WorkingDomain->ClosestRoute->DnsName
|
||
|
));
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
ParentDomain = ParentDomain->Parent;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
Cleanup:
|
||
|
if (ParentList != NULL)
|
||
|
{
|
||
|
MIDL_user_free(ParentList);
|
||
|
}
|
||
|
return(Status);
|
||
|
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KerbFreeDomainList
|
||
|
//
|
||
|
// Synopsis: Frees a domain list element by element.
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
VOID
|
||
|
KdcFreeReferralCache(
|
||
|
IN PLIST_ENTRY ReferralCache
|
||
|
)
|
||
|
{
|
||
|
PREFERRAL_CACHE_ENTRY CacheEntry;
|
||
|
|
||
|
TRACE(KDC, KdcFreeReferralCache, DEB_FUNCTION);
|
||
|
|
||
|
if (ReferralCache->Flink != NULL)
|
||
|
{
|
||
|
while (!IsListEmpty(ReferralCache))
|
||
|
{
|
||
|
CacheEntry = CONTAINING_RECORD(ReferralCache->Flink, REFERRAL_CACHE_ENTRY, ListEntry );
|
||
|
|
||
|
RemoveEntryList(&CacheEntry->ListEntry);
|
||
|
InitializeListHead(&CacheEntry->ListEntry);
|
||
|
KdcDereferenceReferralCacheEntry(CacheEntry);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KerbFreeDomainList
|
||
|
//
|
||
|
// Synopsis: Frees a domain list element by element.
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
VOID
|
||
|
KdcFreeDomainList(
|
||
|
IN PLIST_ENTRY DomainList
|
||
|
)
|
||
|
{
|
||
|
PKDC_DOMAIN_INFO DomainInfo;
|
||
|
|
||
|
TRACE(KDC, KdcFreeDomainList, DEB_FUNCTION);
|
||
|
|
||
|
if (DomainList->Flink != NULL)
|
||
|
{
|
||
|
while (!IsListEmpty(DomainList))
|
||
|
{
|
||
|
DomainInfo = CONTAINING_RECORD(DomainList->Flink, KDC_DOMAIN_INFO, Next );
|
||
|
|
||
|
RemoveEntryList(&DomainInfo->Next);
|
||
|
InitializeListHead(&DomainInfo->Next);
|
||
|
KdcDereferenceDomainInfo(DomainInfo);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#ifdef DBG_BUILD_FOREST
|
||
|
|
||
|
VOID
|
||
|
DebugBuildDomainForest(
|
||
|
OUT PLSAPR_FOREST_TRUST_INFO * ForestInfo
|
||
|
)
|
||
|
{
|
||
|
PLSAPR_FOREST_TRUST_INFO ForestTrustInfo = NULL;
|
||
|
PLSAPR_TREE_TRUST_INFO ChildDomains = NULL;
|
||
|
PLSAPR_TREE_TRUST_INFO ChildRoot = NULL;
|
||
|
UNICODE_STRING TempString;
|
||
|
ULONG Index;
|
||
|
LPWSTR MsNames[4] = {L"ntdev.microsoft.com",L"alpamayo.ntdev.microsoft.com",L"annapurna.alpamayo.ntdev.microsoft.com",L"lhotse.annapurna.alpamayo.ntdev.microsoft.com" };
|
||
|
|
||
|
ForestTrustInfo = (PLSAPR_FOREST_TRUST_INFO) MIDL_user_allocate(sizeof(LSAPR_FOREST_TRUST_INFO));
|
||
|
|
||
|
RtlInitUnicodeString(
|
||
|
&TempString,
|
||
|
MsNames[0]
|
||
|
);
|
||
|
KerbDuplicateString( (PUNICODE_STRING)
|
||
|
&ForestTrustInfo->RootTrust.DnsDomainName,
|
||
|
&TempString
|
||
|
);
|
||
|
KerbDuplicateString( (PUNICODE_STRING)
|
||
|
&ForestTrustInfo->RootTrust.FlatName,
|
||
|
&TempString
|
||
|
);
|
||
|
|
||
|
ChildRoot = &ForestTrustInfo->RootTrust;
|
||
|
|
||
|
for (Index = 1; Index < 4 ; Index++ )
|
||
|
{
|
||
|
ChildRoot->Children = 1;
|
||
|
ChildRoot->ChildDomains = (PLSAPR_TREE_TRUST_INFO) MIDL_user_allocate(sizeof(LSAPR_TREE_TRUST_INFO));
|
||
|
RtlZeroMemory(
|
||
|
ChildRoot->ChildDomains,
|
||
|
sizeof(LSAPR_TREE_TRUST_INFO)
|
||
|
);
|
||
|
|
||
|
RtlInitUnicodeString(
|
||
|
&TempString,
|
||
|
MsNames[Index]
|
||
|
);
|
||
|
KerbDuplicateString( (PUNICODE_STRING)
|
||
|
&ChildRoot->ChildDomains[0].DnsDomainName,
|
||
|
&TempString
|
||
|
);
|
||
|
KerbDuplicateString( (PUNICODE_STRING)
|
||
|
&ChildRoot->ChildDomains[0].FlatName,
|
||
|
&TempString
|
||
|
);
|
||
|
|
||
|
ChildRoot = &ChildRoot->ChildDomains[0];
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Should be all done now
|
||
|
//
|
||
|
|
||
|
|
||
|
*ForestInfo = ForestTrustInfo;
|
||
|
}
|
||
|
|
||
|
#endif // DBG_BUILD_FOREST
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcBuildDomainTree
|
||
|
//
|
||
|
// Synopsis: Enumerates the list of domains and inserts them
|
||
|
// all into a tree
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
NTSTATUS
|
||
|
KdcBuildDomainTree(
|
||
|
IN OUT PLIST_ENTRY DomainList
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS Status;
|
||
|
ULONG Index;
|
||
|
PLSAPR_FOREST_TRUST_INFO ForestInfo = NULL;
|
||
|
LSAPR_TRUSTED_ENUM_BUFFER_EX TrustedBuffer;
|
||
|
LSA_ENUMERATION_HANDLE EnumContext = 0;
|
||
|
ULONG CountReturned;
|
||
|
|
||
|
TRACE(KDC, KdcBuildDomainList, DEB_FUNCTION);
|
||
|
|
||
|
InitializeListHead(DomainList);
|
||
|
|
||
|
RtlZeroMemory(
|
||
|
&TrustedBuffer,
|
||
|
sizeof(LSAPR_TRUSTED_ENUM_BUFFER_EX)
|
||
|
);
|
||
|
|
||
|
//
|
||
|
// Call the LSA to enumerate all the trees in the enterprise and insert
|
||
|
// them into the tree
|
||
|
//
|
||
|
|
||
|
#ifndef DBG_BUILD_FOREST
|
||
|
Status = LsaIQueryForestTrustInfo(
|
||
|
GlobalPolicyHandle,
|
||
|
&ForestInfo
|
||
|
);
|
||
|
|
||
|
//
|
||
|
// If we aren't part of a tree, we may get back STATUS_OBJECT_NAME_NOT_FOUND
|
||
|
// so this is o.k.
|
||
|
//
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
||
|
{
|
||
|
DebugLog((DEB_WARN,"No trust info (0x%x) continuing\n",Status));
|
||
|
Status = STATUS_SUCCESS;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
DebugBuildDomainForest(&ForestInfo);
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// Only use this if the information is usable - it is present
|
||
|
//
|
||
|
|
||
|
|
||
|
if (ForestInfo != NULL)
|
||
|
{
|
||
|
Status = KdcRecurseAddTreeTrust(
|
||
|
DomainList,
|
||
|
&ForestInfo->RootTrust,
|
||
|
NULL
|
||
|
);
|
||
|
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
|
||
|
Status = SecData.SetForestRoot(&(ForestInfo->RootTrust.DnsDomainName));
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
//
|
||
|
// Now add all the trusts in.
|
||
|
//
|
||
|
SecData.SetCrossForestEnabled(FALSE); // set this to FALSE for now
|
||
|
|
||
|
do
|
||
|
{
|
||
|
CountReturned = 0;
|
||
|
|
||
|
Status = LsarEnumerateTrustedDomainsEx(
|
||
|
GlobalPolicyHandle,
|
||
|
&EnumContext,
|
||
|
&TrustedBuffer,
|
||
|
0xffffff // preferred maximum length
|
||
|
);
|
||
|
|
||
|
if (!NT_ERROR(Status))
|
||
|
{
|
||
|
//
|
||
|
// Call the LSA to enumerate all the trust relationships and integrate
|
||
|
// them into the tree
|
||
|
//
|
||
|
|
||
|
for (Index = 0; (Index < TrustedBuffer.EntriesRead) && NT_SUCCESS(Status) ; Index++ )
|
||
|
{
|
||
|
if (TrustedBuffer.EnumerationBuffer[Index].TrustType != TRUST_TYPE_DOWNLEVEL)
|
||
|
{
|
||
|
Status = KdcInsertDomainTrustIntoForest(
|
||
|
DomainList,
|
||
|
&TrustedBuffer.EnumerationBuffer[Index]
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
LsaIFree_LSAPR_TRUSTED_ENUM_BUFFER_EX(&TrustedBuffer);
|
||
|
RtlZeroMemory(
|
||
|
&TrustedBuffer,
|
||
|
sizeof(LSAPR_TRUSTED_ENUM_BUFFER_EX)
|
||
|
);
|
||
|
|
||
|
|
||
|
} while (NT_SUCCESS(Status) && (CountReturned != 0));
|
||
|
|
||
|
if (NT_ERROR(Status))
|
||
|
{
|
||
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
|
||
|
{
|
||
|
DebugLog((DEB_ERROR,"Failed to enumerate trusted domains: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__));
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
Status = STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now compute the shortest path from each domain in the tree.
|
||
|
//
|
||
|
|
||
|
Status = KdcComputeShortestDomainPaths(
|
||
|
DomainList
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
#if DBG
|
||
|
DebugDumpDomainList(DomainList);
|
||
|
#endif
|
||
|
|
||
|
Cleanup:
|
||
|
|
||
|
if (ForestInfo != NULL)
|
||
|
{
|
||
|
LsaIFreeForestTrustInfo(ForestInfo);
|
||
|
}
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
KdcFreeDomainList(DomainList);
|
||
|
}
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcReloadDomainTree
|
||
|
//
|
||
|
// Synopsis: Reloads the domain tree when it has changed
|
||
|
//
|
||
|
// Effects:
|
||
|
//
|
||
|
// Arguments: Dummy - dummy argument requred for CreateThread calls
|
||
|
//
|
||
|
// Requires:
|
||
|
//
|
||
|
// Returns:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
ULONG
|
||
|
KdcReloadDomainTree(
|
||
|
PVOID Dummy
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
LIST_ENTRY DomainList;
|
||
|
|
||
|
TRACE(KDC, KdcBuildDomainList, DEB_FUNCTION);
|
||
|
|
||
|
InitializeListHead(&DomainList);
|
||
|
|
||
|
|
||
|
Status = EnterApiCall();
|
||
|
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
if (!KdcReferralCacheInitialized)
|
||
|
{
|
||
|
|
||
|
Status = RtlInitializeCriticalSection(&KdcReferralCacheLock);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
InitializeListHead(&KdcReferralCache);
|
||
|
KdcReferralCacheInitialized = TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
D_DebugLog((DEB_TRACE,"About to reload domain tree\n"));
|
||
|
|
||
|
if (!KdcDomainListInitialized)
|
||
|
{
|
||
|
Status = RtlInitializeCriticalSection(&KdcDomainListLock);
|
||
|
if (!NT_SUCCESS(Status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
InitializeListHead(&KdcDomainList);
|
||
|
KdcDomainListInitialized = TRUE;
|
||
|
}
|
||
|
|
||
|
Status = KdcBuildDomainTree(&DomainList);
|
||
|
|
||
|
if (NT_SUCCESS(Status))
|
||
|
{
|
||
|
KdcLockDomainList();
|
||
|
|
||
|
KdcFreeDomainList(&KdcDomainList);
|
||
|
|
||
|
KdcDomainList = DomainList;
|
||
|
|
||
|
DomainList.Flink->Blink = &KdcDomainList;
|
||
|
DomainList.Blink->Flink = &KdcDomainList;
|
||
|
|
||
|
KdcUnlockDomainList();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ReportServiceEvent(
|
||
|
EVENTLOG_ERROR_TYPE,
|
||
|
KDCEVENT_DOMAIN_LIST_UPDATE_FAILED,
|
||
|
sizeof(NTSTATUS),
|
||
|
&Status,
|
||
|
0,
|
||
|
NULL
|
||
|
);
|
||
|
|
||
|
}
|
||
|
Cleanup:
|
||
|
LeaveApiCall();
|
||
|
return((ULONG)Status);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: KdcTrustChangeCallback
|
||
|
//
|
||
|
// Synopsis: This is the callback that gets invoked with the Lsa has determined
|
||
|
// that the trust tree has changed. The call is made asynchronously.
|
||
|
//
|
||
|
// Effects: Potentially causes the trust tree to be rebuilt
|
||
|
//
|
||
|
// Arguments: DeltaType - Type of change to the trust tree
|
||
|
//
|
||
|
// Requires: Nothing
|
||
|
//
|
||
|
// Returns: VOID
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
VOID
|
||
|
KdcTrustChangeCallback (
|
||
|
SECURITY_DB_DELTA_TYPE DeltaType
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS Status;
|
||
|
|
||
|
TRACE(KDC, KdcTrustChangeCallback, DEB_FUNCTION);
|
||
|
|
||
|
if ( DeltaType == SecurityDbNew || DeltaType == SecurityDbDelete ||
|
||
|
DeltaType == SecurityDbChange) {
|
||
|
|
||
|
Status = KdcReloadDomainTree( NULL );
|
||
|
|
||
|
if (!NT_SUCCESS(Status)) {
|
||
|
|
||
|
DebugLog((DEB_ERROR,"KdcReloadDomainTree from callback failed with 0x%lx. %ws, line %d\n",
|
||
|
Status, THIS_FILE, __LINE__));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
VOID
|
||
|
KdcLockDomainListFn(
|
||
|
)
|
||
|
{
|
||
|
KdcLockDomainList();
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
KdcUnlockDomainListFn(
|
||
|
)
|
||
|
{
|
||
|
KdcUnlockDomainList();
|
||
|
}
|
||
|
|