/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */
/*
 * Internal PKCS #11 functions. Should only be called by pkcs11.c
 */
#include "pkcs11.h"
#include "pkcs11i.h"
#include "key.h"
#include "certdb.h"


/* declare the internal pkcs11 slot structures:
 *  There are threee:
 *	slot 0 is the generic crypto service token.
 *	slot 1 is the Database service token.
 *	slot 2 is the FIPS token (both generic and database).
 */
static PK11Slot pk11_slot[3];

/*
 * ******************** Attribute Utilities *******************************
 */

/*
 * create a new attribute with type, value, and length. Space is allocated
 * to hold value.
 */
static PK11Attribute *
pk11_NewAttribute(PK11Object *object,
	CK_ATTRIBUTE_TYPE type, CK_VOID_PTR value, CK_ULONG len)
{
    PK11Attribute *attribute;

#ifdef NO_ARENA
    int index;
    /* 
     * NO_ARENA attempts to keep down contention on Malloc and Arena locks
     * by limiting the number of these calls on high traversed paths. this
     * is done for attributes by 'allocating' them from a pool already allocated
     * by the parent object.
     */
    PK11_USE_THREADS(PR_Lock(object->attributeLock);)
    index = object->nextAttr++;
    PK11_USE_THREADS(PR_Unlock(object->attributeLock);)
    PORT_Assert(index < MAX_OBJS_ATTRS);
    if (index >= MAX_OBJS_ATTRS) return NULL;

    attribute = &object->attrList[index];
    attribute->attrib.type = type;
    if (value) {
        if (len <= ATTR_SPACE) {
	    attribute->attrib.pValue = attribute->space;
	} else {
	    attribute->attrib.pValue = PORT_Alloc(len);
	}
	if (attribute->attrib.pValue == NULL) {
	    return NULL;
	}
	PORT_Memcpy(attribute->attrib.pValue,value,len);
	attribute->attrib.ulValueLen = len;
    } else {
	attribute->attrib.pValue = NULL;
	attribute->attrib.ulValueLen = 0;
    }
#else
#ifdef REF_COUNT_ATTRIBUTE
    attribute = (PK11Attribute*)PORT_Alloc(sizeof(PK11Attribute));
#else
    attribute = (PK11Attribute*)PORT_ArenaAlloc(object->arena,sizeof(PK11Attribute));
#endif /* REF_COUNT_ATTRIBUTE */
    if (attribute == NULL) return NULL;

    if (value) {
#ifdef REF_COUNT_ATTRIBUTE
	attribute->attrib.pValue = PORT_Alloc(len);
#else
	attribute->attrib.pValue = PORT_ArenaAlloc(object->arena,len);
#endif /* REF_COUNT_ATTRIBUTE */
	if (attribute->attrib.pValue == NULL) {
#ifdef REF_COUNT_ATTRIBUTE
	    PORT_Free(attribute);
#endif /* REF_COUNT_ATTRIBUTE */
	    return NULL;
	}
	PORT_Memcpy(attribute->attrib.pValue,value,len);
	attribute->attrib.ulValueLen = len;
    } else {
	attribute->attrib.pValue = NULL;
	attribute->attrib.ulValueLen = 0;
    }
#endif /* NO_ARENA */
    attribute->attrib.type = type;
    attribute->handle = type;
    attribute->next = attribute->prev = NULL;
#ifdef REF_COUNT_ATTRIBUTE
    attribute->refCount = 1;
#ifdef PKCS11_USE_THREADS
    attribute->refLock = PR_NewLock();
    if (attribute->refLock == NULL) {
	if (attribute->attrib.pValue) PORT_Free(attribute->attrib.pValue);
	PORT_Free(attribute);
	return NULL;
    }
#else
    attribute->refLock = NULL;
#endif
#endif /* REF_COUNT_ATTRIBUTE */
    return attribute;
}

/*
 * Free up all the memory associated with an attribute. Reference count
 * must be zero to call this.
 */
static void
pk11_DestroyAttribute(PK11Attribute *attribute)
{
#ifdef REF_COUNT_ATTRIBUTE
    PORT_Assert(attribute->refCount == 0);
    PK11_USE_THREADS(PR_DestroyLock(attribute->refLock);)
    if (attribute->attrib.pValue) {
	 /* clear out the data in the attribute value... it may have been
	  * sensitive data */
	 PORT_Memset(attribute->attrib.pValue,0,attribute->attrib.ulValueLen);
	 PORT_Free(attribute->attrib.pValue);
    }
    PORT_Free(attribute);
#endif
}
    
    
/*
 * look up and attribute structure from a type and Object structure.
 * The returned attribute is referenced and needs to be freed when 
 * it is no longer needed.
 */
PK11Attribute *
pk11_FindAttribute(PK11Object *object,CK_ATTRIBUTE_TYPE type)
{
    PK11Attribute *attribute;

    PK11_USE_THREADS(PR_Lock(object->attributeLock);)
    pk11queue_find(attribute,type,object->head,ATTRIBUTE_HASH_SIZE);
#ifdef REF_COUNT_ATTRIBUTE
    if (attribute) {
	/* atomic increment would be nice here */
	PK11_USE_THREADS(PR_Lock(attribute->refLock);)
	attribute->refCount++;
	PK11_USE_THREADS(PR_Unlock(attribute->refLock);)
    }
#endif
    PK11_USE_THREADS(PR_Unlock(object->attributeLock);)

    return(attribute);
}


/*
 * release a reference to an attribute structure
 */
void
pk11_FreeAttribute(PK11Attribute *attribute)
{
#ifdef REF_COUNT_ATTRIBUTE
    PRBool destroy = PR_FALSE;

    PK11_USE_THREADS(PR_Lock(attribute->refLock);)
    if (attribute->refCount == 1) destroy = PR_TRUE;
    attribute->refCount--;
    PK11_USE_THREADS(PR_Unlock(attribute->refLock);)

    if (destroy) pk11_DestroyAttribute(attribute);
#endif
}


/*
 * return true if object has attribute
 */
PRBool
pk11_hasAttribute(PK11Object *object,CK_ATTRIBUTE_TYPE type)
{
    PK11Attribute *attribute;

    PK11_USE_THREADS(PR_Lock(object->attributeLock);)
    pk11queue_find(attribute,type,object->head,ATTRIBUTE_HASH_SIZE);
    PK11_USE_THREADS(PR_Unlock(object->attributeLock);)

    return (PRBool)(attribute != NULL);
}

/*
 * add an attribute to an object
 */
static
void pk11_AddAttribute(PK11Object *object,PK11Attribute *attribute)
{
    PK11_USE_THREADS(PR_Lock(object->attributeLock);)
    pk11queue_add(attribute,attribute->handle,object->head,ATTRIBUTE_HASH_SIZE);
    PK11_USE_THREADS(PR_Unlock(object->attributeLock);)
}

/* 
 * copy an unsigned attribute into a 'signed' SECItem. Secitem is allocated in
 * the specified arena.
 */
CK_RV
pk11_Attribute2SSecItem(PLArenaPool *arena,SECItem *item,PK11Object *object,
                                      CK_ATTRIBUTE_TYPE type)
{
    int len;
    PK11Attribute *attribute;
    unsigned char *start;
    int real_len;

    attribute = pk11_FindAttribute(object, type);
    if (attribute == NULL) return CKR_TEMPLATE_INCOMPLETE;
    real_len = len = attribute->attrib.ulValueLen;
    if ((*((unsigned char *)attribute->attrib.pValue) & 0x80) != 0) {
	real_len++;
    }

    if (arena) {
	start = item->data = (unsigned char *) PORT_ArenaAlloc(arena,real_len);
    } else {
	start = item->data = (unsigned char *) PORT_Alloc(real_len);
    }
    if (item->data == NULL) {
	pk11_FreeAttribute(attribute);
	return CKR_HOST_MEMORY;
    }
    item->len = real_len;
    if (real_len != len) {
	*start = 0;
	start++;
    }
    PORT_Memcpy(start, attribute->attrib.pValue, len);
    pk11_FreeAttribute(attribute);
    return CKR_OK;
}


/*
 * delete an attribute from an object
 */
static void
pk11_DeleteAttribute(PK11Object *object, PK11Attribute *attribute)
{
    PK11_USE_THREADS(PR_Lock(object->attributeLock);)
    if (pk11queue_is_queued(attribute,attribute->handle,
					object->head,ATTRIBUTE_HASH_SIZE)) {
	pk11queue_delete(attribute,attribute->handle,
					object->head,ATTRIBUTE_HASH_SIZE);
    }
    PK11_USE_THREADS(PR_Unlock(object->attributeLock);)
    pk11_FreeAttribute(attribute);
}

/*
 * this is only valid for CK_BBOOL type attributes. Return the state
 * of that attribute.
 */
PRBool
pk11_isTrue(PK11Object *object,CK_ATTRIBUTE_TYPE type)
{
    PK11Attribute *attribute;
    PRBool tok = PR_FALSE;

    attribute=pk11_FindAttribute(object,type);
    if (attribute == NULL) { return PR_FALSE; }
    tok = (PRBool)(*(CK_BBOOL *)attribute->attrib.pValue);
    pk11_FreeAttribute(attribute);

    return tok;
}

/*
 * force an attribute to null.
 * this is for sensitive keys which are stored in the database, we don't
 * want to keep this info around in memory in the clear.
 */
void
pk11_nullAttribute(PK11Object *object,CK_ATTRIBUTE_TYPE type)
{
    PK11Attribute *attribute;

    attribute=pk11_FindAttribute(object,type);
    if (attribute == NULL) return;

    if (attribute->attrib.pValue != NULL) {
	PORT_Memset(attribute->attrib.pValue,0,attribute->attrib.ulValueLen);
#ifdef REF_COUNT_ATTRIBUTE
	PORT_Free(attribute->attrib.pValue);
#endif /* REF_COUNT_ATTRIBUTE */
#ifdef NO_ARENA
	if (attribute->attrib.pValue != attribute->space) {
	    PORT_Free(attribute->attrib.pValue);
	}
#endif /* NO_ARENA */
	attribute->attrib.pValue = NULL;
	attribute->attrib.ulValueLen = 0;
    }
    pk11_FreeAttribute(attribute);
}

/*
 * force an attribute to a spaecif value.
 */
CK_RV
pk11_forceAttribute(PK11Object *object,CK_ATTRIBUTE_TYPE type, void *value,
						unsigned int len)
{
    PK11Attribute *attribute;
    void *att_val = NULL;

    attribute=pk11_FindAttribute(object,type);
    if (attribute == NULL) return pk11_AddAttributeType(object,type,value,len);


    if (value) {
#ifdef NO_ARENA
        if (len <= ATTR_SPACE) {
	    att_val = attribute->space;
	} else {
	    att_val = PORT_Alloc(len);
	}
#else
#ifdef REF_COUNT_ATTRIBUTE
	att_val = PORT_Alloc(len);
#else
	att_val = PORT_ArenaAlloc(object->arena,len);
#endif /* REF_COUNT_ATTRIBUTE */
#endif /* NO_ARENA */
	if (att_val == NULL) {
	    return CKR_HOST_MEMORY;
	}
	if (attribute->attrib.pValue == att_val) {
	    PORT_Memset(attribute->attrib.pValue,0,
					attribute->attrib.ulValueLen);
	}
	PORT_Memcpy(att_val,value,len);
    }
    if (attribute->attrib.pValue != NULL) {
	if (attribute->attrib.pValue != att_val) {
	    PORT_Memset(attribute->attrib.pValue,0,
					attribute->attrib.ulValueLen);
	}
#ifdef REF_COUNT_ATTRIBUTE
	PORT_Free(attribute->attrib.pValue);
#endif /* REF_COUNT_ATTRIBUTE */
#ifdef NO_ARENA
	if (attribute->attrib.pValue != attribute->space) {
	    PORT_Free(attribute->attrib.pValue);
	}
#endif /* NO_ARENA */
	attribute->attrib.pValue = NULL;
	attribute->attrib.ulValueLen = 0;
    }
    if (att_val) {
	attribute->attrib.pValue = att_val;
	attribute->attrib.ulValueLen = len;
    }
    return CKR_OK;
}

/*
 * return a null terminated string from attribute 'type'. This string
 * is allocated and needs to be freed with PORT_Free() When complete.
 */
char *
pk11_getString(PK11Object *object,CK_ATTRIBUTE_TYPE type)
{
    PK11Attribute *attribute;
    char *label = NULL;

    attribute=pk11_FindAttribute(object,type);
    if (attribute == NULL) return NULL;

    if (attribute->attrib.pValue != NULL) {
	label = (char *) PORT_Alloc(attribute->attrib.ulValueLen+1);
	if (label == NULL) {
    	    pk11_FreeAttribute(attribute);
	    return NULL;
	}

	PORT_Memcpy(label,attribute->attrib.pValue,
						attribute->attrib.ulValueLen);
	label[attribute->attrib.ulValueLen] = 0;
    }
    pk11_FreeAttribute(attribute);
    return label;
}

/*
 * decode when a particular attribute may be modified
 * 	PK11_NEVER: This attribute must be set at object creation time and
 *  can never be modified.
 *	PK11_ONCOPY: This attribute may be modified only when you copy the
 *  object.
 *	PK11_SENSITIVE: The CKA_SENSITIVE attribute can only be changed from
 *  CK_FALSE to CK_TRUE.
 *	PK11_ALWAYS: This attribute can always be modified.
 * Some attributes vary their modification type based on the class of the 
 *   object.
 */
PK11ModifyType
pk11_modifyType(CK_ATTRIBUTE_TYPE type, CK_OBJECT_CLASS inClass)
{
    /* if we don't know about it, user user defined, always allow modify */
    PK11ModifyType mtype = PK11_ALWAYS; 

    switch(type) {
    /* NEVER */
    case CKA_CLASS:
    case CKA_CERTIFICATE_TYPE:
    case CKA_KEY_TYPE:
    case CKA_MODULUS:
    case CKA_MODULUS_BITS:
    case CKA_PUBLIC_EXPONENT:
    case CKA_PRIVATE_EXPONENT:
    case CKA_PRIME:
    case CKA_SUBPRIME:
    case CKA_BASE:
    case CKA_PRIME_1:
    case CKA_PRIME_2:
    case CKA_EXPONENT_1:
    case CKA_EXPONENT_2:
    case CKA_COEFFICIENT:
    case CKA_VALUE_LEN:
    case CKA_ALWAYS_SENSITIVE:
    case CKA_NEVER_EXTRACTABLE:
    case CKA_NETSCAPE_DB:
	mtype = PK11_NEVER;
	break;

    /* ONCOPY */
    case CKA_TOKEN:
    case CKA_PRIVATE:
    case CKA_MODIFIABLE:
	mtype = PK11_ONCOPY;
	break;

    /* SENSITIVE */
    case CKA_SENSITIVE:
    case CKA_EXTRACTABLE:
	mtype = PK11_SENSITIVE;
	break;

    /* ALWAYS */
    case CKA_LABEL:
    case CKA_APPLICATION:
    case CKA_ID:
    case CKA_SERIAL_NUMBER:
    case CKA_START_DATE:
    case CKA_END_DATE:
    case CKA_DERIVE:
    case CKA_ENCRYPT:
    case CKA_DECRYPT:
    case CKA_SIGN:
    case CKA_VERIFY:
    case CKA_SIGN_RECOVER:
    case CKA_VERIFY_RECOVER:
    case CKA_WRAP:
    case CKA_UNWRAP:
	mtype = PK11_ALWAYS;
	break;

    /* DEPENDS ON CLASS */
    case CKA_VALUE:
	mtype = (inClass == CKO_DATA) ? PK11_ALWAYS : PK11_NEVER;
	break;

    case CKA_SUBJECT:
	mtype = (inClass == CKO_CERTIFICATE) ? PK11_NEVER : PK11_ALWAYS;
	break;
    default:
	break;
    }
    return mtype;
}

/* decode if a particular attribute is sensitive (cannot be read
 * back to the user of if the object is set to SENSITIVE) */
PRBool
pk11_isSensitive(CK_ATTRIBUTE_TYPE type, CK_OBJECT_CLASS inClass)
{
    switch(type) {
    /* ALWAYS */
    case CKA_PRIVATE_EXPONENT:
    case CKA_PRIME_1:
    case CKA_PRIME_2:
    case CKA_EXPONENT_1:
    case CKA_EXPONENT_2:
    case CKA_COEFFICIENT:
	return PR_TRUE;

    /* DEPENDS ON CLASS */
    case CKA_VALUE:
	/* PRIVATE and SECRET KEYS have SENSITIVE values */
	return (PRBool)((inClass == CKO_PRIVATE_KEY) || (inClass == CKO_SECRET_KEY));

    default:
	break;
    }
    return PR_FALSE;
}

/* 
 * copy an attribute into a SECItem. Secitem is allocated in the specified
 * arena.
 */
CK_RV
pk11_Attribute2SecItem(PLArenaPool *arena,SECItem *item,PK11Object *object,
					CK_ATTRIBUTE_TYPE type)
{
    int len;
    PK11Attribute *attribute;

    attribute = pk11_FindAttribute(object, type);
    if (attribute == NULL) return CKR_TEMPLATE_INCOMPLETE;
    len = attribute->attrib.ulValueLen;

    if (arena) {
    	item->data = (unsigned char *) PORT_ArenaAlloc(arena,len);
    } else {
    	item->data = (unsigned char *) PORT_Alloc(len);
    }
    if (item->data == NULL) {
	pk11_FreeAttribute(attribute);
	return CKR_HOST_MEMORY;
    }
    item->len = len;
    PORT_Memcpy(item->data,attribute->attrib.pValue, len);
    pk11_FreeAttribute(attribute);
    return CKR_OK;
}

void
pk11_DeleteAttributeType(PK11Object *object,CK_ATTRIBUTE_TYPE type)
{
    PK11Attribute *attribute;
    attribute = pk11_FindAttribute(object, type);
    if (attribute == NULL) return ;
    pk11_DeleteAttribute(object,attribute);
    pk11_FreeAttribute(attribute);
}

CK_RV
pk11_AddAttributeType(PK11Object *object,CK_ATTRIBUTE_TYPE type,void *valPtr,
							CK_ULONG length)
{
    PK11Attribute *attribute;
    attribute = pk11_NewAttribute(object,type,valPtr,length);
    if (attribute == NULL) { return CKR_HOST_MEMORY; }
    pk11_AddAttribute(object,attribute);
    return CKR_OK;
}

/*
 * ******************** Object Utilities *******************************
 */

/* allocation hooks that allow us to recycle old object structures */
static PK11Object *
pk11_GetObjectFromList(PRBool *hasLocks) {
    PK11Object *object = (PK11Object*)PORT_ZAlloc(sizeof(PK11Object));
    *hasLocks = PR_FALSE;
    return object;
}

static void
pk11_PutObjectToList(PK11Object *object) {
    PK11_USE_THREADS(PR_DestroyLock(object->attributeLock);)
    PK11_USE_THREADS(PR_DestroyLock(object->refLock);)
    object->attributeLock = object->refLock = NULL;
    PORT_Free(object);
}


/*
 * Create a new object
 */
PK11Object *
pk11_NewObject(PK11Slot *slot)
{
    PK11Object *object;
    PLArenaPool *arena;
    PRBool hasLocks = PR_FALSE;
    int i;


#ifdef NO_ARENA
    object = pk11_GetObjectFromList(&hasLocks);
    if (object == NULL) {
	return NULL;
    }
    object->nextAttr = 0;
#else
    arena = PORT_NewArena(2048);
    if (arena == NULL) return NULL;

    object = (PK11Object*)PORT_ArenaAlloc(arena,sizeof(PK11Object));
    if (object == NULL) {
	PORT_FreeArena(arena,PR_FALSE);
	return NULL;
    }
    object->arena = arena;

    for (i=0; i < MAX_OBJS_ATTRS; i++) {
	object->attrList[i].attrib.pValue = NULL;
    }
#endif

    object->handle = 0;
    object->next = object->prev = NULL;
    object->sessionList.next = NULL;
    object->sessionList.prev = NULL;
    object->sessionList.parent = object;
    object->inDB = PR_FALSE;
    object->label = NULL;
    object->refCount = 1;
    object->session = NULL;
    object->slot = slot;
    object->objclass = 0xffff;
    object->wasDerived = PR_FALSE;
#ifdef PKCS11_USE_THREADS
    if (!hasLocks) object->refLock = PR_NewLock();
    if (object->refLock == NULL) {
#ifdef NO_ARENA
	PORT_Free(object);
#else
	PORT_FreeArena(arena,PR_FALSE);
#endif
	return NULL;
    }
    if (!hasLocks) object->attributeLock = PR_NewLock();
    if (object->attributeLock == NULL) {
	PK11_USE_THREADS(PR_DestroyLock(object->refLock);)
#ifdef NO_ARENA
	PORT_Free(object);
#else
	PORT_FreeArena(arena,PR_FALSE);
#endif
	return NULL;
    }
#else
    object->attributeLock = NULL;
    object->refLock = NULL;
#endif
    for (i=0; i < ATTRIBUTE_HASH_SIZE; i++) {
	object->head[i] = NULL;
    }
    object->objectInfo = NULL;
    object->infoFree = NULL;
    return object;
}

/*
 * free all the data associated with an object. Object reference count must
 * be 'zero'.
 */
static CK_RV
pk11_DestroyObject(PK11Object *object)
{
#if defined(REF_COUNT_ATTRIBUTE) || defined(NO_ARENA)
    int i;
#endif
    SECItem pubKey;
    CK_RV crv = CKR_OK;
    SECStatus rv;
    PLArenaPool *arena = NULL;

    PORT_Assert(object->refCount == 0);

    /* delete the database value */
    if (object->inDB) {
	if (pk11_isToken(object->handle)) {
	/* remove the objects from the real data base */
	    switch (object->handle & PK11_TOKEN_TYPE_MASK) {
	      case PK11_TOKEN_TYPE_PRIV:
		/* KEYID is the public KEY for DSA and DH, and the MODULUS for
		 *  RSA */
		crv=pk11_Attribute2SecItem(NULL,&pubKey,object,CKA_NETSCAPE_DB);
		if (crv != CKR_OK) break;
		rv = SECKEY_DeleteKey(SECKEY_GetDefaultKeyDB(), &pubKey);
		if (rv != SECSuccess) crv= CKR_DEVICE_ERROR;
		break;
	      case PK11_TOKEN_TYPE_CERT:
		rv = SEC_DeletePermCertificate((CERTCertificate *)object->objectInfo);
		if (rv != SECSuccess) crv = CKR_DEVICE_ERROR;
		break;
	    }
	}
    } 
    if (object->label) PORT_Free(object->label);

    object->inDB = PR_FALSE;
    object->label = NULL;

#ifdef NO_ARENA
    for (i=0; i < MAX_OBJS_ATTRS; i++) {
	unsigned char *value = object->attrList[i].attrib.pValue;
	if (value) {
	    PORT_Memset(value,0,object->attrList[i].attrib.ulValueLen);
	    if (value != object->attrList[i].space) {
		PORT_Free(value);
	    }
	    object->attrList[i].attrib.pValue = NULL;
	}
    }
#endif

#ifdef REF_COUNT_ATTRIBUTE
    /* clean out the attributes */
    /* since no one is referencing us, it's safe to walk the chain
     * without a lock */
    for (i=0; i < ATTRIBUTE_HASH_SIZE; i++) {
	PK11Attribute *ap,*next;
	for (ap = object->head[i]; ap != NULL; ap = next) {
	    next = ap->next;
	    /* paranoia */
	    ap->next = ap->prev = NULL;
	    pk11_FreeAttribute(ap);
	}
	object->head[i] = NULL;
    }
#endif
    if (object->objectInfo) {
	(*object->infoFree)(object->objectInfo);
    }
#ifdef NO_ARENA
    pk11_PutObjectToList(object);
#else
    PK11_USE_THREADS(PR_DestroyLock(object->attributeLock);)
    PK11_USE_THREADS(PR_DestroyLock(object->refLock);)
    arena = object->arena;
    PORT_FreeArena(arena,PR_FALSE);
#endif
    return crv;
}

void
pk11_ReferenceObject(PK11Object *object)
{
    PK11_USE_THREADS(PR_Lock(object->refLock);)
    object->refCount++;
    PK11_USE_THREADS(PR_Unlock(object->refLock);)
}

static PK11Object *
pk11_ObjectFromHandleOnSlot(CK_OBJECT_HANDLE handle, PK11Slot *slot)
{
    PK11Object **head;
    PRLock *lock;
    PK11Object *object;

    head = slot->tokObjects;
    lock = slot->objectLock;

    PK11_USE_THREADS(PR_Lock(lock);)
    pk11queue_find(object,handle,head,TOKEN_OBJECT_HASH_SIZE);
    if (object) {
	pk11_ReferenceObject(object);
    }
    PK11_USE_THREADS(PR_Unlock(lock);)

    return(object);
}
/*
 * look up and object structure from a handle. OBJECT_Handles only make
 * sense in terms of a given session.  make a reference to that object
 * structure returned.
 */
PK11Object *
pk11_ObjectFromHandle(CK_OBJECT_HANDLE handle, PK11Session *session)
{
    PK11Slot *slot = pk11_SlotFromSession(session);

    return pk11_ObjectFromHandleOnSlot(handle,slot);
}


/*
 * release a reference to an object handle
 */
PK11FreeStatus
pk11_FreeObject(PK11Object *object)
{
    PRBool destroy = PR_FALSE;
    CK_RV crv;

    PK11_USE_THREADS(PR_Lock(object->refLock);)
    if (object->refCount == 1) destroy = PR_TRUE;
    object->refCount--;
    PK11_USE_THREADS(PR_Unlock(object->refLock);)

    if (destroy) {
	crv = pk11_DestroyObject(object);
	if (crv != CKR_OK) {
	   return PK11_DestroyFailure;
	} 
	return PK11_Destroyed;
    }
    return PK11_Busy;
}
 
/*
 * add an object to a slot and session queue. These two functions
 * adopt the object.
 */
void
pk11_AddSlotObject(PK11Slot *slot, PK11Object *object)
{
    PK11_USE_THREADS(PR_Lock(slot->objectLock);)
    pk11queue_add(object,object->handle,slot->tokObjects,TOKEN_OBJECT_HASH_SIZE);
    PK11_USE_THREADS(PR_Unlock(slot->objectLock);)
}

void
pk11_AddObject(PK11Session *session, PK11Object *object)
{
    PK11Slot *slot = pk11_SlotFromSession(session);

    if (!pk11_isToken(object->handle)) {
	PK11_USE_THREADS(PR_Lock(session->objectLock);)
	pk11queue_add(&object->sessionList,0,session->objects,0);
	object->session = session;
	PK11_USE_THREADS(PR_Unlock(session->objectLock);)
    }
    pk11_AddSlotObject(slot,object);
} 

/*
 * add an object to a slot andsession queue
 */
void
pk11_DeleteObject(PK11Session *session, PK11Object *object)
{
    PK11Slot *slot = pk11_SlotFromSession(session);

    if (object->session) {
	PK11Session *session = object->session;
	PK11_USE_THREADS(PR_Lock(session->objectLock);)
	pk11queue_delete(&object->sessionList,0,session->objects,0);
	PK11_USE_THREADS(PR_Unlock(session->objectLock);)
    }
    PK11_USE_THREADS(PR_Lock(slot->objectLock);)
    pk11queue_delete(object,object->handle,slot->tokObjects,
						TOKEN_OBJECT_HASH_SIZE);
    PK11_USE_THREADS(PR_Unlock(slot->objectLock);)
    pk11_FreeObject(object);
}

/*
 * copy the attributes from one object to another. Don't overwrite existing
 * attributes. NOTE: This is a pretty expensive operation since it
 * grabs the attribute locks for the src object for a *long* time.
 */
CK_RV
pk11_CopyObject(PK11Object *destObject,PK11Object *srcObject)
{
    PK11Attribute *attribute;
    int i;

    PK11_USE_THREADS(PR_Lock(srcObject->attributeLock);)
    for(i=0; i < ATTRIBUTE_HASH_SIZE; i++) {
	attribute = srcObject->head[i];
	do {
	    if (attribute) {
		if (!pk11_hasAttribute(destObject,attribute->handle)) {
		    /* we need to copy the attribute since each attribute
		     * only has one set of link list pointers */
		    PK11Attribute *newAttribute = pk11_NewAttribute(
			  destObject,pk11_attr_expand(&attribute->attrib));
		    if (newAttribute == NULL) {
			PK11_USE_THREADS(PR_Unlock(srcObject->attributeLock);)
			return CKR_HOST_MEMORY;
		    }
		    pk11_AddAttribute(destObject,newAttribute);
		}
		attribute=attribute->next;
	    }
	} while (attribute != NULL);
    }
    PK11_USE_THREADS(PR_Unlock(srcObject->attributeLock);)
    return CKR_OK;
}

/*
 * ******************** Search Utilities *******************************
 */

/* add an object to a search list */
CK_RV
AddToList(PK11ObjectListElement **list,PK11Object *object)
{
     PK11ObjectListElement *newElem = 
	(PK11ObjectListElement *)PORT_Alloc(sizeof(PK11ObjectListElement));

     if (newElem == NULL) return CKR_HOST_MEMORY;

     newElem->next = *list;
     newElem->object = object;
     pk11_ReferenceObject(object);

    *list = newElem;
    return CKR_OK;
}


/* return true if the object matches the template */
PRBool
pk11_objectMatch(PK11Object *object,CK_ATTRIBUTE_PTR theTemplate,int count)
{
    int i;

    for (i=0; i < count; i++) {
	PK11Attribute *attribute = pk11_FindAttribute(object,theTemplate[i].type);
	if (attribute == NULL) {
	    return PR_FALSE;
	}
	if (attribute->attrib.ulValueLen == theTemplate[i].ulValueLen) {
	    if (PORT_Memcmp(attribute->attrib.pValue,theTemplate[i].pValue,
					theTemplate[i].ulValueLen) == 0) {
        	pk11_FreeAttribute(attribute);
		continue;
	    }
	}
        pk11_FreeAttribute(attribute);
	return PR_FALSE;
    }
    return PR_TRUE;
}

/* search through all the objects in the queue and return the template matches
 * in the object list.
 */
CK_RV
pk11_searchObjectList(PK11ObjectListElement **objectList,PK11Object **head,
        PRLock *lock, CK_ATTRIBUTE_PTR theTemplate, int count, PRBool isLoggedIn)
{
    int i;
    PK11Object *object;
    CK_RV crv = CKR_OK;

    for(i=0; i < TOKEN_OBJECT_HASH_SIZE; i++) {
        /* We need to hold the lock to copy a consistant version of
         * the linked list. */
        PK11_USE_THREADS(PR_Lock(lock);)
	for (object = head[i]; object != NULL; object= object->next) {
	    if (pk11_objectMatch(object,theTemplate,count)) {
		/* don't return objects that aren't yet visible */
		if ((!isLoggedIn) && pk11_isTrue(object,CKA_PRIVATE)) continue;
	        crv = AddToList(objectList,object);
		if (crv != CKR_OK) {
		    break;
		}
	    }
	}
        PK11_USE_THREADS(PR_Unlock(lock);)
    }
    return crv;
}

/*
 * free a single list element. Return the Next object in the list.
 */
PK11ObjectListElement *
pk11_FreeObjectListElement(PK11ObjectListElement *objectList)
{
    PK11ObjectListElement *ol = objectList->next;

    pk11_FreeObject(objectList->object);
    PORT_Free(objectList);
    return ol;
}

/* free an entire object list */
void
pk11_FreeObjectList(PK11ObjectListElement *objectList)
{
    PK11ObjectListElement *ol;

    for (ol= objectList; ol != NULL; ol = pk11_FreeObjectListElement(ol)) {}
}

/*
 * free a search structure
 */
void
pk11_FreeSearch(PK11SearchResults *search)
{
    if (search->handles) {
	PORT_Free(search->handles);
    }
    PORT_Free(search);
}

/*
 * ******************** Session Utilities *******************************
 */

/* update the sessions state based in it's flags and wether or not it's
 * logged in */
void
pk11_update_state(PK11Slot *slot,PK11Session *session)
{
    if (slot->isLoggedIn) {
	if (slot->ssoLoggedIn) {
    	    session->info.state = CKS_RW_SO_FUNCTIONS;
	} else if (session->info.flags & CKF_RW_SESSION) {
    	    session->info.state = CKS_RW_USER_FUNCTIONS;
	} else {
    	    session->info.state = CKS_RO_USER_FUNCTIONS;
	}
    } else {
	if (session->info.flags & CKF_RW_SESSION) {
    	    session->info.state = CKS_RW_PUBLIC_SESSION;
	} else {
    	    session->info.state = CKS_RO_PUBLIC_SESSION;
	}
    }
}

/* update the state of all the sessions on a slot */
void
pk11_update_all_states(PK11Slot *slot)
{
    int i;
    PK11Session *session;

    for (i=0; i < SESSION_HASH_SIZE; i++) {
	PK11_USE_THREADS(PR_Lock(slot->sessionLock);)
	for (session = slot->head[i]; session; session = session->next) {
	    pk11_update_state(slot,session);
	}
	PK11_USE_THREADS(PR_Unlock(slot->sessionLock);)
    }
}

/*
 * context are cipher and digest contexts that are associated with a session
 */
void
pk11_FreeContext(PK11SessionContext *context)
{
    if (context->cipherInfo) {
	(*context->destroy)(context->cipherInfo,PR_TRUE);
    }
    if (context->hashInfo) {
	(*context->hashdestroy)(context->hashInfo,PR_TRUE);
    }
    PORT_Free(context);
}

/* look up a slot structure from the ID (used to be a macro when we only
 * had two slots) */
PK11Slot *
pk11_SlotFromID(CK_SLOT_ID slotID)
{
    switch (slotID) {
    case NETSCAPE_SLOT_ID:
	return &pk11_slot[0];
    case PRIVATE_KEY_SLOT_ID:
	return &pk11_slot[1];
    case FIPS_SLOT_ID:
	return &pk11_slot[2];
    default:
	break; /* fall through to NULL */
    }
    return NULL;
}

PK11Slot *
pk11_SlotFromSessionHandle(CK_SESSION_HANDLE handle)
{
    if (handle & PK11_PRIVATE_KEY_FLAG) {
	return &pk11_slot[1];
    }
    if (handle & PK11_FIPS_FLAG) {
	return &pk11_slot[2];
    }
    return &pk11_slot[0];
}

/*
 * create a new nession. NOTE: The session handle is not set, and the
 * session is not added to the slot's session queue.
 */
PK11Session *
pk11_NewSession(CK_SLOT_ID slotID, CK_NOTIFY notify, CK_VOID_PTR pApplication,
							     CK_FLAGS flags)
{
    PK11Session *session;
    PK11Slot *slot = pk11_SlotFromID(slotID);

    if (slot == NULL) return NULL;

    session = (PK11Session*)PORT_Alloc(sizeof(PK11Session));
    if (session == NULL) return NULL;

    session->next = session->prev = NULL;
    session->refCount = 1;
    session->enc_context = NULL;
    session->hash_context = NULL;
    session->sign_context = NULL;
    session->search = NULL;
    session->objectIDCount = 1;
#ifdef PKCS11_USE_THREADS
    session->refLock = PR_NewLock();
    if (session->refLock == NULL) {
	PORT_Free(session);
	return NULL;
    }
    session->objectLock = PR_NewLock();
    if (session->objectLock == NULL) {
	PK11_USE_THREADS(PR_DestroyLock(session->refLock);)
	PORT_Free(session);
	return NULL;
    }
#else
    session->refLock = NULL;
    session->objectLock = NULL;
#endif
    session->objects[0] = NULL;

    session->slot = slot;
    session->notify = notify;
    session->appData = pApplication;
    session->info.flags = flags;
    session->info.slotID = slotID;
    pk11_update_state(slot,session);
    return session;
}


/* free all the data associated with a session. */
static void
pk11_DestroySession(PK11Session *session)
{
    PK11ObjectList *op,*next;
    PORT_Assert(session->refCount == 0);

    /* clean out the attributes */
    /* since no one is referencing us, it's safe to walk the chain
     * without a lock */
    for (op = session->objects[0]; op != NULL; op = next) {
        next = op->next;
        /* paranoia */
	op->next = op->prev = NULL;
	pk11_DeleteObject(session,op->parent);
    }
    PK11_USE_THREADS(PR_DestroyLock(session->objectLock);)
    PK11_USE_THREADS(PR_DestroyLock(session->refLock);)
    if (session->enc_context) {
	pk11_FreeContext(session->enc_context);
    }
    if (session->hash_context) {
	pk11_FreeContext(session->hash_context);
    }
    if (session->sign_context) {
	pk11_FreeContext(session->sign_context);
    }
    if (session->search) {
	pk11_FreeSearch(session->search);
    }
    PORT_Free(session);
}


/*
 * look up a session structure from a session handle
 * generate a reference to it.
 */
PK11Session *
pk11_SessionFromHandle(CK_SESSION_HANDLE handle)
{
    PK11Slot	*slot = pk11_SlotFromSessionHandle(handle);
    PK11Session *session;

    PK11_USE_THREADS(PR_Lock(slot->sessionLock);)
    pk11queue_find(session,handle,slot->head,SESSION_HASH_SIZE);
    if (session) session->refCount++;
    PK11_USE_THREADS(PR_Unlock(slot->sessionLock);)

    return (session);
}

/*
 * release a reference to a session handle
 */
void
pk11_FreeSession(PK11Session *session)
{
    PRBool destroy = PR_FALSE;
    PK11_USE_THREADS(PK11Slot *slot = pk11_SlotFromSession(session);)

    PK11_USE_THREADS(PR_Lock(slot->sessionLock);)
    if (session->refCount == 1) destroy = PR_TRUE;
    session->refCount--;
    PK11_USE_THREADS(PR_Unlock(slot->sessionLock);)

    if (destroy) pk11_DestroySession(session);
}
