//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1999
//
// File:        userapi.cxx
//
// Contents:    User-mode APIs to the NtLm security package
//
//              Main user mode entry points into this dll:
//                SpUserModeInitialize
//                SpInstanceInit
//                SpDeleteUserModeContext
//                SpInitUserModeContext
//                SpMakeSignature
//                SpVerifySignature
//                SpSealMessage
//                SpUnsealMessage
//                SpGetContextToken
//                SpQueryContextAttributes
//                SpCompleteAuthToken
//                SpFormatCredentials
//                SpMarshallSupplementalCreds
//                SpExportSecurityContext
//                SpImportSecurityContext
//
//              Helper functions:
//                ReferenceUserContext
//                FreeUserContext
//                DereferenceUserContext
//                SspGenCheckSum
//                SspEncryptBuffer
//                NtLmMakePackedContext(this is called in the client's process)
//                NtLmCreateUserModeContext
//                SspGetTokenUser
//                SspCreateTokenDacl
//                SspMapContext (this is called in Lsa mode)
//
// History:     ChandanS 26-Jul-1996   Stolen from kerberos\client2\userapi.cxx
//
//------------------------------------------------------------------------
#include <global.h> // Globals!
#include "crc32.h"  // How to use crc32

extern "C"
{
#include <nlp.h>
}

// Keep this is sync with NTLM_KERNEL_CONTEXT defined in
// security\msv_sspi\kernel\krnlapi.cxx

typedef struct _NTLM_CLIENT_CONTEXT{
    union {
    LIST_ENTRY           Next;
    KSEC_LIST_ENTRY      KernelNext;
    };
    ULONG_PTR            LsaContext;
    ULONG                NegotiateFlags;
    HANDLE               ClientTokenHandle;
    PACCESS_TOKEN        AccessToken;
    PULONG                  pSendNonce;      // ptr to nonce to use for send
    PULONG                  pRecvNonce;      // ptr to nonce to use for receive
    struct RC4_KEYSTRUCT *  pSealRc4Sched;   // ptr to key sched used for Seal
    struct RC4_KEYSTRUCT *  pUnsealRc4Sched; // ptr to key sched used to Unseal
    ULONG                   SendNonce;
    ULONG                   RecvNonce;
    LPWSTR                  ContextNames;
    PUCHAR                  pbMarshalledTargetInfo;
    ULONG                   cbMarshalledTargetInfo;
    UCHAR                SessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
    ULONG                ContextSignature;
    ULONG                References ;
    TimeStamp            PasswordExpiry;
    ULONG                UserFlags;
    UCHAR                   SignSessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
    UCHAR                   VerifySessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
    UCHAR                   SealSessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
    UCHAR                   UnsealSessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];

    ULONG64                 Pad1;           // pad keystructs to 64.
    struct RC4_KEYSTRUCT    SealRc4Sched;   // key struct used for Seal
    ULONG64                 Pad2;           // pad keystructs to 64.
    struct RC4_KEYSTRUCT    UnsealRc4Sched; // key struct used to Unseal
} NTLM_CLIENT_CONTEXT, * PNTLM_CLIENT_CONTEXT;

typedef struct _NTLM_PACKED_CONTEXT {
    ULONG   Tag ;
    ULONG   NegotiateFlags ;
    ULONG   ClientTokenHandle ;
    ULONG   SendNonce ;
    ULONG   RecvNonce ;
    UCHAR   SessionKey[ MSV1_0_USER_SESSION_KEY_LENGTH ];
    ULONG   ContextSignature ;
    TimeStamp   PasswordExpiry ;
    ULONG   UserFlags ;
    ULONG   ContextNames ;
    ULONG   ContextNameLength ;
    ULONG   MarshalledTargetInfo;       // offset
    ULONG   MarshalledTargetInfoLength;
    UCHAR   SignSessionKey[ MSV1_0_USER_SESSION_KEY_LENGTH ];
    UCHAR   VerifySessionKey[ MSV1_0_USER_SESSION_KEY_LENGTH ];
    UCHAR   SealSessionKey[ MSV1_0_USER_SESSION_KEY_LENGTH ];
    UCHAR   UnsealSessionKey[ MSV1_0_USER_SESSION_KEY_LENGTH ];
    struct RC4_KEYSTRUCT    SealRc4Sched;   
    struct RC4_KEYSTRUCT    UnsealRc4Sched;
} NTLM_PACKED_CONTEXT, * PNTLM_PACKED_CONTEXT ;

#define NTLM_PACKED_CONTEXT_MAP     0
#define NTLM_PACKED_CONTEXT_EXPORT  1

#define CSSEALMAGIC "session key to client-to-server sealing key magic constant"
#define SCSEALMAGIC "session key to server-to-client sealing key magic constant"
#define CSSIGNMAGIC "session key to client-to-server signing key magic constant"
#define SCSIGNMAGIC "session key to server-to-client signing key magic constant"



#define             NTLM_USERLIST_COUNT         (16)    // count of lists
#define             NTLM_USERLIST_LOCK_COUNT    (2)     // count of locks

LIST_ENTRY          NtLmUserContextList[ NTLM_USERLIST_COUNT ];         // list array.
ULONG               NtLmUserContextCount[ NTLM_USERLIST_COUNT ];        // count of active contexts
RTL_RESOURCE        NtLmUserContextLock[ NTLM_USERLIST_LOCK_COUNT ];    // lock array


// Counter for exported handles;never de-refed
// Should probably do a GetSystemInfo and get a space of handles that cannot
// be valid in the Lsa process
ULONG_PTR ExportedContext = 0;

NTSTATUS
SspCreateTokenDacl(
    HANDLE Token
    );

ULONG
HandleToListIndex(
    ULONG_PTR ContextHandle
    );

ULONG
__inline
ListIndexToLockIndex(
    ULONG ListIndex
    );


//+-------------------------------------------------------------------------
//
//  Function:   SpUserModeInitialize
//
//  Synopsis:   Initialize an the MSV1_0 DLL in  a client's
//              address space
//
//  Effects:
//
//  Arguments:  LsaVersion - Version of the security dll loading the package
//              PackageVersion - Version of the MSV1_0 package
//              UserFunctionTable - Receives a copy of Kerberos's user mode
//                  function table
//              pcTables - Receives count of tables returned.
//
//  Requires:
//
//  Returns:    STATUS_SUCCESS
//
//  Notes: we do what was done in SspInitLocalContexts()
//         from net\svcdlls\ntlmssp\client\sign.c and more.
//
//
//--------------------------------------------------------------------------
NTSTATUS
SEC_ENTRY
SpUserModeInitialize(
    IN ULONG    LsaVersion,
    OUT PULONG  PackageVersion,
    OUT PSECPKG_USER_FUNCTION_TABLE * UserFunctionTable,
    OUT PULONG  pcTables
    )
{
    NTSTATUS Status = STATUS_SUCCESS;

#if DBG
    SspGlobalDbflag = SSP_CRITICAL;
    if( NtLmState != NtLmLsaMode )
    {
        InitializeCriticalSection(&SspGlobalLogFileCritSect);
    }
#endif

    if (LsaVersion != SECPKG_INTERFACE_VERSION)
    {
        Status = STATUS_INVALID_PARAMETER;
        goto Cleanup;
    }

    *PackageVersion = SECPKG_INTERFACE_VERSION;

    NtLmUserFunctionTable.InstanceInit = SpInstanceInit;
    NtLmUserFunctionTable.MakeSignature = SpMakeSignature;
    NtLmUserFunctionTable.VerifySignature = SpVerifySignature;
    NtLmUserFunctionTable.SealMessage = SpSealMessage;
    NtLmUserFunctionTable.UnsealMessage = SpUnsealMessage;
    NtLmUserFunctionTable.GetContextToken = SpGetContextToken;
    NtLmUserFunctionTable.QueryContextAttributes = SpQueryContextAttributes;
    NtLmUserFunctionTable.CompleteAuthToken = SpCompleteAuthToken;
    NtLmUserFunctionTable.InitUserModeContext = SpInitUserModeContext;
    NtLmUserFunctionTable.DeleteUserModeContext = SpDeleteUserModeContext;
    NtLmUserFunctionTable.FormatCredentials = SpFormatCredentials;
    NtLmUserFunctionTable.MarshallSupplementalCreds = SpMarshallSupplementalCreds;
    NtLmUserFunctionTable.ExportContext = SpExportSecurityContext;
    NtLmUserFunctionTable.ImportContext = SpImportSecurityContext;

    *UserFunctionTable = &NtLmUserFunctionTable;
    *pcTables = 1;


    if ( NtLmState != NtLmLsaMode)
    {
        //
        // SafeAllocaInitialize was already called in SpLsaModeInitialize
        //

        SafeAllocaInitialize(SAFEALLOCA_USE_DEFAULT,
                             SAFEALLOCA_USE_DEFAULT,
                             NtLmAllocate,
                             NtLmFree);
    }

Cleanup:

    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}



//+-------------------------------------------------------------------------
//
//  Function:   ReferenceUserContext
//
//  Synopsis:   locates a user context in the list, refrences it
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:    the context, if it is found, else NULL
//
//  Notes: This was SspContextReferenceContext() in
//         net\svcdlls\ntlmssp\common\context.c
//
//
//--------------------------------------------------------------------------
PNTLM_CLIENT_CONTEXT
ReferenceUserContext(
    IN ULONG_PTR ContextHandle,
    IN BOOLEAN RemoveContext )
{
    SspPrint(( SSP_API_MORE, "Entering ReferenceUserContext for 0x%x\n", ContextHandle ));
    PLIST_ENTRY ListEntry;
    PNTLM_CLIENT_CONTEXT pContext = NULL;
    ULONG ListIndex;
    ULONG LockIndex;

    ListIndex = HandleToListIndex( ContextHandle );
    LockIndex = ListIndexToLockIndex( ListIndex );

    RtlAcquireResourceShared(&NtLmUserContextLock[LockIndex], TRUE);

    //
    // Look for a match for the LsaContext, not user context
    //

    for (ListEntry = NtLmUserContextList[ListIndex].Flink;
         ListEntry != &NtLmUserContextList[ListIndex];
         ListEntry = ListEntry->Flink ) {

        pContext = CONTAINING_RECORD(ListEntry, NTLM_CLIENT_CONTEXT, Next );


        if (pContext->LsaContext != ContextHandle)
        {
            continue;
        }

        //
        // Found it!
        //

        if (!RemoveContext)
        {
            InterlockedIncrement( (PLONG)&pContext->References );
            RtlReleaseResource(&NtLmUserContextLock[LockIndex]);
        }
        else
        {
            RtlConvertSharedToExclusive(&NtLmUserContextLock[LockIndex]);
            
            RemoveEntryList (&pContext->Next);
            NtLmUserContextCount[ListIndex]--;
            
            RtlReleaseResource(&NtLmUserContextLock[LockIndex]);

            SspPrint(( SSP_API_MORE, "Delinked Context 0x%lx\n", pContext ));
        }

        SspPrint(( SSP_API_MORE, "Leaving ReferenceUserContext for 0x%x\n", ContextHandle));
        return pContext;
    }


    // No match found

    RtlReleaseResource(&NtLmUserContextLock[LockIndex]);
    SspPrint(( SSP_API_MORE, "Leaving ReferenceUserContext for 0x%x\n", ContextHandle ));
    return NULL;
}

//+-------------------------------------------------------------------------
//
//  Function:   FreeUserContext
//
//  Synopsis:   frees alloced pointers in this context and
//              then frees the context
//
//  Arguments:  lContext  - the unlinked user context
//
//  Returns:    STATUS_SUCCESS on success
//
//  Notes:
//
//--------------------------------------------------------------------------
NTSTATUS
FreeUserContext (
    PNTLM_CLIENT_CONTEXT UserContext
    )
{
    SspPrint(( SSP_API_MORE, "Entering FreeUserContext for context 0x%x\n", UserContext ));

    NTSTATUS Status = STATUS_SUCCESS;

    if (UserContext->ContextNames != NULL)
    {
        NtLmFree (UserContext->ContextNames);
    }

    if (UserContext->ClientTokenHandle != NULL)
    {
        NTSTATUS IgnoreStatus;
        IgnoreStatus = NtClose(UserContext->ClientTokenHandle);
        ASSERT (NT_SUCCESS (IgnoreStatus));
    }

    SspPrint(( SSP_API_MORE, "Deleting Context 0x%x\n", UserContext));

    ZeroMemory( UserContext, sizeof(*UserContext) );
    NtLmFree (UserContext);

    SspPrint(( SSP_API_MORE, "Leaving FreeUserContext for context 0x%x, status = 0x%x\n", Status ));

    return Status;
}

//+-------------------------------------------------------------------------
//
//  Function:   DereferenceUserContext
//
//  Synopsis:   frees alloced elements in the context, frees context
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:     None
//
//  Notes: This was SspContextDereferenceContext() in
//         net\svcdlls\ntlmssp\common\context.c
//
//
//--------------------------------------------------------------------------
NTSTATUS
DereferenceUserContext (
    PNTLM_CLIENT_CONTEXT pContext
    )
{
    SspPrint(( SSP_API_MORE, "Entering DereferenceUserContext 0x%lx\n", pContext ));
    NTSTATUS Status = STATUS_SUCCESS;

    LONG References;

    //
    // Decrement the reference count
    //

///    RtlAcquireResourceShared(&NtLmUserContextLock, TRUE);
////    ASSERT (pContext->References >= 1);
////    References = -- pContext->References;
    References = InterlockedDecrement( (PLONG)&pContext->References );
    ASSERT( References >= 0 );
////    RtlReleaseResource(&NtLmUserContextLock);

    //
    // If the count has dropped to zero, then free all alloced stuff
    //

    if (References == 0)
    {
        Status = FreeUserContext(pContext);
    }
    SspPrint(( SSP_API_MORE, "Leaving DereferenceUserContext\n" ));
    return Status;
}

//+-------------------------------------------------------------------------
//
//  Function:   SpInstanceInit
//
//  Synopsis:   Initialize an instance of the NtLm package in a client's
//              address space
//
//  Effects:
//
//  Arguments:  Version - Version of the security dll loading the package
//              FunctionTable - Contains helper routines for use by NtLm
//              UserFunctions - Receives a copy of NtLm's user mode
//                  function table
//
//  Requires:
//
//  Returns:    STATUS_SUCCESS
//
//  Notes: we do what was done in SspInitLocalContexts()
//         from net\svcdlls\ntlmssp\client\sign.c and more.
//
//
//--------------------------------------------------------------------------


NTSTATUS NTAPI
SpInstanceInit(
    IN ULONG Version,
    IN PSECPKG_DLL_FUNCTIONS DllFunctionTable,
    OUT PVOID * UserFunctionTable
    )
{
    SspPrint(( SSP_API, "Entering SpInstanceInit\n" ));
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG Index;


    // Save the Alloc/Free functions
    if( NtLmState != NtLmLsaMode )
    {
        NtLmState = NtLmUserMode;
    }

    UserFunctions = DllFunctionTable;

    for( Index=0 ; Index < NTLM_USERLIST_COUNT ; Index++ )
    {
        InitializeListHead (&NtLmUserContextList[Index]);
    }
    
    for( Index=0 ; Index < NTLM_USERLIST_LOCK_COUNT ; Index++ )
    {
        __try {
            RtlInitializeResource (&NtLmUserContextLock[Index]);
        } __except(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            break;
        }
    }

    SspPrint(( SSP_API, "Leaving SpInstanceInit: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}



//+-------------------------------------------------------------------------
//
//  Function:   SpDeleteUserModeContext
//
//  Synopsis:   Deletes a user mode context by unlinking it and then
//              dereferencing it.
//
//  Effects:
//
//  Arguments:  ContextHandle - Lsa context handle of the context to delete
//
//  Requires:
//
//  Returns:    STATUS_SUCCESS on success, STATUS_INVALID_HANDLE if the
//              context can't be located
//
//  Notes:
//        If this is an exported context, send a flag back to the LSA so that
//        Lsa does not call the SecpDeleteSecurityContext in the lsa process
//
//
//--------------------------------------------------------------------------


NTSTATUS NTAPI
SpDeleteUserModeContext(
    IN ULONG_PTR ContextHandle
    )
{
    SspPrint(( SSP_API, "Entering SpDeleteUserModeContext 0x%lx\n", ContextHandle ));
    PNTLM_CLIENT_CONTEXT pContext = NULL;
    NTSTATUS Status = STATUS_SUCCESS, SaveStatus = STATUS_SUCCESS;

    //
    // Find the currently existing user context and delink it
    // so that another context cannot Reference it before we
    // Dereference this one.
    //

    pContext = ReferenceUserContext(ContextHandle, TRUE);

    if (pContext == NULL)
    {
        //
        // pContext is legally NULL when we are dealing with an incomplete
        // context.  This can often be the case when the second call to
        // InitializeSecurityContext() fails.
        //
///        Status = STATUS_INVALID_HANDLE;
        Status = STATUS_SUCCESS;
        SspPrint(( SSP_API_MORE, "SpDeleteUserModeContext, local pContext is NULL\n" ));
        goto CleanUp;
    }


    if ((pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_EXPORTED_CONTEXT) != 0)
    {
        // Ignore all other errors and pass back
        SaveStatus = SEC_I_NO_LSA_CONTEXT;
    }


CleanUp:

    if (pContext != NULL)
    {
        Status = DereferenceUserContext(pContext);
    }

    if (SaveStatus == SEC_I_NO_LSA_CONTEXT)
    {
        Status = SaveStatus;
    }

    SspPrint(( SSP_API, "Leaving SpDeleteUserModeContext: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}


VOID
SspRc4Key(
    IN ULONG                NegotiateFlags,
    OUT struct RC4_KEYSTRUCT *pRc4Key,
    IN PUCHAR               pSessionKey
    )
/*++

RoutineDescription:

    Create an RC4 key schedule, making sure key length is OK for export

Arguments:

    NegotiateFlags  negotiate feature flags; NTLM2 bit is only one looked at
    pRc4Key         pointer to RC4 key schedule structure; filled in by this routine
    pSessionKey     pointer to session key -- must be full 16 bytes

Return Value:

--*/
{
    //
    // For NTLM2, effective length was already cut down
    //

    if ((NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) != 0) {

        rc4_key(pRc4Key, MSV1_0_USER_SESSION_KEY_LENGTH, pSessionKey);

    } else if( NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY ) {
        UCHAR Key[MSV1_0_LANMAN_SESSION_KEY_LENGTH];
        ULONG KeyLen;

        ASSERT(MSV1_0_LANMAN_SESSION_KEY_LENGTH == 8);

        // prior to Win2k, negotiated key strength had no bearing on
        // key size.  So, to allow proper interop to NT4, we don't
        // worry about 128bit.  56bit and 40bit are the only supported options.
        // 56bit is enabled because this was introduced in Win2k, and
        // Win2k -> Win2k interops correctly.
        //
#if 0
        if( NegotiateFlags & NTLMSSP_NEGOTIATE_128 ) {
            KeyLen = 8;

        } else
#endif
        if( NegotiateFlags & NTLMSSP_NEGOTIATE_56 ) {
            KeyLen = 7;

            //
            // Put a well-known salt at the end of the key to
            // limit the changing part to 56 bits.
            //

            Key[7] = 0xa0;
        } else {
            KeyLen = 5;

            //
            // Put a well-known salt at the end of the key to
            // limit the changing part to 40 bits.
            //

            Key[5] = 0xe5;
            Key[6] = 0x38;
            Key[7] = 0xb0;
        }

        RtlCopyMemory(Key,pSessionKey,KeyLen);

        SspPrint(( SSP_SESSION_KEYS, "Non NTLMv2 LM_KEY session key size: %lu key=%lx%lx\n",
                    KeyLen,
                    ((DWORD*)Key)[0],
                    ((DWORD*)Key)[1]
                    ));

        rc4_key(pRc4Key, MSV1_0_LANMAN_SESSION_KEY_LENGTH, Key);

    } else {

        SspPrint(( SSP_SESSION_KEYS, "Non NTLMv2 (not LM_KEY) session key size: %lu\n", 16));
        rc4_key(pRc4Key, MSV1_0_USER_SESSION_KEY_LENGTH, pSessionKey);
    }
}



//+-------------------------------------------------------------------------
//
//  Function:   SpInitUserModeContext
//
//  Synopsis:   Creates a user-mode context from a packed LSA mode context
//
//  Effects:
//
//  Arguments:  ContextHandle - Lsa mode context handle for the context
//              PackedContext - A marshalled buffer containing the LSA
//                  mode context.
//
//  Requires:
//
//  Returns:    STATUS_SUCCESS or STATUS_INSUFFICIENT_RESOURCES
//
//  Notes:
//
//
//--------------------------------------------------------------------------


NTSTATUS NTAPI
SpInitUserModeContext(
    IN ULONG_PTR ContextHandle,
    IN PSecBuffer PackedContext
    )
{
    ASSERT(PackedContext);


    SspPrint(( SSP_API, "Entering SpInitUserModeContext 0x%lx\n", ContextHandle ));
    NTSTATUS Status = STATUS_SUCCESS;
    PNTLM_CLIENT_CONTEXT pContext = NULL;
    UINT Length = 0;
    PNTLM_PACKED_CONTEXT pTmpContext  = (PNTLM_PACKED_CONTEXT) PackedContext->pvBuffer;
    ULONG ListIndex;
    ULONG LockIndex;

    if (PackedContext->cbBuffer < sizeof(NTLM_PACKED_CONTEXT))
    {
        Status = STATUS_INVALID_PARAMETER;
        SspPrint(( SSP_CRITICAL, "SpInitUserModeContext, ContextData size < NTLM_CLIENT_CONTEXT\n" ));
        goto Cleanup;
    }

    pContext = (PNTLM_CLIENT_CONTEXT) NtLmAllocate( sizeof(NTLM_CLIENT_CONTEXT) );

    if (!pContext)
    {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        SspPrint(( SSP_CRITICAL, "SpInitUserModeContext, NtLmAllocate returns NULL\n" ));
        goto Cleanup;
    }

    //
    // If ClientTokenHandle is NULL, we are being called as
    // as an effect of InitializeSecurityContext, else we are
    // being called because of AcceptSecurityContext
    //

    if (pTmpContext->ClientTokenHandle != NULL )
    {
        pContext->ClientTokenHandle = (HANDLE) ULongToPtr(pTmpContext->ClientTokenHandle);
        if (FAILED(SspCreateTokenDacl(pContext->ClientTokenHandle)))
        {
            Status = STATUS_INVALID_HANDLE;
            SspPrint(( SSP_CRITICAL, "SpInitUserModeContext, SspCreateTokenDacl failed\n" ));
            goto Cleanup;
        }
    }

    // Copy contents of PackedContext->pvBuffer to pContext

    pContext->LsaContext = ContextHandle;
    pContext->NegotiateFlags = pTmpContext->NegotiateFlags;


    SspPrint((SSP_NEGOTIATE_FLAGS, "SpInitUserModeContext NegotiateFlags: %lx\n", pContext->NegotiateFlags));

    pContext->References = 1;


    //
    // keep all 128 bits here, so signing can be strong even if encrypt can't be
    //

    RtlCopyMemory(  pContext->SessionKey,
                        pTmpContext->SessionKey,
                        MSV1_0_USER_SESSION_KEY_LENGTH);

    //
    // if doing full duplex as part of NTLM2, generate different sign
    // and seal keys for each direction
    //  all we do is MD5 the base session key with a different magic constant
    //

    if ( pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2 ) {
        MD5_CTX Md5Context;
        ULONG KeyLen;

        ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);

        if( pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_128 )
            KeyLen = 16;
        else if( pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_56 )
            KeyLen = 7;
        else
            KeyLen = 5;

        SspPrint(( SSP_SESSION_KEYS, "NTLMv2 session key size: %lu\n", KeyLen));

        //
        // make client to server encryption key
        //

        MD5Init(&Md5Context);
        MD5Update(&Md5Context, pContext->SessionKey, KeyLen);
        MD5Update(&Md5Context, (unsigned char*)CSSEALMAGIC, sizeof(CSSEALMAGIC));
        MD5Final(&Md5Context);

        //
        // if TokenHandle == NULL, this is the client side
        //  put key in the right place: for client it's seal, for server it's unseal
        //

        if (pContext->ClientTokenHandle == NULL)
            RtlCopyMemory(pContext->SealSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
        else
            RtlCopyMemory(pContext->UnsealSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);

        //
        // make server to client encryption key
        //

        MD5Init(&Md5Context);
        MD5Update(&Md5Context, pContext->SessionKey, KeyLen);
        MD5Update(&Md5Context, (unsigned char*)SCSEALMAGIC, sizeof(SCSEALMAGIC));
        MD5Final(&Md5Context);
        ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);
        if (pContext->ClientTokenHandle == NULL)
            RtlCopyMemory(pContext->UnsealSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
        else
            RtlCopyMemory(pContext->SealSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);

        //
        // make client to server signing key -- always 128 bits!
        //

        MD5Init(&Md5Context);
        MD5Update(&Md5Context, pContext->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
        MD5Update(&Md5Context, (unsigned char*)CSSIGNMAGIC, sizeof(CSSIGNMAGIC));
        MD5Final(&Md5Context);
        if (pContext->ClientTokenHandle == NULL)
            RtlCopyMemory(pContext->SignSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
        else
            RtlCopyMemory(pContext->VerifySessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);

        //
        // make server to client signing key
        //

        MD5Init(&Md5Context);
        MD5Update(&Md5Context, pContext->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
        MD5Update(&Md5Context, (unsigned char*)SCSIGNMAGIC, sizeof(SCSIGNMAGIC));
        MD5Final(&Md5Context);
        if (pContext->ClientTokenHandle == NULL)
            RtlCopyMemory(pContext->VerifySessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
        else
            RtlCopyMemory(pContext->SignSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);

        //
        // set pointers to different key schedule and nonce for each direction
        //  key schedule will be filled in later...
        //

        pContext->pSealRc4Sched = &pContext->SealRc4Sched;
        pContext->pUnsealRc4Sched = &pContext->UnsealRc4Sched;
        pContext->pSendNonce = &pContext->SendNonce;
        pContext->pRecvNonce = &pContext->RecvNonce;
   } else {

        //
        // just copy session key to all four keys
        //  leave them 128 bits -- they get cut to 40 bits later
        //

        RtlCopyMemory(  pContext->SealSessionKey,
                        pContext->SessionKey,
                        MSV1_0_USER_SESSION_KEY_LENGTH);
        RtlCopyMemory(  pContext->UnsealSessionKey,
                        pContext->SessionKey,
                        MSV1_0_USER_SESSION_KEY_LENGTH);
        RtlCopyMemory(  pContext->SignSessionKey,
                        pContext->SessionKey,
                        MSV1_0_USER_SESSION_KEY_LENGTH);
        RtlCopyMemory(  pContext->VerifySessionKey,
                        pContext->SessionKey,
                        MSV1_0_USER_SESSION_KEY_LENGTH);

        //
        // set pointers to share a key schedule and nonce for each direction
        //  (OK because half duplex!)
        //

        pContext->pSealRc4Sched = &pContext->SealRc4Sched;
        pContext->pUnsealRc4Sched = &pContext->SealRc4Sched;
        pContext->pSendNonce = &pContext->SendNonce;
        pContext->pRecvNonce = &pContext->SendNonce;
    }

    if ( pTmpContext->ContextNames )
    {
       pContext->ContextNames = (PWSTR) NtLmAllocate( pTmpContext->ContextNameLength );

       if ( pContext->ContextNames == NULL )
       {
           Status = STATUS_INSUFFICIENT_RESOURCES ;
           goto Cleanup ;
       }

       RtlCopyMemory( 
           pContext->ContextNames,
           ((PUCHAR) pTmpContext) + pTmpContext->ContextNames,
           pTmpContext->ContextNameLength );
    }
    else 
    {
        pContext->ContextNames = NULL ;
    }

    pContext->SendNonce = pTmpContext->SendNonce;
    pContext->RecvNonce = pTmpContext->RecvNonce;

    SspRc4Key(pContext->NegotiateFlags, &pContext->SealRc4Sched, pContext->SealSessionKey);
    SspRc4Key(pContext->NegotiateFlags, &pContext->UnsealRc4Sched, pContext->UnsealSessionKey);


    pContext->PasswordExpiry = pTmpContext->PasswordExpiry;
    pContext->UserFlags = pTmpContext->UserFlags;


    ListIndex = HandleToListIndex( pContext->LsaContext );
    LockIndex = ListIndexToLockIndex( ListIndex );
    
    RtlAcquireResourceExclusive(&NtLmUserContextLock[LockIndex], TRUE);

    InsertHeadList ( &NtLmUserContextList[ListIndex], &pContext->Next );
    NtLmUserContextCount[ListIndex]++;

    RtlReleaseResource(&NtLmUserContextLock[LockIndex]);


Cleanup:

    if (!NT_SUCCESS(Status))
    {
        if (pContext != NULL)
        {
            FreeUserContext(pContext);
        }
    }

    // Let FreeContextBuffer handle freeing the virtual allocs

    if (PackedContext->pvBuffer != NULL)
    {
        FreeContextBuffer(PackedContext->pvBuffer);
        PackedContext->pvBuffer = NULL;
    }

    SspPrint(( SSP_API, "Leaving SpInitUserModeContext: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}


//
// Bogus add-shift check sum
//

void
SspGenCheckSum(
    IN  PSecBuffer  pMessage,
    OUT PNTLMSSP_MESSAGE_SIGNATURE  pSig
    )

/*++

RoutineDescription:

    Generate a crc-32 checksum for a buffer

Arguments:

Return Value:
Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
       routine SspGenCheckSum. It's possible that
       bugs got copied too

--*/

{
    Crc32(pSig->CheckSum,pMessage->cbBuffer,pMessage->pvBuffer,&pSig->CheckSum);
}


VOID
SspEncryptBuffer(
    IN PNTLM_CLIENT_CONTEXT pContext,
    IN struct RC4_KEYSTRUCT * pRc4Key,
    IN ULONG BufferSize,
    IN OUT PVOID Buffer
    )

/*++

RoutineDescription:

    Encrypts a buffer with the RC4 key in the context.  If the context
    is for a datagram session, then the key is copied before being used
    to encrypt the buffer.

Arguments:

    pContext - Context containing the key to encrypt the data

    BufferSize - Length of buffer in bytes

    Buffer - Buffer to encrypt.
    Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
           routine SspEncryptBuffer. It's possible that
           bugs got copied too

Return Value:

--*/

{
    struct RC4_KEYSTRUCT TemporaryKey;
///    struct RC4_KEYSTRUCT * EncryptionKey = &pContext->Rc4Key;
    struct RC4_KEYSTRUCT * EncryptionKey = pRc4Key;

    if (BufferSize == 0)
    {
        return;
    }

    //
    // For datagram (application supplied sequence numbers) before NTLM2
    // we used to copy the key before encrypting so we don't
    // have a changing key; but that reused the key stream. Now we only
    // do that when backwards compatibility is explicitly called for.
    //

    if (((pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) != 0) &&
        ((pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) == 0) ) {

        RtlCopyMemory(
            &TemporaryKey,
            EncryptionKey,
            sizeof(struct RC4_KEYSTRUCT)
            );
        EncryptionKey = &TemporaryKey;

    }

    rc4(
        EncryptionKey,
        BufferSize,
        (PUCHAR) Buffer
        );
}


typedef enum _eSignSealOp {
    eSign,      // MakeSignature is calling
    eVerify,    // VerifySignature is calling
    eSeal,      // SealMessage is calling
    eUnseal     // UnsealMessage is calling
} eSignSealOp;

SECURITY_STATUS
SspSignSealHelper(
    IN PNTLM_CLIENT_CONTEXT pContext,
    IN eSignSealOp Op,
    IN OUT PSecBufferDesc pMessage,
    IN ULONG MessageSeqNo,
    OUT PNTLMSSP_MESSAGE_SIGNATURE pSig,
    OUT PNTLMSSP_MESSAGE_SIGNATURE * ppSig
    )
/*++

RoutineDescription:

    Handle signing a message

Arguments:

Return Value:

--*/

{

    HMACMD5_CTX HMACMD5Context;
    UCHAR TempSig[MD5DIGESTLEN];
    NTLMSSP_MESSAGE_SIGNATURE  Sig;
    int Signature;
    ULONG i;
    PUCHAR pKey;                            // ptr to key to use for encryption
    PUCHAR pSignKey;                        // ptr to key to use for signing
    PULONG pNonce;                          // ptr to nonce to use
    struct RC4_KEYSTRUCT * pRc4Sched;       // ptr to key schedule to use

    NTLMSSP_MESSAGE_SIGNATURE  AlignedSig;  // aligned copy of input sig data



    Signature = -1;
    for (i = 0; i < pMessage->cBuffers; i++)
    {
        if ((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_TOKEN)
        {
            Signature = i;
            break;
        }
    }
    if (Signature == -1)
    {
        return(SEC_E_INVALID_TOKEN);
    }

    if (pMessage->pBuffers[Signature].cbBuffer < NTLMSSP_MESSAGE_SIGNATURE_SIZE)
    {
        return(SEC_E_INVALID_TOKEN);
    }

    *ppSig = (NTLMSSP_MESSAGE_SIGNATURE*)pMessage->pBuffers[Signature].pvBuffer;

    RtlCopyMemory( &AlignedSig, *ppSig, sizeof(AlignedSig) );

    //
    // If sequence detect wasn't requested, put on an empty
    // security token . Don't do the check if Seal/Unseal is called.
    //

    if (!(pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) &&
       (Op == eSign || Op == eVerify))
    {
        RtlZeroMemory(pSig,NTLMSSP_MESSAGE_SIGNATURE_SIZE);
        pSig->Version = NTLM_SIGN_VERSION;
        return(SEC_E_OK);
    }

    // figure out which key, key schedule, and nonce to use
    //  depends on the op. SspAddLocalContext set up so that code on client
    //  and server just (un)seals with (un)seal key or key schedule, etc.
    //  and also sets pointers to share sending/receiving key schedule/nonce
    //  when in half duplex mode. Hence, this code gets to act as if it were
    //  always in full duplex mode.
    switch (Op) {
    case eSeal:
        pSignKey = pContext->SignSessionKey;    // if NTLM2
        pKey = pContext->SealSessionKey;
        pRc4Sched = pContext->pSealRc4Sched;
        pNonce = pContext->pSendNonce;
        break;
    case eUnseal:
        pSignKey = pContext->VerifySessionKey;  // if NTLM2
        pKey = pContext->UnsealSessionKey;
        pRc4Sched = pContext->pUnsealRc4Sched;
        pNonce = pContext->pRecvNonce;
        break;
    case eSign:
        pSignKey = pContext->SignSessionKey;    // if NTLM2
        pKey = pContext->SealSessionKey;        // might be used to encrypt the signature
        pRc4Sched = pContext->pSealRc4Sched;
        pNonce = pContext->pSendNonce;
        break;
    case eVerify:
        pSignKey = pContext->VerifySessionKey;  // if NTLM2
        pKey = pContext->UnsealSessionKey;      // might be used to decrypt the signature
        pRc4Sched = pContext->pUnsealRc4Sched;
        pNonce = pContext->pRecvNonce;
        break;
    }

    //
    // Either we can supply the sequence number, or
    // the application can supply the message sequence number.
    //

    Sig.Version = NTLM_SIGN_VERSION;

    // if we're doing the new NTLM2 version:
    if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) {

        if ((pContext->NegotiateFlags & NTLMSSP_APP_SEQ) == 0)
        {
            Sig.Nonce = *pNonce;    // use our sequence number
            (*pNonce) += 1;
        }
        else {

            if (Op == eSeal || Op == eSign || MessageSeqNo != 0)
                Sig.Nonce = MessageSeqNo;
            else
                Sig.Nonce = AlignedSig.Nonce;

            //   if using RC4, must rekey for each packet
            //   RC4 is used for seal, unseal; and for encrypting the HMAC hash if
            //   key exchange was negotiated (we use just HMAC if no key exchange,
            //   so that a good signing option exists with no RC4 encryption needed)

            if (Op == eSeal || Op == eUnseal || pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
            {
                MD5_CTX Md5ContextReKey;

                MD5Init(&Md5ContextReKey);
                MD5Update(&Md5ContextReKey, pKey, MSV1_0_USER_SESSION_KEY_LENGTH);
                MD5Update(&Md5ContextReKey, (unsigned char*)&Sig.Nonce, sizeof(Sig.Nonce));
                MD5Final(&Md5ContextReKey);
                ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);
                SspRc4Key(pContext->NegotiateFlags, pRc4Sched, Md5ContextReKey.digest);
            }
        }

        //
        // using HMAC hash, init it with the key
        //

        HMACMD5Init(&HMACMD5Context, pSignKey, MSV1_0_USER_SESSION_KEY_LENGTH);

        //
        // include the message sequence number
        //

        HMACMD5Update(&HMACMD5Context, (unsigned char*)&Sig.Nonce, sizeof(Sig.Nonce));

        for (i = 0; i < pMessage->cBuffers ; i++ )
        {
            if (((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_DATA) &&
                (pMessage->pBuffers[i].cbBuffer != 0))
            {
                // decrypt (before checksum...) if it's not READ_ONLY
                if ((Op==eUnseal)
                    && !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY)
                )
                {
                    SspEncryptBuffer(
                        pContext,
                        pRc4Sched,
                        pMessage->pBuffers[i].cbBuffer,
                        pMessage->pBuffers[i].pvBuffer
                        );
                }

                HMACMD5Update(
                            &HMACMD5Context,
                            (unsigned char*)pMessage->pBuffers[i].pvBuffer,
                            pMessage->pBuffers[i].cbBuffer);

                //
                // Encrypt if its not READ_ONLY
                //

                if ((Op==eSeal)
                    && !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY)
                )
                {
                    SspEncryptBuffer(
                        pContext,
                        pRc4Sched,
                        pMessage->pBuffers[i].cbBuffer,
                        pMessage->pBuffers[i].pvBuffer
                        );
                }
            }
        }

        HMACMD5Final(&HMACMD5Context, TempSig);

        //
        // use RandomPad and Checksum fields for 8 bytes of MD5 hash
        //

        RtlCopyMemory(&Sig.RandomPad, TempSig, 8);

        //
        // if we're using crypto for KEY_EXCH, may as well use it for signing too...
        //

        if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
            SspEncryptBuffer(
                pContext,
                pRc4Sched,
                8,
                &Sig.RandomPad
                );
    }
    //
    // pre-NTLM2 methods
    //
    else {

        //
        // required by CRC-32 algorithm
        //
        Sig.CheckSum = 0xffffffff;

        for (i = 0; i < pMessage->cBuffers ; i++ )
        {
            if (((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_DATA) &&
                !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY) &&
                (pMessage->pBuffers[i].cbBuffer != 0))
            {
                // decrypt (before checksum...)
                if (Op==eUnseal)
                {
                    SspEncryptBuffer(
                        pContext,
                        pRc4Sched,
                        pMessage->pBuffers[i].cbBuffer,
                        pMessage->pBuffers[i].pvBuffer
                        );
                }

                SspGenCheckSum(&pMessage->pBuffers[i], &Sig);


                // Encrypt
                if (Op==eSeal)
                {
                    SspEncryptBuffer(
                        pContext,
                        pRc4Sched,
                        pMessage->pBuffers[i].cbBuffer,
                        pMessage->pBuffers[i].pvBuffer
                        );
                }
            }
        }

        //
        // Required by CRC-32 algorithm
        //

        Sig.CheckSum ^= 0xffffffff;

        // when we encrypt 0, we will get the cipher stream for the nonce!
        Sig.Nonce = 0;

        SspEncryptBuffer(
            pContext,
            pRc4Sched,
            sizeof(NTLMSSP_MESSAGE_SIGNATURE) - sizeof(ULONG),
            &Sig.RandomPad
            );

        if ((pContext->NegotiateFlags & NTLMSSP_APP_SEQ) == 0)
        {
            Sig.Nonce ^= *pNonce;    // use our sequence number and encrypt it
            (*pNonce) += 1;
        }
        else if (Op == eSeal || Op == eSign || MessageSeqNo != 0)
            Sig.Nonce ^= MessageSeqNo;   // use caller's sequence number and encrypt it
        else
            Sig.Nonce = AlignedSig.Nonce;    // use sender's sequence number

        //
        // for SignMessage calling, does nothing (copies garbage)
        // For VerifyMessage calling, allows it to compare sig block
        // upon return to Verify without knowing whether its MD5 or CRC32
        //

        Sig.RandomPad = AlignedSig.RandomPad;
    }

    pMessage->pBuffers[Signature].cbBuffer = sizeof(NTLMSSP_MESSAGE_SIGNATURE);

    RtlCopyMemory(
        pSig,
        &Sig,
        NTLMSSP_MESSAGE_SIGNATURE_SIZE
        );

    return(SEC_E_OK);
}



//+-------------------------------------------------------------------------
//
//  Function:   SpMakeSignature
//
//  Synopsis:   Signs a message buffer by calculatinga checksum over all
//              the non-read only data buffers and encrypting the checksum
//              along with a nonce.
//
//  Effects:
//
//  Arguments:  ContextHandle - Handle of the context to use to sign the
//                      message.
//              QualityOfProtection - Unused flags.
//              MessageBuffers - Contains an array of buffers to sign and
//                      to store the signature.
//              MessageSequenceNumber - Sequence number for this message,
//                      only used in datagram cases.
//
//  Requires:   STATUS_INVALID_HANDLE - the context could not be found or
//                      was not configured for message integrity.
//              STATUS_INVALID_PARAMETER - the signature buffer could not
//                      be found.
//              STATUS_BUFFER_TOO_SMALL - the signature buffer is too small
//                      to hold the signature
//
//  Returns:
//
//  Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
//         routine SspHandleSignMessage. It's possible that
//         bugs got copied too
//
//
//--------------------------------------------------------------------------


NTSTATUS NTAPI
SpMakeSignature(
    IN ULONG_PTR ContextHandle,
    IN ULONG fQOP,
    IN PSecBufferDesc pMessage,
    IN ULONG MessageSeqNo
    )
{
    SspPrint(( SSP_API, "Entering SpMakeSignature\n" ));
    NTSTATUS Status = S_OK;
    NTSTATUS SubStatus = S_OK;

    PNTLM_CLIENT_CONTEXT pContext;
    NTLMSSP_MESSAGE_SIGNATURE  Sig;
    NTLMSSP_MESSAGE_SIGNATURE  *pSig;

    UNREFERENCED_PARAMETER(fQOP);

    pContext = ReferenceUserContext(ContextHandle, FALSE);

    if (pContext == NULL)
    {
        Status = STATUS_INVALID_HANDLE;
        SspPrint(( SSP_CRITICAL, "SpMakeSignature, ReferenceUserContext returns NULL\n" ));
        goto CleanUp;
    }


    Status = SspSignSealHelper(
                        pContext,
                        eSign,
                        pMessage,
                        MessageSeqNo,
                        &Sig,
                        &pSig
                        );

    if( !NT_SUCCESS(Status) ) {
        SspPrint(( SSP_CRITICAL, "SpMakeSignature, SspSignSealHelper returns %lx\n", Status ));
        goto CleanUp;
    }

    RtlCopyMemory(
        pSig,
        &Sig,
        NTLMSSP_MESSAGE_SIGNATURE_SIZE
        );


CleanUp:

    if (pContext != NULL)
    {
        SubStatus = DereferenceUserContext(pContext);

        // Don't destroy real status

        if (NT_SUCCESS(Status))
        {
            Status = SubStatus;
        }
    }

    SspPrint(( SSP_API, "Leaving SpMakeSignature: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}

//+-------------------------------------------------------------------------
//
//  Function:   SpVerifySignature
//
//  Synopsis:   Verifies a signed message buffer by calculating a checksum over all
//              the non-read only data buffers and encrypting the checksum
//              along with a nonce.
//
//  Effects:
//
//  Arguments:  ContextHandle - Handle of the context to use to sign the
//                      message.
//              MessageBuffers - Contains an array of signed buffers  and
//                      a signature buffer.
//              MessageSequenceNumber - Sequence number for this message,
//                      only used in datagram cases.
//              QualityOfProtection - Unused flags.
//
//  Requires:   STATUS_INVALID_HANDLE - the context could not be found or
//                      was not configured for message integrity.
//              STATUS_INVALID_PARAMETER - the signature buffer could not
//                      be found or was too small.
//
//  Returns:
//
//  Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
//         routine SspHandleVerifyMessage. It's possible that
//         bugs got copied too
//
//
//--------------------------------------------------------------------------



NTSTATUS NTAPI
SpVerifySignature(
    IN ULONG_PTR ContextHandle,
    IN PSecBufferDesc pMessage,
    IN ULONG MessageSeqNo,
    OUT PULONG pfQOP
    )
{
    SspPrint(( SSP_API, "Entering SpVerifySignature\n" ));
    NTSTATUS Status = S_OK;
    NTSTATUS SubStatus = S_OK;
    PNTLM_CLIENT_CONTEXT pContext;
    NTLMSSP_MESSAGE_SIGNATURE   Sig;
    PNTLMSSP_MESSAGE_SIGNATURE  pSig;       // pointer to buffer with sig in it
    NTLMSSP_MESSAGE_SIGNATURE   AlignedSig; // Aligned sig buffer.

    UNREFERENCED_PARAMETER(pfQOP);

    pContext = ReferenceUserContext(ContextHandle, FALSE);

    if (!pContext)
    {
        Status = STATUS_INVALID_HANDLE;
        SspPrint(( SSP_CRITICAL, "SpVerifySignature, ReferenceUserContext returns NULL\n" ));
        goto CleanUp;
    }

    Status = SspSignSealHelper(
                        pContext,
                        eVerify,
                        pMessage,
                        MessageSeqNo,
                        &Sig,
                        &pSig
                        );

    if (!NT_SUCCESS(Status))
    {
        SspPrint(( SSP_CRITICAL, "SpVerifySignature, SspSignSealHelper returns %lx\n", Status ));
        goto CleanUp;
    }


    RtlCopyMemory( &AlignedSig, pSig, sizeof( AlignedSig ) );

    if (AlignedSig.Version != NTLM_SIGN_VERSION) {
        SspPrint(( SSP_CRITICAL, "SpVerifySignature, unknown Version wanted=%lx got=%lx\n",
                    NTLM_SIGN_VERSION,
                    AlignedSig.Version
                    ));
        Status = SEC_E_INVALID_TOKEN;
        goto CleanUp;
    }

    // validate the signature...
    if (AlignedSig.CheckSum != Sig.CheckSum)
    {
        SspPrint(( SSP_CRITICAL, "SpVerifySignature, CheckSum mis-match wanted=%lx got=%lx\n",
                    Sig.CheckSum,
                    AlignedSig.CheckSum
                    ));
        Status = SEC_E_MESSAGE_ALTERED;
        goto CleanUp;
    }

    // with MD5 sig, this now matters!
    if (AlignedSig.RandomPad != Sig.RandomPad)
    {
        SspPrint(( SSP_CRITICAL, "SpVerifySignature, RandomPad mis-match wanted=%lx got=%lx\n",
                    Sig.RandomPad,
                    AlignedSig.RandomPad
                    ));
        Status = SEC_E_MESSAGE_ALTERED;
        goto CleanUp;
    }

    if (AlignedSig.Nonce != Sig.Nonce)
    {
        SspPrint(( SSP_CRITICAL, "SpVerifySignature, Nonce mis-match wanted=%lx got=%lx\n",
                    Sig.Nonce,
                    AlignedSig.Nonce
                    ));

        Status = SEC_E_OUT_OF_SEQUENCE;
        goto CleanUp;
    }


CleanUp:

    if (pContext != NULL)
    {
        SubStatus = DereferenceUserContext(pContext);

        // Don't destroy real status

        if (NT_SUCCESS(Status))
        {
            Status = SubStatus;
        }
    }

    SspPrint(( SSP_API, "Leaving SpVerifySignature: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));

}


//+-------------------------------------------------------------------------
//
//  Function:   SpSealMessage
//
//  Synopsis:   Verifies a signed message buffer by calculating a checksum over all
//              the non-read only data buffers and encrypting the checksum
//              along with a nonce.
//
//  Effects:
//
//  Arguments:  ContextHandle - Handle of the context to use to sign the
//                      message.
//              MessageBuffers - Contains an array of signed buffers  and
//                      a signature buffer.
//              MessageSequenceNumber - Sequence number for this message,
//                      only used in datagram cases.
//              QualityOfProtection - Unused flags.
//
//  Requires:   STATUS_INVALID_HANDLE - the context could not be found or
//                      was not configured for message integrity.
//              STATUS_INVALID_PARAMETER - the signature buffer could not
//                      be found or was too small.
//
//  Returns:
//
//  Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
//         routine SspHandleSealMessage. It's possible that
//         bugs got copied too
//
//
//--------------------------------------------------------------------------

NTSTATUS NTAPI
SpSealMessage(
    IN ULONG_PTR ContextHandle,
    IN ULONG fQOP,
    IN PSecBufferDesc pMessage,
    IN ULONG MessageSeqNo
    )
{
    SspPrint(( SSP_API, "Entering SpSealMessage\n" ));
    NTSTATUS Status = S_OK;
    NTSTATUS SubStatus = S_OK;
    PNTLM_CLIENT_CONTEXT pContext;
    NTLMSSP_MESSAGE_SIGNATURE  Sig;
    PNTLMSSP_MESSAGE_SIGNATURE  pSig;    // pointer to buffer where sig goes

    ULONG i;

    UNREFERENCED_PARAMETER(fQOP);

    pContext = ReferenceUserContext(ContextHandle, FALSE);

    if (!pContext)
    {
        Status = STATUS_INVALID_HANDLE;
        SspPrint(( SSP_CRITICAL, "SpSealMessage, ReferenceUserContext returns NULL\n" ));
        goto CleanUp;
    }


    Status = SspSignSealHelper(
                    pContext,
                    eSeal,
                    pMessage,
                    MessageSeqNo,
                    &Sig,
                    &pSig
                    );

    if (!NT_SUCCESS(Status))
    {
        SspPrint(( SSP_CRITICAL, "SpVerifySignature, SspSignSealHelper returns %lx\n", Status ));
        goto CleanUp;
    }

    RtlCopyMemory(
        pSig,
        &Sig,
        NTLMSSP_MESSAGE_SIGNATURE_SIZE
        );


    //
    // for gss style sign/seal, strip the padding as RC4 requires none.
    // (in fact, we rely on this to simplify the size computation in DecryptMessage).
    // if we support some other block cipher, need to rev the NTLM_ token version to make blocksize
    //

    for (i = 0; i < pMessage->cBuffers; i++)
    {
        if ((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_PADDING)
        {
            //
            // no padding required!
            //

            pMessage->pBuffers[i].cbBuffer = 0;
            break;
        }
    }


CleanUp:

    if (pContext != NULL)
    {
        SubStatus = DereferenceUserContext(pContext);

        // Don't destroy real status

        if (NT_SUCCESS(Status))
        {
            Status = SubStatus;
        }
    }

    SspPrint(( SSP_API, "Leaving SpSealMessage: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));

}

//+-------------------------------------------------------------------------
//
//  Function:   SpUnsealMessage
//
//  Synopsis:   Verifies a signed message buffer by calculating a checksum over all
//              the non-read only data buffers and encrypting the checksum
//              along with a nonce.
//
//  Effects:
//
//  Arguments:  ContextHandle - Handle of the context to use to sign the
//                      message.
//              MessageBuffers - Contains an array of signed buffers  and
//                      a signature buffer.
//              MessageSequenceNumber - Sequence number for this message,
//                      only used in datagram cases.
//              QualityOfProtection - Unused flags.
//
//  Requires:   STATUS_INVALID_HANDLE - the context could not be found or
//                      was not configured for message integrity.
//              STATUS_INVALID_PARAMETER - the signature buffer could not
//                      be found or was too small.
//
//  Returns:
//
//  Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
//         routine SspHandleUnsealMessage. It's possible that
//         bugs got copied too
//
//
//--------------------------------------------------------------------------


NTSTATUS NTAPI
SpUnsealMessage(
    IN ULONG_PTR ContextHandle,
    IN PSecBufferDesc pMessage,
    IN ULONG MessageSeqNo,
    OUT PULONG pfQOP
    )
{
    SspPrint(( SSP_API, "Entering SpUnsealMessage\n" ));
    NTSTATUS Status = S_OK;
    NTSTATUS SubStatus = S_OK;
    PNTLM_CLIENT_CONTEXT pContext;
    NTLMSSP_MESSAGE_SIGNATURE  Sig;
    PNTLMSSP_MESSAGE_SIGNATURE  pSig;       // pointer to buffer where sig goes
    NTLMSSP_MESSAGE_SIGNATURE   AlignedSig; // aligned buffer.

    PSecBufferDesc MessageBuffers = pMessage;
    ULONG Index;
    PSecBuffer SignatureBuffer = NULL;
    PSecBuffer StreamBuffer = NULL;
    PSecBuffer DataBuffer = NULL;
    SecBufferDesc ProcessBuffers;
    SecBuffer wrap_bufs[2];

    UNREFERENCED_PARAMETER(pfQOP);

    pContext = ReferenceUserContext(ContextHandle, FALSE);

    if (!pContext)
    {
        Status = STATUS_INVALID_HANDLE;
        SspPrint(( SSP_CRITICAL, "SpUnsealMessage, ReferenceUserContext returns NULL\n" ));
        goto CleanUp;
    }


    //
    // Find the body and signature SecBuffers from pMessage
    //

    for (Index = 0; Index < MessageBuffers->cBuffers ; Index++ )
    {
        if ((MessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_TOKEN)
        {
            SignatureBuffer = &MessageBuffers->pBuffers[Index];
        }
        else if ((MessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_STREAM)
        {
            StreamBuffer = &MessageBuffers->pBuffers[Index];
        }
        else if ((MessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_DATA)
        {
            DataBuffer = &MessageBuffers->pBuffers[Index];
        }
    }
    
    if( StreamBuffer != NULL )
    {

        if( SignatureBuffer != NULL )
        {
            Status = SEC_E_INVALID_TOKEN;
            SspPrint(( SSP_CRITICAL, "SpUnsealMessage, Both stream and signature buffer present.\n"));
            goto CleanUp;
        }

        //
        // for version 1 NTLM blobs, padding is never present, since RC4 is stream cipher.
        //

        wrap_bufs[0].cbBuffer = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
        wrap_bufs[1].cbBuffer = StreamBuffer->cbBuffer - NTLMSSP_MESSAGE_SIGNATURE_SIZE;

        if( StreamBuffer->cbBuffer < wrap_bufs[0].cbBuffer )
        {
            Status = SEC_E_INVALID_TOKEN;
            SspPrint(( SSP_CRITICAL, "SpUnsealMessage, invalid buffer present in STREAM.\n"));
            goto CleanUp;
        }


        wrap_bufs[0].BufferType = SECBUFFER_TOKEN;
        wrap_bufs[0].pvBuffer = StreamBuffer->pvBuffer;

        wrap_bufs[1].BufferType = SECBUFFER_DATA;
        wrap_bufs[1].pvBuffer = (PBYTE)wrap_bufs[0].pvBuffer + wrap_bufs[0].cbBuffer;

        if( DataBuffer == NULL )
        {
            Status = SEC_E_INVALID_TOKEN;
            SspPrint(( SSP_CRITICAL, "SpUnsealMessage, gss missing SECBUFFER_DATA.\n"));
            goto CleanUp;
        }

        DataBuffer->cbBuffer = wrap_bufs[1].cbBuffer;
        DataBuffer->pvBuffer = wrap_bufs[1].pvBuffer;

        ProcessBuffers.cBuffers = 2;
        ProcessBuffers.pBuffers = wrap_bufs;
        ProcessBuffers.ulVersion = SECBUFFER_VERSION;

    } else {
        ProcessBuffers = *MessageBuffers;
    }
    
    Status = SspSignSealHelper(
                    pContext,
                    eUnseal,
                    &ProcessBuffers,
                    MessageSeqNo,
                    &Sig,
                    &pSig
                    );

    if (!NT_SUCCESS(Status))
    {
        SspPrint(( SSP_CRITICAL, "SpUnsealMessage, SspSignSealHelper returns %lx\n", Status ));
        goto CleanUp;
    }

    RtlCopyMemory( &AlignedSig, pSig, sizeof(AlignedSig) );

    if (AlignedSig.Version != NTLM_SIGN_VERSION) {
        SspPrint(( SSP_CRITICAL, "SpUnsealMessage, unknown Version wanted=%lx got=%lx\n",
                    NTLM_SIGN_VERSION,
                    AlignedSig.Version
                    ));
        Status = SEC_E_INVALID_TOKEN;
        goto CleanUp;
    }

    // validate the signature...
    if (AlignedSig.CheckSum != Sig.CheckSum)
    {
        SspPrint(( SSP_CRITICAL, "SpUnsealMessage, CheckSum mis-match wanted=%lx got=%lx\n",
                    Sig.CheckSum,
                    AlignedSig.CheckSum
                    ));
        Status = SEC_E_MESSAGE_ALTERED;
        goto CleanUp;
    }

    if (AlignedSig.RandomPad != Sig.RandomPad)
    {
        SspPrint(( SSP_CRITICAL, "SpUnsealMessage, RandomPad mis-match wanted=%lx got=%lx\n",
                    Sig.RandomPad,
                    AlignedSig.RandomPad
                    ));
        Status = SEC_E_MESSAGE_ALTERED;
        goto CleanUp;
    }

    if (AlignedSig.Nonce != Sig.Nonce)
    {
        SspPrint(( SSP_CRITICAL, "SpUnsealMessage, Nonce mis-match wanted=%lx got=%lx\n",
                    Sig.Nonce,
                    AlignedSig.Nonce
                    ));
        Status = SEC_E_OUT_OF_SEQUENCE;
        goto CleanUp;
    }

CleanUp:

    if (pContext != NULL)
    {
        SubStatus = DereferenceUserContext(pContext);

        // Don't destroy real status

        if (NT_SUCCESS(Status))
        {
            Status = SubStatus;
        }
    }

    SspPrint(( SSP_API, "Leaving SpUnsealMessage: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}



//+-------------------------------------------------------------------------
//
//  Function:   SpGetContextToken
//
//  Synopsis:   returns a pointer to the token for a server-side context
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------


NTSTATUS NTAPI
SpGetContextToken(
    IN ULONG_PTR ContextHandle,
    OUT PHANDLE ImpersonationToken
    )
{
    SspPrint(( SSP_API, "Entering SpGetContextToken\n" ));
    NTSTATUS Status = S_OK;
    PNTLM_CLIENT_CONTEXT pContext;

    pContext = ReferenceUserContext(ContextHandle, FALSE);

    if (pContext && pContext->ClientTokenHandle)
    {
        *ImpersonationToken = pContext->ClientTokenHandle;
        Status= S_OK;
        goto CleanUp;
    }

    Status = STATUS_INVALID_HANDLE;
    SspPrint(( SSP_CRITICAL, "SpGetContextToken, no token handle\n" ));

CleanUp:

    if (pContext != NULL)
    {
        Status = DereferenceUserContext(pContext);
    }

    SspPrint(( SSP_API, "Leaving SpGetContextToken: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}



//+-------------------------------------------------------------------------
//
//  Function:   SpQueryContextAttributes
//
//  Synopsis:   Querys attributes of the specified context
//              This API allows a customer of the security
//              services to determine certain attributes of
//              the context.  These are: sizes, names, and lifespan.
//
//  Effects:
//
//  Arguments:
//
//    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.
//
//
//  Requires:
//
//  Returns:
//
//        STATUS_SUCCESS - Call completed successfully
//
//        STATUS_INVALID_HANDLE -- Credential/Context Handle is invalid
//        STATUS_NOT_SUPPORTED -- Function code is not supported
//
//  Notes:
//
//--------------------------------------------------------------------------


NTSTATUS NTAPI
SpQueryContextAttributes(
    IN ULONG_PTR ContextHandle,
    IN ULONG Attribute,
    IN OUT PVOID Buffer
    )
{
    NTSTATUS Status = STATUS_SUCCESS;
    PNTLM_CLIENT_CONTEXT pContext = NULL;
    PSecPkgContext_Sizes ContextSizes;
    PSecPkgContext_Flags ContextFlags;
    PSecPkgContext_DceInfo ContextDceInfo = NULL;
    PSecPkgContext_Names ContextNames = NULL;
    PSecPkgContext_PackageInfo PackageInfo;
    PSecPkgContext_NegotiationInfo NegInfo ;
    PSecPkgContext_PasswordExpiry PasswordExpires;
    PSecPkgContext_UserFlags UserFlags;
    PSecPkgContext_SessionKey  SessionKeyInfo;
	PSecPkgContext_AccessToken AccessToken;
    ULONG PackageInfoSize = 0;

    SspPrint(( SSP_API, "Entering SpQueryContextAttributes\n" ));

    pContext = ReferenceUserContext(ContextHandle, FALSE);

    if (pContext == NULL) {
        Status = STATUS_INVALID_HANDLE;
        SspPrint(( SSP_API_MORE, "SpQueryContextAttributes, ReferenceUserContext returns NULL (possible incomplete context)\n" ));
        goto Cleanup;
    }

    //
    // Handle each of the various queried attributes
    //

    SspPrint(( SSP_API_MORE, "SpQueryContextAttributes : 0x%lx\n", Attribute ));
    switch ( Attribute) {
    case SECPKG_ATTR_SIZES:

        ContextSizes = (PSecPkgContext_Sizes) Buffer;
        ContextSizes->cbMaxToken = NTLMSP_MAX_TOKEN_SIZE;

        if (pContext->NegotiateFlags & (NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
                                       NTLMSSP_NEGOTIATE_SIGN |
                                       NTLMSSP_NEGOTIATE_SEAL) ) {
            ContextSizes->cbMaxSignature = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
        } else {
            ContextSizes->cbMaxSignature = 0;
        }

        if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) {
            ContextSizes->cbBlockSize = 1;
            ContextSizes->cbSecurityTrailer = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
        }
        else
        {
            ContextSizes->cbBlockSize = 0;
            if((pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) ||
               (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN))
            {
                ContextSizes->cbSecurityTrailer = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
            } else {
                ContextSizes->cbSecurityTrailer = 0;
            }
        }

        break;

    case SECPKG_ATTR_DCE_INFO:

        ContextDceInfo = (PSecPkgContext_DceInfo) Buffer;

        if (pContext->ContextNames)
        {
            UINT Length = wcslen(pContext->ContextNames);
            ContextDceInfo->pPac = (LPWSTR)UserFunctions->AllocateHeap((Length + 1) * sizeof(WCHAR));
            if (ContextDceInfo->pPac == NULL) {
                Status = STATUS_NO_MEMORY;
                SspPrint(( SSP_CRITICAL, "SpQueryContextAttributes, NtLmAllocate returns NULL\n" ));
                goto Cleanup;
            }

            RtlCopyMemory(
                (LPWSTR) ContextDceInfo->pPac,
                pContext->ContextNames,
                Length * sizeof(WCHAR)
                );
            *((LPWSTR)(ContextDceInfo->pPac) + Length) = L'\0';
        }
        else
        {
            SspPrint(( SSP_API_MORE, "SpQueryContextAttributes no ContextNames\n" ));
            ContextDceInfo->pPac = (LPWSTR) UserFunctions->AllocateHeap(sizeof(WCHAR));
            *((LPWSTR)(ContextDceInfo->pPac)) = L'\0';
        }

        ContextDceInfo->AuthzSvc = 0;

        break;

    case SECPKG_ATTR_SESSION_KEY:
    {
        if (NtLmState != NtLmLsaMode)
        {
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        SessionKeyInfo = (PSecPkgContext_SessionKey) Buffer;
        SessionKeyInfo->SessionKeyLength = sizeof(pContext->SessionKey);

        SessionKeyInfo->SessionKey = (PUCHAR) UserFunctions->AllocateHeap(
                                                    SessionKeyInfo->SessionKeyLength
                                                    );
        if (SessionKeyInfo->SessionKey != NULL)
        {
            RtlCopyMemory(
                SessionKeyInfo->SessionKey,
                pContext->SessionKey,
                SessionKeyInfo->SessionKeyLength
                );
        }
        else
        {
            Status = STATUS_INSUFFICIENT_RESOURCES;
        }

        break;
    }

    case SECPKG_ATTR_NAMES:

        ContextNames = (PSecPkgContext_Names) Buffer;

        if (pContext->ContextNames)
        {
            UINT Length = wcslen(pContext->ContextNames);
            ContextNames->sUserName = (LPWSTR) UserFunctions->AllocateHeap((Length+1) * sizeof(WCHAR));
            if (ContextNames->sUserName == NULL) {
                Status = STATUS_NO_MEMORY;
                SspPrint(( SSP_CRITICAL, "SpQueryContextAttributes, NtLmAllocate returns NULL\n" ));
                goto Cleanup;
            }
            RtlCopyMemory(
                ContextNames->sUserName,
                pContext->ContextNames,
                Length * sizeof(WCHAR)
                );
            *(ContextNames->sUserName + Length) = L'\0';
        }
        else
        {
            SspPrint(( SSP_API_MORE, "SpQueryContextAttributes no ContextNames\n" ));
            ContextNames->sUserName = (LPWSTR) UserFunctions->AllocateHeap(sizeof(WCHAR));
            *(ContextNames->sUserName) = L'\0';
        }

        break;
    case SECPKG_ATTR_PACKAGE_INFO:
    case SECPKG_ATTR_NEGOTIATION_INFO:
        //
        // Return the information about this package. This is useful for
        // callers who used SPNEGO and don't know what package they got.
        //

        PackageInfo = (PSecPkgContext_PackageInfo) Buffer;
        PackageInfoSize = sizeof(SecPkgInfoW) + sizeof(NTLMSP_NAME) + sizeof(NTLMSP_COMMENT);
        PackageInfo->PackageInfo = (PSecPkgInfoW) UserFunctions->AllocateHeap(PackageInfoSize);
        if (PackageInfo->PackageInfo == NULL)
        {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto Cleanup;
        }
        PackageInfo->PackageInfo->Name = (LPWSTR) (PackageInfo->PackageInfo + 1);
        PackageInfo->PackageInfo->Comment = (LPWSTR) ((((PBYTE) PackageInfo->PackageInfo->Name)) + sizeof(NTLMSP_NAME));
        wcscpy(
            PackageInfo->PackageInfo->Name,
            NTLMSP_NAME
            );

        wcscpy(
            PackageInfo->PackageInfo->Comment,
            NTLMSP_COMMENT
            );
        PackageInfo->PackageInfo->wVersion      = SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION;
        PackageInfo->PackageInfo->wRPCID        = RPC_C_AUTHN_WINNT;
        PackageInfo->PackageInfo->fCapabilities = NTLMSP_CAPS;
        PackageInfo->PackageInfo->cbMaxToken    = NTLMSP_MAX_TOKEN_SIZE;

        if ( Attribute == SECPKG_ATTR_NEGOTIATION_INFO )
        {
            NegInfo = (PSecPkgContext_NegotiationInfo) PackageInfo ;
            NegInfo->NegotiationState = SECPKG_NEGOTIATION_COMPLETE ;
        }
        break;

    case SECPKG_ATTR_PASSWORD_EXPIRY:
        PasswordExpires = (PSecPkgContext_PasswordExpiry) Buffer;
        if(pContext->PasswordExpiry.QuadPart != 0) {
            PasswordExpires->tsPasswordExpires = pContext->PasswordExpiry;
        } else {
            Status = SEC_E_UNSUPPORTED_FUNCTION;
        }
        break;

    case SECPKG_ATTR_USER_FLAGS:
        UserFlags = (PSecPkgContext_UserFlags) Buffer;
        UserFlags->UserFlags = pContext->UserFlags;
        break;

    case SECPKG_ATTR_FLAGS:
    {
        BOOLEAN Client = (pContext->ClientTokenHandle == 0);
        ULONG Flags = 0;

        //
        // note: doesn't return all flags; by design.
        //
        ContextFlags = (PSecPkgContext_Flags) Buffer;
        ContextFlags->Flags = 0;

        if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) {
            if( Client )
            {
                Flags |= ISC_RET_CONFIDENTIALITY;
            } else {
                Flags |= ASC_RET_CONFIDENTIALITY;
            }
        }

        if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) {
            if( Client )
            {
                Flags |= ISC_RET_SEQUENCE_DETECT | ISC_RET_REPLAY_DETECT | ISC_RET_INTEGRITY;
            } else {
                Flags |= ASC_RET_SEQUENCE_DETECT | ASC_RET_REPLAY_DETECT | ASC_RET_INTEGRITY;
            }
        }

        if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_NULL_SESSION) {
            if( Client )
            {
                Flags |= ISC_RET_NULL_SESSION;
            } else {
                Flags |= ASC_RET_NULL_SESSION;
            }
        }

        if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) {
            if( Client )
            {
                Flags |= ISC_RET_DATAGRAM;
            } else {
                Flags |= ASC_RET_DATAGRAM;
            }
        }

        if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) {
            if( Client )
            {
                Flags |= ISC_RET_IDENTIFY;
            } else {
                Flags |= ASC_RET_IDENTIFY;
            }
        }

        ContextFlags->Flags |= Flags;

        break;
    }
	case SECPKG_ATTR_ACCESS_TOKEN:    
        AccessToken = (PSecPkgContext_AccessToken) Buffer;
        //
        // ClientTokenHandle can be NULL, for instance:
        // 1. client side context.
        // 2. incomplete server context.
        //
		if(pContext->ClientTokenHandle)
			AccessToken->AccessToken = pContext->ClientTokenHandle;
		else
			Status = SEC_E_NO_IMPERSONATION;        
        break;
    
    case SECPKG_ATTR_LIFESPAN:
    default:
        Status = STATUS_NOT_SUPPORTED;
        break;
    }


Cleanup:

    if (!NT_SUCCESS(Status))
    {
        switch (Attribute) {

        case SECPKG_ATTR_NAMES:

            if (ContextNames != NULL && ContextNames->sUserName )
            {
                NtLmFree(ContextNames->sUserName);
            }
            break;

        case SECPKG_ATTR_DCE_INFO:

            if (ContextDceInfo != NULL && ContextDceInfo->pPac)
            {
                NtLmFree(ContextDceInfo->pPac);
            }
            break;
        }
    }

    if (pContext != NULL)
    {
        (VOID) DereferenceUserContext(pContext);
    }

    SspPrint(( SSP_API, "Leaving SpQueryContextAttributes: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}



//+-------------------------------------------------------------------------
//
//  Function:   SpCompleteAuthToken
//
//  Synopsis:   Completes a context
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------


NTSTATUS NTAPI
SpCompleteAuthToken(
    IN ULONG_PTR ContextHandle,
    IN PSecBufferDesc InputBuffer
    )
{
    UNREFERENCED_PARAMETER (ContextHandle);
    UNREFERENCED_PARAMETER (InputBuffer);
    SspPrint(( SSP_API, "Entering SpCompleteAuthToken\n" ));
    SspPrint(( SSP_API, "Leaving SpCompleteAuthToken\n" ));
    return(SEC_E_UNSUPPORTED_FUNCTION);
}


NTSTATUS NTAPI
SpFormatCredentials(
    IN PSecBuffer Credentials,
    OUT PSecBuffer FormattedCredentials
    )
{
    UNREFERENCED_PARAMETER (Credentials);
    UNREFERENCED_PARAMETER (FormattedCredentials);
    SspPrint(( SSP_API, "Entering SpFormatCredentials\n" ));
    SspPrint(( SSP_API, "Leaving SpFormatCredentials\n" ));
    return(SEC_E_UNSUPPORTED_FUNCTION);
}

NTSTATUS NTAPI
SpMarshallSupplementalCreds(
    IN ULONG CredentialSize,
    IN PUCHAR Credentials,
    OUT PULONG MarshalledCredSize,
    OUT PVOID * MarshalledCreds
    )
{
    UNREFERENCED_PARAMETER (CredentialSize);
    UNREFERENCED_PARAMETER (Credentials);
    UNREFERENCED_PARAMETER (MarshalledCredSize);
    UNREFERENCED_PARAMETER (MarshalledCreds);
    SspPrint(( SSP_API, "Entering SpMarshallSupplementalCreds\n" ));
    SspPrint(( SSP_API, "Leaving SpMarshallSupplementalCreds\n" ));
    return(SEC_E_UNSUPPORTED_FUNCTION);
}

//+-------------------------------------------------------------------------
//
//  Function:   NtLmMakePackedContext
//
//  Synopsis:   Maps a context to the caller's address space
//
//  Effects:
//
//  Arguments:  Context - The context to map
//              MappedContext - Set to TRUE on success
//              ContextData - Receives a buffer in the caller's address space
//                      with the mapped context.
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------


NTSTATUS
NtLmMakePackedContext(
    IN PNTLM_CLIENT_CONTEXT Context,
    OUT PBOOLEAN MappedContext,
    OUT PSecBuffer ContextData,
    IN ULONG Flags
    )
{
    NTSTATUS Status = STATUS_SUCCESS;
    PNTLM_PACKED_CONTEXT PackedContext = NULL;
    ULONG ContextSize, ContextNameSize = 0;


    if (Context->ContextNames)
    {
        ContextNameSize = wcslen(Context->ContextNames);
    }

    ContextSize =  sizeof(NTLM_CLIENT_CONTEXT) +
                   ContextNameSize * sizeof(WCHAR) + sizeof( WCHAR );

    PackedContext = (PNTLM_PACKED_CONTEXT) NtLmAllocateLsaHeap( ContextSize );

    if (PackedContext == NULL)
    {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto Cleanup;
    }

    // Copy all fields of the old context

    PackedContext->Tag = NTLM_PACKED_CONTEXT_MAP ;
    PackedContext->NegotiateFlags = Context->NegotiateFlags ;
    PackedContext->SendNonce = Context->SendNonce ;
    PackedContext->RecvNonce = Context->RecvNonce ;
    RtlCopyMemory(
        PackedContext->SessionKey,
        Context->SessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    PackedContext->ContextSignature = Context->ContextSignature ;
    PackedContext->PasswordExpiry = Context->PasswordExpiry ;
    PackedContext->UserFlags = Context->UserFlags ;
    if ( ContextNameSize )
    {
        PackedContext->ContextNames = sizeof( NTLM_PACKED_CONTEXT );
        PackedContext->ContextNameLength = (ContextNameSize + 1) * sizeof( WCHAR ) ;

        RtlCopyMemory(
            (PackedContext + 1),
            Context->ContextNames,
            PackedContext->ContextNameLength );

    }
    else 
    {
        PackedContext->ContextNames = 0 ;
    }

    RtlCopyMemory(
        PackedContext->SignSessionKey,
        Context->SignSessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    RtlCopyMemory(
        PackedContext->VerifySessionKey,
        Context->VerifySessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    RtlCopyMemory(
        PackedContext->SealSessionKey,
        Context->SealSessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    RtlCopyMemory(
        PackedContext->UnsealSessionKey,
        Context->SealSessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    RtlCopyMemory(
        &PackedContext->SealRc4Sched,
        &Context->SealRc4Sched,
        sizeof( struct RC4_KEYSTRUCT ) );

    RtlCopyMemory(
        &PackedContext->UnsealRc4Sched,
        &Context->UnsealRc4Sched,
        sizeof( struct RC4_KEYSTRUCT ) );


    // Replace some fields


    //
    // Token will be returned by the caller of this routine
    //

    PackedContext->ClientTokenHandle = 0 ;

    // Save the fact that it's exported
    PackedContext->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXPORTED_CONTEXT;

    ContextData->pvBuffer = PackedContext;
    ContextData->cbBuffer = ContextSize;


    *MappedContext = TRUE;


    Status = STATUS_SUCCESS;

Cleanup:


    if (!NT_SUCCESS(Status))
    {
        if (PackedContext != NULL)
        {
            NtLmFreeLsaHeap(PackedContext);
        }
    }

    return(Status);

}

//+-------------------------------------------------------------------------
//
//  Function:   SpExportSecurityContext
//
//  Synopsis:   Exports a security context to another process
//
//  Effects:    Allocates memory for output
//
//  Arguments:  ContextHandle - handle to context to export
//              Flags - Flags concerning duplication. Allowable flags:
//                      SECPKG_CONTEXT_EXPORT_DELETE_OLD - causes old context
//                              to be deleted.
//              PackedContext - Receives serialized context to be freed with
//                      FreeContextBuffer
//              TokenHandle - Optionally receives handle to context's token.
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------

NTSTATUS
SpExportSecurityContext(
    IN ULONG_PTR ContextHandle,
    IN ULONG Flags,
    OUT PSecBuffer PackedContext,
    OUT PHANDLE TokenHandle
    )
{
    PNTLM_CLIENT_CONTEXT Context = NULL ;
    PNTLM_PACKED_CONTEXT pvContext = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
    NTSTATUS SubStatus = STATUS_SUCCESS;
    ULONG ContextSize = 0;
    BOOLEAN MappedContext = FALSE;


    SspPrint(( SSP_API,"Entering SpExportSecurityContext for context 0x%x\n", ContextHandle ));

    if (ARGUMENT_PRESENT(TokenHandle))
    {
        *TokenHandle = NULL;
    }

    PackedContext->pvBuffer = NULL;
    PackedContext->cbBuffer = 0;
    PackedContext->BufferType = 0;

    Context = ReferenceUserContext(
                ContextHandle,
                FALSE           // don't unlink
                );

    if (Context == NULL)
    {
        SspPrint((SSP_CRITICAL, "SpExportSecurityContext: Invalid handle supplied (0x%x)\n",
            ContextHandle));
        Status = STATUS_INVALID_HANDLE;
        goto Cleanup;
    }

    Status = NtLmMakePackedContext(
                Context,
                &MappedContext,
                PackedContext,
                Flags
                );

    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }
    ASSERT(MappedContext);


    //
    // Now either duplicate the token or copy it.
    //

    if (ARGUMENT_PRESENT(TokenHandle))
    {
        ULONG LockIndex;

        LockIndex = ListIndexToLockIndex( HandleToListIndex( ContextHandle ) );

        RtlAcquireResourceShared( &NtLmUserContextLock[LockIndex], TRUE );

        if ((Flags & SECPKG_CONTEXT_EXPORT_DELETE_OLD) != 0)
        {
            RtlConvertSharedToExclusive( &NtLmUserContextLock[LockIndex] );

            *TokenHandle = Context->ClientTokenHandle;
            Context->ClientTokenHandle = NULL;
        }
        else
        {
            Status = NtDuplicateObject(
                        NtCurrentProcess(),
                        Context->ClientTokenHandle,
                        NULL,
                        TokenHandle,
                        0,              // no new access
                        0,              // no handle attributes
                        DUPLICATE_SAME_ACCESS
                        );
        }

        RtlReleaseResource( &NtLmUserContextLock[LockIndex] );

        if (!NT_SUCCESS(Status))
        {
            goto Cleanup;
        }

    }

    pvContext = (PNTLM_PACKED_CONTEXT) PackedContext->pvBuffer;

    // Semantics of this flag: Export from here, but reset the Nonce.
    // We zero out the session key, since all we need is the rc4 key.

    if ((Flags & SECPKG_CONTEXT_EXPORT_RESET_NEW) != 0)
    {
        pvContext->SendNonce = (ULONG) -1;
        pvContext->RecvNonce = (ULONG) -1;
    }

    RtlZeroMemory(
        &pvContext->SessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH
        );

Cleanup:

    if (Context != NULL)
    {
        SubStatus = DereferenceUserContext(Context);

        // Don't destroy real status

        if (NT_SUCCESS(Status))
        {
            Status = SubStatus;
        }
    }

    SspPrint(( SSP_API,"Leaving SpExportContext: 0x%lx\n", Status ));
    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}


//+-------------------------------------------------------------------------
//
//  Function:   NtLmCreateUserModeContext
//
//  Synopsis:   Creates a user-mode context to support impersonation and
//              message integrity and privacy
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------

NTSTATUS
NtLmCreateUserModeContext(
    IN ULONG_PTR ContextHandle,
    IN HANDLE Token,
    IN PSecBuffer MarshalledContext,
    OUT PNTLM_CLIENT_CONTEXT * NewContext
    )
{
    NTSTATUS Status = STATUS_SUCCESS;
    PNTLM_CLIENT_CONTEXT Context = NULL;
    PNTLM_PACKED_CONTEXT PackedContext;
    UINT Length = 0;
    ULONG ListIndex;
    ULONG LockIndex;

    if (MarshalledContext->cbBuffer < sizeof(NTLM_PACKED_CONTEXT))
    {
        SspPrint((SSP_CRITICAL,"NtLmCreateUserModeContext: Invalid buffer size for marshalled context: was 0x%x, needed 0x%x\n",
            MarshalledContext->cbBuffer, sizeof(NTLM_PACKED_CONTEXT)));
        return(STATUS_INVALID_PARAMETER);
    }

    PackedContext = (PNTLM_PACKED_CONTEXT) MarshalledContext->pvBuffer;

    Context = (PNTLM_CLIENT_CONTEXT)NtLmAllocate ( sizeof(NTLM_CLIENT_CONTEXT));

    if (Context == NULL)
    {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto Cleanup;
    }

    Context->NegotiateFlags = PackedContext->NegotiateFlags ;
    Context->SendNonce = PackedContext->SendNonce ;
    Context->RecvNonce = PackedContext->RecvNonce ;
    RtlCopyMemory(
        Context->SessionKey,
        PackedContext->SessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    Context->ContextSignature = PackedContext->ContextSignature ;
    Context->PasswordExpiry = PackedContext->PasswordExpiry ;
    Context->UserFlags = PackedContext->UserFlags ;

    if ( PackedContext->ContextNames )
    {
        Context->ContextNames = (PWSTR) NtLmAllocate( PackedContext->ContextNameLength );
        if ( Context->ContextNames == NULL )
        {
            Status = STATUS_INSUFFICIENT_RESOURCES ;
            goto Cleanup ;
        }

        RtlCopyMemory(
            Context->ContextNames,
            ((PUCHAR) PackedContext) + PackedContext->ContextNames,
            PackedContext->ContextNameLength );

    }
    else 
    {
        Context->ContextNames = NULL ;
    }

    RtlCopyMemory(
        Context->SignSessionKey,
        PackedContext->SignSessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    RtlCopyMemory(
        Context->VerifySessionKey,
        PackedContext->VerifySessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    RtlCopyMemory(
        Context->SealSessionKey,
        PackedContext->SealSessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );
    RtlCopyMemory(
        Context->UnsealSessionKey,
        PackedContext->UnsealSessionKey,
        MSV1_0_USER_SESSION_KEY_LENGTH );

    RtlCopyMemory(
        &Context->SealRc4Sched,
        &PackedContext->SealRc4Sched,
        sizeof( struct RC4_KEYSTRUCT ) );

    RtlCopyMemory(
        &Context->UnsealRc4Sched,
        &PackedContext->UnsealRc4Sched,
        sizeof( struct RC4_KEYSTRUCT ) );

    
    // These need to be changed

    Context->ClientTokenHandle = Token;

    if (Context->SendNonce == (ULONG) -1)
    {
        Context->SendNonce = 0;
    }

    if (Context->RecvNonce == (ULONG) -1)
    {
        Context->RecvNonce = 0;
    }

    if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2 )
    {
        Context->pSealRc4Sched = &Context->SealRc4Sched;
        Context->pUnsealRc4Sched = &Context->UnsealRc4Sched;
        Context->pSendNonce = &Context->SendNonce;
        Context->pRecvNonce = &Context->RecvNonce;
    } else {
        Context->pSealRc4Sched = &Context->SealRc4Sched;
        Context->pUnsealRc4Sched = &Context->SealRc4Sched;
        Context->pSendNonce = &Context->SendNonce;
        Context->pRecvNonce = &Context->SendNonce;
    }




    Context->References = 2;

    //
    // Modify the DACL on the token to grant access to the caller
    //

    if (Context->ClientTokenHandle != NULL)
    {
        Status = SspCreateTokenDacl(
                    Context->ClientTokenHandle
                    );
        if (!NT_SUCCESS(Status))
        {
            goto Cleanup;
        }
    }

    // Dummy up an lsa handle by incrementing a global variable. This
    // will ensure that each imported context has a unique handle.
    // Skip over values that could be interpreted as an aligned pointer,
    // so that they won't get mixed up with real lsa handles.
    Context->LsaContext = InterlockedIncrement((PLONG)&ExportedContext);
    while(Context->LsaContext % MAX_NATURAL_ALIGNMENT == 0)
    {
        Context->LsaContext = InterlockedIncrement((PLONG)&ExportedContext);
    }


    ListIndex = HandleToListIndex( Context->LsaContext );
    LockIndex = ListIndexToLockIndex( ListIndex );
    
    RtlAcquireResourceExclusive(&NtLmUserContextLock[LockIndex], TRUE);

    InsertHeadList ( &NtLmUserContextList[ListIndex], &Context->Next );
    NtLmUserContextCount[ListIndex]++;

    RtlReleaseResource(&NtLmUserContextLock[LockIndex]);

    *NewContext = Context;

Cleanup:
    if (!NT_SUCCESS(Status))
    {
        if (Context != NULL)
        {
            FreeUserContext(Context);
        }
    }
    return(Status);
}

//+-------------------------------------------------------------------------
//
//  Function:   SpImportSecurityContext
//
//  Synopsis:
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------


NTSTATUS
SpImportSecurityContext(
    IN PSecBuffer PackedContext,
    IN HANDLE Token,
    OUT PULONG_PTR ContextHandle
    )
{
    NTSTATUS Status = STATUS_SUCCESS;
    NTSTATUS SubStatus = STATUS_SUCCESS;
    PNTLM_CLIENT_CONTEXT Context = NULL;

    SspPrint(( SSP_API,"Entering SpImportSecurityContext\n"));

    Status = NtLmCreateUserModeContext(
                0,              // no lsa context
                Token,
                PackedContext,
                &Context
                );
    if (!NT_SUCCESS(Status))
    {
        SspPrint((SSP_CRITICAL,"SpImportSecurityContext: Failed to create user mode context: 0x%x\n",
            Status));
        goto Cleanup;
    }

    *ContextHandle = Context->LsaContext;

Cleanup:

    if (Context != NULL)
    {
        SubStatus = DereferenceUserContext(Context);

        // Don't destroy real status

        if (NT_SUCCESS(Status))
        {
            Status = SubStatus;
        }
    }

    SspPrint(( SSP_API,"Leaving SpImportSecurityContext: 0x%lx\n", Status));

    return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
}



NTSTATUS
SspGetTokenUser(
    HANDLE Token,
    PTOKEN_USER pTokenUser,
    PULONG TokenUserSize
    )
/*++

RoutineDescription:

    Gets the TOKEN_USER from an open token

Arguments:

    Token - Handle to a token open for TOKEN_QUERY access

Return Value:

    STATUS_INSUFFICIENT_RESOURCES - not enough memory to complete the
        function.

    Errors from NtQueryInformationToken.

--*/

{
    NTSTATUS Status;

    Status = NtQueryInformationToken(
                Token,
                TokenUser,
                pTokenUser,
                *TokenUserSize,
                TokenUserSize
                );

    return(Status);
}


NTSTATUS
SspCreateTokenDacl(
    HANDLE Token
    )
/*++

RoutineDescription:

    Creates a new DACL for the token granting the server and client
    all access to the token.

Arguments:

    Token - Handle to an impersonation token open for TOKEN_QUERY and
        WRITE_DAC

Return Value:

    STATUS_INSUFFICIENT_RESOURCES - insufficient memory to complete
        the function.

    Errors from NtSetSecurityObject

--*/
{
    NTSTATUS Status;

    PTOKEN_USER ThreadTokenUser;
    PTOKEN_USER ImpersonationTokenUser = NULL;

    PTOKEN_USER SlowProcessTokenUser = NULL;
    PTOKEN_USER SlowThreadTokenUser = NULL;
    PTOKEN_USER SlowImpersonationTokenUser = NULL;

    ULONG_PTR FastThreadTokenUser[ 128/sizeof(ULONG_PTR) ];
    ULONG_PTR FastImpersonationTokenUser[ 128/sizeof(ULONG_PTR) ];
    ULONG TokenUserSize;

    HANDLE ProcessToken = NULL;
    HANDLE ImpersonationToken = NULL;
    BOOL fInsertImpersonatingUser = FALSE;
    SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
    ULONG AclLength;

    PACL NewDacl;
    PACL SlowNewDacl = NULL;
    ULONG_PTR FastNewDacl[ 512/sizeof(ULONG_PTR) ];

    SECURITY_DESCRIPTOR SecurityDescriptor;

    //
    // Build the two well known sids we need.
    //

    if (NtLmGlobalLocalSystemSid == NULL)
    {
        PSID pLocalSidSystem;
        PSID pOldSid;

        Status = RtlAllocateAndInitializeSid(
                    &NtAuthority,
                    1,
                    SECURITY_LOCAL_SYSTEM_RID,
                    0,0,0,0,0,0,0,
                    &pLocalSidSystem
                    );
        if (!NT_SUCCESS(Status))
        {
            SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, RtlAllocateAndInitializeSid returns 0x%lx\n", Status ));
            goto Cleanup;
        }

        pOldSid = InterlockedCompareExchangePointer(
                        &NtLmGlobalLocalSystemSid,
                        pLocalSidSystem,
                        NULL
                        );
        if( pOldSid )
        {
            RtlFreeSid( pLocalSidSystem );
        }
    }

    if (NtLmGlobalAliasAdminsSid == NULL)
    {
        PSID pLocalSidAdmins;
        PSID pOldSid;

        Status = RtlAllocateAndInitializeSid(
                    &NtAuthority,
                    2,
                    SECURITY_BUILTIN_DOMAIN_RID,
                    DOMAIN_ALIAS_RID_ADMINS,
                    0,0,0,0,0,0,
                    &pLocalSidAdmins
                    );
        if (!NT_SUCCESS(Status))
        {
            SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, RtlAllocateAndInitializeSid returns 0x%lx\n", Status ));
            goto Cleanup;
        }

        pOldSid = InterlockedCompareExchangePointer(
                        &NtLmGlobalAliasAdminsSid,
                        pLocalSidAdmins,
                        NULL
                        );
        if( pOldSid )
        {
            RtlFreeSid( pLocalSidAdmins );
        }
    }

    //
    // it's possible that the current thread is impersonating a user.
    // if that's the case, get it's token user, and revert to insure we
    // can open the process token.
    //

    Status = NtOpenThreadToken(
                            NtCurrentThread(),
                            TOKEN_QUERY | TOKEN_IMPERSONATE,
                            TRUE,
                            &ImpersonationToken
                            );

    if( NT_SUCCESS(Status) )
    {
        //
        // stop impersonating.
        //

        RevertToSelf();

        //
        // get the token user for the impersonating user.
        //

        ImpersonationTokenUser = (PTOKEN_USER)FastImpersonationTokenUser;
        TokenUserSize = sizeof(FastImpersonationTokenUser);

        Status = SspGetTokenUser(
                    ImpersonationToken,
                    ImpersonationTokenUser,
                    &TokenUserSize
                    );

        if( Status == STATUS_BUFFER_TOO_SMALL )
        {
            SlowImpersonationTokenUser = (PTOKEN_USER)NtLmAllocate( TokenUserSize );
            if(SlowImpersonationTokenUser == NULL)
            {
                Status = STATUS_INSUFFICIENT_RESOURCES;
                goto Cleanup;
            }

            ImpersonationTokenUser = SlowImpersonationTokenUser;

            Status = SspGetTokenUser(
                        ImpersonationToken,
                        ImpersonationTokenUser,
                        &TokenUserSize
                        );

        }

        if (!NT_SUCCESS(Status))
        {
            SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, SspGetTokenUser returns 0x%lx\n", Status ));
            goto Cleanup;
        }
    }


    if( NtLmGlobalProcessUserSid == NULL )
    {
        PTOKEN_USER ProcessTokenUser;
        ULONG_PTR FastProcessTokenUser[ 128/sizeof(ULONG_PTR) ];
        PSID pOldSid;
        PSID pNewSid;
        ULONG cbNewSid;

        //
        // Open the process token to find out the user sid
        //

        Status = NtOpenProcessToken(
                    NtCurrentProcess(),
                    TOKEN_QUERY,
                    &ProcessToken
                    );

        if(!NT_SUCCESS(Status))
        {
            SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, NtOpenProcessToken returns 0x%lx\n", Status ));
            goto Cleanup;
        }

        //
        // get the token user for the process token.
        //

        ProcessTokenUser = (PTOKEN_USER)FastProcessTokenUser;
        TokenUserSize = sizeof(FastProcessTokenUser);

        Status = SspGetTokenUser(
                    ProcessToken,
                    ProcessTokenUser,
                    &TokenUserSize
                    );

        if( Status == STATUS_BUFFER_TOO_SMALL )
        {
            SlowProcessTokenUser = (PTOKEN_USER)NtLmAllocate( TokenUserSize );
            if(SlowProcessTokenUser == NULL)
            {
                Status = STATUS_INSUFFICIENT_RESOURCES;
                goto Cleanup;
            }

            ProcessTokenUser = SlowProcessTokenUser;

            Status = SspGetTokenUser(
                        ProcessToken,
                        ProcessTokenUser,
                        &TokenUserSize
                        );

        }

        if (!NT_SUCCESS(Status))
        {
            SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, SspGetTokenUser returns 0x%lx\n", Status ));
            goto Cleanup;
        }

        cbNewSid = RtlLengthSid( ProcessTokenUser->User.Sid );
        pNewSid = NtLmAllocate( cbNewSid );
        if( pNewSid == NULL )
        {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto Cleanup;
        }

        CopyMemory( pNewSid, ProcessTokenUser->User.Sid, cbNewSid );

        pOldSid = InterlockedCompareExchangePointer(
                        &NtLmGlobalProcessUserSid,
                        pNewSid,
                        NULL
                        );
        if( pOldSid )
        {
            NtLmFree( pNewSid );
        }

    }



    //
    // Now get the token user for the thread.
    //

    ThreadTokenUser = (PTOKEN_USER)FastThreadTokenUser;
    TokenUserSize = sizeof(FastThreadTokenUser);

    Status = SspGetTokenUser(
                Token,
                ThreadTokenUser,
                &TokenUserSize
                );

    if( Status == STATUS_BUFFER_TOO_SMALL )
    {
        SlowThreadTokenUser = (PTOKEN_USER)NtLmAllocate( TokenUserSize );
        if(SlowThreadTokenUser == NULL)
        {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto Cleanup;
        }

        ThreadTokenUser = SlowThreadTokenUser;

        Status = SspGetTokenUser(
                    Token,
                    ThreadTokenUser,
                    &TokenUserSize
                    );
    }

    if (!NT_SUCCESS(Status))
    {
        SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, SspGetTokenUser returns 0x%lx\n", Status ));
        goto Cleanup;
    }



    AclLength = 4 * sizeof( ACCESS_ALLOWED_ACE ) - 4 * sizeof( ULONG ) +
                RtlLengthSid( NtLmGlobalProcessUserSid ) +
                RtlLengthSid( ThreadTokenUser->User.Sid ) +
                RtlLengthSid( NtLmGlobalLocalSystemSid ) +
                RtlLengthSid( NtLmGlobalAliasAdminsSid ) +
                sizeof( ACL );

    //
    // determine if we need to add impersonation token sid onto the token Dacl.
    //

    if( ImpersonationTokenUser &&
        !RtlEqualSid( ImpersonationTokenUser->User.Sid, NtLmGlobalProcessUserSid ) &&
        !RtlEqualSid( ImpersonationTokenUser->User.Sid, ThreadTokenUser->User.Sid )
        )
    {
        AclLength += (sizeof(ACCESS_ALLOWED_ACE) - sizeof( ULONG )) +
                RtlLengthSid( ImpersonationTokenUser->User.Sid );

        fInsertImpersonatingUser = TRUE;
    }


    if( AclLength <= sizeof(FastNewDacl) )
    {
        NewDacl = (PACL)FastNewDacl;
    } else {

        SlowNewDacl = (PACL)NtLmAllocate(AclLength );

        NewDacl = SlowNewDacl;

        if (SlowNewDacl == NULL) {

            Status = STATUS_INSUFFICIENT_RESOURCES;
            SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, NtLmallocate returns 0x%lx\n", NewDacl));
            goto Cleanup;
        }


    }

    Status = RtlCreateAcl( NewDacl, AclLength, ACL_REVISION2 );
    ASSERT(NT_SUCCESS( Status ));

    Status = RtlAddAccessAllowedAce (
                 NewDacl,
                 ACL_REVISION2,
                 TOKEN_ALL_ACCESS,
                 NtLmGlobalProcessUserSid
                 );
    ASSERT( NT_SUCCESS( Status ));

    Status = RtlAddAccessAllowedAce (
                 NewDacl,
                 ACL_REVISION2,
                 TOKEN_ALL_ACCESS,
                 ThreadTokenUser->User.Sid
                 );
    ASSERT( NT_SUCCESS( Status ));

    if( fInsertImpersonatingUser )
    {
        Status = RtlAddAccessAllowedAce (
                     NewDacl,
                     ACL_REVISION2,
                     TOKEN_ALL_ACCESS,
                     ImpersonationTokenUser->User.Sid
                     );
        ASSERT( NT_SUCCESS( Status ));
    }

    Status = RtlAddAccessAllowedAce (
                 NewDacl,
                 ACL_REVISION2,
                 TOKEN_ALL_ACCESS,
                 NtLmGlobalAliasAdminsSid
                 );
    ASSERT( NT_SUCCESS( Status ));

    Status = RtlAddAccessAllowedAce (
                 NewDacl,
                 ACL_REVISION2,
                 TOKEN_ALL_ACCESS,
                 NtLmGlobalLocalSystemSid
                 );
    ASSERT( NT_SUCCESS( Status ));

    Status = RtlCreateSecurityDescriptor (
                 &SecurityDescriptor,
                 SECURITY_DESCRIPTOR_REVISION
                 );
    ASSERT( NT_SUCCESS( Status ));

    Status = RtlSetDaclSecurityDescriptor(
                 &SecurityDescriptor,
                 TRUE,
                 NewDacl,
                 FALSE
                 );

    ASSERT( NT_SUCCESS( Status ));

    Status = NtSetSecurityObject(
                 Token,
                 DACL_SECURITY_INFORMATION,
                 &SecurityDescriptor
                 );

    ASSERT( NT_SUCCESS( Status ));


Cleanup:

    if (ImpersonationToken != NULL)
    {
        //
        // put the thread token back if we were impersonating.
        //

        SetThreadToken( NULL, ImpersonationToken );
        NtClose(ImpersonationToken);
    }


    if (SlowThreadTokenUser != NULL) {
        NtLmFree( SlowThreadTokenUser );
    }

    if (SlowProcessTokenUser != NULL) {
        NtLmFree( SlowProcessTokenUser );
    }

    if (SlowImpersonationTokenUser != NULL) {

        NtLmFree( SlowImpersonationTokenUser );
    }

    if (SlowNewDacl != NULL) {
        NtLmFree( SlowNewDacl );
    }

    if (ProcessToken != NULL)
    {
        NtClose(ProcessToken);
    }

    return( Status );
}



NTSTATUS
SspMapContext(
    IN PULONG_PTR   phContext,
    IN PUCHAR       pSessionKey,
    IN ULONG        NegotiateFlags,
    IN HANDLE       TokenHandle,
    IN PTimeStamp   PasswordExpiry OPTIONAL,
    IN ULONG        UserFlags,
    OUT PSecBuffer  ContextData
    )

/*++

RoutineDescription:

    Create a local context for a real context
    Don't link it to out list of local contexts.

Arguments:

Return Value:

--*/

{
    NTSTATUS Status = STATUS_SUCCESS;

    PNTLM_PACKED_CONTEXT pContext = NULL;
    ULONG cbContextData;

    WCHAR ContextNames[(UNLEN + DNS_MAX_NAME_LENGTH + 2 + 1) *sizeof (WCHAR)];

    PLUID pLogonId = NULL;
    TOKEN_STATISTICS TokenStats;

    PSSP_CONTEXT pTempContext = (PSSP_CONTEXT)*phContext;
    PACTIVE_LOGON *ActiveLogon = NULL, Logon = NULL;
    LPWSTR UserName = NULL;
    UINT Length = 0;
    BOOLEAN ActiveLogonsAreLocked = FALSE;

    if (pTempContext == NULL)
    {
        Status = STATUS_INVALID_HANDLE;
        SspPrint(( SSP_CRITICAL, "SspMapContext, pTempContext is 0x%lx\n", pTempContext));
        goto Cleanup;
    }

    if( pTempContext->Credential != NULL ) {
        pLogonId = &(pTempContext->Credential->LogonId);
    } else {

        //
        // if it was a local call where the default creds were used, lookup
        // the username and domain name based on the AuthenticationId of the
        // access token.  The local call gets a duplicated access token
        // associated with the original client, so information on this logon
        // should be found in the active logon list.
        //

        if( NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL &&
            pTempContext->UserName.Length == 0 &&
            pTempContext->UserName.Buffer == NULL &&
            pTempContext->DomainName.Length == 0 &&
            pTempContext->DomainName.Buffer == NULL &&
            TokenHandle ) {

            DWORD TokenInfoLength = sizeof( TokenStats );

            if( GetTokenInformation(
                        TokenHandle,
                        TokenStatistics,
                        &TokenStats,
                        TokenInfoLength,
                        &TokenInfoLength
                        )) {

                pLogonId = &(TokenStats.AuthenticationId);

            }

        }

    }

    if( pLogonId )
    {
        NlpLockActiveLogonsRead();
        ActiveLogonsAreLocked = TRUE;
        if (!NlpFindActiveLogon (
                 pLogonId,
                 &ActiveLogon))
        {
//            Status = STATUS_NO_SUCH_LOGON_SESSION;
            SspPrint(( SSP_API_MORE, "SspMapContext, NlpFindActiveLogon returns FALSE\n"));
//            SspPrint(( SSP_CRITICAL, "SspMapContext, NlpFindActiveLogon returns FALSE\n"));
//            goto Cleanup;
        }
    }

    if (ActiveLogon != NULL)
    {
        Logon = *ActiveLogon;
    }

    ContextNames[0] = L'\0';

    if (Logon != NULL)
    {
        if ( (Logon->UserName.Length > 0)  &&
             (Logon->LogonDomainName.Length > 0))
        {
            RtlCopyMemory (ContextNames,
                           Logon->LogonDomainName.Buffer,
                           Logon->LogonDomainName.Length);

            Length = (Logon->LogonDomainName.Length)/sizeof(WCHAR);

            ContextNames[Length] = L'\\';
            Length += 1;
            UserName = &ContextNames[Length];

            RtlCopyMemory (UserName,
                           Logon->UserName.Buffer,
                           Logon->UserName.Length);

            Length += (Logon->UserName.Length)/sizeof(WCHAR);

            ContextNames[Length] = L'\0';

        }
    }
    else
    {
        // We don't store network logons, but in the case of server side
        // mapping, the client has sent us the Context Names, so use that.
        // Also, handle the case where we don't have domainnames, just usernames
        // Must handle the valid case where both domainname & username are NULL (rdr)

        if ((pTempContext->DomainName.Length > 0)  &&
            (pTempContext->UserName.Length > 0))
        {
            RtlCopyMemory (ContextNames,
                           pTempContext->DomainName.Buffer,
                           pTempContext->DomainName.Length);

            Length = (pTempContext->DomainName.Length)/sizeof(WCHAR);

            ContextNames[Length] = L'\\';
            Length += 1;
            UserName = &ContextNames[Length];

            RtlCopyMemory (UserName,
                           pTempContext->UserName.Buffer,
                           pTempContext->UserName.Length);

            Length += (pTempContext->UserName.Length)/sizeof(WCHAR);

            ContextNames[Length] = L'\0';

        }
        else if ((pTempContext->DomainName.Length == 0) &&
                 (pTempContext->UserName.Length >0))
        {
            RtlCopyMemory (ContextNames + Length,
                           pTempContext->UserName.Buffer,
                           pTempContext->UserName.Length);

            Length = (pTempContext->UserName.Length)/sizeof(WCHAR);

            ContextNames[Length] = L'\0';
        }
    }

    //
    // when domain is present, don't supply domain\UPN, supply domain\user
    //

    if( UserName ) {
        DWORD cchUserName = wcslen(UserName);
        DWORD i;

        for( i = 0 ; i < cchUserName ; i++ ) {

            if( UserName[ i ] == L'@' ) {
                UserName[ i ] = L'\0';
                break;
            }
        }
    }

    Length = wcslen(ContextNames) * sizeof(WCHAR);

    cbContextData =
                    sizeof(NTLM_PACKED_CONTEXT) +
                    Length +
                    sizeof(WCHAR);

    cbContextData += pTempContext->cbMarshalledTargetInfo;

     // the first sizeof (NTLM_CLIENT_CONTEXT) bytes can be
     // casted to pContext anyway.

    pContext = (PNTLM_PACKED_CONTEXT)NtLmAllocateLsaHeap( cbContextData );

    if (!pContext)
    {
        SspPrint(( SSP_CRITICAL, "SspMapContext, NtLmAllocate returns NULL\n"));
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto Cleanup;
    }

//    ZeroMemory( pContext, cbContextData );

    pContext->NegotiateFlags = NegotiateFlags;


    RtlCopyMemory(  pContext->SessionKey,
                    pSessionKey,
                    MSV1_0_USER_SESSION_KEY_LENGTH);


    // Save away the LsaContextHandle
//    pContext->LsaContext = *phContext;

    // dup token if it exists

    pContext->ClientTokenHandle = 0 ;

    if (TokenHandle != NULL)
    {
        HANDLE Tmp ;
        Status = LsaFunctions->DuplicateHandle(
                           TokenHandle,
                           &Tmp );

        if (!NT_SUCCESS(Status))
        {
            if (pContext)
            {
                NtLmFreeLsaHeap(pContext);
            }
            SspPrint(( SSP_CRITICAL, "SspMapContext, DuplicateHandle returns 0x%lx\n", Status));
            goto Cleanup;
        }

        pContext->ClientTokenHandle = (ULONG) ((ULONG_PTR) Tmp) ;
    }

    if (cbContextData > sizeof(NTLM_PACKED_CONTEXT) )
    {
        pContext->ContextNames = sizeof(NTLM_PACKED_CONTEXT);
        pContext->ContextNameLength = Length;

        RtlCopyMemory(pContext+1,
                      ContextNames,
                      pContext->ContextNameLength
                      );


        pContext->ContextNameLength += sizeof(WCHAR);
    }
    else
    {
        pContext->ContextNames = 0;
        pContext->ContextNameLength = 0;
    }


    if( pTempContext->pbMarshalledTargetInfo )
    {
        pContext->MarshalledTargetInfo = (ULONG)pContext->ContextNameLength + sizeof(NTLM_PACKED_CONTEXT) ;
        pContext->MarshalledTargetInfoLength = pTempContext->cbMarshalledTargetInfo;

        RtlCopyMemory( (PBYTE)pContext+pContext->MarshalledTargetInfo,
                        pTempContext->pbMarshalledTargetInfo,
                        pContext->MarshalledTargetInfoLength
                        );

    }

    pContext->SendNonce = 0;
    pContext->RecvNonce = 0;


    ContextData->pvBuffer = pContext;
    ContextData->cbBuffer = cbContextData;


    if(ARGUMENT_PRESENT(PasswordExpiry)) {
        pContext->PasswordExpiry = *PasswordExpiry;
    } else {
        pContext->PasswordExpiry.QuadPart = 0;
    }

    pContext->UserFlags = UserFlags;

Cleanup:
    if (ActiveLogonsAreLocked)
    {
        NlpUnlockActiveLogons();
    }
    return(Status);
}


ULONG
HandleToListIndex(
    ULONG_PTR ContextHandle
    )
{
    ASSERT( (NTLM_USERLIST_COUNT != 0) );
    ASSERT( (NTLM_USERLIST_COUNT & 1) == 0 );
    
    ULONG Number ;
    ULONG Hash;
    ULONG HashFinal;

    Number       = (ULONG)ContextHandle;

    Hash         = Number;
    Hash        += Number >> 8;
    Hash        += Number >> 16;
    Hash        += Number >> 24;

    HashFinal    = Hash;
    HashFinal   += Hash >> 4;

    //
    // insure power of two if not one.
    //

    return ( HashFinal & (NTLM_USERLIST_COUNT-1) ) ;
}

ULONG
__inline
ListIndexToLockIndex(
    ULONG ListIndex
    )
{
    ASSERT( (NTLM_USERLIST_LOCK_COUNT) != 0 );
    ASSERT( (NTLM_USERLIST_LOCK_COUNT & 1) == 0 );

    //
    // insure power of two if not one.
    //
    
    return ( ListIndex & (NTLM_USERLIST_LOCK_COUNT-1) );
}