diff options
Diffstat (limited to 'ldap/servers/plugins/acl/aclgroup.c')
-rw-r--r-- | ldap/servers/plugins/acl/aclgroup.c | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/ldap/servers/plugins/acl/aclgroup.c b/ldap/servers/plugins/acl/aclgroup.c new file mode 100644 index 00000000..4cd7039f --- /dev/null +++ b/ldap/servers/plugins/acl/aclgroup.c @@ -0,0 +1,442 @@ +/** 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" + +/*************************************************************************** + * + * This module deals with the global user group cache. + * + * A LRU queue mechanism is used to maintain the groups the user currently in. + * At this moment the QUEUE is invalidated if there is a group change. A better + * way would have been to invalidate only the one which are effected. + * However to accomplish that will require quite a bit of work which may not be + * cost-efftive. + **************************************************************************/ +static aclGroupCache *aclUserGroups; +#define ACL_MAXCACHE_USERGROUPS 200 + +#define ACLG_LOCK_GROUPCACHE_READ() PR_RWLock_Rlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_LOCK_GROUPCACHE_WRITE() PR_RWLock_Wlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_ULOCK_GROUPCACHE_WRITE() PR_RWLock_Unlock ( aclUserGroups->aclg_rwlock ) +#define ACLG_ULOCK_GROUPCACHE_READ() PR_RWLock_Unlock ( aclUserGroups->aclg_rwlock ) + + +static void __aclg__delete_userGroup ( aclUserGroup *u_group ); + + +int +aclgroup_init () +{ + + aclUserGroups = ( aclGroupCache * ) slapi_ch_calloc (1, sizeof ( aclGroupCache ) ); + if ( NULL == (aclUserGroups->aclg_rwlock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,"Group LOCK"))) { + slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Unable to allocate RWLOCK for group cache\n"); + return 1; + } + return 0; +} + +/* + * aclg_init_userGroup + * + * Go thru the Global Group CACHE and see if we have group information for + * the user. The user's group cache is invalidated when a group is modified + * (in which case ALL usergroups are invalidated) or when the user's entry + * is modified in which case just his is invalidated. + * + * We need to scan the whole cache looking for a valid entry that matches + * this user. If we find invalid entries along the way. + * + * If we don't have anything it's fine. we will allocate a space when we + * need it i.e during the group evaluation. + * + * Inputs: + * struct acl_pblock - ACL private block + * char *dn - the client's dn + * int got_lock - 1: already obtained WRITE Lock + * - 0: Nope; get one + * Returns: + * None. + */ + +void +aclg_init_userGroup ( struct acl_pblock *aclpb, const char *n_dn , int got_lock ) +{ + aclUserGroup *u_group = NULL; + aclUserGroup *next_ugroup = NULL; + aclUserGroup *p_group, *n_group; + int found = 0; + + /* Check for Anonymous user */ + if ( n_dn && *n_dn == '\0') return; + + if ( !got_lock ) ACLG_LOCK_GROUPCACHE_WRITE (); + u_group = aclUserGroups->aclg_first; + aclpb->aclpb_groupinfo = NULL; + + while ( u_group != NULL ) { + next_ugroup = u_group->aclug_next; + if ( aclUserGroups->aclg_signature != u_group->aclug_signature) { + /* + * This means that this usergroup is no longer valid and + * this operation so delete this one if no one is using it. + */ + + if ( !u_group->aclug_refcnt ) { + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "In traversal group deallocation\n", 0,0,0 ); + __aclg__delete_userGroup (u_group); + } + } else { + + /* + * Here, u_group is valid--if it matches then take it. + */ + if ( slapi_utf8casecmp((ACLUCHP)u_group->aclug_ndn, + (ACLUCHP)n_dn ) == 0 ) { + u_group->aclug_refcnt++; + aclpb->aclpb_groupinfo = u_group; + found = 1; + break; + } + } + u_group = next_ugroup; + } + + /* Move the new one to the top of the queue */ + if ( found ) { + p_group = u_group->aclug_prev; + n_group = u_group->aclug_next; + + if ( p_group ) { + aclUserGroup *t_group = NULL; + + p_group->aclug_next = n_group; + if ( n_group ) n_group->aclug_prev = p_group; + + t_group = aclUserGroups->aclg_first; + if ( t_group ) t_group->aclug_prev = u_group; + + u_group->aclug_prev = NULL; + u_group->aclug_next = t_group; + aclUserGroups->aclg_first = u_group; + + if ( u_group == aclUserGroups->aclg_last ) + aclUserGroups->aclg_last = p_group; + } + slapi_log_error(SLAPI_LOG_ACL, plugin_name, "acl_init_userGroup: found in cache for dn:%s\n", n_dn,0,0); + } + if (!got_lock ) ACLG_ULOCK_GROUPCACHE_WRITE (); +} + + +/* + * + * aclg_reset_userGroup + * Reset the reference count to the user's group. + * + * Inputs: + * struct acl_pblock -- The acl private block. + * Returns: + * None. + * + * Note: A WRITE Lock on the GroupCache is obtained during the change: + */ +void +aclg_reset_userGroup ( struct acl_pblock *aclpb ) +{ + + aclUserGroup *u_group; + + ACLG_LOCK_GROUPCACHE_WRITE(); + + if ( (u_group = aclpb->aclpb_groupinfo) != NULL ) { + u_group->aclug_refcnt--; + + /* If I am the last one but I was using an invalid group cache + ** in the meantime, it is time now to get rid of it so that we will + ** not have duplicate cache. + */ + if ( !u_group->aclug_refcnt && + ( aclUserGroups->aclg_signature != u_group->aclug_signature )) { + __aclg__delete_userGroup ( u_group ); + } + } + ACLG_ULOCK_GROUPCACHE_WRITE(); + aclpb->aclpb_groupinfo = NULL; +} + +/* + * Find a user group in the global cache, returning a pointer to it, + * ensuring that the refcnt has been bumped to stop + * another thread freeing it underneath us. +*/ + +aclUserGroup* +aclg_find_userGroup(char *n_dn) +{ + aclUserGroup *u_group = NULL; + int i; + + /* Check for Anonymous user */ + if ( n_dn && *n_dn == '\0') return (NULL) ; + + ACLG_LOCK_GROUPCACHE_READ (); + u_group = aclUserGroups->aclg_first; + + for ( i=0; i < aclUserGroups->aclg_num_userGroups; i++ ) { + if ( aclUserGroups->aclg_signature == u_group->aclug_signature && + slapi_utf8casecmp((ACLUCHP)u_group->aclug_ndn, + (ACLUCHP)n_dn ) == 0 ) { + aclg_reader_incr_ugroup_refcnt(u_group); + break; + } + u_group = u_group->aclug_next; + } + + ACLG_ULOCK_GROUPCACHE_READ (); + return(u_group); +} + +/* + * Mark a usergroup for removal from the usergroup cache. + * It will be removed by the first operation traversing the cache + * that finds it. +*/ +void +aclg_markUgroupForRemoval ( aclUserGroup* u_group) { + + ACLG_LOCK_GROUPCACHE_WRITE (); + aclg_regen_ugroup_signature(u_group); + u_group->aclug_refcnt--; + ACLG_ULOCK_GROUPCACHE_WRITE (); +} + +/* + * + * aclg_get_usersGroup + * + * If we already have a the group info then we are done. If we + * don't, then allocate a new one and attach it. + * + * Inputs: + * struct acl_pblock -- The acl private block. + * char *n_dn - normalized client's DN + * + * Returns: + * aclUserGroup - The Group info block. + * + */ +aclUserGroup * +aclg_get_usersGroup ( struct acl_pblock *aclpb , char *n_dn) +{ + + aclUserGroup *u_group, *f_group; + + if ( aclpb && aclpb->aclpb_groupinfo ) + return aclpb->aclpb_groupinfo; + + ACLG_LOCK_GROUPCACHE_WRITE(); + + /* try it one more time. We might have one in the meantime */ + aclg_init_userGroup (aclpb, n_dn , 1 /* got the lock */); + if ( aclpb && aclpb->aclpb_groupinfo ) { + ACLG_ULOCK_GROUPCACHE_WRITE(); + return aclpb->aclpb_groupinfo; + } + + /* + * It is possible at this point that we already have a group cache for the user + * but is is invalid. We can't use it anayway. So, we march along and allocate a new one. + * That's fine as the invalid one will be deallocated when done. + */ + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "ALLOCATING GROUP FOR:%s\n", n_dn,0,0 ); + u_group = ( aclUserGroup * ) slapi_ch_calloc ( 1, sizeof ( aclUserGroup ) ); + + u_group->aclug_refcnt = 1; + if ( (u_group->aclug_refcnt_mutex = PR_NewLock()) == NULL ) { + slapi_ch_free((void **)&u_group); + ACLG_ULOCK_GROUPCACHE_WRITE(); + return(NULL); + } + + u_group->aclug_member_groups = (char **) + slapi_ch_calloc ( 1, + (ACLUG_INCR_GROUPS_LIST * sizeof (char *))); + u_group->aclug_member_group_size = ACLUG_INCR_GROUPS_LIST; + u_group->aclug_numof_member_group = 0; + + u_group->aclug_notmember_groups = (char **) + slapi_ch_calloc ( 1, + (ACLUG_INCR_GROUPS_LIST * sizeof (char *))); + u_group->aclug_notmember_group_size = ACLUG_INCR_GROUPS_LIST; + u_group->aclug_numof_notmember_group = 0; + + u_group->aclug_ndn = slapi_ch_strdup ( n_dn ) ; + + u_group->aclug_signature = aclUserGroups->aclg_signature; + + /* Do we have alreday the max number. If we have then delete the last one */ + if ( aclUserGroups->aclg_num_userGroups >= ACL_MAXCACHE_USERGROUPS - 5 ) { + aclUserGroup *d_group; + + /* We need to traverse thru backwards and delete the one with a refcnt = 0 */ + d_group = aclUserGroups->aclg_last; + while ( d_group ) { + if ( !d_group->aclug_refcnt ) { + __aclg__delete_userGroup ( d_group ); + break; + } else { + d_group = d_group->aclug_prev; + } + } + + /* If we didn't find any, which should be never, + ** we have 5 more tries to do it. + */ + } + f_group = aclUserGroups->aclg_first; + u_group->aclug_next = f_group; + if ( f_group ) f_group->aclug_prev = u_group; + + aclUserGroups->aclg_first = u_group; + if ( aclUserGroups->aclg_last == NULL ) + aclUserGroups->aclg_last = u_group; + + aclUserGroups->aclg_num_userGroups++; + + /* Put it in the queue */ + ACLG_ULOCK_GROUPCACHE_WRITE(); + + /* Now hang on to it */ + aclpb->aclpb_groupinfo = u_group; + return u_group; +} + +/* + * + * __aclg__delete_userGroup + * + * Delete the User's Group cache. + * + * Inputs: + * aclUserGroup - remove this one + * Returns: + * None. + * + * Note: A WRITE Lock on the GroupCache is obtained by the caller + */ +static void +__aclg__delete_userGroup ( aclUserGroup *u_group ) +{ + + aclUserGroup *next_group, *prev_group; + int i; + + if ( !u_group ) return; + + prev_group = u_group->aclug_prev; + next_group = u_group->aclug_next; + + /* + * At this point we must have a 0 refcnt or else we are in a bad shape. + * If we don't have one then at least remove the user's dn so that it will + * be in a condemned state and later deleted. + */ + + slapi_log_error( SLAPI_LOG_ACL, plugin_name, "DEALLOCATING GROUP FOR:%s\n", u_group->aclug_ndn,0,0 ); + + slapi_ch_free ( (void **) &u_group->aclug_ndn ); + + PR_DestroyLock(u_group->aclug_refcnt_mutex); + + /* Remove the member GROUPS */ + for (i=0; i < u_group->aclug_numof_member_group; i++ ) + slapi_ch_free ( (void **) &u_group->aclug_member_groups[i] ); + slapi_ch_free ( (void **) &u_group->aclug_member_groups ); + + /* Remove the NOT member GROUPS */ + for (i=0; i < u_group->aclug_numof_notmember_group; i++ ) + slapi_ch_free ( (void **) &u_group->aclug_notmember_groups[i] ); + slapi_ch_free ( (void **) &u_group->aclug_notmember_groups ); + + slapi_ch_free ( (void **) &u_group ); + + if ( prev_group == NULL && next_group == NULL ) { + aclUserGroups->aclg_first = NULL; + aclUserGroups->aclg_last = NULL; + } else if ( prev_group == NULL ) { + next_group->aclug_prev = NULL; + aclUserGroups->aclg_first = next_group; + } else { + prev_group->aclug_next = next_group; + if ( next_group ) + next_group->aclug_prev = prev_group; + else + aclUserGroups->aclg_last = prev_group; + } + aclUserGroups->aclg_num_userGroups--; +} + +void +aclg_regen_group_signature( ) +{ + aclUserGroups->aclg_signature = aclutil_gen_signature ( aclUserGroups->aclg_signature ); +} + +void +aclg_regen_ugroup_signature( aclUserGroup *ugroup) +{ + ugroup->aclug_signature = + aclutil_gen_signature ( ugroup->aclug_signature ); +} + +void +aclg_lock_groupCache ( int type /* 1 for reader and 2 for writer */) +{ + + if (type == 1 ) + ACLG_LOCK_GROUPCACHE_READ(); + else + ACLG_LOCK_GROUPCACHE_WRITE(); +} + +void +aclg_unlock_groupCache ( int type /* 1 for reader and 2 for writer */) +{ + + if (type == 1 ) + ACLG_ULOCK_GROUPCACHE_READ(); + else + ACLG_ULOCK_GROUPCACHE_WRITE(); +} + + +/* + * If you have the write lock on the group cache, you can + * increment the refcnt without taking the mutex. + * If you just have the reader lock on the refcnt then you need to + * take the mutex on the refcnt to increment it--which is what this routine is + * for. + * +*/ + +void +aclg_reader_incr_ugroup_refcnt(aclUserGroup* u_group) { + + PR_Lock(u_group->aclug_refcnt_mutex); + u_group->aclug_refcnt++; + PR_Unlock(u_group->aclug_refcnt_mutex); +} + +/* You need the usergroups read lock to call this routine*/ +int +aclg_numof_usergroups(void) { + + return(aclUserGroups->aclg_num_userGroups); +} + |