debug: Add new debug class "drs_repl" for DRS replication processing
[samba.git] / source4 / rpc_server / drsuapi / getncchanges.c
index 4de66470407ce7df88a565de17427010bbb098ca..cc41abbc2271001cc83e334f68e361d313385af3 100644 (file)
@@ -32,6 +32,7 @@
 #include "libcli/security/session.h"
 #include "rpc_server/drsuapi/dcesrv_drsuapi.h"
 #include "rpc_server/dcerpc_server_proto.h"
+#include "rpc_server/common/sid_helper.h"
 #include "../libcli/drsuapi/drsuapi.h"
 #include "lib/util/binsearch.h"
 #include "lib/util/tsort.h"
@@ -41,6 +42,9 @@
 #include "lib/dbwrap/dbwrap_rbt.h"
 #include "librpc/gen_ndr/ndr_misc.h"
 
+#undef DBGC_CLASS
+#define DBGC_CLASS            DBGC_DRS_REPL
+
 /* state of a partially completed getncchanges call */
 struct drsuapi_getncchanges_state {
        struct db_context *anc_cache;
@@ -135,7 +139,6 @@ static bool udv_filter(const struct drsuapi_DsReplicaCursorCtrEx *udv,
                return true;
        }
        return false;
-
 }
 
 static int uint32_t_cmp(uint32_t a1, uint32_t a2)
@@ -190,17 +193,39 @@ fail:
        }
 }
 
+/*
+ * Similar to function in repl_meta_data without the extra
+ * dependencies.
+ */
+static WERROR get_parsed_dns_trusted(TALLOC_CTX *mem_ctx, struct ldb_message_element *el,
+                                 struct parsed_dn **pdn)
+{
+       /* Here we get a list of 'struct parsed_dns' without the parsing */
+       int i;
+       *pdn = talloc_zero_array(mem_ctx, struct parsed_dn,
+                                el->num_values);
+       if (!*pdn) {
+               return WERR_DS_DRA_INTERNAL_ERROR;
+       }
+
+       for (i = 0; i < el->num_values; i++) {
+               (*pdn)[i].v = &el->values[i];
+       }
+
+       return WERR_OK;
+}
+
 static WERROR getncchanges_update_revealed_list(struct ldb_context *sam_ctx,
                                                TALLOC_CTX *mem_ctx,
                                                struct ldb_message **msg,
                                                struct ldb_dn *object_dn,
+                                               const struct GUID *object_guid,
                                                const struct dsdb_attribute *sa,
                                                struct replPropertyMetaData1 *meta_data,
                                                struct ldb_message *revealed_users)
 {
        enum ndr_err_code ndr_err;
        int ldb_err;
-       unsigned i;
        char *attr_str = NULL;
        char *attr_hex = NULL;
        DATA_BLOB attr_blob;
@@ -231,30 +256,59 @@ static WERROR getncchanges_update_revealed_list(struct ldb_context *sam_ctx,
        existing = ldb_msg_find_element(revealed_users, "msDS-RevealedUsers");
        if (existing != NULL) {
                /* Replace the old value (if one exists) with the current one */
-               for (i = 0; i < existing->num_values; i++) {
-                       struct dsdb_dn *existing_dn = dsdb_dn_parse_trusted(mem_ctx, sam_ctx, &existing->values[i], DSDB_SYNTAX_BINARY_DN);
-                       if (ldb_dn_compare(object_dn, existing_dn->dn) == 0) {
-                               struct replPropertyMetaData1 existing_meta_data;
-                               ndr_err = ndr_pull_struct_blob_all_noalloc(&existing_dn->extra_part,
-                                                              &existing_meta_data,
-                                                              (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaData1);
-                               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               struct parsed_dn *link_dns;
+               struct parsed_dn *exact = NULL, *unused = NULL;
+               WERROR werr;
+               uint8_t attid[4];
+               DATA_BLOB partial_meta;
+
+               werr = get_parsed_dns_trusted(mem_ctx, existing, &link_dns);
+               if (!W_ERROR_IS_OK(werr)) {
+                       return werr;
+               }
+
+               /* Construct a partial metadata blob to match on in the DB */
+               SIVAL(attid, 0, sa->attributeID_id);
+               partial_meta.length = 4;
+               partial_meta.data = attid;
+
+               /* Binary search using GUID and attribute id for uniqueness */
+               ldb_err = parsed_dn_find(sam_ctx, link_dns, existing->num_values,
+                                        object_guid, object_dn,
+                                        partial_meta, 4,
+                                        &exact, &unused,
+                                        DSDB_SYNTAX_BINARY_DN, true);
+
+               if (ldb_err != LDB_SUCCESS) {
+                       DEBUG(0,(__location__ ": Failed parsed DN find - %s\n",
+                                ldb_errstring(sam_ctx)));
+                       return WERR_DS_DRA_INTERNAL_ERROR;
+               }
+
+               if (exact != NULL) {
+                       /* Perform some verification of the blob */
+                       struct replPropertyMetaData1 existing_meta_data;
+                       ndr_err = ndr_pull_struct_blob_all_noalloc(&exact->dsdb_dn->extra_part,
+                                                                  &existing_meta_data,
+                                                                  (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaData1);
+                       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                               return WERR_DS_DRA_INTERNAL_ERROR;
+                       }
+
+                       if (existing_meta_data.attid == sa->attributeID_id) {
+                               ldb_err = ldb_msg_add_empty(*msg, "msDS-RevealedUsers", LDB_FLAG_MOD_DELETE, &el_del);
+                               if (ldb_err != LDB_SUCCESS) {
                                        return WERR_DS_DRA_INTERNAL_ERROR;
                                }
 
-                               if (existing_meta_data.attid == sa->attributeID_id) {
-                                       ldb_err = ldb_msg_add_empty(*msg, "msDS-RevealedUsers", LDB_FLAG_MOD_DELETE, &el_del);
-                                       if (ldb_err != LDB_SUCCESS) {
-                                               return WERR_DS_DRA_INTERNAL_ERROR;
-                                       }
-
-                                       el_del->values = talloc_array((*msg)->elements, struct ldb_val, 1);
-                                       if (el_del->values == NULL) {
-                                               return WERR_NOT_ENOUGH_MEMORY;
-                                       }
-                                       el_del->values[0] = existing->values[i];
-                                       el_del->num_values = 1;
+                               el_del->values = talloc_array((*msg)->elements, struct ldb_val, 1);
+                               if (el_del->values == NULL) {
+                                       return WERR_NOT_ENOUGH_MEMORY;
                                }
+                               el_del->values[0] = *exact->v;
+                               el_del->num_values = 1;
+                       } else {
+                               return WERR_DS_DRA_INTERNAL_ERROR;
                        }
                }
        }
@@ -276,20 +330,29 @@ static WERROR getncchanges_update_revealed_list(struct ldb_context *sam_ctx,
        return WERR_OK;
 }
 
+/*
+ * This function filter attributes for build_object based on the
+ * uptodatenessvector and partial attribute set.
+ *
+ * Any secret attributes are forced here for REPL_SECRET, and audited at this
+ * point with msDS-RevealedUsers.
+ */
 static WERROR get_nc_changes_filter_attrs(struct drsuapi_DsReplicaObjectListItemEx *obj,
                                          struct replPropertyMetaDataBlob md,
                                          struct ldb_context *sam_ctx,
                                          const struct ldb_message *msg,
+                                         const struct GUID *guid,
                                          uint32_t *count,
                                          uint64_t highest_usn,
                                          const struct dsdb_attribute *rdn_sa,
                                          struct dsdb_schema *schema,
+                                         struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector,
+                                         struct drsuapi_DsPartialAttributeSet *partial_attribute_set,
+                                         uint32_t *local_pas,
+                                         uint32_t *attids,
                                          bool exop_secret,
                                          struct ldb_message **revealed_list_msg,
-                                         struct ldb_message *existing_revealed_list_msg,
-                                         struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector,
-                                         struct drsuapi_DsPartialAttributeSet *partial_attribute_set, uint32_t *local_pas,
-                                         uint32_t *attids)
+                                         struct ldb_message *existing_revealed_list_msg)
 {
        uint32_t i, n;
        WERROR werr;
@@ -330,7 +393,7 @@ static WERROR get_nc_changes_filter_attrs(struct drsuapi_DsReplicaObjectListItem
                                 sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn)));
                        werr = getncchanges_update_revealed_list(sam_ctx, obj,
                                                                 revealed_list_msg,
-                                                                msg->dn, sa,
+                                                                msg->dn, guid, sa,
                                                                 &md.ctr.ctr1.array[i],
                                                                 existing_revealed_list_msg);
                        if (!W_ERROR_IS_OK(werr)) {
@@ -388,7 +451,8 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem
                                          enum drsuapi_DsExtendedOperation extended_op,
                                          bool force_object_return,
                                          uint32_t *local_pas,
-                                         struct ldb_dn *machine_dn)
+                                         struct ldb_dn *machine_dn,
+                                         const struct GUID *guid)
 {
        const struct ldb_val *md_value;
        uint32_t i, n;
@@ -403,7 +467,6 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem
        unsigned int instanceType;
        struct dsdb_syntax_ctx syntax_ctx;
        struct ldb_result *res = NULL;
-       struct ldb_message *revealed_list_msg = NULL, *existing_revealed_list_msg = NULL;
        WERROR werr;
        int ret;
 
@@ -483,7 +546,12 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem
 
        if (extended_op == DRSUAPI_EXOP_REPL_SECRET) {
                /* Get the existing revealed users for the destination */
-               static const char *machine_attrs[] = { "msDS-RevealedUsers", NULL };
+               struct ldb_message *revealed_list_msg = NULL;
+               struct ldb_message *existing_revealed_list_msg = NULL;
+               const char *machine_attrs[] = {
+                       "msDS-RevealedUsers",
+                       NULL
+               };
 
                revealed_list_msg = ldb_msg_new(sam_ctx);
                if (revealed_list_msg == NULL) {
@@ -498,7 +566,7 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem
                        return WERR_DS_DRA_INTERNAL_ERROR;
                }
 
-               ldb_err = dsdb_search_dn(sam_ctx, obj, &res, machine_dn, machine_attrs, 0);
+               ldb_err = dsdb_search_dn(sam_ctx, obj, &res, machine_dn, machine_attrs, DSDB_SEARCH_SHOW_EXTENDED_DN);
                if (ldb_err != LDB_SUCCESS || res->count != 1) {
                        ldb_transaction_cancel(sam_ctx);
                        return WERR_DS_DRA_INTERNAL_ERROR;
@@ -506,14 +574,15 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem
 
                existing_revealed_list_msg = res->msgs[0];
 
-               werr = get_nc_changes_filter_attrs(obj, md, sam_ctx, msg, &n,
-                                                  highest_usn, rdn_sa, schema,
-                                                  true,
-                                                  &revealed_list_msg,
-                                                  existing_revealed_list_msg,
+               werr = get_nc_changes_filter_attrs(obj, md, sam_ctx, msg,
+                                                  guid, &n, highest_usn,
+                                                  rdn_sa, schema,
                                                   uptodateness_vector,
                                                   partial_attribute_set, local_pas,
-                                                  attids);
+                                                  attids,
+                                                  true,
+                                                  &revealed_list_msg,
+                                                  existing_revealed_list_msg);
                if (!W_ERROR_IS_OK(werr)) {
                        ldb_transaction_cancel(sam_ctx);
                        return werr;
@@ -536,14 +605,14 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem
                        return WERR_DS_DRA_INTERNAL_ERROR;
                }
        } else {
-               werr = get_nc_changes_filter_attrs(obj, md, sam_ctx, msg, &n,
-                                                  highest_usn, rdn_sa, schema,
-                                                  false,
-                                                  &revealed_list_msg,
-                                                  existing_revealed_list_msg,
-                                                  uptodateness_vector,
+               werr = get_nc_changes_filter_attrs(obj, md, sam_ctx, msg, guid,
+                                                  &n, highest_usn, rdn_sa,
+                                                  schema, uptodateness_vector,
                                                   partial_attribute_set, local_pas,
-                                                  attids);
+                                                  attids,
+                                                  false,
+                                                  NULL,
+                                                  NULL);
                if (!W_ERROR_IS_OK(werr)) {
                        return werr;
                }
@@ -1074,105 +1143,6 @@ static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state,
        return WERR_OK;
 }
 
-/*
-  return an array of SIDs from a ldb_message given an attribute name
-  assumes the SIDs are in extended DN format
- */
-static WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx,
-                                       struct ldb_message *msg,
-                                       TALLOC_CTX *mem_ctx,
-                                       const char *attr,
-                                       const struct dom_sid ***sids)
-{
-       struct ldb_message_element *el;
-       unsigned int i;
-
-       el = ldb_msg_find_element(msg, attr);
-       if (!el) {
-               *sids = NULL;
-               return WERR_OK;
-       }
-
-       (*sids) = talloc_array(mem_ctx, const struct dom_sid *, el->num_values + 1);
-       W_ERROR_HAVE_NO_MEMORY(*sids);
-
-       for (i=0; i<el->num_values; i++) {
-               struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, sam_ctx, &el->values[i]);
-               NTSTATUS status;
-               struct dom_sid *sid;
-
-               sid = talloc(*sids, struct dom_sid);
-               W_ERROR_HAVE_NO_MEMORY(sid);
-               status = dsdb_get_extended_dn_sid(dn, sid, "SID");
-               if (!NT_STATUS_IS_OK(status)) {
-                       return WERR_INTERNAL_DB_CORRUPTION;
-               }
-               (*sids)[i] = sid;
-       }
-       (*sids)[i] = NULL;
-
-       return WERR_OK;
-}
-
-
-/*
-  return an array of SIDs from a ldb_message given an attribute name
-  assumes the SIDs are in NDR form
- */
-static WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx,
-                                        struct ldb_message *msg,
-                                        TALLOC_CTX *mem_ctx,
-                                        const char *attr,
-                                        const struct dom_sid ***sids)
-{
-       struct ldb_message_element *el;
-       unsigned int i;
-
-       el = ldb_msg_find_element(msg, attr);
-       if (!el) {
-               *sids = NULL;
-               return WERR_OK;
-       }
-
-       (*sids) = talloc_array(mem_ctx, const struct dom_sid *, el->num_values + 1);
-       W_ERROR_HAVE_NO_MEMORY(*sids);
-
-       for (i=0; i<el->num_values; i++) {
-               enum ndr_err_code ndr_err;
-               struct dom_sid *sid;
-
-               sid = talloc(*sids, struct dom_sid);
-               W_ERROR_HAVE_NO_MEMORY(sid);
-
-               ndr_err = ndr_pull_struct_blob(&el->values[i], sid, sid,
-                                              (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
-               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-                       return WERR_INTERNAL_DB_CORRUPTION;
-               }
-               (*sids)[i] = sid;
-       }
-       (*sids)[i] = NULL;
-
-       return WERR_OK;
-}
-
-/*
-  see if any SIDs in list1 are in list2
- */
-static bool sid_list_match(const struct dom_sid **list1, const struct dom_sid **list2)
-{
-       unsigned int i, j;
-       /* do we ever have enough SIDs here to worry about O(n^2) ? */
-       for (i=0; list1[i]; i++) {
-               for (j=0; list2[j]; j++) {
-                       if (dom_sid_equal(list1[i], list2[j])) {
-                               return true;
-                       }
-               }
-       }
-       return false;
-}
-
 /*
   handle a DRSUAPI_EXOP_REPL_SECRET call
  */
@@ -1191,9 +1161,11 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
        int ret;
        const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", "objectGUID", NULL };
        const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL };
-       struct ldb_result *rodc_res, *obj_res;
+       struct ldb_result *rodc_res = NULL, *obj_res = NULL;
        const struct dom_sid **never_reveal_sids, **reveal_sids, **token_sids;
+       const struct dom_sid *object_sid = NULL;
        WERROR werr;
+       const struct dom_sid *additional_sids[] = { NULL, NULL };
 
        DEBUG(3,(__location__ ": DRSUAPI_EXOP_REPL_SECRET extended op on %s\n",
                 drs_ObjectIdentifier_to_string(mem_ctx, ncRoot)));
@@ -1209,7 +1181,7 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
        if (b_state->sam_ctx_system == NULL) {
                /* this operation needs system level access */
                ctr6->extended_ret = DRSUAPI_EXOP_ERR_ACCESS_DENIED;
-               return WERR_DS_DRA_SOURCE_DISABLED;
+               return WERR_DS_DRA_ACCESS_DENIED;
        }
 
        /*
@@ -1257,13 +1229,13 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
         * Which basically means that if you have GET_ALL_CHANGES rights (~== RWDC)
         * then you can do EXOP_REPL_SECRETS
         */
+       obj_dn = drs_ObjectIdentifier_to_dn(mem_ctx, b_state->sam_ctx_system, ncRoot);
+       if (!ldb_dn_validate(obj_dn)) goto failed;
+
        if (has_get_all_changes) {
                goto allowed;
        }
 
-       obj_dn = drs_ObjectIdentifier_to_dn(mem_ctx, b_state->sam_ctx_system, ncRoot);
-       if (!ldb_dn_validate(obj_dn)) goto failed;
-
        rodc_dn = ldb_dn_new_fmt(mem_ctx, b_state->sam_ctx_system, "<SID=%s>",
                                 dom_sid_string(mem_ctx, user_sid));
        if (!ldb_dn_validate(rodc_dn)) goto failed;
@@ -1277,11 +1249,13 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
        if (ret != LDB_SUCCESS || obj_res->count != 1) goto failed;
 
        /* if the object SID is equal to the user_sid, allow */
-       if (dom_sid_equal(user_sid,
-                         samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid"))) {
+       object_sid = samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid");
+       if (dom_sid_equal(user_sid, object_sid)) {
                goto allowed;
        }
 
+       additional_sids[0] = object_sid;
+
        /*
         * Must be an RODC account at this point, verify machine DN matches the
         * SID account
@@ -1322,8 +1296,14 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
                goto denied;
        }
 
+       /*
+        * The SID list needs to include itself as well as the tokenGroups.
+        *
+        * TODO determine if sIDHistory is required for this check
+        */
        werr = samdb_result_sid_array_ndr(b_state->sam_ctx_system, obj_res->msgs[0],
-                                        mem_ctx, "tokenGroups", &token_sids);
+                                         mem_ctx, "tokenGroups", &token_sids,
+                                         additional_sids, 1);
        if (!W_ERROR_IS_OK(werr) || token_sids==NULL) {
                goto denied;
        }
@@ -1348,7 +1328,7 @@ denied:
 allowed:
        DEBUG(2,(__location__ ": Allowed single object with secret replication for %s by %s %s\n",
                 ldb_dn_get_linearized(obj_dn), has_get_all_changes?"RWDC":"RODC",
-                ldb_dn_get_linearized(rodc_res->msgs[0]->dn)));
+                ldb_dn_get_linearized(*machine_dn)));
        ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS;
        req10->highwatermark.highest_usn = 0;
        return WERR_OK;
@@ -2045,7 +2025,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_
        uint32_t link_total = 0;
        uint32_t link_given = 0;
        struct ldb_dn *search_dn = NULL;
-       bool am_rodc, null_scope=false;
+       bool am_rodc;
        enum security_user_level security_level;
        struct ldb_context *sam_ctx;
        struct dom_sid *user_sid;
@@ -2066,6 +2046,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_
        DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE);
        b_state = h->data;
 
+       /* sam_ctx_system is not present for non-administrator users */
        sam_ctx = b_state->sam_ctx_system?b_state->sam_ctx_system:b_state->sam_ctx;
 
        invocation_id = *(samdb_ntds_invocation_id(sam_ctx));
@@ -2130,7 +2111,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_
        user_sid = &dce_call->conn->auth_state.session_info->security_token->sids[PRIMARY_USER_SID_INDEX];
 
        /* all clients must have GUID_DRS_GET_CHANGES */
-       werr = drs_security_access_check_nc_root(b_state->sam_ctx,
+       werr = drs_security_access_check_nc_root(sam_ctx,
                                                 mem_ctx,
                                                 dce_call->conn->auth_state.session_info->security_token,
                                                 req10->naming_context,
@@ -2172,7 +2153,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_
                return werr;
        }
        if (is_gc_pas_request) {
-               werr = drs_security_access_check_nc_root(b_state->sam_ctx,
+               werr = drs_security_access_check_nc_root(sam_ctx,
                                                         mem_ctx,
                                                         dce_call->conn->auth_state.session_info->security_token,
                                                         req10->naming_context,
@@ -2189,7 +2170,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_
                return werr;
        }
        if (is_secret_request) {
-               werr = drs_security_access_check_nc_root(b_state->sam_ctx,
+               werr = drs_security_access_check_nc_root(sam_ctx,
                                                         mem_ctx,
                                                         dce_call->conn->auth_state.session_info->security_token,
                                                         req10->naming_context,
@@ -2243,8 +2224,8 @@ allowed:
                                 ldb_dn_get_linearized(new_dn),
                                 ldb_dn_get_linearized(getnc_state->ncRoot_dn),
                                 ldb_dn_get_linearized(getnc_state->last_dn)));
-                       talloc_free(getnc_state);
-                       getnc_state = NULL;
+                       TALLOC_FREE(getnc_state);
+                       b_state->getncchanges_state = NULL;
                }
        }
 
@@ -2257,37 +2238,38 @@ allowed:
                                 ldb_dn_get_linearized(getnc_state->ncRoot_dn),
                                 (ret > 0) ? "older" : "newer",
                                 ldb_dn_get_linearized(getnc_state->last_dn)));
-                       talloc_free(getnc_state);
-                       getnc_state = NULL;
+                       TALLOC_FREE(getnc_state);
+                       b_state->getncchanges_state = NULL;
                }
        }
 
        if (getnc_state == NULL) {
-               getnc_state = talloc_zero(b_state, struct drsuapi_getncchanges_state);
-               if (getnc_state == NULL) {
-                       return WERR_NOT_ENOUGH_MEMORY;
-               }
-               b_state->getncchanges_state = getnc_state;
-               getnc_state->ncRoot_dn = drs_ObjectIdentifier_to_dn(getnc_state, sam_ctx, ncRoot);
-               if (getnc_state->ncRoot_dn == NULL) {
+               struct ldb_result *res = NULL;
+               const char *attrs[] = {
+                       "instanceType",
+                       "objectGuID",
+                       NULL
+               };
+               uint32_t nc_instanceType;
+               struct ldb_dn *ncRoot_dn;
+
+               ncRoot_dn = drs_ObjectIdentifier_to_dn(mem_ctx, sam_ctx, ncRoot);
+               if (ncRoot_dn == NULL) {
                        return WERR_NOT_ENOUGH_MEMORY;
                }
 
-               ret = dsdb_find_guid_by_dn(b_state->sam_ctx_system,
-                                          getnc_state->ncRoot_dn,
-                                          &getnc_state->ncRoot_guid);
+               ret = dsdb_search_dn(sam_ctx, mem_ctx, &res,
+                                    ncRoot_dn, attrs,
+                                    DSDB_SEARCH_SHOW_DELETED |
+                                    DSDB_SEARCH_SHOW_RECYCLED);
                if (ret != LDB_SUCCESS) {
-                       DEBUG(0,(__location__ ": Failed to find GUID of ncRoot_dn %s\n",
-                                ldb_dn_get_linearized(getnc_state->ncRoot_dn)));
-                       return WERR_DS_DRA_INTERNAL_ERROR;
+                       DBG_WARNING("Failed to find ncRoot_dn %s\n",
+                                   ldb_dn_get_linearized(ncRoot_dn));
+                       return WERR_DS_DRA_BAD_DN;
                }
-               ncRoot->guid = getnc_state->ncRoot_guid;
-
-               /* find out if we are to replicate Schema NC */
-               ret = ldb_dn_compare_base(ldb_get_schema_basedn(b_state->sam_ctx),
-                                         getnc_state->ncRoot_dn);
-
-               getnc_state->is_schema_nc = (0 == ret);
+               nc_instanceType = ldb_msg_find_attr_as_int(res->msgs[0],
+                                                          "instanceType",
+                                                          0);
 
                if (req10->extended_op != DRSUAPI_EXOP_NONE) {
                        r->out.ctr->ctr6.extended_ret = DRSUAPI_EXOP_ERR_SUCCESS;
@@ -2301,6 +2283,16 @@ allowed:
                 */
                switch (req10->extended_op) {
                case DRSUAPI_EXOP_NONE:
+                       if ((nc_instanceType & INSTANCE_TYPE_IS_NC_HEAD) == 0) {
+                               const char *dn_str
+                                       = ldb_dn_get_linearized(ncRoot_dn);
+
+                               DBG_NOTICE("Rejecting full replication on "
+                                          "not NC %s", dn_str);
+
+                               return WERR_DS_CANT_FIND_EXPECTED_NC;
+                       }
+
                        break;
                case DRSUAPI_EXOP_FSMO_RID_ALLOC:
                        werr = getncchanges_rid_alloc(b_state, mem_ctx, req10, &r->out.ctr->ctr6, &search_dn);
@@ -2351,6 +2343,27 @@ allowed:
                                 (unsigned)req10->extended_op));
                        return WERR_DS_DRA_NOT_SUPPORTED;
                }
+
+               /* Initialize the state we'll store over the replication cycle */
+               getnc_state = talloc_zero(b_state, struct drsuapi_getncchanges_state);
+               if (getnc_state == NULL) {
+                       return WERR_NOT_ENOUGH_MEMORY;
+               }
+               b_state->getncchanges_state = getnc_state;
+
+               getnc_state->ncRoot_dn = ncRoot_dn;
+               talloc_steal(getnc_state, ncRoot_dn);
+
+               getnc_state->ncRoot_guid = samdb_result_guid(res->msgs[0],
+                                                            "objectGUID");
+               ncRoot->guid = getnc_state->ncRoot_guid;
+
+               /* find out if we are to replicate Schema NC */
+               ret = ldb_dn_compare_base(ldb_get_schema_basedn(sam_ctx),
+                                         ncRoot_dn);
+               getnc_state->is_schema_nc = (0 == ret);
+
+               TALLOC_FREE(res);
        }
 
        if (!ldb_dn_validate(getnc_state->ncRoot_dn) ||
@@ -2381,28 +2394,35 @@ allowed:
 
                extra_filter = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "object filter");
 
-               if (req10->uptodateness_vector != NULL) {
-                       udv = req10->uptodateness_vector;
-               } else {
-                       udv = &empty_udv;
-               }
+               if (req10->extended_op == DRSUAPI_EXOP_NONE) {
+                       if (req10->uptodateness_vector != NULL) {
+                               udv = req10->uptodateness_vector;
+                       } else {
+                               udv = &empty_udv;
+                       }
 
-               getnc_state->min_usn = req10->highwatermark.highest_usn;
-               for (i = 0; i < udv->count; i++) {
-                       bool match;
-                       const struct drsuapi_DsReplicaCursor *cur =
-                               &udv->cursors[i];
+                       getnc_state->min_usn = req10->highwatermark.highest_usn;
+                       for (i = 0; i < udv->count; i++) {
+                               bool match;
+                               const struct drsuapi_DsReplicaCursor *cur =
+                                       &udv->cursors[i];
 
-                       match = GUID_equal(&invocation_id,
-                                          &cur->source_dsa_invocation_id);
-                       if (!match) {
-                               continue;
-                       }
-                       if (cur->highest_usn > getnc_state->min_usn) {
-                               getnc_state->min_usn = cur->highest_usn;
+                               match = GUID_equal(&invocation_id,
+                                                  &cur->source_dsa_invocation_id);
+                               if (!match) {
+                                       continue;
+                               }
+                               if (cur->highest_usn > getnc_state->min_usn) {
+                                       getnc_state->min_usn = cur->highest_usn;
+                               }
+                               break;
                        }
-                       break;
+               } else {
+                       /* We do not want REPL_SECRETS or REPL_SINGLE to return empty-handed */
+                       udv = &empty_udv;
+                       getnc_state->min_usn = 0;
                }
+
                getnc_state->max_usn = getnc_state->min_usn;
 
                getnc_state->final_udv = talloc_zero(getnc_state,
@@ -2548,7 +2568,7 @@ allowed:
                struct dsdb_syntax_ctx syntax_ctx;
                uint32_t j = 0;
 
-               dsdb_syntax_ctx_init(&syntax_ctx, b_state->sam_ctx, schema);
+               dsdb_syntax_ctx_init(&syntax_ctx, sam_ctx, schema);
                syntax_ctx.pfm_remote = pfm_remote;
 
                local_pas = talloc_array(b_state, uint32_t, req10->partial_attribute_set->num_attids);
@@ -2569,7 +2589,6 @@ allowed:
 
        for (i=getnc_state->num_processed;
             i<getnc_state->num_records &&
-                    !null_scope &&
                     (r->out.ctr->ctr6.object_count < max_objects)
                     && !max_wait_reached;
            i++) {
@@ -2594,8 +2613,13 @@ allowed:
                W_ERROR_HAVE_NO_MEMORY(msg_dn);
 
 
-               /* by re-searching here we avoid having a lot of full
-                * records in memory between calls to getncchanges
+               /*
+                * by re-searching here we avoid having a lot of full
+                * records in memory between calls to getncchanges.
+                *
+                * We expect that we may get some objects that vanish
+                * (tombstone expunge) between the first and second
+                * check.
                 */
                ret = drsuapi_search_with_extended_dn(sam_ctx, obj, &msg_res,
                                                      msg_dn,
@@ -2609,6 +2633,16 @@ allowed:
                        continue;
                }
 
+               if (msg_res->count == 0) {
+                       DEBUG(1,("getncchanges: got LDB_SUCCESS but failed"
+                                "to get any results in fetch of DN "
+                                "%s (race with tombstone expunge?)\n",
+                                ldb_dn_get_extended_linearized(obj,
+                                                               msg_dn, 1)));
+                       talloc_free(obj);
+                       continue;
+               }
+
                msg = msg_res->msgs[0];
 
                /*
@@ -2640,7 +2674,8 @@ allowed:
                                                   req10->uptodateness_vector,
                                                   req10->extended_op,
                                                   max_wait_reached,
-                                                  local_pas, machine_dn);
+                                                  local_pas, machine_dn,
+                                                  &getnc_state->guids[i]);
                if (!W_ERROR_IS_OK(werr)) {
                        return werr;
                }
@@ -2752,7 +2787,8 @@ allowed:
                                                           req10->extended_op,
                                                           false, /* force_object_return */
                                                           local_pas,
-                                                          machine_dn);
+                                                          machine_dn,
+                                                          next_anc_guid);
                        if (!W_ERROR_IS_OK(werr)) {
                                return werr;
                        }
@@ -2832,7 +2868,7 @@ allowed:
                DEBUG(3,("UpdateRefs on getncchanges for %s\n",
                         GUID_string(mem_ctx, &req10->destination_dsa_guid)));
                ureq.naming_context = ncRoot;
-               ureq.dest_dsa_dns_name = samdb_ntds_msdcs_dns_name(b_state->sam_ctx, mem_ctx,
+               ureq.dest_dsa_dns_name = samdb_ntds_msdcs_dns_name(sam_ctx, mem_ctx,
                                                                   &req10->destination_dsa_guid);
                if (!ureq.dest_dsa_dns_name) {
                        return WERR_NOT_ENOUGH_MEMORY;
@@ -2847,7 +2883,9 @@ allowed:
                   to send notifies using the GC SPN */
                ureq.options |= (req10->replica_flags & DRSUAPI_DRS_REF_GCSPN);
 
-               werr = drsuapi_UpdateRefs(b_state, mem_ctx, &ureq);
+               werr = drsuapi_UpdateRefs(dce_call->msg_ctx,
+                                         dce_call->event_ctx, b_state,
+                                         mem_ctx, &ureq);
                if (!W_ERROR_IS_OK(werr)) {
                        DEBUG(0,(__location__ ": Failed UpdateRefs on %s for %s in DsGetNCChanges - %s\n",
                                 drs_ObjectIdentifier_to_string(mem_ctx, ncRoot), ureq.dest_dsa_dns_name,