s4-dsdb: make extended_dn_in a bit more efficient
[amitay/samba.git] / source4 / dsdb / samdb / ldb_modules / extended_dn_in.c
index 4071adda798b77e3c1ab1cbaf7b39a888b16e168..760dd643b4f915ded2cc111896ceb586ba238927 100644 (file)
  */
 
 #include "includes.h"
-#include "ldb/include/ldb.h"
-#include "ldb/include/ldb_errors.h"
-#include "ldb/include/ldb_module.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "dsdb/samdb/samdb.h"
+#include "util.h"
 
 /*
   TODO: if relax is not set then we need to reject the fancy RMD_* and
@@ -44,6 +46,7 @@ struct extended_search_context {
        struct ldb_module *module;
        struct ldb_request *req;
        struct ldb_dn *basedn;
+       struct ldb_dn *dn;
        char *wellknown_object;
        int extended_type;
 };
@@ -103,6 +106,18 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
 
        switch (ares->type) {
        case LDB_REPLY_ENTRY:
+               if (ac->basedn) {
+                       /* we have more than one match! This can
+                          happen as S-1-5-17 appears twice in a
+                          normal provision. We need to return
+                          NO_SUCH_OBJECT */
+                       const char *str = talloc_asprintf(req, "Duplicate base-DN matches found for '%s'",
+                                                         ldb_dn_get_extended_linearized(req, ac->dn, 1));
+                       ldb_set_errstring(ldb_module_get_ctx(ac->module), str);
+                       return ldb_module_done(ac->req, NULL, NULL,
+                                              LDB_ERR_NO_SUCH_OBJECT);
+               }
+
                if (!ac->wellknown_object) {
                        ac->basedn = talloc_steal(ac, ares->message->dn);
                        break;
@@ -156,7 +171,7 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
 
                if (!ac->basedn) {
                        const char *str = talloc_asprintf(req, "Base-DN '%s' not found",
-                                                         ldb_dn_get_extended_linearized(req, ac->req->op.search.base, 1));
+                                                         ldb_dn_get_extended_linearized(req, ac->dn, 1));
                        ldb_set_errstring(ldb_module_get_ctx(ac->module), str);
                        return ldb_module_done(ac->req, NULL, NULL,
                                               LDB_ERR_NO_SUCH_OBJECT);
@@ -248,6 +263,251 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
        return LDB_SUCCESS;
 }
 
+
+/*
+  windows ldap searchs don't allow a baseDN with more
+  than one extended component, or an extended
+  component and a string DN
+
+  We only enforce this over ldap, not for internal
+  use, as there are just too many places where we
+  internally want to use a DN that has come from a
+  search with extended DN enabled, or comes from a DRS
+  naming context.
+
+  Enforcing this would also make debugging samba much
+  harder, as we'd need to use ldb_dn_minimise() in a
+  lot of places, and that would lose the DN string
+  which is so useful for working out what a request is
+  for
+*/
+static bool ldb_dn_match_allowed(struct ldb_dn *dn, struct ldb_request *req)
+{
+       int num_components = ldb_dn_get_comp_num(dn);
+       int num_ex_components = ldb_dn_get_extended_comp_num(dn);
+
+       if (num_ex_components == 0) {
+               return true;
+       }
+
+       if ((num_components != 0 || num_ex_components != 1) &&
+           ldb_req_is_untrusted(req)) {
+               return false;
+       }
+       return true;
+}
+
+
+struct extended_dn_filter_ctx {
+       bool test_only;
+       bool matched;
+       struct ldb_module *module;
+       struct ldb_request *req;
+       struct dsdb_schema *schema;
+};
+
+/*
+  create a always non-matching node from a equality node
+ */
+static void set_parse_tree_false(struct ldb_parse_tree *tree)
+{
+       const char *attr = tree->u.equality.attr;
+       struct ldb_val value = tree->u.equality.value;
+       tree->operation = LDB_OP_EXTENDED;
+       tree->u.extended.attr = attr;
+       tree->u.extended.value = value;
+       tree->u.extended.rule_id = SAMBA_LDAP_MATCH_ALWAYS_FALSE;
+       tree->u.extended.dnAttributes = 0;
+}
+
+/*
+  called on all nodes in the parse tree
+ */
+static int extended_dn_filter_callback(struct ldb_parse_tree *tree, void *private_context)
+{
+       struct extended_dn_filter_ctx *filter_ctx;
+       int ret;
+       struct ldb_dn *dn;
+       const struct ldb_val *sid_val, *guid_val;
+       const char *no_attrs[] = { NULL };
+       struct ldb_result *res;
+       const struct dsdb_attribute *attribute;
+       bool has_extended_component;
+       enum ldb_scope scope;
+       struct ldb_dn *base_dn;
+       const char *expression;
+
+       if (tree->operation != LDB_OP_EQUALITY) {
+               return LDB_SUCCESS;
+       }
+
+       filter_ctx = talloc_get_type_abort(private_context, struct extended_dn_filter_ctx);
+
+       if (filter_ctx->test_only && filter_ctx->matched) {
+               /* the tree already matched */
+               return LDB_SUCCESS;
+       }
+
+       attribute = dsdb_attribute_by_lDAPDisplayName(filter_ctx->schema, tree->u.equality.attr);
+       if (attribute == NULL) {
+               return LDB_SUCCESS;
+       }
+
+       if (attribute->dn_format != DSDB_NORMAL_DN) {
+               return LDB_SUCCESS;
+       }
+
+       has_extended_component = (memchr(tree->u.equality.value.data, '<',
+                                        tree->u.equality.value.length) != NULL);
+
+       if (!attribute->one_way_link && !has_extended_component) {
+               return LDB_SUCCESS;
+       }
+
+       dn = ldb_dn_from_ldb_val(filter_ctx, ldb_module_get_ctx(filter_ctx->module), &tree->u.equality.value);
+       if (dn == NULL) {
+               /* testing against windows shows that we don't raise
+                  an error here */
+               return LDB_SUCCESS;
+       }
+
+       guid_val = ldb_dn_get_extended_component(dn, "GUID");
+       sid_val  = ldb_dn_get_extended_component(dn, "SID");
+
+       if (!guid_val && !sid_val && (attribute->searchFlags & SEARCH_FLAG_ATTINDEX)) {
+               /* if it is indexed, then fixing the string DN will do
+                  no good here, as we will not find the attribute in
+                  the index. So for now fall through to a standard DN
+                  component comparison */
+               return LDB_SUCCESS;
+       }
+
+       if (filter_ctx->test_only) {
+               /* we need to copy the tree */
+               filter_ctx->matched = true;
+               return LDB_SUCCESS;
+       }
+
+       if (!ldb_dn_match_allowed(dn, filter_ctx->req)) {
+               /* we need to make this element of the filter always
+                  be false */
+               set_parse_tree_false(tree);
+               return LDB_SUCCESS;
+       }
+
+       if (guid_val) {
+               expression = talloc_asprintf(filter_ctx, "objectGUID=%s", ldb_binary_encode(filter_ctx, *guid_val));
+               scope = LDB_SCOPE_SUBTREE;
+               base_dn = NULL;
+       } else if (sid_val) {
+               expression = talloc_asprintf(filter_ctx, "objectSID=%s", ldb_binary_encode(filter_ctx, *sid_val));
+               scope = LDB_SCOPE_SUBTREE;
+               base_dn = NULL;
+       } else {
+               /* fallback to searching using the string DN as the base DN */
+               expression = "objectClass=*";
+               base_dn = dn;
+               scope = LDB_SCOPE_BASE;
+       }
+
+       ret = dsdb_module_search(filter_ctx->module,
+                                filter_ctx,
+                                &res,
+                                base_dn,
+                                scope,
+                                no_attrs,
+                                DSDB_FLAG_NEXT_MODULE |
+                                DSDB_SEARCH_SHOW_DELETED |
+                                DSDB_SEARCH_SHOW_EXTENDED_DN |
+                                DSDB_SEARCH_SEARCH_ALL_PARTITIONS,
+                                filter_ctx->req,
+                                "%s", expression);
+       if (scope == LDB_SCOPE_BASE && ret == LDB_ERR_NO_SUCH_OBJECT) {
+               /* note that this will need to change for multi-domain
+                  support */
+               set_parse_tree_false(tree);
+               return LDB_SUCCESS;
+       }
+
+       if (ret != LDB_SUCCESS) {
+               return LDB_SUCCESS;
+       }
+
+
+       if (res->count != 1) {
+               return LDB_SUCCESS;
+       }
+
+       /* replace the search expression element with the matching DN */
+       tree->u.equality.value.data = (uint8_t *)talloc_strdup(tree,
+                                                              ldb_dn_get_extended_linearized(tree, res->msgs[0]->dn, 1));
+       if (tree->u.equality.value.data == NULL) {
+               return ldb_oom(ldb_module_get_ctx(filter_ctx->module));
+       }
+       tree->u.equality.value.length = strlen((const char *)tree->u.equality.value.data);
+       talloc_free(res);
+
+       filter_ctx->matched = true;
+       return LDB_SUCCESS;
+}
+
+/*
+  fix the parse tree to change any extended DN components to their
+  caconical form
+ */
+static int extended_dn_fix_filter(struct ldb_module *module, struct ldb_request *req)
+{
+       struct extended_dn_filter_ctx *filter_ctx;
+       int ret;
+
+       filter_ctx = talloc_zero(req, struct extended_dn_filter_ctx);
+       if (filter_ctx == NULL) {
+               return ldb_module_oom(module);
+       }
+
+       /* first pass through the existing tree to see if anything
+          needs to be modified. Filtering DNs on the input side is rare,
+          so this avoids copying the parse tree in most cases */
+       filter_ctx->test_only = true;
+       filter_ctx->matched   = false;
+       filter_ctx->module    = module;
+       filter_ctx->req       = req;
+       filter_ctx->schema    = dsdb_get_schema(ldb_module_get_ctx(module), filter_ctx);
+
+       ret = ldb_parse_tree_walk(req->op.search.tree, extended_dn_filter_callback, filter_ctx);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(filter_ctx);
+               return ret;
+       }
+
+       if (!filter_ctx->matched) {
+               /* nothing matched, no need for a new parse tree */
+               talloc_free(filter_ctx);
+               return LDB_SUCCESS;
+       }
+
+       filter_ctx->test_only = false;
+       filter_ctx->matched   = false;
+
+       req->op.search.tree = ldb_parse_tree_copy_shallow(req, req->op.search.tree);
+       if (req->op.search.tree == NULL) {
+               return ldb_oom(ldb_module_get_ctx(module));
+       }
+
+       ret = ldb_parse_tree_walk(req->op.search.tree, extended_dn_filter_callback, filter_ctx);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(filter_ctx);
+               return ret;
+       }
+
+       talloc_free(filter_ctx);
+       return LDB_SUCCESS;
+}
+
+/*
+  fix DNs and filter expressions to cope with the semantics of
+  extended DNs
+ */
 static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn)
 {
        struct extended_search_context *ac;
@@ -267,47 +527,56 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
        };
        bool all_partitions = false;
 
+       if (req->operation == LDB_SEARCH) {
+               ret = extended_dn_fix_filter(module, req);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+       }
+
        if (!ldb_dn_has_extended(dn)) {
                /* Move along there isn't anything to see here */
                return ldb_next_request(module, req);
        } else {
                /* It looks like we need to map the DN */
                const struct ldb_val *sid_val, *guid_val, *wkguid_val;
-               int num_components = ldb_dn_get_comp_num(dn);
-               int num_ex_components = ldb_dn_get_extended_comp_num(dn);
 
-               if (num_components != 0 || num_ex_components != 1) {
-                       return ldb_error(ldb_module_get_ctx(module), LDB_ERR_INVALID_DN_SYNTAX, "invalid number of DN components");
+               if (!ldb_dn_match_allowed(dn, req)) {
+                       return ldb_error(ldb_module_get_ctx(module),
+                                        LDB_ERR_INVALID_DN_SYNTAX, "invalid number of DN components");
                }
 
                sid_val = ldb_dn_get_extended_component(dn, "SID");
                guid_val = ldb_dn_get_extended_component(dn, "GUID");
                wkguid_val = ldb_dn_get_extended_component(dn, "WKGUID");
 
-               if (sid_val) {
+               /*
+                 prioritise the GUID - we have had instances of
+                 duplicate SIDs in the database in the
+                 ForeignSecurityPrinciples due to provision errors
+                */
+               if (guid_val) {
                        all_partitions = true;
                        base_dn = ldb_get_default_basedn(ldb_module_get_ctx(module));
-                       base_dn_filter = talloc_asprintf(req, "(objectSid=%s)", 
-                                                        ldb_binary_encode(req, *sid_val));
+                       base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)",
+                                                        ldb_binary_encode(req, *guid_val));
                        if (!base_dn_filter) {
                                return ldb_oom(ldb_module_get_ctx(module));
                        }
                        base_dn_scope = LDB_SCOPE_SUBTREE;
                        base_dn_attrs = no_attr;
 
-               } else if (guid_val) {
-
+               } else if (sid_val) {
                        all_partitions = true;
                        base_dn = ldb_get_default_basedn(ldb_module_get_ctx(module));
-                       base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)", 
-                                                        ldb_binary_encode(req, *guid_val));
+                       base_dn_filter = talloc_asprintf(req, "(objectSid=%s)",
+                                                        ldb_binary_encode(req, *sid_val));
                        if (!base_dn_filter) {
                                return ldb_oom(ldb_module_get_ctx(module));
                        }
                        base_dn_scope = LDB_SCOPE_SUBTREE;
                        base_dn_attrs = no_attr;
 
-
                } else if (wkguid_val) {
                        char *wkguid_dup;
                        char *tail_str;
@@ -354,6 +623,7 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
                
                ac->module = module;
                ac->req = req;
+               ac->dn = dn;
                ac->basedn = NULL;  /* Filled in if the search finds the DN by SID/GUID etc */
                ac->wellknown_object = wellknown_object;