/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    adtlog.c

Abstract:

    Auditing - Audit Record Queuing and Logging Routines

    This file contains functions that construct Audit Records in self-
    relative form from supplied information, enqueue/dequeue them and
    write them to the log.

Author:

    Scott Birrell       (ScottBi)       November 8, 1991

Environment:

    Kernel Mode only

Revision History:

--*/

#include "pch.h"

#pragma hdrstop


#ifdef ALLOC_PRAGMA

#pragma alloc_text(PAGE,SepAdtLogAuditRecord)
#pragma alloc_text(PAGE,SepAuditFailed)
#pragma alloc_text(PAGE,SepAdtMarshallAuditRecord)
#pragma alloc_text(PAGE,SepAdtSetAuditLogInformation)
#pragma alloc_text(PAGE,SepAdtCopyToLsaSharedMemory)
#pragma alloc_text(PAGE,SepQueueWorkItem)
#pragma alloc_text(PAGE,SepDequeueWorkItem)

#endif

VOID
SepAdtLogAuditRecord(
    IN PSE_ADT_PARAMETER_ARRAY AuditParameters
    )

/*++

Routine Description:

    This function manages the logging of Audit Records.  It provides the
    single interface to the Audit Logging component from the Audit/Alarm
    generation routines.  The function constructs an Audit Record in
    self-relative format from the information provided and appends it to
    the Audit Record Queue, a doubly-linked list of Audit Records awaiting
    output to the Audit Log.  A dedicated thread reads this queue, writing
    Audit Records to the Audit Log and removing them from the Audit Queue.

Arguments:

    AuditEventType - Specifies the type of the Audit Event described by
        the audit information provided.

    AuditInformation - Pointer to buffer containing captured auditing
        information related to an Audit Event of type AuditEventType.

Return Value:

    STATUS_SUCCESS
    STATUS_UNSUCCESSFUL - Audit record was not queued
    STATUS_INSUFFICIENT_RESOURCES - unable to allocate heap

--*/

{
    NTSTATUS Status;
    PSEP_LSA_WORK_ITEM AuditWorkItem;

    PAGED_CODE();

    AuditWorkItem = ExAllocatePoolWithTag( PagedPool, sizeof( SEP_LSA_WORK_ITEM ), 'iAeS' );

    if ( AuditWorkItem == NULL ) {

        SepAuditFailed();
        return;
    }

    AuditWorkItem->Tag = SepAuditRecord;
    AuditWorkItem->CommandNumber = LsapWriteAuditMessageCommand;
    AuditWorkItem->ReplyBuffer = NULL;
    AuditWorkItem->ReplyBufferLength = 0;
    AuditWorkItem->CleanupFunction = NULL;

    //
    // Build an Audit record in self-relative format from the supplied
    // Audit Information.
    //

    Status = SepAdtMarshallAuditRecord(
                 AuditParameters,
                 (PSE_ADT_PARAMETER_ARRAY *) &AuditWorkItem->CommandParams.BaseAddress,
                 &AuditWorkItem->CommandParamsMemoryType
                 );

    if (NT_SUCCESS(Status)) {

        //
        // Extract the length of the Audit Record.  Store it as the length
        // of the Command Parameters buffer.
        //

        AuditWorkItem->CommandParamsLength =
            ((PSE_ADT_PARAMETER_ARRAY) AuditWorkItem->CommandParams.BaseAddress)->Length;

        //
        // If we're going to crash on a discarded audit, ignore the queue bounds
        // check and force the item onto the queue.
        //

        if (!SepQueueWorkItem( AuditWorkItem, (BOOLEAN)(SepCrashOnAuditFail ? TRUE : FALSE) )) {

            ExFreePool( AuditWorkItem->CommandParams.BaseAddress );
            ExFreePool( AuditWorkItem );

            //
            // We failed to put the record on the queue.  Take whatever action is
            // appropriate.
            //

            SepAuditFailed();
        }

    } else {

        ExFreePool( AuditWorkItem );
        SepAuditFailed();
    }
}



VOID
SepAuditFailed(
    VOID
    )

/*++

Routine Description:

    Bugchecks the system due to a missed audit (optional requirement
    for C2 compliance).

Arguments:

    None.

Return Value:

    None.

--*/

{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES Obja;
    HANDLE KeyHandle;
    UNICODE_STRING KeyName;
    UNICODE_STRING ValueName;
    UCHAR NewValue;

    ASSERT(sizeof(UCHAR) == sizeof(BOOLEAN));

    if (!SepCrashOnAuditFail) {
        return;
    }

    //
    // Turn off flag in the registry that controls crashing on audit failure
    //

    RtlInitUnicodeString( &KeyName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Lsa");

    InitializeObjectAttributes( &Obja,
                                &KeyName,
                                OBJ_CASE_INSENSITIVE | 
                                    OBJ_KERNEL_HANDLE,
                                NULL,
                                NULL
                                );
    do {

        Status = ZwOpenKey(
                     &KeyHandle,
                     KEY_SET_VALUE,
                     &Obja
                     );

    } while ((Status == STATUS_INSUFFICIENT_RESOURCES) || (Status == STATUS_NO_MEMORY));

    //
    // If the LSA key isn't there, he's got big problems.  But don't crash.
    //

    if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
        SepCrashOnAuditFail = FALSE;
        return;
    }

    if (!NT_SUCCESS( Status )) {
        goto bugcheck;
    }

    RtlInitUnicodeString( &ValueName, CRASH_ON_AUDIT_FAIL_VALUE );

    NewValue = LSAP_ALLOW_ADIMIN_LOGONS_ONLY;

    do {

        Status = ZwSetValueKey( KeyHandle,
                                &ValueName,
                                0,
                                REG_NONE,
                                &NewValue,
                                sizeof(UCHAR)
                                );

    } while ((Status == STATUS_INSUFFICIENT_RESOURCES) || (Status == STATUS_NO_MEMORY));
    ASSERT(NT_SUCCESS(Status));

    if (!NT_SUCCESS( Status )) {
        goto bugcheck;
    }

    do {

        Status = ZwFlushKey( KeyHandle );

    } while ((Status == STATUS_INSUFFICIENT_RESOURCES) || (Status == STATUS_NO_MEMORY));
    ASSERT(NT_SUCCESS(Status));

    //
    // go boom.
    //

bugcheck:

    KeBugCheckEx(AUDIT_FAILURE, 0, 0, 0, 0);
}



NTSTATUS
SepAdtMarshallAuditRecord(
    IN PSE_ADT_PARAMETER_ARRAY AuditParameters,
    OUT PSE_ADT_PARAMETER_ARRAY *MarshalledAuditParameters,
    OUT PSEP_RM_LSA_MEMORY_TYPE RecordMemoryType
    )

/*++

Routine Description:

    This routine will take an AuditParamters structure and create
    a new AuditParameters structure that is suitable for sending
    to LSA.  It will be in self-relative form and allocated as
    a single chunk of memory.

Arguments:


    AuditParameters - A filled in set of AuditParameters to be marshalled.

    MarshalledAuditParameters - Returns a pointer to a block of heap memory
        containing the passed AuditParameters in self-relative form suitable
        for passing to LSA.


Return Value:

    None.

--*/

{
    ULONG i;
    ULONG TotalSize = sizeof( SE_ADT_PARAMETER_ARRAY );
    PUNICODE_STRING TargetString;
    PCHAR Base;
    ULONG BaseIncr;
    ULONG Size;
    PSE_ADT_PARAMETER_ARRAY_ENTRY pInParam, pOutParam;

    PAGED_CODE();

    //
    // Calculate the total size required for the passed AuditParameters
    // block.  This calculation will probably be an overestimate of the
    // amount of space needed, because data smaller that 2 dwords will
    // be stored directly in the parameters structure, but their length
    // will be counted here anyway.  The overestimate can't be more than
    // 24 dwords, and will never even approach that amount, so it isn't
    // worth the time it would take to avoid it.
    //

    for (i=0; i<AuditParameters->ParameterCount; i++) {
        Size = AuditParameters->Parameters[i].Length;
        TotalSize += PtrAlignSize( Size );
    }

    //
    // Allocate a big enough block of memory to hold everything.
    // If it fails, quietly abort, since there isn't much else we
    // can do.
    //

    *MarshalledAuditParameters = ExAllocatePoolWithTag( PagedPool, TotalSize, 'pAeS' );

    if (*MarshalledAuditParameters == NULL) {

        *RecordMemoryType = SepRmNoMemory;
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    *RecordMemoryType = SepRmPagedPoolMemory;

    RtlCopyMemory (
       *MarshalledAuditParameters,
       AuditParameters,
       sizeof( SE_ADT_PARAMETER_ARRAY )
       );

   (*MarshalledAuditParameters)->Length = TotalSize;
   (*MarshalledAuditParameters)->Flags  = SE_ADT_PARAMETERS_SELF_RELATIVE;

    pInParam  = &AuditParameters->Parameters[0];
    pOutParam = &((*MarshalledAuditParameters)->Parameters[0]);
   
    //
    // Start walking down the list of parameters and marshall them
    // into the target buffer.
    //

    Base = (PCHAR) ((PCHAR)(*MarshalledAuditParameters) + sizeof( SE_ADT_PARAMETER_ARRAY ));

    for (i=0; i<AuditParameters->ParameterCount; i++, pInParam++, pOutParam++) {


        switch (AuditParameters->Parameters[i].Type) {
            case SeAdtParmTypeNone:
            case SeAdtParmTypeUlong:
            case SeAdtParmTypeLogonId:
            case SeAdtParmTypeNoLogonId:
            case SeAdtParmTypeTime:
            case SeAdtParmTypeAccessMask:
            case SeAdtParmTypePtr:
                {
                    //
                    // Nothing to do for this
                    //

                    break;

                }
            case SeAdtParmTypeString:
            case SeAdtParmTypeFileSpec:
                {
                    PUNICODE_STRING SourceString;
                    //
                    // We must copy the body of the unicode string
                    // and then copy the body of the string.  Pointers
                    // must be turned into offsets.

                    TargetString = (PUNICODE_STRING)Base;

                    SourceString = pInParam->Address;

                    *TargetString = *SourceString;

                    //
                    // Reset the data pointer in the output parameters to
                    // 'point' to the new string structure.
                    //

                    pOutParam->Address = Base - (ULONG_PTR)(*MarshalledAuditParameters);

                    Base += sizeof( UNICODE_STRING );

                    RtlCopyMemory( Base, SourceString->Buffer, SourceString->Length );

                    //
                    // Make the string buffer in the target string point to where we
                    // just copied the data.
                    //

                    TargetString->Buffer = (PWSTR)(Base - (ULONG_PTR)(*MarshalledAuditParameters));

                    BaseIncr = PtrAlignSize(SourceString->Length);

                    Base += BaseIncr;

                    ASSERT( (ULONG_PTR)Base <= (ULONG_PTR)(*MarshalledAuditParameters) + TotalSize );
                    break;
                }

            //
            // Handle types where we simply copy the buffer.
            //
            case SeAdtParmTypePrivs:
// #if DBG
//                 {
//                     PPRIVILEGE_SET Privileges =
//                         (PPRIVILEGE_SET) pInParam->Address;
//                     ULONG i;

//                     for (i = 0; i < Privileges->PrivilegeCount; i++)
//                     {
//                         ASSERT( Privileges->Privilege[i].Luid.HighPart == 0);
//                     }
//                 }
// #endif

            case SeAdtParmTypeSid:
            case SeAdtParmTypeObjectTypes:
                {
                    //
                    // Copy the data into the output buffer
                    //

                    RtlCopyMemory( Base, pInParam->Address, pInParam->Length );

                    //
                    // Reset the 'address' of the data to be its offset in the
                    // buffer.
                    //

                    pOutParam->Address = Base - (ULONG_PTR)(*MarshalledAuditParameters);

                    Base +=  PtrAlignSize( pInParam->Length );


                    ASSERT( (ULONG_PTR)Base <= (ULONG_PTR)(*MarshalledAuditParameters) + TotalSize );
                    break;
                }

            default:
                {
                    //
                    // We got passed junk, complain.
                    //

                    ASSERT( FALSE );
                    break;
                }
        }
    }

    return( STATUS_SUCCESS );
}


VOID
SepAdtSetAuditLogInformation(
    IN PPOLICY_AUDIT_LOG_INFO AuditLogInformation
    )

/*++

Routine Description:

    This function stores Audit Log Information in the Reference Monitor's
    in-memory database.  This information contains parameters such as Audit
    Log size etc.  It is the caller's responsibility to ensure that the
    supplied information is valid.

    NOTE:  After initialization, this function is only called by the LSA
    via a Reference Monitor command.  This is a necessary restriction
    because the Audit Log Information stored in the LSA Database must
    remain in sync

Arguments:

    AuditLogInformation - Pointer to Audit Log Information structure.

Return Value:

    None.

--*/

{
    PAGED_CODE();

    //
    // Acquire Reference Monitor Database write lock.
    //

    SepRmAcquireDbWriteLock();

    //
    // Write the information
    //

    SepAdtLogInformation = *AuditLogInformation;

    //
    // Release Reference Monitor Database write lock
    //

    SepRmReleaseDbWriteLock();
}



NTSTATUS
SepAdtCopyToLsaSharedMemory(
    IN HANDLE LsaProcessHandle,
    IN PVOID Buffer,
    IN ULONG BufferLength,
    OUT PVOID *LsaBufferAddress
    )

/*++

Routine Description:

    This function allocates memory shared with the LSA and optionally copies
    a given buffer to it.

Arguments:

    LsaProcessHandle - Specifies a handle to the Lsa Process.

    Buffer - Pointer to the buffer to be copied.

    BufferLength - Length of buffer.

    LsaBufferAddress - Receives the address of the buffer valid in the
        Lsa process context.

Return Value:

    NTSTATUS - Standard Nt Result Code

        Result codes returned by called routines.
--*/

{
    NTSTATUS Status, SecondaryStatus;
    PVOID OutputLsaBufferAddress = NULL;
    SIZE_T RegionSize = BufferLength;

    PAGED_CODE();

    Status = ZwAllocateVirtualMemory(
                 LsaProcessHandle,
                 &OutputLsaBufferAddress,
                 0,
                 &RegionSize,
                 MEM_COMMIT,
                 PAGE_READWRITE
                 );

    if (!NT_SUCCESS(Status)) {

        goto CopyToLsaSharedMemoryError;
    }

    Status = ZwWriteVirtualMemory(
                 LsaProcessHandle,
                 OutputLsaBufferAddress,
                 Buffer,
                 BufferLength,
                 NULL
                 );

    if (!NT_SUCCESS(Status)) {

        goto CopyToLsaSharedMemoryError;
    }

    *LsaBufferAddress = OutputLsaBufferAddress;
    return(Status);

CopyToLsaSharedMemoryError:

    //
    // If we allocated memory, free it.
    //

    if (OutputLsaBufferAddress != NULL) {

        RegionSize = 0;

        SecondaryStatus = ZwFreeVirtualMemory(
                              LsaProcessHandle,
                              &OutputLsaBufferAddress,
                              &RegionSize,
                              MEM_RELEASE
                              );

        ASSERT(NT_SUCCESS(SecondaryStatus));

        OutputLsaBufferAddress = NULL;
    }

    return(Status);
}


BOOLEAN
SepQueueWorkItem(
    IN PSEP_LSA_WORK_ITEM LsaWorkItem,
    IN BOOLEAN ForceQueue
    )

/*++

Routine Description:

    Puts the passed work item on the queue to be passed to LSA,
    and returns the state of the queue upon arrival.

Arguments:

    LsaWorkItem - Pointer to the work item to be queued.

    ForceQueue - Indicate that this item is not to be discarded
        because of a full queue.

Return Value:

    TRUE - The item was successfully queued.

    FALSE - The item was not queued and must be discarded.

--*/

{
    BOOLEAN rc = TRUE;
    BOOLEAN StartExThread = FALSE ;

    PAGED_CODE();

#if 0

  DbgPrint("Queueing an audit\n");

#endif

    SepLockLsaQueue();

    //
    // See if LSA has died. If it has then just return with an error.
    //
    if (SepAdtLsaDeadEvent != NULL) {
        rc = FALSE;
        goto Exit;
    }

    if (SepAdtDiscardingAudits && !ForceQueue) {

        if (SepAdtCurrentListLength < SepAdtMinListLength) {

            //
            // We need to generate an audit saying how many audits we've
            // discarded.
            //
            // Since we have the mutex protecting the Audit queue, we don't
            // have to worry about anyone coming along and logging an
            // audit.  But *we* can, since a mutex may be acquired recursively.
            //
            // Since we are so protected, turn off the SepAdtDiscardingAudits
            // flag here so that we don't come through this path again.
            //

            SepAdtDiscardingAudits = FALSE;

            SepAdtGenerateDiscardAudit();
#if 0
            DbgPrint("Auditing resumed\n");
#endif

            //
            // We must assume that that worked, so clear the discard count.
            //

            SepAdtCountEventsDiscarded = 0;

            //
            // Our 'audits discarded' audit is now on the queue,
            // continue logging the one we started with.
            //

        } else {

            //
            // We are not yet below our low water mark.  Toss
            // this audit and increment the discard count.
            //

            SepAdtCountEventsDiscarded++;
            rc = FALSE;
            goto Exit;
        }
    }

    if (SepAdtCurrentListLength < SepAdtMaxListLength || ForceQueue) {

        InsertTailList(&SepLsaQueue, &LsaWorkItem->List);

        if (++SepAdtCurrentListLength == 1) {

#if 0
            DbgPrint("Queueing a work item\n");
#endif

            StartExThread = TRUE ;
        }

    } else {

        //
        // There is no room for this audit on the queue,
        // so change our state to 'discarding' and tell
        // the caller to toss this audit.
        //

        SepAdtDiscardingAudits = TRUE;

#if 0
        DbgPrint("Starting to discard audits\n");
#endif

        rc = FALSE;
    }

Exit:

    SepUnlockLsaQueue();

    if ( StartExThread )
    {
        ExInitializeWorkItem( &SepExWorkItem.WorkItem,
                              (PWORKER_THREAD_ROUTINE) SepRmCallLsa,
                              &SepExWorkItem
                              );

        ExQueueWorkItem( &SepExWorkItem.WorkItem, DelayedWorkQueue );
    }

    return( rc );
}



PSEP_LSA_WORK_ITEM
SepDequeueWorkItem(
    VOID
    )

/*++

Routine Description:

    Removes the top element of the SepLsaQueue and returns the
    next element if there is one, NULL otherwise.

Arguments:

    None.

Return Value:

    A pointer to the next SEP_LSA_WORK_ITEM, or NULL.

--*/

{
    PSEP_LSA_WORK_ITEM OldWorkQueueItem;

    PAGED_CODE();

    SepLockLsaQueue();

    OldWorkQueueItem = (PSEP_LSA_WORK_ITEM)RemoveHeadList(&SepLsaQueue);
    OldWorkQueueItem->List.Flink = NULL;

    SepAdtCurrentListLength--;


#if 0
        DbgPrint("Removing item\n");
#endif

    if (IsListEmpty( &SepLsaQueue )) {
        //
        // If LSA has died and the RM thread is waiting till we finish up. Notify it that we are all done
        //
        if (SepAdtLsaDeadEvent != NULL) {
            KeSetEvent (SepAdtLsaDeadEvent, 0, FALSE);
        }
        SepUnlockLsaQueue();

        ExFreePool( OldWorkQueueItem );
        return( NULL );
    }

    //
    // We know there's something on the queue now, so we
    // can unlock it.
    //

    SepUnlockLsaQueue();

    ExFreePool( OldWorkQueueItem );

    return((PSEP_LSA_WORK_ITEM)(&SepLsaQueue)->Flink);
}
