dsdb: fix double-free in replication failure case on RODC
[amitay/samba.git] / source4 / dsdb / samdb / ldb_modules / repl_meta_data.c
index f201ce3b89447eedba11903ef85e1f7680e00dbe..0418c048568de834ee0f8cc64961e09e5877fdb9 100644 (file)
@@ -258,7 +258,7 @@ static int replmd_process_backlink(struct ldb_module *module, struct la_backlink
                /* we allow LDB_ERR_NO_SUCH_ATTRIBUTE as success to
                   cope with possible corruption where the backlink has
                   already been removed */
-               DEBUG(0,("WARNING: backlink from %s already removed from %s - %s\n",
+               DEBUG(3,("WARNING: backlink from %s already removed from %s - %s\n",
                         ldb_dn_get_linearized(target_dn),
                         ldb_dn_get_linearized(source_dn),
                         ldb_errstring(ldb)));
@@ -395,7 +395,7 @@ static int replmd_op_callback(struct ldb_request *req, struct ldb_reply *ares)
        }
 
        if (ares->error != LDB_SUCCESS) {
-               DEBUG(0,("%s failure. Error is: %s\n", __FUNCTION__, ldb_strerror(ares->error)));
+               DEBUG(5,("%s failure. Error is: %s\n", __FUNCTION__, ldb_strerror(ares->error)));
                return ldb_module_done(ac->req, controls,
                                        ares->response, ares->error);
        }
@@ -1068,7 +1068,13 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb,
 
        /* if the attribute's value haven't changed then return LDB_SUCCESS     */
        if (old_el != NULL && ldb_msg_element_compare(el, old_el) == 0) {
-               return LDB_SUCCESS;
+               if (!ldb_request_get_control(req, LDB_CONTROL_PROVISION_OID)) {
+                       /*
+                        * allow this to make it possible for dbcheck
+                        * to rebuild broken metadata
+                        */
+                       return LDB_SUCCESS;
+               }
        }
 
        for (i=0; i<omd->ctr.ctr1.count; i++) {
@@ -1156,7 +1162,7 @@ static int replmd_update_rpmd(struct ldb_module *module,
        int ret;
        const char * const *attrs = NULL;
        const char * const attrs1[] = { "replPropertyMetaData", "*", NULL };
-       const char * const attrs2[] = { "uSNChanged", "objectClass", NULL };
+       const char * const attrs2[] = { "uSNChanged", "objectClass", "instanceType", NULL };
        struct ldb_result *res;
        struct ldb_context *ldb;
        struct ldb_message_element *objectclass_el;
@@ -1327,6 +1333,8 @@ static int replmd_update_rpmd(struct ldb_module *module,
 
                /*if we are RODC and this is a DRSR update then its ok*/
                if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+                       unsigned instanceType;
+
                        ret = samdb_rodc(ldb, &rodc);
                        if (ret != LDB_SUCCESS) {
                                DEBUG(4, (__location__ ": unable to tell if we are an RODC\n"));
@@ -1334,6 +1342,12 @@ static int replmd_update_rpmd(struct ldb_module *module,
                                ldb_asprintf_errstring(ldb, "RODC modify is forbidden\n");
                                return LDB_ERR_REFERRAL;
                        }
+
+                       instanceType = ldb_msg_find_attr_as_uint(res->msgs[0], "instanceType", INSTANCE_TYPE_WRITE);
+                       if (!(instanceType & INSTANCE_TYPE_WRITE)) {
+                               return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM,
+                                                "cannot change replicated attribute on partial replica");
+                       }
                }
 
                md_value = talloc(msg, struct ldb_val);
@@ -1451,6 +1465,11 @@ static int get_parsed_dns(struct ldb_module *module, TALLOC_CTX *mem_ctx,
                        if (ret != LDB_SUCCESS) {
                                ldb_asprintf_errstring(ldb, "Unable to find GUID for DN %s\n",
                                                       ldb_dn_get_linearized(dn));
+                               if (ret == LDB_ERR_NO_SUCH_OBJECT &&
+                                   LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE &&
+                                   ldb_attr_cmp(el->name, "member") == 0) {
+                                       return LDB_ERR_UNWILLING_TO_PERFORM;
+                               }
                                return ret;
                        }
                        ret = dsdb_set_extended_dn_guid(dn, p->guid, "GUID");
@@ -1768,7 +1787,13 @@ static int replmd_modify_la_add(struct ldb_module *module,
                                ldb_asprintf_errstring(ldb, "Attribute %s already exists for target GUID %s",
                                                       el->name, GUID_string(tmp_ctx, p->guid));
                                talloc_free(tmp_ctx);
-                               return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+                               /* error codes for 'member' need to be
+                                  special cased */
+                               if (ldb_attr_cmp(el->name, "member") == 0) {
+                                       return LDB_ERR_ENTRY_ALREADY_EXISTS;
+                               } else {
+                                       return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+                               }
                        }
                        ret = replmd_update_la_val(old_el->values, p->v, dns[i].dsdb_dn, p->dsdb_dn,
                                                   invocation_id, seq_num, seq_num, now, 0, false);
@@ -1880,13 +1905,21 @@ static int replmd_modify_la_delete(struct ldb_module *module,
                if (!p2) {
                        ldb_asprintf_errstring(ldb, "Attribute %s doesn't exist for target GUID %s",
                                               el->name, GUID_string(tmp_ctx, p->guid));
-                       return LDB_ERR_NO_SUCH_ATTRIBUTE;
+                       if (ldb_attr_cmp(el->name, "member") == 0) {
+                               return LDB_ERR_UNWILLING_TO_PERFORM;
+                       } else {
+                               return LDB_ERR_NO_SUCH_ATTRIBUTE;
+                       }
                }
                rmd_flags = dsdb_dn_rmd_flags(p2->dsdb_dn->dn);
                if (rmd_flags & DSDB_RMD_FLAG_DELETED) {
                        ldb_asprintf_errstring(ldb, "Attribute %s already deleted for target GUID %s",
                                               el->name, GUID_string(tmp_ctx, p->guid));
-                       return LDB_ERR_NO_SUCH_ATTRIBUTE;
+                       if (ldb_attr_cmp(el->name, "member") == 0) {
+                               return LDB_ERR_UNWILLING_TO_PERFORM;
+                       } else {
+                               return LDB_ERR_NO_SUCH_ATTRIBUTE;
+                       }
                }
        }
 
@@ -2395,7 +2428,7 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
        const struct dsdb_attribute *rdn_attr;
        const char *rdn_name;
        const struct ldb_val *rdn_val;
-       const char *attrs[4] = { NULL, };
+       const char *attrs[5] = { NULL, };
        time_t t = time(NULL);
        int ret;
        bool is_urgent = false;
@@ -2508,8 +2541,9 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
         */
        attrs[0] = "replPropertyMetaData";
        attrs[1] = "objectClass";
-       attrs[2] = rdn_name;
-       attrs[3] = NULL;
+       attrs[2] = "instanceType";
+       attrs[3] = rdn_name;
+       attrs[4] = NULL;
 
        ret = replmd_update_rpmd(ac->module, ac->schema, req, attrs,
                                 msg, &ac->seq_num, t, &is_urgent);
@@ -2526,7 +2560,7 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
                                           lpcfg_dnsdomain(lp_ctx),
                                           ldb_dn_get_linearized(olddn));
                ret = ldb_module_send_referral(req, referral);
-               talloc_free(ac);
+               talloc_free(ares);
                return ldb_module_done(req, NULL, NULL, ret);
        }
 
@@ -2699,6 +2733,7 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                                                OBJECT_TOMBSTONE=4, OBJECT_REMOVED=5 };
        enum deletion_state deletion_state, next_deletion_state;
        bool enabled;
+       int functional_level;
 
        if (ldb_dn_is_special(req->op.del.dn)) {
                return ldb_next_request(module, req);
@@ -2715,6 +2750,8 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
+       functional_level = dsdb_functional_level(ldb);
+
        old_dn = ldb_dn_copy(tmp_ctx, req->op.del.dn);
 
        /* we need the complete msg off disk, so we can work out which
@@ -2915,7 +2952,7 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
 
                /* we also mark it as recycled, meaning this object can't be
                   recovered (we are stripping its attributes) */
-               if (dsdb_functional_level(ldb) >= DS_DOMAIN_FUNCTION_2008_R2) {
+               if (functional_level >= DS_DOMAIN_FUNCTION_2008_R2) {
                        ret = ldb_msg_add_string(msg, "isRecycled", "TRUE");
                        if (ret != LDB_SUCCESS) {
                                DEBUG(0,(__location__ ": Failed to add isRecycled string to the msg\n"));
@@ -2939,12 +2976,24 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                                /* don't remove the rDN */
                                continue;
                        }
-                       if (sa->linkID && sa->linkID & 1) {
+                       if (sa->linkID && (sa->linkID & 1)) {
+                               /*
+                                 we have a backlink in this object
+                                 that needs to be removed. We're not
+                                 allowed to remove it directly
+                                 however, so we instead setup a
+                                 modify to delete the corresponding
+                                 forward link
+                                */
                                ret = replmd_delete_remove_link(module, schema, old_dn, el, sa, req);
                                if (ret != LDB_SUCCESS) {
                                        talloc_free(tmp_ctx);
                                        return LDB_ERR_OPERATIONS_ERROR;
                                }
+                               /* now we continue, which means we
+                                  won't remove this backlink
+                                  directly
+                               */
                                continue;
                        }
                        if (!sa->linkID && ldb_attr_in_list(preserved_attrs, el->name)) {
@@ -3509,6 +3558,15 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                                      false, NULL);
        if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
 
+       if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PARTIAL_REPLICA) {
+               /* this tells the partition module to make it a
+                  partial replica if creating an NC */
+               ret = ldb_request_add_control(change_req,
+                                             DSDB_CONTROL_PARTIAL_REPLICA,
+                                             false, NULL);
+               if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+       }
+
        return ldb_next_request(ar->module, change_req);
 }
 
@@ -3632,8 +3690,18 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                                continue;
                        }
 
-                       cmp = replmd_replPropertyMetaData1_is_newer(&nmd.ctr.ctr1.array[j],
-                                                                   &rmd->ctr.ctr1.array[i]);
+                       if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING) {
+                               /* if we compare equal then do an
+                                  update. This is used when a client
+                                  asks for a FULL_SYNC, and can be
+                                  used to recover a corrupt
+                                  replica */
+                               cmp = !replmd_replPropertyMetaData1_is_newer(&rmd->ctr.ctr1.array[i],
+                                                                            &nmd.ctr.ctr1.array[j]);
+                       } else {
+                               cmp = replmd_replPropertyMetaData1_is_newer(&nmd.ctr.ctr1.array[j],
+                                                                           &rmd->ctr.ctr1.array[i]);
+                       }
                        if (cmp) {
                                /* replace the entry */
                                nmd.ctr.ctr1.array[j] = rmd->ctr.ctr1.array[i];
@@ -3932,6 +4000,15 @@ static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *a
 
        unix_to_nt_time(&now, t);
 
+       if (ar->search_msg == NULL) {
+               /* this happens for a REPL_OBJ call where we are
+                  creating the target object by replicating it. The
+                  subdomain join code does this for the partition DN
+               */
+               DEBUG(4,(__location__ ": Skipping UDV and repsFrom update as no target DN\n"));
+               return ldb_module_done(ar->req, NULL, NULL, LDB_SUCCESS);
+       }
+
        instanceType = ldb_msg_find_attr_as_uint(ar->search_msg, "instanceType", 0);
        if (! (instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
                DEBUG(4,(__location__ ": Skipping UDV and repsFrom update as not NC root: %s\n",
@@ -4163,7 +4240,7 @@ static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *a
         */
        nrf_el->flags = LDB_FLAG_MOD_REPLACE;
 
-       if (DEBUGLVL(4)) {
+       if (CHECK_DEBUGLVL(4)) {
                char *s = ldb_ldif_message_string(ldb, ar, LDB_CHANGETYPE_MODIFY, msg);
                DEBUG(4, ("DRS replication uptodate modify message:\n%s\n", s));
                talloc_free(s);
@@ -4211,11 +4288,7 @@ static int replmd_replicated_uptodate_search_callback(struct ldb_request *req,
                break;
 
        case LDB_REPLY_DONE:
-               if (ar->search_msg == NULL) {
-                       ret = replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
-               } else {
-                       ret = replmd_replicated_uptodate_modify(ar);
-               }
+               ret = replmd_replicated_uptodate_modify(ar);
                if (ret != LDB_SUCCESS) {
                        return ldb_module_done(ar->req, NULL, NULL, ret);
                }
@@ -4270,6 +4343,7 @@ static int replmd_extended_replicated_objects(struct ldb_module *module, struct
        uint32_t i;
        struct replmd_private *replmd_private =
                talloc_get_type(ldb_module_get_private(module), struct replmd_private);
+       struct dsdb_control_replicated_update *rep_update;
 
        ldb = ldb_module_get_ctx(module);
 
@@ -4310,8 +4384,15 @@ static int replmd_extended_replicated_objects(struct ldb_module *module, struct
                if (!req->controls) return replmd_replicated_request_werror(ar, WERR_NOMEM);
        }
 
-       /* This allows layers further down to know if a change came in over replication */
-       ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, NULL);
+       /* This allows layers further down to know if a change came in
+          over replication and what the replication flags were */
+       rep_update = talloc_zero(ar, struct dsdb_control_replicated_update);
+       if (rep_update == NULL) {
+               return ldb_module_oom(module);
+       }
+       rep_update->dsdb_repl_flags = objs->dsdb_repl_flags;
+
+       ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, rep_update);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -4510,7 +4591,7 @@ linked_attributes[0]:
           old DN value */
        ret = dsdb_module_dn_by_guid(module, dsdb_dn, &guid, &dsdb_dn->dn, parent);
        if (ret != LDB_SUCCESS) {
-               DEBUG(2,(__location__ ": WARNING: Failed to re-resolve GUID %s - using %s",
+               DEBUG(2,(__location__ ": WARNING: Failed to re-resolve GUID %s - using %s\n",
                         GUID_string(tmp_ctx, &guid),
                         ldb_dn_get_linearized(dsdb_dn->dn)));
        }