diff options
Diffstat (limited to 'source4/dsdb/repl')
-rw-r--r-- | source4/dsdb/repl/drepl_extended.c | 211 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_fsmo.c | 147 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_notify.c | 485 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_out_helpers.c | 1356 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_out_helpers.h | 26 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_out_pull.c | 260 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_partitions.c | 665 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_periodic.c | 157 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_replica.c | 62 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_ridalloc.c | 265 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_secret.c | 146 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_service.c | 545 | ||||
-rw-r--r-- | source4/dsdb/repl/drepl_service.h | 251 | ||||
-rw-r--r-- | source4/dsdb/repl/replicated_objects.c | 1283 |
14 files changed, 5859 insertions, 0 deletions
diff --git a/source4/dsdb/repl/drepl_extended.c b/source4/dsdb/repl/drepl_extended.c new file mode 100644 index 0000000..8b5bb6f --- /dev/null +++ b/source4/dsdb/repl/drepl_extended.c @@ -0,0 +1,211 @@ +/* + Unix SMB/CIFS Implementation. + + DSDB replication service - extended operation code + + Copyright (C) Andrew Tridgell 2010 + Copyright (C) Andrew Bartlett 2010 + Copyright (C) Nadezhda Ivanova 2010 + + based on drepl_notify.c + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "ldb_module.h" +#include "dsdb/samdb/samdb.h" +#include "samba/service.h" +#include "dsdb/repl/drepl_service.h" +#include "param/param.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + + +/* + create the role owner source dsa structure + + nc_dn: the DN of the subtree being replicated + source_dsa_dn: the DN of the server that we are replicating from + */ +static WERROR drepl_create_extended_source_dsa(struct dreplsrv_service *service, + TALLOC_CTX *mem_ctx, + struct ldb_dn *nc_dn, + struct ldb_dn *source_dsa_dn, + uint64_t min_usn, + struct dreplsrv_partition_source_dsa **_sdsa) +{ + struct dreplsrv_partition_source_dsa *sdsa; + struct ldb_context *ldb = service->samdb; + int ret; + WERROR werr; + struct ldb_dn *nc_root; + struct dreplsrv_partition *p; + + sdsa = talloc_zero(service, struct dreplsrv_partition_source_dsa); + W_ERROR_HAVE_NO_MEMORY(sdsa); + + sdsa->partition = talloc_zero(sdsa, struct dreplsrv_partition); + if (!sdsa->partition) { + talloc_free(sdsa); + return WERR_NOT_ENOUGH_MEMORY; + } + + sdsa->partition->dn = ldb_dn_copy(sdsa->partition, nc_dn); + if (!sdsa->partition->dn) { + talloc_free(sdsa); + return WERR_NOT_ENOUGH_MEMORY; + } + sdsa->partition->nc.dn = ldb_dn_alloc_linearized(sdsa->partition, nc_dn); + if (!sdsa->partition->nc.dn) { + talloc_free(sdsa); + return WERR_NOT_ENOUGH_MEMORY; + } + ret = dsdb_find_guid_by_dn(ldb, nc_dn, &sdsa->partition->nc.guid); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find GUID for %s\n", + ldb_dn_get_linearized(nc_dn))); + talloc_free(sdsa); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + sdsa->repsFrom1 = &sdsa->_repsFromBlob.ctr.ctr1; + ret = dsdb_find_guid_by_dn(ldb, source_dsa_dn, &sdsa->repsFrom1->source_dsa_obj_guid); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find objectGUID for %s\n", + ldb_dn_get_linearized(source_dsa_dn))); + talloc_free(sdsa); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + sdsa->repsFrom1->other_info = talloc_zero(sdsa, struct repsFromTo1OtherInfo); + if (!sdsa->repsFrom1->other_info) { + talloc_free(sdsa); + return WERR_NOT_ENOUGH_MEMORY; + } + + sdsa->repsFrom1->other_info->dns_name = samdb_ntds_msdcs_dns_name(ldb, + sdsa->repsFrom1->other_info, + &sdsa->repsFrom1->source_dsa_obj_guid); + if (!sdsa->repsFrom1->other_info->dns_name) { + talloc_free(sdsa); + return WERR_NOT_ENOUGH_MEMORY; + } + + werr = dreplsrv_out_connection_attach(service, sdsa->repsFrom1, &sdsa->conn); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Failed to attach connection to %s\n", + ldb_dn_get_linearized(nc_dn))); + talloc_free(sdsa); + return werr; + } + + ret = dsdb_find_nc_root(service->samdb, sdsa, nc_dn, &nc_root); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find nc_root for %s\n", + ldb_dn_get_linearized(nc_dn))); + talloc_free(sdsa); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* use the partition uptodateness vector */ + ret = dsdb_load_udv_v2(service->samdb, nc_root, sdsa->partition, + &sdsa->partition->uptodatevector.cursors, + &sdsa->partition->uptodatevector.count); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to load UDV for %s\n", + ldb_dn_get_linearized(nc_root))); + talloc_free(sdsa); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* find the highwatermark from the partitions list */ + for (p=service->partitions; p; p=p->next) { + if (ldb_dn_compare(p->dn, nc_root) == 0) { + struct dreplsrv_partition_source_dsa *s; + werr = dreplsrv_partition_source_dsa_by_guid(p, + &sdsa->repsFrom1->source_dsa_obj_guid, + &s); + if (W_ERROR_IS_OK(werr)) { + sdsa->repsFrom1->highwatermark = s->repsFrom1->highwatermark; + sdsa->repsFrom1->replica_flags = s->repsFrom1->replica_flags; + } + } + } + + if (!service->am_rodc) { + sdsa->repsFrom1->replica_flags |= DRSUAPI_DRS_WRIT_REP; + } + + *_sdsa = sdsa; + return WERR_OK; +} + +struct extended_op_data { + dreplsrv_extended_callback_t callback; + void *callback_data; + struct dreplsrv_partition_source_dsa *sdsa; +}; + +/* + called when an extended op finishes + */ +static void extended_op_callback(struct dreplsrv_service *service, + WERROR err, + enum drsuapi_DsExtendedError exop_error, + void *cb_data) +{ + struct extended_op_data *data = talloc_get_type_abort(cb_data, struct extended_op_data); + talloc_unlink(data, data->sdsa); + data->callback(service, err, exop_error, data->callback_data); + talloc_free(data); +} + +/* + schedule a getncchanges request to the role owner for an extended operation + */ +WERROR drepl_request_extended_op(struct dreplsrv_service *service, + struct ldb_dn *nc_dn, + struct ldb_dn *source_dsa_dn, + enum drsuapi_DsExtendedOperation extended_op, + uint64_t fsmo_info, + uint64_t min_usn, + dreplsrv_extended_callback_t callback, + void *callback_data) +{ + WERROR werr; + struct extended_op_data *data; + + data = talloc(service, struct extended_op_data); + W_ERROR_HAVE_NO_MEMORY(data); + + werr = drepl_create_extended_source_dsa(service, data, nc_dn, source_dsa_dn, min_usn, &data->sdsa); + W_ERROR_NOT_OK_RETURN(werr); + + data->callback = callback; + data->callback_data = callback_data; + + werr = dreplsrv_schedule_partition_pull_source(service, data->sdsa, + 0, extended_op, fsmo_info, + extended_op_callback, data); + if (!W_ERROR_IS_OK(werr)) { + talloc_free(data); + } + + dreplsrv_run_pending_ops(service); + + return werr; +} diff --git a/source4/dsdb/repl/drepl_fsmo.c b/source4/dsdb/repl/drepl_fsmo.c new file mode 100644 index 0000000..3c3cbad --- /dev/null +++ b/source4/dsdb/repl/drepl_fsmo.c @@ -0,0 +1,147 @@ +/* + Unix SMB/CIFS Implementation. + + DSDB replication service - FSMO role change + + Copyright (C) Nadezhda Ivanova 2010 + Copyright (C) Andrew Tridgell 2010 + Copyright (C) Andrew Bartlett 2010 + Copyright (C) Anatoliy Atanasov 2010 + + based on drepl_ridalloc.c + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "dsdb/samdb/samdb.h" +#include "samba/service.h" +#include "dsdb/repl/drepl_service.h" +#include "param/param.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +struct fsmo_role_state { + struct irpc_message *msg; + struct drepl_takeFSMORole *r; +}; + +static void drepl_role_callback(struct dreplsrv_service *service, + WERROR werr, + enum drsuapi_DsExtendedError ext_err, + void *cb_data) +{ + struct fsmo_role_state *fsmo = talloc_get_type_abort(cb_data, struct fsmo_role_state); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(2,(__location__ ": Failed role transfer - %s - extended_ret[0x%X]\n", + win_errstr(werr), ext_err)); + } else { + DEBUG(2,(__location__ ": Successful role transfer\n")); + } + fsmo->r->out.result = werr; + irpc_send_reply(fsmo->msg, NT_STATUS_OK); +} + +/* + see which role is we are asked to assume, initialize data and send request + */ +NTSTATUS drepl_take_FSMO_role(struct irpc_message *msg, + struct drepl_takeFSMORole *r) +{ + struct dreplsrv_service *service = talloc_get_type(msg->private_data, + struct dreplsrv_service); + struct ldb_dn *role_owner_dn, *fsmo_role_dn; + TALLOC_CTX *tmp_ctx = talloc_new(service); + uint64_t fsmo_info = 0; + enum drsuapi_DsExtendedOperation extended_op = DRSUAPI_EXOP_NONE; + WERROR werr; + enum drepl_role_master role = r->in.role; + struct fsmo_role_state *fsmo; + bool is_us; + int ret; + + werr = dsdb_get_fsmo_role_info(tmp_ctx, service->samdb, role, + &fsmo_role_dn, &role_owner_dn); + if (!W_ERROR_IS_OK(werr)) { + talloc_free(tmp_ctx); + r->out.result = werr; + return NT_STATUS_OK; + } + + switch (role) { + case DREPL_NAMING_MASTER: + case DREPL_INFRASTRUCTURE_MASTER: + case DREPL_SCHEMA_MASTER: + extended_op = DRSUAPI_EXOP_FSMO_REQ_ROLE; + break; + case DREPL_RID_MASTER: + extended_op = DRSUAPI_EXOP_FSMO_RID_REQ_ROLE; + break; + case DREPL_PDC_MASTER: + extended_op = DRSUAPI_EXOP_FSMO_REQ_PDC; + break; + default: + DEBUG(0,("Unknown role %u in role transfer\n", + (unsigned)role)); + /* IRPC messages are trusted, so this really should not happen */ + smb_panic("Unknown role despite dsdb_get_fsmo_role_info success"); + } + + ret = samdb_dn_is_our_ntdsa(service->samdb, role_owner_dn, &is_us); + if (ret != LDB_SUCCESS) { + DEBUG(0,("FSMO role check failed (failed to confirm if our ntdsDsa) for DN %s and owner %s \n", + ldb_dn_get_linearized(fsmo_role_dn), + ldb_dn_get_linearized(role_owner_dn))); + talloc_free(tmp_ctx); + r->out.result = WERR_DS_DRA_INTERNAL_ERROR; + return NT_STATUS_OK; + } + + if (is_us) { + DEBUG(5,("FSMO role check failed, we already own DN %s with %s\n", + ldb_dn_get_linearized(fsmo_role_dn), + ldb_dn_get_linearized(role_owner_dn))); + r->out.result = WERR_OK; + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + fsmo = talloc(msg, struct fsmo_role_state); + NT_STATUS_HAVE_NO_MEMORY(fsmo); + + fsmo->msg = msg; + fsmo->r = r; + + werr = drepl_request_extended_op(service, + fsmo_role_dn, + role_owner_dn, + extended_op, + fsmo_info, + 0, + drepl_role_callback, + fsmo); + if (!W_ERROR_IS_OK(werr)) { + r->out.result = werr; + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + /* mark this message to be answered later */ + msg->defer_reply = true; + dreplsrv_run_pending_ops(service); + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} diff --git a/source4/dsdb/repl/drepl_notify.c b/source4/dsdb/repl/drepl_notify.c new file mode 100644 index 0000000..20be3b5 --- /dev/null +++ b/source4/dsdb/repl/drepl_notify.c @@ -0,0 +1,485 @@ +/* + Unix SMB/CIFS Implementation. + + DSDB replication service periodic notification handling + + Copyright (C) Andrew Tridgell 2009 + based on drepl_periodic + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "lib/events/events.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "samba/service.h" +#include "dsdb/repl/drepl_service.h" +#include <ldb_errors.h> +#include "../lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "libcli/composite/composite.h" +#include "../lib/util/tevent_ntstatus.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + + +struct dreplsrv_op_notify_state { + struct tevent_context *ev; + struct dreplsrv_notify_operation *op; + void *ndr_struct_ptr; +}; + +static void dreplsrv_op_notify_connect_done(struct tevent_req *subreq); + +/* + start the ReplicaSync async call + */ +static struct tevent_req *dreplsrv_op_notify_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dreplsrv_notify_operation *op) +{ + struct tevent_req *req; + struct dreplsrv_op_notify_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct dreplsrv_op_notify_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->op = op; + + subreq = dreplsrv_out_drsuapi_send(state, + ev, + op->source_dsa->conn); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, dreplsrv_op_notify_connect_done, req); + + return req; +} + +static void dreplsrv_op_notify_replica_sync_trigger(struct tevent_req *req); + +static void dreplsrv_op_notify_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + NTSTATUS status; + + status = dreplsrv_out_drsuapi_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + dreplsrv_op_notify_replica_sync_trigger(req); +} + +static void dreplsrv_op_notify_replica_sync_done(struct tevent_req *subreq); + +static void dreplsrv_op_notify_replica_sync_trigger(struct tevent_req *req) +{ + struct dreplsrv_op_notify_state *state = + tevent_req_data(req, + struct dreplsrv_op_notify_state); + struct dreplsrv_partition *partition = state->op->source_dsa->partition; + struct dreplsrv_drsuapi_connection *drsuapi = state->op->source_dsa->conn->drsuapi; + struct drsuapi_DsReplicaSync *r; + struct tevent_req *subreq; + + r = talloc_zero(state, struct drsuapi_DsReplicaSync); + if (tevent_req_nomem(r, req)) { + return; + } + r->in.req = talloc_zero(r, union drsuapi_DsReplicaSyncRequest); + if (tevent_req_nomem(r, req)) { + return; + } + r->in.bind_handle = &drsuapi->bind_handle; + r->in.level = 1; + r->in.req->req1.naming_context = &partition->nc; + r->in.req->req1.source_dsa_guid = state->op->service->ntds_guid; + r->in.req->req1.options = + DRSUAPI_DRS_ASYNC_OP | + DRSUAPI_DRS_UPDATE_NOTIFICATION | + DRSUAPI_DRS_WRIT_REP; + + if (state->op->is_urgent) { + r->in.req->req1.options |= DRSUAPI_DRS_SYNC_URGENT; + } + + state->ndr_struct_ptr = r; + + if (DEBUGLVL(10)) { + NDR_PRINT_IN_DEBUG(drsuapi_DsReplicaSync, r); + } + + subreq = dcerpc_drsuapi_DsReplicaSync_r_send(state, + state->ev, + drsuapi->drsuapi_handle, + r); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, dreplsrv_op_notify_replica_sync_done, req); +} + +static void dreplsrv_op_notify_replica_sync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct dreplsrv_op_notify_state *state = + tevent_req_data(req, + struct dreplsrv_op_notify_state); + struct drsuapi_DsReplicaSync *r = talloc_get_type(state->ndr_struct_ptr, + struct drsuapi_DsReplicaSync); + NTSTATUS status; + + state->ndr_struct_ptr = NULL; + + status = dcerpc_drsuapi_DsReplicaSync_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (!W_ERROR_IS_OK(r->out.result)) { + status = werror_to_ntstatus(r->out.result); + tevent_req_nterror(req, status); + return; + } + + tevent_req_done(req); +} + +static NTSTATUS dreplsrv_op_notify_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +/* + called when a notify operation has completed + */ +static void dreplsrv_notify_op_callback(struct tevent_req *subreq) +{ + struct dreplsrv_notify_operation *op = + tevent_req_callback_data(subreq, + struct dreplsrv_notify_operation); + NTSTATUS status; + struct dreplsrv_service *s = op->service; + WERROR werr; + + status = dreplsrv_op_notify_recv(subreq); + werr = ntstatus_to_werror(status); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("dreplsrv_notify: Failed to send DsReplicaSync to %s for %s - %s : %s\n", + op->source_dsa->repsFrom1->other_info->dns_name, + ldb_dn_get_linearized(op->source_dsa->partition->dn), + nt_errstr(status), win_errstr(werr)); + } else { + DBG_INFO("dreplsrv_notify: DsReplicaSync successfully sent to %s\n", + op->source_dsa->repsFrom1->other_info->dns_name); + op->source_dsa->notify_uSN = op->uSN; + } + + drepl_reps_update(s, "repsTo", op->source_dsa->partition->dn, + &op->source_dsa->repsFrom1->source_dsa_obj_guid, + werr); + + talloc_free(op); + s->ops.n_current = NULL; + dreplsrv_run_pending_ops(s); +} + +/* + run any pending replica sync calls + */ +void dreplsrv_notify_run_ops(struct dreplsrv_service *s) +{ + struct dreplsrv_notify_operation *op; + struct tevent_req *subreq; + + if (s->ops.n_current || s->ops.current) { + /* if there's still one running, we're done */ + return; + } + + if (!s->ops.notifies) { + /* if there're no pending operations, we're done */ + return; + } + + op = s->ops.notifies; + s->ops.n_current = op; + DLIST_REMOVE(s->ops.notifies, op); + + subreq = dreplsrv_op_notify_send(op, s->task->event_ctx, op); + if (!subreq) { + DBG_ERR("dreplsrv_notify_run_ops: dreplsrv_op_notify_send[%s][%s] - no memory\n", + op->source_dsa->repsFrom1->other_info->dns_name, + ldb_dn_get_linearized(op->source_dsa->partition->dn)); + return; + } + tevent_req_set_callback(subreq, dreplsrv_notify_op_callback, op); + DBG_INFO("started DsReplicaSync for %s to %s\n", + ldb_dn_get_linearized(op->source_dsa->partition->dn), + op->source_dsa->repsFrom1->other_info->dns_name); +} + + +/* + find a source_dsa for a given guid + */ +static struct dreplsrv_partition_source_dsa *dreplsrv_find_notify_dsa(struct dreplsrv_partition *p, + struct GUID *guid) +{ + struct dreplsrv_partition_source_dsa *s; + + /* first check the sources list */ + for (s=p->sources; s; s=s->next) { + if (GUID_equal(&s->repsFrom1->source_dsa_obj_guid, guid)) { + return s; + } + } + + /* then the notifies list */ + for (s=p->notifies; s; s=s->next) { + if (GUID_equal(&s->repsFrom1->source_dsa_obj_guid, guid)) { + return s; + } + } + return NULL; +} + + +/* + schedule a replicaSync message + */ +static WERROR dreplsrv_schedule_notify_sync(struct dreplsrv_service *service, + struct dreplsrv_partition *p, + struct repsFromToBlob *reps, + TALLOC_CTX *mem_ctx, + uint64_t uSN, + bool is_urgent, + uint32_t replica_flags) +{ + struct dreplsrv_notify_operation *op; + struct dreplsrv_partition_source_dsa *s; + + s = dreplsrv_find_notify_dsa(p, &reps->ctr.ctr1.source_dsa_obj_guid); + if (s == NULL) { + DBG_ERR("Unable to find source_dsa for %s\n", + GUID_string(mem_ctx, &reps->ctr.ctr1.source_dsa_obj_guid)); + return WERR_DS_UNAVAILABLE; + } + + /* first try to find an existing notify operation */ + for (op = service->ops.notifies; op; op = op->next) { + if (op->source_dsa != s) { + continue; + } + + if (op->is_urgent != is_urgent) { + continue; + } + + if (op->replica_flags != replica_flags) { + continue; + } + + if (op->uSN < uSN) { + op->uSN = uSN; + } + + /* reuse the notify operation, as it's not yet started */ + return WERR_OK; + } + + op = talloc_zero(mem_ctx, struct dreplsrv_notify_operation); + W_ERROR_HAVE_NO_MEMORY(op); + + op->service = service; + op->source_dsa = s; + op->uSN = uSN; + op->is_urgent = is_urgent; + op->replica_flags = replica_flags; + op->schedule_time = time(NULL); + + DLIST_ADD_END(service->ops.notifies, op); + talloc_steal(service, op); + return WERR_OK; +} + +/* + see if a partition has a hugher uSN than what is in the repsTo and + if so then send a DsReplicaSync + */ +static WERROR dreplsrv_notify_check(struct dreplsrv_service *s, + struct dreplsrv_partition *p, + TALLOC_CTX *mem_ctx) +{ + uint32_t count=0; + struct repsFromToBlob *reps; + WERROR werr; + uint64_t uSNHighest; + uint64_t uSNUrgent; + uint32_t i; + int ret; + + werr = dsdb_loadreps(s->samdb, mem_ctx, p->dn, "repsTo", &reps, &count); + if (!W_ERROR_IS_OK(werr)) { + DBG_ERR("Failed to load repsTo for %s\n", + ldb_dn_get_linearized(p->dn)); + return werr; + } + + /* loads the partition uSNHighest and uSNUrgent */ + ret = dsdb_load_partition_usn(s->samdb, p->dn, &uSNHighest, &uSNUrgent); + if (ret != LDB_SUCCESS || uSNHighest == 0) { + /* nothing to do */ + return WERR_OK; + } + + /* see if any of our partners need some of our objects */ + for (i=0; i<count; i++) { + struct dreplsrv_partition_source_dsa *sdsa; + uint32_t replica_flags; + sdsa = dreplsrv_find_notify_dsa(p, &reps[i].ctr.ctr1.source_dsa_obj_guid); + replica_flags = reps[i].ctr.ctr1.replica_flags; + if (sdsa == NULL) continue; + if (sdsa->notify_uSN < uSNHighest) { + /* we need to tell this partner to replicate + with us */ + bool is_urgent = sdsa->notify_uSN < uSNUrgent; + + /* check if urgent replication is needed */ + werr = dreplsrv_schedule_notify_sync(s, p, &reps[i], mem_ctx, + uSNHighest, is_urgent, replica_flags); + if (!W_ERROR_IS_OK(werr)) { + DBG_ERR("Failed to setup notify to %s for %s\n", + reps[i].ctr.ctr1.other_info->dns_name, + ldb_dn_get_linearized(p->dn)); + return werr; + } + DBG_DEBUG("queued DsReplicaSync for %s to %s " + "(urgent=%s) uSN=%llu:%llu\n", + ldb_dn_get_linearized(p->dn), + reps[i].ctr.ctr1.other_info->dns_name, + is_urgent?"true":"false", + (unsigned long long)sdsa->notify_uSN, + (unsigned long long)uSNHighest); + } + } + + return WERR_OK; +} + +/* + see if any of the partitions have changed, and if so then send a + DsReplicaSync to all the replica partners in the repsTo object + */ +static WERROR dreplsrv_notify_check_all(struct dreplsrv_service *s, TALLOC_CTX *mem_ctx) +{ + WERROR status; + struct dreplsrv_partition *p; + + for (p = s->partitions; p; p = p->next) { + status = dreplsrv_notify_check(s, p, mem_ctx); + W_ERROR_NOT_OK_RETURN(status); + } + + return WERR_OK; +} + +static void dreplsrv_notify_run(struct dreplsrv_service *service); + +static void dreplsrv_notify_handler_te(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct dreplsrv_service *service = talloc_get_type(ptr, struct dreplsrv_service); + WERROR status; + + service->notify.te = NULL; + + dreplsrv_notify_run(service); + + status = dreplsrv_notify_schedule(service, service->notify.interval); + if (!W_ERROR_IS_OK(status)) { + task_server_terminate(service->task, win_errstr(status), false); + return; + } +} + +WERROR dreplsrv_notify_schedule(struct dreplsrv_service *service, uint32_t next_interval) +{ + TALLOC_CTX *tmp_mem; + struct tevent_timer *new_te; + struct timeval next_time; + + /* prevent looping */ + if (next_interval == 0) next_interval = 1; + + next_time = timeval_current_ofs(next_interval, 50); + + if (service->notify.te) { + /* + * if the timestamp of the new event is higher, + * as current next we don't need to reschedule + */ + if (timeval_compare(&next_time, &service->notify.next_event) > 0) { + return WERR_OK; + } + } + + /* reset the next scheduled timestamp */ + service->notify.next_event = next_time; + + new_te = tevent_add_timer(service->task->event_ctx, service, + service->notify.next_event, + dreplsrv_notify_handler_te, service); + W_ERROR_HAVE_NO_MEMORY(new_te); + + tmp_mem = talloc_new(service); + DBG_DEBUG("dreplsrv_notify_schedule(%u) %sscheduled for: %s\n", + next_interval, + (service->notify.te?"re":""), + nt_time_string(tmp_mem, timeval_to_nttime(&next_time))); + talloc_free(tmp_mem); + + talloc_free(service->notify.te); + service->notify.te = new_te; + + return WERR_OK; +} + +static void dreplsrv_notify_run(struct dreplsrv_service *service) +{ + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_new(service); + dreplsrv_notify_check_all(service, mem_ctx); + talloc_free(mem_ctx); + + dreplsrv_run_pending_ops(service); +} diff --git a/source4/dsdb/repl/drepl_out_helpers.c b/source4/dsdb/repl/drepl_out_helpers.c new file mode 100644 index 0000000..d46b19e --- /dev/null +++ b/source4/dsdb/repl/drepl_out_helpers.c @@ -0,0 +1,1356 @@ +/* + Unix SMB/CIFS Implementation. + DSDB replication service helper function for outgoing traffic + + Copyright (C) Stefan Metzmacher 2007 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "samba/service.h" +#include "lib/events/events.h" +#include "dsdb/repl/drepl_service.h" +#include <ldb_errors.h> +#include "../lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "libcli/composite/composite.h" +#include "auth/gensec/gensec.h" +#include "param/param.h" +#include "../lib/util/tevent_ntstatus.h" +#include "libcli/security/security.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +struct dreplsrv_out_drsuapi_state { + struct tevent_context *ev; + + struct dreplsrv_out_connection *conn; + + struct dreplsrv_drsuapi_connection *drsuapi; + + struct drsuapi_DsBindInfoCtr bind_info_ctr; + struct drsuapi_DsBind bind_r; +}; + +static void dreplsrv_out_drsuapi_connect_done(struct composite_context *creq); + +struct tevent_req *dreplsrv_out_drsuapi_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dreplsrv_out_connection *conn) +{ + struct tevent_req *req; + struct dreplsrv_out_drsuapi_state *state; + struct composite_context *creq; + + req = tevent_req_create(mem_ctx, &state, + struct dreplsrv_out_drsuapi_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->conn = conn; + state->drsuapi = conn->drsuapi; + + if (state->drsuapi != NULL) { + struct dcerpc_binding_handle *b = + state->drsuapi->pipe->binding_handle; + bool is_connected = dcerpc_binding_handle_is_connected(b); + + if (is_connected) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + TALLOC_FREE(conn->drsuapi); + } + + state->drsuapi = talloc_zero(state, struct dreplsrv_drsuapi_connection); + if (tevent_req_nomem(state->drsuapi, req)) { + return tevent_req_post(req, ev); + } + + creq = dcerpc_pipe_connect_b_send(state, conn->binding, &ndr_table_drsuapi, + conn->service->system_session_info->credentials, + ev, conn->service->task->lp_ctx); + if (tevent_req_nomem(creq, req)) { + return tevent_req_post(req, ev); + } + composite_continue(NULL, creq, dreplsrv_out_drsuapi_connect_done, req); + + return req; +} + +static void dreplsrv_out_drsuapi_bind_done(struct tevent_req *subreq); + +static void dreplsrv_out_drsuapi_connect_done(struct composite_context *creq) +{ + struct tevent_req *req = talloc_get_type(creq->async.private_data, + struct tevent_req); + struct dreplsrv_out_drsuapi_state *state = tevent_req_data(req, + struct dreplsrv_out_drsuapi_state); + NTSTATUS status; + struct tevent_req *subreq; + + status = dcerpc_pipe_connect_b_recv(creq, + state->drsuapi, + &state->drsuapi->pipe); + if (tevent_req_nterror(req, status)) { + return; + } + + state->drsuapi->drsuapi_handle = state->drsuapi->pipe->binding_handle; + + status = gensec_session_key(state->drsuapi->pipe->conn->security_state.generic_state, + state->drsuapi, + &state->drsuapi->gensec_skey); + if (tevent_req_nterror(req, status)) { + return; + } + + state->bind_info_ctr.length = 28; + state->bind_info_ctr.info.info28 = state->conn->service->bind_info28; + + state->bind_r.in.bind_guid = &state->conn->service->ntds_guid; + state->bind_r.in.bind_info = &state->bind_info_ctr; + state->bind_r.out.bind_handle = &state->drsuapi->bind_handle; + + subreq = dcerpc_drsuapi_DsBind_r_send(state, + state->ev, + state->drsuapi->drsuapi_handle, + &state->bind_r); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, dreplsrv_out_drsuapi_bind_done, req); +} + +static void dreplsrv_out_drsuapi_bind_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct dreplsrv_out_drsuapi_state *state = tevent_req_data(req, + struct dreplsrv_out_drsuapi_state); + NTSTATUS status; + + status = dcerpc_drsuapi_DsBind_r_recv(subreq, state); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (!W_ERROR_IS_OK(state->bind_r.out.result)) { + status = werror_to_ntstatus(state->bind_r.out.result); + tevent_req_nterror(req, status); + return; + } + + ZERO_STRUCT(state->drsuapi->remote_info28); + if (state->bind_r.out.bind_info) { + struct drsuapi_DsBindInfo28 *info28; + info28 = &state->drsuapi->remote_info28; + + switch (state->bind_r.out.bind_info->length) { + case 24: { + struct drsuapi_DsBindInfo24 *info24; + info24 = &state->bind_r.out.bind_info->info.info24; + + info28->supported_extensions = info24->supported_extensions; + info28->site_guid = info24->site_guid; + info28->pid = info24->pid; + info28->repl_epoch = 0; + break; + } + case 28: { + *info28 = state->bind_r.out.bind_info->info.info28; + break; + } + case 32: { + struct drsuapi_DsBindInfo32 *info32; + info32 = &state->bind_r.out.bind_info->info.info32; + + info28->supported_extensions = info32->supported_extensions; + info28->site_guid = info32->site_guid; + info28->pid = info32->pid; + info28->repl_epoch = info32->repl_epoch; + break; + } + case 48: { + struct drsuapi_DsBindInfo48 *info48; + info48 = &state->bind_r.out.bind_info->info.info48; + + info28->supported_extensions = info48->supported_extensions; + info28->site_guid = info48->site_guid; + info28->pid = info48->pid; + info28->repl_epoch = info48->repl_epoch; + break; + } + case 52: { + struct drsuapi_DsBindInfo52 *info52; + info52 = &state->bind_r.out.bind_info->info.info52; + + info28->supported_extensions = info52->supported_extensions; + info28->site_guid = info52->site_guid; + info28->pid = info52->pid; + info28->repl_epoch = info52->repl_epoch; + break; + } + default: + DEBUG(1, ("Warning: invalid info length in bind info: %d\n", + state->bind_r.out.bind_info->length)); + break; + } + } + + tevent_req_done(req); +} + +NTSTATUS dreplsrv_out_drsuapi_recv(struct tevent_req *req) +{ + struct dreplsrv_out_drsuapi_state *state = tevent_req_data(req, + struct dreplsrv_out_drsuapi_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + state->conn->drsuapi = talloc_move(state->conn, &state->drsuapi); + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct dreplsrv_op_pull_source_schema_cycle { + struct repsFromTo1 repsFrom1; + size_t object_count; + struct drsuapi_DsReplicaObjectListItemEx *first_object; + struct drsuapi_DsReplicaObjectListItemEx *last_object; + uint32_t linked_attributes_count; + struct drsuapi_DsReplicaLinkedAttribute *linked_attributes; +}; + +struct dreplsrv_op_pull_source_state { + struct tevent_context *ev; + struct dreplsrv_out_operation *op; + void *ndr_struct_ptr; + /* + * Used when we have to re-try with a different NC, eg for + * EXOP retry or to get a current schema first + */ + struct dreplsrv_partition_source_dsa *source_dsa_retry; + enum drsuapi_DsExtendedOperation extended_op_retry; + bool retry_started; + struct dreplsrv_op_pull_source_schema_cycle *schema_cycle; +}; + +static void dreplsrv_op_pull_source_connect_done(struct tevent_req *subreq); + +struct tevent_req *dreplsrv_op_pull_source_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dreplsrv_out_operation *op) +{ + struct tevent_req *req; + struct dreplsrv_op_pull_source_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct dreplsrv_op_pull_source_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->op = op; + + subreq = dreplsrv_out_drsuapi_send(state, ev, op->source_dsa->conn); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, dreplsrv_op_pull_source_connect_done, req); + + return req; +} + +static bool dreplsrv_op_pull_source_detect_schema_cycle(struct tevent_req *req) +{ + struct dreplsrv_op_pull_source_state *state = + tevent_req_data(req, + struct dreplsrv_op_pull_source_state); + bool is_schema = false; + + if (state->op->extended_op == DRSUAPI_EXOP_NONE) { + struct dreplsrv_out_operation *op = state->op; + struct dreplsrv_service *service = op->service; + struct ldb_dn *schema_dn = ldb_get_schema_basedn(service->samdb); + struct dreplsrv_partition *partition = op->source_dsa->partition; + + is_schema = ldb_dn_compare(partition->dn, schema_dn) == 0; + } + + if (is_schema) { + struct dreplsrv_op_pull_source_schema_cycle *sc; + + sc = talloc_zero(state, + struct dreplsrv_op_pull_source_schema_cycle); + if (tevent_req_nomem(sc, req)) { + return false; + } + sc->repsFrom1 = *state->op->source_dsa->repsFrom1; + + state->schema_cycle = sc; + } + + return true; +} + +static void dreplsrv_op_pull_source_get_changes_trigger(struct tevent_req *req); + +static void dreplsrv_op_pull_source_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + NTSTATUS status; + bool ok; + + status = dreplsrv_out_drsuapi_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + ok = dreplsrv_op_pull_source_detect_schema_cycle(req); + if (!ok) { + return; + } + + dreplsrv_op_pull_source_get_changes_trigger(req); +} + +static void dreplsrv_op_pull_source_get_changes_done(struct tevent_req *subreq); + +/* + get a RODC partial attribute set for a replication call + */ +static NTSTATUS dreplsrv_get_rodc_partial_attribute_set(struct dreplsrv_service *service, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsPartialAttributeSet **_pas, + struct drsuapi_DsReplicaOIDMapping_Ctr **pfm, + bool for_schema) +{ + struct drsuapi_DsPartialAttributeSet *pas; + struct dsdb_schema *schema; + uint32_t i; + + pas = talloc_zero(mem_ctx, struct drsuapi_DsPartialAttributeSet); + NT_STATUS_HAVE_NO_MEMORY(pas); + + schema = dsdb_get_schema(service->samdb, NULL); + + pas->version = 1; + pas->attids = talloc_array(pas, enum drsuapi_DsAttributeId, schema->num_attributes); + if (pas->attids == NULL) { + TALLOC_FREE(pas); + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<schema->num_attributes; i++) { + struct dsdb_attribute *a; + a = schema->attributes_by_attributeID_id[i]; + if (a->systemFlags & (DS_FLAG_ATTR_NOT_REPLICATED | DS_FLAG_ATTR_IS_CONSTRUCTED)) { + continue; + } + if (a->searchFlags & SEARCH_FLAG_RODC_ATTRIBUTE) { + continue; + } + pas->attids[pas->num_attids] = dsdb_attribute_get_attid(a, for_schema); + pas->num_attids++; + } + + pas->attids = talloc_realloc(pas, pas->attids, enum drsuapi_DsAttributeId, pas->num_attids); + if (pas->attids == NULL) { + TALLOC_FREE(pas); + return NT_STATUS_NO_MEMORY; + } + + *_pas = pas; + + if (pfm != NULL) { + dsdb_get_oid_mappings_drsuapi(schema, true, mem_ctx, pfm); + } + + return NT_STATUS_OK; +} + + +/* + get a GC partial attribute set for a replication call + */ +static NTSTATUS dreplsrv_get_gc_partial_attribute_set(struct dreplsrv_service *service, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsPartialAttributeSet **_pas, + struct drsuapi_DsReplicaOIDMapping_Ctr **pfm) +{ + struct drsuapi_DsPartialAttributeSet *pas; + struct dsdb_schema *schema; + uint32_t i; + + pas = talloc_zero(mem_ctx, struct drsuapi_DsPartialAttributeSet); + NT_STATUS_HAVE_NO_MEMORY(pas); + + schema = dsdb_get_schema(service->samdb, NULL); + + pas->version = 1; + pas->attids = talloc_array(pas, enum drsuapi_DsAttributeId, schema->num_attributes); + if (pas->attids == NULL) { + TALLOC_FREE(pas); + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<schema->num_attributes; i++) { + struct dsdb_attribute *a; + a = schema->attributes_by_attributeID_id[i]; + if (a->isMemberOfPartialAttributeSet) { + pas->attids[pas->num_attids] = dsdb_attribute_get_attid(a, false); + pas->num_attids++; + } + } + + pas->attids = talloc_realloc(pas, pas->attids, enum drsuapi_DsAttributeId, pas->num_attids); + if (pas->attids == NULL) { + TALLOC_FREE(pas); + return NT_STATUS_NO_MEMORY; + } + + *_pas = pas; + + if (pfm != NULL) { + dsdb_get_oid_mappings_drsuapi(schema, true, mem_ctx, pfm); + } + + return NT_STATUS_OK; +} + +/* + convert from one udv format to the other + */ +static WERROR udv_convert(TALLOC_CTX *mem_ctx, + const struct replUpToDateVectorCtr2 *udv, + struct drsuapi_DsReplicaCursorCtrEx *udv_ex) +{ + uint32_t i; + + udv_ex->version = 2; + udv_ex->reserved1 = 0; + udv_ex->reserved2 = 0; + udv_ex->count = udv->count; + udv_ex->cursors = talloc_array(mem_ctx, struct drsuapi_DsReplicaCursor, udv->count); + W_ERROR_HAVE_NO_MEMORY(udv_ex->cursors); + + for (i=0; i<udv->count; i++) { + udv_ex->cursors[i].source_dsa_invocation_id = udv->cursors[i].source_dsa_invocation_id; + udv_ex->cursors[i].highest_usn = udv->cursors[i].highest_usn; + } + + return WERR_OK; +} + + +static void dreplsrv_op_pull_source_get_changes_trigger(struct tevent_req *req) +{ + struct dreplsrv_op_pull_source_state *state = tevent_req_data(req, + struct dreplsrv_op_pull_source_state); + const struct repsFromTo1 *rf1 = state->op->source_dsa->repsFrom1; + struct dreplsrv_service *service = state->op->service; + struct dreplsrv_partition *partition = state->op->source_dsa->partition; + struct dreplsrv_drsuapi_connection *drsuapi = state->op->source_dsa->conn->drsuapi; + struct drsuapi_DsGetNCChanges *r; + struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector; + struct tevent_req *subreq; + struct drsuapi_DsPartialAttributeSet *pas = NULL; + NTSTATUS status; + uint32_t replica_flags; + struct drsuapi_DsReplicaHighWaterMark highwatermark; + struct drsuapi_DsReplicaOIDMapping_Ctr *mappings = NULL; + bool is_schema = false; + + if (state->schema_cycle != NULL) { + is_schema = true; + rf1 = &state->schema_cycle->repsFrom1; + } + + r = talloc(state, struct drsuapi_DsGetNCChanges); + if (tevent_req_nomem(r, req)) { + return; + } + + r->out.level_out = talloc(r, uint32_t); + if (tevent_req_nomem(r->out.level_out, req)) { + return; + } + r->in.req = talloc(r, union drsuapi_DsGetNCChangesRequest); + if (tevent_req_nomem(r->in.req, req)) { + return; + } + r->out.ctr = talloc(r, union drsuapi_DsGetNCChangesCtr); + if (tevent_req_nomem(r->out.ctr, req)) { + return; + } + + if (partition->uptodatevector.count != 0 && + partition->uptodatevector_ex.count == 0) { + WERROR werr; + werr = udv_convert(partition, &partition->uptodatevector, &partition->uptodatevector_ex); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Failed to convert UDV for %s : %s\n", + ldb_dn_get_linearized(partition->dn), win_errstr(werr))); + tevent_req_nterror(req, werror_to_ntstatus(werr)); + return; + } + } + + if (partition->uptodatevector_ex.count == 0) { + uptodateness_vector = NULL; + } else { + uptodateness_vector = &partition->uptodatevector_ex; + } + + replica_flags = rf1->replica_flags; + highwatermark = rf1->highwatermark; + + if (state->op->options & DRSUAPI_DRS_GET_ANC) { + replica_flags |= DRSUAPI_DRS_GET_ANC; + } + + if (state->op->options & DRSUAPI_DRS_SYNC_FORCED) { + replica_flags |= DRSUAPI_DRS_SYNC_FORCED; + } + + if (partition->partial_replica) { + status = dreplsrv_get_gc_partial_attribute_set(service, r, + &pas, + &mappings); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": Failed to construct GC partial attribute set : %s\n", nt_errstr(status))); + tevent_req_nterror(req, status); + return; + } + replica_flags &= ~DRSUAPI_DRS_WRIT_REP; + } else if (partition->rodc_replica || state->op->extended_op == DRSUAPI_EXOP_REPL_SECRET) { + status = dreplsrv_get_rodc_partial_attribute_set(service, r, + &pas, + &mappings, + is_schema); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": Failed to construct RODC partial attribute set : %s\n", nt_errstr(status))); + tevent_req_nterror(req, status); + return; + } + replica_flags &= ~DRSUAPI_DRS_WRIT_REP; + if (state->op->extended_op == DRSUAPI_EXOP_REPL_SECRET) { + replica_flags &= ~DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING; + } else { + replica_flags |= DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING; + } + + /* + * As per MS-DRSR: + * + * 4.1.10.4 + * Client Behavior When Sending the IDL_DRSGetNCChanges Request + * + * 4.1.10.4.1 + * ReplicateNCRequestMsg + */ + replica_flags |= DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP; + } else { + replica_flags |= DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP; + } + + if (state->op->extended_op != DRSUAPI_EXOP_NONE) { + /* + * If it's an exop never set the ADD_REF even if it's in + * repsFrom flags. + */ + replica_flags &= ~DRSUAPI_DRS_ADD_REF; + } + + /* is this a full resync of all objects? */ + if (state->op->options & DRSUAPI_DRS_FULL_SYNC_NOW) { + ZERO_STRUCT(highwatermark); + /* clear the FULL_SYNC_NOW option for subsequent + stages of the replication cycle */ + state->op->options &= ~DRSUAPI_DRS_FULL_SYNC_NOW; + state->op->options |= DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS; + replica_flags |= DRSUAPI_DRS_NEVER_SYNCED; + } + if (state->op->options & DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS) { + uptodateness_vector = NULL; + } + + r->in.bind_handle = &drsuapi->bind_handle; + + if (drsuapi->remote_info28.supported_extensions & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V10) { + r->in.level = 10; + r->in.req->req10.destination_dsa_guid = service->ntds_guid; + r->in.req->req10.source_dsa_invocation_id= rf1->source_dsa_invocation_id; + r->in.req->req10.naming_context = &partition->nc; + r->in.req->req10.highwatermark = highwatermark; + r->in.req->req10.uptodateness_vector = uptodateness_vector; + r->in.req->req10.replica_flags = replica_flags; + r->in.req->req10.max_object_count = 133; + r->in.req->req10.max_ndr_size = 1336811; + r->in.req->req10.extended_op = state->op->extended_op; + r->in.req->req10.fsmo_info = state->op->fsmo_info; + r->in.req->req10.partial_attribute_set = pas; + r->in.req->req10.partial_attribute_set_ex= NULL; + r->in.req->req10.mapping_ctr.num_mappings= mappings == NULL ? 0 : mappings->num_mappings; + r->in.req->req10.mapping_ctr.mappings = mappings == NULL ? NULL : mappings->mappings; + + /* the only difference to v8 is the more_flags */ + r->in.req->req10.more_flags = state->op->more_flags; + + } else if (drsuapi->remote_info28.supported_extensions & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8) { + r->in.level = 8; + r->in.req->req8.destination_dsa_guid = service->ntds_guid; + r->in.req->req8.source_dsa_invocation_id= rf1->source_dsa_invocation_id; + r->in.req->req8.naming_context = &partition->nc; + r->in.req->req8.highwatermark = highwatermark; + r->in.req->req8.uptodateness_vector = uptodateness_vector; + r->in.req->req8.replica_flags = replica_flags; + r->in.req->req8.max_object_count = 133; + r->in.req->req8.max_ndr_size = 1336811; + r->in.req->req8.extended_op = state->op->extended_op; + r->in.req->req8.fsmo_info = state->op->fsmo_info; + r->in.req->req8.partial_attribute_set = pas; + r->in.req->req8.partial_attribute_set_ex= NULL; + r->in.req->req8.mapping_ctr.num_mappings= mappings == NULL ? 0 : mappings->num_mappings; + r->in.req->req8.mapping_ctr.mappings = mappings == NULL ? NULL : mappings->mappings; + } else { + r->in.level = 5; + r->in.req->req5.destination_dsa_guid = service->ntds_guid; + r->in.req->req5.source_dsa_invocation_id= rf1->source_dsa_invocation_id; + r->in.req->req5.naming_context = &partition->nc; + r->in.req->req5.highwatermark = highwatermark; + r->in.req->req5.uptodateness_vector = uptodateness_vector; + r->in.req->req5.replica_flags = replica_flags; + r->in.req->req5.max_object_count = 133; + r->in.req->req5.max_ndr_size = 1336770; + r->in.req->req5.extended_op = state->op->extended_op; + r->in.req->req5.fsmo_info = state->op->fsmo_info; + } + +#if 0 + NDR_PRINT_IN_DEBUG(drsuapi_DsGetNCChanges, r); +#endif + + state->ndr_struct_ptr = r; + subreq = dcerpc_drsuapi_DsGetNCChanges_r_send(state, + state->ev, + drsuapi->drsuapi_handle, + r); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, dreplsrv_op_pull_source_get_changes_done, req); +} + +static void dreplsrv_op_pull_source_apply_changes_trigger(struct tevent_req *req, + struct drsuapi_DsGetNCChanges *r, + uint32_t ctr_level, + struct drsuapi_DsGetNCChangesCtr1 *ctr1, + struct drsuapi_DsGetNCChangesCtr6 *ctr6); + +static void dreplsrv_op_pull_source_get_changes_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct dreplsrv_op_pull_source_state *state = tevent_req_data(req, + struct dreplsrv_op_pull_source_state); + NTSTATUS status; + struct drsuapi_DsGetNCChanges *r = talloc_get_type(state->ndr_struct_ptr, + struct drsuapi_DsGetNCChanges); + uint32_t ctr_level = 0; + struct drsuapi_DsGetNCChangesCtr1 *ctr1 = NULL; + struct drsuapi_DsGetNCChangesCtr6 *ctr6 = NULL; + enum drsuapi_DsExtendedError extended_ret = DRSUAPI_EXOP_ERR_NONE; + state->ndr_struct_ptr = NULL; + + status = dcerpc_drsuapi_DsGetNCChanges_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (!W_ERROR_IS_OK(r->out.result)) { + status = werror_to_ntstatus(r->out.result); + tevent_req_nterror(req, status); + return; + } + + if (*r->out.level_out == 1) { + ctr_level = 1; + ctr1 = &r->out.ctr->ctr1; + } else if (*r->out.level_out == 2 && + r->out.ctr->ctr2.mszip1.ts) { + ctr_level = 1; + ctr1 = &r->out.ctr->ctr2.mszip1.ts->ctr1; + } else if (*r->out.level_out == 6) { + ctr_level = 6; + ctr6 = &r->out.ctr->ctr6; + } else if (*r->out.level_out == 7 && + r->out.ctr->ctr7.level == 6 && + r->out.ctr->ctr7.type == DRSUAPI_COMPRESSION_TYPE_MSZIP && + r->out.ctr->ctr7.ctr.mszip6.ts) { + ctr_level = 6; + ctr6 = &r->out.ctr->ctr7.ctr.mszip6.ts->ctr6; + } else if (*r->out.level_out == 7 && + r->out.ctr->ctr7.level == 6 && + r->out.ctr->ctr7.type == DRSUAPI_COMPRESSION_TYPE_WIN2K3_LZ77_DIRECT2 && + r->out.ctr->ctr7.ctr.xpress6.ts) { + ctr_level = 6; + ctr6 = &r->out.ctr->ctr7.ctr.xpress6.ts->ctr6; + } else { + status = werror_to_ntstatus(WERR_BAD_NET_RESP); + tevent_req_nterror(req, status); + return; + } + + if (!ctr1 && !ctr6) { + status = werror_to_ntstatus(WERR_BAD_NET_RESP); + tevent_req_nterror(req, status); + return; + } + + if (ctr_level == 6) { + if (!W_ERROR_IS_OK(ctr6->drs_error)) { + status = werror_to_ntstatus(ctr6->drs_error); + tevent_req_nterror(req, status); + return; + } + extended_ret = ctr6->extended_ret; + } + + if (ctr_level == 1) { + extended_ret = ctr1->extended_ret; + } + + if (state->op->extended_op != DRSUAPI_EXOP_NONE) { + state->op->extended_ret = extended_ret; + + if (extended_ret != DRSUAPI_EXOP_ERR_SUCCESS) { + status = NT_STATUS_UNSUCCESSFUL; + tevent_req_nterror(req, status); + return; + } + } + + dreplsrv_op_pull_source_apply_changes_trigger(req, r, ctr_level, ctr1, ctr6); +} + +/** + * If processing a chunk of replication data fails, check if it is due to a + * problem that can be fixed by setting extra flags in the GetNCChanges request, + * i.e. GET_ANC or GET_TGT. + * @returns NT_STATUS_OK if the request was retried, and an error code if not + */ +static NTSTATUS dreplsrv_op_pull_retry_with_flags(struct tevent_req *req, + WERROR error_code) +{ + struct dreplsrv_op_pull_source_state *state; + NTSTATUS nt_status = NT_STATUS_OK; + + state = tevent_req_data(req, struct dreplsrv_op_pull_source_state); + + /* + * Check if we failed to apply the records due to a missing parent or + * target object. If so, try again and ask for any missing parent/target + * objects to be included this time. + */ + if (W_ERROR_EQUAL(error_code, WERR_DS_DRA_RECYCLED_TARGET)) { + + if (state->op->more_flags & DRSUAPI_DRS_GET_TGT) { + DEBUG(1,("Missing target object despite setting DRSUAPI_DRS_GET_TGT flag\n")); + nt_status = NT_STATUS_INVALID_NETWORK_RESPONSE; + } else { + state->op->more_flags |= DRSUAPI_DRS_GET_TGT; + DEBUG(1,("Missing target object when we didn't set the DRSUAPI_DRS_GET_TGT flag, retrying\n")); + dreplsrv_op_pull_source_get_changes_trigger(req); + } + } else if (W_ERROR_EQUAL(error_code, WERR_DS_DRA_MISSING_PARENT)) { + + if (state->op->options & DRSUAPI_DRS_GET_ANC) { + DEBUG(1,("Missing parent object despite setting DRSUAPI_DRS_GET_ANC flag\n")); + nt_status = NT_STATUS_INVALID_NETWORK_RESPONSE; + } else { + state->op->options |= DRSUAPI_DRS_GET_ANC; + DEBUG(4,("Missing parent object when we didn't set the DRSUAPI_DRS_GET_ANC flag, retrying\n")); + dreplsrv_op_pull_source_get_changes_trigger(req); + } + } else { + nt_status = werror_to_ntstatus(WERR_BAD_NET_RESP); + } + + return nt_status; +} + + +static void dreplsrv_update_refs_trigger(struct tevent_req *req); + +static void dreplsrv_op_pull_source_apply_changes_trigger(struct tevent_req *req, + struct drsuapi_DsGetNCChanges *r, + uint32_t ctr_level, + struct drsuapi_DsGetNCChangesCtr1 *ctr1, + struct drsuapi_DsGetNCChangesCtr6 *ctr6) +{ + struct dreplsrv_op_pull_source_state *state = tevent_req_data(req, + struct dreplsrv_op_pull_source_state); + struct repsFromTo1 rf1 = *state->op->source_dsa->repsFrom1; + struct dreplsrv_service *service = state->op->service; + struct dreplsrv_partition *partition = state->op->source_dsa->partition; + struct dreplsrv_drsuapi_connection *drsuapi = state->op->source_dsa->conn->drsuapi; + struct ldb_dn *schema_dn = ldb_get_schema_basedn(service->samdb); + struct dreplsrv_op_pull_source_schema_cycle *sc = NULL; + struct dsdb_schema *schema; + struct dsdb_schema *working_schema = NULL; + const struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr; + uint32_t object_count; + struct drsuapi_DsReplicaObjectListItemEx *first_object; + uint32_t linked_attributes_count; + struct drsuapi_DsReplicaLinkedAttribute *linked_attributes; + const struct drsuapi_DsReplicaCursor2CtrEx *uptodateness_vector; + struct dsdb_extended_replicated_objects *objects; + bool more_data = false; + WERROR status; + NTSTATUS nt_status; + uint32_t dsdb_repl_flags = 0; + struct ldb_dn *nc_root = NULL; + bool was_schema = false; + int ret; + + switch (ctr_level) { + case 1: + mapping_ctr = &ctr1->mapping_ctr; + object_count = ctr1->object_count; + first_object = ctr1->first_object; + linked_attributes_count = 0; + linked_attributes = NULL; + rf1.source_dsa_obj_guid = ctr1->source_dsa_guid; + rf1.source_dsa_invocation_id = ctr1->source_dsa_invocation_id; + rf1.highwatermark = ctr1->new_highwatermark; + uptodateness_vector = NULL; /* TODO: map it */ + more_data = ctr1->more_data; + break; + case 6: + mapping_ctr = &ctr6->mapping_ctr; + object_count = ctr6->object_count; + first_object = ctr6->first_object; + linked_attributes_count = ctr6->linked_attributes_count; + linked_attributes = ctr6->linked_attributes; + rf1.source_dsa_obj_guid = ctr6->source_dsa_guid; + rf1.source_dsa_invocation_id = ctr6->source_dsa_invocation_id; + rf1.highwatermark = ctr6->new_highwatermark; + uptodateness_vector = ctr6->uptodateness_vector; + more_data = ctr6->more_data; + break; + default: + nt_status = werror_to_ntstatus(WERR_BAD_NET_RESP); + tevent_req_nterror(req, nt_status); + return; + } + + /* + * We need to cache the schema changes until we replicated + * everything before we can apply the new schema. + */ + if (state->schema_cycle != NULL) { + TALLOC_CTX *mem = NULL; + struct drsuapi_DsReplicaObjectListItemEx **ptr = NULL; + struct drsuapi_DsReplicaObjectListItemEx *l = NULL; + + was_schema = true; + sc = state->schema_cycle; + + sc->repsFrom1 = rf1; + + if (sc->first_object == NULL) { + mem = sc; + ptr = &sc->first_object; + } else { + mem = sc->last_object; + ptr = &sc->last_object->next_object; + } + *ptr = talloc_move(mem, &first_object); + for (l = *ptr; l != NULL; l = l->next_object) { + sc->object_count++; + if (l->next_object == NULL) { + sc->last_object = l; + break; + } + } + + if (sc->linked_attributes_count == 0) { + sc->linked_attributes = talloc_move(sc, &linked_attributes); + sc->linked_attributes_count = linked_attributes_count; + linked_attributes_count = 0; + } else if (linked_attributes_count > 0) { + struct drsuapi_DsReplicaLinkedAttribute *new_las = NULL; + struct drsuapi_DsReplicaLinkedAttribute *tmp_las = NULL; + uint64_t new_count; + uint64_t add_size; + uint32_t add_idx; + + new_count = sc->linked_attributes_count; + new_count += linked_attributes_count; + if (new_count > UINT32_MAX) { + nt_status = werror_to_ntstatus(WERR_BAD_NET_RESP); + tevent_req_nterror(req, nt_status); + return; + } + add_size = linked_attributes_count; + add_size *= sizeof(linked_attributes[0]); + if (add_size > SIZE_MAX) { + nt_status = werror_to_ntstatus(WERR_BAD_NET_RESP); + tevent_req_nterror(req, nt_status); + return; + } + add_idx = sc->linked_attributes_count; + + tmp_las = talloc_realloc(sc, + sc->linked_attributes, + struct drsuapi_DsReplicaLinkedAttribute, + new_count); + if (tevent_req_nomem(tmp_las, req)) { + return; + } + new_las = talloc_move(tmp_las, &linked_attributes); + memcpy(&tmp_las[add_idx], new_las, add_size); + sc->linked_attributes = tmp_las; + sc->linked_attributes_count = new_count; + linked_attributes_count = 0; + } + + if (more_data) { + /* we don't need this structure anymore */ + TALLOC_FREE(r); + + dreplsrv_op_pull_source_get_changes_trigger(req); + return; + } + + /* detach sc from state */ + state->schema_cycle = NULL; + } + + schema = dsdb_get_schema(service->samdb, state); + if (!schema) { + DEBUG(0,(__location__ ": Schema is not loaded yet!\n")); + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + /* + * Decide what working schema to use for object conversion. + * We won't need a working schema for empty replicas sent. + */ + if (sc != NULL) { + first_object = talloc_move(r, &sc->first_object); + object_count = sc->object_count; + linked_attributes = talloc_move(r, &sc->linked_attributes); + linked_attributes_count = sc->linked_attributes_count; + TALLOC_FREE(sc); + + if (first_object != NULL) { + /* create working schema to convert objects with */ + status = dsdb_repl_make_working_schema(service->samdb, + schema, + mapping_ctr, + object_count, + first_object, + &drsuapi->gensec_skey, + state, &working_schema); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0,("Failed to create working schema: %s\n", + win_errstr(status))); + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + } + } + + if (partition->partial_replica || partition->rodc_replica) { + dsdb_repl_flags |= DSDB_REPL_FLAG_PARTIAL_REPLICA; + } + if (state->op->options & DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS) { + dsdb_repl_flags |= DSDB_REPL_FLAG_PRIORITISE_INCOMING; + } + if (state->op->options & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { + dsdb_repl_flags |= DSDB_REPL_FLAG_EXPECT_NO_SECRETS; + } + if (state->op->options & DRSUAPI_DRS_CRITICAL_ONLY || + state->op->extended_op != DRSUAPI_EXOP_NONE) { + dsdb_repl_flags |= DSDB_REPL_FLAG_OBJECT_SUBSET; + } + + if (state->op->more_flags & DRSUAPI_DRS_GET_TGT) { + dsdb_repl_flags |= DSDB_REPL_FLAG_TARGETS_UPTODATE; + } + + if (state->op->extended_op != DRSUAPI_EXOP_NONE) { + ret = dsdb_find_nc_root(service->samdb, partition, + partition->dn, &nc_root); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find nc_root for %s\n", + ldb_dn_get_linearized(partition->dn))); + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + } else { + nc_root = partition->dn; + } + + status = dsdb_replicated_objects_convert(service->samdb, + working_schema ? working_schema : schema, + nc_root, + mapping_ctr, + object_count, + first_object, + linked_attributes_count, + linked_attributes, + &rf1, + uptodateness_vector, + &drsuapi->gensec_skey, + dsdb_repl_flags, + state, &objects); + + if (W_ERROR_EQUAL(status, WERR_DS_DRA_SCHEMA_MISMATCH)) { + struct dreplsrv_partition *p; + bool ok; + + if (was_schema) { + nt_status = werror_to_ntstatus(WERR_BAD_NET_RESP); + DBG_ERR("Got mismatch for schema partition: %s/%s\n", + win_errstr(status), nt_errstr(nt_status)); + tevent_req_nterror(req, nt_status); + return; + } + + if (state->retry_started) { + nt_status = werror_to_ntstatus(WERR_BAD_NET_RESP); + DEBUG(0,("Failed to convert objects after retry: %s/%s\n", + win_errstr(status), nt_errstr(nt_status))); + tevent_req_nterror(req, nt_status); + return; + } + + /* + * Change info sync or extended operation into a fetch + * of the schema partition, so we get all the schema + * objects we need. + * + * We don't want to re-do the remote exop, + * unless it was REPL_SECRET so we set the + * fallback operation to just be a fetch of + * the relevant partition. + */ + + + if (state->op->extended_op == DRSUAPI_EXOP_REPL_SECRET) { + state->extended_op_retry = state->op->extended_op; + } else { + state->extended_op_retry = DRSUAPI_EXOP_NONE; + } + state->op->extended_op = DRSUAPI_EXOP_NONE; + + if (ldb_dn_compare(nc_root, partition->dn) == 0) { + state->source_dsa_retry = state->op->source_dsa; + } else { + status = dreplsrv_partition_find_for_nc(service, + NULL, NULL, + ldb_dn_get_linearized(nc_root), + &p); + if (!W_ERROR_IS_OK(status)) { + DEBUG(2, ("Failed to find requested Naming Context for %s: %s\n", + ldb_dn_get_linearized(nc_root), + win_errstr(status))); + nt_status = werror_to_ntstatus(status); + tevent_req_nterror(req, nt_status); + return; + } + status = dreplsrv_partition_source_dsa_by_guid(p, + &state->op->source_dsa->repsFrom1->source_dsa_obj_guid, + &state->source_dsa_retry); + + if (!W_ERROR_IS_OK(status)) { + struct GUID_txt_buf str; + DEBUG(2, ("Failed to find requested source DSA for %s and %s: %s\n", + ldb_dn_get_linearized(nc_root), + GUID_buf_string(&state->op->source_dsa->repsFrom1->source_dsa_obj_guid, &str), + win_errstr(status))); + nt_status = werror_to_ntstatus(status); + tevent_req_nterror(req, nt_status); + return; + } + } + + /* Find schema naming context to be synchronized first */ + status = dreplsrv_partition_find_for_nc(service, + NULL, NULL, + ldb_dn_get_linearized(schema_dn), + &p); + if (!W_ERROR_IS_OK(status)) { + DEBUG(2, ("Failed to find requested Naming Context for schema: %s\n", + win_errstr(status))); + nt_status = werror_to_ntstatus(status); + tevent_req_nterror(req, nt_status); + return; + } + + status = dreplsrv_partition_source_dsa_by_guid(p, + &state->op->source_dsa->repsFrom1->source_dsa_obj_guid, + &state->op->source_dsa); + if (!W_ERROR_IS_OK(status)) { + struct GUID_txt_buf str; + DEBUG(2, ("Failed to find requested source DSA for %s and %s: %s\n", + ldb_dn_get_linearized(schema_dn), + GUID_buf_string(&state->op->source_dsa->repsFrom1->source_dsa_obj_guid, &str), + win_errstr(status))); + nt_status = werror_to_ntstatus(status); + tevent_req_nterror(req, nt_status); + return; + } + DEBUG(4,("Wrong schema when applying reply GetNCChanges, retrying\n")); + + state->retry_started = true; + + ok = dreplsrv_op_pull_source_detect_schema_cycle(req); + if (!ok) { + return; + } + + dreplsrv_op_pull_source_get_changes_trigger(req); + return; + + } else if (!W_ERROR_IS_OK(status)) { + nt_status = werror_to_ntstatus(WERR_BAD_NET_RESP); + DEBUG(0,("Failed to convert objects: %s/%s\n", + win_errstr(status), nt_errstr(nt_status))); + tevent_req_nterror(req, nt_status); + return; + } + + status = dsdb_replicated_objects_commit(service->samdb, + working_schema, + objects, + &state->op->source_dsa->notify_uSN); + talloc_free(objects); + + if (!W_ERROR_IS_OK(status)) { + + /* + * Check if this error can be fixed by resending the GetNCChanges + * request with extra flags set (i.e. GET_ANC/GET_TGT) + */ + nt_status = dreplsrv_op_pull_retry_with_flags(req, status); + + if (NT_STATUS_IS_OK(nt_status)) { + + /* + * We resent the request. Don't update the highwatermark, + * we'll start this part of the cycle again. + */ + return; + } + + DEBUG(0,("Failed to commit objects: %s/%s\n", + win_errstr(status), nt_errstr(nt_status))); + tevent_req_nterror(req, nt_status); + return; + } + + if (state->op->extended_op == DRSUAPI_EXOP_NONE) { + /* if it applied fine, we need to update the highwatermark */ + *state->op->source_dsa->repsFrom1 = rf1; + } + + /* we don't need this maybe very large structure anymore */ + TALLOC_FREE(r); + + if (more_data) { + dreplsrv_op_pull_source_get_changes_trigger(req); + return; + } + + /* + * If we had to divert via doing some other thing, such as + * pulling the schema, then go back and do the original + * operation once we are done. + */ + if (state->source_dsa_retry != NULL) { + state->op->source_dsa = state->source_dsa_retry; + state->op->extended_op = state->extended_op_retry; + state->source_dsa_retry = NULL; + dreplsrv_op_pull_source_get_changes_trigger(req); + return; + } + + if (state->op->extended_op != DRSUAPI_EXOP_NONE || + state->op->service->am_rodc) { + /* + we don't do the UpdateRefs for extended ops or if we + are a RODC + */ + tevent_req_done(req); + return; + } + + /* now we need to update the repsTo record for this partition + on the server. These records are initially established when + we join the domain, but they quickly expire. We do it here + so we can use the already established DRSUAPI pipe + */ + dreplsrv_update_refs_trigger(req); +} + +static void dreplsrv_update_refs_done(struct tevent_req *subreq); + +/* + send a UpdateRefs request to refresh our repsTo record on the server + */ +static void dreplsrv_update_refs_trigger(struct tevent_req *req) +{ + struct dreplsrv_op_pull_source_state *state = tevent_req_data(req, + struct dreplsrv_op_pull_source_state); + struct dreplsrv_service *service = state->op->service; + struct dreplsrv_partition *partition = state->op->source_dsa->partition; + struct dreplsrv_drsuapi_connection *drsuapi = state->op->source_dsa->conn->drsuapi; + struct drsuapi_DsReplicaUpdateRefs *r; + char *ntds_dns_name; + struct tevent_req *subreq; + + r = talloc(state, struct drsuapi_DsReplicaUpdateRefs); + if (tevent_req_nomem(r, req)) { + return; + } + + ntds_dns_name = samdb_ntds_msdcs_dns_name(service->samdb, r, &service->ntds_guid); + if (tevent_req_nomem(ntds_dns_name, req)) { + talloc_free(r); + return; + } + + r->in.bind_handle = &drsuapi->bind_handle; + r->in.level = 1; + r->in.req.req1.naming_context = &partition->nc; + r->in.req.req1.dest_dsa_dns_name = ntds_dns_name; + r->in.req.req1.dest_dsa_guid = service->ntds_guid; + r->in.req.req1.options = DRSUAPI_DRS_ADD_REF | DRSUAPI_DRS_DEL_REF; + if (!service->am_rodc) { + r->in.req.req1.options |= DRSUAPI_DRS_WRIT_REP; + } + + state->ndr_struct_ptr = r; + subreq = dcerpc_drsuapi_DsReplicaUpdateRefs_r_send(state, + state->ev, + drsuapi->drsuapi_handle, + r); + if (tevent_req_nomem(subreq, req)) { + talloc_free(r); + return; + } + tevent_req_set_callback(subreq, dreplsrv_update_refs_done, req); +} + +/* + receive a UpdateRefs reply + */ +static void dreplsrv_update_refs_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct dreplsrv_op_pull_source_state *state = tevent_req_data(req, + struct dreplsrv_op_pull_source_state); + struct drsuapi_DsReplicaUpdateRefs *r = talloc_get_type(state->ndr_struct_ptr, + struct drsuapi_DsReplicaUpdateRefs); + NTSTATUS status; + + state->ndr_struct_ptr = NULL; + + status = dcerpc_drsuapi_DsReplicaUpdateRefs_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("UpdateRefs failed with %s\n", + nt_errstr(status))); + tevent_req_nterror(req, status); + return; + } + + if (!W_ERROR_IS_OK(r->out.result)) { + status = werror_to_ntstatus(r->out.result); + DEBUG(0,("UpdateRefs failed with %s/%s for %s %s\n", + win_errstr(r->out.result), + nt_errstr(status), + r->in.req.req1.dest_dsa_dns_name, + r->in.req.req1.naming_context->dn)); + /* + * TODO we are currently not sending the + * DsReplicaUpdateRefs at the correct moment, + * we do it just after a GetNcChanges which is + * not always correct. + * Especially when another DC is trying to demote + * it will sends us a DsReplicaSync that will trigger a getNcChanges + * this call will succeed but the DsRecplicaUpdateRefs that we send + * just after will not because the DC is in a demote state and + * will reply us a WERR_DS_DRA_BUSY, this error will cause us to + * answer to the DsReplicaSync with a non OK status, the other DC + * will stop the demote due to this error. + * In order to cope with this we will for the moment consider + * a DS_DRA_BUSY not as an error. + * It's not ideal but it should not have a too huge impact for + * running production as this error otherwise never happen and + * due to the fact the send a DsReplicaUpdateRefs after each getNcChanges + */ + if (!W_ERROR_EQUAL(r->out.result, WERR_DS_DRA_BUSY)) { + tevent_req_nterror(req, status); + return; + } + } + + DEBUG(4,("UpdateRefs OK for %s %s\n", + r->in.req.req1.dest_dsa_dns_name, + r->in.req.req1.naming_context->dn)); + + tevent_req_done(req); +} + +WERROR dreplsrv_op_pull_source_recv(struct tevent_req *req) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return ntstatus_to_werror(status); + } + + tevent_req_received(req); + return WERR_OK; +} + diff --git a/source4/dsdb/repl/drepl_out_helpers.h b/source4/dsdb/repl/drepl_out_helpers.h new file mode 100644 index 0000000..158eef4 --- /dev/null +++ b/source4/dsdb/repl/drepl_out_helpers.h @@ -0,0 +1,26 @@ +/* + Unix SMB/CIFS Implementation. + DSDB replication service helper function for outgoing traffic + + Copyright (C) Stefan Metzmacher 2007 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#ifndef DREPL_OUT_HELPERS_H +#define DREPL_OUT_HELPERS_H + + +#endif /* DREPL_OUT_HELPERS_H */ diff --git a/source4/dsdb/repl/drepl_out_pull.c b/source4/dsdb/repl/drepl_out_pull.c new file mode 100644 index 0000000..fe9bd60 --- /dev/null +++ b/source4/dsdb/repl/drepl_out_pull.c @@ -0,0 +1,260 @@ +/* + Unix SMB/CIFS Implementation. + DSDB replication service outgoing Pull-Replication + + Copyright (C) Stefan Metzmacher 2007 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "samba/service.h" +#include "lib/events/events.h" +#include "dsdb/repl/drepl_service.h" +#include <ldb_errors.h> +#include "../lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "libcli/composite/composite.h" +#include "libcli/security/security.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +/* + update repsFrom/repsTo error information + */ +void drepl_reps_update(struct dreplsrv_service *s, const char *reps_attr, + struct ldb_dn *dn, + struct GUID *source_dsa_obj_guid, WERROR status) +{ + struct repsFromToBlob *reps; + uint32_t count, i; + WERROR werr; + TALLOC_CTX *tmp_ctx = talloc_new(s); + time_t t; + NTTIME now; + + t = time(NULL); + unix_to_nt_time(&now, t); + + werr = dsdb_loadreps(s->samdb, tmp_ctx, dn, reps_attr, &reps, &count); + if (!W_ERROR_IS_OK(werr)) { + talloc_free(tmp_ctx); + return; + } + + for (i=0; i<count; i++) { + if (GUID_equal(source_dsa_obj_guid, + &reps[i].ctr.ctr1.source_dsa_obj_guid)) { + break; + } + } + + if (i == count) { + /* no record to update */ + talloc_free(tmp_ctx); + return; + } + + /* only update the status fields */ + reps[i].ctr.ctr1.last_attempt = now; + reps[i].ctr.ctr1.result_last_attempt = status; + if (W_ERROR_IS_OK(status)) { + reps[i].ctr.ctr1.last_success = now; + reps[i].ctr.ctr1.consecutive_sync_failures = 0; + } else { + reps[i].ctr.ctr1.consecutive_sync_failures++; + } + + werr = dsdb_savereps(s->samdb, tmp_ctx, dn, reps_attr, reps, count); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(2,("drepl_reps_update: Failed to save %s for %s: %s\n", + reps_attr, ldb_dn_get_linearized(dn), win_errstr(werr))); + } + talloc_free(tmp_ctx); +} + +WERROR dreplsrv_schedule_partition_pull_source(struct dreplsrv_service *s, + struct dreplsrv_partition_source_dsa *source, + uint32_t options, + enum drsuapi_DsExtendedOperation extended_op, + uint64_t fsmo_info, + dreplsrv_extended_callback_t callback, + void *cb_data) +{ + struct dreplsrv_out_operation *op; + + op = talloc_zero(s, struct dreplsrv_out_operation); + W_ERROR_HAVE_NO_MEMORY(op); + + op->service = s; + /* + * source may either be the long-term list of partners, or + * from dreplsrv_partition_source_dsa_temporary(). Because it + * can be either, we can't talloc_steal() it here, so we + * instead we reference it. + * + * We never talloc_free() the p->sources pointers - indeed we + * never remove them - and the temp source will otherwise go + * away with the msg it is allocated on. + * + * Finally the pointer created in drepl_request_extended_op() + * is removed with talloc_unlink(). + * + */ + op->source_dsa = talloc_reference(op, source); + if (!op->source_dsa) { + return WERR_NOT_ENOUGH_MEMORY; + } + + op->options = options; + op->extended_op = extended_op; + op->fsmo_info = fsmo_info; + op->callback = callback; + op->cb_data = cb_data; + op->schedule_time = time(NULL); + op->more_flags = 0; + + DLIST_ADD_END(s->ops.pending, op); + + return WERR_OK; +} + +static WERROR dreplsrv_schedule_partition_pull(struct dreplsrv_service *s, + struct dreplsrv_partition *p, + TALLOC_CTX *mem_ctx) +{ + WERROR status; + struct dreplsrv_partition_source_dsa *cur; + + for (cur = p->sources; cur; cur = cur->next) { + status = dreplsrv_schedule_partition_pull_source(s, cur, + 0, DRSUAPI_EXOP_NONE, 0, + NULL, NULL); + W_ERROR_NOT_OK_RETURN(status); + } + + return WERR_OK; +} + +WERROR dreplsrv_schedule_pull_replication(struct dreplsrv_service *s, TALLOC_CTX *mem_ctx) +{ + WERROR status; + struct dreplsrv_partition *p; + + for (p = s->partitions; p; p = p->next) { + status = dreplsrv_schedule_partition_pull(s, p, mem_ctx); + W_ERROR_NOT_OK_RETURN(status); + } + + return WERR_OK; +} + + +static void dreplsrv_pending_op_callback(struct tevent_req *subreq) +{ + struct dreplsrv_out_operation *op = tevent_req_callback_data(subreq, + struct dreplsrv_out_operation); + struct repsFromTo1 *rf = op->source_dsa->repsFrom1; + struct dreplsrv_service *s = op->service; + WERROR werr; + + werr = dreplsrv_op_pull_source_recv(subreq); + TALLOC_FREE(subreq); + + DEBUG(4,("dreplsrv_op_pull_source(%s) for %s\n", win_errstr(werr), + ldb_dn_get_linearized(op->source_dsa->partition->dn))); + + if (op->extended_op == DRSUAPI_EXOP_NONE) { + drepl_reps_update(s, "repsFrom", op->source_dsa->partition->dn, + &rf->source_dsa_obj_guid, werr); + } + + if (op->callback) { + op->callback(s, werr, op->extended_ret, op->cb_data); + } + talloc_free(op); + s->ops.current = NULL; + dreplsrv_run_pending_ops(s); +} + +void dreplsrv_run_pull_ops(struct dreplsrv_service *s) +{ + struct dreplsrv_out_operation *op; + time_t t; + NTTIME now; + struct tevent_req *subreq; + WERROR werr; + + if (s->ops.n_current || s->ops.current) { + /* if there's still one running, we're done */ + return; + } + + if (!s->ops.pending) { + /* if there're no pending operations, we're done */ + return; + } + + t = time(NULL); + unix_to_nt_time(&now, t); + + op = s->ops.pending; + s->ops.current = op; + DLIST_REMOVE(s->ops.pending, op); + + op->source_dsa->repsFrom1->last_attempt = now; + + /* check if inbound replication is enabled */ + if (!(op->options & DRSUAPI_DRS_SYNC_FORCED)) { + uint32_t rep_options; + if (samdb_ntds_options(op->service->samdb, &rep_options) != LDB_SUCCESS) { + werr = WERR_DS_DRA_INTERNAL_ERROR; + goto failed; + } + + if ((rep_options & DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL)) { + werr = WERR_DS_DRA_SINK_DISABLED; + goto failed; + } + } + + subreq = dreplsrv_op_pull_source_send(op, s->task->event_ctx, op); + if (!subreq) { + werr = WERR_NOT_ENOUGH_MEMORY; + goto failed; + } + + tevent_req_set_callback(subreq, dreplsrv_pending_op_callback, op); + return; + +failed: + if (op->extended_op == DRSUAPI_EXOP_NONE) { + drepl_reps_update(s, "repsFrom", op->source_dsa->partition->dn, + &op->source_dsa->repsFrom1->source_dsa_obj_guid, werr); + } + /* unblock queue processing */ + s->ops.current = NULL; + /* + * let the callback do its job just like in any other failure situation + */ + if (op->callback) { + op->callback(s, werr, op->extended_ret, op->cb_data); + } +} diff --git a/source4/dsdb/repl/drepl_partitions.c b/source4/dsdb/repl/drepl_partitions.c new file mode 100644 index 0000000..c525329 --- /dev/null +++ b/source4/dsdb/repl/drepl_partitions.c @@ -0,0 +1,665 @@ +/* + Unix SMB/CIFS Implementation. + DSDB replication service + + Copyright (C) Stefan Metzmacher 2007 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "samba/service.h" +#include "lib/events/events.h" +#include "dsdb/repl/drepl_service.h" +#include <ldb_errors.h> +#include "../lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "libcli/security/security.h" +#include "param/param.h" +#include "dsdb/common/util.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +#undef strcasecmp + +/* + load the partitions list based on replicated NC attributes in our + NTDSDSA object + */ +WERROR dreplsrv_load_partitions(struct dreplsrv_service *s) +{ + WERROR status; + static const char *attrs[] = { "hasMasterNCs", "msDS-hasMasterNCs", "hasPartialReplicaNCs", "msDS-HasFullReplicaNCs", NULL }; + unsigned int a; + int ret; + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + struct ldb_message_element *el; + struct ldb_dn *ntds_dn; + + tmp_ctx = talloc_new(s); + W_ERROR_HAVE_NO_MEMORY(tmp_ctx); + + ntds_dn = samdb_ntds_settings_dn(s->samdb, tmp_ctx); + if (!ntds_dn) { + DEBUG(1,(__location__ ": Unable to find ntds_dn: %s\n", ldb_errstring(s->samdb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Searching for hasMasterNCs in NTDS DN failed: %s\n", ldb_errstring(s->samdb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + for (a=0; attrs[a]; a++) { + int i; + + el = ldb_msg_find_element(res->msgs[0], attrs[a]); + if (el == NULL) { + continue; + } + for (i=0; i<el->num_values; i++) { + struct ldb_dn *pdn; + struct dreplsrv_partition *p, *tp; + bool found; + + pdn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]); + if (pdn == NULL) { + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + if (!ldb_dn_validate(pdn)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + p = talloc_zero(s, struct dreplsrv_partition); + W_ERROR_HAVE_NO_MEMORY(p); + + p->dn = talloc_steal(p, pdn); + p->service = s; + + if (strcasecmp(attrs[a], "hasPartialReplicaNCs") == 0) { + p->partial_replica = true; + } else if (strcasecmp(attrs[a], "msDS-HasFullReplicaNCs") == 0) { + p->rodc_replica = true; + } + + /* Do not add partitions more than once */ + found = false; + for (tp = s->partitions; tp; tp = tp->next) { + if (ldb_dn_compare(tp->dn, p->dn) == 0) { + found = true; + break; + } + } + if (found) { + talloc_free(p); + continue; + } + + DLIST_ADD(s->partitions, p); + DEBUG(2, ("dreplsrv_partition[%s] loaded\n", ldb_dn_get_linearized(p->dn))); + } + } + + talloc_free(tmp_ctx); + + status = dreplsrv_refresh_partitions(s); + W_ERROR_NOT_OK_RETURN(status); + + return WERR_OK; +} + +/* + Check if particular SPN exists for an account + */ +static bool dreplsrv_spn_exists(struct ldb_context *samdb, struct ldb_dn *account_dn, + const char *principal_name) +{ + TALLOC_CTX *tmp_ctx; + const char *attrs_empty[] = { NULL }; + int ret; + struct ldb_result *res; + const char *principal_name_encoded = NULL; + + tmp_ctx = talloc_new(samdb); + if (tmp_ctx == NULL) { + return false; + } + + principal_name_encoded = ldb_binary_encode_string(tmp_ctx, principal_name); + if (principal_name_encoded == NULL) { + talloc_free(tmp_ctx); + return false; + } + + ret = dsdb_search(samdb, tmp_ctx, &res, account_dn, LDB_SCOPE_BASE, attrs_empty, + 0, "servicePrincipalName=%s", + principal_name_encoded); + if (ret != LDB_SUCCESS || res->count != 1) { + talloc_free(tmp_ctx); + return false; + } + + talloc_free(tmp_ctx); + return true; +} + +/* + work out the principal to use for DRS replication connections + */ +static NTSTATUS dreplsrv_get_target_principal(struct dreplsrv_service *s, + TALLOC_CTX *mem_ctx, + const struct repsFromTo1 *rft, + char **target_principal) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + const char *attrs_server[] = { "dNSHostName", "serverReference", NULL }; + const char *attrs_ntds[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL }; + int ret; + const char *hostname, *dnsdomain=NULL; + struct ldb_dn *ntds_dn, *server_dn, *computer_dn; + struct ldb_dn *forest_dn, *nc_dn; + + *target_principal = NULL; + + tmp_ctx = talloc_new(mem_ctx); + + /* we need to find their hostname */ + ret = dsdb_find_dn_by_guid(s->samdb, tmp_ctx, &rft->source_dsa_obj_guid, 0, &ntds_dn); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + /* its OK for their NTDSDSA DN not to be in our database */ + return NT_STATUS_OK; + } + + server_dn = ldb_dn_copy(tmp_ctx, ntds_dn); + if (server_dn == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + /* strip off the NTDS Settings */ + if (!ldb_dn_remove_child_components(server_dn, 1)) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, server_dn, attrs_server, 0); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + /* its OK for their server DN not to be in our database */ + return NT_STATUS_OK; + } + + forest_dn = ldb_get_root_basedn(s->samdb); + if (forest_dn == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL); + computer_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "serverReference"); + if (hostname != NULL && computer_dn != NULL) { + char *local_principal; + + /* + if we have the dNSHostName attribute then we can use + the GC/hostname/realm SPN. All DCs should have this SPN + + Windows DC may set up it's dNSHostName before setting up + GC/xx/xx SPN. So make sure it exists, before using it. + */ + local_principal = talloc_asprintf(mem_ctx, "GC/%s/%s", + hostname, + samdb_dn_to_dns_domain(tmp_ctx, forest_dn)); + if (local_principal == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + if (dreplsrv_spn_exists(s->samdb, computer_dn, local_principal)) { + *target_principal = local_principal; + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + talloc_free(local_principal); + } + + /* + if we can't find the dNSHostName then we will try for the + E3514235-4B06-11D1-AB04-00C04FC2DCD2/${NTDSGUID}/${DNSDOMAIN} + SPN. To use that we need the DNS domain name of the target + DC. We find that by first looking for the msDS-HasDomainNCs + in the NTDSDSA object of the DC, and if we don't find that, + then we look for the hasMasterNCs attribute, and eliminate + the known schema and configuruation DNs. Despite how + bizarre this seems, Hongwei tells us that this is in fact + what windows does to find the SPN!! + */ + ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs_ntds, 0); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + nc_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "msDS-HasDomainNCs"); + if (nc_dn != NULL) { + dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn); + } + + if (dnsdomain == NULL) { + struct ldb_message_element *el; + int i; + el = ldb_msg_find_element(res->msgs[0], "hasMasterNCs"); + for (i=0; el && i<el->num_values; i++) { + nc_dn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]); + if (nc_dn == NULL || + ldb_dn_compare(ldb_get_config_basedn(s->samdb), nc_dn) == 0 || + ldb_dn_compare(ldb_get_schema_basedn(s->samdb), nc_dn) == 0) { + continue; + } + /* it must be a domain DN, get the equivalent + DNS domain name */ + dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn); + break; + } + } + + if (dnsdomain != NULL) { + *target_principal = talloc_asprintf(mem_ctx, + "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s@%s", + GUID_string(tmp_ctx, &rft->source_dsa_obj_guid), + dnsdomain, dnsdomain); + } + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + + +WERROR dreplsrv_out_connection_attach(struct dreplsrv_service *s, + const struct repsFromTo1 *rft, + struct dreplsrv_out_connection **_conn) +{ + struct dreplsrv_out_connection *cur, *conn = NULL; + const char *hostname; + + if (!rft->other_info) { + return WERR_FOOBAR; + } + + if (!rft->other_info->dns_name) { + return WERR_FOOBAR; + } + + hostname = rft->other_info->dns_name; + + for (cur = s->connections; cur; cur = cur->next) { + const char *host; + + host = dcerpc_binding_get_string_option(cur->binding, "host"); + if (host == NULL) { + continue; + } + + if (strcmp(host, hostname) == 0) { + conn = cur; + break; + } + } + + if (!conn) { + NTSTATUS nt_status; + char *binding_str; + char *target_principal = NULL; + + conn = talloc_zero(s, struct dreplsrv_out_connection); + W_ERROR_HAVE_NO_MEMORY(conn); + + conn->service = s; + + binding_str = talloc_asprintf(conn, "ncacn_ip_tcp:%s[krb5,seal]", + hostname); + W_ERROR_HAVE_NO_MEMORY(binding_str); + nt_status = dcerpc_parse_binding(conn, binding_str, &conn->binding); + talloc_free(binding_str); + if (!NT_STATUS_IS_OK(nt_status)) { + return ntstatus_to_werror(nt_status); + } + + /* use the GC principal for DRS replication */ + nt_status = dreplsrv_get_target_principal(s, conn->binding, + rft, &target_principal); + if (!NT_STATUS_IS_OK(nt_status)) { + return ntstatus_to_werror(nt_status); + } + + nt_status = dcerpc_binding_set_string_option(conn->binding, + "target_principal", + target_principal); + TALLOC_FREE(target_principal); + if (!NT_STATUS_IS_OK(nt_status)) { + return ntstatus_to_werror(nt_status); + } + + DLIST_ADD_END(s->connections, conn); + + DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", hostname)); + } else { + DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", hostname)); + } + + *_conn = conn; + return WERR_OK; +} + +/* + find an existing source dsa in a list + */ +static struct dreplsrv_partition_source_dsa *dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa *list, + struct GUID *guid) +{ + struct dreplsrv_partition_source_dsa *s; + for (s=list; s; s=s->next) { + if (GUID_equal(&s->repsFrom1->source_dsa_obj_guid, guid)) { + return s; + } + } + return NULL; +} + + + +static WERROR dreplsrv_partition_add_source_dsa(struct dreplsrv_service *s, + struct dreplsrv_partition *p, + struct dreplsrv_partition_source_dsa **listp, + struct dreplsrv_partition_source_dsa *check_list, + const struct ldb_val *val) +{ + WERROR status; + enum ndr_err_code ndr_err; + struct dreplsrv_partition_source_dsa *source, *s2; + + source = talloc_zero(p, struct dreplsrv_partition_source_dsa); + W_ERROR_HAVE_NO_MEMORY(source); + + ndr_err = ndr_pull_struct_blob(val, source, + &source->_repsFromBlob, + (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err); + talloc_free(source); + return ntstatus_to_werror(nt_status); + } + /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */ + if (source->_repsFromBlob.version != 1) { + talloc_free(source); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + source->partition = p; + source->repsFrom1 = &source->_repsFromBlob.ctr.ctr1; + + status = dreplsrv_out_connection_attach(s, source->repsFrom1, &source->conn); + W_ERROR_NOT_OK_RETURN(status); + + if (check_list && + dreplsrv_find_source_dsa(check_list, &source->repsFrom1->source_dsa_obj_guid)) { + /* its in the check list, don't add it again */ + talloc_free(source); + return WERR_OK; + } + + /* re-use an existing source if found */ + for (s2=*listp; s2; s2=s2->next) { + if (GUID_equal(&s2->repsFrom1->source_dsa_obj_guid, + &source->repsFrom1->source_dsa_obj_guid)) { + talloc_free(s2->repsFrom1->other_info); + *s2->repsFrom1 = *source->repsFrom1; + talloc_steal(s2, s2->repsFrom1->other_info); + talloc_free(source); + return WERR_OK; + } + } + + DLIST_ADD_END(*listp, source); + return WERR_OK; +} + +/** + * Find a partition when given a NC + * If the NC can't be found it will return BAD_NC + * Initial checks for invalid parameters have to be done beforehand + */ +WERROR dreplsrv_partition_find_for_nc(struct dreplsrv_service *s, + struct GUID *nc_guid, + struct dom_sid *nc_sid, + const char *nc_dn_str, + struct dreplsrv_partition **_p) +{ + struct dreplsrv_partition *p; + bool valid_sid, valid_guid; + + SMB_ASSERT(_p); + + valid_sid = nc_sid && !is_null_sid(nc_sid); + valid_guid = nc_guid && !GUID_all_zero(nc_guid); + + if (!valid_sid && !valid_guid && (!nc_dn_str)) { + return WERR_DS_DRA_BAD_NC; + } + + for (p = s->partitions; p; p = p->next) { + if ((valid_guid && GUID_equal(&p->nc.guid, nc_guid)) + || strequal(p->nc.dn, nc_dn_str) + || (valid_sid && dom_sid_equal(&p->nc.sid, nc_sid))) + { + /* fill in the right guid and sid if possible */ + if (nc_guid && !valid_guid) { + dsdb_get_extended_dn_guid(p->dn, nc_guid, "GUID"); + } + if (nc_sid && !valid_sid) { + dsdb_get_extended_dn_sid(p->dn, nc_sid, "SID"); + } + *_p = p; + return WERR_OK; + } + } + + return WERR_DS_DRA_BAD_NC; +} + +WERROR dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition *p, + const struct GUID *dsa_guid, + struct dreplsrv_partition_source_dsa **_dsa) +{ + struct dreplsrv_partition_source_dsa *dsa; + + SMB_ASSERT(dsa_guid != NULL); + SMB_ASSERT(!GUID_all_zero(dsa_guid)); + SMB_ASSERT(_dsa); + + for (dsa = p->sources; dsa; dsa = dsa->next) { + if (GUID_equal(dsa_guid, &dsa->repsFrom1->source_dsa_obj_guid)) { + *_dsa = dsa; + return WERR_OK; + } + } + + return WERR_DS_DRA_NO_REPLICA; +} + +WERROR dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition *p, + const char *dsa_dns, + struct dreplsrv_partition_source_dsa **_dsa) +{ + struct dreplsrv_partition_source_dsa *dsa; + + SMB_ASSERT(dsa_dns != NULL); + SMB_ASSERT(_dsa); + + for (dsa = p->sources; dsa; dsa = dsa->next) { + if (strequal(dsa_dns, dsa->repsFrom1->other_info->dns_name)) { + *_dsa = dsa; + return WERR_OK; + } + } + + return WERR_DS_DRA_NO_REPLICA; +} + + +/* + create a temporary dsa structure for a replication. This is needed + for the initial replication of a new partition, such as when a new + domain NC is created and we are a global catalog server + */ +WERROR dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition *p, + TALLOC_CTX *mem_ctx, + const struct GUID *dsa_guid, + struct dreplsrv_partition_source_dsa **_dsa) +{ + struct dreplsrv_partition_source_dsa *dsa; + WERROR werr; + + dsa = talloc_zero(mem_ctx, struct dreplsrv_partition_source_dsa); + W_ERROR_HAVE_NO_MEMORY(dsa); + + dsa->partition = p; + dsa->repsFrom1 = &dsa->_repsFromBlob.ctr.ctr1; + dsa->repsFrom1->replica_flags = 0; + dsa->repsFrom1->source_dsa_obj_guid = *dsa_guid; + + dsa->repsFrom1->other_info = talloc_zero(dsa, struct repsFromTo1OtherInfo); + W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info); + + dsa->repsFrom1->other_info->dns_name = samdb_ntds_msdcs_dns_name(p->service->samdb, + dsa->repsFrom1->other_info, dsa_guid); + W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info->dns_name); + + werr = dreplsrv_out_connection_attach(p->service, dsa->repsFrom1, &dsa->conn); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Failed to attach connection to %s\n", + ldb_dn_get_linearized(p->dn))); + talloc_free(dsa); + return werr; + } + + *_dsa = dsa; + + return WERR_OK; +} + + +static WERROR dreplsrv_refresh_partition(struct dreplsrv_service *s, + struct dreplsrv_partition *p) +{ + WERROR status; + NTSTATUS ntstatus; + struct ldb_message_element *orf_el = NULL; + struct ldb_result *r = NULL; + unsigned int i; + int ret; + TALLOC_CTX *mem_ctx = talloc_new(p); + static const char *attrs[] = { + "repsFrom", + "repsTo", + NULL + }; + struct ldb_dn *dn; + + DEBUG(4, ("dreplsrv_refresh_partition(%s)\n", + ldb_dn_get_linearized(p->dn))); + + ret = dsdb_search_dn(s->samdb, mem_ctx, &r, p->dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* we haven't replicated the partition yet, but we + * can fill in the guid, sid etc from the partition DN */ + dn = p->dn; + } else if (ret != LDB_SUCCESS) { + talloc_free(mem_ctx); + return WERR_FOOBAR; + } else { + dn = r->msgs[0]->dn; + } + + talloc_free(discard_const(p->nc.dn)); + ZERO_STRUCT(p->nc); + p->nc.dn = ldb_dn_alloc_linearized(p, dn); + W_ERROR_HAVE_NO_MEMORY(p->nc.dn); + ntstatus = dsdb_get_extended_dn_guid(dn, &p->nc.guid, "GUID"); + if (!NT_STATUS_IS_OK(ntstatus)) { + DEBUG(0,(__location__ ": unable to get GUID for %s: %s\n", + p->nc.dn, nt_errstr(ntstatus))); + talloc_free(mem_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + dsdb_get_extended_dn_sid(dn, &p->nc.sid, "SID"); + + talloc_free(p->uptodatevector.cursors); + talloc_free(p->uptodatevector_ex.cursors); + ZERO_STRUCT(p->uptodatevector); + ZERO_STRUCT(p->uptodatevector_ex); + + ret = dsdb_load_udv_v2(s->samdb, p->dn, p, &p->uptodatevector.cursors, &p->uptodatevector.count); + if (ret != LDB_SUCCESS) { + DEBUG(4,(__location__ ": no UDV available for %s\n", ldb_dn_get_linearized(p->dn))); + } + + status = WERR_OK; + + if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsFrom"))) { + for (i=0; i < orf_el->num_values; i++) { + status = dreplsrv_partition_add_source_dsa(s, p, &p->sources, + NULL, &orf_el->values[i]); + W_ERROR_NOT_OK_GOTO_DONE(status); + } + } + + if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsTo"))) { + for (i=0; i < orf_el->num_values; i++) { + status = dreplsrv_partition_add_source_dsa(s, p, &p->notifies, + p->sources, &orf_el->values[i]); + W_ERROR_NOT_OK_GOTO_DONE(status); + } + } + +done: + talloc_free(mem_ctx); + return status; +} + +WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s) +{ + WERROR status; + struct dreplsrv_partition *p; + + for (p = s->partitions; p; p = p->next) { + status = dreplsrv_refresh_partition(s, p); + W_ERROR_NOT_OK_RETURN(status); + } + + return WERR_OK; +} diff --git a/source4/dsdb/repl/drepl_periodic.c b/source4/dsdb/repl/drepl_periodic.c new file mode 100644 index 0000000..4cdc8cb --- /dev/null +++ b/source4/dsdb/repl/drepl_periodic.c @@ -0,0 +1,157 @@ +/* + Unix SMB/CIFS Implementation. + DSDB replication service periodic handling + + Copyright (C) Stefan Metzmacher 2007 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "lib/events/events.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "samba/service.h" +#include "dsdb/repl/drepl_service.h" +#include <ldb_errors.h> +#include "../lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +static void dreplsrv_periodic_run(struct dreplsrv_service *service); + +static void dreplsrv_periodic_handler_te(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct dreplsrv_service *service = talloc_get_type(ptr, struct dreplsrv_service); + WERROR status; + + service->periodic.te = NULL; + + dreplsrv_periodic_run(service); + + status = dreplsrv_periodic_schedule(service, service->periodic.interval); + if (!W_ERROR_IS_OK(status)) { + task_server_terminate(service->task, win_errstr(status), false); + return; + } +} + +WERROR dreplsrv_periodic_schedule(struct dreplsrv_service *service, uint32_t next_interval) +{ + TALLOC_CTX *tmp_mem; + struct tevent_timer *new_te; + struct timeval next_time; + + /* prevent looping */ + if (next_interval == 0) next_interval = 1; + + next_time = timeval_current_ofs(next_interval, 50); + + if (service->periodic.te) { + /* + * if the timestamp of the new event is higher, + * as current next we don't need to reschedule + */ + if (timeval_compare(&next_time, &service->periodic.next_event) > 0) { + return WERR_OK; + } + } + + /* reset the next scheduled timestamp */ + service->periodic.next_event = next_time; + + new_te = tevent_add_timer(service->task->event_ctx, service, + service->periodic.next_event, + dreplsrv_periodic_handler_te, service); + W_ERROR_HAVE_NO_MEMORY(new_te); + + tmp_mem = talloc_new(service); + DEBUG(4,("dreplsrv_periodic_schedule(%u) %sscheduled for: %s\n", + next_interval, + (service->periodic.te?"re":""), + nt_time_string(tmp_mem, timeval_to_nttime(&next_time)))); + talloc_free(tmp_mem); + + talloc_free(service->periodic.te); + service->periodic.te = new_te; + + return WERR_OK; +} + +static void dreplsrv_periodic_run(struct dreplsrv_service *service) +{ + TALLOC_CTX *mem_ctx; + + DEBUG(4,("dreplsrv_periodic_run(): schedule pull replication\n")); + + /* + * KCC or some administrative tool + * might have changed Topology graph + * i.e. repsFrom/repsTo + */ + dreplsrv_refresh_partitions(service); + + mem_ctx = talloc_new(service); + dreplsrv_schedule_pull_replication(service, mem_ctx); + talloc_free(mem_ctx); + + DEBUG(4,("dreplsrv_periodic_run(): run pending_ops memory=%u\n", + (unsigned)talloc_total_blocks(service))); + + dreplsrv_ridalloc_check_rid_pool(service); + + dreplsrv_run_pending_ops(service); +} + +/* + run the next pending op, either a notify or a pull + */ +void dreplsrv_run_pending_ops(struct dreplsrv_service *s) +{ + if (!s->ops.notifies && !s->ops.pending) { + return; + } + if (!s->ops.notifies || + (s->ops.pending && + s->ops.notifies->schedule_time > s->ops.pending->schedule_time)) { + dreplsrv_run_pull_ops(s); + } else { + dreplsrv_notify_run_ops(s); + } +} + +static void dreplsrv_pending_pull_handler_im(struct tevent_context *ev, + struct tevent_immediate *im, + void *ptr) +{ + struct dreplsrv_service *service = talloc_get_type(ptr, struct dreplsrv_service); + + dreplsrv_run_pull_ops(service); +} + +void dreplsrv_pendingops_schedule_pull_now(struct dreplsrv_service *service) +{ + tevent_schedule_immediate(service->pending.im, service->task->event_ctx, + dreplsrv_pending_pull_handler_im, + service); + + return; +} + diff --git a/source4/dsdb/repl/drepl_replica.c b/source4/dsdb/repl/drepl_replica.c new file mode 100644 index 0000000..05d0683 --- /dev/null +++ b/source4/dsdb/repl/drepl_replica.c @@ -0,0 +1,62 @@ +/* + Unix SMB/CIFS Implementation. + + DSDB replication service - DsReplica{Add,Del,Mod} handling + + Copyright (C) Andrew Tridgell 2010 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "ldb_module.h" +#include "dsdb/samdb/samdb.h" +#include "samba/service.h" +#include "dsdb/repl/drepl_service.h" +#include "param/param.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +/* + implement DsReplicaAdd (forwarded from DRS server) + */ +NTSTATUS drepl_replica_add(struct dreplsrv_service *service, + struct drsuapi_DsReplicaAdd *r) +{ + NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsReplicaAdd, NDR_IN, r); + return NT_STATUS_NOT_IMPLEMENTED; +} + +/* + implement DsReplicaDel (forwarded from DRS server) + */ +NTSTATUS drepl_replica_del(struct dreplsrv_service *service, + struct drsuapi_DsReplicaDel *r) +{ + NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsReplicaDel, NDR_IN, r); + return NT_STATUS_NOT_IMPLEMENTED; +} + +/* + implement DsReplicaMod (forwarded from DRS server) + */ +NTSTATUS drepl_replica_mod(struct dreplsrv_service *service, + struct drsuapi_DsReplicaMod *r) +{ + NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsReplicaMod, NDR_IN, r); + return NT_STATUS_NOT_IMPLEMENTED; +} diff --git a/source4/dsdb/repl/drepl_ridalloc.c b/source4/dsdb/repl/drepl_ridalloc.c new file mode 100644 index 0000000..529e4e3 --- /dev/null +++ b/source4/dsdb/repl/drepl_ridalloc.c @@ -0,0 +1,265 @@ +/* + Unix SMB/CIFS Implementation. + + DSDB replication service - RID allocation code + + Copyright (C) Andrew Tridgell 2010 + Copyright (C) Andrew Bartlett 2010 + + based on drepl_notify.c + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "ldb_module.h" +#include "dsdb/samdb/samdb.h" +#include "samba/service.h" +#include "dsdb/repl/drepl_service.h" +#include "param/param.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +/* + called when a rid allocation request has completed + */ +static void drepl_new_rid_pool_callback(struct dreplsrv_service *service, + WERROR werr, + enum drsuapi_DsExtendedError ext_err, + void *cb_data) +{ + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": RID Manager failed RID allocation - %s - extended_ret[0x%X]\n", + win_errstr(werr), ext_err)); + } else { + DEBUG(3,(__location__ ": RID Manager completed RID allocation OK\n")); + } + + service->rid_alloc_in_progress = false; +} + +/* + schedule a getncchanges request to the RID Manager to ask for a new + set of RIDs using DRSUAPI_EXOP_FSMO_RID_ALLOC + */ +static WERROR drepl_request_new_rid_pool(struct dreplsrv_service *service, + struct ldb_dn *rid_manager_dn, struct ldb_dn *fsmo_role_dn, + uint64_t alloc_pool) +{ + WERROR werr = drepl_request_extended_op(service, + rid_manager_dn, + fsmo_role_dn, + DRSUAPI_EXOP_FSMO_RID_ALLOC, + alloc_pool, + 0, + drepl_new_rid_pool_callback, NULL); + if (W_ERROR_IS_OK(werr)) { + service->rid_alloc_in_progress = true; + } + return werr; +} + + +/* + see if we are on the last pool we have + */ +static int drepl_ridalloc_pool_exhausted(struct ldb_context *ldb, + bool *exhausted, + uint64_t *_alloc_pool) +{ + struct ldb_dn *server_dn, *machine_dn, *rid_set_dn; + TALLOC_CTX *tmp_ctx = talloc_new(ldb); + uint64_t alloc_pool; + uint64_t prev_pool; + uint32_t prev_pool_lo, prev_pool_hi; + uint32_t next_rid; + static const char * const attrs[] = { + "rIDAllocationPool", + "rIDPreviousAllocationPool", + "rIDNextRid", + NULL + }; + int ret; + struct ldb_result *res; + + *exhausted = false; + *_alloc_pool = UINT64_MAX; + + server_dn = ldb_dn_get_parent(tmp_ctx, samdb_ntds_settings_dn(ldb, tmp_ctx)); + if (!server_dn) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + ret = samdb_reference_dn(ldb, tmp_ctx, server_dn, "serverReference", &machine_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find serverReference in %s - %s\n", + ldb_dn_get_linearized(server_dn), ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return ret; + } + + ret = samdb_reference_dn(ldb, tmp_ctx, machine_dn, "rIDSetReferences", &rid_set_dn); + if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) { + *exhausted = true; + *_alloc_pool = 0; + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find rIDSetReferences in %s - %s\n", + ldb_dn_get_linearized(machine_dn), ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return ret; + } + + ret = ldb_search(ldb, tmp_ctx, &res, rid_set_dn, LDB_SCOPE_BASE, attrs, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to load RID Set attrs from %s - %s\n", + ldb_dn_get_linearized(rid_set_dn), ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return ret; + } + + alloc_pool = ldb_msg_find_attr_as_uint64(res->msgs[0], "rIDAllocationPool", 0); + prev_pool = ldb_msg_find_attr_as_uint64(res->msgs[0], "rIDPreviousAllocationPool", 0); + prev_pool_lo = prev_pool & 0xFFFFFFFF; + prev_pool_hi = prev_pool >> 32; + next_rid = ldb_msg_find_attr_as_uint(res->msgs[0], "rIDNextRid", 0); + + if (alloc_pool != prev_pool) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + if (next_rid < (prev_pool_hi + prev_pool_lo)/2) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + *exhausted = true; + *_alloc_pool = alloc_pool; + talloc_free(tmp_ctx); + return LDB_SUCCESS; +} + + +/* + see if we are low on RIDs in the RID Set rIDAllocationPool. If we + are, then schedule a replication call with DRSUAPI_EXOP_FSMO_RID_ALLOC + to the RID Manager + */ +WERROR dreplsrv_ridalloc_check_rid_pool(struct dreplsrv_service *service) +{ + struct ldb_dn *rid_manager_dn, *fsmo_role_dn; + TALLOC_CTX *tmp_ctx = talloc_new(service); + struct ldb_context *ldb = service->samdb; + bool exhausted; + WERROR werr; + int ret; + uint64_t alloc_pool; + bool is_us; + + if (service->am_rodc) { + talloc_free(tmp_ctx); + return WERR_OK; + } + + if (service->rid_alloc_in_progress) { + talloc_free(tmp_ctx); + return WERR_OK; + } + + /* + steps: + - find who the RID Manager is + - if we are the RID Manager then nothing to do + - find our RID Set object + - load rIDAllocationPool and rIDPreviousAllocationPool + - if rIDAllocationPool != rIDPreviousAllocationPool then + nothing to do + - schedule a getncchanges with DRSUAPI_EXOP_FSMO_RID_ALLOC + to the RID Manager + */ + + /* work out who is the RID Manager */ + ret = samdb_rid_manager_dn(ldb, tmp_ctx, &rid_manager_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0, (__location__ ": Failed to find RID Manager object - %s\n", ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* find the DN of the RID Manager */ + ret = samdb_reference_dn(ldb, tmp_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))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = samdb_dn_is_our_ntdsa(ldb, fsmo_role_dn, &is_us); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find determine if %s is our ntdsDsa object - %s\n", + ldb_dn_get_linearized(fsmo_role_dn), ldb_errstring(ldb))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (is_us) { + /* we are the RID Manager - no need to do a + DRSUAPI_EXOP_FSMO_RID_ALLOC */ + talloc_free(tmp_ctx); + return WERR_OK; + } + + ret = drepl_ridalloc_pool_exhausted(ldb, &exhausted, &alloc_pool); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (!exhausted) { + /* don't need a new pool */ + talloc_free(tmp_ctx); + return WERR_OK; + } + + DEBUG(2,(__location__ ": Requesting more RIDs from RID Manager\n")); + + werr = drepl_request_new_rid_pool(service, rid_manager_dn, fsmo_role_dn, alloc_pool); + talloc_free(tmp_ctx); + return werr; +} + +/* called by the samldb ldb module to tell us to ask for a new RID + pool */ +void dreplsrv_allocate_rid(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct dreplsrv_service *service = talloc_get_type(private_data, struct dreplsrv_service); + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + dreplsrv_ridalloc_check_rid_pool(service); +} diff --git a/source4/dsdb/repl/drepl_secret.c b/source4/dsdb/repl/drepl_secret.c new file mode 100644 index 0000000..47a8ca9 --- /dev/null +++ b/source4/dsdb/repl/drepl_secret.c @@ -0,0 +1,146 @@ +/* + Unix SMB/CIFS Implementation. + + DSDB replication service - repl secret handling + + Copyright (C) Andrew Tridgell 2010 + Copyright (C) Andrew Bartlett 2010 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "ldb_module.h" +#include "dsdb/samdb/samdb.h" +#include "samba/service.h" +#include "dsdb/repl/drepl_service.h" +#include "param/param.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +struct repl_secret_state { + const char *user_dn; +}; + +/* + called when a repl secret has completed + */ +static void drepl_repl_secret_callback(struct dreplsrv_service *service, + WERROR werr, + enum drsuapi_DsExtendedError ext_err, + void *cb_data) +{ + struct repl_secret_state *state = talloc_get_type_abort(cb_data, struct repl_secret_state); + if (!W_ERROR_IS_OK(werr)) { + if (W_ERROR_EQUAL(werr, WERR_DS_DRA_SECRETS_DENIED)) { + DEBUG(3,(__location__ ": repl secret disallowed for user " + "%s - not in allowed replication group\n", + state->user_dn)); + } else { + DEBUG(3,(__location__ ": repl secret failed for user %s - %s: extended_ret[0x%X]\n", + state->user_dn, win_errstr(werr), ext_err)); + } + } else { + DEBUG(3,(__location__ ": repl secret completed OK for '%s'\n", state->user_dn)); + } + talloc_free(state); +} + + +/** + * Called when the auth code wants us to try and replicate + * a users secrets + */ +void drepl_repl_secret(struct dreplsrv_service *service, + const char *user_dn) +{ + WERROR werr; + struct ldb_dn *nc_dn, *nc_root, *source_dsa_dn; + struct dreplsrv_partition *p; + struct GUID *source_dsa_guid; + struct repl_secret_state *state; + int ret; + + state = talloc_zero(service, struct repl_secret_state); + if (state == NULL) { + /* nothing to do, no return value */ + return; + } + + /* keep a copy for logging in the callback */ + state->user_dn = talloc_strdup(state, user_dn); + + nc_dn = ldb_dn_new(state, service->samdb, user_dn); + if (!ldb_dn_validate(nc_dn)) { + DEBUG(0,(__location__ ": Failed to parse user_dn '%s'\n", user_dn)); + talloc_free(state); + return; + } + + /* work out which partition this is in */ + ret = dsdb_find_nc_root(service->samdb, state, nc_dn, &nc_root); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find nc_root for user_dn '%s'\n", user_dn)); + talloc_free(state); + return; + } + + /* find the partition in our list */ + for (p=service->partitions; p; p=p->next) { + if (ldb_dn_compare(p->dn, nc_root) == 0) { + break; + } + } + if (p == NULL) { + DEBUG(0,(__location__ ": Failed to find partition for nc_root '%s'\n", ldb_dn_get_linearized(nc_root))); + talloc_free(state); + return; + } + + if (p->sources == NULL) { + DEBUG(0,(__location__ ": No sources for nc_root '%s' for user_dn '%s'\n", + ldb_dn_get_linearized(nc_root), user_dn)); + talloc_free(state); + return; + } + + /* use the first source, for no particularly good reason */ + source_dsa_guid = &p->sources->repsFrom1->source_dsa_obj_guid; + + source_dsa_dn = ldb_dn_new(state, service->samdb, + talloc_asprintf(state, "<GUID=%s>", + GUID_string(state, source_dsa_guid))); + if (!ldb_dn_validate(source_dsa_dn)) { + DEBUG(0,(__location__ ": Invalid source DSA GUID '%s' for user_dn '%s'\n", + GUID_string(state, source_dsa_guid), user_dn)); + talloc_free(state); + return; + } + + werr = drepl_request_extended_op(service, + nc_dn, + source_dsa_dn, + DRSUAPI_EXOP_REPL_SECRET, + 0, + p->sources->repsFrom1->highwatermark.highest_usn, + drepl_repl_secret_callback, state); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(2,(__location__ ": Failed to setup secret replication for user_dn '%s'\n", user_dn)); + talloc_free(state); + return; + } + DEBUG(3,(__location__ ": started secret replication for %s\n", user_dn)); +} diff --git a/source4/dsdb/repl/drepl_service.c b/source4/dsdb/repl/drepl_service.c new file mode 100644 index 0000000..02ece26 --- /dev/null +++ b/source4/dsdb/repl/drepl_service.c @@ -0,0 +1,545 @@ +/* + Unix SMB/CIFS Implementation. + DSDB replication service + + Copyright (C) Stefan Metzmacher 2007 + Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2010 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "samba/service.h" +#include "lib/events/events.h" +#include "dsdb/repl/drepl_service.h" +#include <ldb_errors.h> +#include "../lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "librpc/gen_ndr/ndr_irpc.h" +#include "param/param.h" +#include "libds/common/roles.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +/** + * Call-back data for _drepl_replica_sync_done_cb() + */ +struct drepl_replica_sync_cb_data { + struct irpc_message *msg; + struct drsuapi_DsReplicaSync *r; + + /* number of ops left to be completed */ + int ops_count; + + /* last failure error code */ + WERROR werr_last_failure; +}; + + +static WERROR dreplsrv_init_creds(struct dreplsrv_service *service) +{ + service->system_session_info = system_session(service->task->lp_ctx); + if (service->system_session_info == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return WERR_OK; +} + +static WERROR dreplsrv_connect_samdb(struct dreplsrv_service *service, struct loadparm_context *lp_ctx) +{ + const struct GUID *ntds_guid; + struct drsuapi_DsBindInfo28 *bind_info28; + + service->samdb = samdb_connect(service, + service->task->event_ctx, + lp_ctx, + service->system_session_info, + NULL, + 0); + if (!service->samdb) { + return WERR_DS_UNAVAILABLE; + } + + ntds_guid = samdb_ntds_objectGUID(service->samdb); + if (!ntds_guid) { + return WERR_DS_UNAVAILABLE; + } + service->ntds_guid = *ntds_guid; + + if (samdb_rodc(service->samdb, &service->am_rodc) != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to determine RODC status\n")); + return WERR_DS_UNAVAILABLE; + } + + bind_info28 = &service->bind_info28; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V5; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT; +#if 0 /* we don't support XPRESS compression yet */ + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_XPRESS_COMPRESS; +#endif + /* TODO: fill in site_guid */ + bind_info28->site_guid = GUID_zero(); + /* TODO: find out how this is really triggered! */ + bind_info28->pid = 0; + bind_info28->repl_epoch = 0; + + return WERR_OK; +} + + +/** + * Callback for dreplsrv_out_operation operation completion. + * + * We just need to complete a waiting IRPC message here. + * In case pull operation has failed, + * caller of this callback will dump + * failure information. + * + * NOTE: cb_data is allocated in IRPC msg's context + * and will be freed during irpc_send_reply() call. + */ +static void _drepl_replica_sync_done_cb(struct dreplsrv_service *service, + WERROR werr, + enum drsuapi_DsExtendedError ext_err, + void *cb_data) +{ + struct drepl_replica_sync_cb_data *data = talloc_get_type(cb_data, + struct drepl_replica_sync_cb_data); + struct irpc_message *msg = data->msg; + struct drsuapi_DsReplicaSync *r = data->r; + + /* store last bad result */ + if (!W_ERROR_IS_OK(werr)) { + data->werr_last_failure = werr; + } + + /* decrement pending ops count */ + data->ops_count--; + + if (data->ops_count == 0) { + /* Return result to client */ + r->out.result = data->werr_last_failure; + + /* complete IRPC message */ + irpc_send_reply(msg, NT_STATUS_OK); + } +} + +/** + * Helper to schedule a replication operation with a source DSA. + * If 'data' is valid pointer, then a callback + * for the operation is passed and 'data->msg' is + * marked as 'deferred' - defer_reply = true + */ +static WERROR _drepl_schedule_replication(struct dreplsrv_service *service, + struct dreplsrv_partition_source_dsa *dsa, + struct drsuapi_DsReplicaObjectIdentifier *nc, + uint32_t rep_options, + struct drepl_replica_sync_cb_data *data, + TALLOC_CTX *mem_ctx) +{ + WERROR werr; + dreplsrv_extended_callback_t fn_callback = NULL; + + if (data) { + fn_callback = _drepl_replica_sync_done_cb; + } + + /* schedule replication item */ + werr = dreplsrv_schedule_partition_pull_source(service, dsa, rep_options, + DRSUAPI_EXOP_NONE, 0, + fn_callback, data); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,("%s: failed setup of sync of partition (%s, %s, %s) - %s\n", + __FUNCTION__, + GUID_string(mem_ctx, &nc->guid), + nc->dn, + dsa->repsFrom1->other_info->dns_name, + win_errstr(werr))); + return werr; + } + /* log we've scheduled a replication item */ + DEBUG(3,("%s: forcing sync of partition (%s, %s, %s)\n", + __FUNCTION__, + GUID_string(mem_ctx, &nc->guid), + nc->dn, + dsa->repsFrom1->other_info->dns_name)); + + /* mark IRPC message as deferred if necessary */ + if (data) { + data->ops_count++; + data->msg->defer_reply = true; + } + + return WERR_OK; +} + +/* + DsReplicaSync messages from the DRSUAPI server are forwarded here + */ +static NTSTATUS drepl_replica_sync(struct irpc_message *msg, + struct drsuapi_DsReplicaSync *r) +{ + WERROR werr; + struct dreplsrv_partition *p; + struct drepl_replica_sync_cb_data *cb_data; + struct dreplsrv_partition_source_dsa *dsa; + struct drsuapi_DsReplicaSyncRequest1 *req1; + struct drsuapi_DsReplicaObjectIdentifier *nc; + struct dreplsrv_service *service = talloc_get_type(msg->private_data, + struct dreplsrv_service); + +#define REPLICA_SYNC_FAIL(_msg, _werr) do {\ + if (!W_ERROR_IS_OK(_werr)) { \ + DEBUG(0,(__location__ ": Failure - %s. werr = %s\n", \ + _msg, win_errstr(_werr))); \ + NDR_PRINT_IN_DEBUG(drsuapi_DsReplicaSync, r); \ + } \ + r->out.result = _werr; \ + goto done;\ + } while(0) + + + if (r->in.level != 1) { + REPLICA_SYNC_FAIL("Unsupported level", + WERR_DS_DRA_INVALID_PARAMETER); + } + + req1 = &r->in.req->req1; + nc = req1->naming_context; + + /* Check input parameters */ + if (!nc) { + REPLICA_SYNC_FAIL("Invalid Naming Context", + WERR_DS_DRA_INVALID_PARAMETER); + } + + /* Find Naming context to be synchronized */ + werr = dreplsrv_partition_find_for_nc(service, + &nc->guid, &nc->sid, nc->dn, + &p); + if (!W_ERROR_IS_OK(werr)) { + REPLICA_SYNC_FAIL("Failed to find requested Naming Context", + werr); + } + + /* should we process it asynchronously? */ + if (req1->options & DRSUAPI_DRS_ASYNC_OP) { + cb_data = NULL; + } else { + cb_data = talloc_zero(msg, struct drepl_replica_sync_cb_data); + if (!cb_data) { + REPLICA_SYNC_FAIL("Not enough memory", + WERR_DS_DRA_INTERNAL_ERROR); + } + + cb_data->msg = msg; + cb_data->r = r; + cb_data->werr_last_failure = WERR_OK; + } + + /* collect source DSAs to sync with */ + if (req1->options & DRSUAPI_DRS_SYNC_ALL) { + for (dsa = p->sources; dsa; dsa = dsa->next) { + /* schedule replication item */ + werr = _drepl_schedule_replication(service, dsa, nc, + req1->options, cb_data, msg); + if (!W_ERROR_IS_OK(werr)) { + REPLICA_SYNC_FAIL("_drepl_schedule_replication() failed", + werr); + } + } + } else { + if (req1->options & DRSUAPI_DRS_SYNC_BYNAME) { + /* client should pass at least valid string */ + if (!req1->source_dsa_dns) { + REPLICA_SYNC_FAIL("'source_dsa_dns' is not valid", + WERR_DS_DRA_INVALID_PARAMETER); + } + + werr = dreplsrv_partition_source_dsa_by_dns(p, + req1->source_dsa_dns, + &dsa); + } else { + /* client should pass at least some GUID */ + if (GUID_all_zero(&req1->source_dsa_guid)) { + REPLICA_SYNC_FAIL("'source_dsa_guid' is not valid", + WERR_DS_DRA_INVALID_PARAMETER); + } + + werr = dreplsrv_partition_source_dsa_by_guid(p, + &req1->source_dsa_guid, + &dsa); + if (W_ERROR_EQUAL(werr, WERR_DS_DRA_NO_REPLICA)) { + /* we don't have this source setup as + a replication partner. Create a + temporary dsa structure for this + replication */ + werr = dreplsrv_partition_source_dsa_temporary(p, + msg, + &req1->source_dsa_guid, + &dsa); + } + } + if (!W_ERROR_IS_OK(werr)) { + REPLICA_SYNC_FAIL("Failed to locate source DSA for given NC", + werr); + } + + /* schedule replication item */ + werr = _drepl_schedule_replication(service, dsa, nc, + req1->options, cb_data, msg); + if (!W_ERROR_IS_OK(werr)) { + REPLICA_SYNC_FAIL("_drepl_schedule_replication() failed", + werr); + } + } + + /* if we got here, everything is OK */ + r->out.result = WERR_OK; + + /* + * schedule replication event to force + * replication as soon as possible + */ + dreplsrv_pendingops_schedule_pull_now(service); + +done: + return NT_STATUS_OK; +} + +/** + * Called when drplsrv should refresh its state. + * For example, when KCC change topology, dreplsrv + * should update its cache + * + * @param partition_dn If not empty/NULL, partition to update + */ +static NTSTATUS dreplsrv_refresh(struct irpc_message *msg, + struct dreplsrv_refresh *r) +{ + struct dreplsrv_service *s = talloc_get_type(msg->private_data, + struct dreplsrv_service); + + r->out.result = dreplsrv_refresh_partitions(s); + + return NT_STATUS_OK; +} + +/** + * Called when the auth code wants us to try and replicate + * a users secrets + */ +static NTSTATUS drepl_trigger_repl_secret(struct irpc_message *msg, + struct drepl_trigger_repl_secret *r) +{ + struct dreplsrv_service *service = talloc_get_type(msg->private_data, + struct dreplsrv_service); + + + drepl_repl_secret(service, r->in.user_dn); + + /* we are not going to be sending a reply to this request */ + msg->no_reply = true; + + return NT_STATUS_OK; +} + + +/* + DsReplicaAdd messages from the DRSUAPI server are forwarded here + */ +static NTSTATUS dreplsrv_replica_add(struct irpc_message *msg, + struct drsuapi_DsReplicaAdd *r) +{ + struct dreplsrv_service *service = talloc_get_type(msg->private_data, + struct dreplsrv_service); + return drepl_replica_add(service, r); +} + +/* + DsReplicaDel messages from the DRSUAPI server are forwarded here + */ +static NTSTATUS dreplsrv_replica_del(struct irpc_message *msg, + struct drsuapi_DsReplicaDel *r) +{ + struct dreplsrv_service *service = talloc_get_type(msg->private_data, + struct dreplsrv_service); + return drepl_replica_del(service, r); +} + +/* + DsReplicaMod messages from the DRSUAPI server are forwarded here + */ +static NTSTATUS dreplsrv_replica_mod(struct irpc_message *msg, + struct drsuapi_DsReplicaMod *r) +{ + struct dreplsrv_service *service = talloc_get_type(msg->private_data, + struct dreplsrv_service); + return drepl_replica_mod(service, r); +} + + +/* + startup the dsdb replicator service task +*/ +static NTSTATUS dreplsrv_task_init(struct task_server *task) +{ + WERROR status; + struct dreplsrv_service *service; + uint32_t periodic_startup_interval; + + switch (lpcfg_server_role(task->lp_ctx)) { + case ROLE_STANDALONE: + task_server_terminate(task, "dreplsrv: no DSDB replication required in standalone configuration", + false); + return NT_STATUS_INVALID_DOMAIN_ROLE; + case ROLE_DOMAIN_MEMBER: + task_server_terminate(task, "dreplsrv: no DSDB replication required in domain member configuration", + false); + return NT_STATUS_INVALID_DOMAIN_ROLE; + case ROLE_ACTIVE_DIRECTORY_DC: + /* Yes, we want DSDB replication */ + break; + } + + task_server_set_title(task, "task[dreplsrv]"); + + service = talloc_zero(task, struct dreplsrv_service); + if (!service) { + task_server_terminate(task, "dreplsrv_task_init: out of memory", true); + return NT_STATUS_NO_MEMORY; + } + service->task = task; + service->startup_time = timeval_current(); + task->private_data = service; + + status = dreplsrv_init_creds(service); + if (!W_ERROR_IS_OK(status)) { + task_server_terminate(task, talloc_asprintf(task, + "dreplsrv: Failed to obtain server credentials: %s\n", + win_errstr(status)), true); + return werror_to_ntstatus(status); + } + + status = dreplsrv_connect_samdb(service, task->lp_ctx); + if (!W_ERROR_IS_OK(status)) { + task_server_terminate(task, talloc_asprintf(task, + "dreplsrv: Failed to connect to local samdb: %s\n", + win_errstr(status)), true); + return werror_to_ntstatus(status); + } + + status = dreplsrv_load_partitions(service); + if (!W_ERROR_IS_OK(status)) { + task_server_terminate(task, talloc_asprintf(task, + "dreplsrv: Failed to load partitions: %s\n", + win_errstr(status)), true); + return werror_to_ntstatus(status); + } + + periodic_startup_interval = lpcfg_parm_int(task->lp_ctx, NULL, "dreplsrv", "periodic_startup_interval", 15); /* in seconds */ + service->periodic.interval = lpcfg_parm_int(task->lp_ctx, NULL, "dreplsrv", "periodic_interval", 300); /* in seconds */ + + status = dreplsrv_periodic_schedule(service, periodic_startup_interval); + if (!W_ERROR_IS_OK(status)) { + task_server_terminate(task, talloc_asprintf(task, + "dreplsrv: Failed to periodic schedule: %s\n", + win_errstr(status)), true); + return werror_to_ntstatus(status); + } + + service->pending.im = tevent_create_immediate(service); + if (service->pending.im == NULL) { + task_server_terminate(task, + "dreplsrv: Failed to create immediate " + "task for future DsReplicaSync\n", + true); + return NT_STATUS_NO_MEMORY; + } + + /* if we are a RODC then we do not send DSReplicaSync*/ + if (!service->am_rodc) { + service->notify.interval = lpcfg_parm_int(task->lp_ctx, NULL, "dreplsrv", + "notify_interval", 5); /* in seconds */ + status = dreplsrv_notify_schedule(service, service->notify.interval); + if (!W_ERROR_IS_OK(status)) { + task_server_terminate(task, talloc_asprintf(task, + "dreplsrv: Failed to setup notify schedule: %s\n", + win_errstr(status)), true); + return werror_to_ntstatus(status); + } + } + + irpc_add_name(task->msg_ctx, "dreplsrv"); + + IRPC_REGISTER(task->msg_ctx, irpc, DREPLSRV_REFRESH, dreplsrv_refresh, service); + IRPC_REGISTER(task->msg_ctx, drsuapi, DRSUAPI_DSREPLICASYNC, drepl_replica_sync, service); + IRPC_REGISTER(task->msg_ctx, drsuapi, DRSUAPI_DSREPLICAADD, dreplsrv_replica_add, service); + IRPC_REGISTER(task->msg_ctx, drsuapi, DRSUAPI_DSREPLICADEL, dreplsrv_replica_del, service); + IRPC_REGISTER(task->msg_ctx, drsuapi, DRSUAPI_DSREPLICAMOD, dreplsrv_replica_mod, service); + IRPC_REGISTER(task->msg_ctx, irpc, DREPL_TAKEFSMOROLE, drepl_take_FSMO_role, service); + IRPC_REGISTER(task->msg_ctx, irpc, DREPL_TRIGGER_REPL_SECRET, drepl_trigger_repl_secret, service); + imessaging_register(task->msg_ctx, service, MSG_DREPL_ALLOCATE_RID, dreplsrv_allocate_rid); + + return NT_STATUS_OK; +} + +/* + register ourselves as a available server +*/ +NTSTATUS server_service_drepl_init(TALLOC_CTX *ctx) +{ + static const struct service_details details = { + .inhibit_fork_on_accept = true, + .inhibit_pre_fork = true, + .task_init = dreplsrv_task_init, + .post_fork = NULL, + }; + return register_server_service(ctx, "drepl", &details); +} diff --git a/source4/dsdb/repl/drepl_service.h b/source4/dsdb/repl/drepl_service.h new file mode 100644 index 0000000..4451df9 --- /dev/null +++ b/source4/dsdb/repl/drepl_service.h @@ -0,0 +1,251 @@ +/* + Unix SMB/CIFS Implementation. + DSDB replication service + + Copyright (C) Stefan Metzmacher 2007 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#ifndef _DSDB_REPL_DREPL_SERVICE_H_ +#define _DSDB_REPL_DREPL_SERVICE_H_ + +#include "librpc/gen_ndr/ndr_drsuapi_c.h" + +struct dreplsrv_service; +struct dreplsrv_partition; + +struct dreplsrv_drsuapi_connection { + /* + * this pipe pointer is also the indicator + * for a valid connection + */ + struct dcerpc_pipe *pipe; + struct dcerpc_binding_handle *drsuapi_handle; + + DATA_BLOB gensec_skey; + struct drsuapi_DsBindInfo28 remote_info28; + struct policy_handle bind_handle; +}; + +struct dreplsrv_out_connection { + struct dreplsrv_out_connection *prev, *next; + + struct dreplsrv_service *service; + + /* + * the binding for the outgoing connection + */ + struct dcerpc_binding *binding; + + /* the out going connection to the source dsa */ + struct dreplsrv_drsuapi_connection *drsuapi; +}; + +struct dreplsrv_partition_source_dsa { + struct dreplsrv_partition_source_dsa *prev, *next; + + struct dreplsrv_partition *partition; + + /* + * the cached repsFrom value for this source dsa + * + * it needs to be updated after each DsGetNCChanges() call + * to the source dsa + * + * repsFrom1 == &_repsFromBlob.ctr.ctr1 + */ + struct repsFromToBlob _repsFromBlob; + struct repsFromTo1 *repsFrom1; + + /* the last uSN when we sent a notify */ + uint64_t notify_uSN; + + /* the reference to the source_dsa and its outgoing connection */ + struct dreplsrv_out_connection *conn; +}; + +struct dreplsrv_partition { + struct dreplsrv_partition *prev, *next; + + struct dreplsrv_service *service; + + /* the dn of the partition */ + struct ldb_dn *dn; + struct drsuapi_DsReplicaObjectIdentifier nc; + + /* + * up-to-date vector needs to be updated before and after each DsGetNCChanges() call + * + * - before: we need to use our own invocationId together with our highestCommittedUSN + * - after: we need to merge in the remote uptodatevector, to avoid reading it again + */ + struct replUpToDateVectorCtr2 uptodatevector; + struct drsuapi_DsReplicaCursorCtrEx uptodatevector_ex; + + /* + * a linked list of all source dsa's we replicate from + */ + struct dreplsrv_partition_source_dsa *sources; + + /* + * a linked list of all source dsa's we will notify, + * that are not also in sources + */ + struct dreplsrv_partition_source_dsa *notifies; + + bool partial_replica; + bool rodc_replica; +}; + +typedef void (*dreplsrv_extended_callback_t)(struct dreplsrv_service *, + WERROR, + enum drsuapi_DsExtendedError, + void *cb_data); + +struct dreplsrv_out_operation { + struct dreplsrv_out_operation *prev, *next; + time_t schedule_time; + + struct dreplsrv_service *service; + + struct dreplsrv_partition_source_dsa *source_dsa; + + /* replication options - currently used by DsReplicaSync */ + uint32_t options; + enum drsuapi_DsExtendedOperation extended_op; + uint64_t fsmo_info; + enum drsuapi_DsExtendedError extended_ret; + dreplsrv_extended_callback_t callback; + void *cb_data; + /* more replication flags - used by DsReplicaSync GET_TGT */ + uint32_t more_flags; +}; + +struct dreplsrv_notify_operation { + struct dreplsrv_notify_operation *prev, *next; + time_t schedule_time; + + struct dreplsrv_service *service; + uint64_t uSN; + + struct dreplsrv_partition_source_dsa *source_dsa; + bool is_urgent; + uint32_t replica_flags; +}; + +struct dreplsrv_service { + /* the whole drepl service is in one task */ + struct task_server *task; + + /* the time the service was started */ + struct timeval startup_time; + + /* + * system session info + * with machine account credentials + */ + struct auth_session_info *system_session_info; + + /* + * a connection to the local samdb + */ + struct ldb_context *samdb; + + /* the guid of our NTDS Settings object, which never changes! */ + struct GUID ntds_guid; + /* + * the struct holds the values used for outgoing DsBind() calls, + * so that we need to set them up only once + */ + struct drsuapi_DsBindInfo28 bind_info28; + + /* some stuff for periodic processing */ + struct { + /* + * the interval between to periodic runs + */ + uint32_t interval; + + /* + * the timestamp for the next event, + * this is the timestamp passed to event_add_timed() + */ + struct timeval next_event; + + /* here we have a reference to the timed event the schedules the periodic stuff */ + struct tevent_timer *te; + } periodic; + + /* some stuff for running only the incoming notify ops */ + struct { + /* + * here we have a reference to the immediate event that was + * scheduled from the DsReplicaSync + */ + struct tevent_immediate *im; + } pending; + + /* some stuff for notify processing */ + struct { + /* + * the interval between notify runs + */ + uint32_t interval; + + /* + * the timestamp for the next event, + * this is the timestamp passed to event_add_timed() + */ + struct timeval next_event; + + /* here we have a reference to the timed event the schedules the notifies */ + struct tevent_timer *te; + } notify; + + /* + * the list of partitions we need to replicate + */ + struct dreplsrv_partition *partitions; + + /* + * the list of cached connections + */ + struct dreplsrv_out_connection *connections; + + struct { + /* the pointer to the current active operation */ + struct dreplsrv_out_operation *current; + + /* the list of pending operations */ + struct dreplsrv_out_operation *pending; + + /* the list of pending notify operations */ + struct dreplsrv_notify_operation *notifies; + + /* an active notify operation */ + struct dreplsrv_notify_operation *n_current; + } ops; + + bool rid_alloc_in_progress; + + bool am_rodc; +}; + +#include "lib/messaging/irpc.h" +#include "dsdb/repl/drepl_out_helpers.h" +#include "dsdb/repl/drepl_service_proto.h" + +#endif /* _DSDB_REPL_DREPL_SERVICE_H_ */ diff --git a/source4/dsdb/repl/replicated_objects.c b/source4/dsdb/repl/replicated_objects.c new file mode 100644 index 0000000..01adae2 --- /dev/null +++ b/source4/dsdb/repl/replicated_objects.c @@ -0,0 +1,1283 @@ +/* + Unix SMB/CIFS Implementation. + Helper functions for applying replicated objects + + Copyright (C) Stefan Metzmacher <metze@samba.org> 2007 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "dsdb/samdb/samdb.h" +#include <ldb_errors.h> +#include "../lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "../libcli/drsuapi/drsuapi.h" +#include "libcli/auth/libcli_auth.h" +#include "param/param.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +static WERROR dsdb_repl_merge_working_schema(struct ldb_context *ldb, + struct dsdb_schema *dest_schema, + const struct dsdb_schema *ref_schema) +{ + const struct dsdb_class *cur_class = NULL; + const struct dsdb_attribute *cur_attr = NULL; + int ret; + + for (cur_class = ref_schema->classes; + cur_class; + cur_class = cur_class->next) + { + const struct dsdb_class *tmp1; + struct dsdb_class *tmp2; + + tmp1 = dsdb_class_by_governsID_id(dest_schema, + cur_class->governsID_id); + if (tmp1 != NULL) { + continue; + } + + /* + * Do a shallow copy so that original next and prev are + * not modified, we don't need to do a deep copy + * as the rest won't be modified and this is for + * a short lived object. + */ + tmp2 = talloc(dest_schema, struct dsdb_class); + if (tmp2 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + *tmp2 = *cur_class; + DLIST_ADD(dest_schema->classes, tmp2); + } + + for (cur_attr = ref_schema->attributes; + cur_attr; + cur_attr = cur_attr->next) + { + const struct dsdb_attribute *tmp1; + struct dsdb_attribute *tmp2; + + tmp1 = dsdb_attribute_by_attributeID_id(dest_schema, + cur_attr->attributeID_id); + if (tmp1 != NULL) { + continue; + } + + /* + * Do a shallow copy so that original next and prev are + * not modified, we don't need to do a deep copy + * as the rest won't be modified and this is for + * a short lived object. + */ + tmp2 = talloc(dest_schema, struct dsdb_attribute); + if (tmp2 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + *tmp2 = *cur_attr; + DLIST_ADD(dest_schema->attributes, tmp2); + } + + ret = dsdb_setup_sorted_accessors(ldb, dest_schema); + if (LDB_SUCCESS != ret) { + DEBUG(0,("Failed to add new attribute to reference schema!\n")); + return WERR_INTERNAL_ERROR; + } + + return WERR_OK; +} + +WERROR dsdb_repl_resolve_working_schema(struct ldb_context *ldb, + struct dsdb_schema_prefixmap *pfm_remote, + uint32_t cycle_before_switching, + struct dsdb_schema *initial_schema, + struct dsdb_schema *resulting_schema, + uint32_t object_count, + const struct drsuapi_DsReplicaObjectListItemEx *first_object) +{ + struct schema_list { + struct schema_list *next, *prev; + const struct drsuapi_DsReplicaObjectListItemEx *obj; + }; + struct schema_list *schema_list = NULL, *schema_list_item, *schema_list_next_item; + WERROR werr; + struct dsdb_schema *working_schema; + const struct drsuapi_DsReplicaObjectListItemEx *cur; + DATA_BLOB empty_key = data_blob_null; + int ret, pass_no; + uint32_t ignore_attids[] = { + DRSUAPI_ATTID_auxiliaryClass, + DRSUAPI_ATTID_mayContain, + DRSUAPI_ATTID_mustContain, + DRSUAPI_ATTID_possSuperiors, + DRSUAPI_ATTID_systemPossSuperiors, + DRSUAPI_ATTID_INVALID + }; + TALLOC_CTX *frame = talloc_stackframe(); + + /* create a list of objects yet to be converted */ + for (cur = first_object; cur; cur = cur->next_object) { + schema_list_item = talloc(frame, struct schema_list); + if (schema_list_item == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + schema_list_item->obj = cur; + DLIST_ADD_END(schema_list, schema_list_item); + } + + /* resolve objects until all are resolved and in local schema */ + pass_no = 1; + working_schema = initial_schema; + + while (schema_list) { + uint32_t converted_obj_count = 0; + uint32_t failed_obj_count = 0; + + if (resulting_schema != working_schema) { + /* + * If the selfmade schema is not the schema used to + * translate and validate replicated object, + * Which means that we are using the bootstrap schema + * Then we add attributes and classes that were already + * translated to the working schema, the idea is that + * we might need to add new attributes and classes + * to be able to translate critical replicated objects + * and without that we wouldn't be able to translate them + */ + werr = dsdb_repl_merge_working_schema(ldb, + working_schema, + resulting_schema); + if (!W_ERROR_IS_OK(werr)) { + talloc_free(frame); + return werr; + } + } + + for (schema_list_item = schema_list; + schema_list_item; + schema_list_item=schema_list_next_item) { + struct dsdb_extended_replicated_object object; + + cur = schema_list_item->obj; + + /* + * Save the next item, now we have saved out + * the current one, so we can DLIST_REMOVE it + * safely + */ + schema_list_next_item = schema_list_item->next; + + /* + * Convert the objects into LDB messages using the + * schema we have so far. It's ok if we fail to convert + * an object. We should convert more objects on next pass. + */ + werr = dsdb_convert_object_ex(ldb, working_schema, + NULL, + pfm_remote, + cur, &empty_key, + ignore_attids, + 0, + schema_list_item, &object); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(4,("debug: Failed to convert schema " + "object %s into ldb msg, " + "will try during next loop\n", + cur->object.identifier->dn)); + + failed_obj_count++; + } else { + /* + * Convert the schema from ldb_message format + * (OIDs as OID strings) into schema, using + * the remote prefixMap + * + * It's not likely, but possible to get the + * same object twice and we should keep + * the last instance. + */ + werr = dsdb_schema_set_el_from_ldb_msg_dups(ldb, + resulting_schema, + object.msg, + true); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(4,("debug: failed to convert " + "object %s into a schema element, " + "will try during next loop: %s\n", + ldb_dn_get_linearized(object.msg->dn), + win_errstr(werr))); + failed_obj_count++; + } else { + DEBUG(8,("Converted object %s into a schema element\n", + ldb_dn_get_linearized(object.msg->dn))); + DLIST_REMOVE(schema_list, schema_list_item); + TALLOC_FREE(schema_list_item); + converted_obj_count++; + } + } + } + + DEBUG(4,("Schema load pass %d: converted %d, %d of %d objects left to be converted.\n", + pass_no, converted_obj_count, failed_obj_count, object_count)); + + /* check if we converted any objects in this pass */ + if (converted_obj_count == 0) { + DEBUG(0,("Can't continue Schema load: " + "didn't manage to convert any objects: " + "all %d remaining of %d objects " + "failed to convert\n", + failed_obj_count, object_count)); + talloc_free(frame); + return WERR_INTERNAL_ERROR; + } + + /* + * Don't try to load the schema if there is missing object + * _and_ we are on the first pass as some critical objects + * might be missing. + */ + if (failed_obj_count == 0 || pass_no > cycle_before_switching) { + /* prepare for another cycle */ + working_schema = resulting_schema; + + ret = dsdb_setup_sorted_accessors(ldb, working_schema); + if (LDB_SUCCESS != ret) { + DEBUG(0,("Failed to create schema-cache indexes!\n")); + talloc_free(frame); + return WERR_INTERNAL_ERROR; + } + } + pass_no++; + } + + talloc_free(frame); + return WERR_OK; +} + +/** + * Multi-pass working schema creation + * Function will: + * - shallow copy initial schema supplied + * - create a working schema in multiple passes + * until all objects are resolved + * Working schema is a schema with Attributes, Classes + * and indexes, but w/o subClassOf, possibleSupperiors etc. + * It is to be used just us cache for converting attribute values. + */ +WERROR dsdb_repl_make_working_schema(struct ldb_context *ldb, + const struct dsdb_schema *initial_schema, + const struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr, + uint32_t object_count, + const struct drsuapi_DsReplicaObjectListItemEx *first_object, + const DATA_BLOB *gensec_skey, + TALLOC_CTX *mem_ctx, + struct dsdb_schema **_schema_out) +{ + WERROR werr; + struct dsdb_schema_prefixmap *pfm_remote; + uint32_t r; + struct dsdb_schema *working_schema; + + /* make a copy of the iniatial_scheam so we don't mess with it */ + working_schema = dsdb_schema_copy_shallow(mem_ctx, ldb, initial_schema); + if (!working_schema) { + DEBUG(0,(__location__ ": schema copy failed!\n")); + return WERR_NOT_ENOUGH_MEMORY; + } + working_schema->resolving_in_progress = true; + + /* we are going to need remote prefixMap for decoding */ + werr = dsdb_schema_pfm_from_drsuapi_pfm(mapping_ctr, true, + working_schema, &pfm_remote, NULL); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Failed to decode remote prefixMap: %s\n", + win_errstr(werr))); + talloc_free(working_schema); + return werr; + } + + for (r=0; r < pfm_remote->length; r++) { + const struct dsdb_schema_prefixmap_oid *rm = &pfm_remote->prefixes[r]; + bool found_oid = false; + uint32_t l; + + for (l=0; l < working_schema->prefixmap->length; l++) { + const struct dsdb_schema_prefixmap_oid *lm = &working_schema->prefixmap->prefixes[l]; + int cmp; + + cmp = data_blob_cmp(&rm->bin_oid, &lm->bin_oid); + if (cmp == 0) { + found_oid = true; + break; + } + } + + if (found_oid) { + continue; + } + + /* + * We prefer the same is as we got from the remote peer + * if there's no conflict. + */ + werr = dsdb_schema_pfm_add_entry(working_schema->prefixmap, + rm->bin_oid, &rm->id, NULL); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Failed to merge remote prefixMap: %s\n", + win_errstr(werr))); + talloc_free(working_schema); + return werr; + } + } + + werr = dsdb_repl_resolve_working_schema(ldb, + pfm_remote, + 0, /* cycle_before_switching */ + working_schema, + working_schema, + object_count, + first_object); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0, ("%s: dsdb_repl_resolve_working_schema() failed: %s\n", + __location__, win_errstr(werr))); + talloc_free(working_schema); + return werr; + } + + working_schema->resolving_in_progress = false; + + *_schema_out = working_schema; + + return WERR_OK; +} + +static bool dsdb_attid_in_list(const uint32_t attid_list[], uint32_t attid) +{ + const uint32_t *cur; + if (!attid_list) { + return false; + } + for (cur = attid_list; *cur != DRSUAPI_ATTID_INVALID; cur++) { + if (*cur == attid) { + return true; + } + } + return false; +} + +WERROR dsdb_convert_object_ex(struct ldb_context *ldb, + const struct dsdb_schema *schema, + struct ldb_dn *partition_dn, + const struct dsdb_schema_prefixmap *pfm_remote, + const struct drsuapi_DsReplicaObjectListItemEx *in, + const DATA_BLOB *gensec_skey, + const uint32_t *ignore_attids, + uint32_t dsdb_repl_flags, + TALLOC_CTX *mem_ctx, + struct dsdb_extended_replicated_object *out) +{ + WERROR status = WERR_OK; + uint32_t i; + struct ldb_message *msg; + struct replPropertyMetaDataBlob *md; + int instanceType; + struct ldb_message_element *instanceType_e = NULL; + NTTIME whenChanged = 0; + time_t whenChanged_t; + const char *whenChanged_s; + struct dom_sid *sid = NULL; + uint32_t rid = 0; + uint32_t attr_count; + + if (!in->object.identifier) { + return WERR_FOOBAR; + } + + if (!in->object.identifier->dn || !in->object.identifier->dn[0]) { + return WERR_FOOBAR; + } + + if (in->object.attribute_ctr.num_attributes != 0 && !in->meta_data_ctr) { + return WERR_FOOBAR; + } + + if (in->object.attribute_ctr.num_attributes != in->meta_data_ctr->count) { + return WERR_FOOBAR; + } + + sid = &in->object.identifier->sid; + if (sid->num_auths > 0) { + rid = sid->sub_auths[sid->num_auths - 1]; + } + + msg = ldb_msg_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(msg); + + msg->dn = ldb_dn_new(msg, ldb, in->object.identifier->dn); + W_ERROR_HAVE_NO_MEMORY(msg->dn); + + msg->num_elements = in->object.attribute_ctr.num_attributes; + msg->elements = talloc_array(msg, struct ldb_message_element, + msg->num_elements); + W_ERROR_HAVE_NO_MEMORY(msg->elements); + + md = talloc(mem_ctx, struct replPropertyMetaDataBlob); + W_ERROR_HAVE_NO_MEMORY(md); + + md->version = 1; + md->reserved = 0; + md->ctr.ctr1.count = in->meta_data_ctr->count; + md->ctr.ctr1.reserved = 0; + md->ctr.ctr1.array = talloc_array(mem_ctx, + struct replPropertyMetaData1, + md->ctr.ctr1.count); + W_ERROR_HAVE_NO_MEMORY(md->ctr.ctr1.array); + + for (i=0, attr_count=0; i < in->meta_data_ctr->count; i++, attr_count++) { + struct drsuapi_DsReplicaAttribute *a; + struct drsuapi_DsReplicaMetaData *d; + struct replPropertyMetaData1 *m; + struct ldb_message_element *e; + uint32_t j; + + a = &in->object.attribute_ctr.attributes[i]; + d = &in->meta_data_ctr->meta_data[i]; + m = &md->ctr.ctr1.array[attr_count]; + e = &msg->elements[attr_count]; + + if (dsdb_attid_in_list(ignore_attids, a->attid)) { + attr_count--; + continue; + } + + if (GUID_all_zero(&d->originating_invocation_id)) { + status = WERR_DS_SRC_GUID_MISMATCH; + DEBUG(0, ("Refusing replication of object containing invalid zero invocationID on attribute %d of %s: %s\n", + a->attid, + ldb_dn_get_linearized(msg->dn), + win_errstr(status))); + return status; + } + + if (a->attid == DRSUAPI_ATTID_instanceType) { + if (instanceType_e != NULL) { + return WERR_FOOBAR; + } + instanceType_e = e; + } + + for (j=0; j<a->value_ctr.num_values; j++) { + status = drsuapi_decrypt_attribute(a->value_ctr.values[j].blob, + gensec_skey, rid, + dsdb_repl_flags, a); + if (!W_ERROR_IS_OK(status)) { + break; + } + } + if (W_ERROR_EQUAL(status, WERR_TOO_MANY_SECRETS)) { + WERROR get_name_status = dsdb_attribute_drsuapi_to_ldb(ldb, schema, pfm_remote, + a, msg->elements, e, NULL); + if (W_ERROR_IS_OK(get_name_status)) { + DEBUG(0, ("Unxpectedly got secret value %s on %s from DRS server\n", + e->name, ldb_dn_get_linearized(msg->dn))); + } else { + DEBUG(0, ("Unxpectedly got secret value on %s from DRS server\n", + ldb_dn_get_linearized(msg->dn))); + } + } else if (!W_ERROR_IS_OK(status)) { + return status; + } + + /* + * This function also fills in the local attid value, + * based on comparing the remote and local prefixMap + * tables. If we don't convert the value, then we can + * have invalid values in the replPropertyMetaData we + * store on disk, as the prefixMap is per host, not + * per-domain. This may be why Microsoft added the + * msDS-IntID feature, however this is not used for + * extra attributes in the schema partition itself. + */ + status = dsdb_attribute_drsuapi_to_ldb(ldb, schema, pfm_remote, + a, msg->elements, e, + &m->attid); + W_ERROR_NOT_OK_RETURN(status); + + m->version = d->version; + m->originating_change_time = d->originating_change_time; + m->originating_invocation_id = d->originating_invocation_id; + m->originating_usn = d->originating_usn; + m->local_usn = 0; + + if (a->attid == DRSUAPI_ATTID_name) { + const struct ldb_val *rdn_val = ldb_dn_get_rdn_val(msg->dn); + if (rdn_val == NULL) { + DEBUG(0, ("Unxpectedly unable to get RDN from %s for validation\n", + ldb_dn_get_linearized(msg->dn))); + return WERR_FOOBAR; + } + if (e->num_values != 1) { + DEBUG(0, ("Unxpectedly got wrong number of attribute values (got %u, expected 1) when checking RDN against name of %s\n", + e->num_values, + ldb_dn_get_linearized(msg->dn))); + return WERR_FOOBAR; + } + if (data_blob_cmp(rdn_val, + &e->values[0]) != 0) { + DEBUG(0, ("Unxpectedly got mismatching RDN values when checking RDN against name of %s\n", + ldb_dn_get_linearized(msg->dn))); + return WERR_FOOBAR; + } + } + if (d->originating_change_time > whenChanged) { + whenChanged = d->originating_change_time; + } + + } + + msg->num_elements = attr_count; + md->ctr.ctr1.count = attr_count; + + if (instanceType_e == NULL) { + return WERR_FOOBAR; + } + + instanceType = ldb_msg_find_attr_as_int(msg, "instanceType", 0); + + if ((instanceType & INSTANCE_TYPE_IS_NC_HEAD) + && partition_dn != NULL) { + int partition_dn_cmp = ldb_dn_compare(partition_dn, msg->dn); + if (partition_dn_cmp != 0) { + DEBUG(4, ("Remote server advised us of a new partition %s while processing %s, ignoring\n", + ldb_dn_get_linearized(msg->dn), + ldb_dn_get_linearized(partition_dn))); + return WERR_DS_ADD_REPLICA_INHIBITED; + } + } + + if (dsdb_repl_flags & DSDB_REPL_FLAG_PARTIAL_REPLICA) { + /* the instanceType type for partial_replica + replication is sent via DRS with TYPE_WRITE set, but + must be used on the client with TYPE_WRITE removed + */ + if (instanceType & INSTANCE_TYPE_WRITE) { + /* + * Make sure we do not change the order + * of msg->elements! + * + * That's why we use + * instanceType_e->num_values = 0 + * instead of + * ldb_msg_remove_attr(msg, "instanceType"); + */ + struct ldb_message_element *e; + + e = ldb_msg_find_element(msg, "instanceType"); + if (e != instanceType_e) { + DEBUG(0,("instanceType_e[%p] changed to e[%p]\n", + instanceType_e, e)); + return WERR_FOOBAR; + } + + instanceType_e->num_values = 0; + + instanceType &= ~INSTANCE_TYPE_WRITE; + if (ldb_msg_add_fmt(msg, "instanceType", "%d", instanceType) != LDB_SUCCESS) { + return WERR_INTERNAL_ERROR; + } + } + } else { + if (!(instanceType & INSTANCE_TYPE_WRITE)) { + DBG_ERR("Refusing to replicate %s from a read-only " + "replica into a read-write replica!\n", + ldb_dn_get_linearized(msg->dn)); + return WERR_DS_DRA_SOURCE_IS_PARTIAL_REPLICA; + } + } + + whenChanged_t = nt_time_to_unix(whenChanged); + whenChanged_s = ldb_timestring(msg, whenChanged_t); + W_ERROR_HAVE_NO_MEMORY(whenChanged_s); + + out->object_guid = in->object.identifier->guid; + + if (in->parent_object_guid == NULL) { + out->parent_guid = NULL; + } else { + out->parent_guid = talloc(mem_ctx, struct GUID); + W_ERROR_HAVE_NO_MEMORY(out->parent_guid); + *out->parent_guid = *in->parent_object_guid; + } + + out->msg = msg; + out->when_changed = whenChanged_s; + out->meta_data = md; + return WERR_OK; +} + +WERROR dsdb_replicated_objects_convert(struct ldb_context *ldb, + const struct dsdb_schema *schema, + struct ldb_dn *partition_dn, + const struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr, + uint32_t object_count, + const struct drsuapi_DsReplicaObjectListItemEx *first_object, + uint32_t linked_attributes_count, + const struct drsuapi_DsReplicaLinkedAttribute *linked_attributes, + const struct repsFromTo1 *source_dsa, + const struct drsuapi_DsReplicaCursor2CtrEx *uptodateness_vector, + const DATA_BLOB *gensec_skey, + uint32_t dsdb_repl_flags, + TALLOC_CTX *mem_ctx, + struct dsdb_extended_replicated_objects **objects) +{ + WERROR status; + struct dsdb_schema_prefixmap *pfm_remote; + struct dsdb_extended_replicated_objects *out; + const struct drsuapi_DsReplicaObjectListItemEx *cur; + struct dsdb_syntax_ctx syntax_ctx; + uint32_t i; + + out = talloc_zero(mem_ctx, struct dsdb_extended_replicated_objects); + W_ERROR_HAVE_NO_MEMORY(out); + out->version = DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION; + out->dsdb_repl_flags = dsdb_repl_flags; + + /* + * Ensure schema is kept valid for as long as 'out' + * which may contain pointers to it + */ + schema = talloc_reference(out, schema); + W_ERROR_HAVE_NO_MEMORY(schema); + + status = dsdb_schema_pfm_from_drsuapi_pfm(mapping_ctr, true, + out, &pfm_remote, NULL); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0,(__location__ ": Failed to decode remote prefixMap: %s\n", + win_errstr(status))); + talloc_free(out); + return status; + } + + /* use default syntax conversion context */ + dsdb_syntax_ctx_init(&syntax_ctx, ldb, schema); + syntax_ctx.pfm_remote = pfm_remote; + + if (ldb_dn_compare(partition_dn, ldb_get_schema_basedn(ldb)) != 0) { + /* + * check for schema changes in case + * we are not replicating Schema NC + */ + status = dsdb_schema_info_cmp(schema, mapping_ctr); + if (!W_ERROR_IS_OK(status)) { + DEBUG(4,("Can't replicate %s because remote schema has changed since we last replicated the schema\n", + ldb_dn_get_linearized(partition_dn))); + talloc_free(out); + return status; + } + } + + out->partition_dn = partition_dn; + + out->source_dsa = source_dsa; + out->uptodateness_vector= uptodateness_vector; + + out->num_objects = 0; + out->objects = talloc_array(out, + struct dsdb_extended_replicated_object, + object_count); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(out->objects, out); + + for (i=0, cur = first_object; cur; cur = cur->next_object, i++) { + if (i == object_count) { + talloc_free(out); + return WERR_FOOBAR; + } + + status = dsdb_convert_object_ex(ldb, schema, out->partition_dn, + pfm_remote, + cur, gensec_skey, + NULL, + dsdb_repl_flags, + out->objects, + &out->objects[out->num_objects]); + + /* + * Check to see if we have been advised of a + * subdomain or new application partition. We don't + * want to start on that here, instead the caller + * should consider if it would like to replicate it + * based on the cross-ref object. + */ + if (W_ERROR_EQUAL(status, WERR_DS_ADD_REPLICA_INHIBITED)) { + struct GUID_txt_buf guid_str; + DBG_ERR("Ignoring object outside partition %s %s: %s\n", + GUID_buf_string(&cur->object.identifier->guid, + &guid_str), + cur->object.identifier->dn, + win_errstr(status)); + continue; + } + + if (!W_ERROR_IS_OK(status)) { + talloc_free(out); + DEBUG(0,("Failed to convert object %s: %s\n", + cur->object.identifier->dn, + win_errstr(status))); + return status; + } + + /* Assuming we didn't skip or error, increment the number of objects */ + out->num_objects++; + } + + DBG_INFO("Processed %"PRIu32" DRS objects, saw %"PRIu32" objects " + "and expected %"PRIu32" objects\n", + out->num_objects, i, object_count); + + out->objects = talloc_realloc(out, out->objects, + struct dsdb_extended_replicated_object, + out->num_objects); + if (out->num_objects != 0 && out->objects == NULL) { + DBG_ERR("FAILURE: talloc_realloc() failed after " + "processing %"PRIu32" DRS objects!\n", + out->num_objects); + talloc_free(out); + return WERR_FOOBAR; + } + if (i != object_count) { + DBG_ERR("FAILURE: saw %"PRIu32" DRS objects, server said we " + "should expect to see %"PRIu32" objects!\n", + i, object_count); + talloc_free(out); + return WERR_FOOBAR; + } + + out->linked_attributes = talloc_array(out, + struct drsuapi_DsReplicaLinkedAttribute, + linked_attributes_count); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(out->linked_attributes, out); + + for (i=0; i < linked_attributes_count; i++) { + const struct drsuapi_DsReplicaLinkedAttribute *ra = &linked_attributes[i]; + struct drsuapi_DsReplicaLinkedAttribute *la = &out->linked_attributes[i]; + + if (ra->identifier == NULL) { + talloc_free(out); + return WERR_BAD_NET_RESP; + } + + *la = *ra; + + la->identifier = talloc_zero(out->linked_attributes, + struct drsuapi_DsReplicaObjectIdentifier); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(la->identifier, out); + + /* + * We typically only get the guid filled + * and the repl_meta_data module only cares abouf + * the guid. + */ + la->identifier->guid = ra->identifier->guid; + + if (ra->value.blob != NULL) { + la->value.blob = talloc_zero(out->linked_attributes, + DATA_BLOB); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(la->value.blob, out); + + if (ra->value.blob->length != 0) { + *la->value.blob = data_blob_dup_talloc(la->value.blob, + *ra->value.blob); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(la->value.blob->data, out); + } + } + + status = dsdb_attribute_drsuapi_remote_to_local(&syntax_ctx, + ra->attid, + &la->attid, + NULL); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0,(__location__": linked_attribute[%u] attid 0x%08X not found: %s\n", + i, ra->attid, win_errstr(status))); + return status; + } + } + + out->linked_attributes_count = linked_attributes_count; + + /* free pfm_remote, we won't need it anymore */ + talloc_free(pfm_remote); + + *objects = out; + return WERR_OK; +} + +/** + * Commits a list of replicated objects. + * + * @param working_schema dsdb_schema to be used for resolving + * Classes/Attributes during Schema replication. If not NULL, + * it will be set on ldb and used while committing replicated objects + */ +WERROR dsdb_replicated_objects_commit(struct ldb_context *ldb, + struct dsdb_schema *working_schema, + struct dsdb_extended_replicated_objects *objects, + uint64_t *notify_uSN) +{ + WERROR werr; + struct ldb_result *ext_res; + struct dsdb_schema *cur_schema = NULL; + struct dsdb_schema *new_schema = NULL; + int ret; + uint64_t seq_num1, seq_num2; + bool used_global_schema = false; + + TALLOC_CTX *tmp_ctx = talloc_new(objects); + if (!tmp_ctx) { + DEBUG(0,("Failed to start talloc\n")); + return WERR_NOT_ENOUGH_MEMORY; + } + + /* wrap the extended operation in a transaction + See [MS-DRSR] 3.3.2 Transactions + */ + ret = ldb_transaction_start(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ " Failed to start transaction: %s\n", + ldb_errstring(ldb))); + return WERR_FOOBAR; + } + + ret = dsdb_load_partition_usn(ldb, objects->partition_dn, &seq_num1, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ " Failed to load partition uSN\n")); + ldb_transaction_cancel(ldb); + TALLOC_FREE(tmp_ctx); + return WERR_FOOBAR; + } + + /* + * Set working_schema for ldb in case we are replicating from Schema NC. + * Schema won't be reloaded during Replicated Objects commit, as it is + * done in a transaction. So we need some way to search for newly + * added Classes and Attributes + */ + if (working_schema) { + /* store current schema so we can fall back in case of failure */ + cur_schema = dsdb_get_schema(ldb, tmp_ctx); + used_global_schema = dsdb_uses_global_schema(ldb); + + ret = dsdb_reference_schema(ldb, working_schema, SCHEMA_MEMORY_ONLY); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ "Failed to reference working schema - %s\n", + ldb_strerror(ret))); + /* TODO: Map LDB Error to NTSTATUS? */ + ldb_transaction_cancel(ldb); + TALLOC_FREE(tmp_ctx); + return WERR_INTERNAL_ERROR; + } + } + + ret = ldb_extended(ldb, DSDB_EXTENDED_REPLICATED_OBJECTS_OID, objects, &ext_res); + if (ret != LDB_SUCCESS) { + /* restore previous schema */ + if (used_global_schema) { + dsdb_set_global_schema(ldb); + } else if (cur_schema) { + dsdb_reference_schema(ldb, cur_schema, SCHEMA_MEMORY_ONLY); + } + + if (W_ERROR_EQUAL(objects->error, WERR_DS_DRA_RECYCLED_TARGET)) { + DEBUG(3,("Missing target while attempting to apply records: %s\n", + ldb_errstring(ldb))); + } else if (W_ERROR_EQUAL(objects->error, WERR_DS_DRA_MISSING_PARENT)) { + DEBUG(3,("Missing parent while attempting to apply records: %s\n", + ldb_errstring(ldb))); + } else { + DEBUG(1,("Failed to apply records: %s: %s\n", + ldb_errstring(ldb), ldb_strerror(ret))); + } + ldb_transaction_cancel(ldb); + TALLOC_FREE(tmp_ctx); + + if (!W_ERROR_IS_OK(objects->error)) { + return objects->error; + } + return WERR_FOOBAR; + } + talloc_free(ext_res); + + /* Save our updated prefixMap and check the schema is good. */ + if (working_schema) { + struct ldb_result *ext_res_2; + + werr = dsdb_write_prefixes_from_schema_to_ldb(working_schema, + ldb, + working_schema); + if (!W_ERROR_IS_OK(werr)) { + /* restore previous schema */ + if (used_global_schema) { + dsdb_set_global_schema(ldb); + } else if (cur_schema ) { + dsdb_reference_schema(ldb, + cur_schema, + SCHEMA_MEMORY_ONLY); + } + DEBUG(0,("Failed to save updated prefixMap: %s\n", + win_errstr(werr))); + ldb_transaction_cancel(ldb); + TALLOC_FREE(tmp_ctx); + return werr; + } + + /* + * Use dsdb_schema_from_db through dsdb extended to check we + * can load the schema currently sitting in the transaction. + * We need this check because someone might have written to + * the schema or prefixMap before we started the transaction, + * which may have caused corruption. + */ + ret = ldb_extended(ldb, DSDB_EXTENDED_SCHEMA_LOAD, + NULL, &ext_res_2); + + if (ret != LDB_SUCCESS) { + if (used_global_schema) { + dsdb_set_global_schema(ldb); + } else if (cur_schema) { + dsdb_reference_schema(ldb, cur_schema, SCHEMA_MEMORY_ONLY); + } + DEBUG(0,("Corrupt schema write attempt detected, " + "aborting schema modification operation.\n" + "This probably happened due to bad timing of " + "another schema edit: %s (%s)\n", + ldb_errstring(ldb), + ldb_strerror(ret))); + ldb_transaction_cancel(ldb); + TALLOC_FREE(tmp_ctx); + return WERR_FOOBAR; + } + } + + ret = ldb_transaction_prepare_commit(ldb); + if (ret != LDB_SUCCESS) { + /* restore previous schema */ + if (used_global_schema) { + dsdb_set_global_schema(ldb); + } else if (cur_schema ) { + dsdb_reference_schema(ldb, cur_schema, SCHEMA_MEMORY_ONLY); + } + DBG_ERR(" Failed to prepare commit of transaction: %s (%s)\n", + ldb_errstring(ldb), + ldb_strerror(ret)); + TALLOC_FREE(tmp_ctx); + return WERR_FOOBAR; + } + + ret = dsdb_load_partition_usn(ldb, objects->partition_dn, &seq_num2, NULL); + if (ret != LDB_SUCCESS) { + /* restore previous schema */ + if (used_global_schema) { + dsdb_set_global_schema(ldb); + } else if (cur_schema ) { + dsdb_reference_schema(ldb, cur_schema, SCHEMA_MEMORY_ONLY); + } + DEBUG(0,(__location__ " Failed to load partition uSN\n")); + ldb_transaction_cancel(ldb); + TALLOC_FREE(tmp_ctx); + return WERR_FOOBAR; + } + + ret = ldb_transaction_commit(ldb); + if (ret != LDB_SUCCESS) { + /* restore previous schema */ + if (used_global_schema) { + dsdb_set_global_schema(ldb); + } else if (cur_schema ) { + dsdb_reference_schema(ldb, cur_schema, SCHEMA_MEMORY_ONLY); + } + DEBUG(0,(__location__ " Failed to commit transaction\n")); + TALLOC_FREE(tmp_ctx); + return WERR_FOOBAR; + } + + if (seq_num1 > *notify_uSN) { + /* + * A notify was already required before + * the current transaction. + */ + } else if (objects->originating_updates) { + /* + * Applying the replicated changes + * required originating updates, + * so a notify is required. + */ + } else { + /* + * There's no need to notify the + * server about the change we just from it. + */ + *notify_uSN = seq_num2; + } + + /* + * Reset the Schema used by ldb. This will lead to + * a schema cache being refreshed from database. + */ + if (working_schema) { + /* Reload the schema */ + new_schema = dsdb_get_schema(ldb, tmp_ctx); + /* TODO: + * If dsdb_get_schema() fails, we just fall back + * to what we had. However, the database is probably + * unable to operate for other users from this + * point... */ + if (new_schema == NULL || new_schema == working_schema) { + DBG_ERR("Failed to re-load schema after commit of " + "transaction (working: %p/%"PRIu64", new: " + "%p/%"PRIu64")\n", new_schema, + new_schema != NULL ? + new_schema->metadata_usn : 0, + working_schema, working_schema->metadata_usn); + dsdb_reference_schema(ldb, cur_schema, SCHEMA_MEMORY_ONLY); + if (used_global_schema) { + dsdb_set_global_schema(ldb); + } + TALLOC_FREE(tmp_ctx); + return WERR_INTERNAL_ERROR; + } else if (used_global_schema) { + dsdb_make_schema_global(ldb, new_schema); + } + } + + DEBUG(2,("Replicated %u objects (%u linked attributes) for %s\n", + objects->num_objects, objects->linked_attributes_count, + ldb_dn_get_linearized(objects->partition_dn))); + + TALLOC_FREE(tmp_ctx); + return WERR_OK; +} + +static WERROR dsdb_origin_object_convert(struct ldb_context *ldb, + const struct dsdb_schema *schema, + const struct drsuapi_DsReplicaObjectListItem *in, + TALLOC_CTX *mem_ctx, + struct ldb_message **_msg) +{ + WERROR status; + unsigned int i; + struct ldb_message *msg; + + if (!in->object.identifier) { + return WERR_FOOBAR; + } + + if (!in->object.identifier->dn || !in->object.identifier->dn[0]) { + return WERR_FOOBAR; + } + + msg = ldb_msg_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(msg); + + msg->dn = ldb_dn_new(msg, ldb, in->object.identifier->dn); + W_ERROR_HAVE_NO_MEMORY(msg->dn); + + msg->num_elements = in->object.attribute_ctr.num_attributes; + msg->elements = talloc_array(msg, struct ldb_message_element, + msg->num_elements); + W_ERROR_HAVE_NO_MEMORY(msg->elements); + + for (i=0; i < msg->num_elements; i++) { + struct drsuapi_DsReplicaAttribute *a; + struct ldb_message_element *e; + + a = &in->object.attribute_ctr.attributes[i]; + e = &msg->elements[i]; + + status = dsdb_attribute_drsuapi_to_ldb(ldb, schema, schema->prefixmap, + a, msg->elements, e, NULL); + W_ERROR_NOT_OK_RETURN(status); + } + + + *_msg = msg; + + return WERR_OK; +} + +WERROR dsdb_origin_objects_commit(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const struct drsuapi_DsReplicaObjectListItem *first_object, + uint32_t *_num, + uint32_t dsdb_repl_flags, + struct drsuapi_DsReplicaObjectIdentifier2 **_ids) +{ + WERROR status; + const struct dsdb_schema *schema; + const struct drsuapi_DsReplicaObjectListItem *cur; + struct ldb_message **objects; + struct drsuapi_DsReplicaObjectIdentifier2 *ids; + uint32_t i; + uint32_t num_objects = 0; + const char * const attrs[] = { + "objectGUID", + "objectSid", + NULL + }; + struct ldb_result *res; + int ret; + + for (cur = first_object; cur; cur = cur->next_object) { + num_objects++; + } + + if (num_objects == 0) { + return WERR_OK; + } + + ret = ldb_transaction_start(ldb); + if (ret != LDB_SUCCESS) { + return WERR_DS_INTERNAL_FAILURE; + } + + objects = talloc_array(mem_ctx, struct ldb_message *, + num_objects); + if (objects == NULL) { + status = WERR_NOT_ENOUGH_MEMORY; + goto cancel; + } + + schema = dsdb_get_schema(ldb, objects); + if (!schema) { + return WERR_DS_SCHEMA_NOT_LOADED; + } + + for (i=0, cur = first_object; cur; cur = cur->next_object, i++) { + status = dsdb_origin_object_convert(ldb, schema, cur, + objects, &objects[i]); + if (!W_ERROR_IS_OK(status)) { + goto cancel; + } + } + + ids = talloc_array(mem_ctx, + struct drsuapi_DsReplicaObjectIdentifier2, + num_objects); + if (ids == NULL) { + status = WERR_NOT_ENOUGH_MEMORY; + goto cancel; + } + + if (dsdb_repl_flags & DSDB_REPL_FLAG_ADD_NCNAME) { + /* check for possible NC creation */ + for (i=0; i < num_objects; i++) { + struct ldb_message *msg = objects[i]; + struct ldb_message_element *el; + struct ldb_dn *nc_dn; + + if (ldb_msg_check_string_attribute(msg, "objectClass", "crossRef") == 0) { + continue; + } + el = ldb_msg_find_element(msg, "nCName"); + if (el == NULL || el->num_values != 1) { + continue; + } + nc_dn = ldb_dn_from_ldb_val(objects, ldb, &el->values[0]); + if (!ldb_dn_validate(nc_dn)) { + continue; + } + ret = dsdb_create_partial_replica_NC(ldb, nc_dn); + if (ret != LDB_SUCCESS) { + status = WERR_DS_INTERNAL_FAILURE; + goto cancel; + } + } + } + + for (i=0; i < num_objects; i++) { + struct dom_sid *sid = NULL; + struct ldb_request *add_req; + + DEBUG(6,(__location__ ": adding %s\n", + ldb_dn_get_linearized(objects[i]->dn))); + + ret = ldb_build_add_req(&add_req, + ldb, + objects, + objects[i], + NULL, + NULL, + ldb_op_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + status = WERR_DS_INTERNAL_FAILURE; + goto cancel; + } + + ret = ldb_request_add_control(add_req, LDB_CONTROL_RELAX_OID, true, NULL); + if (ret != LDB_SUCCESS) { + status = WERR_DS_INTERNAL_FAILURE; + goto cancel; + } + + ret = ldb_request(ldb, add_req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(add_req->handle, LDB_WAIT_ALL); + } + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed add of %s - %s\n", + ldb_dn_get_linearized(objects[i]->dn), ldb_errstring(ldb))); + status = WERR_DS_INTERNAL_FAILURE; + goto cancel; + } + + talloc_free(add_req); + + ret = ldb_search(ldb, objects, &res, objects[i]->dn, + LDB_SCOPE_BASE, attrs, + "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + status = WERR_DS_INTERNAL_FAILURE; + goto cancel; + } + ids[i].guid = samdb_result_guid(res->msgs[0], "objectGUID"); + sid = samdb_result_dom_sid(objects, res->msgs[0], "objectSid"); + if (sid) { + ids[i].sid = *sid; + } else { + ZERO_STRUCT(ids[i].sid); + } + } + + ret = ldb_transaction_commit(ldb); + if (ret != LDB_SUCCESS) { + return WERR_DS_INTERNAL_FAILURE; + } + + talloc_free(objects); + + *_num = num_objects; + *_ids = ids; + return WERR_OK; + +cancel: + talloc_free(objects); + ldb_transaction_cancel(ldb); + return status; +} |