/* 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) {
- ldb_asprintf_errstring(ldb, "Unable to find GUID for DN %s\n",
- ldb_dn_get_linearized(dn));
+ char *dn_str = NULL;
+ dn_str = ldb_dn_get_extended_linearized(mem_ctx,
+ (dn), 1);
+ ldb_asprintf_errstring(ldb,
+ "Unable to find GUID for DN %s\n",
+ dn_str);
if (ret == LDB_ERR_NO_SUCH_OBJECT &&
LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE &&
ldb_attr_cmp(el->name, "member") == 0) {
if (vanish_links) {
unsigned j = 0;
+ struct ldb_val *tmp_vals = NULL;
+
+ tmp_vals = talloc_array(tmp_ctx, struct ldb_val,
+ old_el->num_values);
+ if (tmp_vals == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
for (i = 0; i < old_el->num_values; i++) {
- if (old_dns[i].v != NULL) {
- old_el->values[j] = *old_dns[i].v;
- j++;
+ if (old_dns[i].v == NULL) {
+ continue;
}
+ tmp_vals[j] = *old_dns[i].v;
+ j++;
+ }
+ for (i = 0; i < j; i++) {
+ old_el->values[i] = tmp_vals[i];
}
old_el->num_values = j;
}
continue;
}
if ((schema_attr->linkID & 1) == 1) {
- if (parent && ldb_request_get_control(parent, DSDB_CONTROL_DBCHECK)) {
- continue;
+ if (parent) {
+ struct ldb_control *ctrl;
+
+ ctrl = ldb_request_get_control(parent,
+ DSDB_CONTROL_REPLMD_VANISH_LINKS);
+ if (ctrl != NULL) {
+ ctrl->critical = false;
+ continue;
+ }
+ ctrl = ldb_request_get_control(parent,
+ DSDB_CONTROL_DBCHECK);
+ if (ctrl != NULL) {
+ continue;
+ }
}
+
/* Odd is for the target. Illegal to modify */
ldb_asprintf_errstring(ldb,
"attribute %s must not be modified directly, it is a linked attribute", el->name);
unsigned int functional_level;
const struct ldb_message_element *guid_el = NULL;
struct ldb_control *sd_propagation_control;
+ struct ldb_control *fix_links_control = NULL;
struct replmd_private *replmd_private =
talloc_get_type(ldb_module_get_private(module), struct replmd_private);
ldb = ldb_module_get_ctx(module);
+ fix_links_control = ldb_request_get_control(req,
+ DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS);
+ if (fix_links_control != NULL) {
+ struct dsdb_schema *schema = NULL;
+ const struct dsdb_attribute *sa = NULL;
+
+ if (req->op.mod.message->num_elements != 1) {
+ return ldb_module_operr(module);
+ }
+
+ if (req->op.mod.message->elements[0].flags != LDB_FLAG_MOD_REPLACE) {
+ return ldb_module_operr(module);
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (schema == NULL) {
+ return ldb_module_operr(module);
+ }
+
+ sa = dsdb_attribute_by_lDAPDisplayName(schema,
+ req->op.mod.message->elements[0].name);
+ if (sa == NULL) {
+ return ldb_module_operr(module);
+ }
+
+ if (sa->linkID == 0) {
+ return ldb_module_operr(module);
+ }
+
+ fix_links_control->critical = false;
+ return ldb_next_request(module, req);
+ }
+
ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_modify\n");
guid_el = ldb_msg_find_element(req->op.mod.message, "objectGUID");
ret = dsdb_module_search_dn(module, tmp_ctx, &link_res,
msg->dn, attrs,
DSDB_FLAG_NEXT_MODULE |
- DSDB_SEARCH_SHOW_EXTENDED_DN,
+ DSDB_SEARCH_SHOW_EXTENDED_DN |
+ DSDB_SEARCH_SHOW_RECYCLED,
parent);
if (ret != LDB_SUCCESS) {
"*",
NULL
};
- unsigned int i, el_count = 0;
+ static const struct ldb_val true_val = {
+ .data = discard_const_p(uint8_t, "TRUE"),
+ .length = 4
+ };
+
+ unsigned int i;
uint32_t dsdb_flags = 0;
struct replmd_private *replmd_private;
enum deletion_state deletion_state, next_deletion_state;
guid = samdb_result_guid(old_msg, "objectGUID");
if (deletion_state == OBJECT_NOT_DELETED) {
+ struct ldb_message_element *is_deleted_el;
ret = replmd_make_deleted_child_dn(tmp_ctx,
ldb,
return ret;
}
- ret = ldb_msg_add_string(msg, "isDeleted", "TRUE");
+ ret = ldb_msg_add_value(msg, "isDeleted", &true_val,
+ &is_deleted_el);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb, __location__
": Failed to add isDeleted string to the msg");
talloc_free(tmp_ctx);
return ret;
}
- msg->elements[el_count++].flags = LDB_FLAG_MOD_REPLACE;
+ is_deleted_el->flags = LDB_FLAG_MOD_REPLACE;
} else {
/*
* No matter what has happened with other renames etc, try again to
if (deletion_state == OBJECT_NOT_DELETED) {
struct ldb_dn *parent_dn = ldb_dn_get_parent(tmp_ctx, old_dn);
char *parent_dn_str = NULL;
+ struct ldb_message_element *p_el;
/* we need the storage form of the parent GUID */
ret = dsdb_module_search_dn(module, tmp_ctx, &parent_res,
talloc_free(tmp_ctx);
return ret;
}
- msg->elements[el_count++].flags = LDB_FLAG_MOD_REPLACE;
+ p_el = ldb_msg_find_element(msg,
+ "lastKnownParent");
+ if (p_el == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_operr(module);
+ }
+ p_el->flags = LDB_FLAG_MOD_REPLACE;
if (next_deletion_state == OBJECT_DELETED) {
ret = ldb_msg_add_value(msg, "msDS-LastKnownRDN", rdn_value, NULL);
talloc_free(tmp_ctx);
return ret;
}
- msg->elements[el_count++].flags = LDB_FLAG_MOD_ADD;
+ p_el = ldb_msg_find_element(msg,
+ "msDS-LastKnownRDN");
+ if (p_el == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_operr(module);
+ }
+ p_el->flags = LDB_FLAG_MOD_ADD;
}
}
* not activated and what ever the forest level is.
*/
if (dsdb_attribute_by_lDAPDisplayName(schema, "isRecycled") != NULL) {
- ret = ldb_msg_add_string(msg, "isRecycled", "TRUE");
+ struct ldb_message_element *is_recycled_el;
+
+ ret = ldb_msg_add_value(msg, "isRecycled", &true_val,
+ &is_recycled_el);
if (ret != LDB_SUCCESS) {
DEBUG(0,(__location__ ": Failed to add isRecycled string to the msg\n"));
ldb_module_oom(module);
talloc_free(tmp_ctx);
return ret;
}
- msg->elements[el_count++].flags = LDB_FLAG_MOD_REPLACE;
+ is_recycled_el->flags = LDB_FLAG_MOD_REPLACE;
}
replmd_private = talloc_get_type(ldb_module_get_private(module),
/* don't remove the rDN */
continue;
}
+
if (sa->linkID & 1) {
/*
we have a backlink in this object
replmd_private,
old_dn, &guid,
el, sa, req);
- if (ret != LDB_SUCCESS) {
+ if (ret == LDB_SUCCESS) {
+ /*
+ * now we continue, which means we
+ * won't remove this backlink
+ * directly
+ */
+ continue;
+ }
+
+ if (ret != LDB_ERR_NO_SUCH_ATTRIBUTE) {
const char *old_dn_str
= ldb_dn_get_linearized(old_dn);
ldb_asprintf_errstring(ldb,
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
- /* now we continue, which means we
- won't remove this backlink
- directly
- */
- continue;
+
+ /*
+ * Otherwise vanish the link, we are
+ * out of sync and the controlling
+ * object does not have the source
+ * link any more
+ */
+
+ dsdb_flags |= DSDB_REPLMD_VANISH_LINKS;
+
} else if (sa->linkID == 0) {
if (ldb_attr_in_list(preserved_attrs, el->name)) {
continue;
/*
- form a conflict DN
- */
-static struct ldb_dn *replmd_conflict_dn(TALLOC_CTX *mem_ctx, struct ldb_dn *dn, struct GUID *guid)
-{
- const struct ldb_val *rdn_val;
- const char *rdn_name;
- struct ldb_dn *new_dn;
-
- rdn_val = ldb_dn_get_rdn_val(dn);
- rdn_name = ldb_dn_get_rdn_name(dn);
- if (!rdn_val || !rdn_name) {
- return NULL;
- }
-
- new_dn = ldb_dn_copy(mem_ctx, dn);
- if (!new_dn) {
- return NULL;
- }
-
- if (!ldb_dn_remove_child_components(new_dn, 1)) {
- return NULL;
- }
-
- if (!ldb_dn_add_child_fmt(new_dn, "%s=%s\\0ACNF:%s",
- rdn_name,
- ldb_dn_escape_value(new_dn, *rdn_val),
- GUID_string(new_dn, guid))) {
- return NULL;
- }
-
- return new_dn;
-}
-
-/*
- form a deleted DN
+ form a DN for a deleted (DEL:) or conflict (CNF:) DN
*/
-static int replmd_make_deleted_child_dn(TALLOC_CTX *tmp_ctx,
- struct ldb_context *ldb,
- struct ldb_dn *dn,
- const char *rdn_name,
- const struct ldb_val *rdn_value,
- struct GUID guid)
+static int replmd_make_prefix_child_dn(TALLOC_CTX *tmp_ctx,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn,
+ const char *four_char_prefix,
+ const char *rdn_name,
+ const struct ldb_val *rdn_value,
+ struct GUID guid)
{
struct ldb_val deleted_child_rdn_val;
struct GUID_txt_buf guid_str;
- const char *four_char_prefix = "DEL:";
bool retb;
GUID_buf_string(&guid, &guid_str);
return LDB_ERR_OPERATIONS_ERROR;
}
-
+ /*
+ * TODO: Per MS-ADTS 3.1.1.5.5 Delete Operation
+ * we should truncate this value to ensure the RDN is not more than 255 chars.
+ *
+ * However we MS-ADTS 3.1.1.5.1.2 Naming Constraints indicates that:
+ *
+ * "Naming constraints are not enforced for replicated
+ * updates." so this is safe and we don't have to work out not
+ * splitting a UTF8 char right now.
+ */
deleted_child_rdn_val = ldb_val_dup(tmp_ctx, rdn_value);
/*
}
+/*
+ form a conflict DN
+ */
+static struct ldb_dn *replmd_conflict_dn(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn,
+ struct GUID *guid)
+{
+ const struct ldb_val *rdn_val;
+ const char *rdn_name;
+ struct ldb_dn *new_dn;
+ int ret;
+
+ rdn_val = ldb_dn_get_rdn_val(dn);
+ rdn_name = ldb_dn_get_rdn_name(dn);
+ if (!rdn_val || !rdn_name) {
+ return NULL;
+ }
+
+ new_dn = ldb_dn_get_parent(mem_ctx, dn);
+ if (!new_dn) {
+ return NULL;
+ }
+
+ ret = replmd_make_prefix_child_dn(mem_ctx,
+ ldb, new_dn,
+ "CNF:",
+ rdn_name,
+ rdn_val,
+ *guid);
+ if (ret != LDB_SUCCESS) {
+ return NULL;
+ }
+ return new_dn;
+}
+
+/*
+ form a deleted DN
+ */
+static int replmd_make_deleted_child_dn(TALLOC_CTX *tmp_ctx,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn,
+ const char *rdn_name,
+ const struct ldb_val *rdn_value,
+ struct GUID guid)
+{
+ return replmd_make_prefix_child_dn(tmp_ctx,
+ ldb, dn,
+ "DEL:",
+ rdn_name,
+ rdn_value,
+ guid);
+}
+
+
/*
perform a modify operation which sets the rDN and name attributes to
their current values. This has the effect of changing these
attributes to have been last updated by the current DC. This is
needed to ensure that renames performed as part of conflict
- resolution are propogated to other DCs
+ resolution are propagated to other DCs
*/
static int replmd_name_modify(struct replmd_replicated_request *ar,
struct ldb_request *req, struct ldb_dn *dn)
"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));
+ ret = LDB_ERR_OPERATIONS_ERROR;
goto failed;
}
ldb_dn_get_linearized(conflict_dn)));
goto failed;
}
- new_dn = replmd_conflict_dn(req, conflict_dn, &guid);
+ new_dn = replmd_conflict_dn(req,
+ ldb_module_get_ctx(ar->module),
+ 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;
}
- new_dn = replmd_conflict_dn(req, conflict_dn, &guid);
+ new_dn = replmd_conflict_dn(req,
+ ldb_module_get_ctx(ar->module),
+ conflict_dn, &guid);
if (new_dn == NULL) {
DEBUG(0,(__location__ ": Failed to form conflict DN for %s\n",
ldb_dn_get_linearized(conflict_dn)));
* replication will stop with an error, but there is not much
* else we can do.
*/
+ if (ret == LDB_SUCCESS) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
return ldb_module_done(ar->req, NULL, NULL,
ret);
}
{
struct ldb_message *parent_msg = ares->message;
struct ldb_message *msg = ar->objs->objects[ar->index_current].msg;
- struct ldb_dn *parent_dn;
+ struct ldb_dn *parent_dn = NULL;
int comp_num;
if (!ldb_msg_check_string_attribute(msg, "isDeleted", "TRUE")
"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));
+ ret = LDB_ERR_OPERATIONS_ERROR;
goto failed;
}
if (rename_incoming_record) {
- new_dn = replmd_conflict_dn(msg, msg->dn,
+ new_dn = replmd_conflict_dn(msg,
+ ldb_module_get_ctx(ar->module),
+ msg->dn,
&ar->objs->objects[ar->index_current].object_guid);
if (new_dn == NULL) {
ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
goto failed;
}
- new_dn = replmd_conflict_dn(tmp_ctx, conflict_dn, &guid);
+ new_dn = replmd_conflict_dn(tmp_ctx,
+ ldb_module_get_ctx(ar->module),
+ conflict_dn, &guid);
if (new_dn == NULL) {
DEBUG(0,(__location__ ": Failed to form conflict DN for %s\n",
ldb_dn_get_linearized(conflict_dn)));
ldb_errstring(ldb_module_get_ctx(ar->module))));
goto failed;
}
-failed:
+ talloc_free(tmp_ctx);
+ return ret;
+failed:
/*
* On failure make the caller get the error
* This means replication will stop with an error,
* LDB_ERR_ENTRY_ALREADY_EXISTS case this is exactly what is
* needed.
*/
+ if (ret == LDB_SUCCESS) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
talloc_free(tmp_ctx);
return ret;
return LDB_ERR_OPERATIONS_ERROR;
}
+ /*
+ * All attributes listed here must be dealt with in some way
+ * by replmd_process_linked_attribute() otherwise in the case
+ * of isDeleted: FALSE the modify will fail with:
+ *
+ * Failed to apply linked attribute change 'attribute 'isDeleted':
+ * invalid modify flags on
+ * 'CN=g1_1527570609273,CN=Users,DC=samba,DC=example,DC=com':
+ * 0x0'
+ *
+ * This is becaue isDeleted is a Boolean, so FALSE is a
+ * legitimate value (set by Samba's deletetest.py)
+ */
attrs[0] = attr->lDAPDisplayName;
attrs[1] = "isDeleted";
attrs[2] = "isRecycled";
* 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.
+ *
+ * This needs isDeleted and isRecycled to be included as
+ * attributes in the search and so in msg if set.
*/
replmd_deletion_state(module, msg, &deletion_state, NULL);
return LDB_SUCCESS;
}
+ /*
+ * Now that we know the deletion_state, remove the extra
+ * attributes added for that purpose. We need to do this
+ * otherwise in the case of isDeleted: FALSE the modify will
+ * fail with:
+ *
+ * Failed to apply linked attribute change 'attribute 'isDeleted':
+ * invalid modify flags on
+ * 'CN=g1_1527570609273,CN=Users,DC=samba,DC=example,DC=com':
+ * 0x0'
+ *
+ * This is becaue isDeleted is a Boolean, so FALSE is a
+ * legitimate value (set by Samba's deletetest.py)
+ */
+
+ ldb_msg_remove_attr(msg, "isDeleted");
+ ldb_msg_remove_attr(msg, "isRecycled");
+
old_el = ldb_msg_find_element(msg, attr->lDAPDisplayName);
if (old_el == NULL) {
ret = ldb_msg_add_empty(msg, attr->lDAPDisplayName, LDB_FLAG_MOD_REPLACE, &old_el);