diff options
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 585 |
1 files changed, 566 insertions, 19 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index 3d31cc3f49..7e1d7189b1 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -46,6 +46,9 @@ #include "libcli/security/dom_sid.h" #include "lib/util/dlinklist.h" #include "dsdb/samdb/ldb_modules/util.h" +#include "lib/util/binsearch.h" + +#define W2K3_LINKED_ATTRIBUTES 1 struct replmd_private { TALLOC_CTX *la_ctx; @@ -680,6 +683,20 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb, 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, @@ -721,13 +738,13 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb, */ static int replmd_update_rpmd(struct ldb_module *module, const struct dsdb_schema *schema, - struct ldb_message *msg, uint64_t *seq_num) + 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; - time_t t = time(NULL); NTTIME now; const struct GUID *our_invocation_id; int ret; @@ -830,13 +847,555 @@ static int replmd_update_rpmd(struct ldb_module *module, } +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); + 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_USN = originating_usn + RMD_VERSION = version + */ +static int replmd_build_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct parsed_dn *p, + const struct GUID *invocation_id, uint64_t seq_num, time_t t) +{ + struct ldb_dn *dn = p->dsdb_dn->dn; + NTTIME now; + const char *tstring, *usn_string; + struct ldb_val tval; + struct ldb_val iid; + struct ldb_val usnv; + struct ldb_val vers; + NTSTATUS status; + int ret; + const char *dnstring; + + unix_to_nt_time(&now, t); + tstring = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)now); + 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); + + 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; + } + + 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_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, p->dsdb_dn, 1); + if (dnstring == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + *v = data_blob_string_const(dnstring); + + 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 parsed_dn *p, + struct parsed_dn *old_p, const struct GUID *invocation_id, + uint64_t seq_num, time_t t, bool deleted) +{ + struct ldb_dn *dn = p->dsdb_dn->dn; + NTTIME now; + const char *tstring, *usn_string; + struct ldb_val tval; + struct ldb_val iid; + struct ldb_val usnv; + struct ldb_val vers; + const struct ldb_val *old_addtime, *old_version; + NTSTATUS status; + int ret; + const char *dnstring; + + unix_to_nt_time(&now, t); + tstring = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)now); + 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); + + 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_p->dsdb_dn->dn, "RMD_ADDTIME"); + if (old_addtime == NULL) { + old_addtime = &tval; + } + if (p != old_p) { + 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_USN", &usnv); + if (ret != LDB_SUCCESS) return ret; + + /* increase the version by 1 */ + old_version = ldb_dn_get_extended_component(old_p->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, p->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 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) +{ + 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); + + 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; + } + + /* 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], + invocation_id, seq_num, t); + 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], p, invocation_id, seq_num, t, false); + 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 replacing a linked attribute + */ +static int replmd_modify_la_replace(struct ldb_module *module, + 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) +{ + return LDB_SUCCESS; +} + +/* + handle deleting all active linked attributes + */ +static int replmd_modify_la_delete(struct ldb_module *module, + 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) +{ + 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); + + /* 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; + } + + 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(p->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, p, invocation_id, seq_num, t, 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 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); + + 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_SHOW_DN_IN_STORAGE_FORMAT); + if (ret != LDB_SUCCESS) { + return ret; + } + old_msg = res->msgs[0]; + + for (i=0; i<msg->num_elements; i++) { + struct ldb_message_element *el = &msg->elements[i]; + struct ldb_message_element *old_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) { + case LDB_FLAG_MOD_REPLACE: + ret = replmd_modify_la_replace(module, msg, el, old_el, schema_attr, seq_num, t); + break; + case LDB_FLAG_MOD_DELETE: + ret = replmd_modify_la_delete(module, msg, el, old_el, schema_attr, seq_num, t); + break; + case LDB_FLAG_MOD_ADD: + ret = replmd_modify_la_add(module, msg, el, old_el, schema_attr, seq_num, t); + break; + default: + ldb_asprintf_errstring(ldb, + "invalid flags 0x%x for %s linked attribute", + el->flags, el->name); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } + + talloc_free(res); + return ret; +} + + + static int replmd_modify(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct replmd_replicated_request *ac; struct ldb_request *down_req; struct ldb_message *msg; - struct ldb_result *res; time_t t = time(NULL); int ret; @@ -862,24 +1421,13 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req) return LDB_ERR_OPERATIONS_ERROR; } - /* TODO: - * - give an error when a readonly attribute should - * be modified - * - merge the changed into the old object - * if the caller set values to the same value - * ignore the attribute, return success when no - * attribute was changed - */ - - ret = dsdb_module_search_dn(module, msg, &res, msg->dn, NULL, - DSDB_SEARCH_SHOW_DELETED | - DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT); + ret = replmd_update_rpmd(module, ac->schema, msg, &ac->seq_num, t); if (ret != LDB_SUCCESS) { talloc_free(ac); return ret; } - ret = replmd_update_rpmd(module, ac->schema, msg, &ac->seq_num); + ret = replmd_modify_handle_linked_attribs(module, msg, ac->seq_num, t); if (ret != LDB_SUCCESS) { talloc_free(ac); return ret; @@ -1970,6 +2518,7 @@ static int replmd_process_linked_attribute(struct ldb_module *module, struct ldb_message_element el; const struct ldb_val *guid; WERROR status; + time_t t = time(NULL); drs.value_ctr.num_values = 1; drs.value_ctr.values = &val; @@ -2077,7 +2626,7 @@ linked_attributes[0]: el.values[0] = data_blob_string_const(dsdb_dn_get_extended_linearized(tmp_ctx, dsdb_dn, 1)); } - ret = replmd_update_rpmd(module, schema, msg, &seq_num); + ret = replmd_update_rpmd(module, schema, msg, &seq_num, t); if (ret != LDB_SUCCESS) { talloc_free(tmp_ctx); return ret; @@ -2086,8 +2635,6 @@ linked_attributes[0]: /* we only change whenChanged and uSNChanged if the seq_num has changed */ if (seq_num != 0) { - time_t t = time(NULL); - if (add_time_element(msg, "whenChanged", t) != LDB_SUCCESS) { talloc_free(tmp_ctx); return LDB_ERR_OPERATIONS_ERROR; |