From a81d0ac43f151e0dae2c7439f94b957118a73200 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 24 Mar 2014 17:15:19 +1300 Subject: [PATCH] dsdb: Do checks for invalid renames in samldb, before repl_meta_data This ensures that conflict objects can be created in CN=System, and that we do not stop replication just because some other DC allowed a rename we do not like. This is achived by doing the work in the samldb module, which is above repl_meta_data in the stack. Andrew Bartlett Change-Id: I8c1a7d3e0fbd5a470cf1326cc055044ca885f7d9 Signed-off-by: Andrew Bartlett Reviewed-by: Guenter Kukkukk Tested-by: Guenter Kukkukk (cherry picked from commit d3cd9f1575af18a6765a6b6a31811c9976f9c11e) --- source4/dsdb/samdb/ldb_modules/samldb.c | 261 +++++++++++++++++++++++- source4/dsdb/samdb/ldb_modules/subtree_rename.c | 248 +--------------------- 2 files changed, 271 insertions(+), 238 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 603370f..a63a44e 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -1,7 +1,7 @@ /* SAM ldb module - Copyright (C) Andrew Bartlett 2005 + Copyright (C) Andrew Bartlett 2005-2014 Copyright (C) Simo Sorce 2004-2008 Copyright (C) Matthias Dieter Wallnöfer 2009-2011 Copyright (C) Matthieu Patou 2012 @@ -2595,6 +2595,264 @@ static int samldb_delete(struct ldb_module *module, struct ldb_request *req) return ldb_next_request(module, req); } +/* rename */ + +static int check_rename_constraints(struct ldb_message *msg, + struct samldb_ctx *ac, + struct ldb_dn *olddn, struct ldb_dn *newdn) +{ + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + struct ldb_dn *dn1, *dn2, *nc_root; + int32_t systemFlags; + bool move_op = false; + bool rename_op = false; + int ret; + + /* Skip the checks if old and new DN are the same, or if we have the + * relax control specified or if the returned objects is already + * deleted and needs only to be moved for consistency. */ + + if (ldb_dn_compare(olddn, newdn) == 0) { + return LDB_SUCCESS; + } + if (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) != NULL) { + return LDB_SUCCESS; + } + if (ldb_msg_find_attr_as_bool(msg, "isDeleted", false)) { + return LDB_SUCCESS; + } + + /* Objects under CN=System */ + + dn1 = ldb_dn_copy(ac, ldb_get_default_basedn(ldb)); + if (dn1 == NULL) return ldb_oom(ldb); + + if ( ! ldb_dn_add_child_fmt(dn1, "CN=System")) { + talloc_free(dn1); + return LDB_ERR_OPERATIONS_ERROR; + } + + if ((ldb_dn_compare_base(dn1, olddn) == 0) && + (ldb_dn_compare_base(dn1, newdn) != 0)) { + talloc_free(dn1); + ldb_asprintf_errstring(ldb, + "subtree_rename: Cannot move/rename %s. Objects under CN=System have to stay under it!", + ldb_dn_get_linearized(olddn)); + return LDB_ERR_OTHER; + } + + talloc_free(dn1); + + /* LSA objects */ + + if ((samdb_find_attribute(ldb, msg, "objectClass", "secret") != NULL) || + (samdb_find_attribute(ldb, msg, "objectClass", "trustedDomain") != NULL)) { + ldb_asprintf_errstring(ldb, + "subtree_rename: Cannot move/rename %s. It's an LSA-specific object!", + ldb_dn_get_linearized(olddn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + /* systemFlags */ + + dn1 = ldb_dn_get_parent(ac, olddn); + if (dn1 == NULL) return ldb_oom(ldb); + dn2 = ldb_dn_get_parent(ac, newdn); + if (dn2 == NULL) return ldb_oom(ldb); + + if (ldb_dn_compare(dn1, dn2) == 0) { + rename_op = true; + } else { + move_op = true; + } + + talloc_free(dn1); + talloc_free(dn2); + + systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0); + + /* Fetch name context */ + + ret = dsdb_find_nc_root(ldb, ac, olddn, &nc_root); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0) { + if (move_op) { + ldb_asprintf_errstring(ldb, + "subtree_rename: Cannot move %s within schema partition", + ldb_dn_get_linearized(olddn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + if (rename_op && + (systemFlags & SYSTEM_FLAG_SCHEMA_BASE_OBJECT) != 0) { + ldb_asprintf_errstring(ldb, + "subtree_rename: Cannot rename %s within schema partition", + ldb_dn_get_linearized(olddn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } else if (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) { + if (move_op && + (systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_MOVE) == 0) { + /* Here we have to do more: control the + * "ALLOW_LIMITED_MOVE" flag. This means that the + * grand-grand-parents of two objects have to be equal + * in order to perform the move (this is used for + * moving "server" objects in the "sites" container). */ + bool limited_move = + systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE; + + if (limited_move) { + dn1 = ldb_dn_copy(ac, olddn); + if (dn1 == NULL) return ldb_oom(ldb); + dn2 = ldb_dn_copy(ac, newdn); + if (dn2 == NULL) return ldb_oom(ldb); + + limited_move &= ldb_dn_remove_child_components(dn1, 3); + limited_move &= ldb_dn_remove_child_components(dn2, 3); + limited_move &= ldb_dn_compare(dn1, dn2) == 0; + + talloc_free(dn1); + talloc_free(dn2); + } + + if (!limited_move) { + ldb_asprintf_errstring(ldb, + "subtree_rename: Cannot move %s to %s in config partition", + ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } + if (rename_op && + (systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_RENAME) == 0) { + ldb_asprintf_errstring(ldb, + "subtree_rename: Cannot rename %s to %s within config partition", + ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } else if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) { + if (move_op && + (systemFlags & SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE) != 0) { + ldb_asprintf_errstring(ldb, + "subtree_rename: Cannot move %s to %s - DISALLOW_MOVE set", + ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + if (rename_op && + (systemFlags & SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME) != 0) { + ldb_asprintf_errstring(ldb, + "subtree_rename: Cannot rename %s to %s - DISALLOW_RENAME set", + ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } + + talloc_free(nc_root); + + return LDB_SUCCESS; +} + + +static int samldb_rename_search_base_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct ldb_request *rename_req; + struct samldb_ctx *ac; + int ret; + + ac = talloc_get_type(req->context, struct samldb_ctx); + + 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); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + /* + * This is the root entry of the originating move + * respectively rename request. It has been already + * stored in the list using "subtree_rename_search()". + * Only this one is subject to constraint checking. + */ + ret = check_rename_constraints(ares->message, ac, + ac->req->op.rename.olddn, + ac->req->op.rename.newdn); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, + ret); + } + break; + + case LDB_REPLY_REFERRAL: + /* ignore */ + break; + + case LDB_REPLY_DONE: + + /* + * Great, no problem with the rename, so go ahead as + * if we never were here + */ + ret = ldb_next_request(ac->module, ac->req); + talloc_free(ares); + return ret; + } + + talloc_free(ares); + return LDB_SUCCESS; +} + + +/* rename */ +static int samldb_rename(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + static const char * const attrs[] = { "objectClass", "systemFlags", + "isDeleted", NULL }; + struct ldb_request *search_req; + struct samldb_ctx *ac; + int ret; + + if (ldb_dn_is_special(req->op.rename.olddn)) { /* do not manipulate our control entries */ + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + + ac = samldb_ctx_init(module, req); + if (!ac) { + return ldb_oom(ldb); + } + + ret = ldb_build_search_req(&search_req, ldb, ac, + req->op.rename.olddn, + LDB_SCOPE_BASE, + "(objectClass=*)", + attrs, + NULL, + ac, + samldb_rename_search_base_callback, + req); + LDB_REQ_SET_LOCATION(search_req); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID, + true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + + return ldb_next_request(ac->module, search_req); +} + /* extended */ static int samldb_extended_allocate_rid_pool(struct ldb_module *module, struct ldb_request *req) @@ -2634,6 +2892,7 @@ static const struct ldb_module_ops ldb_samldb_module_ops = { .add = samldb_add, .modify = samldb_modify, .del = samldb_delete, + .rename = samldb_rename, .extended = samldb_extended }; diff --git a/source4/dsdb/samdb/ldb_modules/subtree_rename.c b/source4/dsdb/samdb/ldb_modules/subtree_rename.c index d26dabe..b9ecb3f 100644 --- a/source4/dsdb/samdb/ldb_modules/subtree_rename.c +++ b/source4/dsdb/samdb/ldb_modules/subtree_rename.c @@ -91,166 +91,11 @@ static int subtree_rename_callback(struct ldb_request *req, return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS); } -static int check_constraints(struct ldb_message *msg, - struct subtree_rename_context *ac, - struct ldb_dn *olddn, struct ldb_dn *newdn) -{ - struct ldb_context *ldb = ldb_module_get_ctx(ac->module); - struct ldb_dn *dn1, *dn2, *nc_root; - int32_t systemFlags; - bool move_op = false; - bool rename_op = false; - int ret; - - /* Skip the checks if old and new DN are the same, or if we have the - * relax control specified or if the returned objects is already - * deleted and needs only to be moved for consistency. */ - - if (ldb_dn_compare(olddn, newdn) == 0) { - return LDB_SUCCESS; - } - if (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) != NULL) { - return LDB_SUCCESS; - } - if (ldb_msg_find_attr_as_bool(msg, "isDeleted", false)) { - return LDB_SUCCESS; - } - - /* Objects under CN=System */ - - dn1 = ldb_dn_copy(ac, ldb_get_default_basedn(ldb)); - if (dn1 == NULL) return ldb_oom(ldb); - - if ( ! ldb_dn_add_child_fmt(dn1, "CN=System")) { - talloc_free(dn1); - return LDB_ERR_OPERATIONS_ERROR; - } - - if ((ldb_dn_compare_base(dn1, olddn) == 0) && - (ldb_dn_compare_base(dn1, newdn) != 0)) { - talloc_free(dn1); - ldb_asprintf_errstring(ldb, - "subtree_rename: Cannot move/rename %s. Objects under CN=System have to stay under it!", - ldb_dn_get_linearized(olddn)); - return LDB_ERR_OTHER; - } - - talloc_free(dn1); - - /* LSA objects */ - - if ((samdb_find_attribute(ldb, msg, "objectClass", "secret") != NULL) || - (samdb_find_attribute(ldb, msg, "objectClass", "trustedDomain") != NULL)) { - ldb_asprintf_errstring(ldb, - "subtree_rename: Cannot move/rename %s. It's an LSA-specific object!", - ldb_dn_get_linearized(olddn)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - - /* systemFlags */ - - dn1 = ldb_dn_get_parent(ac, olddn); - if (dn1 == NULL) return ldb_oom(ldb); - dn2 = ldb_dn_get_parent(ac, newdn); - if (dn2 == NULL) return ldb_oom(ldb); - - if (ldb_dn_compare(dn1, dn2) == 0) { - rename_op = true; - } else { - move_op = true; - } - - talloc_free(dn1); - talloc_free(dn2); - - systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0); - - /* Fetch name context */ - - ret = dsdb_find_nc_root(ldb, ac, olddn, &nc_root); - if (ret != LDB_SUCCESS) { - return ret; - } - - if (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0) { - if (move_op) { - ldb_asprintf_errstring(ldb, - "subtree_rename: Cannot move %s within schema partition", - ldb_dn_get_linearized(olddn)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - if (rename_op && - (systemFlags & SYSTEM_FLAG_SCHEMA_BASE_OBJECT) != 0) { - ldb_asprintf_errstring(ldb, - "subtree_rename: Cannot rename %s within schema partition", - ldb_dn_get_linearized(olddn)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - } else if (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) { - if (move_op && - (systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_MOVE) == 0) { - /* Here we have to do more: control the - * "ALLOW_LIMITED_MOVE" flag. This means that the - * grand-grand-parents of two objects have to be equal - * in order to perform the move (this is used for - * moving "server" objects in the "sites" container). */ - bool limited_move = - systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE; - - if (limited_move) { - dn1 = ldb_dn_copy(ac, olddn); - if (dn1 == NULL) return ldb_oom(ldb); - dn2 = ldb_dn_copy(ac, newdn); - if (dn2 == NULL) return ldb_oom(ldb); - - limited_move &= ldb_dn_remove_child_components(dn1, 3); - limited_move &= ldb_dn_remove_child_components(dn2, 3); - limited_move &= ldb_dn_compare(dn1, dn2) == 0; - - talloc_free(dn1); - talloc_free(dn2); - } - - if (!limited_move) { - ldb_asprintf_errstring(ldb, - "subtree_rename: Cannot move %s to %s in config partition", - ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - } - if (rename_op && - (systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_RENAME) == 0) { - ldb_asprintf_errstring(ldb, - "subtree_rename: Cannot rename %s to %s within config partition", - ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - } else if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) { - if (move_op && - (systemFlags & SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE) != 0) { - ldb_asprintf_errstring(ldb, - "subtree_rename: Cannot move %s to %s - DISALLOW_MOVE set", - ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - if (rename_op && - (systemFlags & SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME) != 0) { - ldb_asprintf_errstring(ldb, - "subtree_rename: Cannot rename %s to %s - DISALLOW_RENAME set", - ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - } - - talloc_free(nc_root); - - return LDB_SUCCESS; -} - static int subtree_rename_search_onelevel_callback(struct ldb_request *req, struct ldb_reply *ares) { struct subtree_rename_context *ac; + struct ldb_request *rename_req; int ret; ac = talloc_get_type(req->context, struct subtree_rename_context); @@ -298,7 +143,7 @@ static int subtree_rename_search_onelevel_callback(struct ldb_request *req, case LDB_REPLY_DONE: - ret = ldb_build_rename_req(&req, ldb_module_get_ctx(ac->module), ac, + ret = ldb_build_rename_req(&rename_req, ldb_module_get_ctx(ac->module), ac, ac->req->op.rename.olddn, ac->req->op.rename.newdn, ac->req->controls, @@ -310,88 +155,17 @@ static int subtree_rename_search_onelevel_callback(struct ldb_request *req, } talloc_free(ares); - return ldb_next_request(ac->module, req); + return ldb_next_request(ac->module, rename_req); } return LDB_SUCCESS; } -static int subtree_rename_search_base_callback(struct ldb_request *req, - struct ldb_reply *ares) -{ - struct ldb_request *search_req; - struct subtree_rename_context *ac; - static const char * const no_attrs[] = {NULL}; - int ret; - - ac = talloc_get_type(req->context, struct subtree_rename_context); - - 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); - } - - switch (ares->type) { - case LDB_REPLY_ENTRY: - /* - * This is the root entry of the originating move - * respectively rename request. It has been already - * stored in the list using "subtree_rename_search()". - * Only this one is subject to constraint checking. - */ - ret = check_constraints(ares->message, ac, - ac->req->op.rename.olddn, - ac->req->op.rename.newdn); - if (ret != LDB_SUCCESS) { - return ldb_module_done(ac->req, NULL, NULL, - ret); - } - break; - - case LDB_REPLY_REFERRAL: - /* ignore */ - break; - - case LDB_REPLY_DONE: - - ret = ldb_build_search_req(&search_req, ldb_module_get_ctx(ac->module), ac, - ac->req->op.rename.olddn, - LDB_SCOPE_ONELEVEL, - "(objectClass=*)", - no_attrs, - NULL, - ac, - subtree_rename_search_onelevel_callback, - req); - LDB_REQ_SET_LOCATION(search_req); - if (ret != LDB_SUCCESS) { - return ret; - } - - ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID, - true, NULL); - if (ret != LDB_SUCCESS) { - return ret; - } - - talloc_free(ares); - return ldb_next_request(ac->module, search_req); - } - - talloc_free(ares); - return LDB_SUCCESS; -} - /* rename */ static int subtree_rename(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; - static const char * const attrs[] = { "objectClass", "systemFlags", - "isDeleted", NULL }; + static const char * const no_attrs[] = {NULL}; struct ldb_request *search_req; struct subtree_rename_context *ac; int ret; @@ -416,14 +190,14 @@ static int subtree_rename(struct ldb_module *module, struct ldb_request *req) return ldb_oom(ldb); } - ret = ldb_build_search_req(&search_req, ldb, ac, - req->op.rename.olddn, - LDB_SCOPE_BASE, + ret = ldb_build_search_req(&search_req, ldb_module_get_ctx(ac->module), ac, + ac->req->op.rename.olddn, + LDB_SCOPE_ONELEVEL, "(objectClass=*)", - attrs, + no_attrs, NULL, - ac, - subtree_rename_search_base_callback, + ac, + subtree_rename_search_onelevel_callback, req); LDB_REQ_SET_LOCATION(search_req); if (ret != LDB_SUCCESS) { @@ -436,7 +210,7 @@ static int subtree_rename(struct ldb_module *module, struct ldb_request *req) return ret; } - return ldb_next_request(module, search_req); + return ldb_next_request(ac->module, search_req); } static const struct ldb_module_ops ldb_subtree_rename_module_ops = { -- 1.9.1