s4-drs: Dump exact error when failure occurs during DsReplicaUpdateRefs call
[samba.git] / source4 / rpc_server / drsuapi / updaterefs.c
index 2090dd5dd504ee8f311009d71759708028bed598..8efdfbbdbbaf4d7a8a1814d2f86c729f2d260a2e 100644 (file)
 */
 
 #include "includes.h"
-#include "librpc/gen_ndr/ndr_drsuapi.h"
 #include "rpc_server/dcerpc_server.h"
-#include "rpc_server/common/common.h"
 #include "dsdb/samdb/samdb.h"
-#include "lib/ldb/include/ldb_errors.h"
-#include "param/param.h"
-#include "librpc/gen_ndr/ndr_drsblobs.h"
-#include "auth/auth.h"
 #include "rpc_server/drsuapi/dcesrv_drsuapi.h"
+#include "libcli/security/security.h"
+#include "auth/session.h"
 
 struct repsTo {
        uint32_t count;
        struct repsFromToBlob *r;
 };
 
-/*
-  load the repsTo structure for a given partition GUID
- */
-static WERROR uref_loadreps(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, struct GUID *guid,
-                           struct repsTo *reps)
-{
-       struct ldb_dn *dn;
-       const char *attrs[] = { "repsTo", NULL };
-       struct ldb_result *res;
-       TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
-       int i;
-       struct ldb_message_element *el;
-
-       if (dsdb_find_dn_by_guid(sam_ctx, tmp_ctx, GUID_string(tmp_ctx, guid), &dn) != LDB_SUCCESS) {
-               DEBUG(0,("drsuapi_addref: failed to find partition with GUID %s\n",
-                        GUID_string(tmp_ctx, guid)));
-               talloc_free(tmp_ctx);
-               return WERR_DS_DRA_BAD_NC;
-       }
-
-       /* TODO: possibly check in the rootDSE to see that this DN is
-        * one of our partition roots */         
-
-       if (ldb_search(sam_ctx, tmp_ctx, &res, dn, LDB_SCOPE_BASE, attrs, NULL) != LDB_SUCCESS) {
-               DEBUG(0,("drsuapi_addref: failed to read partition object\n"));
-               talloc_free(tmp_ctx);
-               return WERR_DS_DRA_INTERNAL_ERROR;
-       }
-
-       ZERO_STRUCTP(reps);
-
-       el = ldb_msg_find_element(res->msgs[0], "repsTo");
-       if (el == NULL) {
-               talloc_free(tmp_ctx);
-               return WERR_OK;
-       }
-
-       reps->count = el->num_values;
-       reps->r = talloc_array(mem_ctx, struct repsFromToBlob, reps->count);
-       if (reps->r == NULL) {
-               talloc_free(tmp_ctx);
-               return WERR_DS_DRA_INTERNAL_ERROR;
-       }
-
-       for (i=0; i<reps->count; i++) {
-               enum ndr_err_code ndr_err;
-               ndr_err = ndr_pull_struct_blob(&el->values[i], 
-                                              mem_ctx, lp_iconv_convenience(ldb_get_opaque(sam_ctx, "loadparm")),
-                                              &reps->r[i], 
-                                              (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
-               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-                       talloc_free(tmp_ctx);
-                       return WERR_DS_DRA_INTERNAL_ERROR;
-               }
-       }
-
-       talloc_free(tmp_ctx);
-       
-       return WERR_OK;
-}
-
-/*
-  save the repsTo structure for a given partition GUID
- */
-static WERROR uref_savereps(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, struct GUID *guid,
-                           struct repsTo *reps)
-{
-       struct ldb_dn *dn;
-       TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
-       struct ldb_message *msg;
-       struct ldb_message_element *el;
-       int i;
-
-       if (dsdb_find_dn_by_guid(sam_ctx, tmp_ctx, GUID_string(tmp_ctx, guid), &dn) != LDB_SUCCESS) {
-               DEBUG(0,("drsuapi_addref: failed to find partition with GUID %s\n",
-                        GUID_string(tmp_ctx, guid)));
-               talloc_free(tmp_ctx);
-               return WERR_DS_DRA_BAD_NC;
-       }
-
-       msg = ldb_msg_new(tmp_ctx);
-       msg->dn = dn;
-       if (ldb_msg_add_empty(msg, "repsTo", LDB_FLAG_MOD_REPLACE, &el) != LDB_SUCCESS) {
-               goto failed;
-       }
-
-       el->values = talloc_array(msg, struct ldb_val, reps->count);
-       if (!el->values) {
-               goto failed;
-       }
-
-       for (i=0; i<reps->count; i++) {
-               struct ldb_val v;
-               enum ndr_err_code ndr_err;
-
-               ndr_err = ndr_push_struct_blob(&v, tmp_ctx, lp_iconv_convenience(ldb_get_opaque(sam_ctx, "loadparm")),
-                                              &reps->r[i], 
-                                              (ndr_push_flags_fn_t)ndr_push_repsFromToBlob);
-               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-                       goto failed;
-               }
-
-               el->num_values++;
-               el->values[i] = v;
-       }
-
-       if (ldb_modify(sam_ctx, msg) != LDB_SUCCESS) {
-               DEBUG(0,("Failed to store repsTo - %s\n", ldb_errstring(sam_ctx)));
-               goto failed;
-       }
-
-       talloc_free(tmp_ctx);
-       
-       return WERR_OK;
-
-failed:
-       talloc_free(tmp_ctx);
-       return WERR_DS_DRA_INTERNAL_ERROR;
-}
-
 /*
   add a replication destination for a given partition GUID
  */
 static WERROR uref_add_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, 
-                           struct GUID *guid, struct repsFromTo1 *dest)
+                           struct ldb_dn *dn, struct repsFromTo1 *dest, 
+                           uint32_t options)
 {
        struct repsTo reps;
        WERROR werr;
+       unsigned int i;
 
-       werr = uref_loadreps(sam_ctx, mem_ctx, guid, &reps);
+       werr = dsdb_loadreps(sam_ctx, mem_ctx, dn, "repsTo", &reps.r, &reps.count);
        if (!W_ERROR_IS_OK(werr)) {
                return werr;
        }
 
+       for (i=0; i<reps.count; i++) {
+               if (GUID_compare(&dest->source_dsa_obj_guid, 
+                                &reps.r[i].ctr.ctr1.source_dsa_obj_guid) == 0) {
+                       if (options & DRSUAPI_DRS_GETCHG_CHECK) {
+                               return WERR_OK;
+                       } else {
+                               return WERR_DS_DRA_REF_ALREADY_EXISTS;
+                       }
+               }
+       }
+
        reps.r = talloc_realloc(mem_ctx, reps.r, struct repsFromToBlob, reps.count+1);
        if (reps.r == NULL) {
                return WERR_DS_DRA_INTERNAL_ERROR;
@@ -178,7 +67,7 @@ static WERROR uref_add_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
        reps.r[reps.count].ctr.ctr1 = *dest;
        reps.count++;
 
-       werr = uref_savereps(sam_ctx, mem_ctx, guid, &reps);
+       werr = dsdb_savereps(sam_ctx, mem_ctx, dn, "repsTo", reps.r, reps.count);
        if (!W_ERROR_IS_OK(werr)) {
                return werr;
        }
@@ -190,13 +79,15 @@ static WERROR uref_add_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
   delete a replication destination for a given partition GUID
  */
 static WERROR uref_del_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, 
-                           struct GUID *guid, struct GUID *dest_guid)
+                           struct ldb_dn *dn, struct GUID *dest_guid, 
+                           uint32_t options)
 {
        struct repsTo reps;
        WERROR werr;
-       int i;
+       unsigned int i;
+       bool found = false;
 
-       werr = uref_loadreps(sam_ctx, mem_ctx, guid, &reps);
+       werr = dsdb_loadreps(sam_ctx, mem_ctx, dn, "repsTo", &reps.r, &reps.count);
        if (!W_ERROR_IS_OK(werr)) {
                return werr;
        }
@@ -207,60 +98,60 @@ static WERROR uref_del_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
                                memmove(&reps.r[i], &reps.r[i+1], sizeof(reps.r[i])*(reps.count-(i+1)));
                        }
                        reps.count--;
+                       found = true;
                }
        }
 
-       werr = uref_savereps(sam_ctx, mem_ctx, guid, &reps);
+       werr = dsdb_savereps(sam_ctx, mem_ctx, dn, "repsTo", reps.r, reps.count);
        if (!W_ERROR_IS_OK(werr)) {
                return werr;
        }
 
+       if (!found &&
+           !(options & DRSUAPI_DRS_GETCHG_CHECK) &&
+           !(options & DRSUAPI_DRS_ADD_REF)) {
+               return WERR_DS_DRA_REF_NOT_FOUND;
+       }
+
        return WERR_OK; 
 }
 
 /* 
-  drsuapi_DsReplicaUpdateRefs
+  drsuapi_DsReplicaUpdateRefs - a non RPC version callable from getncchanges
 */
-WERROR dcesrv_drsuapi_DsReplicaUpdateRefs(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
-                                         struct drsuapi_DsReplicaUpdateRefs *r)
+WERROR drsuapi_UpdateRefs(struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx,
+                         struct drsuapi_DsReplicaUpdateRefsRequest1 *req)
 {
-       struct drsuapi_DsReplicaUpdateRefsRequest1 *req;
-       struct ldb_context *sam_ctx;
        WERROR werr;
+       struct ldb_dn *dn;
 
-       if (r->in.level != 1) {
-               DEBUG(0,("DrReplicUpdateRefs - unsupported level %u\n", r->in.level));
-               return WERR_DS_DRA_INVALID_PARAMETER;
-       }
-
-       req = &r->in.req.req1;
-       DEBUG(4,("DrReplicUpdateRefs for host '%s' with GUID %s options 0x%08x nc=%s\n",
+       DEBUG(4,("DsReplicaUpdateRefs for host '%s' with GUID %s options 0x%08x nc=%s\n",
                 req->dest_dsa_dns_name, GUID_string(mem_ctx, &req->dest_dsa_guid),
                 req->options,
                 drs_ObjectIdentifier_to_string(mem_ctx, req->naming_context)));
 
-       /* TODO: We need to authenticate this operation pretty carefully */
-       sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, 
-                               system_session(mem_ctx, dce_call->conn->dce_ctx->lp_ctx));
-       if (!sam_ctx) {
-               return WERR_DS_DRA_INTERNAL_ERROR;              
+       dn = ldb_dn_new(mem_ctx, b_state->sam_ctx, req->naming_context->dn);
+       if (dn == NULL) {
+               return WERR_DS_INVALID_DN_SYNTAX;
        }
 
-       if (ldb_transaction_start(sam_ctx) != LDB_SUCCESS) {
-               DEBUG(0,(__location__ ": Failed to start transaction on samdb\n"));
+       if (ldb_transaction_start(b_state->sam_ctx) != LDB_SUCCESS) {
+               DEBUG(0,(__location__ ": Failed to start transaction on samdb: %s\n",
+                        ldb_errstring(b_state->sam_ctx)));
                return WERR_DS_DRA_INTERNAL_ERROR;              
        }
 
-       if (req->options & DRSUAPI_DS_REPLICA_UPDATE_DELETE_REFERENCE) {
-               werr = uref_del_dest(sam_ctx, mem_ctx, &req->naming_context->guid, &req->dest_dsa_guid);
+       if (req->options & DRSUAPI_DRS_DEL_REF) {
+               werr = uref_del_dest(b_state->sam_ctx, mem_ctx, dn, &req->dest_dsa_guid, req->options);
                if (!W_ERROR_IS_OK(werr)) {
-                       DEBUG(0,("Failed to delete repsTo for %s\n",
-                                GUID_string(dce_call, &req->dest_dsa_guid)));
+                       DEBUG(0,("Failed to delete repsTo for %s: %s\n",
+                                GUID_string(mem_ctx, &req->dest_dsa_guid),
+                                win_errstr(werr)));
                        goto failed;
                }
        }
 
-       if (req->options & DRSUAPI_DS_REPLICA_UPDATE_ADD_REFERENCE) {
+       if (req->options & DRSUAPI_DRS_ADD_REF) {
                struct repsFromTo1 dest;
                struct repsFromTo1OtherInfo oi;
                
@@ -272,25 +163,71 @@ WERROR dcesrv_drsuapi_DsReplicaUpdateRefs(struct dcesrv_call_state *dce_call, TA
                dest.source_dsa_obj_guid = req->dest_dsa_guid;
                dest.replica_flags       = req->options;
 
-               werr = uref_add_dest(sam_ctx, mem_ctx, &req->naming_context->guid, &dest);
+               werr = uref_add_dest(b_state->sam_ctx, mem_ctx, dn, &dest, req->options);
                if (!W_ERROR_IS_OK(werr)) {
-                       DEBUG(0,("Failed to delete repsTo for %s\n",
-                                GUID_string(dce_call, &dest.source_dsa_obj_guid)));
+                       DEBUG(0,("Failed to add repsTo for %s: %s\n",
+                                GUID_string(mem_ctx, &dest.source_dsa_obj_guid),
+                                win_errstr(werr)));
                        goto failed;
                }
        }
 
-       if (ldb_transaction_commit(sam_ctx) != LDB_SUCCESS) {
-               DEBUG(0,(__location__ ": Failed to commit transaction on samdb\n"));
+       if (ldb_transaction_commit(b_state->sam_ctx) != LDB_SUCCESS) {
+               DEBUG(0,(__location__ ": Failed to commit transaction on samdb: %s\n",
+                        ldb_errstring(b_state->sam_ctx)));
                return WERR_DS_DRA_INTERNAL_ERROR;              
        }
 
-       talloc_free(sam_ctx);
        return WERR_OK;
 
 failed:
-       ldb_transaction_cancel(sam_ctx);
-       talloc_free(sam_ctx);
+       ldb_transaction_cancel(b_state->sam_ctx);
        return werr;
 }
 
+/* 
+  drsuapi_DsReplicaUpdateRefs
+*/
+WERROR dcesrv_drsuapi_DsReplicaUpdateRefs(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
+                                         struct drsuapi_DsReplicaUpdateRefs *r)
+{
+       struct dcesrv_handle *h;
+       struct drsuapi_bind_state *b_state;
+       struct drsuapi_DsReplicaUpdateRefsRequest1 *req;
+       WERROR werr;
+       int ret;
+       enum security_user_level security_level;
+
+       DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE);
+       b_state = h->data;
+
+       werr = drs_security_level_check(dce_call, "DsReplicaUpdateRefs", SECURITY_RO_DOMAIN_CONTROLLER,
+                                       samdb_domain_sid(b_state->sam_ctx));
+       if (!W_ERROR_IS_OK(werr)) {
+               return werr;
+       }
+
+       if (r->in.level != 1) {
+               DEBUG(0,("DrReplicUpdateRefs - unsupported level %u\n", r->in.level));
+               return WERR_DS_DRA_INVALID_PARAMETER;
+       }
+
+       req = &r->in.req.req1;
+
+       security_level = security_session_user_level(dce_call->conn->auth_state.session_info, NULL);
+       if (security_level < SECURITY_ADMINISTRATOR) {
+               /* check that they are using an DSA objectGUID that they own */
+               ret = dsdb_validate_dsa_guid(b_state->sam_ctx,
+                                            &req->dest_dsa_guid,
+                                            &dce_call->conn->auth_state.session_info->security_token->sids[PRIMARY_USER_SID_INDEX]);
+               if (ret != LDB_SUCCESS) {
+                       DEBUG(0,(__location__ ": Refusing DsReplicaUpdateRefs for sid %s with GUID %s\n",
+                                dom_sid_string(mem_ctx,
+                                               &dce_call->conn->auth_state.session_info->security_token->sids[PRIMARY_USER_SID_INDEX]),
+                                GUID_string(mem_ctx, &req->dest_dsa_guid)));
+                       return WERR_DS_DRA_ACCESS_DENIED;
+               }
+       }
+
+       return drsuapi_UpdateRefs(b_state, mem_ctx, req);
+}