Directory Services |
Windows Server 2003 includes the restore deleted objects feature.
To enable deleted object restoration, at least one domain controller in the domain must be running on Windows Server 2003. By default, only domain administrators can restore deleted objects, though this can be delegated to others.
The following limitations apply to restoring deleted objects:
When an object is deleted, the object security descriptor is retained. Although the owner is identifiable from the security descriptor, for security reasons, only domain administrators are allowed to restore deleted objects. Domain administrators can grant the permission to restore delete objects to other users and groups by granting the user or group the "Reanimate Tombstone" control access right. The "Reanimate Tombstone" control access right is granted at the Naming Context root. Only users that had read access permission on an object and its attributes are permitted to read the object and accessible attributes after the object is deleted.
Note Granting a user this permission can be a security risk because it could permit the user to restore an account object that has access to resources that the user would not normally have access to. By restoring an account, the user essentially gains control of this account because the user must set the initial password on the account when the account is restored.
To completely restore a deleted object, the user must:
Note The isDeleted attribute is not verified during the restore operation. Neither will the delete-child permission on the "Deleted Objects" container be verified.
To restore a deleted object, the object must first be located in the Deleted Objects container. For more information about retrieving deleted objects, see Retrieving Deleted Objects.
When the object has been located, the following operations must be completed in a single LDAP operation. This requires the use of the ldap_modify_ext_s function with the LDAP_SERVER_SHOW_DELETED_OID control.
Note The objectCategory attribute can also be set when the object is restored, but is not required. If no objectCategory value is specified, the default objectCategory for the object's objectClass is used.
After the object is restored, it can be accessed as it was before it was deleted. At this point, any optional attributes that are important should be restored. Any references to the object from other objects in the directory must also be restored.
As a security measure, user objects are disabled when they are restored. User objects must be enabled after restoring the optional attributes to allow the user object to be used.
For more information and a code example that shows how to restore a deleted object, see the RestoreDeletedObject function below.
The following code example shows how to restore a deleted object.
//*************************************************************************** // // RestoreDeletedObject() // // Restores a deleted object. // // pwszDeletedDN - Contains the fully-qualified distinguished name of the // deleted object. // // pwszDestContainerDN - Contains the fully-qualified distinguished name of // the folder that the delted object should be moved to. // // Returns S_OK if successful or an HRESULT or LDAP error code otherwise. // //*************************************************************************** HRESULT RestoreDeletedObject(LPCWSTR pwszDeletedDN, LPCWSTR pwszDestContainerDN) { if((NULL == pwszDeletedDN) || (NULL == pwszDestContainerDN)) { return E_POINTER; } HRESULT hr = E_FAIL; // Build the new distinguished name. LPWSTR pwszNewDN = new WCHAR[lstrlenW(pwszDeletedDN) + lstrlenW(pwszDestContainerDN) + 1]; if(pwszNewDN) { lstrcpyW(pwszNewDN, pwszDeletedDN); // Search for the first 0x0A character. This is the delimiter in the deleted object name. LPWSTR pwszChar; for(pwszChar = pwszNewDN; *pwszChar; pwszChar = CharNextW(pwszChar)) { if(('\\' == *pwszChar) && ('0' == *(pwszChar + 1)) && ('A' == *(pwszChar + 2))) { break; } } if(0 != *pwszChar) { // Truncate the name string at the delimiter. *pwszChar = 0; // Add the last known parent DN to complete the DN. lstrcatW(pwszNewDN, L","); lstrcatW(pwszNewDN, pwszDestContainerDN); PLDAP ld; // Initialize LDAP. ld = ldap_init(NULL, LDAP_PORT); if(NULL != ld) { ULONG ulRC; ULONG version = LDAP_VERSION3; // Set the LDAP version. ulRC = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void*)&version); if(LDAP_SUCCESS == ulRC) { // Establish a connection with the server. ulRC = ldap_connect(ld, NULL); if(LDAP_SUCCESS == ulRC) { // Bind to the LDAP server. ulRC = ldap_bind_s(ld, NULL, NULL, LDAP_AUTH_NEGOTIATE); if(LDAP_SUCCESS == ulRC) { // Setup the new values. LPWSTR rgNewVals[] = {pwszNewDN, NULL}; /* Remove the isDeleted attribute. This cannot be set to FALSE or the restore operation will not work. */ LDAPModW modIsDeleted = { LDAP_MOD_DELETE, L"isDeleted", NULL }; /* Set the new DN, in effect, moving the deleted object to where it resided before the deletion. */ LDAPModW modDN = { LDAP_MOD_REPLACE, L"distinguishedName", rgNewVals }; // Initialize the LDAPMod structure. PLDAPModW ldapMods[] = { &modIsDeleted, &modDN, NULL }; /* Use the LDAP_SERVER_SHOW_DELETED_OID control to modify deleted objects. */ LDAPControlW showDeletedControl; showDeletedControl.ldctl_oid = LDAP_SERVER_SHOW_DELETED_OID_W; showDeletedControl.ldctl_value.bv_len = 0; showDeletedControl.ldctl_value.bv_val = NULL; showDeletedControl.ldctl_iscritical = TRUE; // Initialzie the LDAPControl structure PLDAPControlW ldapControls[] = { &showDeletedControl, NULL }; /* Modify the specified attributes. This must performed in one step, which is why the LDAP APIs must be used to restore a deleted object. */ ulRC = ldap_modify_ext_sW(ld, (PWCHAR)pwszDeletedDN, ldapMods, ldapControls, NULL); if(LDAP_SUCCESS == ulRC) { hr = S_OK; } else if(LDAP_ALREADY_EXISTS == ulRC) { /* An object already exists with the specified name in the specified target container. At this point, a new name must be selected. */ } } } } if(LDAP_SUCCESS != ulRC) { hr = ulRC; OutputDebugString(ldap_err2string(ulRC)); } // Release the LDAP session. ldap_unbind(ld); } } else { /* If the end of the string is reached before the delimiter is found, just end and fail. */ hr = E_INVALIDARG; } delete pwszNewDN; } else { hr = E_OUTOFMEMORY; } return hr; }