//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1996 - 1996. // // File: DSOBJECT.CXX // // Contents: DSObject support functions // // History: 01-Jul-96 MacM Created // //---------------------------------------------------------------------------- #include #pragma hdrstop #define NO_PROPAGATE #define ACTRL_SD_PROP_NAME L"nTSecurityDescriptor" #define ACTRL_EXT_RIGHTS_CONTAINER L"CN=Extended-Rights," #include #include #include extern "C" { #include #include #include #include #include } #define PSD_FROM_DS_PSD(psd) (PSECURITY_DESCRIPTOR)((PBYTE)psd + sizeof(ULONG)) #define BYTE_0_MASK 0xFF #define BYTE_3(Value) (UCHAR)( (Value) & BYTE_0_MASK) #define BYTE_2(Value) (UCHAR)( ((Value) >> 8) & BYTE_0_MASK) #define BYTE_1(Value) (UCHAR)( ((Value) >> 16) & BYTE_0_MASK) #define BYTE_0(Value) (UCHAR)( ((Value) >> 24) & BYTE_0_MASK) #define MartaPutUlong(Buffer, Value) { \ ((PBYTE)Buffer)[0] = BYTE_0(Value), \ ((PBYTE)Buffer)[1] = BYTE_1(Value), \ ((PBYTE)Buffer)[2] = BYTE_2(Value), \ ((PBYTE)Buffer)[3] = BYTE_3(Value); \ } DWORD ConvertStringAToStringW ( IN PSTR pszString, OUT PWSTR *ppwszString ) /*++ Routine Description: This routine will convert an ASCII string to a UNICODE string. The returned string buffer must be freed via a call to LocalFree Arguments: pszString - The string to convert ppwszString - Where the converted string is returned Return Value: ERROR_SUCCESS - Success ERROR_NOT_ENOUGH_MEMORY - A memory allocation failed --*/ { if(pszString == NULL) { *ppwszString = NULL; } else { ULONG cLen = strlen(pszString); *ppwszString = (PWSTR)AccAlloc(sizeof(WCHAR) * (mbstowcs(NULL, pszString, cLen + 1) + 1)); if(*ppwszString != NULL) { mbstowcs(*ppwszString, pszString, cLen + 1); } else { return(ERROR_NOT_ENOUGH_MEMORY); } } return(ERROR_SUCCESS); } DWORD ConvertStringWToStringA ( IN PWSTR pwszString, OUT PSTR *ppszString ) /*++ Routine Description: This routine will convert a UNICODE string to an ANSI string. The returned string buffer must be freed via a call to LocalFree Arguments: pwszString - The string to convert ppszString - Where the converted string is returned Return Value: ERROR_SUCCESS - Success ERROR_NOT_ENOUGH_MEMORY - A memory allocation failed --*/ { if(pwszString == NULL) { *ppszString = NULL; } else { ULONG cLen = wcslen(pwszString); *ppszString = (PSTR)AccAlloc(sizeof(CHAR) * (wcstombs(NULL, pwszString, cLen + 1) + 1)); if(*ppszString != NULL) { wcstombs(*ppszString, pwszString, cLen + 1); } else { return(ERROR_NOT_ENOUGH_MEMORY); } } return(ERROR_SUCCESS); } //+--------------------------------------------------------------------------- // // Function: DspSplitPath // // Synopsis: This function splits a path into the server portion and the // path portion. If the server portion doesn't exist, a NULL is // returned // // Arguments: [IN pwszObjectPath]-- The name of the object to be split // [OUT ppwszAllocatedServer] -- Where the server name is returned. // Must be freed via AccFree // [OUT ppwszReferencePath] -- Ptr within the input path that // contains the path portion. // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_PATH_NOT_FOUND-- The object was not reachable // ERROR_NOT_ENOUGH_MEMORY A memory allocation failed // // Notes: // //---------------------------------------------------------------------------- DWORD DspSplitPath(IN PWSTR pwszObjectPath, OUT PWSTR *ppwszAllocatedServer, OUT PWSTR *ppwszReferencePath) { DWORD dwErr = ERROR_SUCCESS; PWSTR Temp = NULL; ULONG Len = 0; if(IS_UNC_PATH(pwszObjectPath, wcslen(pwszObjectPath))) { Temp = wcschr(pwszObjectPath + 2, L'\\'); if (Temp == NULL) { Len = wcslen(pwszObjectPath); } else { Len = (ULONG)(Temp - pwszObjectPath); } *ppwszAllocatedServer = ( PWSTR )AccAlloc( ( Len + 1 ) * sizeof( WCHAR ) ); if(*ppwszAllocatedServer == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; } else { wcsncpy( *ppwszAllocatedServer, pwszObjectPath, Len ); *( *ppwszAllocatedServer + Len ) = UNICODE_NULL; } if(Temp != NULL) { *ppwszReferencePath = Temp + 1; } else { *ppwszReferencePath = NULL; } } else { *ppwszReferencePath = pwszObjectPath; *ppwszAllocatedServer = NULL; } return(dwErr); } //+--------------------------------------------------------------------------- // // Function: PingDSObjByNameRes // // Synopsis: "Pings" the specified DS object, to determine if it is // reachable or not // // REMOVE POST BETA - 1. Raid 107329 // // Arguments: [IN pObjectName] -- The name of the object // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_PATH_NOT_FOUND-- The object was not reachable // // Notes: // //---------------------------------------------------------------------------- DWORD PingDSObjByNameRes(IN PWSTR pwszDSObj, IN PDS_NAME_RESULTW pNameRes) { acDebugOut((DEB_TRACE, "in PingDSObjByNameRes\n")); DWORD dwErr; if(pNameRes->cItems == 0 || pNameRes->rItems[0].status != 0) { dwErr = ERROR_PATH_NOT_FOUND; } else { // // Now, we'll bind to the object, and then do the read // PLDAP pLDAP; dwErr = BindToDSObject(NULL, pNameRes->rItems[0].pDomain, &pLDAP); if(dwErr == ERROR_SUCCESS) { PLDAPMessage pMessage = NULL; PWSTR rgAttribs[2]; rgAttribs[0] = L"distinguishedName"; rgAttribs[1] = NULL; if(dwErr == ERROR_SUCCESS) { dwErr = ldap_search_s(pLDAP, (PWSTR)pwszDSObj, LDAP_SCOPE_BASE, L"(objectClass=*)", rgAttribs, 0, &pMessage); dwErr = LdapMapErrorToWin32( dwErr ); } if(dwErr == ERROR_SUCCESS) { LDAPMessage *pEntry = NULL; pEntry = ldap_first_entry(pLDAP, pMessage); if(pEntry == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Now, we'll have to get the values // PWSTR *ppwszValues = ldap_get_values(pLDAP, pEntry, rgAttribs[0]); if(ppwszValues == NULL) { if(pLDAP->ld_errno == LDAP_NO_SUCH_ATTRIBUTE ) { dwErr = ERROR_SUCCESS; } else { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } } else { ldap_value_free(ppwszValues); } } ldap_msgfree(pMessage); } } } acDebugOut((DEB_TRACE, "out PingDSObjByNameRes: %lu\n", dwErr)); return(dwErr); } //+--------------------------------------------------------------------------- // // Function: DspBindAndCrack // // Synopsis: Does a DsCrackName on the object // // Arguments: [IN pwszServer] -- Optional server name to bind to // [IN pwszDSObj] -- The DS object to bind to // [OUT pResults] -- The returned cracked name // // Returns: ERROR_SUCCESS -- The object is reachable // // Notes: // //---------------------------------------------------------------------------- DWORD DspBindAndCrack( IN PWSTR pwszServer, OPTIONAL IN PWSTR pwszDSObj, IN DWORD OptionalDsGetDcFlags, OUT PDS_NAME_RESULTW *pResults ) { return DspBindAndCrackEx( pwszServer, pwszDSObj, OptionalDsGetDcFlags, DS_FQDN_1779_NAME, pResults ); } //+--------------------------------------------------------------------------- // // Function: DspBindAndCrackEx // // Synopsis: Does a DsCrackName on the object // // Arguments: [IN pwszServer] -- Optional server name to bind to // [IN pwszDSObj] -- The DS object to bind to // [IN formatDesired] -- indicates the format of the returned name // [OUT pResults] -- The returned cracked name // // Returns: ERROR_SUCCESS -- The object is reachable // // Notes: // //---------------------------------------------------------------------------- DWORD DspBindAndCrackEx( IN PWSTR pwszServer, IN PWSTR pwszDSObj, IN DWORD OptionalDsGetDcFlags, IN DS_NAME_FORMAT formatDesired, OUT PDS_NAME_RESULTW *pResults ) { DWORD dwErr = ERROR_SUCCESS; HANDLE hDS = NULL; PDS_NAME_RESULTW pNameRes; PDOMAIN_CONTROLLER_INFOW pDCI = NULL; BOOL NamedServer = FALSE; // // The path we are given could be of the form \\\\servername\\path. If it is, it // is not necessary to do the DsGetDcName call. We'll just use the server name // we are given // if(pwszServer != NULL) { NamedServer = TRUE; } else { dwErr = DsGetDcNameW(NULL, NULL, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED | OptionalDsGetDcFlags, // DS_IP_REQUIRED &pDCI); if(dwErr == ERROR_SUCCESS) { pwszServer = pDCI[0].DomainControllerName; // pDCI[0].DomainControllerAddress; } } // // Do the bind and crack // if(dwErr == ERROR_SUCCESS) { dwErr = DsBindW(pwszServer, NULL, &hDS); if(dwErr == ERROR_SUCCESS) { dwErr = DsCrackNamesW(hDS, DS_NAME_NO_FLAGS, DS_UNKNOWN_NAME, formatDesired, 1, &pwszDSObj, &pNameRes); if (dwErr == ERROR_SUCCESS) { if(pNameRes->cItems != 0 && pNameRes->rItems[0].status == DS_NAME_ERROR_DOMAIN_ONLY && NamedServer == FALSE ) { NetApiBufferFree(pDCI); pDCI = NULL; dwErr = DsGetDcNameW(NULL, pNameRes->rItems[0].pDomain, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED | OptionalDsGetDcFlags, // DS_IP_REQUIRED | &pDCI); if(dwErr == ERROR_SUCCESS) { DsUnBindW(&hDS); hDS = NULL; dwErr = DsBindW(pDCI[0].DomainControllerName, // DomainControllerAddress, NULL, &hDS); if(dwErr == ERROR_SUCCESS) { dwErr = DsCrackNamesW(hDS, DS_NAME_NO_FLAGS, DS_UNKNOWN_NAME, formatDesired, 1, &pwszDSObj, &pNameRes); } } } // // If this is a case where we don't have a named server, handle // the case where an object was created on one Dc, but we've just // bound to a second one // // if (dwErr == ERROR_SUCCESS && formatDesired == DS_FQDN_1779_NAME && NamedServer == FALSE ) { dwErr = PingDSObjByNameRes( pwszDSObj,pNameRes ); if(dwErr != ERROR_SUCCESS) { DsFreeNameResultW(pNameRes); } if(dwErr == ERROR_PATH_NOT_FOUND && OptionalDsGetDcFlags == 0) { dwErr = DspBindAndCrackEx( pDCI[0].DomainControllerName, //DomainControllerAddress, pwszDSObj, DS_WRITABLE_REQUIRED, formatDesired, &pNameRes ); } } *pResults = pNameRes; } if(hDS != NULL) { DsUnBindW(&hDS); } } } if(pDCI != NULL) { NetApiBufferFree(pDCI); } return( dwErr ); } //+--------------------------------------------------------------------------- // // Function: BindToDSObject // // Synopsis: Binds to a DS object // // Arguments: [IN pwszServer] -- OPTIONAL. If specified, this is the name // of the server to bind to // [IN pwszDSObj] -- The DS object to bind to // [OUT ppLDAP] -- The returned LDAP handle // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_PATH_NOT_FOUND-- The object was not reachable // // Notes: The returned LDAP handle must be closed via UnbindFromDSObject // //---------------------------------------------------------------------------- DWORD BindToDSObject(IN PWSTR pwszServer, OPTIONAL IN LPWSTR pwszDSObj, OUT PLDAP *ppLDAP) { acDebugOut((DEB_TRACE, "in BindToDSObject\n")); PDOMAIN_CONTROLLER_INFOW pDCI = NULL; DWORD dwErr = ERROR_SUCCESS; // // The path we are given could be of the form \\\\servername\\path. If it is, it // is not necessary to do the DsGetDcName call. We'll just use the server name // we are given // // Change: in order to use mutual authentication, A DNS format domain name must // be passed into ldap_open/ldap_init. So even a servername is passed in, it's // necessary to call DsGetDcNameW to get the DNS format domain name. // Since we asked for DIRECTORY_SERVICE_REQUIRED, this call won't talk to any // NT4 domain and the DNS name should always be returned. If it fails to get the // DNS name, we will fail this function - by design. // dwErr = DsGetDcNameW(pwszServer, NULL, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME, &pDCI); if(dwErr == ERROR_SUCCESS) { *ppLDAP = ldap_open(pDCI->DomainName, LDAP_PORT); if(*ppLDAP == NULL) { dwErr = ERROR_PATH_NOT_FOUND; } else { // // Do a bind... // dwErr = ldap_bind_s(*ppLDAP, NULL, NULL, LDAP_AUTH_SSPI); } } if(pDCI != NULL) { NetApiBufferFree(pDCI); } acDebugOut((DEB_TRACE, "out BindToDSObject: %lu\n", dwErr)); return(dwErr); } //+--------------------------------------------------------------------------- // // Function: UnBindFromDSObject // // Synopsis: Closes a binding to a DS object // // Arguments: [IN ppLDAP] -- The LDAP connection to close // // Returns: ERROR_SUCCESS -- The object is reachable // // Notes: // //---------------------------------------------------------------------------- DWORD UnBindFromDSObject(OUT PLDAP *ppLDAP) { acDebugOut((DEB_TRACE, "in UnBindFromDSObject\n")); DWORD dwErr = ERROR_SUCCESS; if(*ppLDAP != NULL) { ldap_unbind(*ppLDAP); *ppLDAP = NULL; } acDebugOut((DEB_TRACE, "out UnBindFromDSObject: %lu\n", dwErr)); return(dwErr); } //+--------------------------------------------------------------------------- // // Function: ReadDSObjSecDesc // // Synopsis: Reads the security descriptor from the specied object via // the open ldap connection // // Arguments: [IN pLDAP] -- The open LDAP connection // [IN SeInfo] -- Parts of the security descriptor to // read. // [IN pwszDSObj] -- The DSObject to get the security // descriptor for // [OUT ppSD] -- Where the security descriptor is // returned // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_NOT_ENOUGH_MEMORY A memory allocation failed // // Notes: The returned security descriptor must be freed with LocalFree // //---------------------------------------------------------------------------- DWORD ReadDSObjSecDesc(IN PLDAP pLDAP, IN PWSTR pwszObject, IN SECURITY_INFORMATION SeInfo, OUT PSECURITY_DESCRIPTOR *ppSD) { DWORD dwErr = ERROR_SUCCESS; PLDAPMessage pMessage = NULL; PWSTR rgAttribs[2]; BYTE berValue[8]; // // JohnsonA The BER encoding is current hardcoded. Change this to use // AndyHe's BER_printf package once it's done. // berValue[0] = 0x30; berValue[1] = 0x03; berValue[2] = 0x02; berValue[3] = 0x01; berValue[4] = (BYTE)((ULONG)SeInfo & 0xF); LDAPControl SeInfoControl = { LDAP_SERVER_SD_FLAGS_OID_W, { 5, (PCHAR)berValue }, TRUE }; PLDAPControl ServerControls[2] = { &SeInfoControl, NULL }; rgAttribs[0] = ACTRL_SD_PROP_NAME; rgAttribs[1] = NULL; if(dwErr == ERROR_SUCCESS) { dwErr = ldap_search_ext_s(pLDAP, pwszObject, LDAP_SCOPE_BASE, L"(objectClass=*)", rgAttribs, 0, (PLDAPControl *)&ServerControls, NULL, NULL, 10000, &pMessage); dwErr = LdapMapErrorToWin32( dwErr ); } if(dwErr == ERROR_SUCCESS) { LDAPMessage *pEntry = NULL; pEntry = ldap_first_entry(pLDAP, pMessage); if(pEntry == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Now, we'll have to get the values // PWSTR *ppwszValues = ldap_get_values(pLDAP, pEntry, rgAttribs[0]); if(ppwszValues == NULL) { if(pLDAP->ld_errno == LDAP_NO_SUCH_ATTRIBUTE) { dwErr = ERROR_ACCESS_DENIED; } else { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } } else { PLDAP_BERVAL *pSize = ldap_get_values_len(pLDAP, pMessage, rgAttribs[0]); if(pSize == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Allocate the security descriptor to return // *ppSD = (PSECURITY_DESCRIPTOR)AccAlloc((*pSize)->bv_len); if(*ppSD == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; } else { memcpy(*ppSD, (PBYTE)(*pSize)->bv_val, (*pSize)->bv_len); } ldap_value_free_len(pSize); } ldap_value_free(ppwszValues); } } ldap_msgfree(pMessage); } return(dwErr); } //+--------------------------------------------------------------------------- // // Function: GetSDForDSObj // // Synopsis: Gets a security descriptor from a DS object // // Arguments: [IN pwszDSObj] -- The DSObject to get the security // descriptor for // [OUT ppSD] -- Where the security descriptor is // returned // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_NOT_ENOUGH_MEMORY A memory allocation failed // ERROR_INVALID_PARAMETER The object name that was given was in // a bad format (not \\x\y) // // Notes: The returned security descriptor must be freed with LocalFree // //---------------------------------------------------------------------------- DWORD GetSDForDSObj(IN LPWSTR pwszDSObj, IN SECURITY_INFORMATION SeInfo, OUT PSECURITY_DESCRIPTOR *ppSD) { acDebugOut((DEB_TRACE, "in GetSDForDSObj\n")); DWORD dwErr = ERROR_SUCCESS; PWSTR pwszServer = NULL, pwszPath = NULL; dwErr = DspSplitPath(pwszDSObj, &pwszServer, &pwszPath); if(dwErr == ERROR_SUCCESS) { // // Convert the name into attributed format // PDS_NAME_RESULTW pNameRes; dwErr = DspBindAndCrack( pwszServer, pwszPath, 0, &pNameRes ); if(dwErr == ERROR_SUCCESS) { if(pNameRes->cItems == 0 || pNameRes->rItems[0].status != 0) { dwErr = ERROR_PATH_NOT_FOUND; } else { // // Now, we'll bind to the object, and then do the read // PLDAP pLDAP; dwErr = BindToDSObject(pwszServer, pNameRes->rItems[0].pDomain, &pLDAP); if(dwErr == ERROR_SUCCESS) { // // Now, we'll do the read... // dwErr = ReadDSObjSecDesc(pLDAP, pNameRes->rItems[0].pName, SeInfo, ppSD); UnBindFromDSObject(&pLDAP); } } DsFreeNameResultW(pNameRes); } AccFree(pwszServer); } acDebugOut((DEB_TRACE, "Out GetSDForDSObj: %lu\n", dwErr)); return(dwErr); } //+--------------------------------------------------------------------------- // // Function: ReadDSObjPropertyRights // // Synopsis: Reads the specified property rights from the named DS object // // Arguments: [IN pwszDSObj] -- The DSObject to get the security // descriptor for // [IN pRightsList] -- The rights information to get // [IN cRights] -- Number of items in the rights list // [IN AccessList] -- The access list to initialize // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_INVALID_PARAMETER A NULL parameter was given // // Notes: // //---------------------------------------------------------------------------- DWORD ReadDSObjPropertyRights(IN LPWSTR pwszDSObj, IN PACTRL_RIGHTS_INFO pRightsList, IN ULONG cRights, IN CAccessList& AccessList) { acDebugOut((DEB_TRACE, "in ReadDSObjPropertyRights\n")); DWORD dwErr = ERROR_SUCCESS; if(pwszDSObj == NULL || pRightsList == NULL) { return(ERROR_INVALID_PARAMETER); } else { // // Build the security info structure we will need // SECURITY_INFORMATION SeInfo = 0; for(ULONG i = 0; i < cRights; i++) { SeInfo |= pRightsList[i].SeInfo; } PSECURITY_DESCRIPTOR pSD; dwErr = GetSDForDSObj(pwszDSObj, SeInfo, &pSD); if(dwErr == ERROR_SUCCESS) { // // Now, we'll simply add the appropriate property based entries // to our access list. // for(ULONG iIndex = 0; iIndex < cRights && dwErr == ERROR_SUCCESS; iIndex++) { dwErr = AccessList.AddSD(pSD, pRightsList[iIndex].SeInfo, pRightsList[iIndex].pwszProperty, FALSE); } AccFree(pSD); } } acDebugOut((DEB_TRACE, "out ReadDSObjPropertyRights: %lu\n", dwErr)); return(dwErr); } //+--------------------------------------------------------------------------- // // Function: ReadAllDSObjPropertyRights // // Synopsis: Reads the all the property rights from the named DS object // // Arguments: [IN pwszDSObj] -- The DSObject to get the security // descriptor for // [IN pRightsList] -- The rights information to get // [IN cRights] -- Number of items in the rights list // [IN AccessList] -- The access list to initialize // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_INVALID_PARAMETER A NULL parameter was given // // Notes: // //---------------------------------------------------------------------------- DWORD ReadAllDSObjPropertyRights(IN LPWSTR pwszDSObj, IN PACTRL_RIGHTS_INFO pRightsList, IN ULONG cRights, IN CAccessList& AccessList) { DWORD dwErr = ERROR_SUCCESS; if(pwszDSObj == NULL || pRightsList == NULL) { return(ERROR_INVALID_PARAMETER); } else { // // Build the security info structure we will need // SECURITY_INFORMATION SeInfo = 0; for(ULONG i = 0; i < cRights; i++) { SeInfo |= pRightsList[i].SeInfo; } PSECURITY_DESCRIPTOR pSD; dwErr = GetSDForDSObj(pwszDSObj, SeInfo, &pSD); if(dwErr == ERROR_SUCCESS) { // // Now, we'll simply add it to our access list. We'll ignore // any rights info after the first one. // dwErr = AccessList.AddSD(pSD, pRightsList[0].SeInfo, NULL, TRUE); AccFree(pSD); } } return(dwErr); } //+--------------------------------------------------------------------------- // // Function: SetDSObjSecurityInfo // // Synopsis: Sets the security descriptor on the DS object // // Arguments: [IN pwszDSObj] -- The DSObject to get the security // descriptor for // [IN SeInfo] -- Security Infofor the security // descriptor // [IN pwszProperty] -- Object property to set the access on // [IN pSD] -- Security descriptor to set // [IN cSDSize] -- Size of the security descriptor // [IN pfStopFlag] -- The stop flag to monitor // [IN pcProcessed] -- Where to increment the count of // processsed items // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_INVALID_PARAMETER A NULL parameter was given // // Notes: // //---------------------------------------------------------------------------- DWORD SetDSObjSecurityInfo(IN LPWSTR pwszDSObj, IN SECURITY_INFORMATION SeInfo, IN PWSTR pwszProperty, IN PSECURITY_DESCRIPTOR pSD, IN ULONG cSDSize, IN PULONG pfStopFlag, IN PULONG pcProcessed) { DWORD dwErr = ERROR_SUCCESS; PWSTR pwszServer = NULL, pwszPath = NULL; dwErr = DspSplitPath(pwszDSObj, &pwszServer, &pwszPath); if(dwErr == ERROR_SUCCESS) { // // Convert the name into attributed format // PDS_NAME_RESULTW pNameRes; dwErr = DspBindAndCrack( pwszServer, pwszPath, 0, &pNameRes ); if(dwErr == ERROR_SUCCESS) { if(pNameRes->cItems == 0 || pNameRes->rItems[0].status != 0) { dwErr = ERROR_PATH_NOT_FOUND; } else { // // Convert our name to ascii // PLDAP pLDAP; dwErr = BindToDSObject(pwszServer, pNameRes->rItems[0].pDomain, &pLDAP); if(dwErr == ERROR_SUCCESS) { #ifdef NO_PROPAGATE dwErr = StampSD(pNameRes->rItems[0].pName, cSDSize, SeInfo, pSD, pLDAP); #else dwErr = PropagateDSRightsDeep(NULL, pSD, SeInfo, pNameRes->rItems[0].pName, pLDAP, pcProcessed, pfStopFlag); #endif UnBindFromDSObject(&pLDAP); } } DsFreeNameResultW(pNameRes); } AccFree(pwszServer); } return(dwErr); } //+--------------------------------------------------------------------------- // // Function: PingDSObj // // Synopsis: "Pings" the specified DS object, to determine if it is // reachable or not // // Arguments: [IN pObjectName] -- The name of the object // // Returns: ERROR_SUCCESS -- The object is reachable // ERROR_PATH_NOT_FOUND-- The object was not reachable // // Notes: // //---------------------------------------------------------------------------- DWORD PingDSObj(IN LPCWSTR pwszDSObj) { acDebugOut((DEB_TRACE, "in PingDSObj\n")); DWORD dwErr; PWSTR pwszServer = NULL, pwszPath = NULL; dwErr = DspSplitPath((PWSTR)pwszDSObj, &pwszServer, &pwszPath); if(dwErr == ERROR_SUCCESS) { // // Convert the name into attributed format // PDS_NAME_RESULTW pNameRes; dwErr = DspBindAndCrack(pwszServer, pwszPath, 0, &pNameRes); if(dwErr == ERROR_SUCCESS) { if(pNameRes->cItems == 0 || pNameRes->rItems[0].status != 0) { dwErr = ERROR_PATH_NOT_FOUND; } else { // // Now, we'll bind to the object, and then do the read // PLDAP pLDAP; dwErr = BindToDSObject(pwszServer, pNameRes->rItems[0].pDomain, &pLDAP); if(dwErr == ERROR_SUCCESS) { PLDAPMessage pMessage = NULL; PWSTR rgAttribs[2]; rgAttribs[0] = L"distinguishedName"; rgAttribs[1] = NULL; if(dwErr == ERROR_SUCCESS) { dwErr = ldap_search_s(pLDAP, pwszPath, LDAP_SCOPE_BASE, L"(objectClass=*)", rgAttribs, 0, &pMessage); dwErr = LdapMapErrorToWin32( dwErr ); } if(dwErr == ERROR_SUCCESS) { LDAPMessage *pEntry = NULL; pEntry = ldap_first_entry(pLDAP, pMessage); if(pEntry == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Now, we'll have to get the values // PWSTR *ppwszValues = ldap_get_values(pLDAP, pEntry, rgAttribs[0]); if(ppwszValues == NULL) { if(pLDAP->ld_errno == LDAP_NO_SUCH_ATTRIBUTE ) { dwErr = ERROR_SUCCESS; } else { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } } else { ldap_value_free(ppwszValues); } } ldap_msgfree(pMessage); } } } DsFreeNameResultW(pNameRes); } AccFree(pwszServer); } acDebugOut((DEB_TRACE, "out PingDSObj: %lu\n", dwErr)); return(dwErr); } DWORD Nt4NameToNt5Name(IN PWSTR pwszName, IN PWSTR pwszDomain, OUT PWSTR *ppwszNt5Name) { DWORD dwErr = ERROR_SUCCESS; WCHAR wszFullName[MAX_PATH + 1]; LPWSTR pwszFullName; ULONG cLen = wcslen(pwszName) + 1; if(pwszDomain != NULL) { cLen += wcslen(pwszDomain) + 1; } if(cLen < MAX_PATH + 1) { pwszFullName = wszFullName; } else { pwszFullName = (PWSTR)AccAlloc(cLen * sizeof(WCHAR)); if(pwszFullName == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; } } if(dwErr == ERROR_SUCCESS) { if(pwszDomain != NULL) { wcscpy(pwszFullName, pwszDomain); wcscat(pwszFullName, L"\\"); } else { *pwszFullName = L'\0'; } wcscat(pwszFullName, pwszName); } // // Now, for the crack name... // if(dwErr == ERROR_SUCCESS) { PDS_NAME_RESULTW pNameRes; dwErr = DspBindAndCrack(NULL, wszFullName, 0, &pNameRes ); if(dwErr == ERROR_SUCCESS) { if(pNameRes->cItems == 0 || pNameRes->rItems[0].status != 0) { dwErr = ERROR_PATH_NOT_FOUND; } else { ACC_ALLOC_AND_COPY_STRINGW(pNameRes->rItems[0].pName, *ppwszNt5Name, dwErr); } } else { if(dwErr == MAPI_E_LOGON_FAILED) { dwErr = ERROR_LOGON_FAILURE; } } DsFreeNameResultW(pNameRes); } // // See if we need to free our buffer // if(pwszFullName != wszFullName) { AccFree(pwszFullName); } return(dwErr); } #define CLEANUP_ON_INTERRUPT(pstopflag) \ if(*pstopflag != 0) \ { \ goto DSCleanup; \ } //+--------------------------------------------------------------------------- // // Function: PropagateDSRightsDeep, recursive // // Synopsis: Does a deep propagation of the access. // // Arguments: [IN pParentSD] -- The current parent sd // [IN SeInfo] -- What is being written // [IN pwszFile] -- Parent file path // [IN pcProcessed] -- Where the number processed is // returned. // [IN pfStopFlag] -- Stop flag to monitor // // Returns: ERROR_SUCCESS -- Success // ERROR_NOT_ENOUGH_MEMORY -- A memory allocation failed // //---------------------------------------------------------------------------- DWORD PropagateDSRightsDeep(IN PSECURITY_DESCRIPTOR pParentSD, IN PSECURITY_DESCRIPTOR pChildSD, IN SECURITY_INFORMATION SeInfo, IN PWSTR pwszDSObject, IN PLDAP pLDAP, IN PULONG pcProcessed, IN PULONG pfStopFlag) { acDebugOut((DEB_TRACE, "in PropagateDSRightsDeep\n")); DWORD dwErr = ERROR_SUCCESS; BOOL fFreeChildSD = FALSE; acDebugOut((DEB_TRACE_PROP, "Processing %ws\n", pwszDSObject)); // // If our security descriptor is already in DS form, then we'll have // to adjust for that // if(pChildSD != NULL) { PULONG pSE = (PULONG)(pChildSD); if(*pSE == SeInfo) { pChildSD = (PSECURITY_DESCRIPTOR)((PBYTE)pChildSD + sizeof(ULONG)); } } else { dwErr = ReadDSObjSecDesc(pLDAP, pwszDSObject, SeInfo, &pChildSD); if(dwErr == ERROR_SUCCESS) { fFreeChildSD = TRUE; } else { return(dwErr); } } // // Ok, we'll convert our path to a narrow string, and then we'll enumerate // all of the children // PSECURITY_DESCRIPTOR pNewSD = NULL; PLDAPMessage pMessage = NULL; // // First, we'll create the new SD... // HANDLE hProcessToken = NULL; GENERIC_MAPPING GenMap; GenMap.GenericRead = GENERIC_READ_MAPPING; GenMap.GenericWrite = GENERIC_WRITE_MAPPING; GenMap.GenericExecute = GENERIC_EXECUTE_MAPPING; GenMap.GenericAll = GENERIC_ALL_MAPPING; dwErr = GetCurrentToken( &hProcessToken ); if(dwErr == ERROR_SUCCESS) { #ifdef DBG DebugDumpSD("CPOSE ParentSD", pParentSD); DebugDumpSD("CPOSE ChildSD", pChildSD); #endif if(CreatePrivateObjectSecurityEx(pParentSD, pChildSD, &pNewSD, NULL, TRUE, SEF_DACL_AUTO_INHERIT | SEF_SACL_AUTO_INHERIT, hProcessToken, &GenMap) == FALSE) { dwErr = GetLastError(); } else { #ifdef DBG DebugDumpSD("CPOSE NewSD", pNewSD); #endif // // Stamp the SD on the object... This means that we'll have // to allocate a new security descriptor that is 4 bytes // bigger than what we need, and set our SeInfo // PSECURITY_DESCRIPTOR pSetSD = NULL; ULONG cNewSDSize = 0; if(RtlpAreControlBitsSet((PISECURITY_DESCRIPTOR)pChildSD, SE_SELF_RELATIVE)) { cNewSDSize = RtlLengthSecurityDescriptor(pNewSD); ASSERT(cNewSDSize != 0); } else { MakeSelfRelativeSD(pNewSD, NULL, &cNewSDSize); ASSERT(GetLastError() == ERROR_INSUFFICIENT_BUFFER); } cNewSDSize += sizeof(ULONG); pSetSD = (PSECURITY_DESCRIPTOR)AccAlloc(cNewSDSize); if(pSetSD == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; } else { pSetSD = (PSECURITY_DESCRIPTOR) ((PBYTE)pSetSD + sizeof(ULONG)); if(RtlpAreControlBitsSet((PISECURITY_DESCRIPTOR)pChildSD, SE_SELF_RELATIVE)) { memcpy(pSetSD, pNewSD, cNewSDSize - sizeof(ULONG)); } else { if(MakeSelfRelativeSD(pNewSD, pSetSD, &cNewSDSize) == FALSE) { dwErr = GetLastError(); } } PULONG pSE = (PULONG)((PBYTE)pSetSD - sizeof(ULONG)); *pSE = SeInfo; // // We need to pass in the security_information // pSetSD = (PSECURITY_DESCRIPTOR)pSE; // // Now, do the write // dwErr = StampSD(pwszDSObject, cNewSDSize, SeInfo, pSetSD, pLDAP); AccFree(pSetSD); } } } CLEANUP_ON_INTERRUPT(pfStopFlag); if(dwErr == ERROR_SUCCESS) { PWSTR rgAttribs[2]; WCHAR wszAttrib[]=L"distinguishedName"; // // Do the search... // rgAttribs[0] = wszAttrib; rgAttribs[1] = NULL; if(dwErr == ERROR_SUCCESS) { dwErr = ldap_search_s(pLDAP, pwszDSObject, LDAP_SCOPE_ONELEVEL, L"(objectClass=*)", rgAttribs, 0, &pMessage); dwErr = LdapMapErrorToWin32( dwErr ); } if(dwErr == ERROR_SUCCESS) { ULONG cChildren = ldap_count_entries(pLDAP, pMessage); acDebugOut((DEB_TRACE_PROP, "%ws has %lu children\n", pwszDSObject, cChildren)); LDAPMessage *pEntry = ldap_first_entry(pLDAP, pMessage); for(ULONG i = 0; i < cChildren; i++) { if(pEntry == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); break; } // // Now, we'll have to get the values // CLEANUP_ON_INTERRUPT(pfStopFlag); PWSTR *ppwszValues = ldap_get_values(pLDAP, pEntry, rgAttribs[0]); if(ppwszValues == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Go ahead and propagate to the child // acDebugOut((DEB_TRACE_PROP, "Child %ws of %ws [%lu]\n", ppwszValues[0], pwszDSObject, i)); dwErr = PropagateDSRightsDeep(pNewSD, NULL, SeInfo, ppwszValues[0], pLDAP, pcProcessed, pfStopFlag); ldap_value_free(ppwszValues); CLEANUP_ON_INTERRUPT(pfStopFlag); } pEntry = ldap_next_entry(pLDAP, pEntry); } } } DSCleanup: ldap_msgfree(pMessage); DestroyPrivateObjectSecurity(&pNewSD); if(fFreeChildSD == TRUE) { AccFree(pChildSD); } acDebugOut((DEB_TRACE, "Out PropagateDSRightsDeep: %ld\n", dwErr)); return(dwErr); } //+--------------------------------------------------------------------------- // // Function: StampSD // // Synopsis: Actually stamps the security descriptor on the object. // // Arguments: [IN pwszObject] -- The object to stamp the SD on // [IN cSDSize] -- The size of the security descriptor // [IN SeInfo] -- SecurityInformation about the security // descriptor // [IN pSD] -- The SD to stamp // [IN pLDAP] -- The LDAP connection to use // // Returns: ERROR_SUCCESS -- Success // ERROR_NOT_ENOUGH_MEMORY -- A memory allocation failed // //---------------------------------------------------------------------------- DWORD StampSD(IN PWSTR pwszObject, IN ULONG cSDSize, IN SECURITY_INFORMATION SeInfo, IN PSECURITY_DESCRIPTOR pSD, IN PLDAP pLDAP) { DWORD dwErr = ERROR_SUCCESS; acDebugOut((DEB_TRACE_PROP, "Stamping %ws\n", pwszObject)); // // Now, we'll do the write. The security descriptor // we got passed in better not be in the old Ds format, // where the leading 4 bytes are the SECURITY_INFORMATION, which we'll skip // and replace with control information // ASSERT(*(PULONG)pSD > 0xF ); PLDAPMod rgMods[2]; PLDAP_BERVAL pBVals[2]; LDAPMod Mod; LDAP_BERVAL BVal; BYTE ControlBuffer[ 5 ]; LDAPControl SeInfoControl = { LDAP_SERVER_SD_FLAGS_OID_W, { 5, (PCHAR) &ControlBuffer }, TRUE }; // // !!! Hardcoded for now. Use Andyhe's BER_printf once it's done. // ControlBuffer[0] = 0x30; ControlBuffer[1] = 0x3; ControlBuffer[2] = 0x02; // Denotes an integer; ControlBuffer[3] = 0x01; // Size ControlBuffer[4] = (BYTE)((ULONG)SeInfo & 0xF); PLDAPControl ServerControls[2] = { &SeInfoControl, NULL }; ASSERT(IsValidSecurityDescriptor( pSD ) ); rgMods[0] = &Mod; rgMods[1] = NULL; pBVals[0] = &BVal; pBVals[1] = NULL; BVal.bv_len = cSDSize; BVal.bv_val = (PCHAR)pSD; Mod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES; Mod.mod_type = ACTRL_SD_PROP_NAME; Mod.mod_values = (PWSTR *)pBVals; // // Now, we'll do the write... // dwErr = ldap_modify_ext_s(pLDAP, pwszObject, rgMods, (PLDAPControl *)&ServerControls, NULL); dwErr = LdapMapErrorToWin32(dwErr); #if DBG PACL pAcl = RtlpDaclAddrSecurityDescriptor((PISECURITY_DESCRIPTOR)pSD); ACL_SIZE_INFORMATION AclSize; ACL_REVISION_INFORMATION AclRev; PKNOWN_ACE pAce; PSID pSid; DWORD iIndex; DWORD GuidPart; DWORD OldInfoLevel; // // Now, dump all of the aces // if(pAcl) { pAce = (PKNOWN_ACE)FirstAce(pAcl); for(iIndex = 0; iIndex < pAcl->AceCount; iIndex++) { // // If it's an object ace, dump the guids // if(IsObjectAceType(pAce)) { OldInfoLevel = acInfoLevel; // acInfoLevel |= DEB_TRACE_SD; DebugDumpGuid("\t\t\tObjectId", RtlObjectAceObjectType(pAce)); GuidPart = (ULONG)((ULONG_PTR)RtlObjectAceObjectType(pAce)); ASSERT(GuidPart != 0x2bfff20); acInfoLevel = OldInfoLevel; } pAce = (PKNOWN_ACE)NextAce(pAce); } } #endif return(dwErr); } //+--------------------------------------------------------------------------- // // Function: AccDsReadSchemaInfo // // Synopsis: Reads the schema object/property info. // // Arguments: [IN pLDAP] -- LDAP connection to use // [OUT pcClasses] -- Where the count of class info // is returned // [OUT pppwszClasses] -- Where the list of classes info // is returned. Freed with // ldap_value_free // [OUT pcAttributes] -- Where the count of property infos // is returned // [OUT pppwszAttributes] -- Where the list of property infos // is returned. Freed with // ldap_value_free // // Returns: ERROR_SUCCESS -- Success // //---------------------------------------------------------------------------- DWORD AccDsReadSchemaInfo (IN PLDAP pLDAP, OUT PULONG pcClasses, OUT PWSTR **pppwszClasses, OUT PULONG pcAttributes, OUT PWSTR **pppwszAttributes) { DWORD dwErr = ERROR_SUCCESS; PWSTR *ppwszValues = NULL; PWSTR rgwszAttribs[3]; PDS_NAME_RESULTW pNameRes = NULL; LDAPMessage *pMessage, *pEntry; *pcClasses = 0; *pcAttributes = 0; *pppwszAttributes = NULL; *pppwszClasses = NULL; // // Get the subschema path // if(dwErr == ERROR_SUCCESS) { rgwszAttribs[0] = L"subschemaSubentry"; rgwszAttribs[1] = NULL; dwErr = ldap_search_s(pLDAP, NULL, LDAP_SCOPE_BASE, L"(objectClass=*)", rgwszAttribs, 0, &pMessage); if(dwErr == ERROR_SUCCESS) { pEntry = ldap_first_entry(pLDAP, pMessage); if(pEntry == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Now, we'll have to get the values // ppwszValues = ldap_get_values(pLDAP, pEntry, rgwszAttribs[0]); ldap_msgfree(pMessage); if(ppwszValues == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { rgwszAttribs[0] = L"extendedClassInfo"; rgwszAttribs[1] = L"extendedAttributeInfo"; rgwszAttribs[2] = NULL; dwErr = ldap_search_s(pLDAP, ppwszValues[0], LDAP_SCOPE_BASE, L"(objectClass=*)", rgwszAttribs, 0, &pMessage); ldap_value_free(ppwszValues); if(dwErr == ERROR_SUCCESS) { ldap_count_entries( pLDAP, pMessage ); pEntry = ldap_first_entry(pLDAP, pMessage); if(pEntry == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Now, we'll have to get the values // *pppwszClasses = ldap_get_values(pLDAP, pEntry, rgwszAttribs[0]); if(*pppwszClasses == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { *pcClasses = ldap_count_values(*pppwszClasses); *pppwszAttributes = ldap_get_values(pLDAP, pEntry, rgwszAttribs[1]); if(*pppwszAttributes == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); ldap_value_free(*pppwszClasses); } else { *pcAttributes = ldap_count_values(*pppwszAttributes); } } } } else { dwErr = LdapMapErrorToWin32( dwErr ); } ldap_msgfree(pMessage); } } } else { dwErr = LdapMapErrorToWin32( dwErr ); } } return(dwErr); } //+--------------------------------------------------------------------------- // // Function: AccDsReadExtendedRights // // Synopsis: Reads the list of extended rights from the schema // // Arguments: [IN pLDAP] -- LDAP connection to use // [OUT pcItems] -- Where the count of items // is returned // [OUT ppwszNames] -- Where the list of Names is // returned. // [OUT ppwszGuids] -- Where the list of guids is // returned. // // Notes: Freed via AccDsFreeExtendedRights // // Returns: ERROR_SUCCESS -- Success // //---------------------------------------------------------------------------- DWORD AccDsReadExtendedRights(IN PLDAP pLDAP, OUT PULONG pcItems, OUT PWSTR **pppwszNames, OUT PWSTR **pppwszGuids) { DWORD dwErr = ERROR_SUCCESS; PWSTR *ppwszValues = NULL; PWSTR rgwszAttribs[3]; PWSTR pwszERContainer = NULL; PDS_NAME_RESULTW pNameRes = NULL; LDAPMessage *pMessage, *pEntry; ULONG cEntries = 0, i; *pcItems = 0; *pppwszNames = NULL; *pppwszGuids = NULL; // // Get the subschema path // if(dwErr == ERROR_SUCCESS) { rgwszAttribs[0] = L"configurationNamingContext"; rgwszAttribs[1] = NULL; dwErr = ldap_search_s(pLDAP, NULL, LDAP_SCOPE_BASE, L"(objectClass=*)", rgwszAttribs, 0, &pMessage); if(dwErr == ERROR_SUCCESS) { pEntry = ldap_first_entry(pLDAP, pMessage); if(pEntry == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Now, we'll have to get the values // ppwszValues = ldap_get_values(pLDAP, pEntry, rgwszAttribs[0]); ldap_msgfree(pMessage); if(ppwszValues == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { pwszERContainer = (PWSTR)AccAlloc((wcslen(ppwszValues[0]) * sizeof(WCHAR)) + sizeof(ACTRL_EXT_RIGHTS_CONTAINER)); if(pwszERContainer == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; } else { wcscpy(pwszERContainer, ACTRL_EXT_RIGHTS_CONTAINER); wcscat(pwszERContainer, ppwszValues[0]); rgwszAttribs[0] = L"displayName"; rgwszAttribs[1] = L"rightsGuid"; rgwszAttribs[2] = NULL; // // Read the control access rights // dwErr = ldap_search_s(pLDAP, pwszERContainer, LDAP_SCOPE_ONELEVEL, L"(objectClass=controlAccessRight)", rgwszAttribs, 0, &pMessage); dwErr = LdapMapErrorToWin32( dwErr ); AccFree(pwszERContainer); } ldap_value_free(ppwszValues); if(dwErr == ERROR_SUCCESS) { cEntries = ldap_count_entries( pLDAP, pMessage ); *pppwszNames = (PWSTR *)AccAlloc( sizeof( PWSTR ) * cEntries ); if(*pppwszNames == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; } else { *pppwszGuids = (PWSTR *)AccAlloc( sizeof( PWSTR ) * cEntries ); if(*pppwszGuids == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; AccFree(*pppwszNames); *pppwszNames = NULL; } } if(dwErr == ERROR_SUCCESS) { pEntry = ldap_first_entry(pLDAP, pMessage); if(pEntry == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { for(i = 0; i < cEntries && dwErr == ERROR_SUCCESS; i++) { ppwszValues = ldap_get_values(pLDAP, pEntry, rgwszAttribs[0]); if(ppwszValues == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { // // Now, we'll have to get the values // ACC_ALLOC_AND_COPY_STRINGW(ppwszValues[0], (*pppwszNames)[i], dwErr); ldap_value_free(ppwszValues); if(dwErr == ERROR_SUCCESS) { ppwszValues = ldap_get_values(pLDAP, pEntry, rgwszAttribs[1]); if(ppwszValues == NULL) { dwErr = LdapMapErrorToWin32( pLDAP->ld_errno ); } else { ACC_ALLOC_AND_COPY_STRINGW(ppwszValues[0], (*pppwszGuids)[i], dwErr); ldap_value_free(ppwszValues); } } } pEntry = ldap_next_entry( pLDAP, pEntry ); } if(dwErr != ERROR_SUCCESS) { AccDsFreeExtendedRights(i, *pppwszNames, *pppwszGuids); } } } } if(dwErr == ERROR_SUCCESS) { *pcItems = cEntries; } ldap_msgfree(pMessage); } } } else { dwErr = LdapMapErrorToWin32( dwErr ); } } return(dwErr) ; } VOID AccDsFreeExtendedRights(IN ULONG cItems, IN PWSTR *ppwszNames, IN PWSTR *ppwszGuids) { ULONG i; for(i = 0;i < cItems;i++ ) { AccFree( ppwszNames[ i ]); AccFree( ppwszGuids[ i ]); } AccFree(ppwszNames); AccFree(ppwszGuids); }