Microsoft KB Archive/269175

From BetaArchive Wiki

HOWTO: Use Visual C++ to Properly Order ACEs in an ACL

Q269175



The information in this article applies to:


  • Microsoft Active Directory Service Interfaces, version 2.5
  • Microsoft Active Directory Services Interface, System Component
  • Microsoft Active Directory Client Extension





SUMMARY

This article shows how to properly order an access control list (ACL) by using the IADs interface to obtain the object's security descriptor. This article includes C source code that illustrates how to implement the reordering algorithm. Two functions are used to implement the reordering algorithm, ReorderDACL and CombineACLs. ReorderDACL takes as its only argument an IADs interface pointer. The function uses the CombineACLs helper to append one ACL to another.



MORE INFORMATION

The IADsAccessControlList::AddAce method adds the access control entry (ACE) at the top of the ACL. In some cases, adding an ACE at the top will create undesired security access. The proper order of ACEs in an ACL is as follows:

Access-denied ACEs that apply to the object itself

Access-denied ACEs that apply to a child of the object, such as a property set or property
Access-allowed ACEs that apply to the object itself

Access-allowed ACEs that apply to a child object of the object, such as a property set or property

Currently there is no method available through the IADsAccessControlList interface that will properly order an ACL. The ACEs must be sorted into the five groups:

Access-denied on the object

Access-denied on a child object or property
Access-allowed on the object
Access-allowed on a child object or property

All inherited ACEs

The ordering for the inherited ACEs should not be altered. All inherited ACEs are added by the operating system and are ordered appropriately. A programmer can specify that an ACE should be inherited, and the operating system will take care of propagating the ACE to child objects.

There is an exception to the propagation rules. ACEs added to an object's ACL are not automatically applied to existing objects in the tree. It is the responsibility of the programmer to walk the tree and propagate the ACE by adding the ACE to the existing objects. The ACE will be propagated on new objects in the subtree.

Algorithm for Sorting ACEs in an ACL

  1. Obtain the discretionary ACL (DACL) from the security descriptor.
  2. Check the IADsAccessControlEntry::AceFlags to see if the ACE was inherited (check for the ADS_ACEFLAG_INHERITED_ACE bit).
  3. Check the IADsAccessControlEntry::AceType to see what type of access the ACE grants and what the access it granted to (the object itself or properties of the object). The following list outlines the ACE type values and what they mean:
  4. Place the ACE in the appropriate temporary DACL based on the IADsAccessControlListEntry::AceType value.
  5. Rebuild the ACL from the separate ACLs in the following order:
  6. Set the new ACL to the same revision level as the old ACL.
  7. Replace the ACL on the security descriptor.



Visual C++ Source Code to Reorder a DACL

// 
// CombineACLs
// 
// Helper function to 
// Combine two ACLs, takes pACL and adds it using the IADsAccessControList::AddAce
// method to the pDestAcl
// 
HRESULT CombineACLs( IADsAccessControlList *pDestACL, IADsAccessControlList *pACL)
{
    VARIANT varACE;
    IEnumVARIANT *pEnum = NULL;
    IDispatch *pDisp = NULL;
    LPUNKNOWN pUnk = NULL;
    HRESULT hr;
    
    ULONG lFetch;

    hr = pACL->get__NewEnum( &pUnk );
    if (SUCCEEDED(hr))
    {
        hr = pUnk->QueryInterface( IID_IEnumVARIANT, (void**) &pEnum );
        if (SUCCEEDED(hr))
        {
            hr = pEnum->Next( 1, &varACE, &lFetch );
            //Loop to read all ACEs on the object.
            while( hr == S_OK )
            {
                //Check if 1 item returned and returned item is an IDispatch pointer.
                if ( (lFetch == 1) && (varACE.vt==VT_DISPATCH))
                {
                    // 
                    // Add ACE's IDispatch to the destination ACL
                    // 
                    hr = pDestACL->AddAce( V_DISPATCH(&varACE));
                    V_DISPATCH(&varACE)->Release();
                }
                //Clean up the VARIANT for the ACE item.
                VariantClear(&varACE);
                //Get the next ACE
                hr = pEnum->Next( 1, &varACE, &lFetch );
            };//End of While loop
        }
        //Clean up
        if (pEnum)
            pEnum->Release();
    }
    if (pUnk)
        pUnk->Release(); 
    return hr;
}

// 
// ReOrderDacl
// Takes a single argument, an IADs interface for the object
// and reorders the DiscresionaryAcl for the Security Descriptor
// 
HRESULT ReOrderDacl( IADs* pObject )
{
    HRESULT hr;
    IADsSecurityDescriptor *pSD;
    IADsAccessControlList *pACL;
    IADsAccessControlList *pAccessDeniedACL;
    IADsAccessControlList *pAccessDeniedObjectACL;
    IADsAccessControlList *pAccessAllowedACL;
    IADsAccessControlList *pAccessAllowedObjectACL;
    IADsAccessControlList *pAllInheritedAcesACL;
    VARIANT var; 
    IDispatch *pDisp;
    // 
    // Check the object pointer, if its valid, retrieve the pSD pointer
    // 
    if (pObject)
    {
        // 
        //Get the nTSecurityDescriptor
        // 
        VariantClear(&var);
        LPOLESTR szAttribute = L"nTSecurityDescriptor";
        hr = pObject->Get(szAttribute,&var);
        if (SUCCEEDED(hr))
        {
            // 
            //Type should be VT_DISPATCH--an IDispatch ptr to the security descriptor object.
            // 
            if (var.vt==VT_DISPATCH)
            { 
                // 
                //Use the V_DISPATCH macro to get the IDispatch pointer from VARIANT structure
                //and QI for IADsSecurityDescriptor ptr.
                // 
                hr = V_DISPATCH( &var )->QueryInterface(IID_IADsSecurityDescriptor,(void**)&pSD);
                if (SUCCEEDED(hr))
                { // Begin working with Security Descriptor
                    // 
                    //Get the DACL
                    // 
                    hr = pSD->get_DiscretionaryAcl(&pDisp);
                    if (SUCCEEDED(hr))
                    {//Begin working with DACL
                        // 
                        //QI for IADsAccessControlList interface
                        // 
                        hr = pDisp->QueryInterface(IID_IADsAccessControlList,(void**)&pACL);
                        if (SUCCEEDED(hr))
                        { // Begin temp ACL creation
                            // 
                            // Request interfaces for all of the temporary ACLs
                            // 
                            hr  = CoCreateInstance( 
                                                    CLSID_AccessControlList,
                                                    NULL,
                                                    CLSCTX_INPROC_SERVER,
                                                    IID_IADsAccessControlList,
                                                    (void **)&pAccessDeniedACL
                                                  );  

                            if( SUCCEEDED(hr) ) hr = CoCreateInstance (
                                                    CLSID_AccessControlList,
                                                    NULL,
                                                    CLSCTX_INPROC_SERVER,
                                                    IID_IADsAccessControlList,
                                                    (void **)&pAccessDeniedObjectACL
                                                    );
                            else goto Error_Handler;

                            if( SUCCEEDED(hr) ) hr = CoCreateInstance (
                                                    CLSID_AccessControlList,
                                                    NULL,
                                                    CLSCTX_INPROC_SERVER,
                                                    IID_IADsAccessControlList,
                                                    (void **)&pAccessAllowedACL
                                                    );
                            else goto Error_Handler;
                            if( SUCCEEDED(hr) ) hr = CoCreateInstance (
                                                    CLSID_AccessControlList,
                                                    NULL,
                                                    CLSCTX_INPROC_SERVER,
                                                    IID_IADsAccessControlList,
                                                    (void **)&pAccessAllowedObjectACL
                                                    );
                            else goto Error_Handler;
                            if( SUCCEEDED(hr) ) hr = CoCreateInstance (
                                                    CLSID_AccessControlList,
                                                    NULL,
                                                    CLSCTX_INPROC_SERVER,
                                                    IID_IADsAccessControlList,
                                                    (void **)&pAllInheritedAcesACL
                                                    );
                            else goto Error_Handler;
                            if( SUCCEEDED(hr) )
                            {  // Begin sorting list
                                // 
                                // All of the temporary ACLs are ready.
                                // Sort the pACL list into the appropriate 
                                // ACLs
                                // 
                                // Setup the enumeration interfaces and the enumeration loop
                                // 
                                VARIANT varACE;
                                IEnumVARIANT *pEnum = NULL;
                                LPUNKNOWN pUnk = NULL;
                                IADsAccessControlEntry *pACE;
                                IDispatch *pDispatch;
                                long lAceType,lAceFlags;
                                long lAclRevision;

                                ULONG lFetch;

                                hr = pACL->get__NewEnum( &pUnk );
                                if (SUCCEEDED(hr))
                                {
                                    hr = pUnk->QueryInterface( IID_IEnumVARIANT, (void**) &pEnum );
                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pEnum->Next( 1, &varACE, &lFetch );
                                        // 
                                        //Loop to read all ACEs on the object.
                                        // 
                                        while( hr == S_OK )
                                        {
                                            // 
                                            //Check if 1 item returned and returned item is an IDispatch pointer.
                                            // 
                                            if ( (lFetch == 1) && (varACE.vt==VT_DISPATCH))
                                            {
                                                // 
                                                // Add ACE's IDispatch to the destination ACL
                                                // 
                                                hr = V_DISPATCH(&varACE)->QueryInterface(IID_IADsAccessControlEntry,(void**)&pACE);
                                                if( SUCCEEDED(hr))
                                                {
                                                    // 
                                                    // We have an ACE pointer, determine its Destination ACL
                                                    // 
                                                    pACE->get_AceType( &lAceType );
                                                    pACE->get_AceFlags( &lAceFlags);
                                                    if( lAceFlags & ADS_ACEFLAG_INHERITED_ACE  )
                                                    {
                                                        // 
                                                        // All inherited aces go into one bin.  They are set by 
                                                        // OS and cannot be added by a user, so they
                                                        // are in order already.
                                                        // 
                                                        pAllInheritedAcesACL->AddAce( V_DISPATCH(&varACE) );
                                                    }
                                                    else
                                                    {
                                                        switch( lAceType )
                                                        {
                                                        case ADS_ACETYPE_ACCESS_DENIED:
                                                            pAccessDeniedACL->AddAce( V_DISPATCH(&varACE));
                                                            break;
                                                        case ADS_ACETYPE_ACCESS_DENIED_OBJECT:
                                                            pAccessDeniedObjectACL->AddAce( V_DISPATCH(&varACE));
                                                            break;
                                                        case ADS_ACETYPE_ACCESS_ALLOWED:
                                                            pAccessAllowedACL->AddAce( V_DISPATCH(&varACE));
                                                            break;
                                                        case ADS_ACETYPE_ACCESS_ALLOWED_OBJECT:
                                                            pAccessAllowedObjectACL->AddAce( V_DISPATCH(&varACE));
                                                            break;
                                                        default:
                                                            break;
                                                        }
                                                    }
                                                }
                                                V_DISPATCH(&varACE)->Release();
                                            }// End of While Loop
                                            // 
                                            //Clean up the VARIANT for the ACE item.
                                            // 
                                            VariantClear(&varACE);
                                            // 
                                            //Get the next ACE
                                            // 
                                            hr = pEnum->Next( 1, &varACE, &lFetch );
                                        }
                                        // 
                                        //Clean up the enumeration interface
                                        // 
                                        if (pEnum)
                                        pEnum->Release();
                                    }
                                    // 
                                    // Cleanup the IUnknown for the enumeration interface
                                    // 
                                    if (pUnk)
                                        pUnk->Release();
                                }
                                // 
                                // Combine the ACLs
                                // The order is as follows:
                                //  Access Denied for the object
                                //  Access Denied to a property or property set
                                //  Access Allowed for the object
                                //  Access Allowed for a property or property set
                                // 
                                //  Since the Access Denied aces are first, just add the 
                                //  other ACLs to the bottom of the pAccessDeniedACL pointer
                                //  using the helper function CombineACLs
                                // 
                                CombineACLs( pAccessDeniedACL, pAccessDeniedObjectACL);
                                CombineACLs( pAccessDeniedACL, pAccessAllowedACL);
                                CombineACLs( pAccessDeniedACL, pAccessAllowedObjectACL);
                                CombineACLs( pAccessDeniedACL, pAllInheritedAcesACL);
                                // 
                                // Set the revision level to the old ACLs revision level
                                // and obtain an IDispatch pointer to write ACL back into
                                // the Discretionary ACL
                                // 
                                pACL->get_AclRevision( &lAclRevision );
                                pAccessDeniedACL->put_AclRevision( lAclRevision );
                                pAccessDeniedACL->QueryInterface(IID_IDispatch,(void **)&pDispatch);
                                // 
                                //Write the DACL
                                // 
                                hr = pSD->put_DiscretionaryAcl(pDispatch);
                                if (SUCCEEDED(hr))
                                {        
                                    LPOLESTR szAttribute = L"nTSecurityDescriptor";
                                    // 
                                    // Write the ntSecurityDescriptor property to the property cache.
                                    // Var still contains the IDispatch pointer to the ntSecurityDescriptor
                                    // used to obtain pSD pointer
                                    // 
                                    hr = pObject->Put(szAttribute, var);
                                    if (SUCCEEDED(hr))
                                    {
                                        // 
                                        //Call SetInfo to flush the client side changes to the server
                                        // 
                                        hr = pObject->SetInfo();
                                    }
                                }
                                // 
                                // Clean up
                                // 
                                pAccessDeniedACL->Release();
                                pAccessDeniedObjectACL->Release();
                                pAccessAllowedACL->Release();
                                pAccessAllowedObjectACL->Release();
                                if( pDispatch) pDispatch->Release();
                                pDisp->Release();
                           }// End sorting ACLs
                           pACL->Release();
                        }//End ACL Creation
                    }//End working with DACL
                    pSD->Release();
                }// End working with Security Descriptor
            }
            
        }
    }
    return hr;

Error_Handler:
    if( pAccessAllowedACL ) pAccessAllowedACL->Release();
    if( pAccessAllowedObjectACL ) pAccessAllowedObjectACL->Release();
    if( pAccessDeniedACL ) pAccessDeniedACL->Release();
    if( pAccessDeniedObjectACL ) pAccessDeniedObjectACL->Release();
    if( pAllInheritedAcesACL ) pAllInheritedAcesACL->Release();
    if( pSD) pSD->Release();
    if( pACL ) pACL->Release();
    return hr;
} 



REFERENCES

For additional information, click the article numbers below to view the articles in the Microsoft Knowledge Base:

Q269159 HOWTO: Use Visual Basic and ADsSecurity.dll to Properly Order ACEs in an ACL

Q279682 HOWTO: Use ADsSecurity.dll to Add an Access Control Entry to an NTFS Folder

Additional query words:

Keywords : kbADSI kbOSWinNT kbSDKPlatform kbVC _IK
Issue type : kbhowto
Technology : kbAudDeveloper kbActiveDirectory kbADSISearch kbADSI250 kbADSISysComp


Last Reviewed: March 7, 2001
© 2001 Microsoft Corporation. All rights reserved. Terms of Use.