/** 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); }