summaryrefslogtreecommitdiffstats
path: root/ldap/servers/plugins/acl/aclanom.c
diff options
context:
space:
mode:
Diffstat (limited to 'ldap/servers/plugins/acl/aclanom.c')
-rw-r--r--ldap/servers/plugins/acl/aclanom.c536
1 files changed, 536 insertions, 0 deletions
diff --git a/ldap/servers/plugins/acl/aclanom.c b/ldap/servers/plugins/acl/aclanom.c
new file mode 100644
index 00000000..71b0c68a
--- /dev/null
+++ b/ldap/servers/plugins/acl/aclanom.c
@@ -0,0 +1,536 @@
+/** 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"
+
+/************************************************************************
+Anonymous profile
+**************************************************************************/
+
+struct anom_targetacl {
+ int anom_type; /* defines for anom types same as aci_type */
+ int anom_access;
+ Slapi_DN *anom_target; /* target of the ACL */
+ Slapi_Filter *anom_filter; /* targetfilter part */
+ char **anom_targetAttrs; /* list of attrs */
+};
+
+
+struct anom_profile {
+ short anom_signature;
+ short anom_numacls;
+ struct anom_targetacl anom_targetinfo[ACL_ANOM_MAX_ACL];
+};
+
+static struct anom_profile *acl_anom_profile = NULL;
+
+static PRRWLock *anom_rwlock = NULL;
+#define ANOM_LOCK_READ() PR_RWLock_Rlock (anom_rwlock )
+#define ANOM_UNLOCK_READ() PR_RWLock_Unlock (anom_rwlock )
+#define ANOM_LOCK_WRITE() PR_RWLock_Wlock (anom_rwlock )
+#define ANOM_UNLOCK_WRITE() PR_RWLock_Unlock (anom_rwlock )
+
+
+static void __aclanom__del_profile ();
+
+/*
+ * aclanom_init ();
+ * Generate a profile for the anonymous user. We can use this profile
+ * later to determine what resources the client is allowed to.
+ *
+ * Dependency:
+ * Before calling this, it is assumed that all the ACLs have been read
+ * and parsed.
+ *
+ * We will go thru all the ACL and pick the ANYONE ACL and generate the anom
+ * profile.
+ *
+ */
+int
+aclanom_init ()
+{
+
+ acl_anom_profile = (struct anom_profile * )
+ slapi_ch_calloc (1, sizeof ( struct anom_profile ) );
+
+ if (( anom_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"ANOM LOCK") ) == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, plugin_name,
+ "Failed in getting the ANOM rwlock\n" );
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Depending on the context, this routine may need to take the
+ * acicache read lock.
+*/
+void
+aclanom_gen_anomProfile (acl_lock_flag_t lock_flag)
+{
+ aci_t *aci = NULL;
+ int i;
+ Targetattr **srcattrArray;
+ Targetattr *attr;
+ struct anom_profile *a_profile;
+ PRUint32 cookie;
+
+ PR_ASSERT( lock_flag == DO_TAKE_ACLCACHE_READLOCK ||
+ lock_flag == DONT_TAKE_ACLCACHE_READLOCK);
+
+ /*
+ * This routine requires two locks:
+ * the one for the global cache in acllist_acicache_READ_LOCK() and
+ * the one for the anom profile.
+ * They _must_ be taken in the order presented here or there
+ * is a deadlock scenario with acllist_remove_aci_needsLock() which
+ * takes them is this order.
+ */
+
+ if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) {
+ acllist_acicache_READ_LOCK();
+ }
+ ANOM_LOCK_WRITE ();
+ a_profile = acl_anom_profile;
+
+ if ( (!acl_get_aclsignature()) || ( !a_profile) ||
+ (a_profile->anom_signature == acl_get_aclsignature()) ) {
+ ANOM_UNLOCK_WRITE ();
+ if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) {
+ acllist_acicache_READ_UNLOCK();
+ }
+ return;
+ }
+
+ /* D0 we have one already. If we do, then clean it up */
+ __aclanom__del_profile();
+
+ /* We have a new signature now */
+ a_profile->anom_signature = acl_get_aclsignature();
+
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name, "GENERATING ANOM USER PROFILE\n", 0,0,0);
+ /*
+ ** Go thru the ACL list and find all the ACLs which apply to the
+ ** anonymous user i.e anyone. we can generate a profile for that.
+ ** We will llok at the simple case i.e it matches
+ ** cases not handled:
+ ** 1) When there is a mix if rule types ( allows & denies )
+ **
+ */
+
+ aci = acllist_get_first_aci ( NULL, &cookie );
+ while ( aci ) {
+ int a_numacl;
+ struct slapi_filter *f;
+ char **destattrArray;
+
+
+ /*
+ * We must not have a rule like: deny ( all ) userdn != "xyz"
+ * or groupdn !=
+ */
+ if ( (aci->aci_type & ACI_HAS_DENY_RULE) &&
+ ( (aci->aci_type & ACI_CONTAIN_NOT_USERDN ) ||
+ (aci->aci_type & ACI_CONTAIN_NOT_GROUPDN) ||
+ (aci->aci_type & ACI_CONTAIN_NOT_ROLEDN)) ){
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "CANCELLING ANOM USER PROFILE BECAUSE OF DENY RULE\n", 0,0,0);
+ goto cleanup;
+ }
+
+ /* Must be a anyone rule */
+ if ( aci->aci_elevel != ACI_ELEVEL_USERDN_ANYONE ) {
+ aci = acllist_get_next_aci ( NULL, aci, &cookie);
+ continue;
+ }
+ if (! (aci->aci_access & ( SLAPI_ACL_READ | SLAPI_ACL_SEARCH)) ) {
+ aci = acllist_get_next_aci ( NULL, aci, &cookie);
+ continue;
+ }
+ /* If the rule has anything other than userdn = "ldap:///anyone"
+ ** let's not consider complex rules - let's make this lean.
+ */
+ if ( aci->aci_ruleType & ~ACI_USERDN_RULE ){
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "CANCELLING ANOM USER PROFILE BECAUSE OF COMPLEX RULE\n", 0,0,0);
+ goto cleanup;
+ }
+
+ /* Must not be a or have a
+ ** 1 ) DENY RULE 2) targetfilter
+ ** 3) no target pattern ( skip monitor acl )
+ */
+ if ( aci->aci_type & ( ACI_HAS_DENY_RULE | ACI_TARGET_PATTERN |
+ ACI_TARGET_NOT | ACI_TARGET_FILTER_NOT )) {
+ const char *dn = slapi_sdn_get_dn ( aci->aci_sdn );
+
+ /* see if this is a monitor acl */
+ if (( strcasecmp ( dn, "cn=monitor") == 0 ) ||
+ ( strcasecmp ( dn, "cn=monitor,cn=ldbm") == 0 )) {
+ aci = acllist_get_next_aci ( NULL, aci, &cookie);
+ continue;
+ } else {
+ /* clean up before leaving */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "CANCELLING ANOM USER PROFILE 1\n", 0,0,0);
+ goto cleanup;
+ }
+
+ }
+
+ /* Now we have an ALLOW ACL which applies to anyone */
+ a_numacl = a_profile->anom_numacls++;
+
+ if ( a_profile->anom_numacls == ACL_ANOM_MAX_ACL ) {
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name, "CANCELLING ANOM USER PROFILE 2\n", 0,0,0);
+ goto cleanup;
+ }
+
+ if ( (f = aci->target) != NULL ) {
+ char *avaType;
+ struct berval *avaValue;
+ slapi_filter_get_ava ( f, &avaType, &avaValue );
+
+ a_profile->anom_targetinfo[a_numacl].anom_target =
+ slapi_sdn_new_dn_byval ( avaValue->bv_val );
+ } else {
+ a_profile->anom_targetinfo[a_numacl].anom_target =
+ slapi_sdn_dup ( aci->aci_sdn );
+ }
+
+ a_profile->anom_targetinfo[a_numacl].anom_filter = NULL;
+ if ( aci->targetFilterStr )
+ a_profile->anom_targetinfo[a_numacl].anom_filter = slapi_str2filter ( aci->targetFilterStr );
+
+ i = 0;
+ srcattrArray = aci->targetAttr;
+ while ( srcattrArray[i])
+ i++;
+
+ a_profile->anom_targetinfo[a_numacl].anom_targetAttrs =
+ (char **) slapi_ch_calloc ( 1, (i+1) * sizeof(char *));
+
+ srcattrArray = aci->targetAttr;
+ destattrArray = a_profile->anom_targetinfo[a_numacl].anom_targetAttrs;
+
+ i = 0;
+ while ( srcattrArray[i] ) {
+ attr = srcattrArray[i];
+ if ( attr->attr_type & ACL_ATTR_FILTER ) {
+ /* Do'nt want to support these kind now */
+ destattrArray[i] = NULL;
+ /* clean up before leaving */
+ __aclanom__del_profile ();
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "CANCELLING ANOM USER PROFILE 3\n", 0,0,0);
+ goto cleanup;
+ }
+
+ destattrArray[i] = slapi_ch_strdup ( attr->u.attr_str );
+ i++;
+ }
+
+ destattrArray[i] = NULL;
+
+ aclutil_print_aci ( aci, "anom" );
+ /* Here we are storing att the info from the acls. However
+ ** we are only interested in a few things like ACI_TARGETATTR_NOT.
+ */
+ a_profile->anom_targetinfo[a_numacl].anom_type = aci->aci_type;
+ a_profile->anom_targetinfo[a_numacl].anom_access = aci->aci_access;
+
+ aci = acllist_get_next_aci ( NULL, aci, &cookie);
+ }
+
+ ANOM_UNLOCK_WRITE ();
+ if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) {
+ acllist_acicache_READ_UNLOCK();
+ }
+ return;
+
+cleanup:
+ __aclanom__del_profile ();
+ ANOM_UNLOCK_WRITE ();
+ if ( lock_flag == DO_TAKE_ACLCACHE_READLOCK ) {
+ acllist_acicache_READ_UNLOCK();
+ }
+}
+
+
+void
+aclanom_invalidateProfile ()
+{
+ ANOM_LOCK_WRITE();
+ if ( acl_anom_profile && acl_anom_profile->anom_numacls )
+ acl_anom_profile->anom_signature = 0;
+ ANOM_UNLOCK_WRITE();
+
+
+}
+
+/*
+ * __aclanom_del_profile
+ *
+ * Cleanup the anonymous user's profile we have.
+ *
+ * ASSUMPTION: A WRITE LOCK HAS BEEN OBTAINED
+ *
+ */
+static void
+__aclanom__del_profile (void)
+{
+ int i;
+ struct anom_profile *a_profile;
+
+
+ if ( (a_profile = acl_anom_profile) == NULL ) {
+ return;
+ }
+
+ for ( i=0; i < a_profile->anom_numacls; i++ ) {
+ int j = 0;
+ char **destArray = a_profile->anom_targetinfo[i].anom_targetAttrs;
+
+ /* Deallocate target */
+ slapi_sdn_free ( &a_profile->anom_targetinfo[i].anom_target );
+
+ /* Deallocate filter */
+ if ( a_profile->anom_targetinfo[i].anom_filter )
+ slapi_filter_free ( a_profile->anom_targetinfo[i].anom_filter, 1 );
+
+ /* Deallocate attrs */
+ if ( destArray ) {
+ while ( destArray[j] ) {
+ slapi_ch_free ( (void **) &destArray[j] );
+ j++;
+ }
+ slapi_ch_free ( (void **) &destArray );
+ }
+ a_profile->anom_targetinfo[i].anom_targetAttrs = NULL;
+ a_profile->anom_targetinfo[i].anom_type = 0;
+ a_profile->anom_targetinfo[i].anom_access = 0;
+ }
+ a_profile->anom_numacls = 0;
+
+ /* Don't clean the signatue */
+}
+
+/*
+ * This routine sets up a "context" for evaluation of access control
+ * on a given entry for an anonymous user.
+ * It just factors out the scope and targetfilter info into a list
+ * of indices of the global anom profile list, that apply to this
+ * entry, and stores them in the aclpb.
+ * It's use relies on the way that access control is checked in the mailine search
+ * code in the core server, namely: check filter, check entry, then check each
+ * attribute. So, we call this in acl_access_allowed() before calling
+ * aclanom_match_profile()--therafter, aclanom_match_profile() uses the
+ * context to evaluate access to the entry and attributes.
+ *
+ * If there are no anom profiles, or the anom profiles get cancelled
+ * due to complex anon acis, then that's OK, aclanom_match_profile()
+ * returns -1 and the mainline acl code kicks in.
+ *
+ * The lifetime of this context info is the time it takes to check
+ * access control for all parts of this entry (filter, entry, attributes).
+ * So, if for an example an entry changes and a given anom profile entry
+ * no longer applies, we will not notice until the next round of access
+ * control checking on the entry--this is acceptable.
+ *
+ * The gain on doing this factoring in the following type of search
+ * was approx 6%:
+ * anon bind, 20 threads, exact match, ~20 attributes returned,
+ * (searchrate & DirectoryMark).
+ *
+*/
+void
+aclanom_get_suffix_info(Slapi_Entry *e,
+ struct acl_pblock *aclpb ) {
+ int i;
+ char *ndn = NULL;
+ Slapi_DN *e_sdn;
+ const char *aci_ndn;
+ int populate = 0;
+ struct scoped_entry_anominfo *s_e_anominfo =
+ &aclpb->aclpb_scoped_entry_anominfo;
+
+ ANOM_LOCK_READ ();
+
+ s_e_anominfo->anom_e_nummatched=0;
+
+ ndn = slapi_entry_get_ndn ( e ) ;
+ e_sdn= slapi_entry_get_sdn ( e ) ;
+ for (i=acl_anom_profile->anom_numacls-1; i >= 0; i-- ) {
+ aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target);
+ if (!slapi_sdn_issuffix(e_sdn,acl_anom_profile->anom_targetinfo[i].anom_target)
+ || (!slapi_is_rootdse(ndn) && slapi_is_rootdse(aci_ndn)))
+ continue;
+ if ( acl_anom_profile->anom_targetinfo[i].anom_filter ) {
+ if ( slapi_vattr_filter_test( aclpb->aclpb_pblock, e,
+ acl_anom_profile->anom_targetinfo[i].anom_filter,
+ 0 /*don't do acess chk*/) != 0)
+ continue;
+ }
+ s_e_anominfo->anom_e_targetInfo[s_e_anominfo->anom_e_nummatched]=i;
+ s_e_anominfo->anom_e_nummatched++;
+ }
+ ANOM_UNLOCK_READ ();
+}
+
+
+/*
+ * aclanom_match_profile
+ * Look at the anonymous profile and see if we can use it or not.
+ *
+ *
+ * Inputs:
+ * Slapi_Pblock - The Pblock
+ * Slapi_Entry *e - The entry for which we are asking permission.
+ * char *attr - Attribute name
+ * int access - access type
+ *
+ * Return:
+ * LDAP_SUCCESS ( 0 ) - acess is allowed.
+ * LDAP_INSUFFICIENT_ACCESS (50 ) - access denied.
+ * -1 - didn't match the targets
+ *
+ * Assumptions:
+ * The caller of this module has to make sure that the client is
+ * an anonymous client.
+ */
+int
+aclanom_match_profile (Slapi_PBlock *pb, struct acl_pblock *aclpb, Slapi_Entry *e,
+ char *attr, int access )
+{
+
+ struct anom_profile *a_profile;
+ int result, i, k;
+ char **destArray;
+ int tmatched = 0;
+ char ebuf[ BUFSIZ ];
+ int loglevel;
+ struct scoped_entry_anominfo *s_e_anominfo =
+ &aclpb->aclpb_scoped_entry_anominfo;
+
+ loglevel = slapi_is_loglevel_set(SLAPI_LOG_ACL) ? SLAPI_LOG_ACL : SLAPI_LOG_ACLSUMMARY;
+
+ /* WE are only interested for READ/SEARCH */
+ if ( !(access & ( SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) )
+ return -1;
+
+ /* If we are here means, the client is doing a anonymous read/search */
+ if ((a_profile = acl_anom_profile) == NULL ) {
+ return -1;
+ }
+
+ ANOM_LOCK_READ ();
+ /* Check the signature first */
+ if ( a_profile->anom_signature != acl_get_aclsignature () ) {
+ /* Need to regenrate the signature.
+ * Need a WRITE lock to generate the anom profile -
+ * which is obtained in acl__gen_anom_user_profile (). Since
+ * I don't have upgrade lock -- I have to do this way.
+ */
+ ANOM_UNLOCK_READ ();
+ aclanom_gen_anomProfile (DO_TAKE_ACLCACHE_READLOCK);
+ aclanom_get_suffix_info(e, aclpb );
+ ANOM_LOCK_READ ();
+ }
+
+ /* doing this early saves use a malloc/free/normalize cost */
+ if ( !a_profile->anom_numacls ) {
+ ANOM_UNLOCK_READ ();
+ return -1;
+ }
+
+ result = LDAP_INSUFFICIENT_ACCESS;
+
+ for ( k=0; k<s_e_anominfo->anom_e_nummatched; k++ ) {
+ short matched = 0;
+ short j = 0;
+
+ i = s_e_anominfo->anom_e_targetInfo[k];
+
+ /* Check for right */
+ if ( !(a_profile->anom_targetinfo[i].anom_access & access) )
+ continue;
+
+ /*
+ * XXX rbyrne Don't really understand the role of this
+ * but not causing any obvious bugs...get back to it.
+ */
+ tmatched++;
+
+ if ( attr == NULL ) {
+ result = LDAP_SUCCESS;
+ break;
+ }
+
+ destArray = a_profile->anom_targetinfo[i].anom_targetAttrs;
+ while ( destArray[j] ) {
+ if ( strcasecmp ( destArray[j], "*") == 0 ||
+ slapi_attr_type_cmp ( attr, destArray[j], 1 ) == 0 ) {
+ matched = 1;
+ break;
+ }
+ j++;
+ }
+
+ if ( a_profile->anom_targetinfo[i].anom_type & ACI_TARGET_ATTR_NOT )
+ result = matched ? LDAP_INSUFFICIENT_ACCESS : LDAP_SUCCESS;
+ else
+ result = matched ? LDAP_SUCCESS : LDAP_INSUFFICIENT_ACCESS;
+
+ if ( result == LDAP_SUCCESS )
+ break;
+ } /* for */
+
+ if ( slapi_is_loglevel_set(loglevel) ) {
+ char *ndn = NULL;
+ Slapi_Operation *op = NULL;
+
+ ndn = slapi_entry_get_ndn ( e ) ;
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+
+ if ( result == LDAP_SUCCESS) {
+ const char *aci_ndn;
+ aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target);
+
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%d op=%d: Allow access on entry(%s).attr(%s) to anonymous: acidn=\"%s\"\n",
+ op->o_connid, op->o_opid,
+ escape_string_with_punctuation(ndn, ebuf),
+ attr ? attr:"NULL",
+ escape_string_with_punctuation(aci_ndn, ebuf));
+ } else {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%d op=%d: Deny access on entry(%s).attr(%s) to anonymous\n",
+ op->o_connid, op->o_opid,
+ escape_string_with_punctuation(ndn, ebuf), attr ? attr:"NULL" );
+ }
+ }
+
+ ANOM_UNLOCK_READ ();
+ if ( tmatched == 0)
+ return -1;
+ else
+ return result;
+
+}
+int
+aclanom_is_client_anonymous ( Slapi_PBlock *pb )
+{
+ char *clientDn;
+
+
+ slapi_pblock_get ( pb, SLAPI_REQUESTOR_DN, &clientDn );
+ if (acl_anom_profile->anom_numacls &&
+ acl_anom_profile->anom_signature &&
+ (( NULL == clientDn) || (clientDn && *clientDn == '\0')) )
+ return 1;
+
+ return 0;
+}
+