/*
 * PROJECT:         ReactOS Kernel
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            ntoskrnl/lpc/port.c
 * PURPOSE:         Local Procedure Call: Port Management
 * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
 */

/* INCLUDES ******************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>

/* GLOBALS *******************************************************************/

POBJECT_TYPE LpcPortObjectType, LpcWaitablePortObjectType;
ULONG LpcpMaxMessageSize;
PAGED_LOOKASIDE_LIST LpcpMessagesLookaside;
KGUARDED_MUTEX LpcpLock;
ULONG LpcpTraceLevel = 0;
ULONG LpcpNextMessageId = 1, LpcpNextCallbackId = 1;

static GENERIC_MAPPING LpcpPortMapping =
{
    READ_CONTROL | PORT_CONNECT,
    DELETE | PORT_CONNECT,
    0,
    PORT_ALL_ACCESS
};

/* PRIVATE FUNCTIONS *********************************************************/

BOOLEAN
NTAPI
INIT_FUNCTION
LpcInitSystem(VOID)
{
    OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
    UNICODE_STRING Name;

    /* Setup the LPC Lock */
    KeInitializeGuardedMutex(&LpcpLock);

    /* Create the Port Object Type */
    RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
    RtlInitUnicodeString(&Name, L"Port");
    ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
    ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(LPCP_NONPAGED_PORT_QUEUE);
    ObjectTypeInitializer.DefaultPagedPoolCharge = FIELD_OFFSET(LPCP_PORT_OBJECT, WaitEvent);
    ObjectTypeInitializer.GenericMapping = LpcpPortMapping;
    ObjectTypeInitializer.PoolType = PagedPool;
    ObjectTypeInitializer.UseDefaultObject = TRUE;
    ObjectTypeInitializer.CloseProcedure = LpcpClosePort;
    ObjectTypeInitializer.DeleteProcedure = LpcpDeletePort;
    ObjectTypeInitializer.ValidAccessMask = PORT_ALL_ACCESS;
    ObjectTypeInitializer.InvalidAttributes = OBJ_VALID_ATTRIBUTES & ~OBJ_CASE_INSENSITIVE;
    ObCreateObjectType(&Name,
                       &ObjectTypeInitializer,
                       NULL,
                       &LpcPortObjectType);

    RtlInitUnicodeString(&Name, L"WaitablePort");
    ObjectTypeInitializer.PoolType = NonPagedPool;
    ObjectTypeInitializer.DefaultNonPagedPoolCharge += sizeof(LPCP_PORT_OBJECT);
    ObjectTypeInitializer.DefaultPagedPoolCharge = 0;
    ObjectTypeInitializer.UseDefaultObject = FALSE;
    ObCreateObjectType(&Name,
                       &ObjectTypeInitializer,
                       NULL,
                       &LpcWaitablePortObjectType);

    /* Allocate the LPC lookaside list */
    LpcpMaxMessageSize = LPCP_MAX_MESSAGE_SIZE;
    ExInitializePagedLookasideList(&LpcpMessagesLookaside,
                                   NULL,
                                   NULL,
                                   0,
                                   LpcpMaxMessageSize,
                                   'McpL',
                                   32);

    /* We're done */
    return TRUE;
}

BOOLEAN
NTAPI
LpcpValidateClientPort(
    PETHREAD ClientThread,
    PLPCP_PORT_OBJECT Port)
{
    PLPCP_PORT_OBJECT ThreadPort;

    /* Get the thread's port */
    ThreadPort = LpcpGetPortFromThread(ClientThread);
    if (ThreadPort == NULL)
    {
        return FALSE;
    }

    /* Check if the port matches directly */
    if ((Port == ThreadPort) ||
        (Port == ThreadPort->ConnectionPort) ||
        (Port == ThreadPort->ConnectedPort))
    {
        return TRUE;
    }

    /* Check if this is a communication port and the connection port matches */
    if (((Port->Flags & LPCP_PORT_TYPE_MASK) == LPCP_COMMUNICATION_PORT) &&
        (Port->ConnectionPort == ThreadPort))
    {
        return TRUE;
    }

    return FALSE;
}


/* PUBLIC FUNCTIONS **********************************************************/

NTSTATUS
NTAPI
NtImpersonateClientOfPort(IN HANDLE PortHandle,
                          IN PPORT_MESSAGE ClientMessage)
{
    KPROCESSOR_MODE PreviousMode;
    CLIENT_ID ClientId;
    ULONG MessageId;
    PLPCP_PORT_OBJECT Port = NULL, ConnectedPort = NULL;
    PETHREAD ClientThread = NULL;
    SECURITY_CLIENT_CONTEXT ClientContext;
    NTSTATUS Status;
    PAGED_CODE();

    /* Check the previous mode */
    PreviousMode = ExGetPreviousMode();
    if (PreviousMode == KernelMode)
    {
        ClientId = ClientMessage->ClientId;
        MessageId = ClientMessage->MessageId;
    }
    else
    {
        _SEH2_TRY
        {
            ProbeForRead(ClientMessage, sizeof(*ClientMessage), sizeof(PVOID));
            ClientId = ClientMessage->ClientId;
            MessageId = ClientMessage->MessageId;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            DPRINT1("Got exception!\n");
            return _SEH2_GetExceptionCode();
        }
        _SEH2_END;
    }

    /* Reference the port handle */
    Status = ObReferenceObjectByHandle(PortHandle,
                                       PORT_ALL_ACCESS,
                                       LpcPortObjectType,
                                       PreviousMode,
                                       (PVOID*)&Port,
                                       NULL);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Failed to reference port handle: 0x%ls\n", Status);
        return Status;
    }

    /* Make sure this is a connection port */
    if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_COMMUNICATION_PORT)
    {
        /* It isn't, fail */
        DPRINT1("Port is not a communication port\n");
        Status = STATUS_INVALID_PORT_HANDLE;
        goto Cleanup;
    }

    /* Look up the client thread */
    Status = PsLookupProcessThreadByCid(&ClientId, NULL, &ClientThread);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Failed to lookup client thread: 0x%ls\n", Status);
        goto Cleanup;
    }

    /* Acquire the lock */
    KeAcquireGuardedMutex(&LpcpLock);

    /* Get the connected port and try to reference it */
    ConnectedPort = Port->ConnectedPort;
    if ((ConnectedPort == NULL) ||
        !ObReferenceObjectSafe(ConnectedPort))
    {
        DPRINT1("Failed to reference the connected port\n");
        ConnectedPort = NULL;
        Status = STATUS_PORT_DISCONNECTED;
        goto CleanupWithLock;
    }

    /* Check for no-impersonation flag */
    if ((ULONG_PTR)ClientThread->LpcReplyMessage & LPCP_THREAD_FLAG_NO_IMPERSONATION)
    {
        DPRINT1("Reply message has no impersonation flag set\n");
        Status = STATUS_ACCESS_DENIED;
        goto CleanupWithLock;
    }

    /* Check for message id mismatch */
    if ((ClientThread->LpcReplyMessageId != MessageId) || (MessageId == 0))
    {
        DPRINT1("LpcReplyMessageId mismatch: 0x%lx/0x%lx.\n",
                ClientThread->LpcReplyMessageId, MessageId);
        Status = STATUS_REPLY_MESSAGE_MISMATCH;
        goto CleanupWithLock;
    }

    /* Validate the port */
    if (!LpcpValidateClientPort(ClientThread, Port))
    {
        DPRINT1("LpcpValidateClientPort failed\n");
        Status = STATUS_REPLY_MESSAGE_MISMATCH;
        goto CleanupWithLock;
    }

    /* Release the lock */
    KeReleaseGuardedMutex(&LpcpLock);

    /* Check if security is static */
    if (!(ConnectedPort->Flags & LPCP_SECURITY_DYNAMIC))
    {
        /* Use the static security for impersonation */
        Status = SeImpersonateClientEx(&ConnectedPort->StaticSecurity, NULL);
        goto Cleanup;
    }

    /* Create new dynamic security */
    Status = SeCreateClientSecurity(ClientThread,
                                    &ConnectedPort->SecurityQos,
                                    FALSE,
                                    &ClientContext);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("SeCreateClientSecurity failed\n");
        goto Cleanup;
    }

    /* Use dynamic security for impersonation */
    Status = SeImpersonateClientEx(&ClientContext, NULL);

    /* Get rid of the security context */
    SeDeleteClientSecurity(&ClientContext);

    goto Cleanup;

CleanupWithLock:

    /* Release the lock */
    KeReleaseGuardedMutex(&LpcpLock);

Cleanup:

    if (ConnectedPort != NULL)
        ObDereferenceObject(ConnectedPort);

    if (ClientThread != NULL)
        ObDereferenceObject(ClientThread);

    ObDereferenceObject(Port);

    return Status;
}

NTSTATUS
NTAPI
NtQueryPortInformationProcess(VOID)
{
    /* This is all this function does */
    return STATUS_UNSUCCESSFUL;
}

NTSTATUS
NTAPI
NtQueryInformationPort(IN HANDLE PortHandle,
                       IN PORT_INFORMATION_CLASS PortInformationClass,
                       OUT PVOID PortInformation,
                       IN ULONG PortInformationLength,
                       OUT PULONG ReturnLength)
{
    UNIMPLEMENTED;
    return STATUS_NOT_IMPLEMENTED;
}

/* EOF */
