Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5439 lines
159 KiB

//+-----------------------------------------------------------------------
//
// 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