X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source4%2Frpc_server%2Fdrsuapi%2Fgetncchanges.c;h=5faea27967663265c16a72da261758460b17afe2;hb=1dae26336658482481f18c2c179c860c79e13fb8;hp=3245e0191122597d3e41374fb7d5620b30a20406;hpb=1ec5f5c09cc0e61fc41de720820ccddf6f6259e6;p=mat%2Fsamba.git diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c index 3245e01911..5faea27967 100644 --- a/source4/rpc_server/drsuapi/getncchanges.c +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -27,10 +27,11 @@ #include "librpc/gen_ndr/ndr_drsblobs.h" #include "librpc/gen_ndr/ndr_drsuapi.h" #include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" +#include "libcli/security/session.h" #include "rpc_server/drsuapi/dcesrv_drsuapi.h" #include "rpc_server/dcerpc_server_proto.h" #include "../libcli/drsuapi/drsuapi.h" -#include "libcli/security/security.h" #include "lib/util/binsearch.h" #include "lib/util/tsort.h" #include "auth/session.h" @@ -121,7 +122,7 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem enum drsuapi_DsExtendedOperation extended_op) { const struct ldb_val *md_value; - unsigned int i, n; + uint32_t i, n; struct replPropertyMetaDataBlob md; uint32_t rid = 0; enum ndr_err_code ndr_err; @@ -206,7 +207,7 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem instanceType then don't include it */ if (md.ctr.ctr1.array[i].local_usn < highest_usn && extended_op != DRSUAPI_EXOP_REPL_SECRET && - md.ctr.ctr1.array[i].attid != DRSUAPI_ATTRIBUTE_instanceType) continue; + md.ctr.ctr1.array[i].attid != DRSUAPI_ATTID_instanceType) continue; /* don't include the rDN */ if (md.ctr.ctr1.array[i].attid == rdn_sa->attributeID_id) continue; @@ -236,7 +237,7 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem } /* filter by uptodateness_vector */ - if (md.ctr.ctr1.array[i].attid != DRSUAPI_ATTRIBUTE_instanceType && + if (md.ctr.ctr1.array[i].attid != DRSUAPI_ATTID_instanceType && !force_attribute && udv_filter(uptodateness_vector, &md.ctr.ctr1.array[i].originating_invocation_id, @@ -261,7 +262,7 @@ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItem * change the 'name' attribute, so they won't be ignored by * this */ if (n == 0 || - (n == 1 && attids[0] == DRSUAPI_ATTRIBUTE_instanceType)) { + (n == 1 && attids[0] == DRSUAPI_ATTID_instanceType)) { talloc_free(obj->meta_data_ctr); obj->meta_data_ctr = NULL; return WERR_OK; @@ -356,30 +357,95 @@ static WERROR get_nc_changes_add_la(TALLOC_CTX *mem_ctx, active = (dsdb_dn_rmd_flags(dsdb_dn->dn) & DSDB_RMD_FLAG_DELETED) == 0; + if (!active) { + /* We have to check that the inactive link still point to an existing object */ + struct GUID guid; + struct ldb_dn *tdn; + int ret; + const char *v; + + v = ldb_msg_find_attr_as_string(msg, "isDeleted", "false"); + if (strncmp(v, "TRUE", 4) == 0) { + /* + * Note: we skip the transmition of the deleted link even if the other part used to + * know about it because when we transmit the deletion of the object, the link will + * be deleted too due to deletion of object where link points and Windows do so. + */ + if (dsdb_functional_level(sam_ctx) >= DS_DOMAIN_FUNCTION_2008_R2) { + v = ldb_msg_find_attr_as_string(msg, "isRecycled", "true"); + /* + * On Windows 2008R2 isRecycled is always present even if FL or DL are < FL 2K8R2 + * if it join an existing domain with deleted objets, it firsts impose to have a + * schema with the is-Recycled object and for all deleted objects it adds the isRecycled + * either during initial replication or after the getNCChanges. + * Behavior of samba has been changed to always have this attribute if it's present in the schema. + * + * So if FL <2K8R2 isRecycled might be here or not but we don't care, it's meaning less. + * If FL >=2K8R2 we are sure that this attribute will be here. + * For this kind of forest level we do not return the link if the object is recycled + * (isRecycled = true). + */ + if (strncmp(v, "TRUE", 4) == 0) { + DEBUG(2, (" object %s is recycled, not returning linked attribute !\n", + ldb_dn_get_linearized(msg->dn))); + return WERR_OK; + } + } else { + return WERR_OK; + } + } + status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &guid, "GUID"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " Unable to extract GUID in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); + return ntstatus_to_werror(status); + } + ret = dsdb_find_dn_by_guid(sam_ctx, mem_ctx, &guid, &tdn); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(2, (" Search of guid %s returned 0 objects, skipping it !\n", + GUID_string(mem_ctx, &guid))); + return WERR_OK; + } else if (ret != LDB_SUCCESS) { + DEBUG(0, (__location__ " Search of guid %s failed with error code %d\n", + GUID_string(mem_ctx, &guid), + ret)); + return WERR_OK; + } + } la->attid = sa->attributeID_id; la->flags = active?DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE:0; - status = dsdb_get_extended_dn_nttime(dsdb_dn->dn, &la->originating_add_time, "RMD_ADDTIME"); - if (!NT_STATUS_IS_OK(status)) { - return ntstatus_to_werror(status); - } status = dsdb_get_extended_dn_uint32(dsdb_dn->dn, &la->meta_data.version, "RMD_VERSION"); if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " No RMD_VERSION in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); return ntstatus_to_werror(status); } status = dsdb_get_extended_dn_nttime(dsdb_dn->dn, &la->meta_data.originating_change_time, "RMD_CHANGETIME"); if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " No RMD_CHANGETIME in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); return ntstatus_to_werror(status); } status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &la->meta_data.originating_invocation_id, "RMD_INVOCID"); if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " No RMD_INVOCID in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); return ntstatus_to_werror(status); } status = dsdb_get_extended_dn_uint64(dsdb_dn->dn, &la->meta_data.originating_usn, "RMD_ORIGINATING_USN"); if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " No RMD_ORIGINATING_USN in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); return ntstatus_to_werror(status); } + status = dsdb_get_extended_dn_nttime(dsdb_dn->dn, &la->originating_add_time, "RMD_ADDTIME"); + if (!NT_STATUS_IS_OK(status)) { + /* this is possible for upgraded links */ + la->originating_add_time = la->meta_data.originating_change_time; + } + werr = dsdb_dn_la_to_blob(sam_ctx, sa, schema, *la_list, dsdb_dn, &la->value.blob); W_ERROR_NOT_OK_RETURN(werr); @@ -596,7 +662,7 @@ static int site_res_cmp_usn_order(struct ldb_message **m1, struct ldb_message ** */ static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, - struct drsuapi_DsGetNCChangesRequest8 *req8, + struct drsuapi_DsGetNCChangesRequest10 *req10, struct drsuapi_DsGetNCChangesCtr6 *ctr6) { struct ldb_dn *rid_manager_dn, *fsmo_role_dn, *req_dn; @@ -619,13 +685,12 @@ static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state, return WERR_DS_DRA_INTERNAL_ERROR; } - req_dn = drs_ObjectIdentifier_to_dn(mem_ctx, ldb, req8->naming_context); - if (!req_dn || - !ldb_dn_validate(req_dn) || + req_dn = drs_ObjectIdentifier_to_dn(mem_ctx, ldb, req10->naming_context); + if (!ldb_dn_validate(req_dn) || ldb_dn_compare(req_dn, rid_manager_dn) != 0) { /* that isn't the RID Manager DN */ DEBUG(0,(__location__ ": RID Alloc request for wrong DN %s\n", - drs_ObjectIdentifier_to_string(mem_ctx, req8->naming_context))); + drs_ObjectIdentifier_to_string(mem_ctx, req10->naming_context))); ctr6->extended_ret = DRSUAPI_EXOP_ERR_MISMATCH; return WERR_OK; } @@ -649,8 +714,8 @@ static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state, exop = talloc(mem_ctx, struct dsdb_fsmo_extended_op); W_ERROR_HAVE_NO_MEMORY(exop); - exop->fsmo_info = req8->fsmo_info; - exop->destination_dsa_guid = req8->destination_dsa_guid; + exop->fsmo_info = req10->fsmo_info; + exop->destination_dsa_guid = req10->destination_dsa_guid; ret = ldb_transaction_start(ldb); if (ret != LDB_SUCCESS) { @@ -666,7 +731,7 @@ static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state, * building function to return just the set of object * documented for DRSUAPI_EXOP_FSMO_RID_ALLOC extended_op */ - ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ, &req8->highwatermark.highest_usn); + ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ, &req10->highwatermark.highest_usn); ret = ldb_extended(ldb, DSDB_EXTENDED_ALLOCATE_RID_POOL, exop, &ext_res); if (ret != LDB_SUCCESS) { @@ -688,7 +753,7 @@ static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state, base_dn = ldb_get_default_basedn(ldb); DEBUG(2,("Allocated RID pool for server %s\n", - GUID_string(mem_ctx, &req8->destination_dsa_guid))); + GUID_string(mem_ctx, &req10->destination_dsa_guid))); ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; @@ -799,11 +864,11 @@ static bool sid_list_match(const struct dom_sid **list1, const struct dom_sid ** */ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, - struct drsuapi_DsGetNCChangesRequest8 *req8, + struct drsuapi_DsGetNCChangesRequest10 *req10, struct dom_sid *user_sid, struct drsuapi_DsGetNCChangesCtr6 *ctr6) { - struct drsuapi_DsReplicaObjectIdentifier *ncRoot = req8->naming_context; + struct drsuapi_DsReplicaObjectIdentifier *ncRoot = req10->naming_context; struct ldb_dn *obj_dn, *rodc_dn, *krbtgt_link_dn; int ret; const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", NULL }; @@ -864,7 +929,8 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, goto denied; } - if (samdb_result_uint(obj_res->msgs[0], "UserAccountControl", 0) & + if (ldb_msg_find_attr_as_uint(obj_res->msgs[0], + "userAccountControl", 0) & UF_INTERDOMAIN_TRUST_ACCOUNT) { goto denied; } @@ -908,7 +974,7 @@ allowed: DEBUG(2,(__location__ ": Allowed RODC secret replication for %s by RODC %s\n", ldb_dn_get_linearized(obj_dn), ldb_dn_get_linearized(rodc_res->msgs[0]->dn))); ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; - req8->highwatermark.highest_usn = 0; + req10->highwatermark.highest_usn = 0; return WERR_OK; failed: @@ -924,17 +990,17 @@ failed: */ static WERROR getncchanges_repl_obj(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, - struct drsuapi_DsGetNCChangesRequest8 *req8, + struct drsuapi_DsGetNCChangesRequest10 *req10, struct dom_sid *user_sid, struct drsuapi_DsGetNCChangesCtr6 *ctr6) { - struct drsuapi_DsReplicaObjectIdentifier *ncRoot = req8->naming_context; + struct drsuapi_DsReplicaObjectIdentifier *ncRoot = req10->naming_context; DEBUG(3,(__location__ ": DRSUAPI_EXOP_REPL_OBJ extended op on %s\n", drs_ObjectIdentifier_to_string(mem_ctx, ncRoot))); ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; - req8->highwatermark.highest_usn = 0; + req10->highwatermark.highest_usn = 0; return WERR_OK; } @@ -946,7 +1012,7 @@ static WERROR getncchanges_repl_obj(struct drsuapi_bind_state *b_state, */ static WERROR getncchanges_change_master(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, - struct drsuapi_DsGetNCChangesRequest8 *req8, + struct drsuapi_DsGetNCChangesRequest10 *req10, struct drsuapi_DsGetNCChangesCtr6 *ctr6) { struct ldb_dn *fsmo_role_dn, *req_dn, *ntds_dn; @@ -961,12 +1027,11 @@ static WERROR getncchanges_change_master(struct drsuapi_bind_state *b_state, - verify that we are the current master */ - req_dn = drs_ObjectIdentifier_to_dn(mem_ctx, ldb, req8->naming_context); - if (!req_dn || - !ldb_dn_validate(req_dn)) { + req_dn = drs_ObjectIdentifier_to_dn(mem_ctx, ldb, req10->naming_context); + if (!ldb_dn_validate(req_dn)) { /* that is not a valid dn */ DEBUG(0,(__location__ ": FSMO role transfer request for invalid DN %s\n", - drs_ObjectIdentifier_to_string(mem_ctx, req8->naming_context))); + drs_ObjectIdentifier_to_string(mem_ctx, req10->naming_context))); ctr6->extended_ret = DRSUAPI_EXOP_ERR_MISMATCH; return WERR_OK; } @@ -990,15 +1055,17 @@ static WERROR getncchanges_change_master(struct drsuapi_bind_state *b_state, /* change the current master */ msg = ldb_msg_new(ldb); W_ERROR_HAVE_NO_MEMORY(msg); - msg->dn = drs_ObjectIdentifier_to_dn(msg, ldb, req8->naming_context); + msg->dn = drs_ObjectIdentifier_to_dn(msg, ldb, req10->naming_context); W_ERROR_HAVE_NO_MEMORY(msg->dn); - ret = dsdb_find_dn_by_guid(ldb, msg, &req8->destination_dsa_guid, &ntds_dn); + /* TODO: make sure ntds_dn is a valid nTDSDSA object */ + ret = dsdb_find_dn_by_guid(ldb, msg, &req10->destination_dsa_guid, &ntds_dn); if (ret != LDB_SUCCESS) { DEBUG(0, (__location__ ": Unable to find NTDS object for guid %s - %s\n", - GUID_string(mem_ctx, &req8->destination_dsa_guid), ldb_errstring(ldb))); + GUID_string(mem_ctx, &req10->destination_dsa_guid), ldb_errstring(ldb))); talloc_free(msg); - return WERR_DS_DRA_INTERNAL_ERROR; + ctr6->extended_ret = DRSUAPI_EXOP_ERR_UNKNOWN_CALLER; + return WERR_OK; } ret = ldb_msg_add_string(msg, "fSMORoleOwner", ldb_dn_get_linearized(ntds_dn)); @@ -1040,8 +1107,9 @@ static WERROR getncchanges_change_master(struct drsuapi_bind_state *b_state, /* state of a partially completed getncchanges call */ struct drsuapi_getncchanges_state { - struct ldb_result *site_res; - uint32_t num_sent; + struct GUID *guids; + uint32_t num_records; + uint32_t num_processed; struct ldb_dn *ncRoot_dn; bool is_schema_nc; uint64_t min_usn; @@ -1058,16 +1126,16 @@ struct drsuapi_getncchanges_state { see if this getncchanges request includes a request to reveal secret information */ static WERROR dcesrv_drsuapi_is_reveal_secrets_request(struct drsuapi_bind_state *b_state, - struct drsuapi_DsGetNCChangesRequest8 *req8, + struct drsuapi_DsGetNCChangesRequest10 *req10, bool *is_secret_request) { enum drsuapi_DsExtendedOperation exop; - int i; + uint32_t i; struct dsdb_schema *schema; *is_secret_request = true; - exop = req8->extended_op; + exop = req10->extended_op; switch (exop) { case DRSUAPI_EXOP_FSMO_REQ_ROLE: @@ -1084,13 +1152,13 @@ static WERROR dcesrv_drsuapi_is_reveal_secrets_request(struct drsuapi_bind_state break; } - if (req8->replica_flags & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { + if (req10->replica_flags & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { *is_secret_request = false; return WERR_OK; } if (exop == DRSUAPI_EXOP_REPL_SECRET || - req8->partial_attribute_set == NULL) { + req10->partial_attribute_set == NULL) { /* they want secrets */ *is_secret_request = true; return WERR_OK; @@ -1099,9 +1167,9 @@ static WERROR dcesrv_drsuapi_is_reveal_secrets_request(struct drsuapi_bind_state schema = dsdb_get_schema(b_state->sam_ctx, NULL); /* check the attributes they asked for */ - for (i=0; ipartial_attribute_set->num_attids; i++) { + for (i=0; ipartial_attribute_set->num_attids; i++) { const struct dsdb_attribute *sa; - sa = dsdb_attribute_by_attributeID_id(schema, req8->partial_attribute_set->attids[i]); + sa = dsdb_attribute_by_attributeID_id(schema, req10->partial_attribute_set->attids[i]); if (sa == NULL) { return WERR_DS_DRA_SCHEMA_MISMATCH; } @@ -1111,24 +1179,209 @@ static WERROR dcesrv_drsuapi_is_reveal_secrets_request(struct drsuapi_bind_state } } + if (req10->partial_attribute_set_ex) { + /* check the extended attributes they asked for */ + for (i=0; ipartial_attribute_set_ex->num_attids; i++) { + const struct dsdb_attribute *sa; + sa = dsdb_attribute_by_attributeID_id(schema, req10->partial_attribute_set_ex->attids[i]); + if (sa == NULL) { + return WERR_DS_DRA_SCHEMA_MISMATCH; + } + if (!dsdb_attr_in_rodc_fas(sa)) { + *is_secret_request = true; + return WERR_OK; + } + } + } + + *is_secret_request = false; + return WERR_OK; +} + +/* + see if this getncchanges request is only for attributes in the GC + partial attribute set + */ +static WERROR dcesrv_drsuapi_is_gc_pas_request(struct drsuapi_bind_state *b_state, + struct drsuapi_DsGetNCChangesRequest10 *req10, + bool *is_gc_pas_request) +{ + enum drsuapi_DsExtendedOperation exop; + uint32_t i; + struct dsdb_schema *schema; + + exop = req10->extended_op; + + switch (exop) { + case DRSUAPI_EXOP_FSMO_REQ_ROLE: + case DRSUAPI_EXOP_FSMO_RID_ALLOC: + case DRSUAPI_EXOP_FSMO_RID_REQ_ROLE: + case DRSUAPI_EXOP_FSMO_REQ_PDC: + case DRSUAPI_EXOP_FSMO_ABANDON_ROLE: + case DRSUAPI_EXOP_REPL_SECRET: + *is_gc_pas_request = false; + return WERR_OK; + case DRSUAPI_EXOP_REPL_OBJ: + case DRSUAPI_EXOP_NONE: + break; + } + + if (req10->partial_attribute_set == NULL) { + /* they want it all */ + *is_gc_pas_request = false; + return WERR_OK; + } + + schema = dsdb_get_schema(b_state->sam_ctx, NULL); + /* check the attributes they asked for */ - for (i=0; ipartial_attribute_set_ex->num_attids; i++) { + for (i=0; ipartial_attribute_set->num_attids; i++) { const struct dsdb_attribute *sa; - sa = dsdb_attribute_by_attributeID_id(schema, req8->partial_attribute_set_ex->attids[i]); + sa = dsdb_attribute_by_attributeID_id(schema, req10->partial_attribute_set->attids[i]); if (sa == NULL) { return WERR_DS_DRA_SCHEMA_MISMATCH; } - if (!dsdb_attr_in_rodc_fas(sa)) { - *is_secret_request = true; + if (!sa->isMemberOfPartialAttributeSet) { + *is_gc_pas_request = false; return WERR_OK; } } - *is_secret_request = false; + if (req10->partial_attribute_set_ex) { + /* check the extended attributes they asked for */ + for (i=0; ipartial_attribute_set_ex->num_attids; i++) { + const struct dsdb_attribute *sa; + sa = dsdb_attribute_by_attributeID_id(schema, req10->partial_attribute_set_ex->attids[i]); + if (sa == NULL) { + return WERR_DS_DRA_SCHEMA_MISMATCH; + } + if (!sa->isMemberOfPartialAttributeSet) { + *is_gc_pas_request = false; + return WERR_OK; + } + } + } + + *is_gc_pas_request = true; return WERR_OK; } +/* + map from req8 to req10 + */ +static struct drsuapi_DsGetNCChangesRequest10 * +getncchanges_map_req8(TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChangesRequest8 *req8) +{ + struct drsuapi_DsGetNCChangesRequest10 *req10 = talloc_zero(mem_ctx, + struct drsuapi_DsGetNCChangesRequest10); + if (req10 == NULL) { + return NULL; + } + + req10->destination_dsa_guid = req8->destination_dsa_guid; + req10->source_dsa_invocation_id = req8->source_dsa_invocation_id; + req10->naming_context = req8->naming_context; + req10->highwatermark = req8->highwatermark; + req10->uptodateness_vector = req8->uptodateness_vector; + req10->replica_flags = req8->replica_flags; + req10->max_object_count = req8->max_object_count; + req10->max_ndr_size = req8->max_ndr_size; + req10->extended_op = req8->extended_op; + req10->fsmo_info = req8->fsmo_info; + req10->partial_attribute_set = req8->partial_attribute_set; + req10->partial_attribute_set_ex = req8->partial_attribute_set_ex; + req10->mapping_ctr = req8->mapping_ctr; + + return req10; +} + + +/** + * Collects object for normal replication cycle. + */ +static WERROR getncchanges_collect_objects(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct ldb_dn *search_dn, + const char *extra_filter, + struct ldb_result **search_res) +{ + int ret; + char* search_filter; + enum ldb_scope scope = LDB_SCOPE_SUBTREE; + //const char *extra_filter; + struct drsuapi_getncchanges_state *getnc_state = b_state->getncchanges_state; + const char *attrs[] = { "uSNChanged", + "objectGUID" , + NULL }; + + if (req10->extended_op == DRSUAPI_EXOP_REPL_OBJ || + req10->extended_op == DRSUAPI_EXOP_REPL_SECRET) { + scope = LDB_SCOPE_BASE; + } + + //extra_filter = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "object filter"); + + //getnc_state->min_usn = req10->highwatermark.highest_usn; + + /* Construct response. */ + search_filter = talloc_asprintf(mem_ctx, + "(uSNChanged>=%llu)", + (unsigned long long)(getnc_state->min_usn+1)); + + if (extra_filter) { + search_filter = talloc_asprintf(mem_ctx, "(&%s(%s))", search_filter, extra_filter); + } + + if (req10->replica_flags & DRSUAPI_DRS_CRITICAL_ONLY) { + search_filter = talloc_asprintf(mem_ctx, + "(&%s(isCriticalSystemObject=TRUE))", + search_filter); + } + + if (req10->replica_flags & DRSUAPI_DRS_ASYNC_REP) { + scope = LDB_SCOPE_BASE; + } + + if (!search_dn) { + search_dn = getnc_state->ncRoot_dn; + } + + DEBUG(2,(__location__ ": getncchanges on %s using filter %s\n", + ldb_dn_get_linearized(getnc_state->ncRoot_dn), search_filter)); + ret = drsuapi_search_with_extended_dn(b_state->sam_ctx, getnc_state, search_res, + search_dn, scope, attrs, + search_filter); + if (ret != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + return WERR_OK; +} + +/** + * Collects object for normal replication cycle. + */ +static WERROR getncchanges_collect_objects_exop(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct drsuapi_DsGetNCChangesCtr6 *ctr6, + struct ldb_dn *search_dn, + const char *extra_filter, + struct ldb_result **search_res) +{ + /* we have nothing to do in case of ex-op failure */ + if (ctr6->extended_ret != DRSUAPI_EXOP_ERR_SUCCESS) { + return WERR_OK; + } + + /* TODO: implement extended op specific collection + * of objects. Right now we just normal procedure + * for collecting objects */ + return getncchanges_collect_objects(b_state, mem_ctx, req10, search_dn, extra_filter, search_res); +} /* drsuapi_DsGetNCChanges @@ -1140,27 +1393,17 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ { struct drsuapi_DsReplicaObjectIdentifier *ncRoot; int ret; - unsigned int i; + uint32_t i; struct dsdb_schema *schema; struct drsuapi_DsReplicaOIDMapping_Ctr *ctr; struct drsuapi_DsReplicaObjectListItemEx **currentObject; NTSTATUS status; DATA_BLOB session_key; - const char *attrs[] = { "*", "distinguishedName", - "nTSecurityDescriptor", - "parentGUID", - "replPropertyMetaData", - "unicodePwd", - "dBCSPwd", - "ntPwdHistory", - "lmPwdHistory", - "supplementalCredentials", - NULL }; WERROR werr; struct dcesrv_handle *h; struct drsuapi_bind_state *b_state; struct drsuapi_getncchanges_state *getnc_state; - struct drsuapi_DsGetNCChangesRequest8 *req8; + struct drsuapi_DsGetNCChangesRequest10 *req10; uint32_t options; uint32_t max_objects; uint32_t max_links; @@ -1173,6 +1416,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ struct ldb_context *sam_ctx; struct dom_sid *user_sid; bool is_secret_request; + bool is_gc_pas_request; DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); b_state = h->data; @@ -1197,20 +1441,28 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ } /* Check request revision. - TODO: Adding mappings to req8 from the other levels */ - if (r->in.level != 8) { + switch (r->in.level) { + case 8: + req10 = getncchanges_map_req8(mem_ctx, &r->in.req->req8); + if (req10 == NULL) { + return WERR_NOMEM; + } + break; + case 10: + req10 = &r->in.req->req10; + break; + default: DEBUG(0,(__location__ ": Request for DsGetNCChanges with unsupported level %u\n", r->in.level)); return WERR_REVISION_MISMATCH; } - req8 = &r->in.req->req8; /* 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 */ - ncRoot = req8->naming_context; + ncRoot = req10->naming_context; if (ncRoot == NULL) { DEBUG(0,(__location__ ": Request for DsGetNCChanges with no NC\n")); return WERR_DS_DRA_INVALID_PARAMETER; @@ -1221,50 +1473,69 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ } if ((options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) && - !(req8->replica_flags & DRSUAPI_DRS_SYNC_FORCED)) { + !(req10->replica_flags & DRSUAPI_DRS_SYNC_FORCED)) { return WERR_DS_DRA_SOURCE_DISABLED; } 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, mem_ctx, dce_call->conn->auth_state.session_info->security_token, - req8->naming_context, + req10->naming_context, GUID_DRS_GET_CHANGES); if (!W_ERROR_IS_OK(werr)) { return werr; } - werr = dcesrv_drsuapi_is_reveal_secrets_request(b_state, req8, &is_secret_request); + /* allowed if the GC PAS and client has + GUID_DRS_GET_FILTERED_ATTRIBUTES */ + werr = dcesrv_drsuapi_is_gc_pas_request(b_state, req10, &is_gc_pas_request); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + if (is_gc_pas_request) { + werr = drs_security_access_check_nc_root(b_state->sam_ctx, + mem_ctx, + dce_call->conn->auth_state.session_info->security_token, + req10->naming_context, + GUID_DRS_GET_FILTERED_ATTRIBUTES); + if (W_ERROR_IS_OK(werr)) { + goto allowed; + } + } + + werr = dcesrv_drsuapi_is_reveal_secrets_request(b_state, req10, &is_secret_request); if (!W_ERROR_IS_OK(werr)) { return werr; } - if (is_secret_request && req8->extended_op != DRSUAPI_EXOP_REPL_SECRET) { + if (is_secret_request && req10->extended_op != DRSUAPI_EXOP_REPL_SECRET) { werr = drs_security_access_check_nc_root(b_state->sam_ctx, mem_ctx, dce_call->conn->auth_state.session_info->security_token, - req8->naming_context, + req10->naming_context, GUID_DRS_GET_ALL_CHANGES); if (!W_ERROR_IS_OK(werr)) { return werr; } } +allowed: /* for non-administrator replications, check that they have given the correct source_dsa_invocation_id */ security_level = security_session_user_level(dce_call->conn->auth_state.session_info, samdb_domain_sid(sam_ctx)); if (security_level == SECURITY_RO_DOMAIN_CONTROLLER) { - if (req8->replica_flags & DRSUAPI_DRS_WRIT_REP) { + if (req10->replica_flags & DRSUAPI_DRS_WRIT_REP) { /* we rely on this flag being unset for RODC requests */ - req8->replica_flags &= ~DRSUAPI_DRS_WRIT_REP; + req10->replica_flags &= ~DRSUAPI_DRS_WRIT_REP; } } - if (req8->replica_flags & DRSUAPI_DRS_FULL_SYNC_PACKET) { + if (req10->replica_flags & DRSUAPI_DRS_FULL_SYNC_PACKET) { /* Ignore the _in_ uptpdateness vector*/ - req8->uptodateness_vector = NULL; + req10->uptodateness_vector = NULL; } getnc_state = b_state->getncchanges_state; @@ -1295,39 +1566,43 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ ldb_get_schema_basedn(b_state->sam_ctx)); getnc_state->is_schema_nc = (0 == ret); + if (req10->extended_op != DRSUAPI_EXOP_NONE) { + r->out.ctr->ctr6.extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; + } + /* * This is the first replication cycle and it is * a good place to handle extended operations * * FIXME: we don't fully support extended operations yet */ - switch (req8->extended_op) { + switch (req10->extended_op) { case DRSUAPI_EXOP_NONE: break; case DRSUAPI_EXOP_FSMO_RID_ALLOC: - werr = getncchanges_rid_alloc(b_state, mem_ctx, req8, &r->out.ctr->ctr6); + werr = getncchanges_rid_alloc(b_state, mem_ctx, req10, &r->out.ctr->ctr6); W_ERROR_NOT_OK_RETURN(werr); search_dn = ldb_get_default_basedn(sam_ctx); break; case DRSUAPI_EXOP_REPL_SECRET: - werr = getncchanges_repl_secret(b_state, mem_ctx, req8, user_sid, &r->out.ctr->ctr6); + werr = getncchanges_repl_secret(b_state, mem_ctx, req10, user_sid, &r->out.ctr->ctr6); r->out.result = werr; W_ERROR_NOT_OK_RETURN(werr); break; case DRSUAPI_EXOP_FSMO_REQ_ROLE: - werr = getncchanges_change_master(b_state, mem_ctx, req8, &r->out.ctr->ctr6); + werr = getncchanges_change_master(b_state, mem_ctx, req10, &r->out.ctr->ctr6); W_ERROR_NOT_OK_RETURN(werr); break; case DRSUAPI_EXOP_FSMO_RID_REQ_ROLE: - werr = getncchanges_change_master(b_state, mem_ctx, req8, &r->out.ctr->ctr6); + werr = getncchanges_change_master(b_state, mem_ctx, req10, &r->out.ctr->ctr6); W_ERROR_NOT_OK_RETURN(werr); break; case DRSUAPI_EXOP_FSMO_REQ_PDC: - werr = getncchanges_change_master(b_state, mem_ctx, req8, &r->out.ctr->ctr6); + werr = getncchanges_change_master(b_state, mem_ctx, req10, &r->out.ctr->ctr6); W_ERROR_NOT_OK_RETURN(werr); break; case DRSUAPI_EXOP_REPL_OBJ: - werr = getncchanges_repl_obj(b_state, mem_ctx, req8, user_sid, &r->out.ctr->ctr6); + werr = getncchanges_repl_obj(b_state, mem_ctx, req10, user_sid, &r->out.ctr->ctr6); r->out.result = werr; W_ERROR_NOT_OK_RETURN(werr); break; @@ -1335,7 +1610,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ case DRSUAPI_EXOP_FSMO_ABANDON_ROLE: DEBUG(0,(__location__ ": Request for DsGetNCChanges unsupported extended op 0x%x\n", - (unsigned)req8->extended_op)); + (unsigned)req10->extended_op)); return WERR_DS_DRA_NOT_SUPPORTED; } } @@ -1358,63 +1633,55 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ TODO: MS-DRSR section 4.1.10.1.1 Work out if this is the start of a new cycle */ - if (getnc_state->site_res == NULL) { - char* search_filter; - enum ldb_scope scope = LDB_SCOPE_SUBTREE; + if (getnc_state->guids == NULL) { const char *extra_filter; - - if (req8->extended_op == DRSUAPI_EXOP_REPL_OBJ || - req8->extended_op == DRSUAPI_EXOP_REPL_SECRET) { - scope = LDB_SCOPE_BASE; - } + struct ldb_result *search_res = NULL; extra_filter = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "object filter"); - getnc_state->min_usn = req8->highwatermark.highest_usn; + getnc_state->min_usn = req10->highwatermark.highest_usn; - /* Construct response. */ - search_filter = talloc_asprintf(mem_ctx, - "(uSNChanged>=%llu)", - (unsigned long long)(getnc_state->min_usn+1)); - - if (extra_filter) { - search_filter = talloc_asprintf(mem_ctx, "(&%s(%s))", search_filter, extra_filter); - } - - if (req8->replica_flags & DRSUAPI_DRS_CRITICAL_ONLY) { - search_filter = talloc_asprintf(mem_ctx, - "(&%s(isCriticalSystemObject=TRUE))", - search_filter); - } - - if (req8->replica_flags & DRSUAPI_DRS_ASYNC_REP) { - scope = LDB_SCOPE_BASE; + if (req10->extended_op == DRSUAPI_EXOP_NONE) { + werr = getncchanges_collect_objects(b_state, mem_ctx, req10, + search_dn, extra_filter, + &search_res); + } else { + werr = getncchanges_collect_objects_exop(b_state, mem_ctx, req10, + &r->out.ctr->ctr6, + search_dn, extra_filter, + &search_res); } - - if (!search_dn) { - search_dn = getnc_state->ncRoot_dn; + W_ERROR_NOT_OK_RETURN(werr); + + if (search_res) { + if (req10->replica_flags & DRSUAPI_DRS_GET_ANC) { + TYPESAFE_QSORT(search_res->msgs, + search_res->count, + site_res_cmp_parent_order); + } else { + TYPESAFE_QSORT(search_res->msgs, + search_res->count, + site_res_cmp_usn_order); + } } - DEBUG(2,(__location__ ": getncchanges on %s using filter %s\n", - ldb_dn_get_linearized(getnc_state->ncRoot_dn), search_filter)); - ret = drsuapi_search_with_extended_dn(sam_ctx, getnc_state, &getnc_state->site_res, - search_dn, scope, attrs, - search_filter); - if (ret != LDB_SUCCESS) { - return WERR_DS_DRA_INTERNAL_ERROR; - } + /* extract out the GUIDs list */ + getnc_state->num_records = search_res ? search_res->count : 0; + getnc_state->guids = talloc_array(getnc_state, struct GUID, getnc_state->num_records); + W_ERROR_HAVE_NO_MEMORY(getnc_state->guids); - if (req8->replica_flags & DRSUAPI_DRS_GET_ANC) { - TYPESAFE_QSORT(getnc_state->site_res->msgs, - getnc_state->site_res->count, - site_res_cmp_parent_order); - } else { - TYPESAFE_QSORT(getnc_state->site_res->msgs, - getnc_state->site_res->count, - site_res_cmp_usn_order); + for (i=0; inum_records; i++) { + getnc_state->guids[i] = samdb_result_guid(search_res->msgs[i], "objectGUID"); + if (GUID_all_zero(&getnc_state->guids[i])) { + DEBUG(2,("getncchanges: bad objectGUID from %s\n", ldb_dn_get_linearized(search_res->msgs[i]->dn))); + return WERR_DS_DRA_INTERNAL_ERROR; + } } - getnc_state->uptodateness_vector = talloc_steal(getnc_state, req8->uptodateness_vector); + + talloc_free(search_res); + + getnc_state->uptodateness_vector = talloc_steal(getnc_state, req10->uptodateness_vector); if (getnc_state->uptodateness_vector) { /* make sure its sorted */ TYPESAFE_QSORT(getnc_state->uptodateness_vector->cursors, @@ -1449,8 +1716,8 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ r->out.ctr->ctr6.source_dsa_guid = *(samdb_ntds_objectGUID(sam_ctx)); r->out.ctr->ctr6.source_dsa_invocation_id = *(samdb_ntds_invocation_id(sam_ctx)); - r->out.ctr->ctr6.old_highwatermark = req8->highwatermark; - r->out.ctr->ctr6.new_highwatermark = req8->highwatermark; + r->out.ctr->ctr6.old_highwatermark = req10->highwatermark; + r->out.ctr->ctr6.new_highwatermark = req10->highwatermark; r->out.ctr->ctr6.first_object = NULL; currentObject = &r->out.ctr->ctr6.first_object; @@ -1459,33 +1726,64 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ * for working out what object is giving problems */ max_objects = lpcfg_parm_int(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "max object sync", 1000); - if (req8->max_object_count < max_objects) { - max_objects = req8->max_object_count; + if (req10->max_object_count < max_objects) { + max_objects = req10->max_object_count; } /* * TODO: work out how the maximum should be calculated */ max_links = lpcfg_parm_int(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "max link sync", 1500); - for (i=getnc_state->num_sent; - isite_res->count && + for (i=getnc_state->num_processed; + inum_records && !null_scope && (r->out.ctr->ctr6.object_count < max_objects); i++) { int uSN; struct drsuapi_DsReplicaObjectListItemEx *obj; - struct ldb_message *msg = getnc_state->site_res->msgs[i]; + struct ldb_message *msg; + static const char * const msg_attrs[] = { + "*", + "nTSecurityDescriptor", + "parentGUID", + "replPropertyMetaData", + DSDB_SECRET_ATTRIBUTES, + NULL }; + struct ldb_result *msg_res; + struct ldb_dn *msg_dn; obj = talloc_zero(mem_ctx, struct drsuapi_DsReplicaObjectListItemEx); + W_ERROR_HAVE_NO_MEMORY(obj); + + msg_dn = ldb_dn_new_fmt(obj, sam_ctx, "", GUID_string(obj, &getnc_state->guids[i])); + 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 + */ + ret = drsuapi_search_with_extended_dn(sam_ctx, obj, &msg_res, + msg_dn, + LDB_SCOPE_BASE, msg_attrs, NULL); + if (ret != LDB_SUCCESS) { + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(1,("getncchanges: failed to fetch DN %s - %s\n", + ldb_dn_get_extended_linearized(obj, msg_dn, 1), ldb_errstring(sam_ctx))); + } + talloc_free(obj); + continue; + } + + msg = msg_res->msgs[0]; werr = get_nc_changes_build_object(obj, msg, sam_ctx, getnc_state->ncRoot_dn, getnc_state->is_schema_nc, schema, &session_key, getnc_state->min_usn, - req8->replica_flags, - req8->partial_attribute_set, + req10->replica_flags, + req10->partial_attribute_set, getnc_state->uptodateness_vector, - req8->extended_op); + req10->extended_op); if (!W_ERROR_IS_OK(werr)) { return werr; } @@ -1493,7 +1791,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ werr = get_nc_changes_add_links(sam_ctx, getnc_state, getnc_state->ncRoot_dn, schema, getnc_state->min_usn, - req8->replica_flags, + req10->replica_flags, msg, &getnc_state->la_list, &getnc_state->la_count, @@ -1527,30 +1825,38 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ getnc_state->last_dn = ldb_dn_copy(getnc_state, msg->dn); DEBUG(8,(__location__ ": replicating object %s\n", ldb_dn_get_linearized(msg->dn))); + + talloc_free(msg_res); + talloc_free(msg_dn); } - getnc_state->num_sent += r->out.ctr->ctr6.object_count; + getnc_state->num_processed = i; - r->out.ctr->ctr6.nc_object_count = getnc_state->site_res->count; + r->out.ctr->ctr6.nc_object_count = getnc_state->num_records; /* the client can us to call UpdateRefs on its behalf to re-establish monitoring of the NC */ - if ((req8->replica_flags & (DRSUAPI_DRS_ADD_REF | DRSUAPI_DRS_REF_GCSPN)) && - !GUID_all_zero(&req8->destination_dsa_guid)) { + if ((req10->replica_flags & (DRSUAPI_DRS_ADD_REF | DRSUAPI_DRS_REF_GCSPN)) && + !GUID_all_zero(&req10->destination_dsa_guid)) { struct drsuapi_DsReplicaUpdateRefsRequest1 ureq; DEBUG(3,("UpdateRefs on getncchanges for %s\n", - GUID_string(mem_ctx, &req8->destination_dsa_guid))); + GUID_string(mem_ctx, &req10->destination_dsa_guid))); ureq.naming_context = ncRoot; - ureq.dest_dsa_dns_name = talloc_asprintf(mem_ctx, "%s._msdcs.%s", - GUID_string(mem_ctx, &req8->destination_dsa_guid), - lpcfg_dnsdomain(dce_call->conn->dce_ctx->lp_ctx)); + ureq.dest_dsa_dns_name = samdb_ntds_msdcs_dns_name(b_state->sam_ctx, mem_ctx, + &req10->destination_dsa_guid); if (!ureq.dest_dsa_dns_name) { return WERR_NOMEM; } - ureq.dest_dsa_guid = req8->destination_dsa_guid; + ureq.dest_dsa_guid = req10->destination_dsa_guid; ureq.options = DRSUAPI_DRS_ADD_REF | DRSUAPI_DRS_ASYNC_OP | DRSUAPI_DRS_GETCHG_CHECK; + + /* we also need to pass through the + DRSUAPI_DRS_REF_GCSPN bit so that repsTo gets flagged + to send notifies using the GC SPN */ + ureq.options |= (req10->replica_flags & DRSUAPI_DRS_REF_GCSPN); + werr = drsuapi_UpdateRefs(b_state, mem_ctx, &ureq); if (!W_ERROR_IS_OK(werr)) { DEBUG(0,(__location__ ": Failed UpdateRefs in DsGetNCChanges - %s\n", @@ -1572,7 +1878,7 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ link_total = getnc_state->la_count; - if (i < getnc_state->site_res->count) { + if (i < getnc_state->num_records) { r->out.ctr->ctr6.more_data = true; } else { /* sort the whole array the first time */ @@ -1612,25 +1918,24 @@ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_ b_state->getncchanges_state = NULL; } - if (req8->extended_op != DRSUAPI_EXOP_NONE) { + if (req10->extended_op != DRSUAPI_EXOP_NONE) { r->out.ctr->ctr6.uptodateness_vector = NULL; r->out.ctr->ctr6.nc_object_count = 0; ZERO_STRUCT(r->out.ctr->ctr6.new_highwatermark); - r->out.ctr->ctr6.extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; } DEBUG(r->out.ctr->ctr6.more_data?4:2, ("DsGetNCChanges with uSNChanged >= %llu flags 0x%08x on %s gave %u objects (done %u/%u) %u links (done %u/%u (as %s))\n", - (unsigned long long)(req8->highwatermark.highest_usn+1), - req8->replica_flags, drs_ObjectIdentifier_to_string(mem_ctx, ncRoot), + (unsigned long long)(req10->highwatermark.highest_usn+1), + req10->replica_flags, drs_ObjectIdentifier_to_string(mem_ctx, ncRoot), r->out.ctr->ctr6.object_count, - i, r->out.ctr->ctr6.more_data?getnc_state->site_res->count:i, + i, r->out.ctr->ctr6.more_data?getnc_state->num_records:i, r->out.ctr->ctr6.linked_attributes_count, link_given, link_total, dom_sid_string(mem_ctx, user_sid))); #if 0 - if (!r->out.ctr->ctr6.more_data && req8->extended_op != DRSUAPI_EXOP_NONE) { + if (!r->out.ctr->ctr6.more_data && req10->extended_op != DRSUAPI_EXOP_NONE) { NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsGetNCChanges, NDR_BOTH, r); } #endif