/*++

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:

    tokenadj.c

Abstract:

   This module implements the services that perform individual adjustments
   on token objects.

--*/

#include "pch.h"

#pragma hdrstop


#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,NtAdjustPrivilegesToken)
#pragma alloc_text(PAGE,NtAdjustGroupsToken)
#pragma alloc_text(PAGE,SepAdjustPrivileges)
#pragma alloc_text(PAGE,SepAdjustGroups)
#endif


////////////////////////////////////////////////////////////////////////
//                                                                    //
//           Token Object Routines & Methods                          //
//                                                                    //
////////////////////////////////////////////////////////////////////////


NTSTATUS
NtAdjustPrivilegesToken (
    __in HANDLE TokenHandle,
    __in BOOLEAN DisableAllPrivileges,
    __in_opt PTOKEN_PRIVILEGES NewState,
    __in ULONG BufferLength,
    __out_bcount_part_opt(BufferLength,*ReturnLength) PTOKEN_PRIVILEGES PreviousState,
    __out_opt PULONG ReturnLength
    )


/*++


Routine Description:

    This routine is used to disable or enable privileges in the
    specified token.  The absence of some of the privileges listed to
    be changed won't effect the successful modification of the
    privileges that are in the token.  The previous enabled/disabled
    state of changed privileges may optionally be capture (for
    resetting later).

    TOKEN_ADJUST_PRIVILEGES access is required to enable or disable
    privileges in a token.


Arguments:

    TokenHandle - Provides a handle to the token to operate on.

    DisableAllPrivileges - This boolean parameter may be
        used to disable all privileges assigned to the token.  If
        this parameter is specified as TRUE, then the NewState parameter is
        ignored.

    NewState - This (optional) parameter points to a TOKEN_PRIVILEGES
        data structure containing the privileges whose states are to
        be adjusted (disabled or enabled).  Only the Enabled flag of
        the attributes associated with each privilege is used.  It
        provides the new value that is to be assigned to the
        privilege in the token.

    BufferLength - This optional parameter indicates the length (in
        bytes) of the PreviousState buffer.  This value must be
        provided if the PreviousState parameter is provided.

    PreviousState - This (optional) parameter points to a buffer to
        receive the state of any privileges actually changed by this
        request.  This information is formated as a TOKEN_PRIVILEGES
        data structure which may be passed as the NewState parameter
        in a subsequent call to this routine to restore the original
        state of those privileges.  TOKEN_QUERY access is needed to
        use this parameter.

        If this buffer does not contain enough space to receive the
        complete list of modified privileges, then no privilege
        states are changed and STATUS_BUFFER_TOO_SMALL is returned.
        In this case, the ReturnLength OUT parameter will
        contain the actual number of bytes needed to hold the
        information.

    ReturnLength - Indicates the actual number of bytes needed to
        contain the previous privilege state information.
        This parameter is ignored if the PreviousState argument is not
        passed.

Return Value:

    STATUS_SUCCESS - The service successfully completed the requested
        operation.

    STATUS_NOT_ALL_ASSIGNED - This NT_SUCCESS severity return status
        indicates that not all the specified privileges are currently
        assigned to the caller.  All specified privileges that are
        currently assigned have been successfully adjusted.

    STATUS_BUFFER_TOO_SMALL - Indicates the optional buffer provided
        to receive the previous states of changed privileges wasn't
        large enough to receive that information.  No changes to
        privilege states has been made.  The number of bytes needed
        to hold the state change information is returned via the
        ReturnLength parameter.

    STATUS_INVALID_PARAMETER - Indicates neither the DisableAllPrivileges
        parameter was specified as true, nor was an explicit NewState
        provided.

--*/

{
    KPROCESSOR_MODE PreviousMode;
    NTSTATUS Status;

    PTOKEN Token;

    ACCESS_MASK DesiredAccess;

    ULONG CapturedPrivilegeCount = 0;
    PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL;
    ULONG CapturedPrivilegesLength = 0;

    ULONG LocalReturnLength = 0;
    ULONG ChangeCount = 0;
    BOOLEAN ChangesMade = FALSE;

    ULONG ParameterLength = 0;

    PAGED_CODE();

    //
    //  The semantics of the PreviousState parameter leads to a two-pass
    //  approach to adjusting privileges.  The first pass simply checks
    //  to see which privileges will change and counts them.  This allows
    //  the amount of space needed to be calculated and returned.  If
    //  the caller's PreviousState return buffer is not large enough, then
    //  an error is returned without making any modifications.  Otherwise,
    //  a second pass is made to actually make the changes.
    //
    //

    if (!DisableAllPrivileges && !ARGUMENT_PRESENT(NewState)) {
        return STATUS_INVALID_PARAMETER;
    }

    //
    // Get previous processor mode and probe parameters if necessary.
    //

    PreviousMode = KeGetPreviousMode();
    if (PreviousMode != KernelMode) {
        try {

            //
            // Make sure we can see all of the new state
            //

            if (!DisableAllPrivileges) {

                ProbeForReadSmallStructure(
                    NewState,
                    sizeof(TOKEN_PRIVILEGES),
                    sizeof(ULONG)
                    );

                CapturedPrivilegeCount = NewState->PrivilegeCount;
                ParameterLength = (ULONG)sizeof(TOKEN_PRIVILEGES) +
                                  ( (CapturedPrivilegeCount - ANYSIZE_ARRAY) *
                                  (ULONG)sizeof(LUID_AND_ATTRIBUTES)  );

                ProbeForRead(
                    NewState,
                    ParameterLength,
                    sizeof(ULONG)
                    );

            }


            //
            // Check the PreviousState buffer for writeability
            //

            if (ARGUMENT_PRESENT(PreviousState)) {

                ProbeForWrite(
                    PreviousState,
                    BufferLength,
                    sizeof(ULONG)
                    );

                ProbeForWriteUlong(ReturnLength);
            }


        } except(EXCEPTION_EXECUTE_HANDLER) {
            return GetExceptionCode();
        }

    } else {

        if (!DisableAllPrivileges) {

            CapturedPrivilegeCount = NewState->PrivilegeCount;
        }
    }



    //
    // Capture NewState if passed.
    //

    if (!DisableAllPrivileges) {

        try {


            Status = SeCaptureLuidAndAttributesArray(
                         (NewState->Privileges),
                         CapturedPrivilegeCount,
                         PreviousMode,
                         NULL, 0,
                         PagedPool,
                         TRUE,
                         &CapturedPrivileges,
                         &CapturedPrivilegesLength
                         );

        } except(EXCEPTION_EXECUTE_HANDLER) {

            return GetExceptionCode();

        }

        if (!NT_SUCCESS(Status)) {

            return Status;

        }

    }


    //
    // Reference the token object and validate the caller's right
    // to adjust the privileges.
    //

    if (ARGUMENT_PRESENT(PreviousState)) {
        DesiredAccess = (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY);
    } else {
        DesiredAccess = TOKEN_ADJUST_PRIVILEGES;
    }

    Status = ObReferenceObjectByHandle(
             TokenHandle,             // Handle
             DesiredAccess,           // DesiredAccess
             SeTokenObjectType,      // ObjectType
             PreviousMode,            // AccessMode
             (PVOID *)&Token,         // Object
             NULL                     // GrantedAccess
             );

    if ( !NT_SUCCESS(Status) ) {

        if (CapturedPrivileges != NULL) {
            SeReleaseLuidAndAttributesArray(
                CapturedPrivileges,
                PreviousMode,
                TRUE
                );
        }

        return Status;
    }

    //
    //  Gain exclusive access to the token.
    //

    SepAcquireTokenWriteLock( Token );

    //
    // First pass through the privileges list - just count the changes
    //


    Status = SepAdjustPrivileges(
                Token,
                FALSE,                // Don't make changes this pass
                DisableAllPrivileges,
                CapturedPrivilegeCount,
                CapturedPrivileges,
                PreviousState,
                &LocalReturnLength,
                &ChangeCount,
                &ChangesMade
                );

    if (ARGUMENT_PRESENT(PreviousState)) {

        try {

            (*ReturnLength) = LocalReturnLength;

        } except(EXCEPTION_EXECUTE_HANDLER) {

            SepReleaseTokenWriteLock( Token, FALSE );
            ObDereferenceObject( Token );

            if (CapturedPrivileges != NULL) {
                SeReleaseLuidAndAttributesArray(
                    CapturedPrivileges,
                    PreviousMode,
                    TRUE
                    );
            }

            return GetExceptionCode();
        }

    }


    //
    // Make sure there is enough room to return any  requested
    // information.
    //

    if (ARGUMENT_PRESENT(PreviousState)) {
        if (LocalReturnLength > BufferLength) {

            SepReleaseTokenWriteLock( Token, FALSE );
            ObDereferenceObject( Token );

            if (CapturedPrivileges != NULL) {
                SeReleaseLuidAndAttributesArray(
                    CapturedPrivileges,
                    PreviousMode,
                    TRUE
                    );
            }

            return STATUS_BUFFER_TOO_SMALL;
        }
    }

    //
    // Second pass through the privileges list - Make the changes.
    //
    // Note that the internal routine attempts to write the previous
    // state directly to the caller's buffer - and so may get an exception.
    //

    try {

        Status = SepAdjustPrivileges(
                    Token,
                    TRUE,                 // Make the changes this pass
                    DisableAllPrivileges,
                    CapturedPrivilegeCount,
                    CapturedPrivileges,
                    PreviousState,
                    &LocalReturnLength,
                    &ChangeCount,
                    &ChangesMade
                    );


        if (ARGUMENT_PRESENT(PreviousState)) {

            PreviousState->PrivilegeCount = ChangeCount;
                        
            if (ChangeCount == 0) {

                RtlZeroMemory(
                    PreviousState->Privileges, 
                    sizeof(LUID_AND_ATTRIBUTES)
                    );
            }
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {

        SepReleaseTokenWriteLock( Token, TRUE );
        ObDereferenceObject( Token );
        if (CapturedPrivileges != NULL) {
            SeReleaseLuidAndAttributesArray(
                CapturedPrivileges,
                PreviousMode,
                TRUE
                );
        }
        return GetExceptionCode();

    }


    SepReleaseTokenWriteLock( Token, ChangesMade );
    ObDereferenceObject( Token );
    if (CapturedPrivileges != NULL) {
        SeReleaseLuidAndAttributesArray(
            CapturedPrivileges,
            PreviousMode,
            TRUE
            );
    }

    return Status;

}


NTSTATUS
NtAdjustGroupsToken (
    __in HANDLE TokenHandle,
    __in BOOLEAN ResetToDefault,
    __in PTOKEN_GROUPS NewState,
    __in ULONG BufferLength,
    __out_bcount_part_opt(BufferLength, *ReturnLength) PTOKEN_GROUPS PreviousState,
    __out PULONG ReturnLength
    )

/*++


Routine Description:

    This routine is used to disable or enable groups in the specified
    token.  The absence of some of the groups listed to be changed
    won't effect the successful modification of the groups that are in
    the token.  The previous enabled/disabled state of changed groups
    may optionally be capture (for resetting later).

    TOKEN_ADJUST_GROUPS access is required to enable or disable groups
    in a token

    Note that mandatory groups can not be disabled.  An attempt
    disable any mandatory groups will cause the call to fail, leaving
    the state of all groups unchanged.


Arguments:

    TokenHandle - Provides a handle to the token to operate on.

    ResetToDefault - The parameter indicates whether all the groups
        in the token are to be reset to their default enabled/disabled
        state.

    NewState - This parameter points to a TOKEN_GROUPS data structure
        containing the groups whose states are to be adjusted
        (disabled or enabled).  Only the Enabled flag of the
        attributes associated with each group is used.  It provides
        the new value that is to be assigned to the group in the
        token.  If the ResetToDefault argument is specified as TRUE,
        then this argument is ignored.  Otherwise, it must be passed.

    BufferLength - This optional parameter indicates the length (in
        bytes) of the PreviousState buffer.  This value must be
        provided if the PreviousState parameter is provided.

    PreviousState - This (optional) parameter points to a buffer to
        receive the state of any groups actually changed by this
        request.  This information is formated as a TOKEN_GROUPS data
        structure which may be passed as the NewState parameter in a
        subsequent call to NtAdjustGroups to restore the original state
        of those groups.  TOKEN_QUERY access is needed to use this
        parameter.

        If this buffer does not contain enough space to receive the
        complete list of modified groups, then no group states are
        changed and STATUS_BUFFER_TOO_SMALL is returned.  In this
        case, the ReturnLength return parameter will contain the
        actual number of bytes needed to hold the information.

    ReturnLength - Indicates the actual number of bytes needed to
        contain the previous group state information.
        This parameter is ignored if the PreviousState argument is not
        passed.


Return Value:

    STATUS_SUCCESS - The service successfully completed the requested
        operation.

    STATUS_NOT_ALL_ASSIGNED - This NT_SUCCESS severity return status
        indicates that not all the specified groups are currently
        assigned to the caller.  All specified groups that are
        currently assigned have been successfully adjusted.

    STATUS_CANT_DISABLE_MANDATORY - Indicates an attempt was made to
        disable a mandatory group.  The states of all groups remains
        unchanged.

    STATUS_BUFFER_TOO_SMALL - Indicates the optional buffer provided
        to receive the previous states of changed group wasn't large
        enough to receive that information.  No changes to group
        states has been made.  The number of bytes needed to hold the
        state change information is returned via the ReturnLength
        parameter.

    STATUS_INVALID_PARAMETER - Indicates neither the ResetToDefault
        parameter was specified as true, nor was an explicit NewState
        provided.

--*/
{

    KPROCESSOR_MODE PreviousMode;
    NTSTATUS Status;

    PTOKEN Token;

    ACCESS_MASK DesiredAccess;

    ULONG CapturedGroupCount = 0;
    PSID_AND_ATTRIBUTES CapturedGroups = NULL;
    ULONG CapturedGroupsLength = 0;

    ULONG LocalReturnLength;
    ULONG ChangeCount;
    BOOLEAN ChangesMade;
    PSID SidBuffer = NULL;

    PAGED_CODE();

    //
    //  The semantics of the PreviousState parameter and the
    //  STATUS_CANT_DISABLE_MANDATORY completion status leads to a two-pass
    //  approach to adjusting groups.  The first pass simply checks
    //  to see which groups will change and counts them.  This allows
    //  the amount of space needed to be calculated and returned.  If
    //  the caller's PreviousState return buffer is not large enough, or
    //  one of the specified groups is a mandatory group, then an error
    //  is returned without making any modifications.  Otherwise, a second
    //  pass is made to actually make the changes.
    //

    if (!ResetToDefault && !ARGUMENT_PRESENT(NewState)) {
        return STATUS_INVALID_PARAMETER;
    }

    //
    // Get previous processor mode and probe parameters if necessary.
    //

    PreviousMode = KeGetPreviousMode();
    if (PreviousMode != KernelMode) {
        try {

            if (!ResetToDefault) {
                ProbeForReadSmallStructure(
                    NewState,
                    sizeof(TOKEN_GROUPS),
                    sizeof(ULONG)
                    );
            }

            if (ARGUMENT_PRESENT(PreviousState)) {

                ProbeForWrite(
                    PreviousState,
                    BufferLength,
                    sizeof(ULONG)
                    );

                //
                // This parameter is only used if PreviousState
                // is present
                //

                ProbeForWriteUlong(ReturnLength);

            }


        } except(EXCEPTION_EXECUTE_HANDLER) {
            return GetExceptionCode();
        }
    }

    //
    // Capture NewState.
    //

    if (!ResetToDefault) {

        try {

            CapturedGroupCount = NewState->GroupCount;
            Status = SeCaptureSidAndAttributesArray(
                         &(NewState->Groups[0]),
                         CapturedGroupCount,
                         PreviousMode,
                         NULL, 0,
                         PagedPool,
                         TRUE,
                         &CapturedGroups,
                         &CapturedGroupsLength
                         );

            if (!NT_SUCCESS(Status)) {

                return Status;

            }

        } except(EXCEPTION_EXECUTE_HANDLER) {

            return GetExceptionCode();

        } // endtry
    } // endif !ResetToDefault


    //
    // Reference the token object and validate the caller's right
    // to adjust the groups.
    //

    if (ARGUMENT_PRESENT(PreviousState)) {
        DesiredAccess = (TOKEN_ADJUST_GROUPS | TOKEN_QUERY);
    } else {
        DesiredAccess = TOKEN_ADJUST_GROUPS;
    }

    Status = ObReferenceObjectByHandle(
             TokenHandle,             // Handle
             DesiredAccess,           // DesiredAccess
             SeTokenObjectType,      // ObjectType
             PreviousMode,            // AccessMode
             (PVOID *)&Token,         // Object
             NULL                     // GrantedAccess
             );

    if ( !NT_SUCCESS(Status) ) {

        if (ARGUMENT_PRESENT(CapturedGroups)) {
            SeReleaseSidAndAttributesArray( CapturedGroups, PreviousMode, TRUE );
        }

        return Status;
    }

    //
    //  Gain exclusive access to the token.
    //

    SepAcquireTokenWriteLock( Token );

    //
    // First pass through the groups list.
    //
    // This pass is always necessary for groups to make sure the caller
    // isn't trying to do anything illegal to mandatory groups.
    //

    Status = SepAdjustGroups(
                 Token,
                 FALSE,                // Don't make changes this pass
                 ResetToDefault,
                 CapturedGroupCount,
                 CapturedGroups,
                 PreviousState,
                 NULL,                // Not returning SIDs this call
                 &LocalReturnLength,
                 &ChangeCount,
                 &ChangesMade
                 );

    if (ARGUMENT_PRESENT(PreviousState)) {

        try {

            (*ReturnLength) = LocalReturnLength;

        } except(EXCEPTION_EXECUTE_HANDLER) {

            SepReleaseTokenWriteLock( Token, FALSE );
            ObDereferenceObject( Token );

            if (ARGUMENT_PRESENT(CapturedGroups)) {
                SeReleaseSidAndAttributesArray(
                    CapturedGroups,
                    PreviousMode,
                    TRUE
                    );
            }

            return GetExceptionCode();
        }
    }

    //
    // Make sure we didn't encounter an error
    //

    if (!NT_SUCCESS(Status)) {

        SepReleaseTokenWriteLock( Token, FALSE );
        ObDereferenceObject( Token );

        if (ARGUMENT_PRESENT(CapturedGroups)) {
            SeReleaseSidAndAttributesArray(
                CapturedGroups,
                PreviousMode,
                TRUE
                );
        }

        return Status;

    }

    //
    // Make sure there is enough room to return requested information.
    // Also go on to calculate where the SID values go.
    //

    if (ARGUMENT_PRESENT(PreviousState)) {
        if (LocalReturnLength > BufferLength) {

            SepReleaseTokenWriteLock( Token, FALSE );
            ObDereferenceObject( Token );

            if (ARGUMENT_PRESENT(CapturedGroups)) {
                SeReleaseSidAndAttributesArray(
                    CapturedGroups,
                    PreviousMode,
                    TRUE
                    );
            }


            return STATUS_BUFFER_TOO_SMALL;
        }

        //
        // Calculate where the SIDs can be placed in the PreviousState
        // buffer.
        //

        SidBuffer = (PSID)(LongAlignPtr(
                            (PCHAR)PreviousState + (ULONG)sizeof(TOKEN_GROUPS) +
                            (ChangeCount * (ULONG)sizeof(SID_AND_ATTRIBUTES)) -
                            (ANYSIZE_ARRAY * (ULONG)sizeof(SID_AND_ATTRIBUTES))
                            ) );

    }

    //
    // Second pass through the groups list.
    //

    try {

        Status = SepAdjustGroups(
                     Token,
                     TRUE,                 // Make changes in this pass
                     ResetToDefault,
                     CapturedGroupCount,
                     CapturedGroups,
                     PreviousState,
                     SidBuffer,
                     &LocalReturnLength,
                     &ChangeCount,
                     &ChangesMade
                     );

        if (ARGUMENT_PRESENT(PreviousState)) {

            PreviousState->GroupCount = ChangeCount;
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {

        SepReleaseTokenWriteLock( Token, TRUE );
        ObDereferenceObject( Token );
        if (ARGUMENT_PRESENT(CapturedGroups)) {
            SeReleaseSidAndAttributesArray( CapturedGroups, PreviousMode, TRUE );
        }
        return GetExceptionCode();

    }

    SepReleaseTokenWriteLock( Token, ChangesMade );
    ObDereferenceObject( Token );

    if (ARGUMENT_PRESENT(CapturedGroups)) {
        SeReleaseSidAndAttributesArray( CapturedGroups, PreviousMode, TRUE );
    }

    return Status;

}

NTSTATUS
SepAdjustPrivileges(
    IN PTOKEN Token,
    IN BOOLEAN MakeChanges,
    IN BOOLEAN DisableAllPrivileges,
    IN ULONG PrivilegeCount OPTIONAL,
    IN PLUID_AND_ATTRIBUTES NewState OPTIONAL,
    OUT PTOKEN_PRIVILEGES PreviousState OPTIONAL,
    OUT PULONG ReturnLength,
    OUT PULONG ChangeCount,
    OUT PBOOLEAN ChangesMade
    )

/*++


Routine Description:

    This routine is used to walk the privileges array in a token as a
    result of a request to adjust privileges.

    If the MakeChanges parameter is FALSE, this routine simply determines
    what changes are needed and how much space is necessary to save the
    current state of changed privileges.

    If the MakeChanges parameter is TRUE, this routine will not only
    calculate the space necessary to save the current state, but will
    actually make the changes.

    This routine makes the following assumptions:

      1) The token is locked for exclusive access.

      2) The PrivilegeCount and NewState parameters (if passed) are captured
         and accesses to them will not result in access violations.

      4) Any access violations encountered may leave the request
         partially completed.  It is the calling routine's responsibility
         to catch exceptions.

      5) The calling routine is responsible for inrementing the token's
         ModifiedId field.

Arguments:

    Token - Pointer to the token to act upon.

    MakeChanges - A boolean value indicating whether the changes should
        actually be made, or just evaluated.  A value of TRUE indicates
        the changes should be made.

    DisableAllPrivilegs - A boolean value indicating whether all privileges
        are to be disabled, or only select, specified privileges.  A value
        of TRUE indicates all privileges are to be disabled.

    PrivilegeCount - This parameter is required only if the NewState parameter
        is used.  In that case, this parameter indicates how many entries are
        in the NewState parameter.

    NewState - This parameter is ignored if the DisableAllPrivileges
        argument is TRUE.  If the DisableAllPrivileges argument is FALSE,
        then this parameter must be provided and specifies the new state
        to set privileges to (enabled or disabled).

    PreviousState - This (optional) parameter points to a buffer to
        receive the state of any privileges actually changed by this
        request.  This information is formated as a TOKEN_PRIVILEGES data
        structure which may be passed as the NewState parameter in a
        subsequent call to NtAdjustPrivileges to restore the original state
        of those privileges.  It is the caller's responsibility to make
        sure this buffer is large enough to receive all the state
        information.

    ReturnLength - Points to a buffer to receive the number of bytes needed
        to retrieve the previous state information of changed privileges.
        This parameter is ignored if the PreviousState argument is not
        passed.

    ChangeCount - Points to a ULONG to receive the number of privileges
        which were adjusted (or would be adjusted, if changes are made).

    ChangesMade - Points to a boolean flag which is to receive an indication
        as to whether any changes were made as a result of this call.  This
        is expected to be used to decide whether or not to increment the
        token's ModifiedId field.

Return Value:

    STATUS_SUCCESS - Call completed successfully.

    STATUS_NOT_ALL_ASSIGNED - Indicates not all the specified adjustments
        have been made (or could be made, if update wasn't requested).

--*/
{
    NTSTATUS CompletionStatus = STATUS_SUCCESS;

    ULONG OldIndex;
    ULONG NewIndex;
    BOOLEAN Found;
    ULONG MatchCount = 0;

    LUID_AND_ATTRIBUTES CurrentPrivilege;

    PAGED_CODE();

    //
    //  Walk through the privileges array to determine which need to be
    //  adjusted.
    //

    OldIndex = 0;
    (*ChangeCount) = 0;
    (*ChangesMade) = FALSE;

    while (OldIndex < Token->PrivilegeCount) {

        CurrentPrivilege = (Token->Privileges)[OldIndex];

        if (DisableAllPrivileges) {

            if (SepTokenPrivilegeAttributes(Token,OldIndex) &
               SE_PRIVILEGE_ENABLED ) {

                //
                // Change, if necessary (saving previous state if
                // appropriate).
                //

                if (MakeChanges) {

                    if (ARGUMENT_PRESENT(PreviousState)) {

                        PreviousState->Privileges[(*ChangeCount)] =
                            CurrentPrivilege;
                    }

                    SepTokenPrivilegeAttributes(Token,OldIndex) &=
                        ~SE_PRIVILEGE_ENABLED;



                } //endif make changes

                //
                // increment the number of changes
                //

                (*ChangeCount) += 1;

            } // endif privilege enabled

        } else {

            //
            //  Selective adjustments - this is a little trickier
            //  Compare the current privilege to each of those in
            //  the NewState array.  If a match is found, then adjust
            //  the current privilege appropriately.
            //

            NewIndex = 0;
            Found = FALSE;

            while ( (NewIndex < PrivilegeCount) && !Found)  {

                //
                // Look for a comparison
                //

                if (RtlEqualLuid(&CurrentPrivilege.Luid,&NewState[NewIndex].Luid)) {

                    Found = TRUE;
                    MatchCount += 1;

                    //
                    // Check if the caller wants the privilege removed. We give
                    // SE_PRIVILEGE_REMOVED a preferance over any other flags.
                    //

                    if ( (SepArrayPrivilegeAttributes( NewState, NewIndex ) &
                          SE_PRIVILEGE_REMOVED) ) {

                        //
                        // Change, if necessary. There is no need to save the 
                        // previous state. This is a one way journey.
                        //

                        if (MakeChanges) {

                            //
                            // if this is one of the recorded privileges, then
                            // delete its corresponding bit in TokenFlags
                            //

                            if (RtlEqualLuid(&CurrentPrivilege.Luid,
                                              &SeChangeNotifyPrivilege)) {
                                Token->TokenFlags &= ~TOKEN_HAS_TRAVERSE_PRIVILEGE;
                            } else if (RtlEqualLuid(&CurrentPrivilege.Luid,
                                              &SeBackupPrivilege)) {
                                Token->TokenFlags &= ~TOKEN_HAS_BACKUP_PRIVILEGE;
                            } else if (RtlEqualLuid(&CurrentPrivilege.Luid,
                                              &SeRestorePrivilege)) {
                                Token->TokenFlags &= ~TOKEN_HAS_RESTORE_PRIVILEGE;
                            } else if (RtlEqualLuid( &CurrentPrivilege.Luid, 
                                              &SeImpersonatePrivilege)) {
                                Token->TokenFlags &= ~TOKEN_HAS_IMPERSONATE_PRIVILEGE;
                                
                            }

                            //
                            // Swap this privilege with the last one.
                            //

                            if (OldIndex + 1 != Token->PrivilegeCount) {
                                LUID_AND_ATTRIBUTES TempLuidAttr;
                                TempLuidAttr = Token->Privileges[OldIndex];
                                Token->Privileges[OldIndex] = Token->Privileges[Token->PrivilegeCount-1];
                                Token->Privileges[Token->PrivilegeCount-1] = TempLuidAttr;
                            }

                            //
                            // We just lost a privilege. Make note of it.
                            //

                            Token->PrivilegeCount--;
                            OldIndex--;
                            (*ChangesMade) = TRUE;


                        } //endif make changes

                        //
                        // Note: Do NOT increment the number of changes
                        //


                   //
                   // Check if there is a state change from/to enabled to/from 
                   // disabled
                   //

                    } else if ( (SepArrayPrivilegeAttributes( NewState, NewIndex ) &
                          SE_PRIVILEGE_ENABLED)
                        !=
                         (SepTokenPrivilegeAttributes(Token,OldIndex) &
                          SE_PRIVILEGE_ENABLED)  ) {

                        //
                        // Change, if necessary (saving previous state if
                        // appropriate).
                        //

                        if (MakeChanges) {

                            if (ARGUMENT_PRESENT(PreviousState)) {

                                PreviousState->Privileges[(*ChangeCount)] =
                                    CurrentPrivilege;
                            }

                            SepTokenPrivilegeAttributes(Token,OldIndex) &=
                                ~(SepTokenPrivilegeAttributes(Token,OldIndex)
                                  & SE_PRIVILEGE_ENABLED);
                            SepTokenPrivilegeAttributes(Token,OldIndex) |=
                                 (SepArrayPrivilegeAttributes(NewState,NewIndex)
                                  & SE_PRIVILEGE_ENABLED);

                            //
                            // if this is SeChangeNotifyPrivilege, then
                            // change its corresponding bit in TokenFlags
                            // Note that Backup and Restore privileges do not
                            // care about Enabled/Disabled state.
                            //

                            if (RtlEqualLuid(&CurrentPrivilege.Luid,
                                              &SeChangeNotifyPrivilege)) {
                                Token->TokenFlags ^= TOKEN_HAS_TRAVERSE_PRIVILEGE;
                            } else if ( RtlEqualLuid( &CurrentPrivilege.Luid,
                                              &SeImpersonatePrivilege)) {
                                Token->TokenFlags ^= TOKEN_HAS_IMPERSONATE_PRIVILEGE ;
                                
                            }

                        } //endif make changes

                        //
                        // increment the number of changes
                        //

                        (*ChangeCount) += 1;


                    } 

                } // endif found

                NewIndex += 1;

            } // endwhile searching NewState

        } // endelse

        OldIndex += 1;

    } // endwhile privileges in token

    //
    // If we disabled all privileges, then clear the TokenFlags flag
    // corresponding to the SeChangeNotifyPrivilege privilege.
    //


    if (DisableAllPrivileges) {
        Token->TokenFlags &= ~TOKEN_HAS_TRAVERSE_PRIVILEGE;
        Token->TokenFlags &= ~TOKEN_HAS_IMPERSONATE_PRIVILEGE;
    }

    //
    // Set completion status appropriately if some not assigned
    //

    if (!DisableAllPrivileges) {

        if (MatchCount < PrivilegeCount) {
            CompletionStatus = STATUS_NOT_ALL_ASSIGNED;
        }
    }

    //
    //  Indicate whether changes were made
    //

    if ((*ChangeCount) > 0  &&  MakeChanges) {
        (*ChangesMade) = TRUE;
    } 

    //
    // Calculate the space needed to return previous state information
    //

    if (ARGUMENT_PRESENT(PreviousState)) {

        (*ReturnLength) = (ULONG)sizeof(TOKEN_PRIVILEGES) + ((*ChangeCount > ANYSIZE_ARRAY) ?
                          (*ChangeCount - ANYSIZE_ARRAY) * (ULONG)sizeof(LUID_AND_ATTRIBUTES) : 0);
    }

   return CompletionStatus;
}

NTSTATUS
SepAdjustGroups(
    IN PTOKEN Token,
    IN BOOLEAN MakeChanges,
    IN BOOLEAN ResetToDefault,
    IN ULONG GroupCount,
    IN PSID_AND_ATTRIBUTES NewState OPTIONAL,
    OUT PTOKEN_GROUPS PreviousState OPTIONAL,
    OUT PSID SidBuffer OPTIONAL,
    OUT PULONG ReturnLength,
    OUT PULONG ChangeCount,
    OUT PBOOLEAN ChangesMade
    )

/*++


Routine Description:

    This routine is used to walk the groups array in a token as a
    result of a request to adjust groups.

    If the MakeChanges parameter is FALSE, this routine simply determines
    what changes are needed and how much space is necessary to save the
    current state of changed groups.

    If the MakeChanges parameter is TRUE, this routine will not only
    calculate the space necessary to save the current state, but will
    actually make the changes.

    This routine makes the following assumptions:

      1) The token is locked for exclusive access.

      2) The NewState parameter is captured and accesses
         to it will not result in access violations.

      4) Any access violations encountered may leave the request
         partially completed.  It is the calling routine's responsibility
         to catch exceptions.

      5) The calling routine is responsible for inrementing the token's
         ModifiedId field.

Arguments:

    Token - Pointer to the token to act upon.

    MakeChanges - A boolean value indicating whether the changes should
        actually be made, or just evaluated.  A value of TRUE indicates
        the changes should be made.

    ResetToDefault - Indicates that the groups are to be reset to their
        default enabled/disabled state.

    GroupCount - This parameter is required only if the NewState parameter
        is used.  In that case, this parameter indicates how many entries are
        in the NewState parameter.

    NewState - This parameter points to a SID_AND_ATTRIBUTES array
        containing the groups whose states are to be adjusted
        (disabled or enabled).  Only the Enabled flag of the
        attributes associated with each group is used.  It provides
        the new value that is to be assigned to the group in the
        token.  If the ResetToDefault argument is specified as TRUE,
        then this argument is ignored.  Otherwise, it must be passed.

    PreviousState - This (optional) parameter points to a buffer to
        receive the state of any groups actually changed by this
        request.  This information is formated as a TOKEN_GROUPS data
        structure which may be passed as the NewState parameter in a
        subsequent call to NtAdjustGroups to restore the original state
        of those groups.  It is the caller's responsibility to make
        sure this buffer is large enough to receive all the state
        information.

    SidBuffer - Pointer to buffer to receive the SID values corresponding
        to the groups returned in the PreviousState argument.

    ReturnLength - Points to a buffer to receive the number of bytes needed
        to retrieve the previous state information of changed privileges.
        This parameter is ignored if the PreviousState argument is not
        passed.

    ChangeCount - Points to a ULONG to receive the number of groups
        which were adjusted (or would be adjusted, if changes are made).

    ChangesMade - Points to a boolean flag which is to receive an indication
        as to whether any changes were made as a result of this call.  This
        is expected to be used to decide whether or not to increment the
        token's ModifiedId field.

Return Value:

    STATUS_SUCCESS - Call completed successfully.

    STATUS_NOT_ALL_ASSIGNED - Indicates not all the specified adjustments
        have been made (or could be made, if update wasn't requested).

    STATUS_CANT_DISABLE_MANDATORY - Not all adjustments were made (or could
        be made, if update not requested) because an attempt was made to
        disable a mandatory group.  The state of the groups is left
        in an nondeterministic state if update was requested.


--*/
{

    NTSTATUS CompletionStatus = STATUS_SUCCESS;

    ULONG OldIndex;
    ULONG NewIndex;
    ULONG SidLength;
    ULONG LocalReturnLength = 0;
    PSID NextSid;
    BOOLEAN Found;
    ULONG MatchCount = 0;
    BOOLEAN EnableGroup;
    BOOLEAN DisableGroup;
    ULONG TokenGroupAttributes;

    SID_AND_ATTRIBUTES CurrentGroup;

    PAGED_CODE();

    //
    // NextSid is used to copy group SID values if asked for previous state.
    //

    NextSid = SidBuffer;


    //
    //  Walk through the groups array to determine which need to be
    //  adjusted.
    //

    OldIndex = 1;             // Don't evaluate the 0th entry (user ID)
    (*ChangeCount) = 0;

    while (OldIndex < Token->UserAndGroupCount) {

        CurrentGroup = Token->UserAndGroups[OldIndex];

        if (ResetToDefault) {

            TokenGroupAttributes = SepTokenGroupAttributes(Token,OldIndex);

            //
            // If the group is enabled by default and currently disabled,
            // then we must enable it.
            //

            EnableGroup = (BOOLEAN)( (TokenGroupAttributes & SE_GROUP_ENABLED_BY_DEFAULT)
                && !(TokenGroupAttributes & SE_GROUP_ENABLED));

            //
            // If the group is disabled by default and currently enabled,
            // then we must disable it.
            //

            DisableGroup = (BOOLEAN)( !(TokenGroupAttributes & SE_GROUP_ENABLED_BY_DEFAULT)
                && (TokenGroupAttributes & SE_GROUP_ENABLED));

            if ( EnableGroup || DisableGroup ) {

                SidLength = SeLengthSid( CurrentGroup.Sid );
                SidLength = (ULONG)LongAlignSize(SidLength);
                LocalReturnLength += SidLength;

                //
                // Change, if necessary (saving previous state if
                // appropriate).
                //

                if (MakeChanges) {

                    if (ARGUMENT_PRESENT(PreviousState)) {

                        (*(PreviousState)).Groups[(*ChangeCount)].Attributes =
                            CurrentGroup.Attributes;

                        (*(PreviousState)).Groups[(*ChangeCount)].Sid =
                            NextSid;

                        RtlCopySid( SidLength, NextSid, CurrentGroup.Sid );
                        NextSid = (PSID)((ULONG_PTR)NextSid + SidLength);
                    }

                    if (EnableGroup) {
                        SepTokenGroupAttributes(Token,OldIndex) |= SE_GROUP_ENABLED;
                    } else {
                        SepTokenGroupAttributes(Token,OldIndex) &= ~SE_GROUP_ENABLED;
                    }



                } //endif make changes

                //
                // increment the number of changes
                //

                (*ChangeCount) += 1;

            } // endif group enabled

        } else {

            //
            //  Selective adjustments - this is a little trickier
            //  Compare the current group to each of those in
            //  the NewState array.  If a match is found, then adjust
            //  the current group appropriately.
            //

            NewIndex = 0;
            Found = FALSE;

            while ( (NewIndex < GroupCount) && !Found)  {

                //
                // Look for a comparison
                //

                if (RtlEqualSid(
                        CurrentGroup.Sid,
                        NewState[NewIndex].Sid
                        ) ) {

                    Found = TRUE;
                    MatchCount += 1;


                    //
                    // See if it needs to be changed
                    //

                    if ( (SepArrayGroupAttributes( NewState, NewIndex ) &
                            SE_GROUP_ENABLED ) !=
                         (SepTokenGroupAttributes(Token,OldIndex) &
                            SE_GROUP_ENABLED ) ) {

                        //
                        // Make sure group is not mandatory
                        //

                        if (SepTokenGroupAttributes(Token,OldIndex) &
                              SE_GROUP_MANDATORY ) {
                            return STATUS_CANT_DISABLE_MANDATORY;
                        }

                        //
                        // Make sure group is not deny-only
                        //


                        if (SepTokenGroupAttributes(Token,OldIndex) &
                              SE_GROUP_USE_FOR_DENY_ONLY ) {
                            return STATUS_CANT_ENABLE_DENY_ONLY;
                        }

                        SidLength = SeLengthSid( CurrentGroup.Sid );
                        SidLength = (ULONG)LongAlignSize(SidLength);
                        LocalReturnLength += SidLength;

                        //
                        // Change, if necessary (saving previous state if
                        // appropriate).
                        //

                        if (MakeChanges) {

                            if (ARGUMENT_PRESENT(PreviousState)) {

                                PreviousState->Groups[(*ChangeCount)].Attributes =
                                    CurrentGroup.Attributes;

                                PreviousState->Groups[(*ChangeCount)].Sid =
                                    NextSid;

                                RtlCopySid( SidLength, NextSid, CurrentGroup.Sid );

                                NextSid = (PSID)((ULONG_PTR)NextSid + SidLength);
                            }

                            SepTokenGroupAttributes(Token,OldIndex) &=
                                ~(SepTokenGroupAttributes(Token,OldIndex)
                                  & SE_GROUP_ENABLED);
                            SepTokenGroupAttributes(Token,OldIndex) |=
                                 (SepArrayGroupAttributes(NewState,NewIndex)
                                  & SE_GROUP_ENABLED);



                        } //endif make changes

                        //
                        // increment the number of changes
                        //

                        (*ChangeCount) += 1;


                    } // endif change needed

                } // endif found

                NewIndex += 1;

            } // endwhile searching NewState

        } // endelse

        OldIndex += 1;

    } // endwhile more groups in token

    //
    // Set completion status appropriately if some not assigned
    //

    if (!ResetToDefault) {

        if (MatchCount < GroupCount) {
            CompletionStatus = STATUS_NOT_ALL_ASSIGNED;
        }
    }

    //
    //  Indicate whether changes were made
    //

    if ((*ChangeCount) > 0  &&  MakeChanges) {
        (*ChangesMade) = TRUE;
    } else {
        (*ChangesMade) = FALSE;
    }

    //
    // Calculate the space needed to return previous state information
    // (The SID lengths have already been added up in LocalReturnLength).
    //

    if (ARGUMENT_PRESENT(PreviousState)) {

        (*ReturnLength) = LocalReturnLength +
                          (ULONG)sizeof(TOKEN_GROUPS) +
                          ((*ChangeCount) *  (ULONG)sizeof(SID_AND_ATTRIBUTES)) -
                          (ANYSIZE_ARRAY * (ULONG)sizeof(SID_AND_ATTRIBUTES));
    }

   return CompletionStatus;
}

