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.
 
 
 
 
 
 

2990 lines
79 KiB

//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1996
//
// File: logonses.cxx
//
// Contents: Code for managing the global list of logon sessions
//
//
// History: 16-April-1996 Created MikeSw
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#define LOGONSES_ALLOCATE
#include <kerbp.h>
#ifdef DEBUG_SUPPORT
static TCHAR THIS_FILE[]=TEXT(__FILE__);
#endif
LONG LogonSessionCount;
//+-------------------------------------------------------------------------
//
// Function: KerbInitLogonSessionList
//
// Synopsis: Initializes logon session list
//
// Effects: allocates a resources
//
// Arguments: none
//
// Requires:
//
// Returns: STATUS_SUCCESS on success, other error codes
// on failure
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbInitLogonSessionList(
VOID
)
{
NTSTATUS Status;
Status = KerbInitializeList( &KerbLogonSessionList, LS_LIST_LOCK_ENUM );
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
KerberosLogonSessionsInitialized = TRUE;
Cleanup:
if (!NT_SUCCESS(Status))
{
KerbFreeList( &KerbLogonSessionList);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbInitLoopbackDetection
//
// Synopsis: Initialize the network service session loopback detection
//
// Effects: Allocates a resources
//
// Arguments: none
//
// Requires:
//
// Returns: STATUS_SUCCESS on success, other error codes on failure
//
// Notes:
//
//--------------------------------------------------------------------------
NTSTATUS
KerbInitLoopbackDetection(
VOID
)
{
NTSTATUS Status = STATUS_SUCCESS;
InitializeListHead(&KerbSKeyList);
#if DBG
KerbcSKeyEntries = 0;
#endif
KerbhSKeyTimerQueue = NULL;
__try
{
SafeInitializeResource(&KerbSKeyLock, LOCAL_LOOPBACK_SKEY_LOCK);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbFreeSKeyListAndLock
//
// Synopsis: Free the network service session key list and its lock
//
// Effects: Frees a resources
//
// Arguments: none
//
// Requires:
//
// Returns:
//
// Notes:
//
//--------------------------------------------------------------------------
VOID
KerbFreeSKeyListAndLock(
VOID
)
{
if (SafeAcquireResourceExclusive(&KerbSKeyLock, TRUE))
{
for (LIST_ENTRY* pListEntry = KerbSKeyList.Flink;
pListEntry != &KerbSKeyList;
pListEntry = pListEntry->Flink)
{
KERB_SESSION_KEY_ENTRY* pSKeyEntry = CONTAINING_RECORD(pListEntry, KERB_SESSION_KEY_ENTRY, ListEntry);
KerbFreeSKeyEntry(pSKeyEntry);
}
SafeReleaseResource(&KerbSKeyLock);
}
SafeDeleteResource(&KerbSKeyLock);
}
//+-------------------------------------------------------------------------
//
// Function: KerbFreeLogonSessionList
//
// Synopsis: Frees the logon session list
//
// Effects:
//
// Arguments: none
//
// Requires:
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbFreeLogonSessionList(
VOID
)
{
PKERB_LOGON_SESSION LogonSession;
if (KerberosLogonSessionsInitialized)
{
KerbLockList(&KerbLogonSessionList);
//
// Go through the list of logon sessions and dereferences them all
//
while (!IsListEmpty(&KerbLogonSessionList.List))
{
LogonSession = CONTAINING_RECORD(
KerbLogonSessionList.List.Flink,
KERB_LOGON_SESSION,
ListEntry.Next
);
KerbReferenceListEntry(
&KerbLogonSessionList,
&LogonSession->ListEntry,
TRUE
);
KerbDereferenceLogonSession(LogonSession);
}
KerbFreeList(&KerbLogonSessionList);
}
}
//+-------------------------------------------------------------------------
//
// Function: KerbAllocateLogonSession
//
// Synopsis: Allocates a logon session structure
//
// Effects: Allocates a logon session, but does not add it to the
// list of logon sessions
//
// Arguments: NewLogonSession - receives a new logon session allocated
// with KerbAllocate
//
// Requires:
//
// Returns: STATUS_SUCCESS on success
// STATUS_INSUFFICIENT_RESOURCES if the allocation fails
//
// Notes:
//
//
//--------------------------------------------------------------------------
// Init flags
#define LSI_EXTRA_CREDS 0x1
#define LSI_SESSION_LOCK 0x2
#define LSI_CREDMAN 0x4
NTSTATUS
KerbAllocateLogonSession(
PKERB_LOGON_SESSION * NewLogonSession
)
{
NTSTATUS Status = STATUS_SUCCESS;
PKERB_LOGON_SESSION LogonSession;
ULONG InitStatus = 0;
LogonSession = (PKERB_LOGON_SESSION) KerbAllocate(
sizeof(KERB_LOGON_SESSION)
);
if (LogonSession == NULL)
{
return(STATUS_INSUFFICIENT_RESOURCES);
}
//
// Set the references to 1 since we are returning a pointer to the
// logon session
//
KerbInitializeListEntry(
&LogonSession->ListEntry
);
//
// Initialize the ticket caches
//
KerbInitTicketCache(&LogonSession->PrimaryCredentials.ServerTicketCache);
KerbInitTicketCache(&LogonSession->PrimaryCredentials.AuthenticationTicketCache);
KerbInitTicketCache(&LogonSession->PrimaryCredentials.S4UTicketCache);
Status = KerbInitializeList(
&LogonSession->ExtraCredentials.CredList,
LS_EXTRACRED_LOCK_ENUM
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
InitStatus |= LSI_EXTRA_CREDS;
Status = SafeInitializeCriticalSection(
&LogonSession->Lock,
LOGON_SESSION_LOCK_ENUM
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
InitStatus |= LSI_SESSION_LOCK;
Status = KerbInitializeList(
&LogonSession->CredmanCredentials,
LS_CREDMAN_LOCK_ENUM
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
InitStatus |= LSI_CREDMAN;
*NewLogonSession = LogonSession;
Cleanup:
if (!NT_SUCCESS(Status))
{
if (InitStatus & LSI_EXTRA_CREDS)
{
SafeDeleteCriticalSection(&LogonSession->ExtraCredentials.CredList.Lock);
}
if (InitStatus & LSI_SESSION_LOCK)
{
SafeDeleteCriticalSection(&LogonSession->Lock);
}
if (InitStatus & LSI_CREDMAN)
{
SafeDeleteCriticalSection(&LogonSession->CredmanCredentials.Lock);
}
KerbFree(LogonSession);
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbInsertLogonSession
//
// Synopsis: Inserts a logon session into the list of logon sessions
//
// Effects: bumps reference count on logon session
//
// Arguments: LogonSession - LogonSession to insert
//
// Requires:
//
// Returns: STATUS_SUCCESS always
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbInsertLogonSession(
IN PKERB_LOGON_SESSION LogonSession
)
{
KerbInsertListEntry(
&LogonSession->ListEntry,
&KerbLogonSessionList
);
#if DBG
InterlockedIncrement(&LogonSessionCount);
#endif
return(STATUS_SUCCESS);
}
//+-------------------------------------------------------------------------
//
// Function: KerbReferenceLogonSession
//
// Synopsis: Locates a logon session from the logon ID and references it
//
// Effects: Increments reference count and possible unlinks it from list
//
// Arguments: LogonId - LogonId of logon session to locate
// RemoveFromList - If TRUE, logon session will be delinked
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
PKERB_LOGON_SESSION
KerbReferenceLogonSession(
IN PLUID LogonId,
IN BOOLEAN RemoveFromList
)
{
PLIST_ENTRY ListEntry;
PKERB_LOGON_SESSION LogonSession = NULL;
BOOLEAN Found = FALSE;
KerbLockList(&KerbLogonSessionList);
//
// Go through the list of logon sessions looking for the correct
// LUID
//
for (ListEntry = KerbLogonSessionList.List.Flink ;
ListEntry != &KerbLogonSessionList.List ;
ListEntry = ListEntry->Flink )
{
LogonSession = CONTAINING_RECORD(ListEntry, KERB_LOGON_SESSION, ListEntry.Next);
if (RtlEqualLuid(
&LogonSession->LogonId,
LogonId
) )
{
D_DebugLog((DEB_TRACE_LSESS, "Referencing session 0x%x:0x%x, Remove=%d\n",
LogonSession->LogonId.HighPart,
LogonSession->LogonId.LowPart,
RemoveFromList
));
KerbReferenceListEntry(
&KerbLogonSessionList,
&LogonSession->ListEntry,
RemoveFromList
);
Found = TRUE;
break;
}
}
if (!Found)
{
LogonSession = NULL;
}
KerbUnlockList(&KerbLogonSessionList);
return(LogonSession);
}
//+-------------------------------------------------------------------------
//
// Function: KerbFreeLogonSession
//
// Synopsis: Frees a logon session and all associated data
//
// Effects:
//
// Arguments: LogonSession
//
// Requires: the logon session must already be unlinked
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbFreeLogonSession(
IN PKERB_LOGON_SESSION LogonSession
)
{
DsysAssert((LogonSession->ListEntry.Next.Flink == NULL) &&
(LogonSession->ListEntry.Next.Blink == NULL));
// Don't purge creds, as there isn't a ref-count for the credential in
// the logon session list,and there might be outstanding handles to your
// credentials in a local system process.
//KerbPurgeCredentials(&LogonSession->SspCredentials);
KerbPurgeTicketCache(&LogonSession->PrimaryCredentials.ServerTicketCache);
KerbPurgeTicketCache(&LogonSession->PrimaryCredentials.AuthenticationTicketCache);
KerbPurgeTicketCache(&LogonSession->PrimaryCredentials.S4UTicketCache);
if (LogonSession->PrimaryCredentials.Passwords != NULL)
{
KerbFreeStoredCred(LogonSession->PrimaryCredentials.Passwords);
}
if (LogonSession->PrimaryCredentials.OldPasswords != NULL)
{
KerbFreeStoredCred(LogonSession->PrimaryCredentials.OldPasswords);
}
KerbFreeString(&LogonSession->PrimaryCredentials.UserName);
KerbFreeString(&LogonSession->PrimaryCredentials.DomainName);
if (LogonSession->PrimaryCredentials.ClearPassword.Buffer != NULL)
{
RtlZeroMemory(
LogonSession->PrimaryCredentials.ClearPassword.Buffer,
LogonSession->PrimaryCredentials.ClearPassword.Length
);
KerbFreeString(&LogonSession->PrimaryCredentials.ClearPassword);
}
KerbFreeExtraCredList(&LogonSession->ExtraCredentials);
if (LogonSession->PrimaryCredentials.PublicKeyCreds != NULL)
{
#ifndef WIN32_CHICAGO
KerbReleasePkCreds(
LogonSession,
NULL,
FALSE
);
#endif // WIN32_CHICAGO
}
SafeDeleteCriticalSection(&LogonSession->CredmanCredentials.Lock);
SafeDeleteCriticalSection(&LogonSession->ExtraCredentials.CredList.Lock);
SafeDeleteCriticalSection(&LogonSession->Lock);
KerbFree(LogonSession);
}
//+-------------------------------------------------------------------------
//
// Function: KerbDereferenceLogonSession
//
// Synopsis: Dereferences a logon session - if reference count goes
// to zero it frees the logon session
//
// Effects: decrements reference count
//
// Arguments: LogonSession - Logon session to dereference
//
// Requires:
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbDereferenceLogonSession(
IN PKERB_LOGON_SESSION LogonSession
)
{
if (KerbDereferenceListEntry(
&LogonSession->ListEntry,
&KerbLogonSessionList
) )
{
D_DebugLog((DEB_TRACE_LSESS, "Dereferencing and freeing logon session 0x%x:0x%x\n",
LogonSession->LogonId.HighPart,
LogonSession->LogonId.HighPart
));
KerbFreeLogonSession( LogonSession );
#if DBG
InterlockedDecrement(&LogonSessionCount);
#endif
}
}
//+-------------------------------------------------------------------------
//
// Function: KerbReferenceLogonSessionByPointer
//
// Synopsis: References a LogonSession by the LogonSession pointer itself.
//
// Effects: Increments reference count and possible unlinks it from list
//
// Arguments: LogonSession - The LogonSession to reference.
// RemoveFromList - If TRUE, LogonSession will be delinked
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbReferenceLogonSessionByPointer(
IN PKERB_LOGON_SESSION LogonSession,
IN BOOLEAN RemoveFromList
)
{
KerbLockList(&KerbLogonSessionList);
KerbReferenceListEntry(
&KerbLogonSessionList,
&LogonSession->ListEntry,
RemoveFromList
);
KerbUnlockList(&KerbLogonSessionList);
}
//+-------------------------------------------------------------------------
//
// Function: KerbGetSaltForEtype
//
// Synopsis: Looks in the list of salt for an etype & returns it if found
//
// Effects: Allocate the output string
//
// Arguments: EncryptionType - etype searched for
// EtypeInfo - List of etypes
// DefaultSalt - salt to use if none provided
// Salt - receives the salt to use. On error, no key should be
// generated.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbGetSaltForEtype(
IN ULONG EncryptionType,
IN OPTIONAL PKERB_ETYPE_INFO EtypeInfo,
IN OPTIONAL PKERB_STORED_CREDENTIAL PasswordList,
IN PUNICODE_STRING DefaultSalt,
OUT PUNICODE_STRING SaltToUse
)
{
PKERB_ETYPE_INFO ListEntry;
STRING TempString;
//
// If there is no etype, just use the default
//
if (EtypeInfo == NULL)
{
//
// If we have a password list, get the salt from that.
//
if (ARGUMENT_PRESENT(PasswordList))
{
ULONG Index;
for (Index = 0; Index < PasswordList->CredentialCount ; Index++ )
{
if (PasswordList->Credentials[Index].Key.keytype == (int) EncryptionType)
{
if (PasswordList->Credentials[Index].Salt.Buffer != NULL)
{
return(KerbDuplicateString(
SaltToUse,
&PasswordList->Credentials[Index].Salt
));
}
else if (PasswordList->DefaultSalt.Buffer != NULL)
{
return(KerbDuplicateString(
SaltToUse,
&PasswordList->DefaultSalt
));
}
break;
}
}
}
//
// otherise return the default
//
return(KerbDuplicateString(
SaltToUse,
DefaultSalt
));
}
//
// Otherwise, only return salt if the etype is in the list.
//
for (ListEntry = EtypeInfo; ListEntry != NULL ; ListEntry = ListEntry->next )
{
//
// First check for the encryption type we want.
//
if (ListEntry->value.encryption_type == (int) EncryptionType)
{
//
// if it has salt, return that.
//
if ((ListEntry->value.bit_mask & salt_present) != 0)
{
KERBERR KerbErr;
TempString.Buffer = (PCHAR) ListEntry->value.salt.value;
TempString.Length = (USHORT) ListEntry->value.salt.length;
TempString.MaximumLength = (USHORT) ListEntry->value.salt.length;
//
// validate inputs don't overflow USHORT for MaximumLength.
//
if ( TempString.Length > KERB_MAX_STRING )
{
return STATUS_NAME_TOO_LONG;
}
KerbErr = KerbStringToUnicodeString(
SaltToUse,
&TempString
);
return(KerbMapKerbError(KerbErr));
}
else
{
//
// Otherwise return the default
//
return(KerbDuplicateString(
SaltToUse,
DefaultSalt
));
}
}
}
return(STATUS_OBJECT_NAME_NOT_FOUND);
}
//+-------------------------------------------------------------------------
//
// Function: KerbFreeStoredCred
//
// Synopsis: Frees a KERB_STORED_CREDENTIAL
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbFreeStoredCred(
IN PKERB_STORED_CREDENTIAL StoredCred
)
{
USHORT Index;
for (Index = 0; Index < StoredCred->CredentialCount + StoredCred->OldCredentialCount ; Index++ )
{
if (StoredCred->Credentials[Index].Salt.Buffer != NULL)
{
KerbFreeString(&StoredCred->Credentials[Index].Salt);
}
}
KerbFree(StoredCred);
}
//+-------------------------------------------------------------------------
//
// Function: KerbBuildPasswordList
//
// Synopsis: Builds a list of passwords for a logged on user
//
// Effects: allocates memory
//
// Arguments: Password - clear or OWF password
// PasswordFlags - Indicates whether the password is clear or OWF
// PasswordList - Receives new password list
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbBuildPasswordList(
IN PUNICODE_STRING Password,
IN PUNICODE_STRING UserName,
IN PUNICODE_STRING DomainName,
IN PKERB_ETYPE_INFO SuppliedSalt,
IN PKERB_STORED_CREDENTIAL OldPasswords,
IN OPTIONAL PUNICODE_STRING PrincipalName,
IN KERB_ACCOUNT_TYPE AccountType,
IN ULONG PasswordFlags,
OUT PKERB_STORED_CREDENTIAL * PasswordList
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG CryptTypes[KERB_MAX_CRYPTO_SYSTEMS];
ULONG CryptCount = 0 ;
PKERB_STORED_CREDENTIAL Credentials = NULL;
UNICODE_STRING KeySalt = {0};
UNICODE_STRING DefaultSalt = {0};
ULONG CredentialSize = 0;
ULONG CredentialCount = 0;
PCRYPTO_SYSTEM CryptoSystem;
ULONG Index, CredentialIndex = 0;
PUCHAR Base;
ULONG Offset;
KERBERR KerbErr;
*PasswordList = NULL;
//
// If we were passed an OWF, then there is just one password
//
if ((PasswordFlags & PRIMARY_CRED_OWF_PASSWORD) != 0)
{
CredentialSize += Password->Length + sizeof(KERB_KEY_DATA);
CredentialCount++;
#ifndef DONT_SUPPORT_OLD_TYPES
CredentialSize += Password->Length + sizeof(KERB_KEY_DATA);
CredentialCount++;
#endif
}
else if ((PasswordFlags & PRIMARY_CRED_CLEAR_PASSWORD) != 0)
{
//
// Build the key salt.
//
KerbErr = KerbBuildKeySalt(
DomainName,
(ARGUMENT_PRESENT(PrincipalName) && PrincipalName->Length != 0) ? PrincipalName : UserName,
(ARGUMENT_PRESENT(PrincipalName) && PrincipalName->Length != 0) ? UnknownAccount : AccountType,
&DefaultSalt
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_ERROR, "Can't build salt. Might as well fail here\n"));
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
//
// For a cleartext password, build a list of encryption types and
// create a key for each one
//
Status = CDBuildIntegrityVect(
&CryptCount,
CryptTypes
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Can't build a list of encryption types: 0x%x.\n",Status));
goto Cleanup;
}
DsysAssert(CryptCount <= KERB_MAX_CRYPTO_SYSTEMS);
//
// Now find the size of the key for each crypto system
//
for (Index = 0; Index < CryptCount; Index++ )
{
Status = CDLocateCSystem(
CryptTypes[Index],
&CryptoSystem
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Failed to load %d crypt system: 0x%x.\n",CryptTypes[Index],Status));
goto Cleanup;
}
if (((CryptoSystem->Attributes & CSYSTEM_USE_PRINCIPAL_NAME) == 0) ||
(DefaultSalt.Buffer != NULL ))
{
CredentialSize += sizeof(KERB_KEY_DATA) + CryptoSystem->KeySize;
CredentialCount++;
}
}
}
else
{
//
// No flags set, so nothing we can do.
//
D_DebugLog((DEB_TRACE, "KerbBuildPasswordList assword passed but no flags set, do nothing\n"));
return(STATUS_SUCCESS);
}
#ifdef notdef
//
// Add the space for the salt
//
CredentialSize += DefaultSalt.Length;
#endif
//
// Add in the size of the base structure
//
CredentialSize += FIELD_OFFSET(KERB_STORED_CREDENTIAL,Credentials);
Credentials = (PKERB_STORED_CREDENTIAL) KerbAllocate(CredentialSize);
if (Credentials == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
//
// Fill in the base structure
//
Credentials->Revision = KERB_PRIMARY_CRED_REVISION;
Credentials->Flags = 0;
Credentials->OldCredentialCount = 0;
//
// Now fill in the individual keys
//
Base = (PUCHAR) Credentials;
Offset = FIELD_OFFSET(KERB_STORED_CREDENTIAL,Credentials) +
CredentialCount * sizeof(KERB_KEY_DATA);
#ifdef notdef
//
// Add the default salt
//
Credentials->DefaultSalt = DefaultSalt;
Credentials->DefaultSalt.Buffer = (LPWSTR) Base+Offset;
RtlCopyMemory(
Base + Offset,
DefaultSalt.Buffer,
DefaultSalt.Length
);
Offset += Credentials->DefaultSalt.Length;
#endif
if ((PasswordFlags & PRIMARY_CRED_OWF_PASSWORD) != 0)
{
RtlCopyMemory(
Base + Offset,
Password->Buffer,
Password->Length
);
if (!KERB_SUCCESS(KerbCreateKeyFromBuffer(
&Credentials->Credentials[CredentialIndex].Key,
Base + Offset,
Password->Length,
KERB_ETYPE_RC4_HMAC_NT
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
Base += Password->Length;
CredentialIndex++;
#ifndef DONT_SUPPORT_OLD_TYPES
RtlCopyMemory(
Base + Offset,
Password->Buffer,
Password->Length
);
if (!KERB_SUCCESS(KerbCreateKeyFromBuffer(
&Credentials->Credentials[CredentialIndex].Key,
Base + Offset,
Password->Length,
(ULONG) KERB_ETYPE_RC4_HMAC_OLD
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
Base += Password->Length;
CredentialIndex++;
#endif
}
else if ((PasswordFlags & PRIMARY_CRED_CLEAR_PASSWORD) != 0)
{
KERB_ENCRYPTION_KEY TempKey = {0};
//
// Now find the size of the key for each crypto system
//
for (Index = 0; Index < CryptCount; Index++ )
{
CryptoSystem = NULL;
Status = CDLocateCSystem(
CryptTypes[Index],
&CryptoSystem
);
if (!NT_SUCCESS(Status))
{
Status = STATUS_SUCCESS;
continue;
}
KerbFreeString(&KeySalt);
Status = KerbGetSaltForEtype(
CryptTypes[Index],
SuppliedSalt,
OldPasswords,
&DefaultSalt,
&KeySalt
);
if (!NT_SUCCESS(Status))
{
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND)
{
Status = STATUS_SUCCESS;
continue;
}
goto Cleanup;
}
//
// If we don't have salt, skip this crypt system
//
if (((CryptoSystem->Attributes & CSYSTEM_USE_PRINCIPAL_NAME) != 0) &&
(KeySalt.Buffer == NULL ))
{
continue;
}
KerbErr = KerbHashPasswordEx(
Password,
&KeySalt,
CryptTypes[Index],
&TempKey
);
if (!KERB_SUCCESS(KerbErr))
{
//
// It is possible that the password can't be used for every
// encryption scheme, so skip failures
//
D_DebugLog((DEB_WARN, "Failed to hash pasword %wZ with type 0x%x\n",
Password,CryptTypes[Index] ));
if ( CryptTypes[Index] != KERB_ETYPE_RC4_HMAC_NT ) // RC4_HMAC_NT should not fail
{
Status = STATUS_SUCCESS;
continue;
}
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
DsysAssert(CryptoSystem->KeySize >= TempKey.keyvalue.length);
//
// Copy the salt and key data into the credentials
//
Credentials->Credentials[CredentialIndex].Salt = KeySalt;
RtlInitUnicodeString(
&KeySalt,
0
);
Credentials->Credentials[CredentialIndex].Key = TempKey;
Credentials->Credentials[CredentialIndex].Key.keyvalue.value = Base + Offset;
RtlCopyMemory(
Base + Offset,
TempKey.keyvalue.value,
TempKey.keyvalue.length
);
Offset += TempKey.keyvalue.length;
KerbFreeKey(
&TempKey
);
CredentialIndex++;
}
}
Credentials->CredentialCount = (USHORT) CredentialIndex;
*PasswordList = Credentials;
Credentials = NULL;
Cleanup:
if (Credentials != NULL)
{
KerbFreeStoredCred(Credentials);
}
KerbFreeString(&KeySalt);
KerbFreeString(&DefaultSalt);
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbReplacePasswords
//
// Synopsis: Validate supplied credentials == default credentials in special
// case.
//
// Effects: Replaces passwords if they're different than defaulted ones.
//
// Arguments: Current - KERB_CREDENTIAL passwords
// New - Default logon session passwords
//
// Requires:
//
// Returns: STATUS_SUCCESS on success
//
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbReplacePasswords(
IN PKERB_PRIMARY_CREDENTIAL Current,
IN PKERB_PRIMARY_CREDENTIAL New
)
{
PKERB_ENCRYPTION_KEY CurrentKey = NULL;
PKERB_ENCRYPTION_KEY NewKey = NULL;
PKERB_STORED_CREDENTIAL NewPasswords = NULL;
ULONG PasswordSize = 0;
ULONG Index;
PBYTE Where;
DsysAssert(Current->Passwords);
DsysAssert(New->Passwords);
if ( Current->Passwords != NULL )
{
//
// First off, compare the RC4 HMAC OWF - if its equal, then the other keys are equal.
// Note - for DES / AES, this might not be the case, but assume that will be fixed up
// when we re-derive the pwd based on etype info.
//
CurrentKey = KerbGetKeyFromList( Current->Passwords, KERB_ETYPE_RC4_HMAC_NT );
if (CurrentKey == NULL )
{
return STATUS_SUCCESS;
}
NewKey = KerbGetKeyFromList( New->Passwords, KERB_ETYPE_RC4_HMAC_NT );
if ( NewKey == NULL )
{
return STATUS_SUCCESS;
}
DsysAssert( CurrentKey->keyvalue.length == NewKey->keyvalue.length );
if (RtlEqualMemory( CurrentKey->keyvalue.value, NewKey->keyvalue.value, NewKey->keyvalue.length ))
{
return STATUS_SUCCESS;
}
}
DebugLog((DEB_ERROR, "Replacing keys\n"));
//
// Walk the credentials, and whackem.
//
PasswordSize = sizeof(KERB_STORED_CREDENTIAL) - (sizeof(KERB_KEY_DATA) * ANYSIZE_ARRAY) + (New->Passwords->CredentialCount * sizeof(KERB_KEY_DATA));
for (Index = 0; Index < New->Passwords->CredentialCount ; Index++ )
{
PasswordSize += New->Passwords->Credentials[Index].Key.keyvalue.length;
}
NewPasswords = (PKERB_STORED_CREDENTIAL) KerbAllocate(PasswordSize);
if (NewPasswords == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
NewPasswords->Revision = KERB_PRIMARY_CRED_REVISION;
NewPasswords->Flags = 0;
NewPasswords->OldCredentialCount = 0;
//
// Zero the salt so we don't accidentally re-use it.
//
RtlInitUnicodeString(
&NewPasswords->DefaultSalt,
NULL
);
NewPasswords->CredentialCount = New->Passwords->CredentialCount;
Where = (PUCHAR) &NewPasswords->Credentials[NewPasswords->CredentialCount];
//
// Copy all the old passwords.
//
for (Index = 0;
Index < (USHORT) (NewPasswords->CredentialCount) ;
Index++ )
{
RtlInitUnicodeString(
&NewPasswords->Credentials[Index].Salt,
NULL
);
NewPasswords->Credentials[Index].Key = New->Passwords->Credentials[Index].Key;
NewPasswords->Credentials[Index].Key.keyvalue.value = Where;
RtlCopyMemory(
Where,
New->Passwords->Credentials[Index].Key.keyvalue.value,
New->Passwords->Credentials[Index].Key.keyvalue.length
);
Where += NewPasswords->Credentials[Index].Key.keyvalue.length;
}
if ( Current->Passwords != NULL )
{
KerbFreeStoredCred( Current->Passwords );
}
Current->Passwords = NewPasswords;
return STATUS_SUCCESS;
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreatePrimaryCredentials
//
// Synopsis: Fills in a new primary credentials structure
//
// Effects: allocates space for the user name and domain name
//
// Arguments: AccountName - Account name of this user
// DomainName - domain name of this user
// Password - Optionally contains a kereros hash of the password
// Note: if present, it is used and zeroed out.
// PrimaryCredentials - contains structure to fill in.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreatePrimaryCredentials(
IN PUNICODE_STRING AccountName,
IN PUNICODE_STRING DomainName,
IN OPTIONAL PUNICODE_STRING Password,
IN OPTIONAL PUNICODE_STRING OldPassword,
IN ULONG PasswordFlags,
IN PLUID LogonId,
OUT PKERB_PRIMARY_CREDENTIAL PrimaryCredentials
)
{
NTSTATUS Status;
LUID SystemLogonId = SYSTEM_LUID;
LUID NetworkServiceLogonId = NETWORKSERVICE_LUID;
BOOLEAN IsMachineAccountLogon = FALSE;
BOOLEAN IsPersonal = KerbRunningPersonal();
//
// We can only accept account name / service name / pwds of max_unicode_length
// -1, because we NULL terminate these for later DES derivation, and the
// input buffers from LogonUser may not be NULL terminated.
//
if ((AccountName->Length > KERB_MAX_UNICODE_STRING) ||
(DomainName->Length > KERB_MAX_UNICODE_STRING))
{
return (STATUS_NAME_TOO_LONG);
}
if ((ARGUMENT_PRESENT(Password) && (Password->Length > KERB_MAX_UNICODE_STRING)) ||
(ARGUMENT_PRESENT(OldPassword) && (OldPassword->Length > KERB_MAX_UNICODE_STRING)))
{
return (STATUS_NAME_TOO_LONG);
}
if (RtlEqualLuid(
&SystemLogonId,
LogonId)
|| RtlEqualLuid(
&NetworkServiceLogonId,
LogonId))
{
IsMachineAccountLogon = TRUE;
}
Status = KerbDuplicateString(
&PrimaryCredentials->UserName,
AccountName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// The system logon always comes in uppercase, so lowercase it.
//
if (IsMachineAccountLogon)
{
RtlDowncaseUnicodeString(
&PrimaryCredentials->UserName,
&PrimaryCredentials->UserName,
FALSE
);
}
Status = KerbDuplicateString(
&PrimaryCredentials->DomainName,
DomainName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// Neuter personal so it can't act as a server,
// even if someone has hacked in a machine pwd.
//
if (IsMachineAccountLogon && IsPersonal)
{
PrimaryCredentials->Passwords = NULL;
PrimaryCredentials->OldPasswords = NULL;
D_DebugLog((DEB_WARN, "Running personal - No kerberos for SYSTEM LUID\n"));
}
else
{
Status = KerbBuildPasswordList(
Password,
&PrimaryCredentials->UserName,
&PrimaryCredentials->DomainName,
NULL, // no supplied salt
NULL, // no old paswords
NULL, // no principal name
IsMachineAccountLogon ? MachineAccount : UserAccount,
PasswordFlags,
&PrimaryCredentials->Passwords
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (ARGUMENT_PRESENT(OldPassword) && (OldPassword->Buffer != NULL))
{
Status = KerbBuildPasswordList(
OldPassword,
&PrimaryCredentials->UserName,
&PrimaryCredentials->DomainName,
NULL, // no supplied salt
PrimaryCredentials->Passwords,
NULL, // no principal name
IsMachineAccountLogon ? MachineAccount : UserAccount,
PasswordFlags,
&PrimaryCredentials->OldPasswords
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
//
// Store the clear password if necessary
//
if ((PasswordFlags & PRIMARY_CRED_CLEAR_PASSWORD) != 0)
{
Status = KerbDuplicatePassword(
&PrimaryCredentials->ClearPassword,
Password
);
if (NT_SUCCESS(Status))
{
KerbHidePassword(
&PrimaryCredentials->ClearPassword
);
}
}
}
Cleanup:
if (!NT_SUCCESS(Status))
{
KerbFreeString(&PrimaryCredentials->UserName);
KerbFreeString(&PrimaryCredentials->DomainName);
if (PrimaryCredentials->ClearPassword.Buffer != NULL)
{
RtlZeroMemory(
PrimaryCredentials->ClearPassword.Buffer,
PrimaryCredentials->ClearPassword.Length
);
KerbFreeString(&PrimaryCredentials->ClearPassword);
}
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbChangeCredentialsPassword
//
// Synopsis: Changes the password for a KERB_PRIMARY_CREDENTIALS -
// copies the current password into the old password field
// and then sets the new pasword as the primary password.
// If no new password is provided, it just fixes up the salt.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbChangeCredentialsPassword(
IN PKERB_PRIMARY_CREDENTIAL PrimaryCredentials,
IN OPTIONAL PUNICODE_STRING NewPassword,
IN OPTIONAL PKERB_ETYPE_INFO EtypeInfo,
IN KERB_ACCOUNT_TYPE AccountType,
IN ULONG PasswordFlags
)
{
NTSTATUS Status = STATUS_SUCCESS;
PKERB_STORED_CREDENTIAL Passwords = NULL;
//
// LogonSession no password was supplied, use the cleartext password
//
if (!ARGUMENT_PRESENT(NewPassword) && (PrimaryCredentials->ClearPassword.Buffer == NULL))
{
D_DebugLog((DEB_ERROR,"Can't change password without new password\n"));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if (PrimaryCredentials->ClearPassword.Buffer != NULL)
{
KerbRevealPassword(&PrimaryCredentials->ClearPassword);
}
Status = KerbBuildPasswordList(
(ARGUMENT_PRESENT(NewPassword) ? NewPassword : &PrimaryCredentials->ClearPassword),
&PrimaryCredentials->UserName,
&PrimaryCredentials->DomainName,
EtypeInfo,
PrimaryCredentials->Passwords,
NULL, // no principal name
AccountType,
PasswordFlags,
&Passwords
);
if (!NT_SUCCESS(Status))
{
if (PrimaryCredentials->ClearPassword.Buffer != NULL)
{
KerbHidePassword(&PrimaryCredentials->ClearPassword);
}
}
else if ((PasswordFlags & PRIMARY_CRED_CLEAR_PASSWORD) != 0)
{
KerbFreeString(&PrimaryCredentials->ClearPassword);
Status = KerbDuplicatePassword(
&PrimaryCredentials->ClearPassword,
NewPassword
);
if (NT_SUCCESS(Status))
{
KerbHidePassword(
&PrimaryCredentials->ClearPassword
);
}
}
else
{
KerbHidePassword(
&PrimaryCredentials->ClearPassword
);
}
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// Move the current password to the old password
//
if (ARGUMENT_PRESENT(NewPassword))
{
if (PrimaryCredentials->OldPasswords != NULL)
{
KerbFreeStoredCred(PrimaryCredentials->OldPasswords);
}
PrimaryCredentials->OldPasswords = PrimaryCredentials->Passwords;
}
else
{
KerbFreeStoredCred(PrimaryCredentials->Passwords);
}
PrimaryCredentials->Passwords = Passwords;
Passwords = NULL;
Cleanup:
if (Passwords != NULL)
{
MIDL_user_free(Passwords);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreateLogonSession
//
// Synopsis: Allocates a logon session, fills in the various fields,
// and inserts it on the logon session list
//
// Effects:
//
// Arguments: LogonId - LogonId for new logon session
// AccountName - Account name of user
// Domain Name - Domain name of user
// Password - password for user
// OldPassword - Old password for user, if present
// LogonType - Type of logon
// NewLogonSession - Receives new logon session (referenced)
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateLogonSession(
IN PLUID LogonId,
IN PUNICODE_STRING AccountName,
IN PUNICODE_STRING DomainName,
IN OPTIONAL PUNICODE_STRING Password,
IN OPTIONAL PUNICODE_STRING OldPassword,
IN ULONG PasswordFlags,
IN ULONG LogonSessionFlags,
IN BOOLEAN AllowDuplicate,
OUT PKERB_LOGON_SESSION * NewLogonSession
)
{
PKERB_LOGON_SESSION LogonSession = NULL;
NTSTATUS Status;
D_DebugLog((DEB_TRACE_LSESS, "Creating logon session for 0x%x:0x%x\n",
LogonId->HighPart, LogonId->LowPart));
*NewLogonSession = NULL;
//
// Check for a logon session with the same id
//
LogonSession = KerbReferenceLogonSession(
LogonId,
FALSE // don't unlink
);
if (LogonSession != NULL)
{
//
// We already have this logon session, so don't create another one.
// Up the refcount on the logon session, or simply fail if duplicates
// aren't allowed...
//
if ( AllowDuplicate )
{
*NewLogonSession = LogonSession;
return (STATUS_OBJECT_NAME_EXISTS);
}
else
{
KerbDereferenceLogonSession(LogonSession);
return(STATUS_OBJECT_NAME_EXISTS);
}
}
//
// Allocate the new logon session
//
Status = KerbAllocateLogonSession( &LogonSession );
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// Fill in the logon session components
//
LogonSession->LogonId = *LogonId;
LogonSession->Lifetime = KerbGlobalWillNeverTime;
//
// If the domain name is equal to the computer name then the logon was
// local.
//
#ifndef WIN32_CHICAGO
KerbGlobalReadLock();
if (RtlEqualDomainName(
DomainName,
&KerbGlobalMachineName
))
{
LogonSession->LogonSessionFlags |= KERB_LOGON_LOCAL_ONLY;
}
KerbGlobalReleaseLock();
#endif // WIN32_CHICAGO
Status = KerbCreatePrimaryCredentials(
AccountName,
DomainName,
Password,
OldPassword,
PasswordFlags,
LogonId,
&LogonSession->PrimaryCredentials
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
LogonSession->LogonSessionFlags |= ( KERB_LOGON_DEFERRED | LogonSessionFlags);
if ((( LogonSessionFlags & KERB_LOGON_SMARTCARD) == 0) &&
(( LogonSession->PrimaryCredentials.Passwords == NULL) ||
( LogonSession->PrimaryCredentials.Passwords->CredentialCount == 0)))
{
LogonSession->LogonSessionFlags |= KERB_LOGON_NO_PASSWORD;
}
//
// Now that the logon session structure is filled out insert it
// into the list. After this you need to hold the logon session lock
// to read or write this logon session.
//
Status = KerbInsertLogonSession(LogonSession);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
*NewLogonSession = LogonSession;
Cleanup:
if (!NT_SUCCESS(Status))
{
if (NULL != LogonSession)
{
KerbFreeLogonSession(LogonSession); // not yet linked...
}
}
return(Status);
}
#ifndef WIN32_CHICAGO
//+-------------------------------------------------------------------------
//
// Function: KerbCreateDummyLogonSession
//
// Synopsis: Creates a logon session from scratch - likely created
// from another package so call the lsa.
//
// Effects:
//
// Arguments: LogonId - Logon id for the logon session
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateDummyLogonSession(
IN PLUID LogonId,
IN OUT PKERB_LOGON_SESSION * NewLogonSession,
IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
IN BOOLEAN Impersonating,
IN OPTIONAL HANDLE hProcess
)
{
NTSTATUS Status;
PKERB_LOGON_SESSION LogonSession = NULL;
UNICODE_STRING TmpRealmName = {0};
UNICODE_STRING RealmName = {0};
UNICODE_STRING TmpUserName = {0};
UNICODE_STRING UserName = {0};
BOOLEAN OkForProxy, UseSamName = FALSE;
LUID BaseLogonId;
LUID AnonymousLogonId = ANONYMOUS_LOGON_LUID;
ULONG LogonSessionFlags = KERB_LOGON_DUMMY_SESSION;
*NewLogonSession = NULL;
if (!RtlEqualLuid(LogonId, &AnonymousLogonId))
{
//
// Get the user name / dns domain name from the lsa. If
// we fail this call, the logon session is of unknown origin,
// and we'll need to assert (where did it come from?)
//
Status = I_LsaIGetNameFromLuid(
LogonId,
NameSamCompatible,
TRUE, // local only
&TmpUserName
);
if (!NT_SUCCESS(Status))
{
goto cleanup;
}
Status = I_LsaIGetNameFromLuid(
LogonId,
NameDnsDomain,
TRUE, // local only
&TmpRealmName
);
if (!NT_SUCCESS(Status))
{
//
// We may not have a dns domain. In that case,
// use the first part of the sam name.
//
UseSamName = TRUE;
}
else
{
RealmName = TmpRealmName;
}
//
// No method for "sam" name, so lets split it up.
//
for (USHORT i = 0; i <= TmpUserName.Length; i++)
{
if (TmpUserName.Buffer[i] == L'\\')
{
if ( UseSamName )
{
TmpUserName.Buffer[i] = L'\0';
RtlInitUnicodeString(
&RealmName,
TmpUserName.Buffer
);
}
RtlInitUnicodeString(
&UserName,
(PWSTR) &TmpUserName.Buffer[++i]
);
break;
}
}
//
// If we haven't gotten a realm yet, we're hosed. This should *never* happen.
//
if (RealmName.Length == 0)
{
Status = STATUS_NO_SUCH_USER;
DsysAssert(FALSE);
goto cleanup;
}
}
else // anonymous does not have a lsap logon session
{
//
// no need to look up anonymous logon, since anonymous logon with
// no explicit cred supplied will fail with SEC_E_UNKNOWN_CREDENTIALS
//
RtlInitUnicodeString(&UserName, L"ANONYMOUS LOGON");
RtlInitUnicodeString(&RealmName, L"NT AUTHORITY");
LogonSessionFlags |= KERB_LOGON_LOCAL_ONLY; // anonymous is local well-known principal
}
//
// Create a logon session.
// If it was created on us during above processing, use that one.
//
Status = KerbCreateLogonSession(
LogonId,
&UserName,
&RealmName,
NULL,
NULL,
0,
LogonSessionFlags,
TRUE, // Allow duplicate.
&LogonSession
);
if (!NT_SUCCESS(Status))
{
//
// Already created - we're ok. Move on..
//
if ( Status == STATUS_OBJECT_NAME_EXISTS )
{
*NewLogonSession = LogonSession;
LogonSession = NULL;
Status = STATUS_SUCCESS;
}
goto cleanup;
}
if ( Impersonating )
{
Status = KerbGetCallingLuid(
&BaseLogonId,
hProcess
);
if (!NT_SUCCESS( Status ))
{
goto cleanup;
}
OkForProxy = KerbAllowedForS4UProxy(&BaseLogonId);
DsysAssert(ImpersonationLevel != SecurityAnonymous);
KerbWriteLockLogonSessions( LogonSession );
//
// If we're being called from an impersonation level context &&
// the process is marked as OK for proxy, allow this to be used
// for S4u
//
// Local accounts *can't* do S4U either...
//
if (( ImpersonationLevel >= SecurityImpersonation ) &&
( OkForProxy ) &&
(( LogonSession->LogonSessionFlags & KERB_LOGON_LOCAL_ONLY ) == 0 ))
{
LogonSession->LogonSessionFlags |= KERB_LOGON_DELEGATE_OK;
}
KerbUnlockLogonSessions( LogonSession );
}
DebugLog((DEB_TRACE_S4U, "KerbCreateDummyLogonSession created logon session for 0x%x:0x%x - %p\n",
LogonId->HighPart, LogonId->LowPart, LogonSession));
*NewLogonSession = LogonSession;
LogonSession = NULL;
cleanup:
if ( LogonSession != NULL )
{
KerbFreeLogonSession( LogonSession );
}
if (TmpUserName.Buffer)
{
LsaFunctions->FreePrivateHeap(TmpUserName.Buffer);
}
if (TmpRealmName.Buffer)
{
LsaFunctions->FreePrivateHeap(TmpRealmName.Buffer);
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreateLogonSessionFromKerbCred
//
// Synopsis: Creates a logon session from the delegation information
// in a KERB_CRED structure. If a logon session is supplied,
// it is updated with the supplied information.
//
// Effects:
//
// Arguments: LogonId - Logon id for the logon session
// Ticket - Ticket from the AP request containing the client's
// name and realm.
// KerbCred - KERB_CRED containing the delegation tickets
// EncryptedCred - Structure containing information about the
// tickets, such as session keys, flags, etc.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateLogonSessionFromKerbCred(
IN OPTIONAL PLUID LogonId,
IN PKERB_ENCRYPTED_TICKET Ticket,
IN PKERB_CRED KerbCred,
IN PKERB_ENCRYPTED_CRED EncryptedCred,
IN OUT PKERB_LOGON_SESSION *OldLogonSession
)
{
PKERB_LOGON_SESSION LogonSession = NULL;
NTSTATUS Status = STATUS_SUCCESS;
UNICODE_STRING AccountName;
UNICODE_STRING ShortAccountName;
UNICODE_STRING DomainName;
KERB_ENCRYPTED_KDC_REPLY FakeReplyBody;
KERB_KDC_REPLY FakeReply;
PKERB_CRED_INFO CredInfo;
PKERB_CRED_TICKET_LIST TicketList;
PKERB_CRED_INFO_LIST CredInfoList;
PKERB_TICKET_CACHE_ENTRY TicketCacheEntry;
ULONG NameType;
LPWSTR LastSlash;
BOOLEAN LogonSessionLocked = FALSE;
BOOLEAN CreatedLogonSession = TRUE;
PKERB_TICKET_CACHE TicketCache = NULL;
ULONG TgtFlags = KERB_TICKET_CACHE_PRIMARY_TGT;
ULONG CacheFlags = 0;
AccountName.Buffer = NULL;
DomainName.Buffer = NULL;
if (ARGUMENT_PRESENT(LogonId))
{
D_DebugLog((DEB_TRACE_LSESS, "Creating logon session for 0x%x:0x%x\n",
LogonId->HighPart,LogonId->LowPart));
}
if (!KERB_SUCCESS(KerbConvertPrincipalNameToString(
&AccountName,
&NameType,
&Ticket->client_name
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
//
// We need to strip off everything before the last '\' in case there
// was a domain name.
//
LastSlash = wcsrchr(AccountName.Buffer, L'\\');
if (LastSlash != NULL)
{
ShortAccountName.Buffer = LastSlash+1;
RtlInitUnicodeString(
&ShortAccountName,
ShortAccountName.Buffer
);
}
else
{
ShortAccountName = AccountName;
}
if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString(
&DomainName,
&Ticket->client_realm
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
D_DebugLog((DEB_TRACE, "Creating delegation logon session for %wZ \\ %wZ\n",
&DomainName, &ShortAccountName ));
if ((*OldLogonSession) == NULL)
{
//
// Allocate the new logon session
//
Status = KerbAllocateLogonSession( &LogonSession );
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// Fill in the logon session components
//
LogonSession->LogonId = *LogonId;
LogonSession->Lifetime = KerbGlobalWillNeverTime;
Status = KerbCreatePrimaryCredentials(
&ShortAccountName,
&DomainName,
NULL, // no password
NULL, // no old password
0, // no flags
LogonId,
&LogonSession->PrimaryCredentials
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
LogonSession->LogonSessionFlags |= KERB_LOGON_NO_PASSWORD;
}
else
{
CreatedLogonSession = FALSE;
DsysAssert( !LogonSessionLocked );
KerbWriteLockLogonSessions(*OldLogonSession);
LogonSessionLocked = TRUE;
LogonSession = *OldLogonSession;
//
// If the user name & domain name are blank, update them from the
// ticket.
//
if (LogonSession->PrimaryCredentials.UserName.Length == 0)
{
KerbFreeString(&LogonSession->PrimaryCredentials.UserName);
LogonSession->PrimaryCredentials.UserName = AccountName;
AccountName.Buffer = NULL;
}
if (LogonSession->PrimaryCredentials.DomainName.Length == 0)
{
KerbFreeString(&LogonSession->PrimaryCredentials.DomainName);
LogonSession->PrimaryCredentials.DomainName = DomainName;
DomainName.Buffer = NULL;
}
}
//
// Now stick the ticket into the ticket cache. First build up a fake
// KDC reply message from the encryped cred info.
//
TicketList = KerbCred->tickets;
CredInfoList = EncryptedCred->ticket_info;
while (TicketList != NULL)
{
TimeStamp Endtime = {0};
if (CredInfoList == NULL)
{
D_DebugLog((DEB_ERROR, "No ticket info in encrypted cred. %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
CredInfo = &CredInfoList->value;
//
// Set the lifetime to the end or renew_until of the longest lived ticket
//
if ((CredInfo->bit_mask & KERB_CRED_INFO_renew_until_present) != 0)
{
KerbConvertGeneralizedTimeToLargeInt(
&Endtime,
&CredInfo->KERB_CRED_INFO_renew_until,
0 // no usec
);
}
else if ((CredInfo->bit_mask & endtime_present) != 0)
{
KerbConvertGeneralizedTimeToLargeInt(
&Endtime,
&CredInfo->endtime,
0 // no usec
);
}
if (Endtime.QuadPart != 0)
{
if (LogonSession->Lifetime.QuadPart == KerbGlobalWillNeverTime.QuadPart)
{
LogonSession->Lifetime.QuadPart = Endtime.QuadPart;
}
else
{
LogonSession->Lifetime.QuadPart = max(LogonSession->Lifetime.QuadPart,Endtime.QuadPart);
}
}
RtlZeroMemory(
&FakeReplyBody,
sizeof(KERB_ENCRYPTED_KDC_REPLY)
);
FakeReplyBody.session_key = CredInfo->key;
FakeReplyBody.nonce = 0;
//
// Set the ticket flags
//
if (CredInfo->bit_mask & flags_present)
{
FakeReplyBody.flags = CredInfo->flags;
}
else
{
FakeReplyBody.flags.length = 0;
FakeReplyBody.flags.value = NULL;
}
FakeReplyBody.authtime = Ticket->authtime;
if (CredInfo->bit_mask & KERB_CRED_INFO_starttime_present)
{
FakeReplyBody.KERB_ENCRYPTED_KDC_REPLY_starttime =
CredInfo->KERB_CRED_INFO_starttime;
FakeReplyBody.bit_mask |= KERB_ENCRYPTED_KDC_REPLY_starttime_present;
}
//
// If an end time was sent, use it, otherwise assume the ticket
// lasts forever
//
if (CredInfo->bit_mask & endtime_present)
{
FakeReplyBody.endtime =
CredInfo->endtime;
}
else
{
KerbConvertLargeIntToGeneralizedTime(
&FakeReplyBody.endtime,
NULL,
&KerbGlobalWillNeverTime
);
}
if (CredInfo->bit_mask & KERB_CRED_INFO_renew_until_present)
{
FakeReplyBody.KERB_ENCRYPTED_KDC_REPLY_renew_until =
CredInfo->KERB_CRED_INFO_renew_until;
FakeReplyBody.bit_mask |= KERB_ENCRYPTED_KDC_REPLY_renew_until_present;
}
FakeReplyBody.server_name = TicketList->value.server_name;
FakeReplyBody.server_realm = TicketList->value.realm;
//
// Determine which ticket cache to use
//
if ((FakeReplyBody.server_name.name_string != NULL) &&
_stricmp(
FakeReplyBody.server_name.name_string->value,
KDC_PRINCIPAL_NAME_A) == 0)
{
TicketCache = &LogonSession->PrimaryCredentials.AuthenticationTicketCache;
//
// We only want to use the primary_tgt flag the first time through
//
CacheFlags = TgtFlags;
TgtFlags = 0;
D_DebugLog((DEB_TRACE,"Adding ticket from kerb_cred to authentication ticket cache\n"));
}
else
{
TicketCache = &LogonSession->PrimaryCredentials.ServerTicketCache;
CacheFlags = 0;
D_DebugLog((DEB_TRACE,"Adding ticket from kerb_cred to server ticket cache\n"));
}
FakeReply.client_name = Ticket->client_name;
FakeReply.ticket = TicketList->value;
FakeReply.client_realm = Ticket->client_realm;
Status = KerbCreateTicketCacheEntry(
&FakeReply,
&FakeReplyBody,
NULL, // no target name
NULL,
CacheFlags,
TicketCache,
NULL, // no credential key
&TicketCacheEntry
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR, "Failed to cache ticket: 0x%x. %ws, line %d\n",
Status, THIS_FILE, __LINE__));
goto Cleanup;
}
LogonSession->LogonSessionFlags &= ~KERB_LOGON_DEFERRED;
KerbDereferenceTicketCacheEntry(TicketCacheEntry);
CredInfoList = CredInfoList->next;
TicketList = TicketList->next;
}
//
// Now that the logon session structure is filled out insert it
// into the list. After this you need to hold the logon session lock
// to read or write this logon session.
//
if (*OldLogonSession == NULL)
{
Status = KerbInsertLogonSession(LogonSession);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
*OldLogonSession = LogonSession;
}
Cleanup:
if (LogonSessionLocked)
{
KerbUnlockLogonSessions(LogonSession);
}
if (CreatedLogonSession)
{
if (!NT_SUCCESS(Status))
{
if (LogonSession != NULL)
{
KerbFreeLogonSession(LogonSession);
}
}
}
KerbFreeString(&AccountName);
KerbFreeString(&DomainName);
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreateLogonSessionFromTicket
//
// Synopsis: Creates a logon session from a service ticket. Later
// can be used for S4U proxy delegation.
//
// Effects:
//
// Arguments: LogonId - Logon id for the logon session
// Ticket - Ticket from the AP request containing the client's
// name and realm.
// KerbCred - KERB_CRED containing the delegation tickets
// EncryptedCred - Structure containing information about the
// tickets, such as session keys, flags, etc.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateLogonSessionFromTicket(
IN PLUID NewLuid,
IN OPTIONAL PLUID AcceptingLuid,
IN PUNICODE_STRING ClientName,
IN PUNICODE_STRING ClientRealm,
IN PKERB_AP_REQUEST ApRequest,
IN PKERB_ENCRYPTED_TICKET Ticket,
IN OUT PKERB_LOGON_SESSION *NewLogonSession // needed?
)
{
NTSTATUS Status = STATUS_SUCCESS;
PKERB_LOGON_SESSION LogonSession = NULL;
KERB_ENCRYPTED_KDC_REPLY FakeReplyBody;
KERB_KDC_REPLY FakeReply;
PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL;
LUID SystemLogonId = SYSTEM_LUID;
BOOLEAN IsSystemLogon = FALSE;
ULONG TicketFlags = 0;
LUID BaseLogonId;
//
// Only create these if we're allowed to do S4UToProxy. Otherwise,
// there's no reason to keep this ticket around. If it starts becoming
// available, we can always fall back to S4UToSelf to get a new service
// ticket.
//
if (ARGUMENT_PRESENT(NewLogonSession))
{
*NewLogonSession = NULL;
}
if (!ARGUMENT_PRESENT( AcceptingLuid ))
{
Status = KerbGetCallingLuid(
&BaseLogonId,
NULL // no process handle
);
if (!NT_SUCCESS( Status ))
{
goto Cleanup;
}
}
else
{
BaseLogonId = (*AcceptingLuid);
}
if (!KerbAllowedForS4UProxy( &BaseLogonId ))
{
DebugLog((DEB_TRACE_LSESS, "KerbCreateLogonSessionFromTicket NOT creating ASC logon session for %#x:%#x, accepting %#x:%#x\n",
NewLuid->HighPart, NewLuid->LowPart, BaseLogonId.HighPart, BaseLogonId.LowPart));
goto Cleanup;
}
DebugLog((DEB_TRACE_LSESS, "KerbCreateLogonSessionFromTicket creating logon session for %#x:%#x, accepting %#x:%#x, client %wZ@%wZ\n",
NewLuid->HighPart, NewLuid->LowPart, BaseLogonId.HighPart, BaseLogonId.LowPart, ClientName, ClientRealm));
//
// Allocate the new logon session
//
Status = KerbAllocateLogonSession( &LogonSession );
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// Fill in the logon session components
//
LogonSession->LogonId = (*NewLuid);
LogonSession->LogonSessionFlags = KERB_LOGON_ASC_SESSION | KERB_LOGON_NO_PASSWORD | KERB_LOGON_DEFERRED;
KerbConvertGeneralizedTimeToLargeInt(
&LogonSession->Lifetime,
&Ticket->endtime,
NULL
);
if (RtlEqualLuid(
NewLuid,
&SystemLogonId
))
{
IsSystemLogon = TRUE;
}
Status = KerbDuplicateString(
&LogonSession->PrimaryCredentials.UserName,
ClientName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// The system logon always comes in uppercase, so lowercase it.
//
if (IsSystemLogon)
{
RtlDowncaseUnicodeString(
&LogonSession->PrimaryCredentials.UserName,
&LogonSession->PrimaryCredentials.UserName,
FALSE
);
}
Status = KerbDuplicateString(
&LogonSession->PrimaryCredentials.DomainName,
ClientRealm
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// We now have to make a decision about whether to store
// the ticket, and how to mark the logon session. If
// the ticket is non-fwdable, we can't do S4U using it, so we
// might as well not cache it, but we should still create
// the logon session...
//
// This allows for a bit of an optimization, as if the user's ticket
// wasn't fwdable, we shouldn't do S4U2Self for that user, as
// their account must be marked "sensitive and unable to be
// delegated", and we'd be wasting an S4UToSelf tgs_req.
//
TicketFlags = KerbConvertFlagsToUlong(&Ticket->flags);
if (( TicketFlags & KERB_TICKET_FLAGS_forwardable ) != 0)
{
ULONG ApOptions = KerbConvertFlagsToUlong(&ApRequest->ap_options);
ULONG CacheFlags = KERB_TICKET_CACHE_ASC_TICKET;
if (ApOptions & KERB_AP_OPTIONS_use_session_key)
{
D_DebugLog((DEB_TRACE_U2U, "KerbCreateLogonSessionFromTicket set TKT_ENC_IN_SKEY\n"));
CacheFlags |= KERB_TICKET_CACHE_TKT_ENC_IN_SKEY;
}
LogonSession->LogonSessionFlags |= KERB_LOGON_DELEGATE_OK;
//
// Now stick the ticket into the ticket cache. First build up a fake
// KDC reply message from the encryped cred info.
//
FakeReplyBody.authtime = Ticket->authtime;
FakeReplyBody.starttime = Ticket->starttime;
FakeReplyBody.endtime = Ticket->endtime;
FakeReplyBody.server_name = ApRequest->ticket.server_name;
FakeReplyBody.server_realm = ApRequest->ticket.realm;
FakeReplyBody.session_key = Ticket->key;
FakeReplyBody.flags = Ticket->flags;
FakeReply.client_name = Ticket->client_name;
FakeReply.ticket = ApRequest->ticket;
FakeReply.client_realm = Ticket->client_realm;
Status = KerbCreateTicketCacheEntry(
&FakeReply,
&FakeReplyBody,
NULL, // no target name
NULL,
CacheFlags,
NULL,
NULL, // no credential key
&TicketCacheEntry
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR, "Failed to cache ticket: 0x%x. %ws, line %d\n",
Status, THIS_FILE, __LINE__));
goto Cleanup;
}
TicketCacheEntry->EvidenceLogonId = BaseLogonId;
KerbInsertTicketCacheEntry(
&LogonSession->PrimaryCredentials.S4UTicketCache,
TicketCacheEntry
);
}
#if DBG
else
{
DebugLog((DEB_TRACE_LSESS, "Creating non-delegatable ASC logon session\n"));
}
#endif
//
// Now that the logon session structure is filled out insert it
// into the list. After this you need to hold the logon session lock
// to read or write this logon session.
//
Status = KerbInsertLogonSession(LogonSession);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (ARGUMENT_PRESENT( NewLogonSession ))
{
*NewLogonSession = LogonSession;
}
else
{
// We aren't returning it, so...
KerbDereferenceLogonSession( LogonSession );
}
Cleanup:
if (!NT_SUCCESS(Status) && (LogonSession != NULL))
{
KerbFreeLogonSession(LogonSession);
}
if (TicketCacheEntry != NULL)
{
KerbDereferenceTicketCacheEntry(TicketCacheEntry);
}
return(Status);
}
VOID
KerbFreeExtraCred(
PKERB_EXTRA_CRED Cred
)
{
if (Cred != NULL)
{
if (Cred->Passwords != NULL)
{
KerbFreeStoredCred(Cred->Passwords);
}
if (Cred->OldPasswords != NULL)
{
KerbFreeStoredCred(Cred->OldPasswords);
}
KerbFreeString(&Cred->cName);
KerbFreeString(&Cred->cRealm);
KerbFree(Cred);
}
}
VOID
KerbDereferenceExtraCred(
PKERB_EXTRA_CRED Cred
)
{
DsysAssert(Cred->ListEntry.ReferenceCount != 0);
if ( 0 == InterlockedDecrement((LONG *)&Cred->ListEntry.ReferenceCount ))
{
KerbFreeExtraCred(Cred);
}
}
VOID
KerbReferenceExtraCred(
PKERB_EXTRA_CRED Cred
)
{
InterlockedIncrement((LONG *)&Cred->ListEntry.ReferenceCount);
}
VOID
KerbRemoveExtraCredFromList(
PKERB_EXTRA_CRED Cred,
PEXTRA_CRED_LIST CredList
)
{
if ( InterlockedCompareExchange(
&Cred->Linked,
(LONG)FALSE,
(LONG)TRUE ))
{
DsysAssert(Cred->ListEntry.ReferenceCount != 0);
RemoveEntryList(&Cred->ListEntry.Next);
KerbDereferenceExtraCred(Cred);
InterlockedDecrement((LONG*) &CredList->Count );
}
}
VOID
KerbInsertExtraCred(
IN PKERB_EXTRA_CRED Cred,
IN PEXTRA_CRED_LIST Credlist
)
{
if ( !InterlockedCompareExchange(
&Cred->Linked,
(LONG)TRUE,
(LONG)FALSE ))
{
InterlockedIncrement( (LONG *)&Cred->ListEntry.ReferenceCount );
InsertHeadList(
&Credlist->CredList.List,
&Cred->ListEntry.Next
);
InterlockedIncrement((LONG*) &Credlist->Count);
}
}
VOID
KerbFreeExtraCredList(
IN PEXTRA_CRED_LIST Credlist
)
{
PKERB_EXTRA_CRED Cred;
KerbLockList(&Credlist->CredList);
while (!IsListEmpty(&Credlist->CredList.List))
{
Cred = CONTAINING_RECORD(
Credlist->CredList.List.Flink,
KERB_EXTRA_CRED,
ListEntry.Next
);
KerbRemoveExtraCredFromList(
Cred,
Credlist
);
}
DsysAssert(Credlist->Count == 0);
KerbUnlockList(&Credlist->CredList);
}
//+-------------------------------------------------------------------------
//
// Function: KerbAddExtraCredentialsToLogonSession
//
// Synopsis: Initializes logon session list
//
// Effects: allocates a resources
//
// Arguments: none
//
// Requires:
//
// Returns: STATUS_SUCCESS on success, other error codes
// on failure
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbAddExtraCredentialsToLogonSession(
IN PKERB_LOGON_SESSION LogonSession,
IN PKERB_ADD_CREDENTIALS_REQUEST AddCredRequest
)
{
NTSTATUS Status = STATUS_SUCCESS;
PKERB_STORED_CREDENTIAL DerivedPasswords = NULL;
PKERB_EXTRA_CRED Cred = NULL;
PLIST_ENTRY ListEntry;
BOOLEAN Matched = FALSE, ListLocked = FALSE;
//
// YuChen repellant
//
if ((AddCredRequest->UserName.Length > KERB_MAX_UNICODE_STRING) ||
(AddCredRequest->DomainName.Length > KERB_MAX_UNICODE_STRING))
{
return (STATUS_NAME_TOO_LONG);
}
if ((AddCredRequest->Password.Length != 0) && (AddCredRequest->Password.Length > KERB_MAX_UNICODE_STRING))
{
return (STATUS_NAME_TOO_LONG);
}
//
// See if we match an old "extra cred"
//
KerbLockList( &LogonSession->ExtraCredentials.CredList );
ListLocked = TRUE;
for (ListEntry = LogonSession->ExtraCredentials.CredList.List.Flink ;
ListEntry != &LogonSession->ExtraCredentials.CredList.List ;
ListEntry = ListEntry->Flink )
{
Cred = CONTAINING_RECORD(ListEntry, KERB_EXTRA_CRED, ListEntry.Next);
if (RtlEqualUnicodeString(
&Cred->cName,
&AddCredRequest->UserName,
TRUE) &&
RtlEqualUnicodeString(
&Cred->cRealm,
&AddCredRequest->DomainName,
TRUE))
{
D_DebugLog((DEB_TRACE, "Matched extra cred %p\n", Cred));
Matched = TRUE;
break;
}
}
if ( Matched )
{
if (( AddCredRequest->Flags & ( KERB_REQUEST_ADD_CREDENTIAL | KERB_REQUEST_REPLACE_CREDENTIAL)) != 0)
{
Status = KerbBuildPasswordList(
&AddCredRequest->Password,
&AddCredRequest->UserName,
&AddCredRequest->DomainName,
NULL,
NULL,
NULL,
UnknownAccount,
PRIMARY_CRED_CLEAR_PASSWORD,
&DerivedPasswords
);
if (!NT_SUCCESS( Status ))
{
DebugLog((DEB_ERROR, "Failed to build password list (%x) \n", Status));
goto Cleanup;
}
if (Cred->OldPasswords != NULL)
{
KerbFreeStoredCred(Cred->OldPasswords);
}
if ((AddCredRequest->Flags & KERB_REQUEST_REPLACE_CREDENTIAL) == 0)
{
Cred->OldPasswords = Cred->Passwords;
}
else if (Cred->Passwords != NULL)
{
//
// Doing a "replace" operation.
//
KerbFreeStoredCred(Cred->Passwords);
}
Cred->Passwords = DerivedPasswords;
DerivedPasswords = NULL;
}
else
{
//
// Remove the cred from the list, and deref it.
//
D_DebugLog((DEB_TRACE, "Removing extra cred %p\n", Cred));
KerbRemoveExtraCredFromList(
Cred,
&LogonSession->ExtraCredentials
);
}
}
else if (( AddCredRequest->Flags & KERB_REQUEST_REMOVE_CREDENTIAL) == 0)
{
D_DebugLog((DEB_TRACE, "Creating cred %p\n", Cred));
Cred = (PKERB_EXTRA_CRED) KerbAllocate(sizeof(KERB_EXTRA_CRED));
if (NULL == Cred)
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
Status = KerbDuplicateString(
&Cred->cName,
&AddCredRequest->UserName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = KerbDuplicateString(
&Cred->cRealm,
&AddCredRequest->DomainName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = KerbBuildPasswordList(
&AddCredRequest->Password,
&AddCredRequest->UserName,
&AddCredRequest->DomainName,
NULL,
NULL,
NULL,
UnknownAccount,
PRIMARY_CRED_CLEAR_PASSWORD,
&DerivedPasswords
);
if (!NT_SUCCESS( Status ))
{
DebugLog((DEB_ERROR, "Failed to build password list (%x) \n", Status));
goto Cleanup;
}
Cred->Passwords = DerivedPasswords;
DerivedPasswords = NULL;
KerbInsertExtraCred(
Cred,
&LogonSession->ExtraCredentials
);
}
KerbUnlockList( &LogonSession->ExtraCredentials.CredList);
ListLocked = FALSE;
Cleanup:
if ( DerivedPasswords != NULL )
{
KerbFreeStoredCred( DerivedPasswords );
}
if (!NT_SUCCESS( Status ))
{
// only free it on error if we haven't
// matched the credential...
if ( Cred != NULL && !Matched)
{
KerbFreeExtraCred(Cred);
}
}
if ( ListLocked )
{
KerbUnlockList( &LogonSession->ExtraCredentials.CredList );
}
return Status;
}
#endif // WIN32_CHICAGO