//+----------------------------------------------------------------------- // // 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 extern "C" { #include // DNS_MAX_NAME_LENGTH #include // 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(); }