//+----------------------------------------------------------------------- // // Microsoft Windows // // Copyright (c) Microsoft Corporation 1992 - 2001 // // File: kerbs4u.cxx // // Contents: Code for doing S4UToSelf() logon. // // // History: 14-March-2001 Created Todds // //------------------------------------------------------------------------ #include #include #ifdef DEBUG_SUPPORT static TCHAR THIS_FILE[]=TEXT(__FILE__); #endif //+------------------------------------------------------------------------- // // Function: KerbInitGlobalS4UCred // // Synopsis: Create a KERB_CREDENTIAL structure w/ bogus password for AS // location of client. // // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbInitGlobalS4UCred() { return STATUS_SUCCESS; // TBD: Come up w/ scheme for global cred.. } //+------------------------------------------------------------------------- // // Function: KerbGetS4UClientIdentity // // Synopsis: Attempt to gets TGT for an S4U client for name // location purposes. // // // Effects: // // Arguments: LogonSession - Logon session of the service doing the // S4U request // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbGetS4UClientRealm( IN PKERB_LOGON_SESSION LogonSession, IN PKERB_INTERNAL_NAME * S4UClientName, IN OUT PUNICODE_STRING S4UTargetRealm // TBD: Place for credential handle? ) { NTSTATUS Status = STATUS_SUCCESS, LookupStatus = STATUS_SUCCESS; KERBERR KerbErr; PKERB_INTERNAL_NAME KdcServiceKdcName = NULL; PKERB_INTERNAL_NAME ClientName = NULL; UNICODE_STRING ClientRealm = {0}; UNICODE_STRING CorrectRealm = {0}; ULONG RetryCount = KERB_CLIENT_REFERRAL_MAX; ULONG RequestFlags = 0; BOOLEAN UsingSuppliedCreds = FALSE; BOOLEAN MitRealmLogon = FALSE; BOOLEAN UsedPrimaryLogonCreds = FALSE; PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL; RtlInitUnicodeString( S4UTargetRealm, NULL ); // // Use our server credentials to start off the AS_REQ process. // We may get a referral elsewhere, however. // Status = KerbGetClientNameAndRealm( NULL, &LogonSession->PrimaryCredentials, UsingSuppliedCreds, NULL, &MitRealmLogon, TRUE, &ClientName, &ClientRealm ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to get client name & realm: 0x%x, %ws line %d\n", Status, THIS_FILE, __LINE__ )); goto Cleanup; } GetTicketRestart: KerbErr = KerbBuildFullServiceKdcName( &ClientRealm, &KerbGlobalKdcServiceName, KRB_NT_SRV_INST, &KdcServiceKdcName ); if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } Status = KerbGetAuthenticationTicket( LogonSession, NULL, // KerbGlobalS4UCred, NULL, KdcServiceKdcName, &ClientRealm, (*S4UClientName), RequestFlags, KERB_TICKET_CACHE_PRIMARY_TGT, &TicketCacheEntry, NULL, &CorrectRealm ); // // If it failed but gave us another realm to try, go there // if (!NT_SUCCESS(Status) && (CorrectRealm.Length != 0)) { if (--RetryCount != 0) { KerbFreeKdcName(&KdcServiceKdcName); KerbFreeString(&ClientRealm); ClientRealm = CorrectRealm; CorrectRealm.Buffer = NULL; // // TBD: MIT realm support? See KerbGetTicketGrantingTicket() // S4UToSelf goto GetTicketRestart; } else { // Tbd: Log error here? Max referrals reached.. goto Cleanup; } } // // TBD: S4UToSelf() // in KerbGetTgt, we'll be happy to crack the UPN given the xxx@foo.com syntax // Here, we should just fail out.. Right? // // // If we get STATUS_WRONG_PASSWORD, we succeeded in finding the // client realm. Otherwise, we're hosed. As the password we used // is bogus, we should never succeed, btw... // DsysAssert(!NT_SUCCESS(Status)); if (Status == STATUS_WRONG_PASSWORD) { // fester: define new debug level / trace level DebugLog((DEB_ERROR, "Found client")); KerbPrintKdcName(DEB_ERROR, (*S4UClientName)); DebugLog((DEB_ERROR, "\nin realm %wZ\n", &ClientRealm)); *S4UTargetRealm = ClientRealm; Status = STATUS_SUCCESS; } Cleanup: // if we succeeded, we got the correct realm, // and we need to pass that back to caller if (!NT_SUCCESS(Status)) { KerbFreeString(&ClientRealm); } KerbFreeKdcName(&KdcServiceKdcName); KerbFreeKdcName(&ClientName); if (NULL != TicketCacheEntry) { KerbDereferenceTicketCacheEntry(TicketCacheEntry); // fester: make sure we toss this... DsysAssert(TicketCacheEntry->ListEntry.ReferenceCount == 1); } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbBuildS4UPreauth // // Synopsis: Attempt to gets TGT for an S4U client for name // location purposes. // // // Effects: // // Arguments: LogonSession - Logon session of the service doing the // S4U request // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- KERBERR KerbBuildS4UPreauth( IN OUT PKERB_PA_DATA_LIST * PreAuthData, IN PKERB_INTERNAL_NAME S4UClientName, IN PUNICODE_STRING S4UClientRealm ) { KERBERR KerbErr; KERB_PA_FOR_USER S4UserPA = {0}; PKERB_PA_DATA_LIST ListElement = NULL; *PreAuthData = NULL; KerbErr = KerbConvertKdcNameToPrincipalName( &S4UserPA.client_name, S4UClientName ); if (!KERB_SUCCESS(KerbErr)) { goto Cleanup; } KerbErr = KerbConvertUnicodeStringToRealm( &S4UserPA.client_realm, S4UClientRealm ); if (!KERB_SUCCESS(KerbErr)) { goto Cleanup; } ListElement = (PKERB_PA_DATA_LIST) KerbAllocate(sizeof(KERB_PA_DATA_LIST)); if (ListElement == NULL) { goto Cleanup; } KerbErr = KerbPackData( &S4UserPA, KERB_PA_FOR_USER_PDU, (PULONG) &ListElement->value.preauth_data.length, (PUCHAR *) &ListElement->value.preauth_data.value ); if (!KERB_SUCCESS(KerbErr)) { goto Cleanup; } ListElement->value.preauth_data_type = KRB5_PADATA_S4U; ListElement->next = NULL; *PreAuthData = ListElement; Cleanup: KerbFreePrincipalName(&S4UserPA.client_name); KerbFreeRealm(&S4UserPA.client_realm); return KerbErr; } //+------------------------------------------------------------------------- // // Function: KerbGetTgtToS4URealm // // Synopsis: We need a TGT to the client realm under the caller's cred's // so we can make a S4U TGS_REQ. // // // Effects: // // Arguments: LogonSession - Logon session of the service doing the // S4U request // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbGetTgtToS4URealm( IN PKERB_LOGON_SESSION CallerLogonSession, IN PKERB_CREDENTIAL Credential, IN PUNICODE_STRING S4UClientRealm, IN OUT PKERB_TICKET_CACHE_ENTRY * S4UTgt, IN ULONG Flags, IN ULONG TicketOptions, IN ULONG EncryptionType ) { NTSTATUS Status; ULONG RetryFlags = 0; BOOLEAN CrossRealm = FALSE, CacheTicket = TRUE; BOOLEAN TicketCacheLocked = FALSE, LogonSessionsLocked = FALSE; PKERB_TICKET_CACHE_ENTRY TicketGrantingTicket = NULL; PKERB_TICKET_CACHE_ENTRY LastTgt = NULL; PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL; PKERB_KDC_REPLY KdcReply = NULL; PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody = NULL; PKERB_INTERNAL_NAME TargetTgtKdcName = NULL; UNICODE_STRING ClientRealm = NULL_UNICODE_STRING; PKERB_PRIMARY_CREDENTIAL PrimaryCredentials = NULL; *S4UTgt = NULL; if ((Credential != NULL) && (Credential->SuppliedCredentials != NULL)) { PrimaryCredentials = Credential->SuppliedCredentials; } else { PrimaryCredentials = &CallerLogonSession->PrimaryCredentials; } Status = KerbGetTgtForService( CallerLogonSession, Credential, NULL, NULL, S4UClientRealm, 0, &TicketGrantingTicket, &CrossRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // If this isn't cross realm, then we've got a TGT to the realm. // return it and bail. // if (!CrossRealm) { DebugLog((DEB_ERROR, "We have a TGT for %wZ\n", S4UClientRealm)); *S4UTgt = TicketGrantingTicket; TicketGrantingTicket = NULL; goto Cleanup; } if (!KERB_SUCCESS(KerbBuildFullServiceKdcName( S4UClientRealm, &KerbGlobalKdcServiceName, KRB_NT_SRV_INST, &TargetTgtKdcName ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } // // Copy out the client realm name which is used when obtaining the ticket // Status = KerbDuplicateString( &ClientRealm, &PrimaryCredentials->DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Do some referral chasing to get our ticket grantin ticket. // while (!RtlEqualUnicodeString( S4UClientRealm, &TicketGrantingTicket->TargetDomainName, TRUE )) { // // If we just got two TGTs for the same domain, then we must have // gotten as far as we can. Chances our our RealTargetRealm is a // variation of what the KDC hands out. // if ((LastTgt != NULL) && RtlEqualUnicodeString( &LastTgt->TargetDomainName, &TicketGrantingTicket->TargetDomainName, TRUE )) { KerbUnlockTicketCache(); KerbSetTicketCacheEntryTarget( S4UClientRealm, LastTgt ); KerbReadLockTicketCache(); TicketCacheLocked = TRUE; D_DebugLog((DEB_TRACE_CTXT, "Got two TGTs for same realm (%wZ), bailing out of referral loop\n", &LastTgt->TargetDomainName)); break; } D_DebugLog((DEB_TRACE_CTXT, "Getting referral TGT for \n")); D_KerbPrintKdcName(DEB_TRACE_CTXT, TargetTgtKdcName); D_KerbPrintKdcName(DEB_TRACE_CTXT, TicketGrantingTicket->ServiceName); KerbUnlockTicketCache(); TicketCacheLocked = FALSE; // // Cleanup old state // KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL; Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetTgtKdcName, FALSE, TicketOptions, EncryptionType, NULL, NULL, // no PA data here. NULL, // no tgt reply since target is krbtgt &KdcReply, &KdcReplyBody, &RetryFlags ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN,"Failed to get TGS ticket for service 0x%x :", Status )); KerbPrintKdcName(DEB_WARN, TargetTgtKdcName ); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__)); // // We want to map cross-domain failures to failures indicating // that a KDC could not be found. This means that for Kerberos // logons, the negotiate code will retry with a different package // // if (Status == STATUS_NO_TRUST_SAM_ACCOUNT) // { // Status = STATUS_NO_LOGON_SERVERS; // } goto Cleanup; } // // Now we have a ticket - lets cache it // KerbWriteLockLogonSessions(CallerLogonSession); LogonSessionsLocked = TRUE; Status = KerbCacheTicket( &PrimaryCredentials->AuthenticationTicketCache, KdcReply, KdcReplyBody, NULL, // no target name NULL, // no targe realm 0, // no flags CacheTicket, &TicketCacheEntry ); KerbUnlockLogonSessions(CallerLogonSession); LogonSessionsLocked = FALSE; if (!NT_SUCCESS(Status)) { goto Cleanup; } if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); LastTgt = NULL; } LastTgt = TicketGrantingTicket; TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL; KerbReadLockTicketCache(); TicketCacheLocked = TRUE; } // ** WHILE ** *S4UTgt = TicketGrantingTicket; TicketGrantingTicket = NULL; Cleanup: if (TicketCacheLocked) { KerbUnlockTicketCache(); } if (LogonSessionsLocked) { KerbUnlockLogonSessions(CallerLogonSession); } KerbFreeTgsReply( KdcReply ); KerbFreeKdcReplyBody( KdcReplyBody ); KerbFreeKdcName( &TargetTgtKdcName ); if (TicketGrantingTicket != NULL) { KerbDereferenceTicketCacheEntry(TicketGrantingTicket); } KerbFreeString( &ClientRealm ); return Status; } //+------------------------------------------------------------------------- // // Function: KerbBuildS4UPreauth // // Synopsis: Attempt to gets TGT for an S4U client for name // location purposes. // // // Effects: // // Arguments: LogonSession - Logon session of the service doing the // S4U request // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbGetS4UServiceTicket( IN PKERB_LOGON_SESSION CallerLogonSession, IN PKERB_LOGON_SESSION NewLogonSession, IN PKERB_CREDENTIAL Credential, IN PKERB_INTERNAL_NAME S4UClientName, IN PUNICODE_STRING S4UClientRealm, IN OUT PKERB_TICKET_CACHE_ENTRY * S4UTicket, IN ULONG Flags, IN ULONG TicketOptions, IN ULONG EncryptionType ) { NTSTATUS Status; KERBERR KerbErr; PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL; PKERB_TICKET_CACHE_ENTRY TicketGrantingTicket = NULL; PKERB_TICKET_CACHE_ENTRY S4UTgt = NULL; PKERB_TICKET_CACHE_ENTRY LastTgt = NULL; PKERB_KDC_REPLY KdcReply = NULL; PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody = NULL; PKERB_PA_DATA_LIST S4UPaDataList = NULL; BOOLEAN LogonSessionsLocked = FALSE; BOOLEAN TicketCacheLocked = FALSE; BOOLEAN CrossRealm = FALSE; PKERB_INTERNAL_NAME RealTargetName = NULL; PKERB_INTERNAL_NAME TargetName = NULL; UNICODE_STRING RealTargetRealm = NULL_UNICODE_STRING; PKERB_INTERNAL_NAME TargetTgtKdcName = NULL; PKERB_PRIMARY_CREDENTIAL PrimaryCredentials = NULL; BOOLEAN UsedCredentials = FALSE; UNICODE_STRING ClientRealm = NULL_UNICODE_STRING; BOOLEAN CacheTicket = ((Flags & KERB_GET_TICKET_NO_CACHE) == 0); BOOLEAN PurgeTgt = FALSE; ULONG ReferralCount = 0; ULONG RetryFlags = 0; KERBEROS_MACHINE_ROLE Role; BOOLEAN fMITRetryAlreadyMade = FALSE; BOOLEAN TgtRetryMade = FALSE; BOOLEAN PurgedEntry = FALSE; // // Peform S4U TGS_REQ for ourselves // Flags |= KERB_GET_TICKET_S4U; // HACK TicketOptions |= (KERB_KDC_OPTIONS_name_canonicalize | KERB_KDC_OPTIONS_cname_in_pa_data); // // Get our own name, and other globals. // KerbGlobalReadLock(); Role = KerbGetGlobalRole(); Status = KerbDuplicateKdcName( &TargetName, KerbGlobalMitMachineServiceName ); KerbGlobalReleaseLock(); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Check to see if the credential has any primary credentials // TGTRetry: KerbReadLockLogonSessions(CallerLogonSession); LogonSessionsLocked = TRUE; if ((Credential != NULL) && (Credential->SuppliedCredentials != NULL)) { PrimaryCredentials = Credential->SuppliedCredentials; UsedCredentials = TRUE; } else { PrimaryCredentials = &CallerLogonSession->PrimaryCredentials; } // // Here we make sure we have a ticket to the KDC of the user's account // if ((Flags & KERB_GET_TICKET_NO_CACHE) == 0) { // // TBD: Create a S4U Ticket cache to hang off of the // service logon session. // These tickets have a lifetime of 10 minutes. // TicketCacheEntry = KerbLocateTicketCacheEntry( &PrimaryCredentials->S4UTicketCache, S4UClientName, S4UClientRealm ); } // // TBD: More for here? // if (TicketCacheEntry != NULL) { ULONG TicketFlags; ULONG CacheTicketFlags; ULONG CacheEncryptionType; // // Check if the flags are present // KerbReadLockTicketCache(); CacheTicketFlags = TicketCacheEntry->TicketFlags; CacheEncryptionType = TicketCacheEntry->Ticket.encrypted_part.encryption_type; KerbUnlockTicketCache(); TicketFlags = KerbConvertKdcOptionsToTicketFlags(TicketOptions); if (((CacheTicketFlags & TicketFlags) != TicketFlags) || ((EncryptionType != 0) && (CacheEncryptionType != EncryptionType))) { KerbDereferenceTicketCacheEntry(TicketCacheEntry); TicketCacheEntry = NULL; } else { // // Check the ticket time // if (KerbTicketIsExpiring(TicketCacheEntry, TRUE)) { KerbDereferenceTicketCacheEntry(TicketCacheEntry); TicketCacheEntry = NULL; } else { *S4UTicket = TicketCacheEntry; TicketCacheEntry = NULL; goto Cleanup; } } } // // Get a krbtgt/S4URealm ticket // Status = KerbGetTgtToS4URealm( CallerLogonSession, Credential, S4UClientRealm, &TicketGrantingTicket, Flags, // tbd: support for these options? TicketOptions, EncryptionType ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Cannot get S4U Tgt - %x\n", Status)); goto Cleanup; } // // Build the preauth for our TGS req // Status = KerbBuildS4UPreauth( &S4UPaDataList, S4UClientName, S4UClientRealm ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbBuildS4UPreauth failed - %x\n",Status)); goto Cleanup; } // // If the caller wanted any special options, don't cache this ticket. // if ((TicketOptions != 0) || (EncryptionType != 0) || ((Flags & KERB_GET_TICKET_NO_CACHE) != 0)) { CacheTicket = FALSE; } // // Copy out the client realm name which is used when obtaining the ticket // Status = KerbDuplicateString( &ClientRealm, &PrimaryCredentials->DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } KerbUnlockLogonSessions(CallerLogonSession); LogonSessionsLocked = FALSE; ReferralRestart: // // This is our first S4U TGS_REQ. We'll // eventually transit back to our realm. // Note: If we get back a referral, the KDC reply // is a TGT to another realm, so keep trying, but // be sure to use that TGT. // Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetName, // TBD: right name? Flags, TicketOptions, EncryptionType, NULL, S4UPaDataList, NULL, &KdcReply, &KdcReplyBody, &RetryFlags ); // // We're done w/ S4UTgt. Deref, and check // for errors // if (TicketGrantingTicket != NULL) { KerbDereferenceTicketCacheEntry(TicketGrantingTicket); // // Check for bad option TGT purging // if (((RetryFlags & KERB_RETRY_WITH_NEW_TGT) != 0) && !TgtRetryMade) { DebugLog((DEB_WARN, "Doing TGT retry - %p\n", TicketGrantingTicket)); // // Unlink && purge bad tgt // KerbRemoveTicketCacheEntry(TicketGrantingTicket); KerbDereferenceTicketCacheEntry(TicketGrantingTicket); // free from list TgtRetryMade = TRUE; TicketGrantingTicket = NULL; goto TGTRetry; } TicketGrantingTicket = NULL; } if (!NT_SUCCESS(Status)) { // // Check for the MIT retry case // if (((RetryFlags & KERB_MIT_NO_CANONICALIZE_RETRY) != 0) && (!fMITRetryAlreadyMade) && (Role != KerbRoleRealmlessWksta)) { Status = KerbMITGetMachineDomain( CallerLogonSession, TargetName, S4UClientRealm, &TicketGrantingTicket ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed Query policy information %ws, line %d\n", THIS_FILE, __LINE__)); goto Cleanup; } fMITRetryAlreadyMade = TRUE; goto TGTRetry; } DebugLog((DEB_WARN,"Failed to get TGS ticket for service 0x%x : \n", Status )); KerbPrintKdcName(DEB_WARN, TargetName ); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__)); goto Cleanup; } // // Check for referral info in the name // Should be there if S4Urealm != Our Realm Status = KerbGetReferralNames( KdcReplyBody, TargetName, &RealTargetRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // If this is not a referral ticket, just cache it and return // the cache entry. // if (RealTargetRealm.Length == 0) { // // Now we have a ticket - lets cache it // KerbWriteLockLogonSessions(CallerLogonSession); LogonSessionsLocked = TRUE; Status = KerbCacheTicket( &PrimaryCredentials->S4UTicketCache, KdcReply, KdcReplyBody, TargetName, S4UClientRealm, 0, CacheTicket, &TicketCacheEntry ); KerbUnlockLogonSessions(CallerLogonSession); LogonSessionsLocked = FALSE; if (!NT_SUCCESS(Status)) { goto Cleanup; } *S4UTicket = TicketCacheEntry; TicketCacheEntry = NULL; // // We're done, so get out of here. // goto Cleanup; } // // The server referred us to another domain. Get the service's full // name from the ticket and try to find a TGT in that domain. // Status = KerbDuplicateKdcName( &RealTargetName, TargetName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } D_DebugLog((DEB_TRACE_CTXT, "Got referral ticket for service \n")); D_KerbPrintKdcName(DEB_TRACE_CTXT,TargetName); D_DebugLog((DEB_TRACE_CTXT, "in realm \n")); D_KerbPrintKdcName(DEB_TRACE_CTXT,RealTargetName); // // Turn the KDC reply (xrealm tgt w/ s4u pac) into something we can use, // but *don't* cache it. // Status = KerbCacheTicket( NULL, KdcReply, KdcReplyBody, NULL, // no target name NULL, // no targe realm 0, // no flags FALSE, // just create entry &TicketCacheEntry ); if (!NT_SUCCESS(Status)) { goto Cleanup; } TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL; // // cleanup // KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL; // // Now we are in a case where we have a realm to aim for and a TGT. While // we don't have a TGT for the target realm, get one. // if (!KERB_SUCCESS(KerbBuildFullServiceKdcName( &RealTargetRealm, &KerbGlobalKdcServiceName, KRB_NT_SRV_INST, &TargetTgtKdcName ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } KerbReadLockTicketCache(); TicketCacheLocked = TRUE; // // Referral chasing code block - very important to get right // If we know the "real" target realm, eg. from GC, then // we'll walk trusts until we hit "real" target realm. // while (!RtlEqualUnicodeString( &RealTargetRealm, &TicketGrantingTicket->TargetDomainName, TRUE )) { // // If we just got two TGTs for the same domain, then we must have // gotten as far as we can. Chances our our RealTargetRealm is a // variation of what the KDC hands out. // if ((LastTgt != NULL) && RtlEqualUnicodeString( &LastTgt->TargetDomainName, &TicketGrantingTicket->TargetDomainName, TRUE )) { KerbUnlockTicketCache(); KerbSetTicketCacheEntryTarget( &RealTargetRealm, LastTgt ); KerbReadLockTicketCache(); D_DebugLog((DEB_TRACE_CTXT, "Got two TGTs for same realm (%wZ), bailing out of referral loop\n", &LastTgt->TargetDomainName)); break; } D_DebugLog((DEB_TRACE_CTXT, "Getting referral TGT for \n")); D_KerbPrintKdcName(DEB_TRACE_CTXT, TargetTgtKdcName); D_KerbPrintKdcName(DEB_TRACE_CTXT, TicketGrantingTicket->ServiceName); KerbUnlockTicketCache(); TicketCacheLocked = FALSE; // // Cleanup old state // KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL; Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetTgtKdcName, FALSE, TicketOptions, EncryptionType, NULL, S4UPaDataList, NULL, // no tgt reply since target is krbtgt &KdcReply, &KdcReplyBody, &RetryFlags ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN,"Failed to get TGS ticket for service 0x%x :", Status )); KerbPrintKdcName(DEB_WARN, TargetTgtKdcName ); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__)); // // We want to map cross-domain failures to failures indicating // that a KDC could not be found. This means that for Kerberos // logons, the negotiate code will retry with a different package // // if (Status == STATUS_NO_TRUST_SAM_ACCOUNT) // { // Status = STATUS_NO_LOGON_SERVERS; // } goto Cleanup; } // // Now we have a ticket - don't cache it, however // Status = KerbCacheTicket( &PrimaryCredentials->AuthenticationTicketCache, KdcReply, KdcReplyBody, NULL, // no target name NULL, // no targe realm 0, // no flags FALSE, &TicketCacheEntry ); if (!NT_SUCCESS(Status)) { goto Cleanup; } if (LastTgt != NULL) { KerbFreeTicketCacheEntry(LastTgt); LastTgt = NULL; } LastTgt = TicketGrantingTicket; TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL; KerbReadLockTicketCache(); TicketCacheLocked = TRUE; } // ** WHILE ** // // Now we must have a TGT to our service's domain. Get a ticket // to the service. // // FESTER : put assert in to make sure this tgt is to our realm. // // Cleanup old state // KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL; Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetName, FALSE, TicketOptions, EncryptionType, NULL, S4UPaDataList, NULL, &KdcReply, &KdcReplyBody, &RetryFlags ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN,"Failed to get TGS ticket for service 0x%x ", Status )); KerbPrintKdcName(DEB_WARN, RealTargetName); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__)); goto Cleanup; } // // Now that we are in the domain to which we were referred, check for referral // info in the name // KerbFreeString(&RealTargetRealm); Status = KerbGetReferralNames( KdcReplyBody, RealTargetName, &RealTargetRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // If this is not a referral ticket, just cache it and return // the cache entry. // if (RealTargetRealm.Length != 0) { // // To prevent loops, we limit the number of referral we'll take // if (ReferralCount > KerbGlobalMaxReferralCount) { DebugLog((DEB_ERROR,"Maximum referral count exceeded for name: ")); KerbPrintKdcName(DEB_ERROR,RealTargetName); Status = STATUS_MAX_REFERRALS_EXCEEDED; goto Cleanup; } ReferralCount++; // // Don't cache the interdomain TGT, as it has PAC info in it. // Status = KerbCacheTicket( NULL, KdcReply, KdcReplyBody, NULL, // no target name NULL, // no target realm 0, // no flags FALSE, &TicketCacheEntry ); // // Cleanup old state // KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL; if (!NT_SUCCESS(Status)) { goto Cleanup; } if (LastTgt != NULL) { KerbFreeTicketCacheEntry(LastTgt); LastTgt = NULL; } LastTgt = TicketGrantingTicket; TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL; D_DebugLog((DEB_TRACE_CTXT, "Restart referral:%wZ", &RealTargetRealm)); goto ReferralRestart; } // // Now we have a ticket - lets cache it // // // Before doing anything, verify that the client name on the ticket // is equal to the client name requested during the S4u // // TBD: Once ticket cache code is ready for this, implement it. // Also verify PAC information is correct. // KerbWriteLockLogonSessions(CallerLogonSession); LogonSessionsLocked = TRUE; Status = KerbCacheTicket( &PrimaryCredentials->S4UTicketCache, KdcReply, KdcReplyBody, TargetName, S4UClientRealm, 0, // no flags CacheTicket, &TicketCacheEntry ); KerbUnlockLogonSessions(CallerLogonSession); LogonSessionsLocked = FALSE; if (!NT_SUCCESS(Status)) { goto Cleanup; } *S4UTicket = TicketCacheEntry; TicketCacheEntry = NULL; Cleanup: KerbFreeTgsReply( KdcReply ); KerbFreeKdcReplyBody( KdcReplyBody ); KerbFreeKdcName( &TargetTgtKdcName ); KerbFreeString( &RealTargetRealm ); KerbFreeKdcName(&RealTargetName); KerbFreePreAuthData(S4UPaDataList); if (TicketCacheLocked) { KerbUnlockTicketCache(); } if (LogonSessionsLocked) { KerbUnlockLogonSessions(CallerLogonSession); } KerbFreeString(&RealTargetRealm); // // We never cache TGTs in this routine // so it's just a blob of memory // if (TicketGrantingTicket != NULL) { KerbFreeTicketCacheEntry(TicketGrantingTicket); } if (LastTgt != NULL) { KerbFreeTicketCacheEntry(LastTgt); LastTgt = NULL; } // // If we still have a pointer to the ticket cache entry, free it now. // if (TicketCacheEntry != NULL) { KerbRemoveTicketCacheEntry( TicketCacheEntry ); KerbDereferenceTicketCacheEntry(TicketCacheEntry); } KerbFreeString(&ClientRealm); return(Status); } //+------------------------------------------------------------------------- // // Function: KerbCreateS4ULogonSession // // Synopsis: Creates a logon session to accompany the S4ULogon. // // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbCreateS4ULogonSession( IN PKERB_INTERNAL_NAME S4UClientName, IN PUNICODE_STRING S4UClientRealm, IN PLUID pLuid, IN OUT PKERB_LOGON_SESSION * LogonSession ) { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING S4UClient = {0}; *LogonSession = NULL; DsysAssert(S4UClientName->NameCount == 1); DsysAssert(S4UClientName->NameType == KRB_NT_ENTERPRISE_PRINCIPAL); if (!KERB_SUCCESS( KerbConvertKdcNameToString( &S4UClient, S4UClientName, NULL )) ) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } Status = KerbCreateLogonSession( pLuid, &S4UClient, S4UClientRealm, // do we need this? NULL, NULL, KERB_LOGON_S4U_SESSION, // fester Network, LogonSession ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbCreateLogonSession failed %x %ws, line %d\n", Status, THIS_FILE, __LINE__)); goto Cleanup; } Cleanup: KerbFreeString(&S4UClient); return Status; } //+------------------------------------------------------------------------- // // Function: KerbS4UToSelfLogon // // Synopsis: Attempt to gets TGT for an S4U client for name // location purposes. // // // Effects: // // Arguments: LogonSession - Logon session of the service doing the // S4U request // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbS4UToSelfLogon( IN PVOID ProtocolSubmitBuffer, IN PVOID ClientBufferBase, IN ULONG SubmitBufferSize, OUT PKERB_LOGON_SESSION * NewLogonSession, OUT PLUID LogonId, OUT PKERB_TICKET_CACHE_ENTRY * WorkstationTicket, OUT PKERB_INTERNAL_NAME * S4UClientName, OUT PUNICODE_STRING S4UClientRealm ) { NTSTATUS Status; KERBERR KerbErr; PKERB_S4U_LOGON LogonInfo = NULL; PKERB_LOGON_SESSION CallerLogonSession = NULL; PKERB_INTERNAL_NAME KdcServiceKdcName = NULL; SECPKG_CLIENT_INFO ClientInfo; ULONG_PTR Offset; ULONG Flags = KERB_CRACK_NAME_USE_WKSTA_REALM, ProcessFlags = 0; // fester: UNICODE_STRING DummyRealm = {0}; if (!KerbRunningServer()) { D_DebugLog((DEB_ERROR, "Not running server, no S4u!\n")); return SEC_E_UNSUPPORTED_FUNCTION; } *NewLogonSession = NULL; *WorkstationTicket = NULL; *S4UClientName = NULL; RtlInitUnicodeString( S4UClientRealm, NULL ); Status = LsaFunctions->GetClientInfo(&ClientInfo); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to get client information: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; } // // TBD: Any other validation code here? // LogonInfo = (PKERB_S4U_LOGON) ProtocolSubmitBuffer; RELOCATE_ONE(&LogonInfo->ClientUpn); NULL_RELOCATE_ONE(&LogonInfo->ClientRealm); // // TBD: put in cache code here, so we can easily locate a ticket we have // gotten in the "recent" past. // // // Tbd: Any special name rules to put in here? // e.g. if we get a UPN and a realm, which takes // precedence? // // // TBD: Convert client name (unicode) into client name (internal) // Status = KerbProcessTargetNames( &LogonInfo->ClientUpn, NULL, Flags, &ProcessFlags, S4UClientName, &DummyRealm, NULL ); // Dummy Realm == info after @ sign //DsysAssert(DummyRealm.Length == 0); DsysAssert((*S4UClientName)->NameType == KRB_NT_ENTERPRISE_PRINCIPAL); if (!NT_SUCCESS(Status)) { goto Cleanup; } CallerLogonSession = KerbReferenceLogonSession( &ClientInfo.LogonId, FALSE ); if (NULL == CallerLogonSession) { D_DebugLog((DEB_ERROR, "Failed to locate caller's logon session - %x\n", ClientInfo.LogonId)); Status = STATUS_NO_SUCH_LOGON_SESSION; DsysAssert(FALSE); goto Cleanup; } // // First, we need to get the client's realm from the UPN // if (LogonInfo->ClientRealm.Length == 0) { Status = KerbGetS4UClientRealm( CallerLogonSession, S4UClientName, S4UClientRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } else { Status = KerbDuplicateString( S4UClientRealm, &LogonInfo->ClientRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } // // Allocate a locally unique ID for this logon session. We will // create it in the LSA just before returning. // Status = NtAllocateLocallyUniqueId( LogonId ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to allocate locally unique ID: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; } Status = KerbCreateS4ULogonSession( (*S4UClientName), S4UClientRealm, LogonId, NewLogonSession ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to create logon session 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; } Status = KerbGetS4UServiceTicket( CallerLogonSession, (*NewLogonSession), NULL, // tbd: need to put credential here? (*S4UClientName), S4UClientRealm, WorkstationTicket, 0, // no flags 0, // no ticketoptions 0 // no enctype ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Cleanup: if (!NT_SUCCESS(Status)) { // // TBD: Negative cache here, based on client name // KerbFreeString(S4UClientRealm); KerbFreeKdcName(S4UClientName); *S4UClientName = NULL; } return Status; }