Copyright (C) Simo Sorce 2006-2008
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
- Copyright (C) Matthias Dieter Wallnöfer 2010
+ Copyright (C) Matthias Dieter Wallnöfer 2010-2011
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
#include "auth/auth.h"
#include "param/param.h"
#include "../libds/common/flags.h"
-#include "util.h"
+#include "dsdb/samdb/ldb_modules/schema.h"
+#include "dsdb/samdb/ldb_modules/util.h"
struct oc_context {
{
struct ldb_context *ldb;
unsigned int i, lowest;
- struct class_list *unsorted = NULL, *sorted = NULL, *current = NULL, *poss_parent = NULL, *new_parent = NULL, *current_lowest = NULL;
+ struct class_list *unsorted = NULL, *sorted = NULL, *current = NULL,
+ *poss_parent = NULL, *new_parent = NULL,
+ *current_lowest = NULL, *current_lowest_struct = NULL;
ldb = ldb_module_get_ctx(module);
current->objectclass = dsdb_class_by_lDAPDisplayName(schema, "top");
DLIST_ADD_END(sorted, current, struct class_list *);
+ /* If we don't have a schema yet, then just merge the lists again */
+ if (!schema) {
+ DLIST_CONCATENATE(sorted, unsorted, struct class_list *);
+ *sorted_out = sorted;
+ return LDB_SUCCESS;
+ }
/* For each object: find parent chain */
- for (current = unsorted; schema && current; current = current->next) {
+ for (current = unsorted; current != NULL; current = current->next) {
for (poss_parent = unsorted; poss_parent; poss_parent = poss_parent->next) {
if (ldb_attr_cmp(poss_parent->objectclass->lDAPDisplayName, current->objectclass->subClassOf) == 0) {
break;
DLIST_ADD_END(unsorted, new_parent, struct class_list *);
}
- do
- {
+ /* For each object: order by hierarchy */
+ while (unsorted != NULL) {
lowest = UINT_MAX;
- current_lowest = NULL;
- for (current = unsorted; schema && current; current = current->next) {
- if(current->objectclass->subClass_order < lowest) {
- current_lowest = current;
+ current_lowest = current_lowest_struct = NULL;
+ for (current = unsorted; current != NULL; current = current->next) {
+ if (current->objectclass->subClass_order <= lowest) {
+ /*
+ * According to MS-ADTS 3.1.1.1.4 structural
+ * and 88 object classes are always listed after
+ * the other class types in a subclass hierarchy
+ */
+ if (current->objectclass->objectClassCategory > 1) {
+ current_lowest = current;
+ } else {
+ current_lowest_struct = current;
+ }
lowest = current->objectclass->subClass_order;
}
}
+ if (current_lowest == NULL) {
+ current_lowest = current_lowest_struct;
+ }
- if(current_lowest != NULL) {
+ if (current_lowest != NULL) {
DLIST_REMOVE(unsorted,current_lowest);
DLIST_ADD_END(sorted,current_lowest, struct class_list *);
}
- } while(unsorted);
+ }
+ *sorted_out = sorted;
+ return LDB_SUCCESS;
+}
- if (!unsorted) {
- *sorted_out = sorted;
+/*
+ * This checks if we have unrelated object classes in our entry's "objectClass"
+ * attribute. That means "unsatisfied" abstract classes (no concrete subclass)
+ * or two or more disjunct structural ones.
+ * If one of these conditions are true, blame.
+ */
+static int check_unrelated_objectclasses(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ const struct dsdb_class *struct_objectclass,
+ struct ldb_message_element *objectclass_element)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ unsigned int i;
+ bool found;
+
+ if (schema == NULL) {
return LDB_SUCCESS;
}
- if (!schema) {
- /* If we don't have schema yet, then just merge the lists again */
- DLIST_CONCATENATE(sorted, unsorted, struct class_list *);
- *sorted_out = sorted;
- return LDB_SUCCESS;
+ for (i = 0; i < objectclass_element->num_values; i++) {
+ const struct dsdb_class *tmp_class = dsdb_class_by_lDAPDisplayName_ldb_val(schema,
+ &objectclass_element->values[i]);
+ const struct dsdb_class *tmp_class2 = struct_objectclass;
+
+ /* Pointer comparison can be used due to the same schema str. */
+ if (tmp_class == NULL ||
+ tmp_class == struct_objectclass ||
+ tmp_class->objectClassCategory > 2 ||
+ ldb_attr_cmp(tmp_class->lDAPDisplayName, "top") == 0) {
+ continue;
+ }
+
+ found = false;
+ while (!found &&
+ ldb_attr_cmp(tmp_class2->lDAPDisplayName, "top") != 0) {
+ tmp_class2 = dsdb_class_by_lDAPDisplayName(schema,
+ tmp_class2->subClassOf);
+ if (tmp_class2 == tmp_class) {
+ found = true;
+ }
+ }
+ if (found) {
+ continue;
+ }
+
+ ldb_asprintf_errstring(ldb,
+ "objectclass: the objectclass '%s' seems to be unrelated to the entry!",
+ tmp_class->lDAPDisplayName);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
}
- /* This shouldn't happen, and would break MMC, perhaps there
- * was no 'top', a conflict in the objectClasses or some other
- * schema error?
- */
- ldb_asprintf_errstring(ldb, "objectclass %s is not a valid objectClass in objectClass chain", unsorted->objectclass->lDAPDisplayName);
- return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ return LDB_SUCCESS;
}
static int get_search_callback(struct ldb_request *req, struct ldb_reply *ares)
char *upper_rdn_attr;
const struct ldb_val *rdn_val;
- /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+ /* Fix up the DN to be in the standard form, taking particular care to
+ * match the parent DN */
*fixed_dn = ldb_dn_copy(mem_ctx, parent_dn);
+ if (*fixed_dn == NULL) {
+ return ldb_oom(ldb);
+ }
/* We need the attribute name in upper case */
upper_rdn_attr = strupper_talloc(*fixed_dn,
ldb_dn_get_rdn_name(newdn));
- if (!upper_rdn_attr) {
- return ldb_operr(ldb);
+ if (upper_rdn_attr == NULL) {
+ return ldb_oom(ldb);
}
/* Create a new child */
return ldb_operr(ldb);
}
-
rdn_val = ldb_dn_get_rdn_val(newdn);
+ if (rdn_val == NULL) {
+ return ldb_operr(ldb);
+ }
#if 0
/* the rules for rDN length constraints are more complex than
#endif
- /* And replace it with CN=foo (we need the attribute in upper case */
+ /* And replace it with CN=foo (we need the attribute in upper case) */
return ldb_dn_set_component(*fixed_dn, 0, upper_rdn_attr, *rdn_val);
}
struct oc_context *ac;
struct ldb_dn *parent_dn;
const struct ldb_val *val;
- char *value;
int ret;
- static const char * const parent_attrs[] = { "objectGUID", "objectClass", NULL };
+ static const char * const parent_attrs[] = { "objectClass", NULL };
ldb = ldb_module_get_ctx(module);
instanceType = ldb_msg_find_attr_as_uint(req->op.add.message,
"instanceType", 0);
if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+ char *referral_uri;
/* When we are trying to readd the root basedn then
* this is denied, but with an interesting mechanism:
* there is generated a referral with the last
if (val == NULL) {
return ldb_operr(ldb);
}
- value = talloc_asprintf(req, "ldap://%s/%s", val->data,
- ldb_dn_get_linearized(req->op.add.message->dn));
- if (value == NULL) {
- return ldb_oom(ldb);
+ referral_uri = talloc_asprintf(req, "ldap://%s/%s", val->data,
+ ldb_dn_get_linearized(req->op.add.message->dn));
+ if (referral_uri == NULL) {
+ return ldb_module_oom(module);
}
- return ldb_module_send_referral(req, value);
+ return ldb_module_send_referral(req, referral_uri);
}
}
/* get copy of parent DN */
parent_dn = ldb_dn_get_parent(ac, ac->req->op.add.message->dn);
if (parent_dn == NULL) {
- return ldb_oom(ldb);
+ return ldb_operr(ldb);
}
ret = ldb_build_search_req(&search_req, ldb,
NULL,
ac, get_search_callback,
req);
+ LDB_REQ_SET_LOCATION(search_req);
if (ret != LDB_SUCCESS) {
return ret;
}
{
struct ldb_control *rodc_control;
- if (strcasecmp(objectclass->lDAPDisplayName, "nTDSDSA") != 0) {
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSDSA") != 0) {
return false;
}
rodc_control = ldb_request_get_control(ac->req, LDB_CONTROL_RODC_DCPROMO_OID);
static int objectclass_do_add(struct oc_context *ac)
{
- struct ldb_context *ldb;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
struct ldb_request *add_req;
struct ldb_message_element *objectclass_element, *el;
struct ldb_message *msg;
const struct dsdb_class *objectclass;
struct ldb_dn *objectcategory;
int32_t systemFlags = 0;
+ unsigned int i, j;
+ bool found;
int ret;
- ldb = ldb_module_get_ctx(ac->module);
-
msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
/* Check if we have a valid parent - this check is needed since
* we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
/* An add operation on partition DNs without "NC-add" operation
* isn't allowed. */
- instanceType = ldb_msg_find_attr_as_uint(ac->req->op.add.message,
- "instanceType", 0);
+ instanceType = ldb_msg_find_attr_as_uint(msg, "instanceType",
+ 0);
if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, parent does not exist!",
ldb_dn_get_linearized(msg->dn));
}
}
- mem_ctx = talloc_new(ac);
- if (mem_ctx == NULL) {
- return ldb_oom(ldb);
- }
-
if (ac->schema != NULL) {
objectclass_element = ldb_msg_find_element(msg, "objectClass");
if (!objectclass_element) {
ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, no objectclass specified!",
ldb_dn_get_linearized(msg->dn));
- talloc_free(mem_ctx);
return LDB_ERR_OBJECT_CLASS_VIOLATION;
}
+ if (objectclass_element->num_values == 0) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, at least one (structural) objectclass has to be specified!",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ mem_ctx = talloc_new(ac);
+ if (mem_ctx == NULL) {
+ return ldb_module_oom(ac->module);
+ }
/* Here we do now get the "objectClass" list from the
* database. */
/* We must completely replace the existing objectClass entry,
* because we need it sorted. */
- ret = ldb_msg_add_empty(msg, "objectClass", 0, NULL);
+ ret = ldb_msg_add_empty(msg, "objectClass", 0,
+ &objectclass_element);
if (ret != LDB_SUCCESS) {
talloc_free(mem_ctx);
return ret;
/* Move from the linked list back into an ldb msg */
for (current = sorted; current; current = current->next) {
- value = talloc_strdup(msg, current->objectclass->lDAPDisplayName);
- if (value == NULL) {
- talloc_free(mem_ctx);
- return ldb_oom(ldb);
- }
- ret = ldb_msg_add_string(msg, "objectClass", value);
+ const char *objectclass_name = current->objectclass->lDAPDisplayName;
+
+ ret = ldb_msg_add_string(msg, "objectClass", objectclass_name);
if (ret != LDB_SUCCESS) {
ldb_set_errstring(ldb,
"objectclass: could not re-add sorted "
talloc_free(mem_ctx);
- /* Retrive the message again so get_last_structural_class works */
- objectclass_element = ldb_msg_find_element(msg, "objectClass");
-
/* Make sure its valid to add an object of this type */
objectclass = get_last_structural_class(ac->schema,
- objectclass_element);
+ objectclass_element,
+ true);
if(objectclass == NULL) {
ldb_asprintf_errstring(ldb,
"Failed to find a structural class for %s",
return LDB_ERR_UNWILLING_TO_PERFORM;
}
+ ret = check_unrelated_objectclasses(ac->module, ac->schema,
+ objectclass,
+ objectclass_element);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
rdn_name = ldb_dn_get_rdn_name(msg->dn);
- if (objectclass->rDNAttID
- && ldb_attr_cmp(rdn_name, objectclass->rDNAttID) != 0) {
+ if (rdn_name == NULL) {
+ return ldb_operr(ldb);
+ }
+ found = false;
+ for (i = 0; (!found) && (i < objectclass_element->num_values);
+ i++) {
+ const struct dsdb_class *tmp_class =
+ dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+ &objectclass_element->values[i]);
+
+ if (tmp_class == NULL) continue;
+
+ if (ldb_attr_cmp(rdn_name, tmp_class->rDNAttID) == 0)
+ found = true;
+ }
+ if (!found) {
ldb_asprintf_errstring(ldb,
- "RDN %s is not correct for most specific structural objectclass %s, should be %s",
- rdn_name, objectclass->lDAPDisplayName, objectclass->rDNAttID);
+ "objectclass: Invalid RDN '%s' for objectclass '%s'!",
+ rdn_name, objectclass->lDAPDisplayName);
return LDB_ERR_NAMING_VIOLATION;
}
if (objectclass->systemOnly &&
!ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) &&
!check_rodc_ntdsdsa_add(ac, objectclass)) {
- ldb_asprintf_errstring(ldb, "objectClass %s is systemOnly, rejecting creation of %s",
- objectclass->lDAPDisplayName, ldb_dn_get_linearized(msg->dn));
- return LDB_ERR_UNWILLING_TO_PERFORM;
- }
-
- if (((strcmp(objectclass->lDAPDisplayName, "secret") == 0) ||
- (strcmp(objectclass->lDAPDisplayName, "trustedDomain") == 0)) &&
- !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
- ldb_asprintf_errstring(ldb, "objectClass %s is LSA-specific, rejecting creation of %s",
- objectclass->lDAPDisplayName, ldb_dn_get_linearized(msg->dn));
+ ldb_asprintf_errstring(ldb,
+ "objectclass: object class '%s' is system-only, rejecting creation of '%s'!",
+ objectclass->lDAPDisplayName,
+ ldb_dn_get_linearized(msg->dn));
return LDB_ERR_UNWILLING_TO_PERFORM;
}
= ldb_msg_find_element(ac->search_res->message, "objectClass");
bool allowed_class = false;
- unsigned int i, j;
for (i=0; allowed_class == false && oc_el && i < oc_el->num_values; i++) {
const struct dsdb_class *sclass;
objectclass->defaultObjectCategory);
}
if (value == NULL) {
- return ldb_oom(ldb);
+ return ldb_module_oom(ac->module);
}
ret = ldb_msg_add_string(msg, "objectCategory", value);
ldb_msg_remove_attr(msg, "systemFlags");
- /* Only these flags may be set by a client, but we can't tell
- * between a client and our provision at this point
- * systemFlags &= ( SYSTEM_FLAG_CONFIG_ALLOW_RENAME | SYSTEM_FLAG_CONFIG_ALLOW_MOVE | SYSTEM_FLAG_CONFIG_LIMITED_MOVE);
- */
+ /* Only the following flags may be set by a client */
+ if (ldb_request_get_control(ac->req,
+ LDB_CONTROL_RELAX_OID) == NULL) {
+ systemFlags &= ( SYSTEM_FLAG_CONFIG_ALLOW_RENAME
+ | SYSTEM_FLAG_CONFIG_ALLOW_MOVE
+ | SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE
+ | SYSTEM_FLAG_ATTR_IS_RDN );
+ }
- /* This flag is only allowed on attributeSchema objects */
- if (ldb_attr_cmp(objectclass->lDAPDisplayName, "attributeSchema") == 0) {
+ /* But the last one ("ATTR_IS_RDN") is only allowed on
+ * "attributeSchema" objects. So truncate if it does not fit. */
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "attributeSchema") != 0) {
systemFlags &= ~SYSTEM_FLAG_ATTR_IS_RDN;
}
if (ldb_attr_cmp(objectclass->lDAPDisplayName, "server") == 0) {
systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE | SYSTEM_FLAG_CONFIG_ALLOW_RENAME | SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE);
} else if (ldb_attr_cmp(objectclass->lDAPDisplayName, "site") == 0
- || ldb_attr_cmp(objectclass->lDAPDisplayName, "serverContainer") == 0
- || ldb_attr_cmp(objectclass->lDAPDisplayName, "ntDSDSA") == 0) {
+ || ldb_attr_cmp(objectclass->lDAPDisplayName, "serversContainer") == 0
+ || ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSDSA") == 0) {
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "site") == 0)
+ systemFlags |= (int32_t)(SYSTEM_FLAG_CONFIG_ALLOW_RENAME);
systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
-
} else if (ldb_attr_cmp(objectclass->lDAPDisplayName, "siteLink") == 0
+ || ldb_attr_cmp(objectclass->lDAPDisplayName, "subnet") == 0
|| ldb_attr_cmp(objectclass->lDAPDisplayName, "siteLinkBridge") == 0
|| ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSConnection") == 0) {
systemFlags |= (int32_t)(SYSTEM_FLAG_CONFIG_ALLOW_RENAME);
}
-
/* TODO: If parent object is site or subnet, also add (SYSTEM_FLAG_CONFIG_ALLOW_RENAME) */
if (el || systemFlags != 0) {
return ret;
}
}
- }
- ret = ldb_msg_sanity_check(ldb, msg);
- if (ret != LDB_SUCCESS) {
- return ret;
+ /* make sure that "isCriticalSystemObject" is not specified! */
+ el = ldb_msg_find_element(msg, "isCriticalSystemObject");
+ if ((el != NULL) &&
+ !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
+ ldb_set_errstring(ldb,
+ "objectclass: 'isCriticalSystemObject' must not be specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
}
ret = ldb_build_add_req(&add_req, ldb, ac,
ac->req->controls,
ac, oc_op_callback,
ac->req);
+ LDB_REQ_SET_LOCATION(add_req);
if (ret != LDB_SUCCESS) {
return ret;
}
msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
if (msg == NULL) {
- return ldb_operr(ldb);
+ return ldb_module_oom(ac->module);
}
/* For now change everything except the objectclasses */
oc_changes = true;
}
+ /* MS-ADTS 3.1.1.5.3.5 - on a forest level < 2003 we do allow updates
+ * only on application NCs - not on the default ones */
+ if (oc_changes &&
+ (dsdb_forest_functional_level(ldb) < DS_DOMAIN_FUNCTION_2003)) {
+ struct ldb_dn *nc_root;
+
+ ret = dsdb_find_nc_root(ldb, ac, req->op.mod.message->dn,
+ &nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if ((ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) ||
+ (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) ||
+ (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0)) {
+ ldb_set_errstring(ldb,
+ "objectclass: object class changes on objects under the standard name contexts not allowed!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ talloc_free(nc_root);
+ }
+
ret = ldb_build_mod_req(&down_req, ldb, ac,
msg,
req->controls, ac,
oc_changes ? oc_modify_callback : oc_op_callback,
req);
+ LDB_REQ_SET_LOCATION(down_req);
if (ret != LDB_SUCCESS) {
return ret;
}
talloc_free(ares);
/* this looks up the real existing object for fetching some important
- * informations (objectclasses) */
+ * information (objectclasses) */
ret = ldb_build_search_req(&search_req, ldb,
ac, ac->req->op.mod.message->dn,
LDB_SCOPE_BASE,
attrs, NULL,
ac, get_search_callback,
ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
if (ret != LDB_SUCCESS) {
return ldb_module_done(ac->req, NULL, NULL, ret);
}
{
struct ldb_context *ldb;
struct ldb_request *mod_req;
- char *value;
struct ldb_message_element *oc_el_entry, *oc_el_change;
struct ldb_val *vals;
struct ldb_message *msg;
TALLOC_CTX *mem_ctx;
struct class_list *sorted, *current;
const struct dsdb_class *objectclass;
- unsigned int i, j;
- bool found, replace = false;
+ unsigned int i, j, k;
+ bool found;
int ret;
ldb = ldb_module_get_ctx(ac->module);
return ldb_operr(ldb);
}
- oc_el_change = ldb_msg_find_element(ac->req->op.mod.message,
- "objectClass");
- if (oc_el_change == NULL) {
- /* we should have an objectclass change operation */
- return ldb_operr(ldb);
- }
-
/* use a new message structure */
msg = ldb_msg_new(ac);
if (msg == NULL) {
- return ldb_oom(ldb);
+ return ldb_module_oom(ac->module);
}
msg->dn = ac->req->op.mod.message->dn;
mem_ctx = talloc_new(ac);
if (mem_ctx == NULL) {
- return ldb_oom(ldb);
+ return ldb_module_oom(ac->module);
}
- switch (oc_el_change->flags & LDB_FLAG_MOD_MASK) {
- case LDB_FLAG_MOD_ADD:
- /* Merge the two message elements */
- for (i = 0; i < oc_el_change->num_values; i++) {
- for (j = 0; j < oc_el_entry->num_values; j++) {
- if (strcasecmp((char *)oc_el_change->values[i].data,
- (char *)oc_el_entry->values[j].data) == 0) {
- /* we cannot add an already existing object class */
+ /* We've to walk over all "objectClass" message elements */
+ for (k = 0; k < ac->req->op.mod.message->num_elements; k++) {
+ if (ldb_attr_cmp(ac->req->op.mod.message->elements[k].name,
+ "objectClass") != 0) {
+ continue;
+ }
+
+ oc_el_change = &ac->req->op.mod.message->elements[k];
+
+ switch (oc_el_change->flags & LDB_FLAG_MOD_MASK) {
+ case LDB_FLAG_MOD_ADD:
+ /* Merge the two message elements */
+ for (i = 0; i < oc_el_change->num_values; i++) {
+ for (j = 0; j < oc_el_entry->num_values; j++) {
+ if (ldb_attr_cmp((char *)oc_el_change->values[i].data,
+ (char *)oc_el_entry->values[j].data) == 0) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: cannot re-add an existing objectclass: '%.*s'!",
+ (int)oc_el_change->values[i].length,
+ (const char *)oc_el_change->values[i].data);
+ talloc_free(mem_ctx);
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ }
+ }
+ /* append the new object class value - code was
+ * copied from "ldb_msg_add_value" */
+ vals = talloc_realloc(oc_el_entry, oc_el_entry->values,
+ struct ldb_val,
+ oc_el_entry->num_values + 1);
+ if (vals == NULL) {
talloc_free(mem_ctx);
- return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ return ldb_module_oom(ac->module);
}
+ oc_el_entry->values = vals;
+ oc_el_entry->values[oc_el_entry->num_values] =
+ oc_el_change->values[i];
+ ++(oc_el_entry->num_values);
}
- /* append the new object class value - code was copied
- * from "ldb_msg_add_value" */
- vals = talloc_realloc(oc_el_entry, oc_el_entry->values,
- struct ldb_val,
- oc_el_entry->num_values + 1);
- if (vals == NULL) {
- talloc_free(mem_ctx);
- return ldb_oom(ldb);
+
+ break;
+
+ case LDB_FLAG_MOD_REPLACE:
+ /*
+ * In this case the new "oc_el_entry" is simply
+ * "oc_el_change"
+ */
+ oc_el_entry = oc_el_change;
+
+ break;
+
+ case LDB_FLAG_MOD_DELETE:
+ /* Merge the two message elements */
+ for (i = 0; i < oc_el_change->num_values; i++) {
+ found = false;
+ for (j = 0; j < oc_el_entry->num_values; j++) {
+ if (ldb_attr_cmp((char *)oc_el_change->values[i].data,
+ (char *)oc_el_entry->values[j].data) == 0) {
+ found = true;
+ /* delete the object class value
+ * - code was copied from
+ * "ldb_msg_remove_element" */
+ if (j != oc_el_entry->num_values - 1) {
+ memmove(&oc_el_entry->values[j],
+ &oc_el_entry->values[j+1],
+ ((oc_el_entry->num_values-1) - j)*sizeof(struct ldb_val));
+ }
+ --(oc_el_entry->num_values);
+ break;
+ }
+ }
+ if (!found) {
+ /* we cannot delete a not existing
+ * object class */
+ ldb_asprintf_errstring(ldb,
+ "objectclass: cannot delete this objectclass: '%.*s'!",
+ (int)oc_el_change->values[i].length,
+ (const char *)oc_el_change->values[i].data);
+ talloc_free(mem_ctx);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
}
- oc_el_entry->values = vals;
- oc_el_entry->values[oc_el_entry->num_values] =
- oc_el_change->values[i];
- ++(oc_el_entry->num_values);
- }
- objectclass = get_last_structural_class(ac->schema,
- oc_el_change);
- if (objectclass != NULL) {
- /* we cannot add a new structural object class */
- talloc_free(mem_ctx);
- return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ break;
}
- /* Now do the sorting */
- ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
- oc_el_entry, &sorted);
- if (ret != LDB_SUCCESS) {
+ /* Get the new top-most structural object class */
+ objectclass = get_last_structural_class(ac->schema, oc_el_entry,
+ false);
+ if (objectclass == NULL) {
+ ldb_set_errstring(ldb,
+ "objectclass: cannot delete all structural objectclasses!");
talloc_free(mem_ctx);
- return ret;
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
}
- break;
-
- case LDB_FLAG_MOD_REPLACE:
- /* Do the sorting for the change message element */
- ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
- oc_el_change, &sorted);
+ ret = check_unrelated_objectclasses(ac->module, ac->schema,
+ objectclass,
+ oc_el_entry);
if (ret != LDB_SUCCESS) {
talloc_free(mem_ctx);
return ret;
}
- /* this is a replace */
- replace = true;
-
- break;
-
- case LDB_FLAG_MOD_DELETE:
- /* get the actual top-most structural objectclass */
- objectclass = get_last_structural_class(ac->schema,
- oc_el_entry);
- if (objectclass == NULL) {
- talloc_free(mem_ctx);
- return ldb_operr(ldb);
- }
-
- /* Merge the two message elements */
- for (i = 0; i < oc_el_change->num_values; i++) {
- found = false;
- for (j = 0; j < oc_el_entry->num_values; j++) {
- if (strcasecmp((char *)oc_el_change->values[i].data,
- (char *)oc_el_entry->values[j].data) == 0) {
- found = true;
- /* delete the object class value -
- * code was copied from
- * "ldb_msg_remove_element" */
- if (j != oc_el_entry->num_values - 1) {
- memmove(&oc_el_entry->values[j],
- &oc_el_entry->values[j+1],
- ((oc_el_entry->num_values-1) - j)*sizeof(struct ldb_val));
- }
- --(oc_el_entry->num_values);
- break;
- }
- }
- if (!found) {
- /* we cannot delete a not existing object class */
- talloc_free(mem_ctx);
- return LDB_ERR_NO_SUCH_ATTRIBUTE;
- }
- }
-
- /* Make sure that the top-most structural objectclass wasn't
- * deleted */
- found = false;
- for (i = 0; i < oc_el_entry->num_values; i++) {
- if (strcasecmp(objectclass->lDAPDisplayName,
- (char *)oc_el_entry->values[i].data) == 0) {
- found = true; break;
- }
- }
- if (!found) {
- talloc_free(mem_ctx);
- return LDB_ERR_OBJECT_CLASS_VIOLATION;
- }
-
-
/* Now do the sorting */
ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
oc_el_entry, &sorted);
return ret;
}
- break;
- }
-
- ret = ldb_msg_add_empty(msg, "objectClass",
- LDB_FLAG_MOD_REPLACE, &oc_el_change);
- if (ret != LDB_SUCCESS) {
- ldb_oom(ldb);
- talloc_free(mem_ctx);
- return ret;
- }
-
- /* Move from the linked list back into an ldb msg */
- for (current = sorted; current; current = current->next) {
- value = talloc_strdup(msg,
- current->objectclass->lDAPDisplayName);
- if (value == NULL) {
- talloc_free(mem_ctx);
- return ldb_oom(ldb);
- }
- ret = ldb_msg_add_string(msg, "objectClass", value);
+ /* (Re)-add an empty "objectClass" attribute on the object
+ * classes change message "msg". */
+ ldb_msg_remove_attr(msg, "objectClass");
+ ret = ldb_msg_add_empty(msg, "objectClass",
+ LDB_FLAG_MOD_REPLACE, &oc_el_entry);
if (ret != LDB_SUCCESS) {
- ldb_set_errstring(ldb, "objectclass: could not re-add sorted objectclass to modify msg");
talloc_free(mem_ctx);
return ret;
}
- }
- talloc_free(mem_ctx);
+ /* Move from the linked list back into an ldb msg */
+ for (current = sorted; current; current = current->next) {
+ const char *objectclass_name = current->objectclass->lDAPDisplayName;
- if (replace) {
- /* Well, on replace we are nearly done: we have to test if
- * the change and entry message element are identically. We
- * can use "ldb_msg_element_compare" since now the specified
- * objectclasses match for sure in case. */
- ret = ldb_msg_element_compare(oc_el_entry, oc_el_change);
- if (ret == 0) {
- ret = ldb_msg_element_compare(oc_el_change,
- oc_el_entry);
- }
- if (ret == 0) {
- /* they are the same so we are done in this case */
- return ldb_module_done(ac->req, NULL, NULL,
- LDB_SUCCESS);
- } else {
- /* they're not exactly the same */
- return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ ret = ldb_msg_add_string(msg, "objectClass",
+ objectclass_name);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ldb,
+ "objectclass: could not re-add sorted objectclasses!");
+ talloc_free(mem_ctx);
+ return ret;
+ }
}
}
- /* in the other cases we have the real change left to do */
+ talloc_free(mem_ctx);
- ret = ldb_msg_sanity_check(ldb, msg);
- if (ret != LDB_SUCCESS) {
- return ret;
- }
+ /* Now we have the real and definitive change left to do */
ret = ldb_build_mod_req(&mod_req, ldb, ac,
msg,
ac->req->controls,
ac, oc_op_callback,
ac->req);
+ LDB_REQ_SET_LOCATION(mod_req);
if (ret != LDB_SUCCESS) {
return ret;
}
ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_rename\n");
/* do not manipulate our control entries */
- if (ldb_dn_is_special(req->op.rename.newdn)) {
+ if (ldb_dn_is_special(req->op.rename.olddn)) {
return ldb_next_request(module, req);
}
}
/* this looks up the parent object for fetching some important
- * informations (objectclasses, DN normalisation...) */
+ * information (objectclasses, DN normalisation...) */
ret = ldb_build_search_req(&search_req, ldb,
ac, parent_dn, LDB_SCOPE_BASE,
"(objectClass=*)",
attrs, NULL,
ac, get_search_callback,
req);
+ LDB_REQ_SET_LOCATION(search_req);
if (ret != LDB_SUCCESS) {
return ret;
}
- /* we have to add the show deleted control, as otherwise DRS
+ /* we have to add the show recycled control, as otherwise DRS
deletes will be refused as we will think the target parent
does not exist */
- ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_DELETED_OID, false, NULL);
+ ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID,
+ false, NULL);
if (ret != LDB_SUCCESS) {
return ret;
ac->search_res = NULL;
/* this looks up the real existing object for fetching some important
- * informations (objectclasses) */
+ * information (objectclasses) */
ret = ldb_build_search_req(&search_req, ldb,
ac, ac->req->op.rename.olddn,
LDB_SCOPE_BASE,
attrs, NULL,
ac, get_search_callback,
ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
if (ret != LDB_SUCCESS) {
return ret;
}
const char *rdn_name;
bool allowed_class = false;
unsigned int i, j;
+ bool found;
oc_el_entry = ldb_msg_find_element(ac->search_res->message,
"objectClass");
/* existing entry without a valid object class? */
return ldb_operr(ldb);
}
- objectclass = get_last_structural_class(ac->schema, oc_el_entry);
+ objectclass = get_last_structural_class(ac->schema, oc_el_entry,
+ false);
if (objectclass == NULL) {
/* existing entry without a valid object class? */
return ldb_operr(ldb);
}
rdn_name = ldb_dn_get_rdn_name(ac->req->op.rename.newdn);
- if ((objectclass->rDNAttID != NULL) &&
- (ldb_attr_cmp(rdn_name, objectclass->rDNAttID) != 0)) {
+ if (rdn_name == NULL) {
+ return ldb_operr(ldb);
+ }
+ found = false;
+ for (i = 0; (!found) && (i < oc_el_entry->num_values); i++) {
+ const struct dsdb_class *tmp_class =
+ dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+ &oc_el_entry->values[i]);
+
+ if (tmp_class == NULL) continue;
+
+ if (ldb_attr_cmp(rdn_name, tmp_class->rDNAttID) == 0)
+ found = true;
+ }
+ if (!found) {
ldb_asprintf_errstring(ldb,
- "objectclass: RDN %s is not correct for most specific structural objectclass %s, should be %s",
- rdn_name,
- objectclass->lDAPDisplayName,
- objectclass->rDNAttID);
+ "objectclass: Invalid RDN '%s' for objectclass '%s'!",
+ rdn_name, objectclass->lDAPDisplayName);
return LDB_ERR_UNWILLING_TO_PERFORM;
}
ac->req->controls,
ac, oc_op_callback,
ac->req);
+ LDB_REQ_SET_LOCATION(rename_req);
if (ret != LDB_SUCCESS) {
return ret;
}
static int objectclass_delete(struct ldb_module *module, struct ldb_request *req)
{
static const char * const attrs[] = { "nCName", "objectClass",
- "systemFlags", NULL };
+ "systemFlags",
+ "isDeleted",
+ "isCriticalSystemObject", NULL };
struct ldb_context *ldb;
struct ldb_request *search_req;
struct oc_context *ac;
}
/* this looks up the entry object for fetching some important
- * informations (object classes, system flags...) */
+ * information (object classes, system flags...) */
ret = ldb_build_search_req(&search_req, ldb,
ac, req->op.del.dn, LDB_SCOPE_BASE,
"(objectClass=*)",
- attrs, NULL,
+ attrs, req->controls,
ac, get_search_callback,
req);
+ LDB_REQ_SET_LOCATION(search_req);
if (ret != LDB_SUCCESS) {
return ret;
}
struct ldb_context *ldb;
struct ldb_dn *dn;
int32_t systemFlags;
+ bool isCriticalSystemObject;
int ret;
ldb = ldb_module_get_ctx(ac->module);
talloc_free(dn);
}
+ /* Only trusted request from system account are allowed to delete
+ * deleted objects.
+ */
+ if (ldb_msg_check_string_attribute(ac->search_res->message, "isDeleted", "TRUE") &&
+ (ldb_req_is_untrusted(ac->req) ||
+ !dsdb_module_am_system(ac->module))) {
+ ldb_asprintf_errstring(ldb, "Delete of '%s' failed",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
/* crossRef objects regarding config, schema and default domain NCs */
if (samdb_find_attribute(ldb, ac->search_res->message, "objectClass",
"crossRef") != NULL) {
dn = ldb_msg_find_attr_as_dn(ldb, ac, ac->search_res->message,
"nCName");
if ((ldb_dn_compare(dn, ldb_get_default_basedn(ldb)) == 0) ||
- (ldb_dn_compare(dn, ldb_get_config_basedn(ldb)) == 0) ||
- (ldb_dn_compare(dn, ldb_get_schema_basedn(ldb)) == 0)) {
+ (ldb_dn_compare(dn, ldb_get_config_basedn(ldb)) == 0)) {
+ talloc_free(dn);
+
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's a crossRef object to the main or configuration partition!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_NOT_ALLOWED_ON_NON_LEAF;
+ }
+ if (ldb_dn_compare(dn, ldb_get_schema_basedn(ldb)) == 0) {
talloc_free(dn);
- ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's a crossRef object to the three main partitions!",
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's a crossRef object to the schema partition!",
ldb_dn_get_linearized(ac->req->op.del.dn));
return LDB_ERR_UNWILLING_TO_PERFORM;
}
return LDB_ERR_UNWILLING_TO_PERFORM;
}
+ /* isCriticalSystemObject - but this only applies on tree delete
+ * operations - MS-ADTS 3.1.1.5.5.7.2 */
+ if (ldb_request_get_control(ac->req, LDB_CONTROL_TREE_DELETE_OID) != NULL) {
+ isCriticalSystemObject = ldb_msg_find_attr_as_bool(ac->search_res->message,
+ "isCriticalSystemObject", false);
+ if (isCriticalSystemObject) {
+ /*
+ * Following the explaination from Microsoft
+ * https://lists.samba.org/archive/cifs-protocol/2011-August/002046.html
+ * "I finished the investigation on this behavior.
+ * As per MS-ADTS 3.1.5.5.7.2 , when a tree deletion is performed ,
+ * every object in the tree will be checked to see if it has isCriticalSystemObject
+ * set to TRUE, including the root node on which the delete operation is performed
+ * But there is an exception if the root object is a SAM specific objects(3.1.1.5.2.3 MS-ADTS)
+ * Its deletion is done through SAM manger and isCriticalSystemObject attribute is not checked
+ * The root node of the tree delete in your case is CN=ARES,OU=Domain Controllers,DC=w2k8r2,DC=home,DC=matws,DC=net
+ * which is a SAM object with user class. Therefore the tree deletion is performed without any error
+ */
+
+ if (samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "group") == NULL &&
+ samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "samDomain") == NULL &&
+ samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "samServer") == NULL &&
+ samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "user") == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: Cannot tree-delete %s, it's a critical system object!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ }
+
return ldb_next_request(ac->module, ac->req);
}
/* Look for the opaque to indicate we might have to cut down the DN of defaultObjectCategory */
ldb_module_set_private(module, ldb_get_opaque(ldb, DSDB_EXTENDED_DN_STORE_FORMAT_OPAQUE_NAME));
+ ret = ldb_mod_register_control(module, LDB_CONTROL_RODC_DCPROMO_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "objectclass_init: Unable to register control DCPROMO with rootdse\n");
+ return ldb_operr(ldb);
+ }
+
return ret;
}
-_PUBLIC_ const struct ldb_module_ops ldb_objectclass_module_ops = {
+static const struct ldb_module_ops ldb_objectclass_module_ops = {
.name = "objectclass",
.add = objectclass_add,
.modify = objectclass_modify,
.del = objectclass_delete,
.init_context = objectclass_init
};
+
+int ldb_objectclass_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_objectclass_module_ops);
+}