/* 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 . */ #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]; 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", 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; }