From 8d156d0885e31ad6b80c1979efdfa7cfee075215 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 29 Nov 2016 11:12:22 +0100 Subject: [PATCH] s4:rpc_server/drsuapi: implement DRSUAPI_DRS_GET_ANC more correctly The most important case is the combination of DRSUAPI_DRS_CRITICAL_ONLY and DRSUAPI_DRS_GET_ANC. With DRSUAPI_DRS_GET_ANC we need to make sure all ancestors included even if they're not marked with isCriticalSystemObject=TRUE. I guess we still don't behave exactly as Windows, but it's much better than before and fixes the initial replication if someone moved the the administrator account to an OU. BUG: https://bugzilla.samba.org/show_bug.cgi?id=12398 Signed-off-by: Stefan Metzmacher --- source4/rpc_server/drsuapi/getncchanges.c | 212 +++++++++++++++++++++++++++--- 1 file changed, 194 insertions(+), 18 deletions(-) diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 705c8cf..03a6639 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -37,6 +37,9 @@ #include "lib/util/tsort.h" #include "auth/session.h" #include "dsdb/common/util.h" +#include "lib/dbwrap/dbwrap.h" +#include "lib/dbwrap/dbwrap_rbt.h" +#include "librpc/gen_ndr/ndr_misc.h" /* state of a partially completed getncchanges call */ struct drsuapi_getncchanges_state { @@ -724,16 +727,6 @@ struct drsuapi_changed_objects { }; /* - sort the objects we send by tree order - */ -static int site_res_cmp_anc_order(struct drsuapi_changed_objects *m1, - struct drsuapi_changed_objects *m2, - struct drsuapi_getncchanges_state *getnc_state) -{ - return ldb_dn_compare(m2->dn, m1->dn); -} - -/* sort the objects we send first by uSNChanged */ static int site_res_cmp_usn_order(struct drsuapi_changed_objects *m1, @@ -1684,6 +1677,68 @@ static WERROR getncchanges_collect_objects_exop(struct drsuapi_bind_state *b_sta } } +static WERROR dcesrv_drsuapi_anc_cache_add(struct db_context *anc_cache, + const struct GUID *guid) +{ + enum ndr_err_code ndr_err; + uint8_t guid_buf[16] = { 0, }; + DATA_BLOB b = { + .data = guid_buf, + .length = sizeof(guid_buf), + }; + TDB_DATA key = { + .dptr = b.data, + .dsize = b.length, + }; + TDB_DATA val = { + .dptr = NULL, + .dsize = 0, + }; + NTSTATUS status; + + ndr_err = ndr_push_struct_into_fixed_blob(&b, guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + status = dbwrap_store(anc_cache, key, val, TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + return WERR_OK; +} + +static WERROR dcesrv_drsuapi_anc_cache_exists(struct db_context *anc_cache, + const struct GUID *guid) +{ + enum ndr_err_code ndr_err; + uint8_t guid_buf[16] = { 0, }; + DATA_BLOB b = { + .data = guid_buf, + .length = sizeof(guid_buf), + }; + TDB_DATA key = { + .dptr = b.data, + .dsize = b.length, + }; + bool exists; + + ndr_err = ndr_push_struct_into_fixed_blob(&b, guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + exists = dbwrap_exists(anc_cache, key); + if (!exists) { + return WERR_OBJECT_NOT_FOUND; + } + + return WERR_OBJECT_NAME_EXISTS; +} + /* drsuapi_DsGetNCChanges @@ -1728,6 +1783,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ struct dsdb_schema_prefixmap *pfm_remote = NULL; bool full = true; uint32_t *local_pas = NULL; + struct db_context *anc_cache = NULL; DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); b_state = h->data; @@ -2075,11 +2131,6 @@ allowed: /* RID_ALLOC returns 3 objects in a fixed order */ if (req10->extended_op == DRSUAPI_EXOP_FSMO_RID_ALLOC) { /* Do nothing */ - } else if (req10->replica_flags & DRSUAPI_DRS_GET_ANC) { - LDB_TYPESAFE_QSORT(changes, - getnc_state->num_records, - getnc_state, - site_res_cmp_anc_order); } else { LDB_TYPESAFE_QSORT(changes, getnc_state->num_records, @@ -2191,6 +2242,15 @@ allowed: uint32_t_ptr_cmp); } + if (req10->extended_op != DRSUAPI_EXOP_NONE) { + /* Do nothing */ + } else if (req10->replica_flags & DRSUAPI_DRS_GET_ANC) { + anc_cache = db_open_rbt(mem_ctx); + if (anc_cache == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + for (i=getnc_state->num_processed; inum_records && !null_scope && @@ -2198,6 +2258,7 @@ allowed: && !max_wait_reached; i++) { int uSN; + struct drsuapi_DsReplicaObjectListItemEx *new_objs = NULL; struct drsuapi_DsReplicaObjectListItemEx *obj; struct ldb_message *msg; static const char * const msg_attrs[] = { @@ -2209,6 +2270,7 @@ allowed: NULL }; struct ldb_result *msg_res; struct ldb_dn *msg_dn; + const struct GUID *next_anc_guid = NULL; obj = talloc_zero(mem_ctx, struct drsuapi_DsReplicaObjectListItemEx); W_ERROR_HAVE_NO_MEMORY(obj); @@ -2289,10 +2351,122 @@ allowed: continue; } - r->out.ctr->ctr6.object_count++; + new_objs = obj; - *currentObject = obj; - currentObject = &obj->next_object; + if (anc_cache != NULL) { + werr = dcesrv_drsuapi_anc_cache_add(anc_cache, + &getnc_state->guids[i]); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + next_anc_guid = obj->parent_object_guid; + } + + while (next_anc_guid != NULL) { + struct drsuapi_DsReplicaObjectListItemEx *anc_obj = NULL; + struct ldb_message *anc_msg = NULL; + struct ldb_result *anc_res = NULL; + struct ldb_dn *anc_dn = NULL; + + werr = dcesrv_drsuapi_anc_cache_exists(anc_cache, + next_anc_guid); + if (W_ERROR_EQUAL(werr, WERR_OBJECT_NAME_EXISTS)) { + /* + * We don't need to send it twice. + */ + break; + } + if (W_ERROR_IS_OK(werr)) { + return WERR_INTERNAL_ERROR; + } + if (!W_ERROR_EQUAL(werr, WERR_OBJECT_NOT_FOUND)) { + return werr; + } + werr = WERR_OK; + + anc_obj = talloc_zero(mem_ctx, + struct drsuapi_DsReplicaObjectListItemEx); + if (anc_obj == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + anc_dn = ldb_dn_new_fmt(anc_obj, sam_ctx, "", + GUID_string(anc_obj, next_anc_guid)); + if (anc_dn == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = drsuapi_search_with_extended_dn(sam_ctx, anc_obj, + &anc_res, anc_dn, + LDB_SCOPE_BASE, + msg_attrs, NULL); + if (ret != LDB_SUCCESS) { + const char *anc_str = NULL; + const char *obj_str = NULL; + + anc_str = ldb_dn_get_extended_linearized(anc_obj, + anc_dn, + 1); + obj_str = ldb_dn_get_extended_linearized(anc_obj, + msg->dn, + 1), + + DBG_ERR("getncchanges: failed to fetch ANC " + "DN %s for DN %s - %s\n", + anc_str, obj_str, + ldb_errstring(sam_ctx)); + return WERR_DS_DRA_INCONSISTENT_DIT; + } + + anc_msg = anc_res->msgs[0]; + + werr = get_nc_changes_build_object(anc_obj, anc_msg, + sam_ctx, + getnc_state->ncRoot_dn, + getnc_state->is_schema_nc, + schema, &session_key, + getnc_state->min_usn, + req10->replica_flags, + req10->partial_attribute_set, + req10->uptodateness_vector, + req10->extended_op, + true, /* ??? max_wait_reached */ + local_pas); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + anc_msg = NULL; + TALLOC_FREE(anc_res); + TALLOC_FREE(anc_dn); + + werr = dcesrv_drsuapi_anc_cache_add(anc_cache, + next_anc_guid); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + /* + * prepend it to the list + */ + anc_obj->next_object = new_objs; + new_objs = anc_obj; + + /* + * We may need to resolve more... + */ + next_anc_guid = anc_obj->parent_object_guid; + } + + *currentObject = new_objs; + while (new_objs != NULL) { + r->out.ctr->ctr6.object_count += 1; + if (new_objs->next_object == NULL) { + currentObject = &new_objs->next_object; + } + new_objs = new_objs->next_object; + } DEBUG(8,(__location__ ": replicating object %s\n", ldb_dn_get_linearized(msg->dn))); @@ -2435,6 +2609,8 @@ allowed: } } + TALLOC_FREE(anc_cache); + if (!r->out.ctr->ctr6.more_data) { talloc_steal(mem_ctx, getnc_state->la_list); -- 1.9.1