struct ldb_request *req;
const char * const *attrs;
const struct dsdb_schema *schema;
- bool sd;
- bool instance_type;
- bool object_sid;
+ uint32_t sd_flags;
+ bool added_nTSecurityDescriptor;
+ bool added_instanceType;
+ bool added_objectSid;
+ bool added_objectClass;
bool indirsync;
+
+ /* cache on the last parent we checked in this search */
+ struct ldb_dn *last_parent_dn;
+ int last_parent_check_ret;
};
struct aclread_private {
bool enabled;
+
+ /* cache of the last SD we read during any search */
+ struct security_descriptor *sd_cached;
+ struct ldb_val sd_cached_blob;
};
static void aclread_mark_inaccesslible(struct ldb_message_element *el) {
return el->flags & LDB_FLAG_INTERNAL_INACCESSIBLE_ATTRIBUTE;
}
+/*
+ * the object has a parent, so we have to check for visibility
+ *
+ * This helper function uses a per-search cache to avoid checking the
+ * parent object for each of many possible children. This is likely
+ * to help on SCOPE_ONE searches and on typical tree structures for
+ * SCOPE_SUBTREE, where an OU has many users as children.
+ *
+ * We rely for safety on the DB being locked for reads during the full
+ * search.
+ */
+static int aclread_check_parent(struct aclread_context *ac,
+ struct ldb_message *msg,
+ struct ldb_request *req)
+{
+ int ret;
+ struct ldb_dn *parent_dn = NULL;
+
+ /* We may have a cached result from earlier in this search */
+ if (ac->last_parent_dn != NULL) {
+ /*
+ * We try the no-allocation ldb_dn_compare_base()
+ * first however it will not tell parents and
+ * grand-parents apart
+ */
+ int cmp_base = ldb_dn_compare_base(ac->last_parent_dn,
+ msg->dn);
+ if (cmp_base == 0) {
+ /* Now check if it is a direct parent */
+ parent_dn = ldb_dn_get_parent(ac, msg->dn);
+ if (parent_dn == NULL) {
+ return ldb_oom(ldb_module_get_ctx(ac->module));
+ }
+ if (ldb_dn_compare(ac->last_parent_dn,
+ parent_dn) == 0) {
+ TALLOC_FREE(parent_dn);
+
+ /*
+ * If we checked the same parent last
+ * time, then return the cached
+ * result.
+ *
+ * The cache is valid as long as the
+ * search as the DB is read locked and
+ * the session_info (connected user)
+ * is constant.
+ */
+ return ac->last_parent_check_ret;
+ }
+ }
+ }
+
+ {
+ TALLOC_CTX *frame = NULL;
+ frame = talloc_stackframe();
+
+ /*
+ * This may have been set in the block above, don't
+ * re-parse
+ */
+ if (parent_dn == NULL) {
+ parent_dn = ldb_dn_get_parent(ac, msg->dn);
+ if (parent_dn == NULL) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb_module_get_ctx(ac->module));
+ }
+ }
+ ret = dsdb_module_check_access_on_dn(ac->module,
+ frame,
+ parent_dn,
+ SEC_ADS_LIST,
+ NULL, req);
+ talloc_unlink(ac, ac->last_parent_dn);
+ ac->last_parent_dn = parent_dn;
+ ac->last_parent_check_ret = ret;
+
+ TALLOC_FREE(frame);
+ }
+ return ret;
+}
+
+/*
+ * The sd returned from this function is valid until the next call on
+ * this module context
+ *
+ * This helper function uses a cache on the module private data to
+ * speed up repeated use of the same SD.
+ */
+
+static int aclread_get_sd_from_ldb_message(struct aclread_context *ac,
+ struct ldb_message *acl_res,
+ struct security_descriptor **sd)
+{
+ struct ldb_message_element *sd_element;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct aclread_private *private_data
+ = talloc_get_type(ldb_module_get_private(ac->module),
+ struct aclread_private);
+ enum ndr_err_code ndr_err;
+
+ sd_element = ldb_msg_find_element(acl_res, "nTSecurityDescriptor");
+ if (sd_element == NULL) {
+ return ldb_error(ldb, LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS,
+ "nTSecurityDescriptor is missing");
+ }
+
+ if (sd_element->num_values != 1) {
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * The time spent in ndr_pull_security_descriptor() is quite
+ * expensive, so we check if this is the same binary blob as last
+ * time, and if so return the memory tree from that previous parse.
+ */
+
+ if (private_data->sd_cached != NULL &&
+ private_data->sd_cached_blob.data != NULL &&
+ ldb_val_equal_exact(&sd_element->values[0],
+ &private_data->sd_cached_blob)) {
+ *sd = private_data->sd_cached;
+ return LDB_SUCCESS;
+ }
+
+ *sd = talloc(private_data, struct security_descriptor);
+ if(!*sd) {
+ return ldb_oom(ldb);
+ }
+ ndr_err = ndr_pull_struct_blob(&sd_element->values[0], *sd, *sd,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(*sd);
+ return ldb_operr(ldb);
+ }
+
+ talloc_unlink(private_data, private_data->sd_cached_blob.data);
+ if (ac->added_nTSecurityDescriptor) {
+ private_data->sd_cached_blob = sd_element->values[0];
+ talloc_steal(private_data, sd_element->values[0].data);
+ } else {
+ private_data->sd_cached_blob = ldb_val_dup(private_data,
+ &sd_element->values[0]);
+ if (private_data->sd_cached_blob.data == NULL) {
+ TALLOC_FREE(*sd);
+ return ldb_operr(ldb);
+ }
+ }
+
+ talloc_unlink(private_data, private_data->sd_cached);
+ private_data->sd_cached = *sd;
+
+ return LDB_SUCCESS;
+}
+
+
static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares)
{
struct ldb_context *ldb;
struct ldb_message *msg;
int ret, num_of_attrs = 0;
unsigned int i, k = 0;
- struct security_descriptor *sd;
+ struct security_descriptor *sd = NULL;
struct dom_sid *sid = NULL;
TALLOC_CTX *tmp_ctx;
uint32_t instanceType;
+ const struct dsdb_class *objectclass;
ac = talloc_get_type(req->context, struct aclread_context);
ldb = ldb_module_get_ctx(ac->module);
switch (ares->type) {
case LDB_REPLY_ENTRY:
msg = ares->message;
- ret = dsdb_get_sd_from_ldb_message(ldb, tmp_ctx, msg, &sd);
- if (ret != LDB_SUCCESS || sd == NULL ) {
- DEBUG(10, ("acl_read: cannot get descriptor\n"));
+ ret = aclread_get_sd_from_ldb_message(ac, msg, &sd);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "acl_read: cannot get descriptor of %s: %s\n",
+ ldb_dn_get_linearized(msg->dn), ldb_strerror(ret));
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto fail;
+ } else if (sd == NULL) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "acl_read: cannot get descriptor of %s (attribute not found)\n",
+ ldb_dn_get_linearized(msg->dn));
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto fail;
+ }
+ /*
+ * Get the most specific structural object class for the ACL check
+ */
+ objectclass = dsdb_get_structural_oc_from_msg(ac->schema, msg);
+ if (objectclass == NULL) {
+ ldb_asprintf_errstring(ldb, "acl_read: Failed to find a structural class for %s",
+ ldb_dn_get_linearized(msg->dn));
ret = LDB_ERR_OPERATIONS_ERROR;
goto fail;
}
+
sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
/* get the object instance type */
instanceType = ldb_msg_find_attr_as_uint(msg,
if (!ldb_dn_is_null(msg->dn) && !(instanceType & INSTANCE_TYPE_IS_NC_HEAD))
{
/* the object has a parent, so we have to check for visibility */
- struct ldb_dn *parent_dn = ldb_dn_get_parent(tmp_ctx, msg->dn);
-
- ret = dsdb_module_check_access_on_dn(ac->module,
- tmp_ctx,
- parent_dn,
- SEC_ADS_LIST,
- NULL, req);
+ ret = aclread_check_parent(ac, msg, req);
+
if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
talloc_free(tmp_ctx);
return LDB_SUCCESS;
} else if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "acl_read: %s check parent %s - %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_strerror(ret),
+ ldb_errstring(ldb));
goto fail;
}
}
+
/* for every element in the message check RP */
for (i=0; i < msg->num_elements; i++) {
const struct dsdb_attribute *attr;
- bool is_sd, is_objectsid, is_instancetype;
+ bool is_sd, is_objectsid, is_instancetype, is_objectclass;
uint32_t access_mask;
attr = dsdb_attribute_by_lDAPDisplayName(ac->schema,
msg->elements[i].name);
if (!attr) {
- DEBUG(2, ("acl_read: cannot find attribute %s in schema\n",
- msg->elements[i].name));
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "acl_read: %s cannot find attr[%s] in of schema\n",
+ ldb_dn_get_linearized(msg->dn),
+ msg->elements[i].name);
ret = LDB_ERR_OPERATIONS_ERROR;
goto fail;
}
msg->elements[i].name) == 0;
is_instancetype = ldb_attr_cmp("instanceType",
msg->elements[i].name) == 0;
+ is_objectclass = ldb_attr_cmp("objectClass",
+ msg->elements[i].name) == 0;
/* these attributes were added to perform access checks and must be removed */
- if (is_objectsid && ac->object_sid) {
+ if (is_objectsid && ac->added_objectSid) {
+ aclread_mark_inaccesslible(&msg->elements[i]);
+ continue;
+ }
+ if (is_instancetype && ac->added_instanceType) {
aclread_mark_inaccesslible(&msg->elements[i]);
continue;
}
- if (is_instancetype && ac->instance_type) {
+ if (is_objectclass && ac->added_objectClass) {
aclread_mark_inaccesslible(&msg->elements[i]);
continue;
}
- if (is_sd && ac->sd) {
+ if (is_sd && ac->added_nTSecurityDescriptor) {
aclread_mark_inaccesslible(&msg->elements[i]);
continue;
}
/* nTSecurityDescriptor is a special case */
if (is_sd) {
- access_mask = SEC_FLAG_SYSTEM_SECURITY|SEC_STD_READ_CONTROL;
+ access_mask = 0;
+
+ if (ac->sd_flags & (SECINFO_OWNER|SECINFO_GROUP)) {
+ access_mask |= SEC_STD_READ_CONTROL;
+ }
+ if (ac->sd_flags & SECINFO_DACL) {
+ access_mask |= SEC_STD_READ_CONTROL;
+ }
+ if (ac->sd_flags & SECINFO_SACL) {
+ access_mask |= SEC_FLAG_SYSTEM_SECURITY;
+ }
} else {
access_mask = SEC_ADS_READ_PROP;
}
access_mask |= SEC_ADS_CONTROL_ACCESS;
}
+ if (access_mask == 0) {
+ aclread_mark_inaccesslible(&msg->elements[i]);
+ continue;
+ }
+
ret = acl_check_access_on_attribute(ac->module,
tmp_ctx,
sd,
sid,
access_mask,
- attr);
+ attr,
+ objectclass);
/*
* Dirsync control needs the replpropertymetadata attribute
}
}
} else if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "acl_read: %s check attr[%s] gives %s - %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ msg->elements[i].name,
+ ldb_strerror(ret),
+ ldb_errstring(ldb));
goto fail;
}
}
k++;
}
}
+ /*
+ * This should not be needed, but some modules
+ * may allocate values on the wrong context...
+ */
+ talloc_steal(ret_msg->elements, msg);
} else {
ret_msg->elements = NULL;
}
uint32_t flags = ldb_req_get_custom_flags(req);
struct ldb_result *res;
struct aclread_private *p;
+ bool need_sd = false;
+ bool explicit_sd_flags = false;
bool is_untrusted = ldb_req_is_untrusted(req);
+ static const char * const _all_attrs[] = { "*", NULL };
+ bool all_attrs = false;
const char * const *attrs = NULL;
uint32_t instanceType;
static const char *acl_attrs[] = {
ret = dsdb_module_search_dn(module, req, &res, req->op.search.base,
acl_attrs,
DSDB_FLAG_NEXT_MODULE |
- DSDB_SEARCH_SHOW_DELETED, req);
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
if (ret != LDB_SUCCESS) {
return ldb_error(ldb, ret,
"acl_read: Error retrieving instanceType for base.");
if (!ac->schema) {
return ldb_operr(ldb);
}
+
+ attrs = req->op.search.attrs;
+ if (attrs == NULL) {
+ all_attrs = true;
+ attrs = _all_attrs;
+ } else if (attrs[0] == NULL) {
+ all_attrs = true;
+ attrs = _all_attrs;
+ } else if (ldb_attr_in_list(attrs, "*")) {
+ all_attrs = true;
+ }
+
/*
* In theory we should also check for the SD control but control verification is
* expensive so we'd better had the ntsecuritydescriptor to the list of
* searched attribute and then remove it !
*/
- ac->sd = !(ldb_attr_in_list(req->op.search.attrs, "nTSecurityDescriptor"));
- if (req->op.search.attrs && !ldb_attr_in_list(req->op.search.attrs, "*")) {
- if (!ldb_attr_in_list(req->op.search.attrs, "instanceType")) {
- ac->instance_type = true;
- attrs = ldb_attr_list_copy_add(ac, req->op.search.attrs, "instanceType");
- } else {
- attrs = req->op.search.attrs;
+ ac->sd_flags = dsdb_request_sd_flags(ac->req, &explicit_sd_flags);
+
+ if (ldb_attr_in_list(attrs, "nTSecurityDescriptor")) {
+ need_sd = false;
+ } else if (explicit_sd_flags && all_attrs) {
+ need_sd = false;
+ } else {
+ need_sd = true;
+ }
+
+ if (!all_attrs) {
+ if (!ldb_attr_in_list(attrs, "instanceType")) {
+ attrs = ldb_attr_list_copy_add(ac, attrs, "instanceType");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->added_instanceType = true;
}
if (!ldb_attr_in_list(req->op.search.attrs, "objectSid")) {
- ac->object_sid = true;
attrs = ldb_attr_list_copy_add(ac, attrs, "objectSid");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->added_objectSid = true;
+ }
+ if (!ldb_attr_in_list(req->op.search.attrs, "objectClass")) {
+ attrs = ldb_attr_list_copy_add(ac, attrs, "objectClass");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->added_objectClass = true;
}
}
- if (ac->sd) {
- /* avoid replacing all attributes with nTSecurityDescriptor
- * if attribute list is empty */
- if (!attrs) {
- attrs = ldb_attr_list_copy_add(ac, req->op.search.attrs, "*");
- }
+ if (need_sd) {
attrs = ldb_attr_list_copy_add(ac, attrs, "nTSecurityDescriptor");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->added_nTSecurityDescriptor = true;
}
+
ac->attrs = req->op.search.attrs;
ret = ldb_build_search_req_ex(&down_req,
ldb, ac,
if (p == NULL) {
return ldb_module_oom(module);
}
- p->enabled = lpcfg_parm_bool(ldb_get_opaque(ldb, "loadparm"), NULL, "acl", "search", false);
+ p->enabled = lpcfg_parm_bool(ldb_get_opaque(ldb, "loadparm"), NULL, "acl", "search", true);
ldb_module_set_private(module, p);
return ldb_next_init(module);
}