/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    rmlogon.c

Abstract:

    This module implements the kernel mode logon tracking performed by the
    reference monitor.  Logon tracking is performed by keeping a count of
    how many tokens exist for each active logon in a system.  When a logon
    session's reference count drops to zero, the LSA is notified so that
    authentication packages can clean up any related context data.

--*/

#include "pch.h"

#pragma hdrstop

#include "rmp.h"
#include <bugcodes.h>
#include <stdio.h>
#include <zwapi.h>

#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEDATA")
#endif

SEP_LOGON_SESSION_TERMINATED_NOTIFICATION
SeFileSystemNotifyRoutinesHead = {0};


////////////////////////////////////////////////////////////////////////////
//                                                                        //
// Internally defined data types                                          //
//                                                                        //
////////////////////////////////////////////////////////////////////////////

typedef struct _SEP_FILE_SYSTEM_NOTIFY_CONTEXT {
    WORK_QUEUE_ITEM WorkItem;
    LUID LogonId;
} SEP_FILE_SYSTEM_NOTIFY_CONTEXT, *PSEP_FILE_SYSTEM_NOTIFY_CONTEXT;


////////////////////////////////////////////////////////////////////////////
//                                                                        //
// Internally defined routines                                            //
//                                                                        //
////////////////////////////////////////////////////////////////////////////


VOID
SepInformLsaOfDeletedLogon(
    IN PLUID LogonId
    );

VOID
SepInformFileSystemsOfDeletedLogon(
    IN PLUID LogonId
    );

VOID
SepNotifyFileSystems(
    IN PVOID Context
    );

NTSTATUS
SepCleanupLUIDDeviceMapDirectory(
    PLUID pLogonId
    );

NTSTATUS
SeGetLogonIdDeviceMap(
    IN PLUID pLogonId,
    OUT PDEVICE_MAP* ppDevMap
    );

//
// declared in ntos\ob\obp.h
// defined in ntos\ob\obdir.c
// Used to dereference the LUID device map
//
VOID
FASTCALL
ObfDereferenceDeviceMap(
    IN PDEVICE_MAP DeviceMap
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,SepRmCreateLogonSessionWrkr)
#pragma alloc_text(PAGE,SepRmDeleteLogonSessionWrkr)
#pragma alloc_text(PAGE,SepReferenceLogonSession)
#pragma alloc_text(PAGE,SepCleanupLUIDDeviceMapDirectory)
#pragma alloc_text(PAGE,SepDeReferenceLogonSession)
#pragma alloc_text(PAGE,SepCreateLogonSessionTrack)
#pragma alloc_text(PAGE,SepDeleteLogonSessionTrack)
#pragma alloc_text(PAGE,SepInformLsaOfDeletedLogon)
#pragma alloc_text(PAGE,SeRegisterLogonSessionTerminatedRoutine)
#pragma alloc_text(PAGE,SeUnregisterLogonSessionTerminatedRoutine)
#pragma alloc_text(PAGE,SeMarkLogonSessionForTerminationNotification)
#pragma alloc_text(PAGE,SepInformFileSystemsOfDeletedLogon)
#pragma alloc_text(PAGE,SepNotifyFileSystems)
#pragma alloc_text(PAGE,SeGetLogonIdDeviceMap)
#if DBG || TOKEN_LEAK_MONITOR
#pragma alloc_text(PAGE,SepAddTokenLogonSession)
#pragma alloc_text(PAGE,SepRemoveTokenLogonSession)
#endif
#endif



////////////////////////////////////////////////////////////////////////////
//                                                                        //
// Local macros                                                           //
//                                                                        //
////////////////////////////////////////////////////////////////////////////


//
// This macro is used to obtain an index into the logon session tracking
// array given a logon session ID (a LUID).
//

#define SepLogonSessionIndex( PLogonId ) (                                    \
     (PLogonId)->LowPart & SEP_LOGON_TRACK_INDEX_MASK                         \
     )



////////////////////////////////////////////////////////////////////////////
//                                                                        //
// Exported Services                                                      //
//                                                                        //
////////////////////////////////////////////////////////////////////////////

VOID
SepRmCreateLogonSessionWrkr(
    IN PRM_COMMAND_MESSAGE CommandMessage,
    OUT PRM_REPLY_MESSAGE ReplyMessage
    )

/*++

Routine Description:

    This function is the dispatch routine for the LSA --> RM
    "CreateLogonSession" call.

    The arguments passed to this routine are defined by the
    type SEP_RM_COMMAND_WORKER.


Arguments:

    CommandMessage - Points to structure containing RM command message
        information consisting of an LPC PORT_MESSAGE structure followed
        by the command number (RmComponentTestCommand) and a command-specific
        body.  The command-specific body of this parameter is a LUID of the
        logon session to be created.

    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:

    VOID

--*/

{

    NTSTATUS Status;
    LUID LogonId;

    PAGED_CODE();

    //
    // Check that command is expected type
    //

    ASSERT( CommandMessage->CommandNumber == RmCreateLogonSession );


    //
    // Typecast the command parameter to what we expect.
    //

    LogonId = *((LUID UNALIGNED *) CommandMessage->CommandParams);



    //
    // Try to create the logon session tracking record
    //

    Status = SepCreateLogonSessionTrack( &LogonId );



    //
    // Set the reply status
    //

    ReplyMessage->ReturnedStatus = Status;


    return;
}



VOID
SepRmDeleteLogonSessionWrkr(
    IN PRM_COMMAND_MESSAGE CommandMessage,
    OUT PRM_REPLY_MESSAGE ReplyMessage
    )

/*++

Routine Description:

    This function is the dispatch routine for the LSA --> RM
    "DeleteLogonSession" call.

    The arguments passed to this routine are defined by the
    type SEP_RM_COMMAND_WORKER.


Arguments:

    CommandMessage - Points to structure containing RM command message
        information consisting of an LPC PORT_MESSAGE structure followed
        by the command number (RmComponentTestCommand) and a command-specific
        body.  The command-specific body of this parameter is a LUID of the
        logon session to be created.

    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:

    VOID

--*/

{

    NTSTATUS Status;
    LUID LogonId;

    PAGED_CODE();

    //
    // Check that command is expected type
    //

    ASSERT( CommandMessage->CommandNumber == RmDeleteLogonSession );


    //
    // Typecast the command parameter to what we expect.
    //

    LogonId = *((LUID UNALIGNED *) CommandMessage->CommandParams);



    //
    // Try to create the logon session tracking record
    //

    Status = SepDeleteLogonSessionTrack( &LogonId );



    //
    // Set the reply status
    //

    ReplyMessage->ReturnedStatus = Status;


    return;
}


NTSTATUS
SepReferenceLogonSession(
    IN PLUID LogonId,
    OUT PSEP_LOGON_SESSION_REFERENCES *ReturnSession
    )

/*++

Routine Description:

    This routine increments the reference count of a logon session
    tracking record.



Arguments:

    LogonId - Pointer to the logon session ID whose logon track is
        to be incremented.

    ReturnSession - The found session is returned here on success.

Return Value:

    STATUS_SUCCESS - The reference count was successfully incremented.

    STATUS_NO_SUCH_LOGON_SESSION - The specified logon session doesn't
        exist in the reference monitor's database.

--*/

{

    ULONG SessionArrayIndex;
    PSEP_LOGON_SESSION_REFERENCES *Previous, Current;
    ULONG Refs;

    PAGED_CODE();

    SessionArrayIndex = SepLogonSessionIndex( LogonId );

    Previous = &SepLogonSessions[ SessionArrayIndex ];

    //
    // Protect modification of reference monitor database
    //

    SepRmAcquireDbWriteLock(SessionArrayIndex);


    //
    // Now walk the list for our logon session array hash index.
    //

    Current = *Previous;

    while (Current != NULL) {

        //
        // If we found it, increment the reference count and return
        //

        if (RtlEqualLuid( LogonId, &Current->LogonId) ) {

             Refs = InterlockedIncrement (&Current->ReferenceCount);

             SepRmReleaseDbWriteLock(SessionArrayIndex);

             *ReturnSession = Current;
             return STATUS_SUCCESS;
        }

        Current = Current->Next;
    }

    SepRmReleaseDbWriteLock(SessionArrayIndex);

    //
    // Bad news, someone asked us to increment the reference count of
    // a logon session we didn't know existed.  This might be a new
    // token being created, so return an error status and let the caller
    // decide if it warrants a bugcheck or not.
    //

    return STATUS_NO_SUCH_LOGON_SESSION;
}



NTSTATUS
SepCleanupLUIDDeviceMapDirectory(
    PLUID pLogonId
    )
/*++

Routine Description:

    Make the contents of the (LUID's device map)'s directory object 
    temporary so that their names go away.


Arguments:

    pLogonId - Pointer to the logon session ID whose device is to be
               cleaned up

Return Value:

    STATUS_SUCCESS - cleaned up the entire device map

    STATUS_INVALID_PARAMETER - pLogonId is a NULL pointer

    STATUS_NO_MEMORY - could not allocate memory to hold the handle
                       buffer

    appropriate NTSTATUS code

--*/
{
    NTSTATUS          Status;
    OBJECT_ATTRIBUTES Attributes;
    UNICODE_STRING    UnicodeString;
    HANDLE            LinkHandle;
    POBJECT_DIRECTORY_INFORMATION DirInfo = NULL;
    BOOLEAN           RestartScan;
    WCHAR             szString[64]; // \Sessions\0\DosDevices\x-x = 10+1+12+(8)+1+(8)+1 = 41
    ULONG             Context = 0;
    ULONG             ReturnedLength;
    HANDLE            DosDevicesDirectory;
    HANDLE            *HandleArray;
    ULONG             Size = 100;
    ULONG             i, Count = 0;
    ULONG             dirInfoLength = 0;
    BOOLEAN           CallingProcessValid;
    KAPC_STATE        ApcState;

    PAGED_CODE();

    if (pLogonId == NULL) {
        return( STATUS_INVALID_PARAMETER );
    }

    //
    // Attach to the system process if the current process has a reference count of 0 to
    // avoid recursive process deletion.
    //

    CallingProcessValid = ObReferenceObjectSafe(PsGetCurrentProcess());

    if (CallingProcessValid) {
        ObDereferenceObject(PsGetCurrentProcess());
    }
    else {
        KeStackAttachProcess(&PsInitialSystemProcess->Pcb, &ApcState);
    }

    //
    // Open a handle to the directory object for the LUID device map
    // Get a kernel handle
    //

    _snwprintf( szString,
                (sizeof(szString)/sizeof(WCHAR)) - 1,
                L"\\Sessions\\0\\DosDevices\\%08x-%08x",
                pLogonId->HighPart,
                pLogonId->LowPart );

#pragma prefast(suppress:53, "szString is correctly null-terminated.")
    RtlInitUnicodeString(&UnicodeString, szString);

    InitializeObjectAttributes(&Attributes,
                               &UnicodeString,
                               OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                               NULL,
                               NULL);

    Status = ZwOpenDirectoryObject(&DosDevicesDirectory,
                                   DIRECTORY_QUERY,
                                   &Attributes);
    if (!NT_SUCCESS(Status)) {
        if (!CallingProcessValid) {
            KeUnstackDetachProcess(&ApcState);
        }

        return Status;
    }

Restart:

    //
    // Create an array of handles to close with each scan
    // of the directory
    //
    HandleArray = (HANDLE *)ExAllocatePoolWithTag(
                                PagedPool,
                                (Size * sizeof(HANDLE)),
                                'aHeS'
                                );

    if (HandleArray == NULL) {

        ZwClose(DosDevicesDirectory);
        if (DirInfo != NULL) {
            ExFreePool(DirInfo);
        }

        if (!CallingProcessValid) {
            KeUnstackDetachProcess(&ApcState);
        }

        return STATUS_NO_MEMORY;
    }

    RestartScan = TRUE;

    while (TRUE) {

        do {
            //
            // ZwQueryDirectoryObject returns one element at a time
            //
            Status = ZwQueryDirectoryObject( DosDevicesDirectory,
                                             (PVOID)DirInfo,
                                             dirInfoLength,
                                             TRUE,
                                             RestartScan,
                                             &Context,
                                             &ReturnedLength );

            if (Status == STATUS_BUFFER_TOO_SMALL) {
                dirInfoLength = ReturnedLength;
                if (DirInfo != NULL) {
                    ExFreePool(DirInfo);
                }
                DirInfo = ExAllocatePoolWithTag( PagedPool, dirInfoLength, 'bDeS' );
                if (DirInfo == NULL) {
                    Status = STATUS_INSUFFICIENT_RESOURCES;
                }
            }
        }while (Status == STATUS_BUFFER_TOO_SMALL);

        //
        //  Check the status of the operation.
        //
        if (!NT_SUCCESS(Status)) {

            if (Status == STATUS_NO_MORE_ENTRIES) {
                Status = STATUS_SUCCESS;
            }
            break;
        }

        //
        // Check that the element is a symbolic link
        //
        if (!wcscmp(DirInfo->TypeName.Buffer, L"SymbolicLink")) {

            //
            // check if the handle array is full
            //
            if ( Count >= Size ) {

                //
                // empty the handle array by closing all the handles
                // and free the handle array so that we can create
                // a bigger handle array
                // Need to restart the directory scan
                //
                for (i = 0; i < Count ; i++) {
                    ZwClose (HandleArray[i]);
                }
                Size += 20;
                Count = 0;
                ExFreePool((PVOID)HandleArray);
                HandleArray = NULL;
                goto Restart;

            }

            InitializeObjectAttributes( &Attributes,
                                        &DirInfo->Name,
                                        OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                                        DosDevicesDirectory,
                                        NULL);

            Status = ZwOpenSymbolicLinkObject( &LinkHandle,
                                               SYMBOLIC_LINK_ALL_ACCESS,
                                               &Attributes);

            if (NT_SUCCESS(Status)) {

                //
                // Make the object temporary so that its name goes away from
                // the Object Manager's namespace
                //
                Status = ZwMakeTemporaryObject( LinkHandle );

                if (NT_SUCCESS( Status )) {
                    HandleArray[Count] = LinkHandle;
                    Count++;
                }
                else {
                    ZwClose( LinkHandle );
                }
            }
        }
        RestartScan = FALSE;
     }

     //
     // Close all the handles
     //
     
     for (i = 0; i < Count ; i++) {
         ZwClose (HandleArray[i]);
     }

     if (HandleArray != NULL) {
         ExFreePool((PVOID)HandleArray);
     }

     if (DirInfo != NULL) {
         ExFreePool(DirInfo);
     }

     if (DosDevicesDirectory != NULL) {
         ZwClose(DosDevicesDirectory);
     }

     if (!CallingProcessValid) {
         KeUnstackDetachProcess(&ApcState);
     }

     return Status;
}



VOID
SepDeReferenceLogonSession(
    IN PLUID LogonId
    )

/*++

Routine Description:

    This routine decrements the reference count of a logon session
    tracking record.

    If the reference count is decremented to zero, then there is no
    possibility for any more tokens to exist for the logon session.
    In this case, the LSA is notified that a logon session has
    terminated.



Arguments:

    LogonId - Pointer to the logon session ID whose logon track is
        to be decremented.

Return Value:

    None.

--*/

{

    ULONG SessionArrayIndex;
    PSEP_LOGON_SESSION_REFERENCES *Previous, Current;
    PDEVICE_MAP pDeviceMap = NULL;
    ULONG Refs;


    PAGED_CODE();

    SessionArrayIndex = SepLogonSessionIndex( LogonId );

    Previous = &SepLogonSessions[ SessionArrayIndex ];

    //
    // Protect modification of reference monitor database
    //

    SepRmAcquireDbWriteLock(SessionArrayIndex);


    //
    // Now walk the list for our logon session array hash index.
    //

    Current = *Previous;

    while (Current != NULL) {

        //
        // If we found it, decrement the reference count and return
        //

        if (RtlEqualLuid( LogonId, &Current->LogonId) ) {
            Refs = InterlockedDecrement (&Current->ReferenceCount);
            if (Refs == 0) {

                //
                // Pull it from the list
                //

                *Previous = Current->Next;

                //
                // No longer need to protect our pointer to this
                // record.
                //

                SepRmReleaseDbWriteLock(SessionArrayIndex);

                //
                // If the Device Map exist for this LUID,
                // dereference the pointer to the Device Map
                //
                if (Current->pDeviceMap != NULL) {

                    //
                    // Dereference our reference on the device map
                    // our reference should be the last reference,
                    // thus the system will delete the device map
                    // for the LUID
                    //
                    pDeviceMap = Current->pDeviceMap;
                    Current->pDeviceMap = NULL;
                }


                //
                // Make all the contents of the LUID's device map temporary,
                // so that the names go away from the Object Manager's
                // namespace
                // Remove our reference on the LUID's device map
                //
                if (pDeviceMap != NULL) {
                    SepCleanupLUIDDeviceMapDirectory( LogonId );
                    ObfDereferenceDeviceMap( pDeviceMap );
                }

                //
                // Asynchronoously inform file systems that this logon session
                // is going away, if at least one FS expressed interest in this
                // logon session.
                //

                if (Current->Flags & SEP_TERMINATION_NOTIFY) {
                    SepInformFileSystemsOfDeletedLogon( LogonId );
                }

                //
                // Deallocate the logon session track record.
                //

                ExFreePool( (PVOID)Current );

                //
                // Inform the LSA about the deletion of this logon session.
                //

                SepInformLsaOfDeletedLogon( LogonId );



                return;

            }

            //
            // reference count was decremented, but not to zero.
            //

            SepRmReleaseDbWriteLock(SessionArrayIndex);

            return;
        }

        Previous = &Current->Next;
        Current = *Previous;
    }

    SepRmReleaseDbWriteLock(SessionArrayIndex);

    //
    // Bad news, someone asked us to decrement the reference count of
    // a logon session we didn't know existed.
    //

    KeBugCheckEx( DEREF_UNKNOWN_LOGON_SESSION, 0, 0, 0, 0 );

    return;

}


NTSTATUS
SepCreateLogonSessionTrack(
    IN PLUID LogonId
    )

/*++

Routine Description:

    This routine creates a new logon session tracking record.

    This should only be called as a dispatch routine for a LSA->RM
    call (and once during system initialization).

    If the specified logon session already exists, then an error is returned.



Arguments:

    LogonId - Pointer to the logon session ID for which a new logon track is
        to be created.

Return Value:

    STATUS_SUCCESS - The logon session track was created successfully.

    STATUS_LOGON_SESSION_EXISTS - The logon session already exists.
        A new one has not been created.

--*/

{

    ULONG SessionArrayIndex;
    PSEP_LOGON_SESSION_REFERENCES *Previous, Current;
    PSEP_LOGON_SESSION_REFERENCES LogonSessionTrack;

    PAGED_CODE();

    
#if DBG || TOKEN_LEAK_MONITOR
    if (SepTokenLeakTracking) {
        DbgPrint("\nLOGON : 0x%x 0x%x\n\n", LogonId->HighPart, LogonId->LowPart);
    }
#endif


    //
    // Make sure we can allocate a new logon session track record
    //

    LogonSessionTrack = (PSEP_LOGON_SESSION_REFERENCES)
                        ExAllocatePoolWithTag(
                            PagedPool,
                            sizeof(SEP_LOGON_SESSION_REFERENCES),
                            'sLeS'
                            );

    if (LogonSessionTrack == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlZeroMemory(LogonSessionTrack, sizeof(SEP_LOGON_SESSION_REFERENCES));
    LogonSessionTrack->LogonId = (*LogonId);
    LogonSessionTrack->ReferenceCount = 0;
    LogonSessionTrack->pDeviceMap = NULL;

#if DBG || TOKEN_LEAK_MONITOR
    InitializeListHead(&LogonSessionTrack->TokenList);
#endif


    SessionArrayIndex = SepLogonSessionIndex( LogonId );

    Previous = &SepLogonSessions[ SessionArrayIndex ];

    //
    // Protect modification of reference monitor database
    //

    SepRmAcquireDbWriteLock(SessionArrayIndex);


    //
    // Now walk the list for our logon session array hash index
    // looking for a duplicate logon session ID.
    //

    Current = *Previous;

    while (Current != NULL) {

        if (RtlEqualLuid( LogonId, &Current->LogonId) ) {

            //
            // One already exists. Hmmm.
            //

            SepRmReleaseDbWriteLock(SessionArrayIndex);

            ExFreePool(LogonSessionTrack);
            return STATUS_LOGON_SESSION_EXISTS;

        }

        Current = Current->Next;
    }


    //
    // Reached the end of the list without finding a duplicate.
    // Add the new one.
    //

    LogonSessionTrack->Next = *Previous;
    *Previous = LogonSessionTrack;

    SepRmReleaseDbWriteLock(SessionArrayIndex);

    return STATUS_SUCCESS;

}


NTSTATUS
SepDeleteLogonSessionTrack(
    IN PLUID LogonId
    )

/*++

Routine Description:

    This routine creates a new logon session tracking record.

    This should only be called as a dispatch routine for a LSA->RM
    call (and once during system initialization).

    If the specified logon session already exists, then an error is returned.



Arguments:

    LogonId - Pointer to the logon session ID whose logon track is
        to be deleted.

Return Value:

    STATUS_SUCCESS - The logon session track was deleted successfully.

    STATUS_BAD_LOGON_SESSION_STATE - The logon session has a non-zero
        reference count and can not be deleted.

    STATUS_NO_SUCH_LOGON_SESSION - The specified logon session does not
        exist.


--*/

{

    ULONG SessionArrayIndex;
    PSEP_LOGON_SESSION_REFERENCES *Previous, Current;
    PDEVICE_MAP pDeviceMap = NULL;

    PAGED_CODE();

    SessionArrayIndex = SepLogonSessionIndex( LogonId );

    Previous = &SepLogonSessions[ SessionArrayIndex ];

    //
    // Protect modification of reference monitor database
    //

    SepRmAcquireDbWriteLock(SessionArrayIndex);


    //
    // Now walk the list for our logon session array hash index.
    //

    Current = *Previous;

    while (Current != NULL) {

        //
        // If we found it, make sure reference count is zero
        //

        if (RtlEqualLuid( LogonId, &Current->LogonId) ) {

            if (Current->ReferenceCount == 0) {

                //
                // Pull it from the list
                //

                *Previous = Current->Next;

                //
                // If the Device Map exist for this LUID,
                // dereference the pointer to the Device Map
                //
                if (Current->pDeviceMap != NULL) {

                    //
                    // Dereference our reference on the device map
                    // our reference should be the last reference,
                    // thus the system will delete the device map
                    // for the LUID
                    //
                    pDeviceMap = Current->pDeviceMap;
                    Current->pDeviceMap = NULL;
                }

                //
                // No longer need to protect our pointer to this
                // record.
                //

                SepRmReleaseDbWriteLock(SessionArrayIndex);

                //
                // Make all the contents of the LUID's device map temporary,
                // so that the names go away from the Object Manager's
                // namespace
                // Remove our reference on the LUID's device map
                //
                if (pDeviceMap != NULL) {
                    SepCleanupLUIDDeviceMapDirectory( LogonId );
                    ObfDereferenceDeviceMap( pDeviceMap );
                }

                //
                // Deallocate the logon session track record.
                //

                ExFreePool( (PVOID)Current );


                return STATUS_SUCCESS;

            }

            //
            // reference count was not zero.  This is not considered
            // a healthy situation.  Return an error and let someone
            // else declare the bugcheck.
            //

            SepRmReleaseDbWriteLock(SessionArrayIndex);
            return STATUS_BAD_LOGON_SESSION_STATE;
        }

        Previous = &Current->Next;
        Current = *Previous;
    }

    SepRmReleaseDbWriteLock(SessionArrayIndex);

    //
    // Someone asked us to delete a logon session that isn't
    // in the database.
    //

    return STATUS_NO_SUCH_LOGON_SESSION;

}


VOID
SepInformLsaOfDeletedLogon(
    IN PLUID LogonId
    )

/*++

Routine Description:

    This routine informs the LSA about the deletion of a logon session.

    Note that we can not be guaranteed that we are in a whole (or wholesome)
    thread, since we may be in the middle of process deletion and object
    rundown.  Therefore, we must queue the work off to a worker thread which
    can then make an LPC call to the LSA.




Arguments:

    LogonId - Pointer to the logon session ID which has been deleted.

Return Value:

    None.

--*/

{
    PSEP_LSA_WORK_ITEM DeleteLogonItem;

    PAGED_CODE();

    //
    // Pass the LUID value along with the work queue item.
    // Note that the worker thread is responsible for freeing the WorkItem data
    // structure.
    //

    DeleteLogonItem = ExAllocatePoolWithTag( PagedPool, sizeof(SEP_LSA_WORK_ITEM), 'wLeS' );
    if (DeleteLogonItem == NULL) {

        //
        // I don't know what to do here... we loose track of a logon session,
        // but the system isn't really harmed in any way.
        //

        ASSERT("Failed to allocate DeleteLogonItem." && FALSE);
        return;

    }

    DeleteLogonItem->CommandParams.LogonId   = (*LogonId);
    DeleteLogonItem->CommandNumber           = LsapLogonSessionDeletedCommand;
    DeleteLogonItem->CommandParamsLength     = sizeof( LUID );
    DeleteLogonItem->ReplyBuffer             = NULL;
    DeleteLogonItem->ReplyBufferLength       = 0;
    DeleteLogonItem->CleanupFunction         = NULL;
    DeleteLogonItem->CleanupParameter        = 0;
    DeleteLogonItem->Tag                     = SepDeleteLogon;
    DeleteLogonItem->CommandParamsMemoryType = SepRmImmediateMemory;

    if (!SepQueueWorkItem( DeleteLogonItem, TRUE )) {

        ExFreePool( DeleteLogonItem );
    }

    return;

}


NTSTATUS
SeRegisterLogonSessionTerminatedRoutine(
    IN PSE_LOGON_SESSION_TERMINATED_ROUTINE CallbackRoutine
    )

/*++

Routine Description:

    This routine is called by file systems that are interested in being
    notified when a logon session is being deleted.

Arguments:

    CallbackRoutine - Address of routine to call back when a logon session
        is being deleted.

Return Value:

    STATUS_SUCCESS - Successfully registered routine

    STATUS_INVALID_PARAMETER - CallbackRoutine is NULL

    STATUS_INSUFFICIENT_RESOURCE - Unable to allocate list entry.

--*/

{
    PSEP_LOGON_SESSION_TERMINATED_NOTIFICATION NewCallback;

    PAGED_CODE();

    if (CallbackRoutine == NULL) {
        return( STATUS_INVALID_PARAMETER );
    }

    NewCallback = ExAllocatePoolWithTag(
                        PagedPool | POOL_COLD_ALLOCATION,
                        sizeof(SEP_LOGON_SESSION_TERMINATED_NOTIFICATION),
                        'SFeS');

    if (NewCallback == NULL) {
        return( STATUS_INSUFFICIENT_RESOURCES );
    }
    
    SepRmAcquireNotifyLock();

    NewCallback->Next = SeFileSystemNotifyRoutinesHead.Next;

    NewCallback->CallbackRoutine = CallbackRoutine;

    SeFileSystemNotifyRoutinesHead.Next = NewCallback;

    SepRmReleaseNotifyLock();

    return( STATUS_SUCCESS );
}


NTSTATUS
SeUnregisterLogonSessionTerminatedRoutine(
    IN PSE_LOGON_SESSION_TERMINATED_ROUTINE CallbackRoutine
    )

/*++

Routine Description:

    This is the dual of SeRegisterLogonSessionTerminatedRoutine. A File System
    *MUST* call this before it is unloaded.

Arguments:

    CallbackRoutine - Address of routine that was originally passed in to
        SeRegisterLogonSessionTerminatedRoutine.

Return Value:

    STATUS_SUCCESS - Successfully removed callback routine

    STATUS_INVALID_PARAMETER - CallbackRoutine is NULL

    STATUS_NOT_FOUND - Didn't find and entry for CallbackRoutine

--*/
{
    NTSTATUS Status;
    PSEP_LOGON_SESSION_TERMINATED_NOTIFICATION PreviousEntry;
    PSEP_LOGON_SESSION_TERMINATED_NOTIFICATION NotifyEntry;

    PAGED_CODE();

    if (CallbackRoutine == NULL) {
        return( STATUS_INVALID_PARAMETER );
    }

    SepRmAcquireNotifyLock();

    for (PreviousEntry = &SeFileSystemNotifyRoutinesHead,
            NotifyEntry = SeFileSystemNotifyRoutinesHead.Next;
                NotifyEntry != NULL;
                    PreviousEntry = NotifyEntry,
                        NotifyEntry = NotifyEntry->Next) {

         if (NotifyEntry->CallbackRoutine == CallbackRoutine)
             break;

    }

    if (NotifyEntry != NULL) {

        PreviousEntry->Next = NotifyEntry->Next;

        SepRmReleaseNotifyLock();

        ExFreePool( NotifyEntry );

        Status = STATUS_SUCCESS;

    } else {

        SepRmReleaseNotifyLock();

        Status = STATUS_NOT_FOUND;

    }


    return( Status );

}


NTSTATUS
SeMarkLogonSessionForTerminationNotification(
    IN PLUID LogonId
    )

/*++

Routine Description:

    File systems that have registered for logon-termination notification
    can mark logon sessions they are interested in for callback by calling
    this routine.

Arguments:

    LogonId - The logon id for which the file system should be notified
        when the logon session is terminated.

Returns:

    Nothing.

--*/

{

    ULONG SessionArrayIndex;
    PSEP_LOGON_SESSION_REFERENCES *Previous, Current;

    PAGED_CODE();

    SessionArrayIndex = SepLogonSessionIndex( LogonId );

    Previous = &SepLogonSessions[ SessionArrayIndex ];

    //
    // Protect modification of reference monitor database
    //

    SepRmAcquireDbWriteLock(SessionArrayIndex);


    //
    // Now walk the list for our logon session array hash index.
    //

    Current = *Previous;

    while (Current != NULL) {

        //
        // If we found it, decrement the reference count and return
        //

        if (RtlEqualLuid( LogonId, &Current->LogonId) ) {
            Current->Flags |= SEP_TERMINATION_NOTIFY;
            break;
        }

        Current = Current->Next;
    }

    SepRmReleaseDbWriteLock(SessionArrayIndex);

    return( (Current != NULL) ? STATUS_SUCCESS : STATUS_NOT_FOUND );

}


VOID
SepInformFileSystemsOfDeletedLogon(
    IN PLUID LogonId
    )

/*++

Routine Description:

    This routine informs interested file systems of a deleted logon.

    Note that we can not be guaranteed that we are in a whole (or wholesome)
    thread, since we may be in the middle of process deletion and object
    rundown.  Therefore, we must queue the work off to a worker thread.


Arguments:

    LogonId - Pointer to the logon session ID which has been deleted.

Return Value:

    None.

--*/

{
    PSEP_FILE_SYSTEM_NOTIFY_CONTEXT FSNotifyContext;

    PAGED_CODE();

    FSNotifyContext = ExAllocatePoolWithTag(
                            NonPagedPool,
                            sizeof(SEP_FILE_SYSTEM_NOTIFY_CONTEXT),
                            'SFeS');

    if (FSNotifyContext == NULL) {

        //
        // I don't know what to do here... file systems will loose track of a
        // logon session, but the system isn't really harmed in any way.
        //

        ASSERT("Failed to allocate FSNotifyContext." && FALSE);
        return;

    }

    FSNotifyContext->LogonId = *LogonId;

    ExInitializeWorkItem( &FSNotifyContext->WorkItem,
                          (PWORKER_THREAD_ROUTINE) SepNotifyFileSystems,
                          (PVOID) FSNotifyContext);

    ExQueueWorkItem( &FSNotifyContext->WorkItem, DelayedWorkQueue );

}


VOID
SepNotifyFileSystems(
    IN PVOID Context
    )
{
    PSEP_FILE_SYSTEM_NOTIFY_CONTEXT FSNotifyContext =
        (PSEP_FILE_SYSTEM_NOTIFY_CONTEXT) Context;

    PSEP_LOGON_SESSION_TERMINATED_NOTIFICATION NextCallback;

    PAGED_CODE();

    //
    // Protect modification of the list of FS callbacks.
    //

    SepRmAcquireNotifyLock();

    NextCallback = SeFileSystemNotifyRoutinesHead.Next;

    while (NextCallback != NULL) {

        NextCallback->CallbackRoutine( &FSNotifyContext->LogonId );

        NextCallback = NextCallback->Next;
    }
    
    SepRmReleaseNotifyLock();

    ExFreePool( FSNotifyContext );
}


NTSTATUS
SeGetLogonIdDeviceMap(
    IN PLUID pLogonId,
    OUT PDEVICE_MAP* ppDevMap
    )

/*++

Routine Description:

    This routine is called by components that want a handle to the
    Device Map for the specified LUID

Arguments:

    LogonID - LUID of the user

Return Value:

    STATUS_SUCCESS - Successfully registered routine

    STATUS_INVALID_PARAMETER - invalid parameter

    STATUS_INSUFFICIENT_RESOURCE - Unable to allocate list entry.

--*/

{

    PSEP_LOGON_SESSION_REFERENCES *Previous, Current;
    ULONG SessionArrayIndex;
    LONG OldValue;

    PAGED_CODE();

    if( pLogonId == NULL ) {
        return( STATUS_INVALID_PARAMETER );
    }

    if( ppDevMap == NULL ) {
        return( STATUS_INVALID_PARAMETER );
    }

    SessionArrayIndex = SepLogonSessionIndex( pLogonId );

    Previous = &SepLogonSessions[ SessionArrayIndex ];

    //
    // Protect modification of reference monitor database
    //

    SepRmAcquireDbWriteLock(SessionArrayIndex);

    //
    // Now walk the list for our logon session array hash index.
    //

    Current = *Previous;

    while (Current != NULL) {

        //
        // If we found it, return a handle to the device map
        //

        if (RtlEqualLuid( pLogonId, &(Current->LogonId) )) {

            NTSTATUS Status;

            Status = STATUS_SUCCESS;

            //
            // Check if the Device Map does not exist for this LUID
            //
            if (Current->pDeviceMap == NULL) {

                WCHAR  szString[64]; // \Sessions\0\DosDevices\x-x = 10+1+12+(8)+1+(8)+1 = 41
                OBJECT_ATTRIBUTES Obja;
                UNICODE_STRING    UnicodeString, SymLinkUnicodeString;
                HANDLE hDevMap, hSymLink;
                PDEVICE_MAP pDeviceMap = NULL;

                //
                // Drop the lock while we create the devmap
                //
                InterlockedIncrement (&Current->ReferenceCount);
                SepRmReleaseDbWriteLock(SessionArrayIndex);

                _snwprintf( szString,
                            (sizeof(szString)/sizeof(WCHAR)) - 1,
                            L"\\Sessions\\0\\DosDevices\\%08x-%08x",
                            pLogonId->HighPart,
                            pLogonId->LowPart );

                RtlInitUnicodeString( &UnicodeString, szString );

                //
                // Device Map for LUID does not exist
                // Create the Device Map for the LUID
                //
                InitializeObjectAttributes( &Obja,
                                            &UnicodeString,
                                            OBJ_CASE_INSENSITIVE | OBJ_OPENIF | OBJ_KERNEL_HANDLE,
                                            NULL,
                                            NULL );

                Status = ZwCreateDirectoryObject( &hDevMap,
                                                  DIRECTORY_ALL_ACCESS,
                                                  &Obja );

                if (NT_SUCCESS( Status )) {

                    //
                    // Set the DeviceMap for this directory object
                    //
                    Status = ObSetDirectoryDeviceMap( &pDeviceMap,
                                                      hDevMap );

                    if (NT_SUCCESS( Status )) {

                        //
                        // Create the Global SymLink to the global DosDevices
                        //
                        RtlInitUnicodeString( &SymLinkUnicodeString, L"Global" );

                        RtlInitUnicodeString( &UnicodeString, L"\\Global??" );

                        InitializeObjectAttributes(
                                &Obja,
                                &SymLinkUnicodeString,
                                OBJ_PERMANENT | OBJ_CASE_INSENSITIVE | OBJ_OPENIF | OBJ_KERNEL_HANDLE,
                                hDevMap,
                                NULL );

                        Status = ZwCreateSymbolicLinkObject( &hSymLink,
                                                             SYMBOLIC_LINK_ALL_ACCESS,
                                                             &Obja,
                                                             &UnicodeString );
                        if (NT_SUCCESS( Status )) {
                            ZwClose( hSymLink );
                        }
                        else {
                            ObfDereferenceDeviceMap(pDeviceMap);
                        }
                    }

                    ZwClose( hDevMap );
                }

                //
                // Reaquire the lock and modify the LUID structures
                //
                SepRmAcquireDbWriteLock(SessionArrayIndex);

                if (!NT_SUCCESS( Status )) {
                    *ppDevMap = NULL;
                }
                else {
                    if(Current->pDeviceMap == NULL) {
                        Current->pDeviceMap = pDeviceMap;
                    }
                    else {
                        ObfDereferenceDeviceMap(pDeviceMap);
                    }
                    *ppDevMap = Current->pDeviceMap;
                }

                SepRmReleaseDbWriteLock(SessionArrayIndex);

                //
                // Remove the reference we just added
                //

                SepDeReferenceLogonSessionDirect(Current);

                return Status;


            }
            else {

                //
                // Device Map for LUID already exist
                // return the handle to the Device Map
                //

                *ppDevMap = Current->pDeviceMap;
            }

            SepRmReleaseDbWriteLock(SessionArrayIndex);

            return ( Status );
        }

        Current = Current->Next;
    }

    SepRmReleaseDbWriteLock(SessionArrayIndex);

    //
    // Bad news, someone asked us for a device map of
    // a logon session we didn't know existed.  This might be a new
    // token being created, so return an error status and let the caller
    // decide if it warrants a bugcheck or not.
    //

    return STATUS_NO_SUCH_LOGON_SESSION;
}

#if DBG || TOKEN_LEAK_MONITOR

VOID 
SepAddTokenLogonSession(
    IN PTOKEN Token
    )

/*++

Routine Description

    Adds SEP_LOGON_SESSION_TOKEN to a reference monitor track.
    
Arguments

    Token - token to add
    
Return Value

    None
    
--*/

{
    ULONG SessionArrayIndex;
    PSEP_LOGON_SESSION_REFERENCES Current;
    PSEP_LOGON_SESSION_TOKEN TokenTrack = NULL;
    PLUID LogonId = &Token->AuthenticationId;

    PAGED_CODE();

    SessionArrayIndex = SepLogonSessionIndex( LogonId );

    //
    // Protect modification of reference monitor database
    //

    SepRmAcquireDbWriteLock(SessionArrayIndex);

    Current = SepLogonSessions[ SessionArrayIndex ];

    //
    // Now walk the list for our logon session array hash index.
    //

    while (Current != NULL) {

        //
        // If we found it, increment the reference count and return
        //
                   
        if (RtlEqualLuid( LogonId, &Current->LogonId )) {

             // 
             // Stick the token address into the track.  Find the last in the list of tokens
             // for this session.
             //
                        
             TokenTrack = ExAllocatePoolWithTag(PagedPool, sizeof(SEP_LOGON_SESSION_TOKEN), 'sLeS');

             if (TokenTrack) {
                 RtlZeroMemory(TokenTrack, sizeof(SEP_LOGON_SESSION_TOKEN));
                 TokenTrack->Token = Token;
                 InsertTailList(&Current->TokenList, &TokenTrack->ListEntry);
             } 

             SepRmReleaseDbWriteLock(SessionArrayIndex);
             return;
        }

        Current = Current->Next;
    }

    ASSERT(FALSE && L"Failed to add logon session token track.");
    SepRmReleaseDbWriteLock(SessionArrayIndex );
}

VOID
SepRemoveTokenLogonSession(
    IN PTOKEN Token
    )

/*++

Routine Description

    Removes a SEP_LOGON_SESSION_TOKEN from a reference monitor logon track.  
    
Arguments

    Token - token to remove 
    
Return Value

    None.
    
--*/

{
    ULONG SessionArrayIndex;
    PSEP_LOGON_SESSION_REFERENCES Current;
    PSEP_LOGON_SESSION_TOKEN TokenTrack = NULL;
    PLUID LogonId = &Token->AuthenticationId;
    PLIST_ENTRY ListEntry;
    
    PAGED_CODE();

    if (Token->TokenFlags & TOKEN_SESSION_NOT_REFERENCED) {
        return;
    }

    SessionArrayIndex = SepLogonSessionIndex( LogonId );

    //
    // Protect modification of reference monitor database
    //

    SepRmAcquireDbWriteLock(SessionArrayIndex);

    Current = SepLogonSessions[ SessionArrayIndex ];

    //
    // Now walk the list for our logon session array hash index.
    //

    while (Current != NULL) {

        if (RtlEqualLuid( LogonId, &Current->LogonId )) {
            
            //
            // Remove the token from the token list for this session.
            //

            ListEntry = Current->TokenList.Flink;
            
            while (ListEntry != &Current->TokenList) {
                TokenTrack = CONTAINING_RECORD (ListEntry, SEP_LOGON_SESSION_TOKEN, ListEntry);
                if (TokenTrack->Token == Token) {
                    RemoveEntryList (ListEntry);
                    SepRmReleaseDbWriteLock(SessionArrayIndex);

                    ExFreePool(TokenTrack);
                    TokenTrack = NULL;
                    return;
                }
                ListEntry = ListEntry->Flink;
            }
        }

        Current = Current->Next;
    }

#if DBG
        DbgPrint("Failed to delete logon session token track.");
#endif

    SepRmReleaseDbWriteLock(SessionArrayIndex);
}

#endif

#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg()
#endif

