Add showInAdvancedViewOnly to every new object
[samba.git] / source4 / dsdb / samdb / ldb_modules / objectclass.c
index f7b2da9b839a439ab7477b857829ea4f07679ac8..871c38476b912410a0b47040eff4eecfdeb0e492 100644 (file)
  *
  *  Component: objectClass sorting module
  *
- *  Description: sort the objectClass attribute into the class hierarchy
+ *  Description: 
+ *  - sort the objectClass attribute into the class
+ *    hierarchy, 
+ *  - fix DNs and attributes into 'standard' case
+ *  - Add objectCategory and ntSecurityDescriptor defaults
  *
  *  Author: Andrew Bartlett
  */
 #include "librpc/gen_ndr/ndr_security.h"
 #include "libcli/security/security.h"
 #include "auth/auth.h"
+#include "param/param.h"
 
 struct oc_context {
 
-       enum oc_step {OC_DO_REQ, OC_SEARCH_SELF, OC_DO_MOD} step;
+       enum oc_step {OC_DO_REQ, OC_SEARCH_SELF, OC_DO_MOD, 
+                     OC_SEARCH_ADD_PARENT, OC_DO_ADD, 
+                     OC_SEARCH_RENAME_PARENT, OC_DO_RENAME} step;
 
        struct ldb_module *module;
        struct ldb_request *orig_req;
@@ -52,14 +59,18 @@ struct oc_context {
        struct ldb_request *search_req;
        struct ldb_reply *search_res;
 
+       struct ldb_request *add_req;
        struct ldb_request *mod_req;
+       struct ldb_request *rename_req;
 };
 
 struct class_list {
        struct class_list *prev, *next;
-       const char *objectclass;
+       const struct dsdb_class *objectclass;
 };
 
+static int objectclass_do_add(struct ldb_handle *h);
+
 static struct ldb_handle *oc_init_handle(struct ldb_request *req, struct ldb_module *module)
 {
        struct oc_context *ac;
@@ -91,16 +102,21 @@ static struct ldb_handle *oc_init_handle(struct ldb_request *req, struct ldb_mod
        return h;
 }
 
+/* Sort objectClasses into correct order, and validate that all
+ * objectClasses specified actually exist in the schema
+ */
+
 static int objectclass_sort(struct ldb_module *module,
+                           const struct dsdb_schema *schema,
+                           struct ldb_message *msg, /* so that when we create new elements, we put it on the right parent */
                            TALLOC_CTX *mem_ctx,
                            struct ldb_message_element *objectclass_element,
                            struct class_list **sorted_out) 
 {
        int i;
        int layer;
-       const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
        struct class_list *sorted = NULL, *parent_class = NULL,
-               *subclass = NULL, *unsorted = NULL, *current, *poss_subclass;
+               *subclass = NULL, *unsorted = NULL, *current, *poss_subclass, *poss_parent, *new_parent;
        /* DESIGN:
         *
         * We work on 4 different 'bins' (implemented here as linked lists):
@@ -138,17 +154,44 @@ static int objectclass_sort(struct ldb_module *module,
                        talloc_free(mem_ctx);
                        return LDB_ERR_OPERATIONS_ERROR;
                }
-               current->objectclass = (const char *)objectclass_element->values[i].data;
+               current->objectclass = dsdb_class_by_lDAPDisplayName(schema, (const char *)objectclass_element->values[i].data);
+               if (!current->objectclass) {
+                       ldb_asprintf_errstring(module->ldb, "objectclass %s is not a valid objectClass in schema", (const char *)objectclass_element->values[i].data);
+                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
+               }
 
                /* this is the root of the tree.  We will start
                 * looking for subclasses from here */
-               if (ldb_attr_cmp("top", current->objectclass) == 0) {
+               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 (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 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)) {
+                       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 {
@@ -160,13 +203,12 @@ static int objectclass_sort(struct ldb_module *module,
                for (current = parent_class; schema && unsorted && current; current = current->next) {
                        /* Walk the list of possible subclasses in unsorted */
                        for (poss_subclass = unsorted; poss_subclass; ) {
-                               const struct dsdb_class *class = dsdb_class_by_lDAPDisplayName(schema, poss_subclass->objectclass);
                                struct class_list *next;
                                
                                /* Save the next pointer, as the DLIST_ macros will change poss_subclass->next */
                                next = poss_subclass->next;
 
-                               if (class && ldb_attr_cmp(class->subClassOf, current->objectclass) == 0) {
+                               if (ldb_attr_cmp(poss_subclass->objectclass->subClassOf, current->objectclass->lDAPDisplayName) == 0) {
                                        DLIST_REMOVE(unsorted, poss_subclass);
                                        DLIST_ADD(subclass, poss_subclass);
                                        
@@ -188,23 +230,30 @@ static int objectclass_sort(struct ldb_module *module,
                 * the bottom here */
        } while (parent_class);
 
-       /* This shouldn't happen, and would break MMC, but we can't
-        * afford to loose objectClasses.  Perhaps there was no 'top',
-        * or some other schema error? 
-        *
-        * Detecting schema errors is the job of the schema module, so
-        * at this layer we just try not to loose data
-        */
-       DLIST_CONCATENATE(sorted, unsorted, struct class_list *);
+       if (!unsorted) {
+               *sorted_out = sorted;
+               return LDB_SUCCESS;
+       }
 
-       *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;
+       }
+
+       /* 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(module->ldb, "objectclass %s is not a valid objectClass in objectClass chain", unsorted->objectclass->lDAPDisplayName);
+       return LDB_ERR_OBJECT_CLASS_VIOLATION;
 }
 
 static DATA_BLOB *get_sd(struct ldb_module *module, TALLOC_CTX *mem_ctx, 
                         const struct dsdb_class *objectclass) 
 {
-       NTSTATUS status;
+       enum ndr_err_code ndr_err;
        DATA_BLOB *linear_sd;
        struct auth_session_info *session_info
                = ldb_get_opaque(module->ldb, "sessionInfo");
@@ -225,10 +274,11 @@ static DATA_BLOB *get_sd(struct ldb_module *module, TALLOC_CTX *mem_ctx,
                return NULL;
        }
 
-       status = ndr_push_struct_blob(linear_sd, mem_ctx, sd, 
-                                     (ndr_push_flags_fn_t)ndr_push_security_descriptor);
-
-       if (!NT_STATUS_IS_OK(status)) {
+       ndr_err = ndr_push_struct_blob(linear_sd, mem_ctx, 
+                                       lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+                                      sd,
+                                      (ndr_push_flags_fn_t)ndr_push_security_descriptor);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
                return NULL;
        }
        
@@ -236,90 +286,258 @@ static DATA_BLOB *get_sd(struct ldb_module *module, TALLOC_CTX *mem_ctx,
 
 }
 
+static int get_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+       struct oc_context *ac;
+
+       ac = talloc_get_type(context, struct oc_context);
+
+       /* we are interested only in the single reply (base search) we receive here */
+       if (ares->type == LDB_REPLY_ENTRY) {
+               if (ac->search_res != NULL) {
+                       ldb_set_errstring(ldb, "Too many results");
+                       talloc_free(ares);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+
+               ac->search_res = talloc_move(ac, &ares);
+       } else {
+               talloc_free(ares);
+       }
+
+       return LDB_SUCCESS;
+}
+
+/* Fix up the DN to be in the standard form, taking particular care to match the parent DN
+
+   This should mean that if the parent is:
+    CN=Users,DC=samba,DC=example,DC=com
+   and a proposed child is
+    cn=Admins ,cn=USERS,dc=Samba,dc=example,dc=COM
+
+   The resulting DN should be:
+
+    CN=Admins,CN=Users,DC=samba,DC=example,DC=com
+   
+ */
+static int fix_dn(TALLOC_CTX *mem_ctx, 
+                 struct ldb_dn *newdn, struct ldb_dn *parent_dn, 
+                 struct ldb_dn **fixed_dn) 
+{
+       char *upper_rdn_attr;
+       /* 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);
+
+       /* 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;
+       }
+                                              
+       /* Create a new child */
+       if (ldb_dn_add_child_fmt(*fixed_dn, "X=X") == false) {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       /* And replace it with CN=foo (we need the attribute in upper case */
+       return ldb_dn_set_component(*fixed_dn, 0, upper_rdn_attr,
+                                   *ldb_dn_get_rdn_val(newdn));
+}
+
+/* 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);
+               if (!attribute) {
+                       ldb_asprintf_errstring(ldb, "attribute %s is not a valid attribute in schema", msg->elements[i].name);
+                       return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
+               }
+               msg->elements[i].name = attribute->lDAPDisplayName;
+       }
+
+       return LDB_SUCCESS;
+}
+
 static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
 {
-       struct ldb_message_element *objectclass_element;
-       const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
-       struct class_list *sorted, *current;
-       struct ldb_request *down_req;
-       struct ldb_message *msg;
-       int ret;
-       TALLOC_CTX *mem_ctx;
 
+       static const char * const attrs[] = { NULL };
+
+       struct ldb_handle *h;
+       struct oc_context *ac;
+       struct ldb_dn *parent_dn;
+       int ret;
+       
        ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_add\n");
 
-       if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
+       /* do not manipulate our control entries */
+       if (ldb_dn_is_special(req->op.add.message->dn)) {
                return ldb_next_request(module, req);
        }
-       
-       objectclass_element = ldb_msg_find_element(req->op.add.message, "objectClass");
 
-       /* If no part of this add has an objectClass, then we don't
-        * need to make any changes. cn=rootdse doesn't have an objectClass */
-       if (!objectclass_element) {
+       /* Need to object to this, but cn=rootdse doesn't hae an objectClass... */
+       if (ldb_msg_find_element(req->op.add.message, 
+                                "objectClass") == NULL) {
                return ldb_next_request(module, req);
        }
 
-       mem_ctx = talloc_new(req);
-       if (mem_ctx == NULL) {
+       h = oc_init_handle(req, module);
+       if (!h) {
                return LDB_ERR_OPERATIONS_ERROR;
        }
+       ac = talloc_get_type(h->private_data, struct oc_context);
+       
+       /* return or own handle to deal with this call */
+       req->handle = h;
 
-       ret = objectclass_sort(module, mem_ctx, objectclass_element, &sorted);
-       if (ret != LDB_SUCCESS) {
-               return ret;
+       /* If there isn't a parent, just go on to the add processing */
+       if (ldb_dn_get_comp_num(ac->orig_req->op.add.message->dn) == 1) {
+               return objectclass_do_add(h);
        }
 
-       /* prepare the first operation */
-       down_req = talloc(req, struct ldb_request);
-       if (down_req == NULL) {
-               ldb_set_errstring(module->ldb, "Out of memory!");
-               talloc_free(mem_ctx);
+       parent_dn = ldb_dn_get_parent(ac, ac->orig_req->op.add.message->dn);
+       if (parent_dn == NULL) {
+               ldb_oom(module->ldb);
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       *down_req = *req; /* copy the request */
+       ret = ldb_build_search_req(&ac->search_req, module->ldb,
+                                  ac, parent_dn, LDB_SCOPE_BASE,
+                                  "(objectClass=*)",
+                                  attrs, NULL, 
+                                  ac, get_search_callback);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+
+       talloc_steal(ac->search_req, parent_dn);
+
+       ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+       ac->step = OC_SEARCH_ADD_PARENT;
+
+       return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_add(struct ldb_handle *h) 
+{
+       const struct dsdb_schema *schema;
+       struct oc_context *ac;
+       struct ldb_message_element *objectclass_element;
+       struct ldb_message *msg;
+       TALLOC_CTX *mem_ctx;
+       struct class_list *sorted, *current;
+       int ret;
+      
+       ac = talloc_get_type(h->private_data, struct oc_context);
+       schema = dsdb_get_schema(ac->module->ldb);
 
-       down_req->op.add.message = msg = ldb_msg_copy_shallow(down_req, req->op.add.message);
+       mem_ctx = talloc_new(ac);
+       if (mem_ctx == NULL) {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
 
-       if (down_req->op.add.message == NULL) {
+       ac->add_req = talloc(ac, struct ldb_request);
+       if (ac->add_req == NULL) {
                talloc_free(mem_ctx);
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       ldb_msg_remove_attr(msg, "objectClass");
-       ret = ldb_msg_add_empty(msg, "objectClass", 0, NULL);
+       *ac->add_req = *ac->orig_req;
+
+       ac->add_req->op.add.message = msg = ldb_msg_copy_shallow(ac->add_req, ac->orig_req->op.add.message);
+
+       ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->add_req);
        
-       if (ret != LDB_SUCCESS) {
-               talloc_free(mem_ctx);
-               return ret;
+       /* Check we have a valid parent */
+       if (ac->search_res == NULL) {
+               if (ldb_dn_compare(ldb_get_root_basedn(ac->module->ldb), ac->orig_req->op.add.message->dn) == 0) {
+                       /* Allow the tree to be started */
+                       
+                       /* but don't keep any error string, it's meaningless */
+                       ldb_set_errstring(ac->module->ldb, NULL);
+               } else {
+                       ldb_asprintf_errstring(ac->module->ldb, "objectclass: Cannot add %s, parent does not exist!", 
+                                              ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               }
+       } else {
+               
+               /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+               ret = fix_dn(msg, 
+                            ac->orig_req->op.add.message->dn,
+                            ac->search_res->message->dn,
+                            &msg->dn);
+
+               if (ret != LDB_SUCCESS) {
+                       ldb_asprintf_errstring(ac->module->ldb, "Could not munge DN %s into normal form", 
+                                              ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
+                       return ret;
+               }
+
+               /* TODO: Check this is a valid child to this parent,
+                * by reading the allowedChildClasses and
+                * allowedChildClasssesEffective attributes */
+
        }
 
-       /* We must completely replace the existing objectClass entry,
-        * because we need it sorted */
+       if (schema) {
+               ret = fix_attributes(ac->module->ldb, schema, msg);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(mem_ctx);
+                       return ret;
+               }
 
-       /* Move from the linked list back into an ldb msg */
-       for (current = sorted; current; current = current->next) {
-               ret = ldb_msg_add_string(msg, "objectClass", current->objectclass);
+               /* This is now the objectClass list from the database */
+               objectclass_element = ldb_msg_find_element(msg, "objectClass");
+               
+               if (!objectclass_element) {
+                       /* Where did it go?  bail now... */
+                       talloc_free(mem_ctx);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               ret = objectclass_sort(ac->module, schema, msg, mem_ctx, objectclass_element, &sorted);
                if (ret != LDB_SUCCESS) {
-                       ldb_set_errstring(module->ldb, 
-                                         "objectclass: could not re-add sorted "
-                                         "objectclass to modify msg");
                        talloc_free(mem_ctx);
                        return ret;
                }
-               /* Last one is the critical one */
-               if (schema && !current->next) {
-                       const struct dsdb_class *objectclass
-                               = dsdb_class_by_lDAPDisplayName(schema, 
-                                                               current->objectclass);
-                       if (objectclass) {
+               
+               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;
+               }
+               
+               /* We must completely replace the existing objectClass entry,
+                * because we need it sorted */
+               
+               /* Move from the linked list back into an ldb msg */
+               for (current = sorted; current; current = current->next) {
+                       ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
+                       if (ret != LDB_SUCCESS) {
+                               ldb_set_errstring(ac->module->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) {
                                if (!ldb_msg_find_element(msg, "objectCategory")) {
                                        ldb_msg_add_string(msg, "objectCategory", 
-                                                          objectclass->defaultObjectCategory);
+                                                          current->objectclass->defaultObjectCategory);
+                               }
+                               if (!ldb_msg_find_element(msg, "showInAdvancedViewOnly")) {
+                                       ldb_msg_add_string(msg, "showInAdvancedViewOnly", 
+                                                          current->objectclass->defaultHidingValue ? "TRUE" : "FALSE");
                                }
                                if (!ldb_msg_find_element(msg, "nTSecurityDescriptor")) {
-                                       DATA_BLOB *sd = get_sd(module, mem_ctx, objectclass);
+                                       DATA_BLOB *sd = get_sd(ac->module, mem_ctx, current->objectclass);
                                        ldb_msg_add_steal_value(msg, "nTSecurityDescriptor", sd);
                                }
                        }
@@ -327,47 +545,82 @@ static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
        }
 
        talloc_free(mem_ctx);
-       ret = ldb_msg_sanity_check(module->ldb, msg);
+       ret = ldb_msg_sanity_check(ac->module->ldb, msg);
+
 
        if (ret != LDB_SUCCESS) {
                return ret;
        }
 
-       /* go on with the call chain */
-       ret = ldb_next_request(module, down_req);
+       h->state = LDB_ASYNC_INIT;
+       h->status = LDB_SUCCESS;
+
+       ac->step = OC_DO_ADD;
 
-       /* do not free down_req as the call results may be linked to it,
-        * it will be freed when the upper level request get freed */
-       if (ret == LDB_SUCCESS) {
-               req->handle = down_req->handle;
-       }
-       return ret;
+       /* perform the add */
+       return ldb_next_request(ac->module, ac->add_req);
 }
 
 static int objectclass_modify(struct ldb_module *module, struct ldb_request *req)
 {
        struct ldb_message_element *objectclass_element;
        struct ldb_message *msg;
+       const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+       int ret;
+
        ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_modify\n");
 
-       if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
+       /* do not manipulate our control entries */
+       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);
+       }
        objectclass_element = ldb_msg_find_element(req->op.mod.message, "objectClass");
 
        /* If no part of this touches the objectClass, then we don't
         * need to make any changes.  */
-       /* If the only operation is the deletion of the objectClass then go on */
+
+       /* If the only operation is the deletion of the objectClass
+        * then go on with just fixing the attribute case */
        if (!objectclass_element) {
-               return ldb_next_request(module, req);
+               struct ldb_request *down_req = talloc(req, struct ldb_request);
+               if (down_req == NULL) {
+                       ldb_set_errstring(module->ldb, "Out of memory!");
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               
+               *down_req = *req; /* copy the request */
+               
+               down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+               
+               if (down_req->op.mod.message == NULL) {
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               
+               ret = fix_attributes(module->ldb, schema, msg);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+
+               /* go on with the call chain */
+               ret = ldb_next_request(module, down_req);
+               
+               /* do not free down_req as the call results may be linked to it,
+                * it will be freed when the upper level request get freed */
+               if (ret == LDB_SUCCESS) {
+                       req->handle = down_req->handle;
+               }
+               return ret;
        }
 
        switch (objectclass_element->flags & LDB_FLAG_MOD_MASK) {
        case LDB_FLAG_MOD_DELETE:
-               /* Delete everything?  Probably totally illigal, but hey! */
                if (objectclass_element->num_values == 0) {
-                       return ldb_next_request(module, req);
+                       return LDB_ERR_OBJECT_CLASS_MODS_PROHIBITED;
                }
                break;
        case LDB_FLAG_MOD_REPLACE:
@@ -375,7 +628,6 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
                struct ldb_request *down_req;
                struct class_list *sorted, *current;
                TALLOC_CTX *mem_ctx;
-               int ret;
                mem_ctx = talloc_new(req);
                if (mem_ctx == NULL) {
                        return LDB_ERR_OPERATIONS_ERROR;
@@ -393,12 +645,18 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
                
                down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
                
-               if (down_req->op.add.message == NULL) {
+               if (down_req->op.mod.message == NULL) {
                        talloc_free(mem_ctx);
                        return LDB_ERR_OPERATIONS_ERROR;
                }
                
-               ret = objectclass_sort(module, mem_ctx, objectclass_element, &sorted);
+               ret = fix_attributes(module->ldb, schema, msg);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(mem_ctx);
+                       return ret;
+               }
+
+               ret = objectclass_sort(module, schema, msg, mem_ctx, objectclass_element, &sorted);
                if (ret != LDB_SUCCESS) {
                        return ret;
                }
@@ -416,7 +674,7 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
 
                /* Move from the linked list back into an ldb msg */
                for (current = sorted; current; current = current->next) {
-                       ret = ldb_msg_add_string(msg, "objectClass", current->objectclass);
+                       ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
                        if (ret != LDB_SUCCESS) {
                                ldb_set_errstring(module->ldb, "objectclass: could not re-add sorted objectclass to modify msg");
                                talloc_free(mem_ctx);
@@ -444,6 +702,10 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *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
+        */
        {
                struct ldb_handle *h;
                struct oc_context *ac;
@@ -460,12 +722,25 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
                /* prepare the first operation */
                ac->down_req = talloc(ac, struct ldb_request);
                if (ac->down_req == NULL) {
-                       ldb_set_errstring(module->ldb, "Out of memory!");
+                       ldb_oom(ac->module->ldb);
                        return LDB_ERR_OPERATIONS_ERROR;
                }
                
                *(ac->down_req) = *req; /* copy the request */
                
+               ac->down_req->op.mod.message = msg = ldb_msg_copy_shallow(ac->down_req, req->op.mod.message);
+               
+               if (ac->down_req->op.mod.message == NULL) {
+                       ldb_oom(ac->module->ldb);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               
+               ret = fix_attributes(ac->module->ldb, schema, msg);
+               if (ret != LDB_SUCCESS) {
+                       ldb_oom(ac->module->ldb);
+                       return ret;
+               }
+
                ac->down_req->context = NULL;
                ac->down_req->callback = NULL;
                ldb_set_timeout_from_prev_req(module->ldb, req, ac->down_req);
@@ -476,59 +751,24 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
        }
 }
 
-static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+static int objectclass_search_self(struct ldb_handle *h) 
 {
-       struct oc_context *ac;
-
-       if (!context || !ares) {
-               ldb_set_errstring(ldb, "NULL Context or Result in callback");
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-
-       ac = talloc_get_type(context, struct oc_context);
-
-       /* we are interested only in the single reply (base search) we receive here */
-       if (ares->type == LDB_REPLY_ENTRY) {
-               if (ac->search_res != NULL) {
-                       ldb_set_errstring(ldb, "Too many results");
-                       talloc_free(ares);
-                       return LDB_ERR_OPERATIONS_ERROR;
-               }
-
-               ac->search_res = talloc_move(ac, &ares);
-       } else {
-               talloc_free(ares);
-       }
-
-       return LDB_SUCCESS;
-}
-
-static int objectclass_search_self(struct ldb_handle *h) {
-
+       int ret;
        struct oc_context *ac;
        static const char * const attrs[] = { "objectClass", NULL };
 
        ac = talloc_get_type(h->private_data, struct oc_context);
 
-       /* prepare the search operation */
-       ac->search_req = talloc_zero(ac, struct ldb_request);
-       if (ac->search_req == NULL) {
-               ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
+       ret = ldb_build_search_req(&ac->search_req, ac->module->ldb,
+                                  ac, ac->orig_req->op.mod.message->dn, LDB_SCOPE_BASE,
+                                  "(objectClass=*)",
+                                  attrs, NULL, 
+                                  ac, get_search_callback);
 
-       ac->search_req->operation = LDB_SEARCH;
-       ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn;
-       ac->search_req->op.search.scope = LDB_SCOPE_BASE;
-       ac->search_req->op.search.tree = ldb_parse_tree(ac->search_req, NULL);
-       if (ac->search_req->op.search.tree == NULL) {
-               ldb_set_errstring(ac->module->ldb, "objectclass: Internal error producing null search");
-               return LDB_ERR_OPERATIONS_ERROR;
+       if (ret != LDB_SUCCESS) {
+               return ret;
        }
-       ac->search_req->op.search.attrs = attrs;
-       ac->search_req->controls = NULL;
-       ac->search_req->context = ac;
-       ac->search_req->callback = get_self_callback;
+
        ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
 
        ac->step = OC_SEARCH_SELF;
@@ -538,6 +778,7 @@ static int objectclass_search_self(struct ldb_handle *h) {
 
 static int objectclass_do_mod(struct ldb_handle *h) {
 
+       const struct dsdb_schema *schema;
        struct oc_context *ac;
        struct ldb_message_element *objectclass_element;
        struct ldb_message *msg;
@@ -546,6 +787,7 @@ static int objectclass_do_mod(struct ldb_handle *h) {
        int ret;
       
        ac = talloc_get_type(h->private_data, struct oc_context);
+       schema = dsdb_get_schema(ac->module->ldb);
 
        mem_ctx = talloc_new(ac);
        if (mem_ctx == NULL) {
@@ -576,15 +818,15 @@ static int objectclass_do_mod(struct ldb_handle *h) {
        objectclass_element = ldb_msg_find_element(ac->search_res->message, 
                                                   "objectClass");
        if (!objectclass_element) {
-               /* Where did it go?  Move along now, nothing to see here */
+               /* Where did it go?  bail now... */
                talloc_free(mem_ctx);
-               return LDB_SUCCESS;
+               return LDB_ERR_OPERATIONS_ERROR;
        }
        
        /* modify dn */
        msg->dn = ac->orig_req->op.mod.message->dn;
 
-       ret = objectclass_sort(ac->module, mem_ctx, objectclass_element, &sorted);
+       ret = objectclass_sort(ac->module, schema, msg, mem_ctx, objectclass_element, &sorted);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -602,7 +844,7 @@ static int objectclass_do_mod(struct ldb_handle *h) {
        
        /* Move from the linked list back into an ldb msg */
        for (current = sorted; current; current = current->next) {
-               ret = ldb_msg_add_string(msg, "objectClass", current->objectclass);
+               ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
                if (ret != LDB_SUCCESS) {
                        ldb_set_errstring(ac->module->ldb, "objectclass: could not re-add sorted objectclass to modify msg");
                        talloc_free(mem_ctx);
@@ -627,6 +869,106 @@ static int objectclass_do_mod(struct ldb_handle *h) {
        return ldb_next_request(ac->module, ac->mod_req);
 }
 
+static int objectclass_rename(struct ldb_module *module, struct ldb_request *req)
+{
+
+       static const char * const attrs[] = { NULL };
+
+       struct ldb_handle *h;
+       struct oc_context *ac;
+       struct ldb_dn *parent_dn;
+       int ret;
+       
+       ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_rename\n");
+
+       if (ldb_dn_is_special(req->op.rename.newdn)) { /* do not manipulate our control entries */
+               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(module->ldb, "Cannot rename %s to be a child of itself",
+                                      ldb_dn_get_linearized(req->op.rename.olddn));
+               return LDB_ERR_UNWILLING_TO_PERFORM;
+       }
+
+       h = oc_init_handle(req, module);
+       if (!h) {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+       ac = talloc_get_type(h->private_data, struct oc_context);
+       
+       /* return or own handle to deal with this call */
+       req->handle = h;
+
+       parent_dn = ldb_dn_get_parent(ac, ac->orig_req->op.rename.newdn);
+       if (parent_dn == NULL) {
+               ldb_oom(module->ldb);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+       ret = ldb_build_search_req(&ac->search_req, module->ldb,
+                                  ac, parent_dn, LDB_SCOPE_BASE,
+                                  "(objectClass=*)",
+                                  attrs, NULL, 
+                                  ac, get_search_callback);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+       talloc_steal(ac->search_req, parent_dn);
+       ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+       ac->step = OC_SEARCH_RENAME_PARENT;
+
+       return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_rename(struct ldb_handle *h) 
+{
+       struct oc_context *ac;
+       int ret;
+      
+       ac = talloc_get_type(h->private_data, struct oc_context);
+
+       ac->rename_req = talloc(ac, struct ldb_request);
+       if (ac->rename_req == NULL) {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       *ac->rename_req = *ac->orig_req;
+
+       ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->rename_req);
+       
+       /* Check we have a valid parent */
+       if (ac->search_res == NULL) {
+               ldb_asprintf_errstring(ac->module->ldb, "objectclass: Cannot rename %s, parent does not exist!", 
+                                      ldb_dn_get_linearized(ac->orig_req->op.rename.newdn));
+               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->rename_req, 
+                    ac->orig_req->op.rename.newdn, 
+                    ac->search_res->message->dn, 
+                    &ac->rename_req->op.rename.newdn);
+
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+
+       /* TODO: Check this is a valid child to this parent,
+        * by reading the allowedChildClasses and
+        * allowedChildClasssesEffective attributes */
+
+       h->state = LDB_ASYNC_INIT;
+       h->status = LDB_SUCCESS;
+
+       ac->step = OC_DO_RENAME;
+
+       /* perform the rename */
+       return ldb_next_request(ac->module, ac->rename_req);
+}
+
 static int oc_wait(struct ldb_handle *handle) {
        struct oc_context *ac;
        int ret;
@@ -701,6 +1043,81 @@ static int oc_wait(struct ldb_handle *handle) {
 
                break;
                
+       case OC_SEARCH_ADD_PARENT:
+               ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+               if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+                       handle->status = ret;
+                       goto done;
+               }
+               if (ac->search_req->handle->status != LDB_SUCCESS
+                   && ac->search_req->handle->status != LDB_ERR_NO_SUCH_OBJECT) {
+                       handle->status = ac->search_req->handle->status;
+                       goto done;
+               }
+
+               if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+                       return LDB_SUCCESS;
+               }
+
+               /* parent search done, go on */
+               return objectclass_do_add(handle);
+
+       case OC_DO_ADD:
+               ret = ldb_wait(ac->add_req->handle, LDB_WAIT_NONE);
+
+               if (ret != LDB_SUCCESS) {
+                       handle->status = ret;
+                       goto done;
+               }
+               if (ac->add_req->handle->status != LDB_SUCCESS) {
+                       handle->status = ac->add_req->handle->status;
+                       goto done;
+               }
+
+               if (ac->add_req->handle->state != LDB_ASYNC_DONE) {
+                       return LDB_SUCCESS;
+               }
+
+               break;
+               
+       case OC_SEARCH_RENAME_PARENT:
+               ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+               if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+                       handle->status = ret;
+                       goto done;
+               }
+               if (ac->search_req->handle->status != LDB_SUCCESS && ac->search_req->handle->status != LDB_ERR_NO_SUCH_OBJECT) {
+                       handle->status = ac->search_req->handle->status;
+                       goto done;
+               }
+
+               if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+                       return LDB_SUCCESS;
+               }
+
+               /* parent search done, go on */
+               return objectclass_do_rename(handle);
+
+       case OC_DO_RENAME:
+               ret = ldb_wait(ac->rename_req->handle, LDB_WAIT_NONE);
+
+               if (ret != LDB_SUCCESS) {
+                       handle->status = ret;
+                       goto done;
+               }
+               if (ac->rename_req->handle->status != LDB_SUCCESS) {
+                       handle->status = ac->rename_req->handle->status;
+                       goto done;
+               }
+
+               if (ac->rename_req->handle->state != LDB_ASYNC_DONE) {
+                       return LDB_SUCCESS;
+               }
+
+               break;
+               
        default:
                ret = LDB_ERR_OPERATIONS_ERROR;
                goto done;
@@ -740,6 +1157,7 @@ static const struct ldb_module_ops objectclass_ops = {
        .name              = "objectclass",
        .add           = objectclass_add,
        .modify        = objectclass_modify,
+       .rename        = objectclass_rename,
        .wait          = objectclass_wait
 };