/* Editor Settings: expandtabs and use 4 spaces for indentation
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 */

/*
 * Copyright Likewise Software    2004-2008
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.  You should have received a copy of the GNU General
 * Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *        acceptsecctxt.c
 *
 * Abstract:
 *
 *        Likewise Security and Authentication Subsystem (LSASS)
 *
 *        AcceptSecurityContext client wrapper API
 *
 * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
 *          Marc Guy (mguy@likewisesoftware.com)
 */

#include "ntlmsrvapi.h"
#include "lsasrvapi.h"

static
DWORD
NtlmGetLocalGuestAccountSid(
    OUT PSTR* ppszGuestSid
    );

static
DWORD
NtlmCreateValidateResponseContext(
    IN PNTLM_IPC_CALL_CONTEXT pCallContext,
    OUT PNTLM_VALIDATE_RESPONSE_CTXT* ppContext
    );

static
VOID
NtlmFreeValidateResponseContext(
    PNTLM_VALIDATE_RESPONSE_CTXT *ppContext
    );

static
DWORD
NtlmValidateResponse(
    IN HANDLE Handle,
    IN OUT PNTLM_VALIDATE_RESPONSE_CTXT pContext,
    IN NTLM_CRED_HANDLE hCred,
    IN PNTLM_CONTEXT pChlngCtxt,
    IN const NTLM_RESPONSE_MESSAGE_V1* pRespMsg,
    IN DWORD dwRespMsgSize
    );

static
DWORD
NtlmValidateResponseComplete(
    IN OUT PNTLM_VALIDATE_RESPONSE_CTXT pContext,
    IN NTLM_CRED_HANDLE hCred,
    IN const NTLM_RESPONSE_MESSAGE_V1* pRespMsg,
    IN DWORD dwMessageSize,
    IN PNTLM_CONTEXT pChlngCtxt,
    IN DWORD dwError,
    IN OUT PNTLM_CONTEXT_HANDLE phNewNtlmCtxt
    );

static
VOID
NtlmServerCompleteAcceptSecurityContext(
    PVOID pData
    );

DWORD
NtlmServerAcceptSecurityContext(
    PNTLM_IPC_CALL_CONTEXT pContext
    )
{
    PNTLM_IPC_CALL_ARGS_ACCEPT_SEC_CTXT pArgs = NULL;
    DWORD dwMessageSize = 0;
    DWORD dwError = pContext->StatusBlock.Error;

    pArgs = &pContext->Args.AcceptSecCtxt;
    dwMessageSize = pArgs->pInput->cbBuffer;

step:
    switch (pArgs->stage)
    {
        case NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_INITIAL:

            LSA_LOG_TRACE("NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_INITIAL");
            pArgs->pNtlmResp->fContextAttr = 0;
            pArgs->pNtlmResp->tsTimeStamp = 0;
            pArgs->pNtlmResp->Output.pvBuffer = NULL;

            if (!pArgs->hNtlmCtxt)
            {
                pArgs->stage = NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_CREATE_CHALLENGE;
            }
            else
            {
                pArgs->stage =
                    NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_VALIDATE_CHALLENGE;
            }
            goto step;

        case NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_CREATE_CHALLENGE:
        {
            const NTLM_NEGOTIATE_MESSAGE_V1* pNegMsg = NULL;

            LSA_LOG_TRACE("NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_CREATE_CHALLENGE");
            if (pArgs->pInput->BufferType != SECBUFFER_TOKEN ||
                pArgs->pInput->cbBuffer == 0)
            {
                dwError = LW_ERROR_INVALID_PARAMETER;
                BAIL_ON_LSA_ERROR(dwError);
            }

            pNegMsg = pArgs->pInput->pvBuffer;

            if (dwMessageSize < sizeof(*pNegMsg))
            {
                dwError = ERROR_INVALID_PARAMETER;
                BAIL_ON_LSA_ERROR(dwError);
            }

            dwError = NtlmCreateChallengeContext(
                            pNegMsg,
                            pArgs->hCred,
                            &pArgs->hNewNtlmCtxt,
                            &pArgs->pNtlmResp->Output);
            BAIL_ON_LSA_ERROR(dwError);

            dwError = LW_WARNING_CONTINUE_NEEDED;
            BAIL_ON_LSA_ERROR(dwError);
        }
        case NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_VALIDATE_CHALLENGE:
        {
            const NTLM_RESPONSE_MESSAGE_V1 *pRespMsg =
                pArgs->pInput->pvBuffer;

            LSA_LOG_TRACE("NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_VALIDATE_CHALLENGE");
            // In this case we need to grab the response message sent in
            if (pArgs->pInput->BufferType != SECBUFFER_TOKEN ||
                pArgs->pInput->cbBuffer == 0)
            {
                dwError = LW_ERROR_INVALID_PARAMETER;
                BAIL_ON_LSA_ERROR(dwError);
            }

            dwMessageSize = pArgs->pInput->cbBuffer;

            dwError = NtlmCreateValidateResponseContext(
                            pContext,
                            &pArgs->pValidateResponseCtxt);
            BAIL_ON_LSA_ERROR(dwError);

            pArgs->stage =
                NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_PROCESS_RESULT;

            dwError = NtlmValidateResponse(
                            pArgs->Handle,
                            pArgs->pValidateResponseCtxt,
                            pArgs->hCred,
                            pArgs->hNtlmCtxt,
                            pRespMsg,
                            dwMessageSize);

            if (dwError == ERROR_IO_PENDING)
            {
                BAIL_ON_LSA_ERROR(dwError);
            }
            // Intentional fall through
        }
        case NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_PROCESS_RESULT:
        {
            const NTLM_RESPONSE_MESSAGE_V1 *pRespMsg = pArgs->pInput->pvBuffer;

            LSA_LOG_TRACE("NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_PROCESS_RESULT");
            dwError = NtlmValidateResponseComplete(
                            pArgs->pValidateResponseCtxt,
                            pArgs->hCred,
                            pRespMsg,
                            dwMessageSize,
                            pArgs->hNtlmCtxt,
                            dwError,
                            &pArgs->hNewNtlmCtxt);
                            
            break;
        }
        default:
            dwError = LW_ERROR_INTERNAL;
            BAIL_ON_LSA_ERROR(dwError);
            break;
    }

cleanup:
    if (dwError != ERROR_IO_PENDING)
    {
        if (dwError != LW_ERROR_SUCCESS)
        {
            NtlmFreeValidateResponseContext(&pArgs->pValidateResponseCtxt);
        }

        // We should not get invoked again for this IPC call.
        pArgs->stage = NTLM_SRV_ACCEPT_SEC_CTXT_STAGE_INVALID;
        pContext->StatusBlock.Error = dwError;
    }

    return dwError;

error:
    if (dwError != ERROR_IO_PENDING && dwError != LW_WARNING_CONTINUE_NEEDED)
    {
        LW_SAFE_FREE_MEMORY(pArgs->pNtlmResp->Output.pvBuffer);
        pArgs->pNtlmResp->Output.cbBuffer = 0;
        pArgs->pNtlmResp->Output.BufferType = 0;

        NtlmReleaseContext(&pArgs->hNewNtlmCtxt);
    }
    goto cleanup;
}

static
VOID
NtlmServerCompleteAcceptSecurityContext(
    PVOID pData
    )
{
    PNTLM_IPC_CALL_CONTEXT pContext = pData;
    DWORD dwError = LW_ERROR_INTERNAL;

    dwError = NtlmServerAcceptSecurityContext(pContext);

    if (dwError != ERROR_IO_PENDING)
    {
        pContext->AsyncBlock.Callback(pContext->AsyncBlock.CallbackContext);
    }
}

DWORD
NtlmCreateChallengeContext(
    IN const NTLM_NEGOTIATE_MESSAGE_V1* pNtlmNegMsg,
    IN NTLM_CRED_HANDLE hCred,
    OUT PNTLM_CONTEXT *ppNtlmContext,
    OUT PSecBuffer pOutput
    )
{
    DWORD dwError = LW_ERROR_SUCCESS;
    PNTLM_CREDENTIALS pCred = (PNTLM_CREDENTIALS)hCred;
    PNTLM_CONTEXT pNtlmContext = NULL;
    DWORD dwMessageSize = 0;
    PNTLM_CHALLENGE_MESSAGE pMessage = NULL;
    PSTR pServerName = NULL;
    PSTR pDomainName = NULL;
    PSTR pDnsServerName = NULL;
    PSTR pDnsDomainName = NULL;
    BOOLEAN bInLock = FALSE;

    *ppNtlmContext = NULL;

    dwError = NtlmCreateContext(hCred, &pNtlmContext);
    BAIL_ON_LSA_ERROR(dwError);

    if (pCred)
    {
        NTLM_LOCK_MUTEX(bInLock, &pCred->Mutex);

        dwError = NtlmGetNameInformation(
                        pCred->pszDomainName,
                        &pServerName,
                        &pDomainName,
                        &pDnsServerName,
                        &pDnsDomainName);
        BAIL_ON_LSA_ERROR(dwError);

        NTLM_UNLOCK_MUTEX(bInLock, &pCred->Mutex);
    }
    else
    {
        dwError = NtlmGetNameInformation(
                        NULL,
                        &pServerName,
                        &pDomainName,
                        &pDnsServerName,
                        &pDnsDomainName);
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = NtlmGetRandomBuffer(pNtlmContext->Challenge, NTLM_CHALLENGE_SIZE);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = NtlmCreateChallengeMessage(
                    pNtlmNegMsg,
                    pServerName,
                    pDomainName,
                    pDnsServerName,
                    pDnsDomainName,
                    (PBYTE)&gW2KSpoof,
                    pNtlmContext->Challenge,
                    &dwMessageSize,
                    &pMessage);

    BAIL_ON_LSA_ERROR(dwError);

    pNtlmContext->NegotiatedFlags = LW_LTOH32(pMessage->NtlmFlags);
    pOutput->cbBuffer = dwMessageSize;
    pOutput->BufferType = SECBUFFER_TOKEN;
    pOutput->pvBuffer = pMessage;
    pNtlmContext->NtlmState = NtlmStateChallenge;

cleanup:
    *ppNtlmContext = pNtlmContext;

    LW_SAFE_FREE_STRING(pServerName);
    LW_SAFE_FREE_STRING(pDomainName);
    LW_SAFE_FREE_STRING(pDnsServerName);
    LW_SAFE_FREE_STRING(pDnsDomainName);

    if (pCred)
    {
        NTLM_UNLOCK_MUTEX(bInLock, &pCred->Mutex);
    }

    return dwError;

error:
    LW_SAFE_FREE_MEMORY(pMessage);

    if (pNtlmContext)
    {
        NtlmReleaseContext(&pNtlmContext);
        *ppNtlmContext = NULL;
    }
    pOutput->cbBuffer = 0;
    pOutput->BufferType = 0;
    pOutput->pvBuffer = NULL;
    goto cleanup;
}

DWORD
NtlmCreateGuestContext(
    OUT PNTLM_CONTEXT *ppNtlmContext
    )
{
    DWORD dwError = LW_ERROR_SUCCESS;
    PNTLM_CONTEXT pNtlmContext = NULL;
    HANDLE hConnection = NULL;
    LSA_QUERY_LIST queryList = {0};
    PLSA_SECURITY_OBJECT* ppObjects = NULL;
    PSTR pszGuestSid = NULL;

    *ppNtlmContext = NULL;

    dwError = NtlmCreateContext(NULL, &pNtlmContext);
    BAIL_ON_LSA_ERROR(dwError);

    pNtlmContext->NtlmState = NtlmStateResponse;

    // Set the NegotiatedFlags to 0 since there's the resulting
    // context has no direct relation to the original user authentication
    // request and no session key.  This may change with future
    // investigation.

    pNtlmContext->NegotiatedFlags = 0;

    // Verify that the local Guest account has been enabled

    dwError = LsaSrvOpenServer(0, 0, getpid(), &hConnection);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = NtlmGetLocalGuestAccountSid(&pszGuestSid);
    BAIL_ON_LSA_ERROR(dwError);

    queryList.ppszStrings = (PCSTR*)&pszGuestSid;

    dwError = LsaSrvFindObjects(
                    hConnection,
                    LSA_PROVIDER_TAG_LOCAL,
                    0,
                    LSA_OBJECT_TYPE_USER,
                    LSA_QUERY_TYPE_BY_SID,
                    1,
                    queryList,
                    &ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

    if (!ppObjects[0])
    {
        dwError = LW_ERROR_NO_SUCH_USER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (ppObjects[0]->userInfo.bAccountDisabled)
    {
        dwError = LW_ERROR_ACCOUNT_DISABLED;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LwAllocateStringPrintf(
                    &pNtlmContext->pszClientUsername,
                    "%s\\%s",
                    ppObjects[0]->pszNetbiosDomainName,
                    ppObjects[0]->pszSamAccountName);

    // NULL session key
    memset(pNtlmContext->SessionKey, 0x0, NTLM_SESSION_KEY_SIZE);
    pNtlmContext->cbSessionKeyLen = NTLM_SESSION_KEY_SIZE;
    pNtlmContext->bInitiatedSide = FALSE;
    pNtlmContext->MappedToGuest = TRUE;

cleanup:

    LsaUtilFreeSecurityObjectList(1, ppObjects);

    if (hConnection)
    {
        LsaSrvCloseServer(hConnection);
    }

    LW_SAFE_FREE_STRING(pszGuestSid);

    *ppNtlmContext = pNtlmContext;

    return dwError;

error:

    if (pNtlmContext)
    {
        NtlmFreeContext(&pNtlmContext);
    }
    goto cleanup;
}


DWORD
NtlmCreateValidatedContext(
    IN const NTLM_RESPONSE_MESSAGE_V1 *pNtlmRespMsg,
    IN DWORD dwMsgSize,
    IN DWORD NegotiatedFlags,
    IN PBYTE pSessionKey,
    IN DWORD dwSessionKeyLen,
    IN NTLM_CRED_HANDLE hCred,
    OUT PNTLM_CONTEXT *ppNtlmContext
    )
{
    DWORD dwError = LW_ERROR_SUCCESS;
    PNTLM_CONTEXT pNtlmContext = NULL;
    SEC_CHAR* pUserName = NULL;
    SEC_CHAR* pDomainName = NULL;
    PNTLM_RESPONSE_MESSAGE_V2 pV2Message = NULL;
    RC4_KEY Rc4Key;

    *ppNtlmContext = NULL;

    dwError = NtlmCreateContext(hCred, &pNtlmContext);
    BAIL_ON_LSA_ERROR(dwError);

    pNtlmContext->NtlmState = NtlmStateResponse;

    pNtlmContext->NegotiatedFlags = NegotiatedFlags;

    dwError = NtlmGetUserNameFromResponse(
                    pNtlmRespMsg,
                    dwMsgSize,
                    NegotiatedFlags & NTLM_FLAG_UNICODE,
                    &pUserName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = NtlmGetDomainNameFromResponse(
                    pNtlmRespMsg,
                    dwMsgSize,
                    NegotiatedFlags & NTLM_FLAG_UNICODE,
                    &pDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwAllocateStringPrintf(
                    &pNtlmContext->pszClientUsername,
                    "%s\\%s",
                    pDomainName,
                    pUserName);
    BAIL_ON_LSA_ERROR(dwError);

    memcpy(pNtlmContext->SessionKey, pSessionKey, NTLM_SESSION_KEY_SIZE);
    pNtlmContext->cbSessionKeyLen = dwSessionKeyLen;
    pNtlmContext->bInitiatedSide = FALSE;

    if (NegotiatedFlags & NTLM_FLAG_KEY_EXCH)
    {
        pV2Message = (PNTLM_RESPONSE_MESSAGE_V2)pNtlmRespMsg;
        if (dwMsgSize < sizeof(*pV2Message))
        {
            dwError = ERROR_INVALID_PARAMETER;
            BAIL_ON_LSA_ERROR(dwError);
        }

        if (LW_LTOH32(pV2Message->SessionKey.dwOffset) +
            LW_LTOH16(pV2Message->SessionKey.usLength) > dwMsgSize)
        {
            dwError = ERROR_INVALID_PARAMETER;
            BAIL_ON_LSA_ERROR(dwError);
        }

        if (LW_LTOH16(pV2Message->SessionKey.usLength) != NTLM_SESSION_KEY_SIZE)
        {
            dwError = ERROR_INVALID_PARAMETER;
            BAIL_ON_LSA_ERROR(dwError);
        }

        RC4_set_key(
            &Rc4Key,
            pNtlmContext->cbSessionKeyLen,
            pNtlmContext->SessionKey);
        RC4(&Rc4Key,
            NTLM_SESSION_KEY_SIZE,
            LW_LTOH32(pV2Message->SessionKey.dwOffset) + (PBYTE)pV2Message,
            pNtlmContext->SessionKey);
    }

    dwError = NtlmInitializeKeys(pNtlmContext);
    BAIL_ON_LSA_ERROR(dwError);

cleanup:
    LW_SAFE_FREE_MEMORY(pUserName);
    LW_SAFE_FREE_MEMORY(pDomainName);
    *ppNtlmContext = pNtlmContext;
    return dwError;

error:
    if (pNtlmContext)
    {
        NtlmFreeContext(&pNtlmContext);
    }
    goto cleanup;
}

DWORD
NtlmCreateSubkey(
    DWORD dwMasterKeyLen,
    PBYTE pMasterKey,
    PCSTR pszSubkeyMagic,
    RC4_KEY** ppResult
    )
{
    DWORD dwError = ERROR_SUCCESS;
    RC4_KEY* pResult = NULL;
    BYTE md5Result[MD5_DIGEST_LENGTH];
    MD5_CTX ctx = {0};

    dwError = LwAllocateMemory(sizeof(*pResult), OUT_PPVOID(&pResult));
    BAIL_ON_LSA_ERROR(dwError);

    MD5_Init(&ctx);
    MD5_Update(&ctx, pMasterKey, dwMasterKeyLen);
    MD5_Update( &ctx, pszSubkeyMagic, strlen(pszSubkeyMagic) + /*nul*/1);
    MD5_Final(md5Result, &ctx);

    RC4_set_key(pResult, sizeof(md5Result), md5Result);

    *ppResult = pResult;

cleanup:
    memset(md5Result, 0, sizeof(md5Result));
    return dwError;

error:
    *ppResult = NULL;
    LW_SAFE_FREE_MEMORY(pResult);
    goto cleanup;
}

DWORD
NtlmInitializeKeys(
    PNTLM_CONTEXT pNtlmContext
    )
{
    MD5_CTX ctx = {0};
    DWORD dwError = 0;
    const char *clientServerSign =
        "session key to client-to-server signing key magic constant";
    const char *serverClientSign =
        "session key to server-to-client signing key magic constant";
    const char *clientServerSeal =
        "session key to client-to-server sealing key magic constant";
    const char *serverClientSeal =
        "session key to server-to-client sealing key magic constant";

    if (pNtlmContext->NegotiatedFlags & NTLM_FLAG_NTLM2)
    {
        dwError = LwAllocateMemory(
                        sizeof(*pNtlmContext->pdwSendMsgSeq),
                        OUT_PPVOID(&pNtlmContext->pdwSendMsgSeq));
        BAIL_ON_LSA_ERROR(dwError);
        dwError = LwAllocateMemory(
                        sizeof(*pNtlmContext->pdwRecvMsgSeq),
                        OUT_PPVOID(&pNtlmContext->pdwRecvMsgSeq));
        BAIL_ON_LSA_ERROR(dwError);

        MD5_Init(&ctx);
        MD5_Update(
            &ctx,
            pNtlmContext->SessionKey,
            pNtlmContext->cbSessionKeyLen);
        MD5_Update(&ctx, clientServerSign, strlen(clientServerSign) + /*nul*/1);
        MD5_Final(
            pNtlmContext->bInitiatedSide ?
            pNtlmContext->SignKey : pNtlmContext->VerifyKey,
            &ctx);

        MD5_Init(&ctx);
        MD5_Update(
            &ctx,
            pNtlmContext->SessionKey,
            pNtlmContext->cbSessionKeyLen);
        MD5_Update(&ctx, serverClientSign, strlen(serverClientSign) + /*nul*/1);
        MD5_Final(
            pNtlmContext->bInitiatedSide ?
            pNtlmContext->VerifyKey : pNtlmContext->SignKey,
            &ctx);

        // Weaken the master key
        if (pNtlmContext->NegotiatedFlags & NTLM_FLAG_128)
        {
            // Leave the key as is
        }
        else if (pNtlmContext->NegotiatedFlags & NTLM_FLAG_56)
        {
            pNtlmContext->cbSessionKeyLen = 7;
        }
        else
        {
            pNtlmContext->cbSessionKeyLen = 5;
        }

        dwError = NtlmCreateSubkey(
                        pNtlmContext->cbSessionKeyLen,
                        pNtlmContext->SessionKey,
                        clientServerSeal,
                        pNtlmContext->bInitiatedSide ?
                        &pNtlmContext->pSealKey : &pNtlmContext->pUnsealKey);
        BAIL_ON_LSA_ERROR(dwError);

        dwError = NtlmCreateSubkey(
                        pNtlmContext->cbSessionKeyLen,
                        pNtlmContext->SessionKey,
                        serverClientSeal,
                        pNtlmContext->bInitiatedSide ?
                        &pNtlmContext->pUnsealKey : &pNtlmContext->pSealKey);
        BAIL_ON_LSA_ERROR(dwError);
    }
    else
    {
        dwError = LwAllocateMemory(
                        sizeof(*pNtlmContext->pdwSendMsgSeq),
                        OUT_PPVOID(&pNtlmContext->pdwSendMsgSeq));
        BAIL_ON_LSA_ERROR(dwError);
        pNtlmContext->pdwRecvMsgSeq = pNtlmContext->pdwSendMsgSeq;

        dwError = LwAllocateMemory(
                        sizeof(*pNtlmContext->pSealKey),
                        OUT_PPVOID(&pNtlmContext->pSealKey));
        BAIL_ON_LSA_ERROR(dwError);

        RC4_set_key(
            pNtlmContext->pSealKey,
            pNtlmContext->cbSessionKeyLen,
            pNtlmContext->SessionKey);

        pNtlmContext->pUnsealKey = pNtlmContext->pSealKey;
    }

cleanup:
    return dwError;
error:
    goto cleanup;
}

static
DWORD
NtlmCreateValidateResponseContext(
    IN PNTLM_IPC_CALL_CONTEXT pCallContext,
    OUT PNTLM_VALIDATE_RESPONSE_CTXT* ppContext
    )
{
    DWORD dwError = LW_ERROR_INTERNAL;
    PNTLM_VALIDATE_RESPONSE_CTXT pContext = NULL;

    dwError = LwAllocateMemory(sizeof(*pContext), OUT_PPVOID(&pContext));
    BAIL_ON_LSA_ERROR(dwError);

    pContext->AsyncBlock.Callback = NtlmServerCompleteAcceptSecurityContext;
    pContext->AsyncBlock.CallbackContext = pCallContext;
    pContext->pStatusBlock = &pCallContext->StatusBlock;

    *ppContext = pContext;

cleanup:

    return dwError;

error:

    *ppContext = NULL;

    LW_SAFE_FREE_MEMORY(pContext);

    goto cleanup;
}

static
VOID
NtlmFreeValidateResponseContext(
    PNTLM_VALIDATE_RESPONSE_CTXT *ppContext
    )
{
    PNTLM_VALIDATE_RESPONSE_CTXT pContext = *ppContext;

    if (pContext)
    {
        if (pContext->pAuthUserInfo)
        {
            LsaFreeAuthUserInfo(&pContext->pAuthUserInfo);
        }

        LW_SAFE_FREE_MEMORY(pContext->pLMRespBuffer);
        LW_SAFE_FREE_MEMORY(pContext->pNTRespBuffer);
        LW_SAFE_FREE_STRING(pContext->pUserName);
        LW_SAFE_FREE_STRING(pContext->pDomainName);
        LW_SAFE_FREE_STRING(pContext->pWorkstation);
        LW_SAFE_FREE_STRING(pContext->pDomainInstance);

        LW_SAFE_FREE_MEMORY(*ppContext);
    }
}

static
DWORD
NtlmValidateResponse(
    IN HANDLE Handle,
    IN OUT PNTLM_VALIDATE_RESPONSE_CTXT pContext,
    IN NTLM_CRED_HANDLE hCred,
    IN PNTLM_CONTEXT pChlngCtxt,
    IN const NTLM_RESPONSE_MESSAGE_V1* pRespMsg,
    IN DWORD dwRespMsgSize
    )
{
    DWORD dwError = LW_ERROR_INTERNAL;
    PNTLM_CREDENTIALS pCred = (PNTLM_CREDENTIALS)hCred;
    BOOLEAN bInLock = FALSE;

    // sanity check
    if (!pRespMsg)
    {
        dwError = LW_ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (dwRespMsgSize < sizeof(*pRespMsg))
    {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LwAllocateMemory(
                    LW_LTOH16(pRespMsg->LmResponse.usLength),
                    OUT_PPVOID(&pContext->pLMRespBuffer));
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwAllocateMemory(
                    LW_LTOH16(pRespMsg->NtResponse.usLength),
                    OUT_PPVOID(&pContext->pNTRespBuffer));
    BAIL_ON_LSA_ERROR(dwError);

    // The username, domain, and workstation values might come back as Unicode.
    // We could technically prevent this by not allowing NTLM_FLAG_UNICODE to be
    // set during the negotiation phase, but that seems like an odd restriction
    // for now.

    dwError = NtlmGetUserNameFromResponse(
                    pRespMsg,
                    dwRespMsgSize,
                    pChlngCtxt->NegotiatedFlags & NTLM_FLAG_UNICODE,
                    &pContext->pUserName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = NtlmGetDomainNameFromResponse(
                    pRespMsg,
                    dwRespMsgSize,
                    pChlngCtxt->NegotiatedFlags & NTLM_FLAG_UNICODE,
                    &pContext->pDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = NtlmGetWorkstationFromResponse(
                    pRespMsg,
                    dwRespMsgSize,
                    pChlngCtxt->NegotiatedFlags & NTLM_FLAG_UNICODE,
                    &pContext->pWorkstation);
    BAIL_ON_LSA_ERROR(dwError);

    if (LW_LTOH32(pRespMsg->LmResponse.dwOffset) +
        LW_LTOH16(pRespMsg->LmResponse.usLength) > dwRespMsgSize)
    {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    memcpy(
        pContext->pLMRespBuffer,
        (PBYTE)pRespMsg + LW_LTOH32(pRespMsg->LmResponse.dwOffset),
        LW_LTOH16(pRespMsg->LmResponse.usLength));

    if (LW_LTOH32(pRespMsg->NtResponse.dwOffset) +
        LW_LTOH16(pRespMsg->NtResponse.usLength) > dwRespMsgSize)
    {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    memcpy(
        pContext->pNTRespBuffer,
        (PBYTE)pRespMsg + LW_LTOH32(pRespMsg->NtResponse.dwOffset),
        LW_LTOH16(pRespMsg->NtResponse.usLength));

    if (LW_LTOH16(pRespMsg->NtResponse.usLength) == 24 &&
        pChlngCtxt->NegotiatedFlags & NTLM_FLAG_NTLM2)
    {
        // The client sent an NTLM2 session response. That means we need to
        // calculate the challenge the client used.

        if (LW_LTOH16(pRespMsg->LmResponse.usLength) < 8)
        {
            dwError = LW_ERROR_INVALID_PARAMETER;
            BAIL_ON_LSA_ERROR(dwError);
        }

        // Calculate the session nonce first
        memcpy(pContext->SessionNonce + 0, pChlngCtxt->Challenge, 8);
        memcpy(pContext->SessionNonce + 8, pContext->pLMRespBuffer, 8);

        MD5(pContext->SessionNonce, 16, pContext->SessionHashUntrunc);

        pContext->Challenge.dwLen = NTLM_CHALLENGE_SIZE;
        pContext->Challenge.pData = pContext->SessionHashUntrunc;
    }
    else
    {
        pContext->Challenge.dwLen = NTLM_CHALLENGE_SIZE;
        pContext->Challenge.pData = pChlngCtxt->Challenge;
    }

    pContext->LMResp.dwLen = LW_LTOH16(pRespMsg->LmResponse.usLength);
    pContext->LMResp.pData = pContext->pLMRespBuffer;

    pContext->NTResp.dwLen = LW_LTOH16(pRespMsg->NtResponse.usLength);
    pContext->NTResp.pData = pContext->pNTRespBuffer;

    pContext->AuthUserParams.AuthType = LSA_AUTH_CHAP;

    pContext->AuthUserParams.pass.chap.pChallenge = &pContext->Challenge;
    pContext->AuthUserParams.pass.chap.pLM_resp = &pContext->LMResp;
    pContext->AuthUserParams.pass.chap.pNT_resp = &pContext->NTResp;

    pContext->AuthUserParams.pszAccountName = pContext->pUserName;

    pContext->AuthUserParams.pszDomain = pContext->pDomainName;

    pContext->AuthUserParams.pszWorkstation = pContext->pWorkstation;

    if (pCred)
    {
        NTLM_LOCK_MUTEX(bInLock, &pCred->Mutex);

        if (pCred->pszDomainName)
        {
            dwError = LwAllocateStringPrintf(
                            &pContext->pDomainInstance,
                            ":%s",
                            pCred->pszDomainName);
            BAIL_ON_LSA_ERROR(dwError);
        }

        NTLM_UNLOCK_MUTEX(bInLock, &pCred->Mutex);
    }

    dwError = LsaSrvAuthenticateUserEx(
                    Handle,
                    pContext->pDomainInstance,
                    &pContext->AuthUserParams,
                    &pContext->pAuthUserInfo,
                    &pContext->AsyncBlock,
                    pContext->pStatusBlock);
    BAIL_ON_LSA_ERROR(dwError);

cleanup:

    if (pCred)
    {
        NTLM_UNLOCK_MUTEX(bInLock, &pCred->Mutex);
    }

    if (dwError != ERROR_IO_PENDING)
    {
        pContext->pStatusBlock->Error = dwError;
    }

    return dwError;
error:

    goto cleanup;
}

static
DWORD
NtlmValidateResponseComplete(
    IN OUT PNTLM_VALIDATE_RESPONSE_CTXT pContext,
    IN NTLM_CRED_HANDLE hCred,
    IN const NTLM_RESPONSE_MESSAGE_V1* pRespMsg,
    IN DWORD dwMessageSize,
    IN PNTLM_CONTEXT pChlngCtxt,
    IN DWORD dwError,
    IN OUT PNTLM_CONTEXT_HANDLE phNewNtlmCtxt
    )
{
    NTLM_CONTEXT_HANDLE hNewNtlmCtxt = NULL;

    switch (dwError)
    {
        case LW_ERROR_SUCCESS:
            // The response has been validated and contains all the
            // information we are looking for; retain it... the original
            // (challenge) context will be freed when we return.
            LW_ASSERT(
                pContext->pAuthUserInfo->pSessionKey->dwLen ==
                NTLM_SESSION_KEY_SIZE);

            if (LW_LTOH16(pRespMsg->NtResponse.usLength) == 24 &&
                pChlngCtxt->NegotiatedFlags & NTLM_FLAG_NTLM2)
            {
                HMAC(
                    EVP_md5(),
                    pContext->pAuthUserInfo->pSessionKey->pData,
                    NTLM_SESSION_KEY_SIZE,
                    pContext->SessionNonce,
                    16,
                    pContext->SessionKey,
                    NULL);
            }
            else
            {
                memcpy(
                    pContext->SessionKey,
                    pContext->pAuthUserInfo->pSessionKey->pData,
                    NTLM_SESSION_KEY_SIZE);
            }

            // UserInfo for the NTLM context for the challenge
            // is now complete.
            pChlngCtxt->pUserInfo = pContext->pAuthUserInfo;
            pContext->pAuthUserInfo = NULL;

            dwError = NtlmCreateValidatedContext(
                            pRespMsg,
                            dwMessageSize,
                            pChlngCtxt->NegotiatedFlags,
                            pContext->SessionKey,
                            NTLM_SESSION_KEY_SIZE,
                            hCred,
                            &hNewNtlmCtxt);
            BAIL_ON_LSA_ERROR(dwError);

            // Transfer UserInfo to new validated NTLM context.
            hNewNtlmCtxt->pUserInfo = pChlngCtxt->pUserInfo;
            pChlngCtxt->pUserInfo = NULL;
            break;

        case LW_ERROR_NOT_HANDLED:
            // Attempt to fallback to Guest access
            dwError = NtlmCreateGuestContext(&hNewNtlmCtxt);

            // Restore previous error code on failure
            if (dwError != LW_ERROR_SUCCESS)
            {
                dwError = LW_ERROR_NOT_HANDLED;
            }
            BAIL_ON_LSA_ERROR(dwError);

            break;

        default:
            BAIL_ON_LSA_ERROR(dwError);
            break;
    }
cleanup:

    *phNewNtlmCtxt = hNewNtlmCtxt;
    return dwError;

error:
    NtlmReleaseContext(&hNewNtlmCtxt);

    goto cleanup;
}

/******************************************************************************/
DWORD
NtlmGetDomainNameFromResponse(
    IN const NTLM_RESPONSE_MESSAGE_V1* pRespMsg,
    IN DWORD dwRespMsgSize,
    IN BOOLEAN bUnicode,
    OUT PSTR* ppDomainName
    )
{
    DWORD dwError = LW_ERROR_SUCCESS;
    PCHAR pName = NULL;
    DWORD dwNameLength = 0;
    const BYTE* pBuffer = NULL;
    const NTLM_SEC_BUFFER* pSecBuffer = &pRespMsg->AuthTargetName;

    *ppDomainName = NULL;

    if (dwRespMsgSize < sizeof(*pRespMsg))
    {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (LW_LTOH32(pSecBuffer->dwOffset) +
        LW_LTOH16(pSecBuffer->usLength) > dwRespMsgSize)
    {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwNameLength = LW_LTOH16(pSecBuffer->usLength);
    pBuffer = LW_LTOH32(pSecBuffer->dwOffset) + (const BYTE*)pRespMsg;

    if (!bUnicode)
    {
        dwError = LwAllocateMemory(dwNameLength + 1, OUT_PPVOID(&pName));
        BAIL_ON_LSA_ERROR(dwError);

        memcpy(pName, pBuffer, dwNameLength);
    }
    else
    {
        dwError = NtlmGetCStringFromUnicodeBuffer(
                        pBuffer,
                        dwNameLength,
                        &pName);
        BAIL_ON_LSA_ERROR(dwError);
    }

cleanup:
    *ppDomainName = pName;
    return dwError;
error:
    LW_SAFE_FREE_STRING(pName);
    goto cleanup;
}

/******************************************************************************/
DWORD
NtlmGetWorkstationFromResponse(
    IN const NTLM_RESPONSE_MESSAGE_V1* pRespMsg,
    IN DWORD dwRespMsgSize,
    IN BOOLEAN bUnicode,
    OUT PSTR* ppWorkstation
    )
{
    DWORD dwError = LW_ERROR_SUCCESS;
    PCHAR pName = NULL;
    DWORD dwNameLength = 0;
    PBYTE pBuffer = NULL;
    const NTLM_SEC_BUFFER* pSecBuffer = &pRespMsg->Workstation;

    *ppWorkstation = NULL;

    if (dwRespMsgSize < sizeof(*pRespMsg))
    {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (LW_LTOH32(pSecBuffer->dwOffset) +
        LW_LTOH16(pSecBuffer->usLength) > dwRespMsgSize)
    {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwNameLength = LW_LTOH16(pSecBuffer->usLength);
    pBuffer = LW_LTOH32(pSecBuffer->dwOffset) + (PBYTE)pRespMsg;

    if (!bUnicode)
    {
        dwError = LwAllocateMemory(dwNameLength + 1, OUT_PPVOID(&pName));
        BAIL_ON_LSA_ERROR(dwError);

        memcpy(pName, pBuffer, dwNameLength);
    }
    else
    {
        dwError = NtlmGetCStringFromUnicodeBuffer(
                        pBuffer,
                        dwNameLength,
                        &pName);
        BAIL_ON_LSA_ERROR(dwError);
    }

cleanup:
    *ppWorkstation = pName;
    return dwError;
error:
    LW_SAFE_FREE_STRING(pName);
    goto cleanup;
}


static
DWORD
NtlmGetLocalGuestAccountSid(
    OUT PSTR* ppszGuestSid
    )
{
    DWORD dwError = LW_ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PLW_MAP_SECURITY_CONTEXT pContext = NULL;
    PSID pGuestSid = NULL;
    PSTR pszGuestSid = NULL;

    ntStatus = LwMapSecurityCreateContext(&pContext);
    dwError = LwNtStatusToWin32Error(ntStatus);
    BAIL_ON_LSA_ERROR(dwError);

    ntStatus = LwMapSecurityGetLocalGuestAccountSid(pContext, &pGuestSid);
    dwError = LwNtStatusToWin32Error(ntStatus);
    BAIL_ON_LSA_ERROR(dwError);

    ntStatus = RtlAllocateCStringFromSid(&pszGuestSid, pGuestSid);
    dwError = LwNtStatusToWin32Error(ntStatus);
    BAIL_ON_LSA_ERROR(dwError);

cleanup:

    if (pContext && pGuestSid)
    {
        LwMapSecurityFreeSid(pContext, &pGuestSid);
    }
    LwMapSecurityFreeContext(&pContext);

    *ppszGuestSid = pszGuestSid;

    return dwError;

error:

    LW_SAFE_FREE_STRING(pszGuestSid);

    goto cleanup;
}


/*
local variables:
mode: c
c-basic-offset: 4
indent-tabs-mode: nil
tab-width: 4
end:
*/
