/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright Likewise Software
 * 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:
 *
 *        srvgss.c
 *
 * Abstract:
 *
 *        Likewise IO (LWIO) - SRV
 *
 *        Elements
 *
 *        GSS Support
 *
 * Authors: Sriram Nambakam (snambakam@likewise.com)
 *          Gerald Carter <gcarter@likewise.com>
 */

#include "includes.h"
#include "srvgss_p.h"

#define NTLMSSP_SIGNATURE "NTLMSSP"

typedef enum {
    SRVGSS_TOKEN_COMPLETION_STATE_UNKNOWN,
    SRVGSS_TOKEN_COMPLETION_STATE_PARTIAL,
    SRVGSS_TOKEN_COMPLETION_STATE_COMPLETE,
} SRVGSS_TOKEN_COMPLETION_STATE, *PSRVGSS_TOKEN_COMPLETION_STATE;

static
NTSTATUS
SrvGssCheckCompleteToken(
    IN ULONG Size,
    IN PVOID Data,
    OUT OPTIONAL PLONG TotalLengthHint,
    OUT PSRVGSS_TOKEN_COMPLETION_STATE
    );

BOOLEAN
SrvGssNegotiateIsComplete(
    HANDLE hGssNegotiate
    )
{
    PSRV_GSS_NEGOTIATE_CONTEXT pGssNegotiate = (PSRV_GSS_NEGOTIATE_CONTEXT)hGssNegotiate;
    return pGssNegotiate->state == SRV_GSS_CONTEXT_STATE_COMPLETE;
}

static
NTSTATUS
SrvGssSecErrorToNtStatus(
    ULONG MajorStatus,
    ULONG MinorStatus
    )
{
    switch (MajorStatus)
    {
    default:
        return STATUS_LOGON_FAILURE;
    case GSS_S_FAILURE:
        switch (MinorStatus)
        {
        default:
            return STATUS_LOGON_FAILURE;
        case (KRB5KRB_AP_ERR_SKEW & 0xFFFFFFFF):
            return STATUS_TIME_DIFFERENCE_AT_DC;
        }
    }
}

static
NTSTATUS
SrvGssGetSessionKey(
    gss_ctx_id_t Context,
    PBYTE* ppSessionKey,
    PDWORD pdwSessionKeyLength
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PBYTE pSessionKey = NULL;
    DWORD dwSessionKeyLength = 0;
    OM_uint32 gssMajor = GSS_S_COMPLETE;
    OM_uint32 gssMinor = 0;
    gss_buffer_set_t sessionKey = NULL;

    gssMajor = gss_inquire_sec_context_by_oid(
                    &gssMinor,
                    Context,
                    GSS_C_INQ_SSPI_SESSION_KEY,
                    &sessionKey);
    if (gssMajor != GSS_S_COMPLETE)
    {
        srv_display_status("gss_inquire_sec_context_by_oid",
                           GSS_C_INQ_SSPI_SESSION_KEY, gssMajor, gssMinor);
        // TODO - error code conversion
        status = gssMajor;
        BAIL_ON_LWIO_ERROR(status);
    }

    // The key is in element 0 and the key type OID is in element 1
    if (!sessionKey ||
        (sessionKey->count < 1) ||
        !sessionKey->elements[0].value ||
        (0 == sessionKey->elements[0].length))
    {
        LWIO_ASSERT_MSG(FALSE, "Invalid session key");
        status = STATUS_ASSERTION_FAILURE;
        BAIL_ON_LWIO_ERROR(status);
    }

    status = LW_RTL_ALLOCATE(&pSessionKey, BYTE, sessionKey->elements[0].length);
    BAIL_ON_LWIO_ERROR(status);

    memcpy(pSessionKey, sessionKey->elements[0].value, sessionKey->elements[0].length);
    dwSessionKeyLength = sessionKey->elements[0].length;

cleanup:
    gss_release_buffer_set(&gssMinor, &sessionKey);

    *ppSessionKey = pSessionKey;
    *pdwSessionKeyLength = dwSessionKeyLength;

    return status;

error:
    LWIO_SAFE_FREE_MEMORY(pSessionKey);
    dwSessionKeyLength = 0;

    goto cleanup;
}


static
NTSTATUS
SrvGssGetClientPrincipalName(
    gss_ctx_id_t Context,
    PSTR *ppszClientName
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    OM_uint32 gssMajor = GSS_S_COMPLETE;
    OM_uint32 gssMinor = 0;
    gss_buffer_desc nameBuffer = {0};
    gss_buffer_set_t ClientName = NULL;
    PSTR pszClientPrincipalName = NULL;
    gss_name_t initiatorName = {0};
    gss_buffer_desc clientNameBuffer = {0};
    gss_OID nameOid = {0};

    /* Try the old way first */

    gssMajor = gss_inquire_context(
                   &gssMinor,
                   Context,
                   &initiatorName,
                   NULL,
                   NULL,
                   NULL,
                   NULL,
                   NULL,
                   NULL);
    if (gssMajor == GSS_S_COMPLETE)
    {
        gssMajor = gss_display_name(
                       &gssMinor,
                       initiatorName,
                       &clientNameBuffer,
                       &nameOid);
        BAIL_ON_SEC_ERROR(gssMajor);

        nameBuffer = clientNameBuffer;
    }
    else
    {
        /* Fallback to using the newer inquire_by_oid() method */

        gssMajor = gss_inquire_sec_context_by_oid(
                        &gssMinor,
                        Context,
                        GSS_C_NT_STRING_UID_NAME,
                        &ClientName);
        BAIL_ON_SEC_ERROR(gssMajor);

        if (!ClientName || (ClientName->count == 0))
        {
            ntStatus = STATUS_NONE_MAPPED;
            BAIL_ON_NT_STATUS(ntStatus);
        }

        nameBuffer = ClientName->elements[0];
    }

    ntStatus = SrvAllocateMemory(
                   (nameBuffer.length + 1) * sizeof(CHAR),
                   (PVOID*)&pszClientPrincipalName);
    BAIL_ON_NT_STATUS(ntStatus);

    memcpy(pszClientPrincipalName, nameBuffer.value, nameBuffer.length);
    pszClientPrincipalName[nameBuffer.length] = '\0';

    *ppszClientName = pszClientPrincipalName;

cleanup:

    gss_release_buffer_set(&gssMinor, &ClientName);
    gss_release_name(&gssMinor, &initiatorName);
    gss_release_buffer(&gssMinor, &clientNameBuffer);

    return ntStatus;

sec_error:

    ntStatus = SrvGssSecErrorToNtStatus(gssMajor, gssMinor);

error:

    goto cleanup;
}

NTSTATUS
SrvGssGetSessionDetails(
    HANDLE hGssNegotiate,
    PBYTE* ppSessionKey,
    PULONG pulSessionKeyLength,
    PSTR* ppszClientPrincipalName,
    LW_MAP_SECURITY_GSS_CONTEXT* pContextHandle
    )
{
    NTSTATUS ntStatus = 0;
    PSRV_GSS_NEGOTIATE_CONTEXT pGssNegotiate = (PSRV_GSS_NEGOTIATE_CONTEXT)hGssNegotiate;
    PBYTE pSessionKey = NULL;
    DWORD dwSessionKeyLength = 0;
    PSTR pszClientPrincipalName = NULL;

    if (!SrvGssNegotiateIsComplete(hGssNegotiate))
    {
        ntStatus = STATUS_DATA_ERROR;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (ppSessionKey)
    {
        ntStatus = SrvGssGetSessionKey(
                        pGssNegotiate->GssContext,
                        &pSessionKey,
                        &dwSessionKeyLength);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (ppszClientPrincipalName)
    {
        ntStatus = SrvGssGetClientPrincipalName(
                       pGssNegotiate->GssContext,
                       &pszClientPrincipalName);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (ppszClientPrincipalName)
    {
        *ppszClientPrincipalName = pszClientPrincipalName;
    }

    if (ppSessionKey)
    {
        *ppSessionKey = pSessionKey;
        *pulSessionKeyLength = dwSessionKeyLength;
    }

    if (pContextHandle)
    {
        *pContextHandle = pGssNegotiate->GssContext;
    }

cleanup:

    return ntStatus;

error:

    if (ppSessionKey)
    {
        *ppSessionKey = NULL;
    }
    if (pulSessionKeyLength)
    {
        *pulSessionKeyLength = 0;
    }

    if (pSessionKey)
    {
        SrvFreeMemory(pSessionKey);
    }
    if (pszClientPrincipalName)
    {
        SrvFreeMemory(pszClientPrincipalName);
    }

    goto cleanup;
}

NTSTATUS
SrvGssBeginNegotiate(
    PHANDLE phGssResume
    )
{
    NTSTATUS ntStatus = 0;
    PSRV_GSS_NEGOTIATE_CONTEXT pGssNegotiate = NULL;

    ntStatus = SrvAllocateMemory(
                    sizeof(SRV_GSS_NEGOTIATE_CONTEXT),
                    (PVOID*)&pGssNegotiate);
    BAIL_ON_NT_STATUS(ntStatus);

    pGssNegotiate->state = SRV_GSS_CONTEXT_STATE_NEGOTIATE;

    pGssNegotiate->GssContext = GSS_C_NO_CONTEXT;

    pGssNegotiate->SecurityInputToken = NULL;
    pGssNegotiate->SecurityInputTokenLength = 0;
    pGssNegotiate->SecurityInputTokenConsumed = 0;

    *phGssResume = (HANDLE)pGssNegotiate;

cleanup:

    return ntStatus;

error:

    *phGssResume = NULL;

    if (pGssNegotiate)
    {
        SrvGssEndNegotiate((HANDLE)pGssNegotiate);
    }

    goto cleanup;
}

NTSTATUS
SrvGssNegotiate(
    HANDLE  hGssResume,
    PBYTE   pSecurityInputBlob,
    ULONG   ulSecurityInputBlobLen,
    PBYTE*  ppSecurityOutputBlob,
    ULONG*  pulSecurityOutputBloblen
    )
{
    NTSTATUS ntStatus = 0;
    PSRV_GSS_NEGOTIATE_CONTEXT pGssNegotiate = (PSRV_GSS_NEGOTIATE_CONTEXT)hGssResume;
    PBYTE pSecurityBlob = NULL;
    ULONG ulSecurityBlobLen = 0;

    switch(pGssNegotiate->state)
    {
        case SRV_GSS_CONTEXT_STATE_HINTS:

            ntStatus = SrvGssContinueNegotiate(
                            pGssNegotiate,
                            NULL,
                            0,
                            &pSecurityBlob,
                            &ulSecurityBlobLen);

            break;

        case SRV_GSS_CONTEXT_STATE_INITIAL:

            if (pSecurityInputBlob)
            {
                ntStatus = STATUS_INVALID_PARAMETER_3;
                BAIL_ON_NT_STATUS(ntStatus);
            }

            break;

        case SRV_GSS_CONTEXT_STATE_NEGOTIATE:

            if (!pSecurityInputBlob)
            {
                ntStatus = STATUS_INVALID_PARAMETER_3;
                BAIL_ON_NT_STATUS(ntStatus);
            }

            ntStatus = SrvGssContinueNegotiate(
                            pGssNegotiate,
                            pSecurityInputBlob,
                            ulSecurityInputBlobLen,
                            &pSecurityBlob,
                            &ulSecurityBlobLen);

            break;

        case SRV_GSS_CONTEXT_STATE_COMPLETE:

            break;

    }

    BAIL_ON_NT_STATUS(ntStatus);

error:

    *ppSecurityOutputBlob = pSecurityBlob;
    *pulSecurityOutputBloblen = ulSecurityBlobLen;

    return ntStatus;
}

VOID
SrvGssEndNegotiate(
    HANDLE hGssResume
    )
{
    ULONG ulMinorStatus = 0;
    PSRV_GSS_NEGOTIATE_CONTEXT pGssNegotiateContext = NULL;

    pGssNegotiateContext = (PSRV_GSS_NEGOTIATE_CONTEXT)hGssResume;

    if (pGssNegotiateContext->GssContext != GSS_C_NO_CONTEXT)
    {
        gss_delete_sec_context(
                        &ulMinorStatus,
                        &pGssNegotiateContext->GssContext,
                        GSS_C_NO_BUFFER);
    }

    if (pGssNegotiateContext->SecurityInputToken)
    {
        SrvFreeMemory(pGssNegotiateContext->SecurityInputToken);
    }

    SrvFreeMemory(pGssNegotiateContext);
}

NTSTATUS
SrvGssCollapseLogonFailureStatus(
    NTSTATUS Status
    )
{
    switch (Status)
    {
    case STATUS_ACCESS_DENIED:
    case STATUS_WRONG_PASSWORD:
    case STATUS_PASSWORD_EXPIRED:
    case STATUS_PASSWORD_MUST_CHANGE:
    case STATUS_NO_SUCH_USER:
    case STATUS_NO_SUCH_GROUP:
    case STATUS_TIME_DIFFERENCE_AT_DC:
        return STATUS_LOGON_FAILURE;
    default:
        return Status;
    }
}

static
NTSTATUS
SrvGssContinueNegotiate(
    PSRV_GSS_NEGOTIATE_CONTEXT pGssNegotiate,
    PBYTE                      pSecurityInputBlob,
    ULONG                      ulSecurityInputBlobLen,
    PBYTE*                     ppSecurityOutputBlob,
    ULONG*                     pulSecurityOutputBloblen
    )
{
    NTSTATUS ntStatus = 0;
    ULONG ulMajorStatus = 0;
    ULONG ulMinorStatus = 0;
    gss_buffer_desc input_desc = {0};
    gss_buffer_desc output_desc = {0};
    ULONG ret_flags = 0;
    PBYTE pSecurityBlob = NULL;
    ULONG ulSecurityBlobLength = 0;
    SRVGSS_TOKEN_COMPLETION_STATE inputTokenState = SRVGSS_TOKEN_COMPLETION_STATE_UNKNOWN;
    LONG tokenTotalLength = 0;
    PBYTE securityInputBlob = pSecurityInputBlob;
    ULONG securityInputBlobLength = ulSecurityInputBlobLen;

    static gss_OID_desc gss_spnego_mech_oid_desc =
                              {6, (void *)"\x2b\x06\x01\x05\x05\x02"};
    static gss_OID gss_spnego_mech_oid = &gss_spnego_mech_oid_desc;

    if (!pGssNegotiate->SecurityInputToken)
    {
        // Fresh state
        ntStatus = SrvGssCheckCompleteToken(
                       securityInputBlobLength,
                       securityInputBlob,
                       &tokenTotalLength,
                       &inputTokenState);
        BAIL_ON_NT_STATUS(ntStatus);

        switch (inputTokenState)
        {
            case SRVGSS_TOKEN_COMPLETION_STATE_UNKNOWN:
                ntStatus = STATUS_DATA_ERROR;
                break;

            case SRVGSS_TOKEN_COMPLETION_STATE_PARTIAL:
                if (tokenTotalLength == -1)
                {
                    ntStatus = STATUS_BUFFER_TOO_SMALL;
                    BAIL_ON_NT_STATUS(ntStatus);
                }

                pGssNegotiate->SecurityInputTokenLength = tokenTotalLength;

                ntStatus = SrvAllocateMemory(
                               pGssNegotiate->SecurityInputTokenLength,
                               OUT_PPVOID(&pGssNegotiate->SecurityInputToken));
                BAIL_ON_NT_STATUS(ntStatus);

                LwRtlCopyMemory(
                    pGssNegotiate->SecurityInputToken + pGssNegotiate->SecurityInputTokenConsumed,
                    securityInputBlob,
                    securityInputBlobLength);

                pGssNegotiate->SecurityInputTokenConsumed += securityInputBlobLength;

                ntStatus = STATUS_SUCCESS;
                goto cleanup;

                break;

            case SRVGSS_TOKEN_COMPLETION_STATE_COMPLETE:
                ntStatus = STATUS_SUCCESS;
                break;
        }
        BAIL_ON_NT_STATUS(ntStatus);
    }
    else
    {
        // Previous security blob accumulation

        if (pGssNegotiate->SecurityInputTokenLength <
            (pGssNegotiate->SecurityInputTokenConsumed + securityInputBlobLength))
        {
            ntStatus = STATUS_DATA_ERROR;
            BAIL_ON_NT_STATUS(ntStatus);
        }

        LwRtlCopyMemory(
            pGssNegotiate->SecurityInputToken + pGssNegotiate->SecurityInputTokenConsumed,
            securityInputBlob,
            securityInputBlobLength);

        pGssNegotiate->SecurityInputTokenConsumed += securityInputBlobLength;

        // Fresh state
        ntStatus = SrvGssCheckCompleteToken(
                       pGssNegotiate->SecurityInputTokenConsumed,
                       pGssNegotiate->SecurityInputToken,
                       NULL,
                       &inputTokenState);
        BAIL_ON_NT_STATUS(ntStatus);

        switch (inputTokenState)
        {
            case SRVGSS_TOKEN_COMPLETION_STATE_UNKNOWN:
                ntStatus = STATUS_DATA_ERROR;
                break;

            case SRVGSS_TOKEN_COMPLETION_STATE_PARTIAL:
                ntStatus = STATUS_SUCCESS;
                goto cleanup;

                break;

            case SRVGSS_TOKEN_COMPLETION_STATE_COMPLETE:
                securityInputBlob = pGssNegotiate->SecurityInputToken;
                securityInputBlobLength = pGssNegotiate->SecurityInputTokenConsumed;

                ntStatus = STATUS_SUCCESS;
                break;
        }
        BAIL_ON_NT_STATUS(ntStatus);
    }

    input_desc.length = securityInputBlobLength;
    input_desc.value = securityInputBlob;

    ulMajorStatus = gss_accept_sec_context(
                        (OM_uint32 *)&ulMinorStatus,
                        &pGssNegotiate->GssContext,
                        NULL,
                        &input_desc,
                        NULL,
                        NULL,
                        &gss_spnego_mech_oid,
                        &output_desc,
                        &ret_flags,
                        NULL,
                        NULL);

    srv_display_status("gss_accept_sec_context",
                       gss_spnego_mech_oid, ulMajorStatus, ulMinorStatus);

    BAIL_ON_SEC_ERROR(ulMajorStatus);

    switch (ulMajorStatus)
    {
        case GSS_S_CONTINUE_NEEDED:

            pGssNegotiate->state = SRV_GSS_CONTEXT_STATE_NEGOTIATE;

            break;

        case GSS_S_COMPLETE:

            pGssNegotiate->state = SRV_GSS_CONTEXT_STATE_COMPLETE;

            break;

        default:

            ntStatus = LWIO_ERROR_GSS;
            BAIL_ON_NT_STATUS(ntStatus);

            break;
    }

    if (output_desc.length)
    {
        ntStatus = SrvAllocateMemory(
                        output_desc.length,
                        (PVOID*)&pSecurityBlob);
        BAIL_ON_NT_STATUS(ntStatus);

        memcpy(pSecurityBlob, output_desc.value, output_desc.length);

        ulSecurityBlobLength = output_desc.length;
    }

    *ppSecurityOutputBlob = pSecurityBlob;
    *pulSecurityOutputBloblen = ulSecurityBlobLength;

cleanup:

    gss_release_buffer(&ulMinorStatus, &output_desc);

    return ntStatus;

sec_error:

    ntStatus = SrvGssSecErrorToNtStatus(ulMajorStatus, ulMinorStatus);

error:

    *ppSecurityOutputBlob = NULL;
    *pulSecurityOutputBloblen = 0;

    if (pSecurityBlob)
    {
        SrvFreeMemory(pSecurityBlob);
    }

    goto cleanup;
}

static
void
srv_display_status(
    PCSTR         pszId,
    const gss_OID oid,
    OM_uint32     maj_stat,
    OM_uint32     min_stat
    )
{
    LW_RTL_LOG_LEVEL level;

    switch (maj_stat)
    {
#ifdef WIN32
        case SEC_E_OK:
        case SEC_I_CONTINUE_NEEDED:
#else
        case GSS_S_COMPLETE:
        case GSS_S_CONTINUE_NEEDED:
#endif
            level = LW_RTL_LOG_LEVEL_VERBOSE;
            break;
        default:
            level = LW_RTL_LOG_LEVEL_ERROR;
            break;
    }
    
    if ( LwRtlLogGetLevel() >= level )
    {
        srv_display_status_1(pszId, oid, maj_stat, GSS_C_GSS_CODE, level);
        srv_display_status_1(pszId, oid, min_stat, GSS_C_MECH_CODE, level);
    }
}

static
void
srv_display_status_1(
    PCSTR            pszId,
    const gss_OID    oid,
    OM_uint32        code,
    int              type,
    LW_RTL_LOG_LEVEL level
    )
{
    OM_uint32 min_stat;
    gss_buffer_desc msg;
    OM_uint32 msg_ctx;

    if ( code == 0 )
    {
        return;
    }

    msg_ctx = 0;
    while (1)
    {
        (void) gss_display_status(&min_stat, code, type, oid, &msg_ctx, &msg);

        LWIO_LOG_AT(
            level,
            "GSS-API %s calling %s, status = 0x%x: %s\n",
            level == LW_RTL_LOG_LEVEL_VERBOSE ? "success" : "error",
            pszId,
            code,
            (char *)msg.value);

        (void) gss_release_buffer(&min_stat, &msg);

        if (!msg_ctx)
            break;
    }
}

/**
 * Caller must not free the memory since it is reused
 **/

NTSTATUS
SrvGssNegHints(
    PBYTE *ppNegHints,
    ULONG *pulNegHintsLength
    )
{
    NTSTATUS ntStatus      = STATUS_SUCCESS;
    HANDLE   hGssNegotiate = NULL;
    BOOLEAN  bInLock       = FALSE;

    LWIO_LOCK_MUTEX(bInLock, gSrvElements.pMutex);

    if (!gSrvElements.pHintsBuffer)
    {
        ntStatus = SrvGssBeginNegotiate(&hGssNegotiate);
        BAIL_ON_NT_STATUS(ntStatus);

        /* MIT Krb5 1.7 returns the NegHints blob if you call
           gss_accept_sec_context() with a NULL input buffer */

        ((PSRV_GSS_NEGOTIATE_CONTEXT)hGssNegotiate)->state = SRV_GSS_CONTEXT_STATE_HINTS;
        ntStatus = SrvGssNegotiate(
                       hGssNegotiate,
                       NULL,
                       0,
                       &gSrvElements.pHintsBuffer,
                       &gSrvElements.ulHintsLength);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    *ppNegHints        = gSrvElements.pHintsBuffer;
    *pulNegHintsLength = gSrvElements.ulHintsLength;

cleanup:

    LWIO_UNLOCK_MUTEX(bInLock, gSrvElements.pMutex);

    if (hGssNegotiate)
    {
        SrvGssEndNegotiate(hGssNegotiate);
    }

    return ntStatus;

error:

    *ppNegHints = NULL;
    *pulNegHintsLength = 0;

    goto cleanup;
}

//
// Return values
// pCompletionState:
//   * SRVGSS_TOKEN_COMPLETION_STATE_UNKNOWN (error)
//     - "Length == -1" (error)
//   * SRVGSS_TOKEN_COMPLETION_STATE_PARTIAL (incomplete input token)
//     - "Length == -1" (unknown) or "Length > 0" (expected token size)
//   * SRVGSS_TOKEN_COMPLETION_STATE_COMPLETE (expected length indicates a complete token)
//     - "Length >= 0"  (complete token expected size)
//

static
NTSTATUS
SrvGssCheckCompleteToken(
    IN ULONG Size,
    IN PVOID Data,
    OUT OPTIONAL PLONG TotalLengthHint,
    OUT PSRVGSS_TOKEN_COMPLETION_STATE pCompletionState
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    SRVGSS_TOKEN_COMPLETION_STATE completionState = SRVGSS_TOKEN_COMPLETION_STATE_UNKNOWN;
    PBYTE ptr = Data;
    LONG length = -1;
    DWORD lengthOfLength = 0;
    DWORD i = 0;

    if (!LW_IS_BOTH_OR_NEITHER(Data, Size))
    {
        status = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(status);
    }

    if (Size == 0)
    {
        // According to MIT a 0 length token is legitmate for
        // SPNEGO (gssint_get_mech_type in g_glue.c).
        completionState = SRVGSS_TOKEN_COMPLETION_STATE_COMPLETE;
        length = 0;
        goto error;
    }

    if (ptr[0] == 0x60 || ptr[0] == 0x6e || ptr[0] == 0xa1)
    {
        // Standard GSS tokens are ASN.1 encoded using DER rules
        // and begin with an ASN.1 tag of 0x60 followed by a length.
        // You can also receive a raw AP-REQ for krb5 which has an
        // initial ASN.1 tag of 0x6e followed by a length.  GSS-NTLMSSP
        // Type 3 (AUTH) has an initial ASN.1 tag of 0xa1 followed by
        // a length.

        if (Size < 2)
        {
            completionState = SRVGSS_TOKEN_COMPLETION_STATE_PARTIAL;
            length = -1;
            goto error;
        }

        if (ptr[1] & 0x80)
        {
            lengthOfLength = ptr[1] & 0x7f;
            if (lengthOfLength > 4)
            {
                // MIT rejects lengths that take more than 4 bytes
                status = STATUS_DATA_ERROR;
                BAIL_ON_NT_STATUS(status);
            }

            if (Size < 2 + lengthOfLength)
            {
                completionState = SRVGSS_TOKEN_COMPLETION_STATE_PARTIAL;
                length = -1;
                goto error;
            }

            length = ptr[2];

            for (i = 1 ; i < lengthOfLength ; i++)
            {
                length = length << 8;
                length += ptr[i+2];
            }

            length += 2 + lengthOfLength;
        }
        else
        {
            length = 2 + ptr[1];
        }

        if (Size < length)
        {
            completionState = SRVGSS_TOKEN_COMPLETION_STATE_PARTIAL;
        }
        else
        {
            completionState = SRVGSS_TOKEN_COMPLETION_STATE_COMPLETE;
        }
    }
    else if (Size >= sizeof(NTLMSSP_SIGNATURE) &&
             memcmp(Data, NTLMSSP_SIGNATURE, sizeof(NTLMSSP_SIGNATURE)) == 0)
    {
        // NTLM should never be fragmented so assuming complete
        // since calculating the actual length requires parsing
        // the entire token.
        completionState = SRVGSS_TOKEN_COMPLETION_STATE_COMPLETE;
        length = Size;
    }

error:

    if (TotalLengthHint)
    {
        *TotalLengthHint = length;
    }

    *pCompletionState = completionState;

    return status;
}
