/*++

Copyright (c) 1989-2000 Microsoft Corporation

Module Name:

    AcChkSup.c

Abstract:

    This module implements the FAT access checking routine

// @@BEGIN_DDKSPLIT

Author:

    Gary Kimura     [GaryKi]    12-Jun-1989

Revision History:

// @@END_DDKSPLIT

--*/

#include "FatProcs.h"

//
//  Our debug trace level
//

#define Dbg                              (DEBUG_TRACE_ACCHKSUP)

NTSTATUS
FatCreateRestrictEveryoneToken(
    IN PACCESS_TOKEN Token,
    OUT PACCESS_TOKEN *RestrictedToken
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, FatCheckFileAccess)
#pragma alloc_text(PAGE, FatCreateRestrictEveryoneToken)
#pragma alloc_text(PAGE, FatExplicitDeviceAccessGranted)
#endif


BOOLEAN
FatCheckFileAccess (
    PIRP_CONTEXT IrpContext,
    IN UCHAR DirentAttributes,
    IN PACCESS_MASK DesiredAccess
    )

/*++

Routine Description:

    This routine checks if a desired access is allowed to a file represented
    by the specified DirentAttriubutes.

Arguments:

    DirentAttributes - Supplies the Dirent attributes to check access for

    DesiredAccess - Supplies the desired access mask that we are checking for

Return Value:

    BOOLEAN - TRUE if access is allowed and FALSE otherwise

--*/

{
    BOOLEAN Result;

    DebugTrace(+1, Dbg, "FatCheckFileAccess\n", 0);
    DebugTrace( 0, Dbg, "DirentAttributes = %8lx\n", DirentAttributes);
    DebugTrace( 0, Dbg, "DesiredAccess    = %8lx\n", *DesiredAccess);

    //
    //  This procedures is programmed like a string of filters each
    //  filter checks to see if some access is allowed,  if it is not allowed
    //  the filter return FALSE to the user without further checks otherwise
    //  it moves on to the next filter.  The filter check is to check for
    //  desired access flags that are not allowed for a particular dirent
    //

    Result = TRUE;

    try {

        //
        //  Check for Volume ID or Device Dirents, these are not allowed user
        //  access at all
        //

        if (FlagOn(DirentAttributes, FAT_DIRENT_ATTR_VOLUME_ID) ||
            FlagOn(DirentAttributes, FAT_DIRENT_ATTR_DEVICE)) {

            DebugTrace(0, Dbg, "Cannot access volume id or device\n", 0);

            try_return( Result = FALSE );
        }

        //
        //  Check the desired access for the object - we only blackball that
        //  we do not understand.  The model of filesystems using ACLs is that
        //  they do not type the ACL to the object the ACL is on.  Permissions
        //  are not checked for consistency vs. the object type - dir/file.
        //

        if (FlagOn(*DesiredAccess, ~(DELETE |
                                     READ_CONTROL |
                                     WRITE_OWNER |
                                     WRITE_DAC |
                                     SYNCHRONIZE |
                                     ACCESS_SYSTEM_SECURITY |
                                     FILE_WRITE_DATA |
                                     FILE_READ_EA |
                                     FILE_WRITE_EA |
                                     FILE_READ_ATTRIBUTES |
                                     FILE_WRITE_ATTRIBUTES |
                                     FILE_LIST_DIRECTORY |
                                     FILE_TRAVERSE |
                                     FILE_DELETE_CHILD |
                                     FILE_APPEND_DATA))) {

            DebugTrace(0, Dbg, "Cannot open object\n", 0);

            try_return( Result = FALSE );
        }

        //
        //  Check for a read-only Dirent
        //

        if (FlagOn(DirentAttributes, FAT_DIRENT_ATTR_READ_ONLY)) {

            //
            //  Check the desired access for a read-only dirent, we blackball
            //  WRITE, FILE_APPEND_DATA, FILE_ADD_FILE,
            //  FILE_ADD_SUBDIRECTORY, and FILE_DELETE_CHILD
            //

            if (FlagOn(*DesiredAccess, ~(DELETE |
                                         READ_CONTROL |
                                         WRITE_OWNER |
                                         WRITE_DAC |
                                         SYNCHRONIZE |
                                         ACCESS_SYSTEM_SECURITY |
                                         FILE_READ_DATA |
                                         FILE_READ_EA |
                                         FILE_WRITE_EA |
                                         FILE_READ_ATTRIBUTES |
                                         FILE_WRITE_ATTRIBUTES |
                                         FILE_EXECUTE |
                                         FILE_LIST_DIRECTORY |
                                         FILE_TRAVERSE))) {

                DebugTrace(0, Dbg, "Cannot open readonly\n", 0);

                try_return( Result = FALSE );
            }
        }

    try_exit: NOTHING;
    } finally {

        DebugUnwind( FatCheckFileAccess );

        DebugTrace(-1, Dbg, "FatCheckFileAccess -> %08lx\n", Result);
    }

    UNREFERENCED_PARAMETER( IrpContext );

    return Result;
}


NTSTATUS
FatExplicitDeviceAccessGranted (
    IN PIRP_CONTEXT IrpContext,
    IN PDEVICE_OBJECT DeviceObject,
    IN PACCESS_STATE AccessState,
    IN KPROCESSOR_MODE ProcessorMode
    )

/*++

Routine Description:

    This function asks whether the SID described in the input access state has
    been granted any explicit access to the given device object.  It does this
    by acquiring a token stripped of its ability to acquire access via the
    Everyone SID and re-doing the access check.

Arguments:

    DeviceObject - the device whose ACL will be checked
    
    AccessState - the access state describing the security context to be checked
    
    ProcessorMode - the mode this check should occur against

Return Value:

    NTSTATUS - Indicating whether explicit access was granted.

--*/

{
    NTSTATUS Status;
    BOOLEAN Result;

    PACCESS_TOKEN OriginalAccessToken;
    PACCESS_TOKEN RestrictedAccessToken;
    
    PACCESS_TOKEN *EffectiveToken;
    
    PRIVILEGE_SET PrivilegeSet;

    ACCESS_MASK GrantedAccess;

    //
    //  If the access state indicates that specific access other
    //  than traverse was acquired, either Everyone does have such
    //  access or explicit access was granted.  In both cases, we're
    //  happy to let this proceed.
    //

    if (AccessState->PreviouslyGrantedAccess & (SPECIFIC_RIGHTS_ALL ^
                                                FILE_TRAVERSE)) {

        return STATUS_SUCCESS;
    }

    //
    //  If the manage volume privilege is held, this also permits access.
    //

    PrivilegeSet.PrivilegeCount = 1;
    PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
    PrivilegeSet.Privilege[0].Luid = RtlConvertLongToLuid( SE_MANAGE_VOLUME_PRIVILEGE );
    PrivilegeSet.Privilege[0].Attributes = 0;

    if (SePrivilegeCheck( &PrivilegeSet,
                          &AccessState->SubjectSecurityContext,
                          ProcessorMode )) {

        return STATUS_SUCCESS;
    }

    //
    //  Capture the subject context as a prelude to everything below.
    //

    SeLockSubjectContext( &AccessState->SubjectSecurityContext );
    
    //
    //  Convert the token in the subject context into one which does not
    //  acquire access through the Everyone SID.
    //
    //  The logic for deciding which token is effective comes from
    //  SeQuerySubjectContextToken; since there is no natural way
    //  of getting a pointer to it, do it by hand.
    //
    
    if (ARGUMENT_PRESENT( AccessState->SubjectSecurityContext.ClientToken )) {
        EffectiveToken = &AccessState->SubjectSecurityContext.ClientToken;
    } else {
        EffectiveToken = &AccessState->SubjectSecurityContext.PrimaryToken;
    }

    OriginalAccessToken = *EffectiveToken;
    Status = FatCreateRestrictEveryoneToken( OriginalAccessToken, &RestrictedAccessToken );

    if (!NT_SUCCESS(Status)) {
        
        SeReleaseSubjectContext( &AccessState->SubjectSecurityContext );
        return Status;
    }

    //
    //  Now see if the resulting context has access to the device through
    //  its explicitly granted access.  We swap in our restricted token
    //  for this check as the effective client token.
    //

    *EffectiveToken = RestrictedAccessToken;

    Result = SeAccessCheck( DeviceObject->SecurityDescriptor,
                            &AccessState->SubjectSecurityContext,
                            FALSE,
                            AccessState->OriginalDesiredAccess,
                            0,
                            NULL,
                            IoGetFileObjectGenericMapping(),
                            ProcessorMode,
                            &GrantedAccess,
                            &Status );
    
    *EffectiveToken = OriginalAccessToken;
    
    //
    //  Cleanup and return.
    //

    SeUnlockSubjectContext( &AccessState->SubjectSecurityContext );
    ObDereferenceObject( RestrictedAccessToken );

    return Status;
}


NTSTATUS
FatCreateRestrictEveryoneToken (
    IN PACCESS_TOKEN Token,
    OUT PACCESS_TOKEN *RestrictedToken
    )

/*++

Routine Description:

    This function takes a token as the input and returns a new restricted token
    from which Everyone sid has been disabled.  The resulting token may be used
    to find out if access is available to a user-sid by explicit means.

Arguments:

    Token - Input token from which Everyone sid needs to be deactivated.

    RestrictedToken - Receives the the new restricted token.
        This must be released using ObDereferenceObject(*RestrictedToken);

Return Value:

    NTSTATUS - Returned by SeFilterToken.

--*/

{
    //
    // Array of sids to disable.
    //

    TOKEN_GROUPS SidsToDisable;

    NTSTATUS Status = STATUS_SUCCESS;

    //
    //  Restricted token will contain the original sids with one change:
    //  If Everyone sid is present in the token, it will be marked for DenyOnly.
    //

    *RestrictedToken = NULL;

    //
    //  Put Everyone sid in the array of sids to disable. This will mark it
    //  for SE_GROUP_USE_FOR_DENY_ONLY and it'll only be applicable for Deny aces.
    //

    SidsToDisable.GroupCount = 1;
    SidsToDisable.Groups[0].Attributes = 0;
    SidsToDisable.Groups[0].Sid = SeExports->SeWorldSid;

    Status = SeFilterToken(
                 Token,            // Token that needs to be restricted.
                 0,                // No flags
                 &SidsToDisable,   // Disable everyone sid
                 NULL,             // Do not create any restricted sids
                 NULL,             // Do not delete any privileges
                 RestrictedToken   // Restricted token
                 );

    return Status;
}
