/* Unix SMB/CIFS implementation. implement the DRSUpdateRefs call Copyright (C) Anatoliy Atanasov 2009 Copyright (C) Andrew Tridgell 2009 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "rpc_server/dcerpc_server.h" #include "dsdb/samdb/samdb.h" #include "param/param.h" #include "librpc/gen_ndr/ndr_drsblobs.h" #include "librpc/gen_ndr/ndr_drsuapi.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" /* build a DsReplicaObjectIdentifier from a ldb msg */ static struct drsuapi_DsReplicaObjectIdentifier *get_object_identifier(TALLOC_CTX *mem_ctx, struct ldb_message *msg) { struct drsuapi_DsReplicaObjectIdentifier *identifier; struct dom_sid *sid; identifier = talloc(mem_ctx, struct drsuapi_DsReplicaObjectIdentifier); if (identifier == NULL) { return NULL; } identifier->dn = ldb_dn_alloc_linearized(identifier, msg->dn); identifier->guid = samdb_result_guid(msg, "objectGUID"); sid = samdb_result_dom_sid(identifier, msg, "objectSid"); if (sid) { identifier->sid = *sid; } else { ZERO_STRUCT(identifier->sid); } return identifier; } static int udv_compare(const struct GUID *guid1, struct GUID guid2) { return GUID_compare(guid1, &guid2); } /* see if we can filter an attribute using the uptodateness_vector */ static bool udv_filter(const struct drsuapi_DsReplicaCursorCtrEx *udv, const struct GUID *originating_invocation_id, uint64_t originating_usn) { const struct drsuapi_DsReplicaCursor *c; if (udv == NULL) return false; BINARY_ARRAY_SEARCH(udv->cursors, udv->count, source_dsa_invocation_id, originating_invocation_id, udv_compare, c); if (c && originating_usn <= c->highest_usn) { return true; } return false; } /* drsuapi_DsGetNCChanges for one object */ static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItemEx *obj, struct ldb_message *msg, struct ldb_context *sam_ctx, struct ldb_dn *ncRoot_dn, struct dsdb_schema *schema, DATA_BLOB *session_key, uint64_t highest_usn, uint32_t replica_flags, struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector) { const struct ldb_val *md_value; unsigned int i, n; struct replPropertyMetaDataBlob md; uint32_t rid = 0; enum ndr_err_code ndr_err; uint32_t *attids; const char *rdn; const struct dsdb_attribute *rdn_sa; unsigned int instanceType; int rodc_filtered_flags; instanceType = ldb_msg_find_attr_as_uint(msg, "instanceType", 0); if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) { obj->is_nc_prefix = true; obj->parent_object_guid = NULL; } else { obj->is_nc_prefix = false; obj->parent_object_guid = talloc(obj, struct GUID); if (obj->parent_object_guid == NULL) { return WERR_DS_DRA_INTERNAL_ERROR; } *obj->parent_object_guid = samdb_result_guid(msg, "parentGUID"); if (GUID_all_zero(obj->parent_object_guid)) { DEBUG(0,(__location__ ": missing parentGUID for %s\n", ldb_dn_get_linearized(msg->dn))); return WERR_DS_DRA_INTERNAL_ERROR; } } obj->next_object = NULL; md_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData"); if (!md_value) { /* nothing to send */ return WERR_OK; } if (instanceType & INSTANCE_TYPE_UNINSTANT) { /* don't send uninstantiated objects */ return WERR_OK; } ndr_err = ndr_pull_struct_blob(md_value, obj, lp_iconv_convenience(ldb_get_opaque(sam_ctx, "loadparm")), &md, (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_DS_DRA_INTERNAL_ERROR; } if (md.version != 1) { return WERR_DS_DRA_INTERNAL_ERROR; } rdn = ldb_dn_get_rdn_name(msg->dn); if (rdn == NULL) { DEBUG(0,(__location__ ": No rDN for %s\n", ldb_dn_get_linearized(msg->dn))); return WERR_DS_DRA_INTERNAL_ERROR; } rdn_sa = dsdb_attribute_by_lDAPDisplayName(schema, rdn); if (rdn_sa == NULL) { DEBUG(0,(__location__ ": Can't find dsds_attribute for rDN %s in %s\n", rdn, ldb_dn_get_linearized(msg->dn))); return WERR_DS_DRA_INTERNAL_ERROR; } obj->meta_data_ctr = talloc(obj, struct drsuapi_DsReplicaMetaDataCtr); attids = talloc_array(obj, uint32_t, md.ctr.ctr1.count); obj->object.identifier = get_object_identifier(obj, msg); if (obj->object.identifier == NULL) { return WERR_NOMEM; } dom_sid_split_rid(NULL, &obj->object.identifier->sid, NULL, &rid); obj->meta_data_ctr->meta_data = talloc_array(obj, struct drsuapi_DsReplicaMetaData, md.ctr.ctr1.count); for (n=i=0; iattributeID_id) continue; sa = dsdb_attribute_by_attributeID_id(schema, md.ctr.ctr1.array[i].attid); if (!sa) { DEBUG(0,(__location__ ": Failed to find attribute in schema for attrid %u mentioned in replPropertyMetaData of %s\n", (unsigned int)md.ctr.ctr1.array[i].attid, ldb_dn_get_linearized(msg->dn))); return WERR_DS_DRA_INTERNAL_ERROR; } if (sa->linkID) { struct ldb_message_element *el; el = ldb_msg_find_element(msg, sa->lDAPDisplayName); if (el && el->num_values && dsdb_dn_is_upgraded_link_val(&el->values[0])) { /* don't send upgraded links inline */ continue; } } /* filter by uptodateness_vector */ if (md.ctr.ctr1.array[i].attid != DRSUAPI_ATTRIBUTE_instanceType && udv_filter(uptodateness_vector, &md.ctr.ctr1.array[i].originating_invocation_id, md.ctr.ctr1.array[i].originating_usn)) { continue; } /* if the recipient is a RODC, then we should not add any * RODC filtered attribute */ /* TODO: This is not strictly correct, as it doesn't allow for administrators to setup some users to transfer passwords to specific RODCs. To support that we would instead remove this check and rely on extended ACL checking in the dsdb acl module. */ rodc_filtered_flags = SEARCH_FLAG_RODC_ATTRIBUTE | SEARCH_FLAG_CONFIDENTIAL; if ((replica_flags & DRSUAPI_DRS_WRIT_REP) == 0 && (sa->searchFlags & rodc_filtered_flags)) { continue; } obj->meta_data_ctr->meta_data[n].originating_change_time = md.ctr.ctr1.array[i].originating_change_time; obj->meta_data_ctr->meta_data[n].version = md.ctr.ctr1.array[i].version; obj->meta_data_ctr->meta_data[n].originating_invocation_id = md.ctr.ctr1.array[i].originating_invocation_id; obj->meta_data_ctr->meta_data[n].originating_usn = md.ctr.ctr1.array[i].originating_usn; attids[n] = md.ctr.ctr1.array[i].attid; n++; } /* ignore it if its an empty change. Note that renames always * change the 'name' attribute, so they won't be ignored by * this */ if (n == 0 || (n == 1 && attids[0] == DRSUAPI_ATTRIBUTE_instanceType)) { talloc_free(obj->meta_data_ctr); obj->meta_data_ctr = NULL; return WERR_OK; } obj->meta_data_ctr->count = n; obj->object.flags = DRSUAPI_DS_REPLICA_OBJECT_FROM_MASTER; obj->object.attribute_ctr.num_attributes = obj->meta_data_ctr->count; obj->object.attribute_ctr.attributes = talloc_array(obj, struct drsuapi_DsReplicaAttribute, obj->object.attribute_ctr.num_attributes); /* * Note that the meta_data array and the attributes array must * be the same size and in the same order */ for (i=0; iobject.attribute_ctr.num_attributes; i++) { struct ldb_message_element *el; WERROR werr; const struct dsdb_attribute *sa; sa = dsdb_attribute_by_attributeID_id(schema, attids[i]); if (!sa) { DEBUG(0,("Unable to find attributeID %u in schema\n", attids[i])); return WERR_DS_DRA_INTERNAL_ERROR; } el = ldb_msg_find_element(msg, sa->lDAPDisplayName); if (el == NULL) { /* this happens for attributes that have been removed */ DEBUG(5,("No element '%s' for attributeID %u in message\n", sa->lDAPDisplayName, attids[i])); ZERO_STRUCT(obj->object.attribute_ctr.attributes[i]); obj->object.attribute_ctr.attributes[i].attid = attids[i]; } else { werr = dsdb_attribute_ldb_to_drsuapi(sam_ctx, schema, el, obj, &obj->object.attribute_ctr.attributes[i]); if (!W_ERROR_IS_OK(werr)) { DEBUG(0,("Unable to convert %s to DRS object - %s\n", sa->lDAPDisplayName, win_errstr(werr))); return werr; } /* if DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING is set * check if attribute is secret and send a null value */ if (replica_flags & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { drsuapi_process_secret_attribute(&obj->object.attribute_ctr.attributes[i], &obj->meta_data_ctr->meta_data[i]); } /* some attributes needs to be encrypted before being sent */ werr = drsuapi_encrypt_attribute(obj, session_key, rid, &obj->object.attribute_ctr.attributes[i]); if (!W_ERROR_IS_OK(werr)) { DEBUG(0,("Unable to encrypt %s in DRS object - %s\n", sa->lDAPDisplayName, win_errstr(werr))); return werr; } } } return WERR_OK; } /* add one linked attribute from an object to the list of linked attributes in a getncchanges request */ static WERROR get_nc_changes_add_la(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx, const struct dsdb_schema *schema, const struct dsdb_attribute *sa, struct ldb_message *msg, struct dsdb_dn *dsdb_dn, struct drsuapi_DsReplicaLinkedAttribute **la_list, uint32_t *la_count) { struct drsuapi_DsReplicaLinkedAttribute *la; bool active; NTSTATUS status; WERROR werr; (*la_list) = talloc_realloc(mem_ctx, *la_list, struct drsuapi_DsReplicaLinkedAttribute, (*la_count)+1); W_ERROR_HAVE_NO_MEMORY(*la_list); la = &(*la_list)[*la_count]; la->identifier = get_object_identifier(*la_list, msg); W_ERROR_HAVE_NO_MEMORY(la->identifier); active = (dsdb_dn_rmd_flags(dsdb_dn->dn) & DSDB_RMD_FLAG_DELETED) == 0; 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)) { 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)) { 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)) { 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)) { return ntstatus_to_werror(status); } werr = dsdb_dn_la_to_blob(sam_ctx, sa, schema, *la_list, dsdb_dn, &la->value.blob); W_ERROR_NOT_OK_RETURN(werr); (*la_count)++; return WERR_OK; } /* add linked attributes from an object to the list of linked attributes in a getncchanges request */ static WERROR get_nc_changes_add_links(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, struct ldb_dn *ncRoot_dn, struct dsdb_schema *schema, uint64_t highest_usn, uint32_t replica_flags, struct ldb_message *msg, struct drsuapi_DsReplicaLinkedAttribute **la_list, uint32_t *la_count, struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector) { unsigned int i; TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); uint64_t uSNChanged = ldb_msg_find_attr_as_int(msg, "uSNChanged", -1); for (i=0; inum_elements; i++) { struct ldb_message_element *el = &msg->elements[i]; const struct dsdb_attribute *sa; unsigned int j; sa = dsdb_attribute_by_lDAPDisplayName(schema, el->name); if (!sa || sa->linkID == 0 || (sa->linkID & 1)) { /* we only want forward links */ continue; } if (el->num_values && !dsdb_dn_is_upgraded_link_val(&el->values[0])) { /* its an old style link, it will have been * sent in the main replication data */ continue; } for (j=0; jnum_values; j++) { struct dsdb_dn *dsdb_dn; uint64_t local_usn; NTSTATUS status; WERROR werr; dsdb_dn = dsdb_dn_parse(tmp_ctx, sam_ctx, &el->values[j], sa->syntax->ldap_oid); if (dsdb_dn == NULL) { DEBUG(1,(__location__ ": Failed to parse DN for %s in %s\n", el->name, ldb_dn_get_linearized(msg->dn))); talloc_free(tmp_ctx); return WERR_DS_DRA_INTERNAL_ERROR; } status = dsdb_get_extended_dn_uint64(dsdb_dn->dn, &local_usn, "RMD_LOCAL_USN"); if (!NT_STATUS_IS_OK(status)) { /* this can happen for attributes given to us with old style meta data */ continue; } if (local_usn > uSNChanged) { DEBUG(1,(__location__ ": uSNChanged less than RMD_LOCAL_USN for %s on %s\n", el->name, ldb_dn_get_linearized(msg->dn))); talloc_free(tmp_ctx); return WERR_DS_DRA_INTERNAL_ERROR; } if (local_usn < highest_usn) { continue; } werr = get_nc_changes_add_la(mem_ctx, sam_ctx, schema, sa, msg, dsdb_dn, la_list, la_count); if (!W_ERROR_IS_OK(werr)) { talloc_free(tmp_ctx); return werr; } } } talloc_free(tmp_ctx); return WERR_OK; } /* fill in the cursors return based on the replUpToDateVector for the ncRoot_dn */ static WERROR get_nc_changes_udv(struct ldb_context *sam_ctx, struct ldb_dn *ncRoot_dn, struct drsuapi_DsReplicaCursor2CtrEx *udv) { int ret; udv->version = 2; udv->reserved1 = 0; udv->reserved2 = 0; ret = dsdb_load_udv_v2(sam_ctx, ncRoot_dn, udv, &udv->cursors, &udv->count); if (ret != LDB_SUCCESS) { DEBUG(0,(__location__ ": Failed to load UDV for %s - %s\n", ldb_dn_get_linearized(ncRoot_dn), ldb_errstring(sam_ctx))); return WERR_DS_DRA_INTERNAL_ERROR; } return WERR_OK; } /* comparison function for linked attributes - see CompareLinks() in * MS-DRSR section 4.1.10.5.17 */ static int linked_attribute_compare(const struct drsuapi_DsReplicaLinkedAttribute *la1, const struct drsuapi_DsReplicaLinkedAttribute *la2, struct ldb_context *sam_ctx) { int c; WERROR werr; TALLOC_CTX *tmp_ctx; const struct dsdb_schema *schema; const struct dsdb_attribute *schema_attrib; struct dsdb_dn *dn1, *dn2; struct GUID guid1, guid2; NTSTATUS status; c = GUID_compare(&la1->identifier->guid, &la2->identifier->guid); if (c != 0) return c; if (la1->attid != la2->attid) { return la1->attid < la2->attid? -1:1; } if ((la1->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE) != (la2->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)) { return (la1->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)? 1:-1; } /* we need to get the target GUIDs to compare */ tmp_ctx = talloc_new(sam_ctx); schema = dsdb_get_schema(sam_ctx, tmp_ctx); schema_attrib = dsdb_attribute_by_attributeID_id(schema, la1->attid); werr = dsdb_dn_la_from_blob(sam_ctx, schema_attrib, schema, tmp_ctx, la1->value.blob, &dn1); if (!W_ERROR_IS_OK(werr)) { DEBUG(0,(__location__ ": Bad la1 blob in sort\n")); talloc_free(tmp_ctx); return 0; } werr = dsdb_dn_la_from_blob(sam_ctx, schema_attrib, schema, tmp_ctx, la2->value.blob, &dn2); if (!W_ERROR_IS_OK(werr)) { DEBUG(0,(__location__ ": Bad la2 blob in sort\n")); talloc_free(tmp_ctx); return 0; } status = dsdb_get_extended_dn_guid(dn1->dn, &guid1, "GUID"); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,(__location__ ": Bad la1 guid in sort\n")); talloc_free(tmp_ctx); return 0; } status = dsdb_get_extended_dn_guid(dn2->dn, &guid2, "GUID"); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,(__location__ ": Bad la2 guid in sort\n")); talloc_free(tmp_ctx); return 0; } talloc_free(tmp_ctx); return GUID_compare(&guid1, &guid2); } /* sort the objects we send by tree order */ static int site_res_cmp_parent_order(struct ldb_message **m1, struct ldb_message **m2) { return ldb_dn_compare((*m2)->dn, (*m1)->dn); } /* sort the objects we send first by uSNChanged */ static int site_res_cmp_usn_order(struct ldb_message **m1, struct ldb_message **m2) { unsigned usnchanged1, usnchanged2; unsigned cn1, cn2; cn1 = ldb_dn_get_comp_num((*m1)->dn); cn2 = ldb_dn_get_comp_num((*m2)->dn); if (cn1 != cn2) { return cn1 > cn2 ? 1 : -1; } usnchanged1 = ldb_msg_find_attr_as_uint(*m1, "uSNChanged", 0); usnchanged2 = ldb_msg_find_attr_as_uint(*m2, "uSNChanged", 0); if (usnchanged1 == usnchanged2) { return 0; } return usnchanged1 > usnchanged2 ? 1 : -1; } /* handle a DRSUAPI_EXOP_FSMO_RID_ALLOC call */ static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, struct drsuapi_DsGetNCChangesRequest8 *req8, struct drsuapi_DsGetNCChangesCtr6 *ctr6) { struct ldb_dn *rid_manager_dn, *fsmo_role_dn, *req_dn; int ret; struct ldb_context *ldb = b_state->sam_ctx; struct ldb_result *ext_res; struct ldb_dn *base_dn; struct dsdb_fsmo_extended_op *exop; /* steps: - verify that the DN being asked for is the RID Manager DN - verify that we are the RID Manager */ /* work out who is the RID Manager */ ret = samdb_rid_manager_dn(ldb, mem_ctx, &rid_manager_dn); if (ret != LDB_SUCCESS) { DEBUG(0, (__location__ ": Failed to find RID Manager object - %s\n", ldb_errstring(ldb))); return WERR_DS_DRA_INTERNAL_ERROR; } req_dn = ldb_dn_new(mem_ctx, ldb, req8->naming_context->dn); if (!req_dn || !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", req8->naming_context->dn)); ctr6->extended_ret = DRSUAPI_EXOP_ERR_MISMATCH; return WERR_OK; } /* find the DN of the RID Manager */ ret = samdb_reference_dn(ldb, mem_ctx, rid_manager_dn, "fSMORoleOwner", &fsmo_role_dn); if (ret != LDB_SUCCESS) { DEBUG(0,(__location__ ": Failed to find fSMORoleOwner in RID Manager object - %s\n", ldb_errstring(ldb))); ctr6->extended_ret = DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER; return WERR_DS_DRA_INTERNAL_ERROR; } if (ldb_dn_compare(samdb_ntds_settings_dn(ldb), fsmo_role_dn) != 0) { /* we're not the RID Manager - go away */ DEBUG(0,(__location__ ": RID Alloc request when not RID Manager\n")); ctr6->extended_ret = DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER; return WERR_OK; } 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; ret = ldb_transaction_start(ldb); if (ret != LDB_SUCCESS) { DEBUG(0,(__location__ ": Failed transaction start - %s\n", ldb_errstring(ldb))); return WERR_DS_DRA_INTERNAL_ERROR; } ret = ldb_extended(ldb, DSDB_EXTENDED_ALLOCATE_RID_POOL, exop, &ext_res); if (ret != LDB_SUCCESS) { DEBUG(0,(__location__ ": Failed extended allocation RID pool operation - %s\n", ldb_errstring(ldb))); ldb_transaction_cancel(ldb); return WERR_DS_DRA_INTERNAL_ERROR; } ret = ldb_transaction_commit(ldb); if (ret != LDB_SUCCESS) { DEBUG(0,(__location__ ": Failed transaction commit - %s\n", ldb_errstring(ldb))); return WERR_DS_DRA_INTERNAL_ERROR; } talloc_free(ext_res); base_dn = ldb_get_default_basedn(ldb); DEBUG(2,("Allocated RID pool for server %s\n", GUID_string(mem_ctx, &req8->destination_dsa_guid))); ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; return WERR_OK; } /* state of a partially completed getncchanges call */ struct drsuapi_getncchanges_state { struct ldb_result *site_res; uint32_t num_sent; struct ldb_dn *ncRoot_dn; uint64_t min_usn; uint64_t highest_usn; struct ldb_dn *last_dn; struct drsuapi_DsReplicaLinkedAttribute *la_list; uint32_t la_count; struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector; }; /* drsuapi_DsGetNCChanges see MS-DRSR 4.1.10.5.2 for basic logic of this function */ WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct drsuapi_DsGetNCChanges *r) { struct drsuapi_DsReplicaObjectIdentifier *ncRoot; int ret; unsigned int 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; uint32_t options; uint32_t max_objects; struct ldb_dn *search_dn = NULL; bool am_rodc; enum security_user_level security_level; DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); b_state = h->data; *r->out.level_out = 6; /* TODO: linked attributes*/ r->out.ctr->ctr6.linked_attributes_count = 0; r->out.ctr->ctr6.linked_attributes = NULL; r->out.ctr->ctr6.object_count = 0; r->out.ctr->ctr6.nc_object_count = 0; r->out.ctr->ctr6.more_data = false; r->out.ctr->ctr6.uptodateness_vector = NULL; /* a RODC doesn't allow for any replication */ ret = samdb_rodc(b_state->sam_ctx, &am_rodc); if (ret == LDB_SUCCESS && am_rodc) { DEBUG(0,(__location__ ": DsGetNCChanges attempt on RODC\n")); return WERR_DS_DRA_SOURCE_DISABLED; } /* Check request revision. TODO: Adding mappings to req8 from the other levels */ if (r->in.level != 8) { 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; if (ncRoot == NULL) { DEBUG(0,(__location__ ": Request for DsGetNCChanges with no NC\n")); return WERR_DS_DRA_INVALID_PARAMETER; } if (samdb_ntds_options(b_state->sam_ctx, &options) != LDB_SUCCESS) { return WERR_DS_DRA_INTERNAL_ERROR; } if ((options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) && !(req8->replica_flags & DRSUAPI_DRS_SYNC_FORCED)) { return WERR_DS_DRA_SOURCE_DISABLED; } werr = drs_security_level_check(dce_call, "DsGetNCChanges", SECURITY_RO_DOMAIN_CONTROLLER); if (!W_ERROR_IS_OK(werr)) { return werr; } /* 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(b_state->sam_ctx)); if (security_level == SECURITY_RO_DOMAIN_CONTROLLER && (req8->replica_flags & DRSUAPI_DRS_WRIT_REP)) { DEBUG(0,(__location__ ": Attempt to do writeable replication by RODC %s\n", dom_sid_string(mem_ctx, dce_call->conn->auth_state.session_info->security_token->user_sid))); return WERR_DS_DRA_INVALID_PARAMETER; } if (req8->replica_flags & DRSUAPI_DRS_FULL_SYNC_PACKET) { /* Ignore the _in_ uptpdateness vector*/ req8->uptodateness_vector = NULL; } /* we don't yet support extended operations */ switch (req8->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); W_ERROR_NOT_OK_RETURN(werr); search_dn = ldb_get_default_basedn(b_state->sam_ctx); break; case DRSUAPI_EXOP_FSMO_REQ_ROLE: case DRSUAPI_EXOP_FSMO_RID_REQ_ROLE: case DRSUAPI_EXOP_FSMO_REQ_PDC: case DRSUAPI_EXOP_FSMO_ABANDON_ROLE: case DRSUAPI_EXOP_REPL_OBJ: case DRSUAPI_EXOP_REPL_SECRET: DEBUG(0,(__location__ ": Request for DsGetNCChanges unsupported extended op 0x%x\n", (unsigned)req8->extended_op)); return WERR_DS_DRA_NOT_SUPPORTED; } getnc_state = b_state->getncchanges_state; /* see if a previous replication has been abandoned */ if (getnc_state) { struct ldb_dn *new_dn = ldb_dn_new(getnc_state, b_state->sam_ctx, ncRoot->dn); if (ldb_dn_compare(new_dn, getnc_state->ncRoot_dn) != 0) { DEBUG(0,(__location__ ": DsGetNCChanges 2nd replication on different DN %s %s (last_dn %s)\n", 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; } } if (getnc_state == NULL) { getnc_state = talloc_zero(b_state, struct drsuapi_getncchanges_state); if (getnc_state == NULL) { return WERR_NOMEM; } b_state->getncchanges_state = getnc_state; getnc_state->ncRoot_dn = ldb_dn_new(getnc_state, b_state->sam_ctx, ncRoot->dn); } if (!ldb_dn_validate(getnc_state->ncRoot_dn) || ldb_dn_is_null(getnc_state->ncRoot_dn)) { DEBUG(0,(__location__ ": Bad DN '%s'\n", ncRoot->dn)); return WERR_DS_DRA_INVALID_PARAMETER; } /* we need the session key for encrypting password attributes */ status = dcesrv_inherited_session_key(dce_call->conn, &session_key); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,(__location__ ": Failed to get session key\n")); return WERR_DS_DRA_INTERNAL_ERROR; } /* 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; const char *extra_filter; extra_filter = lp_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "object filter"); getnc_state->min_usn = req8->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 (!search_dn) { search_dn = getnc_state->ncRoot_dn; } DEBUG(1,(__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, &getnc_state->site_res, search_dn, scope, attrs, search_filter); if (ret != LDB_SUCCESS) { return WERR_DS_DRA_INTERNAL_ERROR; } 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); } getnc_state->uptodateness_vector = talloc_steal(getnc_state, req8->uptodateness_vector); if (getnc_state->uptodateness_vector) { /* make sure its sorted */ TYPESAFE_QSORT(getnc_state->uptodateness_vector->cursors, getnc_state->uptodateness_vector->count, drsuapi_DsReplicaCursor_compare); } } /* Prefix mapping */ schema = dsdb_get_schema(b_state->sam_ctx, mem_ctx); if (!schema) { DEBUG(0,("No schema in sam_ctx\n")); return WERR_DS_DRA_INTERNAL_ERROR; } r->out.ctr->ctr6.naming_context = talloc(mem_ctx, struct drsuapi_DsReplicaObjectIdentifier); *r->out.ctr->ctr6.naming_context = *ncRoot; if (dsdb_find_guid_by_dn(b_state->sam_ctx, getnc_state->ncRoot_dn, &r->out.ctr->ctr6.naming_context->guid) != 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; } /* find the SID if there is one */ dsdb_find_sid_by_dn(b_state->sam_ctx, getnc_state->ncRoot_dn, &r->out.ctr->ctr6.naming_context->sid); dsdb_get_oid_mappings_drsuapi(schema, true, mem_ctx, &ctr); r->out.ctr->ctr6.mapping_ctr = *ctr; r->out.ctr->ctr6.source_dsa_guid = *(samdb_ntds_objectGUID(b_state->sam_ctx)); r->out.ctr->ctr6.source_dsa_invocation_id = *(samdb_ntds_invocation_id(b_state->sam_ctx)); r->out.ctr->ctr6.old_highwatermark = req8->highwatermark; r->out.ctr->ctr6.new_highwatermark = req8->highwatermark; r->out.ctr->ctr6.first_object = NULL; currentObject = &r->out.ctr->ctr6.first_object; /* use this to force single objects at a time, which is useful * for working out what object is giving problems */ max_objects = lp_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; } for(i=getnc_state->num_sent; isite_res->count && (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]; obj = talloc_zero(mem_ctx, struct drsuapi_DsReplicaObjectListItemEx); werr = get_nc_changes_build_object(obj, msg, b_state->sam_ctx, getnc_state->ncRoot_dn, schema, &session_key, getnc_state->min_usn, req8->replica_flags, getnc_state->uptodateness_vector); if (!W_ERROR_IS_OK(werr)) { return werr; } werr = get_nc_changes_add_links(b_state->sam_ctx, getnc_state, getnc_state->ncRoot_dn, schema, getnc_state->min_usn, req8->replica_flags, msg, &getnc_state->la_list, &getnc_state->la_count, getnc_state->uptodateness_vector); if (!W_ERROR_IS_OK(werr)) { return werr; } uSN = ldb_msg_find_attr_as_int(msg, "uSNChanged", -1); if (uSN > r->out.ctr->ctr6.new_highwatermark.tmp_highest_usn) { r->out.ctr->ctr6.new_highwatermark.tmp_highest_usn = uSN; } if (uSN > getnc_state->highest_usn) { getnc_state->highest_usn = uSN; } if (obj->meta_data_ctr == NULL) { DEBUG(8,(__location__ ": getncchanges skipping send of object %s\n", ldb_dn_get_linearized(msg->dn))); /* no attributes to send */ talloc_free(obj); continue; } r->out.ctr->ctr6.object_count++; *currentObject = obj; currentObject = &obj->next_object; talloc_free(getnc_state->last_dn); getnc_state->last_dn = ldb_dn_copy(getnc_state, msg->dn); DEBUG(8,(__location__ ": replicating object %s\n", ldb_dn_get_linearized(msg->dn))); } getnc_state->num_sent += r->out.ctr->ctr6.object_count; r->out.ctr->ctr6.nc_object_count = getnc_state->site_res->count; /* 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)) { struct drsuapi_DsReplicaUpdateRefsRequest1 ureq; DEBUG(3,("UpdateRefs on getncchanges for %s\n", GUID_string(mem_ctx, &req8->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), lp_realm(dce_call->conn->dce_ctx->lp_ctx)); if (!ureq.dest_dsa_dns_name) { return WERR_NOMEM; } ureq.dest_dsa_guid = req8->destination_dsa_guid; ureq.options = DRSUAPI_DRS_ADD_REF | DRSUAPI_DRS_ASYNC_OP | DRSUAPI_DRS_GETCHG_CHECK; werr = drsuapi_UpdateRefs(b_state, mem_ctx, &ureq); if (!W_ERROR_IS_OK(werr)) { DEBUG(0,(__location__ ": Failed UpdateRefs in DsGetNCChanges - %s\n", win_errstr(werr))); } } if (i < getnc_state->site_res->count) { r->out.ctr->ctr6.more_data = true; } else { r->out.ctr->ctr6.linked_attributes_count = getnc_state->la_count; r->out.ctr->ctr6.linked_attributes = talloc_steal(mem_ctx, getnc_state->la_list); LDB_TYPESAFE_QSORT(r->out.ctr->ctr6.linked_attributes, r->out.ctr->ctr6.linked_attributes_count, b_state->sam_ctx, linked_attribute_compare); r->out.ctr->ctr6.uptodateness_vector = talloc(mem_ctx, struct drsuapi_DsReplicaCursor2CtrEx); r->out.ctr->ctr6.new_highwatermark.highest_usn = r->out.ctr->ctr6.new_highwatermark.tmp_highest_usn; werr = get_nc_changes_udv(b_state->sam_ctx, getnc_state->ncRoot_dn, r->out.ctr->ctr6.uptodateness_vector); if (!W_ERROR_IS_OK(werr)) { return werr; } talloc_free(getnc_state); b_state->getncchanges_state = NULL; } if (req8->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); } DEBUG(r->out.ctr->ctr6.more_data?2:1, ("DsGetNCChanges with uSNChanged >= %llu flags 0x%08x on %s gave %u objects (done %d/%d la=%d)\n", (unsigned long long)(req8->highwatermark.highest_usn+1), req8->replica_flags, ncRoot->dn, r->out.ctr->ctr6.object_count, i, r->out.ctr->ctr6.more_data?getnc_state->site_res->count:i, r->out.ctr->ctr6.linked_attributes_count)); #if 0 if (!r->out.ctr->ctr6.more_data) { NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsGetNCChanges, NDR_BOTH, r); } #endif return WERR_OK; }