DOCUMENT:Q297947 09-APR-2002 [visualc] TITLE :INFO: Use the IADsPropertyValue2 Interface to Return an IADsSecu PRODUCT :Microsoft C Compiler PROD/VER::6.0 OPER/SYS: KEYWORDS:kbADSI kbVC600 kbDSupport w2000adsi w2000schema ====================================================================== ------------------------------------------------------------------------------- The information in this article applies to: - Microsoft Visual C++, 32-bit Editions, version 6.0, used with: - Microsoft Windows 2000 - Microsoft Active Directory Services Interface, Microsoft Active Directory Client - Microsoft Active Directory Services Interface, System Component ------------------------------------------------------------------------------- SUMMARY ======= This article describes a method that you can use to manipulate the defaultSecurityDescriptor attribute of an Active Directory schema object by using the IADsSecurityDescriptor interface that is returned from an Active Directory Service Interfaces (ADSI) property cache that is using the IADsPropertyValue2 interface. MORE INFORMATION ================ Requirements ------------ This article assumes that you are familiar with the following topics: - Windows NT security model. - Security Identifier (SID) and Security Descriptor (SD). - Security Descriptor Definition Languuage (SDDL). - How to use the IADsSecurityDescriptor interface to modify a security descriptor. This article does not address problems that can arise if you add improper access-control entries (ACEs) to an object's default security descriptor. NOTE: the default security descriptor is added to all objects of this class when they are created. Use caution when you create or modify an object's default security descriptor. For more information, refer to the MSDN online documentation before you attempt to use the sample code that is provided in this article. Background ---------- The defaultSecurityDescriptor attribute contains a security descriptor that is stored in its SDDL form. To modify this attribute, use one of the following methods: - Use the Win32 security APIs. - Use Visual C sample code to convert the SDDL SD into an IADsSecurityDescriptor. This article provides sample Visual C code that illustrates how to convert the SDDL SD into an IADsSecurityDescriptor by using the Active Directory Service Interfaces (ADSI) property cache IADsPropertyValue2 interface. Use the Win32 Security APIs --------------------------- 1. Bind to the Schema object, and then retrieve the SDDL form of the SD. 2. Add the appropriate ownership information to the SDDL string. NOTE: for the ADSI property cache to successfully return an IADsSecurityDescriptor interface, include the ownership and group ownership information for the SD in the SDDL string. If the information is missing, you must add it to the SDDL string. 3. Use the ConvertStringSecurityDescriptorToSecurityDescriptor function to obtain a binary SD. 4. Place the binary SD into a VARIANT. 5. Place the VARIANT copy of the SD into the ADSI property cache as an octet string type (array of bytes). 6. Request an NT Security Descriptor type from the ADSI property cache (the cache returns an IDispatch interface). 7. Use the QueryInterface method of the IDispatch interface to obtain an IADsSecurityDescriptor interface. 8. Use the properties and methods of the IADsSecurityDescriptor interface to modify the security descriptor. 9. Place the IDispatch interface for the SD back into the ADSI property cache. 10. Request the octet string form of the SD from the ADSI property cache. 11. Convert the binary SD back into its SDDL form by using the ConvertSecurityDescriptorToStringSecurityDescriptor. 12. Convert the SDDL string into a binary string (BSTR). 13. Place the BSTR into a VARIANT, and then replace the BSTR back onto the Schema object. Use Visual C Sample Code ------------------------ NOTE: This sample code requires the Include and the Library files from the Platform SDK. The Include and Library files that are released with Visual Studio do not contain the IADsPropertyValue2 interface. 1. Start Visual Studio. 2. Create a new Win32 Console Application, and then click OK. 3. On the "Win32 Console Application Step 1 of 1" page, click An Empty Project, and then click Finish. 4. In the "New Project Information" dialog box, click OK. 5. On the File menu, click New, and then select C++ Source File. 6. Name the file, and then click OK. 7. Paste the code from this article into the file that you just created. 8. On the Project menu, click Settings. 9. On the Link tab, add Adsiid.lib and Activeds.lib to the Object/Library Modules edit control, and then click OK. 10. Compile, link, and then run the project. Sample Visual C Code -------------------- #define _WIN32_WINNT 0x0500 #define UNICODE #define _UNICODE #include #include #include #include #include #include #include #include HRESULT BytesToVariantArray(PBYTE pValue, //Pointer to bytes to put in a variant array. ULONG cValueElements,//Size of pValue in bytes. VARIANT *pVariant //Return variant that contains octet string (VT_UI1|VT_ARRAY). ); int main() { HRESULT hr = E_FAIL; PSECURITY_DESCRIPTOR pSD = NULL; IADsSecurityDescriptor *pSecurityDescriptor = NULL; ULONG ulSDLen = 0; IADsPropertyValue *pVal; IADsPropertyValue2 *pVal2; SECURITY_INFORMATION dwSecInfo=0; VARIANT var, varBSTR; BSTR bstrOwner = NULL; long lSDType = ADSTYPE_NT_SECURITY_DESCRIPTOR; SAFEARRAY *pArrayVal = NULL; CComPtr pRootDSE; LPTSTR lpszSDBuffer; ULONG cbszSDBuffer = 0; CoInitialize(NULL); // // Find the Schema naming context. // hr = ADsGetObject(L"LDAP://RootDSE", IID_IADs,(void **) &pRootDSE); if( !SUCCEEDED(hr) ) { printf("Could not bind to RootDSE. Received error %08X\n",hr); return 0; } CComBSTR bSchemaPath; VARIANT varData; VariantInit( &varData ); pRootDSE->Get(L"schemaNamingContext", &varData); // // Build a path to the user schema object. // bSchemaPath = L"LDAP://cn=user,"; bSchemaPath.AppendBSTR( varData.bstrVal); // // Bind to the Schema object to retrieve the default Security Descriptor. // CComPtr pSchemaObj; hr = ADsGetObject(bSchemaPath.m_str,IID_IADs,(void **)&pSchemaObj); if( !SUCCEEDED(hr) ) { printf("Unable to open the schema object error %08X\n",hr); return 0; } VariantClear( &varData ); pSchemaObj->Get(L"defaultSecurityDescriptor", &varData); printf("The default security descriptor on the schema object is\n%S\n",varData.bstrVal); // // Convert the string representation (SDDL) of the security descriptor // into a SECURITY_DESCRIPTOR structure. // // // Check to see whether the ownership information is on the string that is located in // the first few characters; search for O: and G: to see whether this information // is in the SDDL form. If it is not, prefix the information to the string // so that the IADsPropertyValue2 interface can properly convert the // raw security descriptor into an IADsSecurityDescriptor interface. // // Set a flag with the appropriate constants to use when the SDDL // string is rebuilt after you display it. // // Prefix some ownership details. // CComBSTR bSDDLPre = varData.bstrVal; if( wcsstr(varData.bstrVal, L"O:") ) { // // Owner Info is present. Set the bits into // the SECURITY_INFORMATION structure. // // Use dwSecInfo to retrieve only those parts // of the security descriptor that were present in its SDDL form. // For the IADsPropertyValue2 interface to return // an IADsSecurityDescriptor interface, add the ownership information // if it is missing (see the previous comment). // dwSecInfo = dwSecInfo | OWNER_SECURITY_INFORMATION; } else { // // Ownership information is missing. Add it to the // the SDDL form of the SD and make the owner default to // the Domain Administrator. // CComBSTR bTemp = "O:DA"; bTemp.AppendBSTR ( bSDDLPre ); bSDDLPre = bTemp; } // // Check for Group Ownership information. // if( wcsstr( varData.bstrVal, L"G:") ) { // // Group ownership information is present. // Add this information to the SECURITY_INFORMATION // structure to retrieve it after the operation is complete. // dwSecInfo = dwSecInfo | GROUP_SECURITY_INFORMATION; } else { // // Add the Group ownership information to the // SDDL SD. Add this information after the ownership SID ("O:") // and before the DACL information ("D:") // This code assumes that the SDDL SD is set up with O: G: D: // // Default the Group onwnership to the Domain Admins ( G:DA ) // //******************************* // IMPORTANT NOTE: // // This code assumes that the DACL information is present. If this information is missing, // the code fails. //******************************** DWORD len; wchar_t *wsDacl; wchar_t *pSDDL = new wchar_t[bSDDLPre.Length() + 1]; wcscpy( pSDDL, bSDDLPre.m_str); // // Find the start of the DACL string, and then copy all of the code up to that // point into a temporary location. // wsDacl = wcsstr( pSDDL,L"D:"); len = (wsDacl - pSDDL); wchar_t *pCPY = new wchar_t[ len + 1 ]; wcsncpy( pCPY, pSDDL, len ); pCPY[len] = (wchar_t) NULL; // // Build the temporary binary string (BSTR), and then copy it into SDDL variable. // CComBSTR bTemp = pCPY; bTemp.Append("G:DA"); bTemp.Append( wsDacl ); delete pCPY; delete pSDDL; bSDDLPre = bTemp; } // // Check for DACL information. // if( wcsstr( varData.bstrVal, L"D:") ) { // // DACL information is present. // dwSecInfo = dwSecInfo | DACL_SECURITY_INFORMATION; } // // Check for SACL information. // if( wcsstr( varData.bstrVal, L"S:" ) ) { // // SACL information is present. // dwSecInfo = dwSecInfo | SACL_SECURITY_INFORMATION; } // // The SDDL string is built; convert it to a binary SID. // if ( ! ConvertStringSecurityDescriptorToSecurityDescriptor( bSDDLPre.m_str, SDDL_REVISION_1, &pSD, &ulSDLen )) { wprintf(L"Error converting string security descriptor: %d\n", GetLastError() ); return 0; } // // Check to see whether there is a valid binary // security descriptor. // if( IsValidSecurityDescriptor( pSD ) ) printf("Binary Security Descriptor is valid....\n\n"); else printf("Binary Security Descriptor is not valid....\n\n"); // // Initialize a VARIANT and place the SECURITY_DESCRIPTOR structure // in as a Byte array. // VariantInit(&var); hr= BytesToVariantArray( (PBYTE) pSD, //Pointer to bytes to put in a variant array. ulSDLen, //Size of pValue in bytes. &var //Return variant that contains the octet string (VT_UI1|VT_ARRAY). ); // // IADsPropertyValue is a CoClass; use this to get the // desired IADsPropertyValue2 interface for the conversion. // hr = CoCreateInstance(CLSID_PropertyValue, NULL, CLSCTX_INPROC_SERVER, IID_IADsPropertyValue, (void**)&pVal); // // QI for a IADsPropertyValue2 interface. // hr = pVal->QueryInterface(IID_IADsPropertyValue2,(void**)&pVal2); // // Put the VARIANT array into cache as an octet string. // hr = pVal2->PutObjectProperty(ADSTYPE_OCTET_STRING, var);//ADSTYPE_NT_SECURITY_DESCRIPTOR if (!SUCCEEDED(hr) ) { wprintf(L"Error putting security descriptor into cache: %d\n",GetLastError() ); goto Cleanup; } wprintf(L"Put modified security descriptor into cache\n\n"); // // Release the variant (this is very important for memory management). // VariantClear(&var); // // Retrieve the VARIANT array from cache as an IADsSecurityDescriptor. // hr = pVal2->GetObjectProperty(&lSDType, &var); if (!SUCCEEDED(hr) ) { wprintf(L"Error getting the IADsSecurityDescriptor from cache: %d\n", GetLastError() ); goto Cleanup; } wprintf(L"Read the IADsSecurityDescriptor from cache\n\n"); // // QI the IDispatch for an IADsSecurityDescriptor interface. // hr = V_DISPATCH( &var )->QueryInterface(IID_IADsSecurityDescriptor,(void**)&pSecurityDescriptor); if ( FAILED(hr) ) { wprintf(L"QI for IADsSecurityDescriptor failed: 0x%x\n", hr); goto Cleanup; } // // To verify, retrieve the owner of the Security Descriptor through the // IADsSecurityDescriptor::get_Owner method. // // At this point, the user can modify the Descretionary ACl by using the // IADsAccessControlList and IADsAccessControlEntry interfaces. // // This also applies to the SACL. // hr = pSecurityDescriptor->get_Owner(&bstrOwner); if ( FAILED(hr) ) { wprintf(L"Retrieve IADsSecurityDescriptor owner failed: 0x%x\n", hr); goto Cleanup; } wprintf(L"The owner for this security Descriptor is %s\n",bstrOwner); // // Convert the IADsSecurityDescriptor back into a variant that contains the Raw SD. // hr = pVal2->PutObjectProperty(ADSTYPE_NT_SECURITY_DESCRIPTOR ,var ); if( !SUCCEEDED(hr)) { wprintf(L"Error returning IADsSecurityDescriptor to the cache: %d\n", GetLastError()); goto Cleanup; } wprintf(L"Returned IADsSecurityDescriptor to the cache\n\n"); // // Retrieve it as an octet string. // lSDType = ADSTYPE_OCTET_STRING; hr = pVal2->GetObjectProperty( &lSDType, &var ); if ( FAILED(hr) ) { wprintf(L"Retrieve IADsSecurityDescriptor as Octet String failed: 0x%x\n", hr); goto Cleanup; } wprintf(L"Retrieved IADsSecurityDescriptor as Octet String from cache\n\n"); // // Convert it back to its SDDL form based on the dwSecInfo variable // set when the SDDL was parsed earlier. // SAFEARRAY *pAr; pAr = var.parray; SafeArrayAccessData( pAr, (void HUGEP **)&pSD); if( ! ConvertSecurityDescriptorToStringSecurityDescriptor( pSD, // SD SDDL_REVISION_1, // revision level dwSecInfo, // components &lpszSDBuffer, // security descriptor string &cbszSDBuffer // size of security descriptor string ) ) { printf("Error converting it back to a string descriptor: %d\n",GetLastError()); return 0; } else printf("This is the converted string descriptor(SDDL):\n%S\n\n", lpszSDBuffer); // // Convert the octet string into a raw SD pointer by dereferencing the array of bytes. // // This section of code illustrates how to create a variant that contains a BSTR so // that the property can be written back to the AD object. // varBSTR.bstrVal = SysAllocString(lpszSDBuffer ); LocalFree( (HLOCAL)lpszSDBuffer); wprintf(L"-------------------------------------------------------------------------------\n"); wprintf(L"The NEW default security descriptor on the schema object is: %s\n\n", varBSTR.bstrVal); V_VT(&varBSTR) = VT_BSTR; Cleanup: pRootDSE.Release(); pSchemaObj.Release(); if (pVal) pVal->Release(); if (pVal2) pVal2->Release(); if(pSecurityDescriptor) pSecurityDescriptor->Release(); VariantClear(&varBSTR); CoUninitialize(); return 0; } // // This function is located in the Platform SDK documentation. // HRESULT BytesToVariantArray( PBYTE pValue, //Pointer to bytes to put in a variant array. ULONG cValueElements,//Size of pValue in bytes. VARIANT *pVariant //Return variant that contains the octet string (VT_UI1|VT_ARRAY). ) { HRESULT hr = E_FAIL; SAFEARRAY *pArrayVal = NULL; SAFEARRAYBOUND arrayBound; CHAR HUGEP *pArray = NULL; //Set bound for array arrayBound.lLbound = 0; arrayBound.cElements = cValueElements; //Create the safe array for the octet string. Unsigned char elements;single dimension;aBound size. pArrayVal = SafeArrayCreate( VT_UI1, 1, &arrayBound ); if (!(pArrayVal == NULL) ) { hr = SafeArrayAccessData(pArrayVal, (void HUGEP * FAR *) &pArray ); if (SUCCEEDED(hr)) { //Copy the bytes to the safe array. memcpy( pArray, pValue, arrayBound.cElements ); SafeArrayUnaccessData( pArrayVal ); //Set type to array of unsigned char V_VT(pVariant) = VT_ARRAY | VT_UI1; //Assign the safe array to the array member. V_ARRAY(pVariant) = pArrayVal; hr = S_OK; } else { //Clean up if the array cannot be accessed. if ( pArrayVal ) SafeArrayDestroy( pArrayVal ); } } else { hr = E_OUTOFMEMORY; } return hr;} REFERENCES ========== For additional information, click the article numbers below to view the articles in the Microsoft Knowledge Base: Q269175 HOWTO: Use Visual C++ to Properly Order ACEs in an ACL Q255126 PRB: ADSI Err '-2147016694(8007200a)' and '-2147023591(80070519) To download the Platform SDK, click the following link: Platform SDK Update http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ (http://www.microsoft.com/msdownload/platformsdk/sdkupdate/) When you are prompted, click No for more information, or click Yes to download immediately. Additional query words: adsi ====================================================================== Keywords : kbADSI kbVC600 kbDSupport w2000adsi w2000schema Technology : kbVCsearch kbAudDeveloper kbVC32bitSearch Version : :6.0 Issue type : kbinfo ============================================================================= THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY. Copyright Microsoft Corporation 2002.