From: Tim Beale Date: Tue, 22 Aug 2017 22:23:10 +0000 (+1200) Subject: drs: Add basic GET_TGT support X-Git-Tag: ldb-1.3.0~169 X-Git-Url: http://git.samba.org/samba.git/?p=nivanova%2Fsamba-autobuild%2F.git;a=commitdiff_plain;h=ed2fc522439349832ad2a8a56abbf63e56011fb9 drs: Add basic GET_TGT support This adds basic DRS_GET_TGT support. If the GET_TGT flag is specified then the server will use the object cache to store the objects it sends back. If the target object for a linked attribute is not in the cache (i.e. it has not been sent already), then it is added to the response message. Note that large numbers of linked attributes will not be handled well yet - the server could potentially try to send more than will fit in a single repsonse message. Also note that the client can sometimes set the GET_TGT flag even if the server is still sending the links last. In this case, we know the client supports GET_TGT so it's safe to send the links interleaved with the source objects (the alternative of fetching the target objects but not sending the links until last doesn't really make any sense). Signed-off-by: Tim Beale Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam --- diff --git a/selftest/knownfail.d/getnc_exop b/selftest/knownfail.d/getnc_exop deleted file mode 100644 index 0018b0bbd07..00000000000 --- a/selftest/knownfail.d/getnc_exop +++ /dev/null @@ -1,4 +0,0 @@ -# This test fails because Samba doesn't support the GET_TGT option -samba4.drs.getnc_exop.python\(vampire_dc\).getnc_exop.DrsReplicaSyncTestCase.test_link_utdv_hwm\(vampire_dc\) -samba4.drs.getnc_exop.python\(promoted_dc\).getnc_exop.DrsReplicaSyncTestCase.test_link_utdv_hwm\(promoted_dc\) - diff --git a/selftest/knownfail.d/getncchanges b/selftest/knownfail.d/getncchanges index a9dd8162f89..64e2c4284d3 100644 --- a/selftest/knownfail.d/getncchanges +++ b/selftest/knownfail.d/getncchanges @@ -1,12 +1,3 @@ -# These tests fail because Samba doesn't support GET_TGT yet and vampire_dc sends -# the link in a separate chunk before the target object -samba4.drs.getncchanges.python\(vampire_dc\).getncchanges.DrsReplicaSyncIntegrityTestCase.test_repl_get_tgt\(vampire_dc\) -samba4.drs.getncchanges.python\(vampire_dc\).getncchanges.DrsReplicaSyncIntegrityTestCase.test_repl_get_tgt_chain\(vampire_dc\) -samba4.drs.getncchanges.python\(vampire_dc\).getncchanges.DrsReplicaSyncIntegrityTestCase.test_repl_integrity_link_attr\(vampire_dc\) -samba4.drs.getncchanges.python\(vampire_dc\).getncchanges.DrsReplicaSyncIntegrityTestCase.test_repl_get_tgt_and_anc\(vampire_dc\) -samba4.drs.getncchanges.python\(vampire_dc\).getncchanges.DrsReplicaSyncIntegrityTestCase.test_repl_integrity_src_obj_deletion\(vampire_dc\) -samba4.drs.getncchanges.python\(vampire_dc\).getncchanges.DrsReplicaSyncIntegrityTestCase.test_repl_integrity_tgt_obj_deletion\(vampire_dc\) -samba4.drs.getncchanges.python\(promoted_dc\).getncchanges.DrsReplicaSyncIntegrityTestCase.test_repl_integrity_link_attr\(promoted_dc\) # GET_TGT tests currently only work for testenvs that send the links at the # same time as the source objects. Currently this is only the vampire_dc samba4.drs.getncchanges.python\(promoted_dc\).getncchanges.DrsReplicaSyncIntegrityTestCase.test_repl_get_tgt\(promoted_dc\) diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 430620e5b6e..49345e5ecde 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -5555,3 +5555,43 @@ int dsdb_user_obj_set_primary_group_id(struct ldb_context *ldb, struct ldb_messa return LDB_SUCCESS; } + +/** + * Returns True if the source and target DNs both have the same naming context, + * i.e. they're both in the same partition. + */ +bool dsdb_objects_have_same_nc(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_dn *source_dn, + struct ldb_dn *target_dn) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *source_nc; + struct ldb_dn *target_nc; + int ret; + bool same_nc = true; + + tmp_ctx = talloc_new(mem_ctx); + + ret = dsdb_find_nc_root(ldb, tmp_ctx, source_dn, &source_nc); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to find base DN for source %s\n", + ldb_dn_get_linearized(source_dn)); + talloc_free(tmp_ctx); + return true; + } + + ret = dsdb_find_nc_root(ldb, tmp_ctx, target_dn, &target_nc); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to find base DN for target %s\n", + ldb_dn_get_linearized(target_dn)); + talloc_free(tmp_ctx); + return true; + } + + same_nc = (ldb_dn_compare(source_nc, target_nc) == 0); + + talloc_free(tmp_ctx); + + return same_nc; +} diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index d2c2084acb1..68268ef5ee4 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -6725,46 +6725,6 @@ static int replmd_extended_replicated_objects(struct ldb_module *module, struct return replmd_replicated_apply_next(ar); } -/** - * Returns True if the source and target DNs both have the same naming context, - * i.e. they're both in the same partition. - */ -static bool replmd_objects_have_same_nc(struct ldb_context *ldb, - TALLOC_CTX *mem_ctx, - struct ldb_dn *source_dn, - struct ldb_dn *target_dn) -{ - TALLOC_CTX *tmp_ctx; - struct ldb_dn *source_nc; - struct ldb_dn *target_nc; - int ret; - bool same_nc = true; - - tmp_ctx = talloc_new(mem_ctx); - - ret = dsdb_find_nc_root(ldb, tmp_ctx, source_dn, &source_nc); - if (ret != LDB_SUCCESS) { - DBG_ERR("Failed to find base DN for source %s\n", - ldb_dn_get_linearized(source_dn)); - talloc_free(tmp_ctx); - return true; - } - - ret = dsdb_find_nc_root(ldb, tmp_ctx, target_dn, &target_nc); - if (ret != LDB_SUCCESS) { - DBG_ERR("Failed to find base DN for target %s\n", - ldb_dn_get_linearized(target_dn)); - talloc_free(tmp_ctx); - return true; - } - - same_nc = (ldb_dn_compare(source_nc, target_nc) == 0); - - talloc_free(tmp_ctx); - - return same_nc; -} - /** * Checks that the target object for a linked attribute exists. * @param guid returns the target object's GUID (is returned)if it exists) @@ -6868,8 +6828,8 @@ static int replmd_check_target_exists(struct ldb_module *module, ldb_dn_get_linearized(source_dn))); *ignore_link = true; - } else if (replmd_objects_have_same_nc(ldb, tmp_ctx, source_dn, - dsdb_dn->dn)) { + } else if (dsdb_objects_have_same_nc(ldb, tmp_ctx, source_dn, + dsdb_dn->dn)) { ldb_asprintf_errstring(ldb, "Unknown target %s GUID %s linked from %s\n", ldb_dn_get_linearized(dsdb_dn->dn), GUID_string(tmp_ctx, guid), diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index cd3f51fc2b0..e2058703cc7 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -60,6 +60,7 @@ struct drsuapi_getncchanges_state { struct GUID ncRoot_guid; bool is_schema_nc; bool is_get_anc; + bool is_get_tgt; uint64_t min_usn; uint64_t max_usn; struct drsuapi_DsReplicaHighWaterMark last_hwm; @@ -2347,6 +2348,147 @@ static WERROR getncchanges_get_obj_to_send(const struct ldb_message *msg, return werr; } +/** + * Goes through any new linked attributes and checks that the target object + * will be known to the client, i.e. we've already sent it in an replication + * chunk. If not, then it adds the target object to the current replication + * chunk. This is only done when the client specifies DRS_GET_TGT. + */ +static WERROR getncchanges_chunk_add_la_targets(struct getncchanges_repl_chunk *repl_chunk, + struct drsuapi_getncchanges_state *getnc_state, + uint32_t start_la_index, + TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct dsdb_schema *schema, + DATA_BLOB *session_key, + struct drsuapi_DsGetNCChangesRequest10 *req10, + uint32_t *local_pas, + struct ldb_dn *machine_dn) +{ + int ret; + uint32_t i; + WERROR werr = WERR_OK; + static const char * const msg_attrs[] = { + "*", + "nTSecurityDescriptor", + "parentGUID", + "replPropertyMetaData", + DSDB_SECRET_ATTRIBUTES, + NULL }; + + /* loop through any linked attributes to check */ + for (i = start_la_index; i < getnc_state->la_count; i++) { + + struct GUID target_guid; + struct drsuapi_DsReplicaObjectListItemEx *new_objs = NULL; + const struct drsuapi_DsReplicaLinkedAttribute *la; + struct ldb_result *msg_res; + struct ldb_dn *search_dn; + TALLOC_CTX *tmp_ctx; + struct dsdb_dn *dn; + const struct dsdb_attribute *schema_attrib; + NTSTATUS status; + bool same_nc; + + la = &getnc_state->la_list[i]; + tmp_ctx = talloc_new(mem_ctx); + + /* get the GUID of the linked attribute's target object */ + schema_attrib = dsdb_attribute_by_attributeID_id(schema, + la->attid); + + werr = dsdb_dn_la_from_blob(sam_ctx, schema_attrib, schema, + tmp_ctx, la->value.blob, &dn); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Bad la blob\n")); + return werr; + } + + status = dsdb_get_extended_dn_guid(dn->dn, &target_guid, "GUID"); + + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* + * if the target isn't in the cache, then the client + * might not know about it, so send the target now + */ + werr = dcesrv_drsuapi_obj_cache_exists(getnc_state->obj_cache, + &target_guid); + + if (W_ERROR_EQUAL(werr, WERR_OBJECT_NAME_EXISTS)) { + + /* target already sent, nothing to do */ + TALLOC_FREE(tmp_ctx); + continue; + } + + same_nc = dsdb_objects_have_same_nc(sam_ctx, tmp_ctx, dn->dn, + getnc_state->ncRoot_dn); + + /* don't try to fetch target objects from another partition */ + if (!same_nc) { + TALLOC_FREE(tmp_ctx); + continue; + } + + search_dn = ldb_dn_new_fmt(tmp_ctx, sam_ctx, "", + GUID_string(tmp_ctx, &target_guid)); + W_ERROR_HAVE_NO_MEMORY(search_dn); + + ret = drsuapi_search_with_extended_dn(sam_ctx, tmp_ctx, + &msg_res, search_dn, + LDB_SCOPE_BASE, + msg_attrs, NULL); + + /* + * Don't fail the replication if we can't find the target. + * This could happen for a one-way linked attribute, if the + * target is deleted and then later expunged (thus, the source + * object can be left with a hanging link). Continue to send + * the the link (the client-side has already tried once with + * GET_TGT, so it should just end up ignoring it). + */ + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DBG_WARNING("Encountered unknown link target DN %s\n", + ldb_dn_get_extended_linearized(tmp_ctx, dn->dn, 1)); + TALLOC_FREE(tmp_ctx); + continue; + + } else if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to fetch link target DN %s - %s\n", + ldb_dn_get_extended_linearized(tmp_ctx, dn->dn, 1), + ldb_errstring(sam_ctx)); + return WERR_DS_DRA_INCONSISTENT_DIT; + } + + /* + * Construct an object, ready to send (this will include + * the object's ancestors as well, if GET_ANC is set) + */ + werr = getncchanges_get_obj_to_send(msg_res->msgs[0], mem_ctx, + sam_ctx, getnc_state, + schema, session_key, req10, + false, local_pas, + machine_dn, &target_guid, + &new_objs); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + if (new_objs != NULL) { + getncchanges_add_objs_to_resp(repl_chunk, new_objs); + } + TALLOC_FREE(tmp_ctx); + + /* TODO could have 1000s of links. Stop if we fill up the message */ + } + + return WERR_OK; +} + /* drsuapi_DsGetNCChanges @@ -2438,7 +2580,6 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ return WERR_REVISION_MISMATCH; } - /* Perform access checks. */ /* TODO: we need to support a sync on a specific non-root * DN. We'll need to find the real partition root here */ @@ -2843,14 +2984,26 @@ allowed: talloc_free(search_res); talloc_free(changes); - if (req10->extended_op != DRSUAPI_EXOP_NONE) { - /* Do nothing */ - } else if (req10->replica_flags & DRSUAPI_DRS_GET_ANC) { + if (req10->extended_op == DRSUAPI_EXOP_NONE) { + getnc_state->is_get_anc = + ((req10->replica_flags & DRSUAPI_DRS_GET_ANC) != 0); + getnc_state->is_get_tgt = + ((req10->more_flags & DRSUAPI_DRS_GET_TGT) != 0); + } + + /* + * when using GET_ANC or GET_TGT, cache the objects that have + * been already sent, to avoid sending them multiple times + */ + if (getnc_state->is_get_anc || getnc_state->is_get_tgt) { + DEBUG(3,("Using object cache, GET_ANC %u, GET_TGT %u\n", + getnc_state->is_get_anc, + getnc_state->is_get_tgt)); + getnc_state->obj_cache = db_open_rbt(getnc_state); if (getnc_state->obj_cache == NULL) { return WERR_NOT_ENOUGH_MEMORY; } - getnc_state->is_get_anc = true; } } @@ -2911,6 +3064,14 @@ allowed: immediate_link_sync = lpcfg_parm_bool(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "immediate link sync", false); + /* + * If the client has already set GET_TGT then we know they can handle + * receiving the linked attributes interleaved with the source objects + */ + if (getnc_state->is_get_tgt) { + immediate_link_sync = true; + } + /* * Maximum time that we can spend in a getncchanges * in order to avoid timeout of the other part. @@ -3070,6 +3231,24 @@ allowed: getnc_state->total_links += (getnc_state->la_count - old_la_index); + /* + * If the GET_TGT flag was set, check any new links added to + * make sure the client knows about the link target object + */ + if (getnc_state->is_get_tgt) { + werr = getncchanges_chunk_add_la_targets(&repl_chunk, + getnc_state, + old_la_index, + mem_ctx, sam_ctx, + schema, &session_key, + req10, local_pas, + machine_dn); + + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + TALLOC_FREE(tmp_ctx); }