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;
uint64_t mod_usn;
uint64_t mod_usn_urgent;
} *ncs;
+ struct ldb_dn *schema_dn;
+ bool originating_updates;
+ bool sorted_links;
};
struct la_entry {
struct ldb_request *req;
const struct dsdb_schema *schema;
+ struct GUID our_invocation_id;
/* 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;
struct dsdb_extended_replicated_objects *objs;
struct ldb_message *search_msg;
+ struct GUID local_parent_guid;
uint64_t seq_num;
bool is_urgent;
bool isDeleted;
};
+struct parsed_dn {
+ struct dsdb_dn *dsdb_dn;
+ struct GUID guid;
+ struct ldb_val *v;
+};
+
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);
enum urgent_situation {
REPL_URGENT_ON_CREATE = 1,
{
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;
};
+/*
+ a ldb_modify request operating on modules below the
+ current module
+ */
+static int linked_attr_modify(struct ldb_module *module,
+ const struct ldb_message *message,
+ struct ldb_request *parent)
+{
+ struct ldb_request *mod_req;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_mod_req(&mod_req, ldb, tmp_ctx,
+ message,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(mod_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = ldb_request_add_control(mod_req, DSDB_CONTROL_REPLICATED_UPDATE_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Run the new request */
+ ret = ldb_next_request(module, mod_req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(mod_req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
/*
process a backlinks we accumulated during a transaction, adding and
deleting the backlinks from the target objects
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) {
+ struct GUID_txt_buf guid_str;
DEBUG(2,(__location__ ": WARNING: Failed to find target DN for linked attribute with GUID %s\n",
- GUID_string(bl, &bl->target_guid)));
+ GUID_buf_string(&bl->target_guid, &guid_str)));
+ 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,
modified_partition->mod_usn_urgent = ac->seq_num;
}
}
+ if (!ac->apply_mode) {
+ replmd_private->originating_updates = true;
+ }
}
if (ac->apply_mode) {
ldb_dn_get_linearized(modified_partition->dn)));
return ret;
}
+
+ if (ldb_dn_compare(modified_partition->dn,
+ replmd_private->schema_dn) == 0) {
+ struct ldb_result *ext_res;
+ ret = dsdb_module_extended(module,
+ replmd_private->schema_dn,
+ &ext_res,
+ DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID,
+ ext_res,
+ DSDB_FLAG_NEXT_MODULE,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_free(ext_res);
+ }
+
DLIST_REMOVE(replmd_private->ncs, modified_partition);
talloc_free(modified_partition);
}
{
struct ldb_context *ldb;
struct replmd_replicated_request *ac;
+ const struct GUID *our_invocation_id;
ldb = ldb_module_get_ctx(module);
ldb_debug_set(ldb, LDB_DEBUG_FATAL,
"replmd_modify: no dsdb_schema loaded");
DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb)));
+ talloc_free(ac);
+ return NULL;
+ }
+
+ /* get our invocationId */
+ our_invocation_id = samdb_ntds_invocation_id(ldb);
+ if (!our_invocation_id) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "replmd_add: unable to find invocationId\n");
+ talloc_free(ac);
return NULL;
}
+ ac->our_invocation_id = *our_invocation_id;
return ac;
}
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) {
ldb_dn_get_linearized(dn));
return LDB_ERR_CONSTRAINT_VIOLATION;
}
- if (ctr1->array[ctr1->count - 1].attid != rdn_sa->attributeID_id) {
- ldb_debug_set(ldb, LDB_DEBUG_FATAL,
- "No rDN found in replPropertyMetaData for %s!\n",
- ldb_dn_get_linearized(dn));
- return LDB_ERR_CONSTRAINT_VIOLATION;
- }
/* the objectClass attribute is value 0x00000000, so must be first */
if (ctr1->array[0].attid != DRSUAPI_ATTID_objectClass) {
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,
const struct GUID *invocation_id, uint64_t seq_num,
uint64_t local_usn, NTTIME nttime, uint32_t version, bool deleted);
+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, time_t t,
- 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);
- NTTIME now;
-
- unix_to_nt_time(&now, t);
-
- 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;
+ struct ldb_val *new_values = NULL;
+
+ int ret = get_parsed_dns(module, tmp_ctx, el, &pdn,
+ sa->syntax->ldap_oid, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
- /* 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;
- }
- }
+ 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;
+ }
- ret = replmd_build_la_val(el->values, v, dsdb_dn, invocationId,
- seq_num, seq_num, now, 0, false);
+ for (i = 0; i < el->num_values; i++) {
+ struct parsed_dn *p = &pdn[i];
+ 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
*/
static int replmd_add(struct ldb_module *module, struct ldb_request *req)
{
- struct samldb_msds_intid_persistant *msds_intid_struct;
struct ldb_context *ldb;
struct ldb_control *control;
struct replmd_replicated_request *ac;
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;
- const struct GUID *our_invocation_id;
+ 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,
+ * getting a higher resolution timestamp is not required.
+ */
time_t t = time(NULL);
NTTIME now;
char *time_str;
bool allow_add_guid = false;
bool remove_current_guid = false;
bool is_urgent = false;
+ bool is_schema_nc = false;
struct ldb_message_element *objectclass_el;
+ struct replmd_private *replmd_private =
+ talloc_get_type_abort(ldb_module_get_private(module), struct replmd_private);
/* check if there's a show relax control (used by provision to say 'I know what I'm doing') */
control = ldb_request_get_control(req, LDB_CONTROL_RELAX_OID);
} 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);
return ret;
}
- /* get our invocationId */
- our_invocation_id = samdb_ntds_invocation_id(ldb);
- if (!our_invocation_id) {
- ldb_debug_set(ldb, LDB_DEBUG_ERROR,
- "replmd_add: unable to find invocationId\n");
- talloc_free(ac);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
/* we have to copy the message as the caller might have it as a const */
msg = ldb_msg_copy_shallow(ac, req->op.add.message);
if (msg == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
- for (i=0; i < msg->num_elements; i++) {
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
+ for (i=0; i < msg->num_elements;) {
struct ldb_message_element *e = &msg->elements[i];
struct replPropertyMetaData1 *m = &nmd.ctr.ctr1.array[ni];
const struct dsdb_attribute *sa;
- if (e->name[0] == '@') continue;
+ if (e->name[0] == '@') {
+ i++;
+ continue;
+ }
sa = dsdb_attribute_by_lDAPDisplayName(ac->schema, e->name);
if (!sa) {
/* if the attribute is not replicated (0x00000001)
* or constructed (0x00000004) it has no metadata
*/
+ i++;
continue;
}
if (sa->linkID != 0 && functional_level > DS_DOMAIN_FUNCTION_2000) {
- ret = replmd_add_fix_la(module, e, ac->seq_num, our_invocation_id, t, &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;
}
/* linked attributes are not stored in
replPropertyMetaData in FL above w2k */
+ i++;
continue;
}
- m->attid = sa->attributeID_id;
- m->version = 1;
- if (m->attid == 0x20030) {
+ m->attid = dsdb_attribute_get_attid(sa, is_schema_nc);
+ m->version = 1;
+ if (m->attid == DRSUAPI_ATTID_isDeleted) {
const struct ldb_val *rdn_val = ldb_dn_get_rdn_val(msg->dn);
const char* rdn;
} else {
m->originating_change_time = now;
}
- m->originating_invocation_id = *our_invocation_id;
+ m->originating_invocation_id = ac->our_invocation_id;
m->originating_usn = ac->seq_num;
m->local_usn = ac->seq_num;
ni++;
+
+ if (!(e->flags & DSDB_FLAG_INTERNAL_FORCE_META_DATA)) {
+ i++;
+ continue;
+ }
+
+ e->flags &= ~DSDB_FLAG_INTERNAL_FORCE_META_DATA;
+
+ if (e->num_values != 0) {
+ i++;
+ continue;
+ }
+
+ ldb_msg_remove_element(msg, e);
}
/* fix meta data count */
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);
if (control) {
control->critical = 0;
}
- if (ldb_dn_compare_base(ac->schema->base_dn, req->op.add.message->dn) != 0) {
-
- /* Update the usn in the SAMLDB_MSDS_INTID_OPAQUE opaque */
- msds_intid_struct = (struct samldb_msds_intid_persistant *) ldb_get_opaque(ldb, SAMLDB_MSDS_INTID_OPAQUE);
- if (msds_intid_struct) {
- msds_intid_struct->usn = ac->seq_num;
- }
- }
/* go on with the call chain */
return ldb_next_request(module, down_req);
}
uint64_t *seq_num,
const struct GUID *our_invocation_id,
NTTIME now,
+ bool is_schema_nc,
struct ldb_request *req)
{
uint32_t i;
const struct dsdb_attribute *a;
struct replPropertyMetaData1 *md1;
bool may_skip = false;
+ uint32_t attid;
a = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
if (a == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
+ attid = dsdb_attribute_get_attid(a, is_schema_nc);
+
if ((a->systemFlags & DS_FLAG_ATTR_NOT_REPLICATED) || (a->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED)) {
return LDB_SUCCESS;
}
} else if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) {
may_skip = true;
}
+ } else if (a->linkID != 0 && LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE &&
+ ldb_request_get_control(req, DSDB_CONTROL_REPLMD_VANISH_LINKS) != NULL) {
+ /*
+ * We intentionally skip the version bump when attempting to
+ * vanish links.
+ *
+ * The control is set by dbcheck and expunge-tombstones which
+ * both attempt to be non-replicating. Otherwise, making an
+ * alteration to the replication state would trigger a
+ * broadcast of all expunged objects.
+ */
+ may_skip = true;
+ }
+
+ if (el->flags & DSDB_FLAG_INTERNAL_FORCE_META_DATA) {
+ may_skip = false;
+ el->flags &= ~DSDB_FLAG_INTERNAL_FORCE_META_DATA;
}
if (may_skip) {
}
for (i=0; i<omd->ctr.ctr1.count; i++) {
- if (a->attributeID_id == omd->ctr.ctr1.array[i].attid) break;
- }
-
- if (a->linkID != 0 && dsdb_functional_level(ldb) > DS_DOMAIN_FUNCTION_2000) {
+ /*
+ * First check if we find it under the msDS-IntID,
+ * then check if we find it under the OID and
+ * prefixMap ID.
+ *
+ * This allows the administrator to simply re-write
+ * the attributes and so restore replication, which is
+ * likely what they will try to do.
+ */
+ if (attid == omd->ctr.ctr1.array[i].attid) {
+ break;
+ }
+
+ if (a->attributeID_id == omd->ctr.ctr1.array[i].attid) {
+ break;
+ }
+ }
+
+ if (a->linkID != 0 && dsdb_functional_level(ldb) > DS_DOMAIN_FUNCTION_2000) {
/* linked attributes are not stored in
replPropertyMetaData in FL above w2k, but we do
raise the seqnum for the object */
md1 = &omd->ctr.ctr1.array[i];
md1->version++;
- md1->attid = a->attributeID_id;
- if (md1->attid == 0x20030) {
+ 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;
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)
+{
+ const char *rdn_name = ldb_dn_get_rdn_name(msg->dn);
+ const struct dsdb_attribute *rdn_attr =
+ dsdb_attribute_by_lDAPDisplayName(ar->schema, rdn_name);
+ const char *attr_name = rdn_attr != NULL ?
+ rdn_attr->lDAPDisplayName :
+ rdn_name;
+ struct ldb_message_element new_el = {
+ .flags = LDB_FLAG_MOD_REPLACE,
+ .name = attr_name,
+ .num_values = 1,
+ .values = discard_const_p(struct ldb_val, rdn_new)
+ };
+ struct ldb_message_element old_el = {
+ .flags = LDB_FLAG_MOD_REPLACE,
+ .name = attr_name,
+ .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;
struct ldb_request *req,
const char * const *rename_attrs,
struct ldb_message *msg, uint64_t *seq_num,
- time_t t,
+ time_t t, bool is_schema_nc,
bool *is_urgent, bool *rodc)
{
const struct ldb_val *omd_value;
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;
struct ldb_message_element *objectclass_el;
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);
if (ldb_request_get_control(req, DSDB_CONTROL_CHANGEREPLMETADATA_OID)) {
rmd_is_provided = true;
+ if (ldb_request_get_control(req, DSDB_CONTROL_CHANGEREPLMETADATA_RESORT_OID)) {
+ rmd_is_just_resorted = true;
+ }
} else {
rmd_is_provided = false;
}
/* In this case the change_replmetadata control was supplied */
/* We check that it's the only attribute that is provided
* (it's a rare case so it's better to keep the code simplier)
- * We also check that the highest local_usn is bigger than
+ * We also check that the highest local_usn is bigger or the same as
* uSNChanged. */
uint64_t db_seq;
if( msg->num_elements != 1 ||
ldb_dn_get_linearized(msg->dn)));
return LDB_ERR_OPERATIONS_ERROR;
}
- *seq_num = find_max_local_usn(omd);
ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs2,
DSDB_FLAG_NEXT_MODULE |
return ret;
}
- db_seq = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNChanged", 0);
- if (*seq_num <= db_seq) {
- DEBUG(0,(__location__ ": changereplmetada control provided but max(local_usn)"\
- " is less or equal to uSNChanged (max = %lld uSNChanged = %lld)\n",
- (long long)*seq_num, (long long)db_seq));
- return LDB_ERR_OPERATIONS_ERROR;
+ if (rmd_is_just_resorted == false) {
+ *seq_num = find_max_local_usn(omd);
+
+ db_seq = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNChanged", 0);
+
+ /*
+ * The test here now allows for a new
+ * replPropertyMetaData with no change, if was
+ * just dbcheck re-sorting the values.
+ */
+ if (*seq_num <= db_seq) {
+ DEBUG(0,(__location__ ": changereplmetada control provided but max(local_usn)" \
+ " is less than uSNChanged (max = %lld uSNChanged = %lld)\n",
+ (long long)*seq_num, (long long)db_seq));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
}
} else {
return LDB_ERR_OPERATIONS_ERROR;
}
- for (i=0; i<msg->num_elements; i++) {
+ for (i=0; i<msg->num_elements;) {
+ struct ldb_message_element *el = &msg->elements[i];
struct ldb_message_element *old_el;
- old_el = ldb_msg_find_element(res->msgs[0], msg->elements[i].name);
- ret = replmd_update_rpmd_element(ldb, msg, &msg->elements[i], old_el, &omd, schema, seq_num,
- our_invocation_id, now, req);
+
+ old_el = ldb_msg_find_element(res->msgs[0], el->name);
+ ret = replmd_update_rpmd_element(ldb, msg, el, old_el,
+ &omd, schema, seq_num,
+ our_invocation_id,
+ now, is_schema_nc,
+ req);
if (ret != LDB_SUCCESS) {
return ret;
}
if (!*is_urgent && (situation == REPL_URGENT_ON_UPDATE)) {
- *is_urgent = replmd_check_urgent_attribute(&msg->elements[i]);
+ *is_urgent = replmd_check_urgent_attribute(el);
+ }
+
+ if (!(el->flags & DSDB_FLAG_INTERNAL_FORCE_META_DATA)) {
+ i++;
+ continue;
+ }
+
+ el->flags &= ~DSDB_FLAG_INTERNAL_FORCE_META_DATA;
+
+ if (el->num_values != 0) {
+ i++;
+ continue;
}
+ ldb_msg_remove_element(msg, el);
}
}
* replmd_update_rpmd_element has done an update if the
* seq_num is set
*/
- if (*seq_num != 0) {
+ if (*seq_num != 0 || rmd_is_just_resorted == true) {
struct ldb_val *md_value;
struct ldb_message_element *el;
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;
return LDB_SUCCESS;
}
-struct parsed_dn {
- struct dsdb_dn *dsdb_dn;
+struct compare_ctx {
struct GUID *guid;
- struct ldb_val *v;
+ struct ldb_context *ldb;
+ TALLOC_CTX *mem_ctx;
+ const char *ldap_oid;
+ int err;
+ const struct GUID *invocation_id;
};
+/* When a parsed_dn comes from the database, sometimes it is not really parsed. */
+
+static int really_parse_trusted_dn(TALLOC_CTX *mem_ctx, struct ldb_context *ldb,
+ struct parsed_dn *pdn, const char *ldap_oid)
+{
+ NTSTATUS status;
+ struct dsdb_dn *dsdb_dn = dsdb_dn_parse_trusted(mem_ctx, ldb, pdn->v,
+ ldap_oid);
+ if (dsdb_dn == NULL) {
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &pdn->guid, "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pdn->dsdb_dn = dsdb_dn;
+ return LDB_SUCCESS;
+}
+
+/*
+ * We choose, as the sort order, the same order as is used in DRS replication,
+ * which is the memcmp() order of the NDR GUID, not that obtained from
+ * GUID_compare().
+ *
+ * This means that sorted links will be in the same order as a new DC would
+ * see them.
+ */
+static int ndr_guid_compare(struct GUID *guid1, struct GUID *guid2)
+{
+ uint8_t v1_data[16];
+ struct ldb_val v1 = data_blob_const(v1_data, sizeof(v1_data));
+ uint8_t v2_data[16];
+ struct ldb_val v2 = data_blob_const(v2_data, sizeof(v2_data));
+
+ /* This can't fail */
+ ndr_push_struct_into_fixed_blob(&v1, guid1,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ /* This can't fail */
+ ndr_push_struct_into_fixed_blob(&v2, guid2,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ return data_blob_cmp(&v1, &v2);
+}
+
static int parsed_dn_compare(struct parsed_dn *pdn1, struct parsed_dn *pdn2)
{
- return GUID_compare(pdn1->guid, pdn2->guid);
+ 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);
+ }
+ return ret;
}
-static struct parsed_dn *parsed_dn_find(struct parsed_dn *pdn,
- unsigned int count, struct GUID *guid,
- struct ldb_dn *dn)
+static int la_guid_compare_with_trusted_dn(struct compare_ctx *ctx,
+ struct parsed_dn *p)
+{
+ /*
+ * This works like a standard compare function in its return values,
+ * but has an extra trick to deal with errors: zero is returned and
+ * ctx->err is set to the ldb error code.
+ *
+ * That is, if (as is expected in most cases) you get a non-zero
+ * result, you don't need to check for errors.
+ *
+ * We assume the second argument refers to a DN is from the database
+ * and has a GUID -- but this GUID might not have been parsed out yet.
+ */
+ if (p->dsdb_dn == NULL) {
+ int ret = really_parse_trusted_dn(ctx->mem_ctx, ctx->ldb, p,
+ ctx->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ ctx->err = ret;
+ return 0;
+ }
+ }
+ return ndr_guid_compare(ctx->guid, &p->guid);
+}
+
+
+
+static int parsed_dn_find(struct ldb_context *ldb, struct parsed_dn *pdn,
+ unsigned int count,
+ struct GUID *guid,
+ struct ldb_dn *target_dn,
+ struct parsed_dn **exact,
+ struct parsed_dn **next,
+ const char *ldap_oid)
{
- 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];
+ struct compare_ctx ctx;
+ if (pdn == NULL) {
+ *exact = NULL;
+ *next = NULL;
+ return LDB_SUCCESS;
+ }
+
+ if (unlikely(GUID_all_zero(guid))) {
+ /*
+ * When updating a link using DRS, we sometimes get a NULL
+ * GUID when a forward link has been deleted and its GUID has
+ * for some reason been forgotten. The best we can do is try
+ * and match by DN via a linear search. Note that this
+ * probably only happens in the ADD case, in which we only
+ * allow modification of link if it is already deleted, so
+ * this seems very close to an elaborate NO-OP, but we are not
+ * quite prepared to declare it so.
+ *
+ * If the DN is not in our list, we have to add it to the
+ * beginning of the list, where it would naturally sort.
+ */
+ struct parsed_dn *p;
+ if (target_dn == NULL) {
+ /* We don't know the target DN, so we can't search for DN */
+ DEBUG(1, ("parsed_dn_find has a NULL GUID for a linked "
+ "attribute but we don't have a DN to compare "
+ "it with\n"));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *exact = NULL;
+ *next = NULL;
+
+ DEBUG(3, ("parsed_dn_find has a NULL GUID for a link to DN "
+ "%s; searching through links for it",
+ ldb_dn_get_linearized(target_dn)));
+
+ for (i = 0; i < count; i++) {
+ int cmp;
+ p = &pdn[i];
+ if (p->dsdb_dn == NULL) {
+ int ret = really_parse_trusted_dn(pdn, ldb, p, ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ cmp = ldb_dn_compare(p->dsdb_dn->dn, target_dn);
+ if (cmp == 0) {
+ *exact = p;
+ return LDB_SUCCESS;
}
}
- return NULL;
+ /*
+ * Here we have a null guid which doesn't match any existing
+ * link. This is a bit unexpected because null guids occur
+ * when a forward link has been deleted and we are replicating
+ * that deletion.
+ *
+ * The best thing to do is weep into the logs and add the
+ * offending link to the beginning of the list which is
+ * at least the correct sort position.
+ */
+ DEBUG(1, ("parsed_dn_find has been given a NULL GUID for a "
+ "link to unknown DN %s\n",
+ ldb_dn_get_linearized(target_dn)));
+ *next = pdn;
+ return LDB_SUCCESS;
}
- BINARY_ARRAY_SEARCH(pdn, count, guid, guid, GUID_compare, ret);
- return ret;
+
+ ctx.guid = guid;
+ ctx.ldb = ldb;
+ ctx.mem_ctx = pdn;
+ ctx.ldap_oid = ldap_oid;
+ ctx.err = 0;
+
+ BINARY_ARRAY_SEARCH_GTE(pdn, count, &ctx, la_guid_compare_with_trusted_dn,
+ *exact, *next);
+
+ if (ctx.err != 0) {
+ return ctx.err;
+ }
+ return LDB_SUCCESS;
}
/*
const char *ldap_oid, struct ldb_request *parent)
{
unsigned int i;
+ bool values_are_sorted = true;
struct ldb_context *ldb = ldb_module_get_ctx(module);
if (el == NULL) {
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");
- if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ status = dsdb_get_extended_dn_guid(dn, &p->guid, "GUID");
+ 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);
+ 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;
}
} else if (!NT_STATUS_IS_OK(status)) {
return LDB_ERR_OPERATIONS_ERROR;
}
-
+ if (i > 0 && values_are_sorted) {
+ int cmp = parsed_dn_compare(p, &(*pdn)[i - 1]);
+ if (cmp < 0) {
+ values_are_sorted = false;
+ }
+ }
/* keep a pointer to the original ldb_val */
p->v = v;
}
+ if (! values_are_sorted) {
+ TYPESAFE_QSORT(*pdn, el->num_values, parsed_dn_compare);
+ }
+ return LDB_SUCCESS;
+}
- TYPESAFE_QSORT(*pdn, el->num_values, parsed_dn_compare);
+/*
+ * 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;
}
/*
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;
}
*/
static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
- uint64_t seq_num, uint64_t local_usn, NTTIME nttime,
+ uint64_t usn, uint64_t local_usn, NTTIME nttime,
uint32_t version, bool deleted)
{
struct ldb_dn *dn = dsdb_dn->dn;
}
tval = data_blob_string_const(tstring);
- usn_string = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)seq_num);
+ usn_string = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)usn);
if (!usn_string) {
return LDB_ERR_OPERATIONS_ERROR;
}
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,
+ &exact, &next,
+ schema_attr->syntax->ldap_oid);
+ 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)) {
- ldb_asprintf_errstring(ldb, "Attribute %s already exists for target GUID %s",
- el->name, GUID_string(tmp_ctx, p->guid));
+ struct GUID_txt_buf 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 = talloc_new(msg);
+ 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;
}
- 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;
+ tmp_ctx = talloc_new(msg);
+ if (tmp_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
}
- 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) {
- 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;
}
+ if (parent) {
+ vanish_links_ctrl = ldb_request_get_control(parent, DSDB_CONTROL_REPLMD_VANISH_LINKS);
+ if (vanish_links_ctrl) {
+ vanish_links = true;
+ vanish_links_ctrl->critical = false;
+ }
+ }
+
+ /* 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<el->num_values; 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) {
- ldb_asprintf_errstring(ldb, "Attribute %s doesn't exist for target GUID %s",
- el->name, GUID_string(tmp_ctx, p->guid));
- if (ldb_attr_cmp(el->name, "member") == 0) {
- return LDB_ERR_UNWILLING_TO_PERFORM;
- } else {
- return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ 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;
}
}
- rmd_flags = dsdb_dn_rmd_flags(p2->dsdb_dn->dn);
- if (rmd_flags & DSDB_RMD_FLAG_DELETED) {
- ldb_asprintf_errstring(ldb, "Attribute %s already deleted for target GUID %s",
- el->name, GUID_string(tmp_ctx, p->guid));
+
+ 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,
+ &exact, &next,
+ schema_attr->syntax->ldap_oid);
+ 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));
if (ldb_attr_cmp(el->name, "member") == 0) {
+ talloc_free(tmp_ctx);
return LDB_ERR_UNWILLING_TO_PERFORM;
} else {
+ talloc_free(tmp_ctx);
return LDB_ERR_NO_SUCH_ATTRIBUTE;
}
}
- }
- /* 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 (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));
+ }
+ }
- if (el->num_values && parsed_dn_find(dns, el->num_values, p->guid, NULL) == NULL) {
+ /* 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(p->dsdb_dn->dn);
- if (rmd_flags & DSDB_RMD_FLAG_DELETED) continue;
+ rmd_flags = dsdb_dn_rmd_flags(exact->dsdb_dn->dn);
- 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 (rmd_flags & DSDB_RMD_FLAG_DELETED) {
+ struct GUID_txt_buf buf;
+ const char *guid_str = GUID_buf_string(&p->guid, &buf);
+ 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_UNWILLING_TO_PERFORM;
+ } else {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+ }
+
+ 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 ret;
}
-
- ret = replmd_add_backlink(module, schema, msg_guid, old_dns[i].guid, false, schema_attr, true);
+ 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) {
+ 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;
-
- 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;
- }
+ 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;
+ }
- 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;
+ 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;
}
- 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;
- }
- }
+ 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;
+ }
- /* 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_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;
+
+ 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 (seq_num == 0) {
- /* there the replmd_update_rpmd code has already
- * checked and saw that there are no linked
- * attributes */
- return LDB_SUCCESS;
- }
if (dsdb_functional_level(ldb) == DS_DOMAIN_FUNCTION_2000) {
- /* don't do anything special for linked attributes */
+ /*
+ * Nothing special is required for modifying or vanishing links
+ * in fl2000 since they are just strings in a multi-valued
+ * attribute.
+ */
+ struct ldb_control *ctrl = ldb_request_get_control(parent,
+ DSDB_CONTROL_REPLMD_VANISH_LINKS);
+ if (ctrl) {
+ ctrl->critical = false;
+ }
return LDB_SUCCESS;
}
+ /*
+ * TODO:
+ *
+ * We should restrict this to the intersection of the list of
+ * linked attributes in the schema and the list of attributes
+ * being modified.
+ *
+ * This will help performance a little, as otherwise we have
+ * to allocate the entire object value-by-value.
+ */
ret = dsdb_module_search_dn(module, msg, &res, msg->dn, NULL,
DSDB_FLAG_NEXT_MODULE |
DSDB_SEARCH_SHOW_RECYCLED |
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;
old_el = ldb_msg_find_element(old_msg, el->name);
switch (el->flags & LDB_FLAG_MOD_MASK) {
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,
static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
{
- struct samldb_msds_intid_persistant *msds_intid_struct;
struct ldb_context *ldb;
struct replmd_replicated_request *ac;
struct ldb_request *down_req;
time_t t = time(NULL);
int ret;
bool is_urgent = false, rodc = false;
+ bool is_schema_nc = false;
unsigned int functional_level;
- const DATA_BLOB *guid_blob;
+ const struct ldb_message_element *guid_el = NULL;
struct ldb_control *sd_propagation_control;
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(module), struct replmd_private);
/* do not manipulate our control entries */
if (ldb_dn_is_special(req->op.mod.message->dn)) {
ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_modify\n");
- guid_blob = ldb_msg_find_ldb_val(req->op.mod.message, "objectGUID");
- if ( guid_blob != NULL ) {
+ guid_el = ldb_msg_find_element(req->op.mod.message, "objectGUID");
+ if (guid_el != NULL) {
ldb_set_errstring(ldb,
"replmd_modify: it's not allowed to change the objectGUID!");
return LDB_ERR_CONSTRAINT_VIOLATION;
ldb_msg_remove_attr(msg, "whenChanged");
ldb_msg_remove_attr(msg, "uSNChanged");
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
ret = replmd_update_rpmd(module, ac->schema, req, NULL,
- msg, &ac->seq_num, t, &is_urgent, &rodc);
+ msg, &ac->seq_num, t, is_schema_nc,
+ &is_urgent, &rodc);
if (rodc && (ret == LDB_ERR_REFERRAL)) {
struct loadparm_context *lp_ctx;
char *referral;
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;
}
}
- if (!ldb_dn_compare_base(ac->schema->base_dn, msg->dn)) {
- /* Update the usn in the SAMLDB_MSDS_INTID_OPAQUE opaque */
- msds_intid_struct = (struct samldb_msds_intid_persistant *) ldb_get_opaque(ldb, SAMLDB_MSDS_INTID_OPAQUE);
- if (msds_intid_struct) {
- msds_intid_struct->usn = ac->seq_num;
- }
- }
-
/* go on with the call chain */
return ldb_next_request(module, down_req);
}
static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *ares)
{
struct ldb_context *ldb;
- struct replmd_replicated_request *ac;
struct ldb_request *down_req;
struct ldb_message *msg;
const struct dsdb_attribute *rdn_attr;
time_t t = time(NULL);
int ret;
bool is_urgent = false, rodc = false;
+ bool is_schema_nc;
+ struct replmd_replicated_request *ac =
+ talloc_get_type(req->context, struct replmd_replicated_request);
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(ac->module),
+ struct replmd_private);
- ac = talloc_get_type(req->context, struct replmd_replicated_request);
ldb = ldb_module_get_ctx(ac->module);
if (ares->error != LDB_SUCCESS) {
msg->dn = ac->req->op.rename.newdn;
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
rdn_name = ldb_dn_get_rdn_name(msg->dn);
if (rdn_name == NULL) {
talloc_free(ares);
attrs[4] = NULL;
ret = replmd_update_rpmd(ac->module, ac->schema, req, attrs,
- msg, &ac->seq_num, t, &is_urgent, &rodc);
+ 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;
*/
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, &p, &unused,
+ target_attr->syntax->ldap_oid);
+ 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;
- ret = dsdb_module_modify(module, msg, DSDB_FLAG_OWN_MODULE, parent);
+ /*
+ * Ensure that we tell the modification to vanish any linked
+ * attributes (not simply mark them as isDeleted = TRUE)
+ */
+ dsdb_flags |= DSDB_REPLMD_VANISH_LINKS;
+
+ ret = dsdb_module_modify(module, msg, dsdb_flags|DSDB_FLAG_OWN_MODULE, parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
struct ldb_message_element *el;
TALLOC_CTX *tmp_ctx;
struct ldb_result *res, *parent_res;
- const char *preserved_attrs[] = {
+ static const char * const preserved_attrs[] = {
/* yes, this really is a hard coded list. See MS-ADTS
section 3.1.1.5.5.1.1 */
- "nTSecurityDescriptor", "attributeID", "attributeSyntax", "dNReferenceUpdate", "dNSHostName",
- "flatName", "governsID", "groupType", "instanceType", "lDAPDisplayName", "legacyExchangeDN",
- "isDeleted", "isRecycled", "lastKnownParent", "msDS-LastKnownRDN", "mS-DS-CreatorSID",
- "mSMQOwnerID", "nCName", "objectClass", "distinguishedName", "objectGUID", "objectSid",
- "oMSyntax", "proxiedObjectName", "name", "replPropertyMetaData", "sAMAccountName",
- "securityIdentifier", "sIDHistory", "subClassOf", "systemFlags", "trustPartner", "trustDirection",
- "trustType", "trustAttributes", "userAccountControl", "uSNChanged", "uSNCreated", "whenCreated",
- "whenChanged", NULL};
+ "attributeID",
+ "attributeSyntax",
+ "dNReferenceUpdate",
+ "dNSHostName",
+ "flatName",
+ "governsID",
+ "groupType",
+ "instanceType",
+ "lDAPDisplayName",
+ "legacyExchangeDN",
+ "isDeleted",
+ "isRecycled",
+ "lastKnownParent",
+ "msDS-LastKnownRDN",
+ "msDS-PortLDAP",
+ "mS-DS-CreatorSID",
+ "mSMQOwnerID",
+ "nCName",
+ "objectClass",
+ "distinguishedName",
+ "objectGUID",
+ "objectSid",
+ "oMSyntax",
+ "proxiedObjectName",
+ "name",
+ "nTSecurityDescriptor",
+ "replPropertyMetaData",
+ "sAMAccountName",
+ "securityIdentifier",
+ "sIDHistory",
+ "subClassOf",
+ "systemFlags",
+ "trustPartner",
+ "trustDirection",
+ "trustType",
+ "trustAttributes",
+ "userAccountControl",
+ "uSNChanged",
+ "uSNCreated",
+ "whenCreated",
+ "whenChanged",
+ NULL
+ };
+ static const char * const all_attrs[] = {
+ DSDB_SECRET_ATTRIBUTES,
+ "*",
+ NULL
+ };
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)) {
return ldb_next_request(module, req);
}
+ /*
+ * We have to allow dbcheck to remove an object that
+ * is beyond repair, and to do so totally. This could
+ * mean we we can get a partial object from the other
+ * DC, causing havoc, so dbcheck suggests
+ * re-replication first. dbcheck sets both DBCHECK
+ * and RELAX in this situation.
+ */
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)
+ && ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+ /* really, really remove it */
+ return ldb_next_request(module, req);
+ }
+
tmp_ctx = talloc_new(ldb);
if (!tmp_ctx) {
ldb_oom(ldb);
/* we need the complete msg off disk, so we can work out which
attributes need to be removed */
- ret = dsdb_module_search_dn(module, tmp_ctx, &res, old_dn, NULL,
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, old_dn, all_attrs,
DSDB_FLAG_NEXT_MODULE |
DSDB_SEARCH_SHOW_RECYCLED |
DSDB_SEARCH_REVEAL_INTERNALS |
}
if (next_deletion_state == OBJECT_REMOVED) {
- struct auth_session_info *session_info =
- (struct auth_session_info *)ldb_get_opaque(ldb, "sessionInfo");
- if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) {
- ldb_asprintf_errstring(ldb, "Refusing to delete deleted object %s",
- ldb_dn_get_linearized(old_msg->dn));
- return LDB_ERR_UNWILLING_TO_PERFORM;
+ /*
+ * We have to prevent objects being deleted, even if
+ * the administrator really wants them gone, as
+ * without the tombstone, we can get a partial object
+ * from the other DC, causing havoc.
+ *
+ * The only other valid case is when the 180 day
+ * timeout has expired, when relax is specified.
+ */
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+ /* it is already deleted - really remove it this time */
+ talloc_free(tmp_ctx);
+ return ldb_next_request(module, req);
}
- /* it is already deleted - really remove it this time */
- talloc_free(tmp_ctx);
- return ldb_next_request(module, req);
+ ldb_asprintf_errstring(ldb, "Refusing to delete tombstone object %s. "
+ "This check is to prevent corruption of the replicated state.",
+ ldb_dn_get_linearized(old_msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
}
rdn_name = ldb_dn_get_rdn_name(old_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,
ldb_dn_escape_value(tmp_ctx, *rdn_value),
GUID_string(tmp_ctx, &guid));
if (!retb) {
- DEBUG(0,(__location__ ": Unable to add a formatted child to dn: %s",
- ldb_dn_get_linearized(new_dn)));
+ ldb_asprintf_errstring(ldb, __location__
+ ": Unable to add a formatted child to dn: %s",
+ ldb_dn_get_linearized(new_dn));
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
ret = ldb_msg_add_string(msg, "isDeleted", "TRUE");
if (ret != LDB_SUCCESS) {
- DEBUG(0,(__location__ ": Failed to add isDeleted string to the msg\n"));
- ldb_module_oom(module);
+ ldb_asprintf_errstring(ldb, __location__
+ ": Failed to add isDeleted string to the msg");
talloc_free(tmp_ctx);
return ret;
}
struct ldb_dn *rdn = ldb_dn_copy(tmp_ctx, old_dn);
retb = ldb_dn_remove_base_components(rdn, ldb_dn_get_comp_num(rdn) - 1);
if (!retb) {
- DEBUG(0,(__location__ ": Unable to add a prepare rdn of %s",
- ldb_dn_get_linearized(rdn)));
+ ldb_asprintf_errstring(ldb, __location__
+ ": Unable to add a prepare rdn of %s",
+ ldb_dn_get_linearized(rdn));
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
retb = ldb_dn_add_child(new_dn, rdn);
if (!retb) {
- DEBUG(0,(__location__ ": Unable to add rdn %s to base dn: %s",
- ldb_dn_get_linearized(rdn),
- ldb_dn_get_linearized(new_dn)));
+ ldb_asprintf_errstring(ldb, __location__
+ ": Unable to add rdn %s to base dn: %s",
+ ldb_dn_get_linearized(rdn),
+ ldb_dn_get_linearized(new_dn));
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
*/
if (deletion_state == OBJECT_NOT_DELETED) {
+ struct ldb_dn *parent_dn = ldb_dn_get_parent(tmp_ctx, old_dn);
+ char *parent_dn_str = NULL;
+
/* we need the storage form of the parent GUID */
ret = dsdb_module_search_dn(module, tmp_ctx, &parent_res,
- ldb_dn_get_parent(tmp_ctx, old_dn), NULL,
+ parent_dn, NULL,
DSDB_FLAG_NEXT_MODULE |
DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
DSDB_SEARCH_REVEAL_INTERNALS|
DSDB_SEARCH_SHOW_RECYCLED, req);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb_module_get_ctx(module),
- "repmd_delete: Failed to %s %s, because we failed to find it's parent (%s): %s",
+ "repmd_delete: Failed to %s %s, "
+ "because we failed to find it's parent (%s): %s",
re_delete ? "re-delete" : "delete",
ldb_dn_get_linearized(old_dn),
- ldb_dn_get_linearized(ldb_dn_get_parent(tmp_ctx, old_dn)),
+ ldb_dn_get_linearized(parent_dn),
ldb_errstring(ldb_module_get_ctx(module)));
talloc_free(tmp_ctx);
return ret;
}
+ /*
+ * Now we can use the DB version,
+ * it will have the extended DN info in it
+ */
+ parent_dn = parent_res->msgs[0]->dn;
+ parent_dn_str = ldb_dn_get_extended_linearized(tmp_ctx,
+ parent_dn,
+ 1);
+ if (parent_dn_str == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
ret = ldb_msg_add_steal_string(msg, "lastKnownParent",
- ldb_dn_get_extended_linearized(tmp_ctx, parent_res->msgs[0]->dn, 1));
+ parent_dn_str);
if (ret != LDB_SUCCESS) {
- DEBUG(0,(__location__ ": Failed to add lastKnownParent string to the msg\n"));
- ldb_module_oom(module);
+ ldb_asprintf_errstring(ldb, __location__
+ ": Failed to add lastKnownParent "
+ "string when deleting %s",
+ ldb_dn_get_linearized(old_dn));
talloc_free(tmp_ctx);
return ret;
}
if (next_deletion_state == OBJECT_DELETED) {
ret = ldb_msg_add_value(msg, "msDS-LastKnownRDN", rdn_value, NULL);
if (ret != LDB_SUCCESS) {
- DEBUG(0,(__location__ ": Failed to add msDS-LastKnownRDN string to the msg\n"));
- ldb_module_oom(module);
+ ldb_asprintf_errstring(ldb, __location__
+ ": Failed to add msDS-LastKnownRDN "
+ "string when deleting %s",
+ ldb_dn_get_linearized(old_dn));
talloc_free(tmp_ctx);
return ret;
}
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);
+ ldb_asprintf_errstring(ldb,
+ __location__
+ ": Failed to remove backlink of "
+ "%s when deleting %s: %s",
+ el->name,
+ old_dn_str,
+ ldb_errstring(ldb));
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
directly
*/
continue;
- }
- if (!sa->linkID) {
+ } else if (sa->linkID == 0) {
if (ldb_attr_in_list(preserved_attrs, el->name)) {
continue;
}
if (sa->searchFlags & SEARCH_FLAG_PRESERVEONDELETE) {
continue;
}
+ } else {
+ /*
+ * Ensure that we tell the modification to vanish any linked
+ * attributes (not simply mark them as isDeleted = TRUE)
+ */
+ dsdb_flags |= DSDB_REPLMD_VANISH_LINKS;
}
ret = ldb_msg_add_empty(msg, el->name, LDB_FLAG_MOD_DELETE, &el);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
ldb_module_oom(module);
return ret;
- }
- }
-
- /* Duplicate with the below - we remove the
- * samAccountType as an originating update, in case it
- * somehow came back. The objectCategory will have
- * gone in the above */
- ret = ldb_msg_add_empty(msg, "sAMAccountType", LDB_FLAG_MOD_REPLACE, NULL);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- ldb_module_oom(module);
- return ret;
+ }
}
break;
*
*/
- ret = dsdb_module_modify(module, msg, DSDB_FLAG_OWN_MODULE, req);
- if (ret != LDB_SUCCESS) {
- ldb_asprintf_errstring(ldb, "replmd_delete: Failed to modify object %s in delete - %s",
- ldb_dn_get_linearized(old_dn), ldb_errstring(ldb));
- talloc_free(tmp_ctx);
- return ret;
- }
-
/*
* No matter what has happned with other renames, try again to
* get this to be under the deleted DN.
talloc_free(tmp_ctx);
return ret;
}
+ msg->dn = new_dn;
+ }
+
+ ret = dsdb_module_modify(module, msg, dsdb_flags|DSDB_FLAG_OWN_MODULE, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "replmd_delete: Failed to modify object %s in delete - %s",
+ ldb_dn_get_linearized(old_dn), ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
}
talloc_free(tmp_ctx);
{
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
{
struct replmd_replicated_request *ar =
talloc_get_type_abort(req->context, struct replmd_replicated_request);
- struct ldb_dn *conflict_dn;
+ struct ldb_dn *conflict_dn = NULL;
int ret;
if (ares->error != LDB_SUCCESS) {
}
/*
- 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) {
* 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));
/*
* 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;
}
- DEBUG(2,(__location__ ": Resolving conflict record via existing rename '%s' -> '%s'\n",
+ 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,
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);
}
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->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 == NULL) {
+ if (ar->search_msg != NULL) {
+ return replmd_replicated_apply_merge(ar);
+ } else {
+ return replmd_replicated_apply_add(ar);
+ }
+ }
+
+ 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_NOT_ENOUGH_MEMORY);
+
+ ret = ldb_build_search_req(&search_req,
+ ldb,
+ ar,
+ ar->objs->partition_dn,
+ LDB_SCOPE_SUBTREE,
+ filter,
+ attrs,
+ NULL,
+ ar,
+ replmd_replicated_apply_search_for_parent_callback,
+ ar->req);
+ LDB_REQ_SET_LOCATION(search_req);
+
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_SEARCH_SHOW_RECYCLED|
+ DSDB_SEARCH_SHOW_DELETED|
+ DSDB_SEARCH_SHOW_EXTENDED_DN);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ar->module, search_req);
+}
+
+/*
+ handle renames that come in over DRS replication
+ */
+static int replmd_replicated_handle_rename(struct replmd_replicated_request *ar,
+ struct ldb_message *msg,
+ struct ldb_request *parent,
+ bool *renamed)
+{
+ 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)));
+
+
+ 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);
+ *renamed = true;
+ return ret;
+ }
+
+ 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 = 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_ERR_OPERATIONS_ERROR;
+ }
+ /*
+ * we have a conflict, and need to decide if we will keep the
+ * new record or the old record
+ */
+
+ conflict_dn = msg->dn;
+
+ 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;
+ }
- ldb = ldb_module_get_ctx(ar->module);
+ rmd = ar->objs->objects[ar->index_current].meta_data;
- if (!ar->objs->objects[ar->index_current].parent_guid_value.data) {
- if (ar->search_msg != NULL) {
- return replmd_replicated_apply_merge(ar);
- } else {
- return replmd_replicated_apply_add(ar);
- }
+ /*
+ * 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;
}
- 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);
+ /*
+ * 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);
- filter = talloc_asprintf(ar, "(objectGUID=%s)", tmp_str);
- if (!filter) return replmd_replicated_request_werror(ar, WERR_NOMEM);
- talloc_free(tmp_str);
+ if (rename_incoming_record) {
- ret = ldb_build_search_req(&search_req,
- ldb,
- ar,
- ar->objs->partition_dn,
- LDB_SCOPE_SUBTREE,
- filter,
- attrs,
- NULL,
- ar,
- replmd_replicated_apply_search_for_parent_callback,
- ar->req);
- LDB_REQ_SET_LOCATION(search_req);
+ 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));
- ret = dsdb_request_add_controls(search_req,
- DSDB_SEARCH_SHOW_RECYCLED|
- DSDB_SEARCH_SHOW_DELETED|
- DSDB_SEARCH_SHOW_EXTENDED_DN);
- if (ret != LDB_SUCCESS) {
- return ret;
- }
+ return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+ }
- return ldb_next_request(ar->module, search_req);
-}
+ 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);
+ }
-/*
- handle renames that come in over DRS replication
- */
-static int replmd_replicated_handle_rename(struct replmd_replicated_request *ar,
- struct ldb_message *msg,
- struct ldb_request *parent)
-{
- struct ldb_request *req;
- int ret;
- TALLOC_CTX *tmp_ctx = talloc_new(msg);
- struct ldb_result *res;
+ msg->dn = new_dn;
+ *renamed = true;
+ return LDB_SUCCESS;
+ }
- DEBUG(4,("replmd_replicated_request rename %s => %s\n",
- ldb_dn_get_linearized(ar->search_msg->dn),
- ldb_dn_get_linearized(msg->dn)));
+ /* 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;
+ }
- res = talloc_zero(tmp_ctx, struct ldb_result);
- if (!res) {
- talloc_free(tmp_ctx);
- return ldb_oom(ldb_module_get_ctx(ar->module));
+ 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;
}
- /* 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);
+ 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) {
- talloc_free(tmp_ctx);
- return ret;
+ 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;
}
- ret = dsdb_request_add_controls(req, DSDB_MODIFY_RELAX);
+ /*
+ * 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) {
- talloc_free(tmp_ctx);
- return ret;
+ goto failed;
}
- ret = ldb_next_request(ar->module, req);
+ 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)));
- if (ret == LDB_SUCCESS) {
- ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+
+ 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;
nmd.ctr.ctr1.array = talloc_array(ar,
struct replPropertyMetaData1,
nmd.ctr.ctr1.count);
- if (!nmd.ctr.ctr1.array) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ if (!nmd.ctr.ctr1.array) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
/* first copy the old meta data */
for (i=0; i < omd.ctr.ctr1.count; i++) {
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[] = { "repsFrom", "replUpToDateVector",
+ "parentGUID", "instanceType",
+ "replPropertyMetaData", "nTSecurityDescriptor",
+ "isDeleted", 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);
+ if (!filter) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
ret = ldb_build_search_req(&search_req,
ldb,
ar,
- NULL,
+ ar->objs->partition_dn,
LDB_SCOPE_SUBTREE,
filter,
- NULL,
+ attrs,
NULL,
ar,
replmd_replicated_apply_search_callback,
ar->req);
LDB_REQ_SET_LOCATION(search_req);
- ret = dsdb_request_add_controls(search_req, DSDB_SEARCH_SEARCH_ALL_PARTITIONS|DSDB_SEARCH_SHOW_RECYCLED);
+ ret = dsdb_request_add_controls(search_req, DSDB_SEARCH_SHOW_RECYCLED);
if (ret != LDB_SUCCESS) {
return ret;
struct replUpToDateVectorBlob nuv;
struct ldb_val nuv_value;
struct ldb_message_element *nuv_el = NULL;
- const struct GUID *our_invocation_id;
struct ldb_message_element *orf_el = NULL;
struct repsFromToBlob nrf;
struct ldb_val *nrf_value = NULL;
nuv.ctr.ctr2.cursors = talloc_array(ar,
struct drsuapi_DsReplicaCursor2,
nuv.ctr.ctr2.count);
- if (!nuv.ctr.ctr2.cursors) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ if (!nuv.ctr.ctr2.cursors) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
/* first copy the old vector */
for (i=0; i < ouv.ctr.ctr2.count; i++) {
ni++;
}
- /* get our invocation_id if we have one already attached to the ldb */
- our_invocation_id = samdb_ntds_invocation_id(ldb);
- if (our_invocation_id == NULL) {
- DEBUG(0, ("repl_meta_data: Could not find our own server's invocationID!\n"));
- return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
- }
-
/* merge in the source_dsa vector is available */
for (i=0; (ruv && i < ruv->count); i++) {
found = false;
- if (our_invocation_id &&
- GUID_equal(&ruv->cursors[i].source_dsa_invocation_id,
- our_invocation_id)) {
+ if (GUID_equal(&ruv->cursors[i].source_dsa_invocation_id,
+ &ar->our_invocation_id)) {
continue;
}
* create the change ldb_message
*/
msg = ldb_msg_new(ar);
- if (!msg) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ if (!msg) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
msg->dn = ar->search_msg->dn;
ndr_err = ndr_push_struct_blob(&nuv_value, msg, &nuv,
struct repsFromToBlob *trf;
trf = talloc(ar, struct repsFromToBlob);
- if (!trf) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ if (!trf) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
ndr_err = ndr_pull_struct_blob(&orf_el->values[i], trf, trf,
(ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
static int replmd_replicated_uptodate_vector(struct replmd_replicated_request *ar)
{
- struct ldb_context *ldb;
+ struct ldb_context *ldb = ldb_module_get_ctx(ar->module);
+ struct replmd_private *replmd_private =
+ talloc_get_type_abort(ldb_module_get_private(ar->module),
+ struct replmd_private);
int ret;
static const char *attrs[] = {
"replUpToDateVector",
};
struct ldb_request *search_req;
- ldb = ldb_module_get_ctx(ar->module);
ar->search_msg = NULL;
+ /*
+ * Let the caller know that we did an originating updates
+ */
+ ar->objs->originating_updates = replmd_private->originating_updates;
+
ret = ldb_build_search_req(&search_req,
ldb,
ar,
uint32_t i;
struct replmd_private *replmd_private =
talloc_get_type(ldb_module_get_private(module), struct replmd_private);
- struct dsdb_control_replicated_update *rep_update;
ldb = ldb_module_get_ctx(module);
if (req->controls) {
req->controls = talloc_memdup(ar, req->controls,
talloc_get_size(req->controls));
- if (!req->controls) return replmd_replicated_request_werror(ar, WERR_NOMEM);
- }
-
- /* This allows layers further down to know if a change came in
- over replication and what the replication flags were */
- rep_update = talloc_zero(ar, struct dsdb_control_replicated_update);
- if (rep_update == NULL) {
- return ldb_module_oom(module);
+ if (!req->controls) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
}
- rep_update->dsdb_repl_flags = objs->dsdb_repl_flags;
- ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, rep_update);
+ ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, NULL);
if (ret != LDB_SUCCESS) {
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 ldb_result *target_res;
const char *attrs[4];
const char *attrs2[] = { "isDeleted", "isRecycled", NULL };
- struct parsed_dn *pdn_list, *pdn;
+ struct parsed_dn *pdn_list, *pdn, *next;
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;
/* find the attribute being modified */
attr = dsdb_attribute_by_attributeID_id(schema, la->attid);
if (attr == NULL) {
- DEBUG(0, (__location__ ": Unable to find attributeID 0x%x\n", la->attid));
+ struct GUID_txt_buf guid_str;
+ ldb_asprintf_errstring(ldb, "Unable to find attributeID 0x%x for link on <GUID=%s>",
+ la->attid,
+ GUID_buf_string(&la->identifier->guid,
+ &guid_str));
talloc_free(tmp_ctx);
return LDB_ERR_OPERATIONS_ERROR;
}
}
/* parse the existing links */
- ret = get_parsed_dns(module, 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 = get_parsed_dns_trusted(module, replmd_private, tmp_ctx, old_el, &pdn_list,
+ attr->syntax->ldap_oid, parent);
- 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;
}
/* see if this link already exists */
- pdn = parsed_dn_find(pdn_list, old_el->num_values, &guid, dsdb_dn->dn);
+ ret = parsed_dn_find(ldb, pdn_list, old_el->num_values,
+ &guid,
+ dsdb_dn->dn,
+ &pdn, &next,
+ attr->syntax->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+
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, false);
+ 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, false);
+ 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,
la->meta_data.version,
- (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)?false:true);
+ !active);
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);
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ msg->dn,
+ &guid, true, attr,
+ parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
old_el->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
- ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
+ ret = linked_attr_modify(module, msg, parent);
if (ret != LDB_SUCCESS) {
ldb_debug(ldb, LDB_DEBUG_WARNING, "Failed to apply linked attribute change '%s'\n%s\n",
ldb_errstring(ldb),
talloc_free(e);
}
+ replmd_private->originating_updates = false;
+
return ldb_next_start_trans(module);
}
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;