/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details. 
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif


#include <prlog.h>
#include <base/crit.h>
#include <base/ereport.h>
#include <plhash.h>
#include <libaccess/acl.h>
#include "aclpriv.h"
#include <libaccess/aclproto.h>
#include <libaccess/aclglobal.h>
#include <libaccess/usrcache.h>
#include <libaccess/las.h>
#include "aclutil.h"
#define NO_ACL_HASH_FUNCS
#include "permhash.h"
#include "aclcache.h"


static CRITICAL acl_hash_crit = NULL;	/* Controls Global Hash */

enum {
    ACL_URI_HASH,
    ACL_URI_GET_HASH
};

/*	ACL_ListHashKeyHash
 *	Given an ACL List address, computes a randomized hash value of the
 *	ACL structure pointer addresses by simply adding them up.  Returns
 *	the resultant hash value.
 */
static PLHashNumber
ACL_ListHashKeyHash(const void *Iacllist)
{
    PLHashNumber hash=0;
    ACLWrapper_t *wrap;
    ACLListHandle_t *acllist=(ACLListHandle_t *)Iacllist;

    for (wrap=acllist->acl_list_head; wrap; wrap=wrap->wrap_next) {
	hash += (PLHashNumber)(PRSize)wrap->acl;
    }

    return (hash);
}

/*	ACL_ListHashKeyCompare
 *	Given two acl lists, compares the addresses of the acl pointers within
 *	them to see if theyre identical.  Returns 1 if equal, 0 otherwise.
 */
static int
ACL_ListHashKeyCompare(const void *Iacllist1, const void *Iacllist2)
{
    ACLWrapper_t *wrap1, *wrap2;
    ACLListHandle_t *acllist1=(ACLListHandle_t *)Iacllist1;
    ACLListHandle_t *acllist2=(ACLListHandle_t *)Iacllist2;

    if (acllist1->acl_count != acllist2->acl_count) 
        return 0;

    wrap1 = acllist1->acl_list_head;
    wrap2 = acllist2->acl_list_head;

    while ((wrap1 != NULL) && (wrap2 != NULL)) {
	if (wrap1->acl != wrap2->acl) 
            return 0;
	wrap1 = wrap1->wrap_next;
	wrap2 = wrap2->wrap_next;
    }

    if ((wrap1 != NULL) || (wrap2 != NULL)) 
        return 0;
    else 
        return 1;
}

/*  ACL_ListHashValueCompare
 *  Returns 1 if equal, 0 otherwise
 */
static int
ACL_ListHashValueCompare(const void *acllist1, const void *acllist2)
{

    return (acllist1 == acllist2);
}

void 
ACL_ListHashInit()
{
    ACLListHash = PR_NewHashTable(200, 
				  ACL_ListHashKeyHash,
				  ACL_ListHashKeyCompare,
				  ACL_ListHashValueCompare,
				  &ACLPermAllocOps, 
				  NULL);
    if (ACLListHash == NULL) {
	ereport(LOG_SECURITY, const_cast<char *>("Unable to allocate ACL List Hash\n"));
	return;
    }

    return;
}

static void 
ACL_ListHashDestroy()
{
    if (ACLListHash) {
        PR_HashTableDestroy(ACLListHash);
        ACLListHash = NULL;
    }

    return;
}

/*  ACL_ListHashUpdate
 *  Typically called with the &rq->acllist.  Checks if the newly generated
 *  acllist matches one that's already been created.  If so, toss the new
 *  list and set the pointer to the old list in its place.
 */
void 
ACL_ListHashUpdate(ACLListHandle_t **acllistp)
{
    NSErr_t *errp = 0;
    ACLListHandle_t *tmp_acllist;

    PR_ASSERT(ACL_AssertAcllist(*acllistp));

    tmp_acllist = (ACLListHandle_t *)PR_HashTableLookup(ACLListHash, *acllistp);
    if (tmp_acllist  &&  tmp_acllist != *acllistp) {
	PR_ASSERT(*acllistp  &&  *acllistp != ACL_LIST_NO_ACLS);
	ACL_ListDestroy(errp, *acllistp);
	*acllistp = tmp_acllist;
	PR_ASSERT(ACL_CritHeld());
        tmp_acllist->ref_count++;	/* we're gonna use it */
    } else { 			/* Wasn't in the list */
	PR_HashTableAdd(ACLListHash, *acllistp, *acllistp);
    }

    PR_ASSERT(ACL_AssertAcllist(*acllistp));
    return;
}

/*  ACL_ListCacheEnter
 *  In some cases, the URI cache is useless.  E.g. when virtual servers are used.
 *  When that happens, the List Cache is still useful, because the cached ACL
 *  List has the Eval cache in it, plus any LAS caches.
 */
NSAPI_PUBLIC void
ACL_ListHashEnter(ACLListHandle_t **acllistp)
{
    ACL_CritEnter();

    /*  Look for a matching ACL List and use it if we find one.  */
    if (*acllistp)  {
	PR_ASSERT(*acllistp != ACL_LIST_NO_ACLS);
        PR_ASSERT(ACL_AssertAcllist(*acllistp));
	ACL_ListHashUpdate(acllistp);
    } else {
	*acllistp = ACL_LIST_NO_ACLS;
    }

    ACL_CritExit();
    PR_ASSERT(ACL_AssertAcllist(*acllistp));
    return;
}

/*  ACL_ListHashCheck
 *  When Virtual Servers are active, and the ACL URI cache is inactive, someone
 *  with an old ACL List pointer can check to see if it's still valid.  This will
 *  also increment the reference count on it.
 */
NSAPI_PUBLIC int
ACL_ListHashCheck(ACLListHandle_t **acllistp)
{
    ACLListHandle_t *tmp_acllist;

    if (*acllistp == ACL_LIST_NO_ACLS) return 1;

    ACL_CritEnter();

    tmp_acllist = (ACLListHandle_t *)PR_HashTableLookup(ACLListHash, *acllistp);
    if (tmp_acllist) {
	PR_ASSERT(*acllistp  &&  *acllistp != ACL_LIST_NO_ACLS);
	*acllistp = tmp_acllist;
	PR_ASSERT(ACL_CritHeld());
        tmp_acllist->ref_count++;	/* we're gonna use it */
        ACL_CritExit();
	PR_ASSERT(ACL_AssertAcllist(*acllistp));
        return 1;		/* Normal path */
    } else { 			/* Wasn't in the list */
        ACL_CritExit();
        return 0;
    }

}
   

void
ACL_UriHashDestroy(void)
{
    if (acl_uri_hash) {
        PR_HashTableDestroy(acl_uri_hash);
        acl_uri_hash = NULL;
    }
    if (acl_uri_get_hash) {
        PR_HashTableDestroy(acl_uri_get_hash);
        acl_uri_get_hash = NULL;
    }
    pool_destroy((void **)acl_uri_hash_pool);
    acl_uri_hash_pool = NULL;

}

NSAPI_PUBLIC void
ACL_Destroy(void)
{
    ACL_ListHashDestroy();
    ACL_UriHashDestroy();
    ACL_LasHashDestroy();
}

NSAPI_PUBLIC void
ACL_DestroyPools(void)
{
	pool_destroy(ACL_DATABASE_POOL);
	ACLGlobal->databasepool = NULL;
	pool_destroy(ACL_METHOD_POOL);
	ACLGlobal->methodpool = NULL;
	PERM_FREE(ACLGlobal);
	ACLGlobal = NULL;
	PERM_FREE(oldACLGlobal);
	oldACLGlobal = NULL;
	ACL_Attr2IndexListDestroy();
	if(acl_hash_crit)
		crit_terminate(acl_hash_crit);
	acl_hash_crit = NULL;
	pool_terminate();
}

/* 	Only used in ASSERT statements to verify that we have the lock 	 */
int
ACL_CritHeld(void)
{
    return (crit_owner_is_me(acl_hash_crit));
}

NSAPI_PUBLIC void
ACL_CritEnter(void)
{
    crit_enter(acl_hash_crit);
}

NSAPI_PUBLIC void 
ACL_CritExit(void)
{
    crit_exit(acl_hash_crit);
}

void
ACL_CritInit(void)
{
    acl_hash_crit = crit_init();
}

void
ACL_UriHashInit(void)
{
    acl_uri_hash = PR_NewHashTable(200,
                                     PR_HashString,
                                     PR_CompareStrings,
                                     PR_CompareValues,
                                     &ACLPermAllocOps,
                                     NULL);
    acl_uri_get_hash = PR_NewHashTable(200,
                                     PR_HashString,
                                     PR_CompareStrings,
                                     PR_CompareValues,
                                     &ACLPermAllocOps,
                                     NULL);
    acl_uri_hash_pool = pool_create();
}

/*  ACL_CacheCheck
 *  INPUT
 *	uri	A URI string pointer
 *	acllistp A pointer to an acllist placeholder.  E.g. &rq->acllist
 *  OUTPUT
 *	return	1 if cached. 0 if not.   The reference count on the ACL List
 *	is INCREMENTED, and will be decremented when ACL_EvalDestroy or 
 *	ACL_ListDecrement is
 *	called. 
 */
int
ACL_INTCacheCheck(int which, char *uri, ACLListHandle_t **acllistp)
{
    PLHashTable *hash;
    PR_ASSERT(uri && acl_uri_hash && acl_uri_get_hash);

    /*  ACL cache:  If the ACL List is already in the cache, there's no need
     *  to go through the pathcheck directives.
     *  NULL	means that the URI hasn't been accessed before.
     *  ACL_LIST_NO_ACLS	
     *		means that the URI has no ACLs.
     *  Anything else is a pointer to the acllist.
     */
    ACL_CritEnter();

    /* Get the pointer to the hash table after acquiring the lock */
    if (which == ACL_URI_HASH)
	hash = acl_uri_hash;
    else
	hash = acl_uri_get_hash;

    *acllistp = (ACLListHandle_t *)PR_HashTableLookup(hash, uri);
    if (*acllistp != NULL) {
	if (*acllistp != ACL_LIST_NO_ACLS) {
            PR_ASSERT((*acllistp)->ref_count >= 0);
	    PR_ASSERT(ACL_CritHeld());
	    (*acllistp)->ref_count++;
	}
        ACL_CritExit();
	PR_ASSERT(ACL_AssertAcllist(*acllistp));
        return 1;		/* Normal path */
    }

    ACL_CritExit();
    return 0;
}

int
ACL_CacheCheckGet(char *uri, ACLListHandle_t **acllistp)
{
    return (ACL_INTCacheCheck(ACL_URI_GET_HASH, uri, acllistp));
}

int
ACL_CacheCheck(char *uri, ACLListHandle_t **acllistp)
{
    return (ACL_INTCacheCheck(ACL_URI_HASH, uri, acllistp));
}

 
/*  ACL_CacheEnter
 *  INPUT
 *	acllist or 0 if there were no ACLs that applied.
 *  OUTPUT
 *      The acllist address may be changed if it matches an existing one.
 */
static void
ACL_INTCacheEnter(int which, char *uri, ACLListHandle_t **acllistp)
{
    ACLListHandle_t *tmpacllist;
    NSErr_t *errp = 0;
    PLHashTable *hash;

    PR_ASSERT(uri);

    ACL_CritEnter();

    /* Get the pointer to the hash table after acquiring the lock */
    if (which == ACL_URI_HASH)
	hash = acl_uri_hash;
    else
	hash = acl_uri_get_hash;

    /*  Check again (now that we're in the critical section) to see if
     *  someone else created an ACL List for this URI.  If so, discard the
     *  list that we made and replace it with the one just found.
     */
    tmpacllist = (ACLListHandle_t *)PR_HashTableLookup(hash, uri);
    if (tmpacllist != NULL) {
        if (tmpacllist != ACL_LIST_NO_ACLS) {
	    PR_ASSERT(ACL_CritHeld());
            tmpacllist->ref_count++;	/* we're going to use it */
        }
	ACL_CritExit();
	if (*acllistp  &&  *acllistp != ACL_LIST_NO_ACLS) {
	    ACL_ListDestroy(errp, *acllistp);
	}
	*acllistp = tmpacllist;
	PR_ASSERT(ACL_AssertAcllist(*acllistp));
	return;
    }

    /*  Didn't find another list, so put ours in.  */
    /*  Look for a matching ACL List and use it if we find one.  */
    if (*acllistp)  {
	PR_ASSERT(*acllistp != ACL_LIST_NO_ACLS);
        PR_ASSERT(ACL_AssertAcllist(*acllistp));
	ACL_ListHashUpdate(acllistp);
    } else {
	*acllistp = ACL_LIST_NO_ACLS;
    }
    PR_HashTableAdd(hash, pool_strdup((void **)acl_uri_hash_pool, uri), (void *)*acllistp);

    ACL_CritExit();
    PR_ASSERT(ACL_AssertAcllist(*acllistp));
    return;
}

void
ACL_CacheEnter(char *uri, ACLListHandle_t **acllistp)
{
    ACL_INTCacheEnter(ACL_URI_HASH, uri, acllistp);
    return;
}

void
ACL_CacheEnterGet(char *uri, ACLListHandle_t **acllistp)
{
    ACL_INTCacheEnter(ACL_URI_GET_HASH, uri, acllistp);
    return;
}

static int get_is_owner_default (NSErr_t *errp, PList_t subject,
				 PList_t resource, PList_t auth_info,
				 PList_t global_auth, void *unused)
{
    /* Make sure we don't generate error "all getters declined" message from
     * ACL_GetAttribute.
     */
	PListInitProp(subject, ACL_ATTR_IS_OWNER_INDEX, ACL_ATTR_IS_OWNER,
		"true", 0);

    return LAS_EVAL_TRUE;
}


NSAPI_PUBLIC int
ACL_Init(void)
{
    ACL_InitAttr2Index();
    ACLGlobal = (ACLGlobal_p)PERM_CALLOC(sizeof(ACLGlobal_s));
    oldACLGlobal = (ACLGlobal_p)PERM_CALLOC(sizeof(ACLGlobal_s));
    PR_ASSERT(ACLGlobal && oldACLGlobal);
    ACL_DATABASE_POOL = pool_create();
    ACL_METHOD_POOL = pool_create();
    ACL_CritInit();
    ACL_UriHashInit();
    ACL_ListHashInit();
    ACL_LasHashInit();
    ACL_Init2();

    return 0;
}

/* This one gets called at startup AND at cache flush time. */
void
ACL_Init2(void)
{

    /* Register the ACL functions */
    ACL_LasRegister(NULL, "timeofday", LASTimeOfDayEval, LASTimeOfDayFlush);
    ACL_LasRegister(NULL, "dayofweek", LASDayOfWeekEval, LASDayOfWeekFlush);
    ACL_LasRegister(NULL, "ip", LASIpEval, LASIpFlush);
    ACL_LasRegister(NULL, "dns", LASDnsEval, LASDnsFlush);
    ACL_LasRegister(NULL, "dnsalias", LASDnsEval, LASDnsFlush);
    ACL_LasRegister(NULL, "group", LASGroupEval, (LASFlushFunc_t)NULL);
    ACL_LasRegister(NULL, "user", LASUserEval, (LASFlushFunc_t)NULL);
#ifdef MCC_ADMSERV
    ACL_LasRegister(NULL, "program", LASProgramEval, (LASFlushFunc_t)NULL);
#endif
    return;
}

NSAPI_PUBLIC int
ACL_InitPostMagnus(void)
{
    int rv;

    rv = ACL_AttrGetterRegister(NULL, ACL_ATTR_IS_OWNER,
				get_is_owner_default,
				ACL_METHOD_ANY, ACL_DBTYPE_ANY,
				ACL_AT_END, NULL);
    return rv;
}

NSAPI_PUBLIC int
ACL_LateInitPostMagnus(void)
{
    return acl_usr_cache_init();
}
