Copyright (C) Simo Sorce 2006-2008
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Matthias Dieter Wallnöfer 2010
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
/*
* Name: ldb
*
- * Component: objectClass sorting module
+ * Component: objectClass sorting and constraint checking module
*
* Description:
* - sort the objectClass attribute into the class
- * hierarchy,
- * - fix DNs and attributes into 'standard' case
- * - Add objectCategory and ntSecurityDescriptor defaults
+ * hierarchy and perform constraint checks (correct RDN name,
+ * valid parent),
+ * - fix DNs into 'standard' case
+ * - Add objectCategory and some other attribute defaults
*
* Author: Andrew Bartlett
*/
#include "includes.h"
#include "ldb_module.h"
-#include "dlinklist.h"
+#include "util/dlinklist.h"
#include "dsdb/samdb/samdb.h"
#include "librpc/ndr/libndr.h"
#include "librpc/gen_ndr/ndr_security.h"
#include "auth/auth.h"
#include "param/param.h"
#include "../libds/common/flags.h"
+#include "dsdb/samdb/ldb_modules/schema.h"
#include "util.h"
struct oc_context {
struct ldb_module *module;
struct ldb_request *req;
+ const struct dsdb_schema *schema;
struct ldb_reply *search_res;
+ struct ldb_reply *search_res2;
int (*step_fn)(struct oc_context *);
};
ac = talloc_zero(req, struct oc_context);
if (ac == NULL) {
- ldb_set_errstring(ldb, "Out of Memory");
+ ldb_oom(ldb);
return NULL;
}
ac->module = module;
ac->req = req;
+ ac->schema = dsdb_get_schema(ldb, ac);
return ac;
}
struct class_list **sorted_out)
{
struct ldb_context *ldb;
- int i, lowest;
+ unsigned int i, lowest;
struct class_list *unsorted = NULL, *sorted = NULL, *current = NULL, *poss_parent = NULL, *new_parent = NULL, *current_lowest = NULL;
ldb = ldb_module_get_ctx(module);
for (i=0; i < objectclass_element->num_values; i++) {
current = talloc(mem_ctx, struct class_list);
if (!current) {
- ldb_oom(ldb);
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_oom(ldb);
}
current->objectclass = dsdb_class_by_lDAPDisplayName_ldb_val(schema, &objectclass_element->values[i]);
if (!current->objectclass) {
do
{
- lowest = INT_MAX;
+ lowest = UINT_MAX;
current_lowest = NULL;
for (current = unsorted; schema && current; current = current->next) {
if(current->objectclass->subClass_order < lowest) {
return ldb_module_done(ac->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(ac->req, ares->referral);
+ }
+
if (ares->error != LDB_SUCCESS) {
return ldb_module_done(ac->req, ares->controls,
ares->response, ares->error);
CN=Admins,CN=Users,DC=samba,DC=example,DC=com
*/
-static int fix_dn(TALLOC_CTX *mem_ctx,
+static int fix_dn(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
struct ldb_dn *newdn, struct ldb_dn *parent_dn,
struct ldb_dn **fixed_dn)
{
upper_rdn_attr = strupper_talloc(*fixed_dn,
ldb_dn_get_rdn_name(newdn));
if (!upper_rdn_attr) {
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_operr(ldb);
}
/* Create a new child */
if (ldb_dn_add_child_fmt(*fixed_dn, "X=X") == false) {
- return LDB_ERR_OPERATIONS_ERROR;
+ 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
return ldb_dn_set_component(*fixed_dn, 0, upper_rdn_attr, *rdn_val);
}
-/* Fix all attribute names to be in the correct case, and check they are all valid per the schema */
-static int fix_check_attributes(struct ldb_context *ldb,
- const struct dsdb_schema *schema,
- struct ldb_message *msg,
- enum ldb_request_type op)
-{
- unsigned int i;
- for (i=0; i < msg->num_elements; i++) {
- const struct dsdb_attribute *attribute = dsdb_attribute_by_lDAPDisplayName(schema, msg->elements[i].name);
- /* Add in a very special case for 'clearTextPassword',
- * which is used for internal processing only, and is
- * not presented in the schema */
- if (!attribute) {
- if (strcasecmp(msg->elements[i].name, "clearTextPassword") != 0) {
- ldb_asprintf_errstring(ldb, "attribute %s is not a valid attribute in schema", msg->elements[i].name);
- /* Apparently Windows sends exactly this behaviour */
- return LDB_ERR_NO_SUCH_ATTRIBUTE;
- }
- } else {
- msg->elements[i].name = attribute->lDAPDisplayName;
-
- /* We have to deny write operations on constructed attributes */
- if ((attribute->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED) != 0) {
- ldb_asprintf_errstring(ldb, "attribute %s is constructed", msg->elements[i].name);
- if (op == LDB_ADD) {
- return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
- } else {
- return LDB_ERR_CONSTRAINT_VIOLATION;
- }
- }
-
- }
- }
-
- return LDB_SUCCESS;
-}
static int objectclass_do_add(struct oc_context *ac);
struct ldb_request *search_req;
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);
return ldb_next_request(module, req);
}
- /* the objectClass must be specified on add */
- if (ldb_msg_find_element(req->op.add.message,
- "objectClass") == NULL) {
- return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ /* An add operation on the basedn without "NC-add" operation isn't
+ * allowed. */
+ if (ldb_dn_compare(ldb_get_default_basedn(ldb), req->op.add.message->dn) == 0) {
+ unsigned int instanceType;
+
+ instanceType = ldb_msg_find_attr_as_uint(req->op.add.message,
+ "instanceType", 0);
+ if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+ /* 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
+ * component value as hostname. */
+ val = ldb_dn_get_component_val(req->op.add.message->dn,
+ ldb_dn_get_comp_num(req->op.add.message->dn) - 1);
+ 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);
+ }
+
+ return ldb_module_send_referral(req, value);
+ }
}
ac = oc_init_context(module, req);
if (ac == NULL) {
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_operr(ldb);
}
/* If there isn't a parent, just go on to the add processing */
/* get copy of parent DN */
parent_dn = ldb_dn_get_parent(ac, ac->req->op.add.message->dn);
if (parent_dn == NULL) {
- ldb_oom(ldb);
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_oom(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;
}
- talloc_steal(search_req, parent_dn);
ac->step_fn = objectclass_do_add;
return ldb_next_request(ac->module, search_req);
}
+
+/*
+ check if this is a special RODC nTDSDSA add
+ */
+static bool check_rodc_ntdsdsa_add(struct oc_context *ac,
+ const struct dsdb_class *objectclass)
+{
+ struct ldb_control *rodc_control;
+
+ if (strcasecmp(objectclass->lDAPDisplayName, "nTDSDSA") != 0) {
+ return false;
+ }
+ rodc_control = ldb_request_get_control(ac->req, LDB_CONTROL_RODC_DCPROMO_OID);
+ if (!rodc_control) {
+ return false;
+ }
+
+ rodc_control->critical = false;
+ return true;
+}
+
static int objectclass_do_add(struct oc_context *ac)
{
struct ldb_context *ldb;
- const struct dsdb_schema *schema;
struct ldb_request *add_req;
- char *value;
struct ldb_message_element *objectclass_element, *el;
struct ldb_message *msg;
TALLOC_CTX *mem_ctx;
struct class_list *sorted, *current;
- int ret;
+ const char *rdn_name = NULL;
+ char *value;
const struct dsdb_class *objectclass;
+ struct ldb_dn *objectcategory;
int32_t systemFlags = 0;
- const char *rdn_name = NULL;
+ unsigned int i, j;
+ bool found;
+ int ret;
ldb = ldb_module_get_ctx(ac->module);
- schema = dsdb_get_schema(ldb);
-
- mem_ctx = talloc_new(ac);
- if (mem_ctx == NULL) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
- /* Check we have a valid parent */
+ /* Check if we have a valid parent - this check is needed since
+ * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
if (ac->search_res == NULL) {
- if (ldb_dn_compare(ldb_get_root_basedn(ldb),
- msg->dn) == 0) {
- /* Allow the tree to be started */
-
- /* but don't keep any error string, it's meaningless */
- ldb_set_errstring(ldb, NULL);
- } else {
+ unsigned int instanceType;
+
+ /* 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);
+ 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));
- talloc_free(mem_ctx);
return LDB_ERR_NO_SUCH_OBJECT;
}
- } else {
- /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
- ret = fix_dn(msg,
+ /* Don't keep any error messages - we've to add a partition */
+ ldb_set_errstring(ldb, NULL);
+ } else {
+ /* Fix up the DN to be in the standard form, taking
+ * particular care to match the parent DN */
+ ret = fix_dn(ldb, msg,
ac->req->op.add.message->dn,
ac->search_res->message->dn,
&msg->dn);
-
if (ret != LDB_SUCCESS) {
- ldb_asprintf_errstring(ldb, "Could not munge DN %s into normal form",
+ ldb_asprintf_errstring(ldb, "objectclass: Could not munge DN %s into normal form",
ldb_dn_get_linearized(ac->req->op.add.message->dn));
- talloc_free(mem_ctx);
return ret;
}
+ }
+ mem_ctx = talloc_new(ac);
+ if (mem_ctx == NULL) {
+ return ldb_oom(ldb);
}
- if (schema) {
- ret = fix_check_attributes(ldb, schema, msg, ac->req->operation);
- if (ret != LDB_SUCCESS) {
- talloc_free(mem_ctx);
- return ret;
- }
- /* This is now the objectClass list from the database */
+ if (ac->schema != NULL) {
objectclass_element = ldb_msg_find_element(msg, "objectClass");
-
if (!objectclass_element) {
- /* Where did it go? bail now... */
+ 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));
talloc_free(mem_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
+ return LDB_ERR_CONSTRAINT_VIOLATION;
}
- ret = objectclass_sort(ac->module, schema, mem_ctx, objectclass_element, &sorted);
+
+ /* Here we do now get the "objectClass" list from the
+ * database. */
+ ret = objectclass_sort(ac->module, ac->schema, mem_ctx,
+ objectclass_element, &sorted);
if (ret != LDB_SUCCESS) {
talloc_free(mem_ctx);
return ret;
}
- ldb_msg_remove_attr(msg, "objectClass");
+ ldb_msg_remove_element(msg, objectclass_element);
+
+ /* Well, now we shouldn't find any additional "objectClass"
+ * message element (required by the AD specification). */
+ objectclass_element = ldb_msg_find_element(msg, "objectClass");
+ if (objectclass_element != NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, only one 'objectclass' attribute specification is allowed!",
+ ldb_dn_get_linearized(msg->dn));
+ talloc_free(mem_ctx);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* We must completely replace the existing objectClass entry,
+ * because we need it sorted. */
ret = ldb_msg_add_empty(msg, "objectClass", 0, NULL);
-
if (ret != LDB_SUCCESS) {
talloc_free(mem_ctx);
return ret;
}
- /* We must completely replace the existing objectClass entry,
- * because we need it sorted */
-
/* 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) {
- ldb_oom(ldb);
talloc_free(mem_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_oom(ldb);
}
+
ret = ldb_msg_add_string(msg, "objectClass", value);
if (ret != LDB_SUCCESS) {
ldb_set_errstring(ldb,
}
}
+ 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(schema,objectclass_element);
+ objectclass = get_last_structural_class(ac->schema,
+ objectclass_element);
if(objectclass == NULL) {
ldb_asprintf_errstring(ldb,
- "Failed to find a structural class for %s",
- ldb_dn_get_linearized(msg->dn));
- return LDB_ERR_NAMING_VIOLATION;
+ "Failed to find a structural class for %s",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
}
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));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
if (ac->search_res && ac->search_res->message) {
struct ldb_message_element *oc_el
= ldb_msg_find_element(ac->search_res->message, "objectClass");
bool allowed_class = false;
- int i, j;
for (i=0; allowed_class == false && oc_el && i < oc_el->num_values; i++) {
const struct dsdb_class *sclass;
- sclass = dsdb_class_by_lDAPDisplayName_ldb_val(schema, &oc_el->values[i]);
+ sclass = dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+ &oc_el->values[i]);
if (!sclass) {
/* We don't know this class? what is going on? */
continue;
}
- if (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
- for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
- if (ldb_attr_cmp(objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
- allowed_class = true;
- break;
- }
- }
- } else {
- for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
- if (ldb_attr_cmp(objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
- allowed_class = true;
- break;
- }
+ for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
+ allowed_class = true;
+ break;
}
}
}
}
}
- if (objectclass->systemOnly && !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
- 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 (!ldb_msg_find_element(msg, "objectCategory")) {
- struct dsdb_extended_dn_store_format *dn_format = talloc_get_type(ldb_module_get_private(ac->module), struct dsdb_extended_dn_store_format);
+ objectcategory = ldb_msg_find_attr_as_dn(ldb, ac, msg,
+ "objectCategory");
+ if (objectcategory == NULL) {
+ struct dsdb_extended_dn_store_format *dn_format =
+ talloc_get_type(ldb_module_get_private(ac->module),
+ struct dsdb_extended_dn_store_format);
if (dn_format && dn_format->store_extended_dn_in_ldb == false) {
/* Strip off extended components */
- struct ldb_dn *dn = ldb_dn_new(msg, ldb, objectclass->defaultObjectCategory);
+ struct ldb_dn *dn = ldb_dn_new(ac, ldb,
+ objectclass->defaultObjectCategory);
value = ldb_dn_alloc_linearized(msg, dn);
talloc_free(dn);
} else {
- value = talloc_strdup(msg, objectclass->defaultObjectCategory);
+ value = talloc_strdup(msg,
+ objectclass->defaultObjectCategory);
}
if (value == NULL) {
- ldb_oom(ldb);
- talloc_free(mem_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_msg_add_string(msg, "objectCategory", value);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ } else {
+ const struct dsdb_class *ocClass =
+ dsdb_class_by_cn_ldb_val(ac->schema,
+ ldb_dn_get_rdn_val(objectcategory));
+ if (ocClass != NULL) {
+ struct ldb_dn *dn = ldb_dn_new(ac, ldb,
+ ocClass->defaultObjectCategory);
+ if (ldb_dn_compare(objectcategory, dn) != 0) {
+ ocClass = NULL;
+ }
+ }
+ talloc_free(objectcategory);
+ if (ocClass == NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, 'objectCategory' attribute invalid!",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
}
- ldb_msg_add_string(msg, "objectCategory", value);
}
+
if (!ldb_msg_find_element(msg, "showInAdvancedViewOnly") && (objectclass->defaultHidingValue == true)) {
ldb_msg_add_string(msg, "showInAdvancedViewOnly",
"TRUE");
}
- /* There are very special rules for systemFlags, see MS-ADTS 3.1.1.5.2.4 */
+ /* There are very special rules for systemFlags, see MS-ADTS
+ * MS-ADTS 3.1.1.5.2.4 */
+
el = ldb_msg_find_element(msg, "systemFlags");
+ if ((el != NULL) && (el->num_values > 1)) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, 'systemFlags' attribute multivalued!",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0);
- if (el) {
- /* 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); */
- ldb_msg_remove_element(msg, el);
+ ldb_msg_remove_attr(msg, "systemFlags");
+
+ /* 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) {
systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
} else if (ldb_attr_cmp(objectclass->lDAPDisplayName, "siteLink") == 0
/* TODO: If parent object is site or subnet, also add (SYSTEM_FLAG_CONFIG_ALLOW_RENAME) */
if (el || systemFlags != 0) {
- samdb_msg_add_int(ldb, msg, msg, "systemFlags", systemFlags);
+ ret = samdb_msg_add_int(ldb, msg, msg, "systemFlags",
+ systemFlags);
+ 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;
}
}
- talloc_free(mem_ctx);
ret = ldb_msg_sanity_check(ldb, msg);
-
-
if (ret != LDB_SUCCESS) {
return ret;
}
ac->req->controls,
ac, oc_op_callback,
ac->req);
+ LDB_REQ_SET_LOCATION(add_req);
if (ret != LDB_SUCCESS) {
return ret;
}
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_message_element *objectclass_element;
struct ldb_message *msg;
- const struct dsdb_schema *schema = dsdb_get_schema(ldb);
- struct class_list *sorted, *current;
struct ldb_request *down_req;
struct oc_context *ac;
- TALLOC_CTX *mem_ctx;
- char *value;
+ bool oc_changes = false;
int ret;
ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_modify\n");
if (ldb_dn_is_special(req->op.mod.message->dn)) {
return ldb_next_request(module, req);
}
-
- /* Without schema, there isn't much to do here */
- if (!schema) {
- return ldb_next_request(module, req);
- }
/* As with the "real" AD we don't accept empty messages */
if (req->op.mod.message->num_elements == 0) {
ac = oc_init_context(module, req);
if (ac == NULL) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- /* If no part of this touches the objectClass, then we don't
- * need to make any changes. */
- objectclass_element = ldb_msg_find_element(req->op.mod.message, "objectClass");
-
- /* If the only operation is the deletion of the objectClass
- * then go on with just fixing the attribute case */
- if (!objectclass_element) {
- msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
- if (msg == NULL) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- ret = fix_check_attributes(ldb, schema, msg, req->operation);
- if (ret != LDB_SUCCESS) {
- return ret;
- }
-
- ret = ldb_build_mod_req(&down_req, ldb, ac,
- msg,
- req->controls,
- ac, oc_op_callback,
- req);
- if (ret != LDB_SUCCESS) {
- return ret;
- }
-
- /* go on with the call chain */
- return ldb_next_request(module, down_req);
+ return ldb_operr(ldb);
}
- switch (objectclass_element->flags & LDB_FLAG_MOD_MASK) {
- case LDB_FLAG_MOD_DELETE:
- if (objectclass_element->num_values == 0) {
- return LDB_ERR_OBJECT_CLASS_MODS_PROHIBITED;
- }
- break;
-
- case LDB_FLAG_MOD_REPLACE:
- mem_ctx = talloc_new(ac);
- if (mem_ctx == NULL) {
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
- if (msg == NULL) {
- talloc_free(mem_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- ret = fix_check_attributes(ldb, schema, msg, req->operation);
- if (ret != LDB_SUCCESS) {
- talloc_free(mem_ctx);
- return ret;
- }
-
- ret = objectclass_sort(module, schema, mem_ctx, objectclass_element, &sorted);
- if (ret != LDB_SUCCESS) {
- talloc_free(mem_ctx);
- return ret;
- }
-
- /* We must completely replace the existing objectClass entry,
- * because we need it sorted */
-
- ldb_msg_remove_attr(msg, "objectClass");
- ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
-
- 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) {
- /* copy the value as this string is on the schema
- * context and we can't rely on it not changing
- * before the operation is over */
- value = talloc_strdup(msg,
- current->objectclass->lDAPDisplayName);
- if (value == NULL) {
- ldb_oom(ldb);
- talloc_free(mem_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- ret = ldb_msg_add_string(msg, "objectClass", value);
- 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);
-
- ret = ldb_msg_sanity_check(ldb, msg);
- if (ret != LDB_SUCCESS) {
- return ret;
- }
-
- ret = ldb_build_mod_req(&down_req, ldb, ac,
- msg,
- req->controls,
- ac, oc_op_callback,
- req);
- if (ret != LDB_SUCCESS) {
- return ret;
- }
-
- /* go on with the call chain */
- return ldb_next_request(module, down_req);
+ /* Without schema, there isn't much to do here */
+ if (ac->schema == NULL) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
}
- /* This isn't the default branch of the switch, but a 'in any
- * other case'. When a delete isn't for all objectClasses for
- * example
- */
-
msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
if (msg == NULL) {
- ldb_oom(ldb);
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_operr(ldb);
}
- ret = fix_check_attributes(ldb, schema, msg, req->operation);
- if (ret != LDB_SUCCESS) {
- ldb_oom(ldb);
- return ret;
+ /* For now change everything except the objectclasses */
+
+ objectclass_element = ldb_msg_find_element(msg, "objectClass");
+ if (objectclass_element != NULL) {
+ ldb_msg_remove_attr(msg, "objectClass");
+ oc_changes = true;
}
ret = ldb_build_mod_req(&down_req, ldb, ac,
msg,
- req->controls,
- ac, oc_modify_callback,
+ req->controls, ac,
+ oc_changes ? oc_modify_callback : oc_op_callback,
req);
+ LDB_REQ_SET_LOCATION(down_req);
if (ret != LDB_SUCCESS) {
return ret;
}
static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
{
- struct ldb_context *ldb;
static const char * const attrs[] = { "objectClass", NULL };
+ struct ldb_context *ldb;
struct ldb_request *search_req;
struct oc_context *ac;
int ret;
return ldb_module_done(ac->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(ac->req, ares->referral);
+ }
+
if (ares->error != LDB_SUCCESS) {
return ldb_module_done(ac->req, ares->controls,
ares->response, ares->error);
talloc_free(ares);
- ret = ldb_build_search_req(&search_req, ldb, ac,
- ac->req->op.mod.message->dn, LDB_SCOPE_BASE,
+ /* this looks up the real existing object for fetching some important
+ * informations (objectclasses) */
+ ret = ldb_build_search_req(&search_req, ldb,
+ ac, ac->req->op.mod.message->dn,
+ LDB_SCOPE_BASE,
"(objectClass=*)",
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);
}
if (ret != LDB_SUCCESS) {
return ldb_module_done(ac->req, NULL, NULL, ret);
}
+
return LDB_SUCCESS;
}
static int objectclass_do_mod(struct oc_context *ac)
{
struct ldb_context *ldb;
- const struct dsdb_schema *schema;
struct ldb_request *mod_req;
char *value;
- struct ldb_message_element *objectclass_element;
+ 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, k;
+ bool found, replace = false;
int ret;
ldb = ldb_module_get_ctx(ac->module);
+ /* we should always have a valid entry when we enter here */
if (ac->search_res == NULL) {
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_operr(ldb);
}
- schema = dsdb_get_schema(ldb);
- mem_ctx = talloc_new(ac);
- if (mem_ctx == NULL) {
- return LDB_ERR_OPERATIONS_ERROR;
+ oc_el_entry = ldb_msg_find_element(ac->search_res->message,
+ "objectClass");
+ if (oc_el_entry == NULL) {
+ /* existing entry without a valid object class? */
+ return ldb_operr(ldb);
}
/* use a new message structure */
msg = ldb_msg_new(ac);
if (msg == NULL) {
- ldb_set_errstring(ldb,
- "objectclass: could not create new modify msg");
- talloc_free(mem_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_oom(ldb);
}
- /* This is now the objectClass list from the database */
- objectclass_element = ldb_msg_find_element(ac->search_res->message,
- "objectClass");
- if (!objectclass_element) {
- /* Where did it go? bail now... */
- talloc_free(mem_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- /* modify dn */
msg->dn = ac->req->op.mod.message->dn;
- ret = objectclass_sort(ac->module, schema, mem_ctx, objectclass_element, &sorted);
- if (ret != LDB_SUCCESS) {
- return ret;
+ mem_ctx = talloc_new(ac);
+ if (mem_ctx == NULL) {
+ return ldb_oom(ldb);
}
- /* We must completely replace the existing objectClass entry.
- * We could do a constrained add/del, but we are meant to be
- * in a transaction... */
+ /* 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;
+ }
- ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
- if (ret != LDB_SUCCESS) {
- ldb_set_errstring(ldb, "objectclass: could not clear objectclass in modify msg");
- 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) {
- ldb_oom(ldb);
- return LDB_ERR_OPERATIONS_ERROR;
+ 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_oom(ldb);
+ }
+ 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) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: cannot add a new top-most structural objectclass '%s'!",
+ objectclass->lDAPDisplayName);
+ 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);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ 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);
+ 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 (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;
+ }
+ }
+
+ /* Make sure that the top-most structural object class
+ * hasn't been deleted */
+ found = false;
+ for (i = 0; i < oc_el_entry->num_values; i++) {
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName,
+ (char *)oc_el_entry->values[i].data) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: cannot delete the top-most structural objectclass '%s'!",
+ objectclass->lDAPDisplayName);
+ 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);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ break;
}
- 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_change);
if (ret != LDB_SUCCESS) {
- ldb_set_errstring(ldb, "objectclass: could not re-add sorted objectclass to modify msg");
talloc_free(mem_ctx);
- return ret;
+ return ldb_oom(ldb);
}
- }
- ret = ldb_msg_sanity_check(ldb, msg);
- 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);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ldb,
+ "objectclass: could not re-add sorted objectclasses!");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ if (replace) {
+ /* Well, on replace we are nearly done: we have to test
+ * if the change and entry message element are identical
+ * ly. 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 */
+ talloc_free(mem_ctx);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_SUCCESS);
+ } else {
+ ldb_set_errstring(ldb,
+ "objectclass: the specified objectclasses are not exactly the same as on the entry!");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ }
+
+ /* Now we've applied all changes from "oc_el_change" to
+ * "oc_el_entry" therefore the new "oc_el_entry" will be
+ * "oc_el_change". */
+ oc_el_entry = oc_el_change;
}
+ talloc_free(mem_ctx);
+
+ /* 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) {
- talloc_free(mem_ctx);
return ret;
}
- talloc_free(mem_ctx);
- /* perform the modify */
return ldb_next_request(ac->module, mod_req);
}
static int objectclass_rename(struct ldb_module *module, struct ldb_request *req)
{
- static const char * const attrs[] = { NULL };
+ static const char * const attrs[] = { "objectClass", NULL };
struct ldb_context *ldb;
struct ldb_request *search_req;
struct oc_context *ac;
ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_rename\n");
- if (ldb_dn_is_special(req->op.rename.newdn)) { /* do not manipulate our control entries */
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.rename.newdn)) {
return ldb_next_request(module, req);
}
- /* Firstly ensure we are not trying to rename it to be a child of itself */
- if ((ldb_dn_compare_base(req->op.rename.olddn, req->op.rename.newdn) == 0)
- && (ldb_dn_compare(req->op.rename.olddn, req->op.rename.newdn) != 0)) {
- ldb_asprintf_errstring(ldb, "Cannot rename %s to be a child of itself",
- ldb_dn_get_linearized(req->op.rename.olddn));
- return LDB_ERR_UNWILLING_TO_PERFORM;
- }
-
ac = oc_init_context(module, req);
if (ac == NULL) {
- return LDB_ERR_OPERATIONS_ERROR;
+ return ldb_operr(ldb);
}
parent_dn = ldb_dn_get_parent(ac, req->op.rename.newdn);
if (parent_dn == NULL) {
- ldb_oom(ldb);
- return LDB_ERR_OPERATIONS_ERROR;
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, the parent DN does not exist!",
+ ldb_dn_get_linearized(req->op.rename.olddn));
+ return LDB_ERR_NO_SUCH_OBJECT;
}
- /*
- it makes a search request, looking for the parent DN to fix up the new DN
- to a standard one, at objectclass_do_rename()
- */
+ /* this looks up the parent object for fetching some important
+ * informations (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->step_fn = objectclass_do_rename;
return ldb_next_request(ac->module, search_req);
+}
+
+static int objectclass_do_rename2(struct oc_context *ac);
+
+static int objectclass_do_rename(struct oc_context *ac)
+{
+ static const char * const attrs[] = { "objectClass", NULL };
+ struct ldb_context *ldb;
+ struct ldb_request *search_req;
+ int ret;
+ ldb = ldb_module_get_ctx(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. */
+ if (ac->search_res == NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, parent does not exist!",
+ ldb_dn_get_linearized(ac->req->op.rename.olddn));
+ return LDB_ERR_OTHER;
+ }
+
+ /* now assign "search_res2" to the parent entry to have "search_res"
+ * free for another lookup */
+ ac->search_res2 = ac->search_res;
+ ac->search_res = NULL;
+
+ /* this looks up the real existing object for fetching some important
+ * informations (objectclasses) */
+ ret = ldb_build_search_req(&search_req, ldb,
+ ac, ac->req->op.rename.olddn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step_fn = objectclass_do_rename2;
+
+ return ldb_next_request(ac->module, search_req);
}
-static int objectclass_do_rename(struct oc_context *ac)
+static int objectclass_do_rename2(struct oc_context *ac)
{
struct ldb_context *ldb;
struct ldb_request *rename_req;
ldb = ldb_module_get_ctx(ac->module);
- /* Check we have a valid parent */
+ /* Check if we have a valid entry - this check is needed since
+ * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
if (ac->search_res == NULL) {
- ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, parent does not exist!",
- ldb_dn_get_linearized(ac->req->op.rename.newdn));
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, entry does not exist!",
+ ldb_dn_get_linearized(ac->req->op.rename.olddn));
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ if (ac->schema != NULL) {
+ struct ldb_message_element *oc_el_entry, *oc_el_parent;
+ const struct dsdb_class *objectclass;
+ 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");
+ if (oc_el_entry == NULL) {
+ /* existing entry without a valid object class? */
+ return ldb_operr(ldb);
+ }
+ objectclass = get_last_structural_class(ac->schema, oc_el_entry);
+ 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 (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: Invalid RDN '%s' for objectclass '%s'!",
+ rdn_name, objectclass->lDAPDisplayName);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ oc_el_parent = ldb_msg_find_element(ac->search_res2->message,
+ "objectClass");
+ if (oc_el_parent == NULL) {
+ /* existing entry without a valid object class? */
+ return ldb_operr(ldb);
+ }
+
+ for (i=0; allowed_class == false && i < oc_el_parent->num_values; i++) {
+ const struct dsdb_class *sclass;
+
+ sclass = dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+ &oc_el_parent->values[i]);
+ if (!sclass) {
+ /* We don't know this class? what is going on? */
+ continue;
+ }
+ for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
+ allowed_class = true;
+ break;
+ }
+ }
+ }
+
+ if (!allowed_class) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: structural objectClass %s is not a valid child class for %s",
+ objectclass->lDAPDisplayName, ldb_dn_get_linearized(ac->search_res2->message->dn));
+ return LDB_ERR_NAMING_VIOLATION;
+ }
+ }
+
+ /* Ensure we are not trying to rename it to be a child of itself */
+ if ((ldb_dn_compare_base(ac->req->op.rename.olddn,
+ ac->req->op.rename.newdn) == 0) &&
+ (ldb_dn_compare(ac->req->op.rename.olddn,
+ ac->req->op.rename.newdn) != 0)) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s to be a child of itself",
+ ldb_dn_get_linearized(ac->req->op.rename.olddn));
return LDB_ERR_UNWILLING_TO_PERFORM;
}
-
- /* Fix up the DN to be in the standard form,
- * taking particular care to match the parent DN */
- ret = fix_dn(ac,
+
+ /* Fix up the DN to be in the standard form, taking
+ * particular care to match the parent DN */
+ ret = fix_dn(ldb, ac,
ac->req->op.rename.newdn,
- ac->search_res->message->dn,
+ ac->search_res2->message->dn,
&fixed_dn);
if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "objectclass: Could not munge DN %s into normal form",
+ ldb_dn_get_linearized(ac->req->op.rename.newdn));
return ret;
- }
- /* TODO: Check this is a valid child to this parent,
- * by reading the allowedChildClasses and
- * allowedChildClasssesEffective attributes */
+ }
ret = ldb_build_rename_req(&rename_req, ldb, ac,
ac->req->op.rename.olddn, fixed_dn,
ac->req->controls,
ac, oc_op_callback,
ac->req);
+ LDB_REQ_SET_LOCATION(rename_req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(ac->module, rename_req);
}
+static int objectclass_do_delete(struct oc_context *ac);
+
+static int objectclass_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ static const char * const attrs[] = { "nCName", "objectClass",
+ "systemFlags",
+ "isCriticalSystemObject", NULL };
+ struct ldb_context *ldb;
+ struct ldb_request *search_req;
+ struct oc_context *ac;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_delete\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Bypass the constraint checks when we do have the "RELAX" control
+ * set. */
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID) != NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = oc_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* this looks up the entry object for fetching some important
+ * informations (object classes, system flags...) */
+ ret = ldb_build_search_req(&search_req, ldb,
+ ac, req->op.del.dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step_fn = objectclass_do_delete;
+
+ return ldb_next_request(ac->module, search_req);
+}
+
+static int objectclass_do_delete(struct oc_context *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_dn *dn;
+ int32_t systemFlags;
+ bool isCriticalSystemObject;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* Check if we have a valid entry - this check is needed since
+ * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
+ if (ac->search_res == NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, entry does not exist!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ /* DC's ntDSDSA object */
+ if (ldb_dn_compare(ac->req->op.del.dn, samdb_ntds_settings_dn(ldb)) == 0) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's the DC's ntDSDSA object!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* DC's rIDSet object */
+ /* Perform this check only when it does exist - this is needed in order
+ * to don't let existing provisions break. */
+ ret = samdb_rid_set_dn(ldb, ac, &dn);
+ if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) {
+ return ret;
+ }
+ if (ret == LDB_SUCCESS) {
+ if (ldb_dn_compare(ac->req->op.del.dn, dn) == 0) {
+ talloc_free(dn);
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's the DC's rIDSet object!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ talloc_free(dn);
+ }
+
+ /* 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)) {
+ 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 schema partition!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ talloc_free(dn);
+ }
+
+ /* systemFlags */
+
+ systemFlags = ldb_msg_find_attr_as_int(ac->search_res->message,
+ "systemFlags", 0);
+ if ((systemFlags & SYSTEM_FLAG_DISALLOW_DELETE) != 0) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it isn't permitted!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ 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) {
+ 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);
+}
+
static int objectclass_init(struct ldb_module *module)
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
int ret;
+
/* Init everything else */
ret = ldb_next_init(module);
if (ret != LDB_SUCCESS) {
/* 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 = {
- .name = "objectclass",
- .add = objectclass_add,
- .modify = objectclass_modify,
- .rename = objectclass_rename,
- .init_context = objectclass_init
+static const struct ldb_module_ops ldb_objectclass_module_ops = {
+ .name = "objectclass",
+ .add = objectclass_add,
+ .modify = objectclass_modify,
+ .rename = objectclass_rename,
+ .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);
+}