/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <pthread.h>
#include <stdlib.h>
#include <security/cryptoki.h>
#include "softGlobal.h"
#include "softObject.h"
#include "softSession.h"
#include "softKeystore.h"
#include "softKeystoreUtil.h"


CK_RV
C_CreateObject(CK_SESSION_HANDLE hSession,
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulCount,
    CK_OBJECT_HANDLE_PTR phObject)
{

	CK_RV rv;
	soft_session_t *session_p;
	boolean_t lock_held = B_FALSE;

	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	/*
	 * Obtain the session pointer. Also, increment the session
	 * reference count.
	 */
	rv = handle2session(hSession, &session_p);
	if (rv != CKR_OK)
		return (rv);

	if ((pTemplate == NULL) || (ulCount == 0) ||
	    (phObject == NULL)) {
		rv = CKR_ARGUMENTS_BAD;
		goto clean_exit;
	}

	/* Create a new object. */
	rv = soft_add_object(pTemplate, ulCount, phObject, session_p);

clean_exit:
	/*
	 * Decrement the session reference count.
	 * We do not hold the session lock.
	 */
	SES_REFRELE(session_p, lock_held);
	return (rv);
}

CK_RV
C_CopyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
    CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount,
    CK_OBJECT_HANDLE_PTR phNewObject)
{

	CK_RV rv;
	soft_session_t *session_p;
	boolean_t lock_held = B_FALSE;
	soft_object_t *old_object, *new_object = NULL;
	ulong_t i;

	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	/*
	 * Obtain the session pointer. Also, increment the session
	 * reference count.
	 */
	rv = handle2session(hSession, &session_p);
	if (rv != CKR_OK)
		return (rv);

	/* Check arguments */
	if (((ulCount > 0) && (pTemplate == NULL)) ||
	    (phNewObject == NULL)) {
		rv = CKR_ARGUMENTS_BAD;
		goto clean_exit;
	}

	/* Obtain the object pointer. */
	HANDLE2OBJECT(hObject, old_object, rv);
	if (rv != CKR_OK) {
		goto clean_exit;
	}

	/*
	 * Copy the old object to a new object.
	 * The 3rd argument with SOFT_COPY_OBJ value indicates that
	 * everything in the object will be duplicated for C_CopyObject.
	 * The 4th argument has the session pointer that will be
	 * saved in the new copy of the session object.
	 */
	(void) pthread_mutex_lock(&old_object->object_mutex);
	rv = soft_copy_object(old_object, &new_object, SOFT_COPY_OBJECT,
	    session_p);

	if ((rv != CKR_OK) || (new_object == NULL)) {
		/* Most likely we ran out of space. */
		(void) pthread_mutex_unlock(&old_object->object_mutex);
		goto clean_exit1;
	}

	/* No need to hold the lock on the old object. */
	(void) pthread_mutex_unlock(&old_object->object_mutex);

	/* Modifiy the objects if requested */
	for (i = 0; i < ulCount; i++) {
		/* Set the requested attribute into the new object. */
		rv = soft_set_attribute(new_object, &pTemplate[i], B_TRUE);
		if (rv != CKR_OK) {
			goto fail;
		}
	}

	rv = soft_pin_expired_check(new_object);
	if (rv != CKR_OK) {
		goto fail;
	}

	/*
	 * Does the new object violate the creation rule or access rule?
	 */
	rv = soft_object_write_access_check(session_p, new_object);
	if (rv != CKR_OK) {
		goto fail;
	}

	/*
	 * If the new object is a token object, it will be added
	 * to token object list and write to disk.
	 */
	if (IS_TOKEN_OBJECT(new_object)) {
		new_object->version = 1;
		/*
		 * Write to the keystore file.
		 */
		rv = soft_put_object_to_keystore(new_object);
		if (rv != CKR_OK) {
			goto fail;
		}

		new_object->session_handle = (CK_SESSION_HANDLE)NULL;
		/*
		 * Add the newly created token object to the global
		 * token object list in the slot struct.
		 */
		soft_add_token_object_to_slot(new_object);
		OBJ_REFRELE(old_object);
		SES_REFRELE(session_p, lock_held);
		*phNewObject = (CK_ULONG)new_object;

		return (CKR_OK);
	}

	/* Insert new object into this session's object list */
	soft_add_object_to_session(new_object, session_p);

	/*
	 * Decrement the session reference count.
	 * We do not hold the session lock.
	 */
	OBJ_REFRELE(old_object);
	SES_REFRELE(session_p, lock_held);

	/* set handle of the new object */
	*phNewObject = (CK_ULONG)new_object;

	return (rv);

fail:
	soft_cleanup_object(new_object);
	free(new_object);

clean_exit1:
	OBJ_REFRELE(old_object);
clean_exit:
	SES_REFRELE(session_p, lock_held);
	return (rv);
}

CK_RV
C_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject)
{

	CK_RV rv;
	soft_object_t *object_p;
	soft_session_t *session_p = (soft_session_t *)(hSession);
	boolean_t lock_held = B_FALSE;
	CK_SESSION_HANDLE creating_session;


	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	/*
	 * The reason that we don't call handle2session is because
	 * the argument hSession may not be the creating_session of
	 * the object to be destroyed, and we want to avoid the lock
	 * contention. The handle2session will be called later for
	 * the creating_session.
	 */
	if ((session_p == NULL) ||
	    (session_p->magic_marker != SOFTTOKEN_SESSION_MAGIC)) {
		return (CKR_SESSION_HANDLE_INVALID);
	}

	/* Obtain the object pointer. */
	HANDLE2OBJECT_DESTROY(hObject, object_p, rv);
	if (rv != CKR_OK) {
		return (rv);
	}

	/* Obtain the session handle which object belongs to. */
	creating_session = object_p->session_handle;

	if (creating_session == NULL) {
		/*
		 * This is a token object to be deleted.
		 * For token object, there is no creating session concept,
		 * therefore, creating_session is always NULL.
		 */
		rv = soft_pin_expired_check(object_p);
		if (rv != CKR_OK) {
			return (rv);
		}

		/* Obtain the session pointer just for validity check. */
		rv = handle2session(hSession, &session_p);
		if (rv != CKR_OK) {
			return (rv);
		}

		rv = soft_object_write_access_check(session_p, object_p);
		if (rv != CKR_OK) {
			SES_REFRELE(session_p, lock_held);
			return (rv);
		}

		/*
		 * Set OBJECT_IS_DELETING flag so any access to this
		 * object will be rejected.
		 */
		(void) pthread_mutex_lock(&object_p->object_mutex);
		if (object_p->obj_delete_sync & OBJECT_IS_DELETING) {
			(void) pthread_mutex_unlock(&object_p->object_mutex);
			SES_REFRELE(session_p, lock_held);
			return (CKR_OBJECT_HANDLE_INVALID);
		}
		object_p->obj_delete_sync |= OBJECT_IS_DELETING;
		(void) pthread_mutex_unlock(&object_p->object_mutex);
		SES_REFRELE(session_p, lock_held);

		/*
		 * Delete a token object by calling soft_delete_token_object()
		 * with the second argument B_TRUE indicating to delete the
		 * object from keystore and the third argument B_FALSE
		 * indicating that the caller does not hold the slot mutex.
		 */
		soft_delete_token_object(object_p, B_TRUE, B_FALSE);
		return (CKR_OK);
	}

	/*
	 * Obtain the session pointer. Also, increment the session
	 * reference count.
	 */
	rv = handle2session(creating_session, &session_p);
	if (rv != CKR_OK) {
		return (rv);
	}

	/*
	 * Set OBJECT_IS_DELETING flag so any access to this
	 * object will be rejected.
	 */
	(void) pthread_mutex_lock(&object_p->object_mutex);
	if (object_p->obj_delete_sync & OBJECT_IS_DELETING) {
		(void) pthread_mutex_unlock(&object_p->object_mutex);
		SES_REFRELE(session_p, lock_held);
		return (CKR_OBJECT_HANDLE_INVALID);
	}
	object_p->obj_delete_sync |= OBJECT_IS_DELETING;
	(void) pthread_mutex_unlock(&object_p->object_mutex);

	/*
	 * Delete an object by calling soft_delete_object()
	 * with a FALSE boolean argument indicating that
	 * the caller does not hold the session lock.
	 */
	soft_delete_object(session_p, object_p, B_FALSE);

	/*
	 * Decrement the session reference count.
	 * We do not hold the session lock.
	 */
	SES_REFRELE(session_p, lock_held);

	return (rv);
}


CK_RV
C_GetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
    CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{

	CK_RV rv = CKR_OK, rv1 = CKR_OK;
	soft_object_t *object_p;
	soft_session_t *session_p;
	boolean_t lock_held = B_FALSE;
	ulong_t i;

	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	/*
	 * Obtain the session pointer. Also, increment the session
	 * reference count.
	 */
	rv = handle2session(hSession, &session_p);
	if (rv != CKR_OK)
		return (rv);

	if ((pTemplate == NULL) || (ulCount == 0)) {
		/*
		 * Decrement the session reference count.
		 * We do not hold the session lock.
		 */
		SES_REFRELE(session_p, lock_held);
		return (CKR_ARGUMENTS_BAD);
	}

	/* Obtain the object pointer. */
	HANDLE2OBJECT(hObject, object_p, rv);
	if (rv != CKR_OK) {
		/*
		 * Decrement the session reference count.
		 * We do not hold the session lock.
		 */
		SES_REFRELE(session_p, lock_held);
		return (rv);
	}

	if (IS_TOKEN_OBJECT(object_p)) {

		rv = soft_keystore_load_latest_object(object_p);
		if (rv != CKR_OK) {
			OBJ_REFRELE(object_p);
			SES_REFRELE(session_p, lock_held);
			return (rv);
		}
	}

	/* Acquire the lock on the object. */
	(void) pthread_mutex_lock(&object_p->object_mutex);

	for (i = 0; i < ulCount; i++) {
		/*
		 * Get the value of each attribute in the template.
		 * (We must process EVERY attribute in the template.)
		 */
		rv = soft_get_attribute(object_p, &pTemplate[i]);
		if (rv != CKR_OK)
			/* At least we catch some type of error. */
			rv1 = rv;
	}

	/* Release the object lock */
	(void) pthread_mutex_unlock(&object_p->object_mutex);

	/*
	 * Decrement the session reference count.
	 * We do not hold the session lock.
	 */
	OBJ_REFRELE(object_p);
	SES_REFRELE(session_p, lock_held);

	rv = rv1;
	return (rv);
}


CK_RV
C_SetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
    CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{
	CK_RV rv = CKR_OK;
	soft_object_t *object_p;
	soft_object_t *new_object = NULL;
	soft_session_t *session_p;
	boolean_t lock_held = B_FALSE;
	ulong_t i;

	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	/*
	 * Obtain the session pointer. Also, increment the session
	 * reference count.
	 */
	rv = handle2session(hSession, &session_p);
	if (rv != CKR_OK)
		return (rv);

	if ((pTemplate == NULL) || (ulCount == 0)) {
		/*
		 * Decrement the session reference count.
		 * We do not hold the session lock.
		 */
		SES_REFRELE(session_p, lock_held);
		return (CKR_ARGUMENTS_BAD);
	}

	/* Obtain the object pointer. */
	HANDLE2OBJECT(hObject, object_p, rv);
	if (rv != CKR_OK) {
		/*
		 * Decrement the session reference count.
		 * We do not hold the session lock.
		 */
		SES_REFRELE(session_p, lock_held);
		return (rv);
	}

	if (object_p->bool_attr_mask & NOT_MODIFIABLE_BOOL_ON) {
		rv = CKR_ATTRIBUTE_READ_ONLY;
		goto fail_1;
	}

	/*
	 * Start working on the object, so we need to set the write lock so that
	 * no one can write to it but still can read it.
	 */
	if (IS_TOKEN_OBJECT(object_p)) {
		rv = soft_keystore_load_latest_object(object_p);
		if (rv != CKR_OK) {
			goto fail_1;
		}
	}

	/*
	 * Copy the old object to a new object. We work on the copied
	 * version because in case of error we still keep the old one
	 * intact.
	 * The 3rd argument with SOFT_SET_ATTR_VALUE value indicates that
	 * not everything will be duplicated for C_SetAttributeValue.
	 * Information not duplicated are those attributes that are not
	 * modifiable.
	 */
	(void) pthread_mutex_lock(&object_p->object_mutex);
	rv = soft_copy_object(object_p, &new_object, SOFT_SET_ATTR_VALUE, NULL);

	if ((rv != CKR_OK) || (new_object == NULL)) {
		/* Most likely we ran out of space. */
		(void) pthread_mutex_unlock(&object_p->object_mutex);
		/*
		 * Decrement the session reference count.
		 * We do not hold the session lock.
		 */
		goto fail_1;
	}

	/*
	 * No need to hold the lock on the old object, because we
	 * will be working on the new scratch object.
	 */
	(void) pthread_mutex_unlock(&object_p->object_mutex);

	rv = soft_object_write_access_check(session_p, new_object);
	if (rv != CKR_OK) {
		goto fail;
	}

	for (i = 0; i < ulCount; i++) {
		/* Set the requested attribute into the new object. */
		rv = soft_set_attribute(new_object, &pTemplate[i], B_FALSE);

		if (rv != CKR_OK) {
			goto fail;
		}
	}

	/*
	 * We've successfully set all the requested attributes.
	 * Merge the new object with the old object, then destory
	 * the new one. The reason to do the merging is because we
	 * have to keep the original object handle (address of object).
	 */
	(void) pthread_mutex_lock(&object_p->object_mutex);

	soft_merge_object(object_p, new_object);

	/*
	 * The object has been modified, so we write it back to keystore.
	 */
	if (IS_TOKEN_OBJECT(object_p)) {
		object_p->version++;
		rv = soft_modify_object_to_keystore(object_p);
	}

	(void) pthread_mutex_unlock(&object_p->object_mutex);
	free(new_object);

	/*
	 * Decrement the session reference count.
	 * We do not hold the session lock.
	 */
	OBJ_REFRELE(object_p);
	SES_REFRELE(session_p, lock_held);
	return (rv);

fail:
	soft_cleanup_object(new_object);
	free(new_object);

fail_1:
	OBJ_REFRELE(object_p);
	SES_REFRELE(session_p, lock_held);

	return (rv);
}

/*ARGSUSED*/
CK_RV
C_GetObjectSize(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
    CK_ULONG_PTR pulSize)
{
	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	return (CKR_FUNCTION_NOT_SUPPORTED);
}

CK_RV
C_FindObjectsInit(CK_SESSION_HANDLE sh, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulCount)
{

	CK_RV		rv;
	soft_session_t	*session_p;
	boolean_t lock_held = B_TRUE;

	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	/*
	 * Obtain the session pointer. Also, increment the session
	 * reference count.
	 */
	rv = handle2session(sh, &session_p);
	if (rv != CKR_OK)
		return (rv);

	/* Check the arguments */
	if ((ulCount > 0) && (pTemplate == NULL)) {
		/* decrement the session count, we do not hold the lock */
		lock_held = B_FALSE;
		SES_REFRELE(session_p, lock_held);
		return (CKR_ARGUMENTS_BAD);
	}

	/* Acquire the session lock */
	(void) pthread_mutex_lock(&session_p->session_mutex);

	/* Check to see if find operation is already active */
	if (session_p->find_objects.flags & CRYPTO_OPERATION_ACTIVE) {
		/* decrement the session count, and unlock the mutex */
		SES_REFRELE(session_p, lock_held);
		return (CKR_OPERATION_ACTIVE);
	} else {
		/*
		 * This active flag will remain ON until application calls
		 * C_FindObjectsFinal.
		 */
		session_p->find_objects.flags = CRYPTO_OPERATION_ACTIVE;
	}

	(void) pthread_mutex_unlock(&session_p->session_mutex);

	rv = soft_find_objects_init(session_p,  pTemplate, ulCount);

	if (rv != CKR_OK) {
		(void) pthread_mutex_lock(&session_p->session_mutex);
		session_p->find_objects.flags = 0;
		(void) pthread_mutex_unlock(&session_p->session_mutex);
	}

	/* decrement the session count, and unlock the mutex */
	lock_held = B_FALSE;
	SES_REFRELE(session_p, lock_held);
	return (rv);
}

CK_RV
C_FindObjects(CK_SESSION_HANDLE sh,
    CK_OBJECT_HANDLE_PTR phObject,
    CK_ULONG ulMaxObjectCount,
    CK_ULONG_PTR pulObjectCount)
{
	soft_session_t	*session_p;
	CK_RV rv = CKR_OK;
	boolean_t lock_held = B_TRUE;

	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	/*
	 * Obtain the session pointer. Also, increment the session
	 * reference count.
	 */
	rv = handle2session(sh, &session_p);
	if (rv != CKR_OK)
		return (rv);

	/* check for invalid arguments */
	if (((phObject == NULL) && (ulMaxObjectCount != 0)) ||
	    (pulObjectCount == NULL)) {
		/* decrement the session count, we do not hold the lock */
		lock_held = B_FALSE;
		SES_REFRELE(session_p, lock_held);
		return (CKR_ARGUMENTS_BAD);
	}

	if (ulMaxObjectCount == 0) {
		/* don't need to do anything, just return */
		*pulObjectCount = 0;
		/* decrement the session count, we do not hold the lock */
		lock_held = B_FALSE;
		SES_REFRELE(session_p, lock_held);
		return (CKR_OK);
	}

	/* Acquire the session lock */
	(void) pthread_mutex_lock(&session_p->session_mutex);

	/* Check to see if find operation is active */
	if (!(session_p->find_objects.flags & CRYPTO_OPERATION_ACTIVE)) {
		SES_REFRELE(session_p, lock_held);
		return (CKR_OPERATION_NOT_INITIALIZED);
	}

	soft_find_objects(session_p, phObject, ulMaxObjectCount,
	    pulObjectCount);

	/* decrement the session count, and release the lock */
	SES_REFRELE(session_p, lock_held);
	return (rv);
}

CK_RV
C_FindObjectsFinal(CK_SESSION_HANDLE sh)
{
	soft_session_t	*session_p;
	CK_RV rv;
	boolean_t lock_held = B_TRUE;

	if (!softtoken_initialized)
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	/*
	 * Obtain the session pointer. Also, increment the session
	 * reference count.
	 */
	rv = handle2session(sh, &session_p);
	if (rv != CKR_OK)
		return (rv);

	/* Acquire the session lock */
	(void) pthread_mutex_lock(&session_p->session_mutex);

	/* Check to see if find operation is active */
	if (!(session_p->find_objects.flags & CRYPTO_OPERATION_ACTIVE)) {
		SES_REFRELE(session_p, lock_held);
		return (CKR_OPERATION_NOT_INITIALIZED);
	}

	soft_find_objects_final(session_p);

	/* decrement the session count, and release the lock */
	SES_REFRELE(session_p, lock_held);
	return (rv);
}