windows-nt/Source/XPSP1/NT/ds/security/protocols/kerberos/client2/kerbs4u.cxx

1759 lines
43 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 2001
//
// File: kerbs4u.cxx
//
// Contents: Code for doing S4UToSelf() logon.
//
//
// History: 14-March-2001 Created Todds
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#include <kerbp.h>
#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;
}