/*
 * COPYRIGHT (c) International Business Machines Corp. 2001-2017
 *
 * This program is provided under the terms of the Common Public License,
 * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this
 * software constitutes recipient's acceptance of CPL-1.0 terms which can be
 * found in the file LICENSE file or at
 * https://opensource.org/licenses/cpl1.0.php
 */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>

#include "pkcs11types.h"
#include "defs.h"
#include "host_defs.h"
#include "h_extern.h"
#include "tok_spec_struct.h"
#include "pkcs32.h"
#include "shared_memory.h"
#include "trace.h"
#include "ock_syslog.h"

#include <sys/file.h>
#include <syslog.h>

// Function:  dlist_add_as_first()
//
// Adds the specified node to the start of the list
//
// Returns:  pointer to the start of the list
//
DL_NODE *dlist_add_as_first(DL_NODE *list, void *data)
{
    DL_NODE *node = NULL;

    if (!data)
        return list;

    node = (DL_NODE *) malloc(sizeof(DL_NODE));
    if (!node)
        return NULL;

    node->data = data;
    node->prev = NULL;
    node->next = list;
    if (list)
        list->prev = node;

    return node;
}

// Function:  dlist_add_as_last()
//
// Adds the specified node to the end of the list
//
// Returns:  pointer to the start of the list
//
DL_NODE *dlist_add_as_last(DL_NODE *list, void *data)
{
    DL_NODE *node = NULL;

    if (!data)
        return list;

    node = (DL_NODE *) malloc(sizeof(DL_NODE));
    if (!node)
        return NULL;

    node->data = data;
    node->next = NULL;

    if (!list) {
        node->prev = NULL;
        return node;
    } else {
        DL_NODE *temp = dlist_get_last(list);
        temp->next = node;
        node->prev = temp;

        return list;
    }
}

// Function:  dlist_find()
//
DL_NODE *dlist_find(DL_NODE *list, void *data)
{
    DL_NODE *node = list;

    while (node && node->data != data)
        node = node->next;

    return node;
}

// Function:  dlist_get_first()
//
// Returns the last node in the list or NULL if list is empty
//
DL_NODE *dlist_get_first(DL_NODE *list)
{
    DL_NODE *temp = list;

    if (!list)
        return NULL;

    while (temp->prev != NULL)
        temp = temp->prev;

    return temp;
}

// Function:  dlist_get_last()
//
// Returns the last node in the list or NULL if list is empty
//
DL_NODE *dlist_get_last(DL_NODE *list)
{
    DL_NODE *temp = list;

    if (!list)
        return NULL;

    while (temp->next != NULL)
        temp = temp->next;

    return temp;
}

//
//
CK_ULONG dlist_length(DL_NODE *list)
{
    DL_NODE *temp = list;
    CK_ULONG len = 0;

    while (temp) {
        len++;
        temp = temp->next;
    }

    return len;
}

//
//
DL_NODE *dlist_next(DL_NODE *node)
{
    if (!node)
        return NULL;

    return node->next;
}

//
//
DL_NODE *dlist_prev(DL_NODE *node)
{
    if (!node)
        return NULL;

    return node->prev;
}

//
//
void dlist_purge(DL_NODE *list)
{
    DL_NODE *node;

    if (!list)
        return;

    do {
        node = list->next;
        free(list);
        list = node;
    } while (list);
}

// Function:  dlist_remove_node()
//
// Attempts to remove the specified node from the list.  The caller is
// responsible for freeing the data associated with the node prior to
// calling this routine
//
DL_NODE *dlist_remove_node(DL_NODE *list, DL_NODE *node)
{
    DL_NODE *temp = list;

    if (!list || !node)
        return NULL;

    // special case:  removing head of the list
    //
    if (list == node) {
        temp = list->next;
        if (temp)
            temp->prev = NULL;

        free(list);
        return temp;
    }
    // we have no guarantee that the node is in the list
    // so search through the list to find it
    //
    while ((temp != NULL) && (temp->next != node))
        temp = temp->next;

    if (temp != NULL) {
        DL_NODE *next = node->next;

        temp->next = next;
        if (next)
            next->prev = temp;

        free(node);
    }

    return list;
}

// NOTE about Mutexes and cross process locking....
//
// The code uses 2 types of locks... internal locks to prevent threads within
// the same process space from stomping on each other  (pthread_mutex's suffice
// for this).... and Cross Process Locks....
// On AIX we use it's variation of Posix semaphores for this.... Idealy on other
// platforms either POSIXSEMaphores or PTHREADXPL (pthreads xprocess lock) would
// be used.  On Linux unfortunatly  neither of these are available so we need to
// use the old standby of  SYSV semaphores (YECH.... GAG....)....  The only
// pieces which have been tested are the AIX and SYSV portions although
// we expect that the others work correctly.
//
// we use alot more mutexes in the redesign than we did in the original
// design.  so instead of just the single global "pkcs_mutex" we have to
// deal with a number of mutexes.  so we'll make the mutex routines a
// bit more generic.
//

CK_RV _CreateMutex(MUTEX *mutex)
{
    // on AIX we make this a no-op since we assume that
    // the mutex was created in the initialization
    pthread_mutex_init(mutex, NULL);

    return CKR_OK;
}

CK_RV _DestroyMutex(MUTEX *mutex)
{
    // no-op in AIX
    pthread_mutex_destroy((pthread_mutex_t *) mutex);

    return CKR_OK;
}

CK_RV _LockMutex(MUTEX *mutex)
{
    pthread_mutex_lock(mutex);

    return CKR_OK;
}

CK_RV _UnlockMutex(MUTEX *mutex)
{
    pthread_mutex_unlock(mutex);

    return CKR_OK;
}

CK_RV CreateXProcLock(char *tokname, STDLL_TokData_t *tokdata)
{
    char lockfile[2 * PATH_MAX + sizeof(LOCKDIR_PATH) + 6];
    char lockdir[PATH_MAX + sizeof(LOCKDIR_PATH)];
    struct group *grp;
    struct stat statbuf;
    mode_t mode = (S_IRUSR | S_IRGRP);
    int ret = -1;

    if (tokdata->spinxplfd == -1) {

        if (token_specific.t_creatlock != NULL) {
            tokdata->spinxplfd = token_specific.t_creatlock();
            if (tokdata->spinxplfd != -1)
                return CKR_OK;
            else
                return CKR_FUNCTION_FAILED;
        }

        /** create lock subdir for each token if it doesn't exist.
         *  The root directory should be created in slotmgr daemon **/
        if (strlen(tokname) > 0)
            sprintf(lockdir, "%s/%s", LOCKDIR_PATH, tokname);
        else
            sprintf(lockdir, "%s/%s", LOCKDIR_PATH, SUB_DIR);

        ret = stat(lockdir, &statbuf);
        if (ret != 0 && errno == ENOENT) {
            /* dir does not exist, try to create it */
            ret = mkdir(lockdir, S_IRWXU | S_IRWXG);
            if (ret != 0) {
                OCK_SYSLOG(LOG_ERR,
                           "Directory(%s) missing: %s\n",
                           lockdir, strerror(errno));
                goto err;
            }
            grp = getgrnam("pkcs11");
            if (grp == NULL) {
                fprintf(stderr, "getgrname(pkcs11): %s", strerror(errno));
                goto err;
            }
            /* set ownership to euid, and pkcs11 group */
            if (chown(lockdir, geteuid(), grp->gr_gid) != 0) {
                fprintf(stderr, "Failed to set owner:group \
                        ownership on %s directory", lockdir);
                goto err;
            }
            /* mkdir does not set group permission right, so
             ** trying explictly here again */
            if (chmod(lockdir, S_IRWXU | S_IRWXG) != 0) {
                fprintf(stderr, "Failed to change \
                        permissions on %s directory", lockdir);
                goto err;
            }
        }

        /* create user lock file */
        if (strlen(tokname) > 0)
            sprintf(lockfile, "%s/%s/LCK..%s", LOCKDIR_PATH, tokname, tokname);
        else
            sprintf(lockfile, "%s/%s/LCK..%s", LOCKDIR_PATH, SUB_DIR, SUB_DIR);

        if (stat(lockfile, &statbuf) == 0) {
            tokdata->spinxplfd = open(lockfile, O_RDONLY, mode);
        } else {
            tokdata->spinxplfd = open(lockfile, O_CREAT | O_RDONLY, mode);
            if (tokdata->spinxplfd != -1) {
                /* umask may prevent correct mode,so set it. */
                if (fchmod(tokdata->spinxplfd, mode) == -1) {
                    OCK_SYSLOG(LOG_ERR, "fchmod(%s): %s\n",
                               lockfile, strerror(errno));
                    goto err;
                }

                grp = getgrnam("pkcs11");
                if (grp != NULL) {
                    if (fchown(tokdata->spinxplfd, -1, grp->gr_gid) == -1) {
                        OCK_SYSLOG(LOG_ERR,
                                   "fchown(%s): %s\n",
                                   lockfile, strerror(errno));
                        goto err;
                    }
                } else {
                    OCK_SYSLOG(LOG_ERR, "getgrnam(): %s\n", strerror(errno));
                    goto err;
                }
            }
        }
        if (tokdata->spinxplfd == -1) {
            OCK_SYSLOG(LOG_ERR, "open(%s): %s\n", lockfile, strerror(errno));
            return CKR_FUNCTION_FAILED;
        }
    }

    return CKR_OK;

err:
    if (tokdata->spinxplfd != -1)
        close(tokdata->spinxplfd);

    return CKR_FUNCTION_FAILED;
}

void CloseXProcLock(STDLL_TokData_t *tokdata)
{
    if (tokdata->spinxplfd != -1)
        close(tokdata->spinxplfd);
}

CK_RV XProcLock(STDLL_TokData_t *tokdata)
{
    if (tokdata->spinxplfd != -1) {
        flock(tokdata->spinxplfd, LOCK_EX);
    } else {
        TRACE_DEVEL("No file descriptor to lock with.\n");
        return CKR_CANT_LOCK;
    }

    return CKR_OK;
}

CK_RV XProcUnLock(STDLL_TokData_t *tokdata)
{
    if (tokdata->spinxplfd != -1) {
        flock(tokdata->spinxplfd, LOCK_UN);
    } else {
        TRACE_DEVEL("No file descriptor to unlock with.\n");
        return CKR_CANT_LOCK;
    }

    return CKR_OK;
}

void XProcLock_Init(STDLL_TokData_t *tokdata)
{
    tokdata->spinxplfd = -1;
}

//
//

extern const char manuf[];
extern const char model[];
extern const char descr[];
extern const char label[];

//
//
void init_slotInfo(CK_SLOT_INFO *slot_info)
{
    memset(slot_info->slotDescription, ' ', sizeof(slot_info->slotDescription));
    memset(slot_info->manufacturerID, ' ', sizeof(slot_info->manufacturerID));

    memcpy(slot_info->slotDescription, descr, strlen(descr));
    memcpy(slot_info->manufacturerID, manuf, strlen(manuf));

    slot_info->hardwareVersion.major = 1;
    slot_info->hardwareVersion.minor = 0;
    slot_info->firmwareVersion.major = 1;
    slot_info->firmwareVersion.minor = 0;
    slot_info->flags = CKF_TOKEN_PRESENT | CKF_HW_SLOT;
}

//
//
void init_tokenInfo(TOKEN_DATA *nv_token_data)
{
    CK_TOKEN_INFO_32 *token_info = &nv_token_data->token_info;

    memset(token_info->label, ' ', sizeof(token_info->label));
    memset(token_info->manufacturerID, ' ', sizeof(token_info->manufacturerID));
    memset(token_info->model, ' ', sizeof(token_info->model));
    memset(token_info->serialNumber, ' ', sizeof(token_info->serialNumber));
    memset(token_info->utcTime, ' ', sizeof(token_info->utcTime));

    memcpy(token_info->label, label, strlen(label));
    memcpy(token_info->manufacturerID, manuf, strlen(manuf));
    memcpy(token_info->model, model, strlen(model));

    // use the 41-xxxxx serial number from the coprocessor
    //
    memcpy(token_info->serialNumber, "123", 3);

    // I don't see any API support for changing the clock so
    // we will use the system clock for the token's clock.
    //

    token_info->flags = CKF_RNG | CKF_LOGIN_REQUIRED | CKF_CLOCK_ON_TOKEN |
        CKF_SO_PIN_TO_BE_CHANGED;
    // XXX New in v2.11 - KEY

    if (memcmp(nv_token_data->user_pin_sha, "00000000000000000000",
               SHA1_HASH_SIZE) != 0)
        token_info->flags |= CKF_USER_PIN_INITIALIZED;
    else
        token_info->flags |= CKF_USER_PIN_TO_BE_CHANGED;
    // XXX New in v2.11 - KEY

    // For the release, we made these
    // values as CK_UNAVAILABLE_INFORMATION
    //
    token_info->ulMaxSessionCount = (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION;
    token_info->ulSessionCount = (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION;
    token_info->ulMaxRwSessionCount = (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION;
    token_info->ulRwSessionCount = (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION;
    token_info->ulMaxPinLen = MAX_PIN_LEN;
    token_info->ulMinPinLen = MIN_PIN_LEN;
    token_info->ulTotalPublicMemory = (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION;
    token_info->ulFreePublicMemory = (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION;
    token_info->ulTotalPrivateMemory = (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION;
    token_info->ulFreePrivateMemory = (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION;

    token_info->hardwareVersion.major = 1;
    token_info->hardwareVersion.minor = 0;
    token_info->firmwareVersion.major = 1;
    token_info->firmwareVersion.minor = 0;
}

//
//
CK_RV init_token_data(STDLL_TokData_t *tokdata, CK_SLOT_ID slot_id)
{
    CK_RV rc;

    memset((char *) tokdata->nv_token_data, 0, sizeof(TOKEN_DATA));

    // the normal USER pin is not set when the token is initialized
    //
    memcpy(tokdata->nv_token_data->user_pin_sha, "00000000000000000000",
           SHA1_HASH_SIZE);
    memcpy(tokdata->nv_token_data->so_pin_sha, default_so_pin_sha,
           SHA1_HASH_SIZE);

    memset(tokdata->user_pin_md5, 0x0, MD5_HASH_SIZE);
    memcpy(tokdata->so_pin_md5, default_so_pin_md5, MD5_HASH_SIZE);

    memcpy(tokdata->nv_token_data->next_token_object_name, "00000000", 8);

    // generate the master key used for signing the Operation State information
    //                          `
    memset(tokdata->nv_token_data->token_info.label, ' ',
           sizeof(tokdata->nv_token_data->token_info.label));
    memcpy(tokdata->nv_token_data->token_info.label, label,
           strlen(label));

    tokdata->nv_token_data->tweak_vector.allow_weak_des = TRUE;
    tokdata->nv_token_data->tweak_vector.check_des_parity = FALSE;
    tokdata->nv_token_data->tweak_vector.allow_key_mods = TRUE;
    tokdata->nv_token_data->tweak_vector.netscape_mods = TRUE;

    init_tokenInfo(tokdata->nv_token_data);

    if (token_specific.t_init_token_data) {
        rc = token_specific.t_init_token_data(tokdata, slot_id);
        if (rc != CKR_OK)
            return rc;
    } else {
        //
        // FIXME: erase the token object index file (and all token objects)
        //
        rc = generate_master_key(tokdata, tokdata->master_key);
        if (rc != CKR_OK) {
            TRACE_DEVEL("generate_master_key failed.\n");
            return CKR_FUNCTION_FAILED;
        }

        rc = save_masterkey_so(tokdata);
        if (rc != CKR_OK) {
            TRACE_DEVEL("save_masterkey_so failed.\n");
            return rc;
        }
    }

    rc = save_token_data(tokdata, slot_id);

    return rc;
}

// Function:  compute_next_token_obj_name()
//
// Given a token object name (8 bytes in the range [0-9A-Z]) increment by one
// adjusting as necessary
//
// This gives us a namespace of 36^8 = 2,821,109,907,456 objects before wrapping
// around
//
// Note: If the current name contains an invalid character (i.e. not within
//       [0-9A-Z]), then this character is set to '0' in the next name and
//       the following characters are incremented by 1 adjusting as necessary.
//
CK_RV compute_next_token_obj_name(CK_BYTE *current, CK_BYTE *next)
{
    int val[8];
    int i;

    if (!current || !next) {
        TRACE_ERROR("Invalid function arguments.\n");
        return CKR_FUNCTION_FAILED;
    }
    // Convert to integral base 36
    //
    for (i = 0; i < 8; i++) {
        if (current[i] >= '0' && current[i] <= '9')
            val[i] = current[i] - '0';
        else if (current[i] >= 'A' && current[i] <= 'Z')
            val[i] = current[i] - 'A' + 10;
        else
            val[i] = 36;
    }

    val[0]++;

    i = 0;

    while (val[i] > 35) {
        val[i] = 0;

        if (i + 1 < 8) {
            val[i + 1]++;
            i++;
        } else {
            val[0]++;
            i = 0;              // start pass 2
        }
    }

    // now, convert back to [0-9A-Z]
    //
    for (i = 0; i < 8; i++) {
        if (val[i] < 10)
            next[i] = '0' + val[i];
        else
            next[i] = 'A' + val[i] - 10;
    }

    return CKR_OK;
}

//
//
CK_RV build_attribute(CK_ATTRIBUTE_TYPE type,
                      CK_BYTE *data, CK_ULONG data_len, CK_ATTRIBUTE **attrib)
{
    CK_ATTRIBUTE *attr = NULL;

    attr = (CK_ATTRIBUTE *) malloc(sizeof(CK_ATTRIBUTE) + data_len);
    if (!attr) {
        TRACE_ERROR("%s\n", ock_err(ERR_HOST_MEMORY));
        return CKR_HOST_MEMORY;
    }
    attr->type = type;
    attr->ulValueLen = data_len;

    if (data_len > 0) {
        attr->pValue = (CK_BYTE *) attr + sizeof(CK_ATTRIBUTE);
        memcpy(attr->pValue, data, data_len);
    } else {
        attr->pValue = NULL;
    }

    *attrib = attr;

    return CKR_OK;
}

/*
 * Find an attribute in an attribute array.
 *
 * Returns CKR_FUNCTION_FAILED when attribute is not found,
 * CKR_ATTRIBUTE_TYPE_INVALID when length doesn't match the expected and
 * CKR_OK when values is returned in the `value` argument.
 */
CK_RV find_bbool_attribute(CK_ATTRIBUTE *attrs, CK_ULONG attrs_len,
                           CK_ATTRIBUTE_TYPE type, CK_BBOOL *value)
{
    CK_ULONG i;

    for (i = 0; i < attrs_len; i++) {
        if (attrs[i].type == type) {
            /* Check size */
            if (attrs[i].ulValueLen != sizeof(*value))
                return CKR_ATTRIBUTE_TYPE_INVALID;

            /* Get value */
            *value = *((CK_BBOOL *) attrs[i].pValue);
        }
    }

    return CKR_FUNCTION_FAILED;
}

//
//
CK_RV add_pkcs_padding(CK_BYTE *ptr,
                       CK_ULONG block_size, CK_ULONG data_len,
                       CK_ULONG total_len)
{
    CK_ULONG i, pad_len;
    CK_BYTE pad_value;

    pad_len = block_size - (data_len % block_size);
    pad_value = (CK_BYTE) pad_len;

    if (data_len + pad_len > total_len) {
        TRACE_ERROR("The total length is too small to add padding.\n");
        return CKR_FUNCTION_FAILED;
    }
    for (i = 0; i < pad_len; i++)
        ptr[i] = pad_value;

    return CKR_OK;
}

//
//
CK_RV strip_pkcs_padding(CK_BYTE *ptr, CK_ULONG total_len, CK_ULONG *data_len)
{
    CK_BYTE pad_value;

    pad_value = ptr[total_len - 1];
    if (pad_value > total_len) {
        TRACE_ERROR("%s\n", ock_err(ERR_ENCRYPTED_DATA_INVALID));
        return CKR_ENCRYPTED_DATA_INVALID;
    }
    // thus, we have 'pad_value' bytes of 'pad_value' appended to the end
    //
    *data_len = total_len - pad_value;

    return CKR_OK;
}

//
//
CK_BYTE parity_adjust(CK_BYTE b)
{
    if (parity_is_odd(b) == FALSE)
        b = (b & 0xFE) | ((~b) & 0x1);

    return b;
}

//
//
CK_RV parity_is_odd(CK_BYTE b)
{
    b = ((b >> 4) ^ b) & 0x0f;
    b = ((b >> 2) ^ b) & 0x03;
    b = ((b >> 1) ^ b) & 0x01;

    if (b == 1)
        return TRUE;
    else
        return FALSE;
}

CK_RV attach_shm(STDLL_TokData_t *tokdata, CK_SLOT_ID slot_id)
{
    CK_RV rc;
    int ret;
    char buf[PATH_MAX];
    LW_SHM_TYPE **shm = &tokdata->global_shm;

    if (token_specific.t_attach_shm != NULL)
        return token_specific.t_attach_shm(tokdata, slot_id);

    rc = XProcLock(tokdata);
    if (rc != CKR_OK)
        goto err;

    /*
     * Attach to an existing shared memory region or create it if it doesn't
     * exists. When it's created (ret=0) the region is initialized with
     * zeros.
     */
    ret = sm_open(get_pk_dir(buf), 0666, (void **) shm, sizeof(**shm), 0);
    if (ret < 0) {
        TRACE_DEVEL("sm_open failed.\n");
        rc = CKR_FUNCTION_FAILED;
        goto err;
    }

    return XProcUnLock(tokdata);

err:
    XProcUnLock(tokdata);
    return rc;
}

CK_RV detach_shm(STDLL_TokData_t *tokdata)
{
    CK_RV rc;

    rc = XProcLock(tokdata);
    if (rc != CKR_OK)
        goto err;

    if (sm_close((void *) tokdata->global_shm, 0)) {
        TRACE_DEVEL("sm_close failed.\n");
        rc = CKR_FUNCTION_FAILED;
        goto err;
    }

    return XProcUnLock(tokdata);

err:
    XProcUnLock(tokdata);
    return rc;
}

CK_RV get_sha_size(CK_ULONG mech, CK_ULONG *hsize)
{
    switch (mech) {
    case CKM_SHA_1:
        *hsize = SHA1_HASH_SIZE;
        break;
    case CKM_SHA224:
    case CKM_SHA512_224:
        *hsize = SHA224_HASH_SIZE;
        break;
    case CKM_SHA256:
    case CKM_SHA512_256:
        *hsize = SHA256_HASH_SIZE;
        break;
    case CKM_SHA384:
        *hsize = SHA384_HASH_SIZE;
        break;
    case CKM_SHA512:
        *hsize = SHA512_HASH_SIZE;
        break;
    default:
        return CKR_MECHANISM_INVALID;
    }
    return CKR_OK;
}

CK_RV get_sha_block_size(CK_ULONG mech, CK_ULONG *bsize)
{
    switch (mech) {
    case CKM_SHA_1:
        *bsize = SHA1_BLOCK_SIZE;
        break;
    case CKM_SHA224:
        *bsize = SHA224_BLOCK_SIZE;
        break;
    case CKM_SHA256:
        *bsize = SHA256_BLOCK_SIZE;
        break;
    case CKM_SHA384:
        *bsize = SHA384_BLOCK_SIZE;
        break;
    case CKM_SHA512:
    case CKM_SHA512_224:
    case CKM_SHA512_256:
        *bsize = SHA512_BLOCK_SIZE;
        break;
    default:
        return CKR_MECHANISM_INVALID;
    }
    return CKR_OK;
}

/* Compute specified SHA using either software or token implementation */
CK_RV compute_sha(STDLL_TokData_t *tokdata, CK_BYTE *data, CK_ULONG len,
                  CK_BYTE *hash, CK_ULONG mech)
{
    DIGEST_CONTEXT ctx;
    CK_ULONG hash_len;
    CK_RV rv;

    memset(&ctx, 0x0, sizeof(ctx));
    ctx.mech.mechanism = mech;

    rv = get_sha_size(mech, &hash_len);
    if (rv != CKR_OK)
        return rv;

    rv = sha_init(tokdata, NULL, &ctx, &ctx.mech);
    if (rv != CKR_OK) {
        TRACE_DEBUG("failed to create digest.\n");
        return rv;
    }
    rv = sha_hash(tokdata, NULL, FALSE, &ctx, data, len, hash, &hash_len);

    digest_mgr_cleanup(&ctx);
    return rv;
}

/* Compute SHA1 using software implementation */
CK_RV compute_sha1(STDLL_TokData_t *tokdata, CK_BYTE *data, CK_ULONG len,
                   CK_BYTE *hash)
{
    // XXX KEY
    DIGEST_CONTEXT ctx;
    CK_ULONG hash_len = SHA1_HASH_SIZE;

    UNUSED(tokdata);

    memset(&ctx, 0x0, sizeof(ctx));

    sw_sha1_init(&ctx);
    if (ctx.context == NULL)
        return CKR_HOST_MEMORY;

    return sw_sha1_hash(&ctx, data, len, hash, &hash_len);
}

CK_RV compute_md5(STDLL_TokData_t *tokdata, CK_BYTE *data, CK_ULONG len,
                  CK_BYTE *hash)
{
    MD5_CONTEXT ctx;

    memset(&ctx, 0x0, sizeof(ctx));

    ckm_md5_init(tokdata, &ctx);
    ckm_md5_update(tokdata, &ctx, data, len);
    ckm_md5_final(tokdata, &ctx, hash, MD5_HASH_SIZE);

    return CKR_OK;
}

CK_RV get_keytype(STDLL_TokData_t *tokdata, CK_OBJECT_HANDLE hkey,
                  CK_KEY_TYPE *keytype)
{
    CK_RV rc;
    OBJECT *key_obj = NULL;
    CK_ATTRIBUTE *attr = NULL;

    rc = object_mgr_find_in_map1(tokdata, hkey, &key_obj);
    if (rc != CKR_OK) {
        TRACE_DEVEL("object_mgr_find_in_map1 failed.\n");
        return rc;
    }
    rc = template_attribute_find(key_obj->template, CKA_KEY_TYPE, &attr);
    if (rc == FALSE) {
        TRACE_ERROR("%s\n", ock_err(ERR_KEY_TYPE_INCONSISTENT));
        return CKR_KEY_TYPE_INCONSISTENT;
    } else {
        *keytype = *(CK_KEY_TYPE *) attr->pValue;
        return CKR_OK;
    }
}

CK_RV check_user_and_group()
{
    int i;
    uid_t uid, euid;
    struct passwd *pw, *epw;
    struct group *grp;

    /*
     * Check for root user or Group PKCS#11 Membershp.
     * Only these are allowed.
     */
    uid = getuid();
    euid = geteuid();

    /* Root or effective Root is ok */
    if (uid == 0 || euid == 0)
        return CKR_OK;

    /*
     * Check for member of group. SAB get login seems to not work
     * with some instances of application invocations (particularly
     * when forked). So we need to get the group information.
     * Really need to take the uid and map it to a name.
     */
    grp = getgrnam("pkcs11");
    if (grp == NULL) {
        OCK_SYSLOG(LOG_ERR, "getgrnam() failed: %s\n", strerror(errno));
        goto error;
    }

    if (getgid() == grp->gr_gid || getegid() == grp->gr_gid)
        return CKR_OK;
    /* Check if user or effective user is member of pkcs11 group */
    pw = getpwuid(uid);
    epw = getpwuid(euid);
    for (i = 0; grp->gr_mem[i]; i++) {
        if ((pw && (strncmp(pw->pw_name, grp->gr_mem[i],
                            strlen(pw->pw_name)) == 0)) ||
            (epw && (strncmp(epw->pw_name, grp->gr_mem[i],
                             strlen(epw->pw_name)) == 0)))
            return CKR_OK;
    }

error:
    TRACE_ERROR("%s\n", ock_err(ERR_FUNCTION_FAILED));

    return CKR_FUNCTION_FAILED;
}

void copy_token_contents_sensibly(CK_TOKEN_INFO_PTR pInfo,
                                  TOKEN_DATA *nv_token_data)
{
    memcpy(pInfo, &nv_token_data->token_info, sizeof(CK_TOKEN_INFO_32));
    pInfo->flags = nv_token_data->token_info.flags;
    pInfo->ulMaxPinLen = nv_token_data->token_info.ulMaxPinLen;
    pInfo->ulMinPinLen = nv_token_data->token_info.ulMinPinLen;

    if (nv_token_data->token_info.ulTotalPublicMemory ==
        (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION) {
        pInfo->ulTotalPublicMemory = (CK_ULONG) CK_UNAVAILABLE_INFORMATION;
    } else {
        pInfo->ulTotalPublicMemory =
            nv_token_data->token_info.ulTotalPublicMemory;
    }

    if (nv_token_data->token_info.ulFreePublicMemory ==
        (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION) {
        pInfo->ulFreePublicMemory = (CK_ULONG) CK_UNAVAILABLE_INFORMATION;
    } else {
        pInfo->ulFreePublicMemory =
            nv_token_data->token_info.ulFreePublicMemory;
    }

    if (nv_token_data->token_info.ulTotalPrivateMemory ==
        (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION) {
        pInfo->ulTotalPrivateMemory = (CK_ULONG) CK_UNAVAILABLE_INFORMATION;
    } else {
        pInfo->ulTotalPrivateMemory =
            nv_token_data->token_info.ulTotalPrivateMemory;
    }

    if (nv_token_data->token_info.ulFreePrivateMemory ==
        (CK_ULONG_32) CK_UNAVAILABLE_INFORMATION) {
        pInfo->ulFreePrivateMemory = (CK_ULONG) CK_UNAVAILABLE_INFORMATION;
    } else {
        pInfo->ulFreePrivateMemory =
            nv_token_data->token_info.ulFreePrivateMemory;
    }

    pInfo->hardwareVersion = nv_token_data->token_info.hardwareVersion;
    pInfo->firmwareVersion = nv_token_data->token_info.firmwareVersion;
    pInfo->ulMaxSessionCount = ULONG_MAX - 1;
    /* pInfo->ulSessionCount is set at the API level */
    pInfo->ulMaxRwSessionCount = ULONG_MAX - 1;
    pInfo->ulRwSessionCount = CK_UNAVAILABLE_INFORMATION;
}

CK_BBOOL is_secure_key_token()
{
    /*
     * If token has a specific key size that means that it uses secure keys.
     */
    return token_specific.token_keysize > 0 ? TRUE : FALSE;
}
