root/usr/src/lib/pkcs11/pkcs11_softtoken/common/softObjectUtil.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2020 Joyent, Inc.
 */

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

/*
 * Add an object to the session's object list.
 *
 * This function will acquire the lock on the session, and release
 * that lock after adding the object to the session's object list.
 */
void
soft_add_object_to_session(soft_object_t *objp, soft_session_t *sp)
{

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

        /* Insert the new object in front of session's object list. */
        if (sp->object_list == NULL) {
                sp->object_list = objp;
                objp->next = NULL;
                objp->prev = NULL;
        } else {
                sp->object_list->prev = objp;
                objp->next = sp->object_list;
                objp->prev = NULL;
                sp->object_list = objp;
        }

        /* Release the session lock. */
        (void) pthread_mutex_unlock(&sp->session_mutex);
}


/*
 * Clean up and release the storage allocated to the object.
 *
 * The function is called either with the object lock being held
 * (by caller soft_delete_object()), or there is no object lock
 * yet (by soft_build_XXX_object() during creating an object).
 */
void
soft_cleanup_object(soft_object_t *objp)
{
        /*
         * Free the storage allocated to big integer attributes.
         */
        soft_cleanup_object_bigint_attrs(objp);

        /*
         * Free the storage allocated to the extra attribute list.
         */
        soft_cleanup_extra_attr(objp);

        /*
         * Free the storage allocated to certificate attributes.
         */
        soft_cleanup_cert_object(objp);
}


/*
 * Create a new object. Copy the attributes that can be modified
 * (in the boolean attribute mask field and extra attribute list)
 * from the old object to the new object.
 *
 * The caller of this function holds the lock on the old object.
 */
CK_RV
soft_copy_object(soft_object_t *old_object, soft_object_t **new_object,
    CK_ULONG object_func, soft_session_t *sp)
{

        CK_RV rv = CKR_OK;
        soft_object_t *new_objp = NULL;
        CK_ATTRIBUTE_INFO_PTR attrp;

        /* Allocate new object. */
        new_objp = calloc(1, sizeof (soft_object_t));
        if (new_objp == NULL)
                return (CKR_HOST_MEMORY);

        new_objp->class = old_object->class;
        new_objp->bool_attr_mask = old_object->bool_attr_mask;
        new_objp->cert_type = old_object->cert_type;
        new_objp->object_type = old_object->object_type;

        attrp = old_object->extra_attrlistp;
        while (attrp) {
                /*
                 * Copy the attribute_info struct from the old
                 * object to a new attribute_info struct, and add
                 * that new struct to the extra attribute list
                 * of the new object.
                 */
                rv = soft_copy_extra_attr(attrp, new_objp);
                if (rv != CKR_OK) {
                        soft_cleanup_extra_attr(new_objp);
                        free(new_objp);
                        return (rv);
                }
                attrp = attrp->next;
        }

        *new_object = new_objp;

        if (object_func == SOFT_SET_ATTR_VALUE) {
                /* done with copying all information that can be modified */
                return (CKR_OK);
        }

        /*
         * Copy the rest of the object.
         * Certain fields that are not appropriate for coping will be
         * initialized.
         */
        new_objp->key_type = old_object->key_type;
        new_objp->magic_marker = old_object->magic_marker;
        new_objp->mechanism = old_object->mechanism;

        switch (object_func) {
        case SOFT_COPY_OBJ_ORIG_SH:
                new_objp->session_handle = old_object->session_handle;
                break;
        case SOFT_COPY_OBJECT:
                /*
                 * Save the session handle of the C_CopyObject function
                 * in the new copy of the session object.
                 */
                new_objp->session_handle = sp->handle;
                break;
        }

        (void) pthread_cond_init(&(new_objp->obj_free_cond), NULL);
        (void) pthread_mutex_init(&(new_objp->object_mutex), NULL);
        /* copy key related information */
        switch (new_objp->class) {
                case CKO_PUBLIC_KEY:
                        rv = soft_copy_public_key_attr(OBJ_PUB(old_object),
                            &(OBJ_PUB(new_objp)), new_objp->key_type);
                        break;
                case CKO_PRIVATE_KEY:
                        rv = soft_copy_private_key_attr(OBJ_PRI(old_object),
                            &(OBJ_PRI(new_objp)), new_objp->key_type);
                        break;
                case CKO_SECRET_KEY:
                        rv = soft_copy_secret_key_attr(OBJ_SEC(old_object),
                            &(OBJ_SEC(new_objp)));
                        break;
                case CKO_DOMAIN_PARAMETERS:
                        rv = soft_copy_domain_attr(OBJ_DOM(old_object),
                            &(OBJ_DOM(new_objp)), new_objp->key_type);
                        break;
                case CKO_CERTIFICATE:
                        rv = soft_copy_certificate(OBJ_CERT(old_object),
                            &(OBJ_CERT(new_objp)), new_objp->cert_type);
                        break;
                default:
                        /* should never be this case */
                        break;
        }
        if (rv != CKR_OK) {
                /*
                 * don't need to cleanup the memory from failure of copying
                 * any key related stuff.  Each individual function for
                 * copying key attr will free the memory if it fails
                 */
                soft_cleanup_extra_attr(new_objp);
                free(new_objp);
        }
        return (rv);
}


/*
 * Copy the attributes (in the boolean attribute mask field and
 * extra attribute list) from the new object back to the original
 * object. Also, clean up and release all the storage in the extra
 * attribute list of the original object.
 *
 * The caller of this function holds the lock on the old object.
 */
void
soft_merge_object(soft_object_t *old_object, soft_object_t *new_object)
{
        old_object->bool_attr_mask = new_object->bool_attr_mask;
        soft_cleanup_extra_attr(old_object);
        old_object->extra_attrlistp = new_object->extra_attrlistp;
}

/*
 * Sets *object_p to the soft_object_t corresponding to hObject. If
 * hObject is valid, and not in the deleting state, CKR_OK is returned,
 * otherwise an error is returned. If hObject is valid and refhold is B_TRUE,
 * the object is also held.
 */
CK_RV
handle2object(CK_OBJECT_HANDLE hObject, soft_object_t **object_p,
    boolean_t refhold)
{
        soft_object_t *obj;
        soft_object_t node;

        (void) memset(&node, 0, sizeof (node));
        node.handle = hObject;

        VERIFY0(pthread_mutex_lock(&soft_object_mutex));
        if ((obj = avl_find(&soft_object_tree, &node, NULL)) == NULL ||
            obj->magic_marker != SOFTTOKEN_OBJECT_MAGIC) {
                VERIFY0(pthread_mutex_unlock(&soft_object_mutex));
                return (CKR_OBJECT_HANDLE_INVALID);
        }

        (void) pthread_mutex_lock(&obj->object_mutex);
        VERIFY0(pthread_mutex_unlock(&soft_object_mutex));

        if (obj->obj_delete_sync & OBJECT_IS_DELETING) {
                (void) pthread_mutex_unlock(&obj->object_mutex);
                return (CKR_OBJECT_HANDLE_INVALID);
        }

        if (refhold)
                obj->obj_refcnt++;

        (void) pthread_mutex_unlock(&obj->object_mutex);

        *object_p = obj;
        return (CKR_OK);
}

CK_ULONG
set_objecthandle(soft_object_t *obj)
{
        avl_index_t where;

        (void) pthread_mutex_lock(&soft_object_mutex);

        do {
                arc4random_buf(&obj->handle, sizeof (obj->handle));
                if (obj->handle == CK_INVALID_HANDLE)
                        continue;
        } while (avl_find(&soft_object_tree, obj, &where) != NULL);

        avl_insert(&soft_object_tree, obj, where);

        (void) pthread_mutex_unlock(&soft_object_mutex);

        return (obj->handle);
}

/*
 * Create a new object struct, and add it to the session's object list.
 */
CK_RV
soft_add_object(CK_ATTRIBUTE_PTR pTemplate,  CK_ULONG ulCount,
    CK_OBJECT_HANDLE_PTR objecthandle_p, soft_session_t *sp)
{

        CK_RV rv = CKR_OK;
        soft_object_t *new_objp = NULL;

        new_objp = calloc(1, sizeof (soft_object_t));
        if (new_objp == NULL) {
                return (CKR_HOST_MEMORY);
        }

        new_objp->extra_attrlistp = NULL;

        /*
         * Validate attribute template and fill in the attributes
         * in the soft_object_t.
         */
        rv = soft_build_object(pTemplate, ulCount, new_objp);
        if (rv != CKR_OK) {
                goto fail_cleanup1;
        }

        rv = soft_pin_expired_check(new_objp);
        if (rv != CKR_OK) {
                goto fail_cleanup2;
        }

        rv = soft_object_write_access_check(sp, new_objp);
        if (rv != CKR_OK) {
                goto fail_cleanup2;
        }

        /* Initialize the rest of stuffs in soft_object_t. */
        (void) pthread_cond_init(&new_objp->obj_free_cond, NULL);
        (void) pthread_mutex_init(&new_objp->object_mutex, NULL);
        new_objp->magic_marker = SOFTTOKEN_OBJECT_MAGIC;
        new_objp->obj_refcnt = 0;
        new_objp->obj_delete_sync = 0;

        /* Write the new token object to the keystore */
        if (IS_TOKEN_OBJECT(new_objp)) {
                if (!soft_keystore_status(KEYSTORE_INITIALIZED)) {
                        rv = CKR_DEVICE_REMOVED;
                        goto fail_cleanup2;
                }
                new_objp->version = 1;
                rv = soft_put_object_to_keystore(new_objp);
                if (rv != CKR_OK) {
                        (void) pthread_cond_destroy(&new_objp->obj_free_cond);
                        (void) pthread_mutex_destroy(&new_objp->object_mutex);
                        goto fail_cleanup2;
                }
                new_objp->session_handle = CK_INVALID_HANDLE;
                soft_add_token_object_to_slot(new_objp);

                *objecthandle_p = set_objecthandle(new_objp);

                return (CKR_OK);
        }

        new_objp->session_handle = sp->handle;

        /* Add the new object to the session's object list. */
        soft_add_object_to_session(new_objp, sp);

        *objecthandle_p = set_objecthandle(new_objp);

        return (CKR_OK);

fail_cleanup2:
        /*
         * When any error occurs after soft_build_object(), we will need to
         * clean up the memory allocated by the soft_build_object().
         */
        soft_cleanup_object(new_objp);

fail_cleanup1:
        if (new_objp) {
                /*
                 * The storage allocated inside of this object should have
                 * been cleaned up by the soft_build_object() if it failed.
                 * Therefore, we can safely free the object.
                 */
                free(new_objp);
        }

        return (rv);

}


/*
 * Remove an object from the session's object list.
 *
 * The caller of this function holds the session lock.
 */
CK_RV
soft_remove_object_from_session(soft_object_t *objp, soft_session_t *sp)
{
        soft_object_t *tmp_objp;
        boolean_t found = B_FALSE;

        /*
         * Remove the object from the session's object list.
         */
        if ((sp == NULL) ||
            (sp->magic_marker != SOFTTOKEN_SESSION_MAGIC)) {
                return (CKR_SESSION_HANDLE_INVALID);
        }

        if ((sp->object_list == NULL) || (objp == NULL) ||
            (objp->magic_marker != SOFTTOKEN_OBJECT_MAGIC)) {
                return (CKR_OBJECT_HANDLE_INVALID);
        }

        tmp_objp = sp->object_list;
        while (tmp_objp) {
                if (tmp_objp == objp) {
                        found = B_TRUE;
                        break;
                }
                tmp_objp = tmp_objp->next;
        }
        if (!found)
                return (CKR_OBJECT_HANDLE_INVALID);

        if (sp->object_list == objp) {
                /* Object is the first one in the list. */
                if (objp->next) {
                        sp->object_list = objp->next;
                        objp->next->prev = NULL;
                } else {
                        /* Object is the only one in the list. */
                        sp->object_list = NULL;
                }
        } else {
                /* Object is not the first one in the list. */
                if (objp->next) {
                        /* Object is in the middle of the list. */
                        objp->prev->next = objp->next;
                        objp->next->prev = objp->prev;
                } else {
                        /* Object is the last one in the list. */
                        objp->prev->next = NULL;
                }
        }
        return (CKR_OK);
}

/*
 * This function adds the to-be-freed session object to a linked list.
 * When the number of objects queued in the linked list reaches the
 * maximum threshold MAX_OBJ_TO_BE_FREED, it will free the first
 * object (FIFO) in the list.
 */
void
object_delay_free(soft_object_t *objp)
{
        soft_object_t *tmp;

        (void) pthread_mutex_lock(&obj_delay_freed.obj_to_be_free_mutex);

        /* Add the newly deleted object at the end of the list */
        objp->next = NULL;
        if (obj_delay_freed.first == NULL) {
                obj_delay_freed.last = objp;
                obj_delay_freed.first = objp;
        } else {
                obj_delay_freed.last->next = objp;
                obj_delay_freed.last = objp;
        }

        if (++obj_delay_freed.count >= MAX_OBJ_TO_BE_FREED) {
                /*
                 * Free the first object in the list only if
                 * the total count reaches maximum threshold.
                 */
                obj_delay_freed.count--;
                tmp = obj_delay_freed.first->next;
                free(obj_delay_freed.first);
                obj_delay_freed.first = tmp;
        }
        (void) pthread_mutex_unlock(&obj_delay_freed.obj_to_be_free_mutex);
}

static void
soft_delete_object_cleanup(soft_object_t *objp, boolean_t force)
{
        /* Acquire the lock on the object. */
        (void) pthread_mutex_lock(&objp->object_mutex);

        /*
         * Make sure another thread hasn't freed the object.
         */
        if (objp->magic_marker != SOFTTOKEN_OBJECT_MAGIC) {
                (void) pthread_mutex_unlock(&objp->object_mutex);
                return;
        }

        /*
         * The deletion of an object must be blocked when the object
         * reference count is not zero. This means if any object related
         * operation starts prior to the delete object operation gets in,
         * the object deleting thread must wait for the non-deleting
         * operation to be completed before it can proceed the delete
         * operation.
         *
         * Unless we are being forced to shut everything down, this only
         * happens if the libraries _fini() is running not of someone
         * explicitly called C_Finalize().
         */
        if (force)
                objp->obj_refcnt = 0;

        while (objp->obj_refcnt != 0) {
                /*
                 * We set the OBJECT_REFCNT_WAITING flag before we put
                 * this deleting thread in a wait state, so other non-deleting
                 * operation thread will signal to wake it up only when
                 * the object reference count becomes zero and this flag
                 * is set.
                 */
                objp->obj_delete_sync |= OBJECT_REFCNT_WAITING;
                (void) pthread_cond_wait(&objp->obj_free_cond,
                    &objp->object_mutex);
        }

        objp->obj_delete_sync &= ~OBJECT_REFCNT_WAITING;

        /* Mark object as no longer valid. */
        objp->magic_marker = 0;

        (void) pthread_cond_destroy(&objp->obj_free_cond);

        /*
         * Cleanup the contents of this object such as free all the
         * storage allocated for this object.
         */
        soft_cleanup_object(objp);

        (void) pthread_mutex_lock(&soft_object_mutex);
        avl_remove(&soft_object_tree, objp);
        (void) pthread_mutex_unlock(&soft_object_mutex);

        /* Reset OBJECT_IS_DELETING flag. */
        objp->obj_delete_sync &= ~OBJECT_IS_DELETING;

        (void) pthread_mutex_unlock(&objp->object_mutex);
        /* Destroy the object lock */
        (void) pthread_mutex_destroy(&objp->object_mutex);

        /* Free the object itself */
        if (IS_TOKEN_OBJECT(objp))
                free(objp);
        else
                /*
                 * Delay freeing the session object as S1WS/NSS uses session
                 * objects for its SSL Handshake.
                 */
                (void) object_delay_free(objp);
}

/*
 * Delete an object:
 * - Remove the object from the session's object list.
 *   Holding the lock on the session which the object was created at
 *   is needed to do this.
 * - Release the storage allocated to the object.
 *
 * The boolean argument lock_held is used to indicate that whether
 * the caller holds the session lock or not.
 * - When called by soft_delete_all_objects_in_session() -- the
 *   lock_held = TRUE.
 *
 * When the caller does not hold the session lock, this function
 * will acquire that lock in order to proceed, and also release
 * that lock before returning to caller.
 */
void
soft_delete_object(soft_session_t *sp, soft_object_t *objp,
    boolean_t force, boolean_t lock_held)
{

        /*
         * Check to see if the caller holds the lock on the session.
         * If not, we need to acquire that lock in order to proceed.
         */
        if (!lock_held) {
                /* Acquire the session lock. */
                (void) pthread_mutex_lock(&sp->session_mutex);
        }

        /* Remove the object from the session's object list first. */
        if (soft_remove_object_from_session(objp, sp) != CKR_OK) {
                if (!lock_held) {
                        (void) pthread_mutex_unlock(&sp->session_mutex);
                }
                return;
        }

        if (!lock_held) {
                /*
                 * If the session lock is obtained by this function,
                 * then release that lock after removing the object
                 * from session's object list.
                 * We want the releasing of the object storage to
                 * be done without holding the session lock.
                 */
                (void) pthread_mutex_unlock(&sp->session_mutex);
        }

        soft_delete_object_cleanup(objp, force);
}


/*
 * Delete all the objects in a session. The caller holds the lock
 * on the session.
 */
void
soft_delete_all_objects_in_session(soft_session_t *sp, boolean_t force)
{
        soft_object_t *objp = sp->object_list;
        soft_object_t *objp1;

        /* Delete all the objects in the session. */
        while (objp) {
                objp1 = objp->next;

                /*
                 * Delete an object by calling soft_delete_object()
                 * with a TRUE boolean argument indicating that
                 * the caller holds the lock on the session.
                 */
                soft_delete_object(sp, objp, force, B_TRUE);

                objp = objp1;
        }
}

static CK_RV
add_to_search_result(soft_object_t *obj, find_context_t *fcontext,
    CK_ULONG *num_result_alloc)
{
        /*
         * allocate space for storing results if the currently
         * allocated space is not enough
         */
        if (*num_result_alloc <= fcontext->num_results) {
                fcontext->objs_found = realloc(fcontext->objs_found,
                    sizeof (soft_object_t *) * (*num_result_alloc + BUFSIZ));
                if (fcontext->objs_found == NULL) {
                        return (CKR_HOST_MEMORY);
                }
                *num_result_alloc += BUFSIZ;
        }

        (fcontext->objs_found)[(fcontext->num_results)++] = obj;
        return (CKR_OK);
}

static CK_RV
search_for_objects(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount,
    find_context_t *fcontext)
{
        soft_session_t *session_p;
        soft_object_t *obj;
        CK_OBJECT_CLASS pclasses[6]; /* classes attrs possibly exist */
        CK_ULONG num_pclasses;  /* number of possible classes */
        CK_ULONG num_result_alloc = 0; /* spaces allocated for results */
        CK_RV rv = CKR_OK;
        /* whether CKA_TOKEN flag specified or not */
        boolean_t token_specified = B_FALSE;
        /* value of CKA_TOKEN flag, if specified */
        boolean_t token_flag_val = B_FALSE;
        CK_ULONG i;

        if (ulCount > 0) {
                /* there are some search requirement */
                soft_process_find_attr(pclasses, &num_pclasses,
                    pTemplate, ulCount);
        }

        for (i = 0; i < ulCount; i++) {
                if (pTemplate[i].type == CKA_PRIVATE) {
                        (void) pthread_mutex_lock(&soft_giant_mutex);
                        if (soft_slot.userpin_change_needed) {
                                (void) pthread_mutex_unlock(&soft_giant_mutex);
                                return (CKR_PIN_EXPIRED);
                        }
                        (void) pthread_mutex_unlock(&soft_giant_mutex);
                }
        }

        /*
         * look through template and see if it explicitly specifies
         * whether we need to look for token objects or not
         */
        for (i = 0; i < ulCount; i++) {
                if (pTemplate[i].type == CKA_TOKEN) {
                        token_specified = B_TRUE;
                        token_flag_val = *((CK_BBOOL *)pTemplate[i].pValue);
                        break;
                }
        }

        /*
         * Need go through token objects if it explicitly say so, or
         * it is not mentioned in the template.  And this will ONLY be
         * done when the keystore exists. Otherwise, we will skip re-loading
         * the token objects.
         *
         * If a session has not logged into the token, only public
         * objects, if any, will be searched.  If a session is logged
         * into the token, all public and private objects in the keystore
         * are searched.
         */
        if (((token_flag_val) || (!token_specified)) &&
            soft_keystore_status(KEYSTORE_INITIALIZED)) {
                /* acquire token session lock */
                (void) pthread_mutex_lock(&soft_slot.slot_mutex);
                rv = refresh_token_objects();
                if (rv != CKR_OK) {
                        (void) pthread_mutex_unlock(&soft_slot.slot_mutex);
                        return (rv);
                }
                obj = soft_slot.token_object_list;
                while (obj) {
                        (void) pthread_mutex_lock(&obj->object_mutex);
                        if (((token_specified) && (ulCount > 1)) ||
                            ((!token_specified) && (ulCount > 0))) {
                                if (soft_find_match_attrs(obj, pclasses,
                                    num_pclasses, pTemplate, ulCount)) {
                                        rv = add_to_search_result(
                                            obj, fcontext, &num_result_alloc);
                                }
                        } else {
                                /* no search criteria, just record the object */
                                rv = add_to_search_result(obj, fcontext,
                                    &num_result_alloc);
                        }
                        (void) pthread_mutex_unlock(&obj->object_mutex);
                        if (rv != CKR_OK) {
                                (void) pthread_mutex_unlock
                                    (&soft_slot.slot_mutex);
                                return (rv);
                        }
                        obj = obj->next;
                }
                (void) pthread_mutex_unlock(&soft_slot.slot_mutex);
        }

        if (token_flag_val) {
                /* no need to look through session objects */
                return (rv);
        }

        /* Acquire the global session list lock */
        (void) pthread_mutex_lock(&soft_sessionlist_mutex);

        /*
         * Go through all objects in each session.
         * Acquire individual session lock for the session
         * we are searching.
         */
        session_p = soft_session_list;
        while (session_p) {
                (void) pthread_mutex_lock(&session_p->session_mutex);

                obj = session_p->object_list;
                while (obj) {
                        (void) pthread_mutex_lock(&obj->object_mutex);
                        if (ulCount > 0) {
                                if (soft_find_match_attrs(obj, pclasses,
                                    num_pclasses, pTemplate, ulCount)) {
                                        rv = add_to_search_result(
                                            obj, fcontext, &num_result_alloc);
                                }
                        } else {
                                /* no search criteria, just record the object */
                                rv = add_to_search_result(obj, fcontext,
                                    &num_result_alloc);
                        }
                        (void) pthread_mutex_unlock(&obj->object_mutex);
                        if (rv != CKR_OK) {
                                (void) pthread_mutex_unlock(
                                    &session_p->session_mutex);
                                goto cleanup;
                        }
                        obj = obj->next;
                }
                (void) pthread_mutex_unlock(&session_p->session_mutex);
                session_p = session_p->next;
        }

cleanup:
        /* Release the global session list lock */
        (void) pthread_mutex_unlock(&soft_sessionlist_mutex);
        return (rv);
}

/*
 * Initialize the context for C_FindObjects() calls
 */
CK_RV
soft_find_objects_init(soft_session_t *sp, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulCount)
{

        CK_RV rv = CKR_OK;
        CK_OBJECT_CLASS class; /* for soft_validate_attr(). Value unused */
        find_context_t *fcontext;

        if (ulCount) {
                rv = soft_validate_attr(pTemplate, ulCount, &class);
                /* Make sure all attributes in template are valid */
                if (rv != CKR_OK) {
                        return (rv);
                }
        }


        /* prepare the find context */
        fcontext = calloc(1, sizeof (find_context_t));
        if (fcontext == NULL) {
                return (CKR_HOST_MEMORY);
        }

        rv = search_for_objects(pTemplate, ulCount, fcontext);
        if (rv != CKR_OK) {
                free(fcontext);
                return (rv);
        }

        /* store the find_context in the session */
        sp->find_objects.context = (CK_VOID_PTR)fcontext;

        return (rv);
}

void
soft_find_objects_final(soft_session_t *sp)
{
        find_context_t *fcontext;

        fcontext = sp->find_objects.context;
        sp->find_objects.context = NULL;
        sp->find_objects.flags = 0;
        if (fcontext->objs_found != NULL) {
                free(fcontext->objs_found);
        }

        free(fcontext);
}

void
soft_find_objects(soft_session_t *sp, CK_OBJECT_HANDLE *obj_found,
    CK_ULONG max_obj_requested, CK_ULONG *found_obj_count)
{
        find_context_t *fcontext;
        CK_ULONG num_obj_found = 0;
        CK_ULONG i;
        soft_object_t *obj;

        fcontext = sp->find_objects.context;

        for (i = fcontext->next_result_index;
            ((num_obj_found < max_obj_requested) &&
            (i < fcontext->num_results));
            i++) {
                obj = fcontext->objs_found[i];
                if (obj != NULL) {
                        (void) pthread_mutex_lock(&obj->object_mutex);
                        /* a sanity check to make sure the obj is still valid */
                        if (obj->magic_marker == SOFTTOKEN_OBJECT_MAGIC) {
                                obj_found[num_obj_found] = obj->handle;
                                num_obj_found++;
                        }
                        (void) pthread_mutex_unlock(&obj->object_mutex);
                }
        }
        fcontext->next_result_index = i;
        *found_obj_count = num_obj_found;
}

/*
 * Below are the token object related functions
 */
void
soft_add_token_object_to_slot(soft_object_t *objp)
{

        (void) pthread_mutex_lock(&soft_slot.slot_mutex);

        /* Insert the new object in front of slot's token object list. */
        if (soft_slot.token_object_list == NULL) {
                soft_slot.token_object_list = objp;
                objp->next = NULL;
                objp->prev = NULL;
        } else {
                soft_slot.token_object_list->prev = objp;
                objp->next = soft_slot.token_object_list;
                objp->prev = NULL;
                soft_slot.token_object_list = objp;
        }

        (void) pthread_mutex_unlock(&soft_slot.slot_mutex);

}

void
soft_remove_token_object_from_slot(soft_object_t *objp, boolean_t lock_held)
{

        if (!lock_held)
                (void) pthread_mutex_lock(&soft_slot.slot_mutex);

        /*
         * Remove the object from the slot's token object list.
         */
        if (soft_slot.token_object_list == objp) {
                /* Object is the first one in the list. */
                if (objp->next) {
                        soft_slot.token_object_list = objp->next;
                        objp->next->prev = NULL;
                } else {
                        /* Object is the only one in the list. */
                        soft_slot.token_object_list = NULL;
                }
        } else {
                /* Object is not the first one in the list. */
                if (objp->next) {
                        /* Object is in the middle of the list. */
                        objp->prev->next = objp->next;
                        objp->next->prev = objp->prev;
                } else {
                        /* Object is the last one in the list. */
                        objp->prev->next = NULL;
                }
        }

        if (!lock_held)
                (void) pthread_mutex_unlock(&soft_slot.slot_mutex);
}

void
soft_delete_token_object(soft_object_t *objp, boolean_t persistent,
    boolean_t lock_held)
{

        if (!lock_held)
                (void) pthread_mutex_lock(&soft_slot.slot_mutex);
        if (persistent)
                /* Delete the object from the keystore. */
                (void) soft_keystore_del_obj(&objp->ks_handle, B_FALSE);

        /* Remove the object from the slot's token object list. */
        soft_remove_token_object_from_slot(objp, B_TRUE);
        if (!lock_held)
                (void) pthread_mutex_unlock(&soft_slot.slot_mutex);

        soft_delete_object_cleanup(objp, B_FALSE);
}

void
soft_delete_all_in_core_token_objects(token_obj_type_t type)
{

        soft_object_t *objp;
        soft_object_t *objp1;

        (void) pthread_mutex_lock(&soft_slot.slot_mutex);
        objp = soft_slot.token_object_list;

        switch (type) {
        case PRIVATE_TOKEN:
                while (objp) {
                        objp1 = objp->next;
                        if (objp->object_type == TOKEN_PRIVATE) {
                                soft_delete_token_object(objp, B_FALSE, B_TRUE);
                        }
                        objp = objp1;
                }
                break;

        case PUBLIC_TOKEN:
                while (objp) {
                        objp1 = objp->next;
                        if (objp->object_type == TOKEN_PUBLIC) {
                                soft_delete_token_object(objp, B_FALSE, B_TRUE);
                        }
                        objp = objp1;
                }
                break;

        case ALL_TOKEN:
                while (objp) {
                        objp1 = objp->next;
                        soft_delete_token_object(objp, B_FALSE, B_TRUE);
                        objp = objp1;
                }
                break;
        }

        (void) pthread_mutex_unlock(&soft_slot.slot_mutex);

}

/*
 * Mark all the token objects in the global list to be valid.
 */
void
soft_validate_token_objects(boolean_t validate)
{

        soft_object_t *objp;

        (void) pthread_mutex_lock(&soft_slot.slot_mutex);

        objp = soft_slot.token_object_list;

        while (objp) {
                if (validate)
                        objp->magic_marker = SOFTTOKEN_OBJECT_MAGIC;
                else
                        objp->magic_marker = 0;

                objp = objp->next;
        }

        (void) pthread_mutex_unlock(&soft_slot.slot_mutex);

}

/*
 * Verify user's write access rule to the token object.
 */
CK_RV
soft_object_write_access_check(soft_session_t *sp, soft_object_t *objp)
{

        /*
         * This function is called by C_CreateObject, C_CopyObject,
         * C_DestroyObject, C_SetAttributeValue, C_GenerateKey,
         * C_GenerateKeyPairs, C_DeriveKey. All of them will write
         * the token object to the keystore.
         */
        (void) pthread_mutex_lock(&soft_giant_mutex);
        if (!soft_slot.authenticated) {
                (void) pthread_mutex_unlock(&soft_giant_mutex);
                /* User is not logged in */
                if (sp->flags & CKF_RW_SESSION) {
                        /*
                         * For R/W Public Session:
                         * we allow write access to public session or token
                         * object, but not for private token/session object.
                         */
                        if ((objp->object_type == TOKEN_PRIVATE) ||
                            (objp->object_type == SESSION_PRIVATE)) {
                                return (CKR_USER_NOT_LOGGED_IN);
                        }
                } else {
                        /*
                         * For R/O Public Session:
                         * we allow write access to public session object.
                         */
                        if (objp->object_type != SESSION_PUBLIC)
                                return (CKR_SESSION_READ_ONLY);
                }
        } else {
                (void) pthread_mutex_unlock(&soft_giant_mutex);
                /* User is logged in */
                if (!(sp->flags & CKF_RW_SESSION)) {
                        /*
                         * For R/O User Function Session:
                         * we allow write access to public or private
                         * session object, but not for public or private
                         * token object.
                         */
                        if ((objp->object_type == TOKEN_PUBLIC) ||
                            (objp->object_type == TOKEN_PRIVATE)) {
                                return (CKR_SESSION_READ_ONLY);
                        }
                }
        }

        return (CKR_OK);
}

/*
 * Verify if user is required to setpin when accessing the
 * private token/session object.
 */
CK_RV
soft_pin_expired_check(soft_object_t *objp)
{

        /*
         * This function is called by C_CreateObject, C_CopyObject,
         * C_DestroyObject, C_GenerateKey,
         * C_GenerateKeyPairs, C_DeriveKey.
         * All of them will return CKR_PIN_EXPIRED if the
         * "userpin_change_needed" is set.
         *
         * The following functions will not be necessary to call
         * this routine even though CKR_PIN_EXPIRED is one of the
         * valid error code they might return. These functions are:
         * C_EncryptInit, C_DecryptInit, C_DigestInit, C_SignInit,
         * C_SignRecoverInit, C_VerifyInit, C_VerifyRecoverInit.
         * This is because they will not get the object handle
         * before the above functions are called.
         */

        (void) pthread_mutex_lock(&soft_giant_mutex);
        if (soft_slot.userpin_change_needed) {
                /*
                 * Access private token/session object but user's
                 * PIN is expired or never set.
                 */
                if ((objp->object_type == TOKEN_PRIVATE) ||
                    (objp->object_type == SESSION_PRIVATE)) {
                        (void) pthread_mutex_unlock(&soft_giant_mutex);
                        return (CKR_PIN_EXPIRED);
                }
        }

        (void) pthread_mutex_unlock(&soft_giant_mutex);
        return (CKR_OK);
}

/*
 * Copy the selected fields from new token object to old
 * token object.
 */
CK_RV
soft_copy_to_old_object(soft_object_t *new, soft_object_t *old)
{

        CK_RV rv = CKR_OK;
        CK_ATTRIBUTE_INFO_PTR attrp;

        old->class = new->class;
        old->bool_attr_mask = new->bool_attr_mask;
        soft_cleanup_extra_attr(old);
        attrp = new->extra_attrlistp;
        while (attrp) {
                rv = soft_copy_extra_attr(attrp, old);
                if (rv != CKR_OK) {
                        soft_cleanup_extra_attr(old);
                        return (rv);
                }
                attrp = attrp->next;
        }

        /* Done with copying all information that can be modified */
        return (CKR_OK);
}

/*
 * Update an existing object with new data from keystore.
 */
CK_RV
soft_update_object(ks_obj_t *ks_obj, soft_object_t *old_obj)
{

        soft_object_t *new_object;
        CK_RV rv;

        new_object = calloc(1, sizeof (soft_object_t));
        if (new_object == NULL)
                return (CKR_HOST_MEMORY);

        rv = soft_keystore_unpack_obj(new_object, ks_obj);
        if (rv != CKR_OK) {
                soft_cleanup_object(new_object);
                free(new_object);
                return (rv);
        }
        rv = soft_copy_to_old_object(new_object, old_obj);

        soft_cleanup_object(new_object);
        free(new_object);
        return (CKR_OK);
}


CK_RV
soft_keystore_load_latest_object(soft_object_t *old_obj)
{

        uint_t version;
        ks_obj_t *ks_obj = NULL;
        CK_RV rv = CKR_OK;

        /*
         * Get the current version number from the keystore for
         * the specified token object.
         */
        if (soft_keystore_get_object_version(&old_obj->ks_handle, &version,
            B_FALSE) == 1)
                return (CKR_FUNCTION_FAILED);

        /*
         * If the keystore version is newer than the in-core version,
         * re-read the token object from the keystore.
         */
        if (old_obj->version != version) {
                rv = soft_keystore_get_single_obj(&old_obj->ks_handle,
                    &ks_obj, B_FALSE);
                if (rv != CKR_OK)
                        return (rv);
                old_obj->version = version;

                /*
                 * Update an existing object with new data from keystore.
                 */
                rv = soft_update_object(ks_obj, old_obj);
                free(ks_obj->buf);
                free(ks_obj);
        }

        return (rv);
}

/*
 * Insert an object into a list of soft_object_t objects.  It is assumed
 * that the object to be inserted doesn't previously belong to any list
 */
static void
insert_into_list(soft_object_t **list, soft_object_t **end_of_list,
    soft_object_t *objp)
{
        if (*list == NULL) {
                *list = objp;
                objp->next = NULL;
                objp->prev = NULL;
                *end_of_list = objp;
        } else {
                (*list)->prev = objp;
                objp->next = *list;
                objp->prev = NULL;
                *list = objp;
        }
}

/*
 * Move an object from an existing list into a new list of
 * soft_object_t objects.
 */
static void
move_into_list(soft_object_t **existing_list, soft_object_t **new_list,
    soft_object_t **end_of_list, soft_object_t *objp)
{

        /* first, remove object from existing list */
        if (objp == *existing_list) {
                /* first item in list */
                if (objp->next) {
                        *existing_list = objp->next;
                        objp->next->prev = NULL;
                } else {
                        *existing_list = NULL;
                }
        } else {
                if (objp->next) {
                        objp->prev->next = objp->next;
                        objp->next->prev = objp->prev;
                } else {
                        objp->prev->next = NULL;
                }
        }

        /* then, add into new list */
        insert_into_list(new_list, end_of_list, objp);
}

/*
 * Insert "new_list" into "existing_list", new list will always be inserted
 * into the front of existing list
 */
static void
insert_list_into_list(soft_object_t **existing_list,
    soft_object_t *new_list, soft_object_t *end_new_list)
{

        if (new_list == NULL) {
                return;
        }

        if (*existing_list == NULL) {
                *existing_list = new_list;
        } else {
                (*existing_list)->prev = end_new_list;
                end_new_list->next = *existing_list;
                *existing_list = new_list;
        }
}

static void
delete_all_objs_in_list(soft_object_t *list)
{
        soft_object_t *objp, *objp_next;

        if (list == NULL) {
                return;
        }

        objp = list;
        while (objp) {
                objp_next = objp->next;
                soft_delete_object_cleanup(objp, B_FALSE);
                objp = objp_next;
        }
}

/*
 * Makes sure that the list of in-core token objects are up to date
 * with respect to the on disk keystore.  Other process/applications
 * might have modified the keystore since the objects are last loaded
 *
 * If there's any error from refreshing the token object list (eg: unable
 * to read, unable to unpack and object...etc), the in-core list
 * will be restored back to the state before the refresh.  An error
 * will be returned to indicate the failure.
 *
 * It is assumed that the caller holds the lock for the token slot
 */
CK_RV
refresh_token_objects()
{
        uint_t on_disk_ks_version;
        ks_obj_t *on_disk_list = NULL, *tmp_on_disk, *next_on_disk;
        soft_object_t *in_core_obj, *tmp_incore_obj, *new_objp = NULL;
        CK_RV rv = CKR_OK;

        /* deleted in-core objects */
        soft_object_t *del_objs_list = NULL;
        soft_object_t *end_del_objs_list = NULL;

        /* modified in-core objects */
        soft_object_t *mod_objs_list = NULL;
        soft_object_t *end_mod_objs_list = NULL;

        /*
         * copy of modified in-core objects, in case we need
         * undo the change
         */
        soft_object_t *copy_of_mod_objs_list = NULL;
        soft_object_t *end_copy_of_mod_objs_list = NULL;

        /* objects to be added to the in-core list */
        soft_object_t *added_objs_list = NULL;
        soft_object_t *end_added_objs_list = NULL;

        if (soft_keystore_get_version(&on_disk_ks_version, B_FALSE) != 0) {
                return (CKR_FUNCTION_FAILED);
        }

        (void) pthread_mutex_lock(&soft_giant_mutex);
        if (on_disk_ks_version == soft_slot.ks_version) {
                /* no change */
                (void) pthread_mutex_unlock(&soft_giant_mutex);
                return (CKR_OK);
        }

        if (soft_slot.authenticated) {
                /* get both public and private objects */
                (void) pthread_mutex_unlock(&soft_giant_mutex);
                rv = soft_keystore_get_objs(ALL_TOKENOBJS, &on_disk_list,
                    B_FALSE);
        } else {
                /* get both public objects only */
                (void) pthread_mutex_unlock(&soft_giant_mutex);
                rv = soft_keystore_get_objs(PUB_TOKENOBJS, &on_disk_list,
                    B_FALSE);
        }
        if (rv != CKR_OK) {
                return (rv);
        }

        /*
         * The in-core tokens list will be updated as follows:
         *
         * Go through each item in the in-core tokens list.
         * Try to match the in-core object with one of the
         * objects from the on-disk list.  If a match is made,
         * check the version number, and update in-core object
         * as necessary.
         *
         * If there's no match between in-core object with on-disk
         * object, that means the object is deleted since
         * last loaded.  Will remove object from in-core list.
         *
         * When doing the matching of on-disk object list above,
         * Delete every matched on-disk object from the on-disk list
         * regardless the in-core object need to be deleted or not
         *
         * At the end of matching the in-core tokens list, if
         * any object is still left on the on-disk object list,
         * those are all new objects added since last load,
         * include all of them to the in-core list
         *
         * Since we need to be able to revert the in-core list
         * back to original state if there's any error with the refresh,
         * we need to do the following.
         * When an in-core object is "deleted", it is not immediately
         * deleted.  It is moved to the list of "deleted_objects".
         * When an in-core object is "modified", a copy of the
         * unmodified object is made.  After the object is modified,
         * it is temporarily moved to the "mod_objects" list
         * from the in-core list.
         * When the refresh is completed without any error,
         * the actual deleted objects and unmodified objects is deleted.
         */
        in_core_obj = soft_slot.token_object_list;
        while (in_core_obj) {
                /* try to match object with on_disk_list */
                ks_obj_t *ondisk_obj, *prev_ondisk_obj;
                boolean_t found = B_FALSE;
                soft_object_t *obj_copy;

                ondisk_obj = on_disk_list;
                prev_ondisk_obj = NULL;

                /* larval object that has not been written to disk */
                if (in_core_obj->ks_handle.name[0] == '\0') {
                        in_core_obj = in_core_obj->next;
                        continue;
                }

                while ((!found) && (ondisk_obj != NULL)) {

                        if (strcmp((char *)((ondisk_obj->ks_handle).name),
                            (char *)((in_core_obj->ks_handle).name)) == 0) {

                                /* found a match */
                                found = B_TRUE;

                                /* update in-core obj if necessary */
                                if (ondisk_obj->obj_version !=
                                    in_core_obj->version) {
                                        /* make a copy of before updating */
                                        rv = soft_copy_object(in_core_obj,
                                            &obj_copy, SOFT_COPY_OBJ_ORIG_SH,
                                            NULL);
                                        if (rv != CKR_OK) {
                                                goto cleanup;
                                        }
                                        insert_into_list(
                                            &copy_of_mod_objs_list,
                                            &end_copy_of_mod_objs_list,
                                            obj_copy);

                                        rv = soft_update_object(ondisk_obj,
                                            in_core_obj);
                                        if (rv != CKR_OK) {
                                                goto cleanup;
                                        }
                                        move_into_list(
                                            &(soft_slot.token_object_list),
                                            &mod_objs_list, &end_mod_objs_list,
                                            in_core_obj);
                                }

                                /* remove processed obj from on disk list */
                                if (ondisk_obj == on_disk_list) {
                                        /* first item */
                                        on_disk_list = ondisk_obj->next;
                                } else {
                                        prev_ondisk_obj->next =
                                            ondisk_obj->next;
                                }
                                free(ondisk_obj->buf);
                                free(ondisk_obj);
                        } else {
                                prev_ondisk_obj = ondisk_obj;
                                ondisk_obj = ondisk_obj->next;
                        }
                }

                if (!found) {
                        tmp_incore_obj = in_core_obj->next;
                        move_into_list(&(soft_slot.token_object_list),
                            &del_objs_list, &end_del_objs_list, in_core_obj);
                        in_core_obj = tmp_incore_obj;
                } else {
                        in_core_obj = in_core_obj->next;
                }
        }

        /*
         * At this point, if there's still anything on the on_disk_list, they
         * are all newly added objects since in-core list last loaded.
         * include all of them into the in-core list
         */
        next_on_disk = on_disk_list;
        while (next_on_disk) {
                new_objp = calloc(1, sizeof (soft_object_t));
                if (new_objp == NULL) {
                        rv = CKR_HOST_MEMORY;
                        goto cleanup;
                }

                /* Convert the keystore format to memory format */
                rv = soft_keystore_unpack_obj(new_objp, next_on_disk);
                if (rv != CKR_OK) {
                        soft_cleanup_object(new_objp);
                        free(new_objp);
                        goto cleanup;
                }

                insert_into_list(&added_objs_list, &end_added_objs_list,
                    new_objp);

                /* free the on_disk object */
                tmp_on_disk = next_on_disk;
                next_on_disk = tmp_on_disk->next;
                free(tmp_on_disk->buf);
                free(tmp_on_disk);
        }

        if (rv == CKR_OK) {
                (void) pthread_mutex_lock(&soft_giant_mutex);
                soft_slot.ks_version = on_disk_ks_version;
                (void) pthread_mutex_unlock(&soft_giant_mutex);

                /* add the new objects into in-core list */
                insert_list_into_list(&(soft_slot.token_object_list),
                    added_objs_list, end_added_objs_list);

                /* add modified objects back into the in-core list */
                insert_list_into_list(&(soft_slot.token_object_list),
                    mod_objs_list, end_mod_objs_list);

                /* actually remove deleted objs, and copy of modified objs */
                delete_all_objs_in_list(copy_of_mod_objs_list);
                delete_all_objs_in_list(del_objs_list);
        }

        return (rv);

cleanup:
        next_on_disk = on_disk_list;
        while (next_on_disk) {
                tmp_on_disk = next_on_disk;
                next_on_disk = tmp_on_disk->next;
                free(tmp_on_disk->buf);
                free(tmp_on_disk);
        }

        /*
         * restore the in-core list back to the original state by adding
         * copy of original objects and deleted objects back to list
         */
        insert_list_into_list(&(soft_slot.token_object_list),
            del_objs_list, end_del_objs_list);
        insert_list_into_list(&(soft_slot.token_object_list),
            copy_of_mod_objs_list, end_copy_of_mod_objs_list);

        /*
         * remove the modified objects, and newly objects list
         */
        delete_all_objs_in_list(mod_objs_list);
        delete_all_objs_in_list(added_objs_list);
        return (rv);
}

CK_RV
dup_bigint_attr(biginteger_t *bi, CK_BYTE *buf, CK_ULONG buflen)
{
        bi->big_value_len = buflen;
        if ((bi->big_value = malloc(buflen)) == NULL) {
                return (CKR_HOST_MEMORY);
        }
        (void) memcpy(bi->big_value, buf, buflen);
        return (CKR_OK);
}