r16028: Re-add the objectclass module, in the new async scheme.
authorAndrew Bartlett <abartlet@samba.org>
Sat, 3 Jun 2006 11:57:20 +0000 (11:57 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 19:08:51 +0000 (14:08 -0500)
Add a test to show that we need this, and to prove it works (for add
at least).

Andrew Bartlett

source/lib/ldb/config.mk
source/lib/ldb/modules/objectclass.c
source/setup/provision_init.ldif
testprogs/ejs/ldap.js

index 68dbdd10f02e085ace4a950513040bd24b175e2a..85a6cb6c67718240a8919f77ca0e6d8e7e64d51a 100644 (file)
@@ -38,15 +38,15 @@ OBJ_FILES = \
 # End MODULE ldb_operational
 ################################################
 
-# ################################################
-# Start MODULE ldb_objectclass
-[MODULE::ldb_objectclass]
-INIT_FUNCTION = ldb_objectclass_init
-SUBSYSTEM = ldb
-OBJ_FILES = \
-#              modules/objectclass.o
-# End MODULE ldb_objectclass
-# ################################################
+################################################
+# Start MODULE ldb_objectclass
+[MODULE::ldb_objectclass]
+INIT_FUNCTION = ldb_objectclass_init
+SUBSYSTEM = ldb
+OBJ_FILES = \
+               modules/objectclass.o
+# End MODULE ldb_objectclass
+################################################
 
 ################################################
 # Start MODULE ldb_rdn_name
index a9c51341a8c4617e9705bab36291bb4eb66fe103..4423f82aedb8c1b213d66bff15eb693f80aa8993 100644 (file)
@@ -1,7 +1,8 @@
 /* 
    ldb database library
 
-   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+   Copyright (C) Simo Sorce  2006
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2006
 
      ** NOTE! The following LGPL license applies to the ldb
      ** library. This does NOT imply that all of Samba is released
 #include "includes.h"
 #include "ldb/include/includes.h"
 
-/* It turns out the MMC assumes that the last objectClass in the list
- * is the most specific subclass.  As such, we must sort the list,
- * according to the schema.
- *
- * For performance, we do this on the add/modify, not on the search
- *
- * We perform the original add/modify, then search for that is now in
- * the objectClass list. We can then then replace that with the new
- * sorted list.  The backend is expected to preserve ordering for
- * subsequent searches.
- *
- * We are in a transaction, so this is all perfectly safe...
- */
+struct oc_async_context {
+
+       enum oc_step {OC_DO_REQ, OC_SEARCH_SELF, OC_DO_MOD} step;
+
+       struct ldb_module *module;
+       struct ldb_request *orig_req;
+
+       struct ldb_request *down_req;
+
+       struct ldb_request *search_req;
+       struct ldb_async_result *search_res;
+
+       struct ldb_request *mod_req;
+};
 
-static int objectclass_handle(struct ldb_module *module, struct ldb_request *req, const struct ldb_message *msg)
+static struct ldb_async_handle *oc_init_handle(struct ldb_request *req, struct ldb_module *module)
 {
-       TALLOC_CTX *mem_ctx;
-       int ret;
-       struct ldb_request *search_request;
-       struct ldb_request *modify_request;
-       struct ldb_message *modify_msg;
-       struct ldb_result *res;
-       const char *attrs[] = { "objectClass", NULL };
-       struct class_list {
-               struct class_list *prev, *next;
-               const char *objectclass;
-       };
-       struct class_list *sorted = NULL, *parent_class = NULL, 
-               *subclass = NULL, *unsorted = NULL, *current, *poss_subclass;
-       int i;
-       int layer;
+       struct oc_async_context *ac;
+       struct ldb_async_handle *h;
 
-       struct ldb_message_element *objectclass_element;
+       h = talloc_zero(req, struct ldb_async_handle);
+       if (h == NULL) {
+               ldb_set_errstring(module->ldb, talloc_asprintf(module, "Out of Memory"));
+               return NULL;
+       }
+
+       h->module = module;
+
+       ac = talloc_zero(h, struct oc_async_context);
+       if (ac == NULL) {
+               ldb_set_errstring(module->ldb, talloc_asprintf(module, "Out of Memory"));
+               talloc_free(h);
+               return NULL;
+       }
+
+       h->private_data = (void *)ac;
+
+       h->state = LDB_ASYNC_INIT;
+       h->status = LDB_SUCCESS;
+
+       ac->module = module;
+       ac->orig_req = req;
+
+       return h;
+}
+
+static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
+{
+       struct ldb_async_handle *h;
+       struct oc_async_context *ac;
+       struct ldb_message_element *objectClassAttr;
 
-       ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_handle\n");
+       ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_add\n");
 
-       if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
+       if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
                return ldb_next_request(module, req);
        }
+       
+       objectClassAttr = ldb_msg_find_element(req->op.add.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 (!objectClassAttr) {
+               ldb_set_errstring(module->ldb, talloc_asprintf(ac, "Object class violation: no objectClass present"));
+               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       }
+
+       h = oc_init_handle(req, module);
+       if (!h) {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+       ac = talloc_get_type(h->private_data, struct oc_async_context);
 
-       ret = ldb_next_request(module, req);
+       /* return or own handle to deal with this call */
+       req->async.handle = h;
 
-       if (ret) {
-               return ret;
+       /* prepare the first operation */
+       ac->down_req = talloc_zero(ac, struct ldb_request);
+       if (ac->down_req == NULL) {
+               ldb_set_errstring(module->ldb, talloc_asprintf(module->ldb, "Out of memory!"));
+               return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       if (ldb_msg_find_element(msg, "objectClass") == NULL ) {
-               /* No sign of the objectClass:  no change, nothing to see here */
-               return ret;
+       *(ac->down_req) = *req; /* copy the request */
+
+       ac->down_req->async.context = NULL;
+       ac->down_req->async.callback = NULL;
+
+       ac->step = OC_DO_REQ;
+
+       return ldb_next_request(module, ac->down_req);
+}
+
+static int objectclass_modify(struct ldb_module *module, struct ldb_request *req)
+{
+       struct ldb_async_handle *h;
+       struct oc_async_context *ac;
+       struct ldb_message_element *objectClassAttr;
+
+       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 */
+               return ldb_next_request(module, req);
        }
+       
+       objectClassAttr = ldb_msg_find_element(req->op.mod.message, "objectClass");
 
-       /* Thanks to transactions: Now do a search, find the full list
-        * of objectClasses and do the sort */
+       /* 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 (!objectClassAttr) {
+               return ldb_next_request(module, req);
+       }
 
-       mem_ctx = talloc_new(module);
-       if (!mem_ctx) {
+       h = oc_init_handle(req, module);
+       if (!h) {
                return LDB_ERR_OPERATIONS_ERROR;
        }
+       ac = talloc_get_type(h->private_data, struct oc_async_context);
 
-       search_request = talloc(mem_ctx, struct ldb_request);
-       if (!search_request) {
-               talloc_free(mem_ctx);
+       /* return or own handle to deal with this call */
+       req->async.handle = h;
+
+       /* prepare the first operation */
+       ac->down_req = talloc_zero(ac, struct ldb_request);
+       if (ac->down_req == NULL) {
+               ldb_set_errstring(module->ldb, talloc_asprintf(module->ldb, "Out of memory!"));
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       search_request->operation       = LDB_REQ_SEARCH;
-       search_request->op.search.base  = msg->dn;
-       search_request->op.search.scope = LDB_SCOPE_BASE;
-       search_request->op.search.tree  = ldb_parse_tree(module->ldb, NULL);
-       search_request->op.search.attrs = attrs;
-       search_request->controls = NULL;
+       *(ac->down_req) = *req; /* copy the request */
 
-       ret = ldb_next_request(module, search_request);
-       if (ret) {
-               return ret;
+       ac->down_req->async.context = NULL;
+       ac->down_req->async.callback = NULL;
+
+       ac->step = OC_DO_REQ;
+
+       return ldb_next_request(module, ac->down_req);
+}
+
+static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_async_result *ares)
+{
+       struct oc_async_context *ac;
+
+       if (!context || !ares) {
+               ldb_set_errstring(ldb, talloc_asprintf(ldb, "NULL Context or Result in callback"));
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       ac = talloc_get_type(context, struct oc_async_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, talloc_asprintf(ldb, "Too many results"));
+                       talloc_free(ares);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+
+               ac->search_res = talloc_steal(ac, ares);
+       } else {
+               talloc_free(ares);
+       }
+
+       return LDB_SUCCESS;
+}
+
+static int objectclass_search_self(struct ldb_async_handle *h) {
+
+       struct oc_async_context *ac;
+       static const char * const attrs[] = { "objectClass", NULL };
+
+       ac = talloc_get_type(h->private_data, struct oc_async_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;
+       }
+
+       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->module->ldb, NULL);
+       if (ac->search_req->op.search.tree == NULL) {
+               ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: Internal error producing null search"));
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+       ac->search_req->op.search.attrs = attrs;
+       ac->search_req->controls = NULL;
+       ac->search_req->async.context = ac;
+       ac->search_req->async.callback = get_self_callback;
+       ac->search_req->async.timeout = ac->orig_req->async.timeout;
+
+       ac->step = OC_SEARCH_SELF;
+
+       return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_mod(struct ldb_async_handle *h) {
+
+       struct oc_async_context *ac;
+       struct ldb_message_element *objectclass_element;
+       struct ldb_message *msg;
+       TALLOC_CTX *mem_ctx;
+       struct class_list {
+               struct class_list *prev, *next;
+               const char *objectclass;
+       };
+       struct class_list *sorted = NULL, *parent_class = NULL,
+               *subclass = NULL, *unsorted = NULL, *current, *poss_subclass;
+       int i;
+       int layer;
+       int ret;
+      
+       ac = talloc_get_type(h->private_data, struct oc_async_context);
+
+       mem_ctx = talloc_new(ac);
+       if (mem_ctx == NULL) {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       ac->mod_req = talloc(ac, struct ldb_request);
+       if (ac->mod_req == NULL) {
+               talloc_free(mem_ctx);
+               return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       res = search_request->op.search.res;
-       talloc_steal(mem_ctx, res);
-       if (res->count != 1) {
-               ldb_set_errstring(module->ldb, 
-                                 talloc_asprintf(mem_ctx, "objectClass_handle: "
-                                                 "search for %s found %d != 1 objects, for entry we just added/modified",
-                                                 ldb_dn_linearize(mem_ctx, msg->dn),
-                                                 res->count));
-               /* What happened?  The above add/modify worked... */
+       ac->mod_req->operation = LDB_MODIFY;
+       ac->mod_req->controls = NULL;
+       ac->mod_req->async.context = ac;
+       ac->mod_req->async.callback = NULL;
+       ac->mod_req->async.timeout = ac->orig_req->async.timeout;
+       
+       /* use a new message structure */
+       ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req);
+       if (msg == NULL) {
+               ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: could not create new modify msg"));
                talloc_free(mem_ctx);
-               return LDB_ERR_NO_SUCH_OBJECT;
+               return LDB_ERR_OPERATIONS_ERROR;
        }
 
+       /* modify dn */
+       msg->dn = ac->orig_req->op.mod.message->dn;
+
        /* This is now the objectClass list from the database */
-       objectclass_element = ldb_msg_find_element(res->msgs[0], "objectClass");
+       objectclass_element = ldb_msg_find_element(ac->search_res->message, 
+                                                  "objectClass");
        if (!objectclass_element) {
                /* Perhaps the above was a remove?  Move along now, nothing to see here */
                talloc_free(mem_ctx);
@@ -166,6 +325,7 @@ static int objectclass_handle(struct ldb_module *module, struct ldb_request *req
        for (i=0; i < objectclass_element->num_values; i++) {
                current = talloc(mem_ctx, struct class_list);
                if (!current) {
+                       ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: out of memory allocating objectclass list"));
                        talloc_free(mem_ctx);
                        return LDB_ERR_OPERATIONS_ERROR;
                }
@@ -189,7 +349,7 @@ static int objectclass_handle(struct ldb_module *module, struct ldb_request *req
 
                /* Ensure we don't bother if there are no unsorted entries left */
                for (current = parent_class; unsorted && current; current = current->next) {
-                       const char **subclasses = ldb_subclass_list(module->ldb, current->objectclass);
+                       const char **subclasses = ldb_subclass_list(ac->module->ldb, current->objectclass);
 
                        /* Walk the list of possible subclasses in unsorted */
                        for (poss_subclass = unsorted; poss_subclass; ) {
@@ -231,78 +391,162 @@ static int objectclass_handle(struct ldb_module *module, struct ldb_request *req
         */
        DLIST_CONCATENATE(sorted, unsorted, struct class_list *);
 
-       modify_msg = ldb_msg_new(mem_ctx);
-       if (!modify_msg) {
-               talloc_free(mem_ctx);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-       modify_msg->dn = talloc_reference(modify_msg, msg->dn);
-
        /* We must completely replace the existing objectClass entry.
         * We could do a constrained add/del, but we are meant to be
         * in a transaction... */
 
-       ret = ldb_msg_add_empty(modify_msg, "objectClass", LDB_FLAG_MOD_REPLACE);
+       ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE);
        if (ret != LDB_SUCCESS) {
+               ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "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) {
-               ret = ldb_msg_add_string(modify_msg, "objectClass", current->objectclass);
+               ret = ldb_msg_add_string(msg, "objectClass", current->objectclass);
                if (ret != LDB_SUCCESS) {
+                       ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: could not re-add sorted objectclass to modify msg"));
                        talloc_free(mem_ctx);
                        return ret;
                }
        }
 
-       ret = ldb_msg_sanity_check(modify_msg);
+       ret = ldb_msg_sanity_check(msg);
        if (ret != LDB_SUCCESS) {
                talloc_free(mem_ctx);
                return ret;
        }
 
-       modify_request = talloc(mem_ctx, struct ldb_request);
-       if (!modify_request) {
-               talloc_free(mem_ctx);
+
+       h->state = LDB_ASYNC_INIT;
+       h->status = LDB_SUCCESS;
+
+       ac->step = OC_DO_MOD;
+
+       talloc_free(mem_ctx);
+       /* perform the search */
+       return ldb_next_request(ac->module, ac->mod_req);
+}
+
+static int oc_async_wait(struct ldb_async_handle *handle) {
+       struct oc_async_context *ac;
+       int ret;
+    
+       if (!handle || !handle->private_data) {
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       modify_request->operation = LDB_REQ_MODIFY;
-       modify_request->op.mod.message = modify_msg;
-       modify_request->controls = NULL;
+       if (handle->state == LDB_ASYNC_DONE) {
+               return handle->status;
+       }
 
-       /* And now push the write into the database */
-       ret = ldb_next_request(module, modify_request);
-       
-       talloc_free(mem_ctx);
+       handle->state = LDB_ASYNC_PENDING;
+       handle->status = LDB_SUCCESS;
+
+       ac = talloc_get_type(handle->private_data, struct oc_async_context);
+
+       switch (ac->step) {
+       case OC_DO_REQ:
+               ret = ldb_async_wait(ac->down_req->async.handle, LDB_WAIT_NONE);
+
+               if (ret != LDB_SUCCESS) {
+                       handle->status = ret;
+                       goto done;
+               }
+               if (ac->down_req->async.handle->status != LDB_SUCCESS) {
+                       handle->status = ac->down_req->async.handle->status;
+                       goto done;
+               }
+
+               if (ac->down_req->async.handle->state != LDB_ASYNC_DONE) {
+                       return LDB_SUCCESS;
+               }
+
+               /* mods done, go on */
+               return objectclass_search_self(handle);
+
+       case OC_SEARCH_SELF:
+               ret = ldb_async_wait(ac->search_req->async.handle, LDB_WAIT_NONE);
+
+               if (ret != LDB_SUCCESS) {
+                       handle->status = ret;
+                       goto done;
+               }
+               if (ac->search_req->async.handle->status != LDB_SUCCESS) {
+                       handle->status = ac->search_req->async.handle->status;
+                       goto done;
+               }
+
+               if (ac->search_req->async.handle->state != LDB_ASYNC_DONE) {
+                       return LDB_SUCCESS;
+               }
+
+               /* self search done, go on */
+               return objectclass_do_mod(handle);
+
+       case OC_DO_MOD:
+               ret = ldb_async_wait(ac->mod_req->async.handle, LDB_WAIT_NONE);
+
+               if (ret != LDB_SUCCESS) {
+                       handle->status = ret;
+                       goto done;
+               }
+               if (ac->mod_req->async.handle->status != LDB_SUCCESS) {
+                       handle->status = ac->mod_req->async.handle->status;
+                       goto done;
+               }
+
+               if (ac->mod_req->async.handle->state != LDB_ASYNC_DONE) {
+                       return LDB_SUCCESS;
+               }
+
+               break;
+               
+       default:
+               ret = LDB_ERR_OPERATIONS_ERROR;
+               goto done;
+       }
+
+       ret = LDB_SUCCESS;
+
+done:
+       handle->state = LDB_ASYNC_DONE;
        return ret;
 }
 
-static int objectclass_request(struct ldb_module *module, struct ldb_request *req)
-{
-       switch (req->operation) {
+static int oc_async_wait_all(struct ldb_async_handle *handle) {
 
-               /* only care about add and modify requests */
-       case LDB_REQ_ADD:
-               return objectclass_handle(module, req, req->op.add.message);
+       int ret;
 
-       case LDB_REQ_MODIFY:
-               return objectclass_handle(module, req, req->op.mod.message);
+       while (handle->state != LDB_ASYNC_DONE) {
+               ret = oc_async_wait(handle);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+       }
 
-       default:
-               return ldb_next_request(module, req);
+       return handle->status;
+}
 
+static int objectclass_async_wait(struct ldb_async_handle *handle, enum ldb_async_wait_type type)
+{
+       if (type == LDB_WAIT_ALL) {
+               return oc_async_wait_all(handle);
+       } else {
+               return oc_async_wait(handle);
        }
 }
 
 static const struct ldb_module_ops objectclass_ops = {
        .name              = "objectclass",
-       .request           = objectclass_request,
+       .add           = objectclass_add,
+       .modify        = objectclass_modify,
+       .async_wait    = objectclass_async_wait
 };
 
 int ldb_objectclass_init(void)
 {
        return ldb_register_module(&objectclass_ops);
 }
+
index e91d4db63038a990e9170ba86b595a2454c73ce6..ca044842e9f683d5e5ef4415d06daa3e8ca74010 100644 (file)
@@ -85,5 +85,5 @@ vendorVersion: ${VERSION}
 # - samldb must be before password_hash, because password_hash checks that the objectclass is of type person (filled in by samldb)
 
 dn: @MODULES
-@LIST: rootdse,kludge_acl,paged_results,server_sort,extended_dn,asq,samldb,password_hash,operational,objectguid,rdn_name
+@LIST: rootdse,kludge_acl,paged_results,server_sort,extended_dn,asq,samldb,objectclass,password_hash,operational,objectguid,rdn_name
 
index 148c3d050f559561143a2556d30b55160c632a2d..de00113573cdb18fb12c53b4c5e8db597b8958a6 100755 (executable)
@@ -37,36 +37,118 @@ objectClass: person
 cn: LDAPtestUSER
 ");
        if (!ok) {
-               println(ldb.errstring());
-               assert(ok);
+               ok = ldb.del("cn=ldaptestuser,cn=users," + base_dn);
+               if (!ok) {
+                       println(ldb.errstring());
+                       assert(ok);
+               }
+               ok = ldb.add("
+dn: cn=ldaptestuser,cn=users," + base_dn + "
+objectClass: user
+objectClass: person
+cn: LDAPtestUSER
+");
+               if (!ok) {
+                       println(ldb.errstring());
+                       assert(ok);
+               }
        }
 
        ok = ldb.add("
 dn: cn=ldaptestuser2,cn=users," + base_dn + "
+objectClass: person
 objectClass: user
+cn: LDAPtestUSER2
+");
+       if (!ok) {
+               ok = ldb.del("cn=ldaptestuser2,cn=users," + base_dn);
+               if (!ok) {
+                       println(ldb.errstring());
+                       assert(ok);
+               }
+               ok = ldb.add("
+dn: cn=ldaptestuser2,cn=users," + base_dn + "
 objectClass: person
+objectClass: user
 cn: LDAPtestUSER2
+");
+               if (!ok) {
+                       println(ldb.errstring());
+                       assert(ok);
+               }
+       }
+
+       ok = ldb.add("
+dn: cn=ldaptestutf8user èùéìòà ,cn=users," + base_dn + "
+objectClass: user
 ");
        if (!ok) {
-               println(ldb.errstring());
-               assert(ok);
+               ok = ldb.del("cn=ldaptestutf8user èùéìòà ,cn=users," + base_dn);
+               if (!ok) {
+                       println(ldb.errstring());
+                       assert(ok);
+               }
+       ok = ldb.add("
+dn: cn=ldaptestutf8user èùéìòà ,cn=users," + base_dn + "
+objectClass: user
+");
+               if (!ok) {
+                       println(ldb.errstring());
+                       assert(ok);
+               }
        }
 
        ok = ldb.add("
-dn: cn=ldaptestutf8user   èùéìòà ,cn=users," + base_dn + "
+dn: cn=ldaptestutf8user2  èùéìòà ,cn=users," + base_dn + "
 objectClass: user
 ");
        if (!ok) {
-               println(ldb.errstring());
-               assert(ok);
+               ok = ldb.del("cn=ldaptestutf8user2  èùéìòà ,cn=users," + base_dn);
+               if (!ok) {
+                       println(ldb.errstring());
+                       assert(ok);
+               }
+       ok = ldb.add("
+dn: cn=ldaptestutf8user2  èùéìòà ,cn=users," + base_dn + "
+objectClass: user
+");
+               if (!ok) {
+                       println(ldb.errstring());
+                       assert(ok);
+               }
        }
 
-       println("Testing ldb.search");
+       println("Testing ldb.search for (&(cn=ldaptestuser)(objectClass=user))");
        var res = ldb.search("(&(cn=ldaptestuser)(objectClass=user))");
+       if (res.length != 1) {
+               println("Could not find (&(cn=ldaptestuser)(objectClass=user))");
+               assert(res.length == 1);
+       }
 
        assert(res[0].dn == "cn=ldaptestuser,cn=users," + base_dn);
        assert(res[0].cn == "ldaptestuser");
        assert(res[0].name == "ldaptestuser");
+       assert(res[0].objectClass[0] == "top");
+       assert(res[0].objectClass[1] == "person");
+       assert(res[0].objectClass[2] == "organizationalPerson");
+       assert(res[0].objectClass[3] == "user");
+       assert(res[0].objectGUID != undefined);
+       assert(res[0].whenCreated != undefined);
+
+       println("Testing ldb.search for (&(cn=ldaptestuser)(objectClass=user))");
+       var res = ldb.search("(&(cn=ldaptestuser)(objectClass=user))");
+       if (res.length != 1) {
+               println("Could not find (&(cn=ldaptestuser)(objectClass=user))");
+               assert(res.length == 1);
+       }
+
+       assert(res[0].dn == "cn=ldaptestuser,cn=users," + base_dn);
+       assert(res[0].cn == "ldaptestuser");
+       assert(res[0].name == "ldaptestuser");
+       assert(res[0].objectClass[0] == "top");
+       assert(res[0].objectClass[1] == "person");
+       assert(res[0].objectClass[2] == "organizationalPerson");
+       assert(res[0].objectClass[3] == "user");
        assert(res[0].objectGUID != undefined);
        assert(res[0].whenCreated != undefined);
 
@@ -76,12 +158,20 @@ objectClass: user
                assert(ok);
        }
 
-       println("Testing ldb.search");
+       println("Testing ldb.search for (&(cn=ldaptestUSer2)(objectClass=user))");
        var res = ldb.search("(&(cn=ldaptestUSer2)(objectClass=user))");
+       if (res.length != 1) {
+               println("Could not find (&(cn=ldaptestUSer2)(objectClass=user))");
+               assert(res.length == 1);
+       }
 
        assert(res[0].dn == "cn=ldaptestuser2,cn=users," + base_dn);
        assert(res[0].cn == "ldaptestuser2");
        assert(res[0].name == "ldaptestuser2");
+       assert(res[0].objectClass[0] == "top");
+       assert(res[0].objectClass[1] == "person");
+       assert(res[0].objectClass[2] == "organizationalPerson");
+       assert(res[0].objectClass[3] == "user");
        assert(res[0].objectGUID != undefined);
        assert(res[0].whenCreated != undefined);
 
@@ -91,12 +181,21 @@ objectClass: user
                assert(ok);
        }
 
-       println("Testing ldb.search");
+       println("Testing ldb.search for (&(cn=ldaptestutf8user ÈÙÉÌÒÀ)(objectClass=user))");
        var res = ldb.search("(&(cn=ldaptestutf8user ÈÙÉÌÒÀ)(objectClass=user))");
 
-       assert(res[0].dn == "cn=ldaptestutf8user   èùéìòà,cn=users," + base_dn);
-       assert(res[0].cn == "ldaptestutf8user   èùéìòà");
-       assert(res[0].name == "ldaptestutf8user   èùéìòà");
+       if (res.length != 1) {
+               println("Could not find (&(cn=ldaptestutf8user ÈÙÉÌÒÀ)(objectClass=user))");
+               assert(res.length == 1);
+       }
+
+       assert(res[0].dn == "cn=ldaptestutf8user èùéìòà,cn=users," + base_dn);
+       assert(res[0].cn == "ldaptestutf8user èùéìòà");
+       assert(res[0].name == "ldaptestutf8user èùéìòà");
+       assert(res[0].objectClass[0] == "top");
+       assert(res[0].objectClass[1] == "person");
+       assert(res[0].objectClass[2] == "organizationalPerson");
+       assert(res[0].objectClass[3] == "user");
        assert(res[0].objectGUID != undefined);
        assert(res[0].whenCreated != undefined);
 
@@ -105,6 +204,17 @@ objectClass: user
                println(ldb.errstring());
                assert(ok);
        }
+
+       println("Testing ldb.search for (&(cn=ldaptestutf8user2 ÈÙÉÌÒÀ)(objectClass=user))");
+       var res = ldb.search("(&(cn=ldaptestutf8user ÈÙÉÌÒÀ)(objectClass=user))");
+
+       if (res.length != 1) {
+               println("Could not find (expect space collapse, win2k3 fails) (&(cn=ldaptestutf8user2 ÈÙÉÌÒÀ)(objectClass=user))");
+       } else {
+               assert(res[0].dn == "cn=ldaptestutf8user2 èùéìòà,cn=users," + base_dn);
+               assert(res[0].cn == "ldaptestutf8user2 èùéìòà");
+       }
+
 }
 
 function find_basedn(ldb)