s4-drs: When comparing DN, check the DN itself and its extra bits if present
[mat/samba.git] / source4 / dsdb / samdb / ldb_modules / repl_meta_data.c
index 91a5d9233c5a359f1569a8b4a05c29780b033bc3..ad703286eecf69008e032e1a4fe773480e6651d1 100644 (file)
@@ -660,7 +660,15 @@ static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMeta
                                                   const struct replPropertyMetaData1 *m2,
                                                   const uint32_t *rdn_attid)
 {
-       if (m1->attid == m2->attid) {
+       /*
+        * This assignment seems inoccous, but it is critical for the
+        * system, as we need to do the comparisons as a unsigned
+        * quantity, not signed (enums are signed integers)
+        */
+       uint32_t attid_1 = m1->attid;
+       uint32_t attid_2 = m2->attid;
+
+       if (attid_1 == attid_2) {
                return 0;
        }
 
@@ -669,7 +677,7 @@ static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMeta
         * so we need to return a value greater than zero
         * which means m1 is greater than m2
         */
-       if (m1->attid == *rdn_attid) {
+       if (attid_1 == *rdn_attid) {
                return 1;
        }
 
@@ -678,38 +686,78 @@ static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMeta
         * so we need to return a value less than zero
         * which means m2 is greater than m1
         */
-       if (m2->attid == *rdn_attid) {
+       if (attid_2 == *rdn_attid) {
                return -1;
        }
 
-       return m1->attid > m2->attid ? 1 : -1;
+       /*
+        * See above regarding this being an unsigned comparison.
+        * Otherwise when the high bit is set on non-standard
+        * attributes, they would end up first, before objectClass
+        * (0).
+        */
+       return attid_1 > attid_2 ? 1 : -1;
 }
 
-static int replmd_replPropertyMetaDataCtr1_sort(struct replPropertyMetaDataCtr1 *ctr1,
-                                               const struct dsdb_schema *schema,
-                                               struct ldb_dn *dn)
+static int replmd_replPropertyMetaDataCtr1_verify(struct ldb_context *ldb,
+                                                 struct replPropertyMetaDataCtr1 *ctr1,
+                                                 const struct dsdb_attribute *rdn_sa,
+                                                 struct ldb_dn *dn)
+{
+       if (ctr1->count == 0) {
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             "No elements found in replPropertyMetaData for %s!\n",
+                             ldb_dn_get_linearized(dn));
+               return LDB_ERR_CONSTRAINT_VIOLATION;
+       }
+       if (ctr1->array[ctr1->count - 1].attid != rdn_sa->attributeID_id) {
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             "No rDN found in replPropertyMetaData for %s!\n",
+                             ldb_dn_get_linearized(dn));
+               return LDB_ERR_CONSTRAINT_VIOLATION;
+       }
+
+       /* the objectClass attribute is value 0x00000000, so must be first */
+       if (ctr1->array[0].attid != DRSUAPI_ATTID_objectClass) {
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             "No objectClass found in replPropertyMetaData for %s!\n",
+                             ldb_dn_get_linearized(dn));
+               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       }
+
+       return LDB_SUCCESS;
+}
+
+static int replmd_replPropertyMetaDataCtr1_sort_and_verify(struct ldb_context *ldb,
+                                                          struct replPropertyMetaDataCtr1 *ctr1,
+                                                          const struct dsdb_schema *schema,
+                                                          struct ldb_dn *dn)
 {
        const char *rdn_name;
        const struct dsdb_attribute *rdn_sa;
 
        rdn_name = ldb_dn_get_rdn_name(dn);
        if (!rdn_name) {
-               DEBUG(0,(__location__ ": No rDN for %s?\n", ldb_dn_get_linearized(dn)));
-               return LDB_ERR_OPERATIONS_ERROR;
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             __location__ ": No rDN for %s?\n",
+                             ldb_dn_get_linearized(dn));
+               return LDB_ERR_INVALID_DN_SYNTAX;
        }
 
        rdn_sa = dsdb_attribute_by_lDAPDisplayName(schema, rdn_name);
        if (rdn_sa == NULL) {
-               DEBUG(0,(__location__ ": No sa found for rDN %s for %s\n", rdn_name, ldb_dn_get_linearized(dn)));
-               return LDB_ERR_OPERATIONS_ERROR;
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             __location__ ": No sa found for rDN %s for %s\n",
+                             rdn_name, ldb_dn_get_linearized(dn));
+               return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
        }
 
        DEBUG(6,("Sorting rpmd with attid exception %u rDN=%s DN=%s\n",
                 rdn_sa->attributeID_id, rdn_name, ldb_dn_get_linearized(dn)));
 
-       LDB_TYPESAFE_QSORT(ctr1->array, ctr1->count, &rdn_sa->attributeID_id, replmd_replPropertyMetaData1_attid_sort);
-
-       return LDB_SUCCESS;
+       LDB_TYPESAFE_QSORT(ctr1->array, ctr1->count, &rdn_sa->attributeID_id,
+                          replmd_replPropertyMetaData1_attid_sort);
+       return replmd_replPropertyMetaDataCtr1_verify(ldb, ctr1, rdn_sa, dn);
 }
 
 static int replmd_ldb_message_element_attid_sort(const struct ldb_message_element *e1,
@@ -1025,8 +1073,9 @@ static int replmd_add(struct ldb_module *module, struct ldb_request *req)
        /*
         * sort meta data array, and move the rdn attribute entry to the end
         */
-       ret = replmd_replPropertyMetaDataCtr1_sort(&nmd.ctr.ctr1, ac->schema, msg->dn);
+       ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, ac->schema, msg->dn);
        if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb, "%s: error during direct ADD: %s", __func__, ldb_errstring(ldb));
                talloc_free(ac);
                return ret;
        }
@@ -1080,7 +1129,17 @@ static int replmd_add(struct ldb_module *module, struct ldb_request *req)
         */
        replmd_ldb_message_sort(msg, ac->schema);
 
+       /*
+        * Assert that we do have an objectClass
+        */
        objectclass_el = ldb_msg_find_element(msg, "objectClass");
+       if (objectclass_el == NULL) {
+               ldb_asprintf_errstring(ldb, __location__
+                                      ": objectClass missing on %s\n",
+                                      ldb_dn_get_linearized(msg->dn));
+               talloc_free(ac);
+               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       }
        is_urgent = replmd_check_urgent_objectclass(objectclass_el,
                                                        REPL_URGENT_ON_CREATE);
 
@@ -1398,12 +1457,6 @@ static int replmd_update_rpmd(struct ldb_module *module,
                        return ret;
                }
 
-               objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
-               if (is_urgent && replmd_check_urgent_objectclass(objectclass_el,
-                                                               situation)) {
-                       *is_urgent = true;
-               }
-
                db_seq = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNChanged", 0);
                if (*seq_num <= db_seq) {
                        DEBUG(0,(__location__ ": changereplmetada control provided but max(local_usn)"\
@@ -1428,12 +1481,6 @@ static int replmd_update_rpmd(struct ldb_module *module,
                        return ret;
                }
 
-               objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
-               if (is_urgent && replmd_check_urgent_objectclass(objectclass_el,
-                                                               situation)) {
-                       *is_urgent = true;
-               }
-
                omd_value = ldb_msg_find_ldb_val(res->msgs[0], "replPropertyMetaData");
                if (!omd_value) {
                        DEBUG(0,(__location__ ": Object %s does not have a replPropertyMetaData attribute\n",
@@ -1464,12 +1511,33 @@ static int replmd_update_rpmd(struct ldb_module *module,
                                return ret;
                        }
 
-                       if (is_urgent && !*is_urgent && (situation == REPL_URGENT_ON_UPDATE)) {
+                       if (!*is_urgent && (situation == REPL_URGENT_ON_UPDATE)) {
                                *is_urgent = replmd_check_urgent_attribute(&msg->elements[i]);
                        }
 
                }
        }
+
+       /*
+        * Assert that we have an objectClass attribute - this is major
+        * corruption if we don't have this!
+        */
+       objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
+       if (objectclass_el != NULL) {
+               /*
+                * Now check if this objectClass means we need to do urgent replication
+                */
+               if (!*is_urgent && replmd_check_urgent_objectclass(objectclass_el,
+                                                                  situation)) {
+                       *is_urgent = true;
+               }
+       } else if (!ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+               ldb_asprintf_errstring(ldb, __location__
+                                      ": objectClass missing on %s\n",
+                                      ldb_dn_get_linearized(msg->dn));
+               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       }
+
        /*
         * replmd_update_rpmd_element has done an update if the
         * seq_num is set
@@ -1504,8 +1572,9 @@ static int replmd_update_rpmd(struct ldb_module *module,
                        return LDB_ERR_OPERATIONS_ERROR;
                }
 
-               ret = replmd_replPropertyMetaDataCtr1_sort(&omd.ctr.ctr1, schema, msg->dn);
+               ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &omd.ctr.ctr1, schema, msg->dn);
                if (ret != LDB_SUCCESS) {
+                       ldb_asprintf_errstring(ldb, "%s: %s", __func__, ldb_errstring(ldb));
                        return ret;
                }
 
@@ -1544,7 +1613,8 @@ static int parsed_dn_compare(struct parsed_dn *pdn1, struct parsed_dn *pdn2)
 
 static struct parsed_dn *parsed_dn_find(struct parsed_dn *pdn,
                                        unsigned int count, struct GUID *guid,
-                                       struct ldb_dn *dn)
+                                       struct ldb_dn *dn,
+                                       DATA_BLOB extra)
 {
        struct parsed_dn *ret;
        unsigned int i;
@@ -1560,6 +1630,18 @@ static struct parsed_dn *parsed_dn_find(struct parsed_dn *pdn,
                return NULL;
        }
        BINARY_ARRAY_SEARCH(pdn, count, guid, guid, GUID_compare, ret);
+       if (extra.length && ret) {
+               /*
+                * Check the content of the extra it's a match only
+                * if the dn match but also the extra.
+                */
+               if (ret->dsdb_dn->extra_part.length != extra.length) {
+                       return NULL;
+               }
+               if (memcmp(ret->dsdb_dn->extra_part.data, extra.data, extra.length) != 0) {
+                       return NULL;
+               }
+       }
        return ret;
 }
 
@@ -1910,7 +1992,9 @@ static int replmd_modify_la_add(struct ldb_module *module,
 
        /* for each new value, see if it exists already with the same GUID */
        for (i=0; i<el->num_values; i++) {
-               struct parsed_dn *p = parsed_dn_find(old_dns, old_num_values, dns[i].guid, NULL);
+               struct parsed_dn *p = parsed_dn_find(old_dns, old_num_values,
+                                                    dns[i].guid, NULL,
+                                                    dns[i].dsdb_dn->extra_part);
                if (p == NULL) {
                        /* this is a new linked attribute value */
                        new_values = talloc_realloc(tmp_ctx, new_values, struct ldb_val, num_new_values+1);
@@ -2049,7 +2133,8 @@ static int replmd_modify_la_delete(struct ldb_module *module,
                struct parsed_dn *p2;
                uint32_t rmd_flags;
 
-               p2 = parsed_dn_find(old_dns, old_el->num_values, p->guid, NULL);
+               p2 = parsed_dn_find(old_dns, old_el->num_values, p->guid, NULL,
+                                   p->dsdb_dn->extra_part);
                if (!p2) {
                        ldb_asprintf_errstring(ldb, "Attribute %s doesn't exist for target GUID %s",
                                               el->name, GUID_string(tmp_ctx, p->guid));
@@ -2078,7 +2163,9 @@ static int replmd_modify_la_delete(struct ldb_module *module,
                struct parsed_dn *p = &old_dns[i];
                uint32_t rmd_flags;
 
-               if (el->num_values && parsed_dn_find(dns, el->num_values, p->guid, NULL) == NULL) {
+               if (el->num_values && parsed_dn_find(dns, el->num_values,
+                                                    p->guid, NULL,
+                                                    p->dsdb_dn->extra_part) == NULL) {
                        continue;
                }
 
@@ -2181,7 +2268,8 @@ static int replmd_modify_la_replace(struct ldb_module *module,
                        return ret;
                }
 
-               p = parsed_dn_find(dns, el->num_values, old_p->guid, NULL);
+               p = parsed_dn_find(dns, el->num_values, old_p->guid, NULL,
+                                  old_p->dsdb_dn->extra_part);
                if (p) {
                        /* we don't delete it if we are re-adding it */
                        continue;
@@ -2203,7 +2291,8 @@ static int replmd_modify_la_replace(struct ldb_module *module,
 
                if (old_dns &&
                    (old_p = parsed_dn_find(old_dns,
-                                           old_num_values, p->guid, NULL)) != NULL) {
+                                           old_num_values, p->guid, NULL,
+                                           p->dsdb_dn->extra_part)) != NULL) {
                        /* update in place */
                        ret = replmd_update_la_val(old_el->values, old_p->v, p->dsdb_dn,
                                                   old_p->dsdb_dn, invocation_id,
@@ -2922,6 +3011,20 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request
                return ldb_next_request(module, req);
        }
 
+       /*
+        * We have to allow dbcheck to remove an object that
+        * is beyond repair, and to do so totally.  This could
+        * mean we we can get a partial object from the other
+        * DC, causing havoc, so dbcheck suggests
+        * re-replication first.  dbcheck sets both DBCHECK
+        * and RELAX in this situation.
+        */
+       if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)
+           && ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+               /* really, really remove it */
+               return ldb_next_request(module, req);
+       }
+
        tmp_ctx = talloc_new(ldb);
        if (!tmp_ctx) {
                ldb_oom(ldb);
@@ -2965,17 +3068,25 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request
        }
 
        if (next_deletion_state == OBJECT_REMOVED) {
-               struct auth_session_info *session_info =
-                               (struct auth_session_info *)ldb_get_opaque(ldb, "sessionInfo");
-               if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) {
-                       ldb_asprintf_errstring(ldb, "Refusing to delete deleted object %s",
-                                       ldb_dn_get_linearized(old_msg->dn));
-                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               /*
+                * We have to prevent objects being deleted, even if
+                * the administrator really wants them gone, as
+                * without the tombstone, we can get a partial object
+                * from the other DC, causing havoc.
+                *
+                * The only other valid case is when the 180 day
+                * timeout has expired, when relax is specified.
+                */
+               if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+                       /* it is already deleted - really remove it this time */
+                       talloc_free(tmp_ctx);
+                       return ldb_next_request(module, req);
                }
 
-               /* it is already deleted - really remove it this time */
-               talloc_free(tmp_ctx);
-               return ldb_next_request(module, req);
+               ldb_asprintf_errstring(ldb, "Refusing to delete tombstone object %s.  "
+                                      "This check is to prevent corruption of the replicated state.",
+                                      ldb_dn_get_linearized(old_msg->dn));
+               return LDB_ERR_UNWILLING_TO_PERFORM;
        }
 
        rdn_name = ldb_dn_get_rdn_name(old_dn);
@@ -3214,8 +3325,13 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request
                                */
                                continue;
                        }
-                       if (!sa->linkID && ldb_attr_in_list(preserved_attrs, el->name)) {
-                               continue;
+                       if (!sa->linkID) {
+                               if (ldb_attr_in_list(preserved_attrs, el->name)) {
+                                       continue;
+                               }
+                               if (sa->searchFlags & SEARCH_FLAG_PRESERVEONDELETE) {
+                                       continue;
+                               }
                        }
                        ret = ldb_msg_add_empty(msg, el->name, LDB_FLAG_MOD_DELETE, &el);
                        if (ret != LDB_SUCCESS) {
@@ -3826,6 +3942,8 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
        unsigned int i;
        int ret;
        bool remote_isDeleted = false;
+       const struct dsdb_attribute *rdn_sa;
+       const char *rdn_name;
 
        ldb = ldb_module_get_ctx(ar->module);
        msg = ar->objs->objects[ar->index_current].msg;
@@ -3861,6 +3979,13 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                struct ldb_message_element *el = &msg->elements[i];
 
                if (el->num_values == 0) {
+                       if (ldb_attr_cmp(msg->elements[i].name, "objectClass") == 0) {
+                               ldb_asprintf_errstring(ldb, __location__
+                                                      ": empty objectClass sent on %s, aborting replication\n",
+                                                      ldb_dn_get_linearized(msg->dn));
+                               return replmd_replicated_request_error(ar, LDB_ERR_OBJECT_CLASS_VIOLATION);
+                       }
+
                        DEBUG(4,(__location__ ": Removing attribute %s with num_values==0\n",
                                 el->name));
                        memmove(el, el+1, sizeof(*el)*(msg->num_elements - (i+1)));
@@ -3870,12 +3995,38 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                }
        }
 
+       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));
+               talloc_free(s);
+       }
+
        remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
                                                     "isDeleted", false);
 
        /*
         * the meta data array is already sorted by the caller
         */
+
+       rdn_name = ldb_dn_get_rdn_name(msg->dn);
+       if (rdn_name == NULL) {
+               ldb_asprintf_errstring(ldb, __location__ ": No rDN for %s?\n", ldb_dn_get_linearized(msg->dn));
+               return replmd_replicated_request_error(ar, LDB_ERR_INVALID_DN_SYNTAX);
+       }
+
+       rdn_sa = dsdb_attribute_by_lDAPDisplayName(ar->schema, rdn_name);
+       if (rdn_sa == NULL) {
+               ldb_asprintf_errstring(ldb, ": No schema attribute found for rDN %s for %s\n",
+                                      rdn_name, ldb_dn_get_linearized(msg->dn));
+               return replmd_replicated_request_error(ar, LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE);
+       }
+
+       ret = replmd_replPropertyMetaDataCtr1_verify(ldb, &md->ctr.ctr1, rdn_sa, msg->dn);
+       if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb, "%s: error during DRS repl ADD: %s", __func__, ldb_errstring(ldb));
+               return replmd_replicated_request_error(ar, ret);
+       }
+
        for (i=0; i < md->ctr.ctr1.count; i++) {
                md->ctr.ctr1.array[i].local_usn = ar->seq_num;
        }
@@ -3903,12 +4054,6 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
 
        ar->isDeleted = remote_isDeleted;
 
-       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));
-               talloc_free(s);
-       }
-
        ret = ldb_build_add_req(&change_req,
                                ldb,
                                ar,
@@ -4300,11 +4445,19 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                        }
 
                        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 */
+                               /*
+                                * 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.
+                                *
+                                * This call is a bit tricky, what we
+                                * are doing it turning the 'is_newer'
+                                * call into a 'not is older' by
+                                * swapping i and j, and negating the
+                                * outcome.
+                               */
                                cmp = !replmd_replPropertyMetaData1_is_newer(&rmd->ctr.ctr1.array[i],
                                                                             &nmd.ctr.ctr1.array[j]);
                        } else {
@@ -4385,8 +4538,9 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
         *
         * sort the new meta data array
         */
-       ret = replmd_replPropertyMetaDataCtr1_sort(&nmd.ctr.ctr1, ar->schema, msg->dn);
+       ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, ar->schema, msg->dn);
        if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb, "%s: error during DRS repl merge: %s", __func__, ldb_errstring(ldb));
                return ret;
        }
 
@@ -4460,6 +4614,14 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
        /* we want to replace the old values */
        for (i=0; i < msg->num_elements; i++) {
                msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+               if (ldb_attr_cmp(msg->elements[i].name, "objectClass") == 0) {
+                       if (msg->elements[i].num_values == 0) {
+                               ldb_asprintf_errstring(ldb, __location__
+                                                      ": objectClass removed on %s, aborting replication\n",
+                                                      ldb_dn_get_linearized(msg->dn));
+                               return replmd_replicated_request_error(ar, LDB_ERR_OBJECT_CLASS_VIOLATION);
+                       }
+               }
        }
 
        if (DEBUGLVL(4)) {
@@ -5277,8 +5439,8 @@ linked_attributes[0]:
 
        attrs[0] = attr->lDAPDisplayName;
        attrs[1] = "isDeleted";
-       attrs[1] = "isRecycled";
-       attrs[2] = NULL;
+       attrs[2] = "isRecycled";
+       attrs[3] = NULL;
 
        /* get the existing message from the db for the object with
           this GUID, returning attribute being modified. We will then
@@ -5443,7 +5605,8 @@ linked_attributes[0]:
        }
 
        /* see if this link already exists */
-       pdn = parsed_dn_find(pdn_list, old_el->num_values, &guid, dsdb_dn->dn);
+       pdn = parsed_dn_find(pdn_list, old_el->num_values, &guid, dsdb_dn->dn,
+                            dsdb_dn->extra_part);
        if (pdn != NULL) {
                /* see if this update is newer than what we have already */
                struct GUID invocation_id = GUID_zero();