s4:dsdb/samdb/ldb_modules/schema.c - move "get_last_structural_class()" into "util.c"
[obnox/samba/samba-obnox.git] / source4 / dsdb / samdb / ldb_modules / objectclass.c
index ba28d42e7fc52f795ed2ea7bb4b9412ed480034b..d431367b5a0aafc81556b1e4c14debbabeff6313 100644 (file)
@@ -3,6 +3,7 @@
 
    Copyright (C) Simo Sorce  2006-2008
    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+   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
 /*
  *  Name: ldb
  *
- *  Component: objectClass sorting module
+ *  Component: objectClass sorting and constraint checking module
  *
  *  Description: 
  *  - sort the objectClass attribute into the class
- *    hierarchy, 
- *  - fix DNs and attributes into 'standard' case
- *  - Add objectCategory and ntSecurityDescriptor defaults
+ *    hierarchy and perform constraint checks (correct RDN name,
+ *    valid parent),
+ *  - fix DNs into 'standard' case
+ *  - Add objectCategory and some other attribute defaults
  *
  *  Author: Andrew Bartlett
  */
@@ -35,7 +37,6 @@
 
 #include "includes.h"
 #include "ldb_module.h"
-#include "dlinklist.h"
 #include "dsdb/samdb/samdb.h"
 #include "librpc/ndr/libndr.h"
 #include "librpc/gen_ndr/ndr_security.h"
 #include "auth/auth.h"
 #include "param/param.h"
 #include "../libds/common/flags.h"
+#include "dsdb/samdb/ldb_modules/util.h"
 
 struct oc_context {
 
        struct ldb_module *module;
        struct ldb_request *req;
+       const struct dsdb_schema *schema;
 
        struct ldb_reply *search_res;
+       struct ldb_reply *search_res2;
 
        int (*step_fn)(struct oc_context *);
 };
 
-struct class_list {
-       struct class_list *prev, *next;
-       const struct dsdb_class *objectclass;
-};
-
 static struct oc_context *oc_init_context(struct ldb_module *module,
                                          struct ldb_request *req)
 {
@@ -69,173 +68,71 @@ static struct oc_context *oc_init_context(struct ldb_module *module,
 
        ac = talloc_zero(req, struct oc_context);
        if (ac == NULL) {
-               ldb_set_errstring(ldb, "Out of Memory");
+               ldb_oom(ldb);
                return NULL;
        }
 
        ac->module = module;
        ac->req = req;
+       ac->schema = dsdb_get_schema(ldb, ac);
 
        return ac;
 }
 
 static int objectclass_do_add(struct oc_context *ac);
 
-/* Sort objectClasses into correct order, and validate that all
- * objectClasses specified actually exist in the schema
+/*
+ * 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 objectclass_sort(struct ldb_module *module,
-                           const struct dsdb_schema *schema,
-                           TALLOC_CTX *mem_ctx,
-                           struct ldb_message_element *objectclass_element,
-                           struct class_list **sorted_out) 
+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;
-       int i;
-       int layer;
-       struct class_list *sorted = NULL, *parent_class = NULL,
-               *subclass = NULL, *unsorted = NULL, *current, *poss_subclass, *poss_parent, *new_parent;
-
-       ldb = ldb_module_get_ctx(module);
-
-       /* DESIGN:
-        *
-        * We work on 4 different 'bins' (implemented here as linked lists):
-        *
-        * * sorted:       the eventual list, in the order we wish to push
-        *                 into the database.  This is the only ordered list.
-        *
-        * * parent_class: The current parent class 'bin' we are
-        *                 trying to find subclasses for
-        *
-        * * subclass:     The subclasses we have found so far
-        *
-        * * unsorted:     The remaining objectClasses
-        *
-        * The process is a matter of filtering objectClasses up from
-        * unsorted into sorted.  Order is irrelevent in the later 3 'bins'.
-        * 
-        * We start with 'top' (found and promoted to parent_class
-        * initially).  Then we find (in unsorted) all the direct
-        * subclasses of 'top'.  parent_classes is concatenated onto
-        * the end of 'sorted', and subclass becomes the list in
-        * parent_class.
-        *
-        * We then repeat, until we find no more subclasses.  Any left
-        * over classes are added to the end.
-        *
-        */
-
-       /* Firstly, dump all the objectClass elements into the
-        * unsorted bin, except for 'top', which is special */
-       for (i=0; i < objectclass_element->num_values; i++) {
-               current = talloc(mem_ctx, struct class_list);
-               if (!current) {
-                       ldb_oom(ldb);
-                       return LDB_ERR_OPERATIONS_ERROR;
-               }
-               current->objectclass = dsdb_class_by_lDAPDisplayName_ldb_val(schema, &objectclass_element->values[i]);
-               if (!current->objectclass) {
-                       ldb_asprintf_errstring(ldb, "objectclass %.*s is not a valid objectClass in schema", 
-                                              (int)objectclass_element->values[i].length, (const char *)objectclass_element->values[i].data);
-                       /* This looks weird, but windows apparently returns this for invalid objectClass values */
-                       return LDB_ERR_NO_SUCH_ATTRIBUTE;
-               } else if (current->objectclass->isDefunct) {
-                       ldb_asprintf_errstring(ldb, "objectclass %.*s marked as isDefunct objectClass in schema - not valid for new objects", 
-                                              (int)objectclass_element->values[i].length, (const char *)objectclass_element->values[i].data);
-                       /* This looks weird, but windows apparently returns this for invalid objectClass values */
-                       return LDB_ERR_NO_SUCH_ATTRIBUTE;
-               }
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       unsigned int i;
+       bool found;
 
-               /* this is the root of the tree.  We will start
-                * looking for subclasses from here */
-               if (ldb_attr_cmp("top", current->objectclass->lDAPDisplayName) == 0) {
-                       DLIST_ADD_END(parent_class, current, struct class_list *);
-               } else {
-                       DLIST_ADD_END(unsorted, current, struct class_list *);
-               }
+       if (schema == NULL) {
+               return LDB_SUCCESS;
        }
 
-       if (parent_class == NULL) {
-               current = talloc(mem_ctx, struct class_list);
-               current->objectclass = dsdb_class_by_lDAPDisplayName(schema, "top");
-               DLIST_ADD_END(parent_class, current, struct class_list *);
-       }
+       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;
 
-       /* For each object:  find parent chain */
-       for (current = unsorted; schema && current; 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;
-                       }
-               }
-               /* If we didn't get to the end of the list, we need to add this parent */
-               if (poss_parent || (ldb_attr_cmp("top", current->objectclass->subClassOf) == 0)) {
+               /* 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;
                }
 
-               new_parent = talloc(mem_ctx, struct class_list);
-               new_parent->objectclass = dsdb_class_by_lDAPDisplayName(schema, current->objectclass->subClassOf);
-               DLIST_ADD_END(unsorted, new_parent, struct class_list *);
-       }
-
-       /* DEBUGGING aid:  how many layers are we down now? */
-       layer = 0;
-       do {
-               layer++;
-               /* Find all the subclasses of classes in the
-                * parent_classes.  Push them onto the subclass list */
-
-               /* Ensure we don't bother if there are no unsorted entries left */
-               for (current = parent_class; schema && unsorted && current; current = current->next) {
-                       /* Walk the list of possible subclasses in unsorted */
-                       for (poss_subclass = unsorted; poss_subclass; ) {
-                               struct class_list *next;
-                               
-                               /* Save the next pointer, as the DLIST_ macros will change poss_subclass->next */
-                               next = poss_subclass->next;
-
-                               if (ldb_attr_cmp(poss_subclass->objectclass->subClassOf, current->objectclass->lDAPDisplayName) == 0) {
-                                       DLIST_REMOVE(unsorted, poss_subclass);
-                                       DLIST_ADD(subclass, poss_subclass);
-                                       
-                                       break;
-                               }
-                               poss_subclass = next;
+               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;
+               }
 
-               /* Now push the parent_classes as sorted, we are done with
-               these.  Add to the END of the list by concatenation */
-               DLIST_CONCATENATE(sorted, parent_class, struct class_list *);
-
-               /* and now find subclasses of these */
-               parent_class = subclass;
-               subclass = NULL;
-
-               /* If we didn't find any subclasses we will fall out
-                * the bottom here */
-       } while (parent_class);
-
-       if (!unsorted) {
-               *sorted_out = sorted;
-               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;
+               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)
@@ -298,6 +195,11 @@ static int oc_op_callback(struct ldb_request *req, struct ldb_reply *ares)
                return ldb_module_done(ac->req, NULL, NULL,
                                        LDB_ERR_OPERATIONS_ERROR);
        }
+
+       if (ares->type == LDB_REPLY_REFERRAL) {
+               return ldb_module_send_referral(ac->req, ares->referral);
+       }
+
        if (ares->error != LDB_SUCCESS) {
                return ldb_module_done(ac->req, ares->controls,
                                        ares->response, ares->error);
@@ -325,30 +227,37 @@ static int oc_op_callback(struct ldb_request *req, struct ldb_reply *ares)
     CN=Admins,CN=Users,DC=samba,DC=example,DC=com
    
  */
-static int fix_dn(TALLOC_CTX *mem_ctx, 
+static int fix_dn(struct ldb_context *ldb,
+                 TALLOC_CTX *mem_ctx,
                  struct ldb_dn *newdn, struct ldb_dn *parent_dn, 
                  struct ldb_dn **fixed_dn) 
 {
        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_ERR_OPERATIONS_ERROR;
+       if (upper_rdn_attr == NULL) {
+               return ldb_oom(ldb);
        }
 
        /* Create a new child */
        if (ldb_dn_add_child_fmt(*fixed_dn, "X=X") == false) {
-               return LDB_ERR_OPERATIONS_ERROR;
+               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
@@ -361,32 +270,10 @@ static int fix_dn(TALLOC_CTX *mem_ctx,
 #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);
 }
 
-/* Fix all attribute names to be in the correct case, and check they are all valid per the schema */
-static int fix_attributes(struct ldb_context *ldb, const struct dsdb_schema *schema, struct ldb_message *msg) 
-{
-       int i;
-       for (i=0; i < msg->num_elements; i++) {
-               const struct dsdb_attribute *attribute = dsdb_attribute_by_lDAPDisplayName(schema, msg->elements[i].name);
-               /* Add in a very special case for 'clearTextPassword',
-                * which is used for internal processing only, and is
-                * not presented in the schema */
-               if (!attribute) {
-                       if (strcasecmp(msg->elements[i].name, "clearTextPassword") != 0) {
-                               ldb_asprintf_errstring(ldb, "attribute %s is not a valid attribute in schema", msg->elements[i].name);
-                               /* Apparently Windows sends exactly this behaviour */
-                               return LDB_ERR_NO_SUCH_ATTRIBUTE;
-                       }
-               } else {
-                       msg->elements[i].name = attribute->lDAPDisplayName;
-               }
-       }
-
-       return LDB_SUCCESS;
-}
 
 static int objectclass_do_add(struct oc_context *ac);
 
@@ -396,8 +283,9 @@ static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
        struct ldb_request *search_req;
        struct oc_context *ac;
        struct ldb_dn *parent_dn;
+       const struct ldb_val *val;
        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);
 
@@ -408,15 +296,37 @@ static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
                return ldb_next_request(module, req);
        }
 
-       /* the objectClass must be specified on add */
-       if (ldb_msg_find_element(req->op.add.message, 
-                                "objectClass") == NULL) {
-               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       /* An add operation on the basedn without "NC-add" operation isn't
+        * allowed. */
+       if (ldb_dn_compare(ldb_get_default_basedn(ldb), req->op.add.message->dn) == 0) {
+               unsigned int instanceType;
+
+               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
+                        * component value as hostname. */
+                       val = ldb_dn_get_component_val(req->op.add.message->dn,
+                                                      ldb_dn_get_comp_num(req->op.add.message->dn) - 1);
+                       if (val == NULL) {
+                               return ldb_operr(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, referral_uri);
+               }
        }
 
        ac = oc_init_context(module, req);
        if (ac == NULL) {
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_operr(ldb);
        }
 
        /* If there isn't a parent, just go on to the add processing */
@@ -427,8 +337,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) {
-               ldb_oom(ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_operr(ldb);
        }
 
        ret = ldb_build_search_req(&search_req, ldb,
@@ -437,247 +346,315 @@ 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;
        }
-       talloc_steal(search_req, parent_dn);
 
        ac->step_fn = objectclass_do_add;
 
        return ldb_next_request(ac->module, search_req);
 }
 
+
+/*
+  check if this is a special RODC nTDSDSA add
+ */
+static bool check_rodc_ntdsdsa_add(struct oc_context *ac,
+                                  const struct dsdb_class *objectclass)
+{
+       struct ldb_control *rodc_control;
+
+       if (ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSDSA") != 0) {
+               return false;
+       }
+       rodc_control = ldb_request_get_control(ac->req, LDB_CONTROL_RODC_DCPROMO_OID);
+       if (!rodc_control) {
+               return false;
+       }
+
+       rodc_control->critical = false;
+       return true;
+}
+
 static int objectclass_do_add(struct oc_context *ac)
 {
-       struct ldb_context *ldb;
-       const struct dsdb_schema *schema;
+       struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
        struct ldb_request *add_req;
-       char *value;
-       struct ldb_message_element *objectclass_element;
+       struct ldb_message_element *objectclass_element, *el;
        struct ldb_message *msg;
        TALLOC_CTX *mem_ctx;
-       struct class_list *sorted, *current;
+       const char *rdn_name = NULL;
+       char *value;
+       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);
-       schema = dsdb_get_schema(ldb);
-
-       mem_ctx = talloc_new(ac);
-       if (mem_ctx == NULL) {
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-
        msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
+       if (msg == NULL) {
+               return ldb_module_oom(ac->module);
+       }
 
-       /* Check we have a valid parent */
+       /* Check if we have a valid parent - this check is needed since
+        * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
        if (ac->search_res == NULL) {
-               if (ldb_dn_compare(ldb_get_root_basedn(ldb),
-                                                               msg->dn) == 0) {
-                       /* Allow the tree to be started */
-                       
-                       /* but don't keep any error string, it's meaningless */
-                       ldb_set_errstring(ldb, NULL);
-               } else {
+               unsigned int instanceType;
+
+               /* An add operation on partition DNs without "NC-add" operation
+                * isn't allowed. */
+               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));
-                       talloc_free(mem_ctx);
                        return LDB_ERR_NO_SUCH_OBJECT;
                }
-       } else {
-               const struct ldb_val *parent_guid;
 
-               /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
-               ret = fix_dn(msg, 
+               /* Don't keep any error messages - we've to add a partition */
+               ldb_set_errstring(ldb, NULL);
+       } else {
+               /* Fix up the DN to be in the standard form, taking
+                * particular care to match the parent DN */
+               ret = fix_dn(ldb, msg,
                             ac->req->op.add.message->dn,
                             ac->search_res->message->dn,
                             &msg->dn);
-
                if (ret != LDB_SUCCESS) {
-                       ldb_asprintf_errstring(ldb, "Could not munge DN %s into normal form", 
+                       ldb_asprintf_errstring(ldb, "objectclass: Could not munge DN %s into normal form",
                                               ldb_dn_get_linearized(ac->req->op.add.message->dn));
-                       talloc_free(mem_ctx);
                        return ret;
                }
+       }
 
-               parent_guid = ldb_msg_find_ldb_val(ac->search_res->message, "objectGUID");
-               if (parent_guid == NULL) {
-                       ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, parent does not have an objectGUID!", 
+       if (ac->schema != NULL) {
+               /*
+                * Notice: by the normalization function call in "ldb_request()"
+                * case "LDB_ADD" we have always only *one* "objectClass"
+                * attribute at this stage!
+                */
+
+               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_UNWILLING_TO_PERFORM;                    
+                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
                }
-
-               ret = ldb_msg_add_steal_value(msg, "parentGUID", discard_const(parent_guid));
-               if (ret != LDB_SUCCESS) {
-                       ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, failed to add parentGUID", 
+               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));
-                       talloc_free(mem_ctx);
-                       return LDB_ERR_UNWILLING_TO_PERFORM;                                            
+                       return LDB_ERR_CONSTRAINT_VIOLATION;
                }
-       }
-       if (schema) {
-               ret = fix_attributes(ldb, schema, msg);
+
+               mem_ctx = talloc_new(ac);
+               if (mem_ctx == NULL) {
+                       return ldb_module_oom(ac->module);
+               }
+
+               /* Now do the sorting */
+               ret = dsdb_sort_objectClass_attr(ldb, ac->schema, mem_ctx,
+                                                objectclass_element, msg,
+                                                objectclass_element);
                if (ret != LDB_SUCCESS) {
                        talloc_free(mem_ctx);
                        return ret;
                }
 
-               /* This is now the objectClass list from the database */
-               objectclass_element = ldb_msg_find_element(msg, "objectClass");
+               talloc_free(mem_ctx);
 
-               if (!objectclass_element) {
-                       /* Where did it go?  bail now... */
-                       talloc_free(mem_ctx);
-                       return LDB_ERR_OPERATIONS_ERROR;
+               /*
+                * Get the new top-most structural object class and check for
+                * unrelated structural classes
+                */
+               objectclass = dsdb_get_last_structural_class(ac->schema,
+                                                            objectclass_element);
+               if (objectclass == NULL) {
+                       ldb_asprintf_errstring(ldb,
+                                              "Failed to find a structural class for %s",
+                                              ldb_dn_get_linearized(msg->dn));
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
                }
-               ret = objectclass_sort(ac->module, schema, mem_ctx, objectclass_element, &sorted);
+
+               ret = check_unrelated_objectclasses(ac->module, ac->schema,
+                                                   objectclass,
+                                                   objectclass_element);
                if (ret != LDB_SUCCESS) {
-                       talloc_free(mem_ctx);
                        return ret;
                }
-               
-               ldb_msg_remove_attr(msg, "objectClass");
-               ret = ldb_msg_add_empty(msg, "objectClass", 0, NULL);
-               
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(mem_ctx);
-                       return ret;
+
+               rdn_name = ldb_dn_get_rdn_name(msg->dn);
+               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]);
 
-               /* We must completely replace the existing objectClass entry,
-                * because we need it sorted */
+                       if (tmp_class == NULL) continue;
 
-               /* 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) {
-                               ldb_oom(ldb);
-                               talloc_free(mem_ctx);
-                               return LDB_ERR_OPERATIONS_ERROR;
-                       }
-                       ret = ldb_msg_add_string(msg, "objectClass", value);
-                       if (ret != LDB_SUCCESS) {
-                               ldb_set_errstring(ldb,
-                                                 "objectclass: could not re-add sorted "
-                                                 "objectclass to modify msg");
-                               talloc_free(mem_ctx);
-                               return ret;
-                       }
-                       /* Last one is the critical one */
-                       if (!current->next) {
-                               struct ldb_message_element *el;
-                               int32_t systemFlags = 0;
-                               const char *rdn_name = ldb_dn_get_rdn_name(msg->dn);
-                               if (current->objectclass->rDNAttID
-                                   && ldb_attr_cmp(rdn_name, current->objectclass->rDNAttID) != 0) {
-                                       ldb_asprintf_errstring(ldb,
-                                                              "RDN %s is not correct for most specific structural objectclass %s, should be %s",
-                                                              rdn_name, current->objectclass->lDAPDisplayName, current->objectclass->rDNAttID);
-                                       return LDB_ERR_NAMING_VIOLATION;
-                               }
+                       if (ldb_attr_cmp(rdn_name, tmp_class->rDNAttID) == 0)
+                               found = true;
+               }
+               if (!found) {
+                       ldb_asprintf_errstring(ldb,
+                                              "objectclass: Invalid RDN '%s' for objectclass '%s'!",
+                                              rdn_name, objectclass->lDAPDisplayName);
+                       return LDB_ERR_NAMING_VIOLATION;
+               }
 
-                               if (ac->search_res && ac->search_res->message) {
-                                       struct ldb_message_element *oc_el
-                                               = ldb_msg_find_element(ac->search_res->message, "objectClass");
+               if (objectclass->systemOnly &&
+                   !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) &&
+                   !check_rodc_ntdsdsa_add(ac, objectclass)) {
+                       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;
+               }
 
-                                       bool allowed_class = false;
-                                       int i, j;
-                                       for (i=0; allowed_class == false && oc_el && i < oc_el->num_values; i++) {
-                                               const struct dsdb_class *sclass;
+               if (ac->search_res && ac->search_res->message) {
+                       struct ldb_message_element *oc_el
+                               = ldb_msg_find_element(ac->search_res->message, "objectClass");
 
-                                               sclass = dsdb_class_by_lDAPDisplayName_ldb_val(schema, &oc_el->values[i]);
-                                               if (!sclass) {
-                                                       /* We don't know this class?  what is going on? */
-                                                       continue;
-                                               }
-                                               if (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
-                                                       for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
-                                                               if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
-                                                                       allowed_class = true;
-                                                                       break;
-                                                               }
-                                                       }
-                                               } else {
-                                                       for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
-                                                               if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
-                                                                       allowed_class = true;
-                                                                       break;
-                                                               }
-                                                       }
-                                               }
-                                       }
+                       bool allowed_class = false;
+                       for (i=0; allowed_class == false && oc_el && i < oc_el->num_values; i++) {
+                               const struct dsdb_class *sclass;
 
-                                       if (!allowed_class) {
-                                               ldb_asprintf_errstring(ldb, "structural objectClass %s is not a valid child class for %s",
-                                                              current->objectclass->lDAPDisplayName, ldb_dn_get_linearized(ac->search_res->message->dn));
-                                               return LDB_ERR_NAMING_VIOLATION;
+                               sclass = dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+                                                                              &oc_el->values[i]);
+                               if (!sclass) {
+                                       /* We don't know this class?  what is going on? */
+                                       continue;
+                               }
+                               for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
+                                       if (ldb_attr_cmp(objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
+                                               allowed_class = true;
+                                               break;
                                        }
                                }
+                       }
 
-                               if (current->objectclass->systemOnly && !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
-                                       ldb_asprintf_errstring(ldb, "objectClass %s is systemOnly, rejecting creation of %s",
-                                                              current->objectclass->lDAPDisplayName, ldb_dn_get_linearized(msg->dn));
-                                       return LDB_ERR_UNWILLING_TO_PERFORM;
-                               }
+                       if (!allowed_class) {
+                               ldb_asprintf_errstring(ldb, "structural objectClass %s is not a valid child class for %s",
+                                               objectclass->lDAPDisplayName, ldb_dn_get_linearized(ac->search_res->message->dn));
+                               return LDB_ERR_NAMING_VIOLATION;
+                       }
+               }
 
-                               if (!ldb_msg_find_element(msg, "objectCategory")) {
-                                       value = talloc_strdup(msg, current->objectclass->defaultObjectCategory);
-                                       if (value == NULL) {
-                                               ldb_oom(ldb);
-                                               talloc_free(mem_ctx);
-                                               return LDB_ERR_OPERATIONS_ERROR;
-                                       }
-                                       ldb_msg_add_string(msg, "objectCategory", value);
-                               }
-                               if (!ldb_msg_find_element(msg, "showInAdvancedViewOnly") && (current->objectclass->defaultHidingValue == true)) {
-                                       ldb_msg_add_string(msg, "showInAdvancedViewOnly",
-                                                          "TRUE");
+               objectcategory = ldb_msg_find_attr_as_dn(ldb, ac, msg,
+                                                        "objectCategory");
+               if (objectcategory == NULL) {
+                       struct dsdb_extended_dn_store_format *dn_format =
+                                       talloc_get_type(ldb_module_get_private(ac->module),
+                                                       struct dsdb_extended_dn_store_format);
+                       if (dn_format && dn_format->store_extended_dn_in_ldb == false) {
+                               /* Strip off extended components */
+                               struct ldb_dn *dn = ldb_dn_new(ac, ldb,
+                                                              objectclass->defaultObjectCategory);
+                               value = ldb_dn_alloc_linearized(msg, dn);
+                               talloc_free(dn);
+                       } else {
+                               value = talloc_strdup(msg,
+                                                     objectclass->defaultObjectCategory);
+                       }
+                       if (value == NULL) {
+                               return ldb_module_oom(ac->module);
+                       }
+
+                       ret = ldb_msg_add_string(msg, "objectCategory", value);
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+               } else {
+                       const struct dsdb_class *ocClass =
+                                       dsdb_class_by_cn_ldb_val(ac->schema,
+                                                                ldb_dn_get_rdn_val(objectcategory));
+                       if (ocClass != NULL) {
+                               struct ldb_dn *dn = ldb_dn_new(ac, ldb,
+                                                              ocClass->defaultObjectCategory);
+                               if (ldb_dn_compare(objectcategory, dn) != 0) {
+                                       ocClass = NULL;
                                }
+                       }
+                       talloc_free(objectcategory);
+                       if (ocClass == NULL) {
+                               ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, 'objectCategory' attribute invalid!",
+                                                      ldb_dn_get_linearized(msg->dn));
+                               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+                       }
+               }
 
-                               /* There are very special rules for systemFlags, see MS-ADTS 3.1.1.5.2.4 */
-                               el = ldb_msg_find_element(msg, "systemFlags");
+               if (!ldb_msg_find_element(msg, "showInAdvancedViewOnly") && (objectclass->defaultHidingValue == true)) {
+                       ldb_msg_add_string(msg, "showInAdvancedViewOnly",
+                                               "TRUE");
+               }
 
-                               systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0);
+               /* There are very special rules for systemFlags, see MS-ADTS
+                * MS-ADTS 3.1.1.5.2.4 */
 
-                               if (el) {
-                                       /* 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); */
-                                       ldb_msg_remove_element(msg, el);
-                               }
+               el = ldb_msg_find_element(msg, "systemFlags");
+               if ((el != NULL) && (el->num_values > 1)) {
+                       ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, 'systemFlags' attribute multivalued!",
+                                              ldb_dn_get_linearized(msg->dn));
+                       return LDB_ERR_CONSTRAINT_VIOLATION;
+               }
 
-                               /* This flag is only allowed on attributeSchema objects */
-                               if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "attributeSchema") == 0) {
-                                       systemFlags &= ~SYSTEM_FLAG_ATTR_IS_RDN;
-                               }
+               systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0);
 
-                               if (ldb_attr_cmp(current->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(current->objectclass->lDAPDisplayName, "site") == 0
-                                          || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "serverContainer") == 0
-                                          || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "ntDSDSA") == 0) {
-                                       systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
-
-                               } else if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "siteLink") == 0
-                                          || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "siteLinkBridge") == 0
-                                          || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "nTDSConnection") == 0) {
-                                       systemFlags |= (int32_t)(SYSTEM_FLAG_CONFIG_ALLOW_RENAME);
-                               }
+               ldb_msg_remove_attr(msg, "systemFlags");
 
-                               /* TODO: If parent object is site or subnet, also add (SYSTEM_FLAG_CONFIG_ALLOW_RENAME) */
+               /* 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 );
+               }
 
-                               if (el || systemFlags != 0) {
-                                       samdb_msg_add_int(ldb, msg, msg, "systemFlags", systemFlags);
-                               }
-                       }
+               /* 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;
                }
-       }
 
-       talloc_free(mem_ctx);
-       ret = ldb_msg_sanity_check(ldb, msg);
+               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, "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) {
+                       ret = samdb_msg_add_int(ldb, msg, msg, "systemFlags",
+                                               systemFlags);
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+               }
 
-       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,
@@ -685,6 +662,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;
        }
@@ -702,12 +680,9 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
        struct ldb_context *ldb = ldb_module_get_ctx(module);
        struct ldb_message_element *objectclass_element;
        struct ldb_message *msg;
-       const struct dsdb_schema *schema = dsdb_get_schema(ldb);
-       struct class_list *sorted, *current;
        struct ldb_request *down_req;
        struct oc_context *ac;
-       TALLOC_CTX *mem_ctx;
-       char *value;
+       bool oc_changes = false;
        int ret;
 
        ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_modify\n");
@@ -716,11 +691,6 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
        if (ldb_dn_is_special(req->op.mod.message->dn)) {
                return ldb_next_request(module, req);
        }
-       
-       /* Without schema, there isn't much to do here */
-       if (!schema) {
-               return ldb_next_request(module, req);
-       }
 
        /* As with the "real" AD we don't accept empty messages */
        if (req->op.mod.message->num_elements == 0) {
@@ -731,145 +701,57 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
 
        ac = oc_init_context(module, req);
        if (ac == NULL) {
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_operr(ldb);
        }
 
-       /* If no part of this touches the objectClass, then we don't
-        * need to make any changes.  */
-       objectclass_element = ldb_msg_find_element(req->op.mod.message, "objectClass");
-
-       /* If the only operation is the deletion of the objectClass
-        * then go on with just fixing the attribute case */
-       if (!objectclass_element) {
-               msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
-               if (msg == NULL) {
-                       return LDB_ERR_OPERATIONS_ERROR;
-               }
-               
-               ret = fix_attributes(ldb, schema, msg);
-               if (ret != LDB_SUCCESS) {
-                       return ret;
-               }
-
-               ret = ldb_build_mod_req(&down_req, ldb, ac,
-                                       msg,
-                                       req->controls,
-                                       ac, oc_op_callback,
-                                       req);
-               if (ret != LDB_SUCCESS) {
-                       return ret;
-               }
-
-               /* go on with the call chain */
-               return ldb_next_request(module, down_req);
+       /* Without schema, there isn't much to do here */
+       if (ac->schema == NULL) {
+               talloc_free(ac);
+               return ldb_next_request(module, req);
        }
 
-       switch (objectclass_element->flags & LDB_FLAG_MOD_MASK) {
-       case LDB_FLAG_MOD_DELETE:
-               if (objectclass_element->num_values == 0) {
-                       return LDB_ERR_OBJECT_CLASS_MODS_PROHIBITED;
-               }
-               break;
-
-       case LDB_FLAG_MOD_REPLACE:
-               mem_ctx = talloc_new(ac);
-               if (mem_ctx == NULL) {
-                       return LDB_ERR_OPERATIONS_ERROR;
-               }
-
-               msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
-               if (msg == NULL) {
-                       talloc_free(mem_ctx);
-                       return LDB_ERR_OPERATIONS_ERROR;
-               }
-
-               ret = fix_attributes(ldb, schema, msg);
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(mem_ctx);
-                       return ret;
-               }
+       msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
+       if (msg == NULL) {
+               return ldb_module_oom(ac->module);
+       }
 
-               ret = objectclass_sort(module, schema, mem_ctx, objectclass_element, &sorted);
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(mem_ctx);
-                       return ret;
-               }
+       /* For now change everything except the objectclasses */
 
-               /* We must completely replace the existing objectClass entry,
-                * because we need it sorted */
-               
+       objectclass_element = ldb_msg_find_element(msg, "objectClass");
+       if (objectclass_element != NULL) {
                ldb_msg_remove_attr(msg, "objectClass");
-               ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
-               
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(mem_ctx);
-                       return ret;
-               }
+               oc_changes = true;
+       }
 
-               /* Move from the linked list back into an ldb msg */
-               for (current = sorted; current; current = current->next) {
-                       /* copy the value as this string is on the schema
-                        * context and we can't rely on it not changing
-                        * before the operation is over */
-                       value = talloc_strdup(msg,
-                                       current->objectclass->lDAPDisplayName);
-                       if (value == NULL) {
-                               ldb_oom(ldb);
-                               talloc_free(mem_ctx);
-                               return LDB_ERR_OPERATIONS_ERROR;
-                       }
-                       ret = ldb_msg_add_string(msg, "objectClass", value);
-                       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);
+       /* 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 = ldb_msg_sanity_check(ldb, msg);
+               ret = dsdb_find_nc_root(ldb, ac, req->op.mod.message->dn,
+                                       &nc_root);
                if (ret != LDB_SUCCESS) {
                        return ret;
                }
 
-               ret = ldb_build_mod_req(&down_req, ldb, ac,
-                                       msg,
-                                       req->controls,
-                                       ac, oc_op_callback,
-                                       req);
-               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;
                }
 
-               /* go on with the call chain */
-               return ldb_next_request(module, down_req);
-       }
-
-       /* This isn't the default branch of the switch, but a 'in any
-        * other case'.  When a delete isn't for all objectClasses for
-        * example
-        */
-
-       msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
-       if (msg == NULL) {
-               ldb_oom(ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-
-       ret = fix_attributes(ldb, schema, msg);
-       if (ret != LDB_SUCCESS) {
-               ldb_oom(ldb);
-               return ret;
+               talloc_free(nc_root);
        }
 
        ret = ldb_build_mod_req(&down_req, ldb, ac,
                                msg,
-                               req->controls,
-                               ac, oc_modify_callback,
+                               req->controls, ac,
+                               oc_changes ? oc_modify_callback : oc_op_callback,
                                req);
+       LDB_REQ_SET_LOCATION(down_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -879,8 +761,8 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
 
 static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
 {
-       struct ldb_context *ldb;
        static const char * const attrs[] = { "objectClass", NULL };
+       struct ldb_context *ldb;
        struct ldb_request *search_req;
        struct oc_context *ac;
        int ret;
@@ -892,6 +774,11 @@ static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
                return ldb_module_done(ac->req, NULL, NULL,
                                        LDB_ERR_OPERATIONS_ERROR);
        }
+
+       if (ares->type == LDB_REPLY_REFERRAL) {
+               return ldb_module_send_referral(ac->req, ares->referral);
+       }
+
        if (ares->error != LDB_SUCCESS) {
                return ldb_module_done(ac->req, ares->controls,
                                        ares->response, ares->error);
@@ -905,12 +792,16 @@ static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
 
        talloc_free(ares);
 
-       ret = ldb_build_search_req(&search_req, ldb, ac,
-                                  ac->req->op.mod.message->dn, LDB_SCOPE_BASE,
+       /* this looks up the real existing object for fetching some important
+        * information (objectclasses) */
+       ret = ldb_build_search_req(&search_req, ldb,
+                                  ac, ac->req->op.mod.message->dn,
+                                  LDB_SCOPE_BASE,
                                   "(objectClass=*)",
                                   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);
        }
@@ -921,103 +812,187 @@ static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
        if (ret != LDB_SUCCESS) {
                return ldb_module_done(ac->req, NULL, NULL, ret);
        }
+
        return LDB_SUCCESS;
 }
 
 static int objectclass_do_mod(struct oc_context *ac)
 {
        struct ldb_context *ldb;
-       const struct dsdb_schema *schema;
        struct ldb_request *mod_req;
-       char *value;
-       struct ldb_message_element *objectclass_element;
+       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, k;
+       bool found;
        int ret;
 
        ldb = ldb_module_get_ctx(ac->module);
 
+       /* we should always have a valid entry when we enter here */
        if (ac->search_res == NULL) {
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_operr(ldb);
        }
-       schema = dsdb_get_schema(ldb);
 
-       mem_ctx = talloc_new(ac);
-       if (mem_ctx == NULL) {
-               return LDB_ERR_OPERATIONS_ERROR;
+       oc_el_entry = ldb_msg_find_element(ac->search_res->message,
+                                          "objectClass");
+       if (oc_el_entry == NULL) {
+               /* existing entry without a valid object class? */
+               return ldb_operr(ldb);
        }
 
        /* use a new message structure */
        msg = ldb_msg_new(ac);
        if (msg == NULL) {
-               ldb_set_errstring(ldb,
-                       "objectclass: could not create new modify msg");
-               talloc_free(mem_ctx);
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_module_oom(ac->module);
        }
 
-       /* This is now the objectClass list from the database */
-       objectclass_element = ldb_msg_find_element(ac->search_res->message, 
-                                                  "objectClass");
-       if (!objectclass_element) {
-               /* Where did it go?  bail now... */
-               talloc_free(mem_ctx);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-       
-       /* modify dn */
        msg->dn = ac->req->op.mod.message->dn;
 
-       ret = objectclass_sort(ac->module, schema, mem_ctx, objectclass_element, &sorted);
-       if (ret != LDB_SUCCESS) {
-               return ret;
+       mem_ctx = talloc_new(ac);
+       if (mem_ctx == NULL) {
+               return ldb_module_oom(ac->module);
        }
 
-       /* We must completely replace the existing objectClass entry.
-        * We could do a constrained add/del, but we are meant to be
-        * in a transaction... */
+       /* 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;
+               }
 
-       ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
-       if (ret != LDB_SUCCESS) {
-               ldb_set_errstring(ldb, "objectclass: could not clear objectclass in modify msg");
-               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) {
-                       ldb_oom(ldb);
-                       return LDB_ERR_OPERATIONS_ERROR;
+               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_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);
+                       }
+
+                       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;
+                               }
+                       }
+
+                       break;
+               }
+
+               /* Now do the sorting */
+               ret = dsdb_sort_objectClass_attr(ldb, ac->schema, mem_ctx,
+                                                oc_el_entry, msg, oc_el_entry);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(mem_ctx);
+                       return ret;
                }
-               ret = ldb_msg_add_string(msg, "objectClass", value);
+
+               /*
+                * Get the new top-most structural object class and check for
+                * unrelated structural classes
+                */
+               objectclass = dsdb_get_last_structural_class(ac->schema,
+                                                            oc_el_entry);
+               if (objectclass == NULL) {
+                       ldb_set_errstring(ldb,
+                                         "objectclass: cannot delete all structural objectclasses!");
+                       talloc_free(mem_ctx);
+                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
+               }
+
+               /* Check for unrelated objectclasses */
+               ret = check_unrelated_objectclasses(ac->module, ac->schema,
+                                                   objectclass,
+                                                   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;
                }
        }
 
-       ret = ldb_msg_sanity_check(ldb, msg);
+       talloc_free(mem_ctx);
+
+       /* Now add the new object class attribute to the change message */
+       ret = ldb_msg_add(msg, oc_el_entry, LDB_FLAG_MOD_REPLACE);
        if (ret != LDB_SUCCESS) {
-               talloc_free(mem_ctx);
+               ldb_module_oom(ac->module);
                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) {
-               talloc_free(mem_ctx);
                return ret;
        }
 
-       talloc_free(mem_ctx);
-       /* perform the modify */
        return ldb_next_request(ac->module, mod_req);
 }
 
@@ -1025,7 +1000,7 @@ static int objectclass_do_rename(struct oc_context *ac);
 
 static int objectclass_rename(struct ldb_module *module, struct ldb_request *req)
 {
-       static const char * const attrs[] = { "objectGUID", NULL };
+       static const char * const attrs[] = { "objectClass", NULL };
        struct ldb_context *ldb;
        struct ldb_request *search_req;
        struct oc_context *ac;
@@ -1036,45 +1011,41 @@ static int objectclass_rename(struct ldb_module *module, struct ldb_request *req
 
        ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_rename\n");
 
-       if (ldb_dn_is_special(req->op.rename.newdn)) { /* do not manipulate our control entries */
+       /* do not manipulate our control entries */
+       if (ldb_dn_is_special(req->op.rename.olddn)) {
                return ldb_next_request(module, req);
        }
 
-       /* Firstly ensure we are not trying to rename it to be a child of itself */
-       if ((ldb_dn_compare_base(req->op.rename.olddn, req->op.rename.newdn) == 0) 
-           && (ldb_dn_compare(req->op.rename.olddn, req->op.rename.newdn) != 0)) {
-               ldb_asprintf_errstring(ldb, "Cannot rename %s to be a child of itself",
-                                      ldb_dn_get_linearized(req->op.rename.olddn));
-               return LDB_ERR_UNWILLING_TO_PERFORM;
-       }
-
        ac = oc_init_context(module, req);
        if (ac == NULL) {
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_operr(ldb);
        }
 
        parent_dn = ldb_dn_get_parent(ac, req->op.rename.newdn);
        if (parent_dn == NULL) {
-               ldb_oom(ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
+               ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, the parent DN does not exist!",
+                                      ldb_dn_get_linearized(req->op.rename.olddn));
+               return LDB_ERR_NO_SUCH_OBJECT;
        }
 
-       /* note that the results of this search are kept and used to
-          update the parentGUID in objectclass_rename_callback() */
+       /* this looks up the parent object for fetching some important
+        * information (objectclasses, DN normalisation...) */
        ret = ldb_build_search_req(&search_req, ldb,
                                   ac, parent_dn, LDB_SCOPE_BASE,
                                   "(objectClass=*)",
-                                  attrs, NULL, 
+                                  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;
@@ -1085,68 +1056,50 @@ static int objectclass_rename(struct ldb_module *module, struct ldb_request *req
        return ldb_next_request(ac->module, search_req);
 }
 
-/* 
-   called after the rename happens. 
-   We now need to fix the parentGUID of the object to be the objectGUID of
-   the new parent 
-*/
-static int objectclass_rename_callback(struct ldb_request *req, struct ldb_reply *ares)
+static int objectclass_do_rename2(struct oc_context *ac);
+
+static int objectclass_do_rename(struct oc_context *ac)
 {
+       static const char * const attrs[] = { "objectClass", NULL };
        struct ldb_context *ldb;
-       struct oc_context *ac;
-       const struct ldb_val *parent_guid;
-       struct ldb_request *mod_req = NULL;
+       struct ldb_request *search_req;
        int ret;
-       struct ldb_message *msg;
-       struct ldb_message_element *el = NULL;
 
-       ac = talloc_get_type(req->context, struct oc_context);
        ldb = ldb_module_get_ctx(ac->module);
 
-       /* make sure the rename succeeded */
-       if (!ares) {
-               return ldb_module_done(ac->req, NULL, NULL,
-                                       LDB_ERR_OPERATIONS_ERROR);
-       }
-       if (ares->error != LDB_SUCCESS) {
-               return ldb_module_done(ac->req, ares->controls,
-                                       ares->response, ares->error);
-       }
-
-       talloc_free(ares);
-
-       /* the ac->search_res should contain the new parents objectGUID */
-       parent_guid = ldb_msg_find_ldb_val(ac->search_res->message, "objectGUID");
-       if (parent_guid == NULL) {
-               ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, new parent does not have an objectGUID!", 
-                                      ldb_dn_get_linearized(ac->req->op.rename.newdn));
-               return LDB_ERR_UNWILLING_TO_PERFORM;
-
-       }
-
-       /* construct the modify message */
-       msg = ldb_msg_new(ac);
-       if (msg == NULL) {
-               ldb_oom(ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
+       /* Check if we have a valid parent - this check is needed since
+        * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
+       if (ac->search_res == NULL) {
+               ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, parent does not exist!",
+                                      ldb_dn_get_linearized(ac->req->op.rename.olddn));
+               return LDB_ERR_OTHER;
        }
 
-       msg->dn = ac->req->op.rename.newdn;
+       /* now assign "search_res2" to the parent entry to have "search_res"
+        * free for another lookup */
+       ac->search_res2 = ac->search_res;
+       ac->search_res = NULL;
 
-       ret = ldb_msg_add_value(msg, "parentGUID", parent_guid, &el);
+       /* this looks up the real existing object for fetching some important
+        * information (objectclasses) */
+       ret = ldb_build_search_req(&search_req, ldb,
+                                  ac, ac->req->op.rename.olddn,
+                                  LDB_SCOPE_BASE,
+                                  "(objectClass=*)",
+                                  attrs, NULL,
+                                  ac, get_search_callback,
+                                  ac->req);
+       LDB_REQ_SET_LOCATION(search_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
 
-       el->flags = LDB_FLAG_MOD_REPLACE;
+       ac->step_fn = objectclass_do_rename2;
 
-       ret = ldb_build_mod_req(&mod_req, ldb, ac, msg,
-                               NULL, ac, oc_op_callback, req);
-
-       return ldb_next_request(ac->module, mod_req);
+       return ldb_next_request(ac->module, search_req);
 }
 
-static int objectclass_do_rename(struct oc_context *ac)
+static int objectclass_do_rename2(struct oc_context *ac)
 {
        struct ldb_context *ldb;
        struct ldb_request *rename_req;
@@ -1155,32 +1108,118 @@ static int objectclass_do_rename(struct oc_context *ac)
 
        ldb = ldb_module_get_ctx(ac->module);
 
-       /* Check we have a valid parent */
+       /* Check if we have a valid entry - this check is needed since
+        * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
        if (ac->search_res == NULL) {
-               ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, parent does not exist!", 
-                                      ldb_dn_get_linearized(ac->req->op.rename.newdn));
+               ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, entry does not exist!",
+                                      ldb_dn_get_linearized(ac->req->op.rename.olddn));
+               return LDB_ERR_NO_SUCH_OBJECT;
+       }
+
+       if (ac->schema != NULL) {
+               struct ldb_message_element *oc_el_entry, *oc_el_parent;
+               const struct dsdb_class *objectclass;
+               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");
+               if (oc_el_entry == NULL) {
+                       /* existing entry without a valid object class? */
+                       return ldb_operr(ldb);
+               }
+               objectclass = dsdb_get_last_structural_class(ac->schema,
+                                                            oc_el_entry);
+               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 (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: Invalid RDN '%s' for objectclass '%s'!",
+                                              rdn_name, objectclass->lDAPDisplayName);
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               }
+
+               oc_el_parent = ldb_msg_find_element(ac->search_res2->message,
+                                                   "objectClass");
+               if (oc_el_parent == NULL) {
+                       /* existing entry without a valid object class? */
+                       return ldb_operr(ldb);
+               }
+
+               for (i=0; allowed_class == false && i < oc_el_parent->num_values; i++) {
+                       const struct dsdb_class *sclass;
+
+                       sclass = dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+                                                                      &oc_el_parent->values[i]);
+                       if (!sclass) {
+                               /* We don't know this class?  what is going on? */
+                               continue;
+                       }
+                       for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
+                               if (ldb_attr_cmp(objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
+                                       allowed_class = true;
+                                       break;
+                               }
+                       }
+               }
+
+               if (!allowed_class) {
+                       ldb_asprintf_errstring(ldb,
+                                              "objectclass: structural objectClass %s is not a valid child class for %s",
+                                              objectclass->lDAPDisplayName, ldb_dn_get_linearized(ac->search_res2->message->dn));
+                       return LDB_ERR_NAMING_VIOLATION;
+               }
+       }
+
+       /* Ensure we are not trying to rename it to be a child of itself */
+       if ((ldb_dn_compare_base(ac->req->op.rename.olddn,
+                                ac->req->op.rename.newdn) == 0)  &&
+           (ldb_dn_compare(ac->req->op.rename.olddn,
+                           ac->req->op.rename.newdn) != 0)) {
+               ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s to be a child of itself",
+                                      ldb_dn_get_linearized(ac->req->op.rename.olddn));
                return LDB_ERR_UNWILLING_TO_PERFORM;
        }
-       
-       /* Fix up the DN to be in the standard form,
-        * taking particular care to match the parent DN */
-       ret = fix_dn(ac,
+
+       /* Fix up the DN to be in the standard form, taking
+        * particular care to match the parent DN */
+       ret = fix_dn(ldb, ac,
                     ac->req->op.rename.newdn,
-                    ac->search_res->message->dn,
+                    ac->search_res2->message->dn,
                     &fixed_dn);
        if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb, "objectclass: Could not munge DN %s into normal form",
+                                      ldb_dn_get_linearized(ac->req->op.rename.newdn));
                return ret;
-       }
 
-       /* TODO: Check this is a valid child to this parent,
-        * by reading the allowedChildClasses and
-        * allowedChildClasssesEffective attributes */
+       }
 
        ret = ldb_build_rename_req(&rename_req, ldb, ac,
                                   ac->req->op.rename.olddn, fixed_dn,
                                   ac->req->controls,
-                                  ac, objectclass_rename_callback,
+                                  ac, oc_op_callback,
                                   ac->req);
+       LDB_REQ_SET_LOCATION(rename_req);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -1189,9 +1228,212 @@ static int objectclass_do_rename(struct oc_context *ac)
        return ldb_next_request(ac->module, rename_req);
 }
 
-_PUBLIC_ const struct ldb_module_ops ldb_objectclass_module_ops = {
-       .name              = "objectclass",
-       .add           = objectclass_add,
-       .modify        = objectclass_modify,
-       .rename        = objectclass_rename,
+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",
+                                             "isDeleted",
+                                             "isCriticalSystemObject", NULL };
+       struct ldb_context *ldb;
+       struct ldb_request *search_req;
+       struct oc_context *ac;
+       int ret;
+
+       ldb = ldb_module_get_ctx(module);
+
+       ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_delete\n");
+
+       /* do not manipulate our control entries */
+       if (ldb_dn_is_special(req->op.del.dn)) {
+               return ldb_next_request(module, req);
+       }
+
+       /* Bypass the constraint checks when we do have the "RELAX" control
+        * set. */
+       if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID) != NULL) {
+               return ldb_next_request(module, req);
+       }
+
+       ac = oc_init_context(module, req);
+       if (ac == NULL) {
+               return ldb_operr(ldb);
+       }
+
+       /* this looks up the entry object for fetching some important
+        * information (object classes, system flags...) */
+       ret = ldb_build_search_req(&search_req, ldb,
+                                  ac, req->op.del.dn, LDB_SCOPE_BASE,
+                                  "(objectClass=*)",
+                                  attrs, req->controls,
+                                  ac, get_search_callback,
+                                  req);
+       LDB_REQ_SET_LOCATION(search_req);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+
+       ac->step_fn = objectclass_do_delete;
+
+       return ldb_next_request(ac->module, search_req);
+}
+
+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);
+
+       /* Check if we have a valid entry - this check is needed since
+        * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
+       if (ac->search_res == NULL) {
+               ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, entry does not exist!",
+                                      ldb_dn_get_linearized(ac->req->op.del.dn));
+               return LDB_ERR_NO_SUCH_OBJECT;
+       }
+
+       /* DC's ntDSDSA object */
+       if (ldb_dn_compare(ac->req->op.del.dn, samdb_ntds_settings_dn(ldb)) == 0) {
+               ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's the DC's ntDSDSA object!",
+                                      ldb_dn_get_linearized(ac->req->op.del.dn));
+               return LDB_ERR_UNWILLING_TO_PERFORM;
+       }
+
+       /* DC's rIDSet object */
+       /* Perform this check only when it does exist - this is needed in order
+        * to don't let existing provisions break. */
+       ret = samdb_rid_set_dn(ldb, ac, &dn);
+       if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) {
+               return ret;
+       }
+       if (ret == LDB_SUCCESS) {
+               if (ldb_dn_compare(ac->req->op.del.dn, dn) == 0) {
+                       talloc_free(dn);
+                       ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's the DC's rIDSet object!",
+                                              ldb_dn_get_linearized(ac->req->op.del.dn));
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               }
+               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)) {
+                       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 schema partition!",
+                                              ldb_dn_get_linearized(ac->req->op.del.dn));
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               }
+               talloc_free(dn);
+       }
+
+       /* systemFlags */
+
+       systemFlags = ldb_msg_find_attr_as_int(ac->search_res->message,
+                                              "systemFlags", 0);
+       if ((systemFlags & SYSTEM_FLAG_DISALLOW_DELETE) != 0) {
+               ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it isn't permitted!",
+                                      ldb_dn_get_linearized(ac->req->op.del.dn));
+               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);
+}
+
+static int objectclass_init(struct ldb_module *module)
+{
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       int ret;
+
+       /* Init everything else */
+       ret = ldb_next_init(module);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+       
+       /* 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;
+}
+
+static const struct ldb_module_ops ldb_objectclass_module_ops = {
+       .name           = "objectclass",
+       .add            = objectclass_add,
+       .modify         = objectclass_modify,
+       .rename         = objectclass_rename,
+       .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);
+}