mirror of https://github.com/lianthony/NT4.0
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.
4646 lines
129 KiB
4646 lines
129 KiB
/*++
|
|
|
|
Copyright (c) 1993 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
context.c
|
|
|
|
Abstract:
|
|
|
|
API and support routines for handling security contexts.
|
|
|
|
Author:
|
|
|
|
Cliff Van Dyke (CliffV) 13-Jul-1993
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
//
|
|
// Common include files.
|
|
//
|
|
|
|
#include <ntlmcomn.h> // Common definitions for DLL and SERVICE
|
|
#include <ntlmsspi.h> // Data private to the common routines
|
|
#include <align.h> // ALIGN_WCHAR, etc
|
|
#include <crypt.h> // Encryption constants and routine
|
|
#include <rc4.h> // RC4 encryption types and functions
|
|
|
|
//
|
|
// Crit Sect to protect various globals in this module.
|
|
//
|
|
|
|
CRITICAL_SECTION SspContextCritSect;
|
|
LIST_ENTRY SspContextList;
|
|
|
|
|
|
//
|
|
// Variables describing us as a Logon Process
|
|
//
|
|
|
|
HANDLE SspGlobalLogonProcessHandle;
|
|
ULONG SspGlobalAuthenticationPackage;
|
|
|
|
|
|
|
|
PSSP_CONTEXT
|
|
SspContextReferenceContext(
|
|
IN PCtxtHandle ContextHandle,
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection,
|
|
IN BOOLEAN RemoveContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks to see if the Context is for the specified
|
|
Client Connection, and references the Context if it is valid.
|
|
|
|
The caller may optionally request that the Context be
|
|
removed from the list of valid Contexts - preventing future
|
|
requests from finding this Context.
|
|
|
|
Arguments:
|
|
|
|
ContextHandle - Points to the ContextHandle of the Context
|
|
to be referenced.
|
|
|
|
ClientConnection - Points to the client connection of the client
|
|
referencing the handle. (NULL means an internal reference.)
|
|
|
|
RemoveContext - This boolean value indicates whether the caller
|
|
wants the Context to be removed from the list
|
|
of Contexts. TRUE indicates the Context is to be removed.
|
|
FALSE indicates the Context is not to be removed.
|
|
|
|
|
|
Return Value:
|
|
|
|
NULL - the Context was not found.
|
|
|
|
Otherwise - returns a pointer to the referenced Context.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY ListEntry;
|
|
PSSP_CONTEXT Context;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
|
|
if ( ContextHandle->dwLower != SspCommonSecHandleValue ) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Acquire exclusive access to the Context list
|
|
//
|
|
|
|
EnterCriticalSection( &SspContextCritSect );
|
|
|
|
|
|
//
|
|
// Now walk the list of Contexts looking for a match.
|
|
//
|
|
|
|
for ( ListEntry = SspContextList.Flink;
|
|
ListEntry != &SspContextList;
|
|
ListEntry = ListEntry->Flink ) {
|
|
|
|
Context = CONTAINING_RECORD( ListEntry, SSP_CONTEXT, Next );
|
|
|
|
|
|
//
|
|
// Found a match ... reference this Context
|
|
// (if the Context is being removed, we would increment
|
|
// and then decrement the reference, so don't bother doing
|
|
// either - since they cancel each other out).
|
|
//
|
|
|
|
if ( Context == (PSSP_CONTEXT) ContextHandle->dwUpper &&
|
|
(ClientConnection == NULL ||
|
|
ClientConnection == Context->ClientConnection )) {
|
|
|
|
|
|
if (!RemoveContext) {
|
|
|
|
//
|
|
// Timeout this context if caller is not trying to remove it.
|
|
// We only timeout contexts that are being setup, not
|
|
// fully authenticated contexts.
|
|
//
|
|
|
|
if ( SspTimeHasElapsed( Context->StartTime,
|
|
Context->Interval ) ) {
|
|
if ( (Context->State != AuthenticatedState) &&
|
|
(Context->State != AuthenticateSentState) &&
|
|
(Context->State != PassedToServiceState) ) {
|
|
SspPrint(( SSP_API, "Context 0x%lx has timed out.\n",
|
|
ContextHandle->dwUpper ));
|
|
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
Context->References += 1;
|
|
|
|
} else {
|
|
|
|
RemoveEntryList( &Context->Next );
|
|
RemoveEntryList( &Context->NextForThisClient );
|
|
SspPrint(( SSP_API_MORE, "Delinked Context 0x%lx\n",
|
|
Context ));
|
|
}
|
|
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
return Context;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// No match found
|
|
//
|
|
SspPrint(( SSP_API, "Tried to reference unknown Context 0x%lx\n",
|
|
ContextHandle->dwUpper ));
|
|
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
SspContextDereferenceContext(
|
|
PSSP_CONTEXT Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine decrements the specified Context's reference count.
|
|
If the reference count drops to zero, then the Context is deleted
|
|
|
|
Arguments:
|
|
|
|
Context - Points to the Context to be dereferenced.
|
|
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG References;
|
|
|
|
|
|
//
|
|
// Decrement the reference count
|
|
//
|
|
|
|
EnterCriticalSection( &SspContextCritSect );
|
|
ASSERT( Context->References >= 1 );
|
|
References = -- Context->References;
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
|
|
//
|
|
// If the count dropped to zero, then run-down the Context
|
|
//
|
|
|
|
if (References == 0) {
|
|
|
|
SspPrint(( SSP_API_MORE, "Deleting Context 0x%lx\n",
|
|
Context ));
|
|
|
|
|
|
if ( Context->DomainName.Buffer != NULL ) {
|
|
(VOID) LocalFree( Context->DomainName.Buffer );
|
|
}
|
|
if ( Context->UserName.Buffer != NULL ) {
|
|
(VOID) LocalFree( Context->UserName.Buffer );
|
|
}
|
|
if ( Context->Password.Buffer != NULL ) {
|
|
(VOID) LocalFree( Context->Password.Buffer );
|
|
}
|
|
if ( Context->TokenHandle != NULL ) {
|
|
NTSTATUS IgnoreStatus;
|
|
IgnoreStatus = NtClose( Context->TokenHandle );
|
|
ASSERT( NT_SUCCESS(IgnoreStatus) );
|
|
}
|
|
if (Context->Credential != NULL) {
|
|
SspCredentialDereferenceCredential( Context->Credential );
|
|
}
|
|
|
|
(VOID) LocalFree( Context );
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
PSSP_CONTEXT
|
|
SspContextAllocateContext(
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates the security context block, initializes it and
|
|
links it onto the specified credential.
|
|
|
|
Arguments:
|
|
|
|
ClientConnection - Points to the client connection of the client
|
|
referencing the context. (NULL means an internal reference.)
|
|
|
|
|
|
Return Value:
|
|
|
|
NULL -- Not enough memory to allocate context.
|
|
|
|
otherwise -- pointer to allocated and referenced context.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSSP_CONTEXT Context;
|
|
|
|
//
|
|
// Allocate a Context block and initialize it.
|
|
//
|
|
|
|
Context = LocalAlloc( LMEM_ZEROINIT, sizeof(SSP_CONTEXT) );
|
|
|
|
if ( Context == NULL ) {
|
|
SspPrint(( SSP_API, "Cannot allocate Context.\n" ));
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// The reference count is set to 2. 1 to indicate it is on the
|
|
// valid Context list, and one for the our own reference.
|
|
//
|
|
// Actually its on both a global credential list and a per client connection
|
|
// list, but we link/delink from both lists at the same time so a single
|
|
// reference count handles both.
|
|
//
|
|
|
|
Context->References = 2;
|
|
Context->ClientConnection = ClientConnection;
|
|
Context->NegotiateFlags = 0;
|
|
Context->ContextFlags = 0;
|
|
Context->State = IdleState;
|
|
RtlInitUnicodeString(
|
|
&Context->DomainName,
|
|
NULL
|
|
);
|
|
RtlInitUnicodeString(
|
|
&Context->UserName,
|
|
NULL
|
|
);
|
|
RtlInitUnicodeString(
|
|
&Context->Password,
|
|
NULL
|
|
);
|
|
Context->ServerContextHandle.dwLower = 0;
|
|
Context->ServerContextHandle.dwUpper = 0;
|
|
Context->TokenHandle = NULL;
|
|
|
|
//
|
|
// Timeout this context.
|
|
//
|
|
|
|
(VOID) NtQuerySystemTime( &Context->StartTime );
|
|
Context->Interval = NTLMSSP_MAX_LIFETIME;
|
|
|
|
|
|
//
|
|
// Add it to the list of valid Context handles.
|
|
//
|
|
|
|
EnterCriticalSection( &SspContextCritSect );
|
|
InsertHeadList( &SspContextList, &Context->Next );
|
|
if ( ClientConnection != NULL ) {
|
|
InsertHeadList( &ClientConnection->ContextHead, &Context->NextForThisClient );
|
|
} else {
|
|
InitializeListHead( &Context->NextForThisClient );
|
|
}
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
|
|
SspPrint(( SSP_API_MORE, "Added Context 0x%lx\n", Context ));
|
|
|
|
return Context;
|
|
}
|
|
|
|
|
|
|
|
|
|
VOID
|
|
SspContextClientConnectionDropped(
|
|
PSSP_CLIENT_CONNECTION ClientConnection
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when the ClientConnection is dropped to allow
|
|
us to remove any Contexts for the ClientConnection.
|
|
|
|
Arguments:
|
|
|
|
ClientConnection - Pointer to the ClientConnection that has been dropped.
|
|
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Drop any lingering Contexts
|
|
//
|
|
|
|
EnterCriticalSection( &SspContextCritSect );
|
|
while ( !IsListEmpty( &ClientConnection->ContextHead ) ) {
|
|
CtxtHandle ContextHandle;
|
|
PSSP_CONTEXT Context;
|
|
|
|
ContextHandle.dwUpper =
|
|
(LONG) CONTAINING_RECORD( ClientConnection->ContextHead.Flink,
|
|
SSP_CONTEXT,
|
|
NextForThisClient );
|
|
|
|
ContextHandle.dwLower = SspCommonSecHandleValue;
|
|
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
|
|
Context = SspContextReferenceContext(
|
|
&ContextHandle,
|
|
ClientConnection,
|
|
TRUE); // Remove Context
|
|
|
|
if ( Context != NULL ) {
|
|
SspContextDereferenceContext(Context);
|
|
}
|
|
|
|
EnterCriticalSection( &SspContextCritSect );
|
|
}
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
SspContextGetMessage(
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection,
|
|
IN PVOID InputMessage,
|
|
IN ULONG InputMessageSize,
|
|
IN NTLM_MESSAGE_TYPE ExpectedMessageType,
|
|
OUT PVOID* OutputMessage
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine copies the InputMessage into the local address space.
|
|
This routine then validates the message header.
|
|
|
|
Arguments:
|
|
|
|
ClientConnection - Describes the client process.
|
|
|
|
InputMessage - Address of the message in the client process.
|
|
|
|
InputMessageSize - Size of the message (in bytes).
|
|
|
|
ExpectedMessageType - The type of message the should be in the message
|
|
header.
|
|
|
|
OutputMessage - Returns a pointer to an allocated buffer that contains
|
|
the message. The buffer should be freed using LocalFree.
|
|
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Call completed successfully
|
|
|
|
SEC_E_INVALID_TOKEN -- Message improperly formatted
|
|
SEC_E_INSUFFICIENT_MEMORY -- Not enough memory to allocate message
|
|
|
|
--*/
|
|
|
|
{
|
|
SECURITY_STATUS SecStatus;
|
|
PNEGOTIATE_MESSAGE TypicalMessage;
|
|
|
|
|
|
//
|
|
// Allocate a local buffer for the message.
|
|
//
|
|
|
|
ASSERT( NTLMSP_MAX_TOKEN_SIZE >= NTLMSSP_MAX_MESSAGE_SIZE );
|
|
if ( InputMessageSize > NTLMSSP_MAX_MESSAGE_SIZE ) {
|
|
return SEC_E_INVALID_TOKEN;
|
|
}
|
|
|
|
TypicalMessage = LocalAlloc( 0, InputMessageSize );
|
|
|
|
if ( TypicalMessage == NULL ) {
|
|
return SEC_E_INSUFFICIENT_MEMORY;
|
|
}
|
|
|
|
|
|
//
|
|
// Copy the message into the buffer
|
|
//
|
|
|
|
SecStatus = SspLpcCopyFromClientBuffer (
|
|
ClientConnection,
|
|
InputMessageSize,
|
|
TypicalMessage,
|
|
InputMessage );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
(VOID) LocalFree( TypicalMessage );
|
|
return SecStatus;
|
|
}
|
|
|
|
|
|
//
|
|
// Validate the message header.
|
|
//
|
|
|
|
if ( strncmp( TypicalMessage->Signature,
|
|
NTLMSSP_SIGNATURE,
|
|
sizeof(NTLMSSP_SIGNATURE)) != 0 ||
|
|
TypicalMessage->MessageType != ExpectedMessageType ) {
|
|
|
|
(VOID) LocalFree( TypicalMessage );
|
|
return SEC_E_INVALID_TOKEN;
|
|
|
|
}
|
|
|
|
*OutputMessage = TypicalMessage;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
SspContextCopyString(
|
|
IN PVOID MessageBuffer,
|
|
OUT PSTRING OutString,
|
|
IN PSTRING InString,
|
|
IN OUT PCHAR *Where,
|
|
IN BOOLEAN Absolute
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine copies the InString into the MessageBuffer at Where.
|
|
It then updates OutString to be a descriptor for the copied string. The
|
|
descriptor 'address' is an offset from the MessageBuffer unless 'Absolute'
|
|
is TRUE.
|
|
|
|
Where is updated to point to the next available space in the MessageBuffer.
|
|
|
|
The caller is responsible for any alignment requirements and for ensuring
|
|
there is room in the buffer for the string.
|
|
|
|
Arguments:
|
|
|
|
MessageBuffer - Specifies the base address of the buffer being copied into.
|
|
|
|
OutString - Returns a descriptor for the copied string. The descriptor
|
|
is relative to the begining of the buffer.
|
|
|
|
InString - Specifies the string to copy.
|
|
|
|
Where - On input, points to where the string is to be copied.
|
|
On output, points to the first byte after the string.
|
|
|
|
Absolute - If TRUE, OutString->Buffer will be set to the actual buffer
|
|
address rather than an offset.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Copy the data to the Buffer.
|
|
//
|
|
|
|
if ( InString->Buffer != NULL ) {
|
|
RtlCopyMemory( *Where, InString->Buffer, InString->Length );
|
|
}
|
|
|
|
//
|
|
// Build a descriptor to the newly copied data.
|
|
//
|
|
|
|
OutString->Length = OutString->MaximumLength = InString->Length;
|
|
if ( Absolute ) {
|
|
OutString->Buffer = *Where;
|
|
} else {
|
|
OutString->Buffer = (PCHAR)(*Where - ((PCHAR)MessageBuffer));
|
|
}
|
|
|
|
//
|
|
// Update Where to point past the copied data.
|
|
//
|
|
|
|
*Where += InString->Length;
|
|
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
SspConvertRelativeToAbsolute (
|
|
IN PVOID MessageBase,
|
|
IN ULONG MessageSize,
|
|
IN OUT PSTRING StringToRelocate,
|
|
IN BOOLEAN AlignToWchar,
|
|
IN BOOLEAN AllowNullString
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert a Relative string desriptor to be absolute.
|
|
Perform all boudary condition testing.
|
|
|
|
Arguments:
|
|
|
|
MessageBase - a pointer to the base of the buffer that the string
|
|
is relative to. The MaximumLength field of the descriptor is
|
|
forced to be the same as the Length field.
|
|
|
|
MessageSize - Size of the message buffer (in bytes).
|
|
|
|
StringToRelocate - A pointer to the string descriptor to make absolute.
|
|
|
|
AlignToWchar - If TRUE the passed in StringToRelocate must describe
|
|
a buffer that is WCHAR aligned. If not, an error is returned.
|
|
|
|
AllowNullString - If TRUE, the passed in StringToRelocate may be
|
|
a zero length string.
|
|
|
|
Return Value:
|
|
|
|
TRUE - The string descriptor is valid and was properly relocated.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Offset;
|
|
|
|
//
|
|
// If the buffer is allowed to be null,
|
|
// check that special case.
|
|
//
|
|
|
|
if ( AllowNullString ) {
|
|
if ( StringToRelocate->Length == 0 ) {
|
|
StringToRelocate->MaximumLength = StringToRelocate->Length;
|
|
StringToRelocate->Buffer = NULL;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Ensure the string in entirely within the message.
|
|
//
|
|
|
|
Offset = (ULONG) StringToRelocate->Buffer;
|
|
|
|
if ( Offset >= MessageSize ||
|
|
Offset + StringToRelocate->Length > MessageSize ) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Ensure the buffer is properly aligned.
|
|
//
|
|
|
|
if ( AlignToWchar ) {
|
|
if ( !COUNT_IS_ALIGNED( Offset, ALIGN_WCHAR) ||
|
|
!COUNT_IS_ALIGNED( StringToRelocate->Length, ALIGN_WCHAR) ) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Finally make the pointer absolute.
|
|
//
|
|
|
|
StringToRelocate->Buffer = (((PCHAR)MessageBase) + Offset);
|
|
StringToRelocate->MaximumLength = StringToRelocate->Length ;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
SspContextComputeChallenge (
|
|
OUT CHAR Challenge[MSV1_0_CHALLENGE_LENGTH]
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates an encryption key to use as a challenge for a logon.
|
|
|
|
*** Although the MSV1_0 authentication package has a function that
|
|
returns an encryption key, we do not use that function in order
|
|
to avoid a trip through LPC and into LSA.
|
|
|
|
This routine was stolen from the 'GetEncryptionKey' routine in the
|
|
SMB server.
|
|
|
|
Arguments:
|
|
|
|
Challenge - a pointer to a buffer which receives the challenge
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
union {
|
|
LARGE_INTEGER time;
|
|
UCHAR bytes[8];
|
|
} u;
|
|
ULONG Seed;
|
|
ULONG UlongChallenge[2];
|
|
ULONG Result3;
|
|
static ULONG EncryptionKeyCount = 0;
|
|
|
|
//
|
|
// Create a pseudo-random 8-byte number by munging the system time
|
|
// for use as a random number seed.
|
|
//
|
|
// Start by getting the system time.
|
|
//
|
|
|
|
ASSERT( MSV1_0_CHALLENGE_LENGTH == 2 * sizeof(ULONG) );
|
|
|
|
(VOID) NtQuerySystemTime( &u.time );
|
|
|
|
//
|
|
// To ensure that we don't use the same system time twice, add in the
|
|
// count of the number of times this routine has been called. Then
|
|
// increment the counter.
|
|
//
|
|
// *** Since we don't use the low byte of the system time (it doesn't
|
|
// take on enough different values, because of the timer
|
|
// resolution), we increment the counter by 0x100.
|
|
//
|
|
// *** We don't interlock the counter because we don't really care
|
|
// if it's not 100% accurate.
|
|
//
|
|
|
|
u.time.LowPart += EncryptionKeyCount;
|
|
|
|
EncryptionKeyCount += 0x100;
|
|
|
|
//
|
|
// Now use parts of the system time as a seed for the random
|
|
// number generator.
|
|
//
|
|
// *** Because the middle two bytes of the low part of the system
|
|
// time change most rapidly, we use those in forming the seed.
|
|
//
|
|
|
|
Seed = ((u.bytes[1] + 1) << 0) |
|
|
((u.bytes[2] + 0) << 8) |
|
|
((u.bytes[2] - 1) << 16) |
|
|
((u.bytes[1] + 0) << 24);
|
|
|
|
//
|
|
// Now get two random numbers. RtlRandom does not return negative
|
|
// numbers, so we pseudo-randomly negate them.
|
|
//
|
|
// *** Don't use RtlRandom because it generates non-random numbers for
|
|
// the first 128 calls or so. 9/10/93
|
|
|
|
UlongChallenge[0] = RtlUniform( &Seed );
|
|
UlongChallenge[1] = RtlUniform( &Seed );
|
|
Result3 = RtlUniform( &Seed );
|
|
|
|
if ( (Result3 & 0x1) != 0 ) {
|
|
UlongChallenge[0] |= 0x80000000;
|
|
}
|
|
if ( (Result3 & 0x2) != 0 ) {
|
|
UlongChallenge[1] |= 0x80000000;
|
|
}
|
|
|
|
//
|
|
// Return the challenge.
|
|
//
|
|
|
|
RtlCopyMemory( Challenge, UlongChallenge, MSV1_0_CHALLENGE_LENGTH );
|
|
|
|
}
|
|
|
|
|
|
|
|
TimeStamp
|
|
SspContextGetTimeStamp(
|
|
IN PSSP_CONTEXT Context,
|
|
IN BOOLEAN GetExpirationTime
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get the Start time or Expiration time for the specified context.
|
|
|
|
Arguments:
|
|
|
|
Context - Pointer to the context to query
|
|
|
|
GetExpirationTime - If TRUE return the expiration time.
|
|
Otherwise, return the start time for the context.
|
|
|
|
Return Value:
|
|
|
|
Returns the requested time as a local time.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER SystemTime;
|
|
LARGE_INTEGER LocalTime;
|
|
TimeStamp LocalTimeStamp;
|
|
|
|
//
|
|
// Get the requested time in NT system time format.
|
|
//
|
|
|
|
SystemTime = Context->StartTime;
|
|
|
|
if ( GetExpirationTime ) {
|
|
LARGE_INTEGER Interval;
|
|
|
|
//
|
|
// If the time is infinite, return that
|
|
//
|
|
|
|
if ( Context->Interval == INFINITE ) {
|
|
return SspGlobalForever;
|
|
}
|
|
|
|
//
|
|
// Compute the ending time in NT System Time.
|
|
//
|
|
|
|
Interval.QuadPart = Int32x32To64( (LONG) Context->Interval, 10000 );
|
|
SystemTime.QuadPart = Interval.QuadPart + SystemTime.QuadPart;
|
|
}
|
|
|
|
//
|
|
// Convert the time to local time
|
|
//
|
|
|
|
Status = RtlSystemTimeToLocalTime( &SystemTime, &LocalTime );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
return SspGlobalForever;
|
|
}
|
|
|
|
LocalTimeStamp.HighPart = LocalTime.HighPart;
|
|
LocalTimeStamp.LowPart = LocalTime.LowPart;
|
|
|
|
return LocalTimeStamp;
|
|
|
|
}
|
|
|
|
VOID
|
|
SspContextSetTimeStamp(
|
|
IN PSSP_CONTEXT Context,
|
|
IN LARGE_INTEGER ExpirationTime
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set the Expiration time for the specified context.
|
|
|
|
Arguments:
|
|
|
|
Context - Pointer to the context to change
|
|
|
|
ExpirationTime - Expiration time to set
|
|
|
|
Return Value:
|
|
|
|
NONE.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
LARGE_INTEGER BaseGetTickMagicDivisor = { 0xe219652c, 0xd1b71758 };
|
|
CCHAR BaseGetTickMagicShiftCount = 13;
|
|
|
|
LARGE_INTEGER TimeRemaining;
|
|
LARGE_INTEGER MillisecondsRemaining;
|
|
|
|
//
|
|
// If the expiration time is infinite,
|
|
// so is the interval
|
|
//
|
|
|
|
if ( ExpirationTime.HighPart == 0x7FFFFFFF &&
|
|
ExpirationTime.LowPart == 0xFFFFFFFF ) {
|
|
Context->Interval = INFINITE;
|
|
|
|
//
|
|
// Handle non-infinite expiration times
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Compute the time remaining before the expiration time
|
|
//
|
|
|
|
TimeRemaining.QuadPart = ExpirationTime.QuadPart -
|
|
Context->StartTime.QuadPart;
|
|
|
|
//
|
|
// If the time has already expired,
|
|
// indicate so.
|
|
//
|
|
|
|
if ( TimeRemaining.QuadPart < 0 ) {
|
|
|
|
Context->Interval = 0;
|
|
|
|
//
|
|
// If the time hasn't expired, compute the number of milliseconds
|
|
// remaining.
|
|
//
|
|
|
|
} else {
|
|
|
|
MillisecondsRemaining = RtlExtendedMagicDivide(
|
|
TimeRemaining,
|
|
BaseGetTickMagicDivisor,
|
|
BaseGetTickMagicShiftCount );
|
|
|
|
if ( MillisecondsRemaining.HighPart == 0 &&
|
|
MillisecondsRemaining.LowPart < 0x7fffffff ) {
|
|
|
|
Context->Interval = MillisecondsRemaining.LowPart;
|
|
|
|
} else {
|
|
|
|
Context->Interval = INFINITE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SECURITY_STATUS
|
|
SsprHandleFirstCall(
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection,
|
|
IN PCredHandle CredentialHandle,
|
|
IN OUT PCtxtHandle ContextHandle,
|
|
IN ULONG ContextReqFlags,
|
|
IN ULONG InputTokenSize,
|
|
IN PVOID InputToken,
|
|
IN OUT PULONG OutputTokenSize,
|
|
OUT PVOID OutputToken,
|
|
OUT PULONG ContextAttributes,
|
|
OUT PTimeStamp ExpirationTime,
|
|
OUT PUCHAR SessionKey,
|
|
OUT PULONG NegotiateFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle the First Call part of InitializeSecurityContext.
|
|
|
|
Arguments:
|
|
|
|
All arguments same as for InitializeSecurityContext
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS -- All OK
|
|
SEC_I_CALLBACK_NEEDED -- Caller should call again later
|
|
|
|
SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid
|
|
SEC_E_BUFFER_TOO_SMALL -- Buffer for output token isn't big enough
|
|
SEC_E_INSUFFICIENT_MEMORY -- Not enough memory
|
|
|
|
--*/
|
|
|
|
{
|
|
SECURITY_STATUS SecStatus;
|
|
PSSP_CONTEXT Context = NULL;
|
|
PSSP_CREDENTIAL Credential = NULL;
|
|
|
|
PNEGOTIATE_MESSAGE NegotiateMessage = NULL;
|
|
ULONG NegotiateMessageSize;
|
|
PCHAR Where;
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
*ContextAttributes = 0;
|
|
*NegotiateFlags = 0;
|
|
|
|
//
|
|
// Get a pointer to the credential
|
|
//
|
|
|
|
Credential = SspCredentialReferenceCredential(
|
|
CredentialHandle,
|
|
ClientConnection,
|
|
FALSE,
|
|
FALSE );
|
|
|
|
if ( Credential == NULL ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleFirstCall: invalid credential handle.\n" ));
|
|
SecStatus = SEC_E_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( (Credential->CredentialUseFlags & SECPKG_CRED_OUTBOUND) == 0 ) {
|
|
SspPrint(( SSP_API, "SsprHandleFirstCall: invalid credential use.\n" ));
|
|
SecStatus = SEC_E_INVALID_CREDENTIAL_USE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Allocate a new context
|
|
//
|
|
|
|
Context = SspContextAllocateContext( ClientConnection );
|
|
|
|
if ( Context == NULL ) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build a handle to the newly created context.
|
|
//
|
|
|
|
ContextHandle->dwUpper = (DWORD) Context;
|
|
ContextHandle->dwLower = SspCommonSecHandleValue;
|
|
|
|
|
|
//
|
|
// We don't support any options.
|
|
//
|
|
// Complain about those that require we do something.
|
|
//
|
|
|
|
if ( (ContextReqFlags & (ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_PROMPT_FOR_CREDS |
|
|
ISC_REQ_USE_SUPPLIED_CREDS )) != 0 ) {
|
|
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleFirstCall: invalid ContextReqFlags 0x%lx.\n",
|
|
ContextReqFlags ));
|
|
SecStatus = SEC_E_INVALID_CONTEXT_REQ;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Capture the default credentials from the credential structure.
|
|
//
|
|
if ( Credential->DomainName.Length != 0 ) {
|
|
SecStatus = SspDuplicateUnicodeString(
|
|
&Context->DomainName,
|
|
&Credential->DomainName
|
|
);
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
if ( Credential->UserName.Length != 0 ) {
|
|
SecStatus = SspDuplicateUnicodeString(
|
|
&Context->UserName,
|
|
&Credential->UserName
|
|
);
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
SecStatus = SspCredentialGetPassword(
|
|
Credential,
|
|
&Context->Password
|
|
);
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Compute the negotiate flags
|
|
//
|
|
|
|
Context->NegotiateFlags = NTLMSSP_NEGOTIATE_UNICODE |
|
|
NTLMSSP_NEGOTIATE_OEM |
|
|
NTLMSSP_NEGOTIATE_NTLM |
|
|
NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
|
|
|
|
|
//
|
|
// If the caller specified SEQUENCE_DETECT or REPLAY_DETECT,
|
|
// that means they want to use the MakeSignature/VerifySignature
|
|
// calls. Add this to the negotiate.
|
|
//
|
|
|
|
if ((ContextReqFlags & ISC_REQ_SEQUENCE_DETECT) ||
|
|
(ContextReqFlags & ISC_REQ_REPLAY_DETECT)) {
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN |
|
|
#ifndef EXPORT_BUILD
|
|
NTLMSSP_NEGOTIATE_STRONG_CRYPT |
|
|
#endif // EXPORT_BUILD
|
|
NTLMSSP_NEGOTIATE_LM_KEY;
|
|
|
|
*ContextAttributes |= ISC_REQ_SEQUENCE_DETECT;
|
|
Context->ContextFlags |= ISC_REQ_SEQUENCE_DETECT;
|
|
}
|
|
|
|
|
|
if (ContextReqFlags & ISC_REQ_CONFIDENTIALITY) {
|
|
if (SspGlobalEncryptionEnabled) {
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL |
|
|
#ifndef EXPORT_BUILD
|
|
NTLMSSP_NEGOTIATE_STRONG_CRYPT |
|
|
#endif // EXPORT_BUILD
|
|
|
|
NTLMSSP_NEGOTIATE_LM_KEY;
|
|
|
|
*ContextAttributes |= ISC_REQ_CONFIDENTIALITY;
|
|
Context->ContextFlags |= ISC_REQ_CONFIDENTIALITY;
|
|
} else {
|
|
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if the caller wants identify level
|
|
//
|
|
|
|
if ((ContextReqFlags & ISC_REQ_IDENTIFY)!= 0) {
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_IDENTIFY;
|
|
*ContextAttributes |= ISC_RET_IDENTIFY;
|
|
Context->ContextFlags |= ISC_REQ_IDENTIFY;
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_IDENTIFY;
|
|
}
|
|
|
|
|
|
IF_DEBUG( USE_OEM ) {
|
|
Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_UNICODE;
|
|
}
|
|
|
|
//
|
|
// For connection oriented security, we send a negotiate message to
|
|
// the server. For datagram, we get back the server's
|
|
// capabilities in the challenge message.
|
|
//
|
|
|
|
|
|
|
|
|
|
if ((ContextReqFlags & ISC_REQ_DATAGRAM) == 0) {
|
|
|
|
|
|
//
|
|
// Allocate a Negotiate message
|
|
//
|
|
|
|
NegotiateMessageSize = sizeof(*NegotiateMessage) +
|
|
SspGlobalOemComputerNameString.Length +
|
|
SspGlobalOemPrimaryDomainNameString.Length;
|
|
|
|
if ( NegotiateMessageSize > *OutputTokenSize ) {
|
|
SecStatus = SEC_E_BUFFER_TOO_SMALL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
NegotiateMessage = LocalAlloc( LMEM_ZEROINIT, NegotiateMessageSize );
|
|
|
|
if ( NegotiateMessage == NULL ) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// If this is the first call,
|
|
// build a Negotiate message.
|
|
//
|
|
|
|
strcpy( NegotiateMessage->Signature, NTLMSSP_SIGNATURE );
|
|
NegotiateMessage->MessageType = NtLmNegotiate;
|
|
NegotiateMessage->NegotiateFlags = Context->NegotiateFlags;
|
|
|
|
IF_DEBUG( REQUEST_TARGET ) {
|
|
NegotiateMessage->NegotiateFlags |= NTLMSSP_REQUEST_TARGET;
|
|
}
|
|
|
|
|
|
//
|
|
// Copy the DomainName and ComputerName into the negotiate message
|
|
// so the other side can determine if this is a call from the local system.
|
|
//
|
|
// Pass the names in the OEM character set since the character set hasn't
|
|
// been negotiated yet.
|
|
//
|
|
// Skip passing the workstation name if credentials were specified. This
|
|
// Ensures the other side doesn't fall into the case that this is the local
|
|
// system. We wan't to ensure the new credentials are authenticated.
|
|
//
|
|
|
|
Where = (PCHAR)(NegotiateMessage+1);
|
|
|
|
if ( Credential->DomainName.Length == 0 &&
|
|
Credential->UserName.Length == 0 &&
|
|
Credential->Password.Buffer == NULL ) {
|
|
|
|
SspContextCopyString( NegotiateMessage,
|
|
&NegotiateMessage->OemWorkstationName,
|
|
&SspGlobalOemComputerNameString,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
NegotiateMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED;
|
|
}
|
|
|
|
SspContextCopyString( NegotiateMessage,
|
|
&NegotiateMessage->OemDomainName,
|
|
&SspGlobalOemPrimaryDomainNameString,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
NegotiateMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED;
|
|
|
|
|
|
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
NegotiateMessageSize,
|
|
OutputToken,
|
|
NegotiateMessage );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
*OutputTokenSize = NegotiateMessageSize;
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// Save a reference to the credential in the context.
|
|
//
|
|
|
|
Context->Credential = Credential;
|
|
Credential = NULL;
|
|
|
|
//
|
|
// Check for a caller requesting datagram security.
|
|
//
|
|
|
|
if ((ContextReqFlags & ISC_REQ_DATAGRAM) != 0 ) {
|
|
|
|
//
|
|
// Turn off strong crypt, because we can't negotiate it.
|
|
//
|
|
#ifndef EXPORT_BUILD
|
|
Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_STRONG_CRYPT;
|
|
#endif // EXPORT_BUILD
|
|
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_DATAGRAM;
|
|
|
|
Context->ContextFlags |= ISC_REQ_DATAGRAM;
|
|
*ContextAttributes |= ISC_REQ_DATAGRAM;
|
|
*NegotiateFlags = Context->NegotiateFlags;
|
|
|
|
}
|
|
|
|
//
|
|
// If we are negotiating datagram or are building a domestic
|
|
// version, create a good session key
|
|
//
|
|
|
|
#ifdef EXPORT_BUILD
|
|
if ((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) != 0)
|
|
#endif
|
|
{
|
|
RtlZeroMemory(
|
|
Context->SessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH
|
|
);
|
|
|
|
//
|
|
// Generate a session key for this context if sign or seal was
|
|
// requested.
|
|
//
|
|
|
|
if (Context->NegotiateFlags & (NTLMSSP_NEGOTIATE_SIGN |
|
|
NTLMSSP_NEGOTIATE_SEAL) != 0) {
|
|
|
|
SspGenerateRandomBits(
|
|
Context->SessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH
|
|
);
|
|
}
|
|
RtlCopyMemory(
|
|
SessionKey,
|
|
Context->SessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH
|
|
);
|
|
}
|
|
|
|
//
|
|
// Return output parameters to the caller.
|
|
//
|
|
|
|
*ExpirationTime = SspContextGetTimeStamp( Context, TRUE );
|
|
|
|
SecStatus = SEC_I_CALLBACK_NEEDED;
|
|
Context->State = NegotiateSentState;
|
|
|
|
//
|
|
// Free and locally used resources.
|
|
//
|
|
Cleanup:
|
|
|
|
if ( Context != NULL ) {
|
|
|
|
//
|
|
// If we failed,
|
|
// deallocate the context we allocated above.
|
|
//
|
|
// Delinking is a side effect of referencing, so do that.
|
|
//
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
PSSP_CONTEXT LocalContext;
|
|
LocalContext = SspContextReferenceContext( ContextHandle,
|
|
ClientConnection,
|
|
TRUE );
|
|
|
|
ASSERT( LocalContext != NULL );
|
|
if ( LocalContext != NULL ) {
|
|
SspContextDereferenceContext( LocalContext );
|
|
}
|
|
}
|
|
|
|
// Always dereference it.
|
|
|
|
SspContextDereferenceContext( Context );
|
|
}
|
|
|
|
if ( NegotiateMessage != NULL ) {
|
|
(VOID) LocalFree( NegotiateMessage );
|
|
}
|
|
|
|
if ( Credential != NULL ) {
|
|
SspCredentialDereferenceCredential( Credential );
|
|
}
|
|
|
|
return SecStatus;
|
|
UNREFERENCED_PARAMETER( InputToken );
|
|
UNREFERENCED_PARAMETER( InputTokenSize );
|
|
}
|
|
|
|
|
|
|
|
SECURITY_STATUS
|
|
SsprHandleNegotiateMessage(
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection,
|
|
IN PCredHandle CredentialHandle,
|
|
IN OUT PCtxtHandle ContextHandle,
|
|
IN ULONG ContextReqFlags,
|
|
IN ULONG InputTokenSize,
|
|
IN PVOID InputToken,
|
|
IN OUT PULONG OutputTokenSize,
|
|
OUT PVOID OutputToken,
|
|
OUT PULONG ContextAttributes,
|
|
OUT PTimeStamp ExpirationTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle the Negotiate message part of AcceptSecurityContext.
|
|
|
|
Arguments:
|
|
|
|
All arguments same as for AcceptSecurityContext
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Message handled
|
|
SEC_I_CALLBACK_NEEDED -- Caller should call again later
|
|
|
|
SEC_E_INVALID_TOKEN -- Token improperly formatted
|
|
SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid
|
|
SEC_E_BUFFER_TOO_SMALL -- Buffer for output token isn't big enough
|
|
SEC_E_INSUFFICIENT_MEMORY -- Not enough memory
|
|
|
|
--*/
|
|
|
|
{
|
|
SECURITY_STATUS SecStatus;
|
|
PSSP_CONTEXT Context = NULL;
|
|
PSSP_CREDENTIAL Credential = NULL;
|
|
STRING TargetName;
|
|
ULONG TargetFlags = 0;
|
|
|
|
PNEGOTIATE_MESSAGE NegotiateMessage = NULL;
|
|
|
|
PCHALLENGE_MESSAGE ChallengeMessage = NULL;
|
|
ULONG ChallengeMessageSize;
|
|
PCHAR Where;
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
*ContextAttributes = 0;
|
|
RtlInitString( &TargetName, NULL );
|
|
|
|
//
|
|
// Make sure we didn't get any silly context requirements.
|
|
//
|
|
|
|
if ( (ContextReqFlags & ASC_REQ_ALLOCATE_MEMORY) != 0 ) {
|
|
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleNegotiateMessage: invalid ContextReqFlags 0x%lx.\n",
|
|
ContextReqFlags ));
|
|
SecStatus = SEC_E_INVALID_CONTEXT_REQ;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get a pointer to the credential
|
|
//
|
|
|
|
Credential = SspCredentialReferenceCredential(
|
|
CredentialHandle,
|
|
ClientConnection,
|
|
FALSE,
|
|
FALSE );
|
|
|
|
if ( Credential == NULL ) {
|
|
|
|
//
|
|
// If the credential is from the security.dll, ignore it.
|
|
//
|
|
|
|
if ( (CredentialHandle->dwLower != SEC_HANDLE_SECURITY) ||
|
|
(SspCommonSecHandleValue != SEC_HANDLE_NTLMSSPS) ) {
|
|
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleNegotiateMessage: invalid credential handle.\n" ));
|
|
SecStatus = SEC_E_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
} else {
|
|
if ( (Credential->CredentialUseFlags & SECPKG_CRED_INBOUND) == 0 ) {
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleNegotiateMessage: invalid credential use.\n" ));
|
|
SecStatus = SEC_E_INVALID_CREDENTIAL_USE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Allocate a new context
|
|
//
|
|
|
|
Context = SspContextAllocateContext( ClientConnection );
|
|
|
|
if ( Context == NULL ) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build a handle to the newly created context.
|
|
//
|
|
|
|
ContextHandle->dwUpper = (DWORD) Context;
|
|
ContextHandle->dwLower = SspCommonSecHandleValue;
|
|
|
|
|
|
|
|
|
|
if ( ContextReqFlags & ISC_REQ_REPLAY_DETECT ||
|
|
ContextReqFlags & ISC_REQ_SEQUENCE_DETECT ) {
|
|
|
|
Context->ContextFlags = ISC_REQ_SEQUENCE_DETECT;
|
|
}
|
|
|
|
if ( ContextReqFlags & ISC_REQ_CONFIDENTIALITY ) {
|
|
|
|
if (SspGlobalEncryptionEnabled) {
|
|
Context->ContextFlags = ISC_REQ_CONFIDENTIALITY;
|
|
} else {
|
|
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
#ifdef notdef // ?? RPC sends me 0xa03 here
|
|
if ( ContextReqFlags & ~ISC_REQ_REPLAY_DETECT &
|
|
~ISC_REQ_SEQUENCE_DETECT != 0 ) {
|
|
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleNegotiateMessage: invalid ContextReqFlags 0x%lx.\n",
|
|
ContextReqFlags ));
|
|
SecStatus = SEC_E_INVALID_CONTEXT_REQ;
|
|
goto Cleanup;
|
|
}
|
|
#else // notdef
|
|
UNREFERENCED_PARAMETER( ContextReqFlags );
|
|
#endif // notdef
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Get the NegotiateMessage. If we are re-establishing a datagram
|
|
// context then there may not be one.
|
|
//
|
|
|
|
if ( InputTokenSize >= sizeof(OLD_NEGOTIATE_MESSAGE) ) {
|
|
|
|
SecStatus = SspContextGetMessage( ClientConnection,
|
|
InputToken,
|
|
InputTokenSize,
|
|
NtLmNegotiate,
|
|
&NegotiateMessage );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleNegotiateMessage: "
|
|
"NegotiateMessage GetMessage returns 0x%lx\n",
|
|
SecStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Compute the TargetName to return in the ChallengeMessage.
|
|
//
|
|
|
|
if ( NegotiateMessage->NegotiateFlags & NTLMSSP_REQUEST_TARGET ) {
|
|
// Ensure SspGlobalTargetName is up to date.
|
|
SspGetPrimaryDomainNameAndTargetName();
|
|
if ( NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_UNICODE ) {
|
|
TargetName = *((PSTRING)&SspGlobalTargetName);
|
|
} else {
|
|
TargetName = SspGlobalOemTargetName;
|
|
}
|
|
TargetFlags = NTLMSSP_REQUEST_TARGET | SspGlobalTargetFlags;
|
|
|
|
} else {
|
|
TargetFlags = 0;
|
|
}
|
|
|
|
//
|
|
// Allocate a Challenge message
|
|
//
|
|
|
|
ChallengeMessageSize = sizeof(*ChallengeMessage) + TargetName.Length;
|
|
|
|
if ( ChallengeMessageSize > *OutputTokenSize ) {
|
|
SecStatus = SEC_E_BUFFER_TOO_SMALL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
ChallengeMessage = LocalAlloc( LMEM_ZEROINIT, ChallengeMessageSize );
|
|
|
|
if ( ChallengeMessage == NULL ) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
ChallengeMessage->NegotiateFlags = 0;
|
|
|
|
|
|
//
|
|
// Check that both sides can use the same authentication model. For
|
|
// compatibility with beta 1 and 2 (builds 612 and 683), no requested
|
|
// authentication type is assumed to be NTLM. If NetWare is explicitly
|
|
// asked for, it is assumed that NTLM would have been also, so if it
|
|
// wasn't, return an error.
|
|
//
|
|
|
|
|
|
if ( (NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NETWARE) &&
|
|
!(NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM ) ) {
|
|
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleNegotiateMessage: "
|
|
"NegotiateMessage asked for Netware only.\n" ));
|
|
goto Cleanup;
|
|
} else {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM;
|
|
}
|
|
|
|
//
|
|
// Check if the caller requested that we use the LM session key instead
|
|
// of the NT session key.
|
|
//
|
|
|
|
if ( NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY ) {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_LM_KEY;
|
|
}
|
|
|
|
#ifndef EXPORT_BUILD
|
|
if ( NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_STRONG_CRYPT ) {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_STRONG_CRYPT;
|
|
}
|
|
|
|
#endif
|
|
//
|
|
// If the client wants to always sign messages, so be it.
|
|
//
|
|
|
|
if (NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN ) {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
|
}
|
|
|
|
//
|
|
// If the caller wants identify level, so be it.
|
|
//
|
|
|
|
if (NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY ) {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_IDENTIFY;
|
|
}
|
|
|
|
//
|
|
// Determine if the caller wants OEM or UNICODE
|
|
//
|
|
// Prefer UNICODE if caller allows both.
|
|
//
|
|
|
|
if ( NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_UNICODE ) {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;
|
|
} else if ( NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_OEM ){
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM;
|
|
} else {
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleNegotiateMessage: "
|
|
"NegotiateMessage bad NegotiateFlags 0x%lx\n",
|
|
NegotiateMessage->NegotiateFlags ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Client wants Sign capability, OK.
|
|
//
|
|
if (NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN;
|
|
}
|
|
|
|
//
|
|
// Client wants Seal, OK.
|
|
//
|
|
|
|
if (NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL)
|
|
{
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;
|
|
}
|
|
|
|
//
|
|
// If the client supplied the Domain Name and User Name,
|
|
// and did not request datagram, see if the client is running
|
|
// on this local machine.
|
|
//
|
|
IF_DEBUG(NO_LOCAL){
|
|
}
|
|
else
|
|
{
|
|
if ( ( (NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) == 0) &&
|
|
( (NegotiateMessage->NegotiateFlags &
|
|
(NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED|NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED)) ==
|
|
(NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED|NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) ) ) {
|
|
|
|
//
|
|
// The client must pass the new negotiate message if they pass
|
|
// these flags
|
|
//
|
|
|
|
if (InputTokenSize < sizeof(NEGOTIATE_MESSAGE)) {
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
//
|
|
// Convert the names to absolute references so we can compare them
|
|
//
|
|
if ( !SspConvertRelativeToAbsolute( NegotiateMessage,
|
|
InputTokenSize,
|
|
&NegotiateMessage->OemDomainName,
|
|
FALSE, // No special alignment
|
|
FALSE ) ) { // NULL not OK
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( !SspConvertRelativeToAbsolute( NegotiateMessage,
|
|
InputTokenSize,
|
|
&NegotiateMessage->OemWorkstationName,
|
|
FALSE, // No special alignment
|
|
FALSE ) ) { // NULL not OK
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If both strings match,
|
|
// this is a local call.
|
|
//
|
|
// The strings have already been uppercased.
|
|
//
|
|
|
|
if ( RtlEqualString( &NegotiateMessage->OemWorkstationName,
|
|
&SspGlobalOemComputerNameString,
|
|
FALSE ) &&
|
|
RtlEqualString( &NegotiateMessage->OemDomainName,
|
|
&SspGlobalOemPrimaryDomainNameString,
|
|
FALSE ) ) {
|
|
|
|
//
|
|
// If this call is being handled by the security.dll directly
|
|
// then force it to call the NTLMSSP service
|
|
//
|
|
|
|
if ( SspCommonSecHandleValue != SEC_HANDLE_NTLMSSPS ) {
|
|
SecStatus = SEC_I_CALL_NTLMSSP_SERVICE;
|
|
goto Cleanup;
|
|
}
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_LOCAL_CALL;
|
|
ChallengeMessage->ServerContextHandleLower = ContextHandle->dwLower;
|
|
ChallengeMessage->ServerContextHandleUpper = ContextHandle->dwUpper;
|
|
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Check if datagram is being negotiated
|
|
//
|
|
|
|
if ( (NegotiateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) ==
|
|
NTLMSSP_NEGOTIATE_DATAGRAM) {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_DATAGRAM;
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
//
|
|
// No negotiate message. We need to check if the caller is asking
|
|
// for datagram.
|
|
//
|
|
|
|
if ((ContextReqFlags & ISC_REQ_DATAGRAM) == 0 ) {
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleNegotiateMessage: "
|
|
"NegotiateMessage size wrong %ld\n",
|
|
InputTokenSize ));
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
//
|
|
// Allocate a Challenge message
|
|
//
|
|
|
|
ChallengeMessageSize = sizeof(*ChallengeMessage);
|
|
|
|
if ( ChallengeMessageSize > *OutputTokenSize ) {
|
|
SecStatus = SEC_E_BUFFER_TOO_SMALL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
ChallengeMessage = LocalAlloc( LMEM_ZEROINIT, ChallengeMessageSize );
|
|
|
|
if ( ChallengeMessage == NULL ) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Record in the context that we are doing datagram. We will tell
|
|
// the client everything we can negotiate and let it decide what
|
|
// to negotiate. Note that we don't negotate strong crypt -
|
|
// with datagram we start encrypting data before we negotiate
|
|
// so we can't use it.
|
|
//
|
|
|
|
ChallengeMessage->NegotiateFlags = NTLMSSP_NEGOTIATE_DATAGRAM |
|
|
NTLMSSP_NEGOTIATE_UNICODE |
|
|
NTLMSSP_NEGOTIATE_OEM |
|
|
NTLMSSP_NEGOTIATE_SIGN |
|
|
NTLMSSP_NEGOTIATE_LM_KEY |
|
|
NTLMSSP_NEGOTIATE_NTLM |
|
|
NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
|
|
NTLMSSP_NEGOTIATE_IDENTIFY;
|
|
|
|
if (SspGlobalEncryptionEnabled) {
|
|
ChallengeMessage->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// Build the Challenge Message
|
|
//
|
|
|
|
strcpy( ChallengeMessage->Signature, NTLMSSP_SIGNATURE );
|
|
ChallengeMessage->MessageType = NtLmChallenge;
|
|
SspContextComputeChallenge( ChallengeMessage->Challenge );
|
|
|
|
Where = (PCHAR)(ChallengeMessage+1);
|
|
|
|
SspContextCopyString( ChallengeMessage,
|
|
&ChallengeMessage->TargetName,
|
|
&TargetName,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
ChallengeMessage->NegotiateFlags |= TargetFlags;
|
|
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
ChallengeMessageSize,
|
|
OutputToken,
|
|
ChallengeMessage );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
*OutputTokenSize = ChallengeMessageSize;
|
|
|
|
//
|
|
// Save the Challenge and Negotiate Flags in the Context so it
|
|
// is available when the authenticate message comes in.
|
|
//
|
|
|
|
RtlCopyMemory( Context->Challenge,
|
|
ChallengeMessage->Challenge,
|
|
sizeof( Context->Challenge ) );
|
|
|
|
Context->NegotiateFlags = ChallengeMessage->NegotiateFlags;
|
|
|
|
|
|
|
|
|
|
//
|
|
// Return output parameters to the caller.
|
|
//
|
|
*ExpirationTime = SspContextGetTimeStamp( Context, TRUE );
|
|
Context->State = ChallengeSentState;
|
|
|
|
SecStatus = SEC_I_CALLBACK_NEEDED;
|
|
|
|
//
|
|
// Free and locally used resources.
|
|
//
|
|
Cleanup:
|
|
|
|
if ( Context != NULL ) {
|
|
|
|
//
|
|
// If we failed,
|
|
// deallocate the context we allocated above.
|
|
//
|
|
// Delinking is a side effect of referencing, so do that.
|
|
//
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
PSSP_CONTEXT LocalContext;
|
|
LocalContext = SspContextReferenceContext( ContextHandle,
|
|
ClientConnection,
|
|
TRUE );
|
|
|
|
ASSERT( LocalContext != NULL );
|
|
if ( LocalContext != NULL ) {
|
|
SspContextDereferenceContext( LocalContext );
|
|
}
|
|
}
|
|
|
|
// Always dereference it.
|
|
|
|
SspContextDereferenceContext( Context );
|
|
}
|
|
|
|
if ( NegotiateMessage != NULL ) {
|
|
(VOID) LocalFree( NegotiateMessage );
|
|
}
|
|
|
|
if ( ChallengeMessage != NULL ) {
|
|
(VOID) LocalFree( ChallengeMessage );
|
|
}
|
|
|
|
if ( Credential != NULL ) {
|
|
SspCredentialDereferenceCredential( Credential );
|
|
}
|
|
|
|
return SecStatus;
|
|
}
|
|
|
|
|
|
|
|
SECURITY_STATUS
|
|
SsprHandleChallengeMessage(
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection,
|
|
IN PCredHandle CredentialHandle,
|
|
IN OUT PCtxtHandle ContextHandle,
|
|
IN HANDLE ClientTokenHandle,
|
|
IN PLUID LogonId,
|
|
IN ULONG ContextReqFlags,
|
|
IN LPWSTR ContextDomainName,
|
|
IN ULONG DomainNameSize,
|
|
IN LPWSTR ContextUserName,
|
|
IN ULONG UserNameSize,
|
|
IN LPWSTR ContextPassword,
|
|
IN ULONG PasswordSize,
|
|
IN ULONG InputTokenSize,
|
|
IN PVOID InputToken,
|
|
IN OUT PULONG OutputTokenSize,
|
|
OUT PVOID OutputToken,
|
|
OUT PULONG ContextAttributes,
|
|
OUT PTimeStamp ExpirationTime,
|
|
OUT PUCHAR SessionKey,
|
|
OUT PULONG NegotiateFlags,
|
|
OUT LPWSTR ContextNames
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle the Challenge message part of InitializeSecurityContext.
|
|
|
|
Arguments:
|
|
|
|
ClientTokenHandle - Optionally passes in a handle to an impersonation
|
|
token of the client. This impersonation token will be passed directly
|
|
to the server if the server is running on the same machine. In that
|
|
case, this routine will NULL the ClientTokenHandle letting the caller
|
|
know that it need not close the handle. The server will close the handle
|
|
when it's done with it.
|
|
|
|
LogonId -- LogonId of the calling process.
|
|
|
|
DomainName,UserName,Password - Passed in credentials to be used for this
|
|
context.
|
|
|
|
DomainNameSize,userNameSize,PasswordSize - length in characters of the
|
|
credentials to be used for this context.
|
|
|
|
SessionKey - Session key to use for this context
|
|
|
|
NegotiateFlags - Flags negotiated for this context
|
|
|
|
ContextNames - Receives the domainname\username used for this
|
|
context if they were specified separately.
|
|
|
|
All other arguments same as for InitializeSecurityContext
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Message handled
|
|
SEC_I_CALLBACK_NEEDED -- Caller should call again later
|
|
|
|
SEC_E_INVALID_TOKEN -- Token improperly formatted
|
|
SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid
|
|
SEC_E_BUFFER_TOO_SMALL -- Buffer for output token isn't big enough
|
|
SEC_E_NO_CREDENTIALS -- There are no credentials for this client
|
|
SEC_E_INSUFFICIENT_MEMORY -- Not enough memory
|
|
|
|
--*/
|
|
|
|
{
|
|
SECURITY_STATUS SecStatus;
|
|
PSSP_CONTEXT Context = NULL;
|
|
PCHALLENGE_MESSAGE ChallengeMessage = NULL;
|
|
PAUTHENTICATE_MESSAGE AuthenticateMessage = NULL;
|
|
PMSV1_0_GETCHALLENRESP_RESPONSE ChallengeResponseMessage = NULL;
|
|
STRING UserName;
|
|
STRING DomainName;
|
|
STRING Workstation;
|
|
STRING LmChallengeResponse;
|
|
STRING NtChallengeResponse;
|
|
STRING DatagramSessionKey;
|
|
BOOLEAN DoUnicode = TRUE;
|
|
WCHAR Name[UNLEN+DNLEN+2];
|
|
|
|
NTSTATUS Status;
|
|
NTSTATUS ProtocolStatus;
|
|
|
|
LPBYTE GetChallengeResponseBuffer[
|
|
sizeof(MSV1_0_GETCHALLENRESP_REQUEST) +
|
|
(PWLEN+1) * sizeof(WCHAR) ];
|
|
PMSV1_0_GETCHALLENRESP_REQUEST GetChallengeResponse;
|
|
ULONG GetChallengeResponseSize;
|
|
|
|
ULONG ChallengeResponseSize;
|
|
ULONG AuthenticateMessageSize;
|
|
PCHAR Where;
|
|
UCHAR LocalSessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
|
|
UCHAR DatagramKey[MSV1_0_USER_SESSION_KEY_LENGTH];
|
|
PLUID ClientLogonId;
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
*ContextAttributes = 0;
|
|
UserName.Buffer = NULL;
|
|
DomainName.Buffer = NULL;
|
|
GetChallengeResponse =
|
|
(PMSV1_0_GETCHALLENRESP_REQUEST) GetChallengeResponseBuffer;
|
|
|
|
|
|
//
|
|
// Find the currently existing context.
|
|
//
|
|
|
|
Context = SspContextReferenceContext( ContextHandle,
|
|
ClientConnection,
|
|
FALSE );
|
|
|
|
if ( Context == NULL ) {
|
|
|
|
//
|
|
// Check if this is a handle from the security.dll instead of from
|
|
// ntlmssp service.
|
|
//
|
|
|
|
if ( (ContextHandle->dwLower == SEC_HANDLE_SECURITY) &&
|
|
(SspCommonSecHandleValue == SEC_HANDLE_NTLMSSPS) ) {
|
|
|
|
//
|
|
// The context was created in the security.dll and is being
|
|
// completed in the service. So we have to copy over the
|
|
// context structure to get all the flags.
|
|
//
|
|
|
|
SSP_CONTEXT ContextCopy;
|
|
|
|
SecStatus = SspLpcCopyFromClientBuffer(
|
|
ClientConnection,
|
|
sizeof(SSP_CONTEXT),
|
|
&ContextCopy,
|
|
(PVOID) ContextHandle->dwUpper
|
|
);
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
Context = SspContextAllocateContext( ClientConnection );
|
|
|
|
if ( Context == NULL ) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Context->NegotiateFlags = ContextCopy.NegotiateFlags;
|
|
Context->ContextFlags = ContextCopy.ContextFlags;
|
|
Context->State = NegotiateSentState;
|
|
ASSERT(ContextCopy.State == PassedToServiceState);
|
|
Context->StartTime = ContextCopy.StartTime;
|
|
Context->Interval = ContextCopy.Interval;
|
|
|
|
//
|
|
// Copy over the domain name, user name, and password
|
|
// from the old context
|
|
//
|
|
|
|
SecStatus = SspGetUnicodeStringFromClient(
|
|
ClientConnection,
|
|
ContextDomainName,
|
|
DomainNameSize,
|
|
DNLEN,
|
|
&Context->DomainName );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
SspPrint(( SSP_API, "Cannot copy domain name.\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
SecStatus = SspGetUnicodeStringFromClient(
|
|
ClientConnection,
|
|
ContextUserName,
|
|
UserNameSize,
|
|
UNLEN,
|
|
&Context->UserName );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
SspPrint(( SSP_API, "Cannot copy user name.\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
SecStatus = SspGetUnicodeStringFromClient(
|
|
ClientConnection,
|
|
ContextPassword,
|
|
PasswordSize,
|
|
PWLEN,
|
|
&Context->Password );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
SspPrint(( SSP_API, "Cannot copy password.\n" ));
|
|
goto Cleanup;
|
|
}
|
|
SspHidePassword(&Context->Password);
|
|
|
|
ContextHandle->dwUpper = (DWORD) Context;
|
|
ContextHandle->dwLower = SspCommonSecHandleValue;
|
|
|
|
//
|
|
// Set the token and logon id to use to be the one from the
|
|
// client
|
|
//
|
|
|
|
ASSERT(ClientTokenHandle != NULL);
|
|
ClientLogonId = LogonId;
|
|
} else {
|
|
SecStatus = SEC_E_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Check if this context has been passed to the service
|
|
//
|
|
|
|
if (Context->ServerContextHandle.dwUpper != 0) {
|
|
ASSERT(SspCommonSecHandleValue == SEC_HANDLE_SECURITY);
|
|
SecStatus = SEC_I_CALL_NTLMSSP_SERVICE;
|
|
*ContextHandle = Context->ServerContextHandle;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If this is not reauthentication (or is datagram reauthentication)
|
|
// pull the token out of the associated credential.
|
|
//
|
|
|
|
if ((Context->State != AuthenticateSentState) ||
|
|
((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) != 0)) {
|
|
ClientLogonId = &Context->Credential->LogonId;
|
|
ClientTokenHandle = Context->Credential->ClientTokenHandle;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If we have already sent the authenticate message, then this must be
|
|
// RPC calling Initialize a third time to re-authenticate a connection.
|
|
// This happens when a new interface is called over an existing
|
|
// connection. What we do here is build a NULL authenticate message
|
|
// that the server will recognize and also ignore.
|
|
//
|
|
|
|
//
|
|
// That being said, if we are doing datagram style authentication then
|
|
// the story is different. The server may have dropped this security
|
|
// context and then the client sent another packet over. The server
|
|
// will then be trying to restore the context, so we need to build
|
|
// another authenticate message.
|
|
//
|
|
|
|
|
|
if ( Context->State == AuthenticateSentState ) {
|
|
AUTHENTICATE_MESSAGE NullMessage;
|
|
|
|
if (((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) ==
|
|
NTLMSSP_NEGOTIATE_DATAGRAM) &&
|
|
(InputTokenSize != 0) &&
|
|
(InputToken != NULL) ) {
|
|
|
|
//
|
|
// we are doing a reauthentication for datagram, so let this
|
|
// through. We don't want the security.dll remapping this
|
|
// context.
|
|
//
|
|
|
|
*ContextAttributes |= SSP_RET_REAUTHENTICATION;
|
|
|
|
} else {
|
|
|
|
|
|
//
|
|
// To make sure this is the intended meaning of the call, check
|
|
// that the input token is NULL.
|
|
//
|
|
|
|
if ( (InputTokenSize != 0) || (InputToken != NULL) ) {
|
|
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( *OutputTokenSize < sizeof(NullMessage) ) {
|
|
|
|
SecStatus = SEC_E_BUFFER_TOO_SMALL;
|
|
}
|
|
else {
|
|
|
|
strcpy( NullMessage.Signature, NTLMSSP_SIGNATURE );
|
|
NullMessage.MessageType = NtLmAuthenticate;
|
|
RtlZeroMemory(&NullMessage.LmChallengeResponse,5*sizeof(STRING));
|
|
*OutputTokenSize = sizeof(NullMessage);
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
sizeof(NullMessage),
|
|
OutputToken,
|
|
&NullMessage );
|
|
}
|
|
|
|
*ContextAttributes |= SSP_RET_REAUTHENTICATION;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
|
|
} else if ( Context->State != NegotiateSentState ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleChallengeMessage: "
|
|
"Context not in NegotiateSentState\n" ));
|
|
SecStatus = SEC_E_OUT_OF_SEQUENCE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// We don't support any options.
|
|
//
|
|
// Complain about those that require we do something.
|
|
//
|
|
|
|
if ( (ContextReqFlags & (ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_PROMPT_FOR_CREDS |
|
|
ISC_REQ_USE_SUPPLIED_CREDS )) != 0 ) {
|
|
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleChallengeMessage: invalid ContextReqFlags 0x%lx.\n",
|
|
ContextReqFlags ));
|
|
SecStatus = SEC_E_INVALID_CONTEXT_REQ;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Ignore the Credential Handle.
|
|
//
|
|
// Since this is the second call,
|
|
// the credential is implied by the Context.
|
|
// We could double check that the Credential Handle is either NULL or
|
|
// correct. However, our implementation doesn't maintain a close
|
|
// association between the two (actually no association) so checking
|
|
// would require a lot of overhead.
|
|
//
|
|
|
|
UNREFERENCED_PARAMETER( CredentialHandle );
|
|
|
|
|
|
//
|
|
// Get the ChallengeMessage.
|
|
//
|
|
|
|
if ( InputTokenSize < sizeof(OLD_CHALLENGE_MESSAGE) ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleChallengeMessage: "
|
|
"ChallengeMessage size wrong %ld\n",
|
|
InputTokenSize ));
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
SecStatus = SspContextGetMessage( ClientConnection,
|
|
InputToken,
|
|
InputTokenSize,
|
|
NtLmChallenge,
|
|
&ChallengeMessage );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleChallengeMessage: "
|
|
"ChallengeMessage GetMessage returns 0x%lx\n",
|
|
SecStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Determine if the caller wants OEM or UNICODE
|
|
//
|
|
|
|
if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_UNICODE ) {
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE;
|
|
Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_OEM;
|
|
DoUnicode = TRUE;
|
|
} else if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_OEM ){
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM;
|
|
Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_UNICODE;
|
|
DoUnicode = FALSE;
|
|
} else {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleChallengeMessage: "
|
|
"ChallengeMessage bad NegotiateFlags 0x%lx\n",
|
|
ChallengeMessage->NegotiateFlags ));
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy other interesting negotiate flags into the context
|
|
//
|
|
|
|
|
|
if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY ) {
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_LM_KEY;
|
|
} else {
|
|
Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
|
|
}
|
|
|
|
#ifndef EXPORT_BUILD
|
|
DbgPrint("Challenge message flags = 0x%x\n",ChallengeMessage->NegotiateFlags);
|
|
if ( (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_STRONG_CRYPT) != 0 ) {
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_STRONG_CRYPT;
|
|
} else {
|
|
Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_STRONG_CRYPT;
|
|
}
|
|
DbgPrint("Context flags = 0x%x\n",Context->NegotiateFlags);
|
|
#endif // EXPORT_BUILD
|
|
|
|
if (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN ) {
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
|
} else {
|
|
Context->NegotiateFlags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
|
}
|
|
|
|
//
|
|
// Determine that the caller negotated to NTLM or nothing, but not
|
|
// NetWare.
|
|
//
|
|
|
|
if ( (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NETWARE) &&
|
|
!(ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM ) ) {
|
|
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
|
|
SspPrint(( SSP_API,
|
|
"SsprHandleChallengeMessage: "
|
|
"ChallengeMessage asked for Netware only.\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Check if we negotiated for identify level
|
|
//
|
|
|
|
if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) {
|
|
if (ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) {
|
|
|
|
Context->ContextFlags |= ISC_REQ_IDENTIFY;
|
|
*ContextAttributes |= ISC_RET_IDENTIFY;
|
|
} else {
|
|
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// If the server is running on this same machine,
|
|
// just duplicate our caller's token and use it.
|
|
//
|
|
|
|
if ( ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL ) {
|
|
CtxtHandle ServerContextHandle;
|
|
PSSP_CONTEXT ServerContext;
|
|
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel = SecurityImpersonation;
|
|
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_LOCAL_CALL;
|
|
|
|
//
|
|
// We can only do local calls from in the NTLMSSP service, not from
|
|
// any other process
|
|
//
|
|
|
|
if ( SspCommonSecHandleValue == SEC_HANDLE_SECURITY ) {
|
|
SecStatus = SEC_I_CALL_NTLMSSP_SERVICE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Require the new challenge message if we are going to access the
|
|
// server context handle
|
|
//
|
|
|
|
if ( InputTokenSize < sizeof(CHALLENGE_MESSAGE) ) {
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
//
|
|
// Open the server's context here within this process.
|
|
//
|
|
|
|
ServerContextHandle.dwUpper = ChallengeMessage->ServerContextHandleUpper;
|
|
ServerContextHandle.dwLower = ChallengeMessage->ServerContextHandleLower;
|
|
|
|
ServerContext = SspContextReferenceContext(
|
|
&ServerContextHandle,
|
|
NULL,
|
|
FALSE );
|
|
|
|
if ( ServerContext == NULL ) {
|
|
//
|
|
// This means the server has lied about this being a local call or
|
|
// the server process has exitted.
|
|
//
|
|
SspPrint(( SSP_API,
|
|
"SspHandleChallengeMessage: "
|
|
"ChallengeMessage bad ServerContextHandle 0x%lx 0x%lx\n",
|
|
ChallengeMessage->ServerContextHandleUpper,
|
|
ChallengeMessage->ServerContextHandleLower ));
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) != 0) {
|
|
ImpersonationLevel = SecurityIdentification;
|
|
}
|
|
SecStatus = SspDuplicateToken(
|
|
ClientTokenHandle,
|
|
ImpersonationLevel,
|
|
&ServerContext->TokenHandle
|
|
);
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleChallengeMessage: "
|
|
"Could not duplicate client token 0x%lx\n",
|
|
SecStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
SspContextDereferenceContext( ServerContext );
|
|
|
|
RtlZeroMemory(Context->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
//
|
|
// Don't pass any credentials in the authenticate message.
|
|
//
|
|
RtlInitString( &DomainName, NULL );
|
|
RtlInitString( &UserName, NULL );
|
|
RtlInitString( &Workstation, NULL );
|
|
RtlInitString( &NtChallengeResponse, NULL );
|
|
RtlInitString( &LmChallengeResponse, NULL );
|
|
RtlInitString( &DatagramSessionKey, NULL );
|
|
|
|
//
|
|
// If the server is running on a diffent machine,
|
|
// determine the caller's DomainName, UserName and ChallengeResponse
|
|
// to pass back in the AuthenicateMessage.
|
|
//
|
|
} else {
|
|
|
|
//
|
|
//
|
|
// Build the GetChallengeResponse message to pass to the LSA.
|
|
//
|
|
|
|
GetChallengeResponseSize = sizeof(*GetChallengeResponse);
|
|
GetChallengeResponse->MessageType = MsV1_0Lm20GetChallengeResponse;
|
|
GetChallengeResponse->ParameterControl = 0;
|
|
if ( Context->DomainName.Length == 0 ) {
|
|
GetChallengeResponse->ParameterControl |= RETURN_PRIMARY_LOGON_DOMAINNAME;
|
|
}
|
|
if ( Context->UserName.Length == 0 ) {
|
|
GetChallengeResponse->ParameterControl |= RETURN_PRIMARY_USERNAME;
|
|
}
|
|
|
|
//
|
|
// The password may be a zero length password
|
|
//
|
|
|
|
SspRevealPassword(&Context->Password);
|
|
GetChallengeResponse->Password = Context->Password;
|
|
if ( Context->Password.Buffer == NULL ) {
|
|
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
|
|
ULONG TokenInformationSize = sizeof(SECURITY_IMPERSONATION_LEVEL);
|
|
|
|
GetChallengeResponse->ParameterControl |= USE_PRIMARY_PASSWORD;
|
|
|
|
//
|
|
// Check to make sure the client's impersonation level is
|
|
// less than or equal to what they are asking for on the
|
|
// server.
|
|
//
|
|
|
|
Status = NtQueryInformationToken(
|
|
ClientTokenHandle,
|
|
TokenImpersonationLevel,
|
|
(PVOID) &ImpersonationLevel,
|
|
TokenInformationSize,
|
|
&TokenInformationSize
|
|
);
|
|
|
|
//
|
|
// If the token is a primary token the return code will be
|
|
// STATUS_INVALID_INFO_CLASS because the token is forced to be
|
|
// equivalent to SecurityImpersonation.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
if ((ImpersonationLevel == SecurityIdentification) &&
|
|
((Context->ContextFlags & NTLMSSP_NEGOTIATE_IDENTIFY) == 0)) {
|
|
SecStatus = SEC_E_NO_CREDENTIALS;
|
|
goto Cleanup;
|
|
} else if (ImpersonationLevel != SecurityImpersonation) {
|
|
SecStatus = SEC_E_NO_CREDENTIALS;
|
|
}
|
|
} else if (Status != STATUS_INVALID_INFO_CLASS) {
|
|
SecStatus = SspNtStatusToSecStatus(
|
|
Status,
|
|
SEC_E_NO_CREDENTIALS
|
|
);
|
|
goto Cleanup;
|
|
}
|
|
|
|
} else {
|
|
// MSV needs the password to be 'in' the passed in buffer.
|
|
RtlCopyMemory( GetChallengeResponse+1,
|
|
GetChallengeResponse->Password.Buffer,
|
|
GetChallengeResponse->Password.Length + sizeof(WCHAR) );
|
|
GetChallengeResponse->Password.Buffer = (LPWSTR)(GetChallengeResponse+1);
|
|
GetChallengeResponseSize += GetChallengeResponse->Password.Length +
|
|
sizeof(WCHAR);
|
|
}
|
|
|
|
SspHidePassword(&Context->Password);
|
|
|
|
GetChallengeResponse->LogonId = *ClientLogonId;
|
|
|
|
RtlCopyMemory( &GetChallengeResponse->ChallengeToClient,
|
|
ChallengeMessage->Challenge,
|
|
MSV1_0_CHALLENGE_LENGTH );
|
|
|
|
|
|
//
|
|
// Get the DomainName, UserName, and ChallengeResponse from the MSV
|
|
//
|
|
|
|
Status = LsaCallAuthenticationPackage(
|
|
SspGlobalLogonProcessHandle,
|
|
SspGlobalAuthenticationPackage,
|
|
GetChallengeResponse,
|
|
GetChallengeResponseSize,
|
|
&ChallengeResponseMessage,
|
|
&ChallengeResponseSize,
|
|
&ProtocolStatus );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleChallengeMessage: "
|
|
"ChallengeMessage LsaCall to get ChallengeResponse returns 0x%lx\n",
|
|
Status ));
|
|
SecStatus = SspNtStatusToSecStatus( Status, SEC_E_NO_CREDENTIALS );
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( !NT_SUCCESS(ProtocolStatus) ) {
|
|
Status = ProtocolStatus;
|
|
SspPrint(( SSP_API,
|
|
"SspHandleChallengeMessage: "
|
|
"ChallengeMessage LsaCall to get ChallengeResponse returns ProtocolStatus 0x%lx\n",
|
|
Status ));
|
|
SecStatus = SspNtStatusToSecStatus( Status, SEC_E_NO_CREDENTIALS );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Normalize things by copying the default domain name and user name
|
|
// into the ChallengeResponseMessage structure.
|
|
//
|
|
|
|
if ( Context->DomainName.Length != 0 ) {
|
|
ChallengeResponseMessage->LogonDomainName = Context->DomainName;
|
|
}
|
|
if ( Context->UserName.Length != 0 ) {
|
|
ChallengeResponseMessage->UserName = Context->UserName;
|
|
}
|
|
|
|
//
|
|
// Convert the domainname/user name to the right character set.
|
|
//
|
|
|
|
if ( DoUnicode ) {
|
|
DomainName = *(PSTRING)&ChallengeResponseMessage->LogonDomainName;
|
|
UserName = *(PSTRING)&ChallengeResponseMessage->UserName;
|
|
Workstation = *(PSTRING)&SspGlobalUnicodeComputerNameString;
|
|
} else {
|
|
Status = RtlUpcaseUnicodeStringToOemString(
|
|
&DomainName,
|
|
&ChallengeResponseMessage->LogonDomainName,
|
|
TRUE);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
SecStatus = SspNtStatusToSecStatus( Status,
|
|
SEC_E_INSUFFICIENT_MEMORY );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = RtlUpcaseUnicodeStringToOemString(
|
|
&UserName,
|
|
&ChallengeResponseMessage->UserName,
|
|
TRUE);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
SecStatus = SspNtStatusToSecStatus( Status,
|
|
SEC_E_INSUFFICIENT_MEMORY );
|
|
goto Cleanup;
|
|
}
|
|
Workstation = SspGlobalOemComputerNameString;
|
|
|
|
}
|
|
|
|
//
|
|
// Save the ChallengeResponses
|
|
//
|
|
|
|
LmChallengeResponse = ChallengeResponseMessage->CaseInsensitiveChallengeResponse;
|
|
NtChallengeResponse = ChallengeResponseMessage->CaseSensitiveChallengeResponse;
|
|
|
|
//
|
|
// Save the session key in the context for safe keeping unless we are
|
|
// doing datagram, in which case we already saved it.
|
|
//
|
|
|
|
if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY) {
|
|
LM_OWF_PASSWORD LmKey;
|
|
LM_RESPONSE LmResponseKey;
|
|
|
|
RtlZeroMemory(
|
|
LocalSessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH
|
|
);
|
|
|
|
if (LmChallengeResponse.Length != LM_RESPONSE_LENGTH) {
|
|
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// The LM session key is made by taking the LM sesion key
|
|
// given to us by the LSA, extending it to LM_OWF_LENGTH
|
|
// with out salt, and then producing a new challenge-response
|
|
// with it and the original challenge response. The key is
|
|
// made from the first 8 bytes of the key.
|
|
//
|
|
|
|
#ifndef EXPORT_BUILD
|
|
if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_STRONG_CRYPT) {
|
|
int i;
|
|
|
|
RtlCopyMemory( &LmKey,
|
|
ChallengeResponseMessage->LanmanSessionKey,
|
|
MSV1_0_LANMAN_SESSION_KEY_LENGTH );
|
|
|
|
memset( (PUCHAR)(&LmKey) + MSV1_0_LANMAN_SESSION_KEY_LENGTH,
|
|
NTLMSSP_KEY_SALT,
|
|
LM_OWF_PASSWORD_LENGTH - MSV1_0_LANMAN_SESSION_KEY_LENGTH );
|
|
|
|
//
|
|
// Mutate the key a bit so a caller can't spoof us
|
|
//
|
|
|
|
for (i = 0; i < MSV1_0_LANMAN_SESSION_KEY_LENGTH ; i++ ) {
|
|
((PUCHAR)&LmKey)[i] ^= ChallengeResponseMessage->LanmanSessionKey[(i+MSV1_0_LANMAN_SESSION_KEY_LENGTH) % MSV1_0_LANMAN_SESSION_KEY_LENGTH];
|
|
}
|
|
|
|
Status = RtlCalculateLmResponse(
|
|
(PLM_CHALLENGE) LmChallengeResponse.Buffer,
|
|
&LmKey,
|
|
&LmResponseKey
|
|
);
|
|
} else
|
|
|
|
#endif // EXPORT_BUILD
|
|
{
|
|
|
|
RtlCopyMemory( &LmKey,
|
|
ChallengeResponseMessage->LanmanSessionKey,
|
|
MSV1_0_LANMAN_SESSION_KEY_LENGTH );
|
|
|
|
memset( (PUCHAR)(&LmKey) + MSV1_0_LANMAN_SESSION_KEY_LENGTH,
|
|
NTLMSSP_KEY_SALT,
|
|
LM_OWF_PASSWORD_LENGTH - MSV1_0_LANMAN_SESSION_KEY_LENGTH );
|
|
|
|
|
|
Status = RtlCalculateLmResponse(
|
|
(PLM_CHALLENGE) LmChallengeResponse.Buffer,
|
|
&LmKey,
|
|
&LmResponseKey
|
|
);
|
|
|
|
}
|
|
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
SecStatus = SspNtStatusToSecStatus( Status,
|
|
SEC_E_NO_CREDENTIALS );
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
LocalSessionKey,
|
|
&LmResponseKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH
|
|
);
|
|
|
|
} else {
|
|
|
|
RtlCopyMemory( LocalSessionKey,
|
|
ChallengeResponseMessage->UserSessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
}
|
|
|
|
//
|
|
// If we aren't doing datagram, store the session key in the
|
|
// context. Otherwise encrypt the session key to send to the
|
|
// server.
|
|
//
|
|
|
|
if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & (NTLMSSP_NEGOTIATE_DATAGRAM
|
|
#ifndef EXPORT_BUILD
|
|
| NTLMSSP_NEGOTIATE_STRONG_CRYPT
|
|
#endif // EXPORT_BUILD
|
|
) ) == 0) {
|
|
|
|
RtlCopyMemory(
|
|
Context->SessionKey,
|
|
LocalSessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH
|
|
);
|
|
|
|
RtlInitString( &DatagramSessionKey, NULL );
|
|
|
|
} else {
|
|
struct RC4_KEYSTRUCT Rc4Key;
|
|
rc4_key(
|
|
&Rc4Key,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH,
|
|
LocalSessionKey
|
|
);
|
|
|
|
|
|
RtlCopyMemory(
|
|
DatagramKey,
|
|
Context->SessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH
|
|
);
|
|
rc4(
|
|
&Rc4Key,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH,
|
|
DatagramKey
|
|
);
|
|
|
|
DatagramSessionKey.Buffer = DatagramKey;
|
|
DatagramSessionKey.Length =
|
|
DatagramSessionKey.MaximumLength = MSV1_0_USER_SESSION_KEY_LENGTH;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// If the caller specified SEQUENCE_DETECT or REPLAY_DETECT,
|
|
// that means they want to use the MakeSignature/VerifySignature
|
|
// calls. Add this to the returned attributes and the context
|
|
// negotiate flags.
|
|
//
|
|
|
|
if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) ||
|
|
(ContextReqFlags & ISC_REQ_SEQUENCE_DETECT) ||
|
|
(ContextReqFlags & ISC_REQ_REPLAY_DETECT)) {
|
|
|
|
Context->ContextFlags |= ISC_REQ_SEQUENCE_DETECT;
|
|
*ContextAttributes |= ISC_REQ_SEQUENCE_DETECT;
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN;
|
|
}
|
|
|
|
if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) ||
|
|
(ContextReqFlags & ISC_REQ_CONFIDENTIALITY)) {
|
|
if (SspGlobalEncryptionEnabled) {
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL;
|
|
Context->ContextFlags |= ISC_REQ_CONFIDENTIALITY;
|
|
*ContextAttributes |= ISC_REQ_CONFIDENTIALITY;
|
|
} else {
|
|
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if ((Context->NegotiateFlags & ChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) ==
|
|
NTLMSSP_NEGOTIATE_DATAGRAM ) {
|
|
*ContextAttributes |= ISC_RET_DATAGRAM;
|
|
Context->ContextFlags |= ISC_RET_DATAGRAM;
|
|
Context->NegotiateFlags |= NTLMSSP_NEGOTIATE_DATAGRAM;
|
|
}
|
|
|
|
|
|
//
|
|
// Allocate an authenticate message
|
|
//
|
|
|
|
AuthenticateMessageSize =
|
|
sizeof(*AuthenticateMessage) +
|
|
LmChallengeResponse.Length +
|
|
NtChallengeResponse.Length +
|
|
DomainName.Length +
|
|
UserName.Length +
|
|
Workstation.Length +
|
|
DatagramSessionKey.Length;
|
|
|
|
|
|
|
|
if ( AuthenticateMessageSize > *OutputTokenSize ) {
|
|
SecStatus = SEC_E_BUFFER_TOO_SMALL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
AuthenticateMessage = LocalAlloc( 0, AuthenticateMessageSize );
|
|
|
|
if ( AuthenticateMessage == NULL ) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build the authenticate message
|
|
//
|
|
|
|
strcpy( AuthenticateMessage->Signature, NTLMSSP_SIGNATURE );
|
|
AuthenticateMessage->MessageType = NtLmAuthenticate;
|
|
|
|
Where = (PCHAR)(AuthenticateMessage+1);
|
|
|
|
//
|
|
// Copy the strings needing 2 byte alignment.
|
|
//
|
|
SspContextCopyString( AuthenticateMessage,
|
|
&AuthenticateMessage->DomainName,
|
|
&DomainName,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
SspContextCopyString( AuthenticateMessage,
|
|
&AuthenticateMessage->UserName,
|
|
&UserName,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
SspContextCopyString( AuthenticateMessage,
|
|
&AuthenticateMessage->Workstation,
|
|
&Workstation,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
//
|
|
// Copy the strings not needing special alignment.
|
|
//
|
|
SspContextCopyString( AuthenticateMessage,
|
|
&AuthenticateMessage->LmChallengeResponse,
|
|
&LmChallengeResponse,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
SspContextCopyString( AuthenticateMessage,
|
|
&AuthenticateMessage->NtChallengeResponse,
|
|
&NtChallengeResponse,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
SspContextCopyString( AuthenticateMessage,
|
|
&AuthenticateMessage->SessionKey,
|
|
&DatagramSessionKey,
|
|
&Where,
|
|
FALSE ); // Pointers are relative
|
|
|
|
AuthenticateMessage->NegotiateFlags = Context->NegotiateFlags;
|
|
|
|
//
|
|
// Copy the AuthenticateMessage to the caller's address space.
|
|
//
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
AuthenticateMessageSize,
|
|
OutputToken,
|
|
AuthenticateMessage );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
*OutputTokenSize = AuthenticateMessageSize;
|
|
|
|
|
|
SspPrint((SSP_API,"Client session key = %p\n",Context->SessionKey));
|
|
|
|
//
|
|
// Return output parameters to the caller.
|
|
//
|
|
|
|
*ExpirationTime = SspContextGetTimeStamp( Context, TRUE );
|
|
|
|
|
|
|
|
//
|
|
// Compute the context names. Since the buffer is UNLEN+DNLEN+2 make
|
|
// sure the strings fit.
|
|
//
|
|
|
|
Name[0] = L'\0';
|
|
|
|
if (Context->UserName.Buffer != NULL) {
|
|
if (Context->DomainName.Buffer != NULL) {
|
|
if (Context->DomainName.Length / sizeof(WCHAR) <= DNLEN) {
|
|
wcscat(Name,Context->DomainName.Buffer);
|
|
wcscat(Name,L"\\");
|
|
}
|
|
}
|
|
if (Context->UserName.Length / sizeof(WCHAR) <= UNLEN) {
|
|
wcscat(Name,Context->UserName.Buffer);
|
|
} else {
|
|
Name[0] = L'\0';
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy it to the client
|
|
//
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
(wcslen(Name) + 1) * sizeof(WCHAR),
|
|
ContextNames,
|
|
Name
|
|
);
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
SecStatus = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Free and locally used resources.
|
|
//
|
|
Cleanup:
|
|
|
|
if ( Context != NULL ) {
|
|
|
|
//
|
|
// Don't allow this context to be used again.
|
|
//
|
|
|
|
if ( NT_SUCCESS(SecStatus) ) {
|
|
Context->State = AuthenticateSentState;
|
|
} else if ( SecStatus == SEC_I_CALL_NTLMSSP_SERVICE ) {
|
|
Context->State = PassedToServiceState;
|
|
}
|
|
else Context->State = IdleState;
|
|
|
|
RtlCopyMemory(
|
|
SessionKey,
|
|
Context->SessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH );
|
|
|
|
*NegotiateFlags = Context->NegotiateFlags;
|
|
|
|
SspContextDereferenceContext( Context );
|
|
}
|
|
|
|
if ( ChallengeMessage != NULL ) {
|
|
(VOID) LocalFree( ChallengeMessage );
|
|
}
|
|
|
|
if ( AuthenticateMessage != NULL ) {
|
|
(VOID) LocalFree( AuthenticateMessage );
|
|
}
|
|
|
|
if ( ChallengeResponseMessage != NULL ) {
|
|
(VOID) LsaFreeReturnBuffer( ChallengeResponseMessage );
|
|
}
|
|
|
|
if ( !DoUnicode ) {
|
|
if ( DomainName.Buffer != NULL) {
|
|
RtlFreeOemString( &DomainName );
|
|
}
|
|
if ( UserName.Buffer != NULL) {
|
|
RtlFreeOemString( &UserName );
|
|
}
|
|
}
|
|
|
|
return SecStatus;
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
SsprHandleAuthenticateMessage(
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection,
|
|
IN PCredHandle CredentialHandle,
|
|
IN OUT PCtxtHandle ContextHandle,
|
|
IN ULONG ContextReqFlags,
|
|
IN ULONG InputTokenSize,
|
|
IN PVOID InputToken,
|
|
IN OUT PULONG OutputTokenSize,
|
|
OUT PVOID OutputToken,
|
|
OUT PULONG ContextAttributes,
|
|
OUT PTimeStamp ExpirationTime,
|
|
OUT PUCHAR SessionKey,
|
|
OUT PULONG NegotiateFlags,
|
|
OUT PHANDLE TokenHandle,
|
|
OUT PNTSTATUS ApiSubStatus,
|
|
OUT LPWSTR ContextNames,
|
|
OUT PTimeStamp PasswordExpiry
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle the authenticate message part of AcceptSecurityContext.
|
|
|
|
Arguments:
|
|
|
|
ClientConnection - Describes the client process.
|
|
|
|
SessionKey - The session key for the context, used for signing and sealing
|
|
|
|
NegotiateFlags - The flags negotiated for the context, used for sign & seal
|
|
|
|
ApiSubStatus - Returns the substatus for why the logon failed.
|
|
|
|
ContextNames - Receives the domainname\username for a non-shortcutted
|
|
authentication. This is a pointer in the client's address
|
|
space.
|
|
|
|
PasswordExpiry - Contains the time that the authenticated user's password
|
|
expires, or 0x7fffffff ffffffff for local callers.
|
|
|
|
All other arguments same as for AcceptSecurityContext
|
|
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Message handled
|
|
|
|
SEC_E_INVALID_TOKEN -- Token improperly formatted
|
|
SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid
|
|
SEC_E_BUFFER_TOO_SMALL -- Buffer for output token isn't big enough
|
|
SEC_E_LOGON_DENIED -- User is no allowed to logon to this server
|
|
SEC_E_INSUFFICIENT_MEMORY -- Not enough memory
|
|
|
|
--*/
|
|
|
|
{
|
|
SECURITY_STATUS SecStatus;
|
|
NTSTATUS Status;
|
|
PSSP_CONTEXT Context = NULL;
|
|
|
|
PNEGOTIATE_MESSAGE NegotiateMessage = NULL;
|
|
PAUTHENTICATE_MESSAGE AuthenticateMessage = NULL;
|
|
ULONG MsvLogonMessageSize;
|
|
PMSV1_0_LM20_LOGON MsvLogonMessage = NULL;
|
|
ULONG LogonProfileMessageSize;
|
|
PMSV1_0_LM20_LOGON_PROFILE LogonProfileMessage = NULL;
|
|
|
|
BOOLEAN DoUnicode = FALSE;
|
|
UNICODE_STRING DomainName;
|
|
UNICODE_STRING UserName;
|
|
UNICODE_STRING Workstation;
|
|
LARGE_INTEGER KickOffTime;
|
|
|
|
LUID LogonId;
|
|
HANDLE LocalTokenHandle = NULL;
|
|
BOOLEAN LocalTokenHandleOpenned = FALSE;
|
|
TOKEN_SOURCE SourceContext;
|
|
QUOTA_LIMITS Quotas;
|
|
NTSTATUS SubStatus;
|
|
STRING OriginName;
|
|
PCHAR Where;
|
|
UCHAR LocalSessionKey[LM_RESPONSE_LENGTH];
|
|
WCHAR Name[UNLEN+DNLEN+2];
|
|
|
|
ASSERT(LM_RESPONSE_LENGTH >= MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
*ContextAttributes = 0;
|
|
DomainName.Buffer = NULL;
|
|
UserName.Buffer = NULL;
|
|
Workstation.Buffer = NULL;
|
|
*ApiSubStatus = STATUS_SUCCESS;
|
|
PasswordExpiry->LowPart = 0xffffffff;
|
|
PasswordExpiry->HighPart = 0x7fffffff;
|
|
|
|
//
|
|
// Find the currently existing context.
|
|
//
|
|
|
|
Context = SspContextReferenceContext( ContextHandle,
|
|
ClientConnection,
|
|
FALSE );
|
|
|
|
if ( Context == NULL ) {
|
|
|
|
//
|
|
// If we are running in the security.dll and the handle belongs
|
|
// to the NTLMSSP service, return a different error
|
|
//
|
|
|
|
if ( (ContextHandle->dwLower == SEC_HANDLE_NTLMSSPS) &&
|
|
(SspCommonSecHandleValue == SEC_HANDLE_SECURITY) ) {
|
|
SecStatus = SEC_I_CALL_NTLMSSP_SERVICE;
|
|
} else SecStatus = SEC_E_INVALID_HANDLE;
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
if ( ( Context->State != ChallengeSentState) &&
|
|
( Context->State != AuthenticatedState) ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleAuthenticateMessage: "
|
|
"Context not in ChallengeSentState\n" ));
|
|
SecStatus = SEC_E_OUT_OF_SEQUENCE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Ignore the Credential Handle.
|
|
//
|
|
// Since this is the second call,
|
|
// the credential is implied by the Context.
|
|
// We could double check that the Credential Handle is either NULL or
|
|
// correct. However, our implementation doesn't maintain a close
|
|
// association between the two (actually no association) so checking
|
|
// would require a lot of overhead.
|
|
//
|
|
|
|
UNREFERENCED_PARAMETER( CredentialHandle );
|
|
|
|
|
|
|
|
//
|
|
// Get the AuthenticateMessage.
|
|
//
|
|
|
|
if ( InputTokenSize < sizeof(OLD_AUTHENTICATE_MESSAGE) ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleAuthenticateMessage: "
|
|
"AuthenticateMessage size wrong %ld\n",
|
|
InputTokenSize ));
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
SecStatus = SspContextGetMessage( ClientConnection,
|
|
InputToken,
|
|
InputTokenSize,
|
|
NtLmAuthenticate,
|
|
&AuthenticateMessage );
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleAuthenticateMessage: "
|
|
"AuthenticateMessage GetMessage returns 0x%lx\n",
|
|
SecStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If the call comes and we have already authenticated, then it is
|
|
// probably RPC trying to reauthenticate, which happens when someone
|
|
// calls two interfaces on the same connection. In this case we don't
|
|
// have to do anything - we just return success and let them get on
|
|
// with it. We do want to check that the input token is all zeros,
|
|
// though.
|
|
//
|
|
//
|
|
|
|
if ( Context->State == AuthenticatedState ) {
|
|
AUTHENTICATE_MESSAGE NullMessage;
|
|
|
|
*OutputTokenSize = 0;
|
|
|
|
//
|
|
// Check that all the fields are null. There are 5 strings
|
|
// in the Authenticate message that have to be set to zero.
|
|
//
|
|
|
|
RtlZeroMemory(&NullMessage.LmChallengeResponse,5*sizeof(STRING));
|
|
|
|
if (memcmp(&AuthenticateMessage->LmChallengeResponse,
|
|
&NullMessage.LmChallengeResponse,
|
|
sizeof(STRING) * 5) ) {
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
|
|
}
|
|
else
|
|
{
|
|
*ContextAttributes = SSP_RET_REAUTHENTICATION;
|
|
SecStatus = STATUS_SUCCESS;
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// If we are re-establishing a datagram context, get the negotiate flags
|
|
// out of this message.
|
|
//
|
|
|
|
if ((Context->NegotiateFlags & (NTLMSSP_NEGOTIATE_DATAGRAM
|
|
) ) != 0) {
|
|
|
|
if ((InputTokenSize < sizeof(AUTHENTICATE_MESSAGE)) ||
|
|
((AuthenticateMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) == 0) ) {
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Context->NegotiateFlags = AuthenticateMessage->NegotiateFlags;
|
|
}
|
|
|
|
#ifndef EXPORT_BUILD
|
|
if ((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_STRONG_CRYPT ) != 0) {
|
|
|
|
if (InputTokenSize < sizeof(AUTHENTICATE_MESSAGE)) {
|
|
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
#endif // EXPORT_BUILD
|
|
|
|
//
|
|
// Convert relative pointers to absolute.
|
|
//
|
|
|
|
DoUnicode = ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_UNICODE ) != 0;
|
|
|
|
if ( !SspConvertRelativeToAbsolute( AuthenticateMessage,
|
|
InputTokenSize,
|
|
&AuthenticateMessage->LmChallengeResponse,
|
|
FALSE, // No special alignment
|
|
TRUE ) ) { // NULL OK
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( !SspConvertRelativeToAbsolute( AuthenticateMessage,
|
|
InputTokenSize,
|
|
&AuthenticateMessage->NtChallengeResponse,
|
|
FALSE, // No special alignment
|
|
TRUE ) ) { // NULL OK
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( !SspConvertRelativeToAbsolute( AuthenticateMessage,
|
|
InputTokenSize,
|
|
&AuthenticateMessage->DomainName,
|
|
DoUnicode, // Unicode alignment
|
|
TRUE ) ) { // NULL OK
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( !SspConvertRelativeToAbsolute( AuthenticateMessage,
|
|
InputTokenSize,
|
|
&AuthenticateMessage->UserName,
|
|
DoUnicode, // Unicode alignment
|
|
#ifdef notdef
|
|
//
|
|
// Allow null sessions. The server should guard against them if
|
|
// it doesn't want them.
|
|
//
|
|
FALSE )) { // User name cannot be NULL
|
|
|
|
#endif // notdef
|
|
TRUE ) ) { // NULL OK
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( !SspConvertRelativeToAbsolute( AuthenticateMessage,
|
|
InputTokenSize,
|
|
&AuthenticateMessage->Workstation,
|
|
DoUnicode, // Unicode alignment
|
|
TRUE ) ) { // NULL OK
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If this is datagram or strong crypto, get the session key
|
|
//
|
|
|
|
if ((Context->NegotiateFlags & (NTLMSSP_NEGOTIATE_DATAGRAM
|
|
#ifndef EXPORT_BUILD
|
|
| NTLMSSP_NEGOTIATE_STRONG_CRYPT
|
|
#endif // EXPORT_BUILD
|
|
) ) != 0) {
|
|
|
|
if ( !SspConvertRelativeToAbsolute( AuthenticateMessage,
|
|
InputTokenSize,
|
|
&AuthenticateMessage->SessionKey,
|
|
FALSE, // No special alignment
|
|
TRUE ) ) { // NULL o.k.
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// It should only be NULL if this is a local call
|
|
//
|
|
|
|
if (((Context->NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL) == 0) &&
|
|
(AuthenticateMessage->SessionKey.Buffer == NULL)) {
|
|
SecStatus = SEC_E_INVALID_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Convert the domainname/user name/workstation to the right character set.
|
|
//
|
|
|
|
|
|
if ( DoUnicode ) {
|
|
DomainName = *(PUNICODE_STRING)&AuthenticateMessage->DomainName;
|
|
UserName = *(PUNICODE_STRING)&AuthenticateMessage->UserName;
|
|
Workstation = *(PUNICODE_STRING)&AuthenticateMessage->Workstation;
|
|
|
|
} else {
|
|
Status = RtlOemStringToUnicodeString(
|
|
&DomainName,
|
|
&AuthenticateMessage->DomainName,
|
|
TRUE);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
SecStatus = SspNtStatusToSecStatus( Status,
|
|
SEC_E_INSUFFICIENT_MEMORY );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = RtlOemStringToUnicodeString(
|
|
&UserName,
|
|
&AuthenticateMessage->UserName,
|
|
TRUE);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
SecStatus = SspNtStatusToSecStatus( Status,
|
|
SEC_E_INSUFFICIENT_MEMORY );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = RtlOemStringToUnicodeString(
|
|
&Workstation,
|
|
&AuthenticateMessage->Workstation,
|
|
TRUE);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
SecStatus = SspNtStatusToSecStatus( Status,
|
|
SEC_E_INSUFFICIENT_MEMORY );
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// If the client is on the same machine as we are,
|
|
// just use the token the client has already placed in our context structure,
|
|
//
|
|
|
|
if ( (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL ) &&
|
|
Context->TokenHandle != NULL &&
|
|
DomainName.Length == 0 &&
|
|
UserName.Length == 0 &&
|
|
Workstation.Length == 0 &&
|
|
AuthenticateMessage->NtChallengeResponse.Length == 0 &&
|
|
AuthenticateMessage->LmChallengeResponse.Length == 0 ) {
|
|
|
|
LocalTokenHandle = Context->TokenHandle;
|
|
Context->TokenHandle = NULL;
|
|
|
|
KickOffTime.HighPart = 0x7FFFFFFF;
|
|
KickOffTime.LowPart = 0xFFFFFFFF;
|
|
|
|
RtlZeroMemory(Context->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
|
|
//
|
|
// If the client is on a different machine than we are,
|
|
// use LsaLogonUser to create a token for the client.
|
|
//
|
|
} else {
|
|
|
|
//
|
|
// Store the user name and domain name
|
|
//
|
|
|
|
SecStatus = SspDuplicateUnicodeString(
|
|
&Context->UserName,
|
|
&UserName
|
|
);
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
SecStatus = SspDuplicateUnicodeString(
|
|
&Context->DomainName,
|
|
&DomainName
|
|
);
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Allocate an MSV1_0 network logon message
|
|
//
|
|
|
|
MsvLogonMessageSize =
|
|
sizeof(*MsvLogonMessage) +
|
|
DomainName.Length +
|
|
UserName.Length +
|
|
Workstation.Length +
|
|
AuthenticateMessage->NtChallengeResponse.Length +
|
|
AuthenticateMessage->LmChallengeResponse.Length;
|
|
|
|
MsvLogonMessage = LocalAlloc( 0, MsvLogonMessageSize );
|
|
|
|
if ( MsvLogonMessage == NULL ) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build the MSV1_0 network logon message to pass to the LSA.
|
|
//
|
|
|
|
MsvLogonMessage->MessageType = MsV1_0NetworkLogon;
|
|
|
|
Where = (PCHAR)(MsvLogonMessage+1);
|
|
|
|
SspContextCopyString( MsvLogonMessage,
|
|
(PSTRING)&MsvLogonMessage->LogonDomainName,
|
|
(PSTRING)&DomainName,
|
|
&Where,
|
|
TRUE ); // Pointers are absolute
|
|
|
|
SspContextCopyString( MsvLogonMessage,
|
|
(PSTRING)&MsvLogonMessage->UserName,
|
|
(PSTRING)&UserName,
|
|
&Where,
|
|
TRUE ); // Pointers are absolute
|
|
|
|
SspContextCopyString( MsvLogonMessage,
|
|
(PSTRING)&MsvLogonMessage->Workstation,
|
|
(PSTRING)&Workstation,
|
|
&Where,
|
|
TRUE ); // Pointers are absolute
|
|
|
|
RtlCopyMemory( MsvLogonMessage->ChallengeToClient,
|
|
Context->Challenge,
|
|
sizeof( MsvLogonMessage->ChallengeToClient ) );
|
|
|
|
SspContextCopyString( MsvLogonMessage,
|
|
&MsvLogonMessage->CaseSensitiveChallengeResponse,
|
|
&AuthenticateMessage->NtChallengeResponse,
|
|
&Where,
|
|
TRUE ); // Pointers are absolute
|
|
|
|
SspContextCopyString( MsvLogonMessage,
|
|
&MsvLogonMessage->CaseInsensitiveChallengeResponse,
|
|
&AuthenticateMessage->LmChallengeResponse,
|
|
&Where,
|
|
TRUE ); // Pointers are absolute
|
|
|
|
//
|
|
// By passing in the RETURN_PASSWORD_EXPIRY flag, the password
|
|
// expiration time is returned in the logoff time
|
|
//
|
|
|
|
MsvLogonMessage->ParameterControl = MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT |
|
|
MSV1_0_RETURN_PASSWORD_EXPIRY;
|
|
|
|
|
|
//
|
|
// Log this user on.
|
|
//
|
|
|
|
RtlInitString( &OriginName, NULL ); // No origin (could use F(workstaion))
|
|
strncpy( SourceContext.SourceName, "NtLmSsp ", sizeof(SourceContext.SourceName) );
|
|
RtlZeroMemory( &SourceContext.SourceIdentifier,
|
|
sizeof(SourceContext.SourceIdentifier) );
|
|
|
|
Status = LsaLogonUser(
|
|
SspGlobalLogonProcessHandle,
|
|
&OriginName,
|
|
Network,
|
|
SspGlobalAuthenticationPackage,
|
|
MsvLogonMessage,
|
|
MsvLogonMessageSize,
|
|
NULL, // No local groups
|
|
&SourceContext,
|
|
&LogonProfileMessage,
|
|
&LogonProfileMessageSize,
|
|
&LogonId,
|
|
&LocalTokenHandle,
|
|
&Quotas,
|
|
&SubStatus );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleAuthenticateMessage: "
|
|
"LsaLogonUser returns 0x%lx\n",
|
|
Status ));
|
|
SecStatus = SspNtStatusToSecStatus( Status, SEC_E_LOGON_DENIED );
|
|
if (Status == STATUS_ACCOUNT_RESTRICTION) {
|
|
*ApiSubStatus = SubStatus;
|
|
} else {
|
|
*ApiSubStatus = Status;
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
if ( !NT_SUCCESS(SubStatus) ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleAuthenticateMessage: "
|
|
"LsaLogonUser returns SubStatus of 0x%lx\n",
|
|
SubStatus ));
|
|
SecStatus = SspNtStatusToSecStatus( SubStatus,
|
|
SEC_E_LOGON_DENIED );
|
|
goto Cleanup;
|
|
}
|
|
|
|
LocalTokenHandleOpenned = TRUE;
|
|
|
|
//
|
|
// Don't allow cleartext password on the logon.
|
|
//
|
|
|
|
if ( LogonProfileMessage->UserFlags & LOGON_NOENCRYPTION ) {
|
|
SspPrint(( SSP_API,
|
|
"SspHandleAuthenticateMessage: "
|
|
"LsaLogonUser used cleartext password\n" ));
|
|
SecStatus = SEC_E_LOGON_DENIED;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
//
|
|
// If we did a guest logon, set the substatus to be STATUS_NO_SUCH_USER
|
|
//
|
|
|
|
if ( LogonProfileMessage->UserFlags & LOGON_GUEST ) {
|
|
*ApiSubStatus = STATUS_NO_SUCH_USER;
|
|
}
|
|
|
|
//
|
|
// If we need a different level of token,
|
|
// create it here and close the original. We only need to do this
|
|
// after a LsaLogonUser because the token duplicated into our
|
|
// process is already the correct level.
|
|
//
|
|
|
|
if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) {
|
|
HANDLE TempTokenHandle;
|
|
SecStatus = SspDuplicateToken(
|
|
LocalTokenHandle,
|
|
SecurityIdentification,
|
|
&TempTokenHandle
|
|
);
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
} else {
|
|
(VOID) NtClose(LocalTokenHandle);
|
|
LocalTokenHandle = TempTokenHandle;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Save important information about the caller.
|
|
//
|
|
|
|
KickOffTime = LogonProfileMessage->KickOffTime;
|
|
|
|
//
|
|
// By passing in the RETURN_PASSWORD_EXPIRY flag, the password
|
|
// expiration time is returned in the logoff time
|
|
//
|
|
|
|
*PasswordExpiry = LogonProfileMessage->LogoffTime;
|
|
|
|
//
|
|
// Copy out the session key. For unicode clients, use the UserSessionKey
|
|
// and for OEM clients use the LanMan session key.
|
|
//
|
|
|
|
if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY ) {
|
|
|
|
LM_OWF_PASSWORD LmKey;
|
|
|
|
|
|
//
|
|
// If the response is not the right length (i.e this is a null session)
|
|
// fail now since we can't create a key.
|
|
//
|
|
|
|
if (AuthenticateMessage->LmChallengeResponse.Length != LM_RESPONSE_LENGTH) {
|
|
SecStatus = SEC_E_UNSUPPORTED_FUNCTION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlZeroMemory(
|
|
LocalSessionKey,
|
|
LM_RESPONSE_LENGTH
|
|
);
|
|
#ifndef EXPORT_BUILD
|
|
if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_STRONG_CRYPT) {
|
|
int i;
|
|
|
|
RtlCopyMemory( &LmKey,
|
|
LogonProfileMessage->LanmanSessionKey,
|
|
MSV1_0_LANMAN_SESSION_KEY_LENGTH );
|
|
|
|
memset( (PUCHAR)(&LmKey) + MSV1_0_LANMAN_SESSION_KEY_LENGTH,
|
|
NTLMSSP_KEY_SALT,
|
|
LM_OWF_PASSWORD_LENGTH - MSV1_0_LANMAN_SESSION_KEY_LENGTH );
|
|
|
|
//
|
|
// Mutate the key a bit so a caller can't spoof us
|
|
//
|
|
|
|
for (i = 0; i < MSV1_0_LANMAN_SESSION_KEY_LENGTH ; i++ ) {
|
|
((PUCHAR)&LmKey)[i] ^= LogonProfileMessage->LanmanSessionKey[(i+MSV1_0_LANMAN_SESSION_KEY_LENGTH) % MSV1_0_LANMAN_SESSION_KEY_LENGTH];
|
|
}
|
|
|
|
Status = RtlCalculateLmResponse(
|
|
(PLM_CHALLENGE) AuthenticateMessage->LmChallengeResponse.Buffer,
|
|
&LmKey,
|
|
(PLM_RESPONSE) LocalSessionKey );
|
|
} else
|
|
|
|
#endif // EXPORT_BUILD
|
|
{
|
|
|
|
//
|
|
// The LM session key is made by taking the LM sesion key
|
|
// given to us by the LSA, extending it to LM_OWF_LENGTH
|
|
// with out salt, and then producing a new challenge-response
|
|
// with it and the original challenge response. The key is
|
|
// made from the first 8 bytes of the key.
|
|
//
|
|
|
|
RtlCopyMemory( &LmKey,
|
|
LogonProfileMessage->LanmanSessionKey,
|
|
MSV1_0_LANMAN_SESSION_KEY_LENGTH );
|
|
|
|
memset( (PUCHAR)(&LmKey) + MSV1_0_LANMAN_SESSION_KEY_LENGTH,
|
|
NTLMSSP_KEY_SALT,
|
|
LM_OWF_PASSWORD_LENGTH - MSV1_0_LANMAN_SESSION_KEY_LENGTH );
|
|
|
|
|
|
Status = RtlCalculateLmResponse(
|
|
(PLM_CHALLENGE) AuthenticateMessage->LmChallengeResponse.Buffer,
|
|
&LmKey,
|
|
(PLM_RESPONSE) LocalSessionKey );
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
SecStatus = SspNtStatusToSecStatus( Status,
|
|
SEC_E_NO_CREDENTIALS );
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
RtlCopyMemory( LocalSessionKey,
|
|
LogonProfileMessage->UserSessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
}
|
|
|
|
//
|
|
// For anything but datagram, copy the just-computed session key
|
|
// into the context. Otherwise, decrypt the session key that
|
|
// came in the authenticate message.
|
|
//
|
|
|
|
if ((Context->NegotiateFlags & (NTLMSSP_NEGOTIATE_DATAGRAM
|
|
#ifndef EXPORT_BUILD
|
|
| NTLMSSP_NEGOTIATE_STRONG_CRYPT
|
|
#endif // EXPORT_BUILD
|
|
) ) == 0) {
|
|
|
|
RtlCopyMemory(
|
|
Context->SessionKey,
|
|
LocalSessionKey,
|
|
MSV1_0_LANMAN_SESSION_KEY_LENGTH
|
|
);
|
|
|
|
} else {
|
|
struct RC4_KEYSTRUCT Rc4Key;
|
|
|
|
//
|
|
// Use the just-computed session key as an RC4 key to
|
|
// decrypt the real session key.
|
|
//
|
|
|
|
rc4_key(
|
|
&Rc4Key,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH,
|
|
LocalSessionKey
|
|
);
|
|
|
|
RtlZeroMemory(
|
|
Context->SessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH
|
|
);
|
|
|
|
RtlCopyMemory(
|
|
Context->SessionKey,
|
|
AuthenticateMessage->SessionKey.Buffer,
|
|
AuthenticateMessage->SessionKey.Length
|
|
);
|
|
|
|
rc4(
|
|
&Rc4Key,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH,
|
|
Context->SessionKey
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Copy the logon domain name returned by the LSA if it is different
|
|
// from the one the caller passed in. This may happend with temp duplicate
|
|
// accounts and local accounts.
|
|
//
|
|
|
|
if ((LogonProfileMessage != NULL) &&
|
|
(LogonProfileMessage->LogonDomainName.Length != 0) &&
|
|
!RtlEqualUnicodeString(
|
|
&Context->DomainName,
|
|
&LogonProfileMessage->LogonDomainName,
|
|
TRUE // case insensitive
|
|
)) {
|
|
|
|
//
|
|
// erase the old domain name
|
|
//
|
|
|
|
if (Context->DomainName.Buffer != NULL) {
|
|
LocalFree(Context->DomainName.Buffer);
|
|
Context->DomainName.Buffer = NULL;
|
|
}
|
|
SecStatus = SspDuplicateUnicodeString(
|
|
&Context->DomainName,
|
|
&LogonProfileMessage->LogonDomainName
|
|
);
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Allow the context to live until kickoff time.
|
|
//
|
|
|
|
SspContextSetTimeStamp( Context, KickOffTime );
|
|
|
|
//
|
|
// Return output parameters to the caller.
|
|
//
|
|
|
|
*ExpirationTime = SspContextGetTimeStamp( Context, TRUE );
|
|
*OutputTokenSize = 0;
|
|
|
|
//
|
|
// We only support replay and sequence detect options
|
|
//
|
|
|
|
|
|
if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) {
|
|
*ContextAttributes |= ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT;
|
|
}
|
|
|
|
if ( ContextReqFlags & ISC_REQ_REPLAY_DETECT ) {
|
|
*ContextAttributes |= ISC_REQ_REPLAY_DETECT;
|
|
}
|
|
|
|
if ( ContextReqFlags & ISC_REQ_SEQUENCE_DETECT ) {
|
|
*ContextAttributes |= ISC_REQ_SEQUENCE_DETECT;
|
|
}
|
|
|
|
//
|
|
// Compute the context names. Since the buffer is UNLEN+DNLEN+2 make
|
|
// sure the strings fit.
|
|
//
|
|
|
|
Name[0] = L'\0';
|
|
|
|
if (Context->UserName.Buffer != NULL) {
|
|
if (Context->DomainName.Buffer != NULL) {
|
|
if (Context->DomainName.Length / sizeof(WCHAR) <= DNLEN) {
|
|
wcscat(Name,Context->DomainName.Buffer);
|
|
wcscat(Name,L"\\");
|
|
}
|
|
}
|
|
if (Context->UserName.Length / sizeof(WCHAR) <= UNLEN) {
|
|
wcscat(Name,Context->UserName.Buffer);
|
|
} else {
|
|
Name[0] = L'\0';
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy it to the client
|
|
//
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
(wcslen(Name) + 1) * sizeof(WCHAR),
|
|
ContextNames,
|
|
Name
|
|
);
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
UNREFERENCED_PARAMETER( OutputToken );
|
|
|
|
SecStatus = STATUS_SUCCESS;
|
|
|
|
|
|
//
|
|
// Free and locally used resources.
|
|
//
|
|
Cleanup:
|
|
|
|
if ( Context != NULL ) {
|
|
//
|
|
// Don't allow this context to be used again.
|
|
//
|
|
if ( NT_SUCCESS(SecStatus) ) {
|
|
Context->State = AuthenticatedState;
|
|
|
|
if ( LocalTokenHandle ) {
|
|
*TokenHandle = LocalTokenHandle;
|
|
}
|
|
|
|
LocalTokenHandle = NULL;
|
|
|
|
RtlCopyMemory(
|
|
SessionKey,
|
|
Context->SessionKey,
|
|
MSV1_0_USER_SESSION_KEY_LENGTH );
|
|
|
|
*NegotiateFlags = Context->NegotiateFlags;
|
|
|
|
} else {
|
|
Context->State = IdleState;
|
|
}
|
|
SspContextDereferenceContext( Context );
|
|
}
|
|
|
|
if ( NegotiateMessage != NULL ) {
|
|
(VOID) LocalFree( NegotiateMessage );
|
|
}
|
|
|
|
if ( AuthenticateMessage != NULL ) {
|
|
(VOID) LocalFree( AuthenticateMessage );
|
|
}
|
|
|
|
if ( MsvLogonMessage != NULL ) {
|
|
(VOID) LocalFree( MsvLogonMessage );
|
|
}
|
|
|
|
|
|
if ( LogonProfileMessage != NULL ) {
|
|
(VOID) LsaFreeReturnBuffer( LogonProfileMessage );
|
|
}
|
|
|
|
if ( LocalTokenHandle != NULL && LocalTokenHandleOpenned ) {
|
|
(VOID) NtClose( LocalTokenHandle );
|
|
}
|
|
|
|
if ( !DoUnicode ) {
|
|
if ( DomainName.Buffer != NULL) {
|
|
RtlFreeUnicodeString( &DomainName );
|
|
}
|
|
if ( UserName.Buffer != NULL) {
|
|
RtlFreeUnicodeString( &UserName );
|
|
}
|
|
if ( Workstation.Buffer != NULL) {
|
|
RtlFreeUnicodeString( &Workstation );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set a flag telling RPC not to destroy the connection yet
|
|
//
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
*ContextAttributes |= ASC_RET_THIRD_LEG_FAILED;
|
|
}
|
|
|
|
return SecStatus;
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
SsprQueryContextAttributes(
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection,
|
|
IN PCtxtHandle ContextHandle,
|
|
IN ULONG Attribute,
|
|
OUT PVOID Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This API allows a customer of the security services to determine
|
|
certain attributes of the context. These are: sizes, names, and
|
|
lifespan.
|
|
|
|
Arguments:
|
|
|
|
ClientConnection - Describes the client process.
|
|
|
|
ContextHandle - Handle to the context to query.
|
|
|
|
Attribute - Attribute to query.
|
|
|
|
#define SECPKG_ATTR_SIZES 0
|
|
#define SECPKG_ATTR_NAMES 1
|
|
#define SECPKG_ATTR_LIFESPAN 2
|
|
|
|
Buffer - Buffer to copy the data into. The buffer must be large enough
|
|
to fit the queried attribute.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Call completed successfully
|
|
|
|
SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid
|
|
SEC_E_UNSUPPORTED_FUNCTION -- Function code is not supported
|
|
|
|
--*/
|
|
|
|
{
|
|
SECURITY_STATUS SecStatus;
|
|
|
|
PSSP_CONTEXT Context = NULL;
|
|
|
|
SecPkgContext_Sizes ContextSizes;
|
|
SecPkgContext_Lifespan ContextLifespan;
|
|
UCHAR ContextNamesBuffer[sizeof(SecPkgContext_Names)+UNLEN*sizeof(WCHAR)];
|
|
SecPkgContext_DceInfo ContextDceInfo;
|
|
SecPkgContext_Names ContextNames;
|
|
ULONG ContextNamesSize;
|
|
WCHAR Name[UNLEN+DNLEN+1];
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
SspPrint(( SSP_API, "SspQueryContextAttributes Entered\n" ));
|
|
|
|
|
|
|
|
//
|
|
// Find the currently existing context.
|
|
//
|
|
|
|
Context = SspContextReferenceContext( ContextHandle,
|
|
ClientConnection,
|
|
FALSE );
|
|
|
|
if ( Context == NULL ) {
|
|
if ( (ContextHandle->dwLower == SEC_HANDLE_NTLMSSPS) &&
|
|
(SspCommonSecHandleValue == SEC_HANDLE_SECURITY) ) {
|
|
SecStatus = SEC_I_CALL_NTLMSSP_SERVICE;
|
|
} else SecStatus = SEC_E_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// If this context is really on the ntlmssp server, don't query
|
|
// attributes here
|
|
//
|
|
|
|
if (Context->ServerContextHandle.dwUpper != 0) {
|
|
*ContextHandle = Context->ServerContextHandle;
|
|
SecStatus = SEC_I_CALL_NTLMSSP_SERVICE;
|
|
goto Cleanup;
|
|
|
|
}
|
|
//
|
|
// Handle each of the various queried attributes
|
|
//
|
|
|
|
switch ( Attribute) {
|
|
case SECPKG_ATTR_SIZES:
|
|
|
|
ContextSizes.cbMaxToken = NTLMSP_MAX_TOKEN_SIZE;
|
|
|
|
if (Context->NegotiateFlags & (NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
|
|
NTLMSSP_NEGOTIATE_SIGN |
|
|
NTLMSSP_NEGOTIATE_SEAL) ) {
|
|
ContextSizes.cbMaxSignature = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
|
|
} else {
|
|
ContextSizes.cbMaxSignature = 0;
|
|
}
|
|
|
|
if (Context->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) {
|
|
ContextSizes.cbBlockSize = 1;
|
|
ContextSizes.cbSecurityTrailer = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
|
|
}
|
|
else
|
|
{
|
|
ContextSizes.cbBlockSize = 0;
|
|
ContextSizes.cbSecurityTrailer = 0;
|
|
}
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
sizeof(ContextSizes),
|
|
Buffer,
|
|
&ContextSizes );
|
|
|
|
break;
|
|
|
|
//
|
|
// No one uses the function so don't go to the overhead of maintaining
|
|
// the username in the context structure.
|
|
//
|
|
|
|
case SECPKG_ATTR_DCE_INFO:
|
|
|
|
SecStatus = SspLpcCopyFromClientBuffer (
|
|
ClientConnection,
|
|
sizeof(SecPkgContext_DceInfo),
|
|
&ContextDceInfo,
|
|
Buffer );
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We would like to set the name to domain\user. If domain name
|
|
// exists, copy domain\. If user exists, append user to string
|
|
//
|
|
|
|
*Name = L'\0';
|
|
|
|
if (Context->UserName.Buffer != NULL) {
|
|
if (Context->DomainName.Buffer != NULL) {
|
|
wcscat(Name,Context->DomainName.Buffer);
|
|
wcscat(Name,L"\\");
|
|
}
|
|
wcscat(Name,Context->UserName.Buffer);
|
|
}
|
|
|
|
ContextNamesSize = (wcslen(Name) + 1) * sizeof(WCHAR);
|
|
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
ContextNamesSize,
|
|
ContextDceInfo.pPac,
|
|
Name );
|
|
|
|
break;
|
|
|
|
case SECPKG_ATTR_NAMES:
|
|
|
|
SecStatus = SspLpcCopyFromClientBuffer (
|
|
ClientConnection,
|
|
sizeof(SecPkgContext_Names),
|
|
&ContextNames,
|
|
Buffer );
|
|
|
|
if (!NT_SUCCESS(SecStatus)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We would like to set the name to domain\user. If domain name
|
|
// exists, copy domain\. If user exists, append user to string
|
|
//
|
|
|
|
*Name = L'\0';
|
|
|
|
if (Context->UserName.Length != 0) {
|
|
if (Context->DomainName.Length != 0) {
|
|
wcscat(Name,Context->DomainName.Buffer);
|
|
wcscat(Name,L"\\");
|
|
}
|
|
wcscat(Name,Context->UserName.Buffer);
|
|
}
|
|
|
|
ContextNamesSize = (wcslen(Name) + 1) * sizeof(WCHAR);
|
|
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
ContextNamesSize,
|
|
ContextNames.sUserName,
|
|
Name );
|
|
|
|
break;
|
|
|
|
case SECPKG_ATTR_LIFESPAN:
|
|
|
|
// Use the correct times here
|
|
ContextLifespan.tsStart = SspContextGetTimeStamp( Context, FALSE );
|
|
ContextLifespan.tsExpiry = SspContextGetTimeStamp( Context, TRUE );
|
|
|
|
SecStatus = SspLpcCopyToClientBuffer(
|
|
ClientConnection,
|
|
sizeof(ContextLifespan),
|
|
Buffer,
|
|
&ContextLifespan );
|
|
|
|
break;
|
|
|
|
default:
|
|
SecStatus = SEC_E_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// Free local resources
|
|
//
|
|
Cleanup:
|
|
|
|
if ( Context != NULL ) {
|
|
SspContextDereferenceContext( Context );
|
|
}
|
|
|
|
SspPrint(( SSP_API, "SspQueryContextAttributes returns 0x%lx\n", SecStatus ));
|
|
return SecStatus;
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
SsprDeleteSecurityContext (
|
|
IN PSSP_CLIENT_CONNECTION ClientConnection,
|
|
IN OUT PCtxtHandle ContextHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Deletes the local data structures associated with the specified
|
|
security context and generates a token which is passed to a remote peer
|
|
so it too can remove the corresponding security context.
|
|
|
|
This API terminates a context on the local machine, and optionally
|
|
provides a token to be sent to the other machine. The OutputToken
|
|
generated by this call is to be sent to the remote peer (initiator or
|
|
acceptor). If the context was created with the I _REQ_ALLOCATE_MEMORY
|
|
flag, then the package will allocate a buffer for the output token.
|
|
Otherwise, it is the responsibility of the caller.
|
|
|
|
Arguments:
|
|
|
|
ClientConnection - Describes the client process.
|
|
|
|
ContextHandle - Handle to the context to delete
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Call completed successfully
|
|
|
|
SEC_E_NO_SPM -- Security Support Provider is not running
|
|
SEC_E_INVALID_HANDLE -- Credential/Context Handle is invalid
|
|
|
|
--*/
|
|
|
|
{
|
|
SECURITY_STATUS SecStatus = STATUS_SUCCESS;
|
|
PSSP_CONTEXT Context = NULL;
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
SspPrint(( SSP_API, "SspDeleteSecurityContext Entered\n" ));
|
|
|
|
|
|
|
|
//
|
|
// Find the currently existing context (and delink it).
|
|
//
|
|
|
|
Context = SspContextReferenceContext( ContextHandle,
|
|
ClientConnection,
|
|
TRUE );
|
|
|
|
//
|
|
// If the context is a server context, return SEC_I_CALL_NTLMSSP_SERVICE.
|
|
// If there is also a context here, delete that first.
|
|
//
|
|
|
|
if ( Context == NULL ) {
|
|
if ( (ContextHandle->dwLower == SEC_HANDLE_NTLMSSPS) &&
|
|
(SspCommonSecHandleValue == SEC_HANDLE_SECURITY) ) {
|
|
SecStatus = SEC_I_CALL_NTLMSSP_SERVICE;
|
|
} else SecStatus = SEC_E_INVALID_HANDLE;
|
|
} else {
|
|
|
|
if (Context->ServerContextHandle.dwUpper != 0) {
|
|
*ContextHandle = Context->ServerContextHandle;
|
|
SecStatus = SEC_I_CALL_NTLMSSP_SERVICE;
|
|
}
|
|
|
|
SspContextDereferenceContext( Context );
|
|
}
|
|
|
|
|
|
SspPrint(( SSP_API, "SspDeleteSecurityContext returns 0x%lx\n", SecStatus ));
|
|
return SecStatus;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
SspContextRegisterLogonProcess(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function registers this process as a LogonProcess and looks up
|
|
the MSV1_0 authentication package.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Status of the operation.
|
|
|
|
STATUS_NOT_LOGON_PROCESS - This process is not a logon process
|
|
|
|
--*/
|
|
|
|
{
|
|
LSA_OPERATIONAL_MODE SecurityMode;
|
|
NTSTATUS Status;
|
|
STRING LogonProcessName;
|
|
STRING PackageName;
|
|
|
|
//
|
|
// Register us as a logon process.
|
|
//
|
|
|
|
RtlInitAnsiString( &LogonProcessName, "NTLM Security Package" );
|
|
Status = LsaRegisterLogonProcess(
|
|
&LogonProcessName,
|
|
&SspGlobalLogonProcessHandle,
|
|
&SecurityMode );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
if ( Status == STATUS_PORT_CONNECTION_REFUSED ) {
|
|
Status = STATUS_NOT_LOGON_PROCESS;
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
RtlInitAnsiString( &PackageName, MSV1_0_PACKAGE_NAME );
|
|
Status = LsaLookupAuthenticationPackage(
|
|
SspGlobalLogonProcessHandle,
|
|
&PackageName,
|
|
&SspGlobalAuthenticationPackage );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
(VOID) LsaDeregisterLogonProcess( SspGlobalLogonProcessHandle );
|
|
SspGlobalLogonProcessHandle = NULL;
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
SspContextDeregisterLogonProcess(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deregisters this process as a LogonProcess.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Deregister us as a logon process.
|
|
//
|
|
|
|
if ( SspGlobalLogonProcessHandle != NULL ) {
|
|
(VOID) LsaDeregisterLogonProcess( SspGlobalLogonProcessHandle );
|
|
SspGlobalLogonProcessHandle = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
SspContextInitialize(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function initializes this module.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Status of the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Initialize the Context list to be empty.
|
|
//
|
|
|
|
InitializeCriticalSection(&SspContextCritSect);
|
|
InitializeListHead( &SspContextList );
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VOID
|
|
SspContextTerminate(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function cleans up any dangling Contexts.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Status of the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Drop any lingering Contexts
|
|
//
|
|
|
|
EnterCriticalSection( &SspContextCritSect );
|
|
while ( !IsListEmpty( &SspContextList ) ) {
|
|
CredHandle ContextHandle;
|
|
PSSP_CONTEXT Context;
|
|
|
|
ContextHandle.dwUpper =
|
|
(LONG) CONTAINING_RECORD( SspContextList.Flink,
|
|
SSP_CONTEXT,
|
|
Next );
|
|
|
|
ContextHandle.dwLower = SspCommonSecHandleValue;
|
|
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
|
|
Context = SspContextReferenceContext(
|
|
&ContextHandle,
|
|
NULL, // Don't know the Connection
|
|
TRUE); // Remove Context
|
|
|
|
if ( Context != NULL ) {
|
|
SspContextDereferenceContext(Context);
|
|
}
|
|
|
|
EnterCriticalSection( &SspContextCritSect );
|
|
}
|
|
LeaveCriticalSection( &SspContextCritSect );
|
|
|
|
|
|
//
|
|
// Delete the critical section
|
|
//
|
|
|
|
DeleteCriticalSection(&SspContextCritSect);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
SsprContextGetCredentials(
|
|
IN PCtxtHandle ContextHandle,
|
|
OUT LPWSTR * DomainName,
|
|
OUT PULONG DomainNameSize,
|
|
OUT LPWSTR * UserName,
|
|
OUT PULONG UserNameSize,
|
|
OUT LPWSTR * Password,
|
|
OUT PULONG PasswordSize,
|
|
OUT PHANDLE ClientTokenHandle,
|
|
OUT PLUID LogonId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the passed-in credentials of a context to the caller
|
|
|
|
Arguments:
|
|
|
|
ContextHandle - handle to get credentials for
|
|
DomainName - gets pointer to domain name
|
|
DomainNameSize - gets size in bytes of domain name string
|
|
...
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - success
|
|
SEC_E_INVALID_HANDLE - ContextHandle doesn't reference a real context
|
|
SEC_E_INSUFFICIENT_MEMORY - out of memory
|
|
|
|
--*/
|
|
{
|
|
PSSP_CONTEXT Context;
|
|
SECURITY_STATUS SecStatus;
|
|
|
|
//
|
|
// Initialize parameters in case of error
|
|
//
|
|
|
|
*DomainName = NULL;
|
|
*DomainNameSize = 0;
|
|
*UserName = NULL;
|
|
*UserNameSize = 0;
|
|
*Password = NULL;
|
|
*PasswordSize = 0;
|
|
|
|
Context = SspContextReferenceContext(
|
|
ContextHandle,
|
|
NULL,
|
|
FALSE);
|
|
|
|
if (Context == NULL) {
|
|
return(SEC_E_INVALID_HANDLE);
|
|
}
|
|
|
|
|
|
//
|
|
// Copy out the domain name, username, and password, if they
|
|
// exist
|
|
//
|
|
|
|
if (Context->DomainName.Buffer != NULL) {
|
|
*DomainName = SspAllocWStrFromWStr(Context->DomainName.Buffer);
|
|
if (*DomainName == NULL) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
*DomainNameSize = (wcslen(*DomainName) + 1) * sizeof(WCHAR);
|
|
}
|
|
|
|
if (Context->UserName.Buffer != NULL) {
|
|
*UserName = SspAllocWStrFromWStr(Context->UserName.Buffer);
|
|
if (*UserName == NULL) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
*UserNameSize = (wcslen(*UserName) + 1) * sizeof(WCHAR);
|
|
}
|
|
|
|
|
|
if (Context->Password.Buffer != NULL) {
|
|
SspRevealPassword(&Context->Password);
|
|
*Password = SspAllocWStrFromWStr(Context->Password.Buffer);
|
|
SspHidePassword(&Context->Password);
|
|
if (*Password == NULL) {
|
|
SecStatus = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
*PasswordSize = (wcslen(*Password) + 1) * sizeof(WCHAR);
|
|
}
|
|
*LogonId = Context->Credential->LogonId;
|
|
*ClientTokenHandle = Context->Credential->ClientTokenHandle;
|
|
|
|
SecStatus = STATUS_SUCCESS;
|
|
|
|
Cleanup:
|
|
|
|
if ( !NT_SUCCESS(SecStatus) ) {
|
|
if (*DomainName != NULL) {
|
|
LocalFree(*DomainName);
|
|
*DomainName = NULL;
|
|
*DomainNameSize = 0;
|
|
}
|
|
if (*UserName != NULL) {
|
|
LocalFree(*UserName);
|
|
*UserName = NULL;
|
|
*UserNameSize = 0;
|
|
}
|
|
if (*Password != NULL) {
|
|
LocalFree(*Password);
|
|
*Password = NULL;
|
|
*PasswordSize = 0;
|
|
}
|
|
}
|
|
|
|
SspContextDereferenceContext(Context);
|
|
|
|
return(SecStatus);
|
|
}
|
|
|
|
SECURITY_STATUS
|
|
SsprContextUpdateContext(
|
|
PCtxtHandle OldContextHandle,
|
|
PCtxtHandle ServerContextHandle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Updates a context with a server context handle
|
|
|
|
Arguments:
|
|
|
|
ContextHandle - handle to set the server context handle for
|
|
ServerContextHandle - handle to set the server context handle to
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - success
|
|
SEC_E_INVALID_HANDLE - ContextHandle doesn't reference a real context
|
|
|
|
--*/
|
|
{
|
|
PSSP_CONTEXT Context;
|
|
|
|
Context = SspContextReferenceContext(
|
|
OldContextHandle,
|
|
NULL,
|
|
FALSE);
|
|
|
|
if (Context == NULL) {
|
|
return(SEC_E_INVALID_HANDLE);
|
|
}
|
|
|
|
Context->ServerContextHandle = *ServerContextHandle;
|
|
|
|
SspContextDereferenceContext(Context);
|
|
|
|
return(STATUS_SUCCESS);
|
|
}
|