/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    lsarm.c

Abstract:

    Local Security Authority - Reference Monitor Communication

Author:

    Scott Birrell       (ScottBi)      March 26, 1991

Environment:

Revision History:

--*/

#include "lsasrvp.h"

//
// LSA Global State
//

LSAP_STATE LsapState;

//
// Lsa Reference Monitor Server Command Dispatch Table
//

PLSA_COMMAND_WORKER LsapCommandDispatch[] = {

    LsapComponentTestWrkr,
    LsapAdtWriteLogWrkr,
    LsapComponentTestWrkr,
    LsapLogonSessionDeletedWrkr

};


VOID
LsapRmServerThread(
   )

/*++

Routine Description:

    This function is executed by the LSA Reference Monitor Server Thread.  This
    thread receives messages from the Reference Monitor.  Examples of messages
    include Audit Messages,...  The function is implemented as a for loop
    which runs indefinitely unless an error occurs.  Currently, any
    error is fatal.  On each iteration a message is received from the
    Reference Monitor and dispatched to a handler.

Arguments:

    None.

Return Value:

    None.  Any return is a fatal error.

--*/

{
    PLSA_REPLY_MESSAGE Reply;
    LSA_COMMAND_MESSAGE CommandMessage;
    LSA_REPLY_MESSAGE ReplyMessage;

    NTSTATUS Status;

    //
    // Initialize the LPC port message header type and data sizes for
    // for the reply message.
    //

    ReplyMessage.MessageHeader.u2.ZeroInit = 0;
    ReplyMessage.MessageHeader.u1.s1.TotalLength =
        (CSHORT) sizeof(RM_COMMAND_MESSAGE);
    ReplyMessage.MessageHeader.u1.s1.DataLength =
    ReplyMessage.MessageHeader.u1.s1.TotalLength -
        (CSHORT) sizeof(PORT_MESSAGE);

    //
    // First time through, there is no reply.
    //

    Reply = NULL;

    //
    // Now loop indefinitely, processing incoming Command Message Packets
    //

    for(;;) {

        //
        // Wait for and receive a message from the Reference Monitor through
        // the Lsa Command LPC Port.
        //

        Status = NtReplyWaitReceivePort(
                    LsapState.LsaCommandPortHandle,
                    NULL,
                    (PPORT_MESSAGE) Reply,
                    (PPORT_MESSAGE) &CommandMessage
                    );


        if (Status != 0) {
            if (!NT_SUCCESS( Status ) &&
                Status != STATUS_INVALID_CID &&
                Status != STATUS_UNSUCCESSFUL
               ) {
                KdPrint(("LSASS: Lsa message receive from Rm failed x%lx\n", Status));
            }

            //
            // Ignore if client went away.
            //

            Reply = NULL;
            continue;
        }

        //
        // If an LPC request, process it.
        //

        if (CommandMessage.MessageHeader.u2.s2.Type == LPC_REQUEST) {

            //
            //
            // Now dispatch to a routine to handle the command.  Allow
            // command errors to occur without bringing system down.
            //

            Reply = &ReplyMessage;
            ReplyMessage.MessageHeader.ClientId =
                CommandMessage.MessageHeader.ClientId;
            ReplyMessage.MessageHeader.MessageId =
                CommandMessage.MessageHeader.MessageId;

            Status = (LsapCommandDispatch[CommandMessage.CommandNumber])(
                         &CommandMessage,
                         Reply);

            ReplyMessage.ReturnedStatus = Status;
        } else {

            Reply = NULL;
        }

    }  // end_for

    return;
}


BOOLEAN
LsapRmInitializeServer(
    )

/*++

Routine Description:

    This function initializes the Lsa Reference Monitor Server Thread.
    The following steps are performed.

    o Create the Lsa Command LPC Port
    o Open the Lsa Init event created by the Reference Monitor
    o Signal the Lsa Init Event, telling RM to go ahead and connect
      to the port
    o Connect to the Reference Monitor Command Port as client
    o Listen for the Reference Monitor to connect to the port
    o Accept the connection to the port
    o Complete the connection to the port
    o Create the LSA Reference Monitor Server Thread

Arguments:

    None.

Return Value:

    BOOLEAN - TRUE if successful, else FALSE

--*/

{
    NTSTATUS Status;

    BOOLEAN BooleanStatus = FALSE;

    PORT_MESSAGE ConnectionRequest;
    REMOTE_PORT_VIEW ClientView;

    HANDLE LsaInitEventHandle;
    OBJECT_ATTRIBUTES LsaInitEventObjA;
    UNICODE_STRING LsaInitEventName;

    UNICODE_STRING RmCommandPortName, LsaCommandPortName;

    OBJECT_ATTRIBUTES LsaCommandPortObjA;
    SECURITY_QUALITY_OF_SERVICE DynamicQos;

    HANDLE Thread;
    DWORD Ignore;

    //
    // Create the Lsa Command LPC Port.  This port will receive
    // commands from the Reference Monitor.
    //

    RtlInitUnicodeString( &LsaCommandPortName, L"\\SeLsaCommandPort" );

    //
    // Setup to create LSA Command Port
    //

    InitializeObjectAttributes(
        &LsaCommandPortObjA,
        &LsaCommandPortName,
        0,
        NULL,
        NULL
        );

    Status = NtCreatePort(
                 &LsapState.LsaCommandPortHandle,
                 &LsaCommandPortObjA,
                 0,
                 sizeof(LSA_COMMAND_MESSAGE),
                 sizeof(LSA_COMMAND_MESSAGE) * 32
                 );

    if (!NT_SUCCESS(Status)) {

        KdPrint(("LsapRmInitializeServer - Port Create failed 0x%lx\n",Status));
        goto InitServerError;
    }

    //
    // Open the LSA Init Event created by the Reference Monitor
    //

    RtlInitUnicodeString( &LsaInitEventName, L"\\SeLsaInitEvent" );

    InitializeObjectAttributes(
        &LsaInitEventObjA,
        &LsaInitEventName,
        0,
        NULL,
        NULL
        );

    Status = NtOpenEvent(
        &LsaInitEventHandle,
        EVENT_MODIFY_STATE,
        &LsaInitEventObjA
        );

    //
    // If the LSA Init event could not be opened, the LSA cannot
    // synchronize with the Reference Monitor so neither component will
    // function correctly.
    //

    if (!NT_SUCCESS(Status)) {

        KdPrint(("LsapRmInitializeServer - Lsa Init Event Open failed 0x%lx\n",Status));
        goto InitServerError;
    }

    //
    // Signal the LSA Init Event.  If the signalling fails, the LSA
    // is not able to synchronize properly with the Reference Monitor.
    // This is a serious error which prevents both components from
    // functioning correctly.
    //

    Status = NtSetEvent( LsaInitEventHandle, NULL );

    if (!NT_SUCCESS(Status)) {

        KdPrint(("LsapRmInitializeServer - Init Event Open failed 0x%lx\n",Status));
        goto InitServerError;
    }

    //
    // Set up the security quality of service parameters to use over the
    // port.  Use the most efficient (least overhead) - which is dynamic
    // rather than static tracking.
    //

    DynamicQos.ImpersonationLevel = SecurityImpersonation;
    DynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
    DynamicQos.EffectiveOnly = TRUE;

    //
    // Connect to the Reference Monitor Command Port.  This port
    // is used to send commands from the LSA to the Reference Monitor.
    //

    RtlInitUnicodeString( &RmCommandPortName, L"\\SeRmCommandPort" );

    Status = NtConnectPort(
                 &LsapState.RmCommandPortHandle,
                 &RmCommandPortName,
                 &DynamicQos,
                 NULL,
                 NULL,
                 NULL,
                 NULL,
                 NULL
                 );

    if (!NT_SUCCESS(Status)) {

        KdPrint(("LsapRmInitializeServer - Connect to Rm Command Port failed 0x%lx\n",Status));
        goto InitServerError;
    }

    //
    // Listen for the Reference Monitor To Connect to the LSA
    // Command Port.
    //

    ConnectionRequest.u1.s1.TotalLength = sizeof(ConnectionRequest);
    ConnectionRequest.u1.s1.DataLength = (CSHORT)0;
    Status = NtListenPort(
                 LsapState.LsaCommandPortHandle,
                 &ConnectionRequest
                 );

    if (!NT_SUCCESS(Status)) {

        KdPrint(("LsapRmInitializeServer - Port Listen failed 0x%lx\n",Status));
        goto InitServerError;
    }

    //
    // Accept the connection to the Lsa Command Port.
    //

    ClientView.Length = sizeof(ClientView);
    Status = NtAcceptConnectPort(
                 &LsapState.LsaCommandPortHandle,
                 NULL,
                 &ConnectionRequest,
                 TRUE,
                 NULL,
                 &ClientView
                 );

    if (!NT_SUCCESS(Status)) {

        KdPrint(("LsapRmInitializeServer - Port Accept Connect failed 0x%lx\n",Status));

        goto InitServerError;
    }

    //
    // Complete the connection
    //

    Status = NtCompleteConnectPort(LsapState.LsaCommandPortHandle);

    if (!NT_SUCCESS(Status)) {

        KdPrint(("LsapRmInitializeServer - Port Complete Connect failed 0x%lx\n",Status));
        goto InitServerError;
    }

    //
    // Create the LSA Reference Monitor Server Thread
    //

    Thread = CreateThread(
                 NULL,
                 0L,
                 (LPTHREAD_START_ROUTINE) LsapRmServerThread,
                 (LPVOID)0,
                 0L,
                 &Ignore
                 );

    if (Thread == NULL) {

        KdPrint(("LsapRmInitializeServer - Create Thread  failed 0x%lx\n",Status));
    }

    BooleanStatus = TRUE;

    goto InitServerCleanup;

InitServerError:

    //
    // Perform cleanup needed only in error cases here.
    //

InitServerCleanup:

    //
    // Perform cleanup needed in all cases here
    //

    return BooleanStatus;
}


NTSTATUS
LsapCallRm(
    IN RM_COMMAND_NUMBER CommandNumber,
    IN OPTIONAL PVOID CommandParams,
    IN ULONG CommandParamsLength,
    OUT OPTIONAL PVOID ReplyBuffer,
    IN ULONG ReplyBufferLength
    )

/*++

Routine Description:

    This function sends a command to the Reference Monitor from the LSA via
    the LSA Command LPC Port.  This function must only be called from within
    Lsa code.  If the command has parameters, they will be copied directly
    into a message structure and sent via LPC, therefore, the supplied
    parameters may not contain any absolute pointers.  A caller must remove
    pointers by "marshalling" them into the buffer CommandParams.

    To implement a new RM command, do the following:
    ================================================

    (1)  Provide in the executive an RM worker routine called
         SepRm<command>Wrkr to service the command.  See file
         ntos\se\rmmain.c for examples.  NOTE: If the command takes
         parameters, they must not contain any absolute pointers (addresses).

    (2)  In file private\inc\ntrmlsa.h, append the name of the new command to
         the enumerated type RM_COMMAND_NUMBER.  Change the #define for
         RmMaximumCommand to reference the new command.

    (3)  Add the SepRm<command>Wrkr to the command dispatch table structure
         SepRmCommandDispatch[] in file ntos\se\rmmain.c.

    (4)  Add function prototypes to lsap.h and sep.h.

Arguments:

    CommandNumber - Specifies the command

    CommandParams - Optional command-dependent parameters.  The parameters
        must be in marshalled format, that is, there must not be any
        absolute address pointers in the buffer.

    CommandParamsLength - Length in bytes of command parameters.  Must be 0
          if no command parameters supplied.

    ReplyBuffer - Reply Buffer in which data (if any) from the command will
        be returned.

    ReplyBufferLength - Length of ReplyBuffer in bytes.

Return Value:

    NTSTATUS - Result Code.  This is either a result code returned from
        trying to send the command/receive the reply, or a status code
        from the command itself.

--*/

{
    NTSTATUS Status;
    RM_COMMAND_MESSAGE CommandMessage;
    RM_REPLY_MESSAGE ReplyMessage;

    //
    // Assert that the Command Number is valid.
    //

    ASSERT( CommandNumber >= RmMinimumCommand &&
            CommandNumber <= RmMaximumCommand );

    //
    // If command parameters are supplied, assert that the length of the
    // command parameters is positive and not too large.  If no command
    // parameters are supplied, assert that the length field is 0.
    //

    ASSERT( ( ARGUMENT_PRESENT( CommandParams ) &&
              CommandParamsLength > 0 &&
              CommandParamsLength <= RM_MAXIMUM_COMMAND_PARAM_SIZE ) ||

            ( !ARGUMENT_PRESENT( CommandParams ) &&
              CommandParamsLength == 0 )
          );

    //
    // If a Reply Buffer is provided, assert that its length is > 0
    // and not too large.
    //

    ASSERT( ( ARGUMENT_PRESENT( ReplyBuffer ) &&
              ReplyBufferLength > 0 &&
              ReplyBufferLength <= LSA_MAXIMUM_REPLY_BUFFER_SIZE ) ||

            ( !ARGUMENT_PRESENT( ReplyBuffer ) &&
              ReplyBufferLength == 0 )
          );

    //
    // Construct a message for LPC.  First, fill in the message header
    // fields for LPC, specifying the message type and data sizes for
    // the outgoing CommandMessage and the incoming ReplyMessage.
    //

    CommandMessage.MessageHeader.u2.ZeroInit = 0;
    CommandMessage.MessageHeader.u1.s1.TotalLength =
        ((CSHORT) RM_COMMAND_MESSAGE_HEADER_SIZE +
        (CSHORT) CommandParamsLength);
    CommandMessage.MessageHeader.u1.s1.DataLength =
        CommandMessage.MessageHeader.u1.s1.TotalLength -
        (CSHORT) sizeof(PORT_MESSAGE);

    ReplyMessage.MessageHeader.u2.ZeroInit = 0;
    ReplyMessage.MessageHeader.u1.s1.DataLength = (CSHORT) ReplyBufferLength;
    ReplyMessage.MessageHeader.u1.s1.TotalLength =
        ReplyMessage.MessageHeader.u1.s1.DataLength +
        (CSHORT) sizeof(PORT_MESSAGE);

    //
    // Next, fill in the header info needed by the Reference Monitor.
    //

    CommandMessage.CommandNumber = CommandNumber;
    ReplyMessage.ReturnedStatus = STATUS_SUCCESS;

    //
    // Finally, copy the command parameters (if any) into the message buffer.
    //

    if (CommandParamsLength > 0) {

        RtlCopyMemory(CommandMessage.CommandParams,CommandParams,CommandParamsLength);
    }

    // Send Message to the RM via the RM Command Server LPC Port
    //

    Status = NtRequestWaitReplyPort(
                 LsapState.RmCommandPortHandle,
                 (PPORT_MESSAGE) &CommandMessage,
                 (PPORT_MESSAGE) &ReplyMessage
                 );

    //
    // If the command was successful, copy the data back to the output
    // buffer.
    //

    if (NT_SUCCESS(Status)) {

        //
        // Move output from command (if any) to buffer.  Note that this
        // is done even if the command returns status, because some status
        // values are not errors.
        //

        if (ARGUMENT_PRESENT(ReplyBuffer)) {

            RtlCopyMemory(
                ReplyBuffer,
                ReplyMessage.ReplyBuffer,
                ReplyBufferLength
                );

        }

        //
        // Return status from command.
        //

        Status = ReplyMessage.ReturnedStatus;

    } else {

        KdPrint(("Security: Command sent from LSA to RM returned 0x%lx\n",Status));
    }

    return Status;
}



NTSTATUS
LsapComponentTestWrkr(
    IN PLSA_COMMAND_MESSAGE CommandMessage,
    OUT PLSA_REPLY_MESSAGE ReplyMessage
    )

/*++

Routine Description:

    This function processes the Component Test LSA Rm Server command.
    This is a temporary command that can be used to verifiey that the link
    from RM to LSA is working.

Arguments:

    CommandMessage - Pointer to structure containing LSA command message
        information consisting of an LPC PORT_MESSAGE structure followed
        by the command number (LsapComponentTestCommand).  This command
        currently has one parameter, the fixed value 0x1234567.

    ReplyMessage - Pointer to structure containing LSA reply message
        information consisting of an LPC PORT_MESSAGE structure followed
        by the command ReturnedStatus field in which a status code from the
        command will be returned.

Return Value:

    STATUS_SUCCESS - The test call has completed successfully.

    STATUS_INVALID_PARAMETER - The argument value received was not the
        expected argument value.

--*/

{
    NTSTATUS Status = STATUS_SUCCESS;

    //
    // Strict check that command is correct.
    //

    ASSERT( CommandMessage->CommandNumber == LsapComponentTestCommand );

    KdPrint(("Security: LSA Component Test Command Received\n"));

    //
    // Verify that the parameter value passed is as expected.
    //

    if (*((ULONG *) CommandMessage->CommandParams) !=
        LSA_CT_COMMAND_PARAM_VALUE ) {

        Status = STATUS_INVALID_PARAMETER;
    }

    UNREFERENCED_PARAMETER(ReplyMessage); // Intentionally not referenced
    return(Status);
}
