+
+/*
+ * update the replPropertyMetaData for one element
+ */
+static int replmd_update_rpmd_element(struct ldb_context *ldb,
+ struct ldb_message *msg,
+ struct ldb_message_element *el,
+ struct replPropertyMetaDataBlob *omd,
+ const struct dsdb_schema *schema,
+ uint64_t *seq_num,
+ const struct GUID *our_invocation_id,
+ NTTIME now)
+{
+ int i;
+ const struct dsdb_attribute *a;
+ struct replPropertyMetaData1 *md1;
+
+ a = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (a == NULL) {
+ DEBUG(0,(__location__ ": Unable to find attribute %s in schema\n",
+ el->name));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if ((a->systemFlags & DS_FLAG_ATTR_NOT_REPLICATED) || (a->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED)) {
+ return LDB_SUCCESS;
+ }
+
+ for (i=0; i<omd->ctr.ctr1.count; i++) {
+ if (a->attributeID_id == omd->ctr.ctr1.array[i].attid) break;
+ }
+
+#if W2K3_LINKED_ATTRIBUTES
+ 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 */
+ if (*seq_num == 0 &&
+ ldb_sequence_number(ldb, LDB_SEQ_NEXT, seq_num) != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ return LDB_SUCCESS;
+ }
+#endif
+
+ if (i == omd->ctr.ctr1.count) {
+ /* we need to add a new one */
+ omd->ctr.ctr1.array = talloc_realloc(msg, omd->ctr.ctr1.array,
+ struct replPropertyMetaData1, omd->ctr.ctr1.count+1);
+ if (omd->ctr.ctr1.array == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ omd->ctr.ctr1.count++;
+ ZERO_STRUCT(omd->ctr.ctr1.array[i]);
+ }
+
+ /* Get a new sequence number from the backend. We only do this
+ * if we have a change that requires a new
+ * replPropertyMetaData element
+ */
+ if (*seq_num == 0) {
+ int ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, seq_num);
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ md1 = &omd->ctr.ctr1.array[i];
+ md1->version++;
+ md1->attid = a->attributeID_id;
+ md1->originating_change_time = now;
+ md1->originating_invocation_id = *our_invocation_id;
+ md1->originating_usn = *seq_num;
+ md1->local_usn = *seq_num;
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * update the replPropertyMetaData object each time we modify an
+ * object. This is needed for DRS replication, as the merge on the
+ * client is based on this object
+ */
+static int replmd_update_rpmd(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ struct ldb_message *msg, uint64_t *seq_num,
+ time_t t)
+{
+ const struct ldb_val *omd_value;
+ enum ndr_err_code ndr_err;
+ struct replPropertyMetaDataBlob omd;
+ int i;
+ NTTIME now;
+ const struct GUID *our_invocation_id;
+ int ret;
+ const char *attrs[] = { "replPropertyMetaData" , NULL };
+ struct ldb_result *res;
+ struct ldb_context *ldb;
+
+ ldb = ldb_module_get_ctx(module);
+
+ our_invocation_id = samdb_ntds_invocation_id(ldb);
+ if (!our_invocation_id) {
+ /* this happens during an initial vampire while
+ updating the schema */
+ DEBUG(5,("No invocationID - skipping replPropertyMetaData update\n"));
+ return LDB_SUCCESS;
+ }
+
+ unix_to_nt_time(&now, t);
+
+ /* search for the existing replPropertyMetaDataBlob */
+ ret = dsdb_search_dn_with_deleted(ldb, msg, &res, msg->dn, attrs);
+ if (ret != LDB_SUCCESS || res->count != 1) {
+ DEBUG(0,(__location__ ": Object %s failed to find replPropertyMetaData\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+
+ omd_value = ldb_msg_find_ldb_val(res->msgs[0], "replPropertyMetaData");
+ if (!omd_value) {
+ DEBUG(0,(__location__ ": Object %s does not have a replPropertyMetaData attribute\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ndr_err = ndr_pull_struct_blob(omd_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), &omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (omd.version != 1) {
+ DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s\n",
+ omd.version, ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i<msg->num_elements; i++) {
+ ret = replmd_update_rpmd_element(ldb, msg, &msg->elements[i], &omd, schema, seq_num,
+ our_invocation_id, now);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /*
+ * replmd_update_rpmd_element has done an update if the
+ * seq_num is set
+ */
+ if (*seq_num != 0) {
+ struct ldb_val *md_value;
+ struct ldb_message_element *el;
+
+ md_value = talloc(msg, struct ldb_val);
+ if (md_value == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = replmd_replPropertyMetaDataCtr1_sort(&omd.ctr.ctr1, schema, msg->dn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(md_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
+ &omd,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to marshall replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_msg_add_empty(msg, "replPropertyMetaData", LDB_FLAG_MOD_REPLACE, &el);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to add updated replPropertyMetaData %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return ret;
+ }
+
+ el->num_values = 1;
+ el->values = md_value;
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+struct parsed_dn {
+ struct dsdb_dn *dsdb_dn;
+ struct GUID *guid;
+ struct ldb_val *v;
+};
+
+static int parsed_dn_compare(struct parsed_dn *pdn1, struct parsed_dn *pdn2)
+{
+ return GUID_compare(pdn1->guid, pdn2->guid);
+}
+
+static struct parsed_dn *parsed_dn_find(struct parsed_dn *pdn, int count, struct GUID *guid)
+{
+ struct parsed_dn *ret;
+ BINARY_ARRAY_SEARCH(pdn, count, guid, guid, GUID_compare, ret);
+ return ret;
+}
+
+/*
+ get a series of message element values as an array of DNs and GUIDs
+ the result is sorted by GUID
+ */
+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)
+{
+ int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ if (el == NULL) {
+ *pdn = NULL;
+ return LDB_SUCCESS;
+ }
+
+ (*pdn) = talloc_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++) {
+ struct ldb_val *v = &el->values[i];
+ NTSTATUS status;
+ struct ldb_dn *dn;
+ struct parsed_dn *p;
+
+ p = &(*pdn)[i];
+
+ p->dsdb_dn = dsdb_dn_parse(*pdn, ldb, v, ldap_oid);
+ if (p->dsdb_dn == NULL) {
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ 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)) {
+ /* we got a DN without a GUID - go find the GUID */
+ int ret = dsdb_find_guid_by_dn(ldb, dn, p->guid);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Unable to find GUID for DN %s\n",
+ ldb_dn_get_linearized(dn));
+ return ret;
+ }
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* keep a pointer to the original ldb_val */
+ p->v = v;
+ }
+
+ qsort(*pdn, el->num_values, sizeof((*pdn)[0]), (comparison_fn_t)parsed_dn_compare);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ build a new extended DN, including all meta data fields
+
+ DELETED = 1 or missing
+ RMD_ADDTIME = originating_add_time
+ RMD_INVOCID = originating_invocation_id
+ RMD_CHANGETIME = originating_change_time
+ RMD_ORIGINATING_USN = originating_usn
+ RMD_LOCAL_USN = local_usn
+ RMD_VERSION = version
+ */
+static int replmd_build_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
+ const struct GUID *invocation_id, uint64_t seq_num,
+ uint64_t local_usn, NTTIME nttime, bool deleted)
+{
+ struct ldb_dn *dn = dsdb_dn->dn;
+ const char *tstring, *usn_string;
+ struct ldb_val tval;
+ struct ldb_val iid;
+ struct ldb_val usnv, local_usnv;
+ struct ldb_val vers;
+ NTSTATUS status;
+ int ret;
+ const char *dnstring;
+
+ tstring = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)nttime);
+ if (!tstring) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ tval = data_blob_string_const(tstring);
+
+ usn_string = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)seq_num);
+ if (!usn_string) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ usnv = data_blob_string_const(usn_string);
+
+ usn_string = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)local_usn);
+ if (!usn_string) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ local_usnv = data_blob_string_const(usn_string);
+
+ vers = data_blob_string_const("0");
+
+ status = GUID_to_ndr_blob(invocation_id, dn, &iid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (deleted) {
+ struct ldb_val dv;
+ dv = data_blob_string_const("1");
+ ret = ldb_dn_set_extended_component(dn, "DELETED", &dv);
+ } else {
+ ret = ldb_dn_set_extended_component(dn, "DELETED", NULL);
+ }
+ if (ret != LDB_SUCCESS) return ret;
+ ret = ldb_dn_set_extended_component(dn, "RMD_ADDTIME", &tval);
+ if (ret != LDB_SUCCESS) return ret;
+ ret = ldb_dn_set_extended_component(dn, "RMD_INVOCID", &iid);
+ if (ret != LDB_SUCCESS) return ret;
+ ret = ldb_dn_set_extended_component(dn, "RMD_CHANGETIME", &tval);
+ if (ret != LDB_SUCCESS) return ret;
+ ret = ldb_dn_set_extended_component(dn, "RMD_LOCAL_USN", &local_usnv);
+ if (ret != LDB_SUCCESS) return ret;
+ ret = ldb_dn_set_extended_component(dn, "RMD_ORIGINATING_USN", &usnv);
+ if (ret != LDB_SUCCESS) return ret;
+ ret = ldb_dn_set_extended_component(dn, "RMD_VERSION", &vers);
+ if (ret != LDB_SUCCESS) return ret;
+
+ dnstring = dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 1);
+ if (dnstring == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *v = data_blob_string_const(dnstring);
+
+ 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, bool deleted);
+
+/*
+ check if any links need upgrading from w2k format
+ */
+static int replmd_check_upgrade_links(struct parsed_dn *dns, uint32_t count, const struct GUID *invocation_id)
+{
+ int i;
+ for (i=0; i<count; i++) {
+ NTSTATUS status;
+ uint32_t version;
+ int ret;
+
+ 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)) {
+ continue;
+ }
+
+ /* it's an old one that needs upgrading */
+ ret = replmd_update_la_val(dns, dns[i].v, dns[i].dsdb_dn, dns[i].dsdb_dn, invocation_id,
+ 1, 1, 0, false);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+/*
+ update an extended DN, including all meta data fields
+
+ see replmd_build_la_val for value names
+ */
+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, bool deleted)
+{
+ struct ldb_dn *dn = dsdb_dn->dn;
+ const char *tstring, *usn_string;
+ struct ldb_val tval;
+ struct ldb_val iid;
+ struct ldb_val usnv, local_usnv;
+ struct ldb_val vers;
+ const struct ldb_val *old_addtime, *old_version;
+ NTSTATUS status;
+ int ret;
+ const char *dnstring;
+
+ tstring = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)nttime);
+ if (!tstring) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ tval = data_blob_string_const(tstring);
+
+ usn_string = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)seq_num);
+ if (!usn_string) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ usnv = data_blob_string_const(usn_string);
+
+ usn_string = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)local_usn);
+ if (!usn_string) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ local_usnv = data_blob_string_const(usn_string);
+
+ status = GUID_to_ndr_blob(invocation_id, dn, &iid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (deleted) {
+ struct ldb_val dv;
+ dv = data_blob_string_const("1");
+ ret = ldb_dn_set_extended_component(dn, "DELETED", &dv);
+ } else {
+ ret = ldb_dn_set_extended_component(dn, "DELETED", NULL);
+ }
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* get the ADDTIME from the original */
+ old_addtime = ldb_dn_get_extended_component(old_dsdb_dn->dn, "RMD_ADDTIME");
+ if (old_addtime == NULL) {
+ old_addtime = &tval;
+ }
+ if (dsdb_dn != old_dsdb_dn) {
+ ret = ldb_dn_set_extended_component(dn, "RMD_ADDTIME", old_addtime);
+ if (ret != LDB_SUCCESS) return ret;
+ }
+
+ /* use our invocation id */
+ ret = ldb_dn_set_extended_component(dn, "RMD_INVOCID", &iid);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* changetime is the current time */
+ ret = ldb_dn_set_extended_component(dn, "RMD_CHANGETIME", &tval);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* update the USN */
+ ret = ldb_dn_set_extended_component(dn, "RMD_ORIGINATING_USN", &usnv);
+ if (ret != LDB_SUCCESS) return ret;
+
+ ret = ldb_dn_set_extended_component(dn, "RMD_LOCAL_USN", &local_usnv);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* increase the version by 1 */
+ old_version = ldb_dn_get_extended_component(old_dsdb_dn->dn, "RMD_VERSION");
+ if (old_version == NULL) {
+ vers = data_blob_string_const("0");
+ } else {
+ char *vstring;
+ vstring = talloc_strndup(dn, (const char *)old_version->data, old_version->length);
+ if (!vstring) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ vstring = talloc_asprintf(dn, "%lu",
+ (unsigned long)strtoul(vstring, NULL, 0)+1);
+ vers = data_blob_string_const(vstring);
+ }
+ ret = ldb_dn_set_extended_component(dn, "RMD_VERSION", &vers);
+ if (ret != LDB_SUCCESS) return ret;
+
+ dnstring = dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 1);
+ if (dnstring == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *v = data_blob_string_const(dnstring);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ handle adding a linked attribute
+ */
+static int replmd_modify_la_add(struct ldb_module *module,
+ struct dsdb_schema *schema,
+ struct ldb_message *msg,
+ struct ldb_message_element *el,
+ struct ldb_message_element *old_el,
+ const struct dsdb_attribute *schema_attr,
+ uint64_t seq_num,
+ time_t t,
+ struct GUID *msg_guid)
+{
+ int i;
+ 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;
+ 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);
+ 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);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ invocation_id = samdb_ntds_invocation_id(ldb);
+ if (!invocation_id) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = replmd_check_upgrade_links(old_dns, old_num_values, invocation_id);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* for each new value, see if it exists already with the same GUID */
+ for (i=0; i<el->num_values; i++) {
+ struct parsed_dn *p = parsed_dn_find(old_dns, old_num_values, dns[i].guid);
+ 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, 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. */
+ const struct ldb_val *v;
+ v = ldb_dn_get_extended_component(p->dsdb_dn->dn, "DELETED");
+ if (v == NULL) {
+ ldb_asprintf_errstring(ldb, "Attribute %s already exists for target GUID %s",
+ el->name, GUID_string(tmp_ctx, p->guid));
+ talloc_free(tmp_ctx);
+ 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, false);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ ret = replmd_add_backlink(module, schema, msg_guid, dns[i].guid, true, schema_attr, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ /* 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;
+ }
+
+ 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_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 deleting all active linked attributes
+ */
+static int replmd_modify_la_delete(struct ldb_module *module,
+ struct dsdb_schema *schema,
+ struct ldb_message *msg,
+ struct ldb_message_element *el,
+ struct ldb_message_element *old_el,
+ const struct dsdb_attribute *schema_attr,
+ uint64_t seq_num,
+ time_t t,
+ struct GUID *msg_guid)
+{
+ int 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);
+ 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;
+ }
+
+ if (!old_el || old_el->num_values == 0) {
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns, schema_attr->syntax->ldap_oid);
+ 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);
+ 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_el->num_values, invocation_id);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ 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;
+ const struct ldb_val *v;
+
+ p2 = parsed_dn_find(old_dns, old_el->num_values, p->guid);
+ if (!p2) {
+ ldb_asprintf_errstring(ldb, "Attribute %s doesn't exist for target GUID %s",
+ el->name, GUID_string(tmp_ctx, p->guid));
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+ v = ldb_dn_get_extended_component(p2->dsdb_dn->dn, "DELETED");
+ if (v) {
+ ldb_asprintf_errstring(ldb, "Attribute %s already deleted for target GUID %s",
+ el->name, GUID_string(tmp_ctx, p->guid));
+ 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];
+ const struct ldb_val *v;
+
+ if (dns && parsed_dn_find(dns, el->num_values, p->guid) == NULL) {
+ continue;
+ }
+
+ v = ldb_dn_get_extended_component(p->dsdb_dn->dn, "DELETED");
+ if (v != NULL) continue;
+
+ ret = replmd_update_la_val(old_el->values, p->v, p->dsdb_dn, p->dsdb_dn,
+ invocation_id, seq_num, seq_num, now, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = replmd_add_backlink(module, schema, msg_guid, old_dns[i].guid, false, schema_attr, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ el->values = talloc_steal(msg->elements, old_el->values);
+ el->num_values = old_el->num_values;
+
+ 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 replacing a linked attribute
+ */
+static int replmd_modify_la_replace(struct ldb_module *module,
+ struct dsdb_schema *schema,
+ struct ldb_message *msg,
+ struct ldb_message_element *el,
+ struct ldb_message_element *old_el,
+ const struct dsdb_attribute *schema_attr,
+ uint64_t seq_num,
+ time_t t,
+ struct GUID *msg_guid)
+{
+ int 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;
+ uint32_t num_new_values = 0;
+ unsigned old_num_values = old_el?old_el->num_values:0;
+ 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) {
+ return LDB_SUCCESS;
+ }
+
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns, schema_attr->syntax->ldap_oid);
+ 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);
+ 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, invocation_id);
+ 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;
+ const struct ldb_val *v;
+
+ v = ldb_dn_get_extended_component(old_p->dsdb_dn->dn, "DELETED");
+ if (v) 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;
+ }
+
+ p = parsed_dn_find(dns, el->num_values, old_p->guid);
+ if (p) {
+ /* we don't delete it if we are re-adding it */
+ continue;
+ }
+
+ 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, 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) {
+ /* update in place */
+ 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, false);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ } 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);
+ 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, false);
+ 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;
+ }
+ }
+
+ /* 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);
+ }
+
+ 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 ldb_message *msg,
+ uint64_t seq_num, time_t t)
+{
+ struct ldb_result *res;
+ int ret, i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *old_msg;
+ struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ 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 !W2K3_LINKED_ATTRIBUTES
+ return LDB_SUCCESS;
+#endif
+
+ if (dsdb_functional_level(ldb) == DS_DOMAIN_FUNCTION_2000) {
+ /* don't do anything special for linked attributes */
+ return LDB_SUCCESS;
+ }
+
+ ret = dsdb_module_search_dn(module, msg, &res, msg->dn, NULL,
+ DSDB_SEARCH_SHOW_DELETED |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ 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;
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(ldb,
+ "attribute %s is not a valid attribute in schema", el->name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+ if ((schema_attr->linkID & 1) == 1) {
+ /* Odd is for the target. Illegal to modify */
+ ldb_asprintf_errstring(ldb,
+ "attribute %s must not be modified directly, it is a linked attribute", el->name);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ 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);
+ break;
+ case LDB_FLAG_MOD_DELETE:
+ ret = replmd_modify_la_delete(module, schema, msg, el, old_el, schema_attr, seq_num, t, &old_guid);
+ break;
+ case LDB_FLAG_MOD_ADD:
+ ret = replmd_modify_la_add(module, schema, msg, el, old_el, schema_attr, seq_num, t, &old_guid);
+ break;
+ default:
+ ldb_asprintf_errstring(ldb,
+ "invalid flags 0x%x for %s linked attribute",
+ el->flags, el->name);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (old_el) {
+ ldb_msg_remove_attr(old_msg, el->name);
+ }
+ ldb_msg_add_empty(old_msg, el->name, 0, &new_el);
+ new_el->num_values = el->num_values;
+ new_el->values = el->values;
+
+ /* TODO: this relises a bit too heavily on the exact
+ behaviour of ldb_msg_find_element and
+ ldb_msg_remove_element */
+ old_el = ldb_msg_find_element(msg, el->name);
+ if (old_el != el) {
+ ldb_msg_remove_element(msg, old_el);
+ i--;
+ }
+ }
+
+ talloc_free(res);
+ return ret;
+}
+
+
+