drs: Add basic GET_TGT support
authorTim Beale <timbeale@catalyst.net.nz>
Tue, 22 Aug 2017 22:23:10 +0000 (10:23 +1200)
committerGarming Sam <garming@samba.org>
Mon, 18 Sep 2017 03:51:24 +0000 (05:51 +0200)
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 <timbeale@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
selftest/knownfail.d/getnc_exop [deleted file]
selftest/knownfail.d/getncchanges
source4/dsdb/common/util.c
source4/dsdb/samdb/ldb_modules/repl_meta_data.c
source4/rpc_server/drsuapi/getncchanges.c

diff --git a/selftest/knownfail.d/getnc_exop b/selftest/knownfail.d/getnc_exop
deleted file mode 100644 (file)
index 0018b0b..0000000
+++ /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\)
-
index a9dd8162f89e01e49192d037dc6a33932a8e5aec..64e2c4284d3ea4b126160ee9318e7c17bdb8851b 100644 (file)
@@ -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\)
index 430620e5b6e251583625fef6bbbf9079c7335bc2..49345e5ecde6f3200eab24bd41bd69b0113dd28b 100644 (file)
@@ -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;
+}
index d2c2084acb13711a4c68c0fcca7a26cb3dcf29d0..68268ef5ee4570bf294554e92cde0ae40ce5bd8a 100644 (file)
@@ -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),
index cd3f51fc2b0135b69b2c792fd87f3081420be63d..e2058703cc71e4143928334731ab13ca2c84200a 100644 (file)
@@ -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=%s>",
+                                          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);
        }