Leaked source code of windows server 2003
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

//+-----------------------------------------------------------------------
//
// 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();
}