s4:dsdb - enhance "get_last_structural_class()" for optimisations
[obnox/samba/samba-obnox.git] / source4 / dsdb / samdb / ldb_modules / objectclass.c
index cd45963f37ad9ce97dfe1c31e91ee297bd4706df..0d75e5ff89705b512b23db9f0a5ff86203e400c2 100644 (file)
@@ -3,7 +3,7 @@
 
    Copyright (C) Simo Sorce  2006-2008
    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
-   Copyright (C) Matthias Dieter Wallnöfer 2010
+   Copyright (C) Matthias Dieter Wallnöfer 2010-2011
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -45,7 +45,8 @@
 #include "auth/auth.h"
 #include "param/param.h"
 #include "../libds/common/flags.h"
-#include "util.h"
+#include "dsdb/samdb/ldb_modules/schema.h"
+#include "dsdb/samdb/ldb_modules/util.h"
 
 struct oc_context {
 
@@ -99,7 +100,9 @@ static int objectclass_sort(struct ldb_module *module,
 {
        struct ldb_context *ldb;
        unsigned int i, lowest;
-       struct class_list *unsorted = NULL, *sorted = NULL, *current = NULL, *poss_parent = NULL, *new_parent = NULL, *current_lowest = NULL;
+       struct class_list *unsorted = NULL, *sorted = NULL, *current = NULL,
+                         *poss_parent = NULL, *new_parent = NULL,
+                         *current_lowest = NULL, *current_lowest_struct = NULL;
 
        ldb = ldb_module_get_ctx(module);
 
@@ -162,9 +165,15 @@ static int objectclass_sort(struct ldb_module *module,
        current->objectclass = dsdb_class_by_lDAPDisplayName(schema, "top");
        DLIST_ADD_END(sorted, current, struct class_list *);
 
+       /* If we don't have a schema yet, then just merge the lists again */
+       if (!schema) {
+               DLIST_CONCATENATE(sorted, unsorted, struct class_list *);
+               *sorted_out = sorted;
+               return LDB_SUCCESS;
+       }
 
        /* For each object:  find parent chain */
-       for (current = unsorted; schema && current; current = current->next) {
+       for (current = unsorted; current != NULL; current = current->next) {
                for (poss_parent = unsorted; poss_parent; poss_parent = poss_parent->next) {
                        if (ldb_attr_cmp(poss_parent->objectclass->lDAPDisplayName, current->objectclass->subClassOf) == 0) {
                                break;
@@ -180,42 +189,91 @@ static int objectclass_sort(struct ldb_module *module,
                DLIST_ADD_END(unsorted, new_parent, struct class_list *);
        }
 
-       do
-       {
+       /* For each object: order by hierarchy */
+       while (unsorted != NULL) {
                lowest = UINT_MAX;
-               current_lowest = NULL;
-               for (current = unsorted; schema && current; current = current->next) {
-                       if(current->objectclass->subClass_order < lowest) {
-                               current_lowest = current;
+               current_lowest = current_lowest_struct = NULL;
+               for (current = unsorted; current != NULL; current = current->next) {
+                       if (current->objectclass->subClass_order <= lowest) {
+                               /*
+                                * According to MS-ADTS 3.1.1.1.4 structural
+                                * and 88 object classes are always listed after
+                                * the other class types in a subclass hierarchy
+                                */
+                               if (current->objectclass->objectClassCategory > 1) {
+                                       current_lowest = current;
+                               } else {
+                                       current_lowest_struct = current;
+                               }
                                lowest = current->objectclass->subClass_order;
                        }
                }
+               if (current_lowest == NULL) {
+                       current_lowest = current_lowest_struct;
+               }
 
-               if(current_lowest != NULL) {
+               if (current_lowest != NULL) {
                        DLIST_REMOVE(unsorted,current_lowest);
                        DLIST_ADD_END(sorted,current_lowest, struct class_list *);
                }
-       } while(unsorted);
+       }
 
+       *sorted_out = sorted;
+       return LDB_SUCCESS;
+}
 
-       if (!unsorted) {
-               *sorted_out = sorted;
+/*
+ * This checks if we have unrelated object classes in our entry's "objectClass"
+ * attribute. That means "unsatisfied" abstract classes (no concrete subclass)
+ * or two or more disjunct structural ones.
+ * If one of these conditions are true, blame.
+ */
+static int check_unrelated_objectclasses(struct ldb_module *module,
+                                       const struct dsdb_schema *schema,
+                                       const struct dsdb_class *struct_objectclass,
+                                       struct ldb_message_element *objectclass_element)
+{
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       unsigned int i;
+       bool found;
+
+       if (schema == NULL) {
                return LDB_SUCCESS;
        }
 
-       if (!schema) {
-               /* If we don't have schema yet, then just merge the lists again */
-               DLIST_CONCATENATE(sorted, unsorted, struct class_list *);
-               *sorted_out = sorted;
-               return LDB_SUCCESS;
+       for (i = 0; i < objectclass_element->num_values; i++) {
+               const struct dsdb_class *tmp_class = dsdb_class_by_lDAPDisplayName_ldb_val(schema,
+                                                                                          &objectclass_element->values[i]);
+               const struct dsdb_class *tmp_class2 = struct_objectclass;
+
+               /* Pointer comparison can be used due to the same schema str. */
+               if (tmp_class == NULL ||
+                   tmp_class == struct_objectclass ||
+                   tmp_class->objectClassCategory > 2 ||
+                   ldb_attr_cmp(tmp_class->lDAPDisplayName, "top") == 0) {
+                       continue;
+               }
+
+               found = false;
+               while (!found &&
+                      ldb_attr_cmp(tmp_class2->lDAPDisplayName, "top") != 0) {
+                       tmp_class2 = dsdb_class_by_lDAPDisplayName(schema,
+                                                                  tmp_class2->subClassOf);
+                       if (tmp_class2 == tmp_class) {
+                               found = true;
+                       }
+               }
+               if (found) {
+                       continue;
+               }
+
+               ldb_asprintf_errstring(ldb,
+                                      "objectclass: the objectclass '%s' seems to be unrelated to the entry!",
+                                      tmp_class->lDAPDisplayName);
+               return LDB_ERR_OBJECT_CLASS_VIOLATION;
        }
 
-       /* This shouldn't happen, and would break MMC, perhaps there
-        * was no 'top', a conflict in the objectClasses or some other
-        * schema error?
-        */
-       ldb_asprintf_errstring(ldb, "objectclass %s is not a valid objectClass in objectClass chain", unsorted->objectclass->lDAPDisplayName);
-       return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       return LDB_SUCCESS;
 }
 
 static int get_search_callback(struct ldb_request *req, struct ldb_reply *ares)
@@ -318,14 +376,18 @@ static int fix_dn(struct ldb_context *ldb,
        char *upper_rdn_attr;
        const struct ldb_val *rdn_val;
 
-       /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+       /* Fix up the DN to be in the standard form, taking particular care to
+        * match the parent DN */
        *fixed_dn = ldb_dn_copy(mem_ctx, parent_dn);
+       if (*fixed_dn == NULL) {
+               return ldb_oom(ldb);
+       }
 
        /* We need the attribute name in upper case */
        upper_rdn_attr = strupper_talloc(*fixed_dn, 
                                         ldb_dn_get_rdn_name(newdn));
-       if (!upper_rdn_attr) {
-               return ldb_operr(ldb);
+       if (upper_rdn_attr == NULL) {
+               return ldb_oom(ldb);
        }
 
        /* Create a new child */
@@ -333,8 +395,10 @@ static int fix_dn(struct ldb_context *ldb,
                return ldb_operr(ldb);
        }
 
-
        rdn_val = ldb_dn_get_rdn_val(newdn);
+       if (rdn_val == NULL) {
+               return ldb_operr(ldb);
+       }
 
 #if 0
        /* the rules for rDN length constraints are more complex than
@@ -347,7 +411,7 @@ static int fix_dn(struct ldb_context *ldb,
 #endif
 
 
-       /* And replace it with CN=foo (we need the attribute in upper case */
+       /* And replace it with CN=foo (we need the attribute in upper case) */
        return ldb_dn_set_component(*fixed_dn, 0, upper_rdn_attr, *rdn_val);
 }
 
@@ -361,9 +425,8 @@ static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
        struct oc_context *ac;
        struct ldb_dn *parent_dn;
        const struct ldb_val *val;
-       char *value;
        int ret;
-       static const char * const parent_attrs[] = { "objectGUID", "objectClass", NULL };
+       static const char * const parent_attrs[] = { "objectClass", NULL };
 
        ldb = ldb_module_get_ctx(module);
 
@@ -382,6 +445,7 @@ static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
                instanceType = ldb_msg_find_attr_as_uint(req->op.add.message,
                                                         "instanceType", 0);
                if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+                       char *referral_uri;
                        /* When we are trying to readd the root basedn then
                         * this is denied, but with an interesting mechanism:
                         * there is generated a referral with the last
@@ -391,13 +455,13 @@ static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
                        if (val == NULL) {
                                return ldb_operr(ldb);
                        }
-                       value = talloc_asprintf(req, "ldap://%s/%s", val->data,
-                                               ldb_dn_get_linearized(req->op.add.message->dn));
-                       if (value == NULL) {
-                               return ldb_oom(ldb);
+                       referral_uri = talloc_asprintf(req, "ldap://%s/%s", val->data,
+                                                      ldb_dn_get_linearized(req->op.add.message->dn));
+                       if (referral_uri == NULL) {
+                               return ldb_module_oom(module);
                        }
 
-                       return ldb_module_send_referral(req, value);
+                       return ldb_module_send_referral(req, referral_uri);
                }
        }
 
@@ -414,7 +478,7 @@ static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
        /* get copy of parent DN */
        parent_dn = ldb_dn_get_parent(ac, ac->req->op.add.message->dn);
        if (parent_dn == NULL) {
-               return ldb_oom(ldb);
+               return ldb_operr(ldb);
        }
 
        ret = ldb_build_search_req(&search_req, ldb,
@@ -423,6 +487,7 @@ static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
                                   NULL,
                                   ac, get_search_callback,
                                   req);
+       LDB_REQ_SET_LOCATION(search_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -441,7 +506,7 @@ static bool check_rodc_ntdsdsa_add(struct oc_context *ac,
 {
        struct ldb_control *rodc_control;
 
-       if (strcasecmp(objectclass->lDAPDisplayName, "nTDSDSA") != 0) {
+       if (ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSDSA") != 0) {
                return false;
        }
        rodc_control = ldb_request_get_control(ac->req, LDB_CONTROL_RODC_DCPROMO_OID);
@@ -455,7 +520,7 @@ static bool check_rodc_ntdsdsa_add(struct oc_context *ac,
 
 static int objectclass_do_add(struct oc_context *ac)
 {
-       struct ldb_context *ldb;
+       struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
        struct ldb_request *add_req;
        struct ldb_message_element *objectclass_element, *el;
        struct ldb_message *msg;
@@ -466,11 +531,14 @@ static int objectclass_do_add(struct oc_context *ac)
        const struct dsdb_class *objectclass;
        struct ldb_dn *objectcategory;
        int32_t systemFlags = 0;
+       unsigned int i, j;
+       bool found;
        int ret;
 
-       ldb = ldb_module_get_ctx(ac->module);
-
        msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
+       if (msg == NULL) {
+               return ldb_module_oom(ac->module);
+       }
 
        /* Check if we have a valid parent - this check is needed since
         * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
@@ -479,8 +547,8 @@ static int objectclass_do_add(struct oc_context *ac)
 
                /* An add operation on partition DNs without "NC-add" operation
                 * isn't allowed. */
-               instanceType = ldb_msg_find_attr_as_uint(ac->req->op.add.message,
-                                                        "instanceType", 0);
+               instanceType = ldb_msg_find_attr_as_uint(msg, "instanceType",
+                                                        0);
                if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
                        ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, parent does not exist!", 
                                               ldb_dn_get_linearized(msg->dn));
@@ -503,19 +571,23 @@ static int objectclass_do_add(struct oc_context *ac)
                }
        }
 
-       mem_ctx = talloc_new(ac);
-       if (mem_ctx == NULL) {
-               return ldb_oom(ldb);
-       }
-
        if (ac->schema != NULL) {
                objectclass_element = ldb_msg_find_element(msg, "objectClass");
                if (!objectclass_element) {
                        ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, no objectclass specified!",
                                               ldb_dn_get_linearized(msg->dn));
-                       talloc_free(mem_ctx);
                        return LDB_ERR_OBJECT_CLASS_VIOLATION;
                }
+               if (objectclass_element->num_values == 0) {
+                       ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, at least one (structural) objectclass has to be specified!",
+                                              ldb_dn_get_linearized(msg->dn));
+                       return LDB_ERR_CONSTRAINT_VIOLATION;
+               }
+
+               mem_ctx = talloc_new(ac);
+               if (mem_ctx == NULL) {
+                       return ldb_module_oom(ac->module);
+               }
 
                /* Here we do now get the "objectClass" list from the
                 * database. */
@@ -540,7 +612,8 @@ static int objectclass_do_add(struct oc_context *ac)
 
                /* We must completely replace the existing objectClass entry,
                 * because we need it sorted. */
-               ret = ldb_msg_add_empty(msg, "objectClass", 0, NULL);
+               ret = ldb_msg_add_empty(msg, "objectClass", 0,
+                                       &objectclass_element);
                if (ret != LDB_SUCCESS) {
                        talloc_free(mem_ctx);
                        return ret;
@@ -548,12 +621,9 @@ static int objectclass_do_add(struct oc_context *ac)
 
                /* Move from the linked list back into an ldb msg */
                for (current = sorted; current; current = current->next) {
-                       value = talloc_strdup(msg, current->objectclass->lDAPDisplayName);
-                       if (value == NULL) {
-                               talloc_free(mem_ctx);
-                               return ldb_oom(ldb);
-                       }
-                       ret = ldb_msg_add_string(msg, "objectClass", value);
+                       const char *objectclass_name = current->objectclass->lDAPDisplayName;
+
+                       ret = ldb_msg_add_string(msg, "objectClass", objectclass_name);
                        if (ret != LDB_SUCCESS) {
                                ldb_set_errstring(ldb,
                                                  "objectclass: could not re-add sorted "
@@ -565,12 +635,10 @@ static int objectclass_do_add(struct oc_context *ac)
 
                talloc_free(mem_ctx);
 
-               /* Retrive the message again so get_last_structural_class works */
-               objectclass_element = ldb_msg_find_element(msg, "objectClass");
-
                /* Make sure its valid to add an object of this type */
                objectclass = get_last_structural_class(ac->schema,
-                                                       objectclass_element);
+                                                       objectclass_element,
+                                                       true);
                if(objectclass == NULL) {
                        ldb_asprintf_errstring(ldb,
                                               "Failed to find a structural class for %s",
@@ -578,28 +646,43 @@ static int objectclass_do_add(struct oc_context *ac)
                        return LDB_ERR_UNWILLING_TO_PERFORM;
                }
 
+               ret = check_unrelated_objectclasses(ac->module, ac->schema,
+                                                   objectclass,
+                                                   objectclass_element);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+
                rdn_name = ldb_dn_get_rdn_name(msg->dn);
-               if (objectclass->rDNAttID
-                       && ldb_attr_cmp(rdn_name, objectclass->rDNAttID) != 0) {
+               if (rdn_name == NULL) {
+                       return ldb_operr(ldb);
+               }
+               found = false;
+               for (i = 0; (!found) && (i < objectclass_element->num_values);
+                    i++) {
+                       const struct dsdb_class *tmp_class =
+                               dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+                                                                     &objectclass_element->values[i]);
+
+                       if (tmp_class == NULL) continue;
+
+                       if (ldb_attr_cmp(rdn_name, tmp_class->rDNAttID) == 0)
+                               found = true;
+               }
+               if (!found) {
                        ldb_asprintf_errstring(ldb,
-                                               "RDN %s is not correct for most specific structural objectclass %s, should be %s",
-                                               rdn_name, objectclass->lDAPDisplayName, objectclass->rDNAttID);
+                                              "objectclass: Invalid RDN '%s' for objectclass '%s'!",
+                                              rdn_name, objectclass->lDAPDisplayName);
                        return LDB_ERR_NAMING_VIOLATION;
                }
 
                if (objectclass->systemOnly &&
                    !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) &&
                    !check_rodc_ntdsdsa_add(ac, objectclass)) {
-                       ldb_asprintf_errstring(ldb, "objectClass %s is systemOnly, rejecting creation of %s",
-                                               objectclass->lDAPDisplayName, ldb_dn_get_linearized(msg->dn));
-                       return LDB_ERR_UNWILLING_TO_PERFORM;
-               }
-
-               if (((strcmp(objectclass->lDAPDisplayName, "secret") == 0) ||
-                    (strcmp(objectclass->lDAPDisplayName, "trustedDomain") == 0)) &&
-                    !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
-                       ldb_asprintf_errstring(ldb, "objectClass %s is LSA-specific, rejecting creation of %s",
-                                               objectclass->lDAPDisplayName, ldb_dn_get_linearized(msg->dn));
+                       ldb_asprintf_errstring(ldb,
+                                              "objectclass: object class '%s' is system-only, rejecting creation of '%s'!",
+                                              objectclass->lDAPDisplayName,
+                                              ldb_dn_get_linearized(msg->dn));
                        return LDB_ERR_UNWILLING_TO_PERFORM;
                }
 
@@ -608,7 +691,6 @@ static int objectclass_do_add(struct oc_context *ac)
                                = ldb_msg_find_element(ac->search_res->message, "objectClass");
 
                        bool allowed_class = false;
-                       unsigned int i, j;
                        for (i=0; allowed_class == false && oc_el && i < oc_el->num_values; i++) {
                                const struct dsdb_class *sclass;
 
@@ -650,7 +732,7 @@ static int objectclass_do_add(struct oc_context *ac)
                                                      objectclass->defaultObjectCategory);
                        }
                        if (value == NULL) {
-                               return ldb_oom(ldb);
+                               return ldb_module_oom(ac->module);
                        }
 
                        ret = ldb_msg_add_string(msg, "objectCategory", value);
@@ -695,29 +777,35 @@ static int objectclass_do_add(struct oc_context *ac)
 
                ldb_msg_remove_attr(msg, "systemFlags");
 
-               /* Only these flags may be set by a client, but we can't tell
-                * between a client and our provision at this point
-                * systemFlags &= ( SYSTEM_FLAG_CONFIG_ALLOW_RENAME | SYSTEM_FLAG_CONFIG_ALLOW_MOVE | SYSTEM_FLAG_CONFIG_LIMITED_MOVE);
-                */
+               /* Only the following flags may be set by a client */
+               if (ldb_request_get_control(ac->req,
+                                           LDB_CONTROL_RELAX_OID) == NULL) {
+                       systemFlags &= ( SYSTEM_FLAG_CONFIG_ALLOW_RENAME
+                                      | SYSTEM_FLAG_CONFIG_ALLOW_MOVE
+                                      | SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE
+                                      | SYSTEM_FLAG_ATTR_IS_RDN );
+               }
 
-               /* This flag is only allowed on attributeSchema objects */
-               if (ldb_attr_cmp(objectclass->lDAPDisplayName, "attributeSchema") == 0) {
+               /* But the last one ("ATTR_IS_RDN") is only allowed on
+                * "attributeSchema" objects. So truncate if it does not fit. */
+               if (ldb_attr_cmp(objectclass->lDAPDisplayName, "attributeSchema") != 0) {
                        systemFlags &= ~SYSTEM_FLAG_ATTR_IS_RDN;
                }
 
                if (ldb_attr_cmp(objectclass->lDAPDisplayName, "server") == 0) {
                        systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE | SYSTEM_FLAG_CONFIG_ALLOW_RENAME | SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE);
                } else if (ldb_attr_cmp(objectclass->lDAPDisplayName, "site") == 0
-                               || ldb_attr_cmp(objectclass->lDAPDisplayName, "serverContainer") == 0
-                               || ldb_attr_cmp(objectclass->lDAPDisplayName, "ntDSDSA") == 0) {
+                               || ldb_attr_cmp(objectclass->lDAPDisplayName, "serversContainer") == 0
+                               || ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSDSA") == 0) {
+                       if (ldb_attr_cmp(objectclass->lDAPDisplayName, "site") == 0)
+                               systemFlags |= (int32_t)(SYSTEM_FLAG_CONFIG_ALLOW_RENAME);
                        systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
-
                } else if (ldb_attr_cmp(objectclass->lDAPDisplayName, "siteLink") == 0
+                               || ldb_attr_cmp(objectclass->lDAPDisplayName, "subnet") == 0
                                || ldb_attr_cmp(objectclass->lDAPDisplayName, "siteLinkBridge") == 0
                                || ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSConnection") == 0) {
                        systemFlags |= (int32_t)(SYSTEM_FLAG_CONFIG_ALLOW_RENAME);
                }
-
                /* TODO: If parent object is site or subnet, also add (SYSTEM_FLAG_CONFIG_ALLOW_RENAME) */
 
                if (el || systemFlags != 0) {
@@ -727,11 +815,15 @@ static int objectclass_do_add(struct oc_context *ac)
                                return ret;
                        }
                }
-       }
 
-       ret = ldb_msg_sanity_check(ldb, msg);
-       if (ret != LDB_SUCCESS) {
-               return ret;
+               /* make sure that "isCriticalSystemObject" is not specified! */
+               el = ldb_msg_find_element(msg, "isCriticalSystemObject");
+               if ((el != NULL) &&
+                    !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
+                       ldb_set_errstring(ldb,
+                                         "objectclass: 'isCriticalSystemObject' must not be specified!");
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               }
        }
 
        ret = ldb_build_add_req(&add_req, ldb, ac,
@@ -739,6 +831,7 @@ static int objectclass_do_add(struct oc_context *ac)
                                ac->req->controls,
                                ac, oc_op_callback,
                                ac->req);
+       LDB_REQ_SET_LOCATION(add_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -788,7 +881,7 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
 
        msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
        if (msg == NULL) {
-               return ldb_operr(ldb);
+               return ldb_module_oom(ac->module);
        }
 
        /* For now change everything except the objectclasses */
@@ -799,11 +892,35 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
                oc_changes = true;
        }
 
+       /* MS-ADTS 3.1.1.5.3.5 - on a forest level < 2003 we do allow updates
+        * only on application NCs - not on the default ones */
+       if (oc_changes &&
+           (dsdb_forest_functional_level(ldb) < DS_DOMAIN_FUNCTION_2003)) {
+               struct ldb_dn *nc_root;
+
+               ret = dsdb_find_nc_root(ldb, ac, req->op.mod.message->dn,
+                                       &nc_root);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+
+               if ((ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) ||
+                   (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) ||
+                   (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0)) {
+                       ldb_set_errstring(ldb,
+                                         "objectclass: object class changes on objects under the standard name contexts not allowed!");
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               }
+
+               talloc_free(nc_root);
+       }
+
        ret = ldb_build_mod_req(&down_req, ldb, ac,
                                msg,
                                req->controls, ac,
                                oc_changes ? oc_modify_callback : oc_op_callback,
                                req);
+       LDB_REQ_SET_LOCATION(down_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -845,7 +962,7 @@ static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
        talloc_free(ares);
 
        /* this looks up the real existing object for fetching some important
-        * informations (objectclasses) */
+        * information (objectclasses) */
        ret = ldb_build_search_req(&search_req, ldb,
                                   ac, ac->req->op.mod.message->dn,
                                   LDB_SCOPE_BASE,
@@ -853,6 +970,7 @@ static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
                                   attrs, NULL, 
                                   ac, get_search_callback,
                                   ac->req);
+       LDB_REQ_SET_LOCATION(search_req);
        if (ret != LDB_SUCCESS) {
                return ldb_module_done(ac->req, NULL, NULL, ret);
        }
@@ -871,15 +989,14 @@ static int objectclass_do_mod(struct oc_context *ac)
 {
        struct ldb_context *ldb;
        struct ldb_request *mod_req;
-       char *value;
        struct ldb_message_element *oc_el_entry, *oc_el_change;
        struct ldb_val *vals;
        struct ldb_message *msg;
        TALLOC_CTX *mem_ctx;
        struct class_list *sorted, *current;
        const struct dsdb_class *objectclass;
-       unsigned int i, j;
-       bool found, replace = false;
+       unsigned int i, j, k;
+       bool found;
        int ret;
 
        ldb = ldb_module_get_ctx(ac->module);
@@ -896,135 +1013,122 @@ static int objectclass_do_mod(struct oc_context *ac)
                return ldb_operr(ldb);
        }
 
-       oc_el_change = ldb_msg_find_element(ac->req->op.mod.message,
-                                           "objectClass");
-       if (oc_el_change == NULL) {
-               /* we should have an objectclass change operation */
-               return ldb_operr(ldb);
-       }
-
        /* use a new message structure */
        msg = ldb_msg_new(ac);
        if (msg == NULL) {
-               return ldb_oom(ldb);
+               return ldb_module_oom(ac->module);
        }
 
        msg->dn = ac->req->op.mod.message->dn;
 
        mem_ctx = talloc_new(ac);
        if (mem_ctx == NULL) {
-               return ldb_oom(ldb);
+               return ldb_module_oom(ac->module);
        }
 
-       switch (oc_el_change->flags & LDB_FLAG_MOD_MASK) {
-       case LDB_FLAG_MOD_ADD:
-               /* Merge the two message elements */
-               for (i = 0; i < oc_el_change->num_values; i++) {
-                       for (j = 0; j < oc_el_entry->num_values; j++) {
-                               if (strcasecmp((char *)oc_el_change->values[i].data,
-                                              (char *)oc_el_entry->values[j].data) == 0) {
-                                       /* we cannot add an already existing object class */
+       /* We've to walk over all "objectClass" message elements */
+       for (k = 0; k < ac->req->op.mod.message->num_elements; k++) {
+               if (ldb_attr_cmp(ac->req->op.mod.message->elements[k].name,
+                                "objectClass") != 0) {
+                       continue;
+               }
+
+               oc_el_change = &ac->req->op.mod.message->elements[k];
+
+               switch (oc_el_change->flags & LDB_FLAG_MOD_MASK) {
+               case LDB_FLAG_MOD_ADD:
+                       /* Merge the two message elements */
+                       for (i = 0; i < oc_el_change->num_values; i++) {
+                               for (j = 0; j < oc_el_entry->num_values; j++) {
+                                       if (ldb_attr_cmp((char *)oc_el_change->values[i].data,
+                                                        (char *)oc_el_entry->values[j].data) == 0) {
+                                               ldb_asprintf_errstring(ldb,
+                                                                      "objectclass: cannot re-add an existing objectclass: '%.*s'!",
+                                                                      (int)oc_el_change->values[i].length,
+                                                                      (const char *)oc_el_change->values[i].data);
+                                               talloc_free(mem_ctx);
+                                               return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+                                       }
+                               }
+                               /* append the new object class value - code was
+                                * copied from "ldb_msg_add_value" */
+                               vals = talloc_realloc(oc_el_entry, oc_el_entry->values,
+                                                     struct ldb_val,
+                                                     oc_el_entry->num_values + 1);
+                               if (vals == NULL) {
                                        talloc_free(mem_ctx);
-                                       return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+                                       return ldb_module_oom(ac->module);
                                }
+                               oc_el_entry->values = vals;
+                               oc_el_entry->values[oc_el_entry->num_values] =
+                                                       oc_el_change->values[i];
+                               ++(oc_el_entry->num_values);
                        }
-                       /* append the new object class value - code was copied
-                        * from "ldb_msg_add_value" */
-                       vals = talloc_realloc(oc_el_entry, oc_el_entry->values,
-                                             struct ldb_val,
-                                             oc_el_entry->num_values + 1);
-                       if (vals == NULL) {
-                               talloc_free(mem_ctx);
-                               return ldb_oom(ldb);
+
+                       break;
+
+               case LDB_FLAG_MOD_REPLACE:
+                       /*
+                        * In this case the new "oc_el_entry" is simply
+                        * "oc_el_change"
+                        */
+                       oc_el_entry = oc_el_change;
+
+                       break;
+
+               case LDB_FLAG_MOD_DELETE:
+                       /* Merge the two message elements */
+                       for (i = 0; i < oc_el_change->num_values; i++) {
+                               found = false;
+                               for (j = 0; j < oc_el_entry->num_values; j++) {
+                                       if (ldb_attr_cmp((char *)oc_el_change->values[i].data,
+                                                        (char *)oc_el_entry->values[j].data) == 0) {
+                                               found = true;
+                                               /* delete the object class value
+                                                * - code was copied from
+                                                * "ldb_msg_remove_element" */
+                                               if (j != oc_el_entry->num_values - 1) {
+                                                       memmove(&oc_el_entry->values[j],
+                                                               &oc_el_entry->values[j+1],
+                                                               ((oc_el_entry->num_values-1) - j)*sizeof(struct ldb_val));
+                                               }
+                                               --(oc_el_entry->num_values);
+                                               break;
+                                       }
+                               }
+                               if (!found) {
+                                       /* we cannot delete a not existing
+                                        * object class */
+                                       ldb_asprintf_errstring(ldb,
+                                                              "objectclass: cannot delete this objectclass: '%.*s'!",
+                                                              (int)oc_el_change->values[i].length,
+                                                              (const char *)oc_el_change->values[i].data);
+                                       talloc_free(mem_ctx);
+                                       return LDB_ERR_NO_SUCH_ATTRIBUTE;
+                               }
                        }
-                       oc_el_entry->values = vals;
-                       oc_el_entry->values[oc_el_entry->num_values] =
-                               oc_el_change->values[i];
-                       ++(oc_el_entry->num_values);
-               }
 
-               objectclass = get_last_structural_class(ac->schema,
-                                                       oc_el_change);
-               if (objectclass != NULL) {
-                       /* we cannot add a new structural object class */
-                       talloc_free(mem_ctx);
-                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
+                       break;
                }
 
-               /* Now do the sorting */
-               ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
-                                      oc_el_entry, &sorted);
-               if (ret != LDB_SUCCESS) {
+               /* Get the new top-most structural object class */
+               objectclass = get_last_structural_class(ac->schema, oc_el_entry,
+                                                       false);
+               if (objectclass == NULL) {
+                       ldb_set_errstring(ldb,
+                                         "objectclass: cannot delete all structural objectclasses!");
                        talloc_free(mem_ctx);
-                       return ret;
+                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
                }
 
-               break;
-
-       case LDB_FLAG_MOD_REPLACE:
-               /* Do the sorting for the change message element */
-               ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
-                                      oc_el_change, &sorted);
+               ret = check_unrelated_objectclasses(ac->module, ac->schema,
+                                                   objectclass,
+                                                   oc_el_entry);
                if (ret != LDB_SUCCESS) {
                        talloc_free(mem_ctx);
                        return ret;
                }
 
-               /* this is a replace */
-               replace = true;
-
-               break;
-
-       case LDB_FLAG_MOD_DELETE:
-               /* get the actual top-most structural objectclass */
-               objectclass = get_last_structural_class(ac->schema,
-                                                       oc_el_entry);
-               if (objectclass == NULL) {
-                       talloc_free(mem_ctx);
-                       return ldb_operr(ldb);
-               }
-
-               /* Merge the two message elements */
-               for (i = 0; i < oc_el_change->num_values; i++) {
-                       found = false;
-                       for (j = 0; j < oc_el_entry->num_values; j++) {
-                               if (strcasecmp((char *)oc_el_change->values[i].data,
-                                              (char *)oc_el_entry->values[j].data) == 0) {
-                                       found = true;
-                                       /* delete the object class value -
-                                        * code was copied from
-                                        * "ldb_msg_remove_element" */
-                                       if (j != oc_el_entry->num_values - 1) {
-                                               memmove(&oc_el_entry->values[j],
-                                                       &oc_el_entry->values[j+1],
-                                                       ((oc_el_entry->num_values-1) - j)*sizeof(struct ldb_val));
-                                       }
-                                       --(oc_el_entry->num_values);
-                                       break;
-                               }
-                       }
-                       if (!found) {
-                               /* we cannot delete a not existing object class */
-                               talloc_free(mem_ctx);
-                               return LDB_ERR_NO_SUCH_ATTRIBUTE;
-                       }
-               }
-
-               /* Make sure that the top-most structural objectclass wasn't
-                * deleted */
-               found = false;
-               for (i = 0; i < oc_el_entry->num_values; i++) {
-                       if (strcasecmp(objectclass->lDAPDisplayName,
-                           (char *)oc_el_entry->values[i].data) == 0) {
-                               found = true; break;
-                       }
-               }
-               if (!found) {
-                       talloc_free(mem_ctx);
-                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
-               }
-
-
                /* Now do the sorting */
                ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
                                       oc_el_entry, &sorted);
@@ -1033,67 +1137,41 @@ static int objectclass_do_mod(struct oc_context *ac)
                        return ret;
                }
 
-               break;
-       }
-
-       ret = ldb_msg_add_empty(msg, "objectClass",
-                               LDB_FLAG_MOD_REPLACE, &oc_el_change);
-       if (ret != LDB_SUCCESS) {
-               ldb_oom(ldb);
-               talloc_free(mem_ctx);
-               return ret;
-       }
-
-       /* Move from the linked list back into an ldb msg */
-       for (current = sorted; current; current = current->next) {
-               value = talloc_strdup(msg,
-                                     current->objectclass->lDAPDisplayName);
-               if (value == NULL) {
-                       talloc_free(mem_ctx);
-                       return ldb_oom(ldb);
-               }
-               ret = ldb_msg_add_string(msg, "objectClass", value);
+               /* (Re)-add an empty "objectClass" attribute on the object
+                * classes change message "msg". */
+               ldb_msg_remove_attr(msg, "objectClass");
+               ret = ldb_msg_add_empty(msg, "objectClass",
+                                       LDB_FLAG_MOD_REPLACE, &oc_el_entry);
                if (ret != LDB_SUCCESS) {
-                       ldb_set_errstring(ldb, "objectclass: could not re-add sorted objectclass to modify msg");
                        talloc_free(mem_ctx);
                        return ret;
                }
-       }
 
-       talloc_free(mem_ctx);
+               /* Move from the linked list back into an ldb msg */
+               for (current = sorted; current; current = current->next) {
+                       const char *objectclass_name = current->objectclass->lDAPDisplayName;
 
-       if (replace) {
-               /* Well, on replace we are nearly done: we have to test if
-                * the change and entry message element are identically. We
-                * can use "ldb_msg_element_compare" since now the specified
-                * objectclasses match for sure in case. */
-               ret = ldb_msg_element_compare(oc_el_entry, oc_el_change);
-               if (ret == 0) {
-                       ret = ldb_msg_element_compare(oc_el_change,
-                                                     oc_el_entry);
-               }
-               if (ret == 0) {
-                       /* they are the same so we are done in this case */
-                       return ldb_module_done(ac->req, NULL, NULL,
-                                              LDB_SUCCESS);
-               } else {
-                       /* they're not exactly the same */
-                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
+                       ret = ldb_msg_add_string(msg, "objectClass",
+                                                objectclass_name);
+                       if (ret != LDB_SUCCESS) {
+                               ldb_set_errstring(ldb,
+                                                 "objectclass: could not re-add sorted objectclasses!");
+                               talloc_free(mem_ctx);
+                               return ret;
+                       }
                }
        }
 
-       /* in the other cases we have the real change left to do */
+       talloc_free(mem_ctx);
 
-       ret = ldb_msg_sanity_check(ldb, msg);
-       if (ret != LDB_SUCCESS) {
-               return ret;
-       }
+       /* Now we have the real and definitive change left to do */
 
        ret = ldb_build_mod_req(&mod_req, ldb, ac,
                                msg,
                                ac->req->controls,
                                ac, oc_op_callback,
                                ac->req);
+       LDB_REQ_SET_LOCATION(mod_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -1117,7 +1195,7 @@ static int objectclass_rename(struct ldb_module *module, struct ldb_request *req
        ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_rename\n");
 
        /* do not manipulate our control entries */
-       if (ldb_dn_is_special(req->op.rename.newdn)) {
+       if (ldb_dn_is_special(req->op.rename.olddn)) {
                return ldb_next_request(module, req);
        }
 
@@ -1134,21 +1212,23 @@ static int objectclass_rename(struct ldb_module *module, struct ldb_request *req
        }
 
        /* this looks up the parent object for fetching some important
-        * informations (objectclasses, DN normalisation...) */
+        * information (objectclasses, DN normalisation...) */
        ret = ldb_build_search_req(&search_req, ldb,
                                   ac, parent_dn, LDB_SCOPE_BASE,
                                   "(objectClass=*)",
                                   attrs, NULL,
                                   ac, get_search_callback,
                                   req);
+       LDB_REQ_SET_LOCATION(search_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
 
-       /* we have to add the show deleted control, as otherwise DRS
+       /* we have to add the show recycled control, as otherwise DRS
           deletes will be refused as we will think the target parent
           does not exist */
-       ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_DELETED_OID, false, NULL);
+       ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID,
+                                     false, NULL);
 
        if (ret != LDB_SUCCESS) {
                return ret;
@@ -1184,7 +1264,7 @@ static int objectclass_do_rename(struct oc_context *ac)
        ac->search_res = NULL;
 
        /* this looks up the real existing object for fetching some important
-        * informations (objectclasses) */
+        * information (objectclasses) */
        ret = ldb_build_search_req(&search_req, ldb,
                                   ac, ac->req->op.rename.olddn,
                                   LDB_SCOPE_BASE,
@@ -1192,6 +1272,7 @@ static int objectclass_do_rename(struct oc_context *ac)
                                   attrs, NULL,
                                   ac, get_search_callback,
                                   ac->req);
+       LDB_REQ_SET_LOCATION(search_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -1224,6 +1305,7 @@ static int objectclass_do_rename2(struct oc_context *ac)
                const char *rdn_name;
                bool allowed_class = false;
                unsigned int i, j;
+               bool found;
 
                oc_el_entry = ldb_msg_find_element(ac->search_res->message,
                                                   "objectClass");
@@ -1231,20 +1313,32 @@ static int objectclass_do_rename2(struct oc_context *ac)
                        /* existing entry without a valid object class? */
                        return ldb_operr(ldb);
                }
-               objectclass = get_last_structural_class(ac->schema, oc_el_entry);
+               objectclass = get_last_structural_class(ac->schema, oc_el_entry,
+                                                       false);
                if (objectclass == NULL) {
                        /* existing entry without a valid object class? */
                        return ldb_operr(ldb);
                }
 
                rdn_name = ldb_dn_get_rdn_name(ac->req->op.rename.newdn);
-               if ((objectclass->rDNAttID != NULL) &&
-                   (ldb_attr_cmp(rdn_name, objectclass->rDNAttID) != 0)) {
+               if (rdn_name == NULL) {
+                       return ldb_operr(ldb);
+               }
+               found = false;
+               for (i = 0; (!found) && (i < oc_el_entry->num_values); i++) {
+                       const struct dsdb_class *tmp_class =
+                               dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+                                                                     &oc_el_entry->values[i]);
+
+                       if (tmp_class == NULL) continue;
+
+                       if (ldb_attr_cmp(rdn_name, tmp_class->rDNAttID) == 0)
+                               found = true;
+               }
+               if (!found) {
                        ldb_asprintf_errstring(ldb,
-                                              "objectclass: RDN %s is not correct for most specific structural objectclass %s, should be %s",
-                                              rdn_name,
-                                              objectclass->lDAPDisplayName,
-                                              objectclass->rDNAttID);
+                                              "objectclass: Invalid RDN '%s' for objectclass '%s'!",
+                                              rdn_name, objectclass->lDAPDisplayName);
                        return LDB_ERR_UNWILLING_TO_PERFORM;
                }
 
@@ -1308,6 +1402,7 @@ static int objectclass_do_rename2(struct oc_context *ac)
                                   ac->req->controls,
                                   ac, oc_op_callback,
                                   ac->req);
+       LDB_REQ_SET_LOCATION(rename_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -1321,7 +1416,9 @@ static int objectclass_do_delete(struct oc_context *ac);
 static int objectclass_delete(struct ldb_module *module, struct ldb_request *req)
 {
        static const char * const attrs[] = { "nCName", "objectClass",
-                                             "systemFlags", NULL };
+                                             "systemFlags",
+                                             "isDeleted",
+                                             "isCriticalSystemObject", NULL };
        struct ldb_context *ldb;
        struct ldb_request *search_req;
        struct oc_context *ac;
@@ -1348,13 +1445,14 @@ static int objectclass_delete(struct ldb_module *module, struct ldb_request *req
        }
 
        /* this looks up the entry object for fetching some important
-        * informations (object classes, system flags...) */
+        * information (object classes, system flags...) */
        ret = ldb_build_search_req(&search_req, ldb,
                                   ac, req->op.del.dn, LDB_SCOPE_BASE,
                                   "(objectClass=*)",
-                                  attrs, NULL,
+                                  attrs, req->controls,
                                   ac, get_search_callback,
                                   req);
+       LDB_REQ_SET_LOCATION(search_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -1369,6 +1467,7 @@ static int objectclass_do_delete(struct oc_context *ac)
        struct ldb_context *ldb;
        struct ldb_dn *dn;
        int32_t systemFlags;
+       bool isCriticalSystemObject;
        int ret;
 
        ldb = ldb_module_get_ctx(ac->module);
@@ -1405,17 +1504,34 @@ static int objectclass_do_delete(struct oc_context *ac)
                talloc_free(dn);
        }
 
+       /* Only trusted request from system account are allowed to delete
+        * deleted objects.
+        */
+       if (ldb_msg_check_string_attribute(ac->search_res->message, "isDeleted", "TRUE") &&
+                       (ldb_req_is_untrusted(ac->req) ||
+                               !dsdb_module_am_system(ac->module))) {
+               ldb_asprintf_errstring(ldb, "Delete of '%s' failed",
+                                               ldb_dn_get_linearized(ac->req->op.del.dn));
+               return LDB_ERR_UNWILLING_TO_PERFORM;
+       }
+
        /* crossRef objects regarding config, schema and default domain NCs */
        if (samdb_find_attribute(ldb, ac->search_res->message, "objectClass",
                                 "crossRef") != NULL) {
                dn = ldb_msg_find_attr_as_dn(ldb, ac, ac->search_res->message,
                                             "nCName");
                if ((ldb_dn_compare(dn, ldb_get_default_basedn(ldb)) == 0) ||
-                   (ldb_dn_compare(dn, ldb_get_config_basedn(ldb)) == 0) ||
-                   (ldb_dn_compare(dn, ldb_get_schema_basedn(ldb)) == 0)) {
+                   (ldb_dn_compare(dn, ldb_get_config_basedn(ldb)) == 0)) {
+                       talloc_free(dn);
+
+                       ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's a crossRef object to the main or configuration partition!",
+                                              ldb_dn_get_linearized(ac->req->op.del.dn));
+                       return LDB_ERR_NOT_ALLOWED_ON_NON_LEAF;
+               }
+               if (ldb_dn_compare(dn, ldb_get_schema_basedn(ldb)) == 0) {
                        talloc_free(dn);
 
-                       ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's a crossRef object to the three main partitions!",
+                       ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's a crossRef object to the schema partition!",
                                               ldb_dn_get_linearized(ac->req->op.del.dn));
                        return LDB_ERR_UNWILLING_TO_PERFORM;
                }
@@ -1432,6 +1548,37 @@ static int objectclass_do_delete(struct oc_context *ac)
                return LDB_ERR_UNWILLING_TO_PERFORM;
        }
 
+       /* isCriticalSystemObject - but this only applies on tree delete
+        * operations - MS-ADTS 3.1.1.5.5.7.2 */
+       if (ldb_request_get_control(ac->req, LDB_CONTROL_TREE_DELETE_OID) != NULL) {
+               isCriticalSystemObject = ldb_msg_find_attr_as_bool(ac->search_res->message,
+                                                                  "isCriticalSystemObject", false);
+               if (isCriticalSystemObject) {
+                       /*
+                        * Following the explaination from Microsoft
+                        * https://lists.samba.org/archive/cifs-protocol/2011-August/002046.html
+                        * "I finished the investigation on this behavior.
+                        * As per MS-ADTS 3.1.5.5.7.2 , when a tree deletion is performed ,
+                        * every object in the tree will be checked to see if it has isCriticalSystemObject
+                        * set to TRUE, including the root node on which the delete operation is performed
+                        * But there is an exception  if the root object is a SAM specific objects(3.1.1.5.2.3 MS-ADTS)
+                        * Its deletion is done through SAM manger and isCriticalSystemObject attribute is not checked
+                        * The root node of the tree delete in your case is CN=ARES,OU=Domain Controllers,DC=w2k8r2,DC=home,DC=matws,DC=net
+                        * which is a SAM object  with  user class.  Therefore the tree deletion is performed without any error
+                        */
+
+                       if (samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "group") == NULL &&
+                           samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "samDomain") == NULL &&
+                           samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "samServer") == NULL &&
+                           samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "user") == NULL) {
+                                       ldb_asprintf_errstring(ldb,
+                                              "objectclass: Cannot tree-delete %s, it's a critical system object!",
+                                              ldb_dn_get_linearized(ac->req->op.del.dn));
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+                       }
+               }
+       }
+
        return ldb_next_request(ac->module, ac->req);
 }
 
@@ -1449,10 +1596,17 @@ static int objectclass_init(struct ldb_module *module)
        /* Look for the opaque to indicate we might have to cut down the DN of defaultObjectCategory */
        ldb_module_set_private(module, ldb_get_opaque(ldb, DSDB_EXTENDED_DN_STORE_FORMAT_OPAQUE_NAME));
 
+       ret = ldb_mod_register_control(module, LDB_CONTROL_RODC_DCPROMO_OID);
+       if (ret != LDB_SUCCESS) {
+               ldb_debug(ldb, LDB_DEBUG_ERROR,
+                         "objectclass_init: Unable to register control DCPROMO with rootdse\n");
+               return ldb_operr(ldb);
+       }
+
        return ret;
 }
 
-_PUBLIC_ const struct ldb_module_ops ldb_objectclass_module_ops = {
+static const struct ldb_module_ops ldb_objectclass_module_ops = {
        .name           = "objectclass",
        .add            = objectclass_add,
        .modify         = objectclass_modify,
@@ -1460,3 +1614,9 @@ _PUBLIC_ const struct ldb_module_ops ldb_objectclass_module_ops = {
        .del            = objectclass_delete,
        .init_context   = objectclass_init
 };
+
+int ldb_objectclass_module_init(const char *version)
+{
+       LDB_MODULE_CHECK_VERSION(version);
+       return ldb_register_module(&ldb_objectclass_module_ops);
+}