s4:dsdb/extended_dn_in: Fix DNs and filter expressions in extended match ops
[obnox/samba/samba-obnox.git] / source4 / dsdb / samdb / ldb_modules / extended_dn_in.c
index 6d43fb558f7eedc4f255bf9563c43c3efac4fc54..bf0da81a246cbe4aa0fbbac9071535d68d2dcb6d 100644 (file)
@@ -34,7 +34,7 @@
 #include <ldb_errors.h>
 #include <ldb_module.h>
 #include "dsdb/samdb/samdb.h"
-#include "util.h"
+#include "dsdb/samdb/ldb_modules/util.h"
 
 /*
   TODO: if relax is not set then we need to reject the fancy RMD_* and
@@ -51,6 +51,14 @@ struct extended_search_context {
        int extended_type;
 };
 
+static const char *wkattr[] = {
+       "wellKnownObjects",
+       "otherWellKnownObjects",
+       NULL
+};
+
+static const struct ldb_module_ops ldb_extended_dn_in_openldap_module_ops;
+
 /* An extra layer of indirection because LDB does not allow the original request to be altered */
 
 static int extended_final_callback(struct ldb_request *req, struct ldb_reply *ares)
@@ -88,7 +96,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;
-       unsigned int i;
+       unsigned int i, j;
        size_t wkn_len = 0;
        char *valstr = NULL;
        const char *found = NULL;
@@ -125,29 +133,35 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
 
                wkn_len = strlen(ac->wellknown_object);
 
-               el = ldb_msg_find_element(ares->message, "wellKnownObjects");
-               if (!el) {
-                       ac->basedn = NULL;
-                       break;
-               }
-
-               for (i=0; i < el->num_values; i++) {
-                       valstr = talloc_strndup(ac,
-                                               (const char *)el->values[i].data,
-                                               el->values[i].length);
-                       if (!valstr) {
-                               ldb_oom(ldb_module_get_ctx(ac->module));
-                               return ldb_module_done(ac->req, NULL, NULL,
-                                                      LDB_ERR_OPERATIONS_ERROR);
-                       }
+               for (j=0; wkattr[j]; j++) {
 
-                       if (strncasecmp(valstr, ac->wellknown_object, wkn_len) != 0) {
-                               talloc_free(valstr);
+                       el = ldb_msg_find_element(ares->message, wkattr[j]);
+                       if (!el) {
+                               ac->basedn = NULL;
                                continue;
                        }
 
-                       found = &valstr[wkn_len];
-                       break;
+                       for (i=0; i < el->num_values; i++) {
+                               valstr = talloc_strndup(ac,
+                                                       (const char *)el->values[i].data,
+                                                       el->values[i].length);
+                               if (!valstr) {
+                                       ldb_oom(ldb_module_get_ctx(ac->module));
+                                       return ldb_module_done(ac->req, NULL, NULL,
+                                                       LDB_ERR_OPERATIONS_ERROR);
+                               }
+
+                               if (strncasecmp(valstr, ac->wellknown_object, wkn_len) != 0) {
+                                       talloc_free(valstr);
+                                       continue;
+                               }
+
+                               found = &valstr[wkn_len];
+                               break;
+                       }
+                       if (found) {
+                               break;
+                       }
                }
 
                if (!found) {
@@ -264,14 +278,63 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are
 }
 
 
+/*
+  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;
+       uint32_t dsdb_flags;
 };
 
+/*
+  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
  */
@@ -279,20 +342,37 @@ static int extended_dn_filter_callback(struct ldb_parse_tree *tree, void *privat
 {
        struct extended_dn_filter_ctx *filter_ctx;
        int ret;
-       struct ldb_dn *dn;
+       struct ldb_dn *dn = NULL;
        const struct ldb_val *sid_val, *guid_val;
        const char *no_attrs[] = { NULL };
        struct ldb_result *res;
+       const struct dsdb_attribute *attribute = NULL;
+       bool has_extended_component = false;
+       enum ldb_scope scope;
+       struct ldb_dn *base_dn;
        const char *expression;
-       const struct dsdb_attribute *attribute;
+       uint32_t dsdb_flags;
 
-       if (tree->operation != LDB_OP_EQUALITY) {
+       if (tree->operation != LDB_OP_EQUALITY && tree->operation != LDB_OP_EXTENDED) {
                return LDB_SUCCESS;
        }
 
        filter_ctx = talloc_get_type_abort(private_context, struct extended_dn_filter_ctx);
 
-       attribute = dsdb_attribute_by_lDAPDisplayName(filter_ctx->schema, tree->u.equality.attr);
+       if (filter_ctx->test_only && filter_ctx->matched) {
+               /* the tree already matched */
+               return LDB_SUCCESS;
+       }
+
+       if (!filter_ctx->schema) {
+               /* Schema not setup yet */
+               return LDB_SUCCESS;
+       }
+       if (tree->operation == LDB_OP_EQUALITY) {
+               attribute = dsdb_attribute_by_lDAPDisplayName(filter_ctx->schema, tree->u.equality.attr);
+       } else if (tree->operation == LDB_OP_EXTENDED) {
+               attribute = dsdb_attribute_by_lDAPDisplayName(filter_ctx->schema, tree->u.extended.attr);
+       }
        if (attribute == NULL) {
                return LDB_SUCCESS;
        }
@@ -301,69 +381,120 @@ static int extended_dn_filter_callback(struct ldb_parse_tree *tree, void *privat
                return LDB_SUCCESS;
        }
 
-       /* add one_way_link check here */
-
+       if (tree->operation == LDB_OP_EQUALITY) {
+               has_extended_component = (memchr(tree->u.equality.value.data, '<',
+                                                tree->u.equality.value.length) != NULL);
+       } else if (tree->operation == LDB_OP_EXTENDED) {
+               has_extended_component = (memchr(tree->u.extended.value.data, '<',
+                                                tree->u.extended.value.length) != NULL);
+       }
 
-       if (memchr(tree->u.equality.value.data, '<', tree->u.equality.value.length) == NULL) {
-               /* its definately not a magic DN */
+       /*
+        * Don't turn it into an extended DN if we're talking to OpenLDAP.
+        * We just check the module_ops pointer instead of adding a private
+        * pointer and a boolean to tell us the exact same thing.
+        */
+       if (!has_extended_component) {
+               if (!attribute->one_way_link ||
+                   ldb_module_get_ops(filter_ctx->module) == &ldb_extended_dn_in_openldap_module_ops)
                return LDB_SUCCESS;
        }
 
-       dn = ldb_dn_from_ldb_val(filter_ctx, ldb_module_get_ctx(filter_ctx->module), &tree->u.equality.value);
+       if (tree->operation == LDB_OP_EQUALITY) {
+               dn = ldb_dn_from_ldb_val(filter_ctx, ldb_module_get_ctx(filter_ctx->module), &tree->u.equality.value);
+       } else if (tree->operation == LDB_OP_EXTENDED) {
+               dn = ldb_dn_from_ldb_val(filter_ctx, ldb_module_get_ctx(filter_ctx->module), &tree->u.extended.value);
+       }
        if (dn == NULL) {
                /* testing against windows shows that we don't raise
                   an error here */
                return LDB_SUCCESS;
        }
 
-       sid_val  = ldb_dn_get_extended_component(dn, "SID");
        guid_val = ldb_dn_get_extended_component(dn, "GUID");
+       sid_val  = ldb_dn_get_extended_component(dn, "SID");
 
-       if (sid_val == NULL && guid_val == NULL) {
+       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;
+       }
+
+       dsdb_flags = filter_ctx->dsdb_flags | DSDB_FLAG_NEXT_MODULE;
 
-       /*
-         prioritise the GUID - we have had instances of
-         duplicate SIDs in the database in the
-         ForeignSecurityPrinciples due to provision errors
-       */
        if (guid_val) {
                expression = talloc_asprintf(filter_ctx, "objectGUID=%s", ldb_binary_encode(filter_ctx, *guid_val));
-       } else {
+               scope = LDB_SCOPE_SUBTREE;
+               base_dn = NULL;
+               dsdb_flags |= DSDB_SEARCH_SEARCH_ALL_PARTITIONS;
+       } 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;
+               dsdb_flags |= DSDB_SEARCH_SEARCH_ALL_PARTITIONS;
+       } 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,
-                                NULL,
-                                LDB_SCOPE_SUBTREE,
+                                base_dn,
+                                scope,
                                 no_attrs,
-                                DSDB_FLAG_NEXT_MODULE |
-                                DSDB_SEARCH_SHOW_DELETED |
-                                DSDB_SEARCH_SEARCH_ALL_PARTITIONS,
+                                dsdb_flags,
                                 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_linearized(res->msgs[0]->dn));
-       if (tree->u.equality.value.data == NULL) {
-               return ldb_oom(ldb_module_get_ctx(filter_ctx->module));
+       if (tree->operation == LDB_OP_EQUALITY) {
+               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);
+       } else if (tree->operation == LDB_OP_EXTENDED) {
+               tree->u.extended.value.data =
+                       (uint8_t *)talloc_strdup(tree, ldb_dn_get_extended_linearized(tree, res->msgs[0]->dn, 1));
+               if (tree->u.extended.value.data == NULL) {
+                       return ldb_oom(ldb_module_get_ctx(filter_ctx->module));
+               }
+               tree->u.extended.value.length = strlen((const char *)tree->u.extended.value.data);
        }
-       tree->u.equality.value.length = strlen((const char *)tree->u.equality.value.data);
        talloc_free(res);
 
        filter_ctx->matched = true;
@@ -374,7 +505,9 @@ static int extended_dn_filter_callback(struct ldb_parse_tree *tree, void *privat
   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)
+static int extended_dn_fix_filter(struct ldb_module *module,
+                                 struct ldb_request *req,
+                                 uint32_t default_dsdb_flags)
 {
        struct extended_dn_filter_ctx *filter_ctx;
        int ret;
@@ -392,6 +525,7 @@ static int extended_dn_fix_filter(struct ldb_module *module, struct ldb_request
        filter_ctx->module    = module;
        filter_ctx->req       = req;
        filter_ctx->schema    = dsdb_get_schema(ldb_module_get_ctx(module), filter_ctx);
+       filter_ctx->dsdb_flags= default_dsdb_flags;
 
        ret = ldb_parse_tree_walk(req->op.search.tree, extended_dn_filter_callback, filter_ctx);
        if (ret != LDB_SUCCESS) {
@@ -408,6 +542,11 @@ static int extended_dn_fix_filter(struct ldb_module *module, struct ldb_request
        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);
@@ -435,15 +574,23 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
        static const char *no_attr[] = {
                NULL
        };
-       static const char *wkattr[] = {
-               "wellKnownObjects",
-               NULL
-       };
-       bool all_partitions = false;
+       uint32_t dsdb_flags = DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_EXTENDED_DN;
 
-       ret = extended_dn_fix_filter(module, req);
-       if (ret != LDB_SUCCESS) {
-               return ret;
+       if (ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID)) {
+               dsdb_flags |= DSDB_SEARCH_SHOW_DELETED;
+       }
+       if (ldb_request_get_control(req, LDB_CONTROL_SHOW_RECYCLED_OID)) {
+               dsdb_flags |= DSDB_SEARCH_SHOW_RECYCLED;
+       }
+       if (ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+               dsdb_flags |= DSDB_SEARCH_SHOW_RECYCLED;
+       }
+
+       if (req->operation == LDB_SEARCH) {
+               ret = extended_dn_fix_filter(module, req, dsdb_flags);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
        }
 
        if (!ldb_dn_has_extended(dn)) {
@@ -452,28 +599,8 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *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);
 
-               /*
-                 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
-                */
-               if ((num_components != 0 || num_ex_components != 1) &&
-                   ldb_req_is_untrusted(req)) {
+               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");
                }
@@ -488,8 +615,8 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
                  ForeignSecurityPrinciples due to provision errors
                 */
                if (guid_val) {
-                       all_partitions = true;
-                       base_dn = ldb_get_default_basedn(ldb_module_get_ctx(module));
+                       dsdb_flags |= DSDB_SEARCH_SEARCH_ALL_PARTITIONS;
+                       base_dn = NULL;
                        base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)",
                                                         ldb_binary_encode(req, *guid_val));
                        if (!base_dn_filter) {
@@ -499,8 +626,8 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
                        base_dn_attrs = no_attr;
 
                } else if (sid_val) {
-                       all_partitions = true;
-                       base_dn = ldb_get_default_basedn(ldb_module_get_ctx(module));
+                       dsdb_flags |= DSDB_SEARCH_SEARCH_ALL_PARTITIONS;
+                       base_dn = NULL;
                        base_dn_filter = talloc_asprintf(req, "(objectSid=%s)",
                                                         ldb_binary_encode(req, *sid_val));
                        if (!base_dn_filter) {
@@ -568,7 +695,7 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
                                           base_dn_scope,
                                           base_dn_filter,
                                           base_dn_attrs,
-                                          req->controls,
+                                          NULL,
                                           ac, extended_base_callback,
                                           req);
                LDB_REQ_SET_LOCATION(down_req);
@@ -576,17 +703,9 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req
                        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;
-                       }
+               ret = dsdb_request_add_controls(down_req, dsdb_flags);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
                }
 
                /* perform the search */
@@ -622,8 +741,21 @@ static const struct ldb_module_ops ldb_extended_dn_in_module_ops = {
        .rename            = extended_dn_in_rename,
 };
 
+static const struct ldb_module_ops ldb_extended_dn_in_openldap_module_ops = {
+       .name              = "extended_dn_in_openldap",
+       .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)
 {
+       int ret;
        LDB_MODULE_CHECK_VERSION(version);
+       ret = ldb_register_module(&ldb_extended_dn_in_openldap_module_ops);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
        return ldb_register_module(&ldb_extended_dn_in_module_ops);
 }