/* Unix SMB/CIFS mplementation. The module that handles the Schema checkings and dynamic attributes Copyright (C) Stefan Metzmacher 2007 Copyright (C) Andrew Bartlett 2009 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 the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "ldb_module.h" #include "dsdb/samdb/samdb.h" #include "librpc/gen_ndr/ndr_misc.h" #include "librpc/gen_ndr/ndr_drsuapi.h" #include "librpc/gen_ndr/ndr_drsblobs.h" #include "param/param.h" #include "dsdb/samdb/ldb_modules/util.h" static int generate_objectClasses(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema); static int generate_attributeTypes(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema); static int generate_dITContentRules(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema); static int generate_extendedAttributeInfo(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema); static int generate_extendedClassInfo(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema); static int generate_possibleInferiors(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema); static const struct { const char *attr; int (*fn)(struct ldb_context *, struct ldb_message *, const struct dsdb_schema *); bool aggregate; } generated_attrs[] = { { .attr = "objectClasses", .fn = generate_objectClasses, .aggregate = true, }, { .attr = "attributeTypes", .fn = generate_attributeTypes, .aggregate = true, }, { .attr = "dITContentRules", .fn = generate_dITContentRules, .aggregate = true, }, { .attr = "extendedAttributeInfo", .fn = generate_extendedAttributeInfo, .aggregate = true, }, { .attr = "extendedClassInfo", .fn = generate_extendedClassInfo, .aggregate = true, }, { .attr = "possibleInferiors", .fn = generate_possibleInferiors, .aggregate = false, } }; struct schema_data_private_data { struct ldb_dn *aggregate_dn; struct ldb_dn *schema_dn; }; struct schema_data_search_data { struct ldb_module *module; struct ldb_request *req; const struct dsdb_schema *schema; }; static int schema_data_init(struct ldb_module *module) { struct ldb_context *ldb; struct ldb_dn *schema_dn; int ret; struct schema_data_private_data *data; ret = ldb_next_init(module); if (ret != LDB_SUCCESS) { return ret; } ldb = ldb_module_get_ctx(module); schema_dn = ldb_get_schema_basedn(ldb); if (!schema_dn) { ldb_reset_err_string(ldb); ldb_debug(ldb, LDB_DEBUG_WARNING, "schema_data_init: no schema dn present: (skip schema loading)\n"); return LDB_SUCCESS; } data = talloc(module, struct schema_data_private_data); if (data == NULL) { return ldb_oom(ldb); } data->schema_dn = schema_dn; /* Used to check to see if this is a result on the CN=Aggregate schema */ data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data); if (!data->aggregate_dn) { ldb_asprintf_errstring(ldb, "schema_data_init: Could not build aggregate schema DN for schema in %s", ldb_dn_get_linearized(schema_dn)); return LDB_ERR_OPERATIONS_ERROR; } ldb_module_set_private(module, data); return LDB_SUCCESS; } static int schema_data_add(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct dsdb_schema *schema; const struct ldb_val *attributeID = NULL; const struct ldb_val *governsID = NULL; const char *oid_attr = NULL; const char *oid = NULL; struct ldb_dn *parent_dn = NULL; int cmp; WERROR status; bool rodc = false; int ret; ldb = ldb_module_get_ctx(module); /* special objects should always go through */ if (ldb_dn_is_special(req->op.add.message->dn)) { return ldb_next_request(module, req); } /* replicated update should always go through */ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) { return ldb_next_request(module, req); } schema = dsdb_get_schema(ldb, req); if (!schema) { return ldb_next_request(module, req); } if (schema->base_dn == NULL) { ldb_debug_set(ldb, LDB_DEBUG_FATAL, "schema_data_add: base_dn NULL\n"); return LDB_ERR_OPERATIONS_ERROR; } ret = samdb_rodc(ldb, &rodc); if (ret != LDB_SUCCESS) { DEBUG(4, (__location__ ": unable to tell if we are an RODC \n")); } if (!schema->fsmo.we_are_master && !rodc) { ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_add: we are not master: reject request\n"); return LDB_ERR_UNWILLING_TO_PERFORM; } if (!schema->fsmo.update_allowed && !rodc) { ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_add: updates are not allowed: reject request\n"); return LDB_ERR_UNWILLING_TO_PERFORM; } if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) { /* * the provision code needs to create * the schema root object. */ cmp = ldb_dn_compare(req->op.add.message->dn, schema->base_dn); if (cmp == 0) { return ldb_next_request(module, req); } } parent_dn = ldb_dn_get_parent(req, req->op.add.message->dn); if (!parent_dn) { return ldb_oom(ldb); } cmp = ldb_dn_compare(parent_dn, schema->base_dn); if (cmp != 0) { ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_add: no direct child :%s\n", ldb_dn_get_linearized(req->op.add.message->dn)); return LDB_ERR_UNWILLING_TO_PERFORM; } attributeID = ldb_msg_find_ldb_val(req->op.add.message, "attributeID"); governsID = ldb_msg_find_ldb_val(req->op.add.message, "governsID"); if (attributeID) { oid_attr = "attributeID"; oid = talloc_strndup(req, (const char *)attributeID->data, attributeID->length); } else if (governsID) { oid_attr = "governsID"; oid = talloc_strndup(req, (const char *)governsID->data, governsID->length); } else { return ldb_next_request(module, req); } if (!oid) { return ldb_oom(ldb); } status = dsdb_schema_pfm_find_oid(schema->prefixmap, oid, NULL); if (!W_ERROR_IS_OK(status)) { /* check for internal errors */ if (!W_ERROR_EQUAL(status, WERR_NOT_FOUND)) { ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_add: failed to map %s[%s]: %s\n", oid_attr, oid, win_errstr(status)); return LDB_ERR_UNWILLING_TO_PERFORM; } /* Update prefixMap and save it */ status = dsdb_create_prefix_mapping(ldb, schema, oid); if (!W_ERROR_IS_OK(status)) { ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_add: failed to create prefix mapping for %s[%s]: %s\n", oid_attr, oid, win_errstr(status)); return LDB_ERR_UNWILLING_TO_PERFORM; } } return ldb_next_request(module, req); } static int schema_data_modify(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct dsdb_schema *schema; int cmp; bool rodc = false; int ret; struct ldb_control *sd_propagation_control; ldb = ldb_module_get_ctx(module); /* special objects should always go through */ if (ldb_dn_is_special(req->op.mod.message->dn)) { return ldb_next_request(module, req); } /* replicated update should always go through */ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) { return ldb_next_request(module, req); } /* dbcheck should be able to fix things */ if (ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) { return ldb_next_request(module, req); } sd_propagation_control = ldb_request_get_control(req, DSDB_CONTROL_SEC_DESC_PROPAGATION_OID); if (sd_propagation_control != NULL) { if (req->op.mod.message->num_elements != 1) { return ldb_module_operr(module); } ret = strcmp(req->op.mod.message->elements[0].name, "nTSecurityDescriptor"); if (ret != 0) { return ldb_module_operr(module); } return ldb_next_request(module, req); } schema = dsdb_get_schema(ldb, req); if (!schema) { return ldb_next_request(module, req); } cmp = ldb_dn_compare(req->op.mod.message->dn, schema->base_dn); if (cmp == 0) { static const char * const constrained_attrs[] = { "schemaInfo", "prefixMap", "msDs-Schema-Extensions", "msDS-IntId", NULL }; size_t i; struct ldb_message_element *el; if (ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID)) { return ldb_next_request(module, req); } for (i=0; constrained_attrs[i]; i++) { el = ldb_msg_find_element(req->op.mod.message, constrained_attrs[i]); if (el == NULL) { continue; } ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_modify: reject update " "of attribute[%s]\n", constrained_attrs[i]); return LDB_ERR_CONSTRAINT_VIOLATION; } return ldb_next_request(module, req); } ret = samdb_rodc(ldb, &rodc); if (ret != LDB_SUCCESS) { DEBUG(4, (__location__ ": unable to tell if we are an RODC \n")); } if (!schema->fsmo.we_are_master && !rodc) { ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_modify: we are not master: reject request\n"); return LDB_ERR_UNWILLING_TO_PERFORM; } if (!schema->fsmo.update_allowed && !rodc) { ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_modify: updates are not allowed: reject request\n"); return LDB_ERR_UNWILLING_TO_PERFORM; } return ldb_next_request(module, req); } static int schema_data_del(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct dsdb_schema *schema; bool rodc = false; int ret; ldb = ldb_module_get_ctx(module); /* special objects should always go through */ if (ldb_dn_is_special(req->op.del.dn)) { return ldb_next_request(module, req); } /* replicated update should always go through */ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) { return ldb_next_request(module, req); } /* dbcheck should be able to fix things */ if (ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) { return ldb_next_request(module, req); } schema = dsdb_get_schema(ldb, req); if (!schema) { return ldb_next_request(module, req); } ret = samdb_rodc(ldb, &rodc); if (ret != LDB_SUCCESS) { DEBUG(4, (__location__ ": unable to tell if we are an RODC \n")); } if (!schema->fsmo.we_are_master && !rodc) { ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_modify: we are not master: reject request\n"); return LDB_ERR_UNWILLING_TO_PERFORM; } /* * normaly the DACL will prevent delete * with LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS * above us. */ ldb_debug_set(ldb, LDB_DEBUG_ERROR, "schema_data_del: delete is not allowed in the schema\n"); return LDB_ERR_UNWILLING_TO_PERFORM; } static int generate_objectClasses(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema) { const struct dsdb_class *sclass; int ret; for (sclass = schema->classes; sclass; sclass = sclass->next) { char *v = schema_class_to_description(msg, sclass); if (v == NULL) { return ldb_oom(ldb); } ret = ldb_msg_add_steal_string(msg, "objectClasses", v); if (ret != LDB_SUCCESS) { return ret; } } return LDB_SUCCESS; } static int generate_attributeTypes(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema) { const struct dsdb_attribute *attribute; int ret; for (attribute = schema->attributes; attribute; attribute = attribute->next) { char *v = schema_attribute_to_description(msg, attribute); if (v == NULL) { return ldb_oom(ldb); } ret = ldb_msg_add_steal_string(msg, "attributeTypes", v); if (ret != LDB_SUCCESS) { return ret; } } return LDB_SUCCESS; } static int generate_dITContentRules(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema) { const struct dsdb_class *sclass; int ret; for (sclass = schema->classes; sclass; sclass = sclass->next) { if (sclass->auxiliaryClass || sclass->systemAuxiliaryClass) { char *ditcontentrule = schema_class_to_dITContentRule(msg, sclass, schema); if (!ditcontentrule) { return ldb_oom(ldb); } ret = ldb_msg_add_steal_string(msg, "dITContentRules", ditcontentrule); if (ret != LDB_SUCCESS) { return ret; } } } return LDB_SUCCESS; } static int generate_extendedAttributeInfo(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema) { const struct dsdb_attribute *attribute; int ret; for (attribute = schema->attributes; attribute; attribute = attribute->next) { char *val = schema_attribute_to_extendedInfo(msg, attribute); if (!val) { return ldb_oom(ldb); } ret = ldb_msg_add_steal_string(msg, "extendedAttributeInfo", val); if (ret != LDB_SUCCESS) { return ret; } } return LDB_SUCCESS; } static int generate_extendedClassInfo(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema) { const struct dsdb_class *sclass; int ret; for (sclass = schema->classes; sclass; sclass = sclass->next) { char *val = schema_class_to_extendedInfo(msg, sclass); if (!val) { return ldb_oom(ldb); } ret = ldb_msg_add_steal_string(msg, "extendedClassInfo", val); if (ret != LDB_SUCCESS) { return ret; } } return LDB_SUCCESS; } static int generate_possibleInferiors(struct ldb_context *ldb, struct ldb_message *msg, const struct dsdb_schema *schema) { struct ldb_dn *dn = msg->dn; unsigned int i; int ret; const char *first_component_name = ldb_dn_get_component_name(dn, 0); const struct ldb_val *first_component_val; const struct dsdb_class *schema_class; const char **possibleInferiors; if (strcasecmp(first_component_name, "cn") != 0) { return LDB_SUCCESS; } first_component_val = ldb_dn_get_component_val(dn, 0); schema_class = dsdb_class_by_cn_ldb_val(schema, first_component_val); if (schema_class == NULL) { return LDB_SUCCESS; } possibleInferiors = schema_class->possibleInferiors; if (possibleInferiors == NULL) { return LDB_SUCCESS; } for (i=0;possibleInferiors[i];i++) { char *v = talloc_strdup(msg, possibleInferiors[i]); if (v == NULL) { return ldb_oom(ldb); } ret = ldb_msg_add_steal_string(msg, "possibleInferiors", v); if (ret != LDB_SUCCESS) { return ret; } } return LDB_SUCCESS; } /* Add objectClasses, attributeTypes and dITContentRules from the schema object (they are not stored in the database) */ static int schema_data_search_callback(struct ldb_request *req, struct ldb_reply *ares) { struct ldb_context *ldb; struct schema_data_search_data *ac; struct schema_data_private_data *mc; unsigned int i; int ret; ac = talloc_get_type(req->context, struct schema_data_search_data); mc = talloc_get_type(ldb_module_get_private(ac->module), struct schema_data_private_data); ldb = ldb_module_get_ctx(ac->module); if (!ares) { return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); } if (ares->error != LDB_SUCCESS) { return ldb_module_done(ac->req, ares->controls, ares->response, ares->error); } /* Only entries are interesting, and we handle the case of the parent seperatly */ switch (ares->type) { case LDB_REPLY_ENTRY: if (ldb_dn_compare(ares->message->dn, mc->aggregate_dn) == 0) { for (i=0; i < ARRAY_SIZE(generated_attrs); i++) { if (generated_attrs[i].aggregate && ldb_attr_in_list(ac->req->op.search.attrs, generated_attrs[i].attr)) { ret = generated_attrs[i].fn(ldb, ares->message, ac->schema); if (ret != LDB_SUCCESS) { return ret; } } } } else if ((ldb_dn_compare_base(mc->schema_dn, ares->message->dn) == 0) && (ldb_dn_compare(mc->schema_dn, ares->message->dn) != 0)) { for (i=0; i < ARRAY_SIZE(generated_attrs); i++) { if (!generated_attrs[i].aggregate && ldb_attr_in_list(ac->req->op.search.attrs, generated_attrs[i].attr)) { ret = generated_attrs[i].fn(ldb, ares->message, ac->schema); if (ret != LDB_SUCCESS) { return ret; } } } } return ldb_module_send_entry(ac->req, ares->message, ares->controls); case LDB_REPLY_REFERRAL: return ldb_module_send_referral(ac->req, ares->referral); case LDB_REPLY_DONE: return ldb_module_done(ac->req, ares->controls, ares->response, ares->error); } return LDB_SUCCESS; } /* search */ static int schema_data_search(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb = ldb_module_get_ctx(module); unsigned int i; int ret; struct schema_data_search_data *search_context; struct ldb_request *down_req; const struct dsdb_schema *schema; if (!ldb_module_get_private(module)) { /* If there is no module data, there is little we can do */ return ldb_next_request(module, req); } /* The schema manipulation does not apply to special DNs */ if (ldb_dn_is_special(req->op.search.base)) { return ldb_next_request(module, req); } for (i=0; i < ARRAY_SIZE(generated_attrs); i++) { if (ldb_attr_in_list(req->op.search.attrs, generated_attrs[i].attr)) { break; } } if (i == ARRAY_SIZE(generated_attrs)) { /* No request for a generated attr found, nothing to * see here, move along... */ return ldb_next_request(module, req); } schema = dsdb_get_schema(ldb, NULL); if (!schema || !ldb_module_get_private(module)) { /* If there is no schema, there is little we can do */ return ldb_next_request(module, req); } search_context = talloc(req, struct schema_data_search_data); if (!search_context) { return ldb_oom(ldb); } search_context->module = module; search_context->req = req; search_context->schema = talloc_reference(search_context, schema); if (!search_context->schema) { return ldb_oom(ldb); } ret = ldb_build_search_req_ex(&down_req, ldb, search_context, req->op.search.base, req->op.search.scope, req->op.search.tree, req->op.search.attrs, req->controls, search_context, schema_data_search_callback, req); LDB_REQ_SET_LOCATION(down_req); if (ret != LDB_SUCCESS) { return ldb_operr(ldb); } return ldb_next_request(module, down_req); } static const struct ldb_module_ops ldb_schema_data_module_ops = { .name = "schema_data", .init_context = schema_data_init, .add = schema_data_add, .modify = schema_data_modify, .del = schema_data_del, .search = schema_data_search }; int ldb_schema_data_module_init(const char *version) { LDB_MODULE_CHECK_VERSION(version); return ldb_register_module(&ldb_schema_data_module_ops); }