r26182: Extend our linked attribute testsuite to cover many more possible
authorAndrew Bartlett <abartlet@samba.org>
Wed, 28 Nov 2007 03:24:12 +0000 (04:24 +0100)
committerStefan Metzmacher <metze@samba.org>
Fri, 21 Dec 2007 04:46:38 +0000 (05:46 +0100)
modifications, and then extend our implementation to match.

Andrew Bartlett
(This used to be commit 65d17f0ad7ead438333abcccb0bd56b038ffb88e)

source4/dsdb/samdb/ldb_modules/linked_attributes.c
testprogs/ejs/ldap.js

index fd36c16d5699c2a5505c12a705d1015c187a2792..803d24e34ea79db557e5bf6698273ad07d1c60c2 100644 (file)
 #include "dsdb/samdb/samdb.h"
 
 struct linked_attributes_context {
-       enum la_step {LA_SEARCH, LA_DO_OPS} step;
+       enum la_step {LA_SEARCH, LA_DO_OPS, LA_DO_ORIG} step;
        struct ldb_module *module;
        struct ldb_handle *handle;
        struct ldb_request *orig_req;
 
        struct ldb_request *search_req;
        struct ldb_request **down_req;
+       struct ldb_request *orig_down_req;
+
        int num_requests;
        int finished_requests;
 
        const char **linked_attrs;
 };
 
+struct replace_context {
+       struct linked_attributes_context *ac;
+       struct ldb_message_element *el;
+};
+
+static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares);
+
 static struct linked_attributes_context *linked_attributes_init_handle(struct ldb_request *req, 
                                                                 struct ldb_module *module)
 {
@@ -73,6 +82,14 @@ static struct linked_attributes_context *linked_attributes_init_handle(struct ld
        ac->module = module;
        ac->handle = h;
        ac->orig_req = req;
+       
+       ac->orig_down_req = talloc(ac, struct ldb_request);
+       if (!ac->orig_down_req) {
+               ldb_oom(ac->module->ldb);
+               return NULL;
+       }
+
+       *ac->orig_down_req = *req;
 
        req->handle = h;
 
@@ -211,7 +228,7 @@ static int setup_modifies(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
 /* add */
 static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req)
 {
-       int i, ret;
+       int i;
        struct linked_attributes_context *ac;
 
        const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
@@ -231,29 +248,8 @@ static int linked_attributes_add(struct ldb_module *module, struct ldb_request *
                return LDB_ERR_OPERATIONS_ERROR;
        }
        
-       /* prepare the first operation */
-       ac->down_req = talloc_realloc(ac, ac->down_req, 
-                                     struct ldb_request *, 1);
-       if (!ac->down_req) {
-               ldb_oom(ac->module->ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-       
-       ac->down_req[0] = talloc(ac->down_req, struct ldb_request);
-       if (!ac->down_req[0]) {
-               ldb_oom(ac->module->ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-       *(ac->down_req[0]) = *req; /* copy the request */
-       
-       ac->num_requests++;
+       ac->step = LA_DO_OPS;
        
-       /* Run the original request */
-       ret = ldb_next_request(module, ac->down_req[0]);
-       if (ret != LDB_SUCCESS) {
-               return ret;
-       }
-
        /* Need to ensure we only have forward links being specified */
        for (i=0; i < req->op.add.message->num_elements; i++) {
                const struct ldb_message_element *el = &req->op.add.message->elements[i];
@@ -279,12 +275,77 @@ static int linked_attributes_add(struct ldb_module *module, struct ldb_request *
                /* Even link IDs are for the originating attribute */
        }
 
-       ac->step = LA_DO_OPS;
-       
        /* Now call the common routine to setup the modifies across all the attributes */
        return setup_modifies(module->ldb, ac, ac, req->op.add.message, NULL, req->op.add.message->dn);
 }
 
+static int linked_attributes_mod_replace_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares) 
+{
+       struct replace_context *ac2 = talloc_get_type(context, struct replace_context);
+       struct linked_attributes_context *ac = ac2->ac;
+    
+       /* OK, we have one search result here: */
+
+       /* Only entries are interesting, and we only want the olddn */
+       if (ares->type == LDB_REPLY_ENTRY
+           && ldb_dn_compare(ares->message->dn, ac->orig_req->op.mod.message->dn) == 0) {
+               /* only bother at all if there were some linked attributes found */
+               struct ldb_message_element *search_el
+                       = ldb_msg_find_element(ares->message,
+                                              ac2->el->name);
+               
+               /* See if this element already exists */
+               if (search_el) {
+                       int ret;
+                       struct ldb_message *msg = ldb_msg_new(ac);
+                       if (!msg) {
+                               ldb_oom(ac->module->ldb);
+                               return LDB_ERR_OPERATIONS_ERROR;
+                       }
+
+                       /* Lazy option:  Delete and add the elements on all members */
+                       msg->num_elements = 1;
+                       msg->elements = search_el;
+                       msg->dn = ac->orig_req->op.mod.message->dn;
+                       
+                       ret = setup_modifies(ac->module->ldb, ac2, ac, msg, ares->message->dn, NULL);
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+
+                       msg->elements = ac2->el;
+
+                       ret = setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ares->message->dn);
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+                       
+               } else {
+                       /* Looks like it doesn't exist, process like an 'add' */
+                       struct ldb_message *msg = ldb_msg_new(ac);
+                       if (!msg) {
+                               ldb_oom(ac->module->ldb);
+                               return LDB_ERR_OPERATIONS_ERROR;
+                       }
+                       msg->num_elements = 1;
+                       msg->elements = ac2->el;
+                       msg->dn = ac->orig_req->op.mod.message->dn;
+
+                       return setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ac->orig_req->op.mod.message->dn);
+               }
+               talloc_free(ares);
+               return LDB_SUCCESS;
+       } else if (ares->type == LDB_REPLY_ENTRY) {
+               /* Guh?  We only asked for this DN */
+               return LDB_ERR_OPERATIONS_ERROR;
+
+       } else {
+               talloc_free(ares);
+               return LDB_SUCCESS;
+       }
+       
+       
+}
 /* modify */
 static int linked_attributes_modify(struct ldb_module *module, struct ldb_request *req)
 {
@@ -293,7 +354,7 @@ static int linked_attributes_modify(struct ldb_module *module, struct ldb_reques
        /* Determine the effect of the modification */
        /* Apply the modify to the linked entry */
 
-       int i, j, ret;
+       int i, j;
        struct linked_attributes_context *ac;
 
        const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
@@ -314,31 +375,11 @@ static int linked_attributes_modify(struct ldb_module *module, struct ldb_reques
        }
        
        /* prepare the first operation */
-       ac->down_req = talloc_realloc(ac, ac->down_req, 
-                                     struct ldb_request *, 1);
-       if (!ac->down_req) {
-               ldb_oom(ac->module->ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-       
-       ac->down_req[0] = talloc(ac->down_req, struct ldb_request);
-       if (!ac->down_req[0]) {
-               ldb_oom(ac->module->ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-       *(ac->down_req[0]) = *req; /* copy the request */
-       
-       ac->num_requests++;
-       
        ac->step = LA_DO_OPS;
 
-       /* Run the original request */
-       ret = ldb_next_request(module, ac->down_req[0]);
-       if (ret != LDB_SUCCESS) {
-               return ret;
-       }
-
        for (i=0; i < req->op.mod.message->num_elements; i++) {
+               int ret;
+               struct ldb_request *new_req;
                const struct dsdb_attribute *target_attr;
                const struct ldb_message_element *el = &req->op.mod.message->elements[i];
                const struct dsdb_attribute *schema_attr
@@ -370,18 +411,109 @@ static int linked_attributes_modify(struct ldb_module *module, struct ldb_reques
                        return LDB_ERR_OBJECT_CLASS_VIOLATION;                  
                }
 
-               if ((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE) {
-                       ldb_asprintf_errstring(module->ldb, 
-                                              "attribute %s may not be replaced, only added or deleted", req->op.mod.message->elements[i].name);
-                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               if (((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE)
+                   && el->num_values > 0) {
+                       struct replace_context *ac2 = talloc(ac, struct replace_context);
+                       const char **attrs = talloc_array(ac, const char *, 2);
+                       if (!attrs || !ac2) {
+                               ldb_oom(ac->module->ldb);
+                               return LDB_ERR_OPERATIONS_ERROR;
+                       }
+                       attrs[0] = el->name;
+                       attrs[1] = NULL;
+
+                       ac2->ac = ac;
+                       ac2->el = el;
+
+                       /* We need to setup a search, compare with the list, and then setup add/del as required */
+                       
+                       /* The callback does all the hard work here */
+                       ret = ldb_build_search_req(&new_req, module->ldb, req,
+                                                  req->op.mod.message->dn, 
+                                                  LDB_SCOPE_BASE,
+                                                  "(objectClass=*)",
+                                                  attrs,
+                                                  NULL, 
+                                                  ac2, 
+                                                  linked_attributes_mod_replace_search_callback);
+                       
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+                       
+                       talloc_steal(new_req, attrs);
+                       
+                       /* Create a spot in the list for the requests */
+                       ac->down_req = talloc_realloc(ac, ac->down_req, 
+                                                     struct ldb_request *, ac->num_requests + 1);
+                       if (!ac->down_req) {
+                               ldb_oom(ac->module->ldb);
+                               return LDB_ERR_OPERATIONS_ERROR;
+                       }
+
+                       ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
+                       ac->num_requests++;
+
+                       ret = ldb_next_request(module, new_req);
+                       
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+                       
+                       continue;
+               } else if (((el->flags & LDB_FLAG_MOD_MASK) & (LDB_FLAG_MOD_DELETE|LDB_FLAG_MOD_REPLACE)) 
+                          && el->num_values == 0) {
+                       const char **attrs = talloc_array(ac, const char *, 2);
+                       if (!attrs) {
+                               ldb_oom(ac->module->ldb);
+                               return LDB_ERR_OPERATIONS_ERROR;
+                       }
+                       attrs[0] = el->name;
+                       attrs[1] = NULL;
+
+                       /* We need to setup a search, and then setup del as required */
+                       
+                       /* The callback does all the hard work here, acting identically to if we had delted the whole entry */
+                       ret = ldb_build_search_req(&new_req, module->ldb, req,
+                                                  req->op.mod.message->dn, 
+                                                  LDB_SCOPE_BASE,
+                                                  "(objectClass=*)",
+                                                  attrs,
+                                                  NULL, 
+                                                  ac, 
+                                                  linked_attributes_rename_del_search_callback);
+
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+                       
+                       talloc_steal(new_req, attrs);
+                       
+                       /* Create a spot in the list for the requests */
+                       ac->down_req = talloc_realloc(ac, ac->down_req, 
+                                                     struct ldb_request *, ac->num_requests + 1);
+                       if (!ac->down_req) {
+                               ldb_oom(ac->module->ldb);
+                               return LDB_ERR_OPERATIONS_ERROR;
+                       }
+
+                       ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
+                       ac->num_requests++;
+                       
+                       ret = ldb_next_request(module, new_req);
+               
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+                       
+                       continue;
                }
                /* Prepare the modify (mod element) on the targets */
 
                /* For each value being moded, we need to setup the modify */
                for (j=0; j < el->num_values; j++) {
-                       struct ldb_request *new_req;
                        /* Create the modify request */
-                       struct ldb_message *new_msg = ldb_msg_new(ac->down_req);
+                       struct ldb_message *new_msg = ldb_msg_new(ac);
                        if (!new_msg) {
                                ldb_oom(module->ldb);
                                return LDB_ERR_OPERATIONS_ERROR;
@@ -406,7 +538,7 @@ static int linked_attributes_modify(struct ldb_module *module, struct ldb_reques
                                return ret;
                        }
 
-                       ret = ldb_build_mod_req(&new_req, module->ldb, ac->down_req,
+                       ret = ldb_build_mod_req(&new_req, module->ldb, ac,
                                                new_msg,
                                                NULL,
                                                NULL,
@@ -426,7 +558,7 @@ static int linked_attributes_modify(struct ldb_module *module, struct ldb_reques
                                ldb_oom(ac->module->ldb);
                                return LDB_ERR_OPERATIONS_ERROR;
                        }
-                       ac->down_req[ac->num_requests] = new_req;
+                       ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
                        ac->num_requests++;
 
                        /* Run the new request */
@@ -436,12 +568,11 @@ static int linked_attributes_modify(struct ldb_module *module, struct ldb_reques
                        }
                }
        }
-       return ret;
+       return LDB_SUCCESS;
 }
 
 static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares) 
 {
-       struct ldb_request *req;
        struct linked_attributes_context *ac = talloc_get_type(context, struct linked_attributes_context);
        struct ldb_dn *olddn, *newdn;
     
@@ -452,6 +583,13 @@ static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb,
                newdn = NULL;
                break;
        } 
+       /* This isn't the general modify case, just the modify when we are asked to delete all values */
+       case LDB_MODIFY:
+       {
+               olddn = ac->orig_req->op.mod.message->dn;
+               newdn = NULL;
+               break;
+       } 
        case LDB_RENAME:
        {
                olddn = ac->orig_req->op.rename.olddn;
@@ -478,21 +616,6 @@ static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb,
        } else if (ares->type == LDB_REPLY_ENTRY) {
                /* Guh?  We only asked for this DN */
                return LDB_ERR_OPERATIONS_ERROR;
-       } else if (ares->type == LDB_REPLY_DONE) {
-               req = talloc(ac, struct ldb_request);
-               *req = *ac->orig_req;
-               talloc_free(ares);
-
-               ac->down_req = talloc_realloc(ac, ac->down_req, 
-                                             struct ldb_request *, ac->num_requests + 1);
-               if (!ac->down_req) {
-                       ldb_oom(ldb);
-                       return LDB_ERR_OPERATIONS_ERROR;
-               }
-               ac->down_req[ac->num_requests] = req;
-               ac->num_requests++;
-               
-               return ldb_next_request(ac->module, req);
 
        } else {
                talloc_free(ares);
@@ -660,6 +783,27 @@ static int linked_attributes_wait_none(struct ldb_handle *handle) {
                                return LDB_SUCCESS;
                        }
                }
+
+               /* Now run the original request */
+               ac->step = LA_DO_ORIG;
+               return ldb_next_request(ac->module, ac->orig_down_req);
+
+       case LA_DO_ORIG:
+               ret = ldb_wait(ac->orig_down_req->handle, LDB_WAIT_NONE);
+               
+               if (ret != LDB_SUCCESS) {
+                       handle->status = ret;
+                       goto done;
+               }
+               if (ac->orig_down_req->handle->status != LDB_SUCCESS) {
+                       handle->status = ac->orig_down_req->handle->status;
+                       goto done;
+               }
+               
+               if (ac->orig_down_req->handle->state != LDB_ASYNC_DONE) {
+                       return LDB_SUCCESS;
+               }
+               ret = LDB_SUCCESS;
        }
 
 done:
index 40ce404890fe2136ad1d2a59f12bcfac738ee9d0..877240890d57ccc142bea8ce030450d233bc8574 100755 (executable)
@@ -230,16 +230,17 @@ member: cn=ldaptestuser3,cn=users," + base_dn + "
        assert(res.msgs[0].cn == "ldaptestUSER3");
        assert(res.msgs[0].name == "ldaptestUSER3");
 
-       println("Testing ldb.search for (dn=CN=ldaptestUSER3,CN=Users," + base_dn + ")");
-       var res = ldb.search("(dn=CN=ldaptestUSER3,CN=Users," + base_dn + ")");
-       if (res.error != 0 || res.msgs.length != 1) {
-               println("Could not find (dn=CN=ldaptestUSER3,CN=Users," + base_dn + ")");
-               assert(res.error == 0);
-               assert(res.msgs.length == 1);
-       }
-       assert(res.msgs[0].dn == ("CN=ldaptestUSER3,CN=Users," + base_dn));
-       assert(res.msgs[0].cn == "ldaptestUSER3");
-       assert(res.msgs[0].name == "ldaptestUSER3");
+// This is a Samba special, and does not exist in real AD
+//     println("Testing ldb.search for (dn=CN=ldaptestUSER3,CN=Users," + base_dn + ")");
+//     var res = ldb.search("(dn=CN=ldaptestUSER3,CN=Users," + base_dn + ")");
+//     if (res.error != 0 || res.msgs.length != 1) {
+//             println("Could not find (dn=CN=ldaptestUSER3,CN=Users," + base_dn + ")");
+//             assert(res.error == 0);
+//             assert(res.msgs.length == 1);
+//     }
+//     assert(res.msgs[0].dn == ("CN=ldaptestUSER3,CN=Users," + base_dn));
+//     assert(res.msgs[0].cn == "ldaptestUSER3");
+//     assert(res.msgs[0].name == "ldaptestUSER3");
 
        println("Testing ldb.search for (distinguishedName=CN=ldaptestUSER3,CN=Users," + base_dn + ")");
        var res = ldb.search("(distinguishedName=CN=ldaptestUSER3,CN=Users," + base_dn + ")");
@@ -347,6 +348,18 @@ cn: LDAPtestUSER4
                }
        }
 
+       ok = ldb.modify("
+dn: cn=ldaptestgroup2,cn=users," + base_dn + "
+changetype: modify
+add: member
+member: cn=ldaptestuser4,cn=ldaptestcontainer," + base_dn + "
+");
+       if (ok.error != 0) {
+               println("Failure adding ldaptestuser4 to a group");
+               println(ok.errstr);
+               assert(ok.error == 0);
+       }
+       
        println("Testing ldb.rename of cn=ldaptestcontainer," + base_dn + " to cn=ldaptestcontainer2," + base_dn);
        ok = ldb.rename("CN=ldaptestcontainer," + base_dn, "CN=ldaptestcontainer2," + base_dn);
        if (ok.error != 0) {
@@ -385,6 +398,15 @@ cn: LDAPtestUSER4
        }
 
        assert(res.msgs[0].dn == ("CN=ldaptestuser4,CN=ldaptestcontainer2," + base_dn));
+       assert(strupper(res.msgs[0].memberOf[0]) == strupper(("CN=ldaptestgroup2,CN=Users," + base_dn)));
+
+       println("Testing ldb.search for (&(member=CN=ldaptestuser4,CN=ldaptestcontainer2," + base_dn + ")(objectclass=group)) to check subtree renames and linked attributes");
+       var res = ldb.search("(&(member=CN=ldaptestuser4,CN=ldaptestcontainer2," + base_dn + ")(objectclass=group))", base_dn, ldb.SCOPE_SUBTREE);
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(member=CN=ldaptestuser4,CN=ldaptestcontainer2," + base_dn + ")(objectclass=group)), perhaps linked attributes are not conistant with subtree renames?");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
 
        println("Testing ldb.rename (into itself) of cn=ldaptestcontainer2," + base_dn + " to cn=ldaptestcontainer,cn=ldaptestcontainer2," + base_dn);
        ok = ldb.rename("cn=ldaptestcontainer2," + base_dn, "cn=ldaptestcontainer,cn=ldaptestcontainer2," + base_dn);
@@ -734,12 +756,109 @@ objectClass: user
        assert(res.msgs[0].member[0] == ("CN=ldaptestuser2,CN=Users," + base_dn));
        assert(res.msgs[0].member.length == 1);
 
+       ok = ldb.modify("
+dn: cn=ldaptestgroup2,cn=users," + base_dn + "
+changetype: modify
+replace: member
+member: CN=ldaptestuser2,CN=Users," + base_dn + "
+member: CN=ldaptestutf8user èùéìòà,CN=Users," + base_dn + "
+");
+       if (ok.error != 0) {
+               println("Failure testing replace of linked attributes");
+               println(ok.errstr);
+               assert(ok.error == 0);
+       }
+       
+       println("Testing Linked attribute behaviours");
+       ok = ldb.modify("
+dn: cn=ldaptestgroup2,cn=users," + base_dn + "
+changetype: modify
+delete: member
+");
+       if (ok.error != 0) {
+               println("Failure testing delete of linked attributes");
+               println(ok.errstr);
+               assert(ok.error == 0);
+       }
+       
+       ok = ldb.modify("
+dn: cn=ldaptestgroup2,cn=users," + base_dn + "
+changetype: modify
+add: member
+member: CN=ldaptestuser2,CN=Users," + base_dn + "
+member: CN=ldaptestutf8user èùéìòà,CN=Users," + base_dn + "
+");
+       if (ok.error != 0) {
+               println("Failure testing add of linked attributes");
+               println(ok.errstr);
+               assert(ok.error == 0);
+       }
+       
+       ok = ldb.modify("
+dn: cn=ldaptestgroup2,cn=users," + base_dn + "
+changetype: modify
+replace: member
+");
+       if (ok.error != 0) {
+               println("Failure testing replace of linked attributes");
+               println(ok.errstr);
+               assert(ok.error == 0);
+       }
+       
+       ok = ldb.modify("
+dn: cn=ldaptestgroup2,cn=users," + base_dn + "
+changetype: modify
+add: member
+member: CN=ldaptestuser2,CN=Users," + base_dn + "
+member: CN=ldaptestutf8user èùéìòà,CN=Users," + base_dn + "
+");
+       if (ok.error != 0) {
+               println("Failure testing add of linked attributes");
+               println(ok.errstr);
+               assert(ok.error == 0);
+       }
+       
+       ok = ldb.modify("
+dn: cn=ldaptestgroup2,cn=users," + base_dn + "
+changetype: modify
+delete: member
+member: CN=ldaptestutf8user èùéìòà,CN=Users," + base_dn + "
+");
+       if (ok.error != 0) {
+               println("Failure testing replace of linked attributes");
+               println(ok.errstr);
+               assert(ok.error == 0);
+       }
+       
+       var res = ldb.search("(&(cn=ldaptestgroup2)(objectClass=group))", base_dn, ldb.SCOPE_SUBTREE, attrs);
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(cn=ldaptestgroup2)(objectClass=group))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       assert(res.msgs[0].dn == ("CN=ldaptestgroup2,CN=Users," + base_dn));
+       assert(res.msgs[0].member[0] == ("CN=ldaptestuser2,CN=Users," + base_dn));
+       assert(res.msgs[0].member.length == 1);
+
        ok = ldb.del(("CN=ldaptestuser2,CN=Users," + base_dn));
        if (ok.error != 0) {
                println(ok.errstr);
                assert(ok.error == 0);
        }
 
+        var attrs = new Array("cn", "name", "objectClass", "objectGUID", "whenCreated", "nTSecurityDescriptor", "member");
+       println("Testing ldb.search for (&(cn=ldaptestgroup2)(objectClass=group)) to check linked delete");
+       var res = ldb.search("(&(cn=ldaptestgroup2)(objectClass=group))", base_dn, ldb.SCOPE_SUBTREE, attrs);
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(cn=ldaptestgroup2)(objectClass=group)) to check linked delete");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       assert(res.msgs[0].dn == ("CN=ldaptestgroup2,CN=Users," + base_dn));
+       assert(res.msgs[0].member == undefined);
+
        println("Testing ldb.search for (&(cn=ldaptestutf8user ÈÙÉÌÒÀ)(objectClass=user))");
        var res = ldb.search("(&(cn=ldaptestutf8user ÈÙÉÌÒÀ)(objectClass=user))");