diff options
Diffstat (limited to 'ldap/servers/plugins/acl/acl.c')
-rw-r--r-- | ldap/servers/plugins/acl/acl.c | 4118 |
1 files changed, 4118 insertions, 0 deletions
diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c new file mode 100644 index 00000000..8dfbd52a --- /dev/null +++ b/ldap/servers/plugins/acl/acl.c @@ -0,0 +1,4118 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +#include "acl.h" + +/**************************************************************************** +* +* acl.c +* +* +* This file contains the functions related to Access Control List (ACL) +* checking. The ACL checking is based on the ONE ACL design implemented +* in the Web server 2.0. For more information on the ACL design look +* into the barracuda home page. +* +* +******************************************************************************/ + + +/****************************************************************************/ +/* Globals. Must be protected by Mutex. */ +/****************************************************************************/ +/* Signatures to see if things have changed */ +static short acl_signature = 0; + +/****************************************************************************/ +/* Defines, Constants, ande Declarations */ +/****************************************************************************/ +static char *ds_map_generic[2] = { NULL, NULL }; + +/****************************************************************************/ +/* prototypes */ +/****************************************************************************/ +static int acl__resource_match_aci(struct acl_pblock *aclpb, aci_t *aci , + int skip_attrEval, int *a_matched); +static acl__TestRights(Acl_PBlock *aclpb,int access, char **right, + char ** map_generic, aclResultReason_t *result_reason); +static int acl__scan_for_acis(struct acl_pblock *aclpb, int *err); +static void acl__reset_cached_result (struct acl_pblock *aclpb ); +static int acl__scan_match_handles ( struct acl_pblock *aclpb, int type); +static int acl__attr_cached_result (struct acl_pblock *aclpb, char *attr, int access ); +static int acl__match_handlesFromCache (struct acl_pblock *aclpb, char *attr, int access); +static int acl__get_attrEval ( struct acl_pblock *aclpb, char *attr ); +static int acl__config_get_readonly (); +static int acl__recompute_acl (Acl_PBlock *aclpb, AclAttrEval *a_eval, + int access, int aciIndex); +static void __acl_set_aclIndex_inResult ( Acl_PBlock *aclpb, + int access, int index ); +static int acl__make_filter_test_entry ( Slapi_Entry **entry, + char *attr_type, struct berval *attr_val); +static int acl__test_filter ( Slapi_Entry *entry, struct slapi_filter *f, + int filter_sense); +static void print_access_control_summary( char * source, + int ret_val, char *clientDn, + struct acl_pblock *aclpb, + char *right, + char *attr, + const char *edn, + aclResultReason_t *acl_reason); +static int check_rdn_access( Slapi_PBlock *pb,Slapi_Entry *e, char * newrdn, + int access); + + +/* + * Check the rdn permissions for this entry: + * require: write access to the entry, write (add) access to the new + * naming attribute, write (del) access to the old naming attribute if + * deleteoldrdn set. + * + * Valid only for the modrdn operation. +*/ +int +acl_access_allowed_modrdn( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* requested access rights */ + ) +{ + int retCode ; + char *newrdn, *oldrdn; + int deleteoldrdn = 0; + + /* + * First check write permission on the entry--this is actually + * specially for modrdn. + */ + retCode = acl_access_allowed ( pb, e, NULL /* attr */, NULL /* val */, + SLAPI_ACL_WRITE); + + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to entry not allowed\n"); + return(retCode); + } + + /* Now get the new rdn attribute name and value */ + + slapi_pblock_get( pb, SLAPI_MODRDN_TARGET, &oldrdn ); + slapi_pblock_get( pb, SLAPI_MODRDN_NEWRDN, &newrdn ); + + /* Check can add the new naming attribute */ + retCode = check_rdn_access( pb, e, newrdn, ACLPB_SLAPI_ACL_WRITE_ADD) ; + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to add new naming attribute not allowed\n"); + return(retCode); + } + + /* Check can delete the new naming attribute--if required */ + slapi_pblock_get( pb, SLAPI_MODRDN_DELOLDRDN, &deleteoldrdn ); + if ( deleteoldrdn ) { + retCode = check_rdn_access( pb, e, oldrdn, ACLPB_SLAPI_ACL_WRITE_DEL) ; + if ( retCode != LDAP_SUCCESS ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn:write permission to delete old naming attribute not allowed\n"); + return(retCode); + } + } + + return(retCode); + +} +/* + * Test if have access to make the first rdn of dn in entry e. +*/ + +static int check_rdn_access( Slapi_PBlock *pb, Slapi_Entry *e, char *dn, + int access) { + + char **dns; + char **rdns; + int retCode = LDAP_INSUFFICIENT_ACCESS; + int i; + + if ( (dns = ldap_explode_dn( dn, 0 )) != NULL ) { + + if ( (rdns = ldap_explode_rdn( dns[0], 0 )) != NULL ) { + + for ( i = 0; rdns[i] != NULL; i++ ) { + char *type; + struct berval bv; + + if ( slapi_rdn2typeval( rdns[i], &type, &bv ) != 0 ) { + char ebuf[ BUFSIZ ]; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "modrdn: rdn2typeval (%s) failed\n", + escape_string( rdns[i], ebuf )); + retCode = LDAP_INSUFFICIENT_ACCESS; + break; + } else { + if ( (retCode = acl_access_allowed ( pb, e, type /* attr */, + &bv /* val */, + access)) != LDAP_SUCCESS) { + break; + } + } + } + ldap_value_free( rdns ); + } + ldap_value_free( dns ); + } + + return(retCode); +} + +/*************************************************************************** +* +* acl_access_allowed +* Determines if access to the resource is allowed or not. +* +* Input: +* +* +* Returns: +* +* Returns success/Denied/error condition +* +* LDAP_SUCCESS -- access allowed +* LDAP_INSUFFICIENT_ACCESS -- access denied +* +* Errors returned: +* +* Some of the definition of the return values used copied from +* "ldap.h" for convienience. +* LDAP_OPERATIONS_ERROR +* LDAP_PROTOCOL_ERROR +* LDAP_UNWILLING_TO_PERFORM +* +* +* Error Handling: +* Returned error code. +**************************************************************************/ +int +acl_access_allowed( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* requested access rights */ + ) +{ + char *n_edn; /* Normalized DN of the entry */ + int rv; + int err; + int ret_val; + char *right; + int num_handle; + struct acl_pblock *aclpb = NULL; + AclAttrEval *c_attrEval = NULL; + int got_reader_locked = 0; + int deallocate_attrEval = 0; + char ebuf [ BUFSIZ ]; + char *clientDn; + Slapi_DN *e_sdn; + Slapi_Operation *op = NULL; + aclResultReason_t decision_reason; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + slapi_pblock_get(pb, SLAPI_OPERATION, &op); /* for logging */ + + TNF_PROBE_1_DEBUG(acl_access_allowed_start,"ACL","", + tnf_int,access,access); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + /** + * First, if the acl private write/delete on attribute right + * is requested, turn this into SLAPI_ACL_WRITE + * and record the original value. + * Need to make sure that these rights do not clash with the SLAPI + * public rights. This should be easy as the requested rights + * in the aclpb are stored in the bottom byte of aclpb_res_type, + * so if we keep the ACL private bits here too we make sure + * not to clash. + * + */ + + if ( access & (ACLPB_SLAPI_ACL_WRITE_ADD | ACLPB_SLAPI_ACL_WRITE_DEL) ) { + access |= SLAPI_ACL_WRITE; + } + + n_edn = slapi_entry_get_ndn ( e ); + e_sdn = slapi_entry_get_sdn ( e ); + + /* Check if this is a write operation and the database is readonly */ + /* No one, even the rootdn should be allowed to write to the database */ + /* jcm: ReadOnly only applies to the public backends, the private ones */ + /* (the DSEs) should still be writable for configuration. */ + if ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE )) { + int be_readonly, privateBackend; + Slapi_Backend *be; + + slapi_pblock_get ( pb, SLAPI_BE_READONLY, &be_readonly ); + slapi_pblock_get ( pb, SLAPI_BACKEND, &be ); + privateBackend = slapi_be_private ( be ); + + if ( !privateBackend && (be_readonly || slapi_config_get_readonly () )){ + slapi_log_error (loglevel, plugin_name, + "conn=%d op=%d (main): Deny %s on entry(%s)" + ": readonly backend\n", + op->o_connid, op->o_opid, + acl_access2str(access), + escape_string_with_punctuation(n_edn,ebuf)); + return LDAP_UNWILLING_TO_PERFORM; + } + } + + /* Check for things we need to skip */ + TNF_PROBE_0_DEBUG(acl_skipaccess_start,"ACL",""); + if ( acl_skip_access_check ( pb, e )) { + slapi_log_error (loglevel, plugin_name, + "conn=%d op=%d (main): Allow %s on entry(%s)" + ": root user\n", + op->o_connid, op->o_opid, + acl_access2str(access), + escape_string_with_punctuation(n_edn,ebuf)); + return(LDAP_SUCCESS); + } + TNF_PROBE_0_DEBUG(acl_skipaccess_end,"ACL",""); + + + /* Get the bindDN */ + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn ); + + /* get the right acl pblock to work with */ + if ( access & SLAPI_ACL_PROXY ) + aclpb = acl_get_aclpb ( pb, ACLPB_PROXYDN_PBLOCK ); + else + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 1 \n" ); + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + /* check if aclpb is initialized or not */ + TNF_PROBE_0_DEBUG(acl_aclpbinit_start,"ACL",""); + acl_init_aclpb ( pb, aclpb, clientDn, 0 ); + TNF_PROBE_0_DEBUG(acl_aclpbinit_end,"ACL",""); + + + /* Here we mean if "I am trying to add/delete "myself" ? " */ + if (val && (access & SLAPI_ACL_WRITE) && (val->bv_len > 0) ) { + /* should use slapi_sdn_compare() but that'a an extra malloc/free */ + + char *dn_val_to_write = + slapi_dn_normalize(slapi_ch_strdup(val->bv_val)); + + if ( aclpb->aclpb_authorization_sdn && + slapi_utf8casecmp((ACLUCHP)dn_val_to_write, (ACLUCHP) + slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn)) == 0) { + access |= SLAPI_ACL_SELF; + } + + slapi_ch_free( (void **)&dn_val_to_write); + } + + /* Convert access to string of rights eg SLAPI_ACL_ADD->"add". */ + if ((right= acl_access2str(access)) == NULL) { + /* ERROR: unknown rights */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl_access_allowed unknown rights:%d\n", access); + + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + + /* + * Am I a anonymous dude ? then we can use our anonymous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + TNF_PROBE_0_DEBUG(acl_anon_test_start,"ACL",""); + if ( (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ )) && + (clientDn && *clientDn == '\0')) { + aclanom_get_suffix_info(e, aclpb); + ret_val = aclanom_match_profile ( pb, aclpb, e, attr, access ); + if (ret_val != -1 ) { + if (ret_val == LDAP_SUCCESS ) { + decision_reason.reason = ACL_REASON_ANON_ALLOWED; + } else if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + decision_reason.reason = ACL_REASON_ANON_DENIED; + } + goto cleanup_and_ret; + } + } + TNF_PROBE_0_DEBUG(acl_anon_test_end,"ACL",""); + + /* copy the value into the aclpb for later checking by the value acl code */ + + aclpb->aclpb_curr_attrVal = val; + + if (!(aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) && + (access & SLAPI_ACL_SEARCH)) { + /* We are evaluating SEARCH right for the entry. After that + ** we will eval the READ right. We need to refresh the + ** list of acls selected for evaluation for the entry. + ** Initialize the array so that we indicate nothing has been + ** selected. + */ + aclpb->aclpb_handles_index[0] = -1; + /* access is not allowed on entry for search -- it's for + ** read only. + */ + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_ON_ENTRY; + } + + /* set that this is a new entry */ + aclpb->aclpb_res_type |= ACLPB_NEW_ENTRY; + aclpb->aclpb_access = 0; + aclpb->aclpb_access |= access; + + /* + * stub the Slapi_Entry info first time and only it has changed + * or if the pblock is a psearch pblock--in this case the lifetime + * of entries associated with psearches is such that we cannot cache + * pointers to them--we must always start afresh (see psearch.c). + */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op); + if ( operation_is_flag_set(op, OP_FLAG_PS) || + (aclpb->aclpb_curr_entry_sdn == NULL) || + (slapi_sdn_compare ( aclpb->aclpb_curr_entry_sdn, e_sdn) != 0)) { + + TNF_PROBE_0_DEBUG(acl_entry_first_touch_start,"ACL",""); + + slapi_log_error(loglevel, plugin_name, + "#### conn=%d op=%d binddn=\"%s\"\n", + op->o_connid, op->o_opid, clientDn); + aclpb->aclpb_stat_total_entries++; + + if (!(access & SLAPI_ACL_PROXY) && + !( aclpb->aclpb_state & ACLPB_DONOT_EVALUATE_PROXY )) { + Acl_PBlock *proxy_pb; + + proxy_pb = acl_get_aclpb( pb, ACLPB_PROXYDN_PBLOCK ); + if (proxy_pb) { + TNF_PROBE_0_DEBUG(acl_access_allowed_proxy_start,"ACL",""); + ret_val = acl_access_allowed( pb, e, attr, val, SLAPI_ACL_PROXY ); + TNF_PROBE_0_DEBUG(acl_access_allowed_proxy_end,"ACL",""); + + if (ret_val != LDAP_SUCCESS) goto cleanup_and_ret; + } + } + if ( access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_num_entries++; + + if ( aclpb->aclpb_num_entries == 1) { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + } else if ( aclpb->aclpb_state & ACLPB_COPY_EVALCONTEXT ) { + /* We need to copy the evalContext */ + acl_copyEval_context ( aclpb, &aclpb->aclpb_curr_entryEval_context, + &aclpb->aclpb_prev_entryEval_context, 0 ); + aclpb->aclpb_state &= ~ACLPB_COPY_EVALCONTEXT; + } + acl_clean_aclEval_context ( &aclpb->aclpb_curr_entryEval_context, 1 /*scrub */); + } + + /* reset the cached result based on the scope */ + acl__reset_cached_result (aclpb ); + + /* Find all the candidate aci's that apply by scanning up the DIT tree from edn. */ + + TNF_PROBE_0_DEBUG(acl_aciscan_start,"ACL",""); + slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn ); + slapi_sdn_set_dn_byval ( aclpb->aclpb_curr_entry_sdn, n_edn ); + acllist_aciscan_update_scan ( aclpb, n_edn ); + TNF_PROBE_0_DEBUG(acl_aciscan_end,"ACL",""); + + /* Keep the ptr to the current entry */ + aclpb->aclpb_curr_entry = (Slapi_Entry *) e; + + /* Get the attr info */ + deallocate_attrEval = acl__get_attrEval ( aclpb, attr ); + + aclutil_print_resource ( aclpb, right, attr, clientDn ); + + /* + * Used to be PListInitProp(aclpb->aclpb_proplist, 0, + * DS_ATTR_ENTRY, e, 0); + * + * The difference is that PListInitProp() allocates a new property + * every time it's called, overwriting the old name in the PList hash + * table, but not freeing the original property. + * Now, we just create the property at aclpb_malloc() time and + * Assign a new value each time. + */ + + rv = PListAssignValue(aclpb->aclpb_proplist, + DS_ATTR_ENTRY, e, 0); + + if (rv < 0) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Unable to set the Slapi_Entry in the Plist\n",0,0,0); + ret_val = LDAP_OPERATIONS_ERROR; + goto cleanup_and_ret; + } + + TNF_PROBE_0_DEBUG(acl_entry_first_touch_end,"ACL",""); + + } else { + /* we are processing the same entry but for a different + ** attribute. If access is already allowed on that, entry, then + ** it's not a new entry anymore. It's the same old one. + */ + + TNF_PROBE_0_DEBUG(acl_entry_subs_touch_start,"ACL",""); + + aclpb->aclpb_res_type &= ~ACLPB_NEW_ENTRY; + + /* Get the attr info */ + deallocate_attrEval = acl__get_attrEval ( aclpb, attr ); + + TNF_PROBE_0_DEBUG(acl_entry_subs_touch_end,"ACL",""); + + } + + /* get a lock for the reader */ + acllist_acicache_READ_LOCK(); + got_reader_locked = 1; + + /* + ** Check if we can use any cached information to determine + ** access to this resource + */ + if ( (access & SLAPI_ACL_SEARCH) && + (ret_val = acl__match_handlesFromCache ( aclpb , attr, access)) != -1) { + /* means got a result: allowed or not*/ + + if (ret_val == LDAP_SUCCESS ) { + decision_reason.reason = ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + goto cleanup_and_ret; + } + + /* + ** Now we have all the information about the resource. Now we need to + ** figure out if there are any ACLs which can be applied. + ** If no ACLs are there, then it's a DENY as default. + */ + if (!(num_handle = acl__scan_for_acis(aclpb, &err))) { + + /* We might have accessed the ACL first time which could + ** have caused syntax error. + */ + if ( err == ACL_ONEACL_TEXT_ERR) + ret_val = LDAP_INVALID_SYNTAX; + else { + ret_val = LDAP_INSUFFICIENT_ACCESS; + decision_reason.reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS; + } + goto cleanup_and_ret; + } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Processed attr:%s for entry:%s\n", attr ? attr : "NULL", + ACL_ESCAPE_STRING_WITH_PUNCTUATION ( n_edn, ebuf), 0); + + /* + ** Now evaluate the rights. + ** This is what we have been waiting for. + ** The return value should be ACL_RES_DENY or ACL_RES_ALLOW. + */ + rv = acl__TestRights(aclpb, access, &right, ds_map_generic, + &decision_reason); + if ( rv != ACL_RES_ALLOW && (0 == strcasecmp ( right, "selfwrite") ) ) { + /* If I am adding myself to a group, we don't need selfwrite always, + ** write priv is good enough. Since libaccess doesn't provide me a nice + ** way to evaluate OR rights, I have to try again with wite priv. + ** bug: 339051 + */ + right = access_str_write; + rv = acl__TestRights(aclpb, access, &right, ds_map_generic, + &decision_reason); + } + + if (rv == ACL_RES_ALLOW) { + ret_val = LDAP_SUCCESS; + } else { + ret_val = LDAP_INSUFFICIENT_ACCESS; + } + +cleanup_and_ret: + + TNF_PROBE_0_DEBUG(acl_cleanup_start,"ACL",""); + + /* I am ready to get out. */ + if ( got_reader_locked ) acllist_acicache_READ_UNLOCK(); + + /* Store the status of the evaluation for this attr */ + if ( aclpb && (c_attrEval = aclpb->aclpb_curr_attrEval )) { + if ( deallocate_attrEval ) { + /* In this case we are not caching the result as + ** we have too many attrs. we have malloced space. + ** Get rid of it. + */ + slapi_ch_free ( (void **) &c_attrEval->attrEval_name ); + slapi_ch_free ( (void **) &c_attrEval ); + } else if (ret_val == LDAP_SUCCESS ) { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_status |= ACL_ATTREVAL_SUCCESS; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_SUCCESS; + else + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_INVALID; + } else { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_status |= ACL_ATTREVAL_FAIL; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_FAIL; + else + c_attrEval->attrEval_r_status |= ACL_ATTREVAL_INVALID; + } + } + + if ( aclpb ) aclpb->aclpb_curr_attrEval = NULL; + + print_access_control_summary( "main", ret_val, clientDn, aclpb, right, + (attr ? attr : "NULL"), + escape_string_with_punctuation (n_edn, ebuf), + &decision_reason); + TNF_PROBE_0_DEBUG(acl_cleanup_end,"ACL",""); + + TNF_PROBE_0_DEBUG(acl_access_allowed_end,"ACL",""); + + return(ret_val); + +} + +static void print_access_control_summary( char *source, int ret_val, char *clientDn, + struct acl_pblock *aclpb, + char *right, + char *attr, + const char *edn, + aclResultReason_t *acl_reason) +{ + struct codebook { + int code; + char *text; + }; + + static struct codebook reasonbook[] = { + {ACL_REASON_NO_ALLOWS, "no allow acis"}, + {ACL_REASON_RESULT_CACHED_DENY, "cached deny"}, + {ACL_REASON_RESULT_CACHED_ALLOW, "cached allow"}, + {ACL_REASON_EVALUATED_ALLOW, "allowed"}, + {ACL_REASON_EVALUATED_DENY, "denied"}, + {ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS, "no aci matched the resource"}, + {ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS, "no aci matched the subject"}, + {ACL_REASON_ANON_ALLOWED, "allow anyone aci matched anon user"}, + {ACL_REASON_ANON_DENIED, "no matching anyone aci for anon user"}, + {ACL_REASON_EVALCONTEXT_CACHED_ALLOW, "cached context/parent allow"}, + {ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED, "cached context/parent deny"}, + {ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW, "cached context/parent allow any attr"}, + {ACL_REASON_NONE, "error occurred"}, + }; + + char *anon = "anonymous"; + char *null_user = "NULL"; /* bizare case */ + char *real_user = NULL; + char *proxy_user = NULL; + char *access_allowed_string = "Allow"; + char *access_not_allowed_string = "Deny"; + char *access_error_string = "access_error"; + char *access_status = NULL; + char *access_reason_none = "no reason available"; + char *access_reason = access_reason_none; + char acl_info[ BUFSIZ ]; + Slapi_Operation *op = NULL; + int loglevel; + int i; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + if ( !slapi_is_loglevel_set(loglevel) ) { + return; + } + + slapi_pblock_get(aclpb->aclpb_pblock, SLAPI_OPERATION, &op); /* for logging */ + + if (ret_val == LDAP_INSUFFICIENT_ACCESS) { + access_status = access_not_allowed_string; + } else if ( ret_val == LDAP_SUCCESS) { + access_status = access_allowed_string; + } else { /* some kind of error */ + access_status = access_error_string; + } + + /* decode the reason */ + for (i = 0; i < sizeof(reasonbook) / sizeof(struct codebook); i++) { + if ( acl_reason->reason == reasonbook[i].code ) { + access_reason = reasonbook[i].text; + break; + } + } + + /* get the acl */ + acl_info[0] = '\0'; + if (acl_reason->deciding_aci) { + if (acl_reason->reason == ACL_REASON_RESULT_CACHED_DENY || + acl_reason->reason == ACL_REASON_RESULT_CACHED_ALLOW) { + /* acl is in cache. Its detail must have been printed before. + * So no need to print out acl detail this time. + */ + PR_snprintf( &acl_info[0], BUFSIZ, "%s by aci(%d)", + access_reason, + acl_reason->deciding_aci->aci_index); + } + else { + PR_snprintf( &acl_info[0], BUFSIZ, "%s by aci(%d): aciname=%s, acidn=\"%s\"", + access_reason, + acl_reason->deciding_aci->aci_index, + acl_reason->deciding_aci->aclName, + slapi_sdn_get_ndn (acl_reason->deciding_aci->aci_sdn) ); + } + } + + /* Say who was denied access */ + + if (clientDn) { + if (clientDn[0] == '\0') { + /* anon */ + real_user = anon; + } else { + real_user = clientDn; + } + } else { + real_user = null_user; + } + + /* Is there a proxy */ + + if ( aclpb != NULL && aclpb->aclpb_proxy != NULL) { + + if ( aclpb->aclpb_authorization_sdn != NULL ) { + + proxy_user = (char *)(aclpb->aclpb_authorization_sdn->ndn ? + aclpb->aclpb_authorization_sdn->ndn: + null_user); + + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s) to proxy (%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + proxy_user, + acl_info[0] ? acl_info : access_reason); + } else { + proxy_user = null_user; + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s) to proxy (%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + proxy_user, + acl_info[0] ? acl_info : access_reason); + } + } else{ + slapi_log_error(loglevel, plugin_name, + "conn=%d op=%d (%s): %s %s on entry(%s).attr(%s)" + ": %s\n", + op->o_connid, op->o_opid, + source, + access_status, + right, + edn, + attr ? attr: "NULL", + acl_info[0] ? acl_info : access_reason); + } + + +} +/*************************************************************************** +* +* acl_read_access_allowed_on_entry +* check read access control on the given entry. +* +* Only used during seearch to test for read access on the entry. +* (Could be generalized). +* +* attrs is the list of requested attributes passed with the search. +* If the entry has no attributes (weird case) then the routine survives. +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - access allowed +* LDAP_INSUFFICIENT_ACCESS - access denied +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_read_access_allowed_on_entry ( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char **attrs, + int access /* access rights */ + ) +{ + + struct acl_pblock *aclpb; + Slapi_Attr *currAttr; + Slapi_Attr *nextAttr; + int len; + int attr_index = -1; + char *attr_type = NULL; + int rv, isRoot; + char *clientDn; + unsigned long flags; + aclResultReason_t decision_reason; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_entry_start ,"ACL",""); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot ); + + /* + ** If it's the root, or acl is off or the entry is a rootdse, + ** Then you have the privilege to read it. + */ + if ( acl_skip_access_check ( pb, e ) ) { + char *n_edn = slapi_entry_get_ndn ( e ); + char ebuf [ BUFSIZ ]; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Root access (%s) allowed on entry(%s)\n", + acl_access2str(access), + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf)); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,skip_access,""); + return LDAP_SUCCESS; + } + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 2 \n" ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,end,"aclpb error"); + return LDAP_OPERATIONS_ERROR; + } + + /* + * Am I a anonymous dude ? then we can use our anonympous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn ); + if ( clientDn && *clientDn == '\0' ) { + int ret_val; + ret_val = aclanom_match_profile ( pb, aclpb, e, NULL, SLAPI_ACL_READ ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,end,"anon"); + + if (ret_val != -1 ) return ret_val; + } + + aclpb->aclpb_state &= ~ACLPB_RESET_MASK; + if (aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + int ret_val; + ret_val = acl__attr_cached_result (aclpb, NULL, SLAPI_ACL_READ); + if (ret_val != -1 ) { + /* print summary if loglevel set */ + if ( slapi_is_loglevel_set(loglevel) ) { + char *n_edn; + n_edn = slapi_entry_get_ndn ( e ); + if ( ret_val == LDAP_SUCCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + /* + * pass NULL as the attr as this routine is concerned with + * access at the entry level. + */ + print_access_control_summary( "on entry", + ret_val, clientDn, aclpb, + acl_access2str(SLAPI_ACL_READ), + NULL, n_edn, + &decision_reason); + } + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL","", + tnf_string,eval_context_cached,""); + + return ret_val; + } + } + + /* + * Currently do not use this code--it results in confusing + * behaviour..see 529905 + */ +#ifdef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES + + /* Do we have access to the entry by virtue of + ** having access to an attr. Before that, let's find out which attrs + ** the user want. If the user has specified certain attributes, then + ** we check aginst that set of attributes. + */ + if (!((aclpb->aclpb_state & ACLPB_USER_WANTS_ALL_ATTRS) || + (aclpb->aclpb_state & ACLPB_USER_SPECIFIED_ATTARS))) { + int i; + if (attrs == NULL) { + aclpb->aclpb_state |= ACLPB_USER_WANTS_ALL_ATTRS; + } else { + for ( i = 0; attrs != NULL && attrs[i] != NULL; i++ ) { + if ( strcmp( LDAP_ALL_USER_ATTRS, attrs[i] ) == 0 ) { + aclpb->aclpb_state |= ACLPB_USER_WANTS_ALL_ATTRS; + break; + } + } + } + + if (!(aclpb->aclpb_state & ACLPB_USER_WANTS_ALL_ATTRS)) { + for (i = 0; attrs != NULL && attrs[i] != NULL; i++ ) { + if ( !slapi_entry_attr_find ( e, attrs[i], &currAttr ) ) { + aclpb->aclpb_state |= ACLPB_USER_SPECIFIED_ATTARS; + break; + } + } + } + } /* end of all user test*/ + + + /* + ** If user has specified a list of attrs, might as well use it + ** to determine access control. + */ + currAttr = NULL; + attr_index = -1; + if ( aclpb->aclpb_state & ACLPB_USER_SPECIFIED_ATTARS) { + attr_index = 0; + attr_type = attrs[attr_index++]; + } else { + /* Skip the operational attributes -- if there are any in the front */ + slapi_entry_first_attr ( e, &currAttr ); + if (currAttr != NULL) { + slapi_attr_get_flags ( currAttr, &flags ); + while ( flags & SLAPI_ATTR_FLAG_OPATTR ) { + flags = 0; + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( !rv ) slapi_attr_get_flags ( nextAttr, &flags ); + currAttr = nextAttr; + } + + /* Get the attr type */ + if ( currAttr ) slapi_attr_get_type ( currAttr , &attr_type ); + } + } + +#endif /*DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES*/ + +#ifndef DETERMINE_ACCESS_BASED_ON_REQUESTED_ATTRIBUTES + + /* + * Here look at each attribute in the entry and see if + * we have read access to it--if we do + * and we are not denied access to the entry then this + * is taken as implying access to the entry. + */ + slapi_entry_first_attr ( e, &currAttr ); + if (currAttr != NULL) { + slapi_attr_get_type ( currAttr , &attr_type ); + } +#endif + aclpb->aclpb_state |= ACLPB_EVALUATING_FIRST_ATTR; + + while (attr_type) { + if (acl_access_allowed (pb, e,attr_type, NULL, + SLAPI_ACL_READ) == LDAP_SUCCESS) { + /* + ** We found a rule which requires us to test access + ** to the entry. + */ + if ( aclpb->aclpb_state & ACLPB_FOUND_A_ENTRY_TEST_RULE){ + /* Do I have access on the entry itself */ + if (acl_access_allowed (pb, e, NULL, + NULL, access) != LDAP_SUCCESS) { + /* How was I denied ? + ** I could be denied on a DENY rule or because + ** there is no allow rule. If it's a DENY from + ** a DENY rule, then we don't have access to + ** the entry ( nice trick to get in ) + */ + if ( aclpb->aclpb_state & + ACLPB_EXECUTING_DENY_HANDLES) + return LDAP_INSUFFICIENT_ACCESS; + + /* The other case is I don't have an + ** explicit allow rule -- which is fine. + ** Since, I am already here, it means that I have + ** an implicit allow to the entry. + */ + } + } + aclpb->aclpb_state &= ~ACLPB_EVALUATING_FIRST_ATTR; + + /* + ** If we are not sending all the attrs, then we must + ** make sure that we have right on a attr that we are + ** sending + */ + len = strlen(attr_type); + if ( len > ACLPB_MAX_ATTR_LEN) { + slapi_ch_free ( (void **) &aclpb->aclpb_Evalattr); + aclpb->aclpb_Evalattr = slapi_ch_malloc(len); + } + strncpy (aclpb->aclpb_Evalattr, attr_type, len); + aclpb->aclpb_Evalattr[len] = '\0'; + if ( attr_index >= 0 ) { + /* + * access was granted to one of the user specified attributes + * which was found in the entry and that attribute is + * now in aclpb_Evalattr + */ + aclpb->aclpb_state |= + ACLPB_ACCESS_ALLOWED_USERATTR; + } else { + /* + * Access was granted to _an_ attribute in the entry and that + * attribute is now in aclpb_Evalattr + */ + aclpb->aclpb_state |= + ACLPB_ACCESS_ALLOWED_ON_A_ATTR; + } + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_entry_end , "ACL","", + tnf_string,called_access_allowed,""); + + return LDAP_SUCCESS; + } else { + /* try the next one */ + attr_type = NULL; + if (attr_index >= 0) { + attr_type = attrs[attr_index++]; + } else { + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( rv != 0 ) break; + currAttr = nextAttr; + slapi_attr_get_flags ( currAttr, &flags ); + while ( flags & SLAPI_ATTR_FLAG_OPATTR ) { + flags = 0; + rv = slapi_entry_next_attr ( e, currAttr, &nextAttr ); + if ( !rv ) slapi_attr_get_flags ( nextAttr, &flags ); + currAttr = nextAttr; + } + /* Get the attr type */ + if ( currAttr ) slapi_attr_get_type ( currAttr , &attr_type ); + } + } + } + + /* + ** That means. we have searched thru all the attrs and found + ** access is denied on all attrs. + ** + ** If there were no attributes in the entry at all (can have + ** such entries thrown up by the b/e, then we do + ** not have such an implied access. + */ + aclpb->aclpb_state |= ACLPB_ACCESS_DENIED_ON_ALL_ATTRS; + aclpb->aclpb_state &= ~ACLPB_EVALUATING_FIRST_ATTR; + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_entry_end ,"ACL",""); + + return LDAP_INSUFFICIENT_ACCESS; +} + +/*************************************************************************** +* +* acl_read_access_allowed_on_attr +* check access control on the given attr. +* +* Only used during search to test for read access to an attr. +* (Could be generalized) +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - access allowed +* LDAP_INSUFFICIENT_ACCESS - access denied +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_read_access_allowed_on_attr ( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* access rights */ + ) +{ + + struct acl_pblock *aclpb = NULL; + char ebuf [ BUFSIZ ]; + char *clientDn = NULL; + char *n_edn; + aclResultReason_t decision_reason; + int ret_val = -1; + int loglevel; + + loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY; + + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_attr_start ,"ACL",""); + + decision_reason.deciding_aci = NULL; + decision_reason.reason = ACL_REASON_NONE; + + /* I am here, because I have access to the entry */ + + n_edn = slapi_entry_get_ndn ( e ); + + /* If it's the root or acl is off or rootdse, he has all the priv */ + if ( acl_skip_access_check ( pb, e ) ) { + char ebuf [ BUFSIZ ]; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Root access (%s) allowed on entry(%s)\n", + acl_access2str(access), + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), 0); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,skip_aclcheck,""); + + return LDAP_SUCCESS; + } + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + if ( !aclpb ) { + slapi_log_error ( SLAPI_LOG_FATAL, plugin_name, "Missing aclpb 3 \n" ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclpb_error,""); + + return LDAP_OPERATIONS_ERROR; + } + + /* + * Am I a anonymous dude ? then we can use our anonympous profile + * We don't require the aclpb to have been initialized for anom stuff + * + */ + slapi_pblock_get (pb, SLAPI_REQUESTOR_DN ,&clientDn ); + if ( clientDn && *clientDn == '\0' ) { + ret_val = aclanom_match_profile ( pb, aclpb, e, attr, + SLAPI_ACL_READ ); + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,anon_decision,""); + if (ret_val != -1 ) return ret_val; + } + + /* Then I must have a access to the entry. */ + aclpb->aclpb_state |= ACLPB_ACCESS_ALLOWED_ON_ENTRY; + + if ( aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + + ret_val = acl__attr_cached_result (aclpb, attr, SLAPI_ACL_READ); + if (ret_val != -1 ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "MATCHED HANDLE:dn:%s attr: %s val:%d\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), attr, + ret_val ); + if ( ret_val == LDAP_SUCCESS) { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ALLOW; + } else { + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_NOT_ALLOWED; + } + goto acl_access_allowed_on_attr_Exit; + } else { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + } + } + + if (aclpb->aclpb_state & ACLPB_ACCESS_DENIED_ON_ALL_ATTRS) { + /* access is denied on all the attributes */ + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,deny_all_attrs,""); + + return LDAP_INSUFFICIENT_ACCESS; + } + + /* do I have access to all the entries by virtue of having aci + ** rules with targetattr ="*". If yes, then allow access to + ** rest of the attributes. + */ + if (aclpb->aclpb_state & ACLPB_ATTR_STAR_MATCHED) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "STAR Access allowed on attr:%s; entry:%s \n", + attr, ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_edn, ebuf), 0); + decision_reason.reason = + ACL_REASON_EVALCONTEXT_CACHED_ATTR_STAR_ALLOW; + ret_val = LDAP_SUCCESS; + goto acl_access_allowed_on_attr_Exit; + + } + + if (aclpb->aclpb_state & ACLPB_ACCESS_ALLOWED_ON_A_ATTR) { + + /* access is allowed on that attr. + ** for example: Slapi_Entry: cn, sn. phone, uid, passwd, address + ** We found that access is allowed on phone. That means the + ** -- access is denied on cn, sn + ** -- access is allowed on phone + ** -- Don't know about the rest. Need to evaluate. + */ + + if ( slapi_attr_type_cmp (attr, aclpb->aclpb_Evalattr, 1) == 0) { + /* from now on we need to evaluate access on + ** rest of the attrs. + */ + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_ON_A_ATTR; + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclp_Evalattr1,""); + + return LDAP_SUCCESS; + } else { + /* + * Here, the attr that implied access to the entry (aclpb_Evalattr), + * is not + * the one we currently want evaluated--so + * we need to evaluate access to attr--so fall through. + */ + } + + } else if (aclpb->aclpb_state & ACLPB_ACCESS_ALLOWED_USERATTR) { + /* Only skip evaluation on the user attr on which we have + ** evaluated before. + */ + if ( slapi_attr_type_cmp (attr, aclpb->aclpb_Evalattr, 1) == 0) { + aclpb->aclpb_state &= ~ACLPB_ACCESS_ALLOWED_USERATTR; + TNF_PROBE_1_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL","", + tnf_string,aclp_Evalattr2,""); + return LDAP_SUCCESS; + } + } + + /* we need to evaluate the access on this attr */ + return ( acl_access_allowed(pb, e, attr, val, access) ); + + /* This exit point prints a summary and returns ret_val */ +acl_access_allowed_on_attr_Exit: + + /* print summary if loglevel set */ + if ( slapi_is_loglevel_set(loglevel) ) { + + print_access_control_summary( "on attr", + ret_val, clientDn, aclpb, + acl_access2str(SLAPI_ACL_READ), + attr, n_edn, &decision_reason); + } + TNF_PROBE_0_DEBUG(acl_read_access_allowed_on_attr_end ,"ACL",""); + + return(ret_val); +} +/*************************************************************************** +* +* acl_check_mods +* check access control on the given entry to see if +* it allows the given modifications by the user associated with op. +* +* +* Input: +* +* +* Returns: +* LDAP_SUCCESS - mods allowed ok +* <err> - same return value as acl_access_allowed() +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_check_mods( + Slapi_PBlock *pb, + Slapi_Entry *e, + LDAPMod **mods, + char **errbuf +) +{ + int i; + int rv, accessCheckDisabled; + int lastmod = 0; + Slapi_Attr *attr = NULL; + char *n_edn; + Slapi_Backend *be = NULL; + Slapi_DN *e_sdn; + Acl_PBlock *aclpb = acl_get_aclpb ( pb, ACLPB_PROXYDN_PBLOCK ); + LDAPMod *mod; + Slapi_Mods smods; + + rv = slapi_pblock_get ( pb, SLAPI_PLUGIN_DB_NO_ACL, &accessCheckDisabled ); + if ( rv != -1 && accessCheckDisabled ) return LDAP_SUCCESS; + + if ( NULL == aclpb ) + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + + n_edn = slapi_entry_get_ndn ( e ); + e_sdn = slapi_entry_get_sdn ( e ); + + slapi_mods_init_byref(&smods,mods); + + for (mod = slapi_mods_get_first_mod(&smods); + mod != NULL; + mod = slapi_mods_get_next_mod(&smods)) { + switch (mod->mod_op & ~LDAP_MOD_BVALUES ) { + + case LDAP_MOD_DELETE: + if (mod->mod_bvalues != NULL ) { + break; + } + + /* + * Here, check that we have the right to delete all + * the values of the attribute in the entry. + */ + + case LDAP_MOD_REPLACE: + if ( !lastmod ) { + if (be == NULL) { + if (slapi_pblock_get( pb, SLAPI_BACKEND, &be )) { + be = NULL; + } + } + if (be != NULL) + slapi_pblock_get ( pb, SLAPI_BE_LASTMOD, &lastmod ); + } + if (lastmod && + (strcmp (mod->mod_type, "modifiersname")== 0 || + strcmp (mod->mod_type, "modifytimestamp")== 0)) { + continue; + } + + slapi_entry_attr_find (e, mod->mod_type, &attr); + if ( attr != NULL) { + Slapi_Value *sval=NULL; + const struct berval *attrVal=NULL; + int k= slapi_attr_first_value(attr,&sval); + while(k != -1) { + attrVal = slapi_value_get_berval(sval); + rv = slapi_access_allowed (pb, e, + mod->mod_type, + (struct berval *)attrVal, /* XXXggood had to cast away const - BAD */ + ACLPB_SLAPI_ACL_WRITE_DEL); /* was SLAPI_ACL_WRITE */ + if ( rv != LDAP_SUCCESS) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return(rv); + } + k= slapi_attr_next_value(attr, k, &sval); + } + } + else { + rv = slapi_access_allowed (pb, e, + mod->mod_type, + NULL, + ACLPB_SLAPI_ACL_WRITE_DEL); /* was SLAPI_ACL_WRITE */ + if ( rv != LDAP_SUCCESS) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return(rv); + } + } + break; + + default: + break; + } /* switch */ + + /* + * Check that we have add/delete writes on the specific values + * we are trying to add. + */ + + if ( aclpb && aclpb->aclpb_curr_entry_sdn ) + slapi_sdn_done ( aclpb->aclpb_curr_entry_sdn ); + + if ( mod->mod_bvalues != NULL ) { + + /* + * Here, there are specific values specified. + * For add and replace--we need add rights for these values. + * For delete we need delete rights for these values. + */ + + for ( i = 0; mod->mod_bvalues[i] != NULL; i++ ) { + + if ( ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) || + ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_REPLACE)) { + + rv = acl_access_allowed (pb,e, + mod->mod_type, + mod->mod_bvalues[i], + ACLPB_SLAPI_ACL_WRITE_ADD); /*was SLAPI_ACL_WRITE*/ + } else if ((mod->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) { + rv = acl_access_allowed (pb,e, + mod->mod_type, + mod->mod_bvalues[i], + ACLPB_SLAPI_ACL_WRITE_DEL); /*was SLAPI_ACL_WRITE*/ + } else { + rv = LDAP_INSUFFICIENT_ACCESS; + } + + if ( rv != LDAP_SUCCESS ) { + acl_gen_err_msg ( + SLAPI_ACL_WRITE, + n_edn, + mod->mod_type, + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return rv; + } + /* Need to check for all the values because + ** we may be modifying a "self<right>" value. + */ + + /* Are we adding/replacing a aci attribute + ** value. In that case, we need to make + ** sure that the new value has thr right + ** syntax + */ + if (strcmp(mod->mod_type, + aci_attr_type) == 0) { + if ( 0 != (rv = acl_verify_syntax( e_sdn, + mod->mod_bvalues[i]))) { + aclutil_print_err(rv, e_sdn, + mod->mod_bvalues[i], + errbuf); + /* Cleanup */ + slapi_mods_done(&smods); + return LDAP_INVALID_SYNTAX; + } + } + } /* for */ + } + } /* end of big for */ + /* Cleanup */ + slapi_mods_done(&smods); + return( LDAP_SUCCESS ); +} +/*************************************************************************** +* +* acl_modified +* Modifies ( removed, add, changes) the ACI LIST. +* +* Input: +* int *optype - op code +* char *dn - DN of the entry +* void *change - The change struct which contais the +* - change value +* +* Returns: +* None. +* +* Error Handling: +* None. +* +**************************************************************************/ +extern void +acl_modified (Slapi_PBlock *pb, int optype, char *n_dn, void *change) +{ + struct berval **bvalue; + char **value; + int rv=0; /* returned value */ + char* new_RDN; + char* parent_DN; + char* new_DN; + LDAPMod **mods; + struct berval b; + int j; + Slapi_Attr *attr = NULL; + Slapi_Entry *e = NULL; + char ebuf [ BUFSIZ]; + Slapi_DN *e_sdn; + aclUserGroup *ugroup = NULL; + + e_sdn = slapi_sdn_new_ndn_byval ( n_dn ); + /* Before we proceed, Let's first check if we are changing any groups. + ** If we are, then we need to change the signature + */ + switch ( optype ) { + case SLAPI_OPERATION_MODIFY: + case SLAPI_OPERATION_DELETE: + slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, (void*)&e); + break; + case SLAPI_OPERATION_ADD: + e = (Slapi_Entry *)change; + break; + } + + /* e can be null for RDN */ + if ( e ) slapi_entry_attr_find( e, "objectclass", &attr); + + if ( attr ) { + int group_change = 0; + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + i= slapi_attr_first_value ( attr,&sval ); + while(i != -1) { + attrVal = slapi_value_get_berval ( sval ); + if ( (strcasecmp (attrVal->bv_val, "groupOfNames") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfUniqueNames") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfCertificates") == 0 ) || + (strcasecmp (attrVal->bv_val, "groupOfURLs") == 0 ) ) { + group_change= 1; + if ( optype == SLAPI_OPERATION_MODIFY ) { + Slapi_Attr *a = NULL; + int rv; + rv = slapi_entry_attr_find ( e, "uniqueMember", &a); + if ( rv != 0 ) break; + rv = slapi_entry_attr_find ( e, "Member", &a ); + if ( rv != 0 ) break; + rv = slapi_entry_attr_find ( e, "MemberURL", &a ); + if ( rv != 0 ) break; + /* That means we are not changing the member + ** list, so it's okay to let go this + ** change + */ + group_change = 0; + } + break; + } + i= slapi_attr_next_value ( attr, i, &sval ); + } + + /* + ** We can do better here XXX, i.e invalidate the cache for users who + ** use this group. for now just do the whole thing. + */ + if ( group_change ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Group Change: Invalidating entire UserGroup Cache\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION(n_dn, ebuf)); + aclg_regen_group_signature(); + if ( (optype == SLAPI_OPERATION_MODIFY) || (optype == SLAPI_OPERATION_DELETE ) ) { + /* Then we need to invalidate the acl signature also */ + acl_signature = aclutil_gen_signature ( acl_signature ); + } + } + } + + /* + * Here if the target entry is in the group cache + * as a user then, as it's being changed it may move out of any dynamic + * groups it belongs to. + * Just remove it for now--can do better XXX by checking to see if + * it really needs to be removed by testing to see if he's + * still in th group after the change--but this requires keeping + * the memberURL of the group which we don't currently do. + * Also, if we keep + * the attributes that are being used in dynamic + * groups then we could only remove the user if a sensitive + * attribute was being modified (rather than scanning the whole user cache + * all the time). Also could do a hash lookup. + * + * aclg_find_userGroup() incs a refcnt so we can still refer to ugroup. + * aclg_markUgroupForRemoval() decs it and marks it for removal + * , so after that you cannot refer to ugroup. + * + */ + + if ( (ugroup = aclg_find_userGroup(n_dn)) != NULL) { + /* + * Mark this for deletion next time round--try to impact + * this mainline code as little as possible. + */ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Marking entry %s for removal from ACL user Group Cache\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION(n_dn, ebuf)); + aclg_markUgroupForRemoval (ugroup); + } + + /* + * Take the write lock around all the mods--so that + * other operations will see the acicache either before the whole mod + * or after but not, as it was before, during the mod. + * This is in line with the LDAP concept of the operation + * on the whole entry being the atomic unit. + * + */ + + switch(optype) { + case SLAPI_OPERATION_DELETE: + /* In this case we have already checked if the user has + ** right to delete the entry. Part of delete of entry is + ** remove all the ACLs also. + */ + + acllist_acicache_WRITE_LOCK(); + rv = acllist_remove_aci_needsLock(e_sdn, NULL); + acllist_acicache_WRITE_UNLOCK(); + + break; + case SLAPI_OPERATION_ADD: + slapi_entry_attr_find ( (Slapi_Entry *) change, aci_attr_type, &attr ); + + if ( attr ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int i; + + acllist_acicache_WRITE_LOCK(); + i= slapi_attr_first_value ( attr,&sval ); + while ( i != -1 ) { + attrVal = slapi_value_get_berval(sval); + rv= acllist_insert_aci_needsLock(e_sdn, attrVal ); + if (rv <= ACL_ERR) + aclutil_print_err(rv, e_sdn, attrVal, NULL); + /* Print the aci list */ + i= slapi_attr_next_value ( attr, i, &sval ); + } + acllist_acicache_WRITE_UNLOCK(); + } + break; + + case SLAPI_OPERATION_MODIFY: + { + int got_write_lock = 0; + + mods = (LDAPMod **) change; + + for (j=0; mods[j] != NULL; j++) { + if (strcasecmp(mods[j]->mod_type, aci_attr_type) == 0) { + + /* Got an aci to mod in this list of mods, so + * take the acicache lock for the whole list of mods, + * remembering to free it below. + */ + if ( !got_write_lock) { + acllist_acicache_WRITE_LOCK(); + got_write_lock = 1; + } + + switch (mods[j]->mod_op & ~LDAP_MOD_BVALUES) { + case LDAP_MOD_REPLACE: + /* First remove the item */ + rv = acllist_remove_aci_needsLock(e_sdn, NULL); + + /* now fall thru to add the new one */ + case LDAP_MOD_ADD: + /* Add the new aci */ + if (mods[j]->mod_op & LDAP_MOD_BVALUES) { + bvalue = mods[j]->mod_bvalues; + if (bvalue == NULL) + break; + for (; *bvalue != NULL; ++bvalue) { + rv=acllist_insert_aci_needsLock( e_sdn, *bvalue); + if (rv <= ACL_ERR) { + aclutil_print_err(rv, e_sdn, + *bvalue, NULL); + } + } + } else { + value = mods[j]->mod_values; + if (value == NULL) + break; + for (; *value != NULL; ++value) { + b.bv_len = strlen (*value); + b.bv_val = *value; + rv=acllist_insert_aci_needsLock( e_sdn, &b); + if (rv <= ACL_ERR) { + aclutil_print_err(rv, e_sdn, + &b, NULL); + } + } + } + break; + case LDAP_MOD_DELETE: + if (mods[j]->mod_op & LDAP_MOD_BVALUES) { + bvalue = mods[j]->mod_bvalues; + if (bvalue == NULL || *bvalue == NULL) { + rv = acllist_remove_aci_needsLock( e_sdn, NULL); + } else { + for (; *bvalue != NULL; ++bvalue) + acllist_remove_aci_needsLock( e_sdn, *bvalue); + } + } else { + value = mods[j]->mod_values; + if (value == NULL || *value == NULL) { + acllist_remove_aci_needsLock( e_sdn,NULL); + } else { + for (; *value != NULL; ++value) { + b.bv_len = strlen (*value); + b.bv_val = *value; + acllist_remove_aci_needsLock( e_sdn, &b); + } + } + + } + break; + + default: + break; + }/* modtype switch */ + }/* attrtype is aci */ + } /* end of for */ + if ( got_write_lock ) { + acllist_acicache_WRITE_UNLOCK(); + got_write_lock = 0; + } + + break; + }/* case op is modify*/ + + case SLAPI_OPERATION_MODRDN: + + new_RDN = (char*) change; + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "acl_modified (MODRDN %s => \"%s\"\n", + ACL_ESCAPE_STRING_WITH_PUNCTUATION (n_dn, ebuf), new_RDN, 0); + + /* compute new_DN: */ + parent_DN = slapi_dn_parent (n_dn); + if (parent_DN == NULL) { + new_DN = new_RDN; + } else { + new_DN = (char*) slapi_ch_malloc (strlen (new_RDN) + 3 + + strlen (parent_DN)); + strcpy (new_DN, new_RDN); + strcat (new_DN, ","); + strcat (new_DN, parent_DN); + slapi_dn_normalize (new_DN); + } + + /* Change the acls */ + acllist_acicache_WRITE_LOCK(); + acllist_moddn_aci_needsLock ( e_sdn, new_DN ); + acllist_acicache_WRITE_UNLOCK(); + + /* deallocat the parent_DN */ + if (parent_DN != NULL) { + slapi_ch_free ( (void **) &new_DN ); + slapi_ch_free ( (void **) &parent_DN ); + } + break; + + default: + /* print ERROR */ + break; + } /*optype switch */ + + slapi_sdn_free ( &e_sdn ); + +} +/*************************************************************************** +* +* acl__scan_for_acis +* Scan the list and picup the correct acls for evaluation. +* +* Input: +* Acl_PBlock *aclpb - Main ACL pblock +* int *err; - Any error status +* Returns: +* num_handles - Number of handles matched to the +* - resource + 1. +* Error Handling: +* None. +* +**************************************************************************/ +static int +acl__scan_for_acis(Acl_PBlock *aclpb, int *err) +{ + aci_t *aci; + NSErr_t errp; + int attr_matched; + int deny_handle; + int allow_handle; + int gen_allow_handle = ACI_MAX_ELEVEL+1; + int gen_deny_handle = ACI_MAX_ELEVEL+1; + int i; + PRUint32 cookie; + + TNF_PROBE_0_DEBUG(acl__scan_for_acis_start,"ACL",""); + + /* + ** Determine if we are traversing via the list Vs. we have our own + ** generated list + */ + if ( aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST || + aclpb->aclpb_handles_index[0] != -1 ) { + int kk = 0; + while ( kk < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[kk] != -1 ) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Using ACL Cointainer:%d for evaluation\n", kk); + kk++; + } + } + + memset (&errp, 0, sizeof(NSErr_t)); + *err = ACL_FALSE; + aclpb->aclpb_num_deny_handles = -1; + aclpb->aclpb_num_allow_handles = -1; + for (i=0; i <= ACI_MAX_ELEVEL; i++) { + aclpb->aclpb_deny_handles [i] = NULL; + aclpb->aclpb_allow_handles [i] = NULL; + } + + /* Check the signature. If it has changed, start fresh */ + if ( aclpb->aclpb_signature != acl_signature ) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Restart the scan -- due to acl changes\n"); + acllist_init_scan ( aclpb->aclpb_pblock, LDAP_SCOPE_BASE, NULL ); + } + + attr_matched = ACL_FALSE; + deny_handle = 0; + allow_handle = 0; + i = 0; + + aclpb->aclpb_stat_acllist_scanned++; + aci = acllist_get_first_aci ( aclpb, &cookie ); + + while( aci ) { + if (acl__resource_match_aci(aclpb, aci, 0, &attr_matched)) { + /* Generate the ACL list handle */ + if (aci->aci_handle == NULL) { + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + continue; + } + aclutil_print_aci (aci, acl_access2str (aclpb->aclpb_access)); + + if (aci->aci_type & ACI_HAS_DENY_RULE) { + if (aclpb->aclpb_deny_handles[aci->aci_elevel] == NULL ) { + aclpb->aclpb_deny_handles[aci->aci_elevel] = aci; + } else { + if ((gen_deny_handle + ACI_DEFAULT_ELEVEL + 1) == + aclpb->aclpb_deny_handles_size ) { + int num = ACLPB_INCR_LIST_HANDLES + + aclpb->aclpb_deny_handles_size; + /* allocate more space */ + aclpb->aclpb_deny_handles = + (aci_t **) + slapi_ch_realloc ( + (void *) aclpb->aclpb_deny_handles, + num * sizeof (aci_t *)); + aclpb->aclpb_deny_handles_size = num; + } + aclpb->aclpb_deny_handles [gen_deny_handle] = aci; + gen_deny_handle++; + } + deny_handle++; + } + /* + ** It's possible that a single acl is in both the camps i.e + ** It has a allow and a deny rule + ** In that case we keep the same acl in both the camps. + */ + if (aci->aci_type & ACI_HAS_ALLOW_RULE) { + if (aclpb->aclpb_allow_handles[aci->aci_elevel] == NULL ) { + aclpb->aclpb_allow_handles[aci->aci_elevel] = aci; + } else { + if ((gen_allow_handle + ACI_DEFAULT_ELEVEL + 1) == + aclpb->aclpb_allow_handles_size) { + /* allocate more space */ + int num = ACLPB_INCR_LIST_HANDLES + + aclpb->aclpb_allow_handles_size; + + aclpb->aclpb_allow_handles = + (aci_t **) + slapi_ch_realloc ( + (void *) aclpb->aclpb_allow_handles, + num * sizeof (aci_t *)); + aclpb->aclpb_allow_handles_size = num; + } + aclpb->aclpb_allow_handles [gen_allow_handle] = aci; + gen_allow_handle++; + } + allow_handle++; + } + } + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } /* end of while */ + + /* make the last one a null */ + aclpb->aclpb_deny_handles [gen_deny_handle] = NULL; + aclpb->aclpb_allow_handles [gen_allow_handle] = NULL; + + /* specify how many we found */ + aclpb->aclpb_num_deny_handles = deny_handle; + aclpb->aclpb_num_allow_handles = allow_handle; + + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "Num of ALLOW Handles:%d, DENY handles:%d\n", + aclpb->aclpb_num_allow_handles, aclpb->aclpb_num_deny_handles, 0); + + TNF_PROBE_0_DEBUG(acl__scan_for_acis_end,"ACL",""); + + return(allow_handle + deny_handle); +} + +/*************************************************************************** +* +* acl__resource_match_aci +* +* This compares the ACI for the given resource and determines if +* the ACL applies to the resource or not. +* +* For read/search operation, we collect all the possible acls which +* will apply, We will be using this list for future acl evaluation +* for that entry. +* +* Input: +* struct acl_pblock *aclpb - Main acl private block +* aci_t *aci - The ACI item +* int skip_attrEval - DOn't check for attrs +* int *a_matched - Attribute matched +* +* Returns: +* +* ACL_TRUE - Yep, This ACL is applicable to the +* - the resource. +* ACL_FALSE - No it is not. +* +* Error Handling: +* None. +* +**************************************************************************/ +#define ACL_RIGHTS_TARGETATTR_NOT_NEEDED ( SLAPI_ACL_ADD | SLAPI_ACL_DELETE | SLAPI_ACL_PROXY) +static int +acl__resource_match_aci( Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a_matched) +{ + + struct slapi_filter *f; /* filter */ + int rv; /* return value */ + int matches; + int attr_matched; + int attr_matched_in_targetattrfilters = 0; + int dn_matched; + char *res_attr; + int aci_right = 0; + int res_right = 0; + int star_matched = ACL_FALSE; + int num_attrs = 0; + AclAttrEval *c_attrEval = NULL; + const char *res_ndn = NULL; + const char *aci_ndn = NULL; + char *matched_val = NULL; + int add_matched_val_to_ht = 0; + char res_right_str[128]; + + TNF_PROBE_0_DEBUG(acl__resource_match_aci_start,"ACL",""); + + aclpb->aclpb_stat_aclres_matched++; + + /* Assume that resource matches */ + matches = ACL_TRUE; + + /* Figure out if the acl has the correct rights or not */ + aci_right = aci->aci_access; + res_right = aclpb->aclpb_access; + if (!(aci_right & res_right)) { + /* If we are looking for read/search and the acl has read/search + ** then go further because if targets match we may keep that + ** acl in the entry cache list. + */ + if (!((res_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) && + (aci_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)))) + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + + /* first Let's see if the entry is under the subtree where the + ** ACL resides. We can't let somebody affect a target beyond the + ** scope of where the ACL resides + ** Example: ACL is located in "ou=engineering, o=ace industry, c=us + ** but if the target is "o=ace industry, c=us", then we are in trouble. + ** + ** If the aci is in the rootdse and the entry is not, then we do not + ** match--ie. acis in the rootdse do NOT apply below...for the moment. + ** + */ + res_ndn = slapi_sdn_get_ndn ( aclpb->aclpb_curr_entry_sdn ); + aci_ndn = slapi_sdn_get_ndn ( aci->aci_sdn ); + if (!slapi_sdn_issuffix(aclpb->aclpb_curr_entry_sdn, aci->aci_sdn) + || (!slapi_is_rootdse(res_ndn) && slapi_is_rootdse(aci_ndn)) ) { + + /* cant' poke around */ + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + /* + ** We have a single ACI which we need to find if it applies to + ** the resource or not. + */ + if ((aci->aci_type & ACI_TARGET_DN) && + (aclpb->aclpb_curr_entry_sdn)) { + char *avaType; + struct berval *avaValue; + + f = aci->target; + dn_matched = ACL_TRUE; + slapi_filter_get_ava ( f, &avaType, &avaValue ); + + if (!slapi_dn_issuffix( res_ndn, avaValue->bv_val)) { + dn_matched = ACL_FALSE; + } + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + } + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + if (aci->aci_type & ACI_TARGET_PATTERN) { + + f = aci->target; + dn_matched = ACL_TRUE; + + if ((rv = acl_match_substring(f, (char *)res_ndn, 0 /* match suffux */)) != ACL_TRUE) { + dn_matched = ACL_FALSE; + if(rv == ACL_ERR) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl__resource_match_aci:pattern err\n", + 0,0,0); + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + } + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + } + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + /* + * Is it a (target="ldap://cn=*,($dn),o=sun.com") kind of thing. + */ + if (aci->aci_type & ACI_TARGET_MACRO_DN) { + /* + * See if the ($dn) component matches the string and + * retrieve the matched substring for later use + * in the userdn. + * The macro string is a function of the dn only, so if the + * entry is the same one don't recalculate it-- + * this flag only works for search right now, could + * also optimise for mods by making it work for mods. + */ + + if ( (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY) == 0 ) { + /* + * Here same entry so just look up the matched value, + * calculated from the targetdn and stored judiciously there + */ + matched_val = (char *)acl_ht_lookup( aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index); + } + if ( matched_val == NULL && + (aclpb->aclpb_res_type & (ACLPB_NEW_ENTRY | ACLPB_EFFECTIVE_RIGHTS))) { + + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Evaluating macro aci(%d)%s for resource %s\n", + aci->aci_index, aci->aclName, + aclutil__access_str(res_right, res_right_str)); + matched_val = acl_match_macro_in_target( res_ndn, + aci->aci_macro->match_this, + aci->aci_macro->macro_ptr); + add_matched_val_to_ht = 1; /* may need to add matched value to ht */ + } + if (matched_val == NULL) { + dn_matched = ACL_FALSE; + } else { + dn_matched = ACL_TRUE; + } + + if (aci->aci_type & ACI_TARGET_NOT) { + matches = (dn_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (dn_matched ? ACL_TRUE: ACL_FALSE); + } + + if ( add_matched_val_to_ht ) { + if ( matches == ACL_TRUE && matched_val ) { + /* + * matched_val may be needed later for matching on + * other targets or on the subject--so optimistically + * put it in the hash table. + * If, at the end of this routine, we + * find that after all the resource did not match then + * that's ok--the value is freed at aclpb cleanup time. + * If there is already an entry for this aci in this + * aclpb then remove it--it's an old value for a + * different entry. + */ + + acl_ht_add_and_freeOld(aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index, + matched_val); + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "-- Added aci(%d) and matched value (%s) to macro ht\n", + aci->aci_index, matched_val); + acl_ht_display_ht(aclpb->aclpb_macro_ht); + } else { + slapi_ch_free((void **)&matched_val); + if (matches == ACL_FALSE) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "Evaluated ACL_FALSE\n"); + } + } + } + } /* MACRO_DN */ + + /* No need to look further */ + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + + /* + ** Here, if there's a targetfilter field, see if it matches. + ** + ** The commented out code below was an erroneous attempt to skip + ** this test. It is wrong because: 1. you need to store + ** whether the last test matched or not (you cannot just assume it did) + ** and 2. It may not be the same aci, so the previous matched + ** value is a function of the aci. + ** May be interesting to build such a cache...but no evidence for + ** for that right now. See Bug 383424. + ** + ** + ** && ((aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) || + ** (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY)) + */ + if (aci->aci_type & ACI_TARGET_FILTER ) { + int filter_matched = ACL_TRUE; + + /* + * Check for macros. + * For targetfilter we need to fake the lasinfo structure--it's + * created "naturally" for subjects but not targets. + */ + + + if ( aci->aci_type & ACI_TARGET_FILTER_MACRO_DN) { + + lasInfo *lasinfo = NULL; + + lasinfo = (lasInfo*) slapi_ch_malloc( sizeof(lasInfo) ); + + lasinfo->aclpb = aclpb; + lasinfo->resourceEntry = aclpb->aclpb_curr_entry; + aclpb->aclpb_curr_aci = aci; + filter_matched = aclutil_evaluate_macro( aci->targetFilterStr, + lasinfo, + ACL_EVAL_TARGET_FILTER); + slapi_ch_free((void**)&lasinfo); + } else { + + + if (slapi_vattr_filter_test(NULL, aclpb->aclpb_curr_entry, + aci->targetFilter, + 0 /*don't do acess chk*/)!= 0) { + filter_matched = ACL_FALSE; + } + + } + + /* If it's a logical value we can do logic on it...otherwise we do not match */ + if ( filter_matched == ACL_TRUE || filter_matched == ACL_FALSE) { + if (aci->aci_type & ACI_TARGET_FILTER_NOT) { + matches = (filter_matched == ACL_TRUE ? ACL_FALSE : ACL_TRUE); + } else { + matches = (filter_matched == ACL_TRUE ? ACL_TRUE: ACL_FALSE); + } + } else { + matches = ACL_FALSE; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for targetfilter evaluation.\n"); + } + + if (matches == ACL_FALSE) { + goto acl__resource_match_aci_EXIT; + } + } + + /* + * Check to see if we need to evaluate any targetattrfilters. + * They look as follows: + * (targetattrfilters="add=sn:(sn=rob) && gn:(gn!=byrne), + * del=sn:(sn=rob) && gn:(gn=byrne)") + * + * For ADD/DELETE: + * If theres's a targetattrfilter then each add/del filter + * that applies to an attribute in the entry, must be satisfied + * by each value of the attribute in the entry. + * + * For MODIFY: + * If there's a targetattrfilter then the add/del filter + * must be satisfied by the attribute to be added/deleted. + * (MODIFY acl is evaluated one value at a time). + * + * + */ + + if ((aclpb->aclpb_access & SLAPI_ACL_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS )|| + (aclpb->aclpb_access & SLAPI_ACL_DELETE && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) ) { + + Targetattrfilter **attrFilterArray; + + Targetattrfilter *attrFilter = NULL; + + int found_applicable = 0; + Slapi_Attr *attr_ptr = NULL; + Slapi_Value *sval; + const struct berval *attrVal; + int k; + int done; + + + if (aclpb->aclpb_access & SLAPI_ACL_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) { + + attrFilterArray = aci->targetAttrAddFilters; + + } else if (aclpb->aclpb_access & SLAPI_ACL_DELETE && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS) { + + attrFilterArray = aci->targetAttrDelFilters; + + } + + attr_matched = ACL_TRUE; + num_attrs = 0; + + while (attrFilterArray[num_attrs] && attr_matched) { + attrFilter = attrFilterArray[num_attrs]; + + /* + * If this filter applies to an attribute in the entry, + * apply it to the entry. + * Otherwise just ignore it. + * + */ + + if (slapi_entry_attr_find ( aclpb->aclpb_curr_entry, + attrFilter->attr_str, + &attr_ptr) == 0) { + + /* + * This is an applicable filter. + * The filter is to be appplied to the entry being added + * or deleted. + * The filter needs to be satisfied by _each_ occurence + * of the attribute in the entry--otherwise you + * could satisfy the filter and then put loads of other + * values in on the back of it. + */ + + found_applicable = 1; + + sval=NULL; + attrVal=NULL; + k= slapi_attr_first_value(attr_ptr,&sval); + done = 0; + while(k != -1 && !done) { + attrVal = slapi_value_get_berval(sval); + + if ( acl__make_filter_test_entry( + &aclpb->aclpb_filter_test_entry, + attrFilter->attr_str, + (struct berval *)attrVal) == LDAP_SUCCESS ) { + + attr_matched= acl__test_filter( + aclpb->aclpb_filter_test_entry, + attrFilter->filter, + 1 /* Do filter sense evaluation below */ + ); + done = !attr_matched; + slapi_entry_free( aclpb->aclpb_filter_test_entry ); + } + + k= slapi_attr_next_value(attr_ptr, k, &sval); + }/* while */ + + /* + * Here, we applied an applicable filter to the entry. + * So if attr_matched is ACL_TRUE then every value + * of the attribute in the entry satisfied the filter. + * Otherwise, attr_matched is ACL_FALSE and not every + * value satisfied the filter, so we will teminate the + * scan of the filter list. + */ + + } + + num_attrs++; + } /* while */ + + /* + * Here, we've applied all the applicable filters to the entry. + * Each one must have been satisfied by all the values of the attribute. + * The result of this is stored in attr_matched. + */ + +#if 0 + /* + * Don't support a notion of "add != " or "del != " + * at the moment. + * To do this, need to test whether it's an add test or del test + * then if it's add and ACI_TARGET_ATTR_ADD_FILTERS_NOT then + * flip the bit. Same for del. + */ + + if (aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS_NOT) { + matches = (matches ? ACL_FALSE : ACL_TRUE); + } else { + matches = (matches ? ACL_TRUE: ACL_FALSE); + } +#endif + + /* No need to look further */ + if (attr_matched == ACL_FALSE) { + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + } else if ( (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) || + (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS ) ) { + + + /* + * Here, it's a modify add/del and we have attr filters. + * So, we need to scan the add/del filter list to find the filter + * that applies to the current attribute. + * Then the (attribute,value) pair being added/deleted better + * match that filter. + * + * + */ + + Targetattrfilter **attrFilterArray = NULL; + Targetattrfilter *attrFilter; + int found = 0; + + if (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD && + aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS) { + + attrFilterArray = aci->targetAttrAddFilters; + + } else if (aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL && + aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS) { + + attrFilterArray = aci->targetAttrDelFilters; + + } + + + /* + * Scan this filter list for an applicable filter. + */ + + found = 0; + num_attrs = 0; + + while (attrFilterArray[num_attrs] && !found) { + attrFilter = attrFilterArray[num_attrs]; + + /* If this filter applies to the attribute, stop. */ + if ((aclpb->aclpb_curr_attrEval) && + slapi_attr_type_cmp ( aclpb->aclpb_curr_attrEval->attrEval_name, + attrFilter->attr_str, 1) == 0) { + found = 1; + } + num_attrs++; + } + + /* + * Here, if found an applicable filter, then apply the filter to the + * (attr,val) pair. + * Otherwise, ignore the targetattrfilters. + */ + + if (found) { + + if ( acl__make_filter_test_entry( + &aclpb->aclpb_filter_test_entry, + aclpb->aclpb_curr_attrEval->attrEval_name, + aclpb->aclpb_curr_attrVal) == LDAP_SUCCESS ) { + + attr_matched= acl__test_filter(aclpb->aclpb_filter_test_entry, + attrFilter->filter, + 1 /* Do filter sense evaluation below */ + ); + slapi_entry_free( aclpb->aclpb_filter_test_entry ); + } + + /* No need to look further */ + if (attr_matched == ACL_FALSE) { + matches = attr_matched; + goto acl__resource_match_aci_EXIT; + } + + /* + * Here this attribute appeared and was matched in a + * targetattrfilters list, so record this fact so we do + * not have to scan the targetattr list for the attribute. + */ + + attr_matched_in_targetattrfilters = 1; + + + } + } /* targetvaluefilters */ + + + /* There are 3 cases by which acis are selected. + ** 1) By scanning the whole list and picking based on the resource. + ** 2) By picking a subset of the list which will be used for the whole + ** acl evaluation. + ** 3) A finer granularity, i.e, a selected list of acls which will be + ** used for only that entry's evaluation. + */ + if ( !(skip_attrEval) && (aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_ENTRY_LIST) && + (res_right & SLAPI_ACL_SEARCH) && + ((aci->aci_access & SLAPI_ACL_READ) || (aci->aci_access & SLAPI_ACL_SEARCH))) { + int kk=0; + + while ( kk < ACLPB_MAX_SELECTED_ACLS && aclpb->aclpb_handles_index[kk] >=0 ) kk++; + if (kk >= ACLPB_MAX_SELECTED_ACLS) { + aclpb->aclpb_state &= ~ACLPB_SEARCH_BASED_ON_ENTRY_LIST; + } else { + aclpb->aclpb_handles_index[kk++] = aci->aci_index; + aclpb->aclpb_handles_index[kk] = -1; + } + } + + + /* If we are suppose to skip attr eval, then let's skip it */ + if ( (aclpb->aclpb_access & SLAPI_ACL_SEARCH ) && ( ! skip_attrEval ) && + ( aclpb->aclpb_res_type & ACLPB_NEW_ENTRY )) { + aclEvalContext *c_evalContext = &aclpb->aclpb_curr_entryEval_context; + int nhandle = c_evalContext->acle_numof_tmatched_handles; + + if ( nhandle < ACLPB_MAX_SELECTED_ACLS) { + c_evalContext->acle_handles_matched_target[nhandle] = aci->aci_index; + c_evalContext->acle_numof_tmatched_handles++; + } + } + + if ( skip_attrEval ) { + goto acl__resource_match_aci_EXIT; + } + + /* We need to check again because we don't want to select this handle + ** if the right doesn't match for now. + */ + if (!(aci_right & res_right)) { + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + /* + * Here if the request is one that requires matching + * on a targetattr then do it here. + * If we have already matched an attribute in the targetattrfitlers list + * then we do not require a match in the targetattr so we can skip it. + * The operations that require targetattr are SLAPI_ACL_COMPARE, + * SLAPI_ACL_SEARCH, SLAPI_ACL_READ and SLAPI_ACL_WRITE, as long as + * c_attrEval is non-null (otherwise it's a modrdn op which + * does not require the targetattr list). + * + * rbyrneXXX if we had a proper permission for modrdn eg SLAPI_ACL_MODRDN + * then we would not need this crappy way of telling it was a MODRDN + * request ie. SLAPI_ACL_WRITE && !(c_attrEval). + */ + + c_attrEval = aclpb->aclpb_curr_attrEval; + + /* + * If we've already matched on targattrfilter then do not + * bother to look at the attrlist. + */ + + if (!attr_matched_in_targetattrfilters) { + + /* match target attr */ + if ((c_attrEval) && + (aci->aci_type & ACI_TARGET_ATTR)) { + /* there is a target ATTR */ + Targetattr **attrArray = aci->targetAttr; + Targetattr *attr = NULL; + + res_attr = c_attrEval->attrEval_name; + attr_matched = ACL_FALSE; + star_matched = ACL_FALSE; + num_attrs = 0; + + while (attrArray[num_attrs] && !attr_matched) { + attr = attrArray[num_attrs]; + if (attr->attr_type & ACL_ATTR_STRING) { + if (slapi_attr_type_cmp ( res_attr, + attr->u.attr_str, 1) == 0) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + } + } else if (attr->attr_type & ACL_ATTR_FILTER) { + if (ACL_TRUE == acl_match_substring ( + attr->u.attr_filter, + res_attr, 1)) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + } + } else if (attr->attr_type & ACL_ATTR_STAR) { + attr_matched = ACL_TRUE; + *a_matched = ACL_TRUE; + star_matched = ACL_TRUE; + } + num_attrs++; + } + + if (aci->aci_type & ACI_TARGET_ATTR_NOT) { + matches = (attr_matched ? ACL_FALSE : ACL_TRUE); + } else { + matches = (attr_matched ? ACL_TRUE: ACL_FALSE); + } + + + aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED; + /* figure out how it matched, i.e star matched */ + if (matches && star_matched && num_attrs == 1 && + !(aclpb->aclpb_state & ACLPB_FOUND_ATTR_RULE)) + aclpb->aclpb_state |= ACLPB_ATTR_STAR_MATCHED; + else { + /* we are here means that there is a specific + ** attr in the rule for this resource. + ** We need to avoid this case + ** Rule 1: (targetattr = "uid") + ** Rule 2: (targetattr = "*") + ** we cannot use STAR optimization + */ + aclpb->aclpb_state |= ACLPB_FOUND_ATTR_RULE; + aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED; + } + } else if ( (c_attrEval) || + (aci->aci_type & ACI_TARGET_ATTR)) { + if ((aci_right & ACL_RIGHTS_TARGETATTR_NOT_NEEDED) && + (aclpb->aclpb_access & ACL_RIGHTS_TARGETATTR_NOT_NEEDED)) { + /* + ** Targetattr rule doesn't make any sense + ** in this case. So select this rule + ** default: matches = ACL_TRUE; + */ + ; + } else if (aci_right & SLAPI_ACL_WRITE && + (aci->aci_type & ACI_TARGET_ATTR) && + !(c_attrEval)) { + /* We need to handle modrdn operation. Modrdn doesn't + ** change any attrs but changes the RDN and so (attr=NULL). + ** Here we found an acl which has a targetattr but + ** the resource doesn't need one. In that case, we should + ** consider this acl. + ** default: matches = ACL_TRUE; + */ + ; + } else { + matches = ACL_FALSE; + } + } + }/* !attr_matched_in_targetattrfilters */ + + /* + ** Here we are testing if we find a entry test rule (which should + ** be rare). In that case, just remember it. An entry test rule + ** doesn't have "(targetattr)". + */ + if (aclpb && (aclpb->aclpb_state & ACLPB_EVALUATING_FIRST_ATTR) && + (!(aci->aci_type & ACI_TARGET_ATTR))) { + aclpb->aclpb_state |= ACLPB_FOUND_A_ENTRY_TEST_RULE; + } + + /* + * Generic exit point for this routine: + * matches is ACL_TRUE if the aci matches the target of the resource, + * ACL_FALSE othrewise. + * Apologies for the goto--this is a retro-fitted exit point. + */ +acl__resource_match_aci_EXIT: + + /* + * For macro acis, there may be a partial macro string + * placed in the aclpb_macro_ht + * even if the aci did not finally match. + * All the partial strings will be freed at aclpb + * cleanup time. + */ + + TNF_PROBE_0_DEBUG(acl__resource_match_aci_end,"ACL",""); + + return (matches); +} +/* Macro to determine if the cached result is valid or not. */ +#define ACL_CACHED_RESULT_VALID( result) \ + (((result & ACLPB_CACHE_READ_RES_ALLOW) && \ + (result & ACLPB_CACHE_READ_RES_SKIP)) || \ + ((result & ACLPB_CACHE_SEARCH_RES_ALLOW) && \ + (result & ACLPB_CACHE_SEARCH_RES_SKIP))) ? 0 : 1 +/*************************************************************************** +* +* acl__TestRights +* +* Test the rights and find out if access is allowed or not. +* +* Processing Alogorithm: +* +* First, process the DENY rules one by one. If the user is not explicitly +* denied, then check if the user is allowed by processing the ALLOW handles +* one by one. +* The result of the evaluation is cached. Exceptions are +* -- If an acl happens to be in both DENY and ALLOW camp. +* -- Only interested for READ/SEARCH right. +* +* Input: +* struct acl_pblock *aclpb - main acl private block +* int access - The access bits +* char **right - The right we are looking for +* char ** generic - Generic rights +* +* Returns: +* +* ACL_RES_ALLOW - Access allowed +* ACL_RES_DENY - Access denied +* err - error condition +* +* Error Handling: +* None. +* +**************************************************************************/ +static int +acl__TestRights(Acl_PBlock *aclpb,int access, char **right, char ** map_generic, + aclResultReason_t *result_reason) +{ + ACLEvalHandle_t *acleval; + int rights_rv = ACL_RES_DENY; + int rv, i,j, k; + int index; + char *deny = NULL; + char *deny_generic = NULL; + char *acl_tag; + int expr_num; + char *testRights[2]; + aci_t *aci; + int numHandles = 0; + aclEvalContext *c_evalContext = NULL; + + TNF_PROBE_0_DEBUG(acl__TestRights_start,"ACL",""); + + c_evalContext = &aclpb->aclpb_curr_entryEval_context; + + /* record the aci and reason for access decision */ + result_reason->deciding_aci = NULL; + result_reason->reason = ACL_REASON_NONE; + + /* If we don't have any ALLLOW handles, it's DENY by default */ + if (aclpb->aclpb_num_allow_handles <= 0) { + result_reason->deciding_aci = NULL; + result_reason->reason = ACL_REASON_NO_MATCHED_RESOURCE_ALLOWS; + + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,no_allows,""); + + return ACL_RES_DENY; + } + + /* Get the ACL evaluation Context */ + acleval = aclpb->aclpb_acleval; + + testRights[0] = *right; + testRights[1] = '\0'; + + /* + ** START PROCESSING DENY HANDLES + ** Process each handle at a time. Do not concatenate the handles or else + ** all the context information will be build again and we will pay a + ** lot of penalty. The context is built the first time the handle is + ** processed. + ** + ** First we set the default to INVALID so that if rules are not matched, then + ** we get INVALID and if a rule matched, the we get DENY. + */ + aclpb->aclpb_state &= ~ACLPB_EXECUTING_ALLOW_HANDLES; + aclpb->aclpb_state |= ACLPB_EXECUTING_DENY_HANDLES; + ACL_SetDefaultResult (NULL, acleval, ACL_RES_INVALID); + + numHandles = ACI_MAX_ELEVEL + aclpb->aclpb_num_deny_handles; + for (i=0, k=0; i < numHandles && k < aclpb->aclpb_num_deny_handles ; ++i) { + int skip_eval = 0; + + /* + ** If the handle has been evaluated before, we can + ** cache the result. + */ + if (((aci = aclpb->aclpb_deny_handles[i]) == NULL) && (i <= ACI_MAX_ELEVEL)) + continue; + k++; + index = aci->aci_index; + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Evaluating DENY aci(%d) \"%s\"\n", index, aci->aclName); + + if (access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + /* + * aclpb->aclpb_cache_result[0..aclpb->aclpb_last_cache_result] is + * a cache of info about whether applicable acis + * allowed, did_not_allow or denied access + */ + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + short result; + + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if (access & SLAPI_ACL_SEARCH) { + if ( result & ACLPB_CACHE_SEARCH_RES_DENY){ + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found SEARCH DENY in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_deny,""); + return ACL_RES_DENY; + } else if ((result & ACLPB_CACHE_SEARCH_RES_SKIP) || + (result & ACLPB_CACHE_SEARCH_RES_ALLOW)) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found SEARCH SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } else { /* must be READ */ + if (result & ACLPB_CACHE_READ_RES_DENY) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found READ DENY in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_deny,""); + return ACL_RES_DENY; + } else if ( result & ACLPB_CACHE_READ_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "DENY:Found READ SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } + } + } + } + if (skip_eval) { + skip_eval = 0; + continue; + } + + rv = ACL_EvalSetACL(NULL, acleval, aci->aci_handle); + if ( rv < 0) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "acl__TestRights:Unable to set the DENY acllist\n", + 0,0,0); + continue; + } + /* + ** Now we have all the information we need. We need to call + ** the ONE ACL to test the rights. + ** return value: ACL_RES_DENY, ACL_RES_ALLOW, error codes + */ + aclpb->aclpb_curr_aci = aci; + rights_rv = ACL_EvalTestRights (NULL, acleval, testRights, + map_generic, &deny, + &deny_generic, + &acl_tag, &expr_num); + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "Processed:%d DENY handles Result:%d\n",index, rights_rv,0); + + if (rights_rv == ACL_RES_FAIL) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_NONE; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } + + /* have we executed an ATTR RULE */ + if ( aci->aci_ruleType & ACI_ATTR_RULES ) + aclpb->aclpb_state |= ACLPB_ATTR_RULE_EVALUATED; + + if (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS ) { + /* j == aclpb->aclpb_last_cache_result && + j < ACLPB_MAX_CACHE_RESULTS */ + aclpb->aclpb_last_cache_result++; + aclpb->aclpb_cache_result[j].aci_index = index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + + } else { /* cache overflow */ + if ( rights_rv == ACL_RES_DENY) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } else { + continue; + } + } + + __acl_set_aclIndex_inResult ( aclpb, access, index ); + if (rights_rv == ACL_RES_DENY) { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_DENY; + } else { /* MUST BE READ */ + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_DENY; + } + /* We are done -- return */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } else if (rights_rv == ACL_RES_ALLOW) { + /* This will happen, of we have an acl with both deny and allow + ** Since we may not have finished all the deny acl, go thru all + ** of them. We will use this cached result when we evaluate this + ** handle in the context of allow handles. + */ + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_ALLOW; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_ALLOW; + } + + } else { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_SKIP; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_SKIP; + } + continue; + } + } else { + if ( rights_rv == ACL_RES_DENY ) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_DENY; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_deny,""); + return ACL_RES_DENY; + } + } + } + + + /* + ** START PROCESSING ALLOW HANDLES. + ** Process each handle at a time. Do not concatenate the handles or else + ** all the context information will be build again and we will pay a + ** lot of penalty. The context is built the first time the handle is + ** processed. + ** + ** First we set the default to INVALID so that if rules are not matched, then + ** we get INVALID and if a rule matched, the we get ALLOW. + */ + aclpb->aclpb_state &= ~ACLPB_EXECUTING_DENY_HANDLES; + aclpb->aclpb_state |= ACLPB_EXECUTING_ALLOW_HANDLES; + ACL_SetDefaultResult (NULL, acleval, ACL_RES_INVALID); + numHandles = ACI_MAX_ELEVEL + aclpb->aclpb_num_allow_handles; + for (i=0, k=0; i < numHandles && k < aclpb->aclpb_num_allow_handles ; ++i) { + int skip_eval = 0; + /* + ** If the handle has been evaluated before, we can + ** cache the result. + */ + aci = aclpb->aclpb_allow_handles[i]; + if (((aci = aclpb->aclpb_allow_handles[i]) == NULL) && (i <= ACI_MAX_ELEVEL)) + continue; + k++; + index = aci->aci_index; + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "%d. Evaluating ALLOW aci(%d) \"%s\"\n", k, index, aci->aclName); + + if (access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + /* + * aclpb->aclpb_cache_result[0..aclpb->aclpb_last_cache_result] is + * a cache of info about whether applicable acis + * allowed, did_not_allow or denied access + */ + + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + short result; + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if (access & SLAPI_ACL_SEARCH) { + if (result & ACLPB_CACHE_SEARCH_RES_ALLOW) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found SEARCH ALLOW in cache\n"); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_allow,""); + return ACL_RES_ALLOW; + } else if ( result & ACLPB_CACHE_SEARCH_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found SEARCH SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + /* need to evaluate */ + break; + } + } else { + if ( result & ACLPB_CACHE_READ_RES_ALLOW) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found READ ALLOW in cache\n",0,0,0); + __acl_set_aclIndex_inResult ( aclpb, access, index ); + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_RESULT_CACHED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,cached_allow,""); + return ACL_RES_ALLOW; + } else if ( result & ACLPB_CACHE_READ_RES_SKIP) { + slapi_log_error(SLAPI_LOG_ACL, plugin_name, + "Found READ SKIP in cache\n",0,0,0); + skip_eval = 1; + break; + } else { + break; + } + } + } + } + } + if ( skip_eval) { + skip_eval = 0; + continue; + } + + TNF_PROBE_0_DEBUG(acl__libaccess_start,"ACL",""); + rv = ACL_EvalSetACL(NULL, acleval, aci->aci_handle); + if ( rv < 0) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, + "acl__TestRights:Unable to set the acllist\n", + 0,0,0); + continue; + } + /* + ** Now we have all the information we need. We need to call + ** the ONE ACL to test the rights. + ** return value: ACL_RES_DENY, ACL_RES_ALLOW, error codes + */ + aclpb->aclpb_curr_aci = aci; + rights_rv = ACL_EvalTestRights (NULL, acleval, testRights, + map_generic, &deny, + &deny_generic, + &acl_tag, &expr_num); + TNF_PROBE_0_DEBUG(acl__libaccess_end,"ACL",""); + + if (aci->aci_ruleType & ACI_ATTR_RULES) + aclpb->aclpb_state |= ACLPB_ATTR_RULE_EVALUATED; + + if (access & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) { + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (index == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS ) { + /* j == aclpb->aclpb_last_cache_result && + j < ACLPB_MAX_CACHE_RESULTS */ + aclpb->aclpb_last_cache_result++; + aclpb->aclpb_cache_result[j].aci_index = index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + } else { /* cache overflow */ + slapi_log_error (SLAPI_LOG_FATAL, "acl__TestRights", "cache overflown\n"); + if ( rights_rv == ACL_RES_ALLOW) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + } else { + continue; + } + } + + __acl_set_aclIndex_inResult ( aclpb, access, index ); + if (rights_rv == ACL_RES_ALLOW) { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_ALLOW; + } else { /* must be READ */ + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_ALLOW; + } + + /* We are done -- return */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + + } else { + if (access & SLAPI_ACL_SEARCH) { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_SEARCH_RES_SKIP; + } else { + aclpb->aclpb_cache_result[j].result |= + ACLPB_CACHE_READ_RES_SKIP; + } + continue; + } + } else { + if ( rights_rv == ACL_RES_ALLOW ) { + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_EVALUATED_ALLOW; + TNF_PROBE_1_DEBUG(acl__TestRights_end,"ACL","", + tnf_string,evaled_allow,""); + return ACL_RES_ALLOW; + } + } + }/* for */ + result_reason->deciding_aci = aci; + result_reason->reason = ACL_REASON_NO_MATCHED_SUBJECT_ALLOWS; + + TNF_PROBE_0_DEBUG(acl__TestRights_end,"ACL",""); + + return (ACL_RES_DENY); +} +/*************************************************************************** +* +* acl_match_substring +* +* Compare the input string to the patteren in the filter +* +* Input: +* struct slapi_filter *f - Filter which has the patteren +* char *str - String to compare +* int exact_match - 1; match the pattern exactly +* - 0; match the pattern as a suffix +* +* Returns: +* ACL_TRUE - The sting matches with the patteren +* ACL_FALSE - No it doesn't +* ACL_ERR - Error +* +* Error Handling: +* None. +* +**************************************************************************/ +int +acl_match_substring ( Slapi_Filter *f, char *str, int exact_match) +{ + int i, rc, len; + char *p; + char *end, *realval, *tmp; + char pat[BUFSIZ]; + char buf[BUFSIZ]; + char *type, *initial, *final; + char **any; + + if ( 0 != slapi_filter_get_subfilt ( f, &type, &initial, &any, &final ) ) { + return (ACL_FALSE); + } + + /* convert the input to lower. */ + for (p = str; *p; p++) + *p = TOLOWER ( *p ); + + /* construct a regular expression corresponding to the filter: */ + pat[0] = '\0'; + p = pat; + end = pat + sizeof(pat) - 2; /* leave room for null */ + + + if ( initial != NULL) { + strcpy (p, "^"); + p = strchr (p, '\0'); + + /* 2 * in case every char is special */ + if (p + 2 * strlen ( initial ) > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + + return (ACL_ERR); + } + + if (!exact_match) { + strcpy (p, ".*"); + p = strchr (p, '\0'); + } + acl_strcpy_special (p, initial); + p = strchr (p, '\0'); + } + + if ( any != NULL) { + for (i = 0; any && any[i] != NULL; i++) { + /* ".*" + value */ + if (p + 2 * strlen ( any[i]) + 2 > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + return (ACL_ERR); + } + + strcpy (p, ".*"); + p = strchr (p, '\0'); + acl_strcpy_special (p, any[i]); + p = strchr (p, '\0'); + } + } + + + if ( final != NULL) { + /* ".*" + value */ + if (p + 2 * strlen ( final ) + 2 > end) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "not enough pattern space\n", 0, 0, 0); + return (ACL_ERR); + } + + strcpy (p, ".*"); + p = strchr (p, '\0'); + acl_strcpy_special (p, final); + p = strchr (p, '\0'); + strcpy (p, "$"); + } + + /* see if regex matches with the input string */ + tmp = NULL; + len = strlen(str); + + if (len < sizeof(buf)) { + strcpy (buf, str); + realval = buf; + } else { + tmp = (char*) slapi_ch_malloc (len + 1); + strcpy (tmp, str); + realval = tmp; + } + + slapi_dn_normalize (realval); + + + /* What we have built is a regular pattaren expression. + ** Now we will compile the pattern and compare wth the string to + ** see if the input string matches with the patteren or not. + */ + slapd_re_lock(); + if ((p = slapd_re_comp (pat)) != 0) { + slapi_log_error (SLAPI_LOG_ACL, plugin_name, + "acl_match_substring:re_comp failed (%s)\n", p, 0, 0); + slapd_re_unlock(); + return (ACL_ERR); + } + + /* re_exec() returns 1 if the string p1 matches the last compiled + ** regular expression, 0 if the string p1 failed to match + ** (see man pages) + ** + ** IMPORTANT NOTE: If I use compile() and step() to do the patteren + ** matching, it seems that step() is leaking 1036 bytes/search + ** I couldn't figure out why it's leaking. + */ + rc = slapd_re_exec( realval ); + + slapd_re_unlock(); + + if (tmp != NULL) { + slapi_ch_free ( (void **) &tmp ); + } + + if (rc == 1) { + return ACL_TRUE; + } else { + return ACL_FALSE; + } +} +/*************************************************************************** +* +* acl__reset_cached_result +* +* Go thru the cached result and invlalidate the cached evaluation results for +* rules which can only be cached based on the scope. +* If we have scope ACI_CACHE_RESULT_PER_ENTRY, then we need to invalidate the +* cacched reult whenever we hit a new entry. +* +* Returns: +* Null +* +**************************************************************************/ +static void +acl__reset_cached_result (struct acl_pblock *aclpb ) +{ + + int j; + + for (j =0; j < aclpb->aclpb_last_cache_result; j++) { + /* Unless we have to clear the result, move on */ + if (!( aclpb->aclpb_cache_result[j].aci_ruleType & ACI_CACHE_RESULT_PER_ENTRY)) + continue; + aclpb->aclpb_cache_result[j].result = 0; + } +} +/* + * acl_access_allowed_disjoint_resource + * + * This is an internal module which can be used to verify + * access to a resource which may not be inside the search scope. + * + * Returns: + * - Same return val as acl_access_allowed(). + */ +int +acl_access_allowed_disjoint_resource( + Slapi_PBlock *pb, + Slapi_Entry *e, /* The Slapi_Entry */ + char *attr, /* Attribute of the entry */ + struct berval *val, /* value of attr. NOT USED */ + int access /* access rights */ + ) +{ + + int rv; + struct acl_pblock *aclpb = NULL; + + aclpb = acl_get_aclpb ( pb, ACLPB_BINDDN_PBLOCK ); + /* + ** It's possible that we already have a context of ACLs. + ** However once in a while, we need to test + ** access to a resource which (like vlv, schema) which falls + ** outside the search base. In that case, we need to go + ** thru all the ACLs and not depend upon the acls which we have + ** gathered. + */ + + /* If you have the right to use the resource, then we don't need to check for + ** proxy right on that resource. + */ + if (aclpb) + aclpb->aclpb_state |= ( ACLPB_DONOT_USE_CONTEXT_ACLS| ACLPB_DONOT_EVALUATE_PROXY ); + + rv = acl_access_allowed(pb, e, attr, val, access); + + if (aclpb) aclpb->aclpb_state &= ~ACLPB_DONOT_USE_CONTEXT_ACLS; + if (aclpb ) aclpb->aclpb_state &= ~ACLPB_DONOT_EVALUATE_PROXY; + + return rv; +} + +/* + * acl__attr_cached_result + * Loops thru the cached result and determines if we can use the cached value. + * + * Inputs: + * Slapi_pblock *aclpb - acl private block + * char *attr - attribute name + * int access - access type + * Returns: + * LDAP_SUCCESS: - access is granted + * LDAP_INSUFFICIENT_ACCESS - access denied + * ACL_ERR - no cached info about this attr. + * - or the attr had multiple result and so + * - we can't determine the outcome. + * + */ +static int +acl__attr_cached_result (struct acl_pblock *aclpb, char *attr, int access ) +{ + + int i, rc; + aclEvalContext *c_evalContext; + + if ( !(access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ) )) + return ACL_ERR; + + if (aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT ) { + c_evalContext = &aclpb->aclpb_prev_opEval_context; + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "acl__attr_cached_result:Using Context: ACLPB_ACLCB\n", 0,0,0 ); + } else { + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "acl__attr_cached_result:Using Context: ACLPB_PREV\n", 0,0,0 ); + } + + if ( attr == NULL ) { + int eval_read = 0; + /* + ** Do I have access to at least one attribute, then I have + ** access to the entry. + */ + for (i=0; i < c_evalContext->acle_numof_attrs; i++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[i]; + + if ( (access & SLAPI_ACL_READ ) && a_eval->attrEval_r_status && + a_eval->attrEval_r_status < ACL_ATTREVAL_DETERMINISTIC ) { + eval_read++; + if ( a_eval->attrEval_r_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + /* rbyrne: recompute if we have to. + * How does this cached result get turned off for + * attr style acis which acannot be cached becuase entry + * can result in a diff value. + */ + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_r_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + if ( rc == LDAP_SUCCESS) { + return LDAP_SUCCESS; + } + } + } + }/* for */ + /* + * If we have scanned the whole list without success then + * we are not granting access to this entry through access + * to an attribute in the list--however this does not mean + * that we do not have access to the entry via another attribute + * not already in the list, so return -1 meaning + * "don't know". + */ + return(ACL_ERR); +#if 0 + if ( eval_read ) + return LDAP_INSUFFICIENT_ACCESS; + else + return ACL_ERR; +#endif + } + + for (i=0; i < c_evalContext->acle_numof_attrs; i++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[i]; + + if ( a_eval == NULL ) continue; + + if (strcasecmp ( attr, a_eval->attrEval_name ) == 0 ) { + if ( access & SLAPI_ACL_SEARCH ) { + if (a_eval->attrEval_s_status < ACL_ATTREVAL_DETERMINISTIC ) { + if ( a_eval->attrEval_s_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + else if ( a_eval->attrEval_s_status & ACL_ATTREVAL_FAIL) + return LDAP_INSUFFICIENT_ACCESS; + else if ( a_eval->attrEval_s_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_s_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + } else + return ACL_ERR; + } else { + /* This means that for the same attribute and same type of + ** access, we had different results at different time. + ** Since we are not caching per object, we can't + ** determine exactly. So, can't touch this + */ + return ACL_ERR; + } + } else { + if (a_eval->attrEval_r_status < ACL_ATTREVAL_DETERMINISTIC ) { + if ( a_eval->attrEval_r_status & ACL_ATTREVAL_SUCCESS) + return LDAP_SUCCESS; + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_FAIL) + return LDAP_INSUFFICIENT_ACCESS; + else if ( a_eval->attrEval_r_status & ACL_ATTREVAL_RECOMPUTE ) { + rc = acl__recompute_acl ( aclpb, a_eval, access, + a_eval->attrEval_r_aciIndex); + if ( rc != ACL_ERR ) { + acl_copyEval_context ( aclpb, c_evalContext, + &aclpb->aclpb_curr_entryEval_context, 1); + } + } else + return ACL_ERR; + } else { + /* Look above for explanation */ + return ACL_ERR; + } + } + } + } + return ACL_ERR; +} + +/* + * Had to do this juggling of casting to make + * both Nt & unix compiler happy. + */ +static int +acl__cmp(const void *a, const void *b) +{ + short *i = (short *) a; + short *j = (short *) b; + + if ( (short) *i > (short) *j ) + return (1); + if ( (short)*i < (short) *j) + return (-1); + return (0); +} + +/* + * acl__scan_match_handles + * Go thru the ACL list and determine if the list of acls selected matches + * what we have in the cache. + * + * Inputs: + * Acl_PBlock *pb - Main pblock ( blacvk hole) + * int type - Which context to look on + * + * Returns: + * 0 - matches all the acl handles + * ACL_ERR - sorry; no match + * + * ASSUMPTION: A READER LOCK ON ACL LIST + */ +static int +acl__scan_match_handles ( Acl_PBlock *aclpb, int type) +{ + + + int matched = 0; + aci_t *aci = NULL; + int index; + PRUint32 cookie; + aclEvalContext *c_evalContext = NULL; + + if (type == ACLPB_EVALCONTEXT_PREV ) { + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + } else if ( type == ACLPB_EVALCONTEXT_ACLCB ){ + c_evalContext = &aclpb->aclpb_prev_opEval_context; + } else { + return ACL_ERR; + } + + + if ( !c_evalContext->acle_numof_tmatched_handles ) + return ACL_ERR; + + aclpb->aclpb_stat_acllist_scanned++; + aci = acllist_get_first_aci ( aclpb, &cookie ); + + while ( aci ) { + index = aci->aci_index; + if (acl__resource_match_aci(aclpb, aci, 1 /* skip attr matching */, NULL )) { + int j; + int s_matched = matched; + + /* We have a sorted list of handles that matched the target */ + + for (j=0; j < c_evalContext->acle_numof_tmatched_handles ; j++ ) { + if ( c_evalContext->acle_handles_matched_target[j] > index ) + + break; + else if ( index == c_evalContext->acle_handles_matched_target[j] ) { + int jj; + matched++; + + /* See if this is a ATTR rule that matched -- in that case we have + ** to nullify the cached result + */ + if ( aci->aci_ruleType & ACI_ATTR_RULES ) { + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Found an attr Rule [Name:%s Index:%d\n", aci->aclName, + aci->aci_index ); + for ( jj =0; jj < c_evalContext->acle_numof_attrs; jj++ ) { + AclAttrEval *a_eval = &c_evalContext->acle_attrEval[jj]; + if ( a_eval->attrEval_r_aciIndex == aci->aci_index ) + a_eval->attrEval_r_status = ACL_ATTREVAL_RECOMPUTE; + if ( a_eval->attrEval_s_aciIndex == aci->aci_index ) + a_eval->attrEval_s_status = ACL_ATTREVAL_RECOMPUTE; + } + } + break; + } + } + if ( s_matched == matched ) return ACL_ERR; + } + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } + if ( matched == c_evalContext->acle_numof_tmatched_handles ) + return 0; + + return ACL_ERR; +} +/* + * acl_copyEval_context + * Copy the context info which include attr info and handles. + * + * Inputs + * struct acl_pblock *aclpb - acl private main block + * aclEvalContext *src - src context + * aclEvalContext *dest - dest context + * Returns: + * None. + * + */ +void +acl_copyEval_context ( struct acl_pblock *aclpb, aclEvalContext *src, + aclEvalContext *dest , int copy_attr_only ) +{ + + int d_slot, i; + + /* Do a CLEAN copy we have nothing or else do an incremental copy.*/ + if ( src->acle_numof_attrs < 1 ) + return; + + /* Copy the attr info */ + if ( dest->acle_numof_attrs < 1 ) + acl_clean_aclEval_context ( dest, 0 /*clean */ ); + + d_slot = dest->acle_numof_attrs; + for (i=0; i < src->acle_numof_attrs; i++ ) { + int j; + int attr_exists = 0; + int dd_slot = d_slot; + + if ( aclpb && (i == 0) ) aclpb->aclpb_stat_num_copycontext++; + + if ( src->acle_attrEval[i].attrEval_r_status == 0 && + src->acle_attrEval[i].attrEval_s_status == 0 ) + continue; + + for ( j = 0; j < dest->acle_numof_attrs; j++ ) { + if ( strcasecmp ( src->acle_attrEval[i].attrEval_name, + dest->acle_attrEval[j].attrEval_name ) == 0 ) { + /* We have it. skip it. */ + attr_exists = 1; + dd_slot = j; + break; + } + } + if ( !attr_exists ) { + if ( dd_slot >= ACLPB_MAX_ATTRS -1 ) + break; + + if ( aclpb) aclpb->aclpb_stat_num_copy_attrs++; + + if ( dest->acle_attrEval[dd_slot].attrEval_name ) + slapi_ch_free ( (void **) &dest->acle_attrEval[dd_slot].attrEval_name ); + + dest->acle_attrEval[dd_slot].attrEval_name = + slapi_ch_strdup ( src->acle_attrEval[i].attrEval_name ); + } + /* Copy the result status and the aci index */ + dest->acle_attrEval[dd_slot].attrEval_r_status = + src->acle_attrEval[i].attrEval_r_status; + dest->acle_attrEval[dd_slot].attrEval_r_aciIndex = + src->acle_attrEval[i].attrEval_r_aciIndex; + dest->acle_attrEval[dd_slot].attrEval_s_status = + src->acle_attrEval[i].attrEval_s_status; + dest->acle_attrEval[dd_slot].attrEval_s_aciIndex = + src->acle_attrEval[i].attrEval_s_aciIndex; + + if (!attr_exists ) d_slot++; + } + + dest->acle_numof_attrs = d_slot; + dest->acle_attrEval[d_slot].attrEval_name = NULL; + + if ( copy_attr_only ) + return; + + /* First sort the arrays which keeps the acl index numbers */ + qsort ( (char *) src->acle_handles_matched_target, + (size_t)src->acle_numof_tmatched_handles, sizeof( int ), acl__cmp ); + + for (i=0; i < src->acle_numof_tmatched_handles; i++ ) { + dest->acle_handles_matched_target[i] = + src->acle_handles_matched_target[i]; + } + + if ( src->acle_numof_tmatched_handles ) { + dest->acle_numof_tmatched_handles = src->acle_numof_tmatched_handles; + if ( aclpb) aclpb->aclpb_stat_num_tmatched_acls = src->acle_numof_tmatched_handles; + } +} + +/* + * acl_clean_aclEval_context + * Clean the eval context + * + * Inputs: + * aclEvalContext *clean_me - COntext to be cleaned + * int clean_type - 0: clean, 1 scrub + * + */ + +void +acl_clean_aclEval_context ( aclEvalContext *clean_me, int scrub_only ) +{ + int i; + + /* Copy the attr info */ + for (i=0; i < clean_me->acle_numof_attrs; i++ ) { + + char *a_name = clean_me->acle_attrEval[i].attrEval_name; + if ( a_name && !scrub_only) { + slapi_ch_free ( (void **) &a_name ); + clean_me->acle_attrEval[i].attrEval_name = NULL; + } + clean_me->acle_attrEval[i].attrEval_r_status = 0; + clean_me->acle_attrEval[i].attrEval_s_status = 0; + clean_me->acle_attrEval[i].attrEval_r_aciIndex = 0; + clean_me->acle_attrEval[i].attrEval_s_aciIndex = 0; + } + + if ( !scrub_only ) clean_me->acle_numof_attrs = 0; + clean_me->acle_numof_tmatched_handles = 0; +} +/* + * acl__match_handlesFromCache + * + * We have 2 cacheed information + * 1) cached info from the previous operation + * 2) cached info from the prev entry evaluation + * + * What we are doing here is going thru all the acls and see if the same + * set of acls apply to this resource or not. If it does, then do we have + * a cached info for the attr. If we don't for both of the cases then we need + * to evaluate all over again. + * + * Inputs: + * struct acl_pblock - ACL private block; + * char *attr - Attribute name + * int access - acces type + * + * returns: + * LDAP_SUCCESS (0) - The same acls apply and we have + * access ALLOWED on the attr + * LDAP_INSUFFICIENT_ACCESS - The same acls apply and we have + * access DENIED on the attr + * -1 - Acls doesn't match or we don't have + * cached info for this attr. + * + * ASSUMPTIONS: A reader lock has been obtained for the acl list. + */ +static int +acl__match_handlesFromCache ( Acl_PBlock *aclpb, char *attr, int access) +{ + + aclEvalContext *c_evalContext = NULL; + int context_type = 0; + int ret_val = -1; /* it doen't match by default */ + + /* Before we proceed, find out if we have evaluated any ATTR RULE. If we have + ** then we can't use any caching mechanism + */ + + if ( aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT ) { + context_type = ACLPB_EVALCONTEXT_ACLCB; + c_evalContext = &aclpb->aclpb_prev_opEval_context; + } else { + context_type = ACLPB_EVALCONTEXT_PREV; + c_evalContext = &aclpb->aclpb_prev_entryEval_context; + } + + + if ( aclpb->aclpb_res_type & (ACLPB_NEW_ENTRY | ACLPB_EFFECTIVE_RIGHTS) ) { + aclpb->aclpb_state |= ACLPB_MATCHES_ALL_ACLS; + ret_val = acl__scan_match_handles ( aclpb, context_type ); + if ( -1 == ret_val ) { + aclpb->aclpb_state &= ~ACLPB_MATCHES_ALL_ACLS; + aclpb->aclpb_state |= ACLPB_UPD_ACLCB_CACHE; + /* Did not match */ + if ( context_type == ACLPB_HAS_ACLCB_EVALCONTEXT ) { + aclpb->aclpb_state &= ~ACLPB_HAS_ACLCB_EVALCONTEXT; + } else { + aclpb->aclpb_state |= ACLPB_COPY_EVALCONTEXT; + c_evalContext->acle_numof_tmatched_handles = 0; + } + } + } + if ( aclpb->aclpb_state & ACLPB_MATCHES_ALL_ACLS ) { + /* See if we have a cached result for this attr */ + ret_val = acl__attr_cached_result (aclpb, attr, access); + + /* It's not in the ACLCB context but we might have it in the + ** current/prev context. Take a look at it. we might have evaluated + ** this attribute already. + */ + if ( (-1 == ret_val ) && + ( aclpb->aclpb_state & ACLPB_HAS_ACLCB_EVALCONTEXT )) { + aclpb->aclpb_state &= ~ACLPB_HAS_ACLCB_EVALCONTEXT ; + ret_val = acl__attr_cached_result (aclpb, attr, access); + aclpb->aclpb_state |= ACLPB_HAS_ACLCB_EVALCONTEXT ; + + /* We need to do an incremental update */ + if ( !ret_val ) aclpb->aclpb_state |= ACLPB_INCR_ACLCB_CACHE; + } + } + return ret_val; +} +/* + * acl__get_attrEval + * Get the atteval from the current context and hold the ptr in aclpb. + * If we have too many attrs, then allocate a new one. In that case + * we let the caller know about that so that it will be deallocated. + * + * Returns: + * int - 0: The context was indexed. So, no allocations. + * - 1; context was allocated - deallocate it. + */ +static int +acl__get_attrEval ( struct acl_pblock *aclpb, char *attr ) +{ + + int j; + aclEvalContext *c_ContextEval = &aclpb->aclpb_curr_entryEval_context; + int deallocate_attrEval = 0; + AclAttrEval *c_attrEval = NULL; + + if ( !attr ) return deallocate_attrEval; + + aclpb->aclpb_curr_attrEval = NULL; + + /* Go thru and see if we have the attr already */ + for (j=0; j < c_ContextEval->acle_numof_attrs; j++) { + c_attrEval = &c_ContextEval->acle_attrEval[j]; + + if ( c_attrEval && + slapi_attr_type_cmp ( c_attrEval->attrEval_name, attr, 1) == 0 ) { + aclpb->aclpb_curr_attrEval = c_attrEval; + break; + } + } + + if ( !aclpb->aclpb_curr_attrEval) { + if ( c_ContextEval->acle_numof_attrs == ACLPB_MAX_ATTRS -1 ) { + /* Too many attrs. create a temp one */ + c_attrEval = (AclAttrEval * ) slapi_ch_calloc ( 1, sizeof ( AclAttrEval ) ); + deallocate_attrEval =1; + } else { + c_attrEval = &c_ContextEval->acle_attrEval[c_ContextEval->acle_numof_attrs++]; + c_attrEval->attrEval_r_status = 0; + c_attrEval->attrEval_s_status = 0; + c_attrEval->attrEval_r_aciIndex = 0; + c_attrEval->attrEval_s_aciIndex = 0; + } + /* clean it before use */ + c_attrEval->attrEval_name = slapi_ch_strdup ( attr ); + aclpb->aclpb_curr_attrEval = c_attrEval; + } + return deallocate_attrEval; +} +/* + * acl_skip_access_check + * + * See if we need to go thru the ACL check or not. We don't need to if I am root + * or internal operation or ... + * + * returns: + * ACL_TRUE - Yes; skip the ACL check + * ACL_FALSE - No; you have to go thru ACL check + * + */ +int +acl_skip_access_check ( Slapi_PBlock *pb, Slapi_Entry *e ) +{ + int rv, isRoot, accessCheckDisabled; + void *conn = NULL; + Slapi_Backend *be; + + slapi_pblock_get ( pb, SLAPI_REQUESTOR_ISROOT, &isRoot ); + if ( isRoot ) return ACL_TRUE; + + /* See if this is local request */ + slapi_pblock_get ( pb, SLAPI_CONNECTION, &conn); + + if ( NULL == conn ) return ACL_TRUE; + + /* + * Turn on access checking in the rootdse--this code used + * to skip the access check. + * + * check if the entry is the RootDSE entry + if ( e ) { + char * edn = slapi_entry_get_ndn ( e ); + if ( slapi_is_rootdse ( edn ) ) return ACL_TRUE; + } + */ + + /* GB : when referrals are directly set in the mappin tree + * we can reach this code without a backend in the pblock + * in such a case, allow access for now + * we may want to reconsider this is NULL DSE implementation happens + */ + rv = slapi_pblock_get ( pb, SLAPI_BACKEND, &be ); + if (be == NULL) + return ACL_TRUE; + + rv = slapi_pblock_get ( pb, SLAPI_PLUGIN_DB_NO_ACL, &accessCheckDisabled ); + if ( rv != -1 && accessCheckDisabled ) return ACL_TRUE; + + return ACL_FALSE; +} +short +acl_get_aclsignature () +{ + return acl_signature; +} +void +acl_set_aclsignature ( short value) +{ + acl_signature = value; +} +void +acl_regen_aclsignature () +{ + acl_signature = aclutil_gen_signature ( acl_signature ); +} + + + +static int +acl__handle_config_entry (Slapi_Entry *e, void *callback_data ) +{ + + int *value = (int *) callback_data; + + *value = slapi_entry_attr_get_int( e, "nsslapd-readonly"); + + return 0; +} + +static int +acl__config_get_readonly () +{ + + int readonly = 0; + + slapi_search_internal_callback( "cn=config", LDAP_SCOPE_BASE, "(objectclass=*)", + NULL, 0 /* attrsonly */, + &readonly/* callback_data */, + NULL /* controls */, + NULL /* result_callback */, + acl__handle_config_entry, + NULL /* referral_callback */); + + return readonly; +} +/* +* +* Assumptions: +* 1) Called for read/search right. +*/ +static int +acl__recompute_acl ( Acl_PBlock *aclpb, + AclAttrEval *a_eval, + int access, + int aciIndex + ) +{ + + + char *unused_str1, *unused_str2; + char *acl_tag, *testRight[2]; + int j, expr_num; + int result_status, rv, cache_result; + PRUint32 cookie; + aci_t *aci; + + + PR_ASSERT ( aciIndex >= 0 ); + PR_ASSERT ( a_eval != NULL ); + PR_ASSERT (aclpb != NULL ); + + + /* We might have evaluated this acl just now, check it there first */ + + for ( j =0; j < aclpb->aclpb_last_cache_result; j++) { + if (aciIndex == aclpb->aclpb_cache_result[j].aci_index) { + short result; + result_status =ACL_RES_INVALID; + + result = aclpb->aclpb_cache_result[j].result; + if ( result <= 0) break; + if (!ACL_CACHED_RESULT_VALID(result)) { + /* something is wrong. Need to evaluate */ + aclpb->aclpb_cache_result[j].result = -1; + break; + } + + + /* + ** We have a valid cached result. Let's see if we + ** have what we need. + */ + if ((result & ACLPB_CACHE_SEARCH_RES_ALLOW) || + (result & ACLPB_CACHE_READ_RES_ALLOW) ) + result_status = ACL_RES_ALLOW; + else if ((result & ACLPB_CACHE_SEARCH_RES_DENY) || + (result & ACLPB_CACHE_READ_RES_DENY) ) + result_status = ACL_RES_DENY; + + } + } /* end of for */ + + if ( result_status != ACL_RES_INVALID ) { + goto set_result_status; + } + + slapi_log_error ( SLAPI_LOG_ACL, plugin_name, + "Recomputing the ACL Index:%d for entry:%s\n", + aciIndex, slapi_entry_get_ndn ( aclpb->aclpb_curr_entry) ); + + /* First find this one ACL and then evaluate it. */ + + + aci = acllist_get_first_aci ( aclpb, &cookie ); + while ( aci && aci->aci_index != aciIndex ) { + aci = acllist_get_next_aci ( aclpb, aci, &cookie ); + } + + if (NULL == aci) + return -1; + + + ACL_SetDefaultResult (NULL, aclpb->aclpb_acleval, ACL_RES_INVALID); + rv = ACL_EvalSetACL(NULL, aclpb->aclpb_acleval, aci->aci_handle); + + testRight[0] = acl_access2str ( access ); + testRight[1] = '\0'; + aclpb->aclpb_curr_aci = aci; + result_status = ACL_EvalTestRights (NULL, aclpb->aclpb_acleval, testRight, + ds_map_generic, &unused_str1, + &unused_str2, + &acl_tag, &expr_num); + + cache_result = 0; + if ( result_status == ACL_RES_DENY && aci->aci_type & ACI_HAS_DENY_RULE ) { + if ( access & SLAPI_ACL_SEARCH) + cache_result = ACLPB_CACHE_SEARCH_RES_DENY; + else + cache_result = ACLPB_CACHE_READ_RES_DENY; + } else if ( result_status == ACL_RES_ALLOW && aci->aci_type & ACI_HAS_ALLOW_RULE ) { + if ( access & SLAPI_ACL_SEARCH) + cache_result = ACLPB_CACHE_SEARCH_RES_ALLOW; + else + cache_result = ACLPB_CACHE_READ_RES_ALLOW; + + } else { + result_status = -1; + } + + /* Now we need to put the cached result in the aclpb */ + + for (j=0; j <aclpb->aclpb_last_cache_result; ++j) { + if (aciIndex == aclpb->aclpb_cache_result[j].aci_index) { + break; + } + } + + if ( j < aclpb->aclpb_last_cache_result) { + /* already in cache */ + } else if ( j < ACLPB_MAX_CACHE_RESULTS-1) { + /* rbyrneXXX: make this same as other last_cache_result code! */ + j = ++aclpb->aclpb_last_cache_result; + aclpb->aclpb_cache_result[j].aci_index = aci->aci_index; + aclpb->aclpb_cache_result[j].aci_ruleType = aci->aci_ruleType; + + } else { /* No more space */ + goto set_result_status; + } + + /* Add the cached result status */ + aclpb->aclpb_cache_result[j].result |= cache_result; + + + +set_result_status: + if (result_status == ACL_RES_ALLOW) { + if (access & SLAPI_ACL_SEARCH) + /*wrong bit maskes were being used here-- + a_eval->attrEval_s_status = ACLPB_CACHE_SEARCH_RES_ALLOW;*/ + a_eval->attrEval_s_status = ACL_ATTREVAL_SUCCESS; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_SUCCESS; + + } else if ( result_status == ACL_RES_DENY) { + if (access & SLAPI_ACL_SEARCH) + a_eval->attrEval_s_status = ACL_ATTREVAL_FAIL; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_FAIL; + } else { + /* Here, set it to recompute--try again later */ + if (access & SLAPI_ACL_SEARCH) + a_eval->attrEval_s_status = ACL_ATTREVAL_RECOMPUTE; + else + a_eval->attrEval_r_status = ACL_ATTREVAL_RECOMPUTE; + result_status = -1; + } + + return result_status; +} + +static void +__acl_set_aclIndex_inResult ( Acl_PBlock *aclpb, int access, int index ) +{ + AclAttrEval *c_attrEval = aclpb->aclpb_curr_attrEval; + + if ( c_attrEval ) { + if ( access & SLAPI_ACL_SEARCH ) + c_attrEval->attrEval_s_aciIndex = index; + else if ( access & SLAPI_ACL_READ ) + c_attrEval->attrEval_r_aciIndex = index; + } +} + +/* + * If filter_sense is true then return (entry satisfies f). + * Otherwise, return !(entry satisfies f) +*/ + +static int +acl__test_filter ( Slapi_Entry *entry, struct slapi_filter *f, int filter_sense) { + int filter_matched; + + /* slapi_vattr_filter_test() returns 0 for match */ + + filter_matched = !slapi_vattr_filter_test(NULL, entry, + f, + 0 /*don't do acess chk*/); + + if (filter_sense) { + return(filter_matched ? ACL_TRUE : ACL_FALSE); + } else { + return(filter_matched ? ACL_FALSE: ACL_TRUE); + } +} + +/* + * Make an entry consisting of attr_type and attr_val and put + * a pointer to it in *entry. + * We will use this afterwards to test for against a filter. +*/ + +static int +acl__make_filter_test_entry ( Slapi_Entry **entry, char *attr_type, + struct berval *attr_val) { + + struct berval *vals_array[2]; + + vals_array[0] = attr_val; + vals_array[1] = NULL; + + *entry = slapi_entry_alloc(); + slapi_entry_init(*entry, NULL, NULL); + + return (slapi_entry_add_values( *entry, (const char *)attr_type, + (struct berval**)&vals_array[0] )); + +} + +/*********************************************************************************/ +/* E N D */ +/*********************************************************************************/ |