r26419: Add a module to implement 'ambigious name resolution' by munging the
authorAndrew Bartlett <abartlet@samba.org>
Thu, 13 Dec 2007 02:07:38 +0000 (03:07 +0100)
committerStefan Metzmacher <metze@samba.org>
Fri, 21 Dec 2007 04:49:48 +0000 (05:49 +0100)
incoming LDAP filter.

Warning: Any anr search will perform a full index search.  Untill ldb
gets substring indexes, this is unavoidable.

Also implement a testsutie to show we match AD behaviour for this
important extension (used in the Active Directory Users and Computers
MMC plugin, as a genereral 'find').

This will also be useful to OpenChange, as their server needs to
implement this.

Andrew Bartlett
(This used to be commit 044b50947254ccd516c21cb156ab60ab9e3a582d)

source4/dsdb/samdb/ldb_modules/anr.c [new file with mode: 0644]
source4/dsdb/samdb/ldb_modules/config.mk
source4/scripting/libjs/provision.js
testprogs/ejs/ldap.js

diff --git a/source4/dsdb/samdb/ldb_modules/anr.c b/source4/dsdb/samdb/ldb_modules/anr.c
new file mode 100644 (file)
index 0000000..44b47aa
--- /dev/null
@@ -0,0 +1,311 @@
+/* 
+   ldb database library
+
+   Copyright (C) Amdrew Bartlett <abartlet@samba.org> 2007
+   Copyright (C) Andrew Tridgell  2004
+    
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+   
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ *  Name: ldb
+ *
+ *  Component: ldb anr module
+ *
+ *  Description: module to implement 'ambiguous name resolution'
+ *
+ *  Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb_includes.h"
+#include "dsdb/samdb/samdb.h"
+
+/**
+ * Make a and 'and' or 'or' tree from the two supplied elements 
+ */
+struct ldb_parse_tree *make_parse_list(struct ldb_module *module,
+                                      TALLOC_CTX *mem_ctx, enum ldb_parse_op op, 
+                                      struct ldb_parse_tree *first_arm, struct ldb_parse_tree *second_arm)
+{
+       struct ldb_parse_tree *list;
+
+       list = talloc(mem_ctx, struct ldb_parse_tree);
+       if (list == NULL){
+               ldb_oom(module->ldb);
+               return NULL;
+       }
+       list->operation = op;
+       
+       list->u.list.num_elements = 2;
+       list->u.list.elements = talloc_array(list, struct ldb_parse_tree *, 2);
+       if (!list->u.list.elements) {
+               ldb_oom(module->ldb);
+               return NULL;
+       }
+       list->u.list.elements[0] = talloc_steal(list, first_arm);
+       list->u.list.elements[1] = talloc_steal(list, second_arm);
+       return list;
+}
+
+/**
+ * Make an equality or prefix match tree, from the attribute, operation and matching value supplied
+ */
+struct ldb_parse_tree *make_match_tree(struct ldb_module *module,
+                                      TALLOC_CTX *mem_ctx, enum ldb_parse_op op, 
+                                      const char *attr, const DATA_BLOB *match)
+{
+       struct ldb_parse_tree *match_tree;
+
+       match_tree = talloc(mem_ctx, struct ldb_parse_tree);
+       
+       /* Depending on what type of match was selected, fill in the right part of the union */
+        
+       match_tree->operation = op;
+       switch (op) {
+       case LDB_OP_SUBSTRING:
+               match_tree->u.substring.attr = attr;
+               
+               match_tree->u.substring.start_with_wildcard = 0;
+               match_tree->u.substring.end_with_wildcard = 1;
+               match_tree->u.substring.chunks = talloc_array(match_tree, struct ldb_val *, 2);
+               
+               if (match_tree->u.substring.chunks == NULL){
+                       ldb_oom(module->ldb);
+                       return NULL;
+               }
+               match_tree->u.substring.chunks[0] = match;
+               match_tree->u.substring.chunks[1] = NULL;
+               break;
+       case LDB_OP_EQUALITY:
+               match_tree->u.equality.attr = attr;
+               match_tree->u.equality.value = *match;
+               break;
+       }
+       return match_tree;
+}
+
+struct anr_context {
+       bool found_anr;
+       struct ldb_module *module;
+};
+
+/**
+ * Given the match for an 'ambigious name resolution' query, create a
+ * parse tree with an 'or' of all the anr attributes in the schema.  
+ */
+
+typedef struct ldb_parse_tree *(*anr_parse_tree_callback_t)(TALLOC_CTX *mem_ctx,
+                                                          const struct ldb_val *match,
+                                                          void *context);
+
+
+/**
+ * Callback function to do the heavy lifting for the for the parse tree walker 
+ */
+struct ldb_parse_tree *anr_replace_callback(TALLOC_CTX *mem_ctx,
+                                           const struct ldb_val *match,
+                                           void *context)
+{
+       struct ldb_parse_tree *tree = NULL;
+       struct anr_context *anr_context = talloc_get_type(context, struct anr_context);
+       struct ldb_module *module = anr_context->module;
+       struct ldb_parse_tree *match_tree;
+       uint8_t *p;
+       enum ldb_parse_op op;
+       struct dsdb_attribute *cur;
+       const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+       if (!schema) {
+               ldb_asprintf_errstring(module->ldb, "no schema with which to construct anr filter");
+               return NULL;
+       }
+
+       anr_context->found_anr = true;
+
+       if (match->length > 1 && match->data[0] == '=') {
+               DATA_BLOB *match2 = talloc(tree, DATA_BLOB);
+               *match2 = data_blob_const(match->data+1, match->length - 1);
+               if (match2 == NULL){
+                       ldb_oom(module->ldb);
+                       return NULL;
+               }
+               match = match2;
+               op = LDB_OP_EQUALITY;
+       } else {
+               op = LDB_OP_SUBSTRING;
+       }
+       for (cur = schema->attributes; cur; cur = cur->next) {
+               if (!(cur->searchFlags & 0x4)) continue;
+               match_tree = make_match_tree(module, mem_ctx, op, cur->lDAPDisplayName, match);
+
+               if (tree) {
+                       /* Inject an 'or' with the current tree */
+                       tree = make_parse_list(module, mem_ctx,  LDB_OP_OR, tree, match_tree);
+                       if (tree == NULL) {
+                               ldb_oom(module->ldb);
+                               return NULL;
+                       }
+               } else {
+                       tree = match_tree;
+               }
+       }
+
+       
+       /* If the search term has a space in it, 
+          split it up at the first space.  */
+       
+       p = memchr(match->data, ' ', match->length);
+
+       if (p) {
+               struct ldb_parse_tree *first_split_filter, *second_split_filter, *split_filters, *match_tree_1, *match_tree_2;
+               DATA_BLOB *first_match = talloc(tree, DATA_BLOB);
+               DATA_BLOB *second_match = talloc(tree, DATA_BLOB);
+               if (!first_match || !second_match) {
+                       ldb_oom(module->ldb);
+                       return NULL;
+               }
+               *first_match = data_blob_const(match->data, p-match->data);
+               *second_match = data_blob_const(p+1, match->length - (p-match->data) - 1);
+               
+               /* Add (|(&(givenname=first)(sn=second))(&(givenname=second)(sn=first))) */
+
+               match_tree_1 = make_match_tree(module, mem_ctx, op, "givenName", first_match);
+               match_tree_2 = make_match_tree(module, mem_ctx, op, "sn", second_match);
+
+               first_split_filter = make_parse_list(module, context,  LDB_OP_AND, match_tree_1, match_tree_2);
+               if (first_split_filter == NULL){
+                       ldb_oom(module->ldb);
+                       return NULL;
+               }
+               
+               match_tree_1 = make_match_tree(module, mem_ctx, op, "sn", first_match);
+               match_tree_2 = make_match_tree(module, mem_ctx, op, "givenName", second_match);
+
+               second_split_filter = make_parse_list(module, context,  LDB_OP_AND, match_tree_1, match_tree_2);
+               if (second_split_filter == NULL){
+                       ldb_oom(module->ldb);
+                       return NULL;
+               }
+
+               split_filters = make_parse_list(module, mem_ctx,  LDB_OP_OR, 
+                                               first_split_filter, second_split_filter);
+               if (split_filters == NULL) {
+                       ldb_oom(module->ldb);
+                       return NULL;
+               }
+
+               if (tree) {
+                       /* Inject an 'or' with the current tree */
+                       tree = make_parse_list(module, mem_ctx,  LDB_OP_OR, tree, split_filters);
+               } else {
+                       tree = split_filters;
+               }
+       }
+       return tree;
+}
+
+/*
+  replace any occurances of an attribute with a new, generated attribute tree
+*/
+struct ldb_parse_tree *anr_replace_subtrees(struct ldb_parse_tree *tree, 
+                                           const char *attr, 
+                                           anr_parse_tree_callback_t callback,
+                                           void *context)
+{
+       int i;
+       switch (tree->operation) {
+       case LDB_OP_AND:
+       case LDB_OP_OR:
+               for (i=0;i<tree->u.list.num_elements;i++) {
+                       tree->u.list.elements[i] = anr_replace_subtrees(tree->u.list.elements[i],
+                                                                               attr, callback, context);
+                       if (!tree->u.list.elements[i]) {
+                               return NULL;
+                       }
+               }
+               break;
+       case LDB_OP_NOT:
+               tree->u.isnot.child = anr_replace_subtrees(tree->u.isnot.child, attr, callback, context);
+                       if (!tree->u.isnot.child) {
+                               return NULL;
+                       }
+               break;
+       case LDB_OP_EQUALITY:
+               if (ldb_attr_cmp(tree->u.equality.attr, attr) == 0) {
+                       tree = callback(tree, &tree->u.equality.value, 
+                                       context);
+                       if (!tree) {
+                               return NULL;
+                       }
+               }
+               break;
+       case LDB_OP_SUBSTRING:
+               if (ldb_attr_cmp(tree->u.substring.attr, attr) == 0) {
+                       if (tree->u.substring.start_with_wildcard == 0 &&
+                           tree->u.substring.end_with_wildcard == 1 && 
+                           tree->u.substring.chunks[0] != NULL && 
+                           tree->u.substring.chunks[1] == NULL) {
+                               tree = callback(tree, tree->u.substring.chunks[0], context);
+                               if (!tree) {
+                                       return NULL;
+                               }
+                       }
+               }
+               break;
+       }
+       return tree;
+}
+
+/* search */
+static int anr_search(struct ldb_module *module, struct ldb_request *req)
+{
+       struct ldb_parse_tree *anr_tree;
+       struct anr_context *context = talloc(req, struct anr_context);
+       if (!context) {
+               ldb_oom(module->ldb);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       context->module = module;
+       context->found_anr = false;
+
+       /* Yes, this is a problem with req->op.search.tree being const... */
+       anr_tree = anr_replace_subtrees(req->op.search.tree, "anr", anr_replace_callback, context);
+       if (!anr_tree) {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       if (context->found_anr) {
+               /* The above function modifies the tree if it finds "anr", so no
+                * point just setting this on the down_req */
+               req->op.search.tree = talloc_steal(req, anr_tree);
+
+               DEBUG(0, ("anr: %s\n", ldb_filter_from_tree(req, anr_tree)));
+       }
+
+       /* TODO:  Add a callback, and ensure we retry the search with surname and given name if we fail to match */
+
+       return ldb_next_request(module, req);
+}
+
+static const struct ldb_module_ops anr_ops = {
+       .name              = "anr",
+       .search = anr_search
+};
+
+int ldb_anr_init(void)
+{
+       return ldb_register_module(&anr_ops);
+}
+
index b585d0da7f1febab6c01e44d8b26d50afb25bc2a..8350b77b29d2d06d3b9c99b7c461faee5acbcd08 100644 (file)
@@ -304,3 +304,16 @@ OBJ_FILES = \
 # End MODULE ldb_ranged_results
 ################################################
 
+################################################
+# Start MODULE ldb_anr
+[MODULE::ldb_anr]
+INIT_FUNCTION = ldb_anr_init
+CFLAGS = -Ilib/ldb/include
+OUTPUT_TYPE = SHARED_LIBRARY
+PRIVATE_DEPENDENCIES = LIBTALLOC
+SUBSYSTEM = LIBLDB
+OBJ_FILES = \
+               anr.o
+# End MODULE ldb_anr
+################################################
+
index f9814884f35edc87a34fedb3af2144ff48145ed4..0da02ae276837fc6f271e1075451afca757a5d85 100644 (file)
@@ -1018,6 +1018,7 @@ function provision_guess()
        var modules_list     = new Array("rootdse",
                                         "paged_results",
                                         "ranged_results",
+                                        "anr",
                                         "server_sort",
                                         "extended_dn",
                                         "asq",
index 877240890d57ccc142bea8ce030450d233bc8574..7195b29e685dde3e9d6b332848e0575ad06d6a59 100755 (executable)
@@ -51,6 +51,8 @@ dn: cn=ldaptestuser,cn=uSers," + base_dn + "
 objectclass: user
 objectclass: person
 cN: LDAPtestUSER
+givenname: ldap
+sn: testy
 ");
        if (ok.error != 0) {
                ok = ldb.del("cn=ldaptestuser,cn=users," + base_dn);
@@ -63,6 +65,8 @@ dn: cn=ldaptestuser,cn=uSers," + base_dn + "
 objectclass: user
 objectclass: person
 cN: LDAPtestUSER
+givenname: ldap
+sn: testy
 ");
                if (ok.error != 0) {
                        println(ok.errstr);
@@ -112,6 +116,7 @@ dn: cn=ldaptest2computer,cn=computers," + base_dn + "
 objectClass: computer
 cn: LDAPtest2COMPUTER
 userAccountControl: 4096
+displayname: ldap testy
 ");
        if (ok.error != 0) {
                ok = ldb.del("cn=ldaptest2computer,cn=computers," + base_dn);
@@ -124,6 +129,7 @@ dn: cn=ldaptest2computer,cn=computers," + base_dn + "
 objectClass: computer
 cn: LDAPtest2COMPUTER
 userAccountControl: 4096
+displayname: ldap testy
 ");
                if (ok.error != 0) {
                        println(ok.errstr);
@@ -151,6 +157,8 @@ dn: cn=ldaptestuser2,cn=useRs," + base_dn + "
 objectClass: person
 objectClass: user
 cn: LDAPtestUSER2
+givenname: testy
+sn: ldap user2
 ");
        if (ok.error != 0) {
                ok = ldb.del("cn=ldaptestuser2,cn=users," + base_dn);
@@ -163,6 +171,8 @@ dn: cn=ldaptestuser2,cn=useRs," + base_dn + "
 objectClass: person
 objectClass: user
 cn: LDAPtestUSER2
+givenname: testy
+sn: ldap user2
 ");
                if (ok.error != 0) {
                        println(ok.errstr);
@@ -170,6 +180,136 @@ cn: LDAPtestUSER2
                }
        }
 
+
+       println("Testing Ambigious Name Resolution");
+       println("Testing ldb.search for (&(anr=ldap testy)(objectClass=user))");
+       var res = ldb.search("(&(anr=ldap testy)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 3) {
+               println("Could not find (&(anr=ldap testy)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 3);
+       }
+
+       println("Testing ldb.search for (&(anr=testy ldap)(objectClass=user))");
+       var res = ldb.search("(&(anr=testy ldap)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 2) {
+               println("Could not find (&(anr=testy ldap)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 2);
+       }
+
+       println("Testing ldb.search for (&(anr=ldap)(objectClass=user))");
+       var res = ldb.search("(&(anr=ldap)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 4) {
+               println("Found only " + res.msgs.length + " for (&(anr=ldap)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 4);
+       } 
+
+       println("Testing ldb.search for (&(anr==ldap)(objectClass=user))");
+       var res = ldb.search("(&(anr==ldap)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(anr==ldap)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       assert(res.msgs[0].dn == ("CN=ldaptestuser,CN=Users," + base_dn));
+       assert(res.msgs[0].cn == "ldaptestuser");
+       assert(res.msgs[0].name == "ldaptestuser");
+
+       println("Testing ldb.search for (&(anr=testy)(objectClass=user))");
+       var res = ldb.search("(&(anr=testy)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 2) {
+               println("Could not find (&(anr=testy)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 2);
+       }
+
+       println("Testing ldb.search for (&(anr=ldap testy)(objectClass=user))");
+       var res = ldb.search("(&(anr=testy ldap)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 2) {
+               println("Could not find (&(anr=ldap testy)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 2);
+       }
+
+       println("Testing ldb.search for (&(anr==ldap testy)(objectClass=user))");
+       var res = ldb.search("(&(anr==testy ldap)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(anr==ldap testy)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       assert(res.msgs[0].dn == ("CN=ldaptestuser,CN=Users," + base_dn));
+       assert(res.msgs[0].cn == "ldaptestuser");
+       assert(res.msgs[0].name == "ldaptestuser");
+
+       println("Testing ldb.search for (&(anr==testy ldap)(objectClass=user))");
+       var res = ldb.search("(&(anr==testy ldap)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(anr==testy ldap)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       assert(res.msgs[0].dn == ("CN=ldaptestuser,CN=Users," + base_dn));
+       assert(res.msgs[0].cn == "ldaptestuser");
+       assert(res.msgs[0].name == "ldaptestuser");
+
+       println("Testing ldb.search for (&(anr=testy ldap user)(objectClass=user))");
+       var res = ldb.search("(&(anr=testy ldap user)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(anr=testy ldap user)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       assert(res.msgs[0].dn == ("CN=ldaptestuser2,CN=Users," + base_dn));
+       assert(res.msgs[0].cn == "ldaptestuser2");
+       assert(res.msgs[0].name == "ldaptestuser2");
+
+       println("Testing ldb.search for (&(anr==testy ldap user2)(objectClass=user))");
+       var res = ldb.search("(&(anr==testy ldap user2)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(anr==testy ldap user2)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       assert(res.msgs[0].dn == ("CN=ldaptestuser2,CN=Users," + base_dn));
+       assert(res.msgs[0].cn == "ldaptestuser2");
+       assert(res.msgs[0].name == "ldaptestuser2");
+
+       println("Testing ldb.search for (&(anr==ldap user2)(objectClass=user))");
+       var res = ldb.search("(&(anr==ldap user2)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(anr==ldap user2)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       assert(res.msgs[0].dn == ("CN=ldaptestuser2,CN=Users," + base_dn));
+       assert(res.msgs[0].cn == "ldaptestuser2");
+       assert(res.msgs[0].name == "ldaptestuser2");
+
+       println("Testing ldb.search for (&(anr==not ldap user2)(objectClass=user))");
+       var res = ldb.search("(&(anr==not ldap user2)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 0) {
+               println("Must not find (&(anr==not ldap user2)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 0);
+       }
+
+       println("Testing ldb.search for (&(anr=not ldap user2)(objectClass=user))");
+       var res = ldb.search("(&(anr=not ldap user2)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 0) {
+               println("Must not find (&(anr=not ldap user2)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 0);
+       }
+
        println("Testing Group Modifies");
        ok = ldb.modify("
 dn: cn=ldaptestgroup,cn=users," + base_dn + "
@@ -884,6 +1024,20 @@ member: CN=ldaptestutf8user èùéìòà,CN=Users," + base_dn + "
                assert(ok.error == 0);
        }
 
+       println("Testing ldb.search for (&(cn=ldaptestutf8user2*)(objectClass=user))");
+       var res = ldb.search("(&(cn=ldaptestutf8user2*)(objectClass=user))");
+       if (res.error != 0 || res.msgs.length != 1) {
+               println("Could not find (&(cn=ldaptestutf8user2*)(objectClass=user))");
+               assert(res.error == 0);
+               assert(res.msgs.length == 1);
+       }
+
+       ok = ldb.del(res.msgs[0].dn);
+       if (ok.error != 0) {
+               println(ok.errstr);
+               assert(ok.error == 0);
+       }
+
        ok = ldb.del(("CN=ldaptestgroup2,CN=Users," + base_dn))
        if (ok.error != 0) {
                println(ok.errstr);