s4:objectclass LDB module - rework the code which handles the objectclasses modification
authorMatthias Dieter Wallnöfer <mdw@samba.org>
Sat, 5 Jun 2010 21:02:25 +0000 (23:02 +0200)
committerMatthias Dieter Wallnöfer <mdw@samba.org>
Mon, 7 Jun 2010 12:47:25 +0000 (14:47 +0200)
Before it has been very incomplete. We try now to match the Windows Server
behaviour as close as possible.

source4/dsdb/samdb/ldb_modules/objectclass.c

index 7f8c620e160f46519280a717757a291e105c4772..b15dff01f71b7b5623d5bd47d039db1fb2370ec2 100644 (file)
@@ -656,11 +656,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;
-       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");
@@ -682,131 +680,29 @@ static int objectclass_modify(struct ldb_module *module, struct ldb_request *req
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
+       /* Without schema, there isn't much to do here */
        if (ac->schema == NULL) {
-               /* Without schema, there isn't much to do here */
                talloc_free(ac);
                return ldb_next_request(module, req);
        }
 
-       /* 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 = 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);
+       msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
+       if (msg == NULL) {
+               return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       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;
-               }
+       /* For now change everything except the objectclasses */
 
-               ret = objectclass_sort(module, ac->schema, mem_ctx,
-                                      objectclass_element, &sorted);
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(mem_ctx);
-                       return ret;
-               }
-
-               /* 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;
-               }
-
-               /* 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);
-
-               ret = ldb_msg_sanity_check(ldb, 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);
-       }
-
-       /* 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;
+               oc_changes = true;
        }
 
        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);
        if (ret != LDB_SUCCESS) {
                return ret;
@@ -817,8 +713,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;
@@ -848,8 +744,11 @@ 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
+        * informations (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,
@@ -864,6 +763,7 @@ 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;
 }
 
@@ -872,66 +772,188 @@ static int objectclass_do_mod(struct oc_context *ac)
        struct ldb_context *ldb;
        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;
+       bool found, replace = false;
        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;
        }
 
-       mem_ctx = talloc_new(ac);
-       if (mem_ctx == NULL) {
+       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_ERR_OPERATIONS_ERROR;
+       }
+
+       oc_el_change = ldb_msg_find_element(ac->req->op.mod.message,
+                                           "objectClass");
+       if (oc_el_change == NULL) {
+               /* we should have an objectclass change operation */
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
        /* 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);
+               ldb_oom(ldb);
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       /* 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, ac->schema, mem_ctx,
-                              objectclass_element, &sorted);
-       if (ret != LDB_SUCCESS) {
-               return ret;
+       mem_ctx = talloc_new(ac);
+       if (mem_ctx == NULL) {
+               ldb_oom(ldb);
+               return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       /* We must completely replace the existing objectClass entry.
-        * We could do a constrained add/del, but we are meant to be
-        * in a transaction... */
+       switch (oc_el_change->flags & LDB_FLAG_MOD_MASK) {
+       case LDB_FLAG_MOD_ADD:
+               /* Merge the two message elements */
+               for (i = 0; i < oc_el_change->num_values; i++) {
+                       for (j = 0; j < oc_el_entry->num_values; j++) {
+                               if (strcasecmp((char *)oc_el_change->values[i].data,
+                                              (char *)oc_el_entry->values[j].data) == 0) {
+                                       /* we cannot add an already existing object class */
+                                       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) {
+                               ldb_oom(ldb);
+                               talloc_free(mem_ctx);
+                               return LDB_ERR_OPERATIONS_ERROR;
+                       }
+                       oc_el_entry->values = vals;
+                       oc_el_entry->values[oc_el_entry->num_values] =
+                               oc_el_change->values[i];
+                       ++(oc_el_entry->num_values);
+               }
+
+               objectclass = get_last_structural_class(ac->schema,
+                                                       oc_el_change);
+               if (objectclass != NULL) {
+                       /* we cannot add a new structural object class */
+                       talloc_free(mem_ctx);
+                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
+               }
+
+               /* Now do the sorting */
+               ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
+                                      oc_el_entry, &sorted);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(mem_ctx);
+                       return ret;
+               }
+
+               break;
+
+       case LDB_FLAG_MOD_REPLACE:
+               /* Do the sorting for the change message element */
+               ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
+                                      oc_el_change, &sorted);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(mem_ctx);
+                       return ret;
+               }
+
+               /* this is a replace */
+               replace = true;
 
-       ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
+               break;
+
+       case LDB_FLAG_MOD_DELETE:
+               /* get the actual top-most structural objectclass */
+               objectclass = get_last_structural_class(ac->schema,
+                                                       oc_el_entry);
+               if (objectclass == NULL) {
+                       talloc_free(mem_ctx);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+
+               /* Merge the two message elements */
+               for (i = 0; i < oc_el_change->num_values; i++) {
+                       found = false;
+                       for (j = 0; j < oc_el_entry->num_values; j++) {
+                               if (strcasecmp((char *)oc_el_change->values[i].data,
+                                              (char *)oc_el_entry->values[j].data) == 0) {
+                                       found = true;
+                                       /* delete the object class value -
+                                        * code was copied from
+                                        * "ldb_msg_remove_element" */
+                                       if (j != oc_el_entry->num_values - 1) {
+                                               memmove(&oc_el_entry->values[j],
+                                                       &oc_el_entry->values[j+1],
+                                                       ((oc_el_entry->num_values-1) - j)*sizeof(struct ldb_val));
+                                       }
+                                       --(oc_el_entry->num_values);
+                                       break;
+                               }
+                       }
+                       if (!found) {
+                               /* we cannot delete a not existing object class */
+                               talloc_free(mem_ctx);
+                               return LDB_ERR_NO_SUCH_ATTRIBUTE;
+                       }
+               }
+
+               /* Make sure that the top-most structural objectclass wasn't
+                * deleted */
+               found = false;
+               for (i = 0; i < oc_el_entry->num_values; i++) {
+                       if (strcasecmp(objectclass->lDAPDisplayName,
+                           (char *)oc_el_entry->values[i].data) == 0) {
+                               found = true; break;
+                       }
+               }
+               if (!found) {
+                       talloc_free(mem_ctx);
+                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
+               }
+
+
+               /* Now do the sorting */
+               ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
+                                      oc_el_entry, &sorted);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(mem_ctx);
+                       return ret;
+               }
+
+               break;
+       }
+
+       ret = ldb_msg_add_empty(msg, "objectClass",
+                               LDB_FLAG_MOD_REPLACE, &oc_el_change);
        if (ret != LDB_SUCCESS) {
-               ldb_set_errstring(ldb, "objectclass: could not clear objectclass in modify msg");
+               ldb_oom(ldb);
                talloc_free(mem_ctx);
                return ret;
        }
-       
+
        /* Move from the linked list back into an ldb msg */
        for (current = sorted; current; current = current->next) {
-               value = talloc_strdup(msg, current->objectclass->lDAPDisplayName);
+               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);
@@ -942,9 +964,32 @@ static int objectclass_do_mod(struct oc_context *ac)
                }
        }
 
+       talloc_free(mem_ctx);
+
+       if (replace) {
+               /* Well, on replace we are nearly done: we have to test if
+                * the change and entry message element are identically. We
+                * can use "ldb_msg_element_compare" since now the specified
+                * objectclasses match for sure in case. */
+               ret = ldb_msg_element_compare(oc_el_entry, oc_el_change);
+               if (ret == 0) {
+                       ret = ldb_msg_element_compare(oc_el_change,
+                                                     oc_el_entry);
+               }
+               if (ret == 0) {
+                       /* they are the same so we are done in this case */
+                       return ldb_module_done(ac->req, NULL, NULL,
+                                              LDB_SUCCESS);
+               } else {
+                       /* they're not exactly the same */
+                       return LDB_ERR_OBJECT_CLASS_VIOLATION;
+               }
+       }
+
+       /* in the other cases we have the real change left to do */
+
        ret = ldb_msg_sanity_check(ldb, msg);
        if (ret != LDB_SUCCESS) {
-               talloc_free(mem_ctx);
                return ret;
        }
 
@@ -954,12 +999,9 @@ static int objectclass_do_mod(struct oc_context *ac)
                                ac, oc_op_callback,
                                ac->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);
 }