/* -*- 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 SMB Subsystem (LWIO)
 *
 *        SMB SESSION SETUP "wire" API
 *
 * Author: Kaya Bekiroglu (kaya@likewisesoftware.com)
 *
 * @todo: support big endian architectures
 * @todo: support AndX chain parsing
 * @todo: add error logging code
 * @todo: switch to NT error codes where appropriate
 */

#include "includes.h"

/* ASCII is not supported */
/* @todo: test alignment restrictions on Win2k */
static
NTSTATUS
_MarshallSessionSetupData(
    OUT PBYTE pBuffer,
    IN ULONG bufferLen,
    IN uint8_t messageAlignment,
    OUT PULONG pBufferUsed,
    IN const BYTE* pSecurityBlob,
    IN USHORT blobLen,
    IN PCWSTR pwszNativeOS,
    IN PCWSTR pwszNativeLanMan,
    IN PCWSTR pwszNativeDomain
    )
{
    NTSTATUS ntStatus = 0;
    uint32_t bufferUsed = 0;
    uint32_t alignment = 0;

    if (blobLen && bufferUsed + blobLen <= bufferLen)
        memcpy(pBuffer, pSecurityBlob, blobLen);
    bufferUsed += blobLen;

    /* Align strings */
    alignment = (bufferUsed + messageAlignment) % 2;
    if (alignment)
    {
        *(pBuffer + bufferUsed) = 0;
        bufferUsed += alignment;
    }

    ntStatus = SMBPacketAppendUnicodeString(pBuffer, bufferLen, &bufferUsed, pwszNativeOS);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMBPacketAppendUnicodeString(pBuffer, bufferLen, &bufferUsed, pwszNativeLanMan);
    BAIL_ON_NT_STATUS(ntStatus);

    if (pwszNativeDomain)   /* NULL when extended security is not used */
    {
        ntStatus = SMBPacketAppendUnicodeString(pBuffer, bufferLen, &bufferUsed, pwszNativeDomain);
        BAIL_ON_NT_STATUS(ntStatus);
    }

error:
    *pBufferUsed = bufferUsed;

    return ntStatus;
}

NTSTATUS
MarshallSessionSetupRequestData(
    uint8_t         *pBuffer,
    uint32_t         bufferLen,
    uint8_t          messageAlignment,
    uint32_t        *pBufferUsed,
    const uint8_t   *pSecurityBlob,
    uint16_t         blobLen,
    const wchar16_t *pwszNativeOS,
    const wchar16_t *pwszNativeLanMan,
    const wchar16_t *pwszNativeDomain
    )
{
    return _MarshallSessionSetupData(pBuffer, bufferLen, messageAlignment,
        pBufferUsed, pSecurityBlob, blobLen, pwszNativeOS, pwszNativeLanMan,
        pwszNativeDomain);
}

/**
 * @brief This function is used to unmarshal a field of given length from
 *        the supplied cursor (buffer)
 *
 * @param[in,out] ppCursor - the byte buffer from which to unmarshal,
 *                            which we move it to the first unused byte on
 *                            routine success
 * @param[in,out] RemainingBytes - the number of bytes left in the above
 *                                  byte buffer, which will be decremented
 *                                  by the number of bytes used on success
 *                                  of this routine
 * @param[out] ppField - will contain a pointer to the field on success
 * @param[in] fieldLength - the number of bytes that this field will contain
 *
 * @retval STATUS_SUCCESS success
 * @retval STATUS_INVALID_NETWORK_RESPONSE We ran out of buffer
 */
static
NTSTATUS
_UnmarshalField(
    IN OUT PBYTE* ppCursor,
    IN OUT PULONG RemainingBytes,
    OUT PVOID* ppField,
    IN ULONG fieldLength
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PBYTE pCursor = (PBYTE) *ppCursor;
    ULONG remainingBytes = *RemainingBytes;
    PVOID pField = NULL;

    if (fieldLength > remainingBytes)
    {
        ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (fieldLength)
    {
        pField = pCursor;
        pCursor += fieldLength;
        remainingBytes -= fieldLength;
    }

error:
    if (STATUS_SUCCESS != ntStatus)
    {
        pCursor = (PBYTE) *ppCursor;
        remainingBytes = *RemainingBytes;
        pField = NULL;
    }

    *ppCursor = pCursor;
    *RemainingBytes = remainingBytes;
    *ppField = pField;

    return ntStatus;
}

/**
 * @brief This function is used to unmarshal the WordCount 0X0C (12)
 *        body for the SMB_COM_SESSION_SETUP_ANDX (0x73) message
 *
 * @param[in] ppCursor - the byte buffer from which to unmarshal,
 * @param[in] RemainingBytes - the number of bytes left in the above
 *                             byte buffer
 * @param[out] ppHeader - on success of this routine will point a the
 *                        structure containing the Session Setup Request
 *                        header for WordCount 0X0C
 * @param[out] ppSecurityBlob - on success this will contain a pointer
 *                              to the Security Blob
 * @param[out] ppwszNativeOS - on success this will contain a pointer
 *                             to a wide character string copy of the
 *                             NativeOS SMB_STRING.
 * @param[out] ppwszNativeLanMan - on success this will contain a pointer
 *                                 to a wide character string copy of the
 *                                 NativeLanMan SMB_STRING.
 *
 * @retval STATUS_SUCCESS success
 * @retval STATUS_INVALID_NETWORK_RESPONSE We ran out of buffer
 * @retval ... on other errors
 *
 * @note On error all output variable will contain NULL (0)
 */
NTSTATUS
UnmarshallSessionSetupRequest_WC_12(
    IN PSMB_HEADER pSmbHeader,
    IN PBYTE pCursor,
    IN ULONG RemainingBytes,
    OUT SESSION_SETUP_REQUEST_HEADER_WC_12** ppHeader,
    OUT PBYTE* ppSecurityBlob,
    OUT PWSTR* ppwszNativeOS,
    OUT PWSTR* ppwszNativeLanMan
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    ULONG remainingBytes = RemainingBytes;
    ULONG ulBytesUsed = 0;
    SESSION_SETUP_REQUEST_HEADER_WC_12 *pHeader = NULL;
    PBYTE securityBlob = NULL;
    PWSTR nativeOs = NULL;
    PWSTR nativeLanMan = NULL;

    // Header
    pHeader = (SESSION_SETUP_REQUEST_HEADER_WC_12*) pCursor;
    ntStatus = Advance(&pCursor,
                       &remainingBytes,
                       sizeof(SESSION_SETUP_REQUEST_HEADER_WC_12));
    BAIL_ON_NT_STATUS(ntStatus);

    // Security Blob
    securityBlob = pCursor;
    // TODO: Endian swap blob length
    ntStatus = Advance(&pCursor,
                       &remainingBytes,
                       pHeader->securityBlobLength);
    BAIL_ON_NT_STATUS(ntStatus);

    if (pSmbHeader->flags2 & FLAG2_UNICODE)
    {
        // Align for the Wide Character Strings
        ntStatus = Align(
                        (PBYTE) pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &ulBytesUsed,
                        sizeof(WCHAR));
        BAIL_ON_NT_STATUS(ntStatus);
    }

    // Preserve compatibility with old code and treat the NativeOS and
    // NativeLanMan strings as optional
    if (remainingBytes)
    {
        // NativeOS
        ntStatus = SMBUnmarshalSmbString(
                        pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &nativeOs);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (remainingBytes)
    {
        // NativeLanManager
        ntStatus = SMBUnmarshalSmbString(
                        pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &nativeLanMan);
        BAIL_ON_NT_STATUS(ntStatus);
    }

error:
    if (STATUS_SUCCESS != ntStatus)
    {
        // Remap error code
        if (STATUS_BUFFER_TOO_SMALL == ntStatus)
        {
            ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        }

        pHeader = NULL;
        securityBlob = NULL;

        RtlWC16StringFree(&nativeOs);
        RtlWC16StringFree(&nativeLanMan);
    }


    // Assigning values to the Out variables
    *ppHeader = pHeader;
    *ppSecurityBlob = securityBlob;
    *ppwszNativeOS = nativeOs;
    *ppwszNativeLanMan = nativeLanMan;

    return ntStatus;
}

/**
 * @brief This function is used to unmarshal the WordCount 0X0D (13)
 *        body for the SMB_COM_SESSION_SETUP_ANDX (0x73) message
 *
 * @param[in] ppCursor - the byte buffer from which to unmarshal,
 * @param[in] RemainingBytes - the number of bytes left in the above
 *                             byte buffer
 * @param[out] ppHeader - on success of this routine will point a the
 *                        structure containing the Session Setup Request
 *                        header for WordCount 0X0D
 * @param[out] ppSecurityBlob - on success this will contain a pointer
 *                              to the Security Blob
 * @param[out] ppwszNativeOS - on success this will contain a pointer
 *                             to a wide character string copy of the
 *                             NativeOS SMB_STRING.
 * @param[out] ppwszNativeLanMan - on success this will contain a pointer
 *                                 to a wide character string copy of the
 *                                 the NativeLanMan SMB_STRING.
 *
 * @retval STATUS_SUCCESS success
 * @retval STATUS_INVALID_NETWORK_RESPONSE We ran out of buffer
 * @retval ... on other errors
 *
 * @note On error all output variable will contain NULL (0)
 */
NTSTATUS
UnmarshallSessionSetupRequest_WC_13(
    IN PSMB_HEADER pSmbHeader,
    IN PBYTE pCursor,
    IN ULONG RemainingBytes,
    OUT SESSION_SETUP_REQUEST_HEADER_WC_13** ppHeader,
    OUT PLW_MAP_SECURITY_NTLM_LOGON_INFO pNtlmLogonInfo,
    OUT PWSTR* ppwszNativeOS,
    OUT PWSTR* ppwszNativeLanMan
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    ULONG remainingBytes = RemainingBytes;
    ULONG ulBytesUsed = 0;
    SESSION_SETUP_REQUEST_HEADER_WC_13 *pHeader = NULL;
    PVOID LmResponse = NULL;
    PVOID NtResponse = NULL;
    PWSTR accountName = NULL;
    PWSTR domain = NULL;
    PWSTR nativeOs = NULL;
    PWSTR nativeLanMan = NULL;

    // Header
    pHeader = (SESSION_SETUP_REQUEST_HEADER_WC_13*) pCursor;
    // TODO: endian swap as appropriate
    ntStatus = Advance(&pCursor,
                       &remainingBytes,
                       sizeof(SESSION_SETUP_REQUEST_HEADER_WC_13));
    BAIL_ON_NT_STATUS(ntStatus);

    // LM response
    ntStatus = _UnmarshalField(
                    &pCursor,
                    &remainingBytes,
                    &LmResponse,
                    pHeader->lmResponseLength
                    );
    BAIL_ON_NT_STATUS(ntStatus);

    // NT response
    ntStatus = _UnmarshalField(
                    &pCursor,
                    &remainingBytes,
                    &NtResponse,
                    pHeader->ntResponseLength
                    );
    BAIL_ON_NT_STATUS(ntStatus);

    if (pSmbHeader->flags2 & FLAG2_UNICODE)
    {
        // Align for the Wide Character Strings
        ntStatus = Align(
                        (PBYTE) pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &ulBytesUsed,
                        sizeof(WCHAR));
        BAIL_ON_NT_STATUS(ntStatus);
    }

    // Account name
    ntStatus = SMBUnmarshalSmbString(
                    pSmbHeader,
                    &pCursor,
                    &remainingBytes,
                    &accountName);
    BAIL_ON_NT_STATUS(ntStatus);

    // Domain
    ntStatus = SMBUnmarshalSmbString(
                    pSmbHeader,
                    &pCursor,
                    &remainingBytes,
                    &domain);
    BAIL_ON_NT_STATUS(ntStatus);

    // Preserve compatibility with old code and treat the NativeOS and
    // NativeLanMan strings as optional
    if (remainingBytes)
    {
        // NativeOS
        ntStatus = SMBUnmarshalSmbString(
                        pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &nativeOs);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (remainingBytes)
    {
        // NativeLanManager
        ntStatus = SMBUnmarshalSmbString(
                        pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &nativeLanMan);
        BAIL_ON_NT_STATUS(ntStatus);
    }

error:
    if (STATUS_SUCCESS != ntStatus)
    {
        // Remap error code
        if (STATUS_BUFFER_TOO_SMALL == ntStatus)
        {
            ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        }

        pHeader = NULL;
        LmResponse = NULL;
        NtResponse = NULL;

        RtlWC16StringFree(&accountName);
        RtlWC16StringFree(&domain);
        RtlWC16StringFree(&nativeOs);
        RtlWC16StringFree(&nativeLanMan);
    }
    else
    {
        pNtlmLogonInfo->ulLmResponseSize = pHeader->lmResponseLength;
        pNtlmLogonInfo->ulNtResponseSize = pHeader->ntResponseLength;
    }

    // Assigning values to the Out variables
    *ppHeader = pHeader;
    pNtlmLogonInfo->pLmResponse = LmResponse;
    pNtlmLogonInfo->pNtResponse = NtResponse;
    pNtlmLogonInfo->pwszAccountName = accountName;
    pNtlmLogonInfo->pwszDomain = domain;
    *ppwszNativeOS = nativeOs;
    *ppwszNativeLanMan = nativeLanMan;

    return ntStatus;
}

NTSTATUS
MarshallSessionSetupResponseData(
    uint8_t         *pBuffer,
    uint32_t         bufferLen,
    uint8_t          messageAlignment,
    uint32_t        *pBufferUsed,
    const uint8_t   *pSecurityBlob,
    uint16_t         blobLen,
    const wchar16_t *pwszNativeOS,
    const wchar16_t *pwszNativeLanMan,
    const wchar16_t *pwszNativeDomain
    )
{
    return _MarshallSessionSetupData(pBuffer, bufferLen, messageAlignment,
        pBufferUsed, pSecurityBlob, blobLen, pwszNativeOS, pwszNativeLanMan,
        pwszNativeDomain);
}

/**
 * @brief This function is used to unmarshal the WordCount 0X04 (4)
 *        body for the SMB_COM_SESSION_SETUP_ANDX (0x73) message
 *
 * @param[in] ppCursor - the byte buffer from which to unmarshal,
 * @param[in] RemainingBytes - the number of bytes left in the above
 *                             byte buffer
 * @param[out] ppHeader - on success of this routine will point a the
 *                        structure containing the Session Setup Request
 *                        header for WordCount 0X04
 * @param[out] ppSecurityBlob - on success this will contain a pointer
 *                              to the Security Blob
 * @param[out] ppwszNativeOS - on success this will contain a pointer
 *                             to a wide character string copy of the
 *                             NativeOS SMB_STRING.
 * @param[out] ppwszNativeLanMan - on success this will contain a pointer
 *                                 to a wide character string copy of the
 *                                 NativeLanMan SMB_STRING.
 *
 * @retval STATUS_SUCCESS success
 * @retval STATUS_INVALID_NETWORK_RESPONSE We ran out of buffer
 * @retval ... on other errors
 *
 * @note On error all output variable will contain NULL (0)
 */
NTSTATUS
UnmarshallSessionSetupResponse_WC_4(
    IN PSMB_HEADER pSmbHeader,
    IN PBYTE pCursor,
    IN ULONG RemainingBytes,
    OUT SESSION_SETUP_RESPONSE_HEADER_WC_4** ppHeader,
    OUT PBYTE* ppSecurityBlob,
    OUT PWSTR* ppwszNativeOS,
    OUT PWSTR* ppwszNativeLanMan
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    ULONG remainingBytes = RemainingBytes;
    ULONG ulBytesUsed = 0;
    SESSION_SETUP_RESPONSE_HEADER_WC_4 *pHeader = NULL;
    PBYTE securityBlob = NULL;
    PWSTR nativeOs = NULL;
    PWSTR nativeLanMan = NULL;

    // Header
    pHeader = (SESSION_SETUP_RESPONSE_HEADER_WC_4*) pCursor;
    ntStatus = Advance(&pCursor,
                       &remainingBytes,
                       sizeof(SESSION_SETUP_RESPONSE_HEADER_WC_4));
    BAIL_ON_NT_STATUS(ntStatus);

    // byte order conversions
    SMB_LTOH16_INPLACE(pHeader->action);
    SMB_LTOH16_INPLACE(pHeader->securityBlobLength);
    SMB_LTOH16_INPLACE(pHeader->byteCount);

    // Security Blob
    securityBlob = pCursor;
    // TODO: Endian swap blob length
    ntStatus = Advance(&pCursor,
                       &remainingBytes,
                       pHeader->securityBlobLength);
    BAIL_ON_NT_STATUS(ntStatus);

    if (pSmbHeader->flags2 & FLAG2_UNICODE)
    {
        // Align for the Wide Character Strings
        ntStatus = Align(
                        (PBYTE) pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &ulBytesUsed,
                        sizeof(WCHAR));
        BAIL_ON_NT_STATUS(ntStatus);
    }

    // Preserve compatibility with old code and treat the NativeOS,
    // NativeLanMan and PrimaryDomain strings as optional
    if (remainingBytes)
    {
        // NativeOS
        ntStatus = SMBUnmarshalSmbString(
                        pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &nativeOs);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (remainingBytes)
    {
        // NativeLanManager
        ntStatus = SMBUnmarshalSmbString(
                        pSmbHeader,
                        &pCursor,
                        &remainingBytes,
                        &nativeLanMan);
        BAIL_ON_NT_STATUS(ntStatus);
    }

error:

    if (STATUS_SUCCESS != ntStatus)
    {
        // Remap error code
        if (STATUS_BUFFER_TOO_SMALL == ntStatus)
        {
            ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        }

        pHeader = NULL;
        securityBlob = NULL;

        RtlWC16StringFree(&nativeOs);
        RtlWC16StringFree(&nativeLanMan);
    }


    // Assigning values to the Out variables
    *ppHeader = pHeader;
    *ppSecurityBlob = securityBlob;
    *ppwszNativeOS = nativeOs;
    *ppwszNativeLanMan = nativeLanMan;

    return ntStatus;
}
