/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    ELFLPC.C

Abstract:

    This file contains the routines that deal with the LPC port in the
    eventlog service.

Author:

    Rajen Shah  (rajens)    10-Jul-1991

Revision History:



--*/

//
// INCLUDES
//

#include <eventp.h>
#include <ntiolog.h>       // For IO_ERROR_LOG_[MESSAGE/PACKET]
#include <elflpc.h>
#include <wcstr.h>
#include <memory.h>
#include <elfextrn.h> // Computername

#include <nt.h>         // DbgPrint prototype
#include <ntrtl.h>      // DbgPrint prototype
#include <ntdef.h>
#include <ntstatus.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windef.h>
#include <winbase.h>    // LocalAlloc
#include <lmcons.h>
#include <string.h>
#include <lmerr.h>

NTSTATUS
SetUpLPCPort ()


/*++

Routine Description:

    This routine sets up the LPC port for the service.


Arguments:



Return Value:



Note:


--*/
{

    NTSTATUS status;
    UNICODE_STRING unicodePortName;
    OBJECT_ATTRIBUTES objectAttributes;
    PORT_MESSAGE connectionRequest;


    ElfDbgPrint (("[ELF] Set up LPC port\n"));

    // Initialize the handles to zero so that we can determine what to do
    // if we need to clean up.

    ElfConnectionPortHandle = NULL;
    ElfCommunicationPortHandle = NULL;

    //
    // Create the LPC port.
    //
    RtlInitUnicodeString( &unicodePortName, ELF_PORT_NAME_U );

    InitializeObjectAttributes(
            &objectAttributes,
            &unicodePortName,
            OBJ_CASE_INSENSITIVE,
            NULL,
            NULL
            );

    status = NtCreatePort(
                    &ElfConnectionPortHandle,
                    &objectAttributes,
                    0,
                    ELF_PORT_MAX_MESSAGE_LENGTH,
                    ELF_PORT_MAX_MESSAGE_LENGTH * 32
                    );

    if ( !NT_SUCCESS(status) ) {
        ElfDbgPrintNC(( "[ELF] Port not created\n" ));
        goto exit;
    }

    //
    // Start listening for the system thread's connection to the port.  Note
    // that it is OK if it happens to call NtConnectPort first--it will
    // simply block until this call to NtListenPort occurs.
    //
    ElfDbgPrint(( "[ELF] Listening to port.\n" ));

    connectionRequest.u1.s1.TotalLength = sizeof(connectionRequest);
    connectionRequest.u1.s1.DataLength = (CSHORT)0;
    status = NtListenPort(
                 ElfConnectionPortHandle,
                 &connectionRequest
                 );

    if ( !NT_SUCCESS(status) ) {
        ElfDbgPrintNC(( "[ELF] NtListenPort failed: %X\n", status ));
        goto exit;
    }

    //
    // The system thread has initiated the connection. Accept the connection.
    //
    // BUGBUG  We need some security check here.
    //
    ElfDbgPrint(( "[ELF] Accepting connection to port.\n" ));

    status = NtAcceptConnectPort(
                 &ElfCommunicationPortHandle,
                 NULL,                           // PortContext
                 &connectionRequest,
                 TRUE,                           // AcceptConnection
                 NULL,                           // ServerView
                 NULL                            // ClientView
                 );

    if ( !NT_SUCCESS(status) ) {
            ElfDbgPrintNC(( "[ELF] NtAcceptConnectPort failed: %X\n", status ));
       goto exit;
    }


    //
    // Complete the connection to the port, thereby releasing the system
    // thread waiting in NtConnectPort.
    //

        ElfDbgPrint(( "[ELF] Completing connection to port.\n" ));

    status = NtCompleteConnectPort( ElfCommunicationPortHandle );

    if ( !NT_SUCCESS(status) ) {
        ElfDbgPrintNC(( "[ELF] NtCompleteConnectPort failed: %X\n",
                  status ));
       goto exit;
    }

exit:

    //
    // Close open handles and shut down if necessary.
    //

    if ( !NT_SUCCESS(status) && ElfConnectionPortHandle != NULL ) {
       NtClose( ElfConnectionPortHandle );
    }

    if ( !NT_SUCCESS(status) && ElfCommunicationPortHandle != NULL ) {
       NtClose( ElfConnectionPortHandle );
    }

    return(status);

}



NTSTATUS
ElfProcessLPCPacket ( PIO_ERROR_LOG_MESSAGE pIoErrorLogMessage
    )

/*++

Routine Description:

    This routine takes the packet received from the LPC port and processes it.
    The only thing that comes in thru the LPC port are packets generated by
    device drivers and written to this port by the IO Subsystem.  The packet is
    an IO_ERROR_LOG_MESSAGE.  The logfile will be system, the module name will
    be the driver that generated the packet, the SID will always be NULL and
    there will always be one string, which will be the device name.

    It extracts the information from the LPC packet, and then calls the
    common routine to do the work of formatting the data into
    an event record and writing it out to the log file.


Arguments:

        pIoErrorLogMessage - Pointer to the data portion of the packet just
                             received through the LPC port.


Return Value:

        Status of this operation.


--*/

{
    static PLOGMODULE SystemModule = NULL;

    NTSTATUS status;
    ELF_REQUEST_RECORD  Request;
    WRITE_PKT WritePkt;

    UNICODE_STRING SystemString;
    ULONG RecordLength;
    PEVENTLOGRECORD EventLogRecord;
    LPWSTR DestinationString, SourceString;
    PBYTE BinaryData;
    ULONG PadSize;
    LARGE_INTEGER Time;
    ULONG TimeWritten;
    PULONG pEndLength;
    ULONG i = 0;
    PWCHAR pwch;
    PWCHAR pwStart;
    PWCHAR pwEnd;
    ULONG StringLength;

    try {

        //
        // Validate the packet, First make sure there are the correct
        // number of NULL terminated strings, and remember the
        // total number of bytes to copy
        //

        pwStart = pwch = (PWCHAR) ((PBYTE) pIoErrorLogMessage +
            pIoErrorLogMessage->EntryData.StringOffset);

        pwEnd = (PWCHAR) ((PBYTE) pIoErrorLogMessage +
            pIoErrorLogMessage->Size);

        while (pwch < pwEnd &&
            i < pIoErrorLogMessage->EntryData.NumberOfStrings) {
                if (*pwch == L'\0') {
                    i++;
                }
                pwch++;
        }
        StringLength = (pwch - pwStart) * sizeof(WCHAR);

        //
        // Now make sure everything in the packet is true
        //

        if ((i != pIoErrorLogMessage->EntryData.NumberOfStrings)

                ||

            (pIoErrorLogMessage->DriverNameOffset +
                pIoErrorLogMessage->DriverNameLength >=
                pIoErrorLogMessage->Size)

                ||


            (pIoErrorLogMessage->EntryData.StringOffset >=
                pIoErrorLogMessage->Size)

                ||

            (FIELD_OFFSET(IO_ERROR_LOG_MESSAGE, EntryData) +
                FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) +
                pIoErrorLogMessage->EntryData.DumpDataSize >=
                pIoErrorLogMessage->Size)) {

                    //
                    // It's a bad packet, log it and return
                    //

                    ElfDbgPrintNC(("[ELF] Bad packet from LPC port\n"));
                    ElfpCreateElfEvent(EVENT_BadDriverPacket,
                                       EVENTLOG_ERROR_TYPE,
                                       0,                    // EventCategory
                                       0,                    // NumberOfStrings
                                       NULL,                 // Strings
                                       pIoErrorLogMessage,   // Data
                                       pIoErrorLogMessage->Size, // Datalength
                                       0                     // flags
                                       );
                    return(STATUS_UNSUCCESSFUL);
        }
    }
    except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // It's a bad packet, log it and return
        //

        ElfDbgPrintNC(("[ELF] Bad packet from LPC port\n"));
        ElfpCreateElfEvent(EVENT_BadDriverPacket,
                           EVENTLOG_ERROR_TYPE,
                           0,                    // EventCategory
                           0,                    // NumberOfStrings
                           NULL,                 // Strings
                           pIoErrorLogMessage,   // Data
                           pIoErrorLogMessage->Size, // Datalength
                           0                     // flags
                           );
        return(STATUS_UNSUCCESSFUL);

    }

    //
    // We're going to need this everytime, so just get it once
    //

    if (!SystemModule) {

        //
        // Get the system module to log driver events
        //

        RtlInitUnicodeString(&SystemString, ELF_SYSTEM_MODULE_NAME);
        SystemModule = GetModuleStruc (&SystemString);
        ASSERT(SystemModule); // GetModuleStruc never returns NULL

    }

    //
    // The packet should be an IO_ERROR_LOG_MESSAGE
    //

    ASSERT(pIoErrorLogMessage->Type == IO_TYPE_ERROR_MESSAGE);

    //
    // Set up write packet in request packet
    //

    Request.Pkt.WritePkt = &WritePkt;
    Request.Flags = 0;

    //
    // Generate any additional information needed in the record.
    //

    // TIMEWRITTEN
    // We need to generate a time when the log is written. This
    // gets written in the log so that we can use it to test the
    // retention period when wrapping the file.
    //

    NtQuerySystemTime(&Time);
    RtlTimeToSecondsSince1970(
                        &Time,
                        &TimeWritten
                        );

    //
    // Determine how big a buffer is needed for the eventlog record.
    //

    RecordLength = sizeof(EVENTLOGRECORD)
                 + ComputerNameLength             // computername
                 + 2 * sizeof(WCHAR)                    // term's
                 + pIoErrorLogMessage->Size
                 - FIELD_OFFSET(IO_ERROR_LOG_MESSAGE, EntryData)
                 + sizeof(RecordLength);                // final len

    //
    // Determine how many pad bytes are needed to align to a DWORD
    // boundary.
    //

    PadSize = sizeof(ULONG) - (RecordLength % sizeof(ULONG));

    RecordLength += PadSize;    // True size needed

    //
    // Allocate the buffer for the Eventlog record
    //

    EventLogRecord = (PEVENTLOGRECORD) ElfpAllocateBuffer(RecordLength);

    if (EventLogRecord != (PEVENTLOGRECORD) NULL) {

        //
        // Fill up the event record
        //

        EventLogRecord->Length = RecordLength;
        RtlTimeToSecondsSince1970(
                        &pIoErrorLogMessage->TimeStamp,
                        &EventLogRecord->TimeGenerated
                        );
        EventLogRecord->Reserved = ELF_LOG_FILE_SIGNATURE;
        EventLogRecord->TimeWritten = TimeWritten;
        EventLogRecord->EventID = pIoErrorLogMessage->EntryData.ErrorCode;

        // set EventType based on the high order nibble of
        // pIoErrorLogMessage->EntryData.ErrorCode

        if (NT_INFORMATION(pIoErrorLogMessage->EntryData.ErrorCode)) {

                EventLogRecord->EventType =  EVENTLOG_INFORMATION_TYPE;

        }
        else if (NT_WARNING(pIoErrorLogMessage->EntryData.ErrorCode)) {

                EventLogRecord->EventType =  EVENTLOG_WARNING_TYPE;

        }
        else if (NT_ERROR(pIoErrorLogMessage->EntryData.ErrorCode)) {

                EventLogRecord->EventType = EVENTLOG_ERROR_TYPE;

        }
        else {

            //
            // Unknown, set to error
            //

            EventLogRecord->EventType = EVENTLOG_ERROR_TYPE;

        }

        EventLogRecord->NumStrings =
            pIoErrorLogMessage->EntryData.NumberOfStrings;
        EventLogRecord->EventCategory =
            pIoErrorLogMessage->EntryData.EventCategory;
        EventLogRecord->StringOffset = sizeof(EVENTLOGRECORD) +
           pIoErrorLogMessage->DriverNameLength + ComputerNameLength;
        EventLogRecord->DataLength =
            FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) +
            pIoErrorLogMessage->EntryData.DumpDataSize;
        EventLogRecord->DataOffset = EventLogRecord->StringOffset +
            StringLength;
        EventLogRecord->UserSidLength = 0;
        EventLogRecord->UserSidOffset = 0;

        //
        // Fill in the variable-length fields

        // MODULENAME
        //
        // Use the driver name as the module name, since it's location is
        // described by an offset from the start of the IO_ERROR_LOG_MESSAGE
        // turn it into a pointer
        //

        DestinationString = (LPWSTR)((LPBYTE)EventLogRecord +
           sizeof(EVENTLOGRECORD));
        SourceString = (LPWSTR)((LPBYTE) pIoErrorLogMessage +
            pIoErrorLogMessage->DriverNameOffset);

        RtlMoveMemory(DestinationString, SourceString,
            pIoErrorLogMessage->DriverNameLength);

        //
        // Make sure it's NULL terminated
        //

        DestinationString = (LPWSTR)((LPBYTE) DestinationString +
           pIoErrorLogMessage->DriverNameLength) - 1;
        if (*DestinationString != L'\0') {
            *(++DestinationString) = L'\0';
        }

        DestinationString++;

        // COMPUTERNAME
        //

        RtlMoveMemory(DestinationString, LocalComputerName,
            ComputerNameLength);

        //
        // Make sure it's NULL terminated
        //

        DestinationString = (LPWSTR)((LPBYTE) DestinationString +
           ComputerNameLength) - 1;
        if (*DestinationString != L'\0') {
            *(++DestinationString) = L'\0';
        }

        DestinationString++;

        // STRING
        //

        SourceString = pwStart;

        RtlMoveMemory(DestinationString, SourceString, StringLength);

        DestinationString += (StringLength / 2);

        // BINARY DATA
        //
        BinaryData = (LPBYTE) DestinationString;


        RtlMoveMemory (BinaryData, & pIoErrorLogMessage->EntryData,
           FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) +
           pIoErrorLogMessage->EntryData.DumpDataSize);

        // LENGTH at end of record
        //

        pEndLength = (PULONG)((LPBYTE) EventLogRecord + RecordLength -
            sizeof(ULONG));
        *pEndLength = RecordLength;

        //
        // Set up request packet.
        // Link event log record into the request structure.
        //

        Request.Module = SystemModule;
        Request.LogFile = Request.Module->LogFile;
        Request.Command = ELF_COMMAND_WRITE;
        Request.Pkt.WritePkt->Buffer = (PVOID)EventLogRecord;
        Request.Pkt.WritePkt->Datasize = RecordLength;

        //
        // Perform the operation
        //

        ElfPerformRequest( &Request );

        //
        // Free up the buffer
        //

        ElfpFreeBuffer(EventLogRecord );

        status = Request.Status;                // Set status of WRITE

    } else {
        status = STATUS_NO_MEMORY;
    }

    return (status);
}



NTSTATUS
ElfProcessLPCCalls (
    )

/*++

Routine Description:

    This routine waits for messages to come through the LPC port to
    the system thread. When one does, it calls the appropriate routine to
    handle the API, then replies to the system thread indicating that the
    call has completed if the message was a request, if it was a datagram,
    it just waits for the next message.

Arguments:


Return Value:

--*/

{
    NTSTATUS status;

    BOOL SendReply = FALSE;

    ELF_REPLY_MESSAGE replyMessage;
    PELFIOPORTMSG receiveMessage;

    //
    // Loop dispatching API requests.
    //

    receiveMessage = ElfpAllocateBuffer(ELF_PORT_MAX_MESSAGE_LENGTH +
        sizeof(PORT_MESSAGE));
    if (!receiveMessage) {
        return(STATUS_NO_MEMORY);
    }

    while ( TRUE ) {

        //
        // On the first call to NtReplyWaitReceivePort, don't send a
        // reply since there's nobody to reply to.  However, on subsequent
        // calls the reply to the message from the prior time if that message
        // wasn't a LPC_DATAGRAM
        //

        status = NtReplyWaitReceivePort(
                     ElfConnectionPortHandle,
                     NULL,                       // PortContext
                     (PPORT_MESSAGE)( SendReply ? &replyMessage : NULL),
                     (PPORT_MESSAGE) receiveMessage
                     );

        if ( !NT_SUCCESS(status) ) {
            ElfDbgPrintNC(( "[ELF] ElfProcessLPCCalls: NtReplyWaitReceivePort failed: %X\n",
                              status ));
            return status;
        }

        ElfDbgPrint(( "[ELF] ElfProcessLPCCalls: received message\n" ));

        //
        // Take the record received and perform the operation.  Strip off
        // the PortMessage and just send the packet
        //


        //
        // Set up the response message to be sent on the next call to
        // NtReplyWaitReceivePort if this wasn't a datagram.
        // 'status' contains the status to return from this call.
        // Only process messages that are LPC_REQUEST or LPC_DATAGRAM
        //

        if (receiveMessage->PortMessage.u2.s2.Type == LPC_REQUEST) {
            status = ElfProcessLPCPacket (& receiveMessage->IoErrorLogMessage);

            replyMessage.PortMessage.u1.s1.DataLength =
                sizeof(replyMessage) - sizeof(PORT_MESSAGE);
            replyMessage.PortMessage.u1.s1.TotalLength = sizeof(replyMessage);
            replyMessage.PortMessage.u2.ZeroInit = 0;
            replyMessage.PortMessage.ClientId =
                receiveMessage->PortMessage.ClientId;
            replyMessage.PortMessage.MessageId =
               receiveMessage->PortMessage.MessageId;
            replyMessage.Status = status;

            SendReply = TRUE;
        }
        else if (receiveMessage->PortMessage.u2.s2.Type == LPC_DATAGRAM) {
            status = ElfProcessLPCPacket (& receiveMessage->IoErrorLogMessage);
            SendReply = FALSE;
        }
        else {
            //
            // We received a message type we didn't expect, probably due to
            // error.  BUGBUG - write an event
            //

            ElfDbgPrintNC(("[ELF] Unexpected message type received on LPC port\n"));
            ElfDbgPrintNC(("[ELF] Messaage type = %d\n",
                receiveMessage->PortMessage.u2.s2.Type));

        }
   }

} // ElfProcessLPCCalls



DWORD
MainLPCThread (
        LPVOID      LPCThreadParm
        )

/*++

Routine Description:

    This is the main thread that monitors the LPC port from the I/O system.
    It takes care of creating the LPC port, and waiting for input, which
    it then transforms into the right operation on the event log.


Arguments:

    NONE

Return Value:

    NONE

--*/

{
    NTSTATUS    Status;

    ElfDbgPrint(( "[ELF] Inside LPC thread\n" ));

    Status = SetUpLPCPort();

    if (NT_SUCCESS(Status)) {

        //
        // Loop forever. This thread will be killed when the service terminates.
        //
        while (TRUE) {

            Status = ElfProcessLPCCalls ();

        }

    }
    ElfDbgPrintNC (("[ELF] Error from SetUpLPCPort. Status = %lx\n", Status));

    return (Status);

    UNREFERENCED_PARAMETER ( LPCThreadParm );
}



BOOL
StartLPCThread ()

/*++

Routine Description:

    This routine starts up the thread that monitors the LPC port.

Arguments:

    NONE

Return Value:

    TRUE if thread creation succeeded, FALSE otherwise.

Note:


--*/
{
    DWORD       error;
    DWORD       ThreadId;

    ElfDbgPrint(( "[ELF] Start up the LPC thread\n" ));

    //
    // Start up the actual thread.
    //

    LPCThreadHandle = CreateThread(
                            NULL,               // lpThreadAttributes
                            4096,               // dwStackSize
                            MainLPCThread,      // lpStartAddress
                            NULL,               // lpParameter
                            0L,                 // dwCreationFlags
                            &ThreadId        // lpThreadId
                            );

    if ( LPCThreadHandle == NULL ) {
        error = GetLastError();
        ElfDbgPrintNC(( "[ELF]: LPCThread - CreateThread failed: %ld\n", error ));
        return (FALSE);
    }
    return (TRUE);
}
