|
|
//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1996
//
// File: logonapi.cxx
//
// Contents: Code for logon and logoff for the Kerberos package
//
//
// History: 16-April-1996 MikeSw Created
// 15-June-2000 t-ryanj Added event tracing support
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#include <kerbp.h>
#include <utils.hxx>
#ifdef DEBUG_SUPPORT
static TCHAR THIS_FILE[]=TEXT(__FILE__); #endif
#define FILENO FILENO_LOGONAPI
EXTERN BOOLEAN fNewDataAboutDomain; EXTERN BOOLEAN fRebootedSinceJoin;
//+-------------------------------------------------------------------------
//
// Function: KerbFindCommonPaEtype
//
// Synopsis: Finds an encryption type in common between KDC and client.
//
// Effects:
//
// Arguments: Credentials - Client's credentials, must be locked
// InputPaData - PA data from an error return from the KDC
// UseOldPassword - if TRUE, use the old password instead of current password
// UserKey - receives key for common encryption type
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbFindCommonPaEtype( IN PKERB_PRIMARY_CREDENTIAL Credentials, IN OPTIONAL PKERB_PA_DATA_LIST InputPaData, IN BOOLEAN UseOldPassword, IN BOOLEAN IgnoreSaltFailures, OUT PKERB_ENCRYPTION_KEY * UserKey ) { NTSTATUS Status = STATUS_SUCCESS; KERBERR KerbErr; PKERB_PA_DATA InputData = NULL; ULONG PasswordTypes[KERB_MAX_CRYPTO_SYSTEMS]; ULONG PasswordCount; ULONG KdcEtypes[KERB_MAX_CRYPTO_SYSTEMS]; ULONG KdcEtypeCount = 0; PKERB_ETYPE_INFO * EtypeInfo = NULL; PKERB_ETYPE_INFO EtypeEntry; ULONG CommonCryptSystem; ULONG Index; PKERB_STORED_CREDENTIAL Passwords; BOOLEAN UseDES = FALSE;
*UserKey = NULL;
//
// Check to see if the input had any interesting PA data
//
if ((InputPaData != NULL) && (!UseOldPassword)) { InputData = KerbFindPreAuthDataEntry( KRB5_PADATA_ETYPE_INFO, InputPaData ); if (InputData == NULL) { //
// If no etype-info was provided, then we are out of luck.
// Change this to allow for utilizing default DES etype if no
// etypeinfo specified for Heimdel KDC interop. Bug#87960
//
//Status = STATUS_NO_PA_DATA;
//goto Cleanup;
UseDES = TRUE; } else { //
// Unpack the etype info
//
KerbErr = KerbUnpackData( InputData->preauth_data.value, InputData->preauth_data.length, PKERB_ETYPE_INFO_PDU, (PVOID *) &EtypeInfo ); if (!KERB_SUCCESS(KerbErr)) { D_DebugLog((DEB_ERROR,"Failed to unpack ETYPE INFO: 0x%x. %ws, line%d\n",KerbErr, THIS_FILE, __LINE__)); Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } //
// Build a new set of passwords
//
Status = KerbChangeCredentialsPassword( Credentials, NULL, // no password
*EtypeInfo, UnknownAccount, PRIMARY_CRED_CLEAR_PASSWORD ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to update primary creds with new salt: 0x%x, file %ws %d\n", Status, THIS_FILE, __LINE__ ));
if (!IgnoreSaltFailures) { //
// Remap the error, as we want to return a more useful error
//
if (Status == STATUS_INVALID_PARAMETER) { Status = STATUS_WRONG_PASSWORD; } goto Cleanup; } else { Status = STATUS_SUCCESS; }
}
//
// Build a list of crypt types from the etype info
//
KdcEtypeCount = 0; EtypeEntry = *EtypeInfo; while (EtypeEntry != NULL) { KdcEtypes[KdcEtypeCount++] = EtypeEntry->value.encryption_type; EtypeEntry = EtypeEntry->next; if (KdcEtypeCount == KERB_MAX_CRYPTO_SYSTEMS) { break; } } }
} else { ULONG OldFirstEtype;
//
// Include all our crypt types as supported
//
Status = CDBuildIntegrityVect( &KdcEtypeCount, KdcEtypes ); DsysAssert(NT_SUCCESS(Status)); DsysAssert(KdcEtypeCount >= 1);
//
// replace the first etype with the default
//
if (KdcEtypes[0] != KerbGlobalDefaultPreauthEtype) { OldFirstEtype = KdcEtypes[0]; KdcEtypes[0] = KerbGlobalDefaultPreauthEtype;
for (Index = 1; Index < KdcEtypeCount ; Index++ ) { if ( KdcEtypes[Index] == KerbGlobalDefaultPreauthEtype) { KdcEtypes[Index] = OldFirstEtype; break; } } }
}
// Heimdal KDC compat gives us no supported EType info, so
// we've got to rely upon SPEC'd default, DES encryption.
// See bug 87960 for more info. NOTE: We'll try this
// 2 times... Should work to avoid this..
if (UseDES) {
ULONG OldFirstEtype;
//
// Include all our crypt types as supported
//
Status = CDBuildIntegrityVect( &KdcEtypeCount, KdcEtypes ); DsysAssert(NT_SUCCESS(Status)); DsysAssert(KdcEtypeCount >= 1);
//
// Use **only** DES, as it should be supported by all
// KDCs, and w/o preauth ETYPEINFO data, we would have
// to hit every ETYPE.
// TBD: When Heimdal supports RC4, or if they fix their
// padata, then pull this code.
if (KdcEtypes[0] != KERB_ETYPE_DES_CBC_MD5) { OldFirstEtype = KdcEtypes[0]; KdcEtypes[0] = KERB_ETYPE_DES_CBC_MD5;
for (Index = 1; Index < KdcEtypeCount ; Index++ ) { if ( KdcEtypes[Index] == KERB_ETYPE_DES_CBC_MD5) { KdcEtypes[Index] = OldFirstEtype; break; } } }
}
//
// Build the list of passwords
//
if (UseOldPassword) { Passwords = Credentials->OldPasswords; } else { Passwords = Credentials->Passwords; }
if (Passwords == NULL) { Status = STATUS_WRONG_PASSWORD; goto Cleanup; }
PasswordCount = Passwords->CredentialCount; if (PasswordCount > KERB_MAX_CRYPTO_SYSTEMS) { DsysAssert(PasswordCount < KERB_MAX_CRYPTO_SYSTEMS); Status = STATUS_INTERNAL_ERROR; goto Cleanup; }
for (Index = 0; Index < PasswordCount ; Index++ ) { PasswordTypes[Index] = (ULONG) Passwords->Credentials[Index].Key.keytype; }
//
// Now find the common crypt system
//
Status = CDFindCommonCSystemWithKey( KdcEtypeCount, KdcEtypes, PasswordCount, PasswordTypes, &CommonCryptSystem ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// Get the key for the common crypt type
//
*UserKey = KerbGetKeyFromList( Passwords, CommonCryptSystem ); DsysAssert(*UserKey != NULL);
//
// If we were using etype info, and not an old password, and the
// etype doesn't use salt, then fail the operation, as we aren't
// really generating a new key.
//
if (!UseOldPassword && (CommonCryptSystem == KerbGlobalDefaultPreauthEtype) && ARGUMENT_PRESENT(InputPaData)) { PCRYPTO_SYSTEM CryptoSystem = NULL; Status = CDLocateCSystem(CommonCryptSystem, &CryptoSystem); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to load %d crypt system: 0x%x.\n",CommonCryptSystem,Status )); goto Cleanup; }
DsysAssert(CryptoSystem != NULL); if ((CryptoSystem->Attributes & CSYSTEM_USE_PRINCIPAL_NAME) == 0) { if (!IgnoreSaltFailures) { D_DebugLog((DEB_WARN,"Tried to update password with new salt, but keytype 0x%x doesn't use salt.\n", CommonCryptSystem));
*UserKey = NULL; Status = STATUS_WRONG_PASSWORD; goto Cleanup; } } }
Cleanup:
if (EtypeInfo != NULL) { KerbFreeData(PKERB_ETYPE_INFO_PDU, EtypeInfo); } return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbBuildPreAuthData
//
// Synopsis: Builds pre-auth data for type the specified pre-auth types
//
// Effects:
//
// Arguments: Credentials - Client's credentials, must be locked
// RealmName - Name of target realm
// ServiceName - Name of target server
// PaTypeCount - count of pre-auth types to build
// PaTypes - list of pre-auth types to build
// InputPaData - any PA data returned by a previous (failed)
// AS request
// TimeSkew - Time Skew with KDC
// UseOldPassword - Use the old password instead of current one
// PreAuthData - receives list of pre-auth data
// Done - don't call again on pre-auth failure
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbBuildPreAuthData( IN PKERB_PRIMARY_CREDENTIAL Credentials, IN PUNICODE_STRING RealmName, IN PKERB_INTERNAL_NAME ServiceName, IN ULONG PaTypeCount, IN PULONG PaTypes, IN OPTIONAL PKERB_PA_DATA_LIST InputPaData, IN PTimeStamp TimeSkew, IN BOOLEAN UseOldPassword, IN ULONG Nonce, IN KERBERR ErrorCode, OUT PKERB_PA_DATA_LIST * PreAuthData, OUT PKERB_ENCRYPTION_KEY EncryptionKey, OUT PKERB_CRYPT_LIST * CryptList, OUT PBOOLEAN Done ) { KERBERR KerbErr = KDC_ERR_NONE; NTSTATUS Status = STATUS_SUCCESS; PKERB_PA_DATA_LIST ListElement = NULL; PKERB_PA_DATA_LIST OutputList = NULL; ULONG Index; BOOLEAN FoundPreauth = FALSE;
//
// Initialize outputs
//
*PreAuthData = NULL; *Done = FALSE;
for (Index = 0 ; Index < PaTypeCount ; Index++ ) { switch(PaTypes[Index]) { case KRB5_PADATA_ENC_TIMESTAMP: { KERB_ENCRYPTED_TIMESTAMP Timestamp = {0}; TimeStamp CurrentTime; PBYTE EncryptedTime = NULL; ULONG EncryptedTimeSize = 0; KERB_ENCRYPTED_DATA EncryptedData = {0}; PKERB_ENCRYPTION_KEY UserKey = NULL;
FoundPreauth = TRUE; //
// Check for encryption hints in the incoming pa-data
//
Status = KerbFindCommonPaEtype( Credentials, InputPaData, UseOldPassword, ErrorCode == KDC_ERR_PREAUTH_REQUIRED, // ignore salt problems on preauth-req errors
&UserKey ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// If there was any input PA data, we don't want to try again.
//
if (InputPaData != NULL) { *Done = TRUE; } //
// Build the output element
//
ListElement = (PKERB_PA_DATA_LIST) KerbAllocate(sizeof(KERB_PA_DATA_LIST)); if (ListElement == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Now build the encrypted timestamp
//
GetSystemTimeAsFileTime((PFILETIME) &CurrentTime);
//
// Adjust for time skew with KDC
//
KerbSetTime(&CurrentTime, KerbGetTime(CurrentTime) + KerbGetTime(*TimeSkew));
KerbConvertLargeIntToGeneralizedTimeWrapper( &Timestamp.timestamp, &Timestamp.KERB_ENCRYPTED_TIMESTAMP_usec, &CurrentTime );
Timestamp.bit_mask = KERB_ENCRYPTED_TIMESTAMP_usec_present;
KerbErr = KerbPackEncryptedTime( &Timestamp, &EncryptedTimeSize, &EncryptedTime ); if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Now encrypt the time
//
KerbErr = KerbAllocateEncryptionBufferWrapper( UserKey->keytype, EncryptedTimeSize, &EncryptedData.cipher_text.length, &EncryptedData.cipher_text.value );
if (!KERB_SUCCESS(KerbErr)) { D_DebugLog((DEB_ERROR,"\n\nFailed to get encryption overhead. %ws, line %d\n\n", THIS_FILE, __LINE__)); KerbFree(EncryptedTime); Status = KerbMapKerbError(KerbErr); goto Cleanup; }
KerbErr = KerbEncryptDataEx( &EncryptedData, EncryptedTimeSize, EncryptedTime, KERB_NO_KEY_VERSION, KERB_ENC_TIMESTAMP_SALT, UserKey ); KerbFree(EncryptedTime);
if (!KERB_SUCCESS(KerbErr)) { MIDL_user_free(EncryptedData.cipher_text.value); D_DebugLog((DEB_ERROR,"Failed to encrypt PA data. %ws, line %d\n", THIS_FILE, __LINE__)); Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Now pack the encrypted data
//
KerbErr = KerbPackEncryptedData( &EncryptedData, (PULONG) &ListElement->value.preauth_data.length, (PUCHAR *) &ListElement->value.preauth_data.value );
MIDL_user_free(EncryptedData.cipher_text.value);
if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
ListElement->value.preauth_data_type = KRB5_PADATA_ENC_TIMESTAMP; ListElement->next = OutputList; OutputList = ListElement; ListElement = NULL; break; } #ifndef WIN32_CHICAGO
case KRB5_PADATA_PK_AS_REQ: FoundPreauth = TRUE; Status = KerbBuildPkinitPreauthData( Credentials, InputPaData, TimeSkew, ServiceName, RealmName, Nonce, &OutputList, EncryptionKey, CryptList, Done );
break; #endif // WIN32_CHICAGO
default: continue; } } if (!FoundPreauth) { DebugLog((DEB_ERROR,"NO known pa data type passed to KerbBuildPreAuthData. %ws, line %d\n", THIS_FILE, __LINE__ )); Status = STATUS_UNSUPPORTED_PREAUTH; goto Cleanup;
} *PreAuthData = OutputList; OutputList = NULL;
Cleanup:
if (OutputList != NULL) { KerbFreePreAuthData( OutputList ); } if (ListElement != NULL) { KerbFreePreAuthData( ListElement ); }
return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbGetPreAuthDataForRealm
//
// Synopsis: Gets the appropriate pre-auth data for the specified realm.
// Right now it always returns KRB_ENC_TIMESTAMP pre-auth data
// but at some point it might do different things based on
// the realm.
//
// Effects:
//
// Arguments: Credentials - Client's credentials
// TargetRealm - realm from which the client is requesting a ticket
// OldPreAuthData - any pre-auth data returned from the KDC on
// the last AS request.
// TimeSkew - Time skew with KDC
// UseOldPassword - if TRUE, use the old password instead of current
// PreAuthData - Receives the new pre-auth data
// Done - if TRUE, don't bother trying again on a pre-auth required
// failure
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetPreAuthDataForRealm( IN PKERB_PRIMARY_CREDENTIAL Credentials, IN PUNICODE_STRING TargetRealm, IN PKERB_INTERNAL_NAME ServiceName, IN PKERB_PA_DATA_LIST OldPreAuthData, IN PTimeStamp TimeSkew, IN BOOLEAN UseOldPassword, IN ULONG Nonce, IN KERBERR ErrorCode, OUT PKERB_PA_DATA_LIST * PreAuthData, OUT PKERB_ENCRYPTION_KEY EncryptionKey, OUT PKERB_CRYPT_LIST * CryptList, OUT PBOOLEAN Done ) { #define KERB_MAX_PA_DATA_TYPES 10
NTSTATUS Status; ULONG PaTypeCount = 0; ULONG PaDataTypes[KERB_MAX_PA_DATA_TYPES]; PKERB_MIT_REALM MitRealm; BOOLEAN UsedAlternateName; PKERB_PA_DATA_LIST PreAuthElement = NULL;
//
// If an error message was supplied, see if we can pull out the
// list of supported pre-auth types from it.
//
if (ARGUMENT_PRESENT(OldPreAuthData) && (ErrorCode == KDC_ERR_PREAUTH_REQUIRED)) {
//
// Pick the first type from the list as the type
//
PreAuthElement = OldPreAuthData; while ((PaTypeCount < KERB_MAX_PA_DATA_TYPES) && (PreAuthElement != NULL)) { PaDataTypes[PaTypeCount++] = (ULONG) PreAuthElement->value.preauth_data_type; PreAuthElement = PreAuthElement->next; }
} else { //
// For MIT realms, check the list to see what kind of preauth to do.
//
if (KerbLookupMitRealm( TargetRealm, &MitRealm, &UsedAlternateName )) { //
// There are some types of preauth returned from the KDC that we
// need to log an event for. PA-PW-SALT (3) and PA-AFS3-SALT (10)
// are not implemented in our client, so log an error to help admins,
// and retry w/ default for realm.
//
while ((PaTypeCount < KERB_MAX_PA_DATA_TYPES) && (PreAuthElement != NULL)) { if (PreAuthElement->value.preauth_data_type == KRB5_PADATA_PW_SALT || PreAuthElement->value.preauth_data_type == KRB5_PADATA_AFS3_SALT) {
Status = STATUS_UNSUPPORTED_PREAUTH; DebugLog(( DEB_ERROR, "Unsupported Preauth type : %x\n", PreAuthElement->value.preauth_data_type ));
goto Cleanup; }
PaTypeCount++; PreAuthElement = PreAuthElement->next; }
if (MitRealm->PreAuthType != 0) { PaDataTypes[0] = MitRealm->PreAuthType; PaTypeCount = 1; } else { return(STATUS_SUCCESS); } }
//
// Plug in fancier capabilities here.
//
//
// If the caller has public key credentials, use pkinit rather than
// encrypted timestamp
//
else if (Credentials->PublicKeyCreds != NULL) { PaDataTypes[0] = KRB5_PADATA_PK_AS_REQ; PaTypeCount = 1; } else { //
// If we were succeful, ignore this preauth data
//
if ((ErrorCode == KDC_ERR_NONE) && (OldPreAuthData != NULL)) { return(STATUS_SUCCESS); } PaDataTypes[0] = KRB5_PADATA_ENC_TIMESTAMP; PaTypeCount = 1; }
}
Status = KerbBuildPreAuthData( Credentials, TargetRealm, ServiceName, PaTypeCount, PaDataTypes, OldPreAuthData, TimeSkew, UseOldPassword, Nonce, ErrorCode, PreAuthData, EncryptionKey, CryptList, Done );
Cleanup:
return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbUnpackErrorPreauth
//
// Synopsis: Unpacks preauth data from a kerb_error message
//
// Effects:
//
// Arguments: ErrorMessage - ErrorMessage from an AS request that failed
// with KDC_ERR_PREAUTH_REQUIRED
// PreAuthData - returns any preauth data from the error message
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbUnpackErrorPreauth( IN PKERB_ERROR ErrorMessage, OUT PKERB_PA_DATA_LIST ** PreAuthData ) { KERBERR KerbErr = KDC_ERR_NONE; NTSTATUS Status = STATUS_SUCCESS; PKERB_PREAUTH_DATA_LIST * ErrorPreAuth = NULL;
*PreAuthData = NULL;
//
// If there was no data, return now
//
if ((ErrorMessage->bit_mask & error_data_present) == 0) { //
// If we weren't given any hints, we can't do any better so return
// an error.
//
KerbErr = KDC_ERR_PREAUTH_REQUIRED; goto Cleanup; }
KerbErr = KerbUnpackData( ErrorMessage->error_data.value, ErrorMessage->error_data.length, PKERB_PREAUTH_DATA_LIST_PDU, (PVOID *) &ErrorPreAuth );
if (!KERB_SUCCESS(KerbErr)) { D_DebugLog((DEB_ERROR,"Failed to unpack pre-auth data from error message. %ws, line %d\n", THIS_FILE, __LINE__));
//
// This error code isn't particularly informative but we were unable to get the
// error information so this is the best we can do.
//
Status = STATUS_LOGON_FAILURE; goto Cleanup; }
//
// Make sure the two structures are similar
//
DsysAssert(FIELD_OFFSET(KERB_PREAUTH_DATA_LIST,next) == FIELD_OFFSET(KERB_PA_DATA_LIST,next)); DsysAssert(FIELD_OFFSET(KERB_PREAUTH_DATA_LIST,value) == FIELD_OFFSET(KERB_PA_DATA_LIST,value)); DsysAssert(sizeof(KERB_PREAUTH_DATA_LIST) == sizeof(KERB_PA_DATA_LIST));
*PreAuthData = (PKERB_PA_DATA_LIST *) ErrorPreAuth; ErrorPreAuth = NULL;
Cleanup:
if (ErrorPreAuth != NULL) { KerbFreeData(PKERB_PREAUTH_DATA_LIST_PDU,ErrorPreAuth); } return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbAddPacRequestPreAuth
//
// Synopsis: Add the pac-request preauth data to either requst a pac
// or request that no pac be included
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbAddPacRequestPreAuth( OUT PKERB_PA_DATA_LIST * PreAuthData, IN ULONG TicketFlags ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_PA_DATA_LIST ListElement = NULL; PKERB_PA_DATA_LIST LastElement = NULL; KERB_PA_PAC_REQUEST PacRequest = {0};
ListElement = (PKERB_PA_DATA_LIST) KerbAllocate(sizeof(KERB_PA_DATA_LIST)); if (ListElement == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
if ((TicketFlags & KERB_GET_TICKET_NO_PAC) != 0 ) { PacRequest.include_pac = FALSE; } else { PacRequest.include_pac = TRUE; }
//
// Marshall the type into the list element.
//
if (!KERB_SUCCESS(KerbPackData( &PacRequest, KERB_PA_PAC_REQUEST_PDU, (PULONG) &ListElement->value.preauth_data.length, (PUCHAR *) &ListElement->value.preauth_data.value ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } ListElement->value.preauth_data_type = KRB5_PADATA_PAC_REQUEST;
//
// We want this to go at the end, so that it will override any other
// pa-data that may enable a PAC.
//
LastElement = *PreAuthData; if (LastElement != NULL) { while (LastElement->next != NULL) { LastElement = LastElement->next; } LastElement->next = ListElement; } else { *PreAuthData = ListElement; }
ListElement->next = NULL; ListElement = NULL;
Cleanup: if (ListElement != NULL) { KerbFreePreAuthData( ListElement ); }
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbPingWlBalloon
//
// Synopsis: Opens and pulses winlogon event, so they can pop up the balloon,
// informing user of bad pwd, or expired pwd
//
// Effects:
//
// Arguments: LogonSession - Logon session for which to acquire a ticket
//
// Requires:
//
// Returns: STATUS_SUCCESS on success
//
//
//
//
#define KERBEROS_NOTIFICATION_EVENT_NAME L"WlballoonKerberosNotificationEventName"
BOOLEAN KerbPingWlBalloon( PLUID Luid ) { HANDLE EventHandle; WCHAR Event[512];
wsprintfW( Event, L"Global\\%08x%08x_%s", Luid->HighPart, Luid->LowPart, KERBEROS_NOTIFICATION_EVENT_NAME );
EventHandle = OpenEventW(EVENT_MODIFY_STATE, FALSE, Event);
if (EventHandle == NULL) { D_DebugLog((DEB_TRACE, "Opening winlogon event %S failed %x\n", Event, GetLastError())); return FALSE; }
if (!SetEvent(EventHandle)) { D_DebugLog((DEB_ERROR, "SETTING winlogon event %S failed %x\n", Event, GetLastError())); }
if (EventHandle != NULL) { CloseHandle(EventHandle); }
return TRUE;
}
//+-------------------------------------------------------------------------
//
// Function: KerbGetAuthenticationTicket
//
// Synopsis: Gets an AS ticket for the specified logon session
//
// Effects:
//
// Arguments: LogonSession - Logon session for which to acquire a ticket
//
// Requires:
//
// Returns: STATUS_SUCCESS on success
//
//
// Notes: The retry logic here is complex. The idea is that we
// shouldn't retry more than once for any particular failure.
//
//
//--------------------------------------------------------------------------
#define KERB_RETRY_ETYPE_FAILURE 0x0001
#define KERB_RETRY_TIME_FAILURE 0x0002
#define KERB_RETRY_PASSWORD_FAILURE 0x0004
#define KERB_RETRY_WRONG_PREAUTH 0x0008
#define KERB_RETRY_USE_TCP 0x0010
#define KERB_RETRY_CALL_PDC 0x0020
#define KERB_RETRY_SALT_FAILURE 0x0040
#define KERB_RETRY_WITH_ACCOUNT 0x0080
#define KERB_RETRY_BAD_REALM 0x0100
#define KERB_RETRY_PKINIT 0x0200
#define KERB_RETRY_BAD_KDC 0x0400
NTSTATUS KerbGetAuthenticationTicket( IN OUT PKERB_LOGON_SESSION LogonSession, IN OPTIONAL PKERB_CREDENTIAL Credential, IN OPTIONAL PKERB_CREDMAN_CRED CredManCredentials, IN BOOLEAN SupplyPreauth, IN PKERB_INTERNAL_NAME ServiceName, IN PUNICODE_STRING ServerRealm, IN PKERB_INTERNAL_NAME ClientFullName, IN ULONG TicketFlags, IN ULONG CacheFlags, OUT OPTIONAL PKERB_TICKET_CACHE_ENTRY * TicketCacheEntry, OUT OPTIONAL PKERB_ENCRYPTION_KEY CredentialKey, OUT PUNICODE_STRING CorrectRealm ) { NTSTATUS Status = STATUS_SUCCESS; NTSTATUS OldStatus = STATUS_SUCCESS; NTSTATUS ExtendedStatus = STATUS_SUCCESS; KERBERR KerbErr = KDC_ERR_NONE; KERBERR LastKerbErr = KDC_ERR_NONE; KERB_KDC_REQUEST TicketRequest = {0}; PKERB_KDC_REQUEST_BODY RequestBody; PULONG CryptVector = NULL; BOOLEAN LogonSessionsLocked = FALSE; PKERB_KDC_REPLY KdcReply = NULL; PKERB_ENCRYPTED_KDC_REPLY ReplyBody = NULL; PKERB_TICKET_CACHE_ENTRY CacheEntry = NULL; TimeStamp TempTime; KERB_MESSAGE_BUFFER RequestMessage = {0, NULL}; KERB_MESSAGE_BUFFER ReplyMessage = {0, NULL}; UNICODE_STRING ClientName = NULL_UNICODE_STRING; PKERB_ENCRYPTION_KEY ClientKey; ULONG RetryFlags = 0; BOOLEAN CalledPDC = FALSE; PKERB_PRIMARY_CREDENTIAL PrimaryCredentials = NULL; BOOLEAN PreAuthDone = FALSE; PKERB_ERROR ErrorMessage = NULL; PKERB_PA_DATA_LIST * OldPreAuthData = NULL; #ifndef WIN32_CHICAGO
LARGE_INTEGER TimeSkew = {0,0}; #else // WIN32_CHICAGO
TimeStamp TimeSkew = 0; #endif // WIN32_CHICAGO
LONG NameType = KRB_NT_MS_PRINCIPAL; BOOLEAN UsedAlternateName = FALSE; PKERB_MIT_REALM MitRealm = NULL; PKERB_INTERNAL_NAME LocalServiceName = NULL; PKERB_EXT_ERROR pExtendedError = NULL; BOOLEAN UsedCredentials = FALSE; KERB_ENCRYPTION_KEY EncryptionKey = {0}; PKERB_HOST_ADDRESSES HostAddresses = NULL; PKERB_CRYPT_LIST CryptList = NULL; UNICODE_STRING ClientRealm = {0}; ULONG KdcOptions; ULONG KdcFlagOptions, AdditionalFlags = 0; BOOLEAN DoLogonRetry = FALSE; BOOLEAN DoTcpRetry = FALSE; BOOLEAN DoPreauthRetry = FALSE; BOOLEAN DoAccountLookup = FALSE; BOOLEAN IncludeIpAddresses = FALSE; BOOLEAN IncludeNetbiosAddresses = TRUE; LUID SystemLuid = SYSTEM_LUID;
D_DebugLog((DEB_TRACE, "KerbGetAuthenticationTicket getting authentication ticket for client ")); D_KerbPrintKdcName((DEB_TRACE, ClientFullName)); D_DebugLog((DEB_TRACE, "KerbGetAuthenticationTicket for service in realm %wZ, ", ServerRealm)); D_KerbPrintKdcName((DEB_TRACE, ServiceName));
//
// Initialize variables to NULL
//
RequestBody = &TicketRequest.request_body; RtlInitUnicodeString( CorrectRealm, NULL );
if ((ClientFullName->NameCount == 0) || (ClientFullName->Names[0].Length == 0)) { D_DebugLog((DEB_WARN, "KerbGetServiceTicket not requesting ticket for blank server name\n")); Status = STATUS_NO_SUCH_USER; goto Cleanup; }
//
// Build the request
//
KdcOptions = KERB_DEFAULT_TICKET_FLAGS;
//
// The domain name may be null - if so, use our domain for now.
//
//
// Check to see if the domain is an MIT realm
//
if (KerbLookupMitRealm( ServerRealm, &MitRealm, &UsedAlternateName ) || ((TicketFlags & KERB_GET_AUTH_TICKET_NO_CANONICALIZE) != 0)) { DsysAssert(((TicketFlags & KERB_GET_AUTH_TICKET_NO_CANONICALIZE) != 0) || (MitRealm != NULL));
//
// So the user is getting a ticket from an MIT realm. This means
// we don't ask for name canonicalization.
//
KdcOptions &= ~KERB_KDC_OPTIONS_name_canonicalize; }
KdcFlagOptions = KerbConvertUlongToFlagUlong(KdcOptions); RequestBody->kdc_options.value = (PUCHAR) &KdcFlagOptions ; RequestBody->kdc_options.length = sizeof(ULONG) * 8;
RequestBody->nonce = KerbAllocateNonce();
TempTime = KerbGlobalWillNeverTime;
KerbConvertLargeIntToGeneralizedTime( &RequestBody->endtime, NULL, &TempTime );
KerbConvertLargeIntToGeneralizedTime( &RequestBody->KERB_KDC_REQUEST_BODY_renew_until, NULL, &TempTime );
RequestBody->bit_mask |= KERB_KDC_REQUEST_BODY_renew_until_present;
//
// Lock down the logon session while we build the request.
//
DsysAssert( !LogonSessionsLocked ); KerbReadLockLogonSessions(LogonSession); LogonSessionsLocked = TRUE;
//
// If credentials were supplied, use the primary creds from there
//
if (ARGUMENT_PRESENT(Credential) && (Credential->SuppliedCredentials != NULL)) { UsedCredentials = TRUE;
//
// Special case hack for callers using machinename$ / domain, w/ no password.
// always use the current password of the local system luid.
//
if (( Credential->CredentialFlags & KERB_CRED_LS_DEFAULT ) && ( RtlEqualLuid(&LogonSession->LogonId, &SystemLuid) )) { Status = KerbReplacePasswords(Credential->SuppliedCredentials, &LogonSession->PrimaryCredentials); if (!NT_SUCCESS( Status )) { goto Cleanup; } } PrimaryCredentials = Credential->SuppliedCredentials; D_DebugLog((DEB_TRACE_CRED, "KerbGetAuthenticationTicket using supplied credentials %wZ\\%wZ to ", &PrimaryCredentials->DomainName, &PrimaryCredentials->UserName )); D_KerbPrintKdcName((DEB_TRACE_CRED, ServiceName));
} else if (ARGUMENT_PRESENT(CredManCredentials)) { UsedCredentials = TRUE;
PrimaryCredentials = CredManCredentials->SuppliedCredentials; D_DebugLog((DEB_TRACE_CRED, "KerbGetAuthenticationTicket using cred manager credentials %wZ\\%wZ to \n", &PrimaryCredentials->DomainName, &PrimaryCredentials->UserName )); D_KerbPrintKdcName((DEB_TRACE_CRED, ServiceName)); } else { PrimaryCredentials = &LogonSession->PrimaryCredentials; D_DebugLog((DEB_TRACE_CRED, "KerbGetAuthenticationTicket using default credentials %wZ\\%wZ to ", &PrimaryCredentials->DomainName, &PrimaryCredentials->UserName ));
D_KerbPrintKdcName((DEB_TRACE_CRED, ServiceName)); }
if ((PrimaryCredentials->Passwords == NULL) && (PrimaryCredentials->PublicKeyCreds == NULL)) { D_DebugLog((DEB_ERROR,"Can't get AS ticket with no password. %ws, line %d\n", THIS_FILE, __LINE__)); Status = SEC_E_NO_CREDENTIALS; goto Cleanup; }
//
// Copy all the names into the request message
//
//
// Build the client name from the client domain & user name.
//
KerbErr = KerbConvertKdcNameToPrincipalName( &RequestBody->KERB_KDC_REQUEST_BODY_client_name, ClientFullName );
if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
RequestBody->bit_mask |= KERB_KDC_REQUEST_BODY_client_name_present;
//
// If we are talking to an NT Domain, or are using an MIT compatible
// name type, convert the service name as is is
//
KerbErr = KerbConvertKdcNameToPrincipalName( &RequestBody->KERB_KDC_REQUEST_BODY_server_name, ServiceName );
if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
RequestBody->bit_mask |= KERB_KDC_REQUEST_BODY_server_name_present;
//
// Build the list of host addresses. We don't do this for all
// MIT realms
//
if ( KerbGlobalUseClientIpAddresses ) {
IncludeIpAddresses = TRUE; }
//
// MIT realms never care to see the NetBIOS addresses
//
if ( MitRealm != NULL ) {
IncludeNetbiosAddresses = FALSE;
if (( MitRealm->Flags & KERB_MIT_REALM_SEND_ADDRESS ) != 0 ) {
IncludeIpAddresses = TRUE; } }
//
// We always put the NetBIOS name of the client into the request,
// as this is how the workstations restriction is enforced.
// It is understood that the mechanism is bogus, as the client can spoof
// the netbios address, but that's okay -- we're only doing this for
// feature preservation, this is no worse than W2K.
//
// The IP address is only put in based on a registry setting or for MIT
// realms that explicity request it, as having them in the request would
// break us when going through NATs.
//
Status = KerbBuildHostAddresses( IncludeIpAddresses, IncludeNetbiosAddresses, &HostAddresses );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if ( HostAddresses ) { RequestBody->addresses = HostAddresses; RequestBody->bit_mask |= addresses_present; }
TicketRequest.version = KERBEROS_VERSION; TicketRequest.message_type = KRB_AS_REQ;
PreauthRestart: //
// Lock down the logon session while we build the request.
// This is done so that when we try the second time around, the logon
// session list is locked
//
if (!LogonSessionsLocked) { KerbReadLockLogonSessions(LogonSession); LogonSessionsLocked = TRUE; }
DoPreauthRetry = FALSE; if (RequestMessage.Buffer != NULL) { MIDL_user_free(RequestMessage.Buffer); RequestMessage.Buffer = NULL; }
// Free this in case we are doing a retry
KerbFreeRealm( &RequestBody->realm );
KerbErr = KerbConvertUnicodeStringToRealm( &RequestBody->realm, ServerRealm ); if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Stick the PA data in the request, if requested. This is the norm,
// unless we're doing S4U location.
//
if ( SupplyPreauth ) { DsysAssert(TicketRequest.KERB_KDC_REQUEST_preauth_data == NULL); Status = KerbGetPreAuthDataForRealm( PrimaryCredentials, ServerRealm, ServiceName, (OldPreAuthData != NULL) ? *OldPreAuthData : NULL, &TimeSkew, (RetryFlags & KERB_RETRY_PASSWORD_FAILURE) != 0, RequestBody->nonce, LastKerbErr, &TicketRequest.KERB_KDC_REQUEST_preauth_data, &EncryptionKey, &CryptList, &PreAuthDone ); if (!NT_SUCCESS(Status)) { //
// If we couldn't build the preauth, try again
//
if (Status == STATUS_WRONG_PASSWORD) { if ((RetryFlags & KERB_RETRY_PASSWORD_FAILURE) == 0) { RetryFlags |= KERB_RETRY_PASSWORD_FAILURE; goto PreauthRestart; } else if ((RetryFlags & KERB_RETRY_SALT_FAILURE) == 0) { RetryFlags |= KERB_RETRY_SALT_FAILURE; RetryFlags &= ~KERB_RETRY_PASSWORD_FAILURE; goto PreauthRestart; } } else if (Status == STATUS_UNSUPPORTED_PREAUTH) { // Log this, every time, as this is impossible to triage otherwise
KerbReportKerbError( ServiceName, ServerRealm, NULL, Credential, KLIN(FILENO,__LINE__), NULL, KDC_ERR_PADATA_TYPE_NOSUPP, NULL, TRUE ); } DebugLog((DEB_ERROR,"GetAuthenticationTicket: Failed to build pre-auth data: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; } }
//
// Build crypt list
//
KerbFreeCryptList( RequestBody->encryption_type );
RequestBody->encryption_type = NULL;
if (PrimaryCredentials->Passwords != NULL) {
if (!KERB_SUCCESS(KerbConvertKeysToCryptList( &RequestBody->encryption_type, PrimaryCredentials->Passwords ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
} else {
ULONG CryptTypes[KERB_MAX_CRYPTO_SYSTEMS]; ULONG CryptTypeCount = KERB_MAX_CRYPTO_SYSTEMS;
//
// Include all our crypt types as supported
//
Status = CDBuildIntegrityVect( &CryptTypeCount, CryptTypes );
DsysAssert(NT_SUCCESS(Status));
if (!KERB_SUCCESS(KerbConvertArrayToCryptList( &RequestBody->encryption_type, CryptTypes, CryptTypeCount, FALSE))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } }
//
// Add in preauth generated encryption types
//
if (CryptList != NULL) { PKERB_CRYPT_LIST Next; Next = CryptList; while (Next != NULL) { if (Next->next == NULL) { Next->next = RequestBody->encryption_type; RequestBody->encryption_type = CryptList; CryptList = NULL; break; } Next = Next->next;
} }
//
// If the we need to either request the presence or absence of a PAC, do
// it here
//
if (MitRealm == NULL) { Status = KerbAddPacRequestPreAuth( &TicketRequest.KERB_KDC_REQUEST_preauth_data, TicketFlags );
if (!NT_SUCCESS(Status)) { goto Cleanup; } }
if (TicketRequest.KERB_KDC_REQUEST_preauth_data != NULL) { TicketRequest.bit_mask |= KERB_KDC_REQUEST_preauth_data_present; }
//
// Pack the request
//
KerbErr = KerbPackAsRequest( &TicketRequest, &RequestMessage.BufferSize, &RequestMessage.Buffer ); if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
RetryLogon:
DoLogonRetry = FALSE;
//
// Unlock the logon sessions and credential so we don't cause problems
// waiting for a network request to complete.
//
if (LogonSessionsLocked) { KerbUnlockLogonSessions(LogonSession); LogonSessionsLocked = FALSE; }
D_DebugLog((DEB_TRACE_KDC,"KerbGetAuthenticationTicket: Calling KDC\n"));
RetryWithTcp:
if (ReplyMessage.Buffer != NULL) { MIDL_user_free(ReplyMessage.Buffer); ReplyMessage.Buffer = NULL; }
DoTcpRetry = FALSE;
Status = KerbMakeKdcCall( ServerRealm, (DoAccountLookup && ClientFullName->NameType == KRB_NT_PRINCIPAL) ? &ClientFullName->Names[0] : NULL, // send the client name, if available
(RetryFlags & KERB_RETRY_CALL_PDC) != 0, (RetryFlags & KERB_RETRY_USE_TCP) != 0, &RequestMessage, &ReplyMessage, AdditionalFlags, &CalledPDC );
D_DebugLog((DEB_TRACE_KDC,"KerbGetAuthenticationTicket: Returned from KDC status 0x%x\n", Status ));
if (!NT_SUCCESS(Status)) { #if DBG
if (Status != STATUS_NO_LOGON_SERVERS) { DebugLog((DEB_ERROR,"Failed KerbMakeKdcCall for AS request: 0x%x. %ws, line %d\n", Status, THIS_FILE, __LINE__)); } #endif
//
// If this is the second time around (on the PDC) and this fails,
// use the original error
//
if (OldStatus != STATUS_SUCCESS) { Status = OldStatus; } goto Cleanup; }
//
// Free the preauth data now, as it is not necessary any more
//
KerbFreePreAuthData( TicketRequest.KERB_KDC_REQUEST_preauth_data ); TicketRequest.KERB_KDC_REQUEST_preauth_data = NULL;
KerbErr = KerbUnpackAsReply( ReplyMessage.Buffer, ReplyMessage.BufferSize, &KdcReply ); if (!KERB_SUCCESS(KerbErr)) { D_DebugLog((DEB_WARN,"Failed to unpack KDC reply as AS: 0x%x\n", KerbErr ));
//
// Try to unpack it as kerb_error
//
if (ErrorMessage != NULL) { KerbFreeKerbError(ErrorMessage); ErrorMessage = NULL; }
KerbErr = KerbUnpackKerbError( ReplyMessage.Buffer, ReplyMessage.BufferSize, &ErrorMessage ); if (KERB_SUCCESS(KerbErr)) { //
// Let's see if there's any extended error here
//
if (ErrorMessage->bit_mask & error_data_present) { if (NULL != pExtendedError) // might be a re-auth failure. Don't leak!
{ KerbFree(pExtendedError); pExtendedError = NULL; }
KerbErr = KerbUnpackErrorData( ErrorMessage, &pExtendedError );
if (KERB_SUCCESS(KerbErr) && (EXT_CLIENT_INFO_PRESENT(pExtendedError))) { ExtendedStatus = pExtendedError->status; } }
KerbErr = (KERBERR) ErrorMessage->error_code; LastKerbErr = KerbErr; DebugLog((DEB_ERROR,"KerbCallKdc failed: error 0x%x. %ws, line %d\n",KerbErr, THIS_FILE, __LINE__));
Status = KerbMapKerbError(KerbErr);
KerbReportKerbError( ServiceName, ServerRealm, LogonSession, Credential, KLIN(FILENO,__LINE__), ErrorMessage, KerbErr, pExtendedError, FALSE );
if (KerbErr == KRB_ERR_RESPONSE_TOO_BIG) { if ((RetryFlags & KERB_RETRY_USE_TCP) != 0) { D_DebugLog((DEB_ERROR,"Got response too big twice. %ws, %d\n", THIS_FILE, __LINE__ )); Status = STATUS_LOGON_FAILURE; goto Cleanup; } RetryFlags |= KERB_RETRY_USE_TCP; DoTcpRetry = TRUE; }
//
// If we didn't try the PDC, try it now.
//
else if (KerbErr == KDC_ERR_KEY_EXPIRED) { if (CalledPDC || ((RetryFlags & KERB_RETRY_CALL_PDC) != 0) || (!KerbGlobalRetryPdc)) { // If we've already tried the PDC, then we should
// have some extended info w.r.t. what's up w/
// this password.
if (EXT_CLIENT_INFO_PRESENT(pExtendedError)) { Status = ExtendedStatus; } else { Status = KerbMapKerbError(KerbErr); } goto Cleanup; } RetryFlags |= KERB_RETRY_CALL_PDC; DoLogonRetry = TRUE; }
//
// Check for time skew. If so, calculate the skew and retry
//
else if (KerbErr == KRB_AP_ERR_SKEW) { TimeStamp CurrentTime; TimeStamp KdcTime;
if ((RetryFlags & KERB_RETRY_TIME_FAILURE) != 0) { Status = KerbMapKerbError(KerbErr); goto Cleanup; }
RetryFlags |= KERB_RETRY_TIME_FAILURE; DoPreauthRetry = TRUE;
GetSystemTimeAsFileTime((PFILETIME) &CurrentTime);
KerbConvertGeneralizedTimeToLargeInt( &KdcTime, &ErrorMessage->server_time, ErrorMessage->server_usec );
KerbSetTime(&TimeSkew, KerbGetTime(KdcTime) - KerbGetTime(CurrentTime)); KerbUpdateSkewTime(TRUE); }
//
// Check for pre-authenication required
//
else if ((KerbErr == KDC_ERR_PREAUTH_FAILED) || (KerbErr == KRB_AP_ERR_BAD_INTEGRITY)) { //
// This is a bad password failure.
//
if ((RetryFlags & KERB_RETRY_PASSWORD_FAILURE) == 0) { RetryFlags |= KERB_RETRY_PASSWORD_FAILURE; } else if ((RetryFlags & KERB_RETRY_SALT_FAILURE) != 0) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } else { RetryFlags |= KERB_RETRY_SALT_FAILURE; RetryFlags &= ~KERB_RETRY_PASSWORD_FAILURE; }
//
// In this case, there may be data in the error data
//
KerbFreeData( PKERB_PREAUTH_DATA_LIST_PDU, OldPreAuthData );
OldPreAuthData = NULL;
(VOID) KerbUnpackErrorPreauth( ErrorMessage, &OldPreAuthData );
DoPreauthRetry = TRUE;
} else if ((KerbErr == KDC_ERR_ETYPE_NOTSUPP) || (KerbErr == KDC_ERR_PREAUTH_REQUIRED)) { NTSTATUS TempStatus;
if (KerbErr == KDC_ERR_ETYPE_NOTSUPP) { if ((RetryFlags & KERB_RETRY_ETYPE_FAILURE) != 0) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } RetryFlags |= KERB_RETRY_ETYPE_FAILURE; } else { if ((RetryFlags & KERB_RETRY_WRONG_PREAUTH) != 0) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } RetryFlags |= KERB_RETRY_WRONG_PREAUTH;
}
//
// In this case, there should be etype info in the error data
//
KerbFreeData( PKERB_PREAUTH_DATA_LIST_PDU, OldPreAuthData );
OldPreAuthData = NULL;
TempStatus = KerbUnpackErrorPreauth( ErrorMessage, &OldPreAuthData ); if (!NT_SUCCESS(TempStatus)) { D_DebugLog((DEB_ERROR, "KerbGetAuthenticationTicket failed to unpack error for preauth : 0x%x. %ws, line %d\n", TempStatus, THIS_FILE, __LINE__)); D_DebugLog((DEB_ERROR, "KerbGetAuthenticationTicket client was ")); D_KerbPrintKdcName((DEB_ERROR, ClientFullName)); D_DebugLog((DEB_ERROR, "KerbGetAuthenticationTicket for service in realm %wZ : ", ServerRealm)); D_KerbPrintKdcName((DEB_ERROR, ServiceName)); DsysAssert(!NT_SUCCESS(Status)); goto Cleanup; }
if ( SupplyPreauth ) { DoPreauthRetry = TRUE; } } //
// There's something wrong w/ the client's account. In all cases
// this error should be accompanied by an extended error packet.
// and no retry should be attempted (see restrict.cxx)
//
else if ((KerbErr == KDC_ERR_CLIENT_REVOKED || KerbErr == KDC_ERR_POLICY ) && EXT_CLIENT_INFO_PRESENT(pExtendedError)) { Status = pExtendedError->status; goto Cleanup; } //
// For PKINIT, the client not trusted error indicates that SCLogon failed
// as the client certificate was bogus. Log an error here.
// NOTE: The extended status is a wincrypt error, so just return the
// normal status (
//
else if (KerbErr == KDC_ERR_CLIENT_NOT_TRUSTED) {
ULONG PolicyStatus = ERROR_NOT_SUPPORTED; // w2k DCs won't likely have this data.
//
// WE may have trust status on the client certificate
// use this to create an event log. NOte: this is only
// going to happen if logon was against Whistler DC
//
// W2K Dcs returning errors will get mapped to generic
// error.
//
if (EXT_CLIENT_INFO_PRESENT(pExtendedError)) { PolicyStatus = pExtendedError->status; }
KerbReportPkinitError(PolicyStatus, NULL); Status = KerbMapCertChainError(PolicyStatus, TRUE); DebugLog((DEB_ERROR, "Client certificate didn't validate on KDC - %x\n", PolicyStatus));
} else if ((KerbErr == KDC_ERR_PADATA_TYPE_NOSUPP) && (EXT_CLIENT_INFO_PRESENT(pExtendedError))) { if ((RetryFlags & KERB_RETRY_PKINIT) != 0) { Status = KerbMapKerbError(KerbErr); goto Cleanup; }
//
// no KDC certificate error in edata, do a retry against
// another DC
//
if (pExtendedError->status == STATUS_PKINIT_FAILURE) { RetryFlags |= KERB_RETRY_PKINIT; AdditionalFlags = DS_FORCE_REDISCOVERY; DoLogonRetry = TRUE; } } //
// Check if the server didn't know the client principal
//
else if (KerbErr == KDC_ERR_C_PRINCIPAL_UNKNOWN ) {
if ((RetryFlags & KERB_RETRY_WITH_ACCOUNT) != 0) { Status = KerbMapKerbError(KerbErr); goto Cleanup; }
RetryFlags |= KERB_RETRY_WITH_ACCOUNT;
DoAccountLookup = TRUE;
if ((ErrorMessage->bit_mask & client_realm_present) != 0) { UNICODE_STRING TempRealm;
if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString( &TempRealm, &ErrorMessage->client_realm ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
if (!RtlEqualUnicodeString( ServerRealm, &TempRealm, TRUE // case insensitive
)) { D_DebugLog((DEB_TRACE,"Received UPN referral to another domain: %wZ\n", &TempRealm )); //
// Return the correct realm so the caller will retry
//
*CorrectRealm = TempRealm; TempRealm.Buffer = NULL; DoAccountLookup = FALSE; }
KerbFreeString( &TempRealm ); }
if (DoAccountLookup) { DoLogonRetry = TRUE; } } //
// Something's wrong w/ the KDC... Try another one, but 1 time only
//
else if (KerbErr == KDC_ERR_SVC_UNAVAILABLE) {
if ((RetryFlags & KERB_RETRY_BAD_KDC) != 0) { Status = KerbMapKerbError(KerbErr); goto Cleanup; }
D_DebugLog((DEB_ERROR, "Retrying new KDC\n"));
AdditionalFlags = DS_FORCE_REDISCOVERY; DoLogonRetry = TRUE; RetryFlags |= KERB_RETRY_BAD_KDC; } else if (KerbErr == KDC_ERR_WRONG_REALM) { if ((RetryFlags & KERB_RETRY_BAD_REALM) != 0) { Status = KerbMapKerbError(KerbErr); goto Cleanup; }
RetryFlags |= KERB_RETRY_BAD_REALM;
AdditionalFlags = DS_FORCE_REDISCOVERY; // possibly bad cached DC
DoLogonRetry = TRUE;
if ((ErrorMessage->bit_mask & client_realm_present) != 0) { UNICODE_STRING TempRealm; if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString( &TempRealm, &ErrorMessage->client_realm ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
if (!RtlEqualUnicodeString( ServerRealm, &TempRealm, TRUE // case insensitive
)) { D_DebugLog((DEB_TRACE,"Received UPN referral to another domain: %wZ\n", &TempRealm )); //
// Return the correct realm so the caller will retry
//
*CorrectRealm = TempRealm; TempRealm.Buffer = NULL; DoLogonRetry = FALSE; // this is a referral, not a bad cache entry
}
KerbFreeString( &TempRealm );
} }
//
// Retry if need be
//
if (DoPreauthRetry) { goto PreauthRestart; } else if (DoLogonRetry) { goto RetryLogon; } else if (DoTcpRetry) { goto RetryWithTcp; } } else { DebugLog((DEB_ERROR, "Failed to unpack KDC reply as AS or Error: 0x%x\n", KerbErr)); Status = STATUS_INTERNAL_ERROR; }
goto Cleanup; }
//
// Update the skew counter if necessary
//
if ((RetryFlags & KERB_RETRY_TIME_FAILURE) == 0) { KerbUpdateSkewTime(FALSE);
}
//
// Now unpack the reply body:
//
DsysAssert( !LogonSessionsLocked ); KerbWriteLockLogonSessions(LogonSession); LogonSessionsLocked = TRUE;
//
// if there was any pre auth data, process it now
//
if ((KdcReply->bit_mask & KERB_KDC_REPLY_preauth_data_present) != 0) { Status = KerbGetPreAuthDataForRealm( PrimaryCredentials, ServerRealm, ServiceName, (PKERB_PA_DATA_LIST) KdcReply->KERB_KDC_REPLY_preauth_data, &TimeSkew, (RetryFlags & KERB_RETRY_PASSWORD_FAILURE) != 0, RequestBody->nonce, KDC_ERR_NONE, &TicketRequest.KERB_KDC_REQUEST_preauth_data, &EncryptionKey, &CryptList, &PreAuthDone ); if (!NT_SUCCESS(Status)) { if (Status == STATUS_UNSUPPORTED_PREAUTH ) { // Log this, every time, as this is impossible to triage otherwise
KerbReportKerbError( ServiceName, ServerRealm, NULL, Credential, KLIN(FILENO,__LINE__), NULL, KDC_ERR_PADATA_TYPE_NOSUPP, NULL, TRUE ); }
D_DebugLog((DEB_ERROR,"Failed to post process pre-auth data: 0x%x. %ws, %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
KerbFreeCryptList(CryptList); CryptList = NULL; }
//
// If there is any preauth in the response, handle it
//
if (EncryptionKey.keyvalue.value == NULL) { ClientKey = KerbGetKeyFromList( PrimaryCredentials->Passwords, KdcReply->encrypted_part.encryption_type );
DsysAssert(ClientKey != NULL);
if (ClientKey == NULL) { D_DebugLog((DEB_ERROR,"Kdc returned reply with encryption type we don't support: %d. %ws, line %d\n", KdcReply->encrypted_part.encryption_type, THIS_FILE, __LINE__)); Status = STATUS_LOGON_FAILURE; goto Cleanup; } } else { //
// Use the encryption key we have from the pre-auth data
//
ClientKey = &EncryptionKey; }
KerbErr = KerbUnpackKdcReplyBody( &KdcReply->encrypted_part, ClientKey, KERB_ENCRYPTED_AS_REPLY_PDU, &ReplyBody );
//
// if we couldn't decrypt it and we have an old password around,
// give it a try too before heading to the PDC.
//
if ((KerbErr == KRB_AP_ERR_MODIFIED) && (PrimaryCredentials->OldPasswords != NULL) && (EncryptionKey.keyvalue.value == NULL)) { ClientKey = KerbGetKeyFromList( PrimaryCredentials->OldPasswords, KdcReply->encrypted_part.encryption_type ); if (ClientKey != NULL) { KerbErr = KerbUnpackKdcReplyBody( &KdcReply->encrypted_part, ClientKey, KERB_ENCRYPTED_AS_REPLY_PDU, &ReplyBody ); } }
if (!KERB_SUCCESS(KerbErr)) { D_DebugLog((DEB_ERROR,"Failed to decrypt KDC reply body: 0x%x. %ws, line %d\n",KerbErr, THIS_FILE, __LINE__));
//
// If we didn't try the PDC, try it now.
//
if (((RetryFlags & KERB_RETRY_CALL_PDC) == 0) && (KerbErr == KRB_AP_ERR_MODIFIED) && (KerbGlobalRetryPdc)) { RetryFlags |= KERB_RETRY_CALL_PDC;
KerbFreeAsReply(KdcReply); KdcReply = NULL;
ReplyMessage.Buffer = NULL;
D_DebugLog((DEB_TRACE_CRED,"KerbGetAuthenticationTicket: Password wrong, trying PDC\n")); goto RetryLogon; }
//
// <HACK> - If we're doing a S4U location (e.g. SupplyPreauth == FALSE )
// for an account that doesn't require preauth, and we've gotten back
// a response from the KDC that's *not* an error, return a wrong
// password error to KerbGetS4UClientRealm. </HACK>
//
Status = (SupplyPreauth ? STATUS_LOGON_FAILURE : STATUS_WRONG_PASSWORD ); goto Cleanup; }
//
// Verify the nonce is correct:
//
if (RequestBody->nonce != ReplyBody->nonce) { D_DebugLog((DEB_ERROR,"AS Nonces don't match: 0x%x vs 0x%x. %ws, line %d\n",RequestBody->nonce, ReplyBody->nonce, THIS_FILE, __LINE__)); Status = STATUS_LOGON_FAILURE; goto Cleanup; }
//
// Update the logon session with the information if we didn't use
// supplied credentials.
//
{ UNICODE_STRING TempName; UNICODE_STRING TempRealm;
//
// Get the new client realm & user name - if they are different
// we will update the logon session. This is in case the user
// logged on with a nickname (e.g. email name)
//
if (!KERB_SUCCESS(KerbConvertPrincipalNameToString( &ClientName, (PULONG) &NameType, &KdcReply->client_name ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// The KDC may hand back names with domains, so split the name
// now.
//
Status = KerbSplitFullServiceName( &ClientName, &TempRealm, &TempName ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString( &ClientRealm, &KdcReply->client_realm ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
if (!RtlEqualUnicodeString( &PrimaryCredentials->UserName, &TempName, TRUE // case insensitive
)) {
D_DebugLog((DEB_TRACE_LSESS,"UserName different in logon session & AS ticket: %wZ vs %wZ\n", &PrimaryCredentials->UserName, &TempName ));
KerbFreeString( &PrimaryCredentials->UserName );
Status = KerbDuplicateString( &PrimaryCredentials->UserName, &TempName );
if (!NT_SUCCESS(Status)) { goto Cleanup; } }
if (!RtlEqualUnicodeString( &PrimaryCredentials->DomainName, &ClientRealm, FALSE // case sensitive, specially for ext chars.
)) {
D_DebugLog((DEB_TRACE_LSESS, "Domain name is different in logon session & as ticket: %wZ vs %wZ\n", &PrimaryCredentials->DomainName, &ClientRealm ));
KerbFreeString( &PrimaryCredentials->DomainName ); PrimaryCredentials->DomainName = ClientRealm; ClientRealm.Buffer = NULL; } }
//
// Cache the ticket
//
DsysAssert(LogonSessionsLocked);
//
// Free the cleartext password as we now have a ticket acquired with them.
//
if (PrimaryCredentials->ClearPassword.Buffer != NULL) { RtlZeroMemory( PrimaryCredentials->ClearPassword.Buffer, PrimaryCredentials->ClearPassword.Length ); KerbFreeString(&PrimaryCredentials->ClearPassword); }
Status = KerbCreateTicketCacheEntry( KdcReply, ReplyBody, ServiceName, ServerRealm, CacheFlags, &PrimaryCredentials->AuthenticationTicketCache, &EncryptionKey, &CacheEntry );
if (!NT_SUCCESS(Status)) { if (Status == STATUS_TIME_DIFFERENCE_AT_DC && ((RetryFlags & KERB_RETRY_TIME_FAILURE) == 0)) { RetryFlags |= KERB_RETRY_TIME_FAILURE; KerbUpdateSkewTime(TRUE); D_DebugLog((DEB_WARN, "Retrying AS after trying to cache time invalid ticket\n")); goto PreauthRestart; }
goto Cleanup; }
if (ARGUMENT_PRESENT(TicketCacheEntry)) { *TicketCacheEntry = CacheEntry; CacheEntry = NULL; }
if (ARGUMENT_PRESENT(CredentialKey)) { *CredentialKey = EncryptionKey; EncryptionKey.keyvalue.value = NULL; }
Cleanup:
if (HostAddresses != NULL) { KerbFreeHostAddresses(HostAddresses); }
if (ErrorMessage != NULL) { KerbFreeKerbError(ErrorMessage); }
if (pExtendedError) { KerbFreeData(KERB_EXT_ERROR_PDU, pExtendedError); }
if (LogonSessionsLocked) { KerbUnlockLogonSessions(LogonSession); }
if (CryptVector != NULL) { KerbFree(CryptVector); }
if (OldPreAuthData != NULL) { KerbFreeData( PKERB_PREAUTH_DATA_LIST_PDU, OldPreAuthData ); }
KerbFreePreAuthData( TicketRequest.KERB_KDC_REQUEST_preauth_data );
KerbFreeCryptList( RequestBody->encryption_type );
KerbFreeCryptList( CryptList );
KerbFreeString( &ClientName );
KerbFreeKdcName( &LocalServiceName );
KerbFreeString( &ClientRealm );
KerbFreePrincipalName( &RequestBody->KERB_KDC_REQUEST_BODY_client_name );
KerbFreePrincipalName( &RequestBody->KERB_KDC_REQUEST_BODY_server_name );
KerbFreeRealm( &RequestBody->realm );
KerbFreeKdcReplyBody( ReplyBody );
KerbFreeAsReply( KdcReply );
if (CacheEntry != NULL) { KerbDereferenceTicketCacheEntry(CacheEntry); }
if (ReplyMessage.Buffer != NULL) { MIDL_user_free(ReplyMessage.Buffer); }
if (RequestMessage.Buffer != NULL) { MIDL_user_free(RequestMessage.Buffer); }
KerbFreeKey(&EncryptionKey); return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbGetClientNameAndRealm
//
// Synopsis: Proceses the name & realm supplied by a client to determine
// a name & realm to be sent to the KDC
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetClientNameAndRealm( IN OPTIONAL LUID *pLogonId, IN PKERB_PRIMARY_CREDENTIAL PrimaryCreds, IN BOOLEAN SuppliedCreds, IN OPTIONAL PUNICODE_STRING SuppRealm, IN OUT OPTIONAL BOOLEAN * MitRealmUsed, IN BOOLEAN UseWkstaRealm, OUT PKERB_INTERNAL_NAME * ClientName, OUT PUNICODE_STRING ClientRealm ) { NTSTATUS Status = STATUS_SUCCESS; ULONG ParseFlags = 0; ULONG ProcessFlags = 0; BOOLEAN UseCertificateDomain = FALSE; PUNICODE_STRING UserName = NULL;
UNICODE_STRING LocalMachineServiceName; LUID SystemLogonId = SYSTEM_LUID;
KERBEROS_MACHINE_ROLE Role = KerbGetGlobalRole();
LocalMachineServiceName.Buffer = NULL;
//
// if the computer name has changed, we lie about the machineservicename,
// since existing creds contain the wrong username
//
if (( KerbGlobalMachineNameChanged ) && ( !SuppliedCreds ) && ( pLogonId != NULL ) && ( RtlEqualLuid(pLogonId, &SystemLogonId))) { D_DebugLog((DEB_WARN,"Netbios machine name change caused credential over-ride.\n"));
KerbGlobalReadLock(); Status = KerbDuplicateString( &LocalMachineServiceName, &KerbGlobalMachineServiceName ); KerbGlobalReleaseLock();
if(!NT_SUCCESS( Status )) { goto Cleanup; }
UserName = &LocalMachineServiceName; } else { UserName = &PrimaryCreds->UserName; }
//
// Compute the parse flags
//
if (PrimaryCreds->DomainName.Length != 0) { ParseFlags |= KERB_CRACK_NAME_REALM_SUPPLIED; } else if (UseWkstaRealm) { //
// Two choices here - Realmless workstation's don't have a
// "realm" to use. If we've got public key credentials, then try
// using the name from the certifcate's subject.
//
if (( Role == KerbRoleRealmlessWksta ) && ( PrimaryCreds->PublicKeyCreds != NULL ) && ( PrimaryCreds->PublicKeyCreds->AlternateDomainName.Buffer != NULL )) { UseCertificateDomain = TRUE; } else { ParseFlags |= KERB_CRACK_NAME_USE_WKSTA_REALM; } }
Status = KerbProcessTargetNames( UserName, NULL, ParseFlags, &ProcessFlags, ClientName, ClientRealm, NULL );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// If we were not supplied a realm, use the one from the name
//
if (SuppRealm && (SuppRealm->Length != 0)) { KerbFreeString(ClientRealm); Status = KerbDuplicateString( ClientRealm, SuppRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } else if (PrimaryCreds->DomainName.Length != 0) { KerbFreeString(ClientRealm); Status = KerbDuplicateString( ClientRealm, &PrimaryCreds->DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } else if ( UseCertificateDomain ) { KerbFreeString( ClientRealm ); Status = KerbDuplicateString( ClientRealm, &PrimaryCreds->PublicKeyCreds->AlternateDomainName );
if (!NT_SUCCESS(Status)) { goto Cleanup; } }
Cleanup:
if (ARGUMENT_PRESENT(MitRealmUsed)) { *MitRealmUsed = ((ProcessFlags & KERB_MIT_REALM_USED) != 0);
//
// check for MIT realms
//
if (!*MitRealmUsed && (ParseFlags & KERB_CRACK_NAME_REALM_SUPPLIED)) { *MitRealmUsed = KerbLookupMitRealm(ClientRealm, NULL, NULL); } }
KerbFreeString( &LocalMachineServiceName );
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbGetTicketGrantingTicket
//
// Synopsis: Gets a TGT for a set of credentials
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetTicketGrantingTicket( IN OUT PKERB_LOGON_SESSION LogonSession, IN OPTIONAL PKERB_CREDENTIAL Credential, IN OPTIONAL PKERB_CREDMAN_CRED CredManCredentials, IN OPTIONAL PUNICODE_STRING SuppRealm, OUT OPTIONAL PKERB_TICKET_CACHE_ENTRY * TicketCacheEntry, OUT OPTIONAL PKERB_ENCRYPTION_KEY CredentialKey ) { NTSTATUS Status = STATUS_SUCCESS; KERBERR KerbErr; PKERB_INTERNAL_NAME KdcServiceKdcName = NULL; PKERB_INTERNAL_NAME ClientName = NULL; UNICODE_STRING UClientName = {0}; UNICODE_STRING ClientRealm = {0}; UNICODE_STRING CorrectRealm = {0}; ULONG RetryCount = KERB_CLIENT_REFERRAL_MAX; PKERB_PRIMARY_CREDENTIAL PrimaryCreds; PKERB_MIT_REALM MitRealm = NULL; ULONG RequestFlags = 0; BOOLEAN UsingSuppliedCreds = FALSE; BOOLEAN UseWkstaRealm = TRUE; BOOLEAN MitRealmLogon = FALSE; BOOLEAN UsedPrimaryLogonCreds = FALSE; BOOLEAN LogonSessionLocked = FALSE;
LUID LogonId;
//
// Get the proper realm name
//
if (ARGUMENT_PRESENT(Credential) && (Credential->SuppliedCredentials != NULL)) { PrimaryCreds = Credential->SuppliedCredentials; if ((Credential->CredentialFlags & KERB_CRED_NO_PAC) != 0) { RequestFlags |= KERB_GET_TICKET_NO_PAC; } LogonId = Credential->LogonId; UsingSuppliedCreds = TRUE; } else if (ARGUMENT_PRESENT(CredManCredentials)) { PrimaryCreds = CredManCredentials->SuppliedCredentials; LogonId = LogonSession->LogonId; } else { DsysAssert( !LogonSessionLocked ); KerbWriteLockLogonSessions(LogonSession); LogonSessionLocked = TRUE; PrimaryCreds = &LogonSession->PrimaryCredentials; LogonId = LogonSession->LogonId;
Status = KerbDuplicateString( &UClientName, &LogonSession->PrimaryCredentials.UserName );
if (!NT_SUCCESS(Status)) { DsysAssert( LogonSessionLocked ); KerbUnlockLogonSessions(LogonSession); LogonSessionLocked = FALSE; goto Cleanup; }
UsedPrimaryLogonCreds = TRUE; }
//
// Parse the name
//
Status = KerbGetClientNameAndRealm( &LogonId, PrimaryCreds, UsingSuppliedCreds, SuppRealm, &MitRealmLogon, UseWkstaRealm, &ClientName, &ClientRealm ); //
// If we're doing a MIT logon, add the MIT logon flag
//
if (MitRealmLogon && UsedPrimaryLogonCreds) { LogonSession->LogonSessionFlags |= KERB_LOGON_MIT_REALM; }
// only needed lock if we're tinkering w/ primary creds
// in case updates the credentials for that logon id.
if (LogonSessionLocked) { KerbUnlockLogonSessions(LogonSession); LogonSessionLocked = FALSE; }
if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to get client name & realm: 0x%x, %ws line %d\n", Status, THIS_FILE, __LINE__ )); goto Cleanup; }
GetTicketRestart:
D_DebugLog((DEB_TRACE, "KerbGetTicketGrantingTicket GetTicketRestart ClientRealm %wZ\n", &ClientRealm));
KerbErr = KerbBuildFullServiceKdcName( &ClientRealm, &KerbGlobalKdcServiceName, KRB_NT_SRV_INST, &KdcServiceKdcName ); if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Status = KerbGetAuthenticationTicket( LogonSession, Credential, CredManCredentials, TRUE, KdcServiceKdcName, &ClientRealm, ClientName, RequestFlags, KERB_TICKET_CACHE_PRIMARY_TGT, TicketCacheEntry, CredentialKey, &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;
//
// Might be an MIT realm, in which case we'll need to adjust
// the client name. This will also populate the realm list
// with appropriate entries, so the KerbGetKdcBinding will not
// hit DNS again.
//
if (KerbLookupMitRealmWithSrvLookup( &ClientRealm, &MitRealm, FALSE, FALSE )) { D_DebugLog((DEB_TRACE,"Reacquiring client name & realm after referral\n")); UseWkstaRealm = FALSE; KerbFreeKdcName(&ClientName);
Status = KerbGetClientNameAndRealm( &LogonId, PrimaryCreds, UsingSuppliedCreds, NULL, NULL, UseWkstaRealm, &ClientName, &ClientRealm );
if (!NT_SUCCESS(Status)) { goto Cleanup; } }
goto GetTicketRestart; } else { // Tbd: Log error here? Max referrals reached..
goto Cleanup; } } else if (((Status == STATUS_NO_SUCH_USER ) && UsingSuppliedCreds && UseWkstaRealm) || ((Status == STATUS_NO_LOGON_SERVERS ) && UsingSuppliedCreds && !fRebootedSinceJoin)) { //
// We tried using the realm of the workstation and the account couldn't
// be found - try the realm from the UPN now.
//
if (KerbIsThisOurDomain(&ClientRealm)) { DebugLog((DEB_ERROR, "Doing freboot retry\n"));
UseWkstaRealm = FALSE;
KerbFreeKdcName(&ClientName); KerbFreeString(&ClientRealm);
//
// Only do this if the caller did not supply a
// domain name
//
KerbReadLockLogonSessions(LogonSession);
if (PrimaryCreds->DomainName.Length == 0) { Status = KerbGetClientNameAndRealm( &LogonId, PrimaryCreds, UsingSuppliedCreds, NULL, NULL, UseWkstaRealm, &ClientName, &ClientRealm ); }
KerbUnlockLogonSessions(LogonSession);
if (!NT_SUCCESS(Status)) { goto Cleanup; }
goto GetTicketRestart; } }
Cleanup:
if (Status == STATUS_ACCOUNT_DISABLED && UsedPrimaryLogonCreds) { D_DebugLog((DEB_ERROR, "Purging NLP Cache entry due to disabled acct.\n"));
KerbCacheLogonInformation( &UClientName, &ClientRealm, NULL, NULL, NULL, LogonSession, MSV1_0_CACHE_LOGON_DELETE_ENTRY, NULL, NULL, // no supplemental creds
0 ); }
if (UsedPrimaryLogonCreds && ((Status == STATUS_WRONG_PASSWORD) || (Status == STATUS_SMARTCARD_NO_CARD) || (Status == STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED) )) { KerbPingWlBalloon(&LogonSession->LogonId); }
//
// Credman creds need some enhanced logging on failure
//
if (!NT_SUCCESS( Status ) && ARGUMENT_PRESENT( CredManCredentials )) { KerbLogCredmanError( CredManCredentials, Status );
}
KerbFreeString(&ClientRealm); KerbFreeString(&CorrectRealm); KerbFreeString(&UClientName);
KerbFreeKdcName(&KdcServiceKdcName); KerbFreeKdcName(&ClientName);
DsysAssert( !LogonSessionLocked );
return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbPurgeServiceTicketAndTgt
//
// Synopsis: Whacks the service ticket, and its associated TGT, usually as a
// result of some sort of error condition.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOLEAN KerbPurgeServiceTicketAndTgt( IN PKERB_CONTEXT Context, IN LSA_SEC_HANDLE CredentialHandle, IN OPTIONAL PKERB_CREDMAN_CRED CredManHandle ) { PKERB_PRIMARY_CREDENTIAL PrimaryCredentials = NULL; PKERB_LOGON_SESSION LogonSession = NULL; UNICODE_STRING RealmName[3]; PUNICODE_STRING pTmp = RealmName; PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL; PKERB_CREDENTIAL Credential = NULL; BOOLEAN LogonSessionsLocked = FALSE, CacheLocked = FALSE, fRet = FALSE; NTSTATUS Status;
// Validate in params
// If any of these fires, contact Todds
DsysAssert(NULL != CredentialHandle); DsysAssert(NULL != Context->TicketCacheEntry ); DsysAssert(NULL != Context->TicketCacheEntry->TargetDomainName.Buffer);
Status = KerbReferenceCredential( CredentialHandle, 0, FALSE, &Credential );
if (!NT_SUCCESS(Status) || Credential == NULL) { D_DebugLog((DEB_ERROR,"KerbPurgeServiceTicket supplied w/ bogus cred handle\n")); goto Cleanup; }
LogonSession = KerbReferenceLogonSession(&Credential->LogonId, FALSE); if (NULL == LogonSession) { D_DebugLog((DEB_ERROR, "Couldn't find LUID %x\n", Credential->LogonId)); goto Cleanup; }
DsysAssert( !LogonSessionsLocked ); KerbReadLockLogonSessions(&KerbLogonSessionList); LogonSessionsLocked = TRUE;
if (NULL != Credential && Credential->SuppliedCredentials != NULL) { PrimaryCredentials = Credential->SuppliedCredentials; D_DebugLog((DEB_TRACE, "Purging tgt associated with SUPPLIED creds (%S\\%S)\n", PrimaryCredentials->DomainName.Buffer,PrimaryCredentials->UserName.Buffer)); } else if ARGUMENT_PRESENT(CredManHandle) { PrimaryCredentials = CredManHandle->SuppliedCredentials; D_DebugLog((DEB_TRACE, "Purging tgt associated with CREDMAN creds (%S\\%S)\n", PrimaryCredentials->DomainName.Buffer,PrimaryCredentials->UserName.Buffer));
} else { PrimaryCredentials = &LogonSession->PrimaryCredentials; D_DebugLog((DEB_TRACE, "Purging tgt associated with PRIMARY creds (%S\\%S)\n", PrimaryCredentials->DomainName.Buffer,PrimaryCredentials->UserName.Buffer)); }
KerbWriteLockContexts(); TicketCacheEntry = Context->TicketCacheEntry; Context->TicketCacheEntry = NULL; KerbUnlockContexts();
DsysAssert( !CacheLocked ); KerbReadLockTicketCache(); CacheLocked = TRUE;
// Do some mem copy rather than block over ticket cache searches
RtlCopyMemory(RealmName, &TicketCacheEntry->TargetDomainName, sizeof(UNICODE_STRING));
SafeAllocaAllocate(RealmName[0].Buffer, RealmName[0].MaximumLength); if (NULL == RealmName[0].Buffer) { goto Cleanup; }
RtlCopyUnicodeString( &RealmName[0], &TicketCacheEntry->TargetDomainName );
RtlCopyMemory(&RealmName[1],&TicketCacheEntry->AltTargetDomainName, sizeof(UNICODE_STRING)); if (RealmName[1].Buffer != NULL && RealmName[1].MaximumLength != 0) { SafeAllocaAllocate(RealmName[1].Buffer, RealmName[1].MaximumLength); if (NULL == RealmName[1].Buffer) { goto Cleanup; }
RtlCopyUnicodeString( &RealmName[1], &TicketCacheEntry->TargetDomainName ); }
DsysAssert( CacheLocked ); KerbUnlockTicketCache(); CacheLocked = FALSE;
RtlInitUnicodeString( &RealmName[2], NULL );
// Kill the service ticket
KerbRemoveTicketCacheEntry(TicketCacheEntry);
do { PKERB_TICKET_CACHE_ENTRY DummyEntry = NULL;
DummyEntry = KerbLocateTicketCacheEntryByRealm( &PrimaryCredentials->AuthenticationTicketCache, pTmp, KERB_TICKET_CACHE_PRIMARY_TGT );
if (NULL == DummyEntry) { D_DebugLog((DEB_TRACE, "Didn't find primary TGT for %S \n", pTmp->Buffer)); } else { KerbRemoveTicketCacheEntry(DummyEntry); KerbDereferenceTicketCacheEntry(DummyEntry); }
DummyEntry = KerbLocateTicketCacheEntryByRealm( &PrimaryCredentials->AuthenticationTicketCache, pTmp, KERB_TICKET_CACHE_DELEGATION_TGT );
if (NULL == DummyEntry) { D_DebugLog((DEB_TRACE, "Didn't find delegation TGT for %S\n", pTmp->Buffer)); } else { KerbRemoveTicketCacheEntry(DummyEntry); KerbDereferenceTicketCacheEntry(DummyEntry); }
pTmp++; } while (pTmp->Buffer != NULL);
fRet = TRUE;
Cleanup:
if (CacheLocked) { KerbUnlockTicketCache(); }
if (NULL != Credential) { KerbDereferenceCredential(Credential); }
if ( LogonSessionsLocked ) { KerbUnlockLogonSessions(&KerbLogonSessionList); }
if (NULL != LogonSession) { KerbDereferenceLogonSession(LogonSession); }
SafeAllocaFree(RealmName[0].Buffer); SafeAllocaFree(RealmName[1].Buffer);
return fRet; }
//+-------------------------------------------------------------------------
//
// Function: KerbEqualAccount
//
// Synopsis: Compare two SAM style account names to see if they are the
// the same
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//--------------------------------------------------------------------------
NTSTATUS KerbEqualAccount( IN UNICODE_STRING* pUserNameFoo, IN UNICODE_STRING* pDomainNameFoo, IN UNICODE_STRING* pUserNameBar, IN UNICODE_STRING* pDomainNameBar, OUT BOOLEAN* pbIsEqual ) { if (!pbIsEqual) { return STATUS_INVALID_PARAMETER; }
*pbIsEqual = FALSE;
if ((RtlEqualUnicodeString( pUserNameFoo, pUserNameBar, TRUE // case insensitive
)) && (RtlEqualUnicodeString( pDomainNameFoo, pDomainNameBar, TRUE // case insensitive
)) ) { *pbIsEqual = TRUE; return STATUS_SUCCESS; }
return STATUS_SUCCESS; }
//+-------------------------------------------------------------------------
//
// Function: GetPrimaryPrincipalSid
//
// Synopsis: Get SID for an LSAP_LOGON_SESSION by Logon ID
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//--------------------------------------------------------------------------
NTSTATUS GetPrimaryPrincipalSid( IN LUID* pLogonId, OUT PSID* ppsid ) { SECURITY_LOGON_SESSION_DATA* pLogonSessionData = NULL; ULONG cbSid = 0; NTSTATUS NtStatus = STATUS_UNSUCCESSFUL; PSID pSid = NULL;
if (!ppsid || !pLogonId) { return STATUS_INVALID_PARAMETER; }
*ppsid = NULL;
NtStatus = LsaGetLogonSessionData(pLogonId, &pLogonSessionData);
if (NT_SUCCESS(NtStatus)) { NtStatus = pLogonSessionData->Sid ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; }
if (NT_SUCCESS(NtStatus)) { cbSid = RtlLengthSid(pLogonSessionData->Sid);
pSid = KerbAllocate(cbSid);
NtStatus = pSid ? STATUS_SUCCESS : STATUS_NO_MEMORY; }
if (NT_SUCCESS(NtStatus)) { NtStatus = RtlCopySid(cbSid, pSid, pLogonSessionData->Sid); }
if (NT_SUCCESS(NtStatus)) { *ppsid = pSid; } else { KerbFree(pSid); }
if (pLogonSessionData) { LsaFreeReturnBuffer(pLogonSessionData); }
return NtStatus; }
//+-------------------------------------------------------------------------
//
// Function: KerbUpdateOldLogonSession
//
// Synopsis: Update old logon session during unlock logon
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbUpdateOldLogonSession( IN PKERB_LOGON_SESSION LogonSession, IN PSECPKG_PRIMARY_CRED PrimaryCredentials, IN PSECPKG_SUPPLEMENTAL_CRED_ARRAY SupplementalCredentials, IN PLUID OldLogonId, IN PKERB_TICKET_CACHE_ENTRY NewWorkstationTicket ) { NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
BOOLEAN bMatch = FALSE; PKERB_LOGON_SESSION OldLogonSession;
OldLogonSession = KerbReferenceLogonSession( OldLogonId, FALSE // don't unlink
); if (OldLogonSession == NULL) { return; }
KerbWriteLockLogonSessions(OldLogonSession); KerbWriteLockLogonSessions(LogonSession); KerbWriteLockTicketCache();
//
// Make sure the two accounts are the same before copying tickets
// around.
//
NtStatus = KerbEqualAccount( &LogonSession->PrimaryCredentials.UserName, &LogonSession->PrimaryCredentials.DomainName, &OldLogonSession->PrimaryCredentials.UserName, &OldLogonSession->PrimaryCredentials.DomainName, &bMatch );
//
// if names do not match, compare the sids
//
if (NT_SUCCESS(NtStatus) && !bMatch) { PSID UserSidOldLogonSession = NULL;
NtStatus = GetPrimaryPrincipalSid(&OldLogonSession->LogonId, &UserSidOldLogonSession);
if (NT_SUCCESS(NtStatus)) { bMatch = RtlEqualSid(PrimaryCredentials->UserSid, UserSidOldLogonSession); }
if (UserSidOldLogonSession) { KerbFree(UserSidOldLogonSession); } }
if (NT_SUCCESS(NtStatus) && bMatch) { PKERB_TICKET_CACHE_ENTRY ASTicket = NULL;
DebugLog((DEB_TRACE_CRED, "KerbUpdateOldLogonSession refreshing old logon session %#x:%#x, Flags %#x, SupplementalCredentials %p\n", OldLogonSession->LogonId.HighPart, OldLogonSession->LogonId.LowPart, PrimaryCredentials->Flags, SupplementalCredentials));
//
// Search for the new TGT so we can put it in the old
// cache.
//
ASTicket = KerbLocateTicketCacheEntryByRealm( &LogonSession->PrimaryCredentials.AuthenticationTicketCache, NULL, // get initial ticket
KERB_TICKET_CACHE_PRIMARY_TGT );
if (ASTicket != NULL) { OldLogonSession->LogonSessionFlags &= ~KERB_LOGON_DEFERRED; KerbRemoveTicketCacheEntry(ASTicket); } else { //
// Hold on to our old TGT if we didn't get one this time around..
//
D_DebugLog((DEB_ERROR, "Failed to find primary TGT on unlock logon session\n"));
ASTicket = KerbLocateTicketCacheEntryByRealm( &OldLogonSession->PrimaryCredentials.AuthenticationTicketCache, NULL, KERB_TICKET_CACHE_PRIMARY_TGT );
if (ASTicket != NULL) { //
// Copy into new logon session cache for later reuse.
//
KerbRemoveTicketCacheEntry(ASTicket); OldLogonSession->LogonSessionFlags &= ~KERB_LOGON_DEFERRED; } else { //
// No TGT in either new or old logonsession -- we're deferred...
//
D_DebugLog((DEB_ERROR, "Failed to find primary TGT on *OLD* logon session\n")); OldLogonSession->LogonSessionFlags |= KERB_LOGON_DEFERRED; } }
if ((LogonSession->LogonSessionFlags & KERB_LOGON_SMARTCARD) != 0) { OldLogonSession->LogonSessionFlags |= KERB_LOGON_SMARTCARD; } else { OldLogonSession->LogonSessionFlags &= ~KERB_LOGON_SMARTCARD; }
if ((LogonSession->LogonSessionFlags & KERB_LOGON_MIT_REALM) != 0) { OldLogonSession->LogonSessionFlags |= KERB_LOGON_MIT_REALM; }
//
// swap the primary creds
//
KerbFreePrimaryCredentials(&OldLogonSession->PrimaryCredentials, FALSE);
if ( NewWorkstationTicket != NULL ) { KerbRemoveTicketCacheEntry(NewWorkstationTicket); }
KerbPurgeTicketCache(&LogonSession->PrimaryCredentials.AuthenticationTicketCache); KerbPurgeTicketCache(&LogonSession->PrimaryCredentials.S4UTicketCache); KerbPurgeTicketCache(&LogonSession->PrimaryCredentials.ServerTicketCache);
RtlCopyMemory( &OldLogonSession->PrimaryCredentials, &LogonSession->PrimaryCredentials, sizeof(KERB_PRIMARY_CREDENTIAL) );
RtlZeroMemory( &LogonSession->PrimaryCredentials, sizeof(KERB_PRIMARY_CREDENTIAL) );
// Fix up list entry pointers.
KerbInitTicketCache(&OldLogonSession->PrimaryCredentials.AuthenticationTicketCache); KerbInitTicketCache(&OldLogonSession->PrimaryCredentials.S4UTicketCache); KerbInitTicketCache(&OldLogonSession->PrimaryCredentials.ServerTicketCache);
KerbInitTicketCache(&LogonSession->PrimaryCredentials.AuthenticationTicketCache); KerbInitTicketCache(&LogonSession->PrimaryCredentials.S4UTicketCache); KerbInitTicketCache(&LogonSession->PrimaryCredentials.ServerTicketCache);
// insert new tickets into old logon session
if (ASTicket != NULL) { KerbInsertTicketCacheEntry(&OldLogonSession->PrimaryCredentials.AuthenticationTicketCache, ASTicket);
if (( ASTicket->CacheFlags & KERB_TICKET_CACHE_PRIMARY_TGT ) && ( ASTicket->TicketFlags & KERB_TICKET_FLAGS_renewable )) {
KerbScheduleTgtRenewal( ASTicket ); }
KerbDereferenceTicketCacheEntry(ASTicket); // for locate call above
}
if (NewWorkstationTicket != NULL) { //
// On the offchance that the ticket is still linked, unlink it prior to inserting
//
KerbRemoveTicketCacheEntry( NewWorkstationTicket ); DsysAssert( NewWorkstationTicket->ListEntry.ReferenceCount > 0 );
KerbInsertTicketCacheEntry(&OldLogonSession->PrimaryCredentials.ServerTicketCache, NewWorkstationTicket); } }
KerbUnlockTicketCache(); KerbUnlockLogonSessions(LogonSession); KerbUnlockLogonSessions(OldLogonSession); KerbDereferenceLogonSession(OldLogonSession);
//
// changes in clear text passwords are handled by LsaLogonUser followed by
// a change-cached-password request from winlogon. However, supplemental
// credential changes are updated here: we do not know whether there is a
// real password change, but we issue a credential update anyway.
//
// ISSUE: Due to lack of cleartext passwords, we can not trigger a password
// change notification here
//
if (NT_SUCCESS(NtStatus) && bMatch && (0 == (PrimaryCredentials->Flags & PRIMARY_CRED_CLEAR_PASSWORD)) && SupplementalCredentials) { D_DebugLog((DEB_TRACE_CRED, "KerbUpdateOldLogonSession updating credentials for %#x:%#x by %#x:%#x, " "Flag %#x, User %wZ\\%wZ, Upn %wZ, DnsLogonDomain %wZ, LogonServer %wZ\n", OldLogonId->HighPart, OldLogonId->LowPart, LogonSession->LogonId.HighPart, LogonSession->LogonId.LowPart, PrimaryCredentials->Flags, &PrimaryCredentials->DomainName, &PrimaryCredentials->DownlevelName, &PrimaryCredentials->Upn, &PrimaryCredentials->DnsDomainName, &PrimaryCredentials->LogonServer));
DsysAssert(RtlEqualLuid(&PrimaryCredentials->LogonId, &LogonSession->LogonId) && L"KerbUpdateOldLogonSession PrimaryCredentials and logon session must have the same Luid\n");
PrimaryCredentials->Flags |= PRIMARY_CRED_UPDATE; PrimaryCredentials->LogonId = *OldLogonId; LsaFunctions->UpdateCredentials( PrimaryCredentials, SupplementalCredentials ); //
// restore PrimaryCredentials
//
PrimaryCredentials->Flags &= ~PRIMARY_CRED_UPDATE; PrimaryCredentials->LogonId = LogonSession->LogonId; } }
#ifndef WIN32_CHICAGO // later
//+-------------------------------------------------------------------------
//
// Function: KerbCheckDomainlessLogonPolicy
//
// Synopsis: If a machine is not a member of a domain or MIT realm,
// we've got to verify that the kerberos principal is mapped
// to a local account.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS NTAPI KerbCheckRealmlessLogonPolicy( IN PKERB_TICKET_CACHE_ENTRY AsTicket, IN PKERB_INTERNAL_NAME ClientName, IN PUNICODE_STRING ClientRealm ) { NTSTATUS Status = STATUS_SUCCESS;
//
// Sanity check to prevent identity spoofing for
// gaining access to a machine
//
// tbd: Credman support? Seems like we should be using credman
// credentials, if present, but for logon???
//
if (!KerbEqualKdcNames( ClientName, AsTicket->ClientName) || !RtlEqualUnicodeString( &AsTicket->ClientDomainName, ClientRealm, TRUE )) { D_DebugLog((DEB_ERROR, "Logon session and AS ticket identities don't match\n")); // tbd: Log names?
Status = STATUS_NO_SUCH_USER; }
return Status; }
//+-------------------------------------------------------------------------
//
// Function: LsaApLogonUserEx2
//
// Synopsis: Handles service, batch, and interactive logons
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS NTAPI LsaApLogonUserEx2( IN PLSA_CLIENT_REQUEST ClientRequest, IN SECURITY_LOGON_TYPE LogonType, IN PVOID ProtocolSubmitBuffer, IN PVOID ClientBufferBase, IN ULONG SubmitBufferSize, OUT PVOID *ProfileBuffer, OUT PULONG ProfileBufferLength, OUT PLUID NewLogonId, OUT PNTSTATUS ApiSubStatus, OUT PLSA_TOKEN_INFORMATION_TYPE TokenInformationType, OUT PVOID *TokenInformation, OUT PUNICODE_STRING *AccountName, OUT PUNICODE_STRING *AuthenticatingAuthority, OUT PUNICODE_STRING *MachineName, OUT PSECPKG_PRIMARY_CRED PrimaryCredentials, OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY * CachedCredentials ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_LOGON_SESSION LogonSession = NULL; PKERB_INTERACTIVE_LOGON LogonInfo = NULL; UCHAR Seed; UNICODE_STRING TempName = NULL_UNICODE_STRING; UNICODE_STRING TempAuthority = NULL_UNICODE_STRING; UNICODE_STRING NullAuthority = NULL_UNICODE_STRING; UNICODE_STRING MappedClientName = NULL_UNICODE_STRING; LUID LogonId; LUID OldLogonId; LUID AlternateLuid; BOOLEAN DoingUnlock = FALSE, WkstaAccount = FALSE; PKERB_TICKET_CACHE_ENTRY WorkstationTicket = NULL; PKERB_TICKET_CACHE_ENTRY AsTicket = NULL; KERB_MESSAGE_BUFFER ForwardedTgt = {0}; KERBEROS_MACHINE_ROLE Role; PKERB_MIT_REALM MitRealm = NULL; BOOLEAN LocalLogon = FALSE, RealmlessWkstaLogon = FALSE; BOOLEAN UsedAlternateName = FALSE; KERB_ENCRYPTION_KEY CredentialKey = {0}; UNICODE_STRING DomainName = {0}; PKERB_INTERNAL_NAME ClientName = NULL; UNICODE_STRING ClientRealm = {0}; UNICODE_STRING Upn = {0}; PKERB_INTERNAL_NAME MachineServiceName = {0}; PKERB_INTERNAL_NAME S4UClientName = {0}; UNICODE_STRING S4UClientRealm = {0}; UNICODE_STRING CorrectRealm = {0}; PLSAPR_CR_CIPHER_VALUE SecretCurrent = NULL; UNICODE_STRING Prefix, SavedPassword = {0}; BOOLEAN ServiceSecretLogon = FALSE; ULONG ProcessFlags = 0; PCERT_CONTEXT CertContext = NULL; BOOLEAN fSuppliedCertCred = FALSE; PVOID pTempSubmitBuffer = ProtocolSubmitBuffer; PNETLOGON_VALIDATION_SAM_INFO4 ValidationInfo = NULL; PNETLOGON_VALIDATION_SAM_INFO4 SCValidationInfo = NULL; GUID LogonGuid = { 0 };
//
// Credential manager stored credentials.
//
UNICODE_STRING CredmanUserName; UNICODE_STRING CredmanDomainName; UNICODE_STRING CredmanPassword;
#if _WIN64
BOOL fAllocatedSubmitBuffer = FALSE;
#endif // _WIN64
KERB_LOGON_INFO LogonTraceInfo;
if (LogonType == CachedInteractive) {
//
// We don't support cached logons.
//
return STATUS_INVALID_LOGON_TYPE; }
if( KerbEventTraceFlag ) // Event Trace: KerbLogonUserStart {No Data}
{ // Set trace parameters
LogonTraceInfo.EventTrace.Guid = KerbLogonGuid; LogonTraceInfo.EventTrace.Class.Type = EVENT_TRACE_TYPE_START; LogonTraceInfo.EventTrace.Flags = WNODE_FLAG_TRACED_GUID; LogonTraceInfo.EventTrace.Size = sizeof(EVENT_TRACE_HEADER);
TraceEvent( KerbTraceLoggerHandle, (PEVENT_TRACE_HEADER)&LogonTraceInfo); }
//
// First initialize all the output parameters to NULL.
//
*ProfileBuffer = NULL; *ApiSubStatus = STATUS_SUCCESS; *TokenInformation = NULL; *AccountName = NULL; *AuthenticatingAuthority = NULL; *MachineName = NULL; *CachedCredentials = NULL;
CredmanUserName.Buffer = NULL; CredmanDomainName.Buffer = NULL; CredmanPassword.Buffer = NULL;
RtlZeroMemory( PrimaryCredentials, sizeof(SECPKG_PRIMARY_CRED) );
if (!KerbGlobalInitialized) { Status = STATUS_INVALID_SERVER_STATE; goto Cleanup; }
//
// Make sure we have at least tcp as a xport, unless we are doing
// cached sc logon, in which case we'll let them get through.
//
KerbGlobalReadLock(); if (KerbGlobalNoTcpUdp) { Status = STATUS_NETWORK_UNREACHABLE; } KerbGlobalReleaseLock(); if (!NT_SUCCESS(Status)) { goto Cleanup; }
Role = KerbGetGlobalRole();
*AccountName = (PUNICODE_STRING) KerbAllocate(sizeof(UNICODE_STRING)); if (*AccountName == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } (*AccountName)->Buffer = NULL;
*AuthenticatingAuthority = (PUNICODE_STRING) KerbAllocate(sizeof(UNICODE_STRING)); if (*AuthenticatingAuthority == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } (*AuthenticatingAuthority)->Buffer = NULL;
//
// Initialize local pointers to NULL
//
LogonId.LowPart = 0; LogonId.HighPart = 0; *NewLogonId = LogonId;
//
// Check the logon type
//
switch (LogonType) {
case Service: case Interactive: case Batch: case Network: case NetworkCleartext: case RemoteInteractive:
PSECURITY_SEED_AND_LENGTH SeedAndLength;
LogonInfo = (PKERB_INTERACTIVE_LOGON) pTempSubmitBuffer;
if (SubmitBufferSize < sizeof(KERB_LOGON_SUBMIT_TYPE)) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; }
#if _WIN64
SECPKG_CALL_INFO CallInfo;
//
// Expand the ProtocolSubmitBuffer to 64-bit pointers if this
// call came from a WOW client.
//
if(!LsaFunctions->GetCallInfo(&CallInfo)) { Status = STATUS_INTERNAL_ERROR; goto Cleanup; }
if (CallInfo.Attributes & SECPKG_CALL_WOWCLIENT) { Status = KerbConvertWOWLogonBuffer(ProtocolSubmitBuffer, ClientBufferBase, &SubmitBufferSize, LogonInfo->MessageType, &pTempSubmitBuffer);
if (!NT_SUCCESS(Status)) { goto Cleanup; }
fAllocatedSubmitBuffer = TRUE;
//
// Some macros below expand out to use ProtocolSubmitBuffer directly.
// We've secretly replaced their usual ProtocolSubmitBuffer with
// pTempSubmitBuffer -- let's see if they can tell the difference.
//
ProtocolSubmitBuffer = pTempSubmitBuffer; LogonInfo = (PKERB_INTERACTIVE_LOGON) pTempSubmitBuffer; }
#endif // _WIN64
if ((LogonInfo->MessageType == KerbInteractiveLogon) || (LogonInfo->MessageType == KerbWorkstationUnlockLogon)) { //
// Pull the interesting information out of the submit buffer
//
if (LogonInfo->MessageType == KerbInteractiveLogon) { if (SubmitBufferSize < sizeof(KERB_INTERACTIVE_LOGON)) { D_DebugLog((DEB_ERROR,"Submit buffer to logon too small: %d. %ws, line %d\n",SubmitBufferSize, THIS_FILE, __LINE__)); Status = STATUS_INVALID_PARAMETER; goto Cleanup; } } else { if (SubmitBufferSize < sizeof(KERB_INTERACTIVE_UNLOCK_LOGON)) { D_DebugLog((DEB_ERROR,"Submit buffer to logon too small: %d. %ws, line %d\n",SubmitBufferSize, THIS_FILE, __LINE__)); Status = STATUS_INVALID_PARAMETER; goto Cleanup; } PKERB_INTERACTIVE_UNLOCK_LOGON UnlockInfo = (PKERB_INTERACTIVE_UNLOCK_LOGON) LogonInfo;
OldLogonId = UnlockInfo->LogonId; DoingUnlock = TRUE; }
//
// If the password length is greater than 255 (i.e., the
// upper byte of the length is non-zero) then the password
// has been run-encoded for privacy reasons. Get the
// run-encode seed out of the upper-byte of the length
// for later use.
//
SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &LogonInfo->Password.Length; Seed = SeedAndLength->Seed; SeedAndLength->Seed = 0;
//
// Enforce length restrictions on username and password.
//
if ( LogonInfo->UserName.Length > (UNLEN * sizeof(WCHAR)) || LogonInfo->Password.Length > (PWLEN * sizeof(WCHAR)) ) { D_DebugLog((DEB_ERROR,"LsaApLogonUserEx2: Name or password too long. %ws, line%d\n", THIS_FILE, __LINE__)); Status = STATUS_NAME_TOO_LONG; goto Cleanup; }
//
// Relocate any pointers to be relative to 'LogonInfo'
//
RELOCATE_ONE(&LogonInfo->UserName); NULL_RELOCATE_ONE(&LogonInfo->LogonDomainName); NULL_RELOCATE_ONE(&LogonInfo->Password);
//
// preserve the non-null pointer of password buffer if length is 0
//
if (!LogonInfo->Password.Length) { LogonInfo->Password.Buffer = L""; }
if( (LogonInfo->LogonDomainName.Length <= sizeof(WCHAR)) && (LogonInfo->Password.Length <= sizeof(WCHAR)) ) { if(KerbProcessUserNameCredential( &LogonInfo->UserName, &CredmanUserName, &CredmanDomainName, &CredmanPassword ) == STATUS_SUCCESS) { LogonInfo->UserName = CredmanUserName; LogonInfo->LogonDomainName = CredmanDomainName; LogonInfo->Password = CredmanPassword; Seed = 0; } }
if ( LogonType == Service ) { SECPKG_CALL_INFO CallInfo;
//
// If we have a service logon, the password we got is likely the name of the
// secret that is holding the account password. Make sure to read that secret
// here
//
RtlInitUnicodeString( &Prefix, L"_SC_" ); if ( (RtlPrefixUnicodeString( &Prefix, &LogonInfo->Password, TRUE )) && (LsaFunctions->GetCallInfo(&CallInfo)) && (CallInfo.Attributes & SECPKG_CALL_IS_TCB) ) { LSAPR_HANDLE SecretHandle = NULL;
Status = LsarOpenSecret( KerbGlobalPolicyHandle, ( PLSAPR_UNICODE_STRING )&LogonInfo->Password, SECRET_QUERY_VALUE, &SecretHandle );
if ( NT_SUCCESS( Status ) ) {
Status = LsarQuerySecret( SecretHandle, &SecretCurrent, NULL, NULL, NULL );
if ( NT_SUCCESS( Status ) && (SecretCurrent != NULL) ) {
RtlCopyMemory( &SavedPassword, &LogonInfo->Password, sizeof( UNICODE_STRING ) ); LogonInfo->Password.Length = ( USHORT )SecretCurrent->Length; LogonInfo->Password.MaximumLength = ( USHORT )SecretCurrent->MaximumLength; LogonInfo->Password.Buffer = ( USHORT * )SecretCurrent->Buffer; ServiceSecretLogon = TRUE; Seed = 0; // do not run RtlRunDecodeUnicodeString for this password
}
LsarClose( &SecretHandle ); } }
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup; }
}
D_DebugLog((DEB_TRACE,"Logging on user %wZ, domain %wZ\n", &LogonInfo->UserName, &LogonInfo->LogonDomainName ));
KerbGlobalReadLock();
WkstaAccount = RtlEqualUnicodeString( &KerbGlobalMachineName, &LogonInfo->LogonDomainName, TRUE );
KerbGlobalReleaseLock();
// In the case where we're doing a realmless wksta
// logon, then run see if there's a client mapping,
// but only for interactive logons
if ((!WkstaAccount) && (Role == KerbRoleRealmlessWksta) && (LogonType == Interactive )) { Status = KerbProcessTargetNames( &LogonInfo->UserName, NULL, 0, &ProcessFlags, &ClientName, &ClientRealm, NULL );
if (NT_SUCCESS(Status)) { Status = KerbMapClientName( &MappedClientName, ClientName, &ClientRealm ); }
if (NT_SUCCESS(Status)) { D_DebugLog((DEB_WARN, "Mapping user to MIT principal\n")); RealmlessWkstaLogon = TRUE; } }
if (WkstaAccount || (KerbGlobalDomainIsPreNT5 && !RealmlessWkstaLogon) || (KerbGlobalSafeModeBootOptionPresent)) {
D_DebugLog(( DEB_TRACE, "Local Logon, bailing out now\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup ; }
//
// Now decode the password, if necessary
//
if (Seed != 0) { __try { RtlRunDecodeUnicodeString( Seed, &LogonInfo->Password); } __except(EXCEPTION_EXECUTE_HANDLER) { Status = STATUS_ILL_FORMED_PASSWORD; goto Cleanup; } }
//
// Check if the user name holds a cert context thumbprint
//
Status = KerbCheckUserNameForCert( NULL, TRUE, &LogonInfo->UserName, &CertContext ); if (NT_SUCCESS(Status)) { if (NULL != CertContext) { fSuppliedCertCred = TRUE; } else { //
// Copy out the user name and Authenticating Authority so we can audit them.
//
Status = KerbDuplicateString( &TempName, &LogonInfo->UserName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } } else { goto Cleanup; }
if ( LogonInfo->LogonDomainName.Buffer != NULL ) {
Status = KerbDuplicateString( &TempAuthority, &LogonInfo->LogonDomainName ); 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)) { D_DebugLog((DEB_ERROR,"Failed to allocate locally unique ID: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
if (fSuppliedCertCred) { //
// Build a logon session to hold all this information
// for a smart card logon
//
Status = KerbCreateSmartCardLogonSessionFromCertContext( &CertContext, &LogonId, &NullAuthority, &LogonInfo->Password, NULL, // no CSP data
0, // no CSP data
&LogonSession, &TempName ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// UPN logon...
//
Status = KerbDuplicateString( &Upn, &TempName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
D_DebugLog((DEB_TRACE, "UPN logon %wZ\n", &Upn));
} else { //
// Build a logon session to hold all this information
//
Status = KerbCreateLogonSession( &LogonId, &TempName, &TempAuthority, &LogonInfo->Password, NULL, // no old password
PRIMARY_CRED_CLEAR_PASSWORD, 0, FALSE, // don't allow a duplicate.
&LogonSession ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// UPN logon...
//
if (TempAuthority.Length == 0) { Status = KerbDuplicateString( &Upn, &TempName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
D_DebugLog((DEB_TRACE, "UPN logon %wZ\n", &Upn)); }
} } else if ((LogonInfo->MessageType == KerbSmartCardLogon) || (LogonInfo->MessageType == KerbSmartCardUnlockLogon)) {
if (LogonInfo->MessageType == KerbSmartCardUnlockLogon) { if (SubmitBufferSize < sizeof(KERB_SMART_CARD_UNLOCK_LOGON)) { D_DebugLog((DEB_ERROR,"Submit buffer to logon too small: %d. %ws, line %d\n",SubmitBufferSize, THIS_FILE, __LINE__)); Status = STATUS_INVALID_PARAMETER; goto Cleanup; } PKERB_SMART_CARD_UNLOCK_LOGON UnlockInfo = (PKERB_SMART_CARD_UNLOCK_LOGON) LogonInfo;
OldLogonId = UnlockInfo->LogonId; DoingUnlock = TRUE; }
Status = KerbCreateSmartCardLogonSession( pTempSubmitBuffer, ClientBufferBase, SubmitBufferSize, LogonType, &LogonSession, &LogonId, &TempName, &TempAuthority ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbCreateSCloongsession failed %x\n", Status)); goto Cleanup; }
//
// UPN logon...
//
if (TempAuthority.Length == 0) { Status = KerbDuplicateString( &Upn, &TempName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
D_DebugLog((DEB_TRACE, "UPN logon %wZ\n", &Upn)); }
} else if ((LogonInfo->MessageType == KerbTicketLogon) || (LogonInfo->MessageType == KerbTicketUnlockLogon)) { if (LogonInfo->MessageType == KerbTicketUnlockLogon) { if (SubmitBufferSize < sizeof(KERB_TICKET_UNLOCK_LOGON)) { D_DebugLog((DEB_ERROR,"Submit buffer to logon too small: %d. %ws, line %d\n",SubmitBufferSize, THIS_FILE, __LINE__)); Status = STATUS_INVALID_PARAMETER; goto Cleanup; } PKERB_TICKET_UNLOCK_LOGON UnlockInfo = (PKERB_TICKET_UNLOCK_LOGON) LogonInfo;
OldLogonId = UnlockInfo->LogonId; DoingUnlock = TRUE; }
Status = KerbCreateTicketLogonSession( pTempSubmitBuffer, ClientBufferBase, SubmitBufferSize, LogonType, &LogonSession, &LogonId, &WorkstationTicket, &ForwardedTgt ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } //
// The S4UToSelf Logon really is quite different
// than any other form of logon. As such, we're
// going to branch out into the S4UToSelf protocol
// code.
//
else if (LogonInfo->MessageType == KerbS4ULogon) { if (SubmitBufferSize < sizeof(KERB_S4U_LOGON)) { D_DebugLog((DEB_ERROR,"Submit buffer to logon too small: %d. %ws, line %d\n",SubmitBufferSize, THIS_FILE, __LINE__)); Status = STATUS_INVALID_PARAMETER; goto Cleanup; }
if ( LogonType != Network ) { D_DebugLog((DEB_ERROR, "LogonType must be network for S4ULogon\n")); Status = STATUS_INVALID_LOGON_TYPE; goto Cleanup; }
Status = KerbS4UToSelfLogon( pTempSubmitBuffer, ClientBufferBase, SubmitBufferSize, &LogonSession, &LogonId, &WorkstationTicket, &S4UClientName, &S4UClientRealm, &AlternateLuid );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbS4UToSelfLogon failed - %x\n", Status)); goto Cleanup; } }
else { D_DebugLog((DEB_TRACE,"Invalid info class to logon: %d. %ws, line %d\n", LogonInfo->MessageType, THIS_FILE, __LINE__)); Status = STATUS_INVALID_INFO_CLASS; goto Cleanup; }
break;
default: //
// No other logon types are supported.
//
Status = STATUS_INVALID_LOGON_TYPE; D_DebugLog((DEB_ERROR, "Invalid logon type passed to LsaApLogonUserEx2: %d. %ws, line %d\n",LogonType, THIS_FILE, __LINE__)); goto Cleanup; }
D_DebugLog((DEB_TRACE, "LsaApLogonUserEx2: attempting to logon user %wZ\\%wZ\n", &TempAuthority, &TempName ));
//
// If the KDC is not yet started, start it now.
//
#ifndef WIN32_CHICAGO
if ((KerbGlobalRole == KerbRoleDomainController) && !KerbKdcStarted) { D_DebugLog((DEB_TRACE_LOGON,"Waiting for KDC to start\n")); Status = KerbWaitForKdc( KerbGlobalKdcWaitTime ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_TRACE_LOGON,"Failed to wait for KDC to start\n")); goto Cleanup; }
} #endif // WIN32_CHICAGO
//
// If we don't have a workstation ticket already, attempt to get one now.
//
// Per bug 94726, if we're not part of an NT domain, then we should not require
// the workstation ticket to perform the logon successfully.
if (WorkstationTicket == NULL) { //
// Get the initial TGT for the user. This routine figures out the real
// principal names & realm names
//
Status = KerbGetTicketGrantingTicket( LogonSession, NULL, NULL, NULL, // no credential
(Role == KerbRoleRealmlessWksta ? &AsTicket : NULL), &CredentialKey );
if ( Status == STATUS_NO_TRUST_SAM_ACCOUNT ) { Status = STATUS_NO_LOGON_SERVERS ; }
if (NT_SUCCESS(Status)) { if (Role != KerbRoleRealmlessWksta) // joined machine
{ KerbWriteLockLogonSessions(LogonSession); LogonSession->LogonSessionFlags &= ~KERB_LOGON_DEFERRED; KerbUnlockLogonSessions(LogonSession);
//
// Check to see if the client is from an MIT realm
//
KerbGlobalReadLock();
(VOID) KerbLookupMitRealm( &KerbGlobalDnsDomainName, &MitRealm, &UsedAlternateName );
Status = KerbDuplicateKdcName( &MachineServiceName, KerbGlobalMitMachineServiceName );
KerbGlobalReleaseLock(); if (!NT_SUCCESS(Status)) { goto Cleanup; }
Status = KerbGetOurDomainName( &DomainName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// Now that we have a TGT, we need to build a token. The PAC is currently
// hidden inside the TGT, so we need to get a ticket to this workstation
//
D_DebugLog((DEB_TRACE_LOGON,"Getting outbound ticket to ")); D_KerbPrintKdcName((DEB_TRACE_LOGON, MachineServiceName));
Status = KerbGetServiceTicket( LogonSession, NULL, // no credential
NULL, MachineServiceName, &DomainName, NULL, ProcessFlags, 0, // no options
0, // no enc type
NULL, // no error message
NULL, // no authorization data
NULL, // no TGT reply
&WorkstationTicket, &LogonGuid );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"LogonUser: Failed to get workstation ticket for %wZ\\%wZ: 0x%x. %ws, line %d\n", &TempAuthority, &TempName, Status, THIS_FILE, __LINE__ )); goto Cleanup; } } else { D_DebugLog((DEB_ERROR, "Nonjoined workstation\n")); DsysAssert(AsTicket != NULL); DsysAssert(MappedClientName.Buffer != NULL);
Status = KerbCheckRealmlessLogonPolicy( AsTicket, ClientName, &ClientRealm );
if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR, "LogonUser: Failed check for domainless logon\n")); goto Cleanup; } } } else { DebugLog((DEB_WARN, "LogonUser: Failed to get TGT for %wZ\\%wZ : 0x%x\n", &TempAuthority, &TempName, Status ));
//
// If this was a smart card logon, try logging on locally for
// non-definitive errors:
//
if ( ( (LogonInfo->MessageType == KerbSmartCardLogon ) || (LogonInfo->MessageType == KerbSmartCardUnlockLogon ) ) && ( ( Status == STATUS_NO_LOGON_SERVERS ) || ( Status == STATUS_NETWORK_UNREACHABLE ) ||// From DsGetdcName
( Status == STATUS_NETLOGON_NOT_STARTED ) ) ) {
Status = KerbDoLocalSmartCardLogon( LogonSession, TokenInformationType, TokenInformation, ProfileBufferLength, ProfileBuffer, PrimaryCredentials, CachedCredentials, &SCValidationInfo );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed smart card local logon: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
LocalLogon = TRUE;
} else { //
// Not smart card
//
goto Cleanup; } } }
//
// If we didn't already build a ticket, do so now
//
if (!LocalLogon) { Status = KerbCreateTokenFromLogonTicket( WorkstationTicket, &LogonId, LogonInfo, RealmlessWkstaLogon, LogonType, &CredentialKey, &ForwardedTgt, &MappedClientName, S4UClientName, &S4UClientRealm, &AlternateLuid, LogonSession, TokenInformationType, TokenInformation, ProfileBufferLength, ProfileBuffer, PrimaryCredentials, CachedCredentials, &ValidationInfo );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN,"LogonUser: Failed to create token from ticket: 0x%x\n",Status)); goto Cleanup; }
//
// Add the password to the primary credentials.
//
if (LogonInfo->MessageType == KerbInteractiveLogon && !fSuppliedCertCred) { Status = KerbDuplicateString( &PrimaryCredentials->Password, &LogonInfo->Password );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
PrimaryCredentials->Flags |= PRIMARY_CRED_CLEAR_PASSWORD; } }
//
// Get the final doamin name and user name out of the logon session,
// if it is different than the one used for logon.
//
KerbReadLockLogonSessions(LogonSession); if (!RtlEqualUnicodeString( &TempName, &PrimaryCredentials->DownlevelName, TRUE)) { KerbFreeString(&TempName); Status = KerbDuplicateString( &TempName, &PrimaryCredentials->DownlevelName ); if (!NT_SUCCESS(Status)) { KerbUnlockLogonSessions(LogonSession); goto Cleanup; } }
if (!RtlEqualDomainName( &TempAuthority, &PrimaryCredentials->DomainName )) { KerbFreeString(&TempAuthority); Status = KerbDuplicateString( &TempAuthority, &PrimaryCredentials->DomainName ); if (!NT_SUCCESS(Status)) { KerbUnlockLogonSessions(LogonSession); goto Cleanup; } }
KerbUnlockLogonSessions(LogonSession);
//
// Finally create the logon session in the LSA
//
Status = LsaFunctions->CreateLogonSession( &LogonId ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to create logon session: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
//
// Add additional names to the logon session name map. Ignore failure
// as that just means GetUserNameEx calls for these name formats later
// on will be satisfied by hitting the wire.
//
if (ValidationInfo || SCValidationInfo) { PNETLOGON_VALIDATION_SAM_INFO4 Tmp = (ValidationInfo ? ValidationInfo : SCValidationInfo);
if (Tmp->FullName.Length) { D_DebugLog((DEB_TRACE_LOGON, "NameDisplay %wZ\n", &Tmp->FullName)); I_LsaIAddNameToLogonSession(&LogonId, NameDisplay, &Tmp->FullName); }
//
// Smart cards use a "special" UPN for the logon cache, so don't
// use it here, as the cached logon will provide us w/ bogus data...
//
if (Upn.Buffer != NULL) { D_DebugLog((DEB_TRACE_LOGON, "NameUserPrincipal %wZ\n", &Upn)); I_LsaIAddNameToLogonSession(&LogonId, NameUserPrincipal, &Upn); }
if (Tmp->DnsLogonDomainName.Length) { D_DebugLog((DEB_TRACE_LOGON, "NameDnsDomain %wZ\n", &Tmp->DnsLogonDomainName)); I_LsaIAddNameToLogonSession(&LogonId, NameDnsDomain, &Tmp->DnsLogonDomainName); } }
I_LsaISetLogonGuidInLogonSession(&LogonId, &LogonGuid);
//
// If this was an unlock operation, copy the authentication ticket
// cache into the original logon session.
//
if (DoingUnlock) { KerbUpdateOldLogonSession( LogonSession, PrimaryCredentials, *CachedCredentials, &OldLogonId, WorkstationTicket ); } *NewLogonId = LogonId;
Cleanup:
//
// This is a "fake" Info4 struct... Free UPN and dnsdomain
// name manually -- normally a giant struct alloc'd by RPC
//
if (ValidationInfo) { KerbFreeString(&ValidationInfo->DnsLogonDomainName); KerbFreeString(&ValidationInfo->Upn); KerbFreeString(&ValidationInfo->FullName); KerbFree(ValidationInfo); }
if (SCValidationInfo) { LocalFree(SCValidationInfo); // this was alloc'd by cache lookup.
}
KerbFreeString( &CredmanUserName ); KerbFreeString( &CredmanDomainName ); KerbFreeString( &CredmanPassword ); KerbFreeString( &Upn );
if (CertContext != NULL) { CertFreeCertificateContext(CertContext); }
//
// Restore the saved password
//
if ( ServiceSecretLogon ) {
RtlCopyMemory( &LogonInfo->Password, &SavedPassword, sizeof( UNICODE_STRING ) );
//
// Free the secret value we read...
//
LsaIFree_LSAPR_CR_CIPHER_VALUE( SecretCurrent ); }
KerbFreeString( &CorrectRealm ); KerbFreeKey(&CredentialKey); if (*AccountName != NULL) { **AccountName = TempName; TempName.Buffer = NULL; }
if (*AuthenticatingAuthority != NULL) { **AuthenticatingAuthority = TempAuthority; TempAuthority.Buffer = NULL; }
if (!NT_SUCCESS(Status)) { //
// Unlink the new logon session
//
if (LogonSession != NULL) { KerbReferenceLogonSessionByPointer(LogonSession, TRUE); KerbDereferenceLogonSession(LogonSession); } if (*ProfileBuffer != NULL) { LsaFunctions->FreeClientBuffer(NULL, *ProfileBuffer); *ProfileBuffer = NULL; } if (*CachedCredentials != NULL) { KerbFree(*CachedCredentials); *CachedCredentials = NULL; }
//
// Map status codes to prevent specific information from being
// released about this user.
//
switch (Status) { case STATUS_WRONG_PASSWORD: case STATUS_NO_SUCH_USER: case STATUS_PKINIT_FAILURE: case STATUS_SMARTCARD_SUBSYSTEM_FAILURE: case STATUS_SMARTCARD_WRONG_PIN: case STATUS_SMARTCARD_CARD_BLOCKED: case STATUS_SMARTCARD_NO_CARD: case STATUS_SMARTCARD_NO_KEY_CONTAINER: case STATUS_SMARTCARD_NO_CERTIFICATE: case STATUS_SMARTCARD_NO_KEYSET: case STATUS_SMARTCARD_IO_ERROR: case STATUS_SMARTCARD_CERT_REVOKED: case STATUS_SMARTCARD_CERT_EXPIRED: case STATUS_REVOCATION_OFFLINE_C: case STATUS_REVOCATION_OFFLINE_KDC: case STATUS_ISSUING_CA_UNTRUSTED_KDC: case STATUS_ISSUING_CA_UNTRUSTED: case STATUS_PKINIT_NAME_MISMATCH: case STATUS_KDC_CERT_EXPIRED: case STATUS_KDC_CERT_REVOKED: //case STATUS_REVOCATION_OFFLINE_S: Wait for ntstatus.h to propagate
//case STATUS_DC_CERT_REVOKED:
case STATUS_PKINIT_CLIENT_FAILURE:
//
// sleep 3 seconds to "discourage" dictionary attacks.
// Don't worry about interactive logon dictionary attacks.
// They will be slow anyway.
//
// per bug 171041, SField, RichardW, CliffV all decided this
// delay has almost zero value for Win2000. Offline attacks at
// sniffed wire traffic are more efficient and viable. Further,
// opimizations in logon code path make failed interactive logons.
// very fast.
//
//
// if (LogonType != Interactive) {
// Sleep( 3000 );
// }
//
// This is for auditing. Make sure to clear it out before
// passing it out of LSA to the caller.
//
*ApiSubStatus = Status; Status = STATUS_LOGON_FAILURE; break;
case STATUS_INVALID_LOGON_HOURS: case STATUS_INVALID_WORKSTATION: case STATUS_PASSWORD_EXPIRED: case STATUS_ACCOUNT_DISABLED: case STATUS_SMARTCARD_LOGON_REQUIRED: *ApiSubStatus = Status; Status = STATUS_ACCOUNT_RESTRICTION; break;
//
// This shouldn't happen, but guard against it anyway.
//
case STATUS_ACCOUNT_RESTRICTION: *ApiSubStatus = STATUS_ACCOUNT_RESTRICTION; break;
case STATUS_ACCOUNT_EXPIRED: // fix 122291
*ApiSubStatus = STATUS_ACCOUNT_EXPIRED; break;
default: break;
}
if (*TokenInformation != NULL) { KerbFree( *TokenInformation ); *TokenInformation = NULL; } KerbFreeString( &PrimaryCredentials->DownlevelName ); KerbFreeString( &PrimaryCredentials->DomainName ); KerbFreeString( &PrimaryCredentials->LogonServer ); KerbFreeString( &PrimaryCredentials->Password ); if (PrimaryCredentials->UserSid != NULL) { KerbFree(PrimaryCredentials->UserSid); PrimaryCredentials->UserSid = NULL; } RtlZeroMemory( PrimaryCredentials, sizeof(SECPKG_PRIMARY_CRED) );
D_DebugLog((DEB_ERROR, "LogonUser returned %x, %x\n", Status, *ApiSubStatus)); }
if (WorkstationTicket != NULL) { KerbDereferenceTicketCacheEntry( WorkstationTicket ); }
if (AsTicket != NULL) { KerbDereferenceTicketCacheEntry( AsTicket ); }
if (LogonSession != NULL) { KerbDereferenceLogonSession(LogonSession); }
KerbFreeKdcName(&ClientName); KerbFreeKdcName(&S4UClientName);
KerbFreeString(&ClientRealm);
KerbFreeString( &DomainName );
KerbFreeString( &MappedClientName );
KerbFreeString( &TempName ); KerbFreeString( &TempAuthority );
KerbFreeKdcName( &MachineServiceName );
KerbFreeString( &S4UClientRealm );
// Allocate the machine name here. Lsa will free it after auditing is
// done
*MachineName = (PUNICODE_STRING) KerbAllocate( sizeof( UNICODE_STRING ) );
if ( *MachineName != NULL ) { NTSTATUS TempStatus;
KerbGlobalReadLock(); TempStatus = KerbDuplicateString (*MachineName, &KerbGlobalMachineName); KerbGlobalReleaseLock();
if(!NT_SUCCESS(TempStatus)) { D_DebugLog((DEB_ERROR, "Failed to duplicate KerbGlobalMachineName\n")); ZeroMemory( *MachineName, sizeof(UNICODE_STRING) ); } }
if( KerbEventTraceFlag ) // Event Trace: KerbLogonUserEnd {Status, (LogonType), (Username), (Domain)}
{ INSERT_ULONG_INTO_MOF( *ApiSubStatus, LogonTraceInfo.MofData, 0 ); LogonTraceInfo.EventTrace.Size = sizeof(EVENT_TRACE_HEADER) + sizeof(MOF_FIELD);
if( LogonInfo != NULL ) { UNICODE_STRING KerbLogonTypeString;
PCWSTR KerbLogonTypeTable[] = // see enum _KERB_LOGON_SUBMIT_TYPE
{ L"",L"", L"KerbInteractiveLogon", // = 2
L"",L"",L"", L"KerbSmartCardLogon", // = 6
L"KerbWorkstationUnlockLogon", L"KerbSmartCardUnlockLogon", L"KerbProxyLogon", L"KerbTicketLogon", L"KerbTicketUnlockLogon" };
RtlInitUnicodeString( &KerbLogonTypeString, KerbLogonTypeTable[LogonInfo->MessageType] );
INSERT_UNICODE_STRING_INTO_MOF( KerbLogonTypeString, LogonTraceInfo.MofData, 1 ); INSERT_UNICODE_STRING_INTO_MOF( LogonInfo->UserName, LogonTraceInfo.MofData, 3 ); INSERT_UNICODE_STRING_INTO_MOF( LogonInfo->LogonDomainName, LogonTraceInfo.MofData, 5 ); LogonTraceInfo.EventTrace.Size += 6*sizeof(MOF_FIELD); }
// Set trace parameters
LogonTraceInfo.EventTrace.Guid = KerbLogonGuid; LogonTraceInfo.EventTrace.Class.Type = EVENT_TRACE_TYPE_END; LogonTraceInfo.EventTrace.Flags = WNODE_FLAG_TRACED_GUID | WNODE_FLAG_USE_MOF_PTR;
TraceEvent( KerbTraceLoggerHandle, (PEVENT_TRACE_HEADER)&LogonTraceInfo ); }
#if _WIN64
if (fAllocatedSubmitBuffer) { KerbFree(pTempSubmitBuffer); }
#endif // _WIN64
return(Status); }
#endif // WIN32_CHICAGO // later
//+-------------------------------------------------------------------------
//
// Function: LsaApLogonTerminated
//
// Synopsis: This routine is called whenever a logon session terminates
// (the last token for it is closed). It dereferences the
// logon session (if it exists), which may cause any
// associated credentials or contexts to be run down.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID LsaApLogonTerminated( IN PLUID LogonId ) { PKERB_LOGON_SESSION LogonSession;
D_DebugLog((DEB_TRACE_LSESS, "LsaApLogonTerminated called: 0x%x:0x%x\n", LogonId->HighPart, LogonId->LowPart ));
LogonSession = KerbReferenceLogonSession( LogonId, TRUE // unlink logon session
); if (LogonSession != NULL) { KerbDereferenceLogonSession(LogonSession); } return; }
#ifdef WIN32_CHICAGO
//+-------------------------------------------------------------------------
//
// Function: KerbSspiLogonUser
//
// Synopsis: Handles service, batch, and interactive logons
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
SECURITY_STATUS KerbSspiLogonUser( IN LPTSTR PackageName, IN LPTSTR UserName, IN LPTSTR DomainName, IN LPTSTR Password ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_LOGON_SESSION LogonSession = NULL; UNICODE_STRING TempName = NULL_UNICODE_STRING; UNICODE_STRING TempAuthority = NULL_UNICODE_STRING; UNICODE_STRING TempPassword = NULL_UNICODE_STRING; UNICODE_STRING KdcServiceName = NULL_UNICODE_STRING; PKERB_INTERNAL_NAME KdcServiceKdcName = NULL; LUID LogonId; PKERB_MIT_REALM MitRealm = NULL; BOOLEAN UsedAlternateName = NULL;
//
// First initialize all the output parameters to NULL.
//
Status = STATUS_SUCCESS; KdcServiceName.Buffer = NULL;
//
// Initialize local pointers to NULL
//
LogonId.LowPart = 0; LogonId.HighPart = 0;
//
// Copy out the user name and Authenticating Authority so we can audit them.
//
// NOTE - Do we need to enforce username & password restrictions here
// or will the redir do the job when we setuid to it?
if ( UserName != NULL ) {
Status = RtlCreateUnicodeStringFromAsciiz( &TempName, UserName); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if ( DomainName != NULL ) {
Status = RtlCreateUnicodeStringFromAsciiz( &TempAuthority, DomainName); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
if ( Password != NULL ) {
Status = RtlCreateUnicodeStringFromAsciiz( &TempPassword, Password); 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)) { D_DebugLog((DEB_ERROR,"Failed to allocate locally unique ID: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
//
// Check to see if the client is from an MIT realm
//
(VOID) KerbLookupMitRealm( &TempAuthority, &MitRealm, &UsedAlternateName );
if ((MitRealm != NULL) && UsedAlternateName) { KerbFreeString(&TempAuthority); Status = KerbDuplicateString( &TempAuthority, &MitRealm->RealmName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
// For win95, if there is a logon session in our list, remove it.
// This is generated from the logon session dumped in the registry.
// But, we are about to do a new logon. Get rid of the old logon.
// If the new one does not succeed, too bad. But, that's by design.
LsaApLogonTerminated(&LogonId);
//
// Build a logon session to hold all this information
//
Status = KerbCreateLogonSession( &LogonId, &TempName, &TempAuthority, &TempPassword, NULL, // no old password
PRIMARY_CRED_CLEAR_PASSWORD, 0, Network, FALSE, &LogonSession ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
D_DebugLog((DEB_TRACE, "KerbSspiLogonUser attempting to logon user %wZ\\%wZ\n", &TempAuthority, &TempName ));
//
// Now the real work of logon begins - getting a TGT and then a ticket
// to this machine.
//
if (!KERB_SUCCESS(KerbBuildFullServiceKdcName( &TempAuthority, &KerbGlobalKdcServiceName, KRB_NT_MS_PRINCIPAL, &KdcServiceKdcName )))
{ Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Status = KerbGetTicketGrantingTicket( LogonSession, NULL, NULL, NULL, // no credential
NULL, // don't return ticket cache entry
NULL // don't return credential key
);
if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN, "LogonUser: Failed to get authentication ticket for %wZ\\%wZ to %wZ: 0x%x\n", &TempAuthority, &TempName, &KdcServiceName, Status )); goto Cleanup;
}
KerbWriteLockLogonSessions(LogonSession); LogonSession->LogonSessionFlags &= ~KERB_LOGON_DEFERRED; KerbUnlockLogonSessions(LogonSession);
Cleanup:
if (!NT_SUCCESS(Status)) { //
// Unlink the new logon session
//
if (LogonSession != NULL) { KerbReferenceLogonSessionByPointer(LogonSession, TRUE); KerbDereferenceLogonSession(LogonSession); }
//
// Map status codes to prevent specific information from being
// released about this user.
//
switch (Status) { case STATUS_WRONG_PASSWORD: case STATUS_NO_SUCH_USER:
//
// This is for auditing. Make sure to clear it out before
// passing it out of LSA to the caller.
//
Status = STATUS_LOGON_FAILURE; break;
case STATUS_INVALID_LOGON_HOURS: case STATUS_INVALID_WORKSTATION: case STATUS_PASSWORD_EXPIRED: case STATUS_ACCOUNT_DISABLED: case STATUS_ACCOUNT_EXPIRED: Status = STATUS_ACCOUNT_RESTRICTION; break;
//
// This shouldn't happen, but guard against it anyway.
//
case STATUS_ACCOUNT_RESTRICTION: Status = STATUS_ACCOUNT_RESTRICTION; break;
default: break;
}
}
if (LogonSession != NULL) { KerbDereferenceLogonSession(LogonSession); }
KerbFreeString( &TempName ); KerbFreeString( &TempAuthority ); KerbFreeString( &KdcServiceName );
KerbFreeKdcName( &KdcServiceKdcName ); D_DebugLog((DEB_TRACE, "SspiLogonUser: returns 0x%x\n", Status)); return(Status); }
#endif // WIN32_CHICAGO
|