struct dsdb_extended_replicated_objects *objs;
struct ldb_message *search_msg;
+ struct GUID local_parent_guid;
uint64_t seq_num;
bool is_urgent;
return 0;
}
- /*
- * the rdn attribute should be at the end!
- * so we need to return a value greater than zero
- * which means m1 is greater than m2
- */
- if (attid_1 == *rdn_attid) {
- return 1;
- }
-
- /*
- * the rdn attribute should be at the end!
- * so we need to return a value less than zero
- * which means m2 is greater than m1
- */
- if (attid_2 == *rdn_attid) {
- return -1;
- }
-
/*
* See above regarding this being an unsigned comparison.
* Otherwise when the high bit is set on non-standard
static int replmd_replPropertyMetaDataCtr1_verify(struct ldb_context *ldb,
struct replPropertyMetaDataCtr1 *ctr1,
- const struct dsdb_attribute *rdn_sa,
struct ldb_dn *dn)
{
if (ctr1->count == 0) {
static int replmd_replPropertyMetaDataCtr1_sort_and_verify(struct ldb_context *ldb,
struct replPropertyMetaDataCtr1 *ctr1,
- const struct dsdb_schema *schema,
struct ldb_dn *dn)
{
- const char *rdn_name;
- const struct dsdb_attribute *rdn_sa;
-
- rdn_name = ldb_dn_get_rdn_name(dn);
- if (!rdn_name) {
- ldb_debug_set(ldb, LDB_DEBUG_FATAL,
- __location__ ": No rDN for %s?\n",
- ldb_dn_get_linearized(dn));
- return LDB_ERR_INVALID_DN_SYNTAX;
- }
-
- rdn_sa = dsdb_attribute_by_lDAPDisplayName(schema, rdn_name);
- if (rdn_sa == NULL) {
- ldb_debug_set(ldb, LDB_DEBUG_FATAL,
- __location__ ": No sa found for rDN %s for %s\n",
- rdn_name, ldb_dn_get_linearized(dn));
- return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
- }
-
- DEBUG(6,("Sorting rpmd with attid exception %u rDN=%s DN=%s\n",
- rdn_sa->attributeID_id, rdn_name, ldb_dn_get_linearized(dn)));
-
- LDB_TYPESAFE_QSORT(ctr1->array, ctr1->count, &rdn_sa->attributeID_id,
+ /* Note this is O(n^2) for the almost-sorted case, which this is */
+ LDB_TYPESAFE_QSORT(ctr1->array, ctr1->count, NULL,
replmd_replPropertyMetaData1_attid_sort);
- return replmd_replPropertyMetaDataCtr1_verify(ldb, ctr1, rdn_sa, dn);
+ return replmd_replPropertyMetaDataCtr1_verify(ldb, ctr1, dn);
}
static int replmd_ldb_message_element_attid_sort(const struct ldb_message_element *e1,
nmd.ctr.ctr1.count = ni;
/*
- * sort meta data array, and move the rdn attribute entry to the end
+ * sort meta data array
*/
- ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, ac->schema, msg->dn);
+ ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, msg->dn);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb, "%s: error during direct ADD: %s", __func__, ldb_errstring(ldb));
talloc_free(ac);
return LDB_SUCCESS;
}
+/*
+ * Bump the replPropertyMetaData version on an attribute, and if it
+ * has changed (or forced by leaving rdn_old NULL), update the value
+ * in the entry.
+ *
+ * This is important, as calling a modify operation may not change the
+ * version number if the values appear unchanged, but a rename between
+ * parents bumps this value.
+ *
+ */
+static int replmd_update_rpmd_rdn_attr(struct ldb_context *ldb,
+ struct ldb_message *msg,
+ const struct ldb_val *rdn_new,
+ const struct ldb_val *rdn_old,
+ struct replPropertyMetaDataBlob *omd,
+ struct replmd_replicated_request *ar,
+ NTTIME now,
+ bool is_schema_nc)
+{
+ struct ldb_message_element new_el = {
+ .flags = LDB_FLAG_MOD_REPLACE,
+ .name = ldb_dn_get_rdn_name(msg->dn),
+ .num_values = 1,
+ .values = discard_const_p(struct ldb_val, rdn_new)
+ };
+ struct ldb_message_element old_el = {
+ .flags = LDB_FLAG_MOD_REPLACE,
+ .name = ldb_dn_get_rdn_name(msg->dn),
+ .num_values = rdn_old ? 1 : 0,
+ .values = discard_const_p(struct ldb_val, rdn_old)
+ };
+
+ if (ldb_msg_element_equal_ordered(&new_el, &old_el) == false) {
+ int ret = ldb_msg_add(msg, &new_el, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ldb_oom(ldb);
+ }
+ }
+
+ 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);
+
+}
+
static uint64_t find_max_local_usn(struct replPropertyMetaDataBlob omd)
{
uint32_t count = omd.ctr.ctr1.count;
const struct GUID *our_invocation_id;
int ret;
const char * const *attrs = NULL;
- const char * const attrs1[] = { "replPropertyMetaData", "*", NULL };
const char * const attrs2[] = { "uSNChanged", "objectClass", "instanceType", NULL };
struct ldb_result *res;
struct ldb_context *ldb;
enum urgent_situation situation;
bool rmd_is_provided;
bool rmd_is_just_resorted = false;
-
+ const char *not_rename_attrs[4 + msg->num_elements];
+
if (rename_attrs) {
attrs = rename_attrs;
} else {
- attrs = attrs1;
+ for (i = 0; i < msg->num_elements; i++) {
+ not_rename_attrs[i] = msg->elements[i].name;
+ }
+ not_rename_attrs[i] = "replPropertyMetaData";
+ not_rename_attrs[i+1] = "objectClass";
+ not_rename_attrs[i+2] = "instanceType";
+ not_rename_attrs[i+3] = NULL;
+ attrs = not_rename_attrs;
}
ldb = ldb_module_get_ctx(module);
return LDB_ERR_OPERATIONS_ERROR;
}
- ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &omd.ctr.ctr1, schema, msg->dn);
+ ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &omd.ctr.ctr1, msg->dn);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb, "%s: %s", __func__, ldb_errstring(ldb));
return ret;
struct parsed_dn {
struct dsdb_dn *dsdb_dn;
- struct GUID *guid;
+ 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);
+ 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,
}
return NULL;
}
- BINARY_ARRAY_SEARCH(pdn, count, guid, guid, GUID_compare, ret);
+ BINARY_ARRAY_SEARCH(pdn, count, guid, guid, GUID_compare_struct, ret);
return ret;
}
dn = p->dsdb_dn->dn;
- p->guid = talloc(*pdn, struct GUID);
- if (p->guid == NULL) {
- ldb_module_oom(module);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- status = dsdb_get_extended_dn_guid(dn, p->guid, "GUID");
+ status = dsdb_get_extended_dn_guid(dn, &p->guid, "GUID");
if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
/* we got a DN without a GUID - go find the GUID */
- int ret = dsdb_module_guid_by_dn(module, dn, p->guid, parent);
+ int ret = dsdb_module_guid_by_dn(module, dn, &p->guid, parent);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb, "Unable to find GUID for DN %s\n",
ldb_dn_get_linearized(dn));
}
return ret;
}
- ret = dsdb_set_extended_dn_guid(dn, p->guid, "GUID");
+ ret = dsdb_set_extended_dn_guid(dn, &p->guid, "GUID");
if (ret != LDB_SUCCESS) {
return ret;
}
/* 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);
+ 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);
uint32_t rmd_flags = dsdb_dn_rmd_flags(p->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_string(tmp_ctx, p->guid));
+ el->name, GUID_buf_string(&p->guid, &guid_str));
talloc_free(tmp_ctx);
/* error codes for 'member' need to be
special cased */
}
}
- ret = replmd_add_backlink(module, schema, msg_guid, dns[i].guid, true, schema_attr, true);
+ ret = replmd_add_backlink(module, schema, msg_guid, &dns[i].guid, true, schema_attr, true);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
struct parsed_dn *p2;
uint32_t rmd_flags;
- p2 = parsed_dn_find(old_dns, old_el->num_values, p->guid, NULL);
+ p2 = parsed_dn_find(old_dns, old_el->num_values, &p->guid, NULL);
if (!p2) {
+ struct GUID_txt_buf buf;
ldb_asprintf_errstring(ldb, "Attribute %s doesn't exist for target GUID %s",
- el->name, GUID_string(tmp_ctx, p->guid));
+ el->name, GUID_buf_string(&p->guid, &buf));
if (ldb_attr_cmp(el->name, "member") == 0) {
return LDB_ERR_UNWILLING_TO_PERFORM;
} else {
}
rmd_flags = dsdb_dn_rmd_flags(p2->dsdb_dn->dn);
if (rmd_flags & DSDB_RMD_FLAG_DELETED) {
+ struct GUID_txt_buf buf;
ldb_asprintf_errstring(ldb, "Attribute %s already deleted for target GUID %s",
- el->name, GUID_string(tmp_ctx, p->guid));
+ el->name, GUID_buf_string(&p->guid, &buf));
if (ldb_attr_cmp(el->name, "member") == 0) {
return LDB_ERR_UNWILLING_TO_PERFORM;
} else {
struct parsed_dn *p = &old_dns[i];
uint32_t rmd_flags;
- if (el->num_values && parsed_dn_find(dns, el->num_values, p->guid, NULL) == NULL) {
+ if (el->num_values && parsed_dn_find(dns, el->num_values, &p->guid, NULL) == NULL) {
continue;
}
return ret;
}
- ret = replmd_add_backlink(module, schema, msg_guid, old_dns[i].guid, false, schema_attr, true);
+ 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 (rmd_flags & DSDB_RMD_FLAG_DELETED) continue;
- ret = replmd_add_backlink(module, schema, msg_guid, old_dns[i].guid, false, schema_attr, false);
+ 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;
}
- p = parsed_dn_find(dns, el->num_values, old_p->guid, NULL);
+ 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 (old_dns &&
(old_p = parsed_dn_find(old_dns,
- old_num_values, p->guid, NULL)) != NULL) {
+ 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,
num_new_values++;
}
- ret = replmd_add_backlink(module, schema, msg_guid, dns[i].guid, true, schema_attr, false);
+ 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;
{
int ret = LDB_ERR_OTHER;
/* TODO: do some error mapping */
+
+ /* Let the caller know the full WERROR */
+ ar->objs->error = status;
+
return ret;
}
new_m->originating_change_time);
}
+static bool replmd_replPropertyMetaData1_new_should_be_taken(uint32_t dsdb_repl_flags,
+ struct replPropertyMetaData1 *cur_m,
+ struct replPropertyMetaData1 *new_m)
+{
+ bool cmp;
+
+ /*
+ * If the new replPropertyMetaData entry for this attribute is
+ * not provided (this happens in the case where we look for
+ * ATTID_name, but the name was not changed), then the local
+ * state is clearly still current, as the remote
+ * server didn't send it due to being older the high watermark
+ * USN we sent.
+ */
+ if (new_m == NULL) {
+ return false;
+ }
+
+ if (dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING) {
+ /*
+ * if we compare equal then do an
+ * update. This is used when a client
+ * asks for a FULL_SYNC, and can be
+ * used to recover a corrupt
+ * replica.
+ *
+ * This call is a bit tricky, what we
+ * are doing it turning the 'is_newer'
+ * call into a 'not is older' by
+ * swapping cur_m and new_m, and negating the
+ * outcome.
+ */
+ cmp = !replmd_replPropertyMetaData1_is_newer(new_m,
+ cur_m);
+ } else {
+ cmp = replmd_replPropertyMetaData1_is_newer(cur_m,
+ new_m);
+ }
+ return cmp;
+}
+
/*
form a conflict DN
}
/*
- callback for replmd_replicated_apply_add() and replmd_replicated_handle_rename()
+ callback for replmd_replicated_apply_add()
This copes with the creation of conflict records in the case where
the DN exists, but with a different objectGUID
*/
bool rename_incoming_record, rodc;
struct replPropertyMetaData1 *rmd_name, *omd_name;
struct ldb_message *msg;
+ struct ldb_request *down_req = NULL;
- req->callback = callback;
-
- if (ares->error != LDB_ERR_ENTRY_ALREADY_EXISTS) {
- /* call the normal callback for everything except
- conflicts */
- return ldb_module_done(req, ares->controls, ares->response, ares->error);
+ /* call the normal callback for success */
+ if (ares->error == LDB_SUCCESS) {
+ return callback(req, ares);
}
- ret = samdb_rodc(ldb_module_get_ctx(ar->module), &rodc);
- if (ret != LDB_SUCCESS) {
- ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to determine if we are an RODC when attempting to form conflict DN: %s", ldb_errstring(ldb_module_get_ctx(ar->module)));
- return ldb_module_done(req, ares->controls, ares->response, LDB_ERR_OPERATIONS_ERROR);
- }
/*
* we have a conflict, and need to decide if we will keep the
* new record or the old record
*/
msg = ar->objs->objects[ar->index_current].msg;
+ conflict_dn = msg->dn;
+
+ /* For failures other than conflicts, fail the whole operation here */
+ if (ares->error != LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to locally apply remote add of %s: %s",
+ ldb_dn_get_linearized(conflict_dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module)));
+
+ return ldb_module_done(ar->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = samdb_rodc(ldb_module_get_ctx(ar->module), &rodc);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to determine if we are an RODC when attempting to form conflict DN: %s", ldb_errstring(ldb_module_get_ctx(ar->module)));
+ return ldb_module_done(ar->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
- switch (req->operation) {
- case LDB_ADD:
- conflict_dn = msg->dn;
- break;
- case LDB_RENAME:
- conflict_dn = req->op.rename.newdn;
- break;
- default:
- return ldb_module_done(req, ares->controls, ares->response, ldb_module_operr(ar->module));
}
if (rodc) {
/*
* first we need the replPropertyMetaData attribute from the
- * old record
+ * local, conflicting record
*/
ret = dsdb_module_search_dn(ar->module, req, &res, conflict_dn,
attrs,
rmd = ar->objs->objects[ar->index_current].meta_data;
- /* we decide which is newer based on the RPMD on the name
- attribute. See [MS-DRSR] ResolveNameConflict */
+ /*
+ * we decide which is newer based on the RPMD on the name
+ * attribute. See [MS-DRSR] ResolveNameConflict.
+ *
+ * We expect omd_name to be present, as this is from a local
+ * search, but while rmd_name should have been given to us by
+ * the remote server, if it is missing we just prefer the
+ * local name in
+ * replmd_replPropertyMetaData1_new_should_be_taken()
+ */
rmd_name = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
omd_name = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
- if (!rmd_name || !omd_name) {
- DEBUG(0,(__location__ ": Failed to find name attribute in replPropertyMetaData for %s\n",
+ if (!omd_name) {
+ DEBUG(0,(__location__ ": Failed to find name attribute in local LDB replPropertyMetaData for %s\n",
ldb_dn_get_linearized(conflict_dn)));
goto failed;
}
- rename_incoming_record = !(ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING) &&
- !replmd_replPropertyMetaData1_is_newer(omd_name, rmd_name);
+ /*
+ * Should we preserve the current record, and so rename the
+ * incoming record to be a conflict?
+ */
+ rename_incoming_record
+ = !replmd_replPropertyMetaData1_new_should_be_taken(ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING,
+ omd_name, rmd_name);
if (rename_incoming_record) {
struct GUID guid;
struct ldb_dn *new_dn;
- /*
- * We want to run the original callback here, which
- * will return LDB_ERR_ENTRY_ALREADY_EXISTS to the
- * caller, which will in turn know to rename the
- * incoming record. The error string is set in case
- * this isn't handled properly at some point in the
- * future.
- */
- if (req->operation == LDB_RENAME) {
- ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
- "Unable to handle incoming renames where this would "
- "create a conflict. Incoming record is %s (caller to handle)\n",
- ldb_dn_get_extended_linearized(req, conflict_dn, 1));
-
- goto failed;
- }
-
guid = samdb_result_guid(msg, "objectGUID");
if (GUID_all_zero(&guid)) {
DEBUG(0,(__location__ ": Failed to find objectGUID for conflicting incoming record %s\n",
DEBUG(2,(__location__ ": Resolving conflict record via incoming rename '%s' -> '%s'\n",
ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));
- /* re-submit the request, but with a different
- callback, so we don't loop forever. */
+ /* re-submit the request, but with the new DN */
+ callback = replmd_op_name_modify_callback;
msg->dn = new_dn;
- req->callback = replmd_op_name_modify_callback;
-
- return ldb_next_request(ar->module, req);
} else {
/* we are renaming the existing record */
struct GUID guid;
goto failed;
}
- return ldb_next_request(ar->module, req);
+ DEBUG(2,(__location__ ": With conflicting record renamed, re-apply replicated creation of '%s'\n",
+ ldb_dn_get_linearized(req->op.add.message->dn)));
+ }
+
+ ret = ldb_build_add_req(&down_req,
+ ldb_module_get_ctx(ar->module),
+ req,
+ msg,
+ ar->controls,
+ ar,
+ callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+ LDB_REQ_SET_LOCATION(down_req);
+
+ /* current partition control needed by "repmd_op_callback" */
+ ret = ldb_request_add_control(down_req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PARTIAL_REPLICA) {
+ /* this tells the partition module to make it a
+ partial replica if creating an NC */
+ ret = ldb_request_add_control(down_req,
+ DSDB_CONTROL_PARTIAL_REPLICA,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
}
+ /*
+ * Finally we re-run the add, otherwise the new record won't
+ * exist, as we are here because of that exact failure!
+ */
+ return ldb_next_request(ar->module, down_req);
failed:
- /* on failure do the original callback. This means replication
- * will stop with an error, but there is not much else we can
- * do
+
+ /* on failure make the caller get the error. This means
+ * replication will stop with an error, but there is not much
+ * else we can do.
*/
- return ldb_module_done(req, ares->controls, ares->response, ares->error);
+ return ldb_module_done(ar->req, NULL, NULL,
+ ret);
}
/*
return replmd_op_possible_conflict_callback(req, ares, replmd_op_callback);
}
-/*
- callback for replmd_replicated_handle_rename()
- This copes with the creation of conflict records in the case where
- the DN exists, but with a different objectGUID
- */
-static int replmd_op_rename_callback(struct ldb_request *req, struct ldb_reply *ares)
-{
- return replmd_op_possible_conflict_callback(req, ares, ldb_modify_default_callback);
-}
-
/*
this is called when a new object comes in over DRS
*/
unsigned int i;
int ret;
bool remote_isDeleted = false;
- const struct dsdb_attribute *rdn_sa;
- const char *rdn_name;
+ bool is_schema_nc;
+ NTTIME now;
+ time_t t = time(NULL);
+ const struct ldb_val *rdn_val;
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(ar->module),
+ struct replmd_private);
+ unix_to_nt_time(&now, t);
ldb = ldb_module_get_ctx(ar->module);
msg = ar->objs->objects[ar->index_current].msg;
md = ar->objs->objects[ar->index_current].meta_data;
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &ar->seq_num);
if (ret != LDB_SUCCESS) {
return replmd_replicated_request_error(ar, ret);
}
- ret = ldb_msg_add_value(msg, "objectGUID", &ar->objs->objects[ar->index_current].guid_value, NULL);
+ ret = dsdb_msg_add_guid(msg,
+ &ar->objs->objects[ar->index_current].object_guid,
+ "objectGUID");
if (ret != LDB_SUCCESS) {
return replmd_replicated_request_error(ar, ret);
}
}
if (DEBUGLVL(4)) {
+ struct GUID_txt_buf guid_txt;
+
char *s = ldb_ldif_message_string(ldb, ar, LDB_CHANGETYPE_ADD, msg);
- DEBUG(4, ("DRS replication add message:\n%s\n", s));
+ DEBUG(4, ("DRS replication add message of %s:\n%s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid, &guid_txt),
+ s));
talloc_free(s);
}
"isDeleted", false);
/*
- * the meta data array is already sorted by the caller
+ * the meta data array is already sorted by the caller, except
+ * for the RDN, which needs to be added.
*/
- rdn_name = ldb_dn_get_rdn_name(msg->dn);
- if (rdn_name == NULL) {
- ldb_asprintf_errstring(ldb, __location__ ": No rDN for %s?\n", ldb_dn_get_linearized(msg->dn));
- return replmd_replicated_request_error(ar, LDB_ERR_INVALID_DN_SYNTAX);
- }
- rdn_sa = dsdb_attribute_by_lDAPDisplayName(ar->schema, rdn_name);
- if (rdn_sa == NULL) {
- ldb_asprintf_errstring(ldb, ": No schema attribute found for rDN %s for %s\n",
- rdn_name, ldb_dn_get_linearized(msg->dn));
- return replmd_replicated_request_error(ar, LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE);
+ 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);
+ 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);
}
- ret = replmd_replPropertyMetaDataCtr1_verify(ldb, &md->ctr.ctr1, rdn_sa, msg->dn);
+ ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &md->ctr.ctr1, msg->dn);
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);
return ldb_module_done(ar->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
- if (ares->error != LDB_SUCCESS &&
- ares->error != LDB_ERR_NO_SUCH_OBJECT) {
- /*
- * TODO: deal with the above error that the parent object doesn't exist
- */
+ /*
+ * The error NO_SUCH_OBJECT is not expected, unless the search
+ * base is the partition DN, and that case doesn't happen here
+ * because then we wouldn't get a parent_guid_value in any
+ * case.
+ */
+ if (ares->error != LDB_SUCCESS) {
return ldb_module_done(ar->req, ares->controls,
ares->response, ares->error);
}
}
ar->objs->objects[ar->index_current].last_known_parent
= talloc_steal(ar->objs->objects[ar->index_current].msg, parent_msg->dn);
+
} else {
- parent_dn = parent_msg->dn;
+ parent_dn
+ = talloc_steal(ar->objs->objects[ar->index_current].msg, parent_msg->dn);
+
}
+ ar->objs->objects[ar->index_current].local_parent_dn = parent_dn;
comp_num = ldb_dn_get_comp_num(msg->dn);
if (comp_num > 1) {
break;
case LDB_REPLY_DONE:
+
+ if (ar->objs->objects[ar->index_current].local_parent_dn == NULL) {
+ struct GUID_txt_buf str_buf;
+ if (ar->search_msg != NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "No parent with GUID %s found for object locally known as %s",
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid, &str_buf),
+ ldb_dn_get_linearized(ar->search_msg->dn));
+ } else {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "No parent with GUID %s found for object remotely known as %s",
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid, &str_buf),
+ ldb_dn_get_linearized(ar->objs->objects[ar->index_current].msg->dn));
+ }
+
+ /*
+ * This error code is really important, as it
+ * is the flag back to the callers to retry
+ * this with DRSUAPI_DRS_GET_ANC, and so get
+ * the parent objects before the child
+ * objects
+ */
+ return ldb_module_done(ar->req, NULL, NULL,
+ replmd_replicated_request_werror(ar, WERR_DS_DRA_MISSING_PARENT));
+ }
+
if (ar->search_msg != NULL) {
ret = replmd_replicated_apply_merge(ar);
} else {
char *filter;
struct ldb_request *search_req;
static const char *attrs[] = {"isDeleted", NULL};
+ struct GUID_txt_buf guid_str_buf;
ldb = ldb_module_get_ctx(ar->module);
- if (!ar->objs->objects[ar->index_current].parent_guid_value.data) {
+ if (ar->objs->objects[ar->index_current].parent_guid == NULL) {
if (ar->search_msg != NULL) {
return replmd_replicated_apply_merge(ar);
} else {
}
}
- tmp_str = ldb_binary_encode(ar, ar->objs->objects[ar->index_current].parent_guid_value);
- if (!tmp_str) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ tmp_str = GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
+ &guid_str_buf);
filter = talloc_asprintf(ar, "(objectGUID=%s)", tmp_str);
if (!filter) return replmd_replicated_request_werror(ar, WERR_NOMEM);
- talloc_free(tmp_str);
ret = ldb_build_search_req(&search_req,
ldb,
*/
static int replmd_replicated_handle_rename(struct replmd_replicated_request *ar,
struct ldb_message *msg,
- struct ldb_request *parent)
+ struct ldb_request *parent,
+ bool *renamed)
{
- struct ldb_request *req;
int ret;
TALLOC_CTX *tmp_ctx = talloc_new(msg);
struct ldb_result *res;
+ struct ldb_dn *conflict_dn;
+ const char *attrs[] = { "replPropertyMetaData", "objectGUID", NULL };
+ const struct ldb_val *omd_value;
+ struct replPropertyMetaDataBlob omd, *rmd;
+ enum ndr_err_code ndr_err;
+ bool rename_incoming_record, rodc;
+ struct replPropertyMetaData1 *rmd_name, *omd_name;
+ struct ldb_dn *new_dn;
+ struct GUID guid;
DEBUG(4,("replmd_replicated_request rename %s => %s\n",
ldb_dn_get_linearized(ar->search_msg->dn),
ldb_dn_get_linearized(msg->dn)));
- res = talloc_zero(tmp_ctx, struct ldb_result);
- if (!res) {
+ ret = dsdb_module_rename(ar->module, ar->search_msg->dn, msg->dn,
+ DSDB_FLAG_NEXT_MODULE, ar->req);
+ if (ret == LDB_SUCCESS) {
talloc_free(tmp_ctx);
- return ldb_oom(ldb_module_get_ctx(ar->module));
+ *renamed = true;
+ return ret;
}
- /* pass rename to the next module
- * so it doesn't appear as an originating update */
- ret = ldb_build_rename_req(&req, ldb_module_get_ctx(ar->module), tmp_ctx,
- ar->search_msg->dn, msg->dn,
- NULL,
- ar,
- replmd_op_rename_callback,
- parent);
- LDB_REQ_SET_LOCATION(req);
- if (ret != LDB_SUCCESS) {
+ if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS) {
talloc_free(tmp_ctx);
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to locally apply remote rename from %s to %s: %s",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module)));
return ret;
}
- ret = dsdb_request_add_controls(req, DSDB_MODIFY_RELAX);
+ ret = samdb_rodc(ldb_module_get_ctx(ar->module), &rodc);
if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "Failed to determine if we are an RODC when attempting to form conflict DN: %s",
+ ldb_errstring(ldb_module_get_ctx(ar->module)));
+ return LDB_ERR_OPERATIONS_ERROR;
}
+ /*
+ * we have a conflict, and need to decide if we will keep the
+ * new record or the old record
+ */
- ret = ldb_next_request(ar->module, req);
+ conflict_dn = msg->dn;
- if (ret == LDB_SUCCESS) {
- ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ if (rodc) {
+ /*
+ * 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
+ */
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "Conflict adding object '%s' from incoming replication but 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));
+ goto failed;
+ }
+
+ /*
+ * first we need the replPropertyMetaData attribute from the
+ * old record
+ */
+ ret = dsdb_module_search_dn(ar->module, tmp_ctx, &res, conflict_dn,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_DELETED |
+ DSDB_SEARCH_SHOW_RECYCLED, ar->req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Unable to find object for conflicting record '%s'\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
}
+ omd_value = ldb_msg_find_ldb_val(res->msgs[0], "replPropertyMetaData");
+ if (omd_value == NULL) {
+ DEBUG(0,(__location__ ": Unable to find replPropertyMetaData for conflicting record '%s'\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ ndr_err = ndr_pull_struct_blob(omd_value, res->msgs[0], &omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to parse old replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ rmd = ar->objs->objects[ar->index_current].meta_data;
+
+ /*
+ * we decide which is newer based on the RPMD on the name
+ * attribute. See [MS-DRSR] ResolveNameConflict.
+ *
+ * We expect omd_name to be present, as this is from a local
+ * search, but while rmd_name should have been given to us by
+ * the remote server, if it is missing we just prefer the
+ * local name in
+ * replmd_replPropertyMetaData1_new_should_be_taken()
+ */
+ rmd_name = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
+ omd_name = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
+ if (!omd_name) {
+ DEBUG(0,(__location__ ": Failed to find name attribute in local LDB replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ /*
+ * Should we preserve the current record, and so rename the
+ * incoming record to be a conflict?
+ */
+ rename_incoming_record =
+ !replmd_replPropertyMetaData1_new_should_be_taken(
+ ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING,
+ omd_name, rmd_name);
+
+ if (rename_incoming_record) {
+
+ new_dn = replmd_conflict_dn(msg, msg->dn,
+ &ar->objs->objects[ar->index_current].object_guid);
+ if (new_dn == NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "Failed to form conflict DN for %s\n",
+ ldb_dn_get_linearized(msg->dn));
+
+ return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ }
+
+ ret = dsdb_module_rename(ar->module, ar->search_msg->dn, new_dn,
+ DSDB_FLAG_NEXT_MODULE, ar->req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "Failed to rename incoming conflicting dn '%s' (was '%s') to '%s' - %s\n",
+ ldb_dn_get_linearized(conflict_dn),
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(new_dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module)));
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
+ }
+
+ msg->dn = new_dn;
+ *renamed = true;
+ return LDB_SUCCESS;
+ }
+
+ /* we are renaming the existing record */
+
+ guid = samdb_result_guid(res->msgs[0], "objectGUID");
+ if (GUID_all_zero(&guid)) {
+ DEBUG(0,(__location__ ": Failed to find objectGUID for existing conflict record %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ new_dn = replmd_conflict_dn(tmp_ctx, conflict_dn, &guid);
+ if (new_dn == NULL) {
+ DEBUG(0,(__location__ ": Failed to form conflict DN for %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ DEBUG(2,(__location__ ": Resolving conflict record via existing-record rename '%s' -> '%s'\n",
+ ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));
+
+ ret = dsdb_module_rename(ar->module, conflict_dn, new_dn,
+ DSDB_FLAG_OWN_MODULE, ar->req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to rename conflict dn '%s' to '%s' - %s\n",
+ ldb_dn_get_linearized(conflict_dn),
+ ldb_dn_get_linearized(new_dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module))));
+ goto failed;
+ }
+
+ /*
+ * now we need to ensure that the rename is seen as an
+ * originating update. We do that with a modify.
+ */
+ ret = replmd_name_modify(ar, ar->req, new_dn);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ DEBUG(2,(__location__ ": With conflicting record renamed, re-apply replicated rename '%s' -> '%s'\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn)));
+
+
+ ret = dsdb_module_rename(ar->module, ar->search_msg->dn, msg->dn,
+ DSDB_FLAG_NEXT_MODULE, ar->req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": After conflict resolution, failed to rename dn '%s' to '%s' - %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module))));
+ goto failed;
+ }
+failed:
+
+ /*
+ * On failure make the caller get the error
+ * This means replication will stop with an error,
+ * but there is not much else we can do. In the
+ * LDB_ERR_ENTRY_ALREADY_EXISTS case this is exactly what is
+ * needed.
+ */
+
talloc_free(tmp_ctx);
return ret;
}
const struct ldb_val *omd_value;
struct replPropertyMetaDataBlob nmd;
struct ldb_val nmd_value;
+ struct GUID remote_parent_guid;
unsigned int i;
uint32_t j,ni=0;
unsigned int removed_attrs = 0;
bool take_remote_isDeleted = false;
bool sd_updated = false;
bool renamed = false;
+ bool is_schema_nc = false;
+ NTSTATUS nt_status;
+ const struct ldb_val *old_rdn, *new_rdn;
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(ar->module),
+ struct replmd_private);
+ NTTIME now;
+ time_t t = time(NULL);
+ unix_to_nt_time(&now, t);
ldb = ldb_module_get_ctx(ar->module);
msg = ar->objs->objects[ar->index_current].msg;
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
rmd = ar->objs->objects[ar->index_current].meta_data;
ZERO_STRUCT(omd);
omd.version = 1;
ndr_err = ndr_pull_struct_blob(omd_value, ar, &omd,
(ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
- NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ nt_status = ndr_map_error2ntstatus(ndr_err);
return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
}
}
}
+ if (DEBUGLVL(5)) {
+ struct GUID_txt_buf guid_txt;
+
+ char *s = ldb_ldif_message_string(ldb, ar, LDB_CHANGETYPE_MODIFY, msg);
+ DEBUG(5, ("Initial DRS replication modify message of %s is:\n%s\n"
+ "%s\n"
+ "%s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid, &guid_txt),
+ s,
+ ndr_print_struct_string(s,
+ (ndr_print_fn_t)ndr_print_replPropertyMetaDataBlob,
+ "existing replPropertyMetaData",
+ &omd),
+ ndr_print_struct_string(s,
+ (ndr_print_fn_t)ndr_print_replPropertyMetaDataBlob,
+ "incoming replPropertyMetaData",
+ rmd)));
+ talloc_free(s);
+ }
+
local_isDeleted = ldb_msg_find_attr_as_bool(ar->search_msg,
"isDeleted", false);
remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
"isDeleted", false);
- if (strcmp(ldb_dn_get_linearized(msg->dn), ldb_dn_get_linearized(ar->search_msg->dn)) == 0) {
+ /*
+ * Fill in the remote_parent_guid with the GUID or an all-zero
+ * GUID.
+ */
+ if (ar->objs->objects[ar->index_current].parent_guid != NULL) {
+ remote_parent_guid = *ar->objs->objects[ar->index_current].parent_guid;
+ } else {
+ remote_parent_guid = GUID_zero();
+ }
+
+ /*
+ * To ensure we follow a complex rename chain around, we have
+ * to confirm that the DN is the same (mostly to confirm the
+ * RDN) and the parentGUID is the same.
+ *
+ * This ensures we keep things under the correct parent, which
+ * replmd_replicated_handle_rename() will do.
+ */
+
+ if (strcmp(ldb_dn_get_linearized(msg->dn), ldb_dn_get_linearized(ar->search_msg->dn)) == 0
+ && GUID_equal(&remote_parent_guid, &ar->local_parent_guid)) {
ret = LDB_SUCCESS;
} else {
/*
* the peer has an older name to what we have (see
* replmd_replicated_apply_search_callback())
*/
- renamed = true;
- ret = replmd_replicated_handle_rename(ar, msg, ar->req);
+ ret = replmd_replicated_handle_rename(ar, msg, ar->req, &renamed);
}
- /*
- * This particular error code means that we already tried the
- * conflict algrorithm, and the existing record name was newer, so we
- * need to rename the incoming record
- */
- if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
- struct GUID guid;
- NTSTATUS status;
- struct ldb_dn *new_dn;
- status = GUID_from_ndr_blob(&ar->objs->objects[ar->index_current].guid_value, &guid);
- /* This really, really can't fail */
- SMB_ASSERT(NT_STATUS_IS_OK(status));
-
- new_dn = replmd_conflict_dn(msg, msg->dn, &guid);
- if (new_dn == NULL) {
- ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
- "Failed to form conflict DN for %s\n",
- ldb_dn_get_linearized(msg->dn));
-
- return replmd_replicated_request_werror(ar, WERR_NOMEM);
- }
-
- ret = dsdb_module_rename(ar->module, ar->search_msg->dn, new_dn,
- DSDB_FLAG_NEXT_MODULE, ar->req);
- if (ret != LDB_SUCCESS) {
- ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
- "Failed to rename incoming conflicting dn '%s' (was '%s') to '%s' - %s\n",
- ldb_dn_get_linearized(msg->dn),
- ldb_dn_get_linearized(ar->search_msg->dn),
- ldb_dn_get_linearized(new_dn),
- ldb_errstring(ldb_module_get_ctx(ar->module)));
- return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
- }
-
- /* Set the callback to one that will fix up the name to be a conflict DN */
- callback = replmd_op_name_modify_callback;
- msg->dn = new_dn;
- renamed = true;
- } else if (ret != LDB_SUCCESS) {
+ if (ret != LDB_SUCCESS) {
ldb_debug(ldb, LDB_DEBUG_FATAL,
"replmd_replicated_request rename %s => %s failed - %s\n",
ldb_dn_get_linearized(ar->search_msg->dn),
return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
}
+ if (renamed == true) {
+ /*
+ * Set the callback to one that will fix up the name
+ * metadata on the new conflict DN
+ */
+ callback = replmd_op_name_modify_callback;
+ }
+
ZERO_STRUCT(nmd);
nmd.version = 1;
nmd.ctr.ctr1.count = omd.ctr.ctr1.count + rmd->ctr.ctr1.count;
continue;
}
- if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING) {
- /*
- * if we compare equal then do an
- * update. This is used when a client
- * asks for a FULL_SYNC, and can be
- * used to recover a corrupt
- * replica.
- *
- * This call is a bit tricky, what we
- * are doing it turning the 'is_newer'
- * call into a 'not is older' by
- * swapping i and j, and negating the
- * outcome.
- */
- cmp = !replmd_replPropertyMetaData1_is_newer(&rmd->ctr.ctr1.array[i],
- &nmd.ctr.ctr1.array[j]);
- } else {
- cmp = replmd_replPropertyMetaData1_is_newer(&nmd.ctr.ctr1.array[j],
- &rmd->ctr.ctr1.array[i]);
- }
+ cmp = replmd_replPropertyMetaData1_new_should_be_taken(
+ ar->objs->dsdb_repl_flags,
+ &nmd.ctr.ctr1.array[j],
+ &rmd->ctr.ctr1.array[i]);
if (cmp) {
/* replace the entry */
nmd.ctr.ctr1.array[j] = rmd->ctr.ctr1.array[i];
*/
nmd.ctr.ctr1.count = ni;
+ new_rdn = ldb_dn_get_rdn_val(msg->dn);
+ old_rdn = ldb_dn_get_rdn_val(ar->search_msg->dn);
+
+ if (renamed) {
+ ret = replmd_update_rpmd_rdn_attr(ldb, msg, new_rdn, old_rdn,
+ &nmd, ar, now, is_schema_nc);
+ 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);
+ }
+ }
/*
- * the rdn attribute (the alias for the name attribute),
- * 'cn' for most objects is the last entry in the meta data array
- * we have stored
- *
* sort the new meta data array
*/
- ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, ar->schema, msg->dn);
+ ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, msg->dn);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb, "%s: error during DRS repl merge: %s", __func__, ldb_errstring(ldb));
return ret;
ndr_err = ndr_push_struct_blob(&nmd_value, msg, &nmd,
(ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
- NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ nt_status = ndr_map_error2ntstatus(ndr_err);
return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
}
}
if (DEBUGLVL(4)) {
+ struct GUID_txt_buf guid_txt;
+
char *s = ldb_ldif_message_string(ldb, ar, LDB_CHANGETYPE_MODIFY, msg);
- DEBUG(4, ("DRS replication modify message:\n%s\n", s));
+ DEBUG(4, ("Final DRS replication modify message of %s:\n%s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid, &guid_txt),
+ s));
talloc_free(s);
}
const struct ldb_val *omd_value;
struct replPropertyMetaDataBlob *rmd;
struct ldb_message *msg;
-
+ int instanceType;
+ ar->objs->objects[ar->index_current].local_parent_dn = NULL;
ar->objs->objects[ar->index_current].last_known_parent = NULL;
/*
}
}
+ ar->local_parent_guid = samdb_result_guid(ar->search_msg, "parentGUID");
+
+ instanceType = ldb_msg_find_attr_as_int(ar->search_msg, "instanceType", 0);
+ if (((instanceType & INSTANCE_TYPE_IS_NC_HEAD) == 0)
+ && GUID_all_zero(&ar->local_parent_guid)) {
+ DEBUG(0, ("Refusing to replicate new version of %s "
+ "as local object has an all-zero parentGUID attribute, "
+ "despite not being an NC root\n",
+ ldb_dn_get_linearized(ar->search_msg->dn)));
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+
/*
* now we need to check for double renames. We could have a
* local rename pending which our replication partner hasn't
* received yet. We choose which one wins by looking at the
- * attribute stamps on the two objects, the newer one wins
+ * attribute stamps on the two objects, the newer one wins.
+ *
+ * This also simply applies the correct algorithms for
+ * determining if a change was made to name at all, or
+ * if the object has just been renamed under the same
+ * parent.
*/
md_remote = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
- md_local = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
- /* if there is no name attribute then we have to assume the
- object we've received is in fact newer */
- if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING ||
- !md_remote || !md_local ||
- replmd_replPropertyMetaData1_is_newer(md_local, md_remote)) {
+ md_local = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
+ if (!md_local) {
+ DEBUG(0,(__location__ ": Failed to find name attribute in local LDB replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn)));
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
+ }
+
+ /*
+ * if there is no name attribute given then we have to assume the
+ * object we've received has the older name
+ */
+ if (replmd_replPropertyMetaData1_new_should_be_taken(
+ ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING,
+ md_local, md_remote)) {
+ struct GUID_txt_buf p_guid_local;
+ struct GUID_txt_buf p_guid_remote;
+ msg = ar->objs->objects[ar->index_current].msg;
+
+ /* Merge on the existing object, with rename */
+
+ DEBUG(4,(__location__ ": Looking for new parent for object %s currently under %s "
+ "as incoming object changing to %s under %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
+ ldb_dn_get_linearized(msg->dn),
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
+ &p_guid_remote)));
ret = replmd_replicated_apply_search_for_parent(ar);
} else {
+ struct GUID_txt_buf p_guid_local;
+ struct GUID_txt_buf p_guid_remote;
msg = ar->objs->objects[ar->index_current].msg;
- /* Otherwise, just merge on the existing object, force no rename */
- DEBUG(4,(__location__ ": Keeping object %s and rejecting older rename to %s\n",
- ldb_dn_get_linearized(ar->search_msg->dn),
- ldb_dn_get_linearized(msg->dn)));
+ /*
+ * Merge on the existing object, force no
+ * rename (code below just to explain why in
+ * the DEBUG() logs)
+ */
+ if (strcmp(ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn)) == 0) {
+ if (ar->objs->objects[ar->index_current].parent_guid != NULL &&
+ GUID_equal(&ar->local_parent_guid,
+ ar->objs->objects[ar->index_current].parent_guid)
+ == false) {
+ DEBUG(4,(__location__ ": Keeping object %s at under %s "
+ "despite incoming object changing parent to %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
+ &p_guid_remote)));
+ }
+ } else {
+ DEBUG(4,(__location__ ": Keeping object %s at under %s "
+ " and rejecting older rename to %s under %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
+ ldb_dn_get_linearized(msg->dn),
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
+ &p_guid_remote)));
+ }
/*
* This assignment ensures that the strcmp()
- * in replmd_replicated_apply_merge() avoids
- * the rename call
+ * and GUID_equal() calls in
+ * replmd_replicated_apply_merge() avoids the
+ * rename call
*/
+ ar->objs->objects[ar->index_current].parent_guid =
+ &ar->local_parent_guid;
+
msg->dn = ar->search_msg->dn;
ret = replmd_replicated_apply_merge(ar);
}
char *tmp_str;
char *filter;
struct ldb_request *search_req;
+ static const char *attrs[] = { "*", "parentGUID", "instanceType",
+ "replPropertyMetaData", "nTSecurityDescriptor",
+ NULL };
+ struct GUID_txt_buf guid_str_buf;
if (ar->index_current >= ar->objs->num_objects) {
/* done with it, go to next stage */
ar->search_msg = NULL;
ar->isDeleted = false;
- tmp_str = ldb_binary_encode(ar, ar->objs->objects[ar->index_current].guid_value);
- if (!tmp_str) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ tmp_str = GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid,
+ &guid_str_buf);
filter = talloc_asprintf(ar, "(objectGUID=%s)", tmp_str);
if (!filter) return replmd_replicated_request_werror(ar, WERR_NOMEM);
- talloc_free(tmp_str);
ret = ldb_build_search_req(&search_req,
ldb,
ar->objs->partition_dn,
LDB_SCOPE_SUBTREE,
filter,
- NULL,
+ attrs,
NULL,
ar,
replmd_replicated_apply_search_callback,
if (!(rmd_flags & DSDB_RMD_FLAG_DELETED)) {
/* remove the existing backlink */
- ret = replmd_add_backlink(module, schema, &la->identifier->guid, &guid, false, attr, false);
+ ret = replmd_add_backlink(module, schema, &la->identifier->guid, &guid, false, attr, true);
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, false);
+ ret = replmd_add_backlink(module, schema, &la->identifier->guid, &guid, true, attr, true);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
if (active) {
ret = replmd_add_backlink(module, schema, &la->identifier->guid, &guid,
- true, attr, false);
+ true, attr, true);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;