s4:dsdb/repl_meta_data: call dsdb_module_schedule_sd_propagation() for replicated...
[metze/samba/wip.git] / source4 / dsdb / samdb / ldb_modules / repl_meta_data.c
index 930696a26a48bfe1cc2b3c9de7ed29467bfc19c7..4dcb8d505bebb72122524de55e93168d05353979 100644 (file)
@@ -661,13 +661,6 @@ static int replmd_ldb_message_element_attid_sort(const struct ldb_message_elemen
        a1 = dsdb_attribute_by_lDAPDisplayName(schema, e1->name);
        a2 = dsdb_attribute_by_lDAPDisplayName(schema, e2->name);
 
-       /*
-        * TODO: remove this check, we should rely on e1 and e2 having valid attribute names
-        *       in the schema
-        */
-       if (!a1 || !a2) {
-               return strcasecmp(e1->name, e2->name);
-       }
        if (a1->attributeID_id == a2->attributeID_id) {
                return 0;
        }
@@ -2313,12 +2306,28 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
        bool is_urgent = false, rodc = false;
        unsigned int functional_level;
        const DATA_BLOB *guid_blob;
+       struct ldb_control *sd_propagation_control;
 
        /* do not manipulate our control entries */
        if (ldb_dn_is_special(req->op.mod.message->dn)) {
                return ldb_next_request(module, req);
        }
 
+       sd_propagation_control = ldb_request_get_control(req,
+                                       DSDB_CONTROL_SEC_DESC_PROPAGATION_OID);
+       if (sd_propagation_control != NULL) {
+               if (req->op.mod.message->num_elements != 1) {
+                       return ldb_module_operr(module);
+               }
+               ret = strcmp(req->op.mod.message->elements[0].name,
+                            "nTSecurityDescriptor");
+               if (ret != 0) {
+                       return ldb_module_operr(module);
+               }
+
+               return ldb_next_request(module, req);
+       }
+
        ldb = ldb_module_get_ctx(module);
 
        ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_modify\n");
@@ -2987,7 +2996,7 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                        talloc_free(tmp_ctx);
                        return ret;
                }
-               msg->elements[el_count++].flags = LDB_FLAG_MOD_ADD;
+               msg->elements[el_count++].flags = LDB_FLAG_MOD_REPLACE;
        }
 
        switch (next_deletion_state){
@@ -3333,6 +3342,7 @@ static int replmd_op_name_modify_callback(struct ldb_request *req, struct ldb_re
 {
        struct replmd_replicated_request *ar =
                talloc_get_type_abort(req->context, struct replmd_replicated_request);
+       struct ldb_dn *conflict_dn;
        int ret;
 
        if (ares->error != LDB_SUCCESS) {
@@ -3340,8 +3350,19 @@ static int replmd_op_name_modify_callback(struct ldb_request *req, struct ldb_re
                return replmd_op_callback(req, ares);
        }
 
+       switch (req->operation) {
+       case LDB_ADD:
+               conflict_dn = req->op.add.message->dn;
+               break;
+       case LDB_MODIFY:
+               conflict_dn = req->op.mod.message->dn;
+               break;
+       default:
+               smb_panic("replmd_op_name_modify_callback called in unknown circumstances");
+       }
+
        /* perform a modify of the rDN and name of the record */
-       ret = replmd_name_modify(ar, req, req->op.add.message->dn);
+       ret = replmd_name_modify(ar, req, conflict_dn);
        if (ret != LDB_SUCCESS) {
                ares->error = ret;
                return replmd_op_callback(req, ares);
@@ -3391,36 +3412,42 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
        struct ldb_result *res;
        const char *attrs[] = { "replPropertyMetaData", "objectGUID", NULL };
        int ret;
-       const struct ldb_val *rmd_value, *omd_value;
-       struct replPropertyMetaDataBlob omd, rmd;
+       const struct ldb_val *omd_value;
+       struct replPropertyMetaDataBlob omd, *rmd;
        enum ndr_err_code ndr_err;
        bool rename_incoming_record, rodc;
        struct replPropertyMetaData1 *rmd_name, *omd_name;
+       struct ldb_message *msg;
+
+       req->callback = callback;
 
        if (ares->error != LDB_ERR_ENTRY_ALREADY_EXISTS) {
                /* call the normal callback for everything except
                   conflicts */
-               return callback(req, ares);
+               return ldb_module_done(req, ares->controls, ares->response, ares->error);
        }
 
        ret = samdb_rodc(ldb_module_get_ctx(ar->module), &rodc);
        if (ret != LDB_SUCCESS) {
-               return ret;
+               ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to determine if we are an RODC when attempting to form conflict DN: %s", ldb_errstring(ldb_module_get_ctx(ar->module)));
+               return ldb_module_done(req, ares->controls, ares->response, LDB_ERR_OPERATIONS_ERROR);
        }
-       /*
+       /*
         * we have a conflict, and need to decide if we will keep the
         * new record or the old record
         */
 
+       msg = ar->objs->objects[ar->index_current].msg;
+
        switch (req->operation) {
        case LDB_ADD:
-               conflict_dn = req->op.add.message->dn;
+               conflict_dn = msg->dn;
                break;
        case LDB_RENAME:
                conflict_dn = req->op.rename.newdn;
                break;
        default:
-               return ldb_module_operr(ar->module);
+               return ldb_module_done(req, ares->controls, ares->response, ldb_module_operr(ar->module));
        }
 
        if (rodc) {
@@ -3467,28 +3494,11 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                goto failed;
        }
 
-       /*
-        * and the replPropertyMetaData attribute from the
-        * new record
-        */
-       rmd_value = ldb_msg_find_ldb_val(req->op.add.message, "replPropertyMetaData");
-       if (rmd_value == NULL) {
-               DEBUG(0,(__location__ ": Unable to find replPropertyMetaData for new record '%s'\n",
-                        ldb_dn_get_linearized(conflict_dn)));
-               goto failed;
-       }
-
-       ndr_err = ndr_pull_struct_blob(rmd_value, req, &rmd,
-                                      (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
-       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-               DEBUG(0,(__location__ ": Failed to parse new replPropertyMetaData for %s\n",
-                        ldb_dn_get_linearized(conflict_dn)));
-               goto failed;
-       }
+       rmd = ar->objs->objects[ar->index_current].meta_data;
 
        /* we decide which is newer based on the RPMD on the name
           attribute.  See [MS-DRSR] ResolveNameConflict */
-       rmd_name = replmd_replPropertyMetaData1_find_attid(&rmd, DRSUAPI_ATTID_name);
+       rmd_name = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
        omd_name = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
        if (!rmd_name || !omd_name) {
                DEBUG(0,(__location__ ": Failed to find name attribute in replPropertyMetaData for %s\n",
@@ -3504,14 +3514,24 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                struct ldb_dn *new_dn;
                struct ldb_message *new_msg;
 
+               /*
+                * We want to run the original callback here, which
+                * will return LDB_ERR_ENTRY_ALREADY_EXISTS to the
+                * caller, which will in turn know to rename the
+                * incoming record.  The error string is set in case
+                * this isn't handled properly at some point in the
+                * future.
+                */
                if (req->operation == LDB_RENAME) {
-                       DEBUG(0,(__location__ ": Unable to handle incoming renames where this would "
-                                "create a conflict. Incoming record is %s\n",
-                                ldb_dn_get_extended_linearized(req, conflict_dn, 1)));
+                       ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                              "Unable to handle incoming renames where this would "
+                                              "create a conflict. Incoming record is %s (caller to handle)\n",
+                                              ldb_dn_get_extended_linearized(req, conflict_dn, 1));
+
                        goto failed;
                }
 
-               guid = samdb_result_guid(req->op.add.message, "objectGUID");
+               guid = samdb_result_guid(msg, "objectGUID");
                if (GUID_all_zero(&guid)) {
                        DEBUG(0,(__location__ ": Failed to find objectGUID for conflicting incoming record %s\n",
                                 ldb_dn_get_linearized(conflict_dn)));
@@ -3527,27 +3547,17 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                DEBUG(1,(__location__ ": Resolving conflict record via incoming rename '%s' -> '%s'\n",
                         ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));
 
-               switch (req->operation) {
-               case LDB_ADD:
-                       /* re-submit the request, but with a different
-                          callback, so we don't loop forever. */
-                       new_msg = ldb_msg_copy_shallow(req, req->op.add.message);
-                       if (!new_msg) {
-                               goto failed;
-                               DEBUG(0,(__location__ ": Failed to copy conflict DN message for %s\n",
-                                        ldb_dn_get_linearized(conflict_dn)));
-                       }
-                       new_msg->dn = new_dn;
-                       req->op.add.message = new_msg;
-                       req->callback = replmd_op_name_modify_callback;
-                       break;
-               case LDB_RENAME:
-                       req->op.rename.newdn = new_dn;
-                       req->callback = callback;
-                       break;
-               default:
-                       return ldb_module_operr(ar->module);
+               /* re-submit the request, but with a different
+                  callback, so we don't loop forever. */
+               new_msg = ldb_msg_copy_shallow(req, msg);
+               if (!new_msg) {
+                       goto failed;
+                       DEBUG(0,(__location__ ": Failed to copy conflict DN message for %s\n",
+                                ldb_dn_get_linearized(conflict_dn)));
                }
+               new_msg->dn = new_dn;
+               req->op.add.message = new_msg;
+               req->callback = replmd_op_name_modify_callback;
 
                return ldb_next_request(ar->module, req);
        } else {
@@ -3591,8 +3601,6 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                        goto failed;
                }
 
-               req->callback = callback;
-
                return ldb_next_request(ar->module, req);
        }
 
@@ -3601,7 +3609,7 @@ failed:
         * will stop with an error, but there is not much else we can
         * do
         */
-       return callback(req, ares);
+       return ldb_module_done(req, ares->controls, ares->response, ares->error);
 }
 
 /*
@@ -3646,6 +3654,7 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
        struct ldb_val md_value;
        unsigned int i;
        int ret;
+       bool remote_isDeleted = false;
 
        /*
         * TODO: check if the parent object exist
@@ -3699,6 +3708,9 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                }
        }
 
+       remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
+                                                    "isDeleted", false);
+
        /*
         * the meta data array is already sorted by the caller
         */
@@ -3718,6 +3730,14 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
 
        replmd_ldb_message_sort(msg, ar->schema);
 
+       if (!remote_isDeleted) {
+               ret = dsdb_module_schedule_sd_propagation(ar->module,
+                                                         msg->dn, true);
+               if (ret != LDB_SUCCESS) {
+                       return replmd_replicated_request_error(ar, ret);
+               }
+       }
+
        if (DEBUGLVL(4)) {
                char *s = ldb_ldif_message_string(ldb, ar, LDB_CHANGETYPE_ADD, msg);
                DEBUG(4, ("DRS replication add message:\n%s\n", s));
@@ -3894,8 +3914,10 @@ static int replmd_replicated_apply_search_for_parent(struct replmd_replicated_re
                                   ar->req);
        LDB_REQ_SET_LOCATION(search_req);
 
-       ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID,
-                                     true, NULL);
+       ret = dsdb_request_add_controls(search_req, 
+                                       DSDB_SEARCH_SHOW_RECYCLED|
+                                       DSDB_SEARCH_SHOW_DELETED|
+                                       DSDB_SEARCH_SHOW_EXTENDED_DN);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -4001,9 +4023,16 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
        uint32_t j,ni=0;
        unsigned int removed_attrs = 0;
        int ret;
+       int (*callback)(struct ldb_request *req, struct ldb_reply *ares) = replmd_op_callback;
+       bool isDeleted = false;
+       bool local_isDeleted = false;
+       bool remote_isDeleted = false;
+       bool take_remote_isDeleted = false;
+       bool sd_updated = false;
 
        ldb = ldb_module_get_ctx(ar->module);
        msg = ar->objs->objects[ar->index_current].msg;
+
        rmd = ar->objs->objects[ar->index_current].meta_data;
        ZERO_STRUCT(omd);
        omd.version = 1;
@@ -4023,9 +4052,52 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                }
        }
 
+       local_isDeleted = ldb_msg_find_attr_as_bool(ar->search_msg,
+                                                   "isDeleted", false);
+       remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
+                                                    "isDeleted", false);
+
        /* handle renames that come in over DRS */
        ret = replmd_replicated_handle_rename(ar, msg, rmd, &omd, ar->req);
-       if (ret != LDB_SUCCESS) {
+
+       /*
+        * This particular error code means that we already tried the
+        * conflict algrorithm, and the existing record name was newer, so we
+        * need to rename the incoming record
+        */
+       if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+               struct GUID guid;
+               NTSTATUS status;
+               struct ldb_dn *new_dn;
+               status = GUID_from_ndr_blob(&ar->objs->objects[ar->index_current].guid_value, &guid);
+               /* This really, really can't fail */
+               SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+               new_dn = replmd_conflict_dn(msg, msg->dn, &guid);
+               if (new_dn == NULL) {
+                       ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                                                 "Failed to form conflict DN for %s\n",
+                                                                 ldb_dn_get_linearized(msg->dn));
+
+                       return replmd_replicated_request_werror(ar, WERR_NOMEM);
+               }
+
+               ret = dsdb_module_rename(ar->module, ar->search_msg->dn, new_dn,
+                                        DSDB_FLAG_NEXT_MODULE, ar->req);
+               if (ret != LDB_SUCCESS) {
+                       ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                              "Failed to rename incoming conflicting dn '%s' (was '%s') to '%s' - %s\n",
+                                              ldb_dn_get_linearized(msg->dn),
+                                              ldb_dn_get_linearized(ar->search_msg->dn),
+                                              ldb_dn_get_linearized(new_dn),
+                                              ldb_errstring(ldb_module_get_ctx(ar->module)));
+                       return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
+               }
+
+               /* Set the callback to one that will fix up the name to be a conflict DN */
+               callback = replmd_op_name_modify_callback;
+               msg->dn = new_dn;
+       } else if (ret != LDB_SUCCESS) {
                ldb_debug(ldb, LDB_DEBUG_FATAL,
                          "replmd_replicated_request rename %s => %s failed - %s\n",
                          ldb_dn_get_linearized(ar->search_msg->dn),
@@ -4082,6 +4154,16 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                                        }
                                }
                                nmd.ctr.ctr1.array[j].local_usn = ar->seq_num;
+                               switch (nmd.ctr.ctr1.array[j].attid) {
+                               case DRSUAPI_ATTID_ntSecurityDescriptor:
+                                       sd_updated = true;
+                                       break;
+                               case DRSUAPI_ATTID_isDeleted:
+                                       take_remote_isDeleted = true;
+                                       break;
+                               default:
+                                       break;
+                               }
                                found = true;
                                break;
                        }
@@ -4111,6 +4193,16 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                        }
                }
                nmd.ctr.ctr1.array[ni].local_usn = ar->seq_num;
+               switch (nmd.ctr.ctr1.array[ni].attid) {
+               case DRSUAPI_ATTID_ntSecurityDescriptor:
+                       sd_updated = true;
+                       break;
+               case DRSUAPI_ATTID_isDeleted:
+                       take_remote_isDeleted = true;
+                       break;
+               default:
+                       break;
+               }
                ni++;
        }
 
@@ -4145,6 +4237,20 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
        ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: replace %u attributes\n",
                  ar->index_current, msg->num_elements);
 
+       if (take_remote_isDeleted) {
+               isDeleted = remote_isDeleted;
+       } else {
+               isDeleted = local_isDeleted;
+       }
+
+       if (sd_updated && !isDeleted) {
+               ret = dsdb_module_schedule_sd_propagation(ar->module,
+                                                         msg->dn, true);
+               if (ret != LDB_SUCCESS) {
+                       return ldb_operr(ldb);
+               }
+       }
+
        /* create the meta data value */
        ndr_err = ndr_push_struct_blob(&nmd_value, msg, &nmd,
                                       (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
@@ -4189,7 +4295,7 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                                msg,
                                ar->controls,
                                ar,
-                               replmd_op_callback,
+                               callback,
                                ar->req);
        LDB_REQ_SET_LOCATION(change_req);
        if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);