X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source4%2Fdsdb%2Fsamdb%2Fldb_modules%2Facl.c;h=853fa20902e0cfd3f3ada6db091d9927b3802ae2;hb=39b425ac31a4497c162ffb29ccc92dbca95def69;hp=a779821107f0bd22d428c366937e04adb901b4e7;hpb=9b3871ed293f76e770e572cd6b59f59670f1f6f8;p=metze%2Fsamba%2Fwip.git diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c index a779821107f0..853fa20902e0 100644 --- a/source4/dsdb/samdb/ldb_modules/acl.c +++ b/source4/dsdb/samdb/ldb_modules/acl.c @@ -39,6 +39,9 @@ #include "librpc/gen_ndr/ndr_security.h" #include "param/param.h" #include "dsdb/samdb/ldb_modules/util.h" +#include "lib/util/tsort.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" struct extended_access_check_attribute { const char *oa_name; @@ -46,47 +49,38 @@ struct extended_access_check_attribute { }; struct acl_private { - bool acl_perform; + bool acl_search; const char **password_attrs; + void *cached_schema_ptr; + uint64_t cached_schema_metadata_usn; + uint64_t cached_schema_loaded_usn; + const char **confidential_attrs; }; struct acl_context { struct ldb_module *module; struct ldb_request *req; bool am_system; + bool am_administrator; + bool modify_search; + bool constructed_attrs; bool allowedAttributes; bool allowedAttributesEffective; bool allowedChildClasses; bool allowedChildClassesEffective; bool sDRightsEffective; + bool userPassword; const char * const *attrs; + struct dsdb_schema *schema; }; -bool is_root_base_dn(struct ldb_context *ldb, struct ldb_dn *dn_to_check) -{ - int result; - struct ldb_dn *root_base_dn = ldb_get_root_basedn(ldb); - result = ldb_dn_compare(root_base_dn,dn_to_check); - return (result==0); -} - -static struct security_token *acl_user_token(struct ldb_module *module) -{ - struct ldb_context *ldb = ldb_module_get_ctx(module); - struct auth_session_info *session_info - = (struct auth_session_info *)ldb_get_opaque(ldb, "sessionInfo"); - if(!session_info) { - return NULL; - } - return session_info->security_token; -} - static int acl_module_init(struct ldb_module *module) { struct ldb_context *ldb; struct acl_private *data; - int ret, i; - TALLOC_CTX *mem_ctx = talloc_new(module); + int ret; + unsigned int i; + TALLOC_CTX *mem_ctx; static const char *attrs[] = { "passwordAttribute", NULL }; struct ldb_result *res; struct ldb_message *msg; @@ -98,28 +92,29 @@ static int acl_module_init(struct ldb_module *module) if (ret != LDB_SUCCESS) { ldb_debug(ldb, LDB_DEBUG_ERROR, "acl_module_init: Unable to register control with rootdse!\n"); - return LDB_ERR_OPERATIONS_ERROR; + return ldb_operr(ldb); } - data = talloc(module, struct acl_private); + data = talloc_zero(module, struct acl_private); if (data == NULL) { - ldb_oom(ldb); - return LDB_ERR_OPERATIONS_ERROR; + return ldb_oom(ldb); } - data->password_attrs = NULL; - data->acl_perform = lp_parm_bool(ldb_get_opaque(ldb, "loadparm"), - NULL, "acl", "perform", false); + data->acl_search = lpcfg_parm_bool(ldb_get_opaque(ldb, "loadparm"), + NULL, "acl", "search", true); ldb_module_set_private(module, data); + mem_ctx = talloc_new(module); if (!mem_ctx) { - ldb_oom(ldb); - return LDB_ERR_OPERATIONS_ERROR; + return ldb_oom(ldb); } - ret = ldb_search(ldb, mem_ctx, &res, - ldb_dn_new(mem_ctx, ldb, "@KLUDGEACL"), - LDB_SCOPE_BASE, attrs, NULL); + ret = dsdb_module_search_dn(module, mem_ctx, &res, + ldb_dn_new(mem_ctx, ldb, "@KLUDGEACL"), + attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM, + NULL); if (ret != LDB_SUCCESS) { goto done; } @@ -141,8 +136,7 @@ static int acl_module_init(struct ldb_module *module) data->password_attrs = talloc_array(data, const char *, password_attributes->num_values + 1); if (!data->password_attrs) { talloc_free(mem_ctx); - ldb_oom(ldb); - return LDB_ERR_OPERATIONS_ERROR; + return ldb_oom(ldb); } for (i=0; i < password_attributes->num_values; i++) { data->password_attrs[i] = (const char *)password_attributes->values[i].data; @@ -155,262 +149,22 @@ done: return ldb_next_init(module); } -static int get_sd_from_ldb_message(TALLOC_CTX *mem_ctx, - struct ldb_message *acl_res, - struct security_descriptor **sd) -{ - struct ldb_message_element *sd_element; - enum ndr_err_code ndr_err; - - sd_element = ldb_msg_find_element(acl_res, "nTSecurityDescriptor"); - if (!sd_element) { - *sd = NULL; - return LDB_SUCCESS; - } - *sd = talloc(mem_ctx, struct security_descriptor); - if(!*sd) { - return LDB_ERR_OPERATIONS_ERROR; - } - ndr_err = ndr_pull_struct_blob(&sd_element->values[0], *sd, NULL, *sd, - (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); - - if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { - return LDB_ERR_OPERATIONS_ERROR; - } - - return LDB_SUCCESS; -} - -static const struct GUID *get_oc_guid_from_message(struct ldb_module *module, - struct ldb_message *msg) -{ - struct ldb_message_element *oc_el; - struct ldb_context *ldb = ldb_module_get_ctx(module); - - oc_el = ldb_msg_find_element(msg, "objectClass"); - if (!oc_el) { - return NULL; - } - - return class_schemaid_guid_by_lDAPDisplayName(dsdb_get_schema(ldb), - (char *)oc_el->values[oc_el->num_values-1].data); -} - -static int get_dom_sid_from_ldb_message(TALLOC_CTX *mem_ctx, - struct ldb_message *acl_res, - struct dom_sid **sid) -{ - struct ldb_message_element *sid_element; - enum ndr_err_code ndr_err; - - sid_element = ldb_msg_find_element(acl_res, "objectSid"); - if (!sid_element) { - *sid = NULL; - return LDB_SUCCESS; - } - *sid = talloc(mem_ctx, struct dom_sid); - if(!*sid) { - return LDB_ERR_OPERATIONS_ERROR; - } - ndr_err = ndr_pull_struct_blob(&sid_element->values[0], *sid, NULL, *sid, - (ndr_pull_flags_fn_t)ndr_pull_dom_sid); - - if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { - return LDB_ERR_OPERATIONS_ERROR; - } - - return LDB_SUCCESS; -} - - -static void acl_debug(struct security_descriptor *sd, - struct security_token *token, - struct ldb_dn *dn, - bool denied, - int level) -{ - if (denied) { - DEBUG(level, ("Access on %s denied", ldb_dn_get_linearized(dn))); - } else { - DEBUG(level, ("Access on %s granted", ldb_dn_get_linearized(dn))); - } - - DEBUG(level,("Security context: %s\n", - ndr_print_struct_string(0,(ndr_print_fn_t)ndr_print_security_token,"", token))); - DEBUG(level,("Security descriptor: %s\n", - ndr_print_struct_string(0,(ndr_print_fn_t)ndr_print_security_descriptor,"", sd))); -} - -static int check_access_on_dn(struct ldb_module *module, - TALLOC_CTX *mem_ctx, - struct ldb_dn *dn, - uint32_t access, - struct object_tree *tree) -{ - int ret; - struct ldb_context *ldb = ldb_module_get_ctx(module); - struct ldb_result *acl_res; - struct security_descriptor *sd = NULL; - struct dom_sid *sid = NULL; - NTSTATUS status; - uint32_t access_granted; - static const char *acl_attrs[] = { - "nTSecurityDescriptor", - "objectSid", - NULL - }; - - ret = ldb_search(ldb, mem_ctx, &acl_res, dn, LDB_SCOPE_BASE, acl_attrs, NULL); - /* we sould be able to find the parent */ - if (ret != LDB_SUCCESS) { - DEBUG(10,("acl: failed to find object %s\n", ldb_dn_get_linearized(dn))); - return ret; - } - - ret = get_sd_from_ldb_message(mem_ctx, acl_res->msgs[0], &sd); - if (ret != LDB_SUCCESS) { - return LDB_ERR_OPERATIONS_ERROR; - } - /* Theoretically we pass the check if the object has no sd */ - if (!sd) { - return LDB_SUCCESS; - } - ret = get_dom_sid_from_ldb_message(mem_ctx, acl_res->msgs[0], &sid); - if (ret != LDB_SUCCESS) { - return LDB_ERR_OPERATIONS_ERROR; - } - - status = sec_access_check_ds(sd, acl_user_token(module), - access, - &access_granted, - tree, - sid); - if (!NT_STATUS_IS_OK(status)) { - acl_debug(sd, - acl_user_token(module), - dn, - true, - 10); - return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; - } - return LDB_SUCCESS; -} - -static int acl_check_access_on_attribute(struct ldb_module *module, - TALLOC_CTX *mem_ctx, - struct security_descriptor *sd, - struct dom_sid *rp_sid, - uint32_t access, - const struct dsdb_attribute *attr) -{ - int ret; - NTSTATUS status; - uint32_t access_granted; - struct object_tree *root = NULL; - struct object_tree *new_node = NULL; - TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); - struct security_token *token = acl_user_token(module); - if (attr) { - if (!GUID_all_zero(&attr->attributeSecurityGUID)) { - if (!insert_in_object_tree(tmp_ctx, - &attr->attributeSecurityGUID, access, - &root, &new_node)) { - DEBUG(10, ("acl_search: cannot add to object tree securityGUID\n")); - goto fail; - } - - if (!insert_in_object_tree(tmp_ctx, - &attr->schemaIDGUID, access, &new_node, &new_node)) { - DEBUG(10, ("acl_search: cannot add to object tree attributeGUID\n")); - goto fail; - } - } - else { - if (!insert_in_object_tree(tmp_ctx, - &attr->schemaIDGUID, access, &root, &new_node)) { - DEBUG(10, ("acl_search: cannot add to object tree attributeGUID\n")); - goto fail; - } - } - } - status = sec_access_check_ds(sd, token, - access, - &access_granted, - root, - rp_sid); - if (!NT_STATUS_IS_OK(status)) { - ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; - } - else { - ret = LDB_SUCCESS; - } - return ret; -fail: - return LDB_ERR_OPERATIONS_ERROR; -} - -static int acl_check_access_on_class(struct ldb_module *module, - TALLOC_CTX *mem_ctx, - struct security_descriptor *sd, - struct dom_sid *rp_sid, - uint32_t access, - const char *class_name) -{ - int ret; - struct ldb_context *ldb = ldb_module_get_ctx(module); - NTSTATUS status; - uint32_t access_granted; - struct object_tree *root = NULL; - struct object_tree *new_node = NULL; - const struct GUID *guid; - const struct dsdb_schema *schema = dsdb_get_schema(ldb); - TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); - struct security_token *token = acl_user_token(module); - if (class_name) { - guid = class_schemaid_guid_by_lDAPDisplayName(schema, class_name); - if (!guid) { - DEBUG(10, ("acl_search: cannot find class %s\n", - class_name)); - goto fail; - } - if (!insert_in_object_tree(tmp_ctx, - guid, access, - &root, &new_node)) { - DEBUG(10, ("acl_search: cannot add to object tree guid\n")); - goto fail; - } - } - status = sec_access_check_ds(sd, token, - access, - &access_granted, - root, - rp_sid); - if (!NT_STATUS_IS_OK(status)) { - ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; - } - else { - ret = LDB_SUCCESS; - } - return ret; -fail: - return LDB_ERR_OPERATIONS_ERROR; -} - static int acl_allowedAttributes(struct ldb_module *module, + const struct dsdb_schema *schema, struct ldb_message *sd_msg, struct ldb_message *msg, struct acl_context *ac) { struct ldb_message_element *oc_el; struct ldb_context *ldb = ldb_module_get_ctx(module); - const struct dsdb_schema *schema = dsdb_get_schema(ldb); TALLOC_CTX *mem_ctx; const char **attr_list; int i, ret; /* If we don't have a schema yet, we can't do anything... */ if (schema == NULL) { - return LDB_SUCCESS; + ldb_asprintf_errstring(ldb, "cannot add allowedAttributes to %s because no schema is loaded", ldb_dn_get_linearized(msg->dn)); + return LDB_ERR_OPERATIONS_ERROR; } /* Must remove any existing attribute */ @@ -420,8 +174,7 @@ static int acl_allowedAttributes(struct ldb_module *module, mem_ctx = talloc_new(msg); if (!mem_ctx) { - ldb_oom(ldb); - return LDB_ERR_OPERATIONS_ERROR; + return ldb_oom(ldb); } oc_el = ldb_msg_find_element(sd_msg, "objectClass"); @@ -441,6 +194,11 @@ static int acl_allowedAttributes(struct ldb_module *module, struct dom_sid *sid = NULL; struct ldb_control *as_system = ldb_request_get_control(ac->req, LDB_CONTROL_AS_SYSTEM_OID); + + if (as_system != NULL) { + as_system->critical = 0; + } + ldb_msg_remove_attr(msg, "allowedAttributesEffective"); if (ac->am_system || as_system) { for (i=0; attr_list && attr_list[i]; i++) { @@ -449,21 +207,18 @@ static int acl_allowedAttributes(struct ldb_module *module, return LDB_SUCCESS; } - ret = get_sd_from_ldb_message(mem_ctx, sd_msg, &sd); + ret = dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(module), mem_ctx, sd_msg, &sd); if (ret != LDB_SUCCESS) { return ret; } - ret = get_dom_sid_from_ldb_message(mem_ctx, sd_msg, &sid); - if (ret != LDB_SUCCESS) { - return ret; - } + sid = samdb_result_dom_sid(mem_ctx, sd_msg, "objectSid"); for (i=0; attr_list && attr_list[i]; i++) { const struct dsdb_attribute *attr = dsdb_attribute_by_lDAPDisplayName(schema, attr_list[i]); if (!attr) { - return LDB_ERR_OPERATIONS_ERROR; + return ldb_operr(ldb); } /* remove constructed attributes */ if (attr->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED @@ -486,20 +241,21 @@ static int acl_allowedAttributes(struct ldb_module *module, } static int acl_childClasses(struct ldb_module *module, + const struct dsdb_schema *schema, struct ldb_message *sd_msg, struct ldb_message *msg, const char *attrName) { struct ldb_message_element *oc_el; struct ldb_message_element *allowedClasses; - struct ldb_context *ldb = ldb_module_get_ctx(module); - const struct dsdb_schema *schema = dsdb_get_schema(ldb); const struct dsdb_class *sclass; - int i, j, ret; + unsigned int i, j; + int ret; /* If we don't have a schema yet, we can't do anything... */ if (schema == NULL) { - return LDB_SUCCESS; + ldb_asprintf_errstring(ldb_module_get_ctx(module), "cannot add childClassesEffective to %s because no schema is loaded", ldb_dn_get_linearized(msg->dn)); + return LDB_ERR_OPERATIONS_ERROR; } /* Must remove any existing attribute, or else confusion reins */ @@ -523,11 +279,7 @@ static int acl_childClasses(struct ldb_module *module, } } if (allowedClasses->num_values > 1) { - qsort(allowedClasses->values, - allowedClasses->num_values, - sizeof(*allowedClasses->values), - (comparison_fn_t)data_blob_cmp); - + TYPESAFE_QSORT(allowedClasses->values, allowedClasses->num_values, data_blob_cmp); for (i=1 ; i < allowedClasses->num_values; i++) { struct ldb_val *val1 = &allowedClasses->values[i-1]; struct ldb_val *val2 = &allowedClasses->values[i]; @@ -542,44 +294,92 @@ static int acl_childClasses(struct ldb_module *module, return LDB_SUCCESS; } +static int acl_check_access_on_class(struct ldb_module *module, + const struct dsdb_schema *schema, + TALLOC_CTX *mem_ctx, + struct security_descriptor *sd, + struct security_token *token, + struct dom_sid *rp_sid, + uint32_t access_mask, + const char *class_name) +{ + int ret; + NTSTATUS status; + uint32_t access_granted; + struct object_tree *root = NULL; + struct object_tree *new_node = NULL; + const struct GUID *guid; + + if (class_name != NULL) { + guid = class_schemaid_guid_by_lDAPDisplayName(schema, class_name); + if (!guid) { + DEBUG(10, ("acl_search: cannot find class %s\n", + class_name)); + goto fail; + } + if (!insert_in_object_tree(mem_ctx, + guid, access_mask, + &root, &new_node)) { + DEBUG(10, ("acl_search: cannot add to object tree guid\n")); + goto fail; + } + } + + status = sec_access_check_ds(sd, token, + access_mask, + &access_granted, + root, + rp_sid); + if (!NT_STATUS_IS_OK(status)) { + ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } else { + ret = LDB_SUCCESS; + } + return ret; +fail: + return ldb_operr(ldb_module_get_ctx(module)); +} + static int acl_childClassesEffective(struct ldb_module *module, + const struct dsdb_schema *schema, struct ldb_message *sd_msg, struct ldb_message *msg, struct acl_context *ac) { struct ldb_message_element *oc_el; struct ldb_message_element *allowedClasses = NULL; - struct ldb_context *ldb = ldb_module_get_ctx(module); - const struct dsdb_schema *schema = dsdb_get_schema(ldb); const struct dsdb_class *sclass; struct security_descriptor *sd; struct ldb_control *as_system = ldb_request_get_control(ac->req, LDB_CONTROL_AS_SYSTEM_OID); struct dom_sid *sid = NULL; - int i, j, ret; + unsigned int i, j; + int ret; + + if (as_system != NULL) { + as_system->critical = 0; + } if (ac->am_system || as_system) { - return acl_childClasses(module, sd_msg, msg, "allowedChildClassesEffective"); + return acl_childClasses(module, schema, sd_msg, msg, "allowedChildClassesEffective"); } /* If we don't have a schema yet, we can't do anything... */ if (schema == NULL) { - return LDB_SUCCESS; + ldb_asprintf_errstring(ldb_module_get_ctx(module), "cannot add allowedChildClassesEffective to %s because no schema is loaded", ldb_dn_get_linearized(msg->dn)); + return LDB_ERR_OPERATIONS_ERROR; } /* Must remove any existing attribute, or else confusion reins */ ldb_msg_remove_attr(msg, "allowedChildClassesEffective"); oc_el = ldb_msg_find_element(sd_msg, "objectClass"); - ret = get_sd_from_ldb_message(msg, sd_msg, &sd); + ret = dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(module), msg, sd_msg, &sd); if (ret != LDB_SUCCESS) { return ret; } - ret = get_dom_sid_from_ldb_message(msg, sd_msg, &sid); - if (ret != LDB_SUCCESS) { - return ret; - } + sid = samdb_result_dom_sid(msg, sd_msg, "objectSid"); for (i=0; oc_el && i < oc_el->num_values; i++) { sclass = dsdb_class_by_lDAPDisplayName_ldb_val(schema, &oc_el->values[i]); if (!sclass) { @@ -589,8 +389,10 @@ static int acl_childClassesEffective(struct ldb_module *module, for (j=0; sclass->possibleInferiors && sclass->possibleInferiors[j]; j++) { ret = acl_check_access_on_class(module, + schema, msg, sd, + acl_user_token(module), sid, SEC_ADS_CREATE_CHILD, sclass->possibleInferiors[j]); @@ -606,10 +408,7 @@ static int acl_childClassesEffective(struct ldb_module *module, } if (allowedClasses->num_values > 1) { - qsort(allowedClasses->values, - allowedClasses->num_values, - sizeof(*allowedClasses->values), - (comparison_fn_t)data_blob_cmp); + TYPESAFE_QSORT(allowedClasses->values, allowedClasses->num_values, data_blob_cmp); for (i=1 ; i < allowedClasses->num_values; i++) { struct ldb_val *val1 = &allowedClasses->values[i-1]; struct ldb_val *val2 = &allowedClasses->values[i]; @@ -636,6 +435,10 @@ static int acl_sDRightsEffective(struct ldb_module *module, struct dom_sid *sid = NULL; uint32_t flags = 0; + if (as_system != NULL) { + as_system->critical = 0; + } + /* Must remove any existing attribute, or else confusion reins */ ldb_msg_remove_attr(msg, "sDRightsEffective"); ret = ldb_msg_add_empty(msg, "sDRightsEffective", 0, &rightsEffective); @@ -647,15 +450,11 @@ static int acl_sDRightsEffective(struct ldb_module *module, } else { /* Get the security descriptor from the message */ - ret = get_sd_from_ldb_message(msg, sd_msg, &sd); - if (ret != LDB_SUCCESS) { - return ret; - } - ret = get_dom_sid_from_ldb_message(msg, sd_msg, &sid); - + ret = dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(module), msg, sd_msg, &sd); if (ret != LDB_SUCCESS) { return ret; } + sid = samdb_result_dom_sid(msg, sd_msg, "objectSid"); ret = acl_check_access_on_attribute(module, msg, sd, @@ -684,7 +483,237 @@ static int acl_sDRightsEffective(struct ldb_module *module, flags |= SECINFO_SACL; } } - ldb_msg_add_fmt(msg, "sDRightsEffective", "%u", flags); + return samdb_msg_add_uint(ldb_module_get_ctx(module), msg, msg, + "sDRightsEffective", flags); +} + +static int acl_validate_spn_value(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *spn_value, + uint32_t userAccountControl, + const char *samAccountName, + const char *dnsHostName, + const char *netbios_name, + const char *ntds_guid) +{ + int ret; + krb5_context krb_ctx; + krb5_error_code kerr; + krb5_principal principal; + char *instanceName; + char *serviceType; + char *serviceName; + const char *forest_name = samdb_forest_name(ldb, mem_ctx); + const char *base_domain = samdb_default_domain_name(ldb, mem_ctx); + struct loadparm_context *lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), + struct loadparm_context); + bool is_dc = (userAccountControl & UF_SERVER_TRUST_ACCOUNT) || + (userAccountControl & UF_PARTIAL_SECRETS_ACCOUNT); + + if (strcasecmp_m(spn_value, samAccountName) == 0) { + /* MacOS X sets this value, and setting an SPN of your + * own samAccountName is both pointless and safe */ + return LDB_SUCCESS; + } + + kerr = smb_krb5_init_context_basic(mem_ctx, + lp_ctx, + &krb_ctx); + if (kerr != 0) { + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "Could not initialize kerberos context."); + } + + ret = krb5_parse_name(krb_ctx, spn_value, &principal); + if (ret) { + krb5_free_context(krb_ctx); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (principal->name.name_string.len < 2) { + goto fail; + } + + instanceName = principal->name.name_string.val[1]; + serviceType = principal->name.name_string.val[0]; + if (principal->name.name_string.len == 3) { + serviceName = principal->name.name_string.val[2]; + } else { + serviceName = NULL; + } + + if (serviceName) { + if (!is_dc) { + goto fail; + } + if (strcasecmp(serviceType, "ldap") == 0) { + if (strcasecmp(serviceName, netbios_name) != 0 && + strcasecmp(serviceName, forest_name) != 0) { + goto fail; + } + + } else if (strcasecmp(serviceType, "gc") == 0) { + if (strcasecmp(serviceName, forest_name) != 0) { + goto fail; + } + } else { + if (strcasecmp(serviceName, base_domain) != 0 && + strcasecmp(serviceName, netbios_name) != 0) { + goto fail; + } + } + } + /* instanceName can be samAccountName without $ or dnsHostName + * or "ntds_guid._msdcs.forest_domain for DC objects */ + if (strlen(instanceName) == (strlen(samAccountName) - 1) + && strncasecmp(instanceName, samAccountName, strlen(samAccountName) - 1) == 0) { + goto success; + } else if (dnsHostName != NULL && strcasecmp(instanceName, dnsHostName) == 0) { + goto success; + } else if (is_dc) { + const char *guid_str; + guid_str = talloc_asprintf(mem_ctx,"%s._msdcs.%s", + ntds_guid, + forest_name); + if (strcasecmp(instanceName, guid_str) == 0) { + goto success; + } + } + +fail: + krb5_free_principal(krb_ctx, principal); + krb5_free_context(krb_ctx); + return LDB_ERR_CONSTRAINT_VIOLATION; + +success: + krb5_free_principal(krb_ctx, principal); + krb5_free_context(krb_ctx); + return LDB_SUCCESS; +} + +static int acl_check_spn(TALLOC_CTX *mem_ctx, + struct ldb_module *module, + struct ldb_request *req, + struct security_descriptor *sd, + struct dom_sid *sid, + const struct GUID *oc_guid, + const struct dsdb_attribute *attr) +{ + int ret; + unsigned int i; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct ldb_result *acl_res; + struct ldb_result *netbios_res; + struct ldb_message_element *el; + struct ldb_dn *partitions_dn = samdb_partitions_dn(ldb, tmp_ctx); + uint32_t userAccountControl; + const char *samAccountName; + const char *dnsHostName; + const char *netbios_name; + struct GUID ntds; + char *ntds_guid = NULL; + + static const char *acl_attrs[] = { + "samAccountName", + "dnsHostName", + "userAccountControl", + NULL + }; + static const char *netbios_attrs[] = { + "nETBIOSName", + NULL + }; + + /* if we have wp, we can do whatever we like */ + if (acl_check_access_on_attribute(module, + tmp_ctx, + sd, + sid, + SEC_ADS_WRITE_PROP, + attr) == LDB_SUCCESS) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + GUID_DRS_VALIDATE_SPN, + SEC_ADS_SELF_WRITE, + sid); + + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + dsdb_acl_debug(sd, acl_user_token(module), + req->op.mod.message->dn, + true, + 10); + talloc_free(tmp_ctx); + return ret; + } + + ret = dsdb_module_search_dn(module, tmp_ctx, + &acl_res, req->op.mod.message->dn, + acl_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM | + DSDB_SEARCH_SHOW_RECYCLED, + req); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + userAccountControl = ldb_msg_find_attr_as_uint(acl_res->msgs[0], "userAccountControl", 0); + dnsHostName = ldb_msg_find_attr_as_string(acl_res->msgs[0], "dnsHostName", NULL); + samAccountName = ldb_msg_find_attr_as_string(acl_res->msgs[0], "samAccountName", NULL); + + ret = dsdb_module_search(module, tmp_ctx, + &netbios_res, partitions_dn, + LDB_SCOPE_ONELEVEL, + netbios_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM, + req, + "(ncName=%s)", + ldb_dn_get_linearized(ldb_get_default_basedn(ldb))); + + netbios_name = ldb_msg_find_attr_as_string(netbios_res->msgs[0], "nETBIOSName", NULL); + + el = ldb_msg_find_element(req->op.mod.message, "servicePrincipalName"); + if (!el) { + talloc_free(tmp_ctx); + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "Error finding element for servicePrincipalName."); + } + + /* NTDSDSA objectGuid of object we are checking SPN for */ + if (userAccountControl & (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) { + ret = dsdb_module_find_ntdsguid_for_computer(module, tmp_ctx, + req->op.mod.message->dn, &ntds, req); + if (ret != LDB_SUCCESS) { + ldb_asprintf_errstring(ldb, "Failed to find NTDSDSA objectGuid for %s: %s", + ldb_dn_get_linearized(req->op.mod.message->dn), + ldb_strerror(ret)); + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + ntds_guid = GUID_string(tmp_ctx, &ntds); + } + + for (i=0; i < el->num_values; i++) { + ret = acl_validate_spn_value(tmp_ctx, + ldb, + (char *)el->values[i].data, + userAccountControl, + samAccountName, + dnsHostName, + netbios_name, + ntds_guid); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + } + talloc_free(tmp_ctx); return LDB_SUCCESS; } @@ -693,56 +722,228 @@ static int acl_add(struct ldb_module *module, struct ldb_request *req) int ret; struct ldb_dn *parent = ldb_dn_get_parent(req, req->op.add.message->dn); struct ldb_context *ldb; + const struct dsdb_schema *schema; struct ldb_message_element *oc_el; const struct GUID *guid; - struct object_tree *root = NULL; - struct object_tree *new_node = NULL; + struct ldb_dn *nc_root; struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID); - if (dsdb_module_am_system(module) || as_system) { - return ldb_next_request(module, req); + if (as_system != NULL) { + as_system->critical = 0; + } + + if (dsdb_module_am_system(module) || as_system) { + return ldb_next_request(module, req); + } + if (ldb_dn_is_special(req->op.add.message->dn)) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + + /* Creating an NC. There is probably something we should do here, + * but we will establish that later */ + + ret = dsdb_find_nc_root(ldb, req, req->op.add.message->dn, &nc_root); + if (ret != LDB_SUCCESS) { + return ret; + } + if (ldb_dn_compare(nc_root, req->op.add.message->dn) == 0) { + talloc_free(nc_root); + return ldb_next_request(module, req); + } + talloc_free(nc_root); + + schema = dsdb_get_schema(ldb, req); + if (!schema) { + return ldb_operr(ldb); + } + + oc_el = ldb_msg_find_element(req->op.add.message, "objectClass"); + if (!oc_el || oc_el->num_values == 0) { + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "acl: unable to find objectClass on %s\n", + ldb_dn_get_linearized(req->op.add.message->dn)); + return ldb_module_done(req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + guid = class_schemaid_guid_by_lDAPDisplayName(schema, + (char *)oc_el->values[oc_el->num_values-1].data); + ret = dsdb_module_check_access_on_dn(module, req, parent, SEC_ADS_CREATE_CHILD, guid, req); + if (ret != LDB_SUCCESS) { + return ret; + } + return ldb_next_request(module, req); +} + +/* ckecks if modifications are allowed on "Member" attribute */ +static int acl_check_self_membership(TALLOC_CTX *mem_ctx, + struct ldb_module *module, + struct ldb_request *req, + struct security_descriptor *sd, + struct dom_sid *sid, + const struct GUID *oc_guid, + const struct dsdb_attribute *attr) +{ + int ret; + unsigned int i; + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct ldb_dn *user_dn; + struct ldb_message_element *member_el; + /* if we have wp, we can do whatever we like */ + if (acl_check_access_on_attribute(module, + mem_ctx, + sd, + sid, + SEC_ADS_WRITE_PROP, + attr) == LDB_SUCCESS) { + return LDB_SUCCESS; + } + /* if we are adding/deleting ourselves, check for self membership */ + ret = dsdb_find_dn_by_sid(ldb, mem_ctx, + &acl_user_token(module)->sids[PRIMARY_USER_SID_INDEX], + &user_dn); + if (ret != LDB_SUCCESS) { + return ret; + } + member_el = ldb_msg_find_element(req->op.mod.message, "member"); + if (!member_el) { + return ldb_operr(ldb); + } + /* user can only remove oneself */ + if (member_el->num_values == 0) { + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + for (i = 0; i < member_el->num_values; i++) { + if (strcasecmp((const char *)member_el->values[i].data, + ldb_dn_get_extended_linearized(mem_ctx, user_dn, 1)) != 0) { + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } + } + ret = acl_check_extended_right(mem_ctx, sd, acl_user_token(module), + GUID_DRS_SELF_MEMBERSHIP, + SEC_ADS_SELF_WRITE, + sid); + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + dsdb_acl_debug(sd, acl_user_token(module), + req->op.mod.message->dn, + true, + 10); + } + return ret; +} + +static int acl_check_password_rights(TALLOC_CTX *mem_ctx, + struct ldb_module *module, + struct ldb_request *req, + struct security_descriptor *sd, + struct dom_sid *sid, + const struct GUID *oc_guid, + bool userPassword) +{ + int ret = LDB_SUCCESS; + unsigned int del_attr_cnt = 0, add_attr_cnt = 0, rep_attr_cnt = 0; + struct ldb_message_element *el; + struct ldb_message *msg; + const char *passwordAttrs[] = { "userPassword", "clearTextPassword", + "unicodePwd", "dBCSPwd", NULL }, **l; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + msg = ldb_msg_copy_shallow(tmp_ctx, req->op.mod.message); + if (msg == NULL) { + return ldb_module_oom(module); } + for (l = passwordAttrs; *l != NULL; l++) { + if ((!userPassword) && (ldb_attr_cmp(*l, "userPassword") == 0)) { + continue; + } - if (ldb_dn_is_special(req->op.add.message->dn)) { - return ldb_next_request(module, req); + while ((el = ldb_msg_find_element(msg, *l)) != NULL) { + if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) { + ++del_attr_cnt; + } + if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_ADD) { + ++add_attr_cnt; + } + if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_REPLACE) { + ++rep_attr_cnt; + } + ldb_msg_remove_element(msg, el); + } } - ldb = ldb_module_get_ctx(module); - /* Creating an NC. There is probably something we should do here, - * but we will establish that later */ - if ((ldb_dn_compare(req->op.add.message->dn, (ldb_get_schema_basedn(ldb))) == 0) || - (ldb_dn_compare(req->op.add.message->dn, (ldb_get_config_basedn(ldb))) == 0) || - (ldb_dn_compare(req->op.add.message->dn, (ldb_get_root_basedn(ldb))) == 0)) { - return ldb_next_request(module, req); + + /* single deletes will be handled by the "password_hash" LDB module + * later in the stack, so we let it though here */ + if ((del_attr_cnt > 0) && (add_attr_cnt == 0) && (rep_attr_cnt == 0)) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; } - oc_el = ldb_msg_find_element(req->op.add.message, "objectClass"); - if (!oc_el || oc_el->num_values == 0) { - DEBUG(10,("acl:operation error %s\n", ldb_dn_get_linearized(req->op.add.message->dn))); - return ldb_module_done(req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + if (ldb_request_get_control(req, + DSDB_CONTROL_PASSWORD_CHANGE_OID) != NULL) { + /* The "DSDB_CONTROL_PASSWORD_CHANGE_OID" control means that we + * have a user password change and not a set as the message + * looks like. In it's value blob it contains the NT and/or LM + * hash of the old password specified by the user. + * This control is used by the SAMR and "kpasswd" password + * change mechanisms. */ + ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + GUID_DRS_USER_CHANGE_PASSWORD, + SEC_ADS_CONTROL_ACCESS, + sid); + } + else if (rep_attr_cnt > 0 || (add_attr_cnt != del_attr_cnt)) { + ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + GUID_DRS_FORCE_CHANGE_PASSWORD, + SEC_ADS_CONTROL_ACCESS, + sid); + } + else if (add_attr_cnt == 1 && del_attr_cnt == 1) { + ret = acl_check_extended_right(tmp_ctx, sd, acl_user_token(module), + GUID_DRS_USER_CHANGE_PASSWORD, + SEC_ADS_CONTROL_ACCESS, + sid); + /* Very strange, but we get constraint violation in this case */ + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + ret = LDB_ERR_CONSTRAINT_VIOLATION; + } + } + if (ret != LDB_SUCCESS) { + dsdb_acl_debug(sd, acl_user_token(module), + req->op.mod.message->dn, + true, + 10); } + talloc_free(tmp_ctx); + return ret; +} - guid = class_schemaid_guid_by_lDAPDisplayName(dsdb_get_schema(ldb), - (char *)oc_el->values[oc_el->num_values-1].data); +static const struct GUID *get_oc_guid_from_message(const struct dsdb_schema *schema, + struct ldb_message *msg) +{ + struct ldb_message_element *oc_el; + const struct dsdb_class *object_class; - if (!insert_in_object_tree(req, guid, SEC_ADS_CREATE_CHILD, &root, &new_node)) { - return LDB_ERR_OPERATIONS_ERROR; + oc_el = ldb_msg_find_element(msg, "objectClass"); + if (!oc_el) { + return NULL; } - ret = check_access_on_dn(module, req, parent, SEC_ADS_CREATE_CHILD, root); - if (ret != LDB_SUCCESS) { - return ret; + object_class = dsdb_get_last_structural_class(schema, oc_el); + if (object_class == NULL) { + return NULL; } - return ldb_next_request(module, req); + return &object_class->schemaIDGUID; } + static int acl_modify(struct ldb_module *module, struct ldb_request *req) { int ret; struct ldb_context *ldb = ldb_module_get_ctx(module); - const struct dsdb_schema *schema = dsdb_get_schema(ldb); - int i; - bool modify_sd = false; + const struct dsdb_schema *schema; + unsigned int i; const struct GUID *guid; uint32_t access_granted; struct object_tree *root = NULL; @@ -752,6 +953,7 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) struct security_descriptor *sd; struct dom_sid *sid = NULL; struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID); + bool userPassword = dsdb_user_password_support(module, req, req); TALLOC_CTX *tmp_ctx = talloc_new(req); static const char *acl_attrs[] = { "nTSecurityDescriptor", @@ -760,6 +962,10 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) NULL }; + if (as_system != NULL) { + as_system->critical = 0; + } + /* Don't print this debug statement if elements[0].name is going to be NULL */ if(req->op.mod.message->num_elements > 0) { @@ -771,67 +977,155 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) if (ldb_dn_is_special(req->op.mod.message->dn)) { return ldb_next_request(module, req); } - ret = ldb_search(ldb, req, &acl_res, req->op.mod.message->dn, - LDB_SCOPE_BASE, acl_attrs, NULL); + ret = dsdb_module_search_dn(module, tmp_ctx, &acl_res, req->op.mod.message->dn, + acl_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM | + DSDB_SEARCH_SHOW_RECYCLED, + req); if (ret != LDB_SUCCESS) { - return ret; + goto fail; + } + + schema = dsdb_get_schema(ldb, tmp_ctx); + if (!schema) { + ret = LDB_ERR_OPERATIONS_ERROR; + goto fail; } - ret = get_sd_from_ldb_message(req, acl_res->msgs[0], &sd); + ret = dsdb_get_sd_from_ldb_message(ldb, tmp_ctx, acl_res->msgs[0], &sd); if (ret != LDB_SUCCESS) { - DEBUG(10, ("acl_modify: cannot get descriptor\n")); - return ret; + talloc_free(tmp_ctx); + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "acl_modify: Error retrieving security descriptor."); } /* Theoretically we pass the check if the object has no sd */ if (!sd) { - return LDB_SUCCESS; + goto success; } - guid = get_oc_guid_from_message(module,acl_res->msgs[0]); + guid = get_oc_guid_from_message(schema, acl_res->msgs[0]); if (!guid) { - DEBUG(10, ("acl_modify: cannot get guid\n")); - goto fail; - } - - ret = get_dom_sid_from_ldb_message(req, acl_res->msgs[0], &sid); - if (ret != LDB_SUCCESS) { - return LDB_ERR_OPERATIONS_ERROR; + talloc_free(tmp_ctx); + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "acl_modify: Error retrieving object class GUID."); } - + sid = samdb_result_dom_sid(req, acl_res->msgs[0], "objectSid"); if (!insert_in_object_tree(tmp_ctx, guid, SEC_ADS_WRITE_PROP, &root, &new_node)) { - DEBUG(10, ("acl_modify: cannot add to object tree\n")); - goto fail; + talloc_free(tmp_ctx); + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "acl_modify: Error adding new node in object tree."); } for (i=0; i < req->op.mod.message->num_elements; i++){ const struct dsdb_attribute *attr; - /* clearTextPassword is not in schema */ - if (strcmp("clearTextPassword", req->op.mod.message->elements[i].name) == 0) { - attr = dsdb_attribute_by_lDAPDisplayName(schema, "unicodePwd"); - } else { - attr = dsdb_attribute_by_lDAPDisplayName(schema, - req->op.mod.message->elements[i].name); + attr = dsdb_attribute_by_lDAPDisplayName(schema, + req->op.mod.message->elements[i].name); + + if (ldb_attr_cmp("nTSecurityDescriptor", req->op.mod.message->elements[i].name) == 0) { + uint32_t sd_flags = dsdb_request_sd_flags(req, NULL); + uint32_t access_mask = 0; + + if (sd_flags & (SECINFO_OWNER|SECINFO_GROUP)) { + access_mask |= SEC_STD_WRITE_OWNER; + } + if (sd_flags & SECINFO_DACL) { + access_mask |= SEC_STD_WRITE_DAC; + } + if (sd_flags & SECINFO_SACL) { + access_mask |= SEC_FLAG_SYSTEM_SECURITY; + } + + status = sec_access_check_ds(sd, acl_user_token(module), + access_mask, + &access_granted, + NULL, + sid); + + if (!NT_STATUS_IS_OK(status)) { + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "Object %s has no write dacl access\n", + ldb_dn_get_linearized(req->op.mod.message->dn)); + dsdb_acl_debug(sd, + acl_user_token(module), + req->op.mod.message->dn, + true, + 10); + ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + goto fail; + } + } + else if (ldb_attr_cmp("member", req->op.mod.message->elements[i].name) == 0) { + ret = acl_check_self_membership(tmp_ctx, + module, + req, + sd, + sid, + guid, + attr); + if (ret != LDB_SUCCESS) { + goto fail; + } } - if (strcmp("nTSecurityDescriptor", req->op.mod.message->elements[i].name) == 0) { - modify_sd = true; + else if (ldb_attr_cmp("dBCSPwd", req->op.mod.message->elements[i].name) == 0) { + /* this one is not affected by any rights, we should let it through + so that passwords_hash returns the correct error */ + continue; + } + else if (ldb_attr_cmp("unicodePwd", req->op.mod.message->elements[i].name) == 0 || + (userPassword && ldb_attr_cmp("userPassword", req->op.mod.message->elements[i].name) == 0) || + ldb_attr_cmp("clearTextPassword", req->op.mod.message->elements[i].name) == 0) { + ret = acl_check_password_rights(tmp_ctx, + module, + req, + sd, + sid, + guid, + userPassword); + if (ret != LDB_SUCCESS) { + goto fail; + } + } else if (ldb_attr_cmp("servicePrincipalName", req->op.mod.message->elements[i].name) == 0) { + ret = acl_check_spn(tmp_ctx, + module, + req, + sd, + sid, + guid, + attr); + if (ret != LDB_SUCCESS) { + goto fail; + } } else { + /* This basic attribute existence check with the right errorcode + * is needed since this module is the first one which requests + * schema attribute information. + * The complete attribute checking is done in the + * "objectclass_attrs" module behind this one. + */ if (!attr) { - DEBUG(10, ("acl_modify: cannot find attribute %s\n", - req->op.mod.message->elements[i].name)); + ldb_asprintf_errstring(ldb, "acl_modify: attribute '%s' on entry '%s' was not found in the schema!", + req->op.mod.message->elements[i].name, + ldb_dn_get_linearized(req->op.mod.message->dn)); + ret = LDB_ERR_NO_SUCH_ATTRIBUTE; goto fail; } if (!insert_in_object_tree(tmp_ctx, &attr->attributeSecurityGUID, SEC_ADS_WRITE_PROP, &new_node, &new_node)) { - DEBUG(10, ("acl_modify: cannot add to object tree securityGUID\n")); + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "acl_modify: cannot add to object tree securityGUID\n"); + ret = LDB_ERR_OPERATIONS_ERROR; goto fail; } if (!insert_in_object_tree(tmp_ctx, &attr->schemaIDGUID, SEC_ADS_WRITE_PROP, &new_node, &new_node)) { - DEBUG(10, ("acl_modify: cannot add to object tree attributeGUID\n")); + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "acl_modify: cannot add to object tree attributeGUID\n"); + ret = LDB_ERR_OPERATIONS_ERROR; goto fail; } } @@ -845,81 +1139,81 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) sid); if (!NT_STATUS_IS_OK(status)) { - DEBUG(10, ("Object %s nas no write property access\n", - ldb_dn_get_linearized(req->op.mod.message->dn))); - acl_debug(sd, - acl_user_token(module), - req->op.mod.message->dn, - true, - 10); - talloc_free(tmp_ctx); - return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; - } - } - if (modify_sd) { - status = sec_access_check_ds(sd, acl_user_token(module), - SEC_STD_WRITE_DAC, - &access_granted, - NULL, - sid); - - if (!NT_STATUS_IS_OK(status)) { - DEBUG(10, ("Object %s nas no write dacl access\n", - ldb_dn_get_linearized(req->op.mod.message->dn))); - acl_debug(sd, - acl_user_token(module), - req->op.mod.message->dn, - true, - 10); - talloc_free(tmp_ctx); - return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "Object %s has no write property access\n", + ldb_dn_get_linearized(req->op.mod.message->dn)); + dsdb_acl_debug(sd, + acl_user_token(module), + req->op.mod.message->dn, + true, + 10); + ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + goto fail; } } +success: talloc_free(tmp_ctx); return ldb_next_request(module, req); fail: talloc_free(tmp_ctx); - return LDB_ERR_OPERATIONS_ERROR; + return ret; } /* similar to the modify for the time being. - * We need to concider the special delete tree case, though - TODO */ + * We need to consider the special delete tree case, though - TODO */ static int acl_delete(struct ldb_module *module, struct ldb_request *req) { int ret; struct ldb_dn *parent = ldb_dn_get_parent(req, req->op.del.dn); struct ldb_context *ldb; + struct ldb_dn *nc_root; struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID); + if (as_system != NULL) { + as_system->critical = 0; + } + DEBUG(10, ("ldb:acl_delete: %s\n", ldb_dn_get_linearized(req->op.del.dn))); if (dsdb_module_am_system(module) || as_system) { return ldb_next_request(module, req); } - if (ldb_dn_is_special(req->op.del.dn)) { return ldb_next_request(module, req); } + ldb = ldb_module_get_ctx(module); - /* first check if we have delete object right */ - ret = check_access_on_dn(module, req, req->op.del.dn, SEC_STD_DELETE, NULL); - if (ret == LDB_SUCCESS) { - return ldb_next_request(module, req); + + /* Make sure we aren't deleting a NC */ + + ret = dsdb_find_nc_root(ldb, req, req->op.del.dn, &nc_root); + if (ret != LDB_SUCCESS) { + return ret; } + if (ldb_dn_compare(nc_root, req->op.del.dn) == 0) { + talloc_free(nc_root); + DEBUG(10,("acl:deleting a NC\n")); + /* Windows returns "ERR_UNWILLING_TO_PERFORM */ + return ldb_module_done(req, NULL, NULL, + LDB_ERR_UNWILLING_TO_PERFORM); + } + talloc_free(nc_root); - /* Nope, we don't have delete object. Lets check if we have delete child on the parent */ - /* No parent, so check fails */ - if ((ldb_dn_compare(req->op.del.dn, (ldb_get_schema_basedn(ldb))) == 0) || - (ldb_dn_compare(req->op.del.dn, (ldb_get_config_basedn(ldb))) == 0) || - (ldb_dn_compare(req->op.del.dn, (ldb_get_root_basedn(ldb))) == 0)) { - DEBUG(10,("acl:deleting an NC\n")); - return ldb_module_done(req, NULL, NULL, LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS); + /* First check if we have delete object right */ + ret = dsdb_module_check_access_on_dn(module, req, req->op.del.dn, + SEC_STD_DELETE, NULL, req); + if (ret == LDB_SUCCESS) { + return ldb_next_request(module, req); } - ret = check_access_on_dn(module, req, parent, SEC_ADS_DELETE_CHILD, NULL); + /* Nope, we don't have delete object. Lets check if we have delete + * child on the parent */ + ret = dsdb_module_check_access_on_dn(module, req, parent, + SEC_ADS_DELETE_CHILD, NULL, req); if (ret != LDB_SUCCESS) { return ret; } + return ldb_next_request(module, req); } @@ -928,17 +1222,20 @@ static int acl_rename(struct ldb_module *module, struct ldb_request *req) int ret; struct ldb_dn *oldparent = ldb_dn_get_parent(req, req->op.rename.olddn); struct ldb_dn *newparent = ldb_dn_get_parent(req, req->op.rename.newdn); + const struct dsdb_schema *schema; struct ldb_context *ldb; struct security_descriptor *sd = NULL; struct dom_sid *sid = NULL; struct ldb_result *acl_res; const struct GUID *guid; + struct ldb_dn *nc_root; struct object_tree *root = NULL; struct object_tree *new_node = NULL; struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID); TALLOC_CTX *tmp_ctx = talloc_new(req); NTSTATUS status; uint32_t access_granted; + const char *rdn_name; static const char *acl_attrs[] = { "nTSecurityDescriptor", "objectClass", @@ -946,6 +1243,10 @@ static int acl_rename(struct ldb_module *module, struct ldb_request *req) NULL }; + if (as_system != NULL) { + as_system->critical = 0; + } + DEBUG(10, ("ldb:acl_rename: %s\n", ldb_dn_get_linearized(req->op.rename.olddn))); if (dsdb_module_am_system(module) || as_system) { return ldb_next_request(module, req); @@ -953,45 +1254,85 @@ static int acl_rename(struct ldb_module *module, struct ldb_request *req) if (ldb_dn_is_special(req->op.rename.olddn)) { return ldb_next_request(module, req); } + ldb = ldb_module_get_ctx(module); - /* TODO search to include deleted objects */ - ret = ldb_search(ldb, req, &acl_res, req->op.rename.olddn, - LDB_SCOPE_BASE, acl_attrs, NULL); + /* Make sure we aren't renaming/moving a NC */ + + ret = dsdb_find_nc_root(ldb, req, req->op.rename.olddn, &nc_root); + if (ret != LDB_SUCCESS) { + return ret; + } + if (ldb_dn_compare(nc_root, req->op.rename.olddn) == 0) { + talloc_free(nc_root); + DEBUG(10,("acl:renaming/moving a NC\n")); + /* Windows returns "ERR_UNWILLING_TO_PERFORM */ + return ldb_module_done(req, NULL, NULL, + LDB_ERR_UNWILLING_TO_PERFORM); + } + talloc_free(nc_root); + + /* Look for the parent */ + + ret = dsdb_module_search_dn(module, tmp_ctx, &acl_res, + req->op.rename.olddn, acl_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM | + DSDB_SEARCH_SHOW_RECYCLED, req); /* we sould be able to find the parent */ if (ret != LDB_SUCCESS) { DEBUG(10,("acl: failed to find object %s\n", ldb_dn_get_linearized(req->op.rename.olddn))); + talloc_free(tmp_ctx); return ret; } - guid = get_oc_guid_from_message(module,acl_res->msgs[0]); + schema = dsdb_get_schema(ldb, acl_res); + if (!schema) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + guid = get_oc_guid_from_message(schema, acl_res->msgs[0]); if (!insert_in_object_tree(tmp_ctx, guid, SEC_ADS_WRITE_PROP, &root, &new_node)) { - return LDB_ERR_OPERATIONS_ERROR; + talloc_free(tmp_ctx); + return ldb_operr(ldb); }; - guid = attribute_schemaid_guid_by_lDAPDisplayName(dsdb_get_schema(ldb), + guid = attribute_schemaid_guid_by_lDAPDisplayName(schema, "name"); if (!insert_in_object_tree(tmp_ctx, guid, SEC_ADS_WRITE_PROP, &new_node, &new_node)) { - return LDB_ERR_OPERATIONS_ERROR; + talloc_free(tmp_ctx); + return ldb_operr(ldb); + }; + + rdn_name = ldb_dn_get_rdn_name(req->op.rename.olddn); + if (rdn_name == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + guid = attribute_schemaid_guid_by_lDAPDisplayName(schema, + rdn_name); + if (!insert_in_object_tree(tmp_ctx, guid, SEC_ADS_WRITE_PROP, + &new_node, &new_node)) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); }; - ret = get_sd_from_ldb_message(req, acl_res->msgs[0], &sd); + ret = dsdb_get_sd_from_ldb_message(ldb, req, acl_res->msgs[0], &sd); if (ret != LDB_SUCCESS) { - return LDB_ERR_OPERATIONS_ERROR; + talloc_free(tmp_ctx); + return ldb_operr(ldb); } /* Theoretically we pass the check if the object has no sd */ if (!sd) { + talloc_free(tmp_ctx); return LDB_SUCCESS; } - ret = get_dom_sid_from_ldb_message(req, acl_res->msgs[0], &sid); - if (ret != LDB_SUCCESS) { - return LDB_ERR_OPERATIONS_ERROR; - } - + sid = samdb_result_dom_sid(req, acl_res->msgs[0], "objectSid"); status = sec_access_check_ds(sd, acl_user_token(module), SEC_ADS_WRITE_PROP, &access_granted, @@ -999,45 +1340,41 @@ static int acl_rename(struct ldb_module *module, struct ldb_request *req) sid); if (!NT_STATUS_IS_OK(status)) { - DEBUG(10, ("Object %s nas no wp on name\n", - ldb_dn_get_linearized(req->op.rename.olddn))); - acl_debug(sd, + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "Object %s has no wp on name\n", + ldb_dn_get_linearized(req->op.rename.olddn)); + dsdb_acl_debug(sd, acl_user_token(module), req->op.rename.olddn, true, 10); + talloc_free(tmp_ctx); return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; } if (ldb_dn_compare(oldparent, newparent) == 0) { /* regular rename, not move, nothing more to do */ + talloc_free(tmp_ctx); return ldb_next_request(module, req); } - /* What exactly to do in this case? It would fail anyway.. */ - if ((ldb_dn_compare(req->op.rename.newdn, (ldb_get_schema_basedn(ldb))) == 0) || - (ldb_dn_compare(req->op.rename.newdn, (ldb_get_config_basedn(ldb))) == 0) || - (ldb_dn_compare(req->op.rename.newdn, (ldb_get_root_basedn(ldb))) == 0)) { - DEBUG(10,("acl:moving as an NC\n")); - return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; - } /* new parent should have create child */ - talloc_free(tmp_ctx); - tmp_ctx = talloc_new(req); root = NULL; new_node = NULL; - guid = get_oc_guid_from_message(module,acl_res->msgs[0]); + guid = get_oc_guid_from_message(schema, acl_res->msgs[0]); if (!guid) { - DEBUG(10,("acl:renamed object has no object class\n")); + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "acl:renamed object has no object class\n"); + talloc_free(tmp_ctx); return ldb_module_done(req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); } - if (!insert_in_object_tree(tmp_ctx, guid, SEC_ADS_CREATE_CHILD, - &root, &new_node)) { - return LDB_ERR_OPERATIONS_ERROR; - } - ret = check_access_on_dn(module, req, newparent, SEC_ADS_CREATE_CHILD, root); + + ret = dsdb_module_check_access_on_dn(module, req, newparent, SEC_ADS_CREATE_CHILD, guid, req); if (ret != LDB_SUCCESS) { - DEBUG(10,("acl:access_denied renaming %s", ldb_dn_get_linearized(req->op.rename.olddn))); + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "acl:access_denied renaming %s", + ldb_dn_get_linearized(req->op.rename.olddn)); + talloc_free(tmp_ctx); return ret; } /* do we have delete object on the object? */ @@ -1049,20 +1386,83 @@ static int acl_rename(struct ldb_module *module, struct ldb_request *req) sid); if (NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); return ldb_next_request(module, req); } /* what about delete child on the current parent */ - ret = check_access_on_dn(module, req, oldparent, SEC_ADS_DELETE_CHILD, NULL); + ret = dsdb_module_check_access_on_dn(module, req, oldparent, SEC_ADS_DELETE_CHILD, NULL, req); if (ret != LDB_SUCCESS) { - DEBUG(10,("acl:access_denied renaming %s", ldb_dn_get_linearized(req->op.rename.olddn))); + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "acl:access_denied renaming %s", ldb_dn_get_linearized(req->op.rename.olddn)); + talloc_free(tmp_ctx); return ldb_module_done(req, NULL, NULL, ret); } + + talloc_free(tmp_ctx); + return ldb_next_request(module, req); } +static int acl_search_update_confidential_attrs(struct acl_context *ac, + struct acl_private *data) +{ + struct dsdb_attribute *a; + uint32_t n = 0; + + if (data->acl_search) { + /* + * if acl:search is activated we + * the acl_read module protects + * confidential attributes. + */ + return LDB_SUCCESS; + } + + if ((ac->schema == data->cached_schema_ptr) && + (ac->schema->loaded_usn == data->cached_schema_loaded_usn) && + (ac->schema->metadata_usn == data->cached_schema_metadata_usn)) + { + return LDB_SUCCESS; + } + + data->cached_schema_ptr = NULL; + data->cached_schema_loaded_usn = 0; + data->cached_schema_metadata_usn = 0; + TALLOC_FREE(data->confidential_attrs); + + if (ac->schema == NULL) { + return LDB_SUCCESS; + } + + for (a = ac->schema->attributes; a; a = a->next) { + const char **attrs = data->confidential_attrs; + + if (!(a->searchFlags & SEARCH_FLAG_CONFIDENTIAL)) { + continue; + } + + attrs = talloc_realloc(data, attrs, const char *, n + 2); + if (attrs == NULL) { + TALLOC_FREE(data->confidential_attrs); + return ldb_module_oom(ac->module); + } + + attrs[n] = a->lDAPDisplayName; + attrs[n+1] = NULL; + n++; + + data->confidential_attrs = attrs; + } + + data->cached_schema_ptr = ac->schema; + data->cached_schema_loaded_usn = ac->schema->loaded_usn; + data->cached_schema_metadata_usn = ac->schema->metadata_usn; + + return LDB_SUCCESS; +} + static int acl_search_callback(struct ldb_request *req, struct ldb_reply *ares) { - struct ldb_context *ldb; struct acl_context *ac; struct acl_private *data; struct ldb_result *acl_res; @@ -1072,12 +1472,11 @@ static int acl_search_callback(struct ldb_request *req, struct ldb_reply *ares) "objectSid", NULL }; - int ret, i; + int ret; + unsigned int i; ac = talloc_get_type(req->context, struct acl_context); data = talloc_get_type(ldb_module_get_private(ac->module), struct acl_private); - ldb = ldb_module_get_ctx(ac->module); - if (!ares) { return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); @@ -1089,50 +1488,95 @@ static int acl_search_callback(struct ldb_request *req, struct ldb_reply *ares) switch (ares->type) { case LDB_REPLY_ENTRY: - if (ac->allowedAttributes - || ac->allowedChildClasses - || ac->allowedChildClassesEffective - || ac->allowedAttributesEffective - || ac->sDRightsEffective) { - ret = ldb_search(ldb, ac, &acl_res, ares->message->dn, LDB_SCOPE_BASE, acl_attrs, NULL); + if (ac->constructed_attrs) { + ret = dsdb_module_search_dn(ac->module, ac, &acl_res, ares->message->dn, + acl_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM | + DSDB_SEARCH_SHOW_RECYCLED, + req); if (ret != LDB_SUCCESS) { return ldb_module_done(ac->req, NULL, NULL, ret); } - if (ac->allowedAttributes || ac->allowedAttributesEffective) { - ret = acl_allowedAttributes(ac->module, acl_res->msgs[0], ares->message, ac); - if (ret != LDB_SUCCESS) { - return ldb_module_done(ac->req, NULL, NULL, ret); - } + } + + if (ac->allowedAttributes || ac->allowedAttributesEffective) { + ret = acl_allowedAttributes(ac->module, ac->schema, + acl_res->msgs[0], + ares->message, ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); } - if (ac->allowedChildClasses) { - ret = acl_childClasses(ac->module, acl_res->msgs[0], - ares->message, "allowedChildClasses"); - if (ret != LDB_SUCCESS) { - return ldb_module_done(ac->req, NULL, NULL, ret); - } + } + + if (ac->allowedChildClasses) { + ret = acl_childClasses(ac->module, ac->schema, + acl_res->msgs[0], + ares->message, + "allowedChildClasses"); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); } - if (ac->allowedChildClassesEffective) { - ret = acl_childClassesEffective(ac->module, - acl_res->msgs[0], ares->message, ac); - if (ret != LDB_SUCCESS) { - return ldb_module_done(ac->req, NULL, NULL, ret); - } + } + + if (ac->allowedChildClassesEffective) { + ret = acl_childClassesEffective(ac->module, ac->schema, + acl_res->msgs[0], + ares->message, ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); } - if (ac->sDRightsEffective) { - ret = acl_sDRightsEffective(ac->module, - acl_res->msgs[0], ares->message, ac); - if (ret != LDB_SUCCESS) { - return ldb_module_done(ac->req, NULL, NULL, ret); - } + } + + if (ac->sDRightsEffective) { + ret = acl_sDRightsEffective(ac->module, + acl_res->msgs[0], + ares->message, ac); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); } } - if (data && data->password_attrs) { - if (!ac->am_system) { - for (i = 0; data->password_attrs[i]; i++) { - ldb_msg_remove_attr(ares->message, data->password_attrs[i]); + + if (data == NULL) { + return ldb_module_send_entry(ac->req, ares->message, + ares->controls); + } + + if (ac->am_system) { + return ldb_module_send_entry(ac->req, ares->message, + ares->controls); + } + + if (data->password_attrs != NULL) { + for (i = 0; data->password_attrs[i]; i++) { + if ((!ac->userPassword) && + (ldb_attr_cmp(data->password_attrs[i], + "userPassword") == 0)) + { + continue; } + + ldb_msg_remove_attr(ares->message, data->password_attrs[i]); + } + } + + if (ac->am_administrator) { + return ldb_module_send_entry(ac->req, ares->message, + ares->controls); + } + + ret = acl_search_update_confidential_attrs(ac, data); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (data->confidential_attrs != NULL) { + for (i = 0; data->confidential_attrs[i]; i++) { + ldb_msg_remove_attr(ares->message, + data->confidential_attrs[i]); } } + return ldb_module_send_entry(ac->req, ares->message, ares->controls); case LDB_REPLY_REFERRAL: @@ -1150,51 +1594,94 @@ static int acl_search(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct acl_context *ac; + struct ldb_parse_tree *down_tree; struct ldb_request *down_req; struct acl_private *data; - int ret, i; + int ret; + unsigned int i; ldb = ldb_module_get_ctx(module); ac = talloc_zero(req, struct acl_context); if (ac == NULL) { - ldb_oom(ldb); - return LDB_ERR_OPERATIONS_ERROR; + return ldb_oom(ldb); } data = talloc_get_type(ldb_module_get_private(module), struct acl_private); ac->module = module; ac->req = req; ac->am_system = dsdb_module_am_system(module); + ac->am_administrator = dsdb_module_am_administrator(module); + ac->constructed_attrs = false; + ac->modify_search = true; ac->allowedAttributes = ldb_attr_in_list(req->op.search.attrs, "allowedAttributes"); ac->allowedAttributesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedAttributesEffective"); ac->allowedChildClasses = ldb_attr_in_list(req->op.search.attrs, "allowedChildClasses"); ac->allowedChildClassesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedChildClassesEffective"); ac->sDRightsEffective = ldb_attr_in_list(req->op.search.attrs, "sDRightsEffective"); + ac->userPassword = dsdb_user_password_support(module, ac, req); + ac->schema = dsdb_get_schema(ldb, ac); + + ac->constructed_attrs |= ac->allowedAttributes; + ac->constructed_attrs |= ac->allowedChildClasses; + ac->constructed_attrs |= ac->allowedChildClassesEffective; + ac->constructed_attrs |= ac->allowedAttributesEffective; + ac->constructed_attrs |= ac->sDRightsEffective; - /* replace any attributes in the parse tree that are private, - so we don't allow a search for 'userPassword=penguin', - just as we would not allow that attribute to be returned */ + if (data == NULL) { + ac->modify_search = false; + } if (ac->am_system) { - /* FIXME: We should copy the tree and keep the original unmodified. */ - /* remove password attributes */ - if (data && data->password_attrs) { - for (i = 0; data->password_attrs[i]; i++) { - ldb_parse_tree_attr_replace(req->op.search.tree, - data->password_attrs[i], - "kludgeACLredactedattribute"); + ac->modify_search = false; + } + + if (!ac->constructed_attrs && !ac->modify_search) { + return ldb_next_request(module, req); + } + + ret = acl_search_update_confidential_attrs(ac, data); + if (ret != LDB_SUCCESS) { + return ret; + } + + down_tree = ldb_parse_tree_copy_shallow(ac, req->op.search.tree); + if (down_tree == NULL) { + return ldb_oom(ldb); + } + + if (!ac->am_system && data->password_attrs) { + for (i = 0; data->password_attrs[i]; i++) { + if ((!ac->userPassword) && + (ldb_attr_cmp(data->password_attrs[i], + "userPassword") == 0)) + { + continue; } + + ldb_parse_tree_attr_replace(down_tree, + data->password_attrs[i], + "kludgeACLredactedattribute"); } } + + if (!ac->am_system && !ac->am_administrator && data->confidential_attrs) { + for (i = 0; data->confidential_attrs[i]; i++) { + ldb_parse_tree_attr_replace(down_tree, + data->confidential_attrs[i], + "kludgeACLredactedattribute"); + } + } + ret = ldb_build_search_req_ex(&down_req, ldb, ac, req->op.search.base, req->op.search.scope, - req->op.search.tree, + down_tree, req->op.search.attrs, req->controls, ac, acl_search_callback, req); + LDB_REQ_SET_LOCATION(down_req); if (ret != LDB_SUCCESS) { return ret; } @@ -1202,12 +1689,43 @@ static int acl_search(struct ldb_module *module, struct ldb_request *req) return ldb_next_request(module, down_req); } -_PUBLIC_ const struct ldb_module_ops ldb_acl_module_ops = { +static int acl_extended(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID); + + /* allow everybody to read the sequence number */ + if (strcmp(req->op.extended.oid, + LDB_EXTENDED_SEQUENCE_NUMBER) == 0) { + return ldb_next_request(module, req); + } + + if (dsdb_module_am_system(module) || + dsdb_module_am_administrator(module) || as_system) { + return ldb_next_request(module, req); + } else { + ldb_asprintf_errstring(ldb, + "acl_extended: " + "attempted database modify not permitted. " + "User %s is not SYSTEM or an administrator", + acl_user_name(req, module)); + return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS; + } +} + +static const struct ldb_module_ops ldb_acl_module_ops = { .name = "acl", .search = acl_search, .add = acl_add, .modify = acl_modify, .del = acl_delete, .rename = acl_rename, + .extended = acl_extended, .init_context = acl_module_init }; + +int ldb_acl_module_init(const char *version) +{ + LDB_MODULE_CHECK_VERSION(version); + return ldb_register_module(&ldb_acl_module_ops); +}