/* -*- 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:
 *
 *        session_setup.c
 *
 * Abstract:
 *
 *        Likewise IO (LWIO) - SRV
 *
 *        Protocols API - SMBV2
 *
 *        Session Setup
 *
 * Authors: Sriram Nambakam (snambakam@likewise.com)
 *
 */
#include "includes.h"

static
VOID
SrvLogSessionSetup2Result(
    PSRV_EXEC_CONTEXT pExecContext,
    NTSTATUS Status
    )
{
    if (!NT_SUCCESS(Status))
    {
        SrvEventSessionSetupFailure(
            pExecContext->pConnection->pClientAddress,
            Status);
    }
}

NTSTATUS
SrvProcessSessionSetup_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PLWIO_SRV_CONNECTION pConnection = pExecContext->pConnection;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2      = pExecContext->pProtocolContext;
    ULONG                      iMsg          = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest   = &pCtxSmb2->pRequests[iMsg];
    PSRV_MESSAGE_SMB_V2        pSmbResponse  = &pCtxSmb2->pResponses[iMsg];
    PSMB2_SESSION_SETUP_REQUEST_HEADER pSessionSetupHeader = NULL;// Do not free
    PBYTE       pSecurityBlob             = NULL; // Do not free
    ULONG       ulSecurityBlobLen         = 0;
    PBYTE       pReplySecurityBlob        = NULL;
    ULONG       ulReplySecurityBlobLength = 0;
    PSTR        pszClientPrincipalName    = NULL;
    PLWIO_SRV_SESSION_2        pSession     = NULL;
    PLWIO_SRV_SESSION_2        pSessionPrev = NULL;
    PBYTE pOutBuffer       = pSmbResponse->pBuffer;
    ULONG ulBytesAvailable = pSmbResponse->ulBytesAvailable;
    ULONG ulOffset    = 0;
    ULONG ulBytesUsed = 0;
    ULONG ulTotalBytesUsed = 0;
    LW_MAP_SECURITY_GSS_CONTEXT hContextHandle = NULL;
    BOOLEAN bLoggedInAsGuest = FALSE;
    BOOLEAN gssLocked = FALSE;
    PWSTR pDomain = NULL;
    PWSTR pUser = NULL;

    if (pExecContext->bInline)
    {
        SrvScheduleExecContext(pExecContext);

        ntStatus = STATUS_PENDING;

        goto cleanup;
    }

    ntStatus = SMB2UnmarshallSessionSetup(
                    pSmbRequest,
                    &pSessionSetupHeader,
                    &pSecurityBlob,
                    &ulSecurityBlobLen);
    BAIL_ON_NT_STATUS(ntStatus);

    if (pSmbRequest->pHeader->ullSessionId)
    {
        ntStatus = SrvConnection2FindSession_SMB_V2(
                        pCtxSmb2,
                        pConnection,
                        pSmbRequest->pHeader->ullSessionId,
                        &pSession);
        BAIL_ON_NT_STATUS(ntStatus);

        SRV_LOG_VERBOSE(
                pExecContext->pLogContext,
                SMB_PROTOCOL_VERSION_2,
                pSmbRequest->pHeader->command,
                "Using existing session: "
                "command(%u),uid(%lu),cmd-seq(%llu),pid(%u),tid(%u),"
                "credits(%u),flags(0x%x),chain-offset(%u)",
                pSmbRequest->pHeader->command,
                (long long)pSmbRequest->pHeader->ullSessionId,
                (long long)pSmbRequest->pHeader->ullCommandSequence,
                pSmbRequest->pHeader->ulPid,
                pSmbRequest->pHeader->ulTid,
                pSmbRequest->pHeader->Credits,
                pSmbRequest->pHeader->ulFlags,
                pSmbRequest->pHeader->ulChainOffset);
    
        if (pSessionSetupHeader->ullPrevSessionId)
        {
            SrvSession2Lookup(
                    pSessionSetupHeader->ullPrevSessionId,
                    &pSessionPrev);
        }
    }
    else
    {
        ntStatus = SrvConnection2CreateSession(
                        pConnection,
                        &pCtxSmb2->pSession);
        BAIL_ON_NT_STATUS(ntStatus);

        pSession = SrvSession2Acquire(pCtxSmb2->pSession);

        SRV_LOG_VERBOSE(
                pExecContext->pLogContext,
                SMB_PROTOCOL_VERSION_2,
                pSmbRequest->pHeader->command,
                "Created session : "
                "command(%u),uid(%lu),cmd-seq(%llu),pid(%u),tid(%u),"
                "credits(%u),flags(0x%x),chain-offset(%u)",
                pSmbRequest->pHeader->command,
                (long long)pSmbRequest->pHeader->ullSessionId,
                (long long)pSmbRequest->pHeader->ullCommandSequence,
                pSmbRequest->pHeader->ulPid,
                pSmbRequest->pHeader->ulTid,
                pSmbRequest->pHeader->Credits,
                pSmbRequest->pHeader->ulFlags,
                pSmbRequest->pHeader->ulChainOffset);
    }

    LWIO_LOCK_MUTEX(gssLocked, &pSession->GssMutex);

    if (pSession->hGssNegotiate == NULL)
    {
        ntStatus = SrvGssBeginNegotiate(&pSession->hGssNegotiate);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    ntStatus = SrvGssNegotiate(
                    pSession->hGssNegotiate,
                    pSecurityBlob,
                    ulSecurityBlobLen,
                    &pReplySecurityBlob,
                    &ulReplySecurityBlobLength);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMB2MarshalHeader(
                    pOutBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    COM2_SESSION_SETUP,
                    pSmbRequest->CreditCharge,
                    pExecContext->usCreditsGranted,
                    pSmbRequest->pHeader->ulPid,
                    pSmbRequest->pHeader->ullCommandSequence,
                    pSmbRequest->pHeader->ulTid,
                    pSession->ullUid,
                    0LL, /* Async Id   */
                    STATUS_SUCCESS,
                    TRUE,
                    LwIsSetFlag(
                        pSmbRequest->pHeader->ulFlags,
                        SMB2_FLAGS_RELATED_OPERATION),
                    &pSmbResponse->pHeader,
                    &pSmbResponse->ulHeaderSize);
    BAIL_ON_NT_STATUS(ntStatus);

    pOutBuffer       += pSmbResponse->ulHeaderSize;
    ulOffset         += pSmbResponse->ulHeaderSize;
    ulBytesAvailable -= pSmbResponse->ulHeaderSize;
    ulTotalBytesUsed += pSmbResponse->ulHeaderSize;

    if (!SrvGssNegotiateIsComplete(pSession->hGssNegotiate))
    {
        pSmbResponse->pHeader->error = STATUS_MORE_PROCESSING_REQUIRED;
    }
    else
    {
        ntStatus = SrvSetStatSession2Info(pExecContext, pSession);
        BAIL_ON_NT_STATUS(ntStatus);

        /*
         * Session Setup on an existing session does not change the
         * session key.
         */
        ntStatus = SrvGssGetSessionDetails(
                         pSession->hGssNegotiate,
                         pSession->pSessionKey ? NULL : &pSession->pSessionKey,
                         pSession->pSessionKey ? NULL : 
                         &pSession->ulSessionKeyLength,
                         &pszClientPrincipalName,
                         &hContextHandle);

        SRV_NF_LOG_TRACE("status 0x%x, sessionId 0x%lx",
            ntStatus,
            (long long)pSmbRequest->pHeader->ullSessionId);

        memset(&pSession->SigningKey, 0, sizeof(pSession->SigningKey));
        memcpy(
            &pSession->SigningKey,
            pSession->pSessionKey,
            SMB_MIN(
                pSession->ulSessionKeyLength,
                sizeof(pSession->SigningKey)));

        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SrvIoSecCreateSecurityContextFromGssContext(
                       &pSession->pIoSecurityContext,
                       &bLoggedInAsGuest,
                       hContextHandle,
                       pszClientPrincipalName);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SrvIoGetNtNameFromIoSecurityContext(
                        pSession->pIoSecurityContext,
                        &pDomain,
                        &pUser);
        BAIL_ON_NT_STATUS(ntStatus);

        SrvSession2SetNtName(pSession, &pDomain, &pUser);

        // The session key for this connection has to come from an
        // authenticated session

        if (bLoggedInAsGuest)
        {
            SrvSession2SetUserFlags(pSession, SMB_SESSION_FLAG_GUEST);

            SRV_LOG_VERBOSE(
                    pExecContext->pLogContext,
                    SMB_PROTOCOL_VERSION_2,
                    pSmbRequest->pHeader->command,
                    "Session info: "
                    "command(%u),uid(%lu),cmd-seq(%llu),pid(%u),tid(%u),"
                    "credits(%u),flags(0x%x),chain-offset(%u),session-id(%llu) "
                    "is logged in as guest",
                    pSmbRequest->pHeader->command,
                    (long long)pSmbRequest->pHeader->ullSessionId,
                    (long long)pSmbRequest->pHeader->ullCommandSequence,
                    pSmbRequest->pHeader->ulPid,
                    pSmbRequest->pHeader->ulTid,
                    pSmbRequest->pHeader->Credits,
                    pSmbRequest->pHeader->ulFlags,
                    pSmbRequest->pHeader->ulChainOffset,
                    (long long)pSession->ullUid);
        }
        else
        {
            pSession->bRequireSecuritySignatures =
                SrvConnectionShouldSign(pConnection);
        }

        if ((pSessionSetupHeader->ucSecurityMode &
             SMB2_NEGOTIATE_SECURITY_FLAG_SIGNING_REQUIRED) != 0)
        {
            pSession->bRequireSecuritySignatures = TRUE;
        }

        // Go ahead and close out this GSS negotiate state so we can
        // handle another Session setup.

        SrvGssEndNegotiate(pSession->hGssNegotiate);

        pSession->hGssNegotiate = NULL;

        if (pszClientPrincipalName)
        {
            SRV_LOG_VERBOSE(
                    pExecContext->pLogContext,
                    SMB_PROTOCOL_VERSION_2,
                    pSmbRequest->pHeader->command,
                    "Session info: "
                    "command(%u),uid(%lu),cmd-seq(%llu),pid(%u),tid(%u),"
                    "credits(%u),flags(0x%x),chain-offset(%u),"
                    "session-id(%llu),user-principal(%s)",
                    pSmbRequest->pHeader->command,
                    (long long)pSmbRequest->pHeader->ullSessionId,
                    (long long)pSmbRequest->pHeader->ullCommandSequence,
                    pSmbRequest->pHeader->ulPid,
                    pSmbRequest->pHeader->ulTid,
                    pSmbRequest->pHeader->Credits,
                    pSmbRequest->pHeader->ulFlags,
                    pSmbRequest->pHeader->ulChainOffset,
                    (long long)pSession->ullUid,
                    pszClientPrincipalName);

            ntStatus = SrvSession2SetPrincipalName(
                            pSession,
                            pszClientPrincipalName);
            BAIL_ON_NT_STATUS(ntStatus);
        }

        pSmbResponse->pHeader->ullSessionId = pSession->ullUid;

        if (pConnection->pOEMConnection)
        {
            SRV_OEM_SESSION_ID sessionId =
            {
                .ver = SMB_PROTOCOL_VERSION_2,
                .id  = { .ullUid = pSession->ullUid }
            };

            ntStatus = SrvOEMCreateSession(
                            pConnection->pOEMConnection,
                            pConnection->ulOEMConnectionLength,
                            &sessionId,
                            pSession->pIoSecurityContext,
                            pConnection->clientProperties.pwszNativeOS,
                            &pSession->pOEMSession,
                            &pSession->ulOEMSessionLength);
            BAIL_ON_NT_STATUS(ntStatus);
        }

        SrvConnectionSetState(pConnection, LWIO_SRV_CONN_STATE_READY);
    }

    ntStatus = SMB2MarshalSessionSetup(
                    pOutBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    bLoggedInAsGuest ? 0x1 : 0x0,
                    pReplySecurityBlob,
                    ulReplySecurityBlobLength,
                    &ulBytesUsed);
    BAIL_ON_NT_STATUS(ntStatus);

    // pOutBuffer       += ulBytesUsed;
    // ulOffset         += ulBytesUsed;
    // ulBytesAvailable -= ulBytesUsed;
    ulTotalBytesUsed += ulBytesUsed;

    pSmbResponse->ulMessageSize = ulTotalBytesUsed;
 
    // If the previous session is for the same user via different
    // session then we must kill the previous session.

    if (pSessionPrev && pSessionPrev->pIoSecurityContext &&
        pSessionPrev->hGssNegotiate == NULL &&
        pSessionPrev->ullUid != pSession->ullUid &&
        SrvIoSecurityContextUserEqual(
            pSessionPrev->pIoSecurityContext,
            pSession->pIoSecurityContext))
    {
        SRV_NF_LOG_TRACE("new session %lu preempting previous session %lu",
             pSession->ullUid, pSessionPrev->ullUid);
        
        SrvSession2Rundown(pSessionPrev);
    }

cleanup:

    LWIO_UNLOCK_MUTEX(gssLocked, &pSession->GssMutex);

    if (pSession)
    {
        SrvSession2Release(pSession);
    }
    
    if (pSessionPrev)
    {
        SrvSession2Release(pSessionPrev);
    }
    
    SRV_SAFE_FREE_MEMORY(pReplySecurityBlob);
    SRV_SAFE_FREE_MEMORY(pszClientPrincipalName);
    RTL_FREE(&pDomain);
    RTL_FREE(&pUser);

    if (!NT_SUCCESS(ntStatus))
    {
        SrvLogSessionSetup2Result(pExecContext, ntStatus);
        ntStatus = SrvGssCollapseLogonFailureStatus(ntStatus);
    }

    return ntStatus;

error:

    /* If a session exists, rundown the session */
    if (!pSession && pSmbRequest->pHeader->ullSessionId)
    {
        (void) SrvConnection2FindSession(
                pConnection,
                pSmbRequest->pHeader->ullSessionId,
                &pSession);
    }

    if (pSession && pSession->hGssNegotiate)
    {
        SrvGssEndNegotiate(pSession->hGssNegotiate);

        pSession->hGssNegotiate = NULL;
    }

    LWIO_UNLOCK_MUTEX(gssLocked, &pSession->GssMutex);

    if (pSession)
    {
        SrvSession2Rundown(pSession);
    }

    if (ulTotalBytesUsed)
    {
        pSmbResponse->pHeader      = NULL;
        pSmbResponse->ulHeaderSize = 0;
        memset(pSmbResponse->pBuffer, 0, ulTotalBytesUsed);
    }

    pSmbResponse->ulMessageSize = 0;

    goto cleanup;
}
