/* ldb database library Copyright (C) Simo Sorce 2006-2008 Copyright (C) Andrew Bartlett 2005-2007 Copyright (C) Nadezhda Ivanova 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 . */ /* * Name: ldb * * Component: DS Security descriptor module * * Description: * - Calculate the security descriptor of a newly created object * - Perform sd recalculation on a move operation * - Handle sd modification invariants * * Author: Nadezhda Ivanova */ #include "includes.h" #include #include "util/dlinklist.h" #include "dsdb/samdb/samdb.h" #include "librpc/ndr/libndr.h" #include "librpc/gen_ndr/ndr_security.h" #include "libcli/security/security.h" #include "auth/auth.h" #include "param/param.h" #include "dsdb/samdb/ldb_modules/util.h" #include "lib/util/util_tdb.h" #include "lib/dbwrap/dbwrap.h" #include "lib/dbwrap/dbwrap_rbt.h" struct descriptor_changes { struct descriptor_changes *prev, *next; struct ldb_dn *nc_root; struct GUID guid; struct GUID parent_guid; bool force_self; bool force_children; struct ldb_dn *stopped_dn; size_t ref_count; size_t sort_count; }; struct descriptor_transaction { TALLOC_CTX *mem; struct { /* * We used to have a list of changes, appended with each * DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID operation. * * But the main problem was that a replication * cycle (mainly the initial replication) calls * DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID for the * same object[GUID] more than once. With * DRSUAPI_DRS_GET_TGT we'll get the naming * context head object and other top level * containers, every often. * * It means we'll process objects more * than once and waste a lot of time * doing the same work again and again. * * We use an objectGUID based map in order to * avoid registering objects more than once. * In an domain with 22000 object it can * reduce the work from 4 hours down to ~ 3.5 minutes. */ struct descriptor_changes *list; struct db_context *map; size_t num_registrations; size_t num_registered; size_t num_toplevel; size_t num_processed; } changes; struct { struct db_context *map; size_t num_processed; size_t num_skipped; } objects; }; struct descriptor_data { struct descriptor_transaction transaction; }; struct descriptor_context { struct ldb_module *module; struct ldb_request *req; struct ldb_message *msg; struct ldb_reply *search_res; struct ldb_reply *search_oc_res; struct ldb_val *parentsd_val; struct ldb_message_element *sd_element; struct ldb_val *sd_val; uint32_t sd_flags; int (*step_fn)(struct descriptor_context *); }; static struct dom_sid *get_default_ag(TALLOC_CTX *mem_ctx, struct ldb_dn *dn, const struct security_token *token, struct ldb_context *ldb) { TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); const struct dom_sid *domain_sid = samdb_domain_sid(ldb); struct dom_sid *da_sid = dom_sid_add_rid(tmp_ctx, domain_sid, DOMAIN_RID_ADMINS); struct dom_sid *ea_sid = dom_sid_add_rid(tmp_ctx, domain_sid, DOMAIN_RID_ENTERPRISE_ADMINS); struct dom_sid *sa_sid = dom_sid_add_rid(tmp_ctx, domain_sid, DOMAIN_RID_SCHEMA_ADMINS); struct dom_sid *dag_sid; struct ldb_dn *nc_root; int ret; ret = dsdb_find_nc_root(ldb, tmp_ctx, dn, &nc_root); if (ret != LDB_SUCCESS) { talloc_free(tmp_ctx); return NULL; } if (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0) { if (security_token_has_sid(token, sa_sid)) { dag_sid = dom_sid_dup(mem_ctx, sa_sid); } else if (security_token_has_sid(token, ea_sid)) { dag_sid = dom_sid_dup(mem_ctx, ea_sid); } else if (security_token_has_sid(token, da_sid)) { dag_sid = dom_sid_dup(mem_ctx, da_sid); } else if (security_token_is_system(token)) { dag_sid = dom_sid_dup(mem_ctx, sa_sid); } else { dag_sid = NULL; } } else if (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) { if (security_token_has_sid(token, ea_sid)) { dag_sid = dom_sid_dup(mem_ctx, ea_sid); } else if (security_token_has_sid(token, da_sid)) { dag_sid = dom_sid_dup(mem_ctx, da_sid); } else if (security_token_is_system(token)) { dag_sid = dom_sid_dup(mem_ctx, ea_sid); } else { dag_sid = NULL; } } else if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) { if (security_token_has_sid(token, da_sid)) { dag_sid = dom_sid_dup(mem_ctx, da_sid); } else if (security_token_has_sid(token, ea_sid)) { dag_sid = dom_sid_dup(mem_ctx, ea_sid); } else if (security_token_is_system(token)) { dag_sid = dom_sid_dup(mem_ctx, da_sid); } else { dag_sid = NULL; } } else { dag_sid = NULL; } talloc_free(tmp_ctx); return dag_sid; } static struct security_descriptor *get_sd_unpacked(struct ldb_module *module, TALLOC_CTX *mem_ctx, const struct dsdb_class *objectclass) { struct ldb_context *ldb = ldb_module_get_ctx(module); struct security_descriptor *sd; const struct dom_sid *domain_sid = samdb_domain_sid(ldb); if (!objectclass->defaultSecurityDescriptor || !domain_sid) { return NULL; } sd = sddl_decode(mem_ctx, objectclass->defaultSecurityDescriptor, domain_sid); return sd; } static struct dom_sid *get_default_group(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct dom_sid *dag) { /* * This depends on the function level of the DC * which is 2008R2 in our case. Which means it is * higher than 2003 and we should use the * "default administrator group" also as owning group. * * This matches dcpromo for a 2003 domain * on a Windows 2008R2 DC. */ return dag; } static struct security_descriptor *descr_handle_sd_flags(TALLOC_CTX *mem_ctx, struct security_descriptor *new_sd, struct security_descriptor *old_sd, uint32_t sd_flags) { struct security_descriptor *final_sd; /* if there is no control or control == 0 modify everything */ if (!sd_flags) { return new_sd; } final_sd = talloc_zero(mem_ctx, struct security_descriptor); final_sd->revision = SECURITY_DESCRIPTOR_REVISION_1; final_sd->type = SEC_DESC_SELF_RELATIVE; if (sd_flags & (SECINFO_OWNER)) { if (new_sd->owner_sid) { final_sd->owner_sid = talloc_memdup(mem_ctx, new_sd->owner_sid, sizeof(struct dom_sid)); } final_sd->type |= new_sd->type & SEC_DESC_OWNER_DEFAULTED; } else if (old_sd) { if (old_sd->owner_sid) { final_sd->owner_sid = talloc_memdup(mem_ctx, old_sd->owner_sid, sizeof(struct dom_sid)); } final_sd->type |= old_sd->type & SEC_DESC_OWNER_DEFAULTED; } if (sd_flags & (SECINFO_GROUP)) { if (new_sd->group_sid) { final_sd->group_sid = talloc_memdup(mem_ctx, new_sd->group_sid, sizeof(struct dom_sid)); } final_sd->type |= new_sd->type & SEC_DESC_GROUP_DEFAULTED; } else if (old_sd) { if (old_sd->group_sid) { final_sd->group_sid = talloc_memdup(mem_ctx, old_sd->group_sid, sizeof(struct dom_sid)); } final_sd->type |= old_sd->type & SEC_DESC_GROUP_DEFAULTED; } if (sd_flags & (SECINFO_SACL)) { final_sd->sacl = security_acl_dup(mem_ctx,new_sd->sacl); final_sd->type |= new_sd->type & (SEC_DESC_SACL_PRESENT | SEC_DESC_SACL_DEFAULTED|SEC_DESC_SACL_AUTO_INHERIT_REQ | SEC_DESC_SACL_AUTO_INHERITED|SEC_DESC_SACL_PROTECTED | SEC_DESC_SERVER_SECURITY); } else if (old_sd && old_sd->sacl) { final_sd->sacl = security_acl_dup(mem_ctx,old_sd->sacl); final_sd->type |= old_sd->type & (SEC_DESC_SACL_PRESENT | SEC_DESC_SACL_DEFAULTED|SEC_DESC_SACL_AUTO_INHERIT_REQ | SEC_DESC_SACL_AUTO_INHERITED|SEC_DESC_SACL_PROTECTED | SEC_DESC_SERVER_SECURITY); } if (sd_flags & (SECINFO_DACL)) { final_sd->dacl = security_acl_dup(mem_ctx,new_sd->dacl); final_sd->type |= new_sd->type & (SEC_DESC_DACL_PRESENT | SEC_DESC_DACL_DEFAULTED|SEC_DESC_DACL_AUTO_INHERIT_REQ | SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_PROTECTED | SEC_DESC_DACL_TRUSTED); } else if (old_sd && old_sd->dacl) { final_sd->dacl = security_acl_dup(mem_ctx,old_sd->dacl); final_sd->type |= old_sd->type & (SEC_DESC_DACL_PRESENT | SEC_DESC_DACL_DEFAULTED|SEC_DESC_DACL_AUTO_INHERIT_REQ | SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_PROTECTED | SEC_DESC_DACL_TRUSTED); } /* not so sure about this */ final_sd->type |= new_sd->type & SEC_DESC_RM_CONTROL_VALID; return final_sd; } static struct security_descriptor *get_new_descriptor_nonlinear(struct ldb_module *module, struct ldb_dn *dn, TALLOC_CTX *mem_ctx, const struct dsdb_class *objectclass, const struct ldb_val *parent, const struct ldb_val *object, const struct ldb_val *old_sd, uint32_t sd_flags) { struct security_descriptor *user_descriptor = NULL, *parent_descriptor = NULL; struct security_descriptor *old_descriptor = NULL; struct security_descriptor *new_sd, *final_sd; enum ndr_err_code ndr_err; struct ldb_context *ldb = ldb_module_get_ctx(module); struct auth_session_info *session_info = ldb_get_opaque(ldb, DSDB_SESSION_INFO); const struct dom_sid *domain_sid = samdb_domain_sid(ldb); struct dom_sid *default_owner; struct dom_sid *default_group; struct security_descriptor *default_descriptor = NULL; struct GUID *object_list = NULL; if (objectclass != NULL) { default_descriptor = get_sd_unpacked(module, mem_ctx, objectclass); object_list = talloc_zero_array(mem_ctx, struct GUID, 2); if (object_list == NULL) { return NULL; } object_list[0] = objectclass->schemaIDGUID; } if (object) { user_descriptor = talloc(mem_ctx, struct security_descriptor); if (!user_descriptor) { return NULL; } ndr_err = ndr_pull_struct_blob(object, user_descriptor, user_descriptor, (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { talloc_free(user_descriptor); return NULL; } } else { user_descriptor = default_descriptor; } if (old_sd) { old_descriptor = talloc(mem_ctx, struct security_descriptor); if (!old_descriptor) { return NULL; } ndr_err = ndr_pull_struct_blob(old_sd, old_descriptor, old_descriptor, (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { talloc_free(old_descriptor); return NULL; } } if (parent) { parent_descriptor = talloc(mem_ctx, struct security_descriptor); if (!parent_descriptor) { return NULL; } ndr_err = ndr_pull_struct_blob(parent, parent_descriptor, parent_descriptor, (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { talloc_free(parent_descriptor); return NULL; } } if (user_descriptor && default_descriptor && (user_descriptor->dacl == NULL)) { user_descriptor->dacl = default_descriptor->dacl; user_descriptor->type |= default_descriptor->type & ( SEC_DESC_DACL_PRESENT | SEC_DESC_DACL_DEFAULTED|SEC_DESC_DACL_AUTO_INHERIT_REQ | SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_PROTECTED | SEC_DESC_DACL_TRUSTED); } if (user_descriptor && default_descriptor && (user_descriptor->sacl == NULL)) { user_descriptor->sacl = default_descriptor->sacl; user_descriptor->type |= default_descriptor->type & ( SEC_DESC_SACL_PRESENT | SEC_DESC_SACL_DEFAULTED|SEC_DESC_SACL_AUTO_INHERIT_REQ | SEC_DESC_SACL_AUTO_INHERITED|SEC_DESC_SACL_PROTECTED | SEC_DESC_SERVER_SECURITY); } if (!(sd_flags & SECINFO_OWNER) && user_descriptor) { user_descriptor->owner_sid = NULL; /* * We need the correct owner sid * when calculating the DACL or SACL */ if (old_descriptor) { user_descriptor->owner_sid = old_descriptor->owner_sid; } } if (!(sd_flags & SECINFO_GROUP) && user_descriptor) { user_descriptor->group_sid = NULL; /* * We need the correct group sid * when calculating the DACL or SACL */ if (old_descriptor) { user_descriptor->group_sid = old_descriptor->group_sid; } } if (!(sd_flags & SECINFO_DACL) && user_descriptor) { user_descriptor->dacl = NULL; /* * We add SEC_DESC_DACL_PROTECTED so that * create_security_descriptor() skips * the unused inheritance calculation */ user_descriptor->type |= SEC_DESC_DACL_PROTECTED; } if (!(sd_flags & SECINFO_SACL) && user_descriptor) { user_descriptor->sacl = NULL; /* * We add SEC_DESC_SACL_PROTECTED so that * create_security_descriptor() skips * the unused inheritance calculation */ user_descriptor->type |= SEC_DESC_SACL_PROTECTED; } default_owner = get_default_ag(mem_ctx, dn, session_info->security_token, ldb); default_group = get_default_group(mem_ctx, ldb, default_owner); new_sd = create_security_descriptor(mem_ctx, parent_descriptor, user_descriptor, true, object_list, SEC_DACL_AUTO_INHERIT | SEC_SACL_AUTO_INHERIT, session_info->security_token, default_owner, default_group, map_generic_rights_ds); if (!new_sd) { return NULL; } final_sd = descr_handle_sd_flags(mem_ctx, new_sd, old_descriptor, sd_flags); if (!final_sd) { return NULL; } if (final_sd->dacl) { final_sd->dacl->revision = SECURITY_ACL_REVISION_ADS; } if (final_sd->sacl) { final_sd->sacl->revision = SECURITY_ACL_REVISION_ADS; } { TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); DBG_DEBUG("Object %s created with descriptor %s\n\n", ldb_dn_get_linearized(dn), sddl_encode(tmp_ctx, final_sd, domain_sid)); TALLOC_FREE(tmp_ctx); } return final_sd; } static DATA_BLOB *get_new_descriptor(struct ldb_module *module, struct ldb_dn *dn, TALLOC_CTX *mem_ctx, const struct dsdb_class *objectclass, const struct ldb_val *parent, const struct ldb_val *object, const struct ldb_val *old_sd, uint32_t sd_flags) { struct security_descriptor *final_sd = NULL; enum ndr_err_code ndr_err; DATA_BLOB *linear_sd = talloc(mem_ctx, DATA_BLOB); if (!linear_sd) { return NULL; } final_sd = get_new_descriptor_nonlinear(module, dn, mem_ctx, objectclass, parent, object, old_sd, sd_flags); if (final_sd == NULL) { return NULL; } ndr_err = ndr_push_struct_blob(linear_sd, mem_ctx, final_sd, (ndr_push_flags_fn_t)ndr_push_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return NULL; } return linear_sd; } static DATA_BLOB *descr_get_descriptor_to_show(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_val *sd, uint32_t sd_flags) { struct security_descriptor *old_sd, *final_sd; DATA_BLOB *linear_sd; enum ndr_err_code ndr_err; old_sd = talloc(mem_ctx, struct security_descriptor); if (!old_sd) { return NULL; } ndr_err = ndr_pull_struct_blob(sd, old_sd, old_sd, (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { talloc_free(old_sd); return NULL; } final_sd = descr_handle_sd_flags(mem_ctx, old_sd, NULL, sd_flags); if (!final_sd) { return NULL; } linear_sd = talloc(mem_ctx, DATA_BLOB); if (!linear_sd) { return NULL; } ndr_err = ndr_push_struct_blob(linear_sd, mem_ctx, final_sd, (ndr_push_flags_fn_t)ndr_push_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return NULL; } return linear_sd; } static struct descriptor_context *descriptor_init_context(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct descriptor_context *ac; ldb = ldb_module_get_ctx(module); ac = talloc_zero(req, struct descriptor_context); if (ac == NULL) { ldb_set_errstring(ldb, "Out of Memory"); return NULL; } ac->module = module; ac->req = req; return ac; } static int descriptor_search_callback(struct ldb_request *req, struct ldb_reply *ares) { struct descriptor_context *ac; struct ldb_val *sd_val = NULL; struct ldb_message_element *sd_el; DATA_BLOB *show_sd; int ret = LDB_SUCCESS; ac = talloc_get_type(req->context, struct descriptor_context); if (!ares) { ret = LDB_ERR_OPERATIONS_ERROR; goto fail; } if (ares->error != LDB_SUCCESS) { return ldb_module_done(ac->req, ares->controls, ares->response, ares->error); } switch (ares->type) { case LDB_REPLY_ENTRY: sd_el = ldb_msg_find_element(ares->message, "nTSecurityDescriptor"); if (sd_el) { sd_val = sd_el->values; } if (sd_val) { show_sd = descr_get_descriptor_to_show(ac->module, ac->req, sd_val, ac->sd_flags); if (!show_sd) { ret = LDB_ERR_OPERATIONS_ERROR; goto fail; } ldb_msg_remove_attr(ares->message, "nTSecurityDescriptor"); ret = ldb_msg_add_steal_value(ares->message, "nTSecurityDescriptor", show_sd); if (ret != LDB_SUCCESS) { goto fail; } } 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); } fail: talloc_free(ares); return ldb_module_done(ac->req, NULL, NULL, ret); } static bool can_write_owner(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_dn *dn, const struct security_token *security_token, const struct dom_sid *owner_sid) { const struct dom_sid *default_owner = NULL; /* If the user possesses SE_RESTORE_PRIVILEGE, the write is allowed. */ bool ok = security_token_has_privilege(security_token, SEC_PRIV_RESTORE); if (ok) { return true; } /* The user can write their own SID to a security descriptor. */ ok = security_token_is_sid(security_token, owner_sid); if (ok) { return true; } /* * The user can write the SID of the "default administrators group" that * they are a member of. */ default_owner = get_default_ag(mem_ctx, dn, security_token, ldb); if (default_owner != NULL) { ok = security_token_is_sid(security_token, owner_sid); } return ok; } static int descriptor_add(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_request *add_req; struct ldb_message *msg; struct ldb_result *parent_res; const struct ldb_val *parent_sd = NULL; const struct ldb_val *user_sd = NULL; struct ldb_dn *dn = req->op.add.message->dn; struct ldb_dn *parent_dn, *nc_root; struct ldb_message_element *objectclass_element, *sd_element; int ret; const struct dsdb_schema *schema; DATA_BLOB *sd; const struct dsdb_class *objectclass; static const char * const parent_attrs[] = { "nTSecurityDescriptor", NULL }; uint32_t instanceType; bool isNC = false; enum ndr_err_code ndr_err; struct dsdb_control_calculated_default_sd *control_sd = NULL; uint32_t sd_flags = dsdb_request_sd_flags(req, NULL); struct security_descriptor *user_descriptor = NULL; /* do not manipulate our control entries */ if (ldb_dn_is_special(dn)) { return ldb_next_request(module, req); } user_sd = ldb_msg_find_ldb_val(req->op.add.message, "nTSecurityDescriptor"); sd_element = ldb_msg_find_element(req->op.add.message, "nTSecurityDescriptor"); /* nTSecurityDescriptor without a value is an error, letting through so it is handled */ if (user_sd == NULL && sd_element) { return ldb_next_request(module, req); } ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_add: %s\n", ldb_dn_get_linearized(dn)); instanceType = ldb_msg_find_attr_as_uint(req->op.add.message, "instanceType", 0); if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) { isNC = true; } if (!isNC) { ret = dsdb_find_nc_root(ldb, req, dn, &nc_root); if (ret != LDB_SUCCESS) { ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_add: Could not find NC root for %s\n", ldb_dn_get_linearized(dn)); return ret; } if (ldb_dn_compare(dn, nc_root) == 0) { DEBUG(0, ("Found DN %s being a NC by the old method\n", ldb_dn_get_linearized(dn))); isNC = true; } } if (isNC) { DEBUG(2, ("DN: %s is a NC\n", ldb_dn_get_linearized(dn))); } if (!isNC) { /* if the object has a parent, retrieve its SD to * use for calculation. Unfortunately we do not yet have * instanceType, so we use dsdb_find_nc_root. */ parent_dn = ldb_dn_get_parent(req, dn); if (parent_dn == NULL) { return ldb_oom(ldb); } /* we aren't any NC */ ret = dsdb_module_search_dn(module, req, &parent_res, parent_dn, parent_attrs, DSDB_FLAG_NEXT_MODULE | DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_RECYCLED, req); if (ret != LDB_SUCCESS) { ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_add: Could not find SD for %s\n", ldb_dn_get_linearized(parent_dn)); return ret; } if (parent_res->count != 1) { return ldb_operr(ldb); } parent_sd = ldb_msg_find_ldb_val(parent_res->msgs[0], "nTSecurityDescriptor"); } schema = dsdb_get_schema(ldb, req); objectclass_element = ldb_msg_find_element(req->op.add.message, "objectClass"); if (objectclass_element == NULL) { return ldb_operr(ldb); } objectclass = dsdb_get_last_structural_class(schema, objectclass_element); if (objectclass == NULL) { return ldb_operr(ldb); } /* * The SD_FLAG control is ignored on add * and we default to all bits set. */ sd_flags = SECINFO_OWNER|SECINFO_GROUP|SECINFO_SACL|SECINFO_DACL; control_sd = talloc(req, struct dsdb_control_calculated_default_sd); if (control_sd == NULL) { return ldb_operr(ldb); } control_sd->specified_sd = false; control_sd->specified_sacl = false; if (user_sd != NULL) { user_descriptor = talloc(req, struct security_descriptor); if (user_descriptor == NULL) { return ldb_operr(ldb); } ndr_err = ndr_pull_struct_blob(user_sd, user_descriptor, user_descriptor, (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { talloc_free(user_descriptor); return ldb_operr(ldb); } /* * calculate the permissions needed, since in acl we no longer have * access to the original user descriptor */ control_sd->specified_sd = true; control_sd->specified_sacl = user_descriptor->sacl != NULL; if (user_descriptor->owner_sid != NULL) { /* Verify the owner of the security descriptor. */ const struct auth_session_info *session_info = ldb_get_opaque(ldb, DSDB_SESSION_INFO); bool ok = can_write_owner(req, ldb, dn, session_info->security_token, user_descriptor->owner_sid); talloc_free(user_descriptor); if (!ok) { return dsdb_module_werror(module, LDB_ERR_CONSTRAINT_VIOLATION, WERR_INVALID_OWNER, "invalid addition of owner SID"); } } } sd = get_new_descriptor(module, dn, req, objectclass, parent_sd, user_sd, NULL, sd_flags); if (sd == NULL) { return ldb_operr(ldb); } control_sd->default_sd = get_new_descriptor_nonlinear(module, dn, req, objectclass, parent_sd, NULL, NULL, sd_flags); if (control_sd->default_sd == NULL) { return ldb_operr(ldb); } msg = ldb_msg_copy_shallow(req, req->op.add.message); if (msg == NULL) { return ldb_oom(ldb); } if (sd_element != NULL) { sd_element->values[0] = *sd; } else { ret = ldb_msg_add_steal_value(msg, "nTSecurityDescriptor", sd); if (ret != LDB_SUCCESS) { return ret; } } ret = ldb_build_add_req(&add_req, ldb, req, msg, req->controls, req, dsdb_next_callback, req); LDB_REQ_SET_LOCATION(add_req); if (ret != LDB_SUCCESS) { return ldb_error(ldb, ret, "descriptor_add: Error creating new add request."); } *control_sd->default_sd->owner_sid = global_sid_NULL; ret = ldb_request_add_control(add_req, DSDB_CONTROL_CALCULATED_DEFAULT_SD_OID, false, (void *)control_sd); if (ret != LDB_SUCCESS) { return ldb_module_operr(module); } return ldb_next_request(module, add_req); } static int descriptor_modify(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_request *mod_req; struct ldb_message *msg; struct ldb_result *current_res, *parent_res; const struct ldb_val *old_sd = NULL; const struct ldb_val *parent_sd = NULL; const struct ldb_val *user_sd = NULL; struct ldb_dn *dn = req->op.mod.message->dn; struct ldb_dn *parent_dn; struct ldb_message_element *objectclass_element, *sd_element; int ret; uint32_t instanceType; bool explicit_sd_flags = false; uint32_t sd_flags = dsdb_request_sd_flags(req, &explicit_sd_flags); const struct dsdb_schema *schema; DATA_BLOB *sd; const struct dsdb_class *objectclass; static const char * const parent_attrs[] = { "nTSecurityDescriptor", NULL }; static const char * const current_attrs[] = { "nTSecurityDescriptor", "instanceType", "objectClass", NULL }; struct GUID parent_guid = { .time_low = 0 }; struct ldb_control *sd_propagation_control; int cmp_ret = -1; /* do not manipulate our control entries */ if (ldb_dn_is_special(dn)) { 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 (sd_propagation_control->data != module) { return ldb_operr(ldb); } if (req->op.mod.message->num_elements != 0) { return ldb_operr(ldb); } if (explicit_sd_flags) { return ldb_operr(ldb); } if (sd_flags != 0xF) { return ldb_operr(ldb); } if (sd_propagation_control->critical == 0) { return ldb_operr(ldb); } sd_propagation_control->critical = 0; } sd_element = ldb_msg_find_element(req->op.mod.message, "nTSecurityDescriptor"); if (sd_propagation_control == NULL && sd_element == NULL) { return ldb_next_request(module, req); } /* * nTSecurityDescriptor with DELETE is not supported yet. * TODO: handle this correctly. */ if (sd_propagation_control == NULL && LDB_FLAG_MOD_TYPE(sd_element->flags) == LDB_FLAG_MOD_DELETE) { return ldb_module_error(module, LDB_ERR_UNWILLING_TO_PERFORM, "MOD_DELETE for nTSecurityDescriptor " "not supported yet"); } user_sd = ldb_msg_find_ldb_val(req->op.mod.message, "nTSecurityDescriptor"); /* nTSecurityDescriptor without a value is an error, letting through so it is handled */ if (sd_propagation_control == NULL && user_sd == NULL) { return ldb_next_request(module, req); } if (sd_flags & SECINFO_OWNER && user_sd != NULL) { /* Verify the new owner of the security descriptor. */ struct security_descriptor *user_descriptor = NULL; enum ndr_err_code ndr_err; const struct auth_session_info *session_info; bool ok; user_descriptor = talloc(req, struct security_descriptor); if (user_descriptor == NULL) { return ldb_operr(ldb); } ndr_err = ndr_pull_struct_blob(user_sd, user_descriptor, user_descriptor, (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { talloc_free(user_descriptor); return ldb_operr(ldb); } session_info = ldb_get_opaque(ldb, DSDB_SESSION_INFO); ok = can_write_owner(req, ldb, dn, session_info->security_token, user_descriptor->owner_sid); talloc_free(user_descriptor); if (!ok) { return dsdb_module_werror(module, LDB_ERR_CONSTRAINT_VIOLATION, WERR_INVALID_OWNER, "invalid modification of owner SID"); } } ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_modify: %s\n", ldb_dn_get_linearized(dn)); ret = dsdb_module_search_dn(module, req, ¤t_res, dn, current_attrs, DSDB_FLAG_NEXT_MODULE | DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_RECYCLED | DSDB_SEARCH_SHOW_EXTENDED_DN, req); if (ret != LDB_SUCCESS) { ldb_debug(ldb, LDB_DEBUG_ERROR,"descriptor_modify: Could not find %s\n", ldb_dn_get_linearized(dn)); return ret; } instanceType = ldb_msg_find_attr_as_uint(current_res->msgs[0], "instanceType", 0); /* if the object has a parent, retrieve its SD to * use for calculation */ if (!ldb_dn_is_null(current_res->msgs[0]->dn) && !(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) { NTSTATUS status; parent_dn = ldb_dn_get_parent(req, dn); if (parent_dn == NULL) { return ldb_oom(ldb); } ret = dsdb_module_search_dn(module, req, &parent_res, parent_dn, parent_attrs, DSDB_FLAG_NEXT_MODULE | DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_RECYCLED | DSDB_SEARCH_SHOW_EXTENDED_DN, req); if (ret != LDB_SUCCESS) { ldb_debug(ldb, LDB_DEBUG_ERROR, "descriptor_modify: Could not find SD for %s\n", ldb_dn_get_linearized(parent_dn)); return ret; } if (parent_res->count != 1) { return ldb_operr(ldb); } parent_sd = ldb_msg_find_ldb_val(parent_res->msgs[0], "nTSecurityDescriptor"); status = dsdb_get_extended_dn_guid(parent_res->msgs[0]->dn, &parent_guid, "GUID"); if (!NT_STATUS_IS_OK(status)) { return ldb_operr(ldb); } } schema = dsdb_get_schema(ldb, req); objectclass_element = ldb_msg_find_element(current_res->msgs[0], "objectClass"); if (objectclass_element == NULL) { return ldb_operr(ldb); } objectclass = dsdb_get_last_structural_class(schema, objectclass_element); if (objectclass == NULL) { return ldb_operr(ldb); } old_sd = ldb_msg_find_ldb_val(current_res->msgs[0], "nTSecurityDescriptor"); if (old_sd == NULL) { return ldb_operr(ldb); } if (sd_propagation_control != NULL) { /* * This just triggers a recalculation of the * inherited aces. */ user_sd = old_sd; } sd = get_new_descriptor(module, current_res->msgs[0]->dn, req, objectclass, parent_sd, user_sd, old_sd, sd_flags); if (sd == NULL) { return ldb_operr(ldb); } msg = ldb_msg_copy_shallow(req, req->op.mod.message); if (msg == NULL) { return ldb_oom(ldb); } cmp_ret = data_blob_cmp(old_sd, sd); if (sd_propagation_control != NULL) { if (cmp_ret == 0) { /* * The nTSecurityDescriptor is unchanged, * which means we can stop the processing. * * We mark the control as critical again, * as we have not processed it, so the caller * can tell that the descriptor was unchanged. */ sd_propagation_control->critical = 1; return ldb_module_done(req, NULL, NULL, LDB_SUCCESS); } ret = ldb_msg_append_value(msg, "nTSecurityDescriptor", sd, LDB_FLAG_MOD_REPLACE); if (ret != LDB_SUCCESS) { return ldb_oom(ldb); } } else if (cmp_ret != 0) { struct GUID guid; struct ldb_dn *nc_root; NTSTATUS status; ret = dsdb_find_nc_root(ldb, msg, current_res->msgs[0]->dn, &nc_root); if (ret != LDB_SUCCESS) { return ldb_oom(ldb); } status = dsdb_get_extended_dn_guid(current_res->msgs[0]->dn, &guid, "GUID"); if (!NT_STATUS_IS_OK(status)) { return ldb_operr(ldb); } /* * Force SD propagation on children of this record */ ret = dsdb_module_schedule_sd_propagation(module, nc_root, guid, parent_guid, false); if (ret != LDB_SUCCESS) { return ldb_operr(ldb); } sd_element->values[0] = *sd; } else { sd_element->values[0] = *sd; } ret = ldb_build_mod_req(&mod_req, ldb, req, msg, req->controls, req, dsdb_next_callback, req); LDB_REQ_SET_LOCATION(mod_req); if (ret != LDB_SUCCESS) { return ret; } return ldb_next_request(module, mod_req); } static int descriptor_search(struct ldb_module *module, struct ldb_request *req) { int ret; struct ldb_context *ldb; struct ldb_request *down_req; struct descriptor_context *ac; bool explicit_sd_flags = false; uint32_t sd_flags = dsdb_request_sd_flags(req, &explicit_sd_flags); bool show_sd = explicit_sd_flags; if (!show_sd && ldb_attr_in_list(req->op.search.attrs, "nTSecurityDescriptor")) { show_sd = true; } if (!show_sd) { return ldb_next_request(module, req); } ldb = ldb_module_get_ctx(module); ac = descriptor_init_context(module, req); if (ac == NULL) { return ldb_operr(ldb); } ac->sd_flags = sd_flags; ret = ldb_build_search_req_ex(&down_req, ldb, ac, req->op.search.base, req->op.search.scope, req->op.search.tree, req->op.search.attrs, req->controls, ac, descriptor_search_callback, ac->req); LDB_REQ_SET_LOCATION(down_req); if (ret != LDB_SUCCESS) { return ret; } return ldb_next_request(ac->module, down_req); } static int descriptor_rename_callback(struct ldb_request *req, struct ldb_reply *ares) { struct descriptor_context *ac = NULL; struct ldb_context *ldb = NULL; struct ldb_dn *newdn = req->op.rename.newdn; struct GUID guid; struct ldb_dn *nc_root; struct GUID parent_guid = { .time_low = 0 }; int ret; ac = talloc_get_type_abort(req->context, struct descriptor_context); 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); } if (ares->type != LDB_REPLY_DONE) { return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); } ret = dsdb_module_guid_by_dn(ac->module, newdn, &guid, req); if (ret != LDB_SUCCESS) { return ldb_module_done(ac->req, NULL, NULL, ret); } ret = dsdb_find_nc_root(ldb, req, newdn, &nc_root); if (ret != LDB_SUCCESS) { return ldb_module_done(ac->req, NULL, NULL, ret); } /* * After a successful rename, force SD propagation on this * record (get a new inherited SD from the potentially new * parent * * We don't know the parent guid here (it is filled in as * all-zero in the initialiser above), but we're not in a hot * code path here, as the "descriptor" module is located above * the "repl_meta_data", only originating changes are handled * here. * * If it turns out to be a problem we may search for the new * parent guid. */ ret = dsdb_module_schedule_sd_propagation(ac->module, nc_root, guid, parent_guid, true); if (ret != LDB_SUCCESS) { ret = ldb_operr(ldb); return ldb_module_done(ac->req, NULL, NULL, ret); } return ldb_module_done(ac->req, ares->controls, ares->response, ares->error); } static int descriptor_rename(struct ldb_module *module, struct ldb_request *req) { struct descriptor_context *ac = NULL; struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_dn *olddn = req->op.rename.olddn; struct ldb_dn *newdn = req->op.rename.newdn; struct ldb_request *down_req; int ret; /* do not manipulate our control entries */ if (ldb_dn_is_special(req->op.rename.olddn)) { return ldb_next_request(module, req); } ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_rename: %s\n", ldb_dn_get_linearized(olddn)); if (ldb_dn_compare(olddn, newdn) == 0) { /* No special work required for a case-only rename */ return ldb_next_request(module, req); } ac = descriptor_init_context(module, req); if (ac == NULL) { return ldb_operr(ldb); } ret = ldb_build_rename_req(&down_req, ldb, ac, req->op.rename.olddn, req->op.rename.newdn, req->controls, ac, descriptor_rename_callback, req); LDB_REQ_SET_LOCATION(down_req); if (ret != LDB_SUCCESS) { return ret; } return ldb_next_request(module, down_req); } static void descriptor_changes_parser(TDB_DATA key, TDB_DATA data, void *private_data) { struct descriptor_changes **c_ptr = (struct descriptor_changes **)private_data; uintptr_t ptr = 0; SMB_ASSERT(data.dsize == sizeof(ptr)); memcpy(&ptr, data.dptr, data.dsize); *c_ptr = talloc_get_type_abort((void *)ptr, struct descriptor_changes); } static void descriptor_object_parser(TDB_DATA key, TDB_DATA data, void *private_data) { SMB_ASSERT(data.dsize == 0); } static int descriptor_extended_sec_desc_propagation(struct ldb_module *module, struct ldb_request *req) { struct descriptor_data *descriptor_private = talloc_get_type_abort(ldb_module_get_private(module), struct descriptor_data); struct descriptor_transaction *t = &descriptor_private->transaction; struct ldb_context *ldb = ldb_module_get_ctx(module); struct dsdb_extended_sec_desc_propagation_op *op; struct descriptor_changes *c = NULL; TDB_DATA key; NTSTATUS status; op = talloc_get_type(req->op.extended.data, struct dsdb_extended_sec_desc_propagation_op); if (op == NULL) { ldb_debug(ldb, LDB_DEBUG_FATAL, "descriptor_extended_sec_desc_propagation: " "invalid extended data\n"); return LDB_ERR_PROTOCOL_ERROR; } if (t->mem == NULL) { return ldb_module_operr(module); } if (GUID_equal(&op->parent_guid, &op->guid)) { /* * This is an unexpected situation, * it should never happen! */ DBG_ERR("ERROR: Object %s is its own parent (nc_root=%s)\n", GUID_string(t->mem, &op->guid), ldb_dn_get_extended_linearized(t->mem, op->nc_root, 1)); return ldb_module_operr(module); } /* * First we check if we already have an registration * for the given object. */ key = make_tdb_data((const void*)&op->guid, sizeof(op->guid)); status = dbwrap_parse_record(t->changes.map, key, descriptor_changes_parser, &c); if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { c = NULL; status = NT_STATUS_OK; } if (!NT_STATUS_IS_OK(status)) { ldb_debug(ldb, LDB_DEBUG_FATAL, "dbwrap_parse_record() - %s\n", nt_errstr(status)); return ldb_module_operr(module); } if (c == NULL) { /* * Create a new structure if we * don't know about the object yet. */ c = talloc_zero(t->mem, struct descriptor_changes); if (c == NULL) { return ldb_module_oom(module); } c->nc_root = ldb_dn_copy(c, op->nc_root); if (c->nc_root == NULL) { return ldb_module_oom(module); } c->guid = op->guid; } if (ldb_dn_compare(c->nc_root, op->nc_root) != 0) { /* * This is an unexpected situation, * we don't expect the nc root to change * during a replication cycle. */ DBG_ERR("ERROR: Object %s nc_root changed %s => %s\n", GUID_string(c, &c->guid), ldb_dn_get_extended_linearized(c, c->nc_root, 1), ldb_dn_get_extended_linearized(c, op->nc_root, 1)); return ldb_module_operr(module); } c->ref_count += 1; /* * always use the last known parent_guid. */ c->parent_guid = op->parent_guid; /* * Note that we only set, but don't clear values here, * it means c->force_self and c->force_children can * both be true in the end. */ if (op->include_self) { c->force_self = true; } else { c->force_children = true; } if (c->ref_count == 1) { struct TDB_DATA val = make_tdb_data((const void*)&c, sizeof(c)); /* * Remember the change by objectGUID in order * to avoid processing it more than once. */ status = dbwrap_store(t->changes.map, key, val, TDB_INSERT); if (!NT_STATUS_IS_OK(status)) { ldb_debug(ldb, LDB_DEBUG_FATAL, "dbwrap_parse_record() - %s\n", nt_errstr(status)); return ldb_module_operr(module); } DLIST_ADD_END(t->changes.list, c); t->changes.num_registered += 1; } t->changes.num_registrations += 1; return ldb_module_done(req, NULL, NULL, LDB_SUCCESS); } static int descriptor_extended(struct ldb_module *module, struct ldb_request *req) { if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID) == 0) { return descriptor_extended_sec_desc_propagation(module, req); } return ldb_next_request(module, req); } static int descriptor_init(struct ldb_module *module) { struct ldb_context *ldb = ldb_module_get_ctx(module); int ret; struct descriptor_data *descriptor_private; ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID); if (ret != LDB_SUCCESS) { ldb_debug(ldb, LDB_DEBUG_ERROR, "descriptor: Unable to register control with rootdse!\n"); return ldb_operr(ldb); } descriptor_private = talloc_zero(module, struct descriptor_data); if (descriptor_private == NULL) { ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } ldb_module_set_private(module, descriptor_private); return ldb_next_init(module); } static int descriptor_sd_propagation_object(struct ldb_module *module, struct ldb_message *msg, bool *stop) { struct descriptor_data *descriptor_private = talloc_get_type_abort(ldb_module_get_private(module), struct descriptor_data); struct descriptor_transaction *t = &descriptor_private->transaction; struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_request *sub_req; struct ldb_result *mod_res; struct ldb_control *sd_propagation_control; struct GUID guid; int ret; TDB_DATA key; TDB_DATA empty_val = { .dsize = 0, }; NTSTATUS status; struct descriptor_changes *c = NULL; *stop = false; /* * We get the GUID of the object * in order to have the cache key * for the object. */ status = dsdb_get_extended_dn_guid(msg->dn, &guid, "GUID"); if (!NT_STATUS_IS_OK(status)) { return ldb_operr(ldb); } key = make_tdb_data((const void*)&guid, sizeof(guid)); /* * Check if we already processed this object. */ status = dbwrap_parse_record(t->objects.map, key, descriptor_object_parser, NULL); if (NT_STATUS_IS_OK(status)) { /* * All work is already one */ t->objects.num_skipped += 1; *stop = true; return LDB_SUCCESS; } if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { ldb_debug(ldb, LDB_DEBUG_FATAL, "dbwrap_parse_record() - %s\n", nt_errstr(status)); return ldb_module_operr(module); } t->objects.num_processed += 1; /* * Remember that we're processing this object. */ status = dbwrap_store(t->objects.map, key, empty_val, TDB_INSERT); if (!NT_STATUS_IS_OK(status)) { ldb_debug(ldb, LDB_DEBUG_FATAL, "dbwrap_parse_record() - %s\n", nt_errstr(status)); return ldb_module_operr(module); } /* * Check that if there's a descriptor_change in our list, * which we may be able to remove from the pending list * when we processed the object. */ status = dbwrap_parse_record(t->changes.map, key, descriptor_changes_parser, &c); if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { c = NULL; status = NT_STATUS_OK; } if (!NT_STATUS_IS_OK(status)) { ldb_debug(ldb, LDB_DEBUG_FATAL, "dbwrap_parse_record() - %s\n", nt_errstr(status)); return ldb_module_operr(module); } mod_res = talloc_zero(msg, struct ldb_result); if (mod_res == NULL) { return ldb_module_oom(module); } ret = ldb_build_mod_req(&sub_req, ldb, mod_res, msg, NULL, mod_res, ldb_modify_default_callback, NULL); LDB_REQ_SET_LOCATION(sub_req); if (ret != LDB_SUCCESS) { return ldb_module_operr(module); } ldb_req_mark_trusted(sub_req); ret = ldb_request_add_control(sub_req, DSDB_CONTROL_SEC_DESC_PROPAGATION_OID, true, module); if (ret != LDB_SUCCESS) { return ldb_module_operr(module); } sd_propagation_control = ldb_request_get_control(sub_req, DSDB_CONTROL_SEC_DESC_PROPAGATION_OID); if (sd_propagation_control == NULL) { return ldb_module_operr(module); } ret = dsdb_request_add_controls(sub_req, DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_RECYCLED); if (ret != LDB_SUCCESS) { return ldb_module_operr(module); } ret = descriptor_modify(module, sub_req); if (ret == LDB_SUCCESS) { ret = ldb_wait(sub_req->handle, LDB_WAIT_ALL); } if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(ldb_module_get_ctx(module), "descriptor_modify on %s failed: %s", ldb_dn_get_linearized(msg->dn), ldb_errstring(ldb_module_get_ctx(module))); return LDB_ERR_OPERATIONS_ERROR; } if (sd_propagation_control->critical != 0) { if (c == NULL) { /* * If we don't have a * descriptor_changes structure * we're done. */ *stop = true; } else if (!c->force_children) { /* * If we don't need to * propagate to children, * we're done. */ *stop = true; } } if (c != NULL && !c->force_children) { /* * Remove the pending change, * we already done all required work, * there's no need to do it again. * * Note DLIST_REMOVE() is a noop * if the element is not part of * the list. */ DLIST_REMOVE(t->changes.list, c); } talloc_free(mod_res); return LDB_SUCCESS; } static int descriptor_sd_propagation_msg_sort(struct ldb_message **m1, struct ldb_message **m2) { struct ldb_dn *dn1 = (*m1)->dn; struct ldb_dn *dn2 = (*m2)->dn; /* * This sorts in tree order, parents first */ return ldb_dn_compare(dn2, dn1); } static int descriptor_sd_propagation_recursive(struct ldb_module *module, struct descriptor_changes *change) { struct descriptor_data *descriptor_private = talloc_get_type_abort(ldb_module_get_private(module), struct descriptor_data); struct descriptor_transaction *t = &descriptor_private->transaction; struct ldb_result *guid_res = NULL; struct ldb_result *res = NULL; unsigned int i; const char * const no_attrs[] = { "@__NONE__", NULL }; struct ldb_dn *stopped_dn = NULL; struct GUID_txt_buf guid_buf; int ret; bool stop = false; t->changes.num_processed += 1; /* * First confirm this object has children, or exists * (depending on change->force_self) * * LDB_SCOPE_SUBTREE searches are expensive. * * We know this is safe against a rename race as we are in the * prepare_commit(), so must be in a transaction. */ /* Find the DN by GUID, as this is stable under rename */ ret = dsdb_module_search(module, change, &guid_res, change->nc_root, LDB_SCOPE_SUBTREE, no_attrs, DSDB_FLAG_NEXT_MODULE | DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_DELETED | DSDB_SEARCH_SHOW_RECYCLED | DSDB_SEARCH_SHOW_EXTENDED_DN, NULL, /* parent_req */ "(objectGUID=%s)", GUID_buf_string(&change->guid, &guid_buf)); if (ret != LDB_SUCCESS) { return ret; } if (guid_res->count != 1) { /* * We were just given this GUID during the same * transaction, if it is missing this is a big * problem. * * Cleanup of tombstones does not trigger this module * as it just does a delete. */ ldb_asprintf_errstring(ldb_module_get_ctx(module), "failed to find GUID %s under %s " "for transaction-end SD inheritance: %d results", GUID_buf_string(&change->guid, &guid_buf), ldb_dn_get_linearized(change->nc_root), guid_res->count); return LDB_ERR_OPERATIONS_ERROR; } /* * OK, so there was a parent, are there children? Note: that * this time we do not search for deleted/recycled objects */ ret = dsdb_module_search(module, change, &res, guid_res->msgs[0]->dn, LDB_SCOPE_ONELEVEL, no_attrs, DSDB_FLAG_NEXT_MODULE | DSDB_FLAG_AS_SYSTEM, NULL, /* parent_req */ "(objectClass=*)"); if (ret != LDB_SUCCESS) { /* * LDB_ERR_NO_SUCH_OBJECT, say if the DN was a deleted * object, is ignored by the caller */ return ret; } if (res->count == 0 && !change->force_self) { /* All done, no children */ TALLOC_FREE(res); return LDB_SUCCESS; } /* * First, if we are in force_self mode (eg renamed under new * parent) then apply the SD to the top object */ if (change->force_self) { ret = descriptor_sd_propagation_object(module, guid_res->msgs[0], &stop); if (ret != LDB_SUCCESS) { TALLOC_FREE(guid_res); return ret; } if (stop == true && !change->force_children) { /* There was no change, nothing more to do */ TALLOC_FREE(guid_res); return LDB_SUCCESS; } if (res->count == 0) { /* All done! */ TALLOC_FREE(guid_res); return LDB_SUCCESS; } } /* * Look for children * * Note: that we do not search for deleted/recycled objects */ ret = dsdb_module_search(module, change, &res, guid_res->msgs[0]->dn, LDB_SCOPE_SUBTREE, no_attrs, DSDB_FLAG_NEXT_MODULE | DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_EXTENDED_DN, NULL, /* parent_req */ "(objectClass=*)"); if (ret != LDB_SUCCESS) { return ret; } TYPESAFE_QSORT(res->msgs, res->count, descriptor_sd_propagation_msg_sort); /* We start from 1, the top object has been done */ for (i = 1; i < res->count; i++) { /* * ldb_dn_compare_base() does not match for NULL but * this is clearer */ if (stopped_dn != NULL) { ret = ldb_dn_compare_base(stopped_dn, res->msgs[i]->dn); /* * Skip further processing of this * sub-subtree */ if (ret == 0) { continue; } } ret = descriptor_sd_propagation_object(module, res->msgs[i], &stop); if (ret != LDB_SUCCESS) { return ret; } if (stop) { /* * If this child didn't change, then nothing * under it needs to change * * res has been sorted into tree order so the * next few entries can be skipped */ stopped_dn = res->msgs[i]->dn; } } TALLOC_FREE(res); return LDB_SUCCESS; } static int descriptor_start_transaction(struct ldb_module *module) { struct descriptor_data *descriptor_private = talloc_get_type_abort(ldb_module_get_private(module), struct descriptor_data); struct descriptor_transaction *t = &descriptor_private->transaction; if (t->mem != NULL) { return ldb_module_operr(module); } *t = (struct descriptor_transaction) { .mem = NULL, }; t->mem = talloc_new(descriptor_private); if (t->mem == NULL) { return ldb_module_oom(module); } t->changes.map = db_open_rbt(t->mem); if (t->changes.map == NULL) { TALLOC_FREE(t->mem); *t = (struct descriptor_transaction) { .mem = NULL, }; return ldb_module_oom(module); } t->objects.map = db_open_rbt(t->mem); if (t->objects.map == NULL) { TALLOC_FREE(t->mem); *t = (struct descriptor_transaction) { .mem = NULL, }; return ldb_module_oom(module); } return ldb_next_start_trans(module); } static int descriptor_prepare_commit(struct ldb_module *module) { struct descriptor_data *descriptor_private = talloc_get_type_abort(ldb_module_get_private(module), struct descriptor_data); struct descriptor_transaction *t = &descriptor_private->transaction; struct ldb_context *ldb = ldb_module_get_ctx(module); struct descriptor_changes *c, *n; int ret; DBG_NOTICE("changes: num_registrations=%zu\n", t->changes.num_registrations); DBG_NOTICE("changes: num_registered=%zu\n", t->changes.num_registered); /* * The security descriptor propagation * needs to apply the inheritance from * an object to itself and/or all it's * children. * * In the initial replication during * a join, we have every object in our * list. * * In order to avoid useless work it's * better to start with toplevel objects and * move down to the leaf object from there. * * So if the parent_guid is also in our list, * we better move the object behind its parent. * * It allows that the recursive processing of * the parent already does the work needed * for the child. * * If we have a list for this directory tree: * * A * -> B * -> C * -> D * -> E * * The initial list would have the order D, E, B, A, C * * By still processing from the front, we ensure that, * when D is found to be below C, that E follows because * we keep peeling items off the front for checking and * move them behind their parent. * * So we would go: * * E B A C D * * B A C D E * * A B C D E */ for (c = t->changes.list; c; c = n) { struct descriptor_changes *pc = NULL; n = c->next; if (c->sort_count >= t->changes.num_registered) { /* * This should never happen, but it's * a sanity check in order to avoid * endless loops. Just stop sorting. */ break; } /* * Check if we have the parent also in the list. */ if (!GUID_all_zero((const void*)&c->parent_guid)) { TDB_DATA pkey; NTSTATUS status; pkey = make_tdb_data((const void*)&c->parent_guid, sizeof(c->parent_guid)); status = dbwrap_parse_record(t->changes.map, pkey, descriptor_changes_parser, &pc); if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { pc = NULL; status = NT_STATUS_OK; } if (!NT_STATUS_IS_OK(status)) { ldb_debug(ldb, LDB_DEBUG_FATAL, "dbwrap_parse_record() - %s\n", nt_errstr(status)); return ldb_module_operr(module); } } if (pc == NULL) { /* * There is no parent in the list */ t->changes.num_toplevel += 1; continue; } /* * Move the child after the parent * * Note that we do that multiple times * in case the parent already moved itself. * * See the comment above the loop. */ DLIST_REMOVE(t->changes.list, c); DLIST_ADD_AFTER(t->changes.list, c, pc); /* * Remember how often we moved the object * in order to avoid endless loops. */ c->sort_count += 1; } DBG_NOTICE("changes: num_toplevel=%zu\n", t->changes.num_toplevel); while (t->changes.list != NULL) { c = t->changes.list; DLIST_REMOVE(t->changes.list, c); /* * Note that descriptor_sd_propagation_recursive() * may also remove other elements of the list, * so we can't use a next pointer */ ret = descriptor_sd_propagation_recursive(module, c); if (ret == LDB_ERR_NO_SUCH_OBJECT) { continue; } if (ret != LDB_SUCCESS) { return ret; } } DBG_NOTICE("changes: num_processed=%zu\n", t->changes.num_processed); DBG_NOTICE("objects: num_processed=%zu\n", t->objects.num_processed); DBG_NOTICE("objects: num_skipped=%zu\n", t->objects.num_skipped); return ldb_next_prepare_commit(module); } static int descriptor_end_transaction(struct ldb_module *module) { struct descriptor_data *descriptor_private = talloc_get_type_abort(ldb_module_get_private(module), struct descriptor_data); struct descriptor_transaction *t = &descriptor_private->transaction; TALLOC_FREE(t->mem); *t = (struct descriptor_transaction) { .mem = NULL, }; return ldb_next_end_trans(module); } static int descriptor_del_transaction(struct ldb_module *module) { struct descriptor_data *descriptor_private = talloc_get_type_abort(ldb_module_get_private(module), struct descriptor_data); struct descriptor_transaction *t = &descriptor_private->transaction; TALLOC_FREE(t->mem); *t = (struct descriptor_transaction) { .mem = NULL, }; return ldb_next_del_trans(module); } static const struct ldb_module_ops ldb_descriptor_module_ops = { .name = "descriptor", .search = descriptor_search, .add = descriptor_add, .modify = descriptor_modify, .rename = descriptor_rename, .init_context = descriptor_init, .extended = descriptor_extended, .start_transaction = descriptor_start_transaction, .prepare_commit = descriptor_prepare_commit, .end_transaction = descriptor_end_transaction, .del_transaction = descriptor_del_transaction, }; int ldb_descriptor_module_init(const char *version) { LDB_MODULE_CHECK_VERSION(version); return ldb_register_module(&ldb_descriptor_module_ops); }