#include "ldb_module.h"
#include "dsdb/samdb/samdb.h"
#include "dsdb/common/proto.h"
+#include "dsdb/common/util.h"
#include "../libds/common/flags.h"
+#include "librpc/gen_ndr/irpc.h"
#include "librpc/gen_ndr/ndr_misc.h"
#include "librpc/gen_ndr/ndr_drsuapi.h"
#include "librpc/gen_ndr/ndr_drsblobs.h"
#include "libcli/security/security.h"
#include "lib/util/dlinklist.h"
#include "dsdb/samdb/ldb_modules/util.h"
-#include "lib/util/binsearch.h"
#include "lib/util/tsort.h"
/*
struct replmd_private {
TALLOC_CTX *la_ctx;
struct la_entry *la_list;
- TALLOC_CTX *bl_ctx;
- struct la_backlink *la_backlinks;
struct nc_entry {
struct nc_entry *prev, *next;
struct ldb_dn *dn;
} *ncs;
struct ldb_dn *schema_dn;
bool originating_updates;
+ bool sorted_links;
};
struct la_entry {
struct la_entry *next, *prev;
struct drsuapi_DsReplicaLinkedAttribute *la;
+ uint32_t dsdb_repl_flags;
};
struct replmd_replicated_request {
/* the controls we pass down */
struct ldb_control **controls;
+ /*
+ * Backlinks for the replmd_add() case (we want to create
+ * backlinks after creating the user, but before the end of
+ * the ADD request)
+ */
+ struct la_backlink *la_backlinks;
+
/* details for the mode where we apply a bunch of inbound replication meessages */
bool apply_mode;
uint32_t index_current;
static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar);
static int replmd_delete_internals(struct ldb_module *module, struct ldb_request *req, bool re_delete);
+static int replmd_check_upgrade_links(struct ldb_context *ldb,
+ struct parsed_dn *dns, uint32_t count,
+ struct ldb_message_element *el,
+ const char *ldap_oid);
+static int replmd_verify_linked_attribute(struct replmd_replicated_request *ar,
+ struct la_entry *la);
enum urgent_situation {
REPL_URGENT_ON_CREATE = 1,
return false;
}
-
static int replmd_replicated_apply_isDeleted(struct replmd_replicated_request *ar);
/*
{
struct replmd_private *replmd_private;
struct ldb_context *ldb = ldb_module_get_ctx(module);
-
+ static const char *samba_dsdb_attrs[] = { SAMBA_COMPATIBLE_FEATURES_ATTR, NULL };
+ struct ldb_dn *samba_dsdb_dn;
+ struct ldb_result *res;
+ int ret;
+ TALLOC_CTX *frame = talloc_stackframe();
replmd_private = talloc_zero(module, struct replmd_private);
if (replmd_private == NULL) {
ldb_oom(ldb);
+ TALLOC_FREE(frame);
return LDB_ERR_OPERATIONS_ERROR;
}
ldb_module_set_private(module, replmd_private);
replmd_private->schema_dn = ldb_get_schema_basedn(ldb);
+ samba_dsdb_dn = ldb_dn_new(frame, ldb, "@SAMBA_DSDB");
+ if (!samba_dsdb_dn) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search_dn(module, frame, &res, samba_dsdb_dn,
+ samba_dsdb_attrs, DSDB_FLAG_NEXT_MODULE, NULL);
+ if (ret == LDB_SUCCESS) {
+ replmd_private->sorted_links
+ = ldb_msg_check_string_attribute(res->msgs[0],
+ SAMBA_COMPATIBLE_FEATURES_ATTR,
+ SAMBA_SORTED_LINKS_FEATURE);
+ }
+ TALLOC_FREE(frame);
+
return ldb_next_init(module);
}
replmd_private->la_list = NULL;
replmd_private->la_ctx = NULL;
- talloc_free(replmd_private->bl_ctx);
- replmd_private->la_backlinks = NULL;
- replmd_private->bl_ctx = NULL;
}
struct la_backlink {
struct la_backlink *next, *prev;
const char *attr_name;
- struct GUID forward_guid, target_guid;
+ struct ldb_dn *forward_dn;
+ struct GUID target_guid;
bool active;
};
int ret;
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_message *msg;
- TALLOC_CTX *tmp_ctx = talloc_new(bl);
+ TALLOC_CTX *frame = talloc_stackframe();
char *dn_string;
/*
- construct ldb_message
- either an add or a delete
*/
- ret = dsdb_module_dn_by_guid(module, tmp_ctx, &bl->target_guid, &target_dn, parent);
+ ret = dsdb_module_dn_by_guid(module, frame, &bl->target_guid, &target_dn, parent);
if (ret != LDB_SUCCESS) {
- DEBUG(2,(__location__ ": WARNING: Failed to find target DN for linked attribute with GUID %s\n",
- GUID_string(bl, &bl->target_guid)));
+ struct GUID_txt_buf guid_str;
+ DBG_WARNING("Failed to find target DN for linked attribute with GUID %s\n",
+ GUID_buf_string(&bl->target_guid, &guid_str));
+ DBG_WARNING("Please run 'samba-tool dbcheck' to resolve any missing backlinks.\n");
+ talloc_free(frame);
return LDB_SUCCESS;
}
- ret = dsdb_module_dn_by_guid(module, tmp_ctx, &bl->forward_guid, &source_dn, parent);
- if (ret != LDB_SUCCESS) {
- ldb_asprintf_errstring(ldb, "Failed to find source DN for linked attribute with GUID %s\n",
- GUID_string(bl, &bl->forward_guid));
- talloc_free(tmp_ctx);
- return ret;
+ msg = ldb_msg_new(frame);
+ if (msg == NULL) {
+ ldb_module_oom(module);
+ talloc_free(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
}
- msg = ldb_msg_new(tmp_ctx);
- if (msg == NULL) {
+ source_dn = ldb_dn_copy(frame, bl->forward_dn);
+ if (!source_dn) {
ldb_module_oom(module);
- talloc_free(tmp_ctx);
+ talloc_free(frame);
return LDB_ERR_OPERATIONS_ERROR;
+ } else {
+ /* Filter down to the attributes we want in the backlink */
+ const char *accept[] = { "GUID", "SID", NULL };
+ ldb_dn_extended_filter(source_dn, accept);
}
/* construct a ldb_message for adding/deleting the backlink */
msg->dn = target_dn;
- dn_string = ldb_dn_get_extended_linearized(tmp_ctx, source_dn, 1);
+ dn_string = ldb_dn_get_extended_linearized(frame, bl->forward_dn, 1);
if (!dn_string) {
ldb_module_oom(module);
- talloc_free(tmp_ctx);
+ talloc_free(frame);
return LDB_ERR_OPERATIONS_ERROR;
}
ret = ldb_msg_add_steal_string(msg, bl->attr_name, dn_string);
if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
+ talloc_free(frame);
return ret;
}
msg->elements[0].flags = bl->active?LDB_FLAG_MOD_ADD:LDB_FLAG_MOD_DELETE;
ldb_dn_get_linearized(source_dn),
ldb_dn_get_linearized(target_dn),
ldb_errstring(ldb));
- talloc_free(tmp_ctx);
+ talloc_free(frame);
return ret;
}
- talloc_free(tmp_ctx);
+ talloc_free(frame);
return ret;
}
/*
add a backlink to the list of backlinks to add/delete in the prepare
commit
+
+ forward_dn is stolen onto the defereed context
*/
-static int replmd_add_backlink(struct ldb_module *module, const struct dsdb_schema *schema,
- struct GUID *forward_guid, struct GUID *target_guid,
- bool active, const struct dsdb_attribute *schema_attr, bool immediate)
+static int replmd_defer_add_backlink(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ const struct dsdb_schema *schema,
+ struct replmd_replicated_request *ac,
+ struct ldb_dn *forward_dn,
+ struct GUID *target_guid, bool active,
+ const struct dsdb_attribute *schema_attr,
+ struct ldb_request *parent)
{
const struct dsdb_attribute *target_attr;
struct la_backlink *bl;
- struct replmd_private *replmd_private =
- talloc_get_type_abort(ldb_module_get_private(module), struct replmd_private);
+
+ bl = talloc(ac, struct la_backlink);
+ if (bl == NULL) {
+ ldb_module_oom(module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID ^ 1);
if (!target_attr) {
return LDB_SUCCESS;
}
- /* see if its already in the list */
- for (bl=replmd_private->la_backlinks; bl; bl=bl->next) {
- if (GUID_equal(forward_guid, &bl->forward_guid) &&
- GUID_equal(target_guid, &bl->target_guid) &&
- (target_attr->lDAPDisplayName == bl->attr_name ||
- strcmp(target_attr->lDAPDisplayName, bl->attr_name) == 0)) {
- break;
- }
- }
-
- if (bl) {
- /* we found an existing one */
- if (bl->active == active) {
- return LDB_SUCCESS;
- }
- DLIST_REMOVE(replmd_private->la_backlinks, bl);
- talloc_free(bl);
- return LDB_SUCCESS;
- }
-
- if (replmd_private->bl_ctx == NULL) {
- replmd_private->bl_ctx = talloc_new(replmd_private);
- if (replmd_private->bl_ctx == NULL) {
- ldb_module_oom(module);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- }
-
- /* its a new one */
- bl = talloc(replmd_private->bl_ctx, struct la_backlink);
- if (bl == NULL) {
- ldb_module_oom(module);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- /* Ensure the schema does not go away before the bl->attr_name is used */
- if (!talloc_reference(bl, schema)) {
- talloc_free(bl);
- ldb_module_oom(module);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
bl->attr_name = target_attr->lDAPDisplayName;
- bl->forward_guid = *forward_guid;
+ bl->forward_dn = talloc_steal(bl, forward_dn);
bl->target_guid = *target_guid;
bl->active = active;
- /* the caller may ask for this backlink to be processed
- immediately */
- if (immediate) {
- int ret = replmd_process_backlink(module, bl, NULL);
- talloc_free(bl);
- return ret;
+ DLIST_ADD(ac->la_backlinks, bl);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ add a backlink to the list of backlinks to add/delete in the prepare
+ commit
+ */
+static int replmd_add_backlink(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ const struct dsdb_schema *schema,
+ struct ldb_dn *forward_dn,
+ struct GUID *target_guid, bool active,
+ const struct dsdb_attribute *schema_attr,
+ struct ldb_request *parent)
+{
+ const struct dsdb_attribute *target_attr;
+ struct la_backlink bl;
+ int ret;
+
+ target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID ^ 1);
+ if (!target_attr) {
+ /*
+ * windows 2003 has a broken schema where the
+ * definition of msDS-IsDomainFor is missing (which is
+ * supposed to be the backlink of the
+ * msDS-HasDomainNCs attribute
+ */
+ return LDB_SUCCESS;
}
- DLIST_ADD(replmd_private->la_backlinks, bl);
+ bl.attr_name = target_attr->lDAPDisplayName;
+ bl.forward_dn = forward_dn;
+ bl.target_guid = *target_guid;
+ bl.active = active;
- return LDB_SUCCESS;
+ ret = replmd_process_backlink(module, &bl, parent);
+ return ret;
}
NULL, LDB_ERR_OPERATIONS_ERROR);
}
+ if (ac->apply_mode == false) {
+ struct la_backlink *bl;
+ /*
+ * process our backlink list after an replmd_add(),
+ * creating and deleting backlinks as necessary (this
+ * code is sync). The other cases are handled inline
+ * with the modify.
+ */
+ for (bl=ac->la_backlinks; bl; bl=bl->next) {
+ ret = replmd_process_backlink(ac->module, bl, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL,
+ NULL, ret);
+ }
+ }
+ }
+
if (!partition_ctrl) {
ldb_set_errstring(ldb_module_get_ctx(ac->module),"No partition control on reply");
return ldb_module_done(ac->req, NULL,
const struct GUID *invocation_id, uint64_t seq_num,
uint64_t local_usn, NTTIME nttime, uint32_t version, bool deleted);
+static int parsed_dn_compare(struct parsed_dn *pdn1, struct parsed_dn *pdn2);
+
+static int get_parsed_dns(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *el, struct parsed_dn **pdn,
+ const char *ldap_oid, struct ldb_request *parent);
/*
fix up linked attributes in replmd_add.
This involves setting up the right meta-data in extended DN
components, and creating backlinks to the object
*/
-static int replmd_add_fix_la(struct ldb_module *module, struct ldb_message_element *el,
- uint64_t seq_num, const struct GUID *invocationId, NTTIME now,
- struct GUID *guid, const struct dsdb_attribute *sa, struct ldb_request *parent)
+static int replmd_add_fix_la(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct replmd_private *replmd_private,
+ struct ldb_message_element *el,
+ struct replmd_replicated_request *ac,
+ NTTIME now,
+ struct ldb_dn *forward_dn,
+ const struct dsdb_attribute *sa,
+ struct ldb_request *parent)
{
unsigned int i;
- TALLOC_CTX *tmp_ctx = talloc_new(el->values);
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
struct ldb_context *ldb = ldb_module_get_ctx(module);
-
+ struct parsed_dn *pdn;
/* We will take a reference to the schema in replmd_add_backlink */
const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
+ struct ldb_val *new_values = NULL;
+ int ret;
- for (i=0; i<el->num_values; i++) {
- struct ldb_val *v = &el->values[i];
- struct dsdb_dn *dsdb_dn = dsdb_dn_parse(tmp_ctx, ldb, v, sa->syntax->ldap_oid);
- struct GUID target_guid;
- NTSTATUS status;
- int ret;
+ if (dsdb_check_single_valued_link(sa, el) == LDB_SUCCESS) {
+ el->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
+ } else {
+ ldb_asprintf_errstring(ldb,
+ "Attribute %s is single valued but "
+ "more than one value has been supplied",
+ el->name);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ret = get_parsed_dns(module, tmp_ctx, el, &pdn,
+ sa->syntax->ldap_oid, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
- if (dsdb_dn == NULL) {
- talloc_free(tmp_ctx);
- return LDB_ERR_INVALID_DN_SYNTAX;
- }
+ new_values = talloc_array(tmp_ctx, struct ldb_val, el->num_values);
+ if (new_values == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
- /* note that the DN already has the extended
- components from the extended_dn_store module */
- status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &target_guid, "GUID");
- if (!NT_STATUS_IS_OK(status) || GUID_all_zero(&target_guid)) {
- ret = dsdb_module_guid_by_dn(module, dsdb_dn->dn, &target_guid, parent);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
- ret = dsdb_set_extended_dn_guid(dsdb_dn->dn, &target_guid, "GUID");
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
+ for (i = 0; i < el->num_values; i++) {
+ struct parsed_dn *p = &pdn[i];
+ if (i > 0 && parsed_dn_compare(p, &pdn[i - 1]) == 0) {
+ ldb_asprintf_errstring(ldb,
+ "Linked attribute %s has "
+ "multiple identical values", el->name);
+ talloc_free(tmp_ctx);
+ if (ldb_attr_cmp(el->name, "member") == 0) {
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ } else {
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
}
}
-
- ret = replmd_build_la_val(el->values, v, dsdb_dn, invocationId,
- seq_num, seq_num, now, 0, false);
+ ret = replmd_build_la_val(el->values, p->v, p->dsdb_dn,
+ &ac->our_invocation_id,
+ ac->seq_num, ac->seq_num, now, 0, false);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- ret = replmd_add_backlink(module, schema, guid, &target_guid, true, sa, false);
+ ret = replmd_defer_add_backlink(module, replmd_private,
+ schema, ac,
+ forward_dn, &p->guid, true, sa,
+ parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+
+ new_values[i] = *p->v;
}
+ el->values = talloc_steal(mem_ctx, new_values);
talloc_free(tmp_ctx);
return LDB_SUCCESS;
}
+static int replmd_add_make_extended_dn(struct ldb_request *req,
+ const DATA_BLOB *guid_blob,
+ struct ldb_dn **_extended_dn)
+{
+ int ret;
+ const DATA_BLOB *sid_blob;
+ /* Calculate an extended DN for any linked attributes */
+ struct ldb_dn *extended_dn = ldb_dn_copy(req, req->op.add.message->dn);
+ if (!extended_dn) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_dn_set_extended_component(extended_dn, "GUID", guid_blob);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ sid_blob = ldb_msg_find_ldb_val(req->op.add.message, "objectSID");
+ if (sid_blob != NULL) {
+ ret = ldb_dn_set_extended_component(extended_dn, "SID", sid_blob);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ *_extended_dn = extended_dn;
+ return LDB_SUCCESS;
+}
/*
intercept add requests
struct ldb_request *down_req;
struct ldb_message *msg;
const DATA_BLOB *guid_blob;
+ DATA_BLOB guid_blob_stack;
struct GUID guid;
+ uint8_t guid_data[16];
struct replPropertyMetaDataBlob nmd;
struct ldb_val nmd_value;
-
+ struct ldb_dn *extended_dn = NULL;
+
/*
* The use of a time_t here seems odd, but as the NTTIME
* elements are actually declared as NTTIME_1sec in the IDL,
} else {
/* a new GUID */
guid = GUID_random();
+
+ guid_blob_stack = data_blob_const(guid_data, sizeof(guid_data));
+
+ /* This can't fail */
+ ndr_push_struct_into_fixed_blob(&guid_blob_stack, &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ guid_blob = &guid_blob_stack;
}
ac = replmd_ctx_init(module, req);
}
if (sa->linkID != 0 && functional_level > DS_DOMAIN_FUNCTION_2000) {
- ret = replmd_add_fix_la(module, e, ac->seq_num,
- &ac->our_invocation_id, now,
- &guid, sa, req);
+ if (extended_dn == NULL) {
+ ret = replmd_add_make_extended_dn(req,
+ guid_blob,
+ &extended_dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
+ /*
+ * Prepare the context for the backlinks and
+ * create metadata for the forward links. The
+ * backlinks are created in
+ * replmd_op_callback() after the successful
+ * ADD of the object.
+ */
+ ret = replmd_add_fix_la(module, msg->elements,
+ replmd_private, e,
+ ac, now,
+ extended_dn,
+ sa, req);
if (ret != LDB_SUCCESS) {
talloc_free(ac);
return ret;
const struct GUID *our_invocation_id,
NTTIME now,
bool is_schema_nc,
+ bool is_forced_rodc,
struct ldb_request *req)
{
uint32_t i;
md1 = &omd->ctr.ctr1.array[i];
md1->version++;
md1->attid = attid;
+
if (md1->attid == DRSUAPI_ATTID_isDeleted) {
const struct ldb_val *rdn_val = ldb_dn_get_rdn_val(msg->dn);
const char* rdn;
md1->originating_usn = *seq_num;
md1->local_usn = *seq_num;
+ if (is_forced_rodc) {
+ /* Force version to 0 to be overriden later via replication */
+ md1->version = 0;
+ }
+
return LDB_SUCCESS;
}
struct replPropertyMetaDataBlob *omd,
struct replmd_replicated_request *ar,
NTTIME now,
- bool is_schema_nc)
+ bool is_schema_nc,
+ bool is_forced_rodc)
{
const char *rdn_name = ldb_dn_get_rdn_name(msg->dn);
const struct dsdb_attribute *rdn_attr =
return replmd_update_rpmd_element(ldb, msg, &new_el, NULL,
omd, ar->schema, &ar->seq_num,
&ar->our_invocation_id,
- now, is_schema_nc, ar->req);
+ now, is_schema_nc, is_forced_rodc,
+ ar->req);
}
bool rmd_is_provided;
bool rmd_is_just_resorted = false;
const char *not_rename_attrs[4 + msg->num_elements];
-
+ bool is_forced_rodc = false;
+
if (rename_attrs) {
attrs = rename_attrs;
} else {
ldb = ldb_module_get_ctx(module);
+ ret = samdb_rodc(ldb, rodc);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(4, (__location__ ": unable to tell if we are an RODC\n"));
+ *rodc = false;
+ }
+
+ if (*rodc &&
+ ldb_request_get_control(req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE)) {
+ is_forced_rodc = true;
+ }
+
our_invocation_id = samdb_ntds_invocation_id(ldb);
if (!our_invocation_id) {
/* this happens during an initial vampire while
&omd, schema, seq_num,
our_invocation_id,
now, is_schema_nc,
+ is_forced_rodc,
req);
if (ret != LDB_SUCCESS) {
return ret;
/*if we are RODC and this is a DRSR update then its ok*/
if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)
- && !ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA)) {
+ && !ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA)
+ && !is_forced_rodc) {
unsigned instanceType;
- ret = samdb_rodc(ldb, rodc);
- if (ret != LDB_SUCCESS) {
- DEBUG(4, (__location__ ": unable to tell if we are an RODC\n"));
- } else if (*rodc) {
+ if (*rodc) {
ldb_set_errstring(ldb, "RODC modify is forbidden!");
return LDB_ERR_REFERRAL;
}
return LDB_SUCCESS;
}
-struct parsed_dn {
- struct dsdb_dn *dsdb_dn;
- struct GUID guid;
- struct ldb_val *v;
-};
-
static int parsed_dn_compare(struct parsed_dn *pdn1, struct parsed_dn *pdn2)
{
- return GUID_compare(&pdn1->guid, &pdn2->guid);
-}
-
-static int GUID_compare_struct(struct GUID *g1, struct GUID g2)
-{
- return GUID_compare(g1, &g2);
-}
-
-static struct parsed_dn *parsed_dn_find(struct parsed_dn *pdn,
- unsigned int count, struct GUID *guid,
- struct ldb_dn *dn)
-{
- struct parsed_dn *ret;
- unsigned int i;
- if (dn && GUID_all_zero(guid)) {
- /* when updating a link using DRS, we sometimes get a
- NULL GUID. We then need to try and match by DN */
- for (i=0; i<count; i++) {
- if (ldb_dn_compare(pdn[i].dsdb_dn->dn, dn) == 0) {
- dsdb_get_extended_dn_guid(pdn[i].dsdb_dn->dn, guid, "GUID");
- return &pdn[i];
- }
- }
- return NULL;
+ int ret = ndr_guid_compare(&pdn1->guid, &pdn2->guid);
+ if (ret == 0) {
+ return data_blob_cmp(&pdn1->dsdb_dn->extra_part,
+ &pdn2->dsdb_dn->extra_part);
}
- BINARY_ARRAY_SEARCH(pdn, count, guid, guid, GUID_compare_struct, ret);
return ret;
}
dn = p->dsdb_dn->dn;
status = dsdb_get_extended_dn_guid(dn, &p->guid, "GUID");
- if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) ||
+ unlikely(GUID_all_zero(&p->guid))) {
/* we got a DN without a GUID - go find the GUID */
int ret = dsdb_module_guid_by_dn(module, dn, &p->guid, parent);
if (ret != LDB_SUCCESS) {
return LDB_SUCCESS;
}
+/*
+ * Get a series of trusted message element values. The result is sorted by
+ * GUID, even though the GUIDs might not be known. That works because we trust
+ * the database to give us the elements like that if the
+ * replmd_private->sorted_links flag is set.
+ *
+ * We also ensure that the links are in the Functional Level 2003
+ * linked attributes format.
+ */
+static int get_parsed_dns_trusted(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *el,
+ struct parsed_dn **pdn,
+ const char *ldap_oid,
+ struct ldb_request *parent)
+{
+ unsigned int i;
+ int ret;
+ if (el == NULL) {
+ *pdn = NULL;
+ return LDB_SUCCESS;
+ }
+
+ if (!replmd_private->sorted_links) {
+ /* We need to sort the list. This is the slow old path we want
+ to avoid.
+ */
+ ret = get_parsed_dns(module, mem_ctx, el, pdn, ldap_oid,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ } else {
+ /* Here we get a list of 'struct parsed_dns' without the parsing */
+ *pdn = talloc_zero_array(mem_ctx, struct parsed_dn,
+ el->num_values);
+ if (!*pdn) {
+ ldb_module_oom(module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ (*pdn)[i].v = &el->values[i];
+ }
+ }
+
+ /*
+ * This upgrades links to FL2003 style, and sorts the result
+ * if that was needed.
+ *
+ * TODO: Add a database feature that asserts we have no FL2000
+ * style links to avoid this check or add a feature that
+ * uses a similar check to find sorted/unsorted links
+ * for an on-the-fly upgrade.
+ */
+
+ ret = replmd_check_upgrade_links(ldb_module_get_ctx(module),
+ *pdn, el->num_values,
+ el,
+ ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
/*
build a new extended DN, including all meta data fields
/*
check if any links need upgrading from w2k format
-
- The parent_ctx is the ldb_message_element which contains the values array that dns[i].v points at, and which should be used for allocating any new value.
*/
-static int replmd_check_upgrade_links(struct parsed_dn *dns, uint32_t count, struct ldb_message_element *parent_ctx, const struct GUID *invocation_id)
+static int replmd_check_upgrade_links(struct ldb_context *ldb,
+ struct parsed_dn *dns, uint32_t count,
+ struct ldb_message_element *el,
+ const char *ldap_oid)
{
uint32_t i;
+ const struct GUID *invocation_id = NULL;
for (i=0; i<count; i++) {
NTSTATUS status;
uint32_t version;
int ret;
+ if (dns[i].dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(dns, ldb, &dns[i],
+ ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+ }
- status = dsdb_get_extended_dn_uint32(dns[i].dsdb_dn->dn, &version, "RMD_VERSION");
+ status = dsdb_get_extended_dn_uint32(dns[i].dsdb_dn->dn,
+ &version, "RMD_VERSION");
if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * We optimistically assume they are all the same; if
+ * the first one is fixed, they are all fixed.
+ *
+ * If the first one was *not* fixed and we find a
+ * later one that is, that is an occasion to shout
+ * with DEBUG(0).
+ */
+ if (i == 0) {
+ return LDB_SUCCESS;
+ }
+ DEBUG(0, ("Mixed w2k and fixed format "
+ "linked attributes\n"));
continue;
}
+ if (invocation_id == NULL) {
+ invocation_id = samdb_ntds_invocation_id(ldb);
+ if (invocation_id == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+
/* it's an old one that needs upgrading */
- ret = replmd_update_la_val(parent_ctx->values, dns[i].v, dns[i].dsdb_dn, dns[i].dsdb_dn, invocation_id,
- 1, 1, 0, 0, false);
+ ret = replmd_update_la_val(el->values, dns[i].v,
+ dns[i].dsdb_dn, dns[i].dsdb_dn,
+ invocation_id, 1, 1, 0, 0, false);
if (ret != LDB_SUCCESS) {
return ret;
}
}
+
+ /*
+ * This sort() is critical for the operation of
+ * get_parsed_dns_trusted() because callers of this function
+ * expect a sorted list, and FL2000 style links are not
+ * sorted. In particular, as well as the upgrade case,
+ * get_parsed_dns_trusted() is called from
+ * replmd_delete_remove_link() even in FL2000 mode
+ *
+ * We do not normally pay the cost of the qsort() due to the
+ * early return in the RMD_VERSION found case.
+ */
+ TYPESAFE_QSORT(dns, count, parsed_dn_compare);
return LDB_SUCCESS;
}
handle adding a linked attribute
*/
static int replmd_modify_la_add(struct ldb_module *module,
+ struct replmd_private *replmd_private,
const struct dsdb_schema *schema,
struct ldb_message *msg,
struct ldb_message_element *el,
const struct dsdb_attribute *schema_attr,
uint64_t seq_num,
time_t t,
- struct GUID *msg_guid,
+ struct ldb_dn *msg_dn,
struct ldb_request *parent)
{
- unsigned int i;
+ unsigned int i, j;
struct parsed_dn *dns, *old_dns;
TALLOC_CTX *tmp_ctx = talloc_new(msg);
int ret;
struct ldb_val *new_values = NULL;
- unsigned int num_new_values = 0;
- unsigned old_num_values = old_el?old_el->num_values:0;
+ unsigned old_num_values = old_el ? old_el->num_values : 0;
+ unsigned num_values = 0;
+ unsigned max_num_values;
const struct GUID *invocation_id;
struct ldb_context *ldb = ldb_module_get_ctx(module);
NTTIME now;
-
unix_to_nt_time(&now, t);
- ret = get_parsed_dns(module, tmp_ctx, el, &dns, schema_attr->syntax->ldap_oid, parent);
+ invocation_id = samdb_ntds_invocation_id(ldb);
+ if (!invocation_id) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* get the DNs to be added, fully parsed.
+ *
+ * We need full parsing because they came off the wire and we don't
+ * trust them, besides which we need their details to know where to put
+ * them.
+ */
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns,
+ schema_attr->syntax->ldap_oid, parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- ret = get_parsed_dns(module, tmp_ctx, old_el, &old_dns, schema_attr->syntax->ldap_oid, parent);
+ /* get the existing DNs, lazily parsed */
+ ret = get_parsed_dns_trusted(module, replmd_private,
+ tmp_ctx, old_el, &old_dns,
+ schema_attr->syntax->ldap_oid, parent);
+
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- invocation_id = samdb_ntds_invocation_id(ldb);
- if (!invocation_id) {
+ max_num_values = old_num_values + el->num_values;
+ if (max_num_values < old_num_values) {
+ DEBUG(0, ("we seem to have overflow in replmd_modify_la_add. "
+ "old values: %u, new values: %u, sum: %u",
+ old_num_values, el->num_values, max_num_values));
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
- ret = replmd_check_upgrade_links(old_dns, old_num_values, old_el, invocation_id);
- if (ret != LDB_SUCCESS) {
+ new_values = talloc_zero_array(tmp_ctx, struct ldb_val, max_num_values);
+
+ if (new_values == NULL) {
+ ldb_module_oom(module);
talloc_free(tmp_ctx);
- return ret;
+ return LDB_ERR_OPERATIONS_ERROR;
}
- /* for each new value, see if it exists already with the same GUID */
- for (i=0; i<el->num_values; i++) {
- struct parsed_dn *p = parsed_dn_find(old_dns, old_num_values, &dns[i].guid, NULL);
- if (p == NULL) {
- /* this is a new linked attribute value */
- new_values = talloc_realloc(tmp_ctx, new_values, struct ldb_val, num_new_values+1);
- if (new_values == NULL) {
- ldb_module_oom(module);
- talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- ret = replmd_build_la_val(new_values, &new_values[num_new_values], dns[i].dsdb_dn,
- invocation_id, seq_num, seq_num, now, 0, false);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
- num_new_values++;
- } else {
- /* this is only allowed if the GUID was
- previously deleted. */
- uint32_t rmd_flags = dsdb_dn_rmd_flags(p->dsdb_dn->dn);
+ /*
+ * For each new value, find where it would go in the list. If there is
+ * a matching GUID there, we update the existing value; otherwise we
+ * put it in place.
+ */
+ j = 0;
+ for (i = 0; i < el->num_values; i++) {
+ struct parsed_dn *exact;
+ struct parsed_dn *next;
+ unsigned offset;
+ int err = parsed_dn_find(ldb, old_dns, old_num_values,
+ &dns[i].guid,
+ dns[i].dsdb_dn->dn,
+ dns[i].dsdb_dn->extra_part, 0,
+ &exact, &next,
+ schema_attr->syntax->ldap_oid,
+ true);
+ if (err != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return err;
+ }
+
+ if (exact != NULL) {
+ /*
+ * We are trying to add one that exists, which is only
+ * allowed if it was previously deleted.
+ *
+ * When we do undelete a link we change it in place.
+ * It will be copied across into the right spot in due
+ * course.
+ */
+ uint32_t rmd_flags;
+ rmd_flags = dsdb_dn_rmd_flags(exact->dsdb_dn->dn);
if (!(rmd_flags & DSDB_RMD_FLAG_DELETED)) {
struct GUID_txt_buf guid_str;
- ldb_asprintf_errstring(ldb, "Attribute %s already exists for target GUID %s",
- el->name, GUID_buf_string(&p->guid, &guid_str));
+ ldb_asprintf_errstring(ldb,
+ "Attribute %s already "
+ "exists for target GUID %s",
+ el->name,
+ GUID_buf_string(&exact->guid,
+ &guid_str));
talloc_free(tmp_ctx);
/* error codes for 'member' need to be
special cased */
return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
}
}
- ret = replmd_update_la_val(old_el->values, p->v, dns[i].dsdb_dn, p->dsdb_dn,
- invocation_id, seq_num, seq_num, now, 0, false);
+
+ ret = replmd_update_la_val(new_values, exact->v,
+ dns[i].dsdb_dn,
+ exact->dsdb_dn,
+ invocation_id, seq_num,
+ seq_num, now, 0, false);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ msg_dn,
+ &dns[i].guid,
+ true,
+ schema_attr,
+ parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
+ }
+ continue;
+ }
+ /*
+ * Here we don't have an exact match.
+ *
+ * If next is NULL, this one goes beyond the end of the
+ * existing list, so we need to add all of those ones first.
+ *
+ * If next is not NULL, we need to add all the ones before
+ * next.
+ */
+ if (next == NULL) {
+ offset = old_num_values;
+ } else {
+ /* next should have been parsed, but let's make sure */
+ if (next->dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(tmp_ctx, ldb, next,
+ schema_attr->syntax->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
}
+ offset = MIN(next - old_dns, old_num_values);
+ }
+
+ /* put all the old ones before next on the list */
+ for (; j < offset; j++) {
+ new_values[num_values] = *old_dns[j].v;
+ num_values++;
}
- ret = replmd_add_backlink(module, schema, msg_guid, &dns[i].guid, true, schema_attr, true);
+ ret = replmd_add_backlink(module, replmd_private,
+ schema, msg_dn,
+ &dns[i].guid,
+ true, schema_attr,
+ parent);
+ /* Make the new linked attribute ldb_val. */
+ ret = replmd_build_la_val(new_values, &new_values[num_values],
+ dns[i].dsdb_dn, invocation_id,
+ seq_num, seq_num,
+ now, 0, false);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ num_values++;
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
}
-
- /* add the new ones on to the end of the old values, constructing a new el->values */
- el->values = talloc_realloc(msg->elements, old_el?old_el->values:NULL,
- struct ldb_val,
- old_num_values+num_new_values);
- if (el->values == NULL) {
- ldb_module_oom(module);
- return LDB_ERR_OPERATIONS_ERROR;
+ /* copy the rest of the old ones (if any) */
+ for (; j < old_num_values; j++) {
+ new_values[num_values] = *old_dns[j].v;
+ num_values++;
}
- memcpy(&el->values[old_num_values], new_values, num_new_values*sizeof(struct ldb_val));
- el->num_values = old_num_values + num_new_values;
-
- talloc_steal(msg->elements, el->values);
- talloc_steal(el->values, new_values);
+ talloc_steal(msg->elements, new_values);
+ if (old_el != NULL) {
+ talloc_steal(msg->elements, old_el->values);
+ }
+ el->values = new_values;
+ el->num_values = num_values;
talloc_free(tmp_ctx);
handle deleting all active linked attributes
*/
static int replmd_modify_la_delete(struct ldb_module *module,
+ struct replmd_private *replmd_private,
const struct dsdb_schema *schema,
struct ldb_message *msg,
struct ldb_message_element *el,
const struct dsdb_attribute *schema_attr,
uint64_t seq_num,
time_t t,
- struct GUID *msg_guid,
+ struct ldb_dn *msg_dn,
struct ldb_request *parent)
{
unsigned int i;
struct parsed_dn *dns, *old_dns;
TALLOC_CTX *tmp_ctx = NULL;
int ret;
- const struct GUID *invocation_id;
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_control *vanish_links_ctrl = NULL;
bool vanish_links = false;
unsigned int num_to_delete = el->num_values;
+ uint32_t rmd_flags;
+ const struct GUID *invocation_id;
NTTIME now;
unix_to_nt_time(&now, t);
- /* check if there is nothing to delete */
- if ((!old_el || old_el->num_values == 0) &&
- el->num_values == 0) {
- return LDB_SUCCESS;
+ invocation_id = samdb_ntds_invocation_id(ldb);
+ if (!invocation_id) {
+ return LDB_ERR_OPERATIONS_ERROR;
}
- if (!old_el || old_el->num_values == 0) {
+ if (old_el == NULL || old_el->num_values == 0) {
+ /* there is nothing to delete... */
+ if (num_to_delete == 0) {
+ /* and we're deleting nothing, so that's OK */
+ return LDB_SUCCESS;
+ }
return LDB_ERR_NO_SUCH_ATTRIBUTE;
}
return LDB_ERR_OPERATIONS_ERROR;
}
- ret = get_parsed_dns(module, tmp_ctx, el, &dns, schema_attr->syntax->ldap_oid, parent);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
-
- ret = get_parsed_dns(module, tmp_ctx, old_el, &old_dns, schema_attr->syntax->ldap_oid, parent);
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns,
+ schema_attr->syntax->ldap_oid, parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- invocation_id = samdb_ntds_invocation_id(ldb);
- if (!invocation_id) {
- talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
+ ret = get_parsed_dns_trusted(module, replmd_private,
+ tmp_ctx, old_el, &old_dns,
+ schema_attr->syntax->ldap_oid, parent);
- ret = replmd_check_upgrade_links(old_dns, old_el->num_values, old_el, invocation_id);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
}
+ /* we empty out el->values here to avoid damage if we return early. */
el->num_values = 0;
el->values = NULL;
- /* see if we are being asked to delete any links that
- don't exist or are already deleted */
- for (i=0; i < num_to_delete; i++) {
- struct parsed_dn *p = &dns[i];
- struct parsed_dn *p2;
- uint32_t rmd_flags;
+ /*
+ * If vanish links is set, we are actually removing members of
+ * old_el->values; otherwise we are just marking them deleted.
+ *
+ * There is a special case when no values are given: we remove them
+ * all. When we have the vanish_links control we just have to remove
+ * the backlinks and change our element to replace the existing values
+ * with the empty list.
+ */
- p2 = parsed_dn_find(old_dns, old_el->num_values, &p->guid, NULL);
- if (!p2) {
+ if (num_to_delete == 0) {
+ for (i = 0; i < old_el->num_values; i++) {
+ struct parsed_dn *p = &old_dns[i];
+ if (p->dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(tmp_ctx, ldb, p,
+ schema_attr->syntax->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ ret = replmd_add_backlink(module, replmd_private,
+ schema, msg_dn, &p->guid,
+ false, schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (vanish_links) {
+ continue;
+ }
+
+ rmd_flags = dsdb_dn_rmd_flags(p->dsdb_dn->dn);
+ if (rmd_flags & DSDB_RMD_FLAG_DELETED) {
+ continue;
+ }
+
+ ret = replmd_update_la_val(old_el->values, p->v,
+ p->dsdb_dn, p->dsdb_dn,
+ invocation_id, seq_num,
+ seq_num, now, 0, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ if (vanish_links) {
+ el->flags = LDB_FLAG_MOD_REPLACE;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ }
+
+
+ for (i = 0; i < num_to_delete; i++) {
+ struct parsed_dn *p = &dns[i];
+ struct parsed_dn *exact = NULL;
+ struct parsed_dn *next = NULL;
+ ret = parsed_dn_find(ldb, old_dns, old_el->num_values,
+ &p->guid,
+ NULL,
+ p->dsdb_dn->extra_part, 0,
+ &exact, &next,
+ schema_attr->syntax->ldap_oid,
+ true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (exact == NULL) {
struct GUID_txt_buf buf;
- ldb_asprintf_errstring(ldb, "Attribute %s doesn't exist for target GUID %s",
- el->name, GUID_buf_string(&p->guid, &buf));
+ ldb_asprintf_errstring(ldb, "Attribute %s doesn't "
+ "exist for target GUID %s",
+ el->name,
+ GUID_buf_string(&p->guid, &buf));
if (ldb_attr_cmp(el->name, "member") == 0) {
talloc_free(tmp_ctx);
return LDB_ERR_UNWILLING_TO_PERFORM;
return LDB_ERR_NO_SUCH_ATTRIBUTE;
}
}
- rmd_flags = dsdb_dn_rmd_flags(p2->dsdb_dn->dn);
+
+ if (vanish_links) {
+ if (CHECK_DEBUGLVL(5)) {
+ rmd_flags = dsdb_dn_rmd_flags(exact->dsdb_dn->dn);
+ if ((rmd_flags & DSDB_RMD_FLAG_DELETED)) {
+ struct GUID_txt_buf buf;
+ const char *guid_str = \
+ GUID_buf_string(&p->guid, &buf);
+ DEBUG(5, ("Deleting deleted linked "
+ "attribute %s to %s, because "
+ "vanish_links control is set\n",
+ el->name, guid_str));
+ }
+ }
+
+ /* remove the backlink */
+ ret = replmd_add_backlink(module,
+ replmd_private,
+ schema,
+ msg_dn,
+ &p->guid,
+ false, schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* We flag the deletion and tidy it up later. */
+ exact->v = NULL;
+ continue;
+ }
+
+ rmd_flags = dsdb_dn_rmd_flags(exact->dsdb_dn->dn);
+
if (rmd_flags & DSDB_RMD_FLAG_DELETED) {
struct GUID_txt_buf buf;
const char *guid_str = GUID_buf_string(&p->guid, &buf);
- if (vanish_links) {
- DEBUG(0, ("Deleting deleted linked attribute %s to %s, "
- "because vanish_links control is set\n",
- el->name, guid_str));
- continue;
- }
- ldb_asprintf_errstring(ldb, "Attribute %s already deleted for target GUID %s",
+ ldb_asprintf_errstring(ldb, "Attribute %s already "
+ "deleted for target GUID %s",
el->name, guid_str);
if (ldb_attr_cmp(el->name, "member") == 0) {
talloc_free(tmp_ctx);
return LDB_ERR_NO_SUCH_ATTRIBUTE;
}
}
- }
- if (vanish_links) {
- if (num_to_delete == old_el->num_values || num_to_delete == 0) {
- el->flags = LDB_FLAG_MOD_REPLACE;
-
- for (i = 0; i < old_el->num_values; i++) {
- ret = replmd_add_backlink(module, schema, msg_guid, &old_dns[i].guid, false, schema_attr, true);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
- }
+ ret = replmd_update_la_val(old_el->values, exact->v,
+ exact->dsdb_dn, exact->dsdb_dn,
+ invocation_id, seq_num, seq_num,
+ now, 0, true);
+ if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
- return LDB_SUCCESS;
- } else {
- unsigned int num_values = 0;
- unsigned int j = 0;
- for (i = 0; i < old_el->num_values; i++) {
- if (parsed_dn_find(dns, num_to_delete, &old_dns[i].guid, NULL) != NULL) {
- /* The element is in the delete list. mark it dead. */
- ret = replmd_add_backlink(module, schema, msg_guid, &old_dns[i].guid, false, schema_attr, true);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
- old_dns[i].v->length = 0;
- } else {
- num_values++;
- }
- }
- for (i = 0; i < old_el->num_values; i++) {
- if (old_el->values[i].length != 0) {
- old_el->values[j] = old_el->values[i];
- j++;
- if (j == num_values) {
- break;
- }
- }
- }
- old_el->num_values = num_values;
+ return ret;
}
- } else {
-
- /* for each new value, see if it exists already with the same GUID
- if it is not already deleted and matches the delete list then delete it
- */
- for (i=0; i<old_el->num_values; i++) {
- struct parsed_dn *p = &old_dns[i];
- uint32_t rmd_flags;
-
- if (num_to_delete && parsed_dn_find(dns, num_to_delete, &p->guid, NULL) == NULL) {
- continue;
- }
-
- rmd_flags = dsdb_dn_rmd_flags(p->dsdb_dn->dn);
- if (rmd_flags & DSDB_RMD_FLAG_DELETED) continue;
+ ret = replmd_add_backlink(module, replmd_private,
+ schema, msg_dn,
+ &p->guid,
+ false, schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
- ret = replmd_update_la_val(old_el->values, p->v, p->dsdb_dn, p->dsdb_dn,
- invocation_id, seq_num, seq_num, now, 0, true);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
- ret = replmd_add_backlink(module, schema, msg_guid, &old_dns[i].guid, false, schema_attr, true);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
+ if (vanish_links) {
+ unsigned j = 0;
+ for (i = 0; i < old_el->num_values; i++) {
+ if (old_dns[i].v != NULL) {
+ old_el->values[j] = *old_dns[i].v;
+ j++;
}
}
+ old_el->num_values = j;
}
+
el->values = talloc_steal(msg->elements, old_el->values);
el->num_values = old_el->num_values;
handle replacing a linked attribute
*/
static int replmd_modify_la_replace(struct ldb_module *module,
+ struct replmd_private *replmd_private,
const struct dsdb_schema *schema,
struct ldb_message *msg,
struct ldb_message_element *el,
const struct dsdb_attribute *schema_attr,
uint64_t seq_num,
time_t t,
- struct GUID *msg_guid,
+ struct ldb_dn *msg_dn,
struct ldb_request *parent)
{
- unsigned int i;
+ unsigned int i, old_i, new_i;
struct parsed_dn *dns, *old_dns;
TALLOC_CTX *tmp_ctx = talloc_new(msg);
int ret;
const struct GUID *invocation_id;
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_val *new_values = NULL;
- unsigned int num_new_values = 0;
- unsigned int old_num_values = old_el?old_el->num_values:0;
+ const char *ldap_oid = schema_attr->syntax->ldap_oid;
+ unsigned int old_num_values;
+ unsigned int repl_num_values;
+ unsigned int max_num_values;
NTTIME now;
unix_to_nt_time(&now, t);
- /* check if there is nothing to replace */
- if ((!old_el || old_el->num_values == 0) &&
- el->num_values == 0) {
+ invocation_id = samdb_ntds_invocation_id(ldb);
+ if (!invocation_id) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * The replace operation is unlike the replace and delete cases in that
+ * we need to look at every existing link to see whether it is being
+ * retained or deleted. In other words, we can't avoid parsing the GUIDs.
+ *
+ * As we are trying to combine two sorted lists, the algorithm we use
+ * is akin to the merge phase of a merge sort. We interleave the two
+ * lists, doing different things depending on which side the current
+ * item came from.
+ *
+ * There are three main cases, with some sub-cases.
+ *
+ * - a DN is in the old list but not the new one. It needs to be
+ * marked as deleted (but left in the list).
+ * - maybe it is already deleted, and we have less to do.
+ *
+ * - a DN is in both lists. The old data gets replaced by the new,
+ * and the list doesn't grow. The old link may have been marked as
+ * deleted, in which case we undelete it.
+ *
+ * - a DN is in the new list only. We add it in the right place.
+ */
+
+ old_num_values = old_el ? old_el->num_values : 0;
+ repl_num_values = el->num_values;
+ max_num_values = old_num_values + repl_num_values;
+
+ if (max_num_values == 0) {
+ /* There is nothing to do! */
return LDB_SUCCESS;
}
- ret = get_parsed_dns(module, tmp_ctx, el, &dns, schema_attr->syntax->ldap_oid, parent);
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns, ldap_oid, parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- ret = get_parsed_dns(module, tmp_ctx, old_el, &old_dns, schema_attr->syntax->ldap_oid, parent);
+ ret = get_parsed_dns(module, tmp_ctx, old_el, &old_dns,
+ ldap_oid, parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- invocation_id = samdb_ntds_invocation_id(ldb);
- if (!invocation_id) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- ret = replmd_check_upgrade_links(old_dns, old_num_values, old_el, invocation_id);
+ ret = replmd_check_upgrade_links(ldb, old_dns, old_num_values,
+ old_el, ldap_oid);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- /* mark all the old ones as deleted */
- for (i=0; i<old_num_values; i++) {
- struct parsed_dn *old_p = &old_dns[i];
- struct parsed_dn *p;
- uint32_t rmd_flags = dsdb_dn_rmd_flags(old_p->dsdb_dn->dn);
-
- if (rmd_flags & DSDB_RMD_FLAG_DELETED) continue;
+ new_values = talloc_array(tmp_ctx, struct ldb_val, max_num_values);
+ if (new_values == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
- ret = replmd_add_backlink(module, schema, msg_guid, &old_dns[i].guid, false, schema_attr, false);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
+ old_i = 0;
+ new_i = 0;
+ for (i = 0; i < max_num_values; i++) {
+ int cmp;
+ struct parsed_dn *old_p, *new_p;
+ if (old_i < old_num_values && new_i < repl_num_values) {
+ old_p = &old_dns[old_i];
+ new_p = &dns[new_i];
+ cmp = parsed_dn_compare(old_p, new_p);
+ } else if (old_i < old_num_values) {
+ /* the new list is empty, read the old list */
+ old_p = &old_dns[old_i];
+ new_p = NULL;
+ cmp = -1;
+ } else if (new_i < repl_num_values) {
+ /* the old list is empty, read new list */
+ old_p = NULL;
+ new_p = &dns[new_i];
+ cmp = 1;
+ } else {
+ break;
}
- p = parsed_dn_find(dns, el->num_values, &old_p->guid, NULL);
- if (p) {
- /* we don't delete it if we are re-adding it */
- continue;
- }
+ if (cmp < 0) {
+ /*
+ * An old ones that come before the next replacement
+ * (if any). We mark it as deleted and add it to the
+ * final list.
+ */
+ uint32_t rmd_flags = dsdb_dn_rmd_flags(old_p->dsdb_dn->dn);
+ if ((rmd_flags & DSDB_RMD_FLAG_DELETED) == 0) {
+ ret = replmd_update_la_val(new_values, old_p->v,
+ old_p->dsdb_dn,
+ old_p->dsdb_dn,
+ invocation_id,
+ seq_num, seq_num,
+ now, 0, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
- ret = replmd_update_la_val(old_el->values, old_p->v, old_p->dsdb_dn, old_p->dsdb_dn,
- invocation_id, seq_num, seq_num, now, 0, true);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
- }
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ msg_dn,
+ &old_p->guid, false,
+ schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ new_values[i] = *old_p->v;
+ old_i++;
+ } else if (cmp == 0) {
+ /*
+ * We are overwriting one. If it was previously
+ * deleted, we need to add a backlink.
+ *
+ * Note that if any RMD_FLAGs in an extended new DN
+ * will be ignored.
+ */
+ uint32_t rmd_flags;
- /* for each new value, either update its meta-data, or add it
- * to old_el
- */
- for (i=0; i<el->num_values; i++) {
- struct parsed_dn *p = &dns[i], *old_p;
-
- if (old_dns &&
- (old_p = parsed_dn_find(old_dns,
- old_num_values, &p->guid, NULL)) != NULL) {
- /* update in place */
- ret = replmd_update_la_val(old_el->values, old_p->v, p->dsdb_dn,
- old_p->dsdb_dn, invocation_id,
- seq_num, seq_num, now, 0, false);
+ ret = replmd_update_la_val(new_values, old_p->v,
+ new_p->dsdb_dn,
+ old_p->dsdb_dn,
+ invocation_id,
+ seq_num, seq_num,
+ now, 0, false);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+
+ rmd_flags = dsdb_dn_rmd_flags(old_p->dsdb_dn->dn);
+ if ((rmd_flags & DSDB_RMD_FLAG_DELETED) != 0) {
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ msg_dn,
+ &new_p->guid, true,
+ schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ new_values[i] = *old_p->v;
+ old_i++;
+ new_i++;
} else {
- /* add a new one */
- new_values = talloc_realloc(tmp_ctx, new_values, struct ldb_val,
- num_new_values+1);
- if (new_values == NULL) {
- ldb_module_oom(module);
+ /*
+ * Replacements that don't match an existing one. We
+ * just add them to the final list.
+ */
+ ret = replmd_build_la_val(new_values,
+ new_p->v,
+ new_p->dsdb_dn,
+ invocation_id,
+ seq_num, seq_num,
+ now, 0, false);
+ if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
+ return ret;
}
- ret = replmd_build_la_val(new_values, &new_values[num_new_values], dns[i].dsdb_dn,
- invocation_id, seq_num, seq_num, now, 0, false);
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ msg_dn,
+ &new_p->guid, true,
+ schema_attr,
+ parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- num_new_values++;
- }
-
- ret = replmd_add_backlink(module, schema, msg_guid, &dns[i].guid, true, schema_attr, false);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
+ new_values[i] = *new_p->v;
+ new_i++;
}
}
-
- /* add the new values to the end of old_el */
- if (num_new_values != 0) {
- el->values = talloc_realloc(msg->elements, old_el?old_el->values:NULL,
- struct ldb_val, old_num_values+num_new_values);
- if (el->values == NULL) {
- ldb_module_oom(module);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- memcpy(&el->values[old_num_values], &new_values[0],
- sizeof(struct ldb_val)*num_new_values);
- el->num_values = old_num_values + num_new_values;
- talloc_steal(msg->elements, new_values);
- } else {
- el->values = old_el->values;
- el->num_values = old_el->num_values;
- talloc_steal(msg->elements, el->values);
+ if (old_el != NULL) {
+ talloc_steal(msg->elements, old_el->values);
}
-
+ el->values = talloc_steal(msg->elements, new_values);
+ el->num_values = i;
talloc_free(tmp_ctx);
- /* we now tell the backend to replace all existing values
- with the one we have constructed */
el->flags = LDB_FLAG_MOD_REPLACE;
return LDB_SUCCESS;
handle linked attributes in modify requests
*/
static int replmd_modify_handle_linked_attribs(struct ldb_module *module,
+ struct replmd_private *replmd_private,
struct ldb_message *msg,
uint64_t seq_num, time_t t,
struct ldb_request *parent)
struct ldb_message *old_msg;
const struct dsdb_schema *schema;
- struct GUID old_guid;
if (dsdb_functional_level(ldb) == DS_DOMAIN_FUNCTION_2000) {
/*
old_msg = res->msgs[0];
- old_guid = samdb_result_guid(old_msg, "objectGUID");
-
for (i=0; i<msg->num_elements; i++) {
struct ldb_message_element *el = &msg->elements[i];
struct ldb_message_element *old_el, *new_el;
+ unsigned int mod_type = LDB_FLAG_MOD_TYPE(el->flags);
const struct dsdb_attribute *schema_attr
= dsdb_attribute_by_lDAPDisplayName(schema, el->name);
if (!schema_attr) {
return LDB_ERR_UNWILLING_TO_PERFORM;
}
old_el = ldb_msg_find_element(old_msg, el->name);
- switch (el->flags & LDB_FLAG_MOD_MASK) {
+ switch (mod_type) {
case LDB_FLAG_MOD_REPLACE:
- ret = replmd_modify_la_replace(module, schema, msg, el, old_el, schema_attr, seq_num, t, &old_guid, parent);
+ ret = replmd_modify_la_replace(module, replmd_private,
+ schema, msg, el, old_el,
+ schema_attr, seq_num, t,
+ old_msg->dn,
+ parent);
break;
case LDB_FLAG_MOD_DELETE:
- ret = replmd_modify_la_delete(module, schema, msg, el, old_el, schema_attr, seq_num, t, &old_guid, parent);
+ ret = replmd_modify_la_delete(module, replmd_private,
+ schema, msg, el, old_el,
+ schema_attr, seq_num, t,
+ old_msg->dn,
+ parent);
break;
case LDB_FLAG_MOD_ADD:
- ret = replmd_modify_la_add(module, schema, msg, el, old_el, schema_attr, seq_num, t, &old_guid, parent);
+ ret = replmd_modify_la_add(module, replmd_private,
+ schema, msg, el, old_el,
+ schema_attr, seq_num, t,
+ old_msg->dn,
+ parent);
break;
default:
ldb_asprintf_errstring(ldb,
ldb_asprintf_errstring(ldb,
"Attribute %s is single valued but more than one value has been supplied",
el->name);
- return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ /* Return codes as found on Windows 2012r2 */
+ if (mod_type == LDB_FLAG_MOD_REPLACE) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ } else {
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ }
} else {
el->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
}
-
-
if (ret != LDB_SUCCESS) {
return ret;
}
}
-
-static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
+static int send_rodc_referral(struct ldb_request *req,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn)
{
- struct ldb_context *ldb;
- struct replmd_replicated_request *ac;
- struct ldb_request *down_req;
- struct ldb_message *msg;
- time_t t = time(NULL);
- int ret;
- bool is_urgent = false, rodc = false;
- bool is_schema_nc = false;
- unsigned int functional_level;
+ char *referral = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct ldb_dn *fsmo_role_dn = NULL;
+ struct ldb_dn *role_owner_dn = NULL;
+ const char *domain = NULL;
+ WERROR werr;
+
+ lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ werr = dsdb_get_fsmo_role_info(req, ldb, DREPL_PDC_MASTER,
+ &fsmo_role_dn, &role_owner_dn);
+
+ if (W_ERROR_IS_OK(werr)) {
+ struct ldb_dn *server_dn = ldb_dn_copy(req, role_owner_dn);
+ if (server_dn != NULL) {
+ ldb_dn_remove_child_components(server_dn, 1);
+ domain = samdb_dn_to_dnshostname(ldb, req,
+ server_dn);
+ }
+ }
+
+ if (domain == NULL) {
+ domain = lpcfg_dnsdomain(lp_ctx);
+ }
+
+ referral = talloc_asprintf(req, "ldap://%s/%s",
+ domain,
+ ldb_dn_get_linearized(dn));
+ if (referral == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return ldb_module_send_referral(req, referral);
+}
+
+
+static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct replmd_replicated_request *ac;
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ time_t t = time(NULL);
+ int ret;
+ bool is_urgent = false, rodc = false;
+ bool is_schema_nc = false;
+ unsigned int functional_level;
const struct ldb_message_element *guid_el = NULL;
struct ldb_control *sd_propagation_control;
struct replmd_private *replmd_private =
msg, &ac->seq_num, t, is_schema_nc,
&is_urgent, &rodc);
if (rodc && (ret == LDB_ERR_REFERRAL)) {
- struct loadparm_context *lp_ctx;
- char *referral;
-
- lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
- struct loadparm_context);
-
- referral = talloc_asprintf(req,
- "ldap://%s/%s",
- lpcfg_dnsdomain(lp_ctx),
- ldb_dn_get_linearized(msg->dn));
- ret = ldb_module_send_referral(req, referral);
+ ret = send_rodc_referral(req, ldb, msg->dn);
talloc_free(ac);
return ret;
+
}
if (ret != LDB_SUCCESS) {
return ret;
}
- ret = replmd_modify_handle_linked_attribs(module, msg, ac->seq_num, t, req);
+ ret = replmd_modify_handle_linked_attribs(module, replmd_private,
+ msg, ac->seq_num, t, req);
if (ret != LDB_SUCCESS) {
talloc_free(ac);
return ret;
msg, &ac->seq_num, t,
is_schema_nc, &is_urgent, &rodc);
if (rodc && (ret == LDB_ERR_REFERRAL)) {
- struct ldb_dn *olddn = ac->req->op.rename.olddn;
- struct loadparm_context *lp_ctx;
- char *referral;
-
- lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
- struct loadparm_context);
-
- referral = talloc_asprintf(req,
- "ldap://%s/%s",
- lpcfg_dnsdomain(lp_ctx),
- ldb_dn_get_linearized(olddn));
- ret = ldb_module_send_referral(req, referral);
+ ret = send_rodc_referral(req, ldb, ac->req->op.rename.olddn);
talloc_free(ares);
return ldb_module_done(req, NULL, NULL, ret);
}
*/
static int replmd_delete_remove_link(struct ldb_module *module,
const struct dsdb_schema *schema,
+ struct replmd_private *replmd_private,
struct ldb_dn *dn,
+ struct GUID *guid,
struct ldb_message_element *el,
const struct dsdb_attribute *sa,
struct ldb_request *parent)
for (i=0; i<el->num_values; i++) {
struct dsdb_dn *dsdb_dn;
- NTSTATUS status;
int ret;
- struct GUID guid2;
struct ldb_message *msg;
const struct dsdb_attribute *target_attr;
struct ldb_message_element *el2;
+ const char *dn_str;
struct ldb_val dn_val;
uint32_t dsdb_flags = 0;
+ const char *attrs[] = { NULL, NULL };
+ struct ldb_result *link_res;
+ struct ldb_message *link_msg;
+ struct ldb_message_element *link_el;
+ struct parsed_dn *link_dns;
+ struct parsed_dn *p = NULL, *unused = NULL;
if (dsdb_dn_is_deleted_val(&el->values[i])) {
continue;
return LDB_ERR_OPERATIONS_ERROR;
}
- status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &guid2, "GUID");
- if (!NT_STATUS_IS_OK(status)) {
- talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
/* remove the link */
msg = ldb_msg_new(tmp_ctx);
if (!msg) {
if (target_attr == NULL) {
continue;
}
+ attrs[0] = target_attr->lDAPDisplayName;
- ret = ldb_msg_add_empty(msg, target_attr->lDAPDisplayName, LDB_FLAG_MOD_DELETE, &el2);
+ ret = ldb_msg_add_empty(msg, target_attr->lDAPDisplayName,
+ LDB_FLAG_MOD_DELETE, &el2);
if (ret != LDB_SUCCESS) {
ldb_module_oom(module);
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
- dn_val = data_blob_string_const(ldb_dn_get_linearized(dn));
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &link_res,
+ msg->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ parent);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ link_msg = link_res->msgs[0];
+ link_el = ldb_msg_find_element(link_msg,
+ target_attr->lDAPDisplayName);
+ if (link_el == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ /*
+ * This call 'upgrades' the links in link_dns, but we
+ * do not commit the result back into the database, so
+ * this is safe to call in FL2000 or on databases that
+ * have been run at that level in the past.
+ */
+ ret = get_parsed_dns_trusted(module, replmd_private, tmp_ctx,
+ link_el, &link_dns,
+ target_attr->syntax->ldap_oid, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = parsed_dn_find(ldb, link_dns, link_el->num_values,
+ guid, dn,
+ data_blob_null, 0,
+ &p, &unused,
+ target_attr->syntax->ldap_oid, false);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (p == NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Failed to find forward link on %s "
+ "as %s to remove backlink %s on %s",
+ ldb_dn_get_linearized(msg->dn),
+ target_attr->lDAPDisplayName,
+ sa->lDAPDisplayName,
+ ldb_dn_get_linearized(dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+
+ /* This needs to get the Binary DN, by first searching */
+ dn_str = dsdb_dn_get_linearized(tmp_ctx,
+ p->dsdb_dn);
+
+ dn_val = data_blob_string_const(dn_str);
el2->values = &dn_val;
el2->num_values = 1;
};
unsigned int i, el_count = 0;
uint32_t dsdb_flags = 0;
+ struct replmd_private *replmd_private;
enum deletion_state deletion_state, next_deletion_state;
if (ldb_dn_is_special(req->op.del.dn)) {
}
}
- if (deletion_state == OBJECT_NOT_DELETED) {
- /* get the objects GUID from the search we just did */
- guid = samdb_result_guid(old_msg, "objectGUID");
+ /* get the objects GUID from the search we just did */
+ guid = samdb_result_guid(old_msg, "objectGUID");
+ if (deletion_state == OBJECT_NOT_DELETED) {
/* Add a formatted child */
retb = ldb_dn_add_child_fmt(new_dn, "%s=%s\\0ADEL:%s",
rdn_name,
msg->elements[el_count++].flags = LDB_FLAG_MOD_REPLACE;
}
+ replmd_private = talloc_get_type(ldb_module_get_private(module),
+ struct replmd_private);
/* work out which of the old attributes we will be removing */
for (i=0; i<old_msg->num_elements; i++) {
const struct dsdb_attribute *sa;
/* don't remove the rDN */
continue;
}
- if (sa->linkID && (sa->linkID & 1)) {
+ if (sa->linkID & 1) {
/*
we have a backlink in this object
that needs to be removed. We're not
modify to delete the corresponding
forward link
*/
- ret = replmd_delete_remove_link(module, schema, old_dn, el, sa, req);
+ ret = replmd_delete_remove_link(module, schema,
+ replmd_private,
+ old_dn, &guid,
+ el, sa, req);
if (ret != LDB_SUCCESS) {
const char *old_dn_str
= ldb_dn_get_linearized(old_dn);
directly
*/
continue;
- }
- if (!sa->linkID) {
+ } else if (sa->linkID == 0) {
if (ldb_attr_in_list(preserved_attrs, el->name)) {
continue;
}
goto failed;
}
- ret = dsdb_module_modify(ar->module, msg, DSDB_FLAG_OWN_MODULE, req);
+ /*
+ * We have to mark this as a replicated update otherwise
+ * schema_data may reject a rename in the schema partition
+ */
+
+ ret = dsdb_module_modify(ar->module, msg,
+ DSDB_FLAG_OWN_MODULE|DSDB_FLAG_REPLICATED_UPDATE,
+ req);
if (ret != LDB_SUCCESS) {
- DEBUG(0,(__location__ ": Failed to modify rDN/name of conflict DN '%s' - %s",
+ DEBUG(0,(__location__ ": Failed to modify rDN/name of DN being DRS renamed '%s' - %s",
ldb_dn_get_linearized(dn),
ldb_errstring(ldb_module_get_ctx(ar->module))));
return ret;
failed:
talloc_free(msg);
- DEBUG(0,(__location__ ": Failed to setup modify rDN/name of conflict DN '%s'",
+ DEBUG(0,(__location__ ": Failed to setup modify rDN/name of DN being DRS renamed '%s'",
ldb_dn_get_linearized(dn)));
return LDB_ERR_OPERATIONS_ERROR;
}
* We are on an RODC, or were a GC for this
* partition, so we have to fail this until
* someone who owns the partition sorts it
- * out
+ * out
*/
- ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
"Conflict adding object '%s' from incoming replication as we are read only for the partition. \n"
" - We must fail the operation until a master for this partition resolves the conflict",
ldb_dn_get_linearized(conflict_dn));
rdn_val = ldb_dn_get_rdn_val(msg->dn);
ret = replmd_update_rpmd_rdn_attr(ldb, msg, rdn_val, NULL,
- md, ar, now, is_schema_nc);
+ md, ar, now, is_schema_nc,
+ false);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb, "%s: error during DRS repl ADD: %s", __func__, ldb_errstring(ldb));
return replmd_replicated_request_error(ar, ret);
ldb_dn_get_linearized(parent_msg->dn));
return ldb_module_done(ar->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
}
-
+
ret = dsdb_wellknown_dn(ldb_module_get_ctx(ar->module), msg,
nc_root,
DS_GUID_LOSTANDFOUND_CONTAINER,
ar->req);
LDB_REQ_SET_LOCATION(search_req);
- ret = dsdb_request_add_controls(search_req,
+ ret = dsdb_request_add_controls(search_req,
DSDB_SEARCH_SHOW_RECYCLED|
DSDB_SEARCH_SHOW_DELETED|
DSDB_SEARCH_SHOW_EXTENDED_DN);
if (renamed) {
ret = replmd_update_rpmd_rdn_attr(ldb, msg, new_rdn, old_rdn,
- &nmd, ar, now, is_schema_nc);
+ &nmd, ar, now, is_schema_nc,
+ false);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb, "%s: error during DRS repl merge: %s", __func__, ldb_errstring(ldb));
return replmd_replicated_request_error(ar, ret);
return LDB_SUCCESS;
}
+/**
+ * Stores the linked attributes received in the replication chunk - these get
+ * applied at the end of the transaction. We also check that each linked
+ * attribute is valid, i.e. source and target objects are known.
+ */
+static int replmd_store_linked_attributes(struct replmd_replicated_request *ar)
+{
+ int ret = LDB_SUCCESS;
+ uint32_t i;
+ struct ldb_module *module = ar->module;
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(module), struct replmd_private);
+ struct ldb_context *ldb;
+
+ ldb = ldb_module_get_ctx(module);
+
+ DEBUG(4,("linked_attributes_count=%u\n", ar->objs->linked_attributes_count));
+
+ /* save away the linked attributes for the end of the transaction */
+ for (i = 0; i < ar->objs->linked_attributes_count; i++) {
+ struct la_entry *la_entry;
+
+ if (replmd_private->la_ctx == NULL) {
+ replmd_private->la_ctx = talloc_new(replmd_private);
+ }
+ la_entry = talloc(replmd_private->la_ctx, struct la_entry);
+ if (la_entry == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ la_entry->la = talloc(la_entry, struct drsuapi_DsReplicaLinkedAttribute);
+ if (la_entry->la == NULL) {
+ talloc_free(la_entry);
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *la_entry->la = ar->objs->linked_attributes[i];
+ la_entry->dsdb_repl_flags = ar->objs->dsdb_repl_flags;
+
+ /* we need to steal the non-scalars so they stay
+ around until the end of the transaction */
+ talloc_steal(la_entry->la, la_entry->la->identifier);
+ talloc_steal(la_entry->la, la_entry->la->value.blob);
+
+ ret = replmd_verify_linked_attribute(ar, la_entry);
+
+ if (ret != LDB_SUCCESS) {
+ break;
+ }
+
+ DLIST_ADD(replmd_private->la_list, la_entry);
+ }
+
+ return ret;
+}
+
static int replmd_replicated_uptodate_vector(struct replmd_replicated_request *ar);
static int replmd_replicated_apply_next(struct replmd_replicated_request *ar)
struct GUID_txt_buf guid_str_buf;
if (ar->index_current >= ar->objs->num_objects) {
- /* done with it, go to next stage */
+
+ /*
+ * Now that we've applied all the objects, check the new linked
+ * attributes and store them (we apply them in .prepare_commit)
+ */
+ ret = replmd_store_linked_attributes(ar);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* done applying objects, move on to the next stage */
return replmd_replicated_uptodate_vector(ar);
}
struct replmd_replicated_request *ar;
struct ldb_control **ctrls;
int ret;
- uint32_t i;
- struct replmd_private *replmd_private =
- talloc_get_type(ldb_module_get_private(module), struct replmd_private);
ldb = ldb_module_get_ctx(module);
ar->controls = req->controls;
req->controls = ctrls;
- DEBUG(4,("linked_attributes_count=%u\n", objs->linked_attributes_count));
+ return replmd_replicated_apply_next(ar);
+}
- /* save away the linked attributes for the end of the
- transaction */
- for (i=0; i<ar->objs->linked_attributes_count; i++) {
- struct la_entry *la_entry;
+/**
+ * Returns True if the source and target DNs both have the same naming context,
+ * i.e. they're both in the same partition.
+ */
+static bool replmd_objects_have_same_nc(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *source_dn,
+ struct ldb_dn *target_dn)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *source_nc;
+ struct ldb_dn *target_nc;
+ int ret;
+ bool same_nc = true;
- if (replmd_private->la_ctx == NULL) {
- replmd_private->la_ctx = talloc_new(replmd_private);
- }
- la_entry = talloc(replmd_private->la_ctx, struct la_entry);
- if (la_entry == NULL) {
- ldb_oom(ldb);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- la_entry->la = talloc(la_entry, struct drsuapi_DsReplicaLinkedAttribute);
- if (la_entry->la == NULL) {
- talloc_free(la_entry);
- ldb_oom(ldb);
- return LDB_ERR_OPERATIONS_ERROR;
+ tmp_ctx = talloc_new(mem_ctx);
+
+ ret = dsdb_find_nc_root(ldb, tmp_ctx, source_dn, &source_nc);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Failed to find base DN for source %s\n",
+ ldb_dn_get_linearized(source_dn));
+ talloc_free(tmp_ctx);
+ return true;
+ }
+
+ ret = dsdb_find_nc_root(ldb, tmp_ctx, target_dn, &target_nc);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Failed to find base DN for target %s\n",
+ ldb_dn_get_linearized(target_dn));
+ talloc_free(tmp_ctx);
+ return true;
+ }
+
+ same_nc = (ldb_dn_compare(source_nc, target_nc) == 0);
+
+ talloc_free(tmp_ctx);
+
+ return same_nc;
+}
+
+/**
+ * Checks that the target object for a linked attribute exists.
+ * @param guid returns the target object's GUID (is returned)if it exists)
+ * @param ignore_link set to true if the linked attribute should be ignored
+ * (i.e. the target doesn't exist, but that it's OK to skip the link)
+ */
+static int replmd_check_target_exists(struct ldb_module *module,
+ struct dsdb_dn *dsdb_dn,
+ struct la_entry *la_entry,
+ struct ldb_dn *source_dn,
+ struct GUID *guid,
+ bool *ignore_link)
+{
+ struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_result *target_res;
+ TALLOC_CTX *tmp_ctx = talloc_new(la_entry);
+ const char *attrs[] = { "isDeleted", "isRecycled", NULL };
+ NTSTATUS ntstatus;
+ int ret;
+ enum deletion_state target_deletion_state = OBJECT_REMOVED;
+ bool active = (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE) ? true : false;
+
+ *ignore_link = false;
+ ntstatus = dsdb_get_extended_dn_guid(dsdb_dn->dn, guid, "GUID");
+
+ if (!NT_STATUS_IS_OK(ntstatus) && !active) {
+
+ /*
+ * This strange behaviour (allowing a NULL/missing
+ * GUID) originally comes from:
+ *
+ * commit e3054ce0fe0f8f62d2f5b2a77893e7a1479128bd
+ * Author: Andrew Tridgell <tridge@samba.org>
+ * Date: Mon Dec 21 21:21:55 2009 +1100
+ *
+ * s4-drs: cope better with NULL GUIDS from DRS
+ *
+ * It is valid to get a NULL GUID over DRS for a deleted forward link. We
+ * need to match by DN if possible when seeing if we should update an
+ * existing link.
+ *
+ * Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org>
+ */
+ ret = dsdb_module_search_dn(module, tmp_ctx, &target_res,
+ dsdb_dn->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL);
+ } else if (!NT_STATUS_IS_OK(ntstatus)) {
+ ldb_asprintf_errstring(ldb, "Failed to find GUID in linked attribute 0x%x blob for %s from %s",
+ la->attid,
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_dn_get_linearized(source_dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ } else {
+ ret = dsdb_module_search(module, tmp_ctx, &target_res,
+ NULL, LDB_SCOPE_SUBTREE,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL,
+ "objectGUID=%s",
+ GUID_string(tmp_ctx, guid));
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to re-resolve GUID %s: %s\n",
+ GUID_string(tmp_ctx, guid),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (target_res->count == 0) {
+
+ /*
+ * we may not be able to resolve link targets properly when
+ * dealing with subsets of objects, e.g. the source is a
+ * critical object and the target isn't
+ *
+ * TODO:
+ * When we implement Trusted Domains we need to consider
+ * whether they get treated as an incomplete replica here or not
+ */
+ if (la_entry->dsdb_repl_flags & DSDB_REPL_FLAG_OBJECT_SUBSET) {
+
+ /*
+ * We don't increase the highwater-mark in the object
+ * subset cases, so subsequent replications should
+ * resolve any missing links
+ */
+ DEBUG(2,(__location__
+ ": Failed to find target %s linked from %s\n",
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_dn_get_linearized(source_dn)));
+ *ignore_link = true;
+
+ } else if (replmd_objects_have_same_nc(ldb, tmp_ctx, source_dn,
+ dsdb_dn->dn)) {
+ ldb_asprintf_errstring(ldb, "Unknown target %s GUID %s linked from %s\n",
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ GUID_string(tmp_ctx, guid),
+ ldb_dn_get_linearized(source_dn));
+ ret = LDB_ERR_NO_SUCH_OBJECT;
+ } else {
+
+ /*
+ * The target of the cross-partition link is missing.
+ * Continue and try to at least add the forward-link.
+ * This isn't great, but if we can add a partial link
+ * then it's better than nothing.
+ */
+ DEBUG(2,("Failed to resolve cross-partition link between %s and %s\n",
+ ldb_dn_get_linearized(source_dn),
+ ldb_dn_get_linearized(dsdb_dn->dn)));
}
- *la_entry->la = ar->objs->linked_attributes[i];
+ } else if (target_res->count != 1) {
+ ldb_asprintf_errstring(ldb, "More than one object found matching objectGUID %s\n",
+ GUID_string(tmp_ctx, guid));
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ } else {
+ struct ldb_message *target_msg = target_res->msgs[0];
- /* we need to steal the non-scalars so they stay
- around until the end of the transaction */
- talloc_steal(la_entry->la, la_entry->la->identifier);
- talloc_steal(la_entry->la, la_entry->la->value.blob);
+ dsdb_dn->dn = talloc_steal(dsdb_dn, target_msg->dn);
- DLIST_ADD(replmd_private->la_list, la_entry);
+ /* Get the object's state (i.e. Not Deleted, Tombstone, etc) */
+ replmd_deletion_state(module, target_msg,
+ &target_deletion_state, NULL);
+
+ /*
+ * Check for deleted objects as per MS-DRSR 4.1.10.6.14
+ * ProcessLinkValue(). Link updates should not be sent for
+ * recycled and tombstone objects (deleting the links should
+ * happen when we delete the object). This probably means our
+ * copy of the target object isn't up to date.
+ */
+ if (target_deletion_state >= OBJECT_RECYCLED) {
+
+ if (la_entry->dsdb_repl_flags & DSDB_REPL_FLAG_TARGETS_UPTODATE) {
+
+ /*
+ * target should already be uptodate so there's no
+ * point retrying - it's probably just bad timing
+ */
+ *ignore_link = true;
+ DEBUG(0, ("%s is deleted but up to date. "
+ "Ignoring link from %s\n",
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_dn_get_linearized(source_dn)));
+
+ } else {
+ ldb_asprintf_errstring(ldb,
+ "Deleted target %s GUID %s linked from %s",
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ GUID_string(tmp_ctx, guid),
+ ldb_dn_get_linearized(source_dn));
+ ret = LDB_ERR_NO_SUCH_OBJECT;
+ }
+ }
}
- return replmd_replicated_apply_next(ar);
+ talloc_free(tmp_ctx);
+ return ret;
}
-/*
- process one linked attribute structure
+/**
+ * Extracts the key details about the source/target object for a
+ * linked-attribute entry.
+ * This returns the following details:
+ * @param ret_attr the schema details for the linked attribute
+ * @param source_msg the search result for the source object
+ * @param target_dsdb_dn the unpacked DN info for the target object
*/
-static int replmd_process_linked_attribute(struct ldb_module *module,
+static int replmd_extract_la_entry_details(struct ldb_module *module,
struct la_entry *la_entry,
- struct ldb_request *parent)
+ TALLOC_CTX *mem_ctx,
+ const struct dsdb_attribute **ret_attr,
+ struct ldb_message **source_msg,
+ struct dsdb_dn **target_dsdb_dn)
{
struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la;
struct ldb_context *ldb = ldb_module_get_ctx(module);
- struct ldb_message *msg;
- struct ldb_message *target_msg = NULL;
- TALLOC_CTX *tmp_ctx = talloc_new(la_entry);
- const struct dsdb_schema *schema = dsdb_get_schema(ldb, tmp_ctx);
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb, mem_ctx);
int ret;
const struct dsdb_attribute *attr;
- struct dsdb_dn *dsdb_dn;
- uint64_t seq_num = 0;
- struct ldb_message_element *old_el;
WERROR status;
- time_t t = time(NULL);
struct ldb_result *res;
- struct ldb_result *target_res;
const char *attrs[4];
- const char *attrs2[] = { "isDeleted", "isRecycled", NULL };
- struct parsed_dn *pdn_list, *pdn;
- struct GUID guid = GUID_zero();
- NTSTATUS ntstatus;
- bool active = (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)?true:false;
- const struct GUID *our_invocation_id;
-
- enum deletion_state deletion_state = OBJECT_NOT_DELETED;
- enum deletion_state target_deletion_state = OBJECT_NOT_DELETED;
/*
linked_attributes[0]:
la->attid,
GUID_buf_string(&la->identifier->guid,
&guid_str));
- talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
attrs[2] = "isRecycled";
attrs[3] = NULL;
- /* get the existing message from the db for the object with
- this GUID, returning attribute being modified. We will then
- use this msg as the basis for a modify call */
- ret = dsdb_module_search(module, tmp_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs,
+ /*
+ * get the existing message from the db for the object with
+ * this GUID, returning attribute being modified. We will then
+ * use this msg as the basis for a modify call
+ */
+ ret = dsdb_module_search(module, mem_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs,
DSDB_FLAG_NEXT_MODULE |
DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
DSDB_SEARCH_SHOW_RECYCLED |
DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
DSDB_SEARCH_REVEAL_INTERNALS,
- parent,
- "objectGUID=%s", GUID_string(tmp_ctx, &la->identifier->guid));
+ NULL,
+ "objectGUID=%s", GUID_string(mem_ctx, &la->identifier->guid));
if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
return ret;
}
if (res->count != 1) {
ldb_asprintf_errstring(ldb, "DRS linked attribute for GUID %s - DN not found",
- GUID_string(tmp_ctx, &la->identifier->guid));
- talloc_free(tmp_ctx);
+ GUID_string(mem_ctx, &la->identifier->guid));
return LDB_ERR_NO_SUCH_OBJECT;
}
- msg = res->msgs[0];
+
+ *source_msg = res->msgs[0];
+
+ /* the value blob for the attribute holds the target object DN */
+ status = dsdb_dn_la_from_blob(ldb, attr, schema, mem_ctx, la->value.blob, target_dsdb_dn);
+ if (!W_ERROR_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb, "Failed to parsed linked attribute blob for %s on %s - %s\n",
+ attr->lDAPDisplayName,
+ ldb_dn_get_linearized(res->msgs[0]->dn),
+ win_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *ret_attr = attr;
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Verifies the source and target objects are known for a linked attribute
+ */
+static int replmd_verify_linked_attribute(struct replmd_replicated_request *ar,
+ struct la_entry *la)
+{
+ int ret = LDB_SUCCESS;
+ TALLOC_CTX *tmp_ctx = talloc_new(la);
+ struct ldb_module *module = ar->module;
+ struct ldb_message *src_msg;
+ const struct dsdb_attribute *attr;
+ struct dsdb_dn *tgt_dsdb_dn;
+ struct GUID guid = GUID_zero();
+ bool dummy;
+
+ ret = replmd_extract_la_entry_details(module, la, tmp_ctx, &attr,
+ &src_msg, &tgt_dsdb_dn);
/*
- * Check for deleted objects per MS-DRSR 4.1.10.6.13
+ * When we fail to find the source object, the error code we pass
+ * back here is really important. It flags back to the callers to
+ * retry this request with DRSUAPI_DRS_GET_ANC. This case should
+ * never happen if we're replicating from a Samba DC, but it is
+ * needed to talk to a Windows DC
+ */
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = replmd_replicated_request_werror(ar, WERR_DS_DRA_MISSING_PARENT);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = replmd_check_target_exists(module, tgt_dsdb_dn, la,
+ src_msg->dn, &guid, &dummy);
+
+ /*
+ * When we fail to find the target object, the error code we pass
+ * back here is really important. It flags back to the callers to
+ * retry this request with DRSUAPI_DRS_GET_TGT
+ */
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = replmd_replicated_request_werror(ar, WERR_DS_DRA_RECYCLED_TARGET);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ process one linked attribute structure
+ */
+static int replmd_process_linked_attribute(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ struct la_entry *la_entry,
+ struct ldb_request *parent)
+{
+ struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *msg;
+ TALLOC_CTX *tmp_ctx = talloc_new(la_entry);
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb, tmp_ctx);
+ int ret;
+ const struct dsdb_attribute *attr;
+ struct dsdb_dn *dsdb_dn;
+ uint64_t seq_num = 0;
+ struct ldb_message_element *old_el;
+ time_t t = time(NULL);
+ struct parsed_dn *pdn_list, *pdn, *next;
+ struct GUID guid = GUID_zero();
+ bool active = (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)?true:false;
+ bool ignore_link;
+ enum deletion_state deletion_state = OBJECT_NOT_DELETED;
+
+ /*
+ * get the attribute being modified, the search result for the source object,
+ * and the target object's DN details
+ */
+ ret = replmd_extract_la_entry_details(module, la_entry, tmp_ctx, &attr,
+ &msg, &dsdb_dn);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * Check for deleted objects per MS-DRSR 4.1.10.6.14
* ProcessLinkValue, because link updates are not applied to
* recycled and tombstone objects. We don't have to delete
* any existing link, that should have happened when the
* object deletion was replicated or initiated.
*/
-
replmd_deletion_state(module, msg, &deletion_state, NULL);
if (deletion_state >= OBJECT_RECYCLED) {
}
/* parse the existing links */
- ret = get_parsed_dns(module, tmp_ctx, old_el, &pdn_list, attr->syntax->ldap_oid, parent);
+ ret = get_parsed_dns_trusted(module, replmd_private, tmp_ctx, old_el, &pdn_list,
+ attr->syntax->ldap_oid, parent);
+
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- /* get our invocationId */
- our_invocation_id = samdb_ntds_invocation_id(ldb);
- if (!our_invocation_id) {
- ldb_debug_set(ldb, LDB_DEBUG_ERROR, __location__ ": unable to find invocationId\n");
- talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
+ ret = replmd_check_target_exists(module, dsdb_dn, la_entry, msg->dn,
+ &guid, &ignore_link);
- ret = replmd_check_upgrade_links(pdn_list, old_el->num_values, old_el, our_invocation_id);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- status = dsdb_dn_la_from_blob(ldb, attr, schema, tmp_ctx, la->value.blob, &dsdb_dn);
- if (!W_ERROR_IS_OK(status)) {
- ldb_asprintf_errstring(ldb, "Failed to parsed linked attribute blob for %s on %s - %s\n",
- old_el->name, ldb_dn_get_linearized(msg->dn), win_errstr(status));
- talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- ntstatus = dsdb_get_extended_dn_guid(dsdb_dn->dn, &guid, "GUID");
- if (!NT_STATUS_IS_OK(ntstatus) && !active) {
- /*
- * This strange behaviour (allowing a NULL/missing
- * GUID) originally comes from:
- *
- * commit e3054ce0fe0f8f62d2f5b2a77893e7a1479128bd
- * Author: Andrew Tridgell <tridge@samba.org>
- * Date: Mon Dec 21 21:21:55 2009 +1100
- *
- * s4-drs: cope better with NULL GUIDS from DRS
- *
- * It is valid to get a NULL GUID over DRS for a deleted forward link. We
- * need to match by DN if possible when seeing if we should update an
- * existing link.
- *
- * Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org>
- */
-
- ret = dsdb_module_search_dn(module, tmp_ctx, &target_res,
- dsdb_dn->dn, attrs2,
- DSDB_FLAG_NEXT_MODULE |
- DSDB_SEARCH_SHOW_RECYCLED |
- DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
- DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
- parent);
- } else if (!NT_STATUS_IS_OK(ntstatus)) {
- ldb_asprintf_errstring(ldb, "Failed to find GUID in linked attribute blob for %s on %s from %s",
- old_el->name,
- ldb_dn_get_linearized(dsdb_dn->dn),
- ldb_dn_get_linearized(msg->dn));
+ /*
+ * there are some cases where the target object doesn't exist, but it's
+ * OK to ignore the linked attribute
+ */
+ if (ignore_link) {
talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- } else {
- ret = dsdb_module_search(module, tmp_ctx, &target_res,
- NULL, LDB_SCOPE_SUBTREE,
- attrs2,
- DSDB_FLAG_NEXT_MODULE |
- DSDB_SEARCH_SHOW_RECYCLED |
- DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
- DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
- parent,
- "objectGUID=%s",
- GUID_string(tmp_ctx, &guid));
+ return ret;
}
+ /* see if this link already exists */
+ ret = parsed_dn_find(ldb, pdn_list, old_el->num_values,
+ &guid,
+ dsdb_dn->dn,
+ dsdb_dn->extra_part, 0,
+ &pdn, &next,
+ attr->syntax->ldap_oid,
+ true);
if (ret != LDB_SUCCESS) {
- ldb_asprintf_errstring(ldb_module_get_ctx(module), "Failed to re-resolve GUID %s: %s\n",
- GUID_string(tmp_ctx, &guid),
- ldb_errstring(ldb_module_get_ctx(module)));
talloc_free(tmp_ctx);
return ret;
}
- if (target_res->count == 0) {
- DEBUG(2,(__location__ ": WARNING: Failed to re-resolve GUID %s - using %s\n",
- GUID_string(tmp_ctx, &guid),
- ldb_dn_get_linearized(dsdb_dn->dn)));
- } else if (target_res->count != 1) {
- ldb_asprintf_errstring(ldb_module_get_ctx(module), "More than one object found matching objectGUID %s\n",
- GUID_string(tmp_ctx, &guid));
- talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- } else {
- target_msg = target_res->msgs[0];
- dsdb_dn->dn = talloc_steal(dsdb_dn, target_msg->dn);
- }
-
- /*
- * Check for deleted objects per MS-DRSR 4.1.10.6.13
- * ProcessLinkValue, because link updates are not applied to
- * recycled and tombstone objects. We don't have to delete
- * any existing link, that should have happened when the
- * object deletion was replicated or initiated.
- */
- replmd_deletion_state(module, target_msg,
- &target_deletion_state, NULL);
- if (target_deletion_state >= OBJECT_RECYCLED) {
- talloc_free(tmp_ctx);
- return LDB_SUCCESS;
- }
-
- /* see if this link already exists */
- pdn = parsed_dn_find(pdn_list, old_el->num_values, &guid, dsdb_dn->dn);
if (pdn != NULL) {
/* see if this update is newer than what we have already */
struct GUID invocation_id = GUID_zero();
if (!(rmd_flags & DSDB_RMD_FLAG_DELETED)) {
/* remove the existing backlink */
- ret = replmd_add_backlink(module, schema, &la->identifier->guid, &guid, false, attr, true);
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ msg->dn,
+ &guid, false, attr,
+ parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
if (active) {
/* add the new backlink */
- ret = replmd_add_backlink(module, schema, &la->identifier->guid, &guid, true, attr, true);
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ msg->dn,
+ &guid, true, attr,
+ parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
}
} else {
+ unsigned offset;
/* get a seq_num for this change */
ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &seq_num);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
+ /*
+ * We know where the new one needs to be, from the *next
+ * pointer into pdn_list.
+ */
+ if (next == NULL) {
+ offset = old_el->num_values;
+ } else {
+ if (next->dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(tmp_ctx, ldb, next,
+ attr->syntax->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ offset = next - pdn_list;
+ if (offset > old_el->num_values) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
old_el->values = talloc_realloc(msg->elements, old_el->values,
struct ldb_val, old_el->num_values+1);
ldb_module_oom(module);
return LDB_ERR_OPERATIONS_ERROR;
}
+
+ if (offset != old_el->num_values) {
+ memmove(&old_el->values[offset + 1], &old_el->values[offset],
+ (old_el->num_values - offset) * sizeof(old_el->values[0]));
+ }
+
old_el->num_values++;
- ret = replmd_build_la_val(tmp_ctx, &old_el->values[old_el->num_values-1], dsdb_dn,
+ ret = replmd_build_la_val(tmp_ctx, &old_el->values[offset], dsdb_dn,
&la->meta_data.originating_invocation_id,
la->meta_data.originating_usn, seq_num,
la->meta_data.originating_change_time,
}
if (active) {
- ret = replmd_add_backlink(module, schema, &la->identifier->guid, &guid,
- true, attr, true);
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ msg->dn,
+ &guid, true, attr,
+ parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
struct replmd_private *replmd_private =
talloc_get_type(ldb_module_get_private(module), struct replmd_private);
struct la_entry *la, *prev;
- struct la_backlink *bl;
int ret;
- /* walk the list backwards, to do the first entry first, as we
+ /*
+ * Walk the list of linked attributes from DRS replication.
+ *
+ * We walk backwards, to do the first entry first, as we
* added the entries with DLIST_ADD() which puts them at the
- * start of the list */
+ * start of the list
+ */
for (la = DLIST_TAIL(replmd_private->la_list); la; la=prev) {
prev = DLIST_PREV(la);
DLIST_REMOVE(replmd_private->la_list, la);
- ret = replmd_process_linked_attribute(module, la, NULL);
- if (ret != LDB_SUCCESS) {
- replmd_txn_cleanup(replmd_private);
- return ret;
- }
- }
-
- /* process our backlink list, creating and deleting backlinks
- as necessary */
- for (bl=replmd_private->la_backlinks; bl; bl=bl->next) {
- ret = replmd_process_backlink(module, bl, NULL);
+ ret = replmd_process_linked_attribute(module, replmd_private,
+ la, NULL);
if (ret != LDB_SUCCESS) {
replmd_txn_cleanup(replmd_private);
return ret;