diff --git a/source4/lib/ldb/config.mk b/source4/lib/ldb/config.mk index 68dbdd10f02..85a6cb6c677 100644 --- a/source4/lib/ldb/config.mk +++ b/source4/lib/ldb/config.mk @@ -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 diff --git a/source4/lib/ldb/modules/objectclass.c b/source4/lib/ldb/modules/objectclass.c index a9c51341a8c..4423f82aedb 100644 --- a/source4/lib/ldb/modules/objectclass.c +++ b/source4/lib/ldb/modules/objectclass.c @@ -1,7 +1,8 @@ /* ldb database library - Copyright (C) Andrew Bartlett 2005 + Copyright (C) Simo Sorce 2006 + Copyright (C) Andrew Bartlett 2005-2006 ** NOTE! The following LGPL license applies to the ldb ** library. This does NOT imply that all of Samba is released @@ -35,98 +36,256 @@ #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 { -static int objectclass_handle(struct ldb_module *module, struct ldb_request *req, const struct ldb_message *msg) + 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 struct ldb_async_handle *oc_init_handle(struct ldb_request *req, struct ldb_module *module) { + struct oc_async_context *ac; + struct ldb_async_handle *h; + + 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_add\n"); + + 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); + + /* 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; + } + + *(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"); + + /* 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); + } + + h = oc_init_handle(req, module); + if (!h) { + return LDB_ERR_OPERATIONS_ERROR; + } + ac = talloc_get_type(h->private_data, struct oc_async_context); + + /* 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; + } + + *(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 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; - 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, + 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); - struct ldb_message_element *objectclass_element; - - ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_handle\n"); - - if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */ - return ldb_next_request(module, req); - } - - ret = ldb_next_request(module, req); - - if (ret) { - return ret; - } - - if (ldb_msg_find_element(msg, "objectClass") == NULL ) { - /* No sign of the objectClass: no change, nothing to see here */ - return ret; - } - - /* Thanks to transactions: Now do a search, find the full list - * of objectClasses and do the sort */ - - mem_ctx = talloc_new(module); - if (!mem_ctx) { + mem_ctx = talloc_new(ac); + if (mem_ctx == NULL) { return LDB_ERR_OPERATIONS_ERROR; } - search_request = talloc(mem_ctx, struct ldb_request); - if (!search_request) { + ac->mod_req = talloc(ac, struct ldb_request); + if (ac->mod_req == NULL) { talloc_free(mem_ctx); 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; - - ret = ldb_next_request(module, search_request); - if (ret) { - return ret; - } - - 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) +static int oc_async_wait_all(struct ldb_async_handle *handle) { + + int ret; + + while (handle->state != LDB_ASYNC_DONE) { + ret = oc_async_wait(handle); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + return handle->status; +} + +static int objectclass_async_wait(struct ldb_async_handle *handle, enum ldb_async_wait_type type) { - switch (req->operation) { - - /* only care about add and modify requests */ - case LDB_REQ_ADD: - return objectclass_handle(module, req, req->op.add.message); - - case LDB_REQ_MODIFY: - return objectclass_handle(module, req, req->op.mod.message); - - default: - return ldb_next_request(module, req); - + 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); } + diff --git a/source4/setup/provision_init.ldif b/source4/setup/provision_init.ldif index e91d4db6303..ca044842e9f 100644 --- a/source4/setup/provision_init.ldif +++ b/source4/setup/provision_init.ldif @@ -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 diff --git a/testprogs/ejs/ldap.js b/testprogs/ejs/ldap.js index 148c3d050f5..de00113573c 100755 --- a/testprogs/ejs/ldap.js +++ b/testprogs/ejs/ldap.js @@ -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: user objectClass: person +objectClass: user cn: LDAPtestUSER2 "); if (!ok) { - println(ldb.errstring()); - assert(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 + " +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); + } } - println("Testing ldb.search"); + ok = ldb.add(" +dn: cn=ldaptestutf8user2 èùéìòà ,cn=users," + base_dn + " +objectClass: user +"); + if (!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 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)