summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/common/util_links.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/common/util_links.c')
-rw-r--r--source4/dsdb/common/util_links.c229
1 files changed, 229 insertions, 0 deletions
diff --git a/source4/dsdb/common/util_links.c b/source4/dsdb/common/util_links.c
new file mode 100644
index 0000000..d41d1f2
--- /dev/null
+++ b/source4/dsdb/common/util_links.c
@@ -0,0 +1,229 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Helpers to search for links in the DB
+
+ Copyright (C) Catalyst.Net Ltd 2017
+
+ 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/>.
+*/
+
+#include "includes.h"
+#include "dsdb/samdb/samdb.h"
+#include "lib/util/binsearch.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+
+/*
+ * We choose, as the sort order, the same order as is used in DRS replication,
+ * which is the memcmp() order of the NDR GUID, not that obtained from
+ * GUID_compare().
+ *
+ * This means that sorted links will be in the same order as a new DC would
+ * see them.
+ */
+int ndr_guid_compare(const struct GUID *guid1, const struct GUID *guid2)
+{
+ uint8_t v1_data[16] = { 0 };
+ struct ldb_val v1 = data_blob_const(v1_data, sizeof(v1_data));
+ uint8_t v2_data[16];
+ struct ldb_val v2 = data_blob_const(v2_data, sizeof(v2_data));
+
+ /* This can't fail */
+ ndr_push_struct_into_fixed_blob(&v1, guid1,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ /* This can't fail */
+ ndr_push_struct_into_fixed_blob(&v2, guid2,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ return data_blob_cmp(&v1, &v2);
+}
+
+
+static int la_guid_compare_with_trusted_dn(struct compare_ctx *ctx,
+ struct parsed_dn *p)
+{
+ int cmp = 0;
+ /*
+ * This works like a standard compare function in its return values,
+ * but has an extra trick to deal with errors: zero is returned and
+ * ctx->err is set to the ldb error code.
+ *
+ * That is, if (as is expected in most cases) you get a non-zero
+ * result, you don't need to check for errors.
+ *
+ * We assume the second argument refers to a DN is from the database
+ * and has a GUID -- but this GUID might not have been parsed out yet.
+ */
+ if (p->dsdb_dn == NULL) {
+ int ret = really_parse_trusted_dn(ctx->mem_ctx, ctx->ldb, p,
+ ctx->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ ctx->err = ret;
+ return 0;
+ }
+ }
+ cmp = ndr_guid_compare(ctx->guid, &p->guid);
+ if (cmp == 0 && ctx->compare_extra_part) {
+ if (ctx->partial_extra_part_length != 0) {
+ /* Allow a prefix match on the blob. */
+ return memcmp(ctx->extra_part.data,
+ p->dsdb_dn->extra_part.data,
+ MIN(ctx->partial_extra_part_length,
+ p->dsdb_dn->extra_part.length));
+ } else {
+ return data_blob_cmp(&ctx->extra_part,
+ &p->dsdb_dn->extra_part);
+ }
+ }
+
+ return cmp;
+}
+
+/* When a parsed_dn comes from the database, sometimes it is not really parsed. */
+
+int really_parse_trusted_dn(TALLOC_CTX *mem_ctx, struct ldb_context *ldb,
+ struct parsed_dn *pdn, const char *ldap_oid)
+{
+ NTSTATUS status;
+ struct dsdb_dn *dsdb_dn = dsdb_dn_parse_trusted(mem_ctx, ldb, pdn->v,
+ ldap_oid);
+ if (dsdb_dn == NULL) {
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &pdn->guid, "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pdn->dsdb_dn = dsdb_dn;
+ return LDB_SUCCESS;
+}
+
+
+int get_parsed_dns_trusted(TALLOC_CTX *mem_ctx, struct ldb_message_element *el,
+ struct parsed_dn **pdn)
+{
+ /* Here we get a list of 'struct parsed_dns' without the parsing */
+ unsigned int i;
+ *pdn = talloc_zero_array(mem_ctx, struct parsed_dn,
+ el->num_values);
+ if (!*pdn) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ (*pdn)[i].v = &el->values[i];
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+int parsed_dn_find(struct ldb_context *ldb, struct parsed_dn *pdn,
+ unsigned int count,
+ const struct GUID *guid,
+ struct ldb_dn *target_dn,
+ DATA_BLOB extra_part,
+ size_t partial_extra_part_length,
+ struct parsed_dn **exact,
+ struct parsed_dn **next,
+ const char *ldap_oid,
+ bool compare_extra_part)
+{
+ unsigned int i;
+ struct compare_ctx ctx;
+ if (pdn == NULL) {
+ *exact = NULL;
+ *next = NULL;
+ return LDB_SUCCESS;
+ }
+
+ if (unlikely(GUID_all_zero(guid))) {
+ /*
+ * When updating a link using DRS, we sometimes get a NULL
+ * GUID when a forward link has been deleted and its GUID has
+ * for some reason been forgotten. The best we can do is try
+ * and match by DN via a linear search. Note that this
+ * probably only happens in the ADD case, in which we only
+ * allow modification of link if it is already deleted, so
+ * this seems very close to an elaborate NO-OP, but we are not
+ * quite prepared to declare it so.
+ *
+ * If the DN is not in our list, we have to add it to the
+ * beginning of the list, where it would naturally sort.
+ */
+ struct parsed_dn *p;
+ if (target_dn == NULL) {
+ /* We don't know the target DN, so we can't search for DN */
+ DEBUG(1, ("parsed_dn_find has a NULL GUID for a linked "
+ "attribute but we don't have a DN to compare "
+ "it with\n"));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *exact = NULL;
+ *next = NULL;
+
+ DEBUG(3, ("parsed_dn_find has a NULL GUID for a link to DN "
+ "%s; searching through links for it\n",
+ ldb_dn_get_linearized(target_dn)));
+
+ for (i = 0; i < count; i++) {
+ int cmp;
+ p = &pdn[i];
+ if (p->dsdb_dn == NULL) {
+ int ret = really_parse_trusted_dn(pdn, ldb, p, ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ cmp = ldb_dn_compare(p->dsdb_dn->dn, target_dn);
+ if (cmp == 0) {
+ *exact = p;
+ return LDB_SUCCESS;
+ }
+ }
+ /*
+ * Here we have a null guid which doesn't match any existing
+ * link. This is a bit unexpected because null guids occur
+ * when a forward link has been deleted and we are replicating
+ * that deletion.
+ *
+ * The best thing to do is weep into the logs and add the
+ * offending link to the beginning of the list which is
+ * at least the correct sort position.
+ */
+ DEBUG(1, ("parsed_dn_find has been given a NULL GUID for a "
+ "link to unknown DN %s\n",
+ ldb_dn_get_linearized(target_dn)));
+ *next = pdn;
+ return LDB_SUCCESS;
+ }
+
+ ctx.guid = guid;
+ ctx.ldb = ldb;
+ ctx.mem_ctx = pdn;
+ ctx.ldap_oid = ldap_oid;
+ ctx.extra_part = extra_part;
+ ctx.partial_extra_part_length = partial_extra_part_length;
+ ctx.compare_extra_part = compare_extra_part;
+ ctx.err = 0;
+
+ BINARY_ARRAY_SEARCH_GTE(pdn, count, &ctx, la_guid_compare_with_trusted_dn,
+ *exact, *next);
+
+ if (ctx.err != 0) {
+ return ctx.err;
+ }
+ return LDB_SUCCESS;
+}