/*
ldb database library
Copyright (C) Simo Sorce 2006-2008
Copyright (C) Nadezhda Ivanova 2009
Copyright (C) Anatoliy Atanasov 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: ldb ACL module
*
* Description: Module that performs authorisation access checks based on the
* account's security context and the DACL of the object being polled.
* Only DACL checks implemented at this point
*
* Authors: Nadezhda Ivanova, Anatoliy Atanasov
*/
#include "includes.h"
#include "ldb_module.h"
#include "auth/auth.h"
#include "libcli/security/security.h"
#include "librpc/gen_ndr/ndr_security.h"
#include "dsdb/samdb/samdb.h"
#include "librpc/gen_ndr/ndr_security.h"
#include "param/param.h"
/* acl_search helper */
struct acl_context {
struct ldb_module *module;
struct ldb_request *req;
struct ldb_request *down_req;
/*needed if we have to identify if this is SYSTEM_USER*/
enum security_user_level user_type;
uint32_t access_needed;
struct ldb_dn * dn_to_check;
/* set to true when we need to process the request as a SYSTEM_USER, regardless
* of the user's actual rights - for example when we need to retrieve the
* ntSecurityDescriptor */
bool ignore_security;
struct security_token *token;
/*needed to identify if we have requested these attributes*/
bool nTSecurityDescriptor;
bool objectClass;
int sec_result;
};
struct extended_access_check_attribute {
const char *oa_name;
const uint32_t requires_rights;
};
struct acl_private{
bool perform_check;
};
static int acl_search_callback(struct ldb_request *req, struct ldb_reply *ares);
/*FIXME: Perhaps this should go in the .idl file*/
#define SEC_GENERIC_ACCESS_NEVER_GRANTED ( 0xFFFFFFFF )
/*Contains a part of the attributes - the ones that have predefined required rights*/
static const struct extended_access_check_attribute extended_access_checks_table[] =
{
{
.oa_name = "nTSecurityDescriptor",
.requires_rights = SEC_FLAG_SYSTEM_SECURITY & SEC_STD_READ_CONTROL,
},
{
.oa_name = "pekList",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "currentValue",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "dBCSPwd",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "unicodePwd",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "ntPwdHistory",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "priorValue",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "supplementalCredentials",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "trustAuthIncoming",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "trustAuthOutgoing",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "ImPwdHistory",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "initialAuthIncoming",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "initialAuthOutgoing",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
{
.oa_name = "msDS-ExecuteScriptPassword",
.requires_rights = SEC_GENERIC_ACCESS_NEVER_GRANTED,
},
};
static NTSTATUS extended_access_check(const char *attribute_name, const int access_rights, uint32_t searchFlags)
{
int i = 0;
if (access_rights == SEC_GENERIC_ACCESS_NEVER_GRANTED) {
return NT_STATUS_ACCESS_DENIED;
}
/*Check if the attribute is in the table first*/
for ( i = 0; extended_access_checks_table[i].oa_name; i++ ) {
if (ldb_attr_cmp(extended_access_checks_table[i].oa_name, attribute_name) == 0) {
if ((access_rights & extended_access_checks_table[i].requires_rights) == access_rights) {
return NT_STATUS_OK;
} else {
return NT_STATUS_ACCESS_DENIED;
}
}
}
/*Check for attribute whose attributeSchema has 0x80 set in searchFlags*/
if ((searchFlags & SEARCH_FLAG_CONFIDENTIAL) == SEARCH_FLAG_CONFIDENTIAL) {
if (((SEC_ADS_READ_PROP & SEC_ADS_CONTROL_ACCESS) & access_rights) == access_rights) {
return NT_STATUS_OK;
} else {
return NT_STATUS_ACCESS_DENIED;
}
}
/*Check attributes with *special* behaviour*/
if (ldb_attr_cmp("msDS-QuotaEffective", attribute_name) == 0 || ldb_attr_cmp("msDS-QuotaUsed", attribute_name) == 0){
/*Rights required:
*
*(RIGHT_DS_READ_PROPERTY on the Quotas container or
*((the client is querying the quota for the security principal it is authenticated as) and
*(DS-Query-Self-Quota control access right on the Quotas container))
*/
}
if (ldb_attr_cmp("userPassword", attribute_name) == 0) {
/*When the dSHeuristics.fUserPwdSupport flag is false, the requester must be granted RIGHT_DS_READ_PROPERTY.
*When the dSHeuristics.fUserPwdSupport flag is true, access is never granted.
*/
}
if (ldb_attr_cmp("sDRightsEffective", attribute_name) == 0) {
/*FIXME:3.1.1.4.5.4 in MS-ADTS*/
}
if (ldb_attr_cmp("allowedChildClassesEffective", attribute_name) == 0) {
/*FIXME:3.1.1.4.5.5 in MS-ADTS*/
}
if (ldb_attr_cmp("allowedAttributesEffective", attribute_name) == 0) {
/*FIXME:3.1.1.4.5.7 in MS-ADTS*/
}
if (ldb_attr_cmp("msDS-Approx-Immed-Subordinates", attribute_name) == 0) {
/*FIXME:3.1.1.4.5.15 in MS-ADTS*/
}
if (ldb_attr_cmp("msDS-QuotaEffective", attribute_name) == 0) {
/*FIXME:3.1.1.4.5.22 in MS-ADTS*/
}
if (ldb_attr_cmp("msDS-ReplAttributeMetaData", attribute_name) == 0 || ldb_attr_cmp("msDS-ReplAttributeMetaData", attribute_name) == 0) {
/*The security context of the requester must be granted the following rights on the replPropertyMetaData attribute:
*(RIGHT_DS_READ_PROPERTY)or (DS-Replication-Manage-Topology by ON!nTSecurityDescriptor)
*/
}
if (ldb_attr_cmp("msDS-NCReplInboundNeighbors", attribute_name) == 0) {
/*The security context of the requester must be granted the following rights on repsFrom:
*(RIGHT_DS_READ_PROPERTY) or (DS-Replication-Manage-Topology) or (DS-Replication-Monitor-Topology)
*/
}
if (ldb_attr_cmp("msDS-NCReplOutboundNeighbors", attribute_name) == 0) {
/*The security context of the requester must be granted the following rights on repsTo:
*(RIGHT_DS_READ_PROPERTY) or (DS-Replication-Manage-Topology) or (DS-Replication-Monitor-Topology)
*/
}
if (ldb_attr_cmp("msDS-NCReplCursors", attribute_name) == 0) {
/*The security context of the requester must be granted the following rights on replUpToDateVector: (RIGHT_DS_READ_PROPERTY)
*or (DS-Replication-Manage-Topology) or (DS-Replication-Monitor-Topology)
*/
}
if (ldb_attr_cmp("msDS-IsUserCachableAtRodc", attribute_name) == 0) {
/*The security context of the requester must be granted
*the DS-Replication-Secrets-Synchronize control access right on the root of the default NC.
*/
}
return NT_STATUS_OK;
}
/* Builds an object tree for object specific access checks */
static struct object_tree * build_object_tree_form_attr_list(TALLOC_CTX *mem_ctx, /* Todo this context or separate? */
struct ldb_context *ldb,
const char ** attr_names,
int num_attrs,
const char * object_class,
uint32_t init_access)
{
const struct dsdb_schema *schema = dsdb_get_schema(ldb);
const struct GUID *oc_guid = class_schemaid_guid_by_lDAPDisplayName(schema, object_class);
struct object_tree *tree;
int i;
if (!oc_guid)
return NULL;
tree = insert_in_object_tree(mem_ctx, oc_guid, NULL, init_access, NULL);
if (attr_names){
for (i=0; i < num_attrs; i++){
const struct dsdb_attribute *attribute = dsdb_attribute_by_lDAPDisplayName(schema,attr_names[i]);
if (attribute)
insert_in_object_tree(mem_ctx,
&attribute->schemaIDGUID,
&attribute->attributeSecurityGUID,
init_access,
tree);
}
}
return tree;
}
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 int acl_op_callback(struct ldb_request *req, struct ldb_reply *ares)
{
struct acl_context *ac;
ac = talloc_get_type(req->context, struct acl_context);
if (!ares) {
return ldb_module_done(ac->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
if (ares->error != LDB_SUCCESS) {
return ldb_module_done(ac->req, ares->controls,
ares->response, ares->error);
}
if (ares->type != LDB_REPLY_DONE) {
talloc_free(ares);
return ldb_module_done(ac->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
return ldb_module_done(ac->req, ares->controls,
ares->response, ares->error);
}
static int acl_access_check_add(struct ldb_reply *ares,
struct acl_context *ac,
struct security_descriptor *sd)
{
uint32_t access_granted = 0;
NTSTATUS status;
struct ldb_dn *parent;
struct ldb_dn *grandparent;
struct object_tree *tree = NULL;
parent = ldb_dn_get_parent(ac->req, ac->req->op.add.message->dn);
grandparent = ldb_dn_get_parent(ac->req, parent);
if (ldb_dn_compare(ares->message->dn, grandparent) == 0)
status = sec_access_check_ds(sd, ac->token,
SEC_ADS_LIST,
&access_granted,
NULL);
else if (ldb_dn_compare(ares->message->dn, parent) == 0){
struct ldb_message_element *oc_el;
struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
const struct dsdb_schema *schema = dsdb_get_schema(ldb);
int i;
oc_el = ldb_msg_find_element(ares->message, "objectClass");
if (!oc_el || oc_el->num_values == 0)
return LDB_SUCCESS;
for (i = 0; i < oc_el->num_values; i++){
const struct GUID *guid = class_schemaid_guid_by_lDAPDisplayName(schema,
oc_el->values[i].data);
ac->sec_result = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
tree = insert_in_object_tree(ac->req, guid, NULL, SEC_ADS_CREATE_CHILD,
tree);
status = sec_access_check_ds(sd, ac->token, SEC_ADS_CREATE_CHILD,&access_granted, tree);
if (NT_STATUS_IS_OK(status))
ac->sec_result = LDB_SUCCESS;
}
}
else
return LDB_SUCCESS;
return ac->sec_result;
}
static int acl_access_check_modify(struct ldb_reply *ares, struct acl_context *ac,
struct security_descriptor *sd)
{
uint32_t access_granted = 0;
NTSTATUS status;
struct ldb_dn *parent;
struct object_tree *tree = NULL;
parent = ldb_dn_get_parent(ac->req, ac->req->op.add.message->dn);
if (ldb_dn_compare(ares->message->dn, parent) == 0)
status = sec_access_check_ds(sd, ac->token, SEC_ADS_LIST,&access_granted, NULL);
else if (ldb_dn_compare(ares->message->dn, ac->req->op.add.message->dn) == 0){
struct ldb_message_element *oc_el;
struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
const struct dsdb_schema *schema = dsdb_get_schema(ldb);
int i;
struct GUID *guid;
oc_el = ldb_msg_find_element(ares->message, "objectClass");
if (!oc_el || oc_el->num_values == 0)
return LDB_SUCCESS;
guid = class_schemaid_guid_by_lDAPDisplayName(schema,
oc_el->values[oc_el->num_values-1].data);
tree = insert_in_object_tree(ac->req, guid, NULL, SEC_ADS_WRITE_PROP,
tree);
for (i=0; i < ac->req->op.mod.message->num_elements; i++){
const struct dsdb_attribute *attr = dsdb_attribute_by_lDAPDisplayName(schema,
ac->req->op.mod.message->elements[i].name);
if (!attr)
return LDB_ERR_OPERATIONS_ERROR; /* What should we actually return here? */
insert_in_object_tree(ac, &attr->schemaIDGUID,
&attr->attributeSecurityGUID, ac->access_needed, tree);
}
status = sec_access_check_ds(sd, ac->token, SEC_ADS_WRITE_PROP ,&access_granted, tree);
if (!NT_STATUS_IS_OK(status))
ac->sec_result = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
}
else
return LDB_SUCCESS;
return ac->sec_result;
}
/*TODO*/
static int acl_access_check_rename(struct ldb_reply *ares, struct acl_context *ac,
struct security_descriptor *sd)
{
return ac->sec_result;
}
static int acl_access_check_delete(struct ldb_reply *ares, struct acl_context *ac,
struct security_descriptor *sd)
{
uint32_t access_granted = 0;
NTSTATUS status;
struct ldb_dn *parent;
struct object_tree *tree = NULL;
parent = ldb_dn_get_parent(ac->req, ac->req->op.del.dn);
if (ldb_dn_compare(ares->message->dn, parent) == 0){
status = sec_access_check_ds(sd, ac->token, SEC_ADS_LIST,&access_granted, NULL);
if (!NT_STATUS_IS_OK(status)){
ac->sec_result = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
return ac->sec_result;
}
status = sec_access_check_ds(sd, ac->token, SEC_ADS_DELETE_CHILD,&access_granted, NULL);
if (NT_STATUS_IS_OK(status)){
ac->sec_result = LDB_SUCCESS;
return ac->sec_result;
}
}
else if (ldb_dn_compare(ares->message->dn, ac->req->op.del.dn) == 0){
status = sec_access_check_ds(sd, ac->token, SEC_STD_DELETE, &access_granted, NULL);
if (!NT_STATUS_IS_OK(status))
ac->sec_result = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
}
return ac->sec_result;
}
static int acl_access_check_search(struct ldb_reply *ares, struct acl_context *ac,
struct security_descriptor *sd)
{
uint32_t access_granted;
NTSTATUS status;
struct ldb_dn *parent;
if (ac->user_type == SECURITY_SYSTEM || ac->user_type == SECURITY_ANONYMOUS) {
return LDB_SUCCESS;/*FIXME: we have anonymous access*/
}
parent = ldb_dn_get_parent(ac->req, ac->dn_to_check);
ac->sec_result = LDB_SUCCESS;
if (ldb_dn_compare(ares->message->dn, parent) == 0) {
status = sec_access_check_ds(sd, ac->token, SEC_ADS_LIST,&access_granted, NULL);
if (!NT_STATUS_IS_OK(status)) {
ac->sec_result = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
}
}
return ac->sec_result;
}
static int acl_perform_access_check(struct ldb_request *req, struct ldb_reply *ares,
struct acl_context *ac)
{
struct ldb_message_element *oc_el;
struct security_descriptor *sd;
enum ndr_err_code ndr_err;
oc_el = ldb_msg_find_element(ares->message, "ntSecurityDescriptor");
if (!oc_el || oc_el->num_values == 0)
return LDB_SUCCESS;
sd = talloc(ac, struct security_descriptor);
if(!sd) {
return ldb_module_done(ac->req, ares->controls,
ares->response, LDB_ERR_OPERATIONS_ERROR);
}
ndr_err = ndr_pull_struct_blob(&oc_el->values[0], sd, NULL, sd,
(ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err))
return ldb_module_done(ac->req, ares->controls,
ares->response, LDB_ERR_OPERATIONS_ERROR);
switch (ac->req->operation) {
case LDB_SEARCH:
return acl_access_check_search(ares, ac, sd);
case LDB_ADD:
return acl_access_check_add(ares, ac, sd);
case LDB_MODIFY:
return acl_access_check_modify(ares, ac, sd);
case LDB_DELETE:
return acl_access_check_delete(ares, ac, sd);
case LDB_RENAME:
return acl_access_check_rename(ares, ac, sd);
default:
return ldb_module_done(ac->req, ares->controls,
ares->response, LDB_ERR_OPERATIONS_ERROR);
}
return LDB_SUCCESS;
}
static int acl_forward_add(struct ldb_reply *ares,
struct acl_context *ac)
{
struct ldb_request *newreq;
struct ldb_context *ldb;
int ret;
ldb = ldb_module_get_ctx(ac->module);
ret = ldb_build_add_req(&newreq,ldb,
ac,
ac->req->op.add.message,
ac->req->controls,
ac,
acl_op_callback,
ac->req);
if (ret != LDB_SUCCESS)
return ldb_module_done(ac->req, ares->controls,
ares->response, LDB_ERR_OPERATIONS_ERROR);
return ldb_next_request(ac->module, newreq);
}
static int acl_forward_modify(struct ldb_reply *ares,
struct acl_context *ac)
{
struct ldb_request *newreq;
struct ldb_context *ldb;
int ret;
ldb = ldb_module_get_ctx(ac->module);
ret = ldb_build_mod_req(&newreq,ldb,
ac,
ac->req->op.mod.message,
ac->req->controls,
ac,
acl_op_callback,
ac->req);
if (ret != LDB_SUCCESS)
return ldb_module_done(ac->req, ares->controls,
ares->response, LDB_ERR_OPERATIONS_ERROR);
return ldb_next_request(ac->module, newreq);
}
static int acl_forward_delete(struct ldb_reply *ares,
struct acl_context *ac)
{
struct ldb_request *newreq;
struct ldb_context *ldb;
int ret;
ldb = ldb_module_get_ctx(ac->module);
ret = ldb_build_del_req(&newreq, ldb,
ac,
ac->req->op.del.dn,
ac->req->controls,
ac,
acl_op_callback,
ac->req);
if (ret != LDB_SUCCESS)
return ldb_module_done(ac->req, ares->controls,
ares->response, LDB_ERR_OPERATIONS_ERROR);
return ldb_next_request(ac->module, newreq);
}
static int acl_forward_rename(struct ldb_reply *ares,
struct acl_context *ac)
{
return LDB_SUCCESS;
}
static int acl_forward_search(struct acl_context *ac)
{
int ret;
const char * const *attrs;
struct ldb_control *sd_control;
struct ldb_control **sd_saved_controls;
struct ldb_context *ldb;
struct ldb_request *newreq;
ldb = ldb_module_get_ctx(ac->module);
attrs = ac->req->op.search.attrs;
if (attrs) {
ac->nTSecurityDescriptor = false;
ac->objectClass = false;
if (!ldb_attr_in_list(ac->req->op.search.attrs, "nTSecurityDescriptor")) {
attrs = ldb_attr_list_copy_add(ac, attrs, "nTSecurityDescriptor");
ac->nTSecurityDescriptor = true;
}
if (!ldb_attr_in_list(ac->req->op.search.attrs, "objectClass")) {
attrs = ldb_attr_list_copy_add(ac, attrs, "objectClass");
ac->objectClass = true;
}
}
ret = ldb_build_search_req_ex(&newreq,ldb,
ac,
ac->req->op.search.base,
ac->req->op.search.scope,
ac->req->op.search.tree,
attrs,
ac->req->controls,
ac, acl_search_callback,
ac->req);
if (ret != LDB_SUCCESS) {
return LDB_ERR_OPERATIONS_ERROR;
}
/* check if there's an SD_FLAGS control */
sd_control = ldb_request_get_control(newreq, LDB_CONTROL_SD_FLAGS_OID);
if (sd_control) {
/* save it locally and remove it from the list */
/* we do not need to replace them later as we
* are keeping the original req intact */
if (!save_controls(sd_control, newreq, &sd_saved_controls)) {
return LDB_ERR_OPERATIONS_ERROR;
}
}
return ldb_next_request(ac->module, newreq);
}
static int acl_forward_request(struct ldb_reply *ares,
struct acl_context *ac)
{
switch (ac->req->operation) {
case LDB_SEARCH:
return acl_forward_search(ac);
case LDB_ADD:
return acl_forward_add(ares,ac);
case LDB_MODIFY:
return acl_forward_modify(ares,ac);
case LDB_DELETE:
return acl_forward_delete(ares,ac);
case LDB_RENAME:
return acl_forward_rename(ares,ac);
default:
return ldb_module_done(ac->req, ares->controls,
ares->response, LDB_ERR_OPERATIONS_ERROR);
}
return LDB_SUCCESS;
}
static int acl_visible_callback(struct ldb_request *req, struct ldb_reply *ares)
{
struct acl_context *ac;
ac = talloc_get_type(req->context, struct acl_context);
if (!ares) {
return ldb_module_done(ac->req, NULL, NULL,
LDB_ERR_OPERATIONS_ERROR);
}
if (ares->error != LDB_SUCCESS) {
return ldb_module_done(ac->req, ares->controls,
ares->response, ares->error);
}
switch (ares->type) {
case LDB_REPLY_ENTRY:
return acl_perform_access_check(req, ares, ac);
case LDB_REPLY_REFERRAL:
return ldb_module_send_referral(ac->req, ares->referral); /* what to do here actually? */
case LDB_REPLY_DONE:
if (ac->sec_result != LDB_SUCCESS) {
return ldb_module_done(ac->req, ares->controls,
ares->response, ac->sec_result);
}
return acl_forward_request(ares,ac);
default:
break;
}
return LDB_SUCCESS;
}
static enum security_user_level what_is_user(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");
return security_session_user_level(session_info);
}
static struct security_token * 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 make_req_access_check(struct ldb_module *module, struct ldb_request *req,
struct acl_context *ac, const char *filter)
{
struct ldb_context *ldb;
int ret;
const char **attrs = talloc_array(ac, const char *, 3);
struct ldb_parse_tree *tree = ldb_parse_tree(req, filter);
attrs[0] = talloc_strdup(attrs, "ntSecurityDescriptor");
attrs[1] = talloc_strdup(attrs, "objectClass");
attrs[2] = NULL;
ldb = ldb_module_get_ctx(module);
ret = ldb_build_search_req_ex(&ac->down_req,
ldb, ac,
ac->dn_to_check,
LDB_SCOPE_SUBTREE,
tree,
attrs,
NULL,
ac, acl_visible_callback,
req);
return ret;
}
static const char *user_name(TALLOC_CTX *mem_ctx, 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 "UNKNOWN (NULL)";
}
return talloc_asprintf(mem_ctx, "%s\\%s",
session_info->server_info->domain_name,
session_info->server_info->account_name);
}
static int acl_module_init(struct ldb_module *module)
{
struct ldb_context *ldb;
struct acl_private *data;
int ret;
ldb = ldb_module_get_ctx(module);
ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID);
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;
}
data = talloc(module, struct acl_private);
data->perform_check = lp_parm_bool(ldb_get_opaque(ldb, "loadparm"),
NULL, "acl", "perform", false);
ldb_module_set_private(module, data);
return ldb_next_init(module);
}
static int acl_add(struct ldb_module *module, struct ldb_request *req)
{
int ret;
struct acl_context *ac;
struct ldb_dn * parent = ldb_dn_get_parent(req, req->op.add.message->dn);
char * filter;
struct ldb_context *ldb;
struct acl_private *data;
ldb = ldb_module_get_ctx(module);
data = talloc_get_type(ldb_module_get_private(module), struct acl_private);
if (!data->perform_check)
return ldb_next_request(module, req);
ac = talloc(req, struct acl_context);
if (ac == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
if (what_is_user(module) == SECURITY_SYSTEM)
return ldb_next_request(module, req);
ac->module = module;
ac->req = req;
ac->ignore_security = true;
ac->dn_to_check = ldb_dn_get_parent(req, parent);
ac->token = user_token(module);
ac->user_type = what_is_user(module);
ac->sec_result = LDB_SUCCESS;
if (!is_root_base_dn(ldb, req->op.add.message->dn) && parent && !is_root_base_dn(ldb, parent)){
filter = talloc_asprintf(req,"(&(objectClass=*)(|(%s=%s)(%s=%s))))",
ldb_dn_get_component_name(parent,0),
ldb_dn_get_component_val(parent,0)->data,
ldb_dn_get_component_name(ac->dn_to_check,0),
ldb_dn_get_component_val(ac->dn_to_check,0)->data);
ret = make_req_access_check(module, req, ac, filter);
if (ret != LDB_SUCCESS){
return ret;
}
return ldb_next_request(module, ac->down_req);
}
return ldb_next_request(module, req);
}
static int acl_modify(struct ldb_module *module, struct ldb_request *req)
{
int ret;
struct acl_context *ac;
struct ldb_dn * parent = ldb_dn_get_parent(req, req->op.mod.message->dn);
char * filter;
struct ldb_context *ldb;
struct acl_private *data;
ldb = ldb_module_get_ctx(module);
data = talloc_get_type(ldb_module_get_private(module), struct acl_private);
if (!data->perform_check)
return ldb_next_request(module, req);
ac = talloc(req, struct acl_context);
if (ac == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
/* if (what_is_user(module) == SECURITY_SYSTEM) */
return ldb_next_request(module, req);
ac->module = module;
ac->req = req;
ac->ignore_security = true;
ac->dn_to_check = req->op.mod.message->dn;
ac->token = user_token(module);
ac->user_type = what_is_user(module);
ac->sec_result = LDB_SUCCESS;
if (!is_root_base_dn(ldb, req->op.mod.message->dn) && parent && !is_root_base_dn(ldb, parent)){
filter = talloc_asprintf(req,"(&(objectClass=*)(|(%s=%s)(%s=%s))))",
ldb_dn_get_component_name(parent,0),
ldb_dn_get_component_val(parent,0)->data,
ldb_dn_get_component_name(req->op.mod.message->dn,0),
ldb_dn_get_component_val(req->op.mod.message->dn,0)->data);
ret = make_req_access_check(module, req, ac, filter);
if (ret != LDB_SUCCESS){
return ret;
}
return ldb_next_request(module, ac->down_req);
}
return ldb_next_request(module, req);
}
/* similar to the modify for the time being.
* We need to concider the special delete tree case, though - TODO */
static int acl_delete(struct ldb_module *module, struct ldb_request *req)
{
int ret;
struct acl_context *ac;
struct ldb_dn * parent = ldb_dn_get_parent(req, req->op.del.dn);
char * filter;
struct ldb_context *ldb;
struct acl_private *data;
ldb = ldb_module_get_ctx(module);
data = talloc_get_type(ldb_module_get_private(module), struct acl_private);
if (!data->perform_check)
return ldb_next_request(module, req);
ac = talloc(req, struct acl_context);
if (ac == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
if (ac->user_type == SECURITY_SYSTEM)
return ldb_next_request(module, req);
ac->module = module;
ac->req = req;
ac->ignore_security = true;
ac->dn_to_check = req->op.del.dn;
ac->token = user_token(module);
ac->user_type = what_is_user(module);
ac->sec_result = LDB_SUCCESS;
if (parent) {
filter = talloc_asprintf(req,"(&(objectClass=*)(|(%s=%s)(%s=%s))))",
ldb_dn_get_component_name(parent,0),
ldb_dn_get_component_val(parent,0)->data,
ldb_dn_get_component_name(req->op.del.dn,0),
ldb_dn_get_component_val(req->op.del.dn,0)->data);
ret = make_req_access_check(module, req, ac, filter);
if (ret != LDB_SUCCESS){
return ret;
}
return ldb_next_request(module, ac->down_req);
}
return ldb_next_request(module, req);
}
static int acl_rename(struct ldb_module *module, struct ldb_request *req)
{
struct ldb_dn *source_parent;
struct ldb_dn *dest_parent;
int ret;
struct acl_context *ac;
char * filter;
struct ldb_context *ldb;
struct acl_private *data;
ldb = ldb_module_get_ctx(module);
data = talloc_get_type(ldb_module_get_private(module), struct acl_private);
if (!data->perform_check)
return ldb_next_request(module, req);
ac = talloc(req, struct acl_context);
if (ac == NULL) {
return LDB_ERR_OPERATIONS_ERROR;
}
if (ac->user_type == SECURITY_SYSTEM)
return ldb_next_request(module, req);
ac->module = module;
ac->req = req;
ac->ignore_security = true;
ac->token = user_token(module);
ac->user_type = what_is_user(module);
ac->sec_result = LDB_SUCCESS;
/* We need to know if it is a simple rename or a move operation */
source_parent = ldb_dn_get_parent(req, req->op.rename.olddn);
dest_parent = ldb_dn_get_parent(req, req->op.rename.newdn);
if (ldb_dn_compare(source_parent, dest_parent) == 0){
/*Not a move, just rename*/
filter = talloc_asprintf(req,"(&(objectClass=*)(|(%s=%s)(%s=%s))))",
ldb_dn_get_component_name(dest_parent,0),
ldb_dn_get_component_val(dest_parent,0)->data,
ldb_dn_get_component_name(req->op.rename.olddn,0),
ldb_dn_get_component_val(req->op.rename.olddn,0)->data);
}
else{
filter = talloc_asprintf(req,"(&(objectClass=*)(|(%s=%s)(%s=%s))))",
ldb_dn_get_component_name(dest_parent,0),
ldb_dn_get_component_val(dest_parent,0)->data,
ldb_dn_get_component_name(source_parent,0),
ldb_dn_get_component_val(source_parent,0)->data);
}
ret = make_req_access_check(module, req, ac, filter);
if (ret != LDB_SUCCESS){
return ret;
}
return ldb_next_request(module, ac->down_req);
}
static int acl_search_callback(struct ldb_request *req, struct ldb_reply *ares)
{
struct ldb_context *ldb;
struct acl_context *ac;
struct security_descriptor *sd;
uint32_t searchFlags;
uint32_t access_mask;
struct object_tree *ot;
int i, ret;
NTSTATUS status;
struct ldb_message_element *element_security_descriptor;
struct ldb_message_element *element_object_class;
const struct dsdb_attribute *attr;
const struct dsdb_schema *schema;
struct GUID *oc_guid;
ac = talloc_get_type(req->context, struct acl_context);
ldb = ldb_module_get_ctx(ac->module);
schema = dsdb_get_schema(ldb);
if (!ares) {
return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
}
if (ares->error != LDB_SUCCESS) {
return ldb_module_done(ac->req, ares->controls, ares->response, ares->error);
}
switch (ares->type) {
case LDB_REPLY_ENTRY:
switch (ac->user_type) {
case SECURITY_SYSTEM:
case SECURITY_ANONYMOUS:/*FIXME: should we let anonymous have system access*/
break;
default:
/* Access checks
*
* 0. If we do not have nTSecurityDescriptor, we do not have an object in the response,
* so check the parent dn.
* 1. Call sec_access_check on empty tree
* 2. For each attribute call extended_access_check
* 3. For each attribute call build_object_tree_form_attr_list and then check with sec_access_check
*
*/
element_security_descriptor = ldb_msg_find_element(ares->message, "nTSecurityDescriptor");
element_object_class = ldb_msg_find_element(ares->message, "objectClass");
if (!element_security_descriptor || !element_object_class)
break;
sd = talloc(ldb, struct security_descriptor);
if(!sd) {
return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
}
if(!NDR_ERR_CODE_IS_SUCCESS(ndr_pull_struct_blob(&element_security_descriptor->values[0],
ldb,
NULL,
sd,
(ndr_pull_flags_fn_t)ndr_pull_security_descriptor))) {
DEBUG(0, ("acl_search_callback: Error parsing security descriptor\n"));
return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
}
oc_guid = class_schemaid_guid_by_lDAPDisplayName(schema, element_object_class->values[0].data);
for (i=0; imessage->num_elements; i++) {
attr = dsdb_attribute_by_lDAPDisplayName(schema, ares->message->elements[i].name);
if (attr) {
searchFlags = attr->searchFlags;
} else {
searchFlags = 0x0;
}
/*status = extended_access_check(ares->message->elements[i].name, access_mask, searchFlags); */ /* Todo FIXME */
ac->access_needed = SEC_ADS_READ_PROP;
if (NT_STATUS_IS_OK(status)) {
ot = insert_in_object_tree(req, oc_guid, NULL, ac->access_needed, NULL);
insert_in_object_tree(req,
&attr->schemaIDGUID,
&attr->attributeSecurityGUID,
ac->access_needed,
ot);
status = sec_access_check_ds(sd,
ac->token,
ac->access_needed,
&access_mask,
ot);
if (NT_STATUS_IS_OK(status)) {
continue;
}
}
ldb_msg_remove_attr(ares->message, ares->message->elements[i].name);
}
break;
}
if (ac->nTSecurityDescriptor) {
ldb_msg_remove_attr(ares->message, "nTSecurityDescriptor");
} else if (ac->objectClass) {
ldb_msg_remove_attr(ares->message, "objectClass");
}
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, LDB_SUCCESS);
}
return LDB_SUCCESS;
}
static int acl_search(struct ldb_module *module, struct ldb_request *req)
{
int ret;
struct ldb_context *ldb;
struct acl_context *ac;
const char **attrs;
struct ldb_control *sd_control;
struct ldb_control **sd_saved_controls;
struct ldb_dn * parent;
struct acl_private *data;
ldb = ldb_module_get_ctx(module);
data = talloc_get_type(ldb_module_get_private(module), struct acl_private);
if (!data || !data->perform_check)
return ldb_next_request(module, req);
if (what_is_user(module) == SECURITY_SYSTEM)
return ldb_next_request(module, req);
ac = talloc_get_type(req->context, struct acl_context);
if ( ac == NULL ) {
ac = talloc(req, struct acl_context);
if (ac == NULL) {
ldb_oom(ldb);
return LDB_ERR_OPERATIONS_ERROR;
}
ac->module = module;
ac->req = req;
ac->ignore_security = false;
ac->user_type = what_is_user(module);
ac->token = user_token(module);
ac->dn_to_check = req->op.search.base;
ac->sec_result = LDB_SUCCESS;
attrs = talloc_array(ac, const char*, 2);
attrs[0] = talloc_strdup(attrs, "nTSecurityDescriptor");
attrs[1] = NULL;
parent = ldb_dn_get_parent(req, ac->dn_to_check);
if (!is_root_base_dn(ldb, req->op.search.base) && parent && !is_root_base_dn(ldb, parent)) {
/*we have parent so check for visibility*/
ret = ldb_build_search_req(&ac->down_req,
ldb, ac,
parent,
LDB_SCOPE_BASE,
"(objectClass=*)",
attrs,
req->controls,
ac, acl_visible_callback,
req);
if (ret != LDB_SUCCESS) {
return ret;
}
return ldb_next_request(module, ac->down_req);
} else {
return acl_forward_search(ac);
}
}
return ldb_next_request(module, req);
}
static int acl_extended(struct ldb_module *module, struct ldb_request *req)
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
enum security_user_level user_type;
struct acl_private *data;
data = talloc_get_type(ldb_module_get_private(module), struct acl_private);
if (!data->perform_check)
return ldb_next_request(module, req);
/* allow everybody to read the sequence number */
if (strcmp(req->op.extended.oid, LDB_EXTENDED_SEQUENCE_NUMBER) == 0) {
return ldb_next_request(module, req);
}
user_type = what_is_user(module);
switch (user_type) {
case SECURITY_SYSTEM:
case SECURITY_ADMINISTRATOR:
return ldb_next_request(module, req);
default:
ldb_asprintf_errstring(ldb,
"acl_extended: attempted database modify not permitted."
"User %s is not SYSTEM or an Administrator",
user_name(req, module));
return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
}
}
_PUBLIC_ 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
};