You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2425 lines
62 KiB
2425 lines
62 KiB
|
|
//+-----------------------------------------------------------------------
|
|
//
|
|
// 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 = {0};
|
|
RTL_CRITICAL_SECTION KdcDomainListLock;
|
|
BOOLEAN KdcDomainListInitialized = FALSE;
|
|
|
|
LIST_ENTRY KdcReferralCache = {0};
|
|
RTL_CRITICAL_SECTION KdcReferralCacheLock;
|
|
BOOLEAN KdcReferralCacheInitialized = FALSE;
|
|
UNICODE_STRING KdcForestRootDomainName = {0};
|
|
|
|
#define KdcLockReferralCache() (RtlEnterCriticalSection(&KdcReferralCacheLock))
|
|
#define KdcUnlockReferralCache() (RtlLeaveCriticalSection(&KdcReferralCacheLock))
|
|
|
|
#define KdcReferenceDomainInfo(_x_) \
|
|
InterlockedIncrement(&(_x_)->References)
|
|
|
|
#define KdcReferenceReferralCacheEntry(_x_) \
|
|
InterlockedIncrement(&(_x_)->References)
|
|
|
|
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.
|
|
//
|
|
//
|
|
// 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 = sizeof( CrackedDnsDomain ) / sizeof( WCHAR );
|
|
WCHAR CrackedName[UNLEN+DNS_MAX_NAME_LENGTH + 2];
|
|
ULONG CrackedNameLength = sizeof( CrackedName ) / sizeof( WCHAR );
|
|
ULONG CrackError = 0;
|
|
BOOLEAN RootDomain = SecData.IsForestRoot();
|
|
UNICODE_STRING Target = {0};
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
if (!NT_SUCCESS(Status) || (CrackError != DS_NAME_ERROR_TRUST_REFERRAL))
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS,
|
|
"KDC presented w/ a unknown Xrealm TGT (%wZ)\n",
|
|
DestinationDomain));
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "Crack Error %x, Status %x\n", CrackError, Status));
|
|
KerbErr = KDC_ERR_PATH_NOT_ACCEPTED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "TGT Cracked realm (FOREST) %S\n", CrackedDnsDomain));
|
|
|
|
//
|
|
// 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 | KDC_NAME_UPN_TARGET )) != 0)
|
|
{
|
|
UNICODE_STRING TmpName = {0};
|
|
|
|
//
|
|
// Default behavior is to refer to the root - otherwise, simply
|
|
// utilize the cross forest link.
|
|
//
|
|
|
|
if ( !RootDomain )
|
|
{
|
|
Status = SecData.GetKdcForestRoot(&Target);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RtlInitUnicodeString(
|
|
&Target,
|
|
CrackedDnsDomain
|
|
);
|
|
}
|
|
|
|
KerbErr = KdcFindReferralTarget(
|
|
ReferralTarget,
|
|
ReferralRealm,
|
|
pExtendedError,
|
|
&Target,
|
|
FALSE, // we'll accept closest
|
|
NameFlags
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
//
|
|
// Hack for broken trust recursion.
|
|
//
|
|
if (KerbErr == KDC_ERR_NO_TRUST_PATH)
|
|
{
|
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
|
}
|
|
D_DebugLog((DEB_ERROR, "No referral info for %wZ\n", &Target));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// swap w/ our dns domain for referral realm, unless we're
|
|
// processing a UPN, or we're in the root of the forest.
|
|
//
|
|
|
|
if (!RootDomain)
|
|
{
|
|
KerbFreeString(ReferralRealm);
|
|
|
|
RtlInitUnicodeString(
|
|
&TmpName,
|
|
CrackedDnsDomain
|
|
);
|
|
|
|
if (!NT_SUCCESS(KerbDuplicateString(ReferralRealm, &TmpName)))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "Found Xforest referral to %wZ\n", ReferralRealm));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// UPN referral...
|
|
//
|
|
|
|
UNICODE_STRING TmpString = {0};
|
|
|
|
RtlInitUnicodeString(
|
|
&TmpString,
|
|
CrackedDnsDomain
|
|
);
|
|
|
|
if (!NT_SUCCESS(KerbDuplicateString(ReferralRealm, &TmpString)))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "Found Xforest UPN referral to %wZ\n", ReferralRealm));
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
KerbFreeString(&ServiceName);
|
|
|
|
//
|
|
// !rootdomain == we allocated the target from the GetKdcForestRoot...
|
|
//
|
|
if (!RootDomain && Target.Buffer != NULL)
|
|
{
|
|
KerbFreeString(&Target);
|
|
}
|
|
|
|
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: !!! IMPORTANT !!! Any KDC_ERR_NO_TRUST_PATH returns **MUST**
|
|
// be converted to an on-the-wire protocol..
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
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 ULONG NameFlags
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
PKDC_DOMAIN_INFO DomainInfo = NULL;
|
|
PKDC_DOMAIN_INFO ClosestRoute = NULL;
|
|
BOOLEAN fListLocked = FALSE;
|
|
KDC_DOMAIN_INFO_DIRECTION TrustDirection = Outbound;
|
|
|
|
TRACE(KDC, KdcFindReferralTarget, DEB_FUNCTION);
|
|
|
|
RtlInitUnicodeString(
|
|
ReferralRealm,
|
|
NULL
|
|
);
|
|
D_DebugLog((DEB_TRACE, "KdcFindReferralTarget [entering] generating referral for target %wZ\n",DestinationDomain));
|
|
|
|
if ((NameFlags & KDC_NAME_INBOUND) != 0)
|
|
{
|
|
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;
|
|
}
|
|
|
|
TrustDirection = Inbound;
|
|
|
|
//
|
|
// 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, "KdcFindReferralTarget KLIN(%x) Failed to find referral target %wZ\n", KLIN(FILENO, __LINE__), 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, "KdcFindReferralTarget KLIN(%x) Needed exact match and got a transitively-trusted domain.\n", KLIN(FILENO, __LINE__)));
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// For UPN logons, we don't need the ticket info.
|
|
// We're just generating a referral realm.
|
|
//
|
|
if ((NameFlags & KDC_NAME_CLIENT) == 0)
|
|
{
|
|
//
|
|
// Now get the ticket info for the domain
|
|
//
|
|
|
|
KerbErr = KdcGetTicketInfoForDomain(
|
|
ReferralTarget,
|
|
pExtendedError,
|
|
ClosestRoute,
|
|
TrustDirection
|
|
);
|
|
|
|
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: KdcPickAuthInfo
|
|
//
|
|
// Synopsis: Pick password authinfo
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: AuthInfoCount - Number of AuthInfo
|
|
// AuthInfo - An array of authinfo
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
PLSAPR_AUTH_INFORMATION
|
|
KdcPickAuthInfo(
|
|
IN ULONG AuthInfoCount,
|
|
IN PLSAPR_AUTH_INFORMATION AuthInfo
|
|
)
|
|
{
|
|
PLSAPR_AUTH_INFORMATION Nt4OwfAuthInfo = NULL;
|
|
|
|
//
|
|
// Loop through the various auth infos looking for the password
|
|
//
|
|
|
|
for ( ULONG i = 0; i < AuthInfoCount; i++ )
|
|
{
|
|
if (AuthInfo[i].AuthType == TRUST_AUTH_TYPE_CLEAR)
|
|
{
|
|
return AuthInfo + i; // prefer clear text authinfo
|
|
}
|
|
else if (!Nt4OwfAuthInfo && (AuthInfo[i].AuthType == TRUST_AUTH_TYPE_NT4OWF)) // prefer the first NT4OWF
|
|
{
|
|
Nt4OwfAuthInfo = AuthInfo + i;
|
|
}
|
|
}
|
|
|
|
return Nt4OwfAuthInfo;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// 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 = KdcPickAuthInfo(
|
|
TrustInfo->TrustedAuthInfo.IncomingAuthInfos,
|
|
TrustInfo->TrustedAuthInfo.IncomingAuthenticationInformation
|
|
);
|
|
|
|
OldAuthInfo = KdcPickAuthInfo(
|
|
TrustInfo->TrustedAuthInfo.IncomingAuthInfos,
|
|
TrustInfo->TrustedAuthInfo.IncomingPreviousAuthenticationInformation
|
|
);
|
|
}
|
|
else
|
|
{
|
|
AuthInfo = KdcPickAuthInfo(
|
|
TrustInfo->TrustedAuthInfo.OutgoingAuthInfos,
|
|
TrustInfo->TrustedAuthInfo.OutgoingAuthenticationInformation
|
|
);
|
|
|
|
OldAuthInfo = KdcPickAuthInfo(
|
|
TrustInfo->TrustedAuthInfo.OutgoingAuthInfos,
|
|
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;
|
|
|
|
//DebugLog((DEB_ERROR, "Using old auth info\n"));
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
DsysAssert((AuthInfo->AuthType == TRUST_AUTH_TYPE_CLEAR) || (AuthInfo->AuthType == TRUST_AUTH_TYPE_NT4OWF));
|
|
|
|
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;
|
|
|
|
DsysAssert((OldAuthInfo->AuthType == TRUST_AUTH_TYPE_CLEAR) || (OldAuthInfo->AuthType == TRUST_AUTH_TYPE_NT4OWF));
|
|
|
|
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;
|
|
}
|
|
|
|
TicketInfo->PasswordExpires = tsInfinity;
|
|
TicketInfo->UserAccountControl = USER_INTERDOMAIN_TRUST_ACCOUNT;
|
|
|
|
//
|
|
// 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;
|
|
//
|
|
// buggy buggy buggy LSA
|
|
//
|
|
// The TRUST_ATTRIBUTE_NON_TRANSITIVE is not defined on NT trusts, so
|
|
// assume its an external trust- if its to a domain outside of our forest
|
|
// then its non-transitive.
|
|
//
|
|
if ( DomainInfo->Type == TRUST_TYPE_MIT )
|
|
{
|
|
if ((DomainInfo->Attributes & TRUST_ATTRIBUTE_NON_TRANSITIVE) == 0)
|
|
{
|
|
TicketInfo->fTicketOpts |= AUTH_REQ_TRANSITIVE_TRUST;
|
|
}
|
|
|
|
//
|
|
// we select the session key type based on DES_KEY_ONLY flag, set it
|
|
// here so we won't use rc4 session keys for the referral TGTs to MIT
|
|
// trusts
|
|
//
|
|
|
|
TicketInfo->UserAccountControl |= USER_USE_DES_KEY_ONLY;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// not an MIT trust - all external trusts within forest are transitive
|
|
// xforest trusts are transitive...
|
|
// external trusts outside of forest are not transitive
|
|
//
|
|
if ((DomainInfo->Attributes & ( TRUST_ATTRIBUTE_FOREST_TRANSITIVE | TRUST_ATTRIBUTE_WITHIN_FOREST)) != 0)
|
|
{
|
|
TicketInfo->fTicketOpts |= AUTH_REQ_TRANSITIVE_TRUST;
|
|
}
|
|
}
|
|
|
|
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;
|
|
TicketInfo->TrustType = DomainInfo->Type;
|
|
|
|
//
|
|
// Add trusted forest UNICODE STRING onto ticket info
|
|
// if its Xforest
|
|
//
|
|
if ((DomainInfo->Attributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE ) != 0)
|
|
{
|
|
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_NO_TRUST_PATH;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
NewDomainInfo->Attributes |= TRUST_ATTRIBUTE_WITHIN_FOREST; // we know from the xref that we're w/i the forest
|
|
|
|
//
|
|
// 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 (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.
|
|
//
|
|
if (WorkingDomain->Touched == TouchedIndex)
|
|
{
|
|
|
|
ReportServiceEvent(
|
|
EVENTLOG_ERROR_TYPE,
|
|
KDCEVENT_TRUST_LOOP,
|
|
sizeof(NTSTATUS),
|
|
&Status,
|
|
1,
|
|
WorkingDomain->DnsName.Buffer
|
|
);
|
|
|
|
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();
|
|
}
|
|
|