/* -*- 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 (c) Likewise Software.  All rights Reserved.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the license, or (at
 * your option) any later version.
 *
 * This library 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 Lesser
 * General Public License for more details.  You should have received a copy
 * of the GNU Lesser 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
 * LESSER 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@likewise.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *     ea.c
 *
 * Abstract:
 *
 *     Likewise IO (LWIO) EA
 *
 * Authors: Danilo Almeida (dalmeida@likewise.com)
 */

#include "includes.h"
#include <lw/list.h>
#include <lw/rtlgoto.h>
#include <lwio/iortl.h>
#include "ntlogmacros.h"

static
BOOLEAN
IoRtlpEaIsValidWindowsEaNameCharacter(
    IN CHAR Character
    )
{
    BOOLEAN isValid = FALSE;

    if (Character <= 0x1F)
    {
        isValid = FALSE;
        GOTO_CLEANUP();
    }
    switch (Character)
    {
        case '\\':
        case '/':
        case ':':
        case '*':
        case '?':
        case '"':
        case '<':
        case '>':
        case '|':
        case ',':
        case '+':
        case '=':
        case '[':
        case ']':
            isValid = FALSE;
            break;
        default:
            isValid = TRUE;
    }

cleanup:
    return isValid;
}

BOOLEAN
IoRtlEaIsValidEaName(
    IN PCHAR EaName,
    IN ULONG EaNameLength,
    IN IO_EA_NAME_TYPE EaNameType
    )
{
    BOOLEAN isValid = FALSE;
    ULONG i = 0;

    // From the definition of FILE_GET_EA_INFORMATION and
    // FILE_FULL_EA_INFORMATION structures, the EA name cannot be longer
    // than the length that can fit into a UCHAR (256).
    if (EaNameLength > (UCHAR)-1)
    {
        isValid = FALSE;
        GOTO_CLEANUP();
    }

    switch (EaNameType)
    {
        case IO_EA_NAME_TYPE_UNKNOWN:
            // No specific semantics are desired, so anything
            // is valid as long as NULL is not present.
            for (i = 0; i < EaNameLength; i++)
            {
                if (!EaName[i])
                {
                    isValid = FALSE;
                    GOTO_CLEANUP();
                }
            }
            break;
        case IO_EA_NAME_TYPE_WINDOWS:
            // EA name must be "less than 255 characters" and cannot contain
            // any of these: 0x00-0x1F \ / : * ? " < > | , + = [ ]
            if (EaNameLength >= 255)
            {
                isValid = FALSE;
                GOTO_CLEANUP();
            }
            for (i = 0; i < EaNameLength; i++)
            {
                if (!IoRtlpEaIsValidWindowsEaNameCharacter(EaName[i]))
                {
                    isValid = FALSE;
                    GOTO_CLEANUP();
                }
            }
            break;
        default:
            // Cause higher level operation to fail.
            isValid = FALSE;
            GOTO_CLEANUP();
    }

    isValid = TRUE;

cleanup:
    return isValid;
}

NTSTATUS
IoRtlEaCheckGetEaInformation(
    IN OPTIONAL PFILE_GET_EA_INFORMATION Buffer,
    IN ULONG Length,
    IN IO_EA_NAME_TYPE NameType,
    OUT OPTIONAL PULONG ErrorOffset
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    int EE = 0;
    ULONG offset = 0;

    if (!Buffer && Length)
    {
        status = STATUS_INVALID_PARAMETER;
        GOTO_CLEANUP_EE(EE);
    }
    else if (!Length)
    {
        status = STATUS_SUCCESS;
        GOTO_CLEANUP_EE(EE);
    }

    for (;;)
    {
        PFILE_GET_EA_INFORMATION current = (PFILE_GET_EA_INFORMATION) LwRtlOffsetToPointer(Buffer, offset);
        ULONG remaining = Length - offset;
        ULONG size = LW_FIELD_OFFSET(FILE_GET_EA_INFORMATION, EaName);

        // Check fixed part size
        if (remaining < size)
        {
            status = STATUS_EA_LIST_INCONSISTENT;
            GOTO_CLEANUP_EE(EE);
        }

        // Check variable payload size
        size += current->EaNameLength + 1;
        if (remaining < size)
        {
            status = STATUS_EA_LIST_INCONSISTENT;
            GOTO_CLEANUP_EE(EE);
        }

        // Check next entry offset
        if (current->NextEntryOffset)
        {
            size = LW_ALIGN_UP(size, sizeof(ULONG));
            if (current->NextEntryOffset < size)
            {
                status = STATUS_EA_LIST_INCONSISTENT;
                GOTO_CLEANUP_EE(EE);
            }
            if (remaining < size)
            {
                status = STATUS_EA_LIST_INCONSISTENT;
                GOTO_CLEANUP_EE(EE);
            }
        }

        // Check that EA name has NULL termination.
        if (current->EaName[current->EaNameLength])
        {
            status = STATUS_EA_LIST_INCONSISTENT;
            GOTO_CLEANUP_EE(EE);
        }

        // Check for valid characters in EA name.
        if (!IoRtlEaIsValidEaName(current->EaName, current->EaNameLength, NameType))
        {
            status = STATUS_INVALID_EA_NAME;
            GOTO_CLEANUP_EE(EE);
        }

        if (!current->NextEntryOffset)
        {
            break;
        }

        offset += current->NextEntryOffset;
    }

    status = STATUS_SUCCESS;

cleanup:
    if (ErrorOffset && !NT_SUCCESS(status))
    {
        *ErrorOffset = offset;
    }

    LOG_LEAVE_IF_STATUS_EE(status, EE);

    LWIO_ASSERT(NT_SUCCESS_OR_NOT(status));

    return status;
}

NTSTATUS
IoRtlEaCheckFullEaInformation(
    IN OPTIONAL PFILE_FULL_EA_INFORMATION Buffer,
    IN ULONG Length,
    IN IO_EA_NAME_TYPE NameType,
    OUT OPTIONAL PULONG ErrorOffset
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    int EE = 0;
    ULONG offset = 0;

    if (!Buffer && Length)
    {
        status = STATUS_INVALID_PARAMETER;
        GOTO_CLEANUP_EE(EE);
    }
    else if (!Length)
    {
        status = STATUS_SUCCESS;
        GOTO_CLEANUP_EE(EE);
    }

    for (;;)
    {
        PFILE_FULL_EA_INFORMATION current = (PFILE_FULL_EA_INFORMATION) LwRtlOffsetToPointer(Buffer, offset);
        ULONG remaining = Length - offset;
        ULONG size = LW_FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName);

        // Check fixed part size
        if (remaining < size)
        {
            status = STATUS_EA_LIST_INCONSISTENT;
            GOTO_CLEANUP_EE(EE);
        }

        // Check variable payload size
        size += current->EaNameLength + /*NUL*/1 + current->EaValueLength;
        if (remaining < size)
        {
            status = STATUS_EA_LIST_INCONSISTENT;
            GOTO_CLEANUP_EE(EE);
        }

        // Check next entry offset
        if (current->NextEntryOffset)
        {
            size = LW_ALIGN_UP(size, sizeof(ULONG));
            if (current->NextEntryOffset < size)
            {
                status = STATUS_EA_LIST_INCONSISTENT;
                GOTO_CLEANUP_EE(EE);
            }
            if (remaining < size)
            {
                status = STATUS_EA_LIST_INCONSISTENT;
                GOTO_CLEANUP_EE(EE);
            }
        }

        // Check EA flags
        if (IsSetFlag(current->Flags, ~FILE_EA_FLAGS_VALID_MASK))
        {
            status = STATUS_INVALID_EA_FLAG;
            GOTO_CLEANUP_EE(EE);
        }

        // Check that EA name has NULL termination.
        if (current->EaName[current->EaNameLength])
        {
            status = STATUS_EA_LIST_INCONSISTENT;
            GOTO_CLEANUP_EE(EE);
        }

        // Check for valid characters in EA name.
        if (!IoRtlEaIsValidEaName(current->EaName, current->EaNameLength, NameType))
        {
            status = STATUS_INVALID_EA_NAME;
            GOTO_CLEANUP_EE(EE);
        }

        if (!current->NextEntryOffset)
        {
            break;
        }

        offset += current->NextEntryOffset;
    }

    status = STATUS_SUCCESS;

cleanup:
    if (ErrorOffset && !NT_SUCCESS(status))
    {
        *ErrorOffset = offset;
    }

    LOG_LEAVE_IF_STATUS_EE(status, EE);

    LWIO_ASSERT(NT_SUCCESS_OR_NOT(status));

    return status;
}
