s4-dsdb: make extended_dn_in a bit more efficient
[amitay/samba.git] / source4 / dsdb / samdb / ldb_modules / extended_dn_in.c
index c3db4fa588916f6f474499a4c53c2992737c0221..760dd643b4f915ded2cc111896ceb586ba238927 100644 (file)
  */
 
 #include "includes.h"
-#include "ldb/include/ldb.h"
-#include "ldb/include/ldb_errors.h"
-#include "ldb/include/ldb_private.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
+  DELETED extended DN codes
+ */
 
 /* search */
 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;
 };
@@ -80,7 +88,7 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
        struct ldb_request *down_req;
        struct ldb_message_element *el;
        int ret;
-       size_t i;
+       unsigned int i;
        size_t wkn_len = 0;
        char *valstr = NULL;
        const char *found = NULL;
@@ -98,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;
@@ -116,7 +136,7 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
                                                (const char *)el->values[i].data,
                                                el->values[i].length);
                        if (!valstr) {
-                               ldb_oom(ac->module->ldb);
+                               ldb_oom(ldb_module_get_ctx(ac->module));
                                return ldb_module_done(ac->req, NULL, NULL,
                                                       LDB_ERR_OPERATIONS_ERROR);
                        }
@@ -134,10 +154,10 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
                        break;
                }
 
-               ac->basedn = ldb_dn_new(ac, ac->module->ldb, found);
+               ac->basedn = ldb_dn_new(ac, ldb_module_get_ctx(ac->module), found);
                talloc_free(valstr);
                if (!ac->basedn) {
-                       ldb_oom(ac->module->ldb);
+                       ldb_oom(ldb_module_get_ctx(ac->module));
                        return ldb_module_done(ac->req, NULL, NULL,
                                               LDB_ERR_OPERATIONS_ERROR);
                }
@@ -151,8 +171,8 @@ 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_linearized(ac->req->op.search.base));
-                       ldb_set_errstring(ac->module->ldb, str);
+                                                         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);
                }
@@ -160,7 +180,7 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
                switch (ac->req->operation) {
                case LDB_SEARCH:
                        ret = ldb_build_search_req_ex(&down_req,
-                                                     ac->module->ldb, ac->req,
+                                                     ldb_module_get_ctx(ac->module), ac->req,
                                                      ac->basedn,
                                                      ac->req->op.search.scope,
                                                      ac->req->op.search.tree,
@@ -168,12 +188,13 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
                                                      ac->req->controls,
                                                      ac, extended_final_callback, 
                                                      ac->req);
+                       LDB_REQ_SET_LOCATION(down_req);
                        break;
                case LDB_ADD:
                {
                        struct ldb_message *add_msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
                        if (!add_msg) {
-                               ldb_oom(ac->module->ldb);
+                               ldb_oom(ldb_module_get_ctx(ac->module));
                                return ldb_module_done(ac->req, NULL, NULL,
                                                       LDB_ERR_OPERATIONS_ERROR);
                        }
@@ -181,18 +202,19 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
                        add_msg->dn = ac->basedn;
 
                        ret = ldb_build_add_req(&down_req,
-                                               ac->module->ldb, ac->req,
+                                               ldb_module_get_ctx(ac->module), ac->req,
                                                add_msg, 
                                                ac->req->controls,
                                                ac, extended_final_callback, 
                                                ac->req);
+                       LDB_REQ_SET_LOCATION(down_req);
                        break;
                }
                case LDB_MODIFY:
                {
                        struct ldb_message *mod_msg = ldb_msg_copy_shallow(ac, ac->req->op.mod.message);
                        if (!mod_msg) {
-                               ldb_oom(ac->module->ldb);
+                               ldb_oom(ldb_module_get_ctx(ac->module));
                                return ldb_module_done(ac->req, NULL, NULL,
                                                       LDB_ERR_OPERATIONS_ERROR);
                        }
@@ -200,29 +222,32 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
                        mod_msg->dn = ac->basedn;
 
                        ret = ldb_build_mod_req(&down_req,
-                                               ac->module->ldb, ac->req,
+                                               ldb_module_get_ctx(ac->module), ac->req,
                                                mod_msg, 
                                                ac->req->controls,
                                                ac, extended_final_callback, 
                                                ac->req);
+                       LDB_REQ_SET_LOCATION(down_req);
                        break;
                }
                case LDB_DELETE:
                        ret = ldb_build_del_req(&down_req,
-                                               ac->module->ldb, ac->req,
+                                               ldb_module_get_ctx(ac->module), ac->req,
                                                ac->basedn, 
                                                ac->req->controls,
                                                ac, extended_final_callback, 
                                                ac->req);
+                       LDB_REQ_SET_LOCATION(down_req);
                        break;
                case LDB_RENAME:
                        ret = ldb_build_rename_req(&down_req,
-                                                  ac->module->ldb, ac->req,
+                                                  ldb_module_get_ctx(ac->module), ac->req,
                                                   ac->basedn, 
                                                   ac->req->op.rename.newdn,
                                                   ac->req->controls,
                                                   ac, extended_final_callback, 
                                                   ac->req);
+                       LDB_REQ_SET_LOCATION(down_req);
                        break;
                default:
                        return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
@@ -238,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;
@@ -255,6 +525,14 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
                "wellKnownObjects",
                NULL
        };
+       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 */
@@ -263,36 +541,42 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
                /* It looks like we need to map the DN */
                const struct ldb_val *sid_val, *guid_val, *wkguid_val;
 
+               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) {
-                       /* TODO: do a search over all partitions */
-                       base_dn = ldb_get_default_basedn(module->ldb);
-                       base_dn_filter = talloc_asprintf(req, "(objectSid=%s)", 
-                                                        ldb_binary_encode(req, *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, "(objectGUID=%s)",
+                                                        ldb_binary_encode(req, *guid_val));
                        if (!base_dn_filter) {
-                               ldb_oom(module->ldb);
-                               return LDB_ERR_OPERATIONS_ERROR;
+                               return ldb_oom(ldb_module_get_ctx(module));
                        }
                        base_dn_scope = LDB_SCOPE_SUBTREE;
                        base_dn_attrs = no_attr;
 
-               } else if (guid_val) {
-
-                       /* TODO: do a search over all partitions */
-                       base_dn = ldb_get_default_basedn(module->ldb);
-                       base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)", 
-                                                        ldb_binary_encode(req, *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, "(objectSid=%s)",
+                                                        ldb_binary_encode(req, *sid_val));
                        if (!base_dn_filter) {
-                               ldb_oom(module->ldb);
-                               return LDB_ERR_OPERATIONS_ERROR;
+                               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;
@@ -302,7 +586,8 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
 
                        p = strchr(wkguid_dup, ',');
                        if (!p) {
-                               return LDB_ERR_INVALID_DN_SYNTAX;
+                               return ldb_error(ldb_module_get_ctx(module), LDB_ERR_INVALID_DN_SYNTAX,
+                                                "Invalid WKGUID format");
                        }
 
                        p[0] = '\0';
@@ -310,37 +595,35 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
 
                        wellknown_object = talloc_asprintf(req, "B:32:%s:", wkguid_dup);
                        if (!wellknown_object) {
-                               ldb_oom(module->ldb);
-                               return LDB_ERR_OPERATIONS_ERROR;
+                               return ldb_oom(ldb_module_get_ctx(module));
                        }
 
                        tail_str = p;
 
-                       base_dn = ldb_dn_new(req, module->ldb, tail_str);
+                       base_dn = ldb_dn_new(req, ldb_module_get_ctx(module), tail_str);
                        talloc_free(wkguid_dup);
                        if (!base_dn) {
-                               ldb_oom(module->ldb);
-                               return LDB_ERR_OPERATIONS_ERROR;
+                               return ldb_oom(ldb_module_get_ctx(module));
                        }
                        base_dn_filter = talloc_strdup(req, "(objectClass=*)");
                        if (!base_dn_filter) {
-                               ldb_oom(module->ldb);
-                               return LDB_ERR_OPERATIONS_ERROR;
+                               return ldb_oom(ldb_module_get_ctx(module));
                        }
                        base_dn_scope = LDB_SCOPE_BASE;
                        base_dn_attrs = wkattr;
                } else {
-                       return LDB_ERR_INVALID_DN_SYNTAX;
+                       return ldb_error(ldb_module_get_ctx(module), LDB_ERR_INVALID_DN_SYNTAX,
+                                        "Invalid extended DN component");
                }
 
                ac = talloc_zero(req, struct extended_search_context);
                if (ac == NULL) {
-                       ldb_oom(module->ldb);
-                       return LDB_ERR_OPERATIONS_ERROR;
+                       return ldb_oom(ldb_module_get_ctx(module));
                }
                
                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;
                
@@ -348,16 +631,30 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
                 * GUID) then search for that, so we can proceed with the original operation */
 
                ret = ldb_build_search_req(&down_req,
-                                          module->ldb, ac,
+                                          ldb_module_get_ctx(module), ac,
                                           base_dn,
                                           base_dn_scope,
                                           base_dn_filter,
                                           base_dn_attrs,
-                                          NULL,
+                                          req->controls,
                                           ac, extended_base_callback,
                                           req);
+               LDB_REQ_SET_LOCATION(down_req);
                if (ret != LDB_SUCCESS) {
-                       return LDB_ERR_OPERATIONS_ERROR;
+                       return ldb_operr(ldb_module_get_ctx(module));
+               }
+
+               if (all_partitions) {
+                       struct ldb_search_options_control *control;
+                       control = talloc(down_req, struct ldb_search_options_control);
+                       control->search_options = 2;
+                       ret = ldb_request_replace_control(down_req,
+                                                     LDB_CONTROL_SEARCH_OPTIONS_OID,
+                                                     true, control);
+                       if (ret != LDB_SUCCESS) {
+                               ldb_oom(ldb_module_get_ctx(module));
+                               return ret;
+                       }
                }
 
                /* perform the search */
@@ -385,10 +682,16 @@ static int extended_dn_in_rename(struct ldb_module *module, struct ldb_request *
        return extended_dn_in_fix(module, req, req->op.rename.olddn);
 }
 
-_PUBLIC_ const struct ldb_module_ops ldb_extended_dn_in_module_ops = {
+static const struct ldb_module_ops ldb_extended_dn_in_module_ops = {
        .name              = "extended_dn_in",
        .search            = extended_dn_in_search,
        .modify            = extended_dn_in_modify,
        .del               = extended_dn_in_del,
        .rename            = extended_dn_in_rename,
 };
+
+int ldb_extended_dn_in_module_init(const char *version)
+{
+       LDB_MODULE_CHECK_VERSION(version);
+       return ldb_register_module(&ldb_extended_dn_in_module_ops);
+}